diff --git a/chains/manager.go b/chains/manager.go index 61e40f789ddf..2537e8ce1d9a 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -28,6 +28,7 @@ import ( "github.com/ava-labs/avalanchego/network" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/consensus/snowball" "github.com/ava-labs/avalanchego/snow/engine/avalanche/bootstrap/queue" "github.com/ava-labs/avalanchego/snow/engine/avalanche/state" "github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex" @@ -924,7 +925,7 @@ func (m *manager) createAvalancheChain( return nil, fmt.Errorf("couldn't initialize snow base message handler: %w", err) } - var snowmanConsensus smcon.Consensus = &smcon.Topological{} + var snowmanConsensus smcon.Consensus = &smcon.Topological{Factory: snowball.SnowflakeFactory} if m.TracingEnabled { snowmanConsensus = smcon.Trace(snowmanConsensus, m.Tracer) } @@ -1318,7 +1319,7 @@ func (m *manager) createSnowmanChain( return nil, fmt.Errorf("couldn't initialize snow base message handler: %w", err) } - var consensus smcon.Consensus = &smcon.Topological{} + var consensus smcon.Consensus = &smcon.Topological{Factory: snowball.SnowflakeFactory} if m.TracingEnabled { consensus = smcon.Trace(consensus, m.Tracer) } diff --git a/snow/consensus/snowball/tree.go b/snow/consensus/snowball/tree.go index c6773e30c54e..a11b92e1fd03 100644 --- a/snow/consensus/snowball/tree.go +++ b/snow/consensus/snowball/tree.go @@ -360,7 +360,7 @@ func (u *unaryNode) Add(newChoice ids.ID) node { newChild := &unaryNode{ tree: u.tree, preference: newChoice, - decidedPrefix: index + 1, // The new child assumes this branch has decided in it's favor + decidedPrefix: index + 1, // The new child assumes this branch has decided in its favor commonPrefix: ids.NumBits, // The new child has no conflicts under this branch snow: newChildSnow, } diff --git a/snow/consensus/snowman/consensus_test.go b/snow/consensus/snowman/consensus_test.go index bbeda976698b..9ea3256131ef 100644 --- a/snow/consensus/snowman/consensus_test.go +++ b/snow/consensus/snowman/consensus_test.go @@ -691,7 +691,7 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) { votesFor3 := bag.Of(block3.ID()) require.NoError(sm.RecordPoll(context.Background(), votesFor3)) require.Equal(2, sm.NumProcessing()) - require.Equal(block2.ID(), sm.Preference()) + require.Equal(block3.ID(), sm.Preference()) require.NoError(sm.RecordPoll(context.Background(), votesFor3)) require.Zero(sm.NumProcessing()) diff --git a/snow/consensus/snowman/mixed_test.go b/snow/consensus/snowman/mixed_test.go new file mode 100644 index 000000000000..f495923e6e87 --- /dev/null +++ b/snow/consensus/snowman/mixed_test.go @@ -0,0 +1,55 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package snowman + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "gonum.org/v1/gonum/mathext/prng" + + "github.com/ava-labs/avalanchego/snow/consensus/snowball" +) + +func TestConvergenceSnowFlakeSnowBall(t *testing.T) { + require := require.New(t) + + params := snowball.Parameters{ + K: 20, + AlphaPreference: 11, + AlphaConfidence: 11, + Beta: 20, + ConcurrentRepolls: 1, + OptimalProcessing: 1, + MaxOutstandingItems: 1, + MaxItemProcessingTime: 1, + } + + for peerCount := 20; peerCount < 2000; peerCount *= 10 { + numNodes := peerCount + + t.Run(fmt.Sprintf("%d nodes", numNodes), func(t *testing.T) { + n := NewNetwork(params, 10, prng.NewMT19937()) + for i := 0; i < numNodes; i++ { + var sbFactory snowball.Factory + if i%2 == 0 { + sbFactory = snowball.SnowflakeFactory + } else { + sbFactory = snowball.SnowballFactory + } + + factory := TopologicalFactory{factory: sbFactory} + sm := factory.New() + require.NoError(n.AddNode(t, sm)) + } + + for !n.Finalized() { + require.NoError(n.Round()) + } + + require.True(n.Agreement()) + }) + } +} diff --git a/snow/consensus/snowman/snowman_block.go b/snow/consensus/snowman/snowman_block.go index 236c93645a02..84b14422287e 100644 --- a/snow/consensus/snowman/snowman_block.go +++ b/snow/consensus/snowman/snowman_block.go @@ -36,7 +36,7 @@ func (n *snowmanBlock) AddChild(child Block) { // if the snowball instance is nil, this is the first child. So the instance // should be initialized. if n.sb == nil { - n.sb = snowball.NewTree(snowball.SnowballFactory, n.t.params, childID) + n.sb = snowball.NewTree(n.t.Factory, n.t.params, childID) n.children = make(map[ids.ID]Block) } else { n.sb.Add(childID) diff --git a/snow/consensus/snowman/topological.go b/snow/consensus/snowman/topological.go index 9888652f0764..c3939297fb4c 100644 --- a/snow/consensus/snowman/topological.go +++ b/snow/consensus/snowman/topological.go @@ -29,16 +29,20 @@ var ( ) // TopologicalFactory implements Factory by returning a topological struct -type TopologicalFactory struct{} +type TopologicalFactory struct { + factory snowball.Factory +} -func (TopologicalFactory) New() Consensus { - return &Topological{} +func (tf TopologicalFactory) New() Consensus { + return &Topological{Factory: tf.factory} } // Topological implements the Snowman interface by using a tree tracking the // strongly preferred branch. This tree structure amortizes network polls to // vote on more than just the next block. type Topological struct { + Factory snowball.Factory + metrics *metrics // pollNumber is the number of times RecordPolls has been called diff --git a/snow/consensus/snowman/topological_test.go b/snow/consensus/snowman/topological_test.go index 540b5a8f2eb1..f85ed36b7261 100644 --- a/snow/consensus/snowman/topological_test.go +++ b/snow/consensus/snowman/topological_test.go @@ -3,8 +3,12 @@ package snowman -import "testing" +import ( + "testing" + + "github.com/ava-labs/avalanchego/snow/consensus/snowball" +) func TestTopological(t *testing.T) { - runConsensusTests(t, TopologicalFactory{}) + runConsensusTests(t, TopologicalFactory{factory: snowball.SnowflakeFactory}) } diff --git a/snow/engine/snowman/config_test.go b/snow/engine/snowman/config_test.go index fe13d4f474c1..c1649121ee44 100644 --- a/snow/engine/snowman/config_test.go +++ b/snow/engine/snowman/config_test.go @@ -34,6 +34,6 @@ func DefaultConfig(t testing.TB) Config { MaxOutstandingItems: 1, MaxItemProcessingTime: 1, }, - Consensus: &snowman.Topological{}, + Consensus: &snowman.Topological{Factory: snowball.SnowflakeFactory}, } } diff --git a/snow/engine/snowman/engine_test.go b/snow/engine/snowman/engine_test.go index 758153c13514..c9a3cbc0d5f3 100644 --- a/snow/engine/snowman/engine_test.go +++ b/snow/engine/snowman/engine_test.go @@ -3026,7 +3026,7 @@ func TestGetProcessingAncestor(t *testing.T) { ctx = snowtest.ConsensusContext( snowtest.Context(t, snowtest.PChainID), ) - consensus = &snowman.Topological{} + consensus = &snowman.Topological{Factory: snowball.SnowflakeFactory} ) require.NoError(consensus.Initialize( ctx, @@ -3100,7 +3100,7 @@ func TestShouldIssueBlock(t *testing.T) { require.NoError(t, blocks[0].Accept(context.Background())) - c := &snowman.Topological{} + c := &snowman.Topological{Factory: snowball.SnowflakeFactory} require.NoError(t, c.Initialize( ctx, snowball.DefaultParameters, diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index 2d1740a650e0..6c309ce5cf30 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -1376,7 +1376,7 @@ func TestBootstrapPartiallyAccepted(t *testing.T) { MaxOutstandingItems: 1, MaxItemProcessingTime: 1, }, - Consensus: &smcon.Topological{}, + Consensus: &smcon.Topological{Factory: snowball.SnowflakeFactory}, } engine, err := smeng.New(engineConfig) require.NoError(err)