github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/snowman/bootstrap/bootstrapper_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package bootstrap
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/MetalBlockchain/metalgo/database"
    17  	"github.com/MetalBlockchain/metalgo/database/memdb"
    18  	"github.com/MetalBlockchain/metalgo/ids"
    19  	"github.com/MetalBlockchain/metalgo/network/p2p"
    20  	"github.com/MetalBlockchain/metalgo/snow"
    21  	"github.com/MetalBlockchain/metalgo/snow/choices"
    22  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowman"
    23  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowman/snowmantest"
    24  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    25  	"github.com/MetalBlockchain/metalgo/snow/engine/common/tracker"
    26  	"github.com/MetalBlockchain/metalgo/snow/engine/snowman/block"
    27  	"github.com/MetalBlockchain/metalgo/snow/engine/snowman/bootstrap/interval"
    28  	"github.com/MetalBlockchain/metalgo/snow/engine/snowman/getter"
    29  	"github.com/MetalBlockchain/metalgo/snow/snowtest"
    30  	"github.com/MetalBlockchain/metalgo/snow/validators"
    31  	"github.com/MetalBlockchain/metalgo/utils/set"
    32  	"github.com/MetalBlockchain/metalgo/version"
    33  
    34  	p2ppb "github.com/MetalBlockchain/metalgo/proto/pb/p2p"
    35  )
    36  
    37  var errUnknownBlock = errors.New("unknown block")
    38  
    39  func newConfig(t *testing.T) (Config, ids.NodeID, *common.SenderTest, *block.TestVM) {
    40  	require := require.New(t)
    41  
    42  	snowCtx := snowtest.Context(t, snowtest.CChainID)
    43  	ctx := snowtest.ConsensusContext(snowCtx)
    44  
    45  	vdrs := validators.NewManager()
    46  
    47  	sender := &common.SenderTest{}
    48  	vm := &block.TestVM{}
    49  
    50  	sender.T = t
    51  	vm.T = t
    52  
    53  	sender.Default(true)
    54  	vm.Default(true)
    55  
    56  	isBootstrapped := false
    57  	bootstrapTracker := &common.BootstrapTrackerTest{
    58  		T: t,
    59  		IsBootstrappedF: func() bool {
    60  			return isBootstrapped
    61  		},
    62  		BootstrappedF: func(ids.ID) {
    63  			isBootstrapped = true
    64  		},
    65  	}
    66  
    67  	sender.CantSendGetAcceptedFrontier = false
    68  
    69  	peer := ids.GenerateTestNodeID()
    70  	require.NoError(vdrs.AddStaker(ctx.SubnetID, peer, nil, ids.Empty, 1))
    71  
    72  	totalWeight, err := vdrs.TotalWeight(ctx.SubnetID)
    73  	require.NoError(err)
    74  	startupTracker := tracker.NewStartup(tracker.NewPeers(), totalWeight/2+1)
    75  	vdrs.RegisterSetCallbackListener(ctx.SubnetID, startupTracker)
    76  
    77  	require.NoError(startupTracker.Connected(context.Background(), peer, version.CurrentApp))
    78  
    79  	snowGetHandler, err := getter.New(vm, sender, ctx.Log, time.Second, 2000, ctx.Registerer)
    80  	require.NoError(err)
    81  
    82  	peerTracker, err := p2p.NewPeerTracker(
    83  		ctx.Log,
    84  		"",
    85  		prometheus.NewRegistry(),
    86  		nil,
    87  		nil,
    88  	)
    89  	require.NoError(err)
    90  
    91  	peerTracker.Connected(peer, version.CurrentApp)
    92  
    93  	return Config{
    94  		AllGetsServer:                  snowGetHandler,
    95  		Ctx:                            ctx,
    96  		Beacons:                        vdrs,
    97  		SampleK:                        vdrs.Count(ctx.SubnetID),
    98  		StartupTracker:                 startupTracker,
    99  		PeerTracker:                    peerTracker,
   100  		Sender:                         sender,
   101  		BootstrapTracker:               bootstrapTracker,
   102  		Timer:                          &common.TimerTest{},
   103  		AncestorsMaxContainersReceived: 2000,
   104  		DB:                             memdb.New(),
   105  		VM:                             vm,
   106  	}, peer, sender, vm
   107  }
   108  
   109  func TestBootstrapperStartsOnlyIfEnoughStakeIsConnected(t *testing.T) {
   110  	require := require.New(t)
   111  
   112  	sender := &common.SenderTest{T: t}
   113  	vm := &block.TestVM{
   114  		TestVM: common.TestVM{T: t},
   115  	}
   116  
   117  	sender.Default(true)
   118  	vm.Default(true)
   119  	snowCtx := snowtest.Context(t, snowtest.CChainID)
   120  	ctx := snowtest.ConsensusContext(snowCtx)
   121  	// create boostrapper configuration
   122  	peers := validators.NewManager()
   123  	sampleK := 2
   124  	alpha := uint64(10)
   125  	startupAlpha := alpha
   126  
   127  	startupTracker := tracker.NewStartup(tracker.NewPeers(), startupAlpha)
   128  	peers.RegisterSetCallbackListener(ctx.SubnetID, startupTracker)
   129  
   130  	snowGetHandler, err := getter.New(vm, sender, ctx.Log, time.Second, 2000, ctx.Registerer)
   131  	require.NoError(err)
   132  
   133  	peerTracker, err := p2p.NewPeerTracker(
   134  		ctx.Log,
   135  		"",
   136  		prometheus.NewRegistry(),
   137  		nil,
   138  		nil,
   139  	)
   140  	require.NoError(err)
   141  
   142  	cfg := Config{
   143  		AllGetsServer:                  snowGetHandler,
   144  		Ctx:                            ctx,
   145  		Beacons:                        peers,
   146  		SampleK:                        sampleK,
   147  		StartupTracker:                 startupTracker,
   148  		PeerTracker:                    peerTracker,
   149  		Sender:                         sender,
   150  		BootstrapTracker:               &common.BootstrapTrackerTest{},
   151  		Timer:                          &common.TimerTest{},
   152  		AncestorsMaxContainersReceived: 2000,
   153  		DB:                             memdb.New(),
   154  		VM:                             vm,
   155  	}
   156  
   157  	vm.CantLastAccepted = false
   158  	vm.LastAcceptedF = func(context.Context) (ids.ID, error) {
   159  		return snowmantest.GenesisID, nil
   160  	}
   161  	vm.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) {
   162  		require.Equal(snowmantest.GenesisID, blkID)
   163  		return snowmantest.Genesis, nil
   164  	}
   165  
   166  	// create bootstrapper
   167  	dummyCallback := func(context.Context, uint32) error {
   168  		cfg.Ctx.State.Set(snow.EngineState{
   169  			Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   170  			State: snow.NormalOp,
   171  		})
   172  		return nil
   173  	}
   174  	bs, err := New(cfg, dummyCallback)
   175  	require.NoError(err)
   176  
   177  	vm.CantSetState = false
   178  	vm.CantConnected = true
   179  	vm.ConnectedF = func(context.Context, ids.NodeID, *version.Application) error {
   180  		return nil
   181  	}
   182  
   183  	frontierRequested := false
   184  	sender.CantSendGetAcceptedFrontier = false
   185  	sender.SendGetAcceptedFrontierF = func(context.Context, set.Set[ids.NodeID], uint32) {
   186  		frontierRequested = true
   187  	}
   188  
   189  	// attempt starting bootstrapper with no stake connected. Bootstrapper should stall.
   190  	require.NoError(bs.Start(context.Background(), 0))
   191  	require.False(frontierRequested)
   192  
   193  	// attempt starting bootstrapper with not enough stake connected. Bootstrapper should stall.
   194  	vdr0 := ids.GenerateTestNodeID()
   195  	require.NoError(peers.AddStaker(ctx.SubnetID, vdr0, nil, ids.Empty, startupAlpha/2))
   196  
   197  	peerTracker.Connected(vdr0, version.CurrentApp)
   198  	require.NoError(bs.Connected(context.Background(), vdr0, version.CurrentApp))
   199  
   200  	require.NoError(bs.Start(context.Background(), 0))
   201  	require.False(frontierRequested)
   202  
   203  	// finally attempt starting bootstrapper with enough stake connected. Frontiers should be requested.
   204  	vdr := ids.GenerateTestNodeID()
   205  	require.NoError(peers.AddStaker(ctx.SubnetID, vdr, nil, ids.Empty, startupAlpha))
   206  
   207  	peerTracker.Connected(vdr, version.CurrentApp)
   208  	require.NoError(bs.Connected(context.Background(), vdr, version.CurrentApp))
   209  	require.True(frontierRequested)
   210  }
   211  
   212  // Single node in the accepted frontier; no need to fetch parent
   213  func TestBootstrapperSingleFrontier(t *testing.T) {
   214  	require := require.New(t)
   215  
   216  	config, _, _, vm := newConfig(t)
   217  
   218  	blks := snowmantest.BuildChain(1)
   219  	initializeVMWithBlockchain(vm, blks)
   220  
   221  	bs, err := New(
   222  		config,
   223  		func(context.Context, uint32) error {
   224  			config.Ctx.State.Set(snow.EngineState{
   225  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   226  				State: snow.NormalOp,
   227  			})
   228  			return nil
   229  		},
   230  	)
   231  	require.NoError(err)
   232  
   233  	require.NoError(bs.Start(context.Background(), 0))
   234  
   235  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[0:1])))
   236  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   237  }
   238  
   239  // Requests the unknown block and gets back a Ancestors with unexpected block.
   240  // Requests again and gets the expected block.
   241  func TestBootstrapperUnknownByzantineResponse(t *testing.T) {
   242  	require := require.New(t)
   243  
   244  	config, peerID, sender, vm := newConfig(t)
   245  
   246  	blks := snowmantest.BuildChain(2)
   247  	initializeVMWithBlockchain(vm, blks)
   248  
   249  	bs, err := New(
   250  		config,
   251  		func(context.Context, uint32) error {
   252  			config.Ctx.State.Set(snow.EngineState{
   253  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   254  				State: snow.NormalOp,
   255  			})
   256  			return nil
   257  		},
   258  	)
   259  	require.NoError(err)
   260  
   261  	require.NoError(bs.Start(context.Background(), 0))
   262  
   263  	var requestID uint32
   264  	sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) {
   265  		require.Equal(peerID, nodeID)
   266  		require.Equal(blks[1].ID(), blkID)
   267  		requestID = reqID
   268  	}
   269  
   270  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:2]))) // should request blk1
   271  
   272  	oldReqID := requestID
   273  	require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks[0:1]))) // respond with wrong block
   274  	require.NotEqual(oldReqID, requestID)
   275  
   276  	require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks[1:2])))
   277  
   278  	require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State)
   279  	requireStatusIs(require, blks, choices.Accepted)
   280  
   281  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:2])))
   282  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   283  }
   284  
   285  // There are multiple needed blocks and multiple Ancestors are required
   286  func TestBootstrapperPartialFetch(t *testing.T) {
   287  	require := require.New(t)
   288  
   289  	config, peerID, sender, vm := newConfig(t)
   290  
   291  	blks := snowmantest.BuildChain(4)
   292  	initializeVMWithBlockchain(vm, blks)
   293  
   294  	bs, err := New(
   295  		config,
   296  		func(context.Context, uint32) error {
   297  			config.Ctx.State.Set(snow.EngineState{
   298  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   299  				State: snow.NormalOp,
   300  			})
   301  			return nil
   302  		},
   303  	)
   304  	require.NoError(err)
   305  
   306  	require.NoError(bs.Start(context.Background(), 0))
   307  
   308  	var (
   309  		requestID uint32
   310  		requested ids.ID
   311  	)
   312  	sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) {
   313  		require.Equal(peerID, nodeID)
   314  		require.Contains([]ids.ID{blks[1].ID(), blks[3].ID()}, blkID)
   315  		requestID = reqID
   316  		requested = blkID
   317  	}
   318  
   319  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4]))) // should request blk3
   320  	require.Equal(blks[3].ID(), requested)
   321  
   322  	require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks[2:4]))) // respond with blk3 and blk2
   323  	require.Equal(blks[1].ID(), requested)
   324  
   325  	require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks[1:2]))) // respond with blk1
   326  
   327  	require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State)
   328  	requireStatusIs(require, blks, choices.Accepted)
   329  
   330  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4])))
   331  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   332  }
   333  
   334  // There are multiple needed blocks and some validators do not have all the
   335  // blocks.
   336  func TestBootstrapperEmptyResponse(t *testing.T) {
   337  	require := require.New(t)
   338  
   339  	config, peerID, sender, vm := newConfig(t)
   340  
   341  	blks := snowmantest.BuildChain(2)
   342  	initializeVMWithBlockchain(vm, blks)
   343  
   344  	bs, err := New(
   345  		config,
   346  		func(context.Context, uint32) error {
   347  			config.Ctx.State.Set(snow.EngineState{
   348  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   349  				State: snow.NormalOp,
   350  			})
   351  			return nil
   352  		},
   353  	)
   354  	require.NoError(err)
   355  
   356  	require.NoError(bs.Start(context.Background(), 0))
   357  
   358  	var (
   359  		requestedNodeID ids.NodeID
   360  		requestID       uint32
   361  	)
   362  	sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) {
   363  		require.Equal(blks[1].ID(), blkID)
   364  		requestedNodeID = nodeID
   365  		requestID = reqID
   366  	}
   367  
   368  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:2])))
   369  	require.Equal(requestedNodeID, peerID)
   370  
   371  	// Add another peer to allow a new node to be selected. A new node should be
   372  	// sampled if the prior response was empty.
   373  	bs.PeerTracker.Connected(ids.GenerateTestNodeID(), version.CurrentApp)
   374  
   375  	require.NoError(bs.Ancestors(context.Background(), requestedNodeID, requestID, nil)) // respond with empty
   376  	require.NotEqual(requestedNodeID, peerID)
   377  
   378  	require.NoError(bs.Ancestors(context.Background(), requestedNodeID, requestID, blocksToBytes(blks[1:2])))
   379  	require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State)
   380  	requireStatusIs(require, blks, choices.Accepted)
   381  }
   382  
   383  // There are multiple needed blocks and Ancestors returns all at once
   384  func TestBootstrapperAncestors(t *testing.T) {
   385  	require := require.New(t)
   386  
   387  	config, peerID, sender, vm := newConfig(t)
   388  
   389  	blks := snowmantest.BuildChain(4)
   390  	initializeVMWithBlockchain(vm, blks)
   391  
   392  	bs, err := New(
   393  		config,
   394  		func(context.Context, uint32) error {
   395  			config.Ctx.State.Set(snow.EngineState{
   396  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   397  				State: snow.NormalOp,
   398  			})
   399  			return nil
   400  		},
   401  	)
   402  	require.NoError(err)
   403  
   404  	require.NoError(bs.Start(context.Background(), 0))
   405  
   406  	var (
   407  		requestID uint32
   408  		requested ids.ID
   409  	)
   410  	sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) {
   411  		require.Equal(peerID, nodeID)
   412  		require.Equal(blks[3].ID(), blkID)
   413  		requestID = reqID
   414  		requested = blkID
   415  	}
   416  
   417  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4]))) // should request blk3
   418  	require.Equal(blks[3].ID(), requested)
   419  
   420  	require.NoError(bs.Ancestors(context.Background(), peerID, requestID, blocksToBytes(blks))) // respond with all the blocks
   421  
   422  	require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State)
   423  	requireStatusIs(require, blks, choices.Accepted)
   424  
   425  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4])))
   426  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   427  }
   428  
   429  func TestBootstrapperFinalized(t *testing.T) {
   430  	require := require.New(t)
   431  
   432  	config, peerID, sender, vm := newConfig(t)
   433  
   434  	blks := snowmantest.BuildChain(3)
   435  	initializeVMWithBlockchain(vm, blks)
   436  
   437  	bs, err := New(
   438  		config,
   439  		func(context.Context, uint32) error {
   440  			config.Ctx.State.Set(snow.EngineState{
   441  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   442  				State: snow.NormalOp,
   443  			})
   444  			return nil
   445  		},
   446  	)
   447  	require.NoError(err)
   448  
   449  	require.NoError(bs.Start(context.Background(), 0))
   450  
   451  	requestIDs := map[ids.ID]uint32{}
   452  	sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) {
   453  		require.Equal(peerID, nodeID)
   454  		requestIDs[blkID] = reqID
   455  	}
   456  
   457  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:3]))) // should request blk1 and blk2
   458  
   459  	reqIDBlk2, ok := requestIDs[blks[2].ID()]
   460  	require.True(ok)
   461  
   462  	require.NoError(bs.Ancestors(context.Background(), peerID, reqIDBlk2, blocksToBytes(blks[1:3])))
   463  
   464  	require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State)
   465  	requireStatusIs(require, blks, choices.Accepted)
   466  
   467  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[2:3])))
   468  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   469  }
   470  
   471  func TestRestartBootstrapping(t *testing.T) {
   472  	require := require.New(t)
   473  
   474  	config, peerID, sender, vm := newConfig(t)
   475  
   476  	blks := snowmantest.BuildChain(5)
   477  	initializeVMWithBlockchain(vm, blks)
   478  
   479  	bs, err := New(
   480  		config,
   481  		func(context.Context, uint32) error {
   482  			config.Ctx.State.Set(snow.EngineState{
   483  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   484  				State: snow.NormalOp,
   485  			})
   486  			return nil
   487  		},
   488  	)
   489  	require.NoError(err)
   490  
   491  	require.NoError(bs.Start(context.Background(), 0))
   492  
   493  	requestIDs := map[ids.ID]uint32{}
   494  	sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) {
   495  		require.Equal(peerID, nodeID)
   496  		requestIDs[blkID] = reqID
   497  	}
   498  
   499  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[3:4]))) // should request blk3
   500  
   501  	reqID, ok := requestIDs[blks[3].ID()]
   502  	require.True(ok)
   503  
   504  	require.NoError(bs.Ancestors(context.Background(), peerID, reqID, blocksToBytes(blks[2:4])))
   505  	require.Contains(requestIDs, blks[1].ID())
   506  
   507  	// Remove request, so we can restart bootstrapping via startSyncing
   508  	_, removed := bs.outstandingRequests.DeleteValue(blks[1].ID())
   509  	require.True(removed)
   510  	clear(requestIDs)
   511  
   512  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[4:5])))
   513  
   514  	blk1RequestID, ok := requestIDs[blks[1].ID()]
   515  	require.True(ok)
   516  	blk4RequestID, ok := requestIDs[blks[4].ID()]
   517  	require.True(ok)
   518  
   519  	require.NoError(bs.Ancestors(context.Background(), peerID, blk1RequestID, blocksToBytes(blks[1:2])))
   520  	require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State)
   521  	require.Equal(choices.Accepted, blks[0].Status())
   522  	requireStatusIs(require, blks[1:], choices.Processing)
   523  
   524  	require.NoError(bs.Ancestors(context.Background(), peerID, blk4RequestID, blocksToBytes(blks[4:5])))
   525  	require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State)
   526  	requireStatusIs(require, blks, choices.Accepted)
   527  
   528  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[4:5])))
   529  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   530  }
   531  
   532  func TestBootstrapOldBlockAfterStateSync(t *testing.T) {
   533  	require := require.New(t)
   534  
   535  	config, peerID, sender, vm := newConfig(t)
   536  
   537  	blks := snowmantest.BuildChain(2)
   538  	initializeVMWithBlockchain(vm, blks)
   539  
   540  	blks[0].StatusV = choices.Processing
   541  	require.NoError(blks[1].Accept(context.Background()))
   542  
   543  	bs, err := New(
   544  		config,
   545  		func(context.Context, uint32) error {
   546  			config.Ctx.State.Set(snow.EngineState{
   547  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   548  				State: snow.NormalOp,
   549  			})
   550  			return nil
   551  		},
   552  	)
   553  	require.NoError(err)
   554  
   555  	require.NoError(bs.Start(context.Background(), 0))
   556  
   557  	requestIDs := map[ids.ID]uint32{}
   558  	sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) {
   559  		require.Equal(peerID, nodeID)
   560  		requestIDs[blkID] = reqID
   561  	}
   562  
   563  	// Force Accept, the already transitively accepted, blk0
   564  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[0:1]))) // should request blk0
   565  
   566  	reqID, ok := requestIDs[blks[0].ID()]
   567  	require.True(ok)
   568  
   569  	require.NoError(bs.Ancestors(context.Background(), peerID, reqID, blocksToBytes(blks[0:1])))
   570  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   571  	require.Equal(choices.Processing, blks[0].Status())
   572  	require.Equal(choices.Accepted, blks[1].Status())
   573  }
   574  
   575  func TestBootstrapContinueAfterHalt(t *testing.T) {
   576  	require := require.New(t)
   577  
   578  	config, _, _, vm := newConfig(t)
   579  
   580  	blks := snowmantest.BuildChain(2)
   581  	initializeVMWithBlockchain(vm, blks)
   582  
   583  	bs, err := New(
   584  		config,
   585  		func(context.Context, uint32) error {
   586  			config.Ctx.State.Set(snow.EngineState{
   587  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   588  				State: snow.NormalOp,
   589  			})
   590  			return nil
   591  		},
   592  	)
   593  	require.NoError(err)
   594  
   595  	getBlockF := vm.GetBlockF
   596  	vm.GetBlockF = func(ctx context.Context, blkID ids.ID) (snowman.Block, error) {
   597  		bs.Halt(ctx)
   598  		return getBlockF(ctx, blkID)
   599  	}
   600  
   601  	require.NoError(bs.Start(context.Background(), 0))
   602  
   603  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:2])))
   604  	require.Equal(1, bs.missingBlockIDs.Len())
   605  }
   606  
   607  func TestBootstrapNoParseOnNew(t *testing.T) {
   608  	require := require.New(t)
   609  
   610  	snowCtx := snowtest.Context(t, snowtest.CChainID)
   611  	ctx := snowtest.ConsensusContext(snowCtx)
   612  	peers := validators.NewManager()
   613  
   614  	sender := &common.SenderTest{}
   615  	vm := &block.TestVM{}
   616  
   617  	sender.T = t
   618  	vm.T = t
   619  
   620  	sender.Default(true)
   621  	vm.Default(true)
   622  
   623  	isBootstrapped := false
   624  	bootstrapTracker := &common.BootstrapTrackerTest{
   625  		T: t,
   626  		IsBootstrappedF: func() bool {
   627  			return isBootstrapped
   628  		},
   629  		BootstrappedF: func(ids.ID) {
   630  			isBootstrapped = true
   631  		},
   632  	}
   633  
   634  	sender.CantSendGetAcceptedFrontier = false
   635  
   636  	peer := ids.GenerateTestNodeID()
   637  	require.NoError(peers.AddStaker(ctx.SubnetID, peer, nil, ids.Empty, 1))
   638  
   639  	totalWeight, err := peers.TotalWeight(ctx.SubnetID)
   640  	require.NoError(err)
   641  	startupTracker := tracker.NewStartup(tracker.NewPeers(), totalWeight/2+1)
   642  	peers.RegisterSetCallbackListener(ctx.SubnetID, startupTracker)
   643  	require.NoError(startupTracker.Connected(context.Background(), peer, version.CurrentApp))
   644  
   645  	snowGetHandler, err := getter.New(vm, sender, ctx.Log, time.Second, 2000, ctx.Registerer)
   646  	require.NoError(err)
   647  
   648  	blk1 := snowmantest.BuildChild(snowmantest.Genesis)
   649  
   650  	vm.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) {
   651  		require.Equal(snowmantest.GenesisID, blkID)
   652  		return snowmantest.Genesis, nil
   653  	}
   654  
   655  	intervalDB := memdb.New()
   656  	tree, err := interval.NewTree(intervalDB)
   657  	require.NoError(err)
   658  	_, err = interval.Add(intervalDB, tree, 0, blk1.Height(), blk1.Bytes())
   659  	require.NoError(err)
   660  
   661  	vm.GetBlockF = nil
   662  
   663  	peerTracker, err := p2p.NewPeerTracker(
   664  		ctx.Log,
   665  		"",
   666  		prometheus.NewRegistry(),
   667  		nil,
   668  		nil,
   669  	)
   670  	require.NoError(err)
   671  
   672  	peerTracker.Connected(peer, version.CurrentApp)
   673  
   674  	config := Config{
   675  		AllGetsServer:                  snowGetHandler,
   676  		Ctx:                            ctx,
   677  		Beacons:                        peers,
   678  		SampleK:                        peers.Count(ctx.SubnetID),
   679  		StartupTracker:                 startupTracker,
   680  		PeerTracker:                    peerTracker,
   681  		Sender:                         sender,
   682  		BootstrapTracker:               bootstrapTracker,
   683  		Timer:                          &common.TimerTest{},
   684  		AncestorsMaxContainersReceived: 2000,
   685  		DB:                             intervalDB,
   686  		VM:                             vm,
   687  	}
   688  
   689  	_, err = New(
   690  		config,
   691  		func(context.Context, uint32) error {
   692  			config.Ctx.State.Set(snow.EngineState{
   693  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   694  				State: snow.NormalOp,
   695  			})
   696  			return nil
   697  		},
   698  	)
   699  	require.NoError(err)
   700  }
   701  
   702  func TestBootstrapperReceiveStaleAncestorsMessage(t *testing.T) {
   703  	require := require.New(t)
   704  
   705  	config, peerID, sender, vm := newConfig(t)
   706  
   707  	blks := snowmantest.BuildChain(3)
   708  	initializeVMWithBlockchain(vm, blks)
   709  
   710  	bs, err := New(
   711  		config,
   712  		func(context.Context, uint32) error {
   713  			config.Ctx.State.Set(snow.EngineState{
   714  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   715  				State: snow.NormalOp,
   716  			})
   717  			return nil
   718  		},
   719  	)
   720  	require.NoError(err)
   721  
   722  	require.NoError(bs.Start(context.Background(), 0))
   723  
   724  	requestIDs := map[ids.ID]uint32{}
   725  	sender.SendGetAncestorsF = func(_ context.Context, nodeID ids.NodeID, reqID uint32, blkID ids.ID) {
   726  		require.Equal(peerID, nodeID)
   727  		requestIDs[blkID] = reqID
   728  	}
   729  
   730  	require.NoError(bs.startSyncing(context.Background(), blocksToIDs(blks[1:3]))) // should request blk1 and blk2
   731  
   732  	reqIDBlk1, ok := requestIDs[blks[1].ID()]
   733  	require.True(ok)
   734  	reqIDBlk2, ok := requestIDs[blks[2].ID()]
   735  	require.True(ok)
   736  
   737  	require.NoError(bs.Ancestors(context.Background(), peerID, reqIDBlk2, blocksToBytes(blks[1:3])))
   738  	require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State)
   739  	requireStatusIs(require, blks, choices.Accepted)
   740  
   741  	require.NoError(bs.Ancestors(context.Background(), peerID, reqIDBlk1, blocksToBytes(blks[1:2])))
   742  	require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State)
   743  }
   744  
   745  func TestBootstrapperRollbackOnSetState(t *testing.T) {
   746  	require := require.New(t)
   747  
   748  	config, _, _, vm := newConfig(t)
   749  
   750  	blks := snowmantest.BuildChain(2)
   751  	initializeVMWithBlockchain(vm, blks)
   752  
   753  	blks[1].StatusV = choices.Accepted
   754  
   755  	bs, err := New(
   756  		config,
   757  		func(context.Context, uint32) error {
   758  			config.Ctx.State.Set(snow.EngineState{
   759  				Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   760  				State: snow.NormalOp,
   761  			})
   762  			return nil
   763  		},
   764  	)
   765  	require.NoError(err)
   766  
   767  	vm.SetStateF = func(context.Context, snow.State) error {
   768  		blks[1].StatusV = choices.Processing
   769  		return nil
   770  	}
   771  
   772  	require.NoError(bs.Start(context.Background(), 0))
   773  	require.Equal(blks[0].HeightV, bs.startingHeight)
   774  }
   775  
   776  func initializeVMWithBlockchain(vm *block.TestVM, blocks []*snowmantest.Block) {
   777  	vm.CantSetState = false
   778  	vm.LastAcceptedF = func(context.Context) (ids.ID, error) {
   779  		var (
   780  			lastAcceptedID     ids.ID
   781  			lastAcceptedHeight uint64
   782  		)
   783  		for _, blk := range blocks {
   784  			height := blk.Height()
   785  			if blk.Status() == choices.Accepted && height >= lastAcceptedHeight {
   786  				lastAcceptedID = blk.ID()
   787  				lastAcceptedHeight = height
   788  			}
   789  		}
   790  		return lastAcceptedID, nil
   791  	}
   792  	vm.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) {
   793  		for _, blk := range blocks {
   794  			if blk.Status() == choices.Accepted && blk.ID() == blkID {
   795  				return blk, nil
   796  			}
   797  		}
   798  		return nil, database.ErrNotFound
   799  	}
   800  	vm.ParseBlockF = func(_ context.Context, blkBytes []byte) (snowman.Block, error) {
   801  		for _, blk := range blocks {
   802  			if bytes.Equal(blk.Bytes(), blkBytes) {
   803  				return blk, nil
   804  			}
   805  		}
   806  		return nil, errUnknownBlock
   807  	}
   808  }
   809  
   810  func requireStatusIs(require *require.Assertions, blocks []*snowmantest.Block, status choices.Status) {
   811  	for i, blk := range blocks {
   812  		require.Equal(status, blk.Status(), i)
   813  	}
   814  }
   815  
   816  func blocksToIDs(blocks []*snowmantest.Block) []ids.ID {
   817  	blkIDs := make([]ids.ID, len(blocks))
   818  	for i, blk := range blocks {
   819  		blkIDs[i] = blk.ID()
   820  	}
   821  	return blkIDs
   822  }
   823  
   824  func blocksToBytes(blocks []*snowmantest.Block) [][]byte {
   825  	numBlocks := len(blocks)
   826  	blkBytes := make([][]byte, numBlocks)
   827  	for i, blk := range blocks {
   828  		blkBytes[numBlocks-i-1] = blk.Bytes()
   829  	}
   830  	return blkBytes
   831  }