From 3f52bbcae72cabc43572b658f35c7ec5b274546c Mon Sep 17 00:00:00 2001 From: Dylan Tinianov Date: Fri, 4 Oct 2024 12:12:34 -0400 Subject: [PATCH] MultiNode Integration: TOML Configurations (#844) * MultiNode integration setup * Update MultiNode files * Add MultiNode flag * Remove internal dependency * Fix build * Fix import cycle * tidy * Update client_test.go * lint * Fix duplicate metrics * Add chain multinode flag * Extend client * Add defaults * Address comments * lint * Fix lint overflow issues * Update transaction_sender.go * Fix lint * Validate node config * Update toml.go * Add SendOnly nodes * Use pointers on config * Use test context * Use configured selection mode * Set defaults * lint * Add nil check * Update multinode.go * Add comments * Update multinode.go * Wrap multinode config * Fix imports * Update .golangci.yml * Use MultiNode config --- pkg/solana/chain.go | 14 +-- pkg/solana/config/multinode.go | 223 +++++++++++++++++++++++++-------- pkg/solana/config/toml.go | 9 +- 3 files changed, 181 insertions(+), 65 deletions(-) diff --git a/pkg/solana/chain.go b/pkg/solana/chain.go index 85f71ee92..95edd5cb4 100644 --- a/pkg/solana/chain.go +++ b/pkg/solana/chain.go @@ -230,10 +230,10 @@ func newChain(id string, cfg *config.TOMLConfig, ks loop.Keystore, lggr logger.L clientCache: map[string]*verifiedCachedClient{}, } - if cfg.MultiNodeEnabled() { + if cfg.MultiNode.Enabled() { chainFamily := "solana" - mnCfg := cfg.MultiNodeConfig() + mnCfg := &cfg.MultiNode var nodes []mn.Node[mn.StringID, *client.Client] var sendOnlyNodes []mn.SendOnlyNode[mn.StringID, *client.Client] @@ -258,8 +258,8 @@ func newChain(id string, cfg *config.TOMLConfig, ks loop.Keystore, lggr logger.L multiNode := mn.NewMultiNode[mn.StringID, *client.Client]( lggr, - mn.NodeSelectionModeRoundRobin, - 0, + mnCfg.SelectionMode(), + mnCfg.LeaseDuration(), nodes, sendOnlyNodes, mn.StringID(id), @@ -398,7 +398,7 @@ func (c *chain) ChainID() string { // getClient returns a client, randomly selecting one from available and valid nodes func (c *chain) getClient() (client.ReaderWriter, error) { - if c.cfg.MultiNodeEnabled() { + if c.cfg.MultiNode.Enabled() { return c.multiNode.SelectRPC() } @@ -482,7 +482,7 @@ func (c *chain) Start(ctx context.Context) error { c.lggr.Debug("Starting balance monitor") var ms services.MultiStart startAll := []services.StartClose{c.txm, c.balanceMonitor} - if c.cfg.MultiNodeEnabled() { + if c.cfg.MultiNode.Enabled() { c.lggr.Debug("Starting multinode") startAll = append(startAll, c.multiNode, c.txSender) } @@ -496,7 +496,7 @@ func (c *chain) Close() error { c.lggr.Debug("Stopping txm") c.lggr.Debug("Stopping balance monitor") closeAll := []io.Closer{c.txm, c.balanceMonitor} - if c.cfg.MultiNodeEnabled() { + if c.cfg.MultiNode.Enabled() { c.lggr.Debug("Stopping multinode") closeAll = append(closeAll, c.multiNode, c.txSender) } diff --git a/pkg/solana/config/multinode.go b/pkg/solana/config/multinode.go index 1755e6ee6..0c49d8b22 100644 --- a/pkg/solana/config/multinode.go +++ b/pkg/solana/config/multinode.go @@ -1,87 +1,206 @@ package config -import "time" +import ( + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + + mn "github.com/smartcontractkit/chainlink-solana/pkg/solana/client/multinode" +) + +// MultiNodeConfig is a wrapper to provide required functions while keeping configs Public +type MultiNodeConfig struct { + MultiNode +} type MultiNode struct { - // TODO: Determine current config overlap https://smartcontract-it.atlassian.net/browse/BCI-4065 // Feature flag - multiNodeEnabled bool + Enabled *bool // Node Configs - pollFailureThreshold uint32 - pollInterval time.Duration - selectionMode string - syncThreshold uint32 - nodeIsSyncingEnabled bool - finalizedBlockPollInterval time.Duration - enforceRepeatableRead bool - deathDeclarationDelay time.Duration + PollFailureThreshold *uint32 + PollInterval *config.Duration + SelectionMode *string + SyncThreshold *uint32 + NodeIsSyncingEnabled *bool + LeaseDuration *config.Duration + FinalizedBlockPollInterval *config.Duration + EnforceRepeatableRead *bool + DeathDeclarationDelay *config.Duration // Chain Configs - nodeNoNewHeadsThreshold time.Duration - noNewFinalizedHeadsThreshold time.Duration - finalityDepth uint32 - finalityTagEnabled bool - finalizedBlockOffset uint32 + NodeNoNewHeadsThreshold *config.Duration + NoNewFinalizedHeadsThreshold *config.Duration + FinalityDepth *uint32 + FinalityTagEnabled *bool + FinalizedBlockOffset *uint32 } -func (c *MultiNode) MultiNodeEnabled() bool { - return c.multiNodeEnabled +func (c *MultiNodeConfig) Enabled() bool { + return c.MultiNode.Enabled != nil && *c.MultiNode.Enabled } -func (c *MultiNode) PollFailureThreshold() uint32 { - return c.pollFailureThreshold +func (c *MultiNodeConfig) PollFailureThreshold() uint32 { + return *c.MultiNode.PollFailureThreshold } -func (c *MultiNode) PollInterval() time.Duration { - return c.pollInterval +func (c *MultiNodeConfig) PollInterval() time.Duration { + return c.MultiNode.PollInterval.Duration() } -func (c *MultiNode) SelectionMode() string { - return c.selectionMode -} +func (c *MultiNodeConfig) SelectionMode() string { + return *c.MultiNode.SelectionMode +} -func (c *MultiNode) SyncThreshold() uint32 { - return c.syncThreshold +func (c *MultiNodeConfig) SyncThreshold() uint32 { + return *c.MultiNode.SyncThreshold } -func (c *MultiNode) NodeIsSyncingEnabled() bool { - return c.nodeIsSyncingEnabled +func (c *MultiNodeConfig) NodeIsSyncingEnabled() bool { + return *c.MultiNode.NodeIsSyncingEnabled } -func (c *MultiNode) FinalizedBlockPollInterval() time.Duration { - return c.finalizedBlockPollInterval -} +func (c *MultiNodeConfig) LeaseDuration() time.Duration { return c.MultiNode.LeaseDuration.Duration() } -func (c *MultiNode) EnforceRepeatableRead() bool { - return c.enforceRepeatableRead +func (c *MultiNodeConfig) FinalizedBlockPollInterval() time.Duration { + return c.MultiNode.FinalizedBlockPollInterval.Duration() } -func (c *MultiNode) DeathDeclarationDelay() time.Duration { - return c.deathDeclarationDelay -} +func (c *MultiNodeConfig) EnforceRepeatableRead() bool { return *c.MultiNode.EnforceRepeatableRead } -func (c *MultiNode) NodeNoNewHeadsThreshold() time.Duration { - return c.nodeNoNewHeadsThreshold +func (c *MultiNodeConfig) DeathDeclarationDelay() time.Duration { + return c.MultiNode.DeathDeclarationDelay.Duration() } -func (c *MultiNode) NoNewFinalizedHeadsThreshold() time.Duration { - return c.noNewFinalizedHeadsThreshold +func (c *MultiNodeConfig) NodeNoNewHeadsThreshold() time.Duration { + return c.MultiNode.NodeNoNewHeadsThreshold.Duration() } -func (c *MultiNode) FinalityDepth() uint32 { - return c.finalityDepth +func (c *MultiNodeConfig) NoNewFinalizedHeadsThreshold() time.Duration { + return c.MultiNode.NoNewFinalizedHeadsThreshold.Duration() } -func (c *MultiNode) FinalityTagEnabled() bool { - return c.finalityTagEnabled -} +func (c *MultiNodeConfig) FinalityDepth() uint32 { return *c.MultiNode.FinalityDepth } -func (c *MultiNode) FinalizedBlockOffset() uint32 { - return c.finalizedBlockOffset -} +func (c *MultiNodeConfig) FinalityTagEnabled() bool { return *c.MultiNode.FinalityTagEnabled } + +func (c *MultiNodeConfig) FinalizedBlockOffset() uint32 { return *c.MultiNode.FinalizedBlockOffset } + +func (c *MultiNodeConfig) SetDefaults() { + // MultiNode is disabled as it's not fully implemented yet: BCFR-122 + if c.MultiNode.Enabled == nil { + c.MultiNode.Enabled = ptr(false) + } + + /* Node Configs */ + // Failure threshold for polling set to 5 to tolerate some polling failures before taking action. + if c.MultiNode.PollFailureThreshold == nil { + c.MultiNode.PollFailureThreshold = ptr(uint32(5)) + } + // Poll interval is set to 10 seconds to ensure timely updates while minimizing resource usage. + if c.MultiNode.PollInterval == nil { + c.MultiNode.PollInterval = config.MustNewDuration(10 * time.Second) + } + // Selection mode defaults to priority level to enable using node priorities + if c.MultiNode.SelectionMode == nil { + c.MultiNode.SelectionMode = ptr(mn.NodeSelectionModePriorityLevel) + } + // The sync threshold is set to 5 to allow for some flexibility in node synchronization before considering it out of sync. + if c.MultiNode.SyncThreshold == nil { + c.MultiNode.SyncThreshold = ptr(uint32(5)) + } + // Lease duration is set to 1 minute by default to allow node locks for a reasonable amount of time. + if c.MultiNode.LeaseDuration == nil { + c.MultiNode.LeaseDuration = config.MustNewDuration(time.Minute) + } + // Node syncing is not relevant for Solana and is disabled by default. + if c.MultiNode.NodeIsSyncingEnabled == nil { + c.MultiNode.NodeIsSyncingEnabled = ptr(false) + } + // The finalized block polling interval is set to 5 seconds to ensure timely updates while minimizing resource usage. + if c.MultiNode.FinalizedBlockPollInterval == nil { + c.MultiNode.FinalizedBlockPollInterval = config.MustNewDuration(5 * time.Second) + } + // Repeatable read guarantee should be enforced by default. + if c.MultiNode.EnforceRepeatableRead == nil { + c.MultiNode.EnforceRepeatableRead = ptr(true) + } + // The delay before declaring a node dead is set to 10 seconds to give nodes time to recover from temporary issues. + if c.MultiNode.DeathDeclarationDelay == nil { + c.MultiNode.DeathDeclarationDelay = config.MustNewDuration(10 * time.Second) + } -func (c *MultiNode) SetDefaults() { - // TODO: Set defaults for MultiNode config https://smartcontract-it.atlassian.net/browse/BCI-4065 - c.multiNodeEnabled = false + /* Chain Configs */ + // Threshold for no new heads is set to 10 seconds, assuming that heads should update at a reasonable pace. + if c.MultiNode.NodeNoNewHeadsThreshold == nil { + c.MultiNode.NodeNoNewHeadsThreshold = config.MustNewDuration(10 * time.Second) + } + // Similar to heads, finalized heads should be updated within 10 seconds. + if c.MultiNode.NoNewFinalizedHeadsThreshold == nil { + c.MultiNode.NoNewFinalizedHeadsThreshold = config.MustNewDuration(10 * time.Second) + } + // Finality tags are used in Solana and enabled by default. + if c.MultiNode.FinalityTagEnabled == nil { + c.MultiNode.FinalityTagEnabled = ptr(true) + } + // Finality depth will not be used since finality tags are enabled. + if c.MultiNode.FinalityDepth == nil { + c.MultiNode.FinalityDepth = ptr(uint32(0)) + } + // Finalized block offset will not be used since finality tags are enabled. + if c.MultiNode.FinalizedBlockOffset == nil { + c.MultiNode.FinalizedBlockOffset = ptr(uint32(0)) + } +} + +func (c *MultiNodeConfig) SetFrom(f *MultiNodeConfig) { + if f.MultiNode.Enabled != nil { + c.MultiNode.Enabled = f.MultiNode.Enabled + } + + // Node Configs + if f.MultiNode.PollFailureThreshold != nil { + c.MultiNode.PollFailureThreshold = f.MultiNode.PollFailureThreshold + } + if f.MultiNode.PollInterval != nil { + c.MultiNode.PollInterval = f.MultiNode.PollInterval + } + if f.MultiNode.SelectionMode != nil { + c.MultiNode.SelectionMode = f.MultiNode.SelectionMode + } + if f.MultiNode.SyncThreshold != nil { + c.MultiNode.SyncThreshold = f.MultiNode.SyncThreshold + } + if f.MultiNode.NodeIsSyncingEnabled != nil { + c.MultiNode.NodeIsSyncingEnabled = f.MultiNode.NodeIsSyncingEnabled + } + if f.MultiNode.LeaseDuration != nil { + c.MultiNode.LeaseDuration = f.MultiNode.LeaseDuration + } + if f.MultiNode.FinalizedBlockPollInterval != nil { + c.MultiNode.FinalizedBlockPollInterval = f.MultiNode.FinalizedBlockPollInterval + } + if f.MultiNode.EnforceRepeatableRead != nil { + c.MultiNode.EnforceRepeatableRead = f.MultiNode.EnforceRepeatableRead + } + if f.MultiNode.DeathDeclarationDelay != nil { + c.MultiNode.DeathDeclarationDelay = f.MultiNode.DeathDeclarationDelay + } + + // Chain Configs + if f.MultiNode.NodeNoNewHeadsThreshold != nil { + c.MultiNode.NodeNoNewHeadsThreshold = f.MultiNode.NodeNoNewHeadsThreshold + } + if f.MultiNode.NoNewFinalizedHeadsThreshold != nil { + c.MultiNode.NoNewFinalizedHeadsThreshold = f.MultiNode.NoNewFinalizedHeadsThreshold + } + if f.MultiNode.FinalityDepth != nil { + c.MultiNode.FinalityDepth = f.MultiNode.FinalityDepth + } + if f.MultiNode.FinalityTagEnabled != nil { + c.MultiNode.FinalityTagEnabled = f.MultiNode.FinalityTagEnabled + } + if f.MultiNode.FinalizedBlockOffset != nil { + c.MultiNode.FinalizedBlockOffset = f.MultiNode.FinalizedBlockOffset + } } diff --git a/pkg/solana/config/toml.go b/pkg/solana/config/toml.go index 056fcc210..e03f3d849 100644 --- a/pkg/solana/config/toml.go +++ b/pkg/solana/config/toml.go @@ -113,8 +113,8 @@ type TOMLConfig struct { // Do not access directly, use [IsEnabled] Enabled *bool Chain - MultiNode - Nodes Nodes + MultiNode MultiNodeConfig + Nodes Nodes } func (c *TOMLConfig) IsEnabled() bool { @@ -130,6 +130,7 @@ func (c *TOMLConfig) SetFrom(f *TOMLConfig) { } setFromChain(&c.Chain, &f.Chain) c.Nodes.SetFrom(&f.Nodes) + c.MultiNode.SetFrom(&f.MultiNode) } func setFromChain(c, f *Chain) { @@ -285,10 +286,6 @@ func (c *TOMLConfig) ListNodes() Nodes { return c.Nodes } -func (c *TOMLConfig) MultiNodeConfig() *MultiNode { - return &c.MultiNode -} - func (c *TOMLConfig) SetDefaults() { c.Chain.SetDefaults() c.MultiNode.SetDefaults()