diff --git a/chains/manager.go b/chains/manager.go index 985741e5eb3a..3d5792fad843 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -929,29 +929,20 @@ func (m *manager) createAvalancheChain( } // create bootstrap gear - _, specifiedLinearizationTime := version.CortinaTimes[ctx.NetworkID] - specifiedLinearizationTime = specifiedLinearizationTime && ctx.ChainID == m.XChainID avalancheBootstrapperConfig := avbootstrap.Config{ - Config: common.Config{ - Ctx: ctx, - Beacons: vdrs, - SampleK: sampleK, - StartupTracker: startupTracker, - Alpha: bootstrapWeight/2 + 1, // must be > 50% - Sender: avalancheMessageSender, - BootstrapTracker: sb, - Timer: h, - RetryBootstrap: m.RetryBootstrap, - RetryBootstrapWarnFrequency: m.RetryBootstrapWarnFrequency, - AncestorsMaxContainersReceived: m.BootstrapAncestorsMaxContainersReceived, - SharedCfg: &common.SharedConfig{}, - }, - AllGetsServer: avaGetHandler, - VtxBlocked: vtxBlocker, - TxBlocked: txBlocker, - Manager: vtxManager, - VM: linearizableVM, - LinearizeOnStartup: !specifiedLinearizationTime, + AllGetsServer: avaGetHandler, + Ctx: ctx, + Beacons: vdrs, + StartupTracker: startupTracker, + Sender: avalancheMessageSender, + AncestorsMaxContainersReceived: m.BootstrapAncestorsMaxContainersReceived, + VtxBlocked: vtxBlocker, + TxBlocked: txBlocker, + Manager: vtxManager, + VM: linearizableVM, + } + if ctx.ChainID == m.XChainID { + avalancheBootstrapperConfig.StopVertexID = version.CortinaXChainStopVertexID[ctx.NetworkID] } avalancheBootstrapper, err := avbootstrap.New( diff --git a/node/node.go b/node/node.go index 04ab4a5015be..ba876d09b2d4 100644 --- a/node/node.go +++ b/node/node.go @@ -1022,7 +1022,7 @@ func (n *Node) initChainManager(avaxAssetID ids.ID) error { BootstrapAncestorsMaxContainersSent: n.Config.BootstrapAncestorsMaxContainersSent, BootstrapAncestorsMaxContainersReceived: n.Config.BootstrapAncestorsMaxContainersReceived, ApricotPhase4Time: version.GetApricotPhase4Time(n.Config.NetworkID), - ApricotPhase4MinPChainHeight: version.GetApricotPhase4MinPChainHeight(n.Config.NetworkID), + ApricotPhase4MinPChainHeight: version.ApricotPhase4MinPChainHeight[n.Config.NetworkID], ResourceTracker: n.resourceTracker, StateSyncBeacons: n.Config.StateSyncIDs, TracingEnabled: n.Config.TraceConfig.Enabled, diff --git a/snow/engine/avalanche/bootstrap/bootstrapper.go b/snow/engine/avalanche/bootstrap/bootstrapper.go index 0f8a2484a4e2..e58538b541ba 100644 --- a/snow/engine/avalanche/bootstrap/bootstrapper.go +++ b/snow/engine/avalanche/bootstrap/bootstrapper.go @@ -42,6 +42,8 @@ func New( StateSummaryFrontierHandler: common.NewNoOpStateSummaryFrontierHandler(config.Ctx.Log), AcceptedStateSummaryHandler: common.NewNoOpAcceptedStateSummaryHandler(config.Ctx.Log), + AcceptedFrontierHandler: common.NewNoOpAcceptedFrontierHandler(config.Ctx.Log), + AcceptedHandler: common.NewNoOpAcceptedHandler(config.Ctx.Log), PutHandler: common.NewNoOpPutHandler(config.Ctx.Log), QueryHandler: common.NewNoOpQueryHandler(config.Ctx.Log), ChitsHandler: common.NewNoOpChitsHandler(config.Ctx.Log), @@ -52,41 +54,41 @@ func New( OnFinished: onFinished, }, } - - if err := b.metrics.Initialize("bs", config.Ctx.AvalancheRegisterer); err != nil { - return nil, err - } - - config.Config.Bootstrapable = b - b.Bootstrapper = common.NewCommonBootstrapper(config.Config) - return b, nil + return b, b.metrics.Initialize("bs", config.Ctx.AvalancheRegisterer) } // Note: To align with the Snowman invariant, it should be guaranteed the VM is // not used until after the bootstrapper has been Started. type bootstrapper struct { Config + common.Halter // list of NoOpsHandler for messages dropped by bootstrapper common.StateSummaryFrontierHandler common.AcceptedStateSummaryHandler + common.AcceptedFrontierHandler + common.AcceptedHandler common.PutHandler common.QueryHandler common.ChitsHandler common.AppHandler - common.Bootstrapper common.Fetcher metrics - started bool - // IDs of vertices that we will send a GetAncestors request for once we are // not at the max number of outstanding requests needToFetch set.Set[ids.ID] // Contains IDs of vertices that have recently been processed processedCache *cache.LRU[ids.ID, struct{}] + + // Tracks the last requestID that was used in a request + requestID uint32 +} + +func (b *bootstrapper) Context() *snow.ConsensusContext { + return b.Ctx } func (b *bootstrapper) Clear(context.Context) error { @@ -256,16 +258,7 @@ func (b *bootstrapper) Connected( return err } - if err := b.StartupTracker.Connected(ctx, nodeID, nodeVersion); err != nil { - return err - } - - if b.started || !b.StartupTracker.ShouldStart() { - return nil - } - - b.started = true - return b.Startup(ctx) + return b.StartupTracker.Connected(ctx, nodeID, nodeVersion) } func (b *bootstrapper) Disconnected(ctx context.Context, nodeID ids.NodeID) error { @@ -327,7 +320,7 @@ func (b *bootstrapper) Start(ctx context.Context, startReqID uint32) error { return err } - b.Config.SharedCfg.RequestID = startReqID + b.requestID = startReqID // If the network was already linearized, don't attempt to linearize it // again. @@ -336,38 +329,38 @@ func (b *bootstrapper) Start(ctx context.Context, startReqID uint32) error { return fmt.Errorf("failed to get linearization status: %w", err) } if linearized { - edge := b.Manager.Edge(ctx) - return b.ForceAccepted(ctx, edge) + return b.ForceAccepted(ctx, nil) } - // If requested, assume the currently accepted state is what was linearized. - // - // Note: This is used to linearize networks that were created after the - // linearization occurred. - if b.Config.LinearizeOnStartup { - edge := b.Manager.Edge(ctx) - stopVertex, err := b.Manager.BuildStopVtx(ctx, edge) - if err != nil { - return fmt.Errorf("failed to create stop vertex: %w", err) - } - if err := stopVertex.Accept(ctx); err != nil { - return fmt.Errorf("failed to accept stop vertex: %w", err) - } - - stopVertexID := stopVertex.ID() - b.Ctx.Log.Info("accepted stop vertex", - zap.Stringer("vtxID", stopVertexID), + // If a stop vertex is well known, accept that. + if b.Config.StopVertexID != ids.Empty { + b.Ctx.Log.Info("using well known stop vertex", + zap.Stringer("vtxID", b.Config.StopVertexID), ) - return b.ForceAccepted(ctx, []ids.ID{stopVertexID}) + return b.ForceAccepted(ctx, []ids.ID{b.Config.StopVertexID}) } - if !b.StartupTracker.ShouldStart() { - return nil + // If a stop vertex isn't well known, treat the current state as the final + // DAG state. + // + // Note: This is used to linearize networks that were created after the + // linearization occurred. + edge := b.Manager.Edge(ctx) + stopVertex, err := b.Manager.BuildStopVtx(ctx, edge) + if err != nil { + return fmt.Errorf("failed to create stop vertex: %w", err) + } + if err := stopVertex.Accept(ctx); err != nil { + return fmt.Errorf("failed to accept stop vertex: %w", err) } - b.started = true - return b.Startup(ctx) + stopVertexID := stopVertex.ID() + b.Ctx.Log.Info("generated stop vertex", + zap.Stringer("vtxID", stopVertexID), + ) + + return b.ForceAccepted(ctx, nil) } func (b *bootstrapper) HealthCheck(ctx context.Context) (interface{}, error) { @@ -410,10 +403,10 @@ func (b *bootstrapper) fetch(ctx context.Context, vtxIDs ...ids.ID) error { return fmt.Errorf("dropping request for %s as there are no validators", vtxID) } validatorID := validatorIDs[0] - b.Config.SharedCfg.RequestID++ + b.requestID++ - b.OutstandingRequests.Add(validatorID, b.Config.SharedCfg.RequestID, vtxID) - b.Config.Sender.SendGetAncestors(ctx, validatorID, b.Config.SharedCfg.RequestID, vtxID) // request vertex and ancestors + b.OutstandingRequests.Add(validatorID, b.requestID, vtxID) + b.Config.Sender.SendGetAncestors(ctx, validatorID, b.requestID, vtxID) // request vertex and ancestors } return b.checkFinish(ctx) } @@ -498,15 +491,9 @@ func (b *bootstrapper) process(ctx context.Context, vtxs ...avalanche.Vertex) er verticesFetchedSoFar := b.VtxBlocked.Jobs.PendingJobs() if verticesFetchedSoFar%common.StatusUpdateFrequency == 0 { // Periodically print progress - if !b.Config.SharedCfg.Restarted { - b.Ctx.Log.Info("fetched vertices", - zap.Uint64("numVerticesFetched", verticesFetchedSoFar), - ) - } else { - b.Ctx.Log.Debug("fetched vertices", - zap.Uint64("numVerticesFetched", verticesFetchedSoFar), - ) - } + b.Ctx.Log.Info("fetched vertices", + zap.Uint64("numVerticesFetched", verticesFetchedSoFar), + ) } parents, err := vtx.Parents() @@ -578,58 +565,36 @@ func (b *bootstrapper) ForceAccepted(ctx context.Context, acceptedContainerIDs [ // checkFinish repeatedly executes pending transactions and requests new frontier blocks until there aren't any new ones // after which it finishes the bootstrap process func (b *bootstrapper) checkFinish(ctx context.Context) error { - // If there are outstanding requests for vertices or we still need to fetch vertices, we can't finish - pendingJobs := b.VtxBlocked.MissingIDs() - if b.IsBootstrapped() || len(pendingJobs) > 0 { + // If we still need to fetch vertices, we can't finish + if len(b.VtxBlocked.MissingIDs()) > 0 { return nil } - if !b.Config.SharedCfg.Restarted { - b.Ctx.Log.Info("executing transactions") - } else { - b.Ctx.Log.Debug("executing transactions") - } - + b.Ctx.Log.Info("executing transactions") _, err := b.TxBlocked.ExecuteAll( ctx, b.Config.Ctx, b, - b.Config.SharedCfg.Restarted, + false, b.Ctx.TxAcceptor, ) if err != nil || b.Halted() { return err } - if !b.Config.SharedCfg.Restarted { - b.Ctx.Log.Info("executing vertices") - } else { - b.Ctx.Log.Debug("executing vertices") - } - + b.Ctx.Log.Info("executing vertices") _, err = b.VtxBlocked.ExecuteAll( ctx, b.Config.Ctx, b, - b.Config.SharedCfg.Restarted, + false, b.Ctx.VertexAcceptor, ) if err != nil || b.Halted() { return err } - // If the chain is linearized, we should immediately move on to start - // bootstrapping snowman. - linearized, err := b.Manager.StopVertexAccepted(ctx) - if err != nil { - return err - } - if !linearized { - b.Ctx.Log.Debug("checking for stop vertex before finishing bootstrapping") - return b.Restart(ctx, true) - } - - // Invariant: edge will only be the stop vertex after its acceptance. + // Invariant: edge will only be the stop vertex edge := b.Manager.Edge(ctx) stopVertexID := edge[0] if err := b.VM.Linearize(ctx, stopVertexID); err != nil { @@ -637,7 +602,7 @@ func (b *bootstrapper) checkFinish(ctx context.Context) error { } b.processedCache.Flush() - return b.OnFinished(ctx, b.Config.SharedCfg.RequestID) + return b.OnFinished(ctx, b.requestID) } // A vertex is less than another vertex if it is unknown. Ties are broken by diff --git a/snow/engine/avalanche/bootstrap/bootstrapper_test.go b/snow/engine/avalanche/bootstrap/bootstrapper_test.go index f8a60c36bf46..4e61e9b137ee 100644 --- a/snow/engine/avalanche/bootstrap/bootstrapper_test.go +++ b/snow/engine/avalanche/bootstrap/bootstrapper_test.go @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" @@ -62,23 +64,10 @@ func newConfig(t *testing.T) (Config, ids.NodeID, *common.SenderTest, *vertex.Te vm := &vertex.TestVM{} vm.T = t - isBootstrapped := false - bootstrapTracker := &common.BootstrapTrackerTest{ - T: t, - IsBootstrappedF: func() bool { - return isBootstrapped - }, - BootstrappedF: func(ids.ID) { - isBootstrapped = true - }, - } - sender.Default(true) manager.Default(true) vm.Default(true) - sender.CantSendGetAcceptedFrontier = false - peer := ids.GenerateTestNodeID() require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, peer, nil, ids.Empty, 1)) @@ -94,29 +83,20 @@ func newConfig(t *testing.T) (Config, ids.NodeID, *common.SenderTest, *vertex.Te startupTracker := tracker.NewStartup(peerTracker, totalWeight/2+1) vdrs.RegisterCallbackListener(constants.PrimaryNetworkID, startupTracker) - commonConfig := common.Config{ + avaGetHandler, err := getter.New(manager, sender, ctx.Log, time.Second, 2000, ctx.AvalancheRegisterer) + require.NoError(err) + + return Config{ + AllGetsServer: avaGetHandler, Ctx: ctx, Beacons: vdrs, - SampleK: vdrs.Count(constants.PrimaryNetworkID), - Alpha: totalWeight/2 + 1, StartupTracker: startupTracker, Sender: sender, - BootstrapTracker: bootstrapTracker, - Timer: &common.TimerTest{}, AncestorsMaxContainersReceived: 2000, - SharedCfg: &common.SharedConfig{}, - } - - avaGetHandler, err := getter.New(manager, sender, ctx.Log, time.Second, 2000, ctx.AvalancheRegisterer) - require.NoError(err) - - return Config{ - Config: commonConfig, - AllGetsServer: avaGetHandler, - VtxBlocked: vtxBlocker, - TxBlocked: txBlocker, - Manager: manager, - VM: vm, + VtxBlocked: vtxBlocker, + TxBlocked: txBlocker, + Manager: manager, + VM: vm, }, peer, sender, manager, vm } @@ -148,7 +128,10 @@ func TestBootstrapperSingleFrontier(t *testing.T) { IDV: vtxID1, StatusV: choices.Processing, }, - HeightV: 0, + ParentsV: []avalanche.Vertex{ + vtx0, + }, + HeightV: 1, BytesV: vtxBytes1, } vtx2 := &avalanche.TestVertex{ // vtx2 is the stop vertex @@ -156,10 +139,14 @@ func TestBootstrapperSingleFrontier(t *testing.T) { IDV: vtxID2, StatusV: choices.Processing, }, - HeightV: 0, + ParentsV: []avalanche.Vertex{ + vtx1, + }, + HeightV: 2, BytesV: vtxBytes2, } + config.StopVertexID = vtxID2 bs, err := New( config, func(context.Context, uint32) error { @@ -172,11 +159,6 @@ func TestBootstrapperSingleFrontier(t *testing.T) { ) require.NoError(err) - vm.CantSetState = false - require.NoError(bs.Start(context.Background(), 0)) - - acceptedIDs := []ids.ID{vtxID0, vtxID1, vtxID2} - manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { switch vtxID { case vtxID0: @@ -219,7 +201,8 @@ func TestBootstrapperSingleFrontier(t *testing.T) { return nil } - require.NoError(bs.ForceAccepted(context.Background(), acceptedIDs)) + vm.CantSetState = false + require.NoError(bs.Start(context.Background(), 0)) require.Equal(snow.NormalOp, config.Ctx.State.Get().State) require.Equal(choices.Accepted, vtx0.Status()) require.Equal(choices.Accepted, vtx1.Status()) @@ -269,6 +252,7 @@ func TestBootstrapperByzantineResponses(t *testing.T) { BytesV: vtxBytes2, } + config.StopVertexID = vtxID1 bs, err := New( config, func(context.Context, uint32) error { @@ -281,10 +265,6 @@ func TestBootstrapperByzantineResponses(t *testing.T) { ) require.NoError(err) - vm.CantSetState = false - require.NoError(bs.Start(context.Background(), 0)) - - acceptedIDs := []ids.ID{vtxID1} manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { switch vtxID { case vtxID1: @@ -324,7 +304,8 @@ func TestBootstrapperByzantineResponses(t *testing.T) { } } - require.NoError(bs.ForceAccepted(context.Background(), acceptedIDs)) // should request vtx0 + vm.CantSetState = false + require.NoError(bs.Start(context.Background(), 0)) // should request vtx0 require.Equal(vtxID0, reqVtxID) oldReqID := *requestID @@ -437,6 +418,7 @@ func TestBootstrapperTxDependencies(t *testing.T) { BytesV: vtxBytes1, } + config.StopVertexID = vtxID1 bs, err := New( config, func(context.Context, uint32) error { @@ -449,11 +431,6 @@ func TestBootstrapperTxDependencies(t *testing.T) { ) require.NoError(err) - vm.CantSetState = false - require.NoError(bs.Start(context.Background(), 0)) - - acceptedIDs := []ids.ID{vtxID1} - manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) { switch { case bytes.Equal(vtxBytes, vtxBytes1): @@ -485,7 +462,8 @@ func TestBootstrapperTxDependencies(t *testing.T) { *reqIDPtr = reqID } - require.NoError(bs.ForceAccepted(context.Background(), acceptedIDs)) // should request vtx0 + vm.CantSetState = false + require.NoError(bs.Start(context.Background(), 0)) manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) { switch { @@ -563,6 +541,7 @@ func TestBootstrapperIncompleteAncestors(t *testing.T) { BytesV: vtxBytes2, } + config.StopVertexID = vtxID2 bs, err := New( config, func(context.Context, uint32) error { @@ -575,10 +554,6 @@ func TestBootstrapperIncompleteAncestors(t *testing.T) { ) require.NoError(err) - vm.CantSetState = false - require.NoError(bs.Start(context.Background(), 0)) - - acceptedIDs := []ids.ID{vtxID2} manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { switch { case vtxID == vtxID0: @@ -617,7 +592,8 @@ func TestBootstrapperIncompleteAncestors(t *testing.T) { requested = vtxID } - require.NoError(bs.ForceAccepted(context.Background(), acceptedIDs)) // should request vtx1 + vm.CantSetState = false + require.NoError(bs.Start(context.Background(), 0)) // should request vtx1 require.Equal(vtxID1, requested) require.NoError(bs.Ancestors(context.Background(), peerID, *reqIDPtr, [][]byte{vtxBytes1})) // Provide vtx1; should request vtx0 @@ -645,7 +621,7 @@ func TestBootstrapperIncompleteAncestors(t *testing.T) { require.Equal(choices.Accepted, vtx2.Status()) } -func TestBootstrapperFinalized(t *testing.T) { +func TestBootstrapperUnexpectedVertex(t *testing.T) { require := require.New(t) config, peerID, sender, manager, vm := newConfig(t) @@ -674,6 +650,7 @@ func TestBootstrapperFinalized(t *testing.T) { BytesV: vtxBytes1, } + config.StopVertexID = vtxID1 bs, err := New( config, func(context.Context, uint32) error { @@ -686,10 +663,6 @@ func TestBootstrapperFinalized(t *testing.T) { ) require.NoError(err) - vm.CantSetState = false - require.NoError(bs.Start(context.Background(), 0)) - - acceptedIDs := []ids.ID{vtxID0, vtxID1} parsedVtx0 := false parsedVtx1 := false manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { @@ -728,20 +701,17 @@ func TestBootstrapperFinalized(t *testing.T) { requestIDs := map[ids.ID]uint32{} sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, vtxID ids.ID) { require.Equal(peerID, vdr) - requestIDs[vtxID] = reqID } - require.NoError(bs.ForceAccepted(context.Background(), acceptedIDs)) // should request vtx0 and vtx1 + vm.CantSetState = false + require.NoError(bs.Start(context.Background(), 0)) // should request vtx1 require.Contains(requestIDs, vtxID1) reqID := requestIDs[vtxID1] - require.NoError(bs.Ancestors(context.Background(), peerID, reqID, [][]byte{vtxBytes1, vtxBytes0})) - require.Contains(requestIDs, vtxID0) - - manager.StopVertexAcceptedF = func(context.Context) (bool, error) { - return vtx1.Status() == choices.Accepted, nil - } + maps.Clear(requestIDs) + require.NoError(bs.Ancestors(context.Background(), peerID, reqID, [][]byte{vtxBytes0})) + require.Contains(requestIDs, vtxID1) manager.EdgeF = func(context.Context) []ids.ID { require.Equal(choices.Accepted, vtx1.Status()) @@ -753,356 +723,8 @@ func TestBootstrapperFinalized(t *testing.T) { return nil } - reqID = requestIDs[vtxID0] - require.NoError(bs.GetAncestorsFailed(context.Background(), peerID, reqID)) - require.Equal(snow.NormalOp, config.Ctx.State.Get().State) - require.Equal(choices.Accepted, vtx0.Status()) - require.Equal(choices.Accepted, vtx1.Status()) -} - -// Test that Ancestors accepts the parents of the first vertex returned -func TestBootstrapperAcceptsAncestorsParents(t *testing.T) { - require := require.New(t) - - config, peerID, sender, manager, vm := newConfig(t) - - vtxID0 := ids.Empty.Prefix(0) - vtxID1 := ids.Empty.Prefix(1) - vtxID2 := ids.Empty.Prefix(2) - - vtxBytes0 := []byte{0} - vtxBytes1 := []byte{1} - vtxBytes2 := []byte{2} - - vtx0 := &avalanche.TestVertex{ - TestDecidable: choices.TestDecidable{ - IDV: vtxID0, - StatusV: choices.Unknown, - }, - HeightV: 0, - BytesV: vtxBytes0, - } - vtx1 := &avalanche.TestVertex{ - TestDecidable: choices.TestDecidable{ - IDV: vtxID1, - StatusV: choices.Unknown, - }, - ParentsV: []avalanche.Vertex{vtx0}, - HeightV: 1, - BytesV: vtxBytes1, - } - vtx2 := &avalanche.TestVertex{ // vtx2 is the stop vertex - TestDecidable: choices.TestDecidable{ - IDV: vtxID2, - StatusV: choices.Unknown, - }, - ParentsV: []avalanche.Vertex{vtx1}, - HeightV: 2, - BytesV: vtxBytes2, - } - - bs, err := New( - config, - func(context.Context, uint32) error { - config.Ctx.State.Set(snow.EngineState{ - Type: p2p.EngineType_ENGINE_TYPE_AVALANCHE, - State: snow.NormalOp, - }) - return nil - }, - ) - require.NoError(err) - - vm.CantSetState = false - require.NoError(bs.Start(context.Background(), 0)) - - acceptedIDs := []ids.ID{vtxID2} - parsedVtx0 := false - parsedVtx1 := false - parsedVtx2 := false - manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { - switch vtxID { - case vtxID0: - if parsedVtx0 { - return vtx0, nil - } - case vtxID1: - if parsedVtx1 { - return vtx1, nil - } - case vtxID2: - if parsedVtx2 { - return vtx2, nil - } - default: - require.FailNow(errUnknownVertex.Error()) - return nil, errUnknownVertex - } - return nil, errUnknownVertex - } - manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) { - switch { - case bytes.Equal(vtxBytes, vtxBytes0): - vtx0.StatusV = choices.Processing - parsedVtx0 = true - return vtx0, nil - case bytes.Equal(vtxBytes, vtxBytes1): - vtx1.StatusV = choices.Processing - parsedVtx1 = true - return vtx1, nil - case bytes.Equal(vtxBytes, vtxBytes2): - vtx2.StatusV = choices.Processing - parsedVtx2 = true - return vtx2, nil - default: - require.FailNow(errUnknownVertex.Error()) - return nil, errUnknownVertex - } - } - - requestIDs := map[ids.ID]uint32{} - sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, vtxID ids.ID) { - require.Equal(peerID, vdr) - - requestIDs[vtxID] = reqID - } - - require.NoError(bs.ForceAccepted(context.Background(), acceptedIDs)) // should request vtx2 - require.Contains(requestIDs, vtxID2) - - manager.StopVertexAcceptedF = func(context.Context) (bool, error) { - return vtx2.Status() == choices.Accepted, nil - } - - manager.EdgeF = func(context.Context) []ids.ID { - require.Equal(choices.Accepted, vtx2.Status()) - return []ids.ID{vtxID2} - } - - vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error { - require.Equal(vtxID2, stopVertexID) - return nil - } - - reqID := requestIDs[vtxID2] - require.NoError(bs.Ancestors(context.Background(), peerID, reqID, [][]byte{vtxBytes2, vtxBytes1, vtxBytes0})) - require.Equal(snow.NormalOp, config.Ctx.State.Get().State) + require.NoError(bs.Ancestors(context.Background(), peerID, reqID, [][]byte{vtxBytes1, vtxBytes0})) require.Equal(choices.Accepted, vtx0.Status()) require.Equal(choices.Accepted, vtx1.Status()) - require.Equal(choices.Accepted, vtx2.Status()) -} - -func TestRestartBootstrapping(t *testing.T) { - require := require.New(t) - - config, peerID, sender, manager, vm := newConfig(t) - - vtxID0 := ids.GenerateTestID() - vtxID1 := ids.GenerateTestID() - vtxID2 := ids.GenerateTestID() - vtxID3 := ids.GenerateTestID() - vtxID4 := ids.GenerateTestID() - vtxID5 := ids.GenerateTestID() - - vtxBytes0 := []byte{0} - vtxBytes1 := []byte{1} - vtxBytes2 := []byte{2} - vtxBytes3 := []byte{3} - vtxBytes4 := []byte{4} - vtxBytes5 := []byte{5} - - vtx0 := &avalanche.TestVertex{ - TestDecidable: choices.TestDecidable{ - IDV: vtxID0, - StatusV: choices.Unknown, - }, - HeightV: 0, - BytesV: vtxBytes0, - } - vtx1 := &avalanche.TestVertex{ - TestDecidable: choices.TestDecidable{ - IDV: vtxID1, - StatusV: choices.Unknown, - }, - ParentsV: []avalanche.Vertex{vtx0}, - HeightV: 1, - BytesV: vtxBytes1, - } - vtx2 := &avalanche.TestVertex{ - TestDecidable: choices.TestDecidable{ - IDV: vtxID2, - StatusV: choices.Unknown, - }, - ParentsV: []avalanche.Vertex{vtx1}, - HeightV: 2, - BytesV: vtxBytes2, - } - vtx3 := &avalanche.TestVertex{ - TestDecidable: choices.TestDecidable{ - IDV: vtxID3, - StatusV: choices.Unknown, - }, - ParentsV: []avalanche.Vertex{vtx2}, - HeightV: 3, - BytesV: vtxBytes3, - } - vtx4 := &avalanche.TestVertex{ - TestDecidable: choices.TestDecidable{ - IDV: vtxID4, - StatusV: choices.Unknown, - }, - ParentsV: []avalanche.Vertex{vtx2}, - HeightV: 3, - BytesV: vtxBytes4, - } - vtx5 := &avalanche.TestVertex{ // vtx5 is the stop vertex - TestDecidable: choices.TestDecidable{ - IDV: vtxID5, - StatusV: choices.Unknown, - }, - ParentsV: []avalanche.Vertex{vtx3, vtx4}, - HeightV: 4, - BytesV: vtxBytes5, - } - - bsIntf, err := New( - config, - func(context.Context, uint32) error { - config.Ctx.State.Set(snow.EngineState{ - Type: p2p.EngineType_ENGINE_TYPE_AVALANCHE, - State: snow.NormalOp, - }) - return nil - }, - ) - require.NoError(err) - - bs := bsIntf.(*bootstrapper) - - vm.CantSetState = false - require.NoError(bs.Start(context.Background(), 0)) - - parsedVtx0 := false - parsedVtx1 := false - parsedVtx2 := false - parsedVtx3 := false - parsedVtx4 := false - parsedVtx5 := false - manager.GetVtxF = func(_ context.Context, vtxID ids.ID) (avalanche.Vertex, error) { - switch vtxID { - case vtxID0: - if parsedVtx0 { - return vtx0, nil - } - case vtxID1: - if parsedVtx1 { - return vtx1, nil - } - case vtxID2: - if parsedVtx2 { - return vtx2, nil - } - case vtxID3: - if parsedVtx3 { - return vtx3, nil - } - case vtxID4: - if parsedVtx4 { - return vtx4, nil - } - case vtxID5: - if parsedVtx5 { - return vtx5, nil - } - default: - require.FailNow(errUnknownVertex.Error()) - return nil, errUnknownVertex - } - return nil, errUnknownVertex - } - manager.ParseVtxF = func(_ context.Context, vtxBytes []byte) (avalanche.Vertex, error) { - switch { - case bytes.Equal(vtxBytes, vtxBytes0): - vtx0.StatusV = choices.Processing - parsedVtx0 = true - return vtx0, nil - case bytes.Equal(vtxBytes, vtxBytes1): - vtx1.StatusV = choices.Processing - parsedVtx1 = true - return vtx1, nil - case bytes.Equal(vtxBytes, vtxBytes2): - vtx2.StatusV = choices.Processing - parsedVtx2 = true - return vtx2, nil - case bytes.Equal(vtxBytes, vtxBytes3): - vtx3.StatusV = choices.Processing - parsedVtx3 = true - return vtx3, nil - case bytes.Equal(vtxBytes, vtxBytes4): - vtx4.StatusV = choices.Processing - parsedVtx4 = true - return vtx4, nil - case bytes.Equal(vtxBytes, vtxBytes5): - vtx5.StatusV = choices.Processing - parsedVtx5 = true - return vtx5, nil - default: - require.FailNow(errUnknownVertex.Error()) - return nil, errUnknownVertex - } - } - - requestIDs := map[ids.ID]uint32{} - sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, vtxID ids.ID) { - require.Equal(peerID, vdr) - - requestIDs[vtxID] = reqID - } - - require.NoError(bs.ForceAccepted(context.Background(), []ids.ID{vtxID3, vtxID4})) // should request vtx3 and vtx4 - require.Contains(requestIDs, vtxID3) - require.Contains(requestIDs, vtxID4) - - vtx3ReqID := requestIDs[vtxID3] - require.NoError(bs.Ancestors(context.Background(), peerID, vtx3ReqID, [][]byte{vtxBytes3, vtxBytes2})) - require.Contains(requestIDs, vtxID1) - require.True(bs.OutstandingRequests.RemoveAny(vtxID4)) - require.True(bs.OutstandingRequests.RemoveAny(vtxID1)) - - bs.needToFetch.Clear() - requestIDs = map[ids.ID]uint32{} - - require.NoError(bs.ForceAccepted(context.Background(), []ids.ID{vtxID5, vtxID3})) - require.Contains(requestIDs, vtxID1) - require.Contains(requestIDs, vtxID4) - require.Contains(requestIDs, vtxID5) - require.NotContains(requestIDs, vtxID3) - - vtx5ReqID := requestIDs[vtxID5] - require.NoError(bs.Ancestors(context.Background(), peerID, vtx5ReqID, [][]byte{vtxBytes5, vtxBytes4, vtxBytes2, vtxBytes1})) - require.Contains(requestIDs, vtxID0) - - manager.StopVertexAcceptedF = func(context.Context) (bool, error) { - return vtx5.Status() == choices.Accepted, nil - } - - manager.EdgeF = func(context.Context) []ids.ID { - require.Equal(choices.Accepted, vtx5.Status()) - return []ids.ID{vtxID5} - } - - vm.LinearizeF = func(_ context.Context, stopVertexID ids.ID) error { - require.Equal(vtxID5, stopVertexID) - return nil - } - - vtx1ReqID := requestIDs[vtxID1] - require.NoError(bs.Ancestors(context.Background(), peerID, vtx1ReqID, [][]byte{vtxBytes1, vtxBytes0})) require.Equal(snow.NormalOp, config.Ctx.State.Get().State) - require.Equal(choices.Accepted, vtx0.Status()) - require.Equal(choices.Accepted, vtx1.Status()) - require.Equal(choices.Accepted, vtx2.Status()) - require.Equal(choices.Accepted, vtx3.Status()) - require.Equal(choices.Accepted, vtx4.Status()) - require.Equal(choices.Accepted, vtx5.Status()) } diff --git a/snow/engine/avalanche/bootstrap/config.go b/snow/engine/avalanche/bootstrap/config.go index 0569342cc328..54fe7f2e45fa 100644 --- a/snow/engine/avalanche/bootstrap/config.go +++ b/snow/engine/avalanche/bootstrap/config.go @@ -4,21 +4,37 @@ package bootstrap import ( + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/common/queue" + "github.com/ava-labs/avalanchego/snow/engine/common/tracker" + "github.com/ava-labs/avalanchego/snow/validators" ) type Config struct { - common.Config common.AllGetsServer + Ctx *snow.ConsensusContext + Beacons validators.Manager + + StartupTracker tracker.Startup + Sender common.Sender + + // This node will only consider the first [AncestorsMaxContainersReceived] + // containers in an ancestors message it receives. + AncestorsMaxContainersReceived int + // VtxBlocked tracks operations that are blocked on vertices VtxBlocked *queue.JobsWithMissing // TxBlocked tracks operations that are blocked on transactions TxBlocked *queue.Jobs - Manager vertex.Manager - VM vertex.LinearizableVM - LinearizeOnStartup bool + Manager vertex.Manager + VM vertex.LinearizableVM + + // If StopVertexID is empty, the engine will generate the stop vertex based + // on the current state. + StopVertexID ids.ID } diff --git a/snow/engine/avalanche/getter/getter.go b/snow/engine/avalanche/getter/getter.go index 88f485ab1118..3796bd9888b5 100644 --- a/snow/engine/avalanche/getter/getter.go +++ b/snow/engine/avalanche/getter/getter.go @@ -83,6 +83,8 @@ func (gh *getter) GetAcceptedStateSummary(_ context.Context, nodeID ids.NodeID, return nil } +// TODO: Remove support for GetAcceptedFrontier messages after v1.11.x is +// activated. func (gh *getter) GetAcceptedFrontier(ctx context.Context, validatorID ids.NodeID, requestID uint32) error { acceptedFrontier := gh.storage.Edge(ctx) // Since all the DAGs are linearized, we only need to return the stop @@ -93,6 +95,7 @@ func (gh *getter) GetAcceptedFrontier(ctx context.Context, validatorID ids.NodeI return nil } +// TODO: Remove support for GetAccepted messages after v1.11.x is activated. func (gh *getter) GetAccepted(ctx context.Context, nodeID ids.NodeID, requestID uint32, containerIDs []ids.ID) error { acceptedVtxIDs := make([]ids.ID, 0, len(containerIDs)) for _, vtxID := range containerIDs { diff --git a/version/constants.go b/version/constants.go index 690e68b3505f..25cedb5b73c1 100644 --- a/version/constants.go +++ b/version/constants.go @@ -9,6 +9,7 @@ import ( _ "embed" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/constants" ) @@ -75,7 +76,6 @@ var ( constants.MainnetID: 793005, constants.FujiID: 47437, } - ApricotPhase4DefaultMinPChainHeight uint64 ApricotPhase5Times = map[uint32]time.Time{ constants.MainnetID: time.Date(2021, time.December, 2, 18, 0, 0, 0, time.UTC), @@ -96,6 +96,7 @@ var ( constants.MainnetID: time.Date(2023, time.April, 25, 15, 0, 0, 0, time.UTC), constants.FujiID: time.Date(2023, time.April, 6, 15, 0, 0, 0, time.UTC), } + CortinaXChainStopVertexID map[uint32]ids.ID // TODO: update this before release DTimes = map[uint32]time.Time{ @@ -123,6 +124,29 @@ func init() { } RPCChainVMProtocolCompatibility[rpcChainVMProtocol] = versions } + + // The mainnet stop vertex is well known. It can be verified on any fully + // synced node by looking at the parentID of the genesis block. + // + // Ref: https://subnets.avax.network/x-chain/block/0 + mainnetXChainStopVertexID, err := ids.FromString("jrGWDh5Po9FMj54depyunNixpia5PN4aAYxfmNzU8n752Rjga") + if err != nil { + panic(err) + } + + // The fuji stop vertex is well known. It can be verified on any fully + // synced node by looking at the parentID of the genesis block. + // + // Ref: https://subnets-test.avax.network/x-chain/block/0 + fujiXChainStopVertexID, err := ids.FromString("2D1cmbiG36BqQMRyHt4kFhWarmatA1ighSpND3FeFgz3vFVtCZ") + if err != nil { + panic(err) + } + + CortinaXChainStopVertexID = map[uint32]ids.ID{ + constants.MainnetID: mainnetXChainStopVertexID, + constants.FujiID: fujiXChainStopVertexID, + } } func GetApricotPhase3Time(networkID uint32) time.Time { @@ -139,13 +163,6 @@ func GetApricotPhase4Time(networkID uint32) time.Time { return DefaultUpgradeTime } -func GetApricotPhase4MinPChainHeight(networkID uint32) uint64 { - if minHeight, exists := ApricotPhase4MinPChainHeight[networkID]; exists { - return minHeight - } - return ApricotPhase4DefaultMinPChainHeight -} - func GetApricotPhase5Time(networkID uint32) time.Time { if upgradeTime, exists := ApricotPhase5Times[networkID]; exists { return upgradeTime