github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/avalanche/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/memdb"
    17  	"github.com/MetalBlockchain/metalgo/database/prefixdb"
    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/avalanche"
    23  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowstorm"
    24  	"github.com/MetalBlockchain/metalgo/snow/engine/avalanche/bootstrap/queue"
    25  	"github.com/MetalBlockchain/metalgo/snow/engine/avalanche/getter"
    26  	"github.com/MetalBlockchain/metalgo/snow/engine/avalanche/vertex"
    27  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    28  	"github.com/MetalBlockchain/metalgo/snow/engine/common/tracker"
    29  	"github.com/MetalBlockchain/metalgo/snow/snowtest"
    30  	"github.com/MetalBlockchain/metalgo/snow/validators"
    31  	"github.com/MetalBlockchain/metalgo/utils/constants"
    32  	"github.com/MetalBlockchain/metalgo/utils/logging"
    33  	"github.com/MetalBlockchain/metalgo/utils/set"
    34  	"github.com/MetalBlockchain/metalgo/version"
    35  
    36  	p2ppb "github.com/MetalBlockchain/metalgo/proto/pb/p2p"
    37  )
    38  
    39  var (
    40  	errUnknownVertex       = errors.New("unknown vertex")
    41  	errParsedUnknownVertex = errors.New("parsed unknown vertex")
    42  	errUnknownTx           = errors.New("unknown tx")
    43  )
    44  
    45  type testTx struct {
    46  	snowstorm.Tx
    47  
    48  	tx *snowstorm.TestTx
    49  }
    50  
    51  func (t *testTx) Accept(ctx context.Context) error {
    52  	if err := t.Tx.Accept(ctx); err != nil {
    53  		return err
    54  	}
    55  	t.tx.DependenciesV = nil
    56  	return nil
    57  }
    58  
    59  func newConfig(t *testing.T) (Config, ids.NodeID, *common.SenderTest, *vertex.TestManager, *vertex.TestVM) {
    60  	require := require.New(t)
    61  
    62  	snowCtx := snowtest.Context(t, snowtest.CChainID)
    63  	ctx := snowtest.ConsensusContext(snowCtx)
    64  
    65  	vdrs := validators.NewManager()
    66  	db := memdb.New()
    67  	sender := &common.SenderTest{T: t}
    68  	manager := vertex.NewTestManager(t)
    69  	vm := &vertex.TestVM{}
    70  	vm.T = t
    71  
    72  	sender.Default(true)
    73  	manager.Default(true)
    74  	vm.Default(true)
    75  
    76  	peer := ids.GenerateTestNodeID()
    77  	require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, peer, nil, ids.Empty, 1))
    78  
    79  	vtxBlocker, err := queue.NewWithMissing(prefixdb.New([]byte("vtx"), db), "vtx", prometheus.NewRegistry())
    80  	require.NoError(err)
    81  
    82  	txBlocker, err := queue.New(prefixdb.New([]byte("tx"), db), "tx", prometheus.NewRegistry())
    83  	require.NoError(err)
    84  
    85  	peerTracker := tracker.NewPeers()
    86  	totalWeight, err := vdrs.TotalWeight(constants.PrimaryNetworkID)
    87  	require.NoError(err)
    88  	startupTracker := tracker.NewStartup(peerTracker, totalWeight/2+1)
    89  	vdrs.RegisterSetCallbackListener(constants.PrimaryNetworkID, startupTracker)
    90  
    91  	avaGetHandler, err := getter.New(manager, sender, ctx.Log, time.Second, 2000, prometheus.NewRegistry())
    92  	require.NoError(err)
    93  
    94  	p2pTracker, err := p2p.NewPeerTracker(
    95  		logging.NoLog{},
    96  		"",
    97  		prometheus.NewRegistry(),
    98  		nil,
    99  		version.CurrentApp,
   100  	)
   101  	require.NoError(err)
   102  
   103  	p2pTracker.Connected(peer, version.CurrentApp)
   104  
   105  	return Config{
   106  		AllGetsServer:                  avaGetHandler,
   107  		Ctx:                            ctx,
   108  		StartupTracker:                 startupTracker,
   109  		Sender:                         sender,
   110  		PeerTracker:                    p2pTracker,
   111  		AncestorsMaxContainersReceived: 2000,
   112  		VtxBlocked:                     vtxBlocker,
   113  		TxBlocked:                      txBlocker,
   114  		Manager:                        manager,
   115  		VM:                             vm,
   116  	}, peer, sender, manager, vm
   117  }
   118  
   119  // Three vertices in the accepted frontier. None have parents. No need to fetch
   120  // anything
   121  func TestBootstrapperSingleFrontier(t *testing.T) {
   122  	require := require.New(t)
   123  
   124  	config, _, _, manager, vm := newConfig(t)
   125  
   126  	vtxID0 := ids.Empty.Prefix(0)
   127  	vtxID1 := ids.Empty.Prefix(1)
   128  	vtxID2 := ids.Empty.Prefix(2)
   129  
   130  	vtxBytes0 := []byte{0}
   131  	vtxBytes1 := []byte{1}
   132  	vtxBytes2 := []byte{2}
   133  
   134  	vtx0 := &avalanche.TestVertex{
   135  		TestDecidable: choices.TestDecidable{
   136  			IDV:     vtxID0,
   137  			StatusV: choices.Processing,
   138  		},
   139  		HeightV: 0,
   140  		BytesV:  vtxBytes0,
   141  	}
   142  	vtx1 := &avalanche.TestVertex{
   143  		TestDecidable: choices.TestDecidable{
   144  			IDV:     vtxID1,
   145  			StatusV: choices.Processing,
   146  		},
   147  		ParentsV: []avalanche.Vertex{
   148  			vtx0,
   149  		},
   150  		HeightV: 1,
   151  		BytesV:  vtxBytes1,
   152  	}
   153  	vtx2 := &avalanche.TestVertex{ // vtx2 is the stop vertex
   154  		TestDecidable: choices.TestDecidable{
   155  			IDV:     vtxID2,
   156  			StatusV: choices.Processing,
   157  		},
   158  		ParentsV: []avalanche.Vertex{
   159  			vtx1,
   160  		},
   161  		HeightV: 2,
   162  		BytesV:  vtxBytes2,
   163  	}
   164  
   165  	config.StopVertexID = vtxID2
   166  	bs, err := New(
   167  		config,
   168  		func(context.Context, uint32) error {
   169  			config.Ctx.State.Set(snow.EngineState{
   170  				Type:  p2ppb.EngineType_ENGINE_TYPE_AVALANCHE,
   171  				State: snow.NormalOp,
   172  			})
   173  			return nil
   174  		},
   175  		prometheus.NewRegistry(),
   176  	)
   177  	require.NoError(err)
   178  
   179  	manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) {
   180  		switch vtxID {
   181  		case vtxID0:
   182  			return vtx0, nil
   183  		case vtxID1:
   184  			return vtx1, nil
   185  		case vtxID2:
   186  			return vtx2, nil
   187  		default:
   188  			require.FailNow(errUnknownVertex.Error())
   189  			return nil, errUnknownVertex
   190  		}
   191  	}
   192  
   193  	manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) {
   194  		switch {
   195  		case bytes.Equal(vtxBytes, vtxBytes0):
   196  			return vtx0, nil
   197  		case bytes.Equal(vtxBytes, vtxBytes1):
   198  			return vtx1, nil
   199  		case bytes.Equal(vtxBytes, vtxBytes2):
   200  			return vtx2, nil
   201  		default:
   202  			require.FailNow(errParsedUnknownVertex.Error())
   203  			return nil, errParsedUnknownVertex
   204  		}
   205  	}
   206  
   207  	manager.StopVertexAcceptedF = func(context.Context) (bool, error) {
   208  		return vtx2.Status() == choices.Accepted, nil
   209  	}
   210  
   211  	manager.EdgeF = func(context.Context) []ids.ID {
   212  		require.Equal(choices.Accepted, vtx2.Status())
   213  		return []ids.ID{vtxID2}
   214  	}
   215  
   216  	vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error {
   217  		require.Equal(vtxID2, stopVertexID)
   218  		return nil
   219  	}
   220  
   221  	vm.CantSetState = false
   222  	require.NoError(bs.Start(context.Background(), 0))
   223  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   224  	require.Equal(choices.Accepted, vtx0.Status())
   225  	require.Equal(choices.Accepted, vtx1.Status())
   226  	require.Equal(choices.Accepted, vtx2.Status())
   227  }
   228  
   229  // Accepted frontier has one vertex, which has one vertex as a dependency.
   230  // Requests again and gets an unexpected vertex. Requests again and gets the
   231  // expected vertex and an additional vertex that should not be accepted.
   232  func TestBootstrapperByzantineResponses(t *testing.T) {
   233  	require := require.New(t)
   234  
   235  	config, peerID, sender, manager, vm := newConfig(t)
   236  
   237  	vtxID0 := ids.Empty.Prefix(0)
   238  	vtxID1 := ids.Empty.Prefix(1)
   239  	vtxID2 := ids.Empty.Prefix(2)
   240  
   241  	vtxBytes0 := []byte{0}
   242  	vtxBytes1 := []byte{1}
   243  	vtxBytes2 := []byte{2}
   244  
   245  	vtx0 := &avalanche.TestVertex{
   246  		TestDecidable: choices.TestDecidable{
   247  			IDV:     vtxID0,
   248  			StatusV: choices.Unknown,
   249  		},
   250  		HeightV: 0,
   251  		BytesV:  vtxBytes0,
   252  	}
   253  	vtx1 := &avalanche.TestVertex{ // vtx1 is the stop vertex
   254  		TestDecidable: choices.TestDecidable{
   255  			IDV:     vtxID1,
   256  			StatusV: choices.Processing,
   257  		},
   258  		ParentsV: []avalanche.Vertex{vtx0},
   259  		HeightV:  1,
   260  		BytesV:   vtxBytes1,
   261  	}
   262  	// Should not receive transitive votes from [vtx1]
   263  	vtx2 := &avalanche.TestVertex{
   264  		TestDecidable: choices.TestDecidable{
   265  			IDV:     vtxID2,
   266  			StatusV: choices.Unknown,
   267  		},
   268  		HeightV: 0,
   269  		BytesV:  vtxBytes2,
   270  	}
   271  
   272  	config.StopVertexID = vtxID1
   273  	bs, err := New(
   274  		config,
   275  		func(context.Context, uint32) error {
   276  			config.Ctx.State.Set(snow.EngineState{
   277  				Type:  p2ppb.EngineType_ENGINE_TYPE_AVALANCHE,
   278  				State: snow.NormalOp,
   279  			})
   280  			return nil
   281  		},
   282  		prometheus.NewRegistry(),
   283  	)
   284  	require.NoError(err)
   285  
   286  	manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) {
   287  		switch vtxID {
   288  		case vtxID1:
   289  			return vtx1, nil
   290  		case vtxID0:
   291  			return nil, errUnknownVertex
   292  		default:
   293  			require.FailNow(errUnknownVertex.Error())
   294  			return nil, errUnknownVertex
   295  		}
   296  	}
   297  
   298  	requestID := new(uint32)
   299  	reqVtxID := ids.Empty
   300  	sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, vtxID ids.ID) {
   301  		require.Equal(peerID, vdr)
   302  		require.Equal(vtxID0, vtxID)
   303  
   304  		*requestID = reqID
   305  		reqVtxID = vtxID
   306  	}
   307  
   308  	manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) {
   309  		switch {
   310  		case bytes.Equal(vtxBytes, vtxBytes0):
   311  			vtx0.StatusV = choices.Processing
   312  			return vtx0, nil
   313  		case bytes.Equal(vtxBytes, vtxBytes1):
   314  			vtx1.StatusV = choices.Processing
   315  			return vtx1, nil
   316  		case bytes.Equal(vtxBytes, vtxBytes2):
   317  			vtx2.StatusV = choices.Processing
   318  			return vtx2, nil
   319  		default:
   320  			require.FailNow(errParsedUnknownVertex.Error())
   321  			return nil, errParsedUnknownVertex
   322  		}
   323  	}
   324  
   325  	vm.CantSetState = false
   326  	require.NoError(bs.Start(context.Background(), 0)) // should request vtx0
   327  	require.Equal(vtxID0, reqVtxID)
   328  
   329  	oldReqID := *requestID
   330  	require.NoError(bs.Ancestors(context.Background(), peerID, *requestID, [][]byte{vtxBytes2})) // send unexpected vertex
   331  	require.NotEqual(oldReqID, *requestID)                                                       // should have sent a new request
   332  
   333  	oldReqID = *requestID
   334  	manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) {
   335  		switch vtxID {
   336  		case vtxID1:
   337  			return vtx1, nil
   338  		case vtxID0:
   339  			return vtx0, nil
   340  		default:
   341  			require.FailNow(errUnknownVertex.Error())
   342  			return nil, errUnknownVertex
   343  		}
   344  	}
   345  
   346  	manager.StopVertexAcceptedF = func(context.Context) (bool, error) {
   347  		return vtx1.Status() == choices.Accepted, nil
   348  	}
   349  
   350  	manager.EdgeF = func(context.Context) []ids.ID {
   351  		require.Equal(choices.Accepted, vtx1.Status())
   352  		return []ids.ID{vtxID1}
   353  	}
   354  
   355  	vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error {
   356  		require.Equal(vtxID1, stopVertexID)
   357  		return nil
   358  	}
   359  
   360  	require.NoError(bs.Ancestors(context.Background(), peerID, *requestID, [][]byte{vtxBytes0, vtxBytes2})) // send expected vertex and vertex that should not be accepted
   361  	require.Equal(oldReqID, *requestID)                                                                     // shouldn't have sent a new request
   362  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   363  	require.Equal(choices.Accepted, vtx0.Status())
   364  	require.Equal(choices.Accepted, vtx1.Status())
   365  	require.Equal(choices.Processing, vtx2.Status())
   366  }
   367  
   368  // Vertex has a dependency and tx has a dependency
   369  func TestBootstrapperTxDependencies(t *testing.T) {
   370  	require := require.New(t)
   371  
   372  	config, peerID, sender, manager, vm := newConfig(t)
   373  
   374  	txID0 := ids.GenerateTestID()
   375  	txID1 := ids.GenerateTestID()
   376  
   377  	txBytes0 := []byte{0}
   378  	txBytes1 := []byte{1}
   379  
   380  	innerTx0 := &snowstorm.TestTx{
   381  		TestDecidable: choices.TestDecidable{
   382  			IDV:     txID0,
   383  			StatusV: choices.Processing,
   384  		},
   385  		BytesV: txBytes0,
   386  	}
   387  
   388  	// Depends on tx0
   389  	tx1 := &snowstorm.TestTx{
   390  		TestDecidable: choices.TestDecidable{
   391  			IDV:     txID1,
   392  			StatusV: choices.Processing,
   393  		},
   394  		DependenciesV: set.Of(innerTx0.IDV),
   395  		BytesV:        txBytes1,
   396  	}
   397  
   398  	tx0 := &testTx{
   399  		Tx: innerTx0,
   400  		tx: tx1,
   401  	}
   402  
   403  	vtxID0 := ids.GenerateTestID()
   404  	vtxID1 := ids.GenerateTestID()
   405  
   406  	vtxBytes0 := []byte{2}
   407  	vtxBytes1 := []byte{3}
   408  	vm.ParseTxF = func(_ context.Context, b []byte) (snowstorm.Tx, error) {
   409  		switch {
   410  		case bytes.Equal(b, txBytes0):
   411  			return tx0, nil
   412  		case bytes.Equal(b, txBytes1):
   413  			return tx1, nil
   414  		default:
   415  			return nil, errUnknownTx
   416  		}
   417  	}
   418  
   419  	vtx0 := &avalanche.TestVertex{
   420  		TestDecidable: choices.TestDecidable{
   421  			IDV:     vtxID0,
   422  			StatusV: choices.Unknown,
   423  		},
   424  		HeightV: 0,
   425  		TxsV:    []snowstorm.Tx{tx1},
   426  		BytesV:  vtxBytes0,
   427  	}
   428  	vtx1 := &avalanche.TestVertex{ // vtx1 is the stop vertex
   429  		TestDecidable: choices.TestDecidable{
   430  			IDV:     vtxID1,
   431  			StatusV: choices.Processing,
   432  		},
   433  		ParentsV: []avalanche.Vertex{vtx0}, // Depends on vtx0
   434  		HeightV:  1,
   435  		TxsV:     []snowstorm.Tx{tx0},
   436  		BytesV:   vtxBytes1,
   437  	}
   438  
   439  	config.StopVertexID = vtxID1
   440  	bs, err := New(
   441  		config,
   442  		func(context.Context, uint32) error {
   443  			config.Ctx.State.Set(snow.EngineState{
   444  				Type:  p2ppb.EngineType_ENGINE_TYPE_AVALANCHE,
   445  				State: snow.NormalOp,
   446  			})
   447  			return nil
   448  		},
   449  		prometheus.NewRegistry(),
   450  	)
   451  	require.NoError(err)
   452  
   453  	manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) {
   454  		switch {
   455  		case bytes.Equal(vtxBytes, vtxBytes1):
   456  			return vtx1, nil
   457  		case bytes.Equal(vtxBytes, vtxBytes0):
   458  			return vtx0, nil
   459  		default:
   460  			require.FailNow(errParsedUnknownVertex.Error())
   461  			return nil, errParsedUnknownVertex
   462  		}
   463  	}
   464  	manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) {
   465  		switch vtxID {
   466  		case vtxID1:
   467  			return vtx1, nil
   468  		case vtxID0:
   469  			return nil, errUnknownVertex
   470  		default:
   471  			require.FailNow(errUnknownVertex.Error())
   472  			return nil, errUnknownVertex
   473  		}
   474  	}
   475  
   476  	reqIDPtr := new(uint32)
   477  	sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, vtxID ids.ID) {
   478  		require.Equal(peerID, vdr)
   479  		require.Equal(vtxID0, vtxID)
   480  
   481  		*reqIDPtr = reqID
   482  	}
   483  
   484  	vm.CantSetState = false
   485  	require.NoError(bs.Start(context.Background(), 0))
   486  
   487  	manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) {
   488  		switch {
   489  		case bytes.Equal(vtxBytes, vtxBytes1):
   490  			return vtx1, nil
   491  		case bytes.Equal(vtxBytes, vtxBytes0):
   492  			vtx0.StatusV = choices.Processing
   493  			return vtx0, nil
   494  		default:
   495  			require.FailNow(errParsedUnknownVertex.Error())
   496  			return nil, errParsedUnknownVertex
   497  		}
   498  	}
   499  
   500  	manager.StopVertexAcceptedF = func(context.Context) (bool, error) {
   501  		return vtx1.Status() == choices.Accepted, nil
   502  	}
   503  
   504  	manager.EdgeF = func(context.Context) []ids.ID {
   505  		require.Equal(choices.Accepted, vtx1.Status())
   506  		return []ids.ID{vtxID1}
   507  	}
   508  
   509  	vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error {
   510  		require.Equal(vtxID1, stopVertexID)
   511  		return nil
   512  	}
   513  
   514  	require.NoError(bs.Ancestors(context.Background(), peerID, *reqIDPtr, [][]byte{vtxBytes0}))
   515  	require.Equal(snow.NormalOp, config.Ctx.State.Get().State)
   516  	require.Equal(choices.Accepted, tx0.Status())
   517  	require.Equal(choices.Accepted, tx1.Status())
   518  	require.Equal(choices.Accepted, vtx0.Status())
   519  	require.Equal(choices.Accepted, vtx1.Status())
   520  }
   521  
   522  // Ancestors only contains 1 of the two needed vertices; have to issue another GetAncestors
   523  func TestBootstrapperIncompleteAncestors(t *testing.T) {
   524  	require := require.New(t)
   525  
   526  	config, peerID, sender, manager, vm := newConfig(t)
   527  
   528  	vtxID0 := ids.Empty.Prefix(0)
   529  	vtxID1 := ids.Empty.Prefix(1)
   530  	vtxID2 := ids.Empty.Prefix(2)
   531  
   532  	vtxBytes0 := []byte{0}
   533  	vtxBytes1 := []byte{1}
   534  	vtxBytes2 := []byte{2}
   535  
   536  	vtx0 := &avalanche.TestVertex{
   537  		TestDecidable: choices.TestDecidable{
   538  			IDV:     vtxID0,
   539  			StatusV: choices.Unknown,
   540  		},
   541  		HeightV: 0,
   542  		BytesV:  vtxBytes0,
   543  	}
   544  	vtx1 := &avalanche.TestVertex{
   545  		TestDecidable: choices.TestDecidable{
   546  			IDV:     vtxID1,
   547  			StatusV: choices.Unknown,
   548  		},
   549  		ParentsV: []avalanche.Vertex{vtx0},
   550  		HeightV:  1,
   551  		BytesV:   vtxBytes1,
   552  	}
   553  	vtx2 := &avalanche.TestVertex{ // vtx2 is the stop vertex
   554  		TestDecidable: choices.TestDecidable{
   555  			IDV:     vtxID2,
   556  			StatusV: choices.Processing,
   557  		},
   558  		ParentsV: []avalanche.Vertex{vtx1},
   559  		HeightV:  2,
   560  		BytesV:   vtxBytes2,
   561  	}
   562  
   563  	config.StopVertexID = vtxID2
   564  	bs, err := New(
   565  		config,
   566  		func(context.Context, uint32) error {
   567  			config.Ctx.State.Set(snow.EngineState{
   568  				Type:  p2ppb.EngineType_ENGINE_TYPE_AVALANCHE,
   569  				State: snow.NormalOp,
   570  			})
   571  			return nil
   572  		},
   573  		prometheus.NewRegistry(),
   574  	)
   575  	require.NoError(err)
   576  
   577  	manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) {
   578  		switch {
   579  		case vtxID == vtxID0:
   580  			return nil, errUnknownVertex
   581  		case vtxID == vtxID1:
   582  			return nil, errUnknownVertex
   583  		case vtxID == vtxID2:
   584  			return vtx2, nil
   585  		default:
   586  			require.FailNow(errUnknownVertex.Error())
   587  			return nil, errUnknownVertex
   588  		}
   589  	}
   590  	manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) {
   591  		switch {
   592  		case bytes.Equal(vtxBytes, vtxBytes0):
   593  			vtx0.StatusV = choices.Processing
   594  			return vtx0, nil
   595  		case bytes.Equal(vtxBytes, vtxBytes1):
   596  			vtx1.StatusV = choices.Processing
   597  			return vtx1, nil
   598  		case bytes.Equal(vtxBytes, vtxBytes2):
   599  			return vtx2, nil
   600  		default:
   601  			require.FailNow(errParsedUnknownVertex.Error())
   602  			return nil, errParsedUnknownVertex
   603  		}
   604  	}
   605  	reqIDPtr := new(uint32)
   606  	requested := ids.Empty
   607  	sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, vtxID ids.ID) {
   608  		require.Equal(peerID, vdr)
   609  		require.Contains([]ids.ID{vtxID1, vtxID0}, vtxID)
   610  
   611  		*reqIDPtr = reqID
   612  		requested = vtxID
   613  	}
   614  
   615  	vm.CantSetState = false
   616  	require.NoError(bs.Start(context.Background(), 0)) // should request vtx1
   617  	require.Equal(vtxID1, requested)
   618  
   619  	require.NoError(bs.Ancestors(context.Background(), peerID, *reqIDPtr, [][]byte{vtxBytes1})) // Provide vtx1; should request vtx0
   620  	require.Equal(snow.Bootstrapping, bs.Context().State.Get().State)
   621  	require.Equal(vtxID0, requested)
   622  
   623  	manager.StopVertexAcceptedF = func(context.Context) (bool, error) {
   624  		return vtx2.Status() == choices.Accepted, nil
   625  	}
   626  
   627  	manager.EdgeF = func(context.Context) []ids.ID {
   628  		require.Equal(choices.Accepted, vtx2.Status())
   629  		return []ids.ID{vtxID2}
   630  	}
   631  
   632  	vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error {
   633  		require.Equal(vtxID2, stopVertexID)
   634  		return nil
   635  	}
   636  
   637  	require.NoError(bs.Ancestors(context.Background(), peerID, *reqIDPtr, [][]byte{vtxBytes0})) // Provide vtx0; can finish now
   638  	require.Equal(snow.NormalOp, bs.Context().State.Get().State)
   639  	require.Equal(choices.Accepted, vtx0.Status())
   640  	require.Equal(choices.Accepted, vtx1.Status())
   641  	require.Equal(choices.Accepted, vtx2.Status())
   642  }