@@ -116,6 +116,12 @@ func (m *FollowerState) ExtendCertified(ctx context.Context, candidate *flow.Blo
116116 defer span .End ()
117117
118118 blockID := candidate .ID ()
119+ // check if candidate block has been already processed
120+ processed , err := m .checkBlockAlreadyProcessed (blockID )
121+ if err != nil || processed {
122+ return err
123+ }
124+
119125 // sanity check if certifyingQC actually certifies candidate block
120126 if certifyingQC .View != candidate .Header .View {
121127 return fmt .Errorf ("qc doesn't certify candidate block, expect %d view, got %d" , candidate .Header .View , certifyingQC .View )
@@ -125,7 +131,7 @@ func (m *FollowerState) ExtendCertified(ctx context.Context, candidate *flow.Blo
125131 }
126132
127133 // check if the block header is a valid extension of parent block
128- err : = m .headerExtend (candidate )
134+ err = m .headerExtend (candidate )
129135 if err != nil {
130136 // since we have a QC for this block, it cannot be an invalid extension
131137 return fmt .Errorf ("unexpected invalid block (id=%x) with certifying qc (id=%x): %s" ,
@@ -156,8 +162,14 @@ func (m *ParticipantState) Extend(ctx context.Context, candidate *flow.Block) er
156162 span , ctx := m .tracer .StartSpanFromContext (ctx , trace .ProtoStateMutatorExtend )
157163 defer span .End ()
158164
165+ // check if candidate block has been already processed
166+ processed , err := m .checkBlockAlreadyProcessed (candidate .ID ())
167+ if err != nil || processed {
168+ return err
169+ }
170+
159171 // check if the block header is a valid extension of parent block
160- err : = m .headerExtend (candidate )
172+ err = m .headerExtend (candidate )
161173 if err != nil {
162174 return fmt .Errorf ("header not compliant with chain state: %w" , err )
163175 }
@@ -244,6 +256,23 @@ func (m *FollowerState) headerExtend(candidate *flow.Block) error {
244256 return nil
245257}
246258
259+ // checkBlockAlreadyProcessed checks if block with given blockID has been added to the protocol state.
260+ // Returns:
261+ // * (true, nil) - block has been already processed.
262+ // * (false, nil) - block has not been processed.
263+ // * (false, error) - unknown error when trying to query protocol state.
264+ // No errors are expected during normal operation.
265+ func (m * FollowerState ) checkBlockAlreadyProcessed (blockID flow.Identifier ) (bool , error ) {
266+ _ , err := m .headers .ByBlockID (blockID )
267+ if err != nil {
268+ if errors .Is (err , storage .ErrNotFound ) {
269+ return false , nil
270+ }
271+ return false , fmt .Errorf ("could not check if candidate block (%x) has been already processed: %w" , blockID , err )
272+ }
273+ return true , nil
274+ }
275+
247276// checkOutdatedExtension checks whether candidate block is
248277// valid in the context of the entire state. For this, the block needs to
249278// directly connect, through its ancestors, to the last finalized block.
0 commit comments