@@ -2660,3 +2660,188 @@ func TestSelectChildPChainHeight(t *testing.T) {
26602660 })
26612661 }
26622662}
2663+
2664+ // This tests the case where a chain has bootstrapped to a last accepted block
2665+ // which references a P-Chain height that is not locally accepted yet.
2666+ func TestBootstrappingAheadOfPChainBuildBlockRegression (t * testing.T ) {
2667+ t .Skip ("FIXME" )
2668+
2669+ require := require .New (t )
2670+
2671+ // innerVMBlks is appended to throughout the test, which modifies the
2672+ // behavior of coreVM.
2673+ innerVMBlks := []* snowmantest.Block {
2674+ snowmantest .Genesis ,
2675+ }
2676+
2677+ var innerToEngine chan <- common.Message
2678+ coreVM := & blocktest.VM {
2679+ VM : enginetest.VM {
2680+ T : t ,
2681+ InitializeF : func (_ context.Context , _ * snow.Context , _ database.Database , _ []byte , _ []byte , _ []byte , toEngine chan <- common.Message , _ []* common.Fx , _ common.AppSender ) error {
2682+ innerToEngine = toEngine
2683+ return nil
2684+ },
2685+ },
2686+ ParseBlockF : func (_ context.Context , blkBytes []byte ) (snowman.Block , error ) {
2687+ for _ , blk := range innerVMBlks {
2688+ if bytes .Equal (blk .Bytes (), blkBytes ) {
2689+ return blk , nil
2690+ }
2691+ }
2692+ return nil , errUnknownBlock
2693+ },
2694+ GetBlockF : func (_ context.Context , blkID ids.ID ) (snowman.Block , error ) {
2695+ for _ , blk := range innerVMBlks {
2696+ if blk .Status == snowtest .Accepted && blk .ID () == blkID {
2697+ return blk , nil
2698+ }
2699+ }
2700+ return nil , database .ErrNotFound
2701+ },
2702+ LastAcceptedF : func (context.Context ) (ids.ID , error ) {
2703+ var (
2704+ lastAcceptedID ids.ID
2705+ lastAcceptedHeight uint64
2706+ )
2707+ for _ , blk := range innerVMBlks {
2708+ if blk .Status == snowtest .Accepted && blk .Height () >= lastAcceptedHeight {
2709+ lastAcceptedID = blk .ID ()
2710+ lastAcceptedHeight = blk .Height ()
2711+ }
2712+ }
2713+ return lastAcceptedID , nil
2714+ },
2715+ }
2716+
2717+ proVM := New (
2718+ coreVM ,
2719+ Config {
2720+ Upgrades : upgrade.Config {
2721+ ApricotPhase4Time : snowmantest .GenesisTimestamp ,
2722+ ApricotPhase4MinPChainHeight : 0 ,
2723+ DurangoTime : snowmantest .GenesisTimestamp ,
2724+ },
2725+ MinBlkDelay : DefaultMinBlockDelay ,
2726+ NumHistoricalBlocks : DefaultNumHistoricalBlocks ,
2727+ StakingLeafSigner : pTestSigner ,
2728+ StakingCertLeaf : pTestCert ,
2729+ Registerer : prometheus .NewRegistry (),
2730+ },
2731+ )
2732+ proVM .Set (snowmantest .GenesisTimestamp )
2733+
2734+ // We mark the P-chain as having synced to height=1.
2735+ const currentPChainHeight = 1
2736+ valState := & validatorstest.State {
2737+ T : t ,
2738+ GetMinimumHeightF : func (context.Context ) (uint64 , error ) {
2739+ return currentPChainHeight , nil
2740+ },
2741+ GetCurrentHeightF : func (context.Context ) (uint64 , error ) {
2742+ return currentPChainHeight , nil
2743+ },
2744+ GetValidatorSetF : func (_ context.Context , height uint64 , _ ids.ID ) (map [ids.NodeID ]* validators.GetValidatorOutput , error ) {
2745+ if height > currentPChainHeight {
2746+ return nil , fmt .Errorf ("requested height (%d) > current P-chain height (%d)" , height , currentPChainHeight )
2747+ }
2748+ return map [ids.NodeID ]* validators.GetValidatorOutput {
2749+ proVM .ctx .NodeID : {
2750+ NodeID : proVM .ctx .NodeID ,
2751+ Weight : 10 ,
2752+ },
2753+ }, nil
2754+ },
2755+ }
2756+
2757+ ctx := snowtest .Context (t , ids.ID {1 })
2758+ ctx .NodeID = ids .NodeIDFromCert (pTestCert )
2759+ ctx .ValidatorState = valState
2760+
2761+ db := prefixdb .New ([]byte {0 }, memdb .New ())
2762+
2763+ toEngine := make (chan common.Message , 1 )
2764+ require .NoError (proVM .Initialize (
2765+ context .Background (),
2766+ ctx ,
2767+ db ,
2768+ nil ,
2769+ nil ,
2770+ nil ,
2771+ toEngine ,
2772+ nil ,
2773+ nil ,
2774+ ))
2775+ defer func () {
2776+ require .NoError (proVM .Shutdown (context .Background ()))
2777+ }()
2778+
2779+ require .NoError (proVM .SetState (context .Background (), snow .Bootstrapping ))
2780+
2781+ // During bootstrapping, the first post-fork block is verified against the
2782+ // P-chain height, so we provide a valid height.
2783+ innerBlock1 := snowmantest .BuildChild (snowmantest .Genesis )
2784+ innerVMBlks = append (innerVMBlks , innerBlock1 )
2785+ statelessBlock1 , err := statelessblock .BuildUnsigned (
2786+ snowmantest .GenesisID ,
2787+ snowmantest .GenesisTimestamp ,
2788+ currentPChainHeight ,
2789+ innerBlock1 .Bytes (),
2790+ )
2791+ require .NoError (err )
2792+
2793+ block1 , err := proVM .ParseBlock (context .Background (), statelessBlock1 .Bytes ())
2794+ require .NoError (err )
2795+
2796+ require .NoError (block1 .Verify (context .Background ()))
2797+ require .NoError (block1 .Accept (context .Background ()))
2798+
2799+ // During bootstrapping, the additional post-fork blocks are not verified
2800+ // against the local P-chain height, so even if we provide a height higher
2801+ // than our P-chain height, verification will succeed.
2802+ innerBlock2 := snowmantest .BuildChild (innerBlock1 )
2803+ innerVMBlks = append (innerVMBlks , innerBlock2 )
2804+ statelessBlock2 , err := statelessblock .Build (
2805+ statelessBlock1 .ID (),
2806+ statelessBlock1 .Timestamp (),
2807+ currentPChainHeight + 1 ,
2808+ pTestCert ,
2809+ innerBlock2 .Bytes (),
2810+ ctx .ChainID ,
2811+ pTestSigner ,
2812+ )
2813+ require .NoError (err )
2814+
2815+ block2 , err := proVM .ParseBlock (context .Background (), statelessBlock2 .Bytes ())
2816+ require .NoError (err )
2817+
2818+ require .NoError (block2 .Verify (context .Background ()))
2819+ require .NoError (block2 .Accept (context .Background ()))
2820+
2821+ require .NoError (proVM .SetPreference (context .Background (), statelessBlock2 .ID ()))
2822+
2823+ // At this point, the VM has a last accepted block with a P-chain height
2824+ // greater than our locally accepted P-chain.
2825+ require .NoError (proVM .SetState (context .Background (), snow .NormalOp ))
2826+
2827+ // If the inner VM requests building a block, the proposervm passes that
2828+ // message to the consensus engine. This is really the source of the issue,
2829+ // as the proposervm is not currently in a state where it can correctly
2830+ // build any blocks.
2831+ innerToEngine <- common .PendingTxs
2832+ require .Equal (common .PendingTxs , <- toEngine )
2833+
2834+ innerBlock3 := snowmantest .BuildChild (innerBlock2 )
2835+ innerVMBlks = append (innerVMBlks , innerBlock3 )
2836+
2837+ coreVM .BuildBlockF = func (context.Context ) (snowman.Block , error ) {
2838+ return innerBlock3 , nil
2839+ }
2840+
2841+ // Attempting to build a block now errors with an unexpected error. This
2842+ // results in dropping the build block request, which breaks the invariant
2843+ // that BuildBlock will be called at least once after sending a PendingTxs
2844+ // message on the ToEngine channel.
2845+ _ , err = proVM .BuildBlock (context .Background ())
2846+ require .NoError (err )
2847+ }
0 commit comments