diff --git a/relayer/application_relayer.go b/relayer/application_relayer.go index c1c93910..a0aff4a7 100644 --- a/relayer/application_relayer.go +++ b/relayer/application_relayer.go @@ -62,7 +62,7 @@ type ApplicationRelayer struct { signingSubnetID ids.ID destinationClient vms.DestinationClient relayerID database.RelayerID - warpQuorum config.WarpQuorum + warpConfig config.WarpConfig checkpointManager CheckpointManager sourceWarpSignatureClient *rpc.Client // nil if configured to fetch signatures via AppRequest for the source blockchain signatureAggregator *aggregator.SignatureAggregator @@ -79,19 +79,20 @@ func NewApplicationRelayer( cfg *config.Config, signatureAggregator *aggregator.SignatureAggregator, ) (*ApplicationRelayer, error) { - quorum, err := cfg.GetWarpQuorum(relayerID.DestinationBlockchainID) + warpConfig, err := cfg.GetWarpConfig(relayerID.DestinationBlockchainID) if err != nil { logger.Error( - "Failed to get warp quorum from config. Relayer may not be configured to deliver to the destination chain.", + "Failed to get warp config. Relayer may not be configured to deliver to the destination chain.", zap.String("destinationBlockchainID", relayerID.DestinationBlockchainID.String()), zap.Error(err), ) return nil, err } + var signingSubnet ids.ID - if sourceBlockchain.GetSubnetID() == constants.PrimaryNetworkID { - // If the message originates from the primary subnet, then we instead "self sign" - // the message using the validators of the destination subnet. + if sourceBlockchain.GetSubnetID() == constants.PrimaryNetworkID && !warpConfig.RequirePrimaryNetworkSigners { + // If the message originates from the primary network, and the primary network is validated by + // the destination subnet we can "self-sign" the message using the validators of the destination subnet. signingSubnet = cfg.GetSubnetID(relayerID.DestinationBlockchainID) } else { // Otherwise, the source subnet signs the message. @@ -128,7 +129,7 @@ func NewApplicationRelayer( destinationClient: destinationClient, relayerID: relayerID, signingSubnetID: signingSubnet, - warpQuorum: quorum, + warpConfig: warpConfig, checkpointManager: checkpointManager, sourceWarpSignatureClient: warpClient, signatureAggregator: signatureAggregator, @@ -206,7 +207,7 @@ func (r *ApplicationRelayer) ProcessMessage(handler messages.MessageHandler) (co unsignedMessage, nil, r.signingSubnetID, - r.warpQuorum.QuorumNumerator, + r.warpConfig.QuorumNumerator, ) r.incFetchSignatureAppRequestCount() if err != nil { @@ -282,7 +283,7 @@ func (r *ApplicationRelayer) createSignedMessage( &signedWarpMessageBytes, "warp_getMessageAggregateSignature", unsignedMessage.ID(), - r.warpQuorum.QuorumNumerator, + r.warpConfig.QuorumNumerator, r.signingSubnetID.String(), ) if err == nil { diff --git a/relayer/config/config.go b/relayer/config/config.go index 3f643dd2..747ceec3 100644 --- a/relayer/config/config.go +++ b/relayer/config/config.go @@ -14,7 +14,6 @@ import ( "github.com/ava-labs/awm-relayer/peers" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" @@ -50,8 +49,6 @@ awm-relayer --version Display awm-relayer vers awm-relayer --help Display awm-relayer usage and exit. ` -var errFailedToGetWarpQuorum = errors.New("failed to get warp quorum") - // Top-level configuration type Config struct { LogLevel string `mapstructure:"log-level" json:"log-level"` @@ -156,31 +153,24 @@ func (c *Config) GetSubnetID(blockchainID ids.ID) ids.ID { } // If the numerator in the Warp config is 0, use the default value -func calculateQuorumNumerator(cfgNumerator uint64) uint64 { - if cfgNumerator == 0 { - return warp.WarpDefaultQuorumNumerator +func warpConfigFromSubnetWarpConfig(inputConfig warp.Config) WarpConfig { + if inputConfig.QuorumNumerator == 0 { + return WarpConfig{ + QuorumNumerator: warp.WarpDefaultQuorumNumerator, + RequirePrimaryNetworkSigners: inputConfig.RequirePrimaryNetworkSigners, + } } - return cfgNumerator -} - -// Helper to retrieve the Warp Quorum from the chain config. -// Differentiates between subnet-evm and coreth RPC internally -func getWarpQuorum( - subnetID ids.ID, - blockchainID ids.ID, - client ethclient.Client, -) (WarpQuorum, error) { - if subnetID == constants.PrimaryNetworkID { - return WarpQuorum{ - QuorumNumerator: warp.WarpDefaultQuorumNumerator, - QuorumDenominator: warp.WarpQuorumDenominator, - }, nil + return WarpConfig{ + QuorumNumerator: inputConfig.QuorumNumerator, + RequirePrimaryNetworkSigners: inputConfig.RequirePrimaryNetworkSigners, } +} +func getWarpConfig(client ethclient.Client) (*warp.Config, error) { // Fetch the subnet's chain config chainConfig, err := client.ChainConfig(context.Background()) if err != nil { - return WarpQuorum{}, fmt.Errorf("failed to fetch chain config for blockchain %s: %w", blockchainID, err) + return nil, fmt.Errorf("failed to fetch chain config") } // First, check the list of precompile upgrades to get the most up to date Warp config @@ -202,30 +192,24 @@ func getWarpQuorum( } } if warpConfig != nil { - return WarpQuorum{ - QuorumNumerator: calculateQuorumNumerator(warpConfig.QuorumNumerator), - QuorumDenominator: warp.WarpQuorumDenominator, - }, nil + return warpConfig, nil } - // If we didn't find the Warp config in the upgrade precompile list, check the genesis config warpConfig, ok := chainConfig.GenesisPrecompiles[warpConfigKey].(*warp.Config) - if ok { - return WarpQuorum{ - QuorumNumerator: calculateQuorumNumerator(warpConfig.QuorumNumerator), - QuorumDenominator: warp.WarpQuorumDenominator, - }, nil + if !ok { + return nil, fmt.Errorf("no Warp config found in chain config") } - return WarpQuorum{}, fmt.Errorf("failed to find warp config for blockchain %s", blockchainID) + return warpConfig, nil } -func (c *Config) InitializeWarpQuorums() error { - // Fetch the Warp quorum values for each destination subnet. +// Initializes Warp configurations (quorum and self-signing settings) for each destination subnet +func (c *Config) InitializeWarpConfigs() error { + // Fetch the Warp config values for each destination subnet. for _, destinationSubnet := range c.DestinationBlockchains { - err := destinationSubnet.initializeWarpQuorum() + err := destinationSubnet.initializeWarpConfigs() if err != nil { return fmt.Errorf( - "failed to initialize Warp quorum for destination subnet %s: %w", + "failed to initialize Warp config for destination subnet %s: %w", destinationSubnet.SubnetID, err, ) @@ -247,13 +231,13 @@ func (c *Config) GetOverwrittenOptions() []string { // Top-level config getters // -func (c *Config) GetWarpQuorum(blockchainID ids.ID) (WarpQuorum, error) { +func (c *Config) GetWarpConfig(blockchainID ids.ID) (WarpConfig, error) { for _, s := range c.DestinationBlockchains { if blockchainID.String() == s.BlockchainID { - return s.warpQuorum, nil + return s.warpConfig, nil } } - return WarpQuorum{}, errFailedToGetWarpQuorum + return WarpConfig{}, fmt.Errorf("blockchain %s not configured as a destination", blockchainID) } var _ peers.Config = &Config{} diff --git a/relayer/config/config_test.go b/relayer/config/config_test.go index 06f54248..710f1f67 100644 --- a/relayer/config/config_test.go +++ b/relayer/config/config_test.go @@ -242,7 +242,7 @@ func TestEitherKMSOrAccountPrivateKey(t *testing.T) { } } -func TestGetWarpQuorum(t *testing.T) { +func TestGetWarpConfig(t *testing.T) { blockchainID, err := ids.FromString("p433wpuXyJiDhyazPYyZMJeaoPSW76CBZ2x7wrVPLgvokotXz") require.NoError(t, err) subnetID, err := ids.FromString("2PsShLjrFFwR51DMcAh8pyuwzLn1Ym3zRhuXLTmLCR1STk2mL6") @@ -255,19 +255,8 @@ func TestGetWarpQuorum(t *testing.T) { chainConfig params.ChainConfigWithUpgradesJSON getChainConfigCalls int expectedError error - expectedQuorum WarpQuorum + expectedWarpConfig WarpConfig }{ - { - name: "primary network", - blockchainID: blockchainID, - subnetID: ids.Empty, - getChainConfigCalls: 0, - expectedError: nil, - expectedQuorum: WarpQuorum{ - QuorumNumerator: warp.WarpDefaultQuorumNumerator, - QuorumDenominator: warp.WarpQuorumDenominator, - }, - }, { name: "subnet genesis precompile", blockchainID: blockchainID, @@ -283,9 +272,9 @@ func TestGetWarpQuorum(t *testing.T) { }, }, expectedError: nil, - expectedQuorum: WarpQuorum{ - QuorumNumerator: warp.WarpDefaultQuorumNumerator, - QuorumDenominator: warp.WarpQuorumDenominator, + expectedWarpConfig: WarpConfig{ + QuorumNumerator: warp.WarpDefaultQuorumNumerator, + RequirePrimaryNetworkSigners: false, }, }, { @@ -303,9 +292,9 @@ func TestGetWarpQuorum(t *testing.T) { }, }, expectedError: nil, - expectedQuorum: WarpQuorum{ - QuorumNumerator: 50, - QuorumDenominator: warp.WarpQuorumDenominator, + expectedWarpConfig: WarpConfig{ + QuorumNumerator: 50, + RequirePrimaryNetworkSigners: false, }, }, { @@ -325,9 +314,9 @@ func TestGetWarpQuorum(t *testing.T) { }, }, expectedError: nil, - expectedQuorum: WarpQuorum{ - QuorumNumerator: warp.WarpDefaultQuorumNumerator, - QuorumDenominator: warp.WarpQuorumDenominator, + expectedWarpConfig: WarpConfig{ + QuorumNumerator: warp.WarpDefaultQuorumNumerator, + RequirePrimaryNetworkSigners: false, }, }, { @@ -347,9 +336,51 @@ func TestGetWarpQuorum(t *testing.T) { }, }, expectedError: nil, - expectedQuorum: WarpQuorum{ - QuorumNumerator: 50, - QuorumDenominator: warp.WarpQuorumDenominator, + expectedWarpConfig: WarpConfig{ + QuorumNumerator: 50, + RequirePrimaryNetworkSigners: false, + }, + }, + { + name: "require primary network signers", + blockchainID: blockchainID, + subnetID: subnetID, + getChainConfigCalls: 1, + chainConfig: params.ChainConfigWithUpgradesJSON{ + ChainConfig: params.ChainConfig{ + GenesisPrecompiles: params.Precompiles{ + warpConfigKey: &warp.Config{ + QuorumNumerator: 0, + RequirePrimaryNetworkSigners: true, + }, + }, + }, + }, + expectedError: nil, + expectedWarpConfig: WarpConfig{ + QuorumNumerator: warp.WarpDefaultQuorumNumerator, + RequirePrimaryNetworkSigners: true, + }, + }, + { + name: "require primary network signers explicit false", + blockchainID: blockchainID, + subnetID: subnetID, + getChainConfigCalls: 1, + chainConfig: params.ChainConfigWithUpgradesJSON{ + ChainConfig: params.ChainConfig{ + GenesisPrecompiles: params.Precompiles{ + warpConfigKey: &warp.Config{ + QuorumNumerator: 0, + RequirePrimaryNetworkSigners: false, + }, + }, + }, + }, + expectedError: nil, + expectedWarpConfig: WarpConfig{ + QuorumNumerator: warp.WarpDefaultQuorumNumerator, + RequirePrimaryNetworkSigners: false, }, }, } @@ -364,9 +395,10 @@ func TestGetWarpQuorum(t *testing.T) { ).Times(testCase.getChainConfigCalls), ) - quorum, err := getWarpQuorum(testCase.subnetID, testCase.blockchainID, client) + subnetWarpConfig, err := getWarpConfig(client) require.Equal(t, testCase.expectedError, err) - require.Equal(t, testCase.expectedQuorum, quorum) + expectedWarpConfig := warpConfigFromSubnetWarpConfig(*subnetWarpConfig) + require.Equal(t, testCase.expectedWarpConfig, expectedWarpConfig) }) } } diff --git a/relayer/config/destination_blockchain.go b/relayer/config/destination_blockchain.go index 2e0c9eb1..044439e1 100644 --- a/relayer/config/destination_blockchain.go +++ b/relayer/config/destination_blockchain.go @@ -6,8 +6,10 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" basecfg "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/utils" + "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "github.com/ethereum/go-ethereum/crypto" ) @@ -23,7 +25,7 @@ type DestinationBlockchain struct { AccountPrivateKey string `mapstructure:"account-private-key" json:"account-private-key"` // Fetched from the chain after startup - warpQuorum WarpQuorum + warpConfig WarpConfig // convenience fields to access parsed data after initialization subnetID ids.ID @@ -77,7 +79,7 @@ func (s *DestinationBlockchain) GetBlockchainID() ids.ID { return s.blockchainID } -func (s *DestinationBlockchain) initializeWarpQuorum() error { +func (s *DestinationBlockchain) initializeWarpConfigs() error { blockchainID, err := ids.FromString(s.BlockchainID) if err != nil { return fmt.Errorf("invalid blockchainID in configuration. error: %w", err) @@ -86,6 +88,14 @@ func (s *DestinationBlockchain) initializeWarpQuorum() error { if err != nil { return fmt.Errorf("invalid subnetID in configuration. error: %w", err) } + // If the destination blockchain is the primary network, use the default quorum + // primary network signers here are irrelevant and can be left at default value + if subnetID == constants.PrimaryNetworkID { + s.warpConfig = WarpConfig{ + QuorumNumerator: warp.WarpDefaultQuorumNumerator, + } + return nil + } client, err := utils.NewEthClientWithConfig( context.Background(), @@ -93,21 +103,20 @@ func (s *DestinationBlockchain) initializeWarpQuorum() error { s.RPCEndpoint.HTTPHeaders, s.RPCEndpoint.QueryParams, ) + defer client.Close() if err != nil { return fmt.Errorf("failed to dial destination blockchain %s: %w", blockchainID, err) } - defer client.Close() - quorum, err := getWarpQuorum(subnetID, blockchainID, client) + subnetWarpConfig, err := getWarpConfig(client) if err != nil { - return fmt.Errorf("failed to fetch warp quorum for subnet %s: %w", subnetID, err) + return fmt.Errorf("failed to fetch warp config for blockchain %s: %w", blockchainID, err) } - - s.warpQuorum = quorum + s.warpConfig = warpConfigFromSubnetWarpConfig(*subnetWarpConfig) return nil } -// Warp Quorum configuration, fetched from the chain config -type WarpQuorum struct { - QuorumNumerator uint64 - QuorumDenominator uint64 +// Warp Configuration, fetched from the chain config +type WarpConfig struct { + QuorumNumerator uint64 + RequirePrimaryNetworkSigners bool } diff --git a/relayer/main/main.go b/relayer/main/main.go index 2e7e4b99..177bb82e 100644 --- a/relayer/main/main.go +++ b/relayer/main/main.go @@ -78,9 +78,9 @@ func main() { if err != nil { panic(fmt.Errorf("couldn't build config: %w", err)) } - // Initialize the Warp Quorum values by fetching via RPC + // Initialize the Warp Config values by fetching via RPC // We do this here so that BuildConfig doesn't need to make RPC calls - if err = cfg.InitializeWarpQuorums(); err != nil { + if err = cfg.InitializeWarpConfigs(); err != nil { panic(fmt.Errorf("couldn't initialize warp quorums: %w", err)) } diff --git a/relayer/network_utils.go b/relayer/network_utils.go index fc1e925a..4cea04d7 100644 --- a/relayer/network_utils.go +++ b/relayer/network_utils.go @@ -69,13 +69,13 @@ func connectToNonPrimaryNetworkPeers( } for _, destination := range sourceBlockchain.SupportedDestinations { blockchainID := destination.GetBlockchainID() - if ok, quorum, err := checkForSufficientConnectedStake(logger, cfg, connectedValidators, blockchainID); !ok { + if ok, warpConfig, err := checkForSufficientConnectedStake(logger, cfg, connectedValidators, blockchainID); !ok { logger.Warn( "Failed to connect to a threshold of stake", zap.String("destinationBlockchainID", blockchainID.String()), zap.Uint64("connectedWeight", connectedValidators.ConnectedWeight), zap.Uint64("totalValidatorWeight", connectedValidators.TotalValidatorWeight), - zap.Any("warpQuorum", quorum), + zap.Any("WarpConfig", warpConfig), ) return err } @@ -104,13 +104,13 @@ func connectToPrimaryNetworkPeers( return err } - if ok, quorum, err := checkForSufficientConnectedStake(logger, cfg, connectedValidators, blockchainID); !ok { + if ok, warpConfig, err := checkForSufficientConnectedStake(logger, cfg, connectedValidators, blockchainID); !ok { logger.Warn( "Failed to connect to a threshold of stake", zap.String("destinationBlockchainID", blockchainID.String()), zap.Uint64("connectedWeight", connectedValidators.ConnectedWeight), zap.Uint64("totalValidatorWeight", connectedValidators.TotalValidatorWeight), - zap.Any("warpQuorum", quorum), + zap.Any("WarpConfig", warpConfig), ) return err } @@ -118,17 +118,17 @@ func connectToPrimaryNetworkPeers( return nil } -// Fetch the warp quorum from the config and check if the connected stake exceeds the threshold +// Fetch the warp config from the destination chain config and check if the connected stake exceeds the threshold func checkForSufficientConnectedStake( logger logging.Logger, cfg *config.Config, connectedValidators *peers.ConnectedCanonicalValidators, destinationBlockchainID ids.ID, -) (bool, *config.WarpQuorum, error) { - quorum, err := cfg.GetWarpQuorum(destinationBlockchainID) +) (bool, *config.WarpConfig, error) { + warpConfig, err := cfg.GetWarpConfig(destinationBlockchainID) if err != nil { logger.Error( - "Failed to get warp quorum from config", + "Failed to get warp config from chain config", zap.String("destinationBlockchainID", destinationBlockchainID.String()), zap.Error(err), ) @@ -137,7 +137,6 @@ func checkForSufficientConnectedStake( return utils.CheckStakeWeightExceedsThreshold( big.NewInt(0).SetUint64(connectedValidators.ConnectedWeight), connectedValidators.TotalValidatorWeight, - quorum.QuorumNumerator, - quorum.QuorumDenominator, - ), &quorum, nil + warpConfig.QuorumNumerator, + ), &warpConfig, nil } diff --git a/signature-aggregator/aggregator/aggregator.go b/signature-aggregator/aggregator/aggregator.go index 7d2b9b01..cd38ca87 100644 --- a/signature-aggregator/aggregator/aggregator.go +++ b/signature-aggregator/aggregator/aggregator.go @@ -136,7 +136,7 @@ func (s *SignatureAggregator) CreateSignedMessage( float64(connectedValidators.TotalValidatorWeight) * 100, ) - if !utils.CheckStakeWeightPercentageExceedsThreshold( + if !utils.CheckStakeWeightExceedsThreshold( big.NewInt(0).SetUint64(connectedValidators.ConnectedWeight), connectedValidators.TotalValidatorWeight, quorumPercentage, @@ -453,7 +453,7 @@ func (s *SignatureAggregator) aggregateIfSufficientWeight( quorumPercentage uint64, ) (*avalancheWarp.Message, error) { // As soon as the signatures exceed the stake weight threshold we try to aggregate and send the transaction. - if !utils.CheckStakeWeightPercentageExceedsThreshold( + if !utils.CheckStakeWeightExceedsThreshold( accumulatedSignatureWeight, connectedValidators.TotalValidatorWeight, quorumPercentage, diff --git a/utils/utils.go b/utils/utils.go index 7aa51fbc..581f92e4 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -13,6 +13,7 @@ import ( "time" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "github.com/ethereum/go-ethereum/common" ) @@ -40,7 +41,6 @@ func CheckStakeWeightExceedsThreshold( accumulatedSignatureWeight *big.Int, totalWeight uint64, quorumNumerator uint64, - quorumDenominator uint64, ) bool { if accumulatedSignatureWeight == nil { return false @@ -49,20 +49,11 @@ func CheckStakeWeightExceedsThreshold( // Verifies that quorumNum * totalWeight <= quorumDen * sigWeight totalWeightBI := new(big.Int).SetUint64(totalWeight) scaledTotalWeight := new(big.Int).Mul(totalWeightBI, new(big.Int).SetUint64(quorumNumerator)) - scaledSigWeight := new(big.Int).Mul(accumulatedSignatureWeight, new(big.Int).SetUint64(quorumDenominator)) + scaledSigWeight := new(big.Int).Mul(accumulatedSignatureWeight, new(big.Int).SetUint64(warp.WarpQuorumDenominator)) return scaledTotalWeight.Cmp(scaledSigWeight) != 1 } -// Wrapper for CheckStakeWeightExceedThreshold with a quorumDen of 100. -func CheckStakeWeightPercentageExceedsThreshold( - accumulatedSignatureWeight *big.Int, - totalWeight uint64, - stakeWeightPercentage uint64, -) bool { - return CheckStakeWeightExceedsThreshold(accumulatedSignatureWeight, totalWeight, stakeWeightPercentage, 100) -} - // // Chain Utils // diff --git a/utils/utils_test.go b/utils/utils_test.go index 4fa21908..65aed4ad 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -79,7 +79,6 @@ func TestCheckStakeWeightExceedsThreshold(t *testing.T) { accumulatedSignatureWeight uint64 totalWeight uint64 quorumNumerator uint64 - quorumDenominator uint64 expectedResult bool }{ { @@ -87,7 +86,6 @@ func TestCheckStakeWeightExceedsThreshold(t *testing.T) { accumulatedSignatureWeight: 0, totalWeight: 0, quorumNumerator: 0, - quorumDenominator: 0, expectedResult: true, }, { @@ -95,7 +93,6 @@ func TestCheckStakeWeightExceedsThreshold(t *testing.T) { accumulatedSignatureWeight: 67_000_000, totalWeight: 100_000_000, quorumNumerator: 67, - quorumDenominator: 100, expectedResult: true, }, { @@ -103,7 +100,6 @@ func TestCheckStakeWeightExceedsThreshold(t *testing.T) { accumulatedSignatureWeight: 66_999_999, totalWeight: 100_000_000, quorumNumerator: 67, - quorumDenominator: 100, expectedResult: false, }, { @@ -111,7 +107,6 @@ func TestCheckStakeWeightExceedsThreshold(t *testing.T) { accumulatedSignatureWeight: 67_000_000, totalWeight: 67_000_000, quorumNumerator: 100, - quorumDenominator: 100, expectedResult: true, }, { @@ -119,7 +114,6 @@ func TestCheckStakeWeightExceedsThreshold(t *testing.T) { accumulatedSignatureWeight: 66_999_999, totalWeight: 67_000_000, quorumNumerator: 100, - quorumDenominator: 100, expectedResult: false, }, } @@ -129,7 +123,6 @@ func TestCheckStakeWeightExceedsThreshold(t *testing.T) { new(big.Int).SetUint64(testCase.accumulatedSignatureWeight), testCase.totalWeight, testCase.quorumNumerator, - testCase.quorumDenominator, ) require.Equal(t, testCase.expectedResult, actualResult) })