From 11ebe7c6b17a0a030d9b46b9d0a940badd7ec9ab Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Wed, 1 Apr 2020 21:20:31 -0400 Subject: [PATCH] properly weight bootstrapping validators by stake --- chains/manager.go | 35 +++++++++++++++------- snow/engine/avalanche/bootstrapper_test.go | 2 +- snow/engine/common/bootstrapper.go | 30 ++++++++++++++++--- snow/engine/common/config.go | 2 +- snow/engine/snowman/bootstrapper_test.go | 2 +- snow/networking/awaiting_connections.go | 32 +++++++++++++------- snow/validators/set.go | 19 ++++++++++++ vms/spchainvm/consensus_benchmark_test.go | 4 +-- 8 files changed, 97 insertions(+), 29 deletions(-) diff --git a/chains/manager.go b/chains/manager.go index 8eacef5..aef8064 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -27,6 +27,7 @@ import ( "github.com/ava-labs/gecko/snow/triggers" "github.com/ava-labs/gecko/snow/validators" "github.com/ava-labs/gecko/utils/logging" + "github.com/ava-labs/gecko/utils/math" "github.com/ava-labs/gecko/vms" avacon "github.com/ava-labs/gecko/snow/consensus/avalanche" @@ -390,13 +391,22 @@ func (m *manager) createAvalancheChain( }, } + bootstrapWeight := uint64(0) + for _, beacon := range beacons.List() { + newWeight, err := math.Add64(bootstrapWeight, beacon.Weight()) + if err != nil { + return err + } + bootstrapWeight = newWeight + } + engine.Initialize(avaeng.Config{ BootstrapConfig: avaeng.BootstrapConfig{ Config: common.Config{ Context: ctx, Validators: validators, Beacons: beacons, - Alpha: beacons.Len()/2 + 1, // must be > 50% + Alpha: bootstrapWeight/2 + 1, // must be > 50% Sender: &sender, }, VtxBlocked: vtxBlocker, @@ -417,6 +427,8 @@ func (m *manager) createAvalancheChain( go ctx.Log.RecoverAndPanic(handler.Dispatch) awaiting := &networking.AwaitingConnections{ + Requested: beacons, + WeightRequired: (3*bootstrapWeight + 3) / 4, // 75% must be connected to Finish: func() { ctx.Lock.Lock() defer ctx.Lock.Unlock() @@ -424,10 +436,6 @@ func (m *manager) createAvalancheChain( engine.Startup() }, } - for _, vdr := range beacons.List() { - awaiting.Requested.Add(vdr.ID()) - } - awaiting.NumRequired = (3*awaiting.Requested.Len() + 3) / 4 // 75% must be connected to m.awaiter.AwaitConnections(awaiting) return nil @@ -468,6 +476,15 @@ func (m *manager) createSnowmanChain( sender := sender.Sender{} sender.Initialize(ctx, m.sender, m.chainRouter, m.timeoutManager) + bootstrapWeight := uint64(0) + for _, beacon := range beacons.List() { + newWeight, err := math.Add64(bootstrapWeight, beacon.Weight()) + if err != nil { + return err + } + bootstrapWeight = newWeight + } + // The engine handles consensus engine := smeng.Transitive{} engine.Initialize(smeng.Config{ @@ -476,7 +493,7 @@ func (m *manager) createSnowmanChain( Context: ctx, Validators: validators, Beacons: beacons, - Alpha: beacons.Len()/2 + 1, // must be > 50% + Alpha: bootstrapWeight/2 + 1, // must be > 50% Sender: &sender, }, Blocked: blocked, @@ -496,6 +513,8 @@ func (m *manager) createSnowmanChain( go ctx.Log.RecoverAndPanic(handler.Dispatch) awaiting := &networking.AwaitingConnections{ + Requested: beacons, + WeightRequired: (3*bootstrapWeight + 3) / 4, // 75% must be connected to Finish: func() { ctx.Lock.Lock() defer ctx.Lock.Unlock() @@ -503,10 +522,6 @@ func (m *manager) createSnowmanChain( engine.Startup() }, } - for _, vdr := range beacons.List() { - awaiting.Requested.Add(vdr.ID()) - } - awaiting.NumRequired = (3*awaiting.Requested.Len() + 3) / 4 // 75% must be connected to m.awaiter.AwaitConnections(awaiting) return nil } diff --git a/snow/engine/avalanche/bootstrapper_test.go b/snow/engine/avalanche/bootstrapper_test.go index bcfd3dc..cc63b68 100644 --- a/snow/engine/avalanche/bootstrapper_test.go +++ b/snow/engine/avalanche/bootstrapper_test.go @@ -69,7 +69,7 @@ func newConfig(t *testing.T) (BootstrapConfig, ids.ShortID, *common.SenderTest, Context: ctx, Validators: peers, Beacons: peers, - Alpha: peers.Len()/2 + 1, + Alpha: uint64(peers.Len()/2 + 1), Sender: sender, } return BootstrapConfig{ diff --git a/snow/engine/common/bootstrapper.go b/snow/engine/common/bootstrapper.go index 9eebe0e..cda4a43 100644 --- a/snow/engine/common/bootstrapper.go +++ b/snow/engine/common/bootstrapper.go @@ -4,7 +4,10 @@ package common import ( + stdmath "math" + "github.com/ava-labs/gecko/ids" + "github.com/ava-labs/gecko/utils/math" ) // Bootstrapper implements the Engine interface. @@ -15,7 +18,7 @@ type Bootstrapper struct { acceptedFrontier ids.Set pendingAccepted ids.ShortSet - accepted ids.Bag + acceptedVotes map[[32]byte]uint64 RequestID uint32 } @@ -30,7 +33,7 @@ func (b *Bootstrapper) Initialize(config Config) { b.pendingAccepted.Add(vdrID) } - b.accepted.SetThreshold(config.Alpha) + b.acceptedVotes = make(map[[32]byte]uint64) } // Startup implements the Engine interface. @@ -95,10 +98,29 @@ func (b *Bootstrapper) Accepted(validatorID ids.ShortID, requestID uint32, conta } b.pendingAccepted.Remove(validatorID) - b.accepted.Add(containerIDs.List()...) + weight := uint64(0) + if vdr, ok := b.Validators.Get(validatorID); ok { + weight = vdr.Weight() + } + + for _, containerID := range containerIDs.List() { + key := containerID.Key() + previousWeight := b.acceptedVotes[key] + newWeight, err := math.Add64(weight, previousWeight) + if err != nil { + newWeight = stdmath.MaxUint64 + } + b.acceptedVotes[key] = newWeight + } if b.pendingAccepted.Len() == 0 { - accepted := b.accepted.Threshold() + accepted := ids.Set{} + for key, weight := range b.acceptedVotes { + if weight >= b.Config.Alpha { + accepted.Add(ids.NewID(key)) + } + } + if size := accepted.Len(); size == 0 && b.Config.Beacons.Len() > 0 { b.Context.Log.Warn("Bootstrapping finished with no accepted frontier. This is likely a result of failing to be able to connect to the specified bootstraps, or no transactions have been issued on this network yet") } else { diff --git a/snow/engine/common/config.go b/snow/engine/common/config.go index e3e6b10..e75a957 100644 --- a/snow/engine/common/config.go +++ b/snow/engine/common/config.go @@ -15,7 +15,7 @@ type Config struct { Validators validators.Set Beacons validators.Set - Alpha int + Alpha uint64 Sender Sender Bootstrapable Bootstrapable } diff --git a/snow/engine/snowman/bootstrapper_test.go b/snow/engine/snowman/bootstrapper_test.go index 1b56d0d..6168df2 100644 --- a/snow/engine/snowman/bootstrapper_test.go +++ b/snow/engine/snowman/bootstrapper_test.go @@ -62,7 +62,7 @@ func newConfig(t *testing.T) (BootstrapConfig, ids.ShortID, *common.SenderTest, Context: ctx, Validators: peers, Beacons: peers, - Alpha: peers.Len()/2 + 1, + Alpha: uint64(peers.Len()/2 + 1), Sender: sender, } return BootstrapConfig{ diff --git a/snow/networking/awaiting_connections.go b/snow/networking/awaiting_connections.go index 0b5047d..5887cea 100644 --- a/snow/networking/awaiting_connections.go +++ b/snow/networking/awaiting_connections.go @@ -4,31 +4,43 @@ package networking import ( + stdmath "math" + "github.com/ava-labs/gecko/ids" + "github.com/ava-labs/gecko/snow/validators" + "github.com/ava-labs/gecko/utils/math" ) // AwaitingConnections ... type AwaitingConnections struct { - Requested ids.ShortSet - NumRequired int - Finish func() + Requested validators.Set + WeightRequired uint64 + Finish func() - connected ids.ShortSet + weight uint64 } // Add ... func (aw *AwaitingConnections) Add(conn ids.ShortID) { - if aw.Requested.Contains(conn) { - aw.connected.Add(conn) + vdr, ok := aw.Requested.Get(conn) + if !ok { + return } + weight, err := math.Add64(vdr.Weight(), aw.weight) + if err != nil { + weight = stdmath.MaxUint64 + } + aw.weight = weight } // Remove ... func (aw *AwaitingConnections) Remove(conn ids.ShortID) { - aw.connected.Remove(conn) + vdr, ok := aw.Requested.Get(conn) + if !ok { + return + } + aw.weight -= vdr.Weight() } // Ready ... -func (aw *AwaitingConnections) Ready() bool { - return aw.connected.Len() >= aw.NumRequired -} +func (aw *AwaitingConnections) Ready() bool { return aw.weight >= aw.WeightRequired } diff --git a/snow/validators/set.go b/snow/validators/set.go index 26dd22f..50210bf 100644 --- a/snow/validators/set.go +++ b/snow/validators/set.go @@ -24,6 +24,9 @@ type Set interface { // Add the provided validator to the set. Add(Validator) + // Get the validator from the set. + Get(ids.ShortID) (Validator, bool) + // Remove the validator with the specified ID. Remove(ids.ShortID) @@ -102,6 +105,22 @@ func (s *set) add(vdr Validator) { s.sampler.Weights = append(s.sampler.Weights, w) } +// Get implements the Set interface. +func (s *set) Get(vdrID ids.ShortID) (Validator, bool) { + s.lock.Lock() + defer s.lock.Unlock() + + return s.get(vdrID) +} + +func (s *set) get(vdrID ids.ShortID) (Validator, bool) { + index, ok := s.vdrMap[vdrID.Key()] + if !ok { + return nil, false + } + return s.vdrSlice[index], true +} + // Remove implements the Set interface. func (s *set) Remove(vdrID ids.ShortID) { s.lock.Lock() diff --git a/vms/spchainvm/consensus_benchmark_test.go b/vms/spchainvm/consensus_benchmark_test.go index 08c63ab..721fa44 100644 --- a/vms/spchainvm/consensus_benchmark_test.go +++ b/vms/spchainvm/consensus_benchmark_test.go @@ -86,7 +86,7 @@ func ConsensusLeader(numBlocks, numTxsPerBlock int, b *testing.B) { Context: ctx, Validators: vdrs, Beacons: beacons, - Alpha: (beacons.Len() + 1) / 2, + Alpha: uint64(beacons.Len()/2 + 1), Sender: &sender, }, Blocked: blocked, @@ -217,7 +217,7 @@ func ConsensusFollower(numBlocks, numTxsPerBlock int, b *testing.B) { Context: ctx, Validators: vdrs, Beacons: beacons, - Alpha: (beacons.Len() + 1) / 2, + Alpha: uint64(beacons.Len()/2 + 1), Sender: &sender, }, Blocked: blocked,