diff --git a/vms/proposervm/batched_vm.go b/vms/proposervm/batched_vm.go index fd104318cea..1a0cde92c81 100644 --- a/vms/proposervm/batched_vm.go +++ b/vms/proposervm/batched_vm.go @@ -107,8 +107,11 @@ func (vm *VM) BatchedParseBlock(ctx context.Context, blks [][]byte) ([]snowman.B } blkID := statelessBlock.ID() - block, exists := vm.verifiedBlocks[blkID] - if exists { + if block, exists := vm.verifiedProposerBlocks[blkID]; exists { + blocks[blocksIndex] = block + continue + } + if block, exists := vm.verifiedBlocks[blkID]; exists { blocks[blocksIndex] = block continue } @@ -168,6 +171,9 @@ func (vm *VM) BatchedParseBlock(ctx context.Context, blks [][]byte) ([]snowman.B } func (vm *VM) getStatelessBlk(blkID ids.ID) (statelessblock.Block, error) { + if currentBlk, exists := vm.verifiedProposerBlocks[blkID]; exists { + return currentBlk.getStatelessBlk(), nil + } if currentBlk, exists := vm.verifiedBlocks[blkID]; exists { return currentBlk.getStatelessBlk(), nil } diff --git a/vms/proposervm/block.go b/vms/proposervm/block.go index c62e5e31b9b..30e6e6d108c 100644 --- a/vms/proposervm/block.go +++ b/vms/proposervm/block.go @@ -51,7 +51,6 @@ type Block interface { acceptOuterBlk() error acceptInnerBlk(context.Context) error - verifyProposerPreForkChild(ctx context.Context, child *preForkBlock) error verifyPreForkChild(ctx context.Context, child *preForkBlock) error verifyProposerPostForkChild(ctx context.Context, child *postForkBlock) error @@ -94,7 +93,6 @@ func (p *postForkCommonComponents) Height() uint64 { // 6) [childPChainHeight] <= the current P-Chain height // 7) [child]'s timestamp is within its proposer's window // 8) [child] has a valid signature from its proposer -// 9) [child]'s inner block is valid func (p *postForkCommonComponents) Verify( ctx context.Context, parentTimestamp time.Time, @@ -128,58 +126,53 @@ func (p *postForkCommonComponents) Verify( // If the node is currently syncing - we don't assume that the P-chain has // been synced up to this point yet. - if p.vm.consensusState == snow.NormalOp { - childID := child.ID() - currentPChainHeight, err := p.vm.ctx.ValidatorState.GetCurrentHeight(ctx) - if err != nil { - p.vm.ctx.Log.Error("block verification failed", - zap.String("reason", "failed to get current P-Chain height"), - zap.Stringer("blkID", childID), - zap.Error(err), - ) - return err - } - if childPChainHeight > currentPChainHeight { - return fmt.Errorf("%w: %d > %d", - errPChainHeightNotReached, - childPChainHeight, - currentPChainHeight, - ) - } + if p.vm.consensusState != snow.NormalOp { + return nil + } - childHeight := child.Height() - proposerID := child.Proposer() - minDelay, err := p.vm.Windower.Delay(ctx, childHeight, parentPChainHeight, proposerID) - if err != nil { - return err - } + childID := child.ID() + currentPChainHeight, err := p.vm.ctx.ValidatorState.GetCurrentHeight(ctx) + if err != nil { + p.vm.ctx.Log.Error("block verification failed", + zap.String("reason", "failed to get current P-Chain height"), + zap.Stringer("blkID", childID), + zap.Error(err), + ) + return err + } + if childPChainHeight > currentPChainHeight { + return fmt.Errorf("%w: %d > %d", + errPChainHeightNotReached, + childPChainHeight, + currentPChainHeight, + ) + } - delay := childTimestamp.Sub(parentTimestamp) - if delay < minDelay { - return errProposerWindowNotStarted - } + childHeight := child.Height() + proposerID := child.Proposer() + minDelay, err := p.vm.Windower.Delay(ctx, childHeight, parentPChainHeight, proposerID) + if err != nil { + return err + } - // Verify the signature of the node - shouldHaveProposer := delay < proposer.MaxDelay - if err := child.SignedBlock.Verify(shouldHaveProposer, p.vm.ctx.ChainID); err != nil { - return err - } + delay := childTimestamp.Sub(parentTimestamp) + if delay < minDelay { + return errProposerWindowNotStarted + } - p.vm.ctx.Log.Debug("verified post-fork block", - zap.Stringer("blkID", childID), - zap.Time("parentTimestamp", parentTimestamp), - zap.Duration("minDelay", minDelay), - zap.Time("blockTimestamp", childTimestamp), - ) + // Verify the signature of the node + shouldHaveProposer := delay < proposer.MaxDelay + if err := child.SignedBlock.Verify(shouldHaveProposer, p.vm.ctx.ChainID); err != nil { + return err } - return p.vm.verifyAndRecordInnerBlk( - ctx, - &smblock.Context{ - PChainHeight: parentPChainHeight, - }, - child, + p.vm.ctx.Log.Debug("verified post-fork block", + zap.Stringer("blkID", childID), + zap.Time("parentTimestamp", parentTimestamp), + zap.Duration("minDelay", minDelay), + zap.Time("blockTimestamp", childTimestamp), ) + return nil } // Return the child (a *postForkBlock) of this block diff --git a/vms/proposervm/post_fork_block.go b/vms/proposervm/post_fork_block.go index c0b3eb5232a..17341291788 100644 --- a/vms/proposervm/post_fork_block.go +++ b/vms/proposervm/post_fork_block.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowman" + smblock "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/vms/proposervm/block" ) @@ -46,7 +47,9 @@ func (b *postForkBlock) acceptInnerBlk(ctx context.Context) error { func (b *postForkBlock) Reject(context.Context) error { // We do not reject the inner block here because it may be accepted later - delete(b.vm.verifiedBlocks, b.ID()) + blkID := b.ID() + delete(b.vm.verifiedProposerBlocks, blkID) + delete(b.vm.verifiedBlocks, blkID) b.status = choices.Rejected return nil } @@ -64,6 +67,14 @@ func (b *postForkBlock) Parent() ids.ID { return b.ParentID() } +func (b *postForkBlock) VerifyProposer(ctx context.Context) error { + parent, err := b.vm.getBlock(ctx, b.ParentID()) + if err != nil { + return err + } + return parent.verifyProposerPostForkChild(ctx, b) +} + // If Verify() returns nil, Accept() or Reject() will eventually be called on // [b] and [b.innerBlk] func (b *postForkBlock) Verify(ctx context.Context) error { @@ -113,26 +124,40 @@ func (b *postForkBlock) Options(ctx context.Context) ([2]snowman.Block, error) { } // A post-fork block can never have a pre-fork child -func (*postForkBlock) verifyProposerPreForkChild(context.Context, *preForkBlock) error { - return errUnsignedChild -} - func (*postForkBlock) verifyPreForkChild(context.Context, *preForkBlock) error { return errUnsignedChild } -func (b *postForkBlock) verifyPostForkChild(ctx context.Context, child *postForkBlock) error { +func (b *postForkBlock) verifyProposerPostForkChild(ctx context.Context, child *postForkBlock) error { parentTimestamp := b.Timestamp() parentPChainHeight := b.PChainHeight() - return b.postForkCommonComponents.Verify( + err := b.postForkCommonComponents.Verify( ctx, parentTimestamp, parentPChainHeight, child, ) + if err != nil { + return err + } + + childID := child.ID() + child.vm.verifiedProposerBlocks[childID] = child + return nil } -func (b *postForkBlock) verifyPostForkOption(ctx context.Context, child *postForkOption) error { +func (b *postForkBlock) verifyPostForkChild(ctx context.Context, child *postForkBlock) error { + parentPChainHeight := b.PChainHeight() + return child.vm.verifyAndRecordInnerBlk( + ctx, + &smblock.Context{ + PChainHeight: parentPChainHeight, + }, + child, + ) +} + +func (b *postForkBlock) verifyProposerPostForkOption(ctx context.Context, child *postForkOption) error { if err := verifyIsOracleBlock(ctx, b.innerBlk); err != nil { return err } @@ -144,6 +169,12 @@ func (b *postForkBlock) verifyPostForkOption(ctx context.Context, child *postFor return errInnerParentMismatch } + childID := child.ID() + child.vm.verifiedProposerBlocks[childID] = child + return nil +} + +func (b *postForkBlock) verifyPostForkOption(ctx context.Context, child *postForkOption) error { return child.vm.verifyAndRecordInnerBlk(ctx, nil, child) } diff --git a/vms/proposervm/post_fork_option.go b/vms/proposervm/post_fork_option.go index fc47c6fe60e..0c3916ee9d1 100644 --- a/vms/proposervm/post_fork_option.go +++ b/vms/proposervm/post_fork_option.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/choices" + smblock "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/vms/proposervm/block" ) @@ -54,8 +55,9 @@ func (b *postForkOption) acceptInnerBlk(ctx context.Context) error { func (b *postForkOption) Reject(context.Context) error { // we do not reject the inner block here because that block may be contained // in the proposer block that causing this block to be rejected. - - delete(b.vm.verifiedBlocks, b.ID()) + blkID := b.ID() + delete(b.vm.verifiedProposerBlocks, blkID) + delete(b.vm.verifiedBlocks, blkID) b.status = choices.Rejected return nil } @@ -71,6 +73,14 @@ func (b *postForkOption) Parent() ids.ID { return b.ParentID() } +func (b *postForkOption) VerifyProposer(ctx context.Context) error { + parent, err := b.vm.getBlock(ctx, b.ParentID()) + if err != nil { + return err + } + return parent.verifyProposerPostForkOption(ctx, b) +} + // If Verify returns nil, Accept or Reject is eventually called on [b] and // [b.innerBlk]. func (b *postForkOption) Verify(ctx context.Context) error { @@ -82,27 +92,43 @@ func (b *postForkOption) Verify(ctx context.Context) error { return parent.verifyPostForkOption(ctx, b) } -// A *preForkBlock's parent must be a *preForkBlock -func (*postForkOption) verifyProposerPreForkChild(context.Context, *preForkBlock) error { - return errUnsignedChild -} - func (*postForkOption) verifyPreForkChild(context.Context, *preForkBlock) error { return errUnsignedChild } -func (b *postForkOption) verifyPostForkChild(ctx context.Context, child *postForkBlock) error { +func (b *postForkOption) verifyProposerPostForkChild(ctx context.Context, child *postForkBlock) error { parentTimestamp := b.Timestamp() parentPChainHeight, err := b.pChainHeight(ctx) if err != nil { return err } - return b.postForkCommonComponents.Verify( + err = b.postForkCommonComponents.Verify( ctx, parentTimestamp, parentPChainHeight, child, ) + if err != nil { + return err + } + + childID := child.ID() + child.vm.verifiedProposerBlocks[childID] = child + return nil +} + +func (b *postForkOption) verifyPostForkChild(ctx context.Context, child *postForkBlock) error { + parentPChainHeight, err := b.pChainHeight(ctx) + if err != nil { + return err + } + return child.vm.verifyAndRecordInnerBlk( + ctx, + &smblock.Context{ + PChainHeight: parentPChainHeight, + }, + child, + ) } // A *postForkOption's parent can't be a *postForkOption diff --git a/vms/proposervm/pre_fork_block.go b/vms/proposervm/pre_fork_block.go index 8bdeee6d506..9b2d07e8dae 100644 --- a/vms/proposervm/pre_fork_block.go +++ b/vms/proposervm/pre_fork_block.go @@ -60,7 +60,7 @@ func (b *preForkBlock) Status() choices.Status { return b.Block.Status() } -func (b *preForkBlock) Verify(ctx context.Context) error { +func (b *preForkBlock) VerifyProposer(ctx context.Context) error { parent, err := b.vm.getPreForkBlock(ctx, b.Block.Parent()) if err != nil { return err @@ -68,6 +68,10 @@ func (b *preForkBlock) Verify(ctx context.Context) error { return parent.verifyPreForkChild(ctx, b) } +func (b *preForkBlock) Verify(ctx context.Context) error { + return nil // Block verification is fully handled by VerifyProposer +} + func (b *preForkBlock) Options(ctx context.Context) ([2]snowman.Block, error) { oracleBlk, ok := b.Block.(snowman.OracleBlock) if !ok { @@ -95,7 +99,7 @@ func (b *preForkBlock) getInnerBlk() snowman.Block { return b.Block } -func (b *preForkBlock) verifyProposerPreForkChild(ctx context.Context, child *preForkBlock) error { +func (b *preForkBlock) verifyPreForkChild(ctx context.Context, child *preForkBlock) error { parentTimestamp := b.Timestamp() if parentTimestamp.Before(b.vm.activationTime) { return nil @@ -109,15 +113,13 @@ func (b *preForkBlock) verifyProposerPreForkChild(ctx context.Context, child *pr zap.String("reason", "parent is an oracle block"), zap.Stringer("blkID", b.ID()), ) - return child.Block.VerifyProposer(ctx) -} - -func (b *preForkBlock) verifyPreForkChild(ctx context.Context, child *preForkBlock) error { + if err := child.Block.VerifyProposer(ctx); err != nil { + return err + } return child.Block.Verify(ctx) } -// This method only returns nil once (during the transition) -func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkBlock) error { +func (b *preForkBlock) verifyProposerPostForkChild(ctx context.Context, child *postForkBlock) error { if err := verifyIsNotOracleBlock(ctx, b.Block); err != nil { return err } @@ -172,10 +174,11 @@ func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkB } // Verify the lack of signature on the node - if err := child.SignedBlock.Verify(false, b.vm.ctx.ChainID); err != nil { - return err - } + return child.SignedBlock.Verify(false, b.vm.ctx.ChainID) +} +// This method only returns nil once (during the transition) +func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkBlock) error { // Verify the inner block and track it as verified return b.vm.verifyAndRecordInnerBlk(ctx, nil, child) } diff --git a/vms/proposervm/vm.go b/vms/proposervm/vm.go index ae9691ab380..fceac778410 100644 --- a/vms/proposervm/vm.go +++ b/vms/proposervm/vm.go @@ -114,7 +114,8 @@ type VM struct { // Block ID --> Block // Each element is a block that passed verification but // hasn't yet been accepted/rejected - verifiedBlocks map[ids.ID]PostForkBlock + verifiedProposerBlocks map[ids.ID]PostForkBlock + verifiedBlocks map[ids.ID]PostForkBlock // Stateless block ID --> inner block. // Only contains post-fork blocks near the tip so that the cache doesn't get // filled with random blocks every time this node parses blocks while @@ -225,6 +226,7 @@ func (vm *VM) Initialize( scheduler.Dispatch(time.Now()) }) + vm.verifiedProposerBlocks = make(map[ids.ID]PostForkBlock) vm.verifiedBlocks = make(map[ids.ID]PostForkBlock) detachedCtx := utils.Detach(ctx) context, cancel := context.WithCancel(detachedCtx) @@ -748,8 +750,10 @@ func (vm *VM) getForkHeight() (uint64, error) { } func (vm *VM) getPostForkBlock(ctx context.Context, blkID ids.ID) (PostForkBlock, error) { - block, exists := vm.verifiedBlocks[blkID] - if exists { + if block, exists := vm.verifiedProposerBlocks[blkID]; exists { + return block, nil + } + if block, exists := vm.verifiedBlocks[blkID]; exists { return block, nil } @@ -797,6 +801,7 @@ func (vm *VM) acceptPostForkBlock(blk PostForkBlock) error { blkID := blk.ID() vm.lastAcceptedHeight = height + delete(vm.verifiedProposerBlocks, blkID) delete(vm.verifiedBlocks, blkID) // Persist this block, its height index, and its status