diff --git a/chain/block.go b/chain/block.go index a06f336351..7966266f3f 100644 --- a/chain/block.go +++ b/chain/block.go @@ -349,7 +349,7 @@ func (b *StatefulBlock) Verify(ctx context.Context) error { // need to compute it (we assume that we built a correct block and it isn't // necessary to re-verify anything). log.Info( - "skipping verification, already processed", + "skipping verification of locally built block", zap.Uint64("height", b.Hght), zap.Stringer("blkID", b.ID()), ) @@ -448,13 +448,16 @@ func (b *StatefulBlock) innerVerify(ctx context.Context, vctx VerifyContext) err return ErrTimestampTooEarly } - // Ensure tx cannot be replayed + // Expiry replay protection // - // Before node is considered ready (emap is fully populated), this may return - // false when other validators think it is true. + // Replay protection confirms a transaction has not been included within the + // past validity window. + // Before the node is ready (we have synced a validity window of blocks), this + // function may return an error when other nodes see the block as valid. // // If a block is already accepted, its transactions have already been added - // to the VM's seen emap and calling [IsRepeat] will return a non-zero value. + // to the VM's seen emap and calling [IsRepeat] will return a non-zero value + // when it should already be considered valid, so we skip this step here. if !b.accepted { oldestAllowed := b.Tmstmp - r.GetValidityWindow() if oldestAllowed < 0 { diff --git a/chain/builder.go b/chain/builder.go index 7c24b812c0..906ca51a80 100644 --- a/chain/builder.go +++ b/chain/builder.go @@ -85,8 +85,6 @@ func BuildBlock( // // If the parent block is not yet verified, we will attempt to // execute it. - mempoolSize := vm.Mempool().Len(ctx) - changesEstimate := min(mempoolSize, maxViewPreallocation) parentView, err := parent.View(ctx, true) if err != nil { log.Warn("block building failed: couldn't get parent db", zap.Error(err)) @@ -107,6 +105,9 @@ func BuildBlock( maxUnits := r.GetMaxBlockUnits() targetUnits := r.GetWindowTargetUnits() + mempoolSize := vm.Mempool().Len(ctx) + changesEstimate := min(mempoolSize, maxViewPreallocation) + var ( ts = tstate.New(changesEstimate) oldestAllowed = nextTime - r.GetValidityWindow() @@ -151,6 +152,8 @@ func BuildBlock( ctx, executeSpan := vm.Tracer().Start(ctx, "chain.BuildBlock.Execute") //nolint:spancheck // Perform a batch repeat check + // IsRepeat only returns an error if we fail to fetch the full validity window of blocks. + // This should only happen after startup, so we add the transactions back to the mempool. dup, err := parent.IsRepeat(ctx, oldestAllowed, txs, set.NewBits(), false) if err != nil { restorable = append(restorable, txs...) diff --git a/chain/dependencies.go b/chain/dependencies.go index 8c656de94c..2f913cb106 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -95,6 +95,10 @@ type VM interface { type VerifyContext interface { View(ctx context.Context, verify bool) (state.View, error) + // IsRepeat returns a bitset containing the indices of [txs] that are repeats from this context back to + // [oldestAllowed]. + // If [stop] is true, the search will stop at the first repeat transaction. This supports early termination + // during verification when any invalid transaction will cause the block to fail verification. IsRepeat(ctx context.Context, oldestAllowed int64, txs []*Transaction, marker set.Bits, stop bool) (set.Bits, error) }