From 1dddf3021a0b42a7d18bc83e2b9204778b9d7380 Mon Sep 17 00:00:00 2001 From: Dhruba Basu <7675102+dhrubabasu@users.noreply.github.com> Date: Wed, 29 Nov 2023 07:16:27 -0800 Subject: [PATCH 1/6] Rename `D` to `Durango` (#2389) --- node/node.go | 2 +- version/constants.go | 6 +++--- vms/platformvm/config/config.go | 9 ++++----- vms/platformvm/txs/executor/staker_tx_verification.go | 6 +++--- vms/platformvm/txs/executor/standard_tx_executor.go | 4 ++-- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/node/node.go b/node/node.go index 06544e8f9e6b..7867625fc982 100644 --- a/node/node.go +++ b/node/node.go @@ -1084,7 +1084,7 @@ func (n *Node) initVMs() error { ApricotPhase5Time: version.GetApricotPhase5Time(n.Config.NetworkID), BanffTime: version.GetBanffTime(n.Config.NetworkID), CortinaTime: version.GetCortinaTime(n.Config.NetworkID), - DTime: version.GetDTime(n.Config.NetworkID), + DurangoTime: version.GetDurangoTime(n.Config.NetworkID), UseCurrentHeight: n.Config.UseCurrentHeight, }, }), diff --git a/version/constants.go b/version/constants.go index 5d57933e424b..a9bc3d2a6f91 100644 --- a/version/constants.go +++ b/version/constants.go @@ -99,7 +99,7 @@ var ( CortinaXChainStopVertexID map[uint32]ids.ID // TODO: update this before release - DTimes = map[uint32]time.Time{ + DurangoTimes = map[uint32]time.Time{ constants.MainnetID: time.Date(10000, time.December, 1, 0, 0, 0, 0, time.UTC), constants.FujiID: time.Date(10000, time.December, 1, 0, 0, 0, 0, time.UTC), } @@ -191,8 +191,8 @@ func GetCortinaTime(networkID uint32) time.Time { return DefaultUpgradeTime } -func GetDTime(networkID uint32) time.Time { - if upgradeTime, exists := DTimes[networkID]; exists { +func GetDurangoTime(networkID uint32) time.Time { + if upgradeTime, exists := DurangoTimes[networkID]; exists { return upgradeTime } return DefaultUpgradeTime diff --git a/vms/platformvm/config/config.go b/vms/platformvm/config/config.go index 05068e46a69d..f9504708f78a 100644 --- a/vms/platformvm/config/config.go +++ b/vms/platformvm/config/config.go @@ -104,8 +104,8 @@ type Config struct { // Time of the Cortina network upgrade CortinaTime time.Time - // Time of the D network upgrade - DTime time.Time + // Time of the Durango network upgrade + DurangoTime time.Time // UseCurrentHeight forces [GetMinimumHeight] to return the current height // of the P-Chain instead of the oldest block in the [recentlyAccepted] @@ -133,9 +133,8 @@ func (c *Config) IsCortinaActivated(timestamp time.Time) bool { return !timestamp.Before(c.CortinaTime) } -// TODO: Rename -func (c *Config) IsDActivated(timestamp time.Time) bool { - return !timestamp.Before(c.DTime) +func (c *Config) IsDurangoActivated(timestamp time.Time) bool { + return !timestamp.Before(c.DurangoTime) } func (c *Config) GetCreateBlockchainTxFee(timestamp time.Time) uint64 { diff --git a/vms/platformvm/txs/executor/staker_tx_verification.go b/vms/platformvm/txs/executor/staker_tx_verification.go index 9ec4880e4a44..7fae0e78a85b 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification.go +++ b/vms/platformvm/txs/executor/staker_tx_verification.go @@ -37,7 +37,7 @@ var ( ErrDuplicateValidator = errors.New("duplicate validator") ErrDelegateToPermissionedValidator = errors.New("delegation to permissioned validator") ErrWrongStakedAssetID = errors.New("incorrect staked assetID") - ErrDUpgradeNotActive = errors.New("attempting to use a D-upgrade feature prior to activation") + ErrDurangoUpgradeNotActive = errors.New("attempting to use a Durango-upgrade feature prior to activation") ) // verifySubnetValidatorPrimaryNetworkRequirements verifies the primary @@ -727,8 +727,8 @@ func verifyTransferSubnetOwnershipTx( sTx *txs.Tx, tx *txs.TransferSubnetOwnershipTx, ) error { - if !backend.Config.IsDActivated(chainState.GetTimestamp()) { - return ErrDUpgradeNotActive + if !backend.Config.IsDurangoActivated(chainState.GetTimestamp()) { + return ErrDurangoUpgradeNotActive } // Verify the tx is well-formed diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 22bab59afd3b..63069cb5d5d5 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -516,8 +516,8 @@ func (e *StandardTxExecutor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwn } func (e *StandardTxExecutor) BaseTx(tx *txs.BaseTx) error { - if !e.Backend.Config.IsDActivated(e.State.GetTimestamp()) { - return ErrDUpgradeNotActive + if !e.Backend.Config.IsDurangoActivated(e.State.GetTimestamp()) { + return ErrDurangoUpgradeNotActive } // Verify the tx is well-formed From 56c2ad9a86d8d55feeb2e97210bf2ddc603052d9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 29 Nov 2023 14:35:33 -0500 Subject: [PATCH 2/6] Replace periodic push accepted gossip with pull preference gossip for block discovery (#2367) --- chains/manager.go | 8 +-- config/config.go | 17 +++-- config/flags.go | 2 +- config/keys.go | 4 +- node/config.go | 4 +- node/node.go | 2 +- node/overridden_manager.go | 4 ++ snow/engine/snowman/transitive.go | 97 ++++++++++++++++++++------ snow/engine/snowman/transitive_test.go | 12 ++-- snow/validators/manager.go | 19 +++++ snow/validators/set.go | 23 ++++++ utils/constants/networking.go | 4 +- 12 files changed, 150 insertions(+), 46 deletions(-) diff --git a/chains/manager.go b/chains/manager.go index b4f28b1290be..a1158e67716c 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -205,8 +205,8 @@ type ManagerConfig struct { MeterVMEnabled bool // Should each VM be wrapped with a MeterVM Metrics metrics.MultiGatherer - AcceptedFrontierGossipFrequency time.Duration - ConsensusAppConcurrency int + FrontierPollFrequency time.Duration + ConsensusAppConcurrency int // Max Time to spend fetching a container and its // ancestors when responding to a GetAncestors @@ -824,7 +824,7 @@ func (m *manager) createAvalancheChain( ctx, vdrs, msgChan, - m.AcceptedFrontierGossipFrequency, + m.FrontierPollFrequency, m.ConsensusAppConcurrency, m.ResourceTracker, validators.UnhandledSubnetConnector, // avalanche chains don't use subnet connector @@ -1166,7 +1166,7 @@ func (m *manager) createSnowmanChain( ctx, vdrs, msgChan, - m.AcceptedFrontierGossipFrequency, + m.FrontierPollFrequency, m.ConsensusAppConcurrency, m.ResourceTracker, subnetConnector, diff --git a/config/config.go b/config/config.go index db080ec2514a..d894e832d889 100644 --- a/config/config.go +++ b/config/config.go @@ -60,8 +60,9 @@ const ( subnetConfigFileExt = ".json" ipResolutionTimeout = 30 * time.Second - ipcDeprecationMsg = "IPC API is deprecated" - keystoreDeprecationMsg = "keystore API is deprecated" + ipcDeprecationMsg = "IPC API is deprecated" + keystoreDeprecationMsg = "keystore API is deprecated" + acceptedFrontierGossipDeprecationMsg = "push-based accepted frontier gossip is deprecated" ) var ( @@ -72,6 +73,12 @@ var ( IpcsChainIDsKey: ipcDeprecationMsg, IpcsPathKey: ipcDeprecationMsg, KeystoreAPIEnabledKey: keystoreDeprecationMsg, + ConsensusGossipAcceptedFrontierValidatorSizeKey: acceptedFrontierGossipDeprecationMsg, + ConsensusGossipAcceptedFrontierNonValidatorSizeKey: acceptedFrontierGossipDeprecationMsg, + ConsensusGossipAcceptedFrontierPeerSizeKey: acceptedFrontierGossipDeprecationMsg, + ConsensusGossipOnAcceptValidatorSizeKey: acceptedFrontierGossipDeprecationMsg, + ConsensusGossipOnAcceptNonValidatorSizeKey: acceptedFrontierGossipDeprecationMsg, + ConsensusGossipOnAcceptPeerSizeKey: acceptedFrontierGossipDeprecationMsg, } errSybilProtectionDisabledStakerWeights = errors.New("sybil protection disabled weights must be positive") @@ -1320,9 +1327,9 @@ func GetNodeConfig(v *viper.Viper) (node.Config, error) { } // Gossiping - nodeConfig.AcceptedFrontierGossipFrequency = v.GetDuration(ConsensusAcceptedFrontierGossipFrequencyKey) - if nodeConfig.AcceptedFrontierGossipFrequency < 0 { - return node.Config{}, fmt.Errorf("%s must be >= 0", ConsensusAcceptedFrontierGossipFrequencyKey) + nodeConfig.FrontierPollFrequency = v.GetDuration(ConsensusFrontierPollFrequencyKey) + if nodeConfig.FrontierPollFrequency < 0 { + return node.Config{}, fmt.Errorf("%s must be >= 0", ConsensusFrontierPollFrequencyKey) } // App handling diff --git a/config/flags.go b/config/flags.go index 20f42381320f..6e381e1c6a85 100644 --- a/config/flags.go +++ b/config/flags.go @@ -177,9 +177,9 @@ func addNodeFlags(fs *pflag.FlagSet) { fs.Duration(BenchlistMinFailingDurationKey, constants.DefaultBenchlistMinFailingDuration, "Minimum amount of time messages to a peer must be failing before the peer is benched") // Router - fs.Duration(ConsensusAcceptedFrontierGossipFrequencyKey, constants.DefaultAcceptedFrontierGossipFrequency, "Frequency of gossiping accepted frontiers") fs.Uint(ConsensusAppConcurrencyKey, constants.DefaultConsensusAppConcurrency, "Maximum number of goroutines to use when handling App messages on a chain") fs.Duration(ConsensusShutdownTimeoutKey, constants.DefaultConsensusShutdownTimeout, "Timeout before killing an unresponsive chain") + fs.Duration(ConsensusFrontierPollFrequencyKey, constants.DefaultFrontierPollFrequency, "Frequency of polling for new consensus frontiers") fs.Uint(ConsensusGossipAcceptedFrontierValidatorSizeKey, constants.DefaultConsensusGossipAcceptedFrontierValidatorSize, "Number of validators to gossip to when gossiping accepted frontier") fs.Uint(ConsensusGossipAcceptedFrontierNonValidatorSizeKey, constants.DefaultConsensusGossipAcceptedFrontierNonValidatorSize, "Number of non-validators to gossip to when gossiping accepted frontier") fs.Uint(ConsensusGossipAcceptedFrontierPeerSizeKey, constants.DefaultConsensusGossipAcceptedFrontierPeerSize, "Number of peers to gossip to when gossiping accepted frontier") diff --git a/config/keys.go b/config/keys.go index 1fe19cd2424a..c627abfc1b57 100644 --- a/config/keys.go +++ b/config/keys.go @@ -143,8 +143,9 @@ const ( IpcsChainIDsKey = "ipcs-chain-ids" IpcsPathKey = "ipcs-path" MeterVMsEnabledKey = "meter-vms-enabled" - ConsensusAcceptedFrontierGossipFrequencyKey = "consensus-accepted-frontier-gossip-frequency" ConsensusAppConcurrencyKey = "consensus-app-concurrency" + ConsensusShutdownTimeoutKey = "consensus-shutdown-timeout" + ConsensusFrontierPollFrequencyKey = "consensus-frontier-poll-frequency" ConsensusGossipAcceptedFrontierValidatorSizeKey = "consensus-accepted-frontier-gossip-validator-size" ConsensusGossipAcceptedFrontierNonValidatorSizeKey = "consensus-accepted-frontier-gossip-non-validator-size" ConsensusGossipAcceptedFrontierPeerSizeKey = "consensus-accepted-frontier-gossip-peer-size" @@ -154,7 +155,6 @@ const ( AppGossipValidatorSizeKey = "consensus-app-gossip-validator-size" AppGossipNonValidatorSizeKey = "consensus-app-gossip-non-validator-size" AppGossipPeerSizeKey = "consensus-app-gossip-peer-size" - ConsensusShutdownTimeoutKey = "consensus-shutdown-timeout" ProposerVMUseCurrentHeightKey = "proposervm-use-current-height" FdLimitKey = "fd-limit" IndexEnabledKey = "index-enabled" diff --git a/node/config.go b/node/config.go index 87783bd9935b..5839da75960a 100644 --- a/node/config.go +++ b/node/config.go @@ -181,8 +181,8 @@ type Config struct { ConsensusRouter router.Router `json:"-"` RouterHealthConfig router.HealthConfig `json:"routerHealthConfig"` ConsensusShutdownTimeout time.Duration `json:"consensusShutdownTimeout"` - // Gossip a container in the accepted frontier every [AcceptedFrontierGossipFrequency] - AcceptedFrontierGossipFrequency time.Duration `json:"consensusGossipFreq"` + // Poll for new frontiers every [FrontierPollFrequency] + FrontierPollFrequency time.Duration `json:"consensusGossipFreq"` // ConsensusAppConcurrency defines the maximum number of goroutines to // handle App messages per chain. ConsensusAppConcurrency int `json:"consensusAppConcurrency"` diff --git a/node/node.go b/node/node.go index 7867625fc982..40eb8dc16bef 100644 --- a/node/node.go +++ b/node/node.go @@ -1014,7 +1014,7 @@ func (n *Node) initChainManager(avaxAssetID ids.ID) error { Metrics: n.MetricsGatherer, SubnetConfigs: n.Config.SubnetConfigs, ChainConfigs: n.Config.ChainConfigs, - AcceptedFrontierGossipFrequency: n.Config.AcceptedFrontierGossipFrequency, + FrontierPollFrequency: n.Config.FrontierPollFrequency, ConsensusAppConcurrency: n.Config.ConsensusAppConcurrency, BootstrapMaxTimeGetAncestors: n.Config.BootstrapMaxTimeGetAncestors, BootstrapAncestorsMaxContainersSent: n.Config.BootstrapAncestorsMaxContainersSent, diff --git a/node/overridden_manager.go b/node/overridden_manager.go index 91d8c198a4c3..80295f8636ea 100644 --- a/node/overridden_manager.go +++ b/node/overridden_manager.go @@ -68,6 +68,10 @@ func (o *overriddenManager) Sample(_ ids.ID, size int) ([]ids.NodeID, error) { return o.manager.Sample(o.subnetID, size) } +func (o *overriddenManager) UniformSample(_ ids.ID, size int) ([]ids.NodeID, error) { + return o.manager.UniformSample(o.subnetID, size) +} + func (o *overriddenManager) GetMap(ids.ID) map[ids.NodeID]*validators.GetValidatorOutput { return o.manager.GetMap(o.subnetID) } diff --git a/snow/engine/snowman/transitive.go b/snow/engine/snowman/transitive.go index 7675cff931fe..7f06698cbab0 100644 --- a/snow/engine/snowman/transitive.go +++ b/snow/engine/snowman/transitive.go @@ -34,7 +34,14 @@ import ( "github.com/ava-labs/avalanchego/utils/wrappers" ) -const nonVerifiedCacheSize = 64 * units.MiB +const ( + nonVerifiedCacheSize = 64 * units.MiB + + // putGossipPeriod specifies the number of times Gossip will be called per + // Put gossip. This is done to avoid splitting Gossip into multiple + // functions and to allow more frequent pull gossip than push gossip. + putGossipPeriod = 10 +) var _ Engine = (*Transitive)(nil) @@ -63,6 +70,8 @@ type Transitive struct { requestID uint32 + gossipCounter int + // track outstanding preference requests polls poll.Set @@ -151,6 +160,69 @@ func newTransitive(config Config) (*Transitive, error) { return t, t.metrics.Initialize("", config.Ctx.Registerer) } +func (t *Transitive) Gossip(ctx context.Context) error { + lastAcceptedID, lastAcceptedHeight := t.Consensus.LastAccepted() + if numProcessing := t.Consensus.NumProcessing(); numProcessing == 0 { + t.Ctx.Log.Verbo("sampling from validators", + zap.Stringer("validators", t.Validators), + ) + + // Uniform sampling is used here to reduce bandwidth requirements of + // nodes with a large amount of stake weight. + vdrIDs, err := t.Validators.UniformSample(t.Ctx.SubnetID, 1) + if err != nil { + t.Ctx.Log.Error("skipping block gossip", + zap.String("reason", "no validators"), + zap.Error(err), + ) + return nil + } + + nextHeightToAccept, err := math.Add64(lastAcceptedHeight, 1) + if err != nil { + t.Ctx.Log.Error("skipping block gossip", + zap.String("reason", "block height overflow"), + zap.Stringer("blkID", lastAcceptedID), + zap.Uint64("lastAcceptedHeight", lastAcceptedHeight), + zap.Error(err), + ) + return nil + } + + t.requestID++ + vdrSet := set.Of(vdrIDs...) + preferredID := t.Consensus.Preference() + t.Sender.SendPullQuery(ctx, vdrSet, t.requestID, preferredID, nextHeightToAccept) + } else { + t.Ctx.Log.Debug("skipping block gossip", + zap.String("reason", "blocks currently processing"), + zap.Int("numProcessing", numProcessing), + ) + } + + // TODO: Remove periodic push gossip after v1.11.x is activated + t.gossipCounter++ + t.gossipCounter %= putGossipPeriod + if t.gossipCounter > 0 { + return nil + } + + lastAccepted, err := t.GetBlock(ctx, lastAcceptedID) + if err != nil { + t.Ctx.Log.Warn("dropping gossip request", + zap.String("reason", "block couldn't be loaded"), + zap.Stringer("blkID", lastAcceptedID), + zap.Error(err), + ) + return nil + } + t.Ctx.Log.Verbo("gossiping accepted block to the network", + zap.Stringer("blkID", lastAcceptedID), + ) + t.Sender.SendGossip(ctx, lastAccepted.Bytes()) + return nil +} + func (t *Transitive) Put(ctx context.Context, nodeID ids.NodeID, requestID uint32, blkBytes []byte) error { blk, err := t.VM.ParseBlock(ctx, blkBytes) if err != nil { @@ -383,28 +455,6 @@ func (*Transitive) Timeout(context.Context) error { return nil } -func (t *Transitive) Gossip(ctx context.Context) error { - blkID, err := t.VM.LastAccepted(ctx) - if err != nil { - return err - } - - blk, err := t.GetBlock(ctx, blkID) - if err != nil { - t.Ctx.Log.Warn("dropping gossip request", - zap.String("reason", "block couldn't be loaded"), - zap.Stringer("blkID", blkID), - zap.Error(err), - ) - return nil - } - t.Ctx.Log.Verbo("gossiping accepted block to the network", - zap.Stringer("blkID", blkID), - ) - t.Sender.SendGossip(ctx, blk.Bytes()) - return nil -} - func (*Transitive) Halt(context.Context) {} func (t *Transitive) Shutdown(ctx context.Context) error { @@ -873,6 +923,7 @@ func (t *Transitive) sendQuery( t.Ctx.Log.Error("dropped query for block", zap.String("reason", "insufficient number of validators"), zap.Stringer("blkID", blkID), + zap.Int("size", t.Params.K), ) return } diff --git a/snow/engine/snowman/transitive_test.go b/snow/engine/snowman/transitive_test.go index 69ca9611cc3f..26f6c1127bbf 100644 --- a/snow/engine/snowman/transitive_test.go +++ b/snow/engine/snowman/transitive_test.go @@ -1382,7 +1382,7 @@ func TestEngineUndeclaredDependencyDeadlock(t *testing.T) { func TestEngineGossip(t *testing.T) { require := require.New(t) - _, _, sender, vm, te, gBlk := setupDefaultConfig(t) + nodeID, _, sender, vm, te, gBlk := setupDefaultConfig(t) vm.LastAcceptedF = func(context.Context) (ids.ID, error) { return gBlk.ID(), nil @@ -1392,15 +1392,15 @@ func TestEngineGossip(t *testing.T) { return gBlk, nil } - called := new(bool) - sender.SendGossipF = func(_ context.Context, blkBytes []byte) { - *called = true - require.Equal(gBlk.Bytes(), blkBytes) + var calledSendPullQuery bool + sender.SendPullQueryF = func(_ context.Context, nodeIDs set.Set[ids.NodeID], _ uint32, _ ids.ID, _ uint64) { + calledSendPullQuery = true + require.Equal(set.Of(nodeID), nodeIDs) } require.NoError(te.Gossip(context.Background())) - require.True(*called) + require.True(calledSendPullQuery) } func TestEngineInvalidBlockIgnoredFromUnexpectedPeer(t *testing.T) { diff --git a/snow/validators/manager.go b/snow/validators/manager.go index c42ea779d96b..8cf634f29bd7 100644 --- a/snow/validators/manager.go +++ b/snow/validators/manager.go @@ -85,6 +85,10 @@ type Manager interface { // If sampling the requested size isn't possible, an error will be returned. Sample(subnetID ids.ID, size int) ([]ids.NodeID, error) + // UniformSample returns a collection of validatorIDs in the subnet. + // If sampling the requested size isn't possible, an error will be returned. + UniformSample(subnetID ids.ID, size int) ([]ids.NodeID, error) + // Map of the validators in this subnet GetMap(subnetID ids.ID) map[ids.NodeID]*GetValidatorOutput @@ -253,6 +257,21 @@ func (m *manager) Sample(subnetID ids.ID, size int) ([]ids.NodeID, error) { return set.Sample(size) } +func (m *manager) UniformSample(subnetID ids.ID, size int) ([]ids.NodeID, error) { + if size == 0 { + return nil, nil + } + + m.lock.RLock() + set, exists := m.subnetToVdrs[subnetID] + m.lock.RUnlock() + if !exists { + return nil, ErrMissingValidators + } + + return set.UniformSample(size) +} + func (m *manager) GetMap(subnetID ids.ID) map[ids.NodeID]*GetValidatorOutput { m.lock.RLock() set, exists := m.subnetToVdrs[subnetID] diff --git a/snow/validators/set.go b/snow/validators/set.go index dfa294a70bbe..564cd107153a 100644 --- a/snow/validators/set.go +++ b/snow/validators/set.go @@ -243,6 +243,13 @@ func (s *vdrSet) Sample(size int) ([]ids.NodeID, error) { return s.sample(size) } +func (s *vdrSet) UniformSample(size int) ([]ids.NodeID, error) { + s.lock.RLock() + defer s.lock.RUnlock() + + return s.uniformSample(size) +} + func (s *vdrSet) sample(size int) ([]ids.NodeID, error) { if !s.samplerInitialized { if err := s.sampler.Initialize(s.weights); err != nil { @@ -263,6 +270,22 @@ func (s *vdrSet) sample(size int) ([]ids.NodeID, error) { return list, nil } +func (s *vdrSet) uniformSample(size int) ([]ids.NodeID, error) { + uniform := sampler.NewUniform() + uniform.Initialize(uint64(len(s.vdrSlice))) + + indices, err := uniform.Sample(size) + if err != nil { + return nil, err + } + + list := make([]ids.NodeID, size) + for i, index := range indices { + list[i] = s.vdrSlice[index].NodeID + } + return list, nil +} + func (s *vdrSet) TotalWeight() (uint64, error) { s.lock.RLock() defer s.lock.RUnlock() diff --git a/utils/constants/networking.go b/utils/constants/networking.go index d26f3db1070d..a9417eac37c9 100644 --- a/utils/constants/networking.go +++ b/utils/constants/networking.go @@ -71,12 +71,12 @@ const ( DefaultBenchlistMinFailingDuration = 2*time.Minute + 30*time.Second // Router - DefaultAcceptedFrontierGossipFrequency = 10 * time.Second DefaultConsensusAppConcurrency = 2 DefaultConsensusShutdownTimeout = time.Minute + DefaultFrontierPollFrequency = 100 * time.Millisecond DefaultConsensusGossipAcceptedFrontierValidatorSize = 0 DefaultConsensusGossipAcceptedFrontierNonValidatorSize = 0 - DefaultConsensusGossipAcceptedFrontierPeerSize = 15 + DefaultConsensusGossipAcceptedFrontierPeerSize = 1 DefaultConsensusGossipOnAcceptValidatorSize = 0 DefaultConsensusGossipOnAcceptNonValidatorSize = 0 DefaultConsensusGossipOnAcceptPeerSize = 10 From c5169a333864c4d18bfe70f7075f88059130e069 Mon Sep 17 00:00:00 2001 From: David Boehm <91908103+dboehm-avalabs@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:34:57 -0500 Subject: [PATCH 3/6] MerkleDB Remove ID from Node to reduce size and removal channel creation. (#2324) Co-authored-by: Dan Laine --- x/merkledb/codec.go | 4 +- x/merkledb/codec_test.go | 8 ++-- x/merkledb/db.go | 59 +++++++++++------------ x/merkledb/history_test.go | 4 +- x/merkledb/node.go | 33 +++++++------ x/merkledb/proof.go | 2 +- x/merkledb/trieview.go | 98 +++++++++++++------------------------- 7 files changed, 86 insertions(+), 122 deletions(-) diff --git a/x/merkledb/codec.go b/x/merkledb/codec.go index c9837abb509f..973dd5888ab6 100644 --- a/x/merkledb/codec.go +++ b/x/merkledb/codec.go @@ -160,7 +160,7 @@ func (c *codecImpl) decodeDBNode(b []byte, n *dbNode) error { return io.ErrUnexpectedEOF } - n.children = make(map[byte]child, numChildren) + n.children = make(map[byte]*child, numChildren) var previousChild uint64 for i := uint64(0); i < numChildren; i++ { index, err := c.decodeUint(src) @@ -184,7 +184,7 @@ func (c *codecImpl) decodeDBNode(b []byte, n *dbNode) error { if err != nil { return err } - n.children[byte(index)] = child{ + n.children[byte(index)] = &child{ compressedKey: compressedKey, id: childID, hasValue: hasValue, diff --git a/x/merkledb/codec_test.go b/x/merkledb/codec_test.go index 00e5790b3171..699db9a4bd81 100644 --- a/x/merkledb/codec_test.go +++ b/x/merkledb/codec_test.go @@ -146,7 +146,7 @@ func FuzzCodecDBNodeDeterministic(f *testing.F) { numChildren := r.Intn(int(bf)) // #nosec G404 - children := map[byte]child{} + children := map[byte]*child{} for i := 0; i < numChildren; i++ { var childID ids.ID _, _ = r.Read(childID[:]) // #nosec G404 @@ -154,7 +154,7 @@ func FuzzCodecDBNodeDeterministic(f *testing.F) { childKeyBytes := make([]byte, r.Intn(32)) // #nosec G404 _, _ = r.Read(childKeyBytes) // #nosec G404 - children[byte(i)] = child{ + children[byte(i)] = &child{ compressedKey: ToKey(childKeyBytes), id: childID, } @@ -202,14 +202,14 @@ func FuzzEncodeHashValues(f *testing.F) { for _, bf := range validBranchFactors { // Create a random node r := rand.New(rand.NewSource(int64(randSeed))) // #nosec G404 - children := map[byte]child{} + children := map[byte]*child{} numChildren := r.Intn(int(bf)) // #nosec G404 for i := 0; i < numChildren; i++ { compressedKeyLen := r.Intn(32) // #nosec G404 compressedKeyBytes := make([]byte, compressedKeyLen) _, _ = r.Read(compressedKeyBytes) // #nosec G404 - children[byte(i)] = child{ + children[byte(i)] = &child{ compressedKey: ToKey(compressedKeyBytes), id: ids.GenerateTestID(), hasValue: r.Intn(2) == 1, // #nosec G404 diff --git a/x/merkledb/db.go b/x/merkledb/db.go index b1ee699bab97..c813a9478b1f 100644 --- a/x/merkledb/db.go +++ b/x/merkledb/db.go @@ -205,6 +205,7 @@ type merkleDB struct { // It is the node with a nil key and is the ancestor of all nodes in the trie. // If it has a value or has multiple children, it is also the root of the trie. sentinelNode *node + rootID ids.ID // Valid children of this trie. childViews []*trieView @@ -260,14 +261,13 @@ func newDatabase( tokenSize: BranchFactorToTokenSize[config.BranchFactor], } - root, err := trieDB.initializeRootIfNeeded() - if err != nil { + if err := trieDB.initializeRoot(); err != nil { return nil, err } // add current root to history (has no changes) trieDB.history.record(&changeSummary{ - rootID: root, + rootID: trieDB.rootID, values: map[Key]*change[maybe.Maybe[[]byte]]{}, nodes: map[Key]*change[*node]{}, }) @@ -578,13 +578,7 @@ func (db *merkleDB) GetMerkleRoot(ctx context.Context) (ids.ID, error) { // Assumes [db.lock] is read locked. func (db *merkleDB) getMerkleRoot() ids.ID { - if !isSentinelNodeTheRoot(db.sentinelNode) { - // if the sentinel node should be skipped, the trie's root is the nil key node's only child - for _, childEntry := range db.sentinelNode.children { - return childEntry.id - } - } - return db.sentinelNode.id + return db.rootID } // isSentinelNodeTheRoot returns true if the passed in sentinel node has a value and or multiple child nodes @@ -982,6 +976,7 @@ func (db *merkleDB) commitChanges(ctx context.Context, trieToCommit *trieView) e // Only modify in-memory state after the commit succeeds // so that we don't need to clean up on error. db.sentinelNode = sentinelChange.after + db.rootID = changes.rootID db.history.record(changes) return nil } @@ -1161,34 +1156,38 @@ func (db *merkleDB) invalidateChildrenExcept(exception *trieView) { } } -func (db *merkleDB) initializeRootIfNeeded() (ids.ID, error) { - // not sure if the sentinel node exists or if it had a value - // check under both prefixes +func (db *merkleDB) initializeRoot() error { + // Not sure if the sentinel node exists or if it had a value, + // so check under both prefixes var err error db.sentinelNode, err = db.intermediateNodeDB.Get(Key{}) + if errors.Is(err, database.ErrNotFound) { + // Didn't find the sentinel in the intermediateNodeDB, check the valueNodeDB db.sentinelNode, err = db.valueNodeDB.Get(Key{}) } - if err == nil { - // sentinel node already exists, so calculate the root ID of the trie - db.sentinelNode.calculateID(db.metrics) - return db.getMerkleRoot(), nil - } - if !errors.Is(err, database.ErrNotFound) { - return ids.Empty, err - } - - // sentinel node doesn't exist; make a new one. - db.sentinelNode = newNode(Key{}) - // update its ID - db.sentinelNode.calculateID(db.metrics) + if err != nil { + if !errors.Is(err, database.ErrNotFound) { + return err + } - if err := db.intermediateNodeDB.Put(Key{}, db.sentinelNode); err != nil { - return ids.Empty, err + // Sentinel node doesn't exist in either database prefix. + // Make a new one and store it in the intermediateNodeDB + db.sentinelNode = newNode(Key{}) + if err := db.intermediateNodeDB.Put(Key{}, db.sentinelNode); err != nil { + return err + } } - return db.sentinelNode.id, nil + db.rootID = db.sentinelNode.calculateID(db.metrics) + if !isSentinelNodeTheRoot(db.sentinelNode) { + // If the sentinel node is not the root, the trie's root is the sentinel node's only child + for _, childEntry := range db.sentinelNode.children { + db.rootID = childEntry.id + } + } + return nil } // Returns a view of the trie as it was when it had root [rootID] for keys within range [start, end]. @@ -1289,7 +1288,7 @@ func (db *merkleDB) Clear() error { // Clear root db.sentinelNode = newNode(Key{}) - db.sentinelNode.calculateID(db.metrics) + db.rootID = db.sentinelNode.calculateID(db.metrics) // Clear history db.history = newTrieHistory(db.history.maxHistoryLen) diff --git a/x/merkledb/history_test.go b/x/merkledb/history_test.go index 2ee1e5f4b31b..d2945c9c5018 100644 --- a/x/merkledb/history_test.go +++ b/x/merkledb/history_test.go @@ -660,8 +660,8 @@ func TestHistoryGetChangesToRoot(t *testing.T) { rootID: ids.GenerateTestID(), nodes: map[Key]*change[*node]{ ToKey([]byte{byte(i)}): { - before: &node{id: ids.GenerateTestID()}, - after: &node{id: ids.GenerateTestID()}, + before: &node{}, + after: &node{}, }, }, values: map[Key]*change[maybe.Maybe[[]byte]]{ diff --git a/x/merkledb/node.go b/x/merkledb/node.go index 3fd38021a0c8..9a63ef82c4a7 100644 --- a/x/merkledb/node.go +++ b/x/merkledb/node.go @@ -4,7 +4,6 @@ package merkledb import ( - "golang.org/x/exp/maps" "golang.org/x/exp/slices" "github.com/ava-labs/avalanchego/ids" @@ -17,7 +16,7 @@ const HashLength = 32 // Representation of a node stored in the database. type dbNode struct { value maybe.Maybe[[]byte] - children map[byte]child + children map[byte]*child } type child struct { @@ -29,7 +28,6 @@ type child struct { // node holds additional information on top of the dbNode that makes calculations easier to do type node struct { dbNode - id ids.ID key Key nodeBytes []byte valueDigest maybe.Maybe[[]byte] @@ -39,7 +37,7 @@ type node struct { func newNode(key Key) *node { return &node{ dbNode: dbNode{ - children: make(map[byte]child, 2), + children: make(map[byte]*child, 2), }, key: key, } @@ -78,19 +76,14 @@ func (n *node) bytes() []byte { // clear the cached values that will need to be recalculated whenever the node changes // for example, node ID and byte representation func (n *node) onNodeChanged() { - n.id = ids.Empty n.nodeBytes = nil } // Returns and caches the ID of this node. -func (n *node) calculateID(metrics merkleMetrics) { - if n.id != ids.Empty { - return - } - +func (n *node) calculateID(metrics merkleMetrics) ids.ID { metrics.HashCalculated() bytes := codec.encodeHashValues(n) - n.id = hashing.ComputeHash256Array(bytes) + return hashing.ComputeHash256Array(bytes) } // Set [n]'s value to [val]. @@ -114,16 +107,15 @@ func (n *node) setValueDigest() { func (n *node) addChild(childNode *node, tokenSize int) { n.setChildEntry( childNode.key.Token(n.key.length, tokenSize), - child{ + &child{ compressedKey: childNode.key.Skip(n.key.length + tokenSize), - id: childNode.id, hasValue: childNode.hasValue(), }, ) } // Adds a child to [n] without a reference to the child node. -func (n *node) setChildEntry(index byte, childEntry child) { +func (n *node) setChildEntry(index byte, childEntry *child) { n.onNodeChanged() n.children[index] = childEntry } @@ -139,16 +131,23 @@ func (n *node) removeChild(child *node, tokenSize int) { // if this ever changes, value will need to be copied as well // it is safe to clone all fields because they are only written/read while one or both of the db locks are held func (n *node) clone() *node { - return &node{ - id: n.id, + result := &node{ key: n.key, dbNode: dbNode{ value: n.value, - children: maps.Clone(n.children), + children: make(map[byte]*child, len(n.children)), }, valueDigest: n.valueDigest, nodeBytes: n.nodeBytes, } + for key, existing := range n.children { + result.children[key] = &child{ + compressedKey: existing.compressedKey, + id: existing.id, + hasValue: existing.hasValue, + } + } + return result } // Returns the ProofNode representation of this node. diff --git a/x/merkledb/proof.go b/x/merkledb/proof.go index e348a83f0f13..39ceff3d3157 100644 --- a/x/merkledb/proof.go +++ b/x/merkledb/proof.go @@ -847,7 +847,7 @@ func addPathInfo( // We only need the IDs to be correct so that the calculated hash is correct. n.setChildEntry( index, - child{ + &child{ id: childID, compressedKey: compressedKey, }) diff --git a/x/merkledb/trieview.go b/x/merkledb/trieview.go index 622bfcb11207..730d4b0187d0 100644 --- a/x/merkledb/trieview.go +++ b/x/merkledb/trieview.go @@ -251,9 +251,15 @@ func (t *trieView) calculateNodeIDs(ctx context.Context) error { } _ = t.db.calculateNodeIDsSema.Acquire(context.Background(), 1) - t.calculateNodeIDsHelper(t.sentinelNode) + t.changes.rootID = t.calculateNodeIDsHelper(t.sentinelNode) t.db.calculateNodeIDsSema.Release(1) - t.changes.rootID = t.getMerkleRoot() + + // If the sentinel node is not the root, the trie's root is the sentinel node's only child + if !isSentinelNodeTheRoot(t.sentinelNode) { + for _, childEntry := range t.sentinelNode.children { + t.changes.rootID = childEntry.id + } + } // ensure no ancestor changes occurred during execution if t.isInvalid() { @@ -266,58 +272,40 @@ func (t *trieView) calculateNodeIDs(ctx context.Context) error { // Calculates the ID of all descendants of [n] which need to be recalculated, // and then calculates the ID of [n] itself. -func (t *trieView) calculateNodeIDsHelper(n *node) { - var ( - // We use [wg] to wait until all descendants of [n] have been updated. - wg sync.WaitGroup - updatedChildren = make(chan *node, len(n.children)) - ) +func (t *trieView) calculateNodeIDsHelper(n *node) ids.ID { + // We use [wg] to wait until all descendants of [n] have been updated. + var wg sync.WaitGroup - for childIndex, child := range n.children { - childKey := n.key.Extend(ToToken(childIndex, t.tokenSize), child.compressedKey) + for childIndex := range n.children { + childEntry := n.children[childIndex] + childKey := n.key.Extend(ToToken(childIndex, t.tokenSize), childEntry.compressedKey) childNodeChange, ok := t.changes.nodes[childKey] if !ok { // This child wasn't changed. continue } - - wg.Add(1) - calculateChildID := func() { - defer wg.Done() - - t.calculateNodeIDsHelper(childNodeChange.after) - - // Note that this will never block - updatedChildren <- childNodeChange.after - } + n.onNodeChanged() + childEntry.hasValue = childNodeChange.after.hasValue() // Try updating the child and its descendants in a goroutine. if ok := t.db.calculateNodeIDsSema.TryAcquire(1); ok { + wg.Add(1) go func() { - calculateChildID() + childEntry.id = t.calculateNodeIDsHelper(childNodeChange.after) t.db.calculateNodeIDsSema.Release(1) + wg.Done() }() } else { // We're at the goroutine limit; do the work in this goroutine. - calculateChildID() + childEntry.id = t.calculateNodeIDsHelper(childNodeChange.after) } } // Wait until all descendants of [n] have been updated. wg.Wait() - close(updatedChildren) - - for updatedChild := range updatedChildren { - index := updatedChild.key.Token(n.key.length, t.tokenSize) - n.setChildEntry(index, child{ - compressedKey: n.children[index].compressedKey, - id: updatedChild.id, - hasValue: updatedChild.hasValue(), - }) - } // The IDs [n]'s descendants are up to date so we can calculate [n]'s ID. - n.calculateID(t.db.metrics) + return n.calculateID(t.db.metrics) } // GetProof returns a proof that [bytesPath] is in or not in trie [t]. @@ -381,8 +369,7 @@ func (t *trieView) getProof(ctx context.Context, key []byte) (*Proof, error) { return proof, nil } - childNode, err := t.getNodeWithID( - child.id, + childNode, err := t.getNode( closestNode.key.Extend(ToToken(nextIndex, t.tokenSize), child.compressedKey), child.hasValue, ) @@ -568,17 +555,7 @@ func (t *trieView) GetMerkleRoot(ctx context.Context) (ids.ID, error) { if err := t.calculateNodeIDs(ctx); err != nil { return ids.Empty, err } - return t.getMerkleRoot(), nil -} - -func (t *trieView) getMerkleRoot() ids.ID { - if !isSentinelNodeTheRoot(t.sentinelNode) { - for _, childEntry := range t.sentinelNode.children { - return childEntry.id - } - } - - return t.sentinelNode.id + return t.changes.rootID, nil } func (t *trieView) GetValues(ctx context.Context, keys [][]byte) ([][]byte, []error) { @@ -650,7 +627,7 @@ func (t *trieView) remove(key Key) error { } // confirm a node exists with a value - keyNode, err := t.getNodeWithID(ids.Empty, key, true) + keyNode, err := t.getNode(key, true) if err != nil { if errors.Is(err, database.ErrNotFound) { // key didn't exist @@ -719,7 +696,7 @@ func (t *trieView) compressNodePath(parent, node *node) error { } var ( - childEntry child + childEntry *child childKey Key ) // There is only one child, but we don't know the index. @@ -733,7 +710,7 @@ func (t *trieView) compressNodePath(parent, node *node) error { // [node] is the first node with multiple children. // combine it with the [node] passed in. parent.setChildEntry(childKey.Token(parent.key.length, t.tokenSize), - child{ + &child{ compressedKey: childKey.Skip(parent.key.length + t.tokenSize), id: childEntry.id, hasValue: childEntry.hasValue, @@ -765,7 +742,7 @@ func (t *trieView) visitPathToKey(key Key, visitNode func(*node) error) error { return nil } // grab the next node along the path - currentNode, err = t.getNodeWithID(nextChildEntry.id, key.Take(currentNode.key.length+t.tokenSize+nextChildEntry.compressedKey.length), nextChildEntry.hasValue) + currentNode, err = t.getNode(key.Take(currentNode.key.length+t.tokenSize+nextChildEntry.compressedKey.length), nextChildEntry.hasValue) if err != nil { return err } @@ -784,7 +761,7 @@ func (t *trieView) getEditableNode(key Key, hadValue bool) (*node, error) { } // grab the node in question - n, err := t.getNodeWithID(ids.Empty, key, hadValue) + n, err := t.getNode(key, hadValue) if err != nil { return nil, err } @@ -880,7 +857,7 @@ func (t *trieView) insert( // add the existing child onto the branch node branchNode.setChildEntry( existingChildEntry.compressedKey.Token(commonPrefixLength, t.tokenSize), - child{ + &child{ compressedKey: existingChildEntry.compressedKey.Skip(commonPrefixLength + t.tokenSize), id: existingChildEntry.id, hasValue: existingChildEntry.hasValue, @@ -924,8 +901,7 @@ func (t *trieView) getRoot() (*node, error) { if !isSentinelNodeTheRoot(t.sentinelNode) { // sentinelNode has one child, which is the root for index, childEntry := range t.sentinelNode.children { - return t.getNodeWithID( - childEntry.id, + return t.getNode( t.sentinelNode.key.Extend(ToToken(index, t.tokenSize), childEntry.compressedKey), childEntry.hasValue) } @@ -1004,7 +980,7 @@ func (t *trieView) recordValueChange(key Key, value maybe.Maybe[[]byte]) error { // sets the node's ID to [id]. // If the node is loaded from the baseDB, [hasValue] determines which database the node is stored in. // Returns database.ErrNotFound if the node doesn't exist. -func (t *trieView) getNodeWithID(id ids.ID, key Key, hasValue bool) (*node, error) { +func (t *trieView) getNode(key Key, hasValue bool) (*node, error) { // check for the key within the changed nodes if nodeChange, isChanged := t.changes.nodes[key]; isChanged { t.db.metrics.ViewNodeCacheHit() @@ -1015,17 +991,7 @@ func (t *trieView) getNodeWithID(id ids.ID, key Key, hasValue bool) (*node, erro } // get the node from the parent trie and store a local copy - parentTrieNode, err := t.getParentTrie().getEditableNode(key, hasValue) - if err != nil { - return nil, err - } - - // only need to initialize the id if it's from the parent trie. - // nodes in the current view change list have already been initialized. - if id != ids.Empty { - parentTrieNode.id = id - } - return parentTrieNode, nil + return t.getParentTrie().getEditableNode(key, hasValue) } // Get the parent trie of the view From 0da5bccfb04f98e1e1ac5079acea84056457fcbc Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Wed, 29 Nov 2023 16:32:16 -0500 Subject: [PATCH 4/6] Remove method `CappedList` from `set.Set` (#2395) --- .../avalanche/bootstrap/bootstrapper.go | 3 +- utils/set/set.go | 21 -------------- utils/set/set_test.go | 29 ------------------- 3 files changed, 1 insertion(+), 52 deletions(-) diff --git a/snow/engine/avalanche/bootstrap/bootstrapper.go b/snow/engine/avalanche/bootstrap/bootstrapper.go index 967d65711abc..162937dc7860 100644 --- a/snow/engine/avalanche/bootstrap/bootstrapper.go +++ b/snow/engine/avalanche/bootstrap/bootstrapper.go @@ -401,8 +401,7 @@ func (b *bootstrapper) HealthCheck(ctx context.Context) (interface{}, error) { func (b *bootstrapper) fetch(ctx context.Context, vtxIDs ...ids.ID) error { b.needToFetch.Add(vtxIDs...) for b.needToFetch.Len() > 0 && b.outstandingRequests.Len() < maxOutstandingGetAncestorsRequests { - vtxID := b.needToFetch.CappedList(1)[0] - b.needToFetch.Remove(vtxID) + vtxID, _ := b.needToFetch.Pop() // Length checked in predicate above // Make sure we haven't already requested this vertex if b.outstandingRequests.HasValue(vtxID) { diff --git a/utils/set/set.go b/utils/set/set.go index 29eb8fe11bd4..7ab330fcc066 100644 --- a/utils/set/set.go +++ b/utils/set/set.go @@ -118,27 +118,6 @@ func (s Set[T]) List() []T { return maps.Keys(s) } -// CappedList returns a list of length at most [size]. -// Size should be >= 0. If size < 0, returns nil. -func (s Set[T]) CappedList(size int) []T { - if size < 0 { - return nil - } - if l := s.Len(); l < size { - size = l - } - i := 0 - elts := make([]T, size) - for elt := range s { - if i >= size { - break - } - elts[i] = elt - i++ - } - return elts -} - // Equals returns true if the sets contain the same elements func (s Set[T]) Equals(other Set[T]) bool { return maps.Equal(s, other) diff --git a/utils/set/set_test.go b/utils/set/set_test.go index bcba36944adf..4e0a3d1fa3ed 100644 --- a/utils/set/set_test.go +++ b/utils/set/set_test.go @@ -87,35 +87,6 @@ func TestOf(t *testing.T) { } } -func TestSetCappedList(t *testing.T) { - require := require.New(t) - s := Set[int]{} - - id := 0 - - require.Empty(s.CappedList(0)) - - s.Add(id) - - require.Empty(s.CappedList(0)) - require.Len(s.CappedList(1), 1) - require.Equal(s.CappedList(1)[0], id) - require.Len(s.CappedList(2), 1) - require.Equal(s.CappedList(2)[0], id) - - id2 := 1 - s.Add(id2) - - require.Empty(s.CappedList(0)) - require.Len(s.CappedList(1), 1) - require.Len(s.CappedList(2), 2) - require.Len(s.CappedList(3), 2) - gotList := s.CappedList(2) - require.Contains(gotList, id) - require.Contains(gotList, id2) - require.NotEqual(gotList[0], gotList[1]) -} - func TestSetClear(t *testing.T) { require := require.New(t) From 96d451d2e2986fedc45d3c60834d2aae9c404643 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 29 Nov 2023 18:15:47 -0500 Subject: [PATCH 5/6] Periodically PullGossip only from connected validators (#2399) --- chains/manager.go | 32 ++++++++++++++------------ node/overridden_manager.go | 4 ---- snow/engine/common/tracker/peers.go | 14 +++++++++++ snow/engine/snowman/config.go | 16 +++++++------ snow/engine/snowman/config_test.go | 10 ++++---- snow/engine/snowman/transitive.go | 17 ++++++++------ snow/engine/snowman/transitive_test.go | 4 ++++ snow/validators/manager.go | 19 --------------- snow/validators/set.go | 23 ------------------ utils/set/set.go | 2 +- 10 files changed, 61 insertions(+), 80 deletions(-) diff --git a/chains/manager.go b/chains/manager.go index a1158e67716c..e764a5dfb3ce 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -859,13 +859,14 @@ func (m *manager) createAvalancheChain( // Create engine, bootstrapper and state-syncer in this order, // to make sure start callbacks are duly initialized snowmanEngineConfig := smeng.Config{ - Ctx: ctx, - AllGetsServer: snowGetHandler, - VM: vmWrappingProposerVM, - Sender: snowmanMessageSender, - Validators: vdrs, - Params: consensusParams, - Consensus: snowmanConsensus, + Ctx: ctx, + AllGetsServer: snowGetHandler, + VM: vmWrappingProposerVM, + Sender: snowmanMessageSender, + Validators: vdrs, + ConnectedValidators: connectedValidators, + Params: consensusParams, + Consensus: snowmanConsensus, } snowmanEngine, err := smeng.New(snowmanEngineConfig) if err != nil { @@ -1201,14 +1202,15 @@ func (m *manager) createSnowmanChain( // Create engine, bootstrapper and state-syncer in this order, // to make sure start callbacks are duly initialized engineConfig := smeng.Config{ - Ctx: ctx, - AllGetsServer: snowGetHandler, - VM: vm, - Sender: messageSender, - Validators: vdrs, - Params: consensusParams, - Consensus: consensus, - PartialSync: m.PartialSyncPrimaryNetwork && ctx.ChainID == constants.PlatformChainID, + Ctx: ctx, + AllGetsServer: snowGetHandler, + VM: vm, + Sender: messageSender, + Validators: vdrs, + ConnectedValidators: connectedValidators, + Params: consensusParams, + Consensus: consensus, + PartialSync: m.PartialSyncPrimaryNetwork && ctx.ChainID == constants.PlatformChainID, } engine, err := smeng.New(engineConfig) if err != nil { diff --git a/node/overridden_manager.go b/node/overridden_manager.go index 80295f8636ea..91d8c198a4c3 100644 --- a/node/overridden_manager.go +++ b/node/overridden_manager.go @@ -68,10 +68,6 @@ func (o *overriddenManager) Sample(_ ids.ID, size int) ([]ids.NodeID, error) { return o.manager.Sample(o.subnetID, size) } -func (o *overriddenManager) UniformSample(_ ids.ID, size int) ([]ids.NodeID, error) { - return o.manager.UniformSample(o.subnetID, size) -} - func (o *overriddenManager) GetMap(ids.ID) map[ids.NodeID]*validators.GetValidatorOutput { return o.manager.GetMap(o.subnetID) } diff --git a/snow/engine/common/tracker/peers.go b/snow/engine/common/tracker/peers.go index ad9592209a5a..94d653a53b1f 100644 --- a/snow/engine/common/tracker/peers.go +++ b/snow/engine/common/tracker/peers.go @@ -33,6 +33,9 @@ type Peers interface { ConnectedPercent() float64 // TotalWeight returns the total validator weight TotalWeight() uint64 + // SampleValidator returns a randomly selected connected validator. If there + // are no currently connected validators then it will return false. + SampleValidator() (ids.NodeID, bool) // PreferredPeers returns the currently connected validators. If there are // no currently connected validators then it will return the currently // connected peers. @@ -108,6 +111,13 @@ func (p *lockedPeers) TotalWeight() uint64 { return p.peers.TotalWeight() } +func (p *lockedPeers) SampleValidator() (ids.NodeID, bool) { + p.lock.RLock() + defer p.lock.RUnlock() + + return p.peers.SampleValidator() +} + func (p *lockedPeers) PreferredPeers() set.Set[ids.NodeID] { p.lock.RLock() defer p.lock.RUnlock() @@ -263,6 +273,10 @@ func (p *peerData) TotalWeight() uint64 { return p.totalWeight } +func (p *peerData) SampleValidator() (ids.NodeID, bool) { + return p.connectedValidators.Peek() +} + func (p *peerData) PreferredPeers() set.Set[ids.NodeID] { if p.connectedValidators.Len() == 0 { connectedPeers := set.NewSet[ids.NodeID](p.connectedPeers.Len()) diff --git a/snow/engine/snowman/config.go b/snow/engine/snowman/config.go index ed63af2f4936..65a24a2ea816 100644 --- a/snow/engine/snowman/config.go +++ b/snow/engine/snowman/config.go @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowball" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/common/tracker" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/validators" ) @@ -16,11 +17,12 @@ import ( type Config struct { common.AllGetsServer - Ctx *snow.ConsensusContext - VM block.ChainVM - Sender common.Sender - Validators validators.Manager - Params snowball.Parameters - Consensus snowman.Consensus - PartialSync bool + Ctx *snow.ConsensusContext + VM block.ChainVM + Sender common.Sender + Validators validators.Manager + ConnectedValidators tracker.Peers + Params snowball.Parameters + Consensus snowman.Consensus + PartialSync bool } diff --git a/snow/engine/snowman/config_test.go b/snow/engine/snowman/config_test.go index 23fc0fc39fd4..9611990d9d95 100644 --- a/snow/engine/snowman/config_test.go +++ b/snow/engine/snowman/config_test.go @@ -8,16 +8,18 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowball" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/common/tracker" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/validators" ) func DefaultConfig() Config { return Config{ - Ctx: snow.DefaultConsensusContextTest(), - VM: &block.TestVM{}, - Sender: &common.SenderTest{}, - Validators: validators.NewManager(), + Ctx: snow.DefaultConsensusContextTest(), + VM: &block.TestVM{}, + Sender: &common.SenderTest{}, + Validators: validators.NewManager(), + ConnectedValidators: tracker.NewPeers(), Params: snowball.Parameters{ K: 1, AlphaPreference: 1, diff --git a/snow/engine/snowman/transitive.go b/snow/engine/snowman/transitive.go index 7f06698cbab0..4b43dcda0acb 100644 --- a/snow/engine/snowman/transitive.go +++ b/snow/engine/snowman/transitive.go @@ -169,11 +169,10 @@ func (t *Transitive) Gossip(ctx context.Context) error { // Uniform sampling is used here to reduce bandwidth requirements of // nodes with a large amount of stake weight. - vdrIDs, err := t.Validators.UniformSample(t.Ctx.SubnetID, 1) - if err != nil { + vdrID, ok := t.ConnectedValidators.SampleValidator() + if !ok { t.Ctx.Log.Error("skipping block gossip", - zap.String("reason", "no validators"), - zap.Error(err), + zap.String("reason", "no connected validators"), ) return nil } @@ -190,9 +189,13 @@ func (t *Transitive) Gossip(ctx context.Context) error { } t.requestID++ - vdrSet := set.Of(vdrIDs...) - preferredID := t.Consensus.Preference() - t.Sender.SendPullQuery(ctx, vdrSet, t.requestID, preferredID, nextHeightToAccept) + t.Sender.SendPullQuery( + ctx, + set.Of(vdrID), + t.requestID, + t.Consensus.Preference(), + nextHeightToAccept, + ) } else { t.Ctx.Log.Debug("skipping block gossip", zap.String("reason", "blocks currently processing"), diff --git a/snow/engine/snowman/transitive_test.go b/snow/engine/snowman/transitive_test.go index 26f6c1127bbf..738f20440c58 100644 --- a/snow/engine/snowman/transitive_test.go +++ b/snow/engine/snowman/transitive_test.go @@ -22,6 +22,7 @@ import ( "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/version" ) var ( @@ -41,6 +42,9 @@ func setup(t *testing.T, engCfg Config) (ids.NodeID, validators.Manager, *common vdr := ids.GenerateTestNodeID() require.NoError(vals.AddStaker(engCfg.Ctx.SubnetID, vdr, nil, ids.Empty, 1)) + require.NoError(engCfg.ConnectedValidators.Connected(context.Background(), vdr, version.CurrentApp)) + + vals.RegisterCallbackListener(engCfg.Ctx.SubnetID, engCfg.ConnectedValidators) sender := &common.SenderTest{T: t} engCfg.Sender = sender diff --git a/snow/validators/manager.go b/snow/validators/manager.go index 8cf634f29bd7..c42ea779d96b 100644 --- a/snow/validators/manager.go +++ b/snow/validators/manager.go @@ -85,10 +85,6 @@ type Manager interface { // If sampling the requested size isn't possible, an error will be returned. Sample(subnetID ids.ID, size int) ([]ids.NodeID, error) - // UniformSample returns a collection of validatorIDs in the subnet. - // If sampling the requested size isn't possible, an error will be returned. - UniformSample(subnetID ids.ID, size int) ([]ids.NodeID, error) - // Map of the validators in this subnet GetMap(subnetID ids.ID) map[ids.NodeID]*GetValidatorOutput @@ -257,21 +253,6 @@ func (m *manager) Sample(subnetID ids.ID, size int) ([]ids.NodeID, error) { return set.Sample(size) } -func (m *manager) UniformSample(subnetID ids.ID, size int) ([]ids.NodeID, error) { - if size == 0 { - return nil, nil - } - - m.lock.RLock() - set, exists := m.subnetToVdrs[subnetID] - m.lock.RUnlock() - if !exists { - return nil, ErrMissingValidators - } - - return set.UniformSample(size) -} - func (m *manager) GetMap(subnetID ids.ID) map[ids.NodeID]*GetValidatorOutput { m.lock.RLock() set, exists := m.subnetToVdrs[subnetID] diff --git a/snow/validators/set.go b/snow/validators/set.go index 564cd107153a..dfa294a70bbe 100644 --- a/snow/validators/set.go +++ b/snow/validators/set.go @@ -243,13 +243,6 @@ func (s *vdrSet) Sample(size int) ([]ids.NodeID, error) { return s.sample(size) } -func (s *vdrSet) UniformSample(size int) ([]ids.NodeID, error) { - s.lock.RLock() - defer s.lock.RUnlock() - - return s.uniformSample(size) -} - func (s *vdrSet) sample(size int) ([]ids.NodeID, error) { if !s.samplerInitialized { if err := s.sampler.Initialize(s.weights); err != nil { @@ -270,22 +263,6 @@ func (s *vdrSet) sample(size int) ([]ids.NodeID, error) { return list, nil } -func (s *vdrSet) uniformSample(size int) ([]ids.NodeID, error) { - uniform := sampler.NewUniform() - uniform.Initialize(uint64(len(s.vdrSlice))) - - indices, err := uniform.Sample(size) - if err != nil { - return nil, err - } - - list := make([]ids.NodeID, size) - for i, index := range indices { - list[i] = s.vdrSlice[index].NodeID - } - return list, nil -} - func (s *vdrSet) TotalWeight() (uint64, error) { s.lock.RLock() defer s.lock.RUnlock() diff --git a/utils/set/set.go b/utils/set/set.go index 7ab330fcc066..fd6525b6b127 100644 --- a/utils/set/set.go +++ b/utils/set/set.go @@ -184,7 +184,7 @@ func (s Set[_]) MarshalJSON() ([]byte, error) { return jsonBuf.Bytes(), errs.Err } -// Returns an element. If the set is empty, returns false +// Returns a random element. If the set is empty, returns false func (s *Set[T]) Peek() (T, bool) { for elt := range *s { return elt, true From 907b34c5aa0cf0037c765183101bfcf0f2850f5b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 29 Nov 2023 21:32:43 -0500 Subject: [PATCH 6/6] Update bootstrap IPs (#2396) --- genesis/bootstrappers.json | 48 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/genesis/bootstrappers.json b/genesis/bootstrappers.json index 62784fc7b4da..341828bc7c23 100644 --- a/genesis/bootstrappers.json +++ b/genesis/bootstrappers.json @@ -2,99 +2,99 @@ "mainnet": [ { "id": "NodeID-A6onFGyJjA37EZ7kYHANMR1PFRT8NmXrF", - "ip": "54.94.43.49:9651" + "ip": "54.232.137.108:9651" }, { "id": "NodeID-6SwnPJLH8cWfrJ162JjZekbmzaFpjPcf", - "ip": "52.79.47.77:9651" + "ip": "13.124.187.98:9651" }, { "id": "NodeID-GSgaA47umS1px2ohVjodW9621Ks63xDxD", - "ip": "18.229.206.191:9651" + "ip": "54.232.142.167:9651" }, { "id": "NodeID-BQEo5Fy1FRKLbX51ejqDd14cuSXJKArH2", - "ip": "3.34.221.73:9651" + "ip": "3.39.67.183:9651" }, { "id": "NodeID-Drv1Qh7iJvW3zGBBeRnYfCzk56VCRM2GQ", - "ip": "13.244.155.170:9651" + "ip": "13.245.185.253:9651" }, { "id": "NodeID-DAtCoXfLT6Y83dgJ7FmQg8eR53hz37J79", - "ip": "13.244.47.224:9651" + "ip": "13.246.169.11:9651" }, { "id": "NodeID-FGRoKnyYKFWYFMb6Xbocf4hKuyCBENgWM", - "ip": "122.248.200.212:9651" + "ip": "13.251.82.39:9651" }, { "id": "NodeID-Dw7tuwxpAmcpvVGp9JzaHAR3REPoJ8f2R", - "ip": "52.30.9.211:9651" + "ip": "34.250.50.224:9651" }, { "id": "NodeID-4kCLS16Wy73nt1Zm54jFZsL7Msrv3UCeJ", - "ip": "122.248.199.127:9651" + "ip": "18.142.247.237:9651" }, { "id": "NodeID-9T7NXBFpp8LWCyc58YdKNoowDipdVKAWz", - "ip": "18.202.190.40:9651" + "ip": "34.252.106.116:9651" }, { "id": "NodeID-6ghBh6yof5ouMCya2n9fHzhpWouiZFVVj", - "ip": "15.206.182.45:9651" + "ip": "43.205.156.229:9651" }, { "id": "NodeID-HiFv1DpKXkAAfJ1NHWVqQoojjznibZXHP", - "ip": "15.207.11.193:9651" + "ip": "13.233.176.118:9651" }, { "id": "NodeID-Fv3t2shrpkmvLnvNzcv1rqRKbDAYFnUor", - "ip": "44.226.118.72:9651" + "ip": "35.164.160.193:9651" }, { "id": "NodeID-AaxT2P4uuPAHb7vAD8mNvjQ3jgyaV7tu9", - "ip": "54.185.87.50:9651" + "ip": "54.185.77.104:9651" }, { "id": "NodeID-kZNuQMHhydefgnwjYX1fhHMpRNAs9my1", - "ip": "18.158.15.12:9651" + "ip": "3.74.3.14:9651" }, { "id": "NodeID-A7GwTSd47AcDVqpTVj7YtxtjHREM33EJw", - "ip": "3.21.38.33:9651" + "ip": "3.135.107.20:9651" }, { "id": "NodeID-Hr78Fy8uDYiRYocRYHXp4eLCYeb8x5UuM", - "ip": "54.93.182.129:9651" + "ip": "3.77.28.168:9651" }, { "id": "NodeID-9CkG9MBNavnw7EVSRsuFr7ws9gascDQy3", - "ip": "3.128.138.36:9651" + "ip": "18.216.88.69:9651" }, { "id": "NodeID-A8jypu63CWp76STwKdqP6e9hjL675kdiG", - "ip": "3.104.107.241:9651" + "ip": "3.24.26.175:9651" }, { "id": "NodeID-HsBEx3L71EHWSXaE6gvk2VsNntFEZsxqc", - "ip": "3.106.25.139:9651" + "ip": "52.64.55.185:9651" }, { "id": "NodeID-Nr584bLpGgbCUbZFSBaBz3Xum5wpca9Ym", - "ip": "18.162.129.129:9651" + "ip": "16.162.27.145:9651" }, { "id": "NodeID-QKGoUvqcgormCoMj6yPw9isY7DX9H4mdd", - "ip": "18.162.161.230:9651" + "ip": "18.163.169.191:9651" }, { "id": "NodeID-HCw7S2TVbFPDWNBo1GnFWqJ47f9rDJtt1", - "ip": "52.47.181.114:9651" + "ip": "13.39.184.151:9651" }, { "id": "NodeID-FYv1Lb29SqMpywYXH7yNkcFAzRF2jvm3K", - "ip": "15.188.9.42:9651" + "ip": "13.36.28.133:9651" } ], "fuji": [