From 68d4f2f1493ae65a45a54b3e5c0e9388f1ac013b Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Tue, 28 Nov 2023 12:23:26 -0500 Subject: [PATCH] Infra for neutron & neutrontestnet (#2869) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --------- Co-authored-by: Trevor Porter --- .../templates/external-secret.yaml | 12 +- .../templates/relayer-external-secret.yaml | 9 +- .../templates/validator-configmap.yaml | 3 +- .../templates/validator-external-secret.yaml | 7 +- typescript/infra/config/contexts.ts | 1 + .../config/environments/mainnet3/agent.ts | 52 +++++++++ .../config/environments/mainnet3/chains.ts | 25 ++--- .../mainnet3/core/verification.json | 80 ++++++++++++++ .../environments/mainnet3/gas-oracle.ts | 9 ++ .../environments/mainnet3/infrastructure.ts | 1 + .../config/environments/mainnet3/owners.ts | 1 + .../environments/mainnet3/validators.ts | 44 ++++++++ .../config/environments/testnet4/agent.ts | 27 +++++ .../config/environments/testnet4/chains.ts | 22 ++-- .../environments/testnet4/gas-oracle.ts | 2 + .../environments/testnet4/infrastructure.ts | 1 + .../environments/testnet4/validators.ts | 28 +++++ typescript/infra/config/routingIsm.ts | 2 +- typescript/infra/package.json | 1 + typescript/infra/scripts/agents/utils.ts | 23 ++-- .../infra/scripts/announce-validators.ts | 1 + typescript/infra/src/agents/aws/s3.ts | 9 +- typescript/infra/src/agents/aws/validator.ts | 8 +- typescript/infra/src/agents/gcp.ts | 16 +++ typescript/infra/src/agents/index.ts | 39 ++++--- typescript/infra/src/agents/key-utils.ts | 48 +++++--- typescript/infra/src/config/agent/agent.ts | 27 ++++- typescript/infra/src/config/agent/relayer.ts | 50 +++++---- .../infra/src/config/agent/validator.ts | 35 +++++- typescript/infra/src/utils/utils.ts | 7 +- typescript/sdk/src/consts/chainMetadata.ts | 28 ++++- typescript/sdk/src/consts/chains.ts | 1 + .../sdk/src/consts/environments/mainnet.json | 16 +++ typescript/sdk/src/consts/multisigIsm.ts | 30 ++++- typescript/sdk/src/metadata/agentConfig.ts | 103 +++++++++++++----- yarn.lock | 1 + 36 files changed, 622 insertions(+), 147 deletions(-) diff --git a/rust/helm/hyperlane-agent/templates/external-secret.yaml b/rust/helm/hyperlane-agent/templates/external-secret.yaml index 98a4bc3b29..5d0eae5ced 100644 --- a/rust/helm/hyperlane-agent/templates/external-secret.yaml +++ b/rust/helm/hyperlane-agent/templates/external-secret.yaml @@ -28,17 +28,25 @@ spec: {{- range .Values.hyperlane.chains }} {{- if not .disabled }} HYP_CHAINS_{{ .name | upper }}_CUSTOMRPCURLS: {{ printf "'{{ .%s_rpcs | mustFromJson | join \",\" }}'" .name }} + {{- if eq .protocol "cosmos" }} + HYP_CHAINS_{{ .name | upper }}_GRPCURL: {{ printf "'{{ .%s_grpc }}'" .name }} + {{- end }} {{- end }} {{- end }} data: {{- /* - * For each network, load the secret in GCP secret manager with the form: environment-rpc-endpoint-network, - * and associate it with the secret key networkname_rpc. + * For each network, load the secret in GCP secret manager with the form: environment-rpc-endpoints-network, + * and associate it with the secret key networkname_rpcs. */}} {{- range .Values.hyperlane.chains }} {{- if not .disabled }} - secretKey: {{ printf "%s_rpcs" .name }} remoteRef: key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv .name }} + {{- if eq .protocol "cosmos" }} + - secretKey: {{ printf "%s_grpc" .name }} + remoteRef: + key: {{ printf "%s-grpc-endpoint-%s" $.Values.hyperlane.runEnv .name }} + {{- end }} {{- end }} {{- end }} diff --git a/rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml b/rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml index 35ac56b548..c9bcd9a27d 100644 --- a/rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml +++ b/rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml @@ -22,20 +22,23 @@ spec: {{- include "agent-common.labels" . | nindent 10 }} data: {{- range .Values.hyperlane.relayerChains }} - {{- if eq .signer.type "hexKey" }} + {{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") }} HYP_CHAINS_{{ .name | upper }}_SIGNER_KEY: {{ printf "'{{ .%s_signer_key | toString }}'" .name }} + {{- include "agent-common.config-env-vars" (dict "config" .signer "format" "config_map" "key_name_prefix" (printf "CHAINS_%s_SIGNER_" (.name | upper))) | nindent 8 }} {{- end }} {{- if and (eq .signer.type "aws") $.Values.hyperlane.relayer.aws }} HYP_CHAINS_{{ .name | upper }}_SIGNER_TYPE: aws HYP_CHAINS_{{ .name | upper }}_SIGNER_ID: {{ .signer.id }} HYP_CHAINS_{{ .name | upper }}_SIGNER_REGION: {{ .signer.region}} + {{- end }} + {{- end }} + {{- if .Values.hyperlane.relayer.aws }} AWS_ACCESS_KEY_ID: {{ print "'{{ .aws_access_key_id | toString }}'" }} AWS_SECRET_ACCESS_KEY: {{ print "'{{ .aws_secret_access_key | toString }}'" }} {{- end }} - {{- end }} data: {{- range .Values.hyperlane.relayerChains }} - {{- if eq .signer.type "hexKey" }} + {{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") }} - secretKey: {{ printf "%s_signer_key" .name }} remoteRef: {{- if $.Values.hyperlane.relayer.usingDefaultSignerKey }} diff --git a/rust/helm/hyperlane-agent/templates/validator-configmap.yaml b/rust/helm/hyperlane-agent/templates/validator-configmap.yaml index cabed1644a..8594b96e21 100644 --- a/rust/helm/hyperlane-agent/templates/validator-configmap.yaml +++ b/rust/helm/hyperlane-agent/templates/validator-configmap.yaml @@ -8,6 +8,7 @@ metadata: data: {{- range $index, $config := .Values.hyperlane.validator.configs }} validator-{{ $index }}.env: | - {{- include "agent-common.config-env-vars" (dict "config" $config "format" "dot_env") | nindent 4 }} + {{- include "agent-common.config-env-vars" (dict "config" (get $config "chainSigner") "format" "dot_env" "key_name_prefix" (printf "CHAINS_%s_SIGNER_" ($config.originChainName | upper))) | nindent 4 }} + {{- include "agent-common.config-env-vars" (dict "config" (omit $config "chainSigner") "format" "dot_env") | nindent 4 }} {{- end }} {{- end }} diff --git a/rust/helm/hyperlane-agent/templates/validator-external-secret.yaml b/rust/helm/hyperlane-agent/templates/validator-external-secret.yaml index c15081f119..61f115584d 100644 --- a/rust/helm/hyperlane-agent/templates/validator-external-secret.yaml +++ b/rust/helm/hyperlane-agent/templates/validator-external-secret.yaml @@ -26,18 +26,21 @@ spec: validator-{{ $index }}.env: | {{- if eq .validator.type "hexKey" }} HYP_VALIDATOR_KEY={{ printf "'{{ .signer_key_%d | toString }}'" $index }} - HYP_CHAINS_{{ .originChainName | upper }}_SIGNER_KEY={{ printf "'{{ .signer_key_%d | toString }}'" $index }} {{- end }} {{- if or (eq .checkpointSyncer.type "s3") $.Values.hyperlane.aws }} AWS_ACCESS_KEY_ID={{ printf "'{{ .aws_access_key_id_%d | toString }}'" $index }} AWS_SECRET_ACCESS_KEY={{ printf "'{{ .aws_secret_access_key_%d | toString }}'" $index }} {{- end }} + + {{- if or (eq .chainSigner.type "hexKey") (eq .chainSigner.type "cosmosKey") }} + HYP_CHAINS_{{ .originChainName | upper }}_SIGNER_KEY={{ printf "'{{ .signer_key_%d | toString }}'" $index }} + {{- end }} {{ $index = add1 $index }} {{- end }} data: {{ $index = 0 }} {{- range .Values.hyperlane.validator.configs }} -{{- if eq .validator.type "hexKey" }} +{{- if or (eq .validator.type "hexKey") (eq .chainSigner.type "hexKey") (eq .chainSigner.type "cosmosKey") }} - secretKey: signer_key_{{ $index }} remoteRef: key: {{ printf "%s-%s-key-%s-validator-%d" $.Values.hyperlane.context $.Values.hyperlane.runEnv .originChainName $index }} diff --git a/typescript/infra/config/contexts.ts b/typescript/infra/config/contexts.ts index a8df3d4d65..6c9e551700 100644 --- a/typescript/infra/config/contexts.ts +++ b/typescript/infra/config/contexts.ts @@ -2,4 +2,5 @@ export enum Contexts { Hyperlane = 'hyperlane', ReleaseCandidate = 'rc', + Neutron = 'neutron', } diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index c867c33a19..aaafa1b5fe 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -2,6 +2,7 @@ import { GasPaymentEnforcementPolicyType, RpcConsensusType, chainMetadata, + getDomainId, } from '@hyperlane-xyz/sdk'; import { RootAgentConfig, allAgentChainNames } from '../../../src/config'; @@ -51,6 +52,14 @@ const hyperlane: RootAgentConfig = { repo, tag: '1bee32a-20231121-121303', }, + chainDockerOverrides: { + [chainMetadata.neutron.name]: { + tag: '5070398-20231108-172634', + }, + [chainMetadata.mantapacific.name]: { + tag: '5070398-20231108-172634', + }, + }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.Hyperlane), }, @@ -90,7 +99,50 @@ const releaseCandidate: RootAgentConfig = { }, }; +const neutron: RootAgentConfig = { + ...contextBase, + contextChainNames: { + validator: [], + relayer: [ + chainMetadata.neutron.name, + chainMetadata.mantapacific.name, + chainMetadata.arbitrum.name, + ], + scraper: [], + }, + context: Contexts.Neutron, + rolesWithKeys: [Role.Relayer], + relayer: { + rpcConsensusType: RpcConsensusType.Fallback, + docker: { + repo, + tag: '68bad33-20231109-024958', + }, + gasPaymentEnforcement: [ + { + type: GasPaymentEnforcementPolicyType.None, + matchingList: [ + { + originDomain: getDomainId(chainMetadata.neutron), + destinationDomain: getDomainId(chainMetadata.mantapacific), + senderAddress: '*', + recipientAddress: '*', + }, + { + originDomain: getDomainId(chainMetadata.neutron), + destinationDomain: getDomainId(chainMetadata.arbitrum), + senderAddress: '*', + recipientAddress: '*', + }, + ], + }, + ...gasPaymentEnforcement, + ], + }, +}; + export const agents = { [Contexts.Hyperlane]: hyperlane, [Contexts.ReleaseCandidate]: releaseCandidate, + [Contexts.Neutron]: neutron, }; diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index 14465a19ce..a6b3584355 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -41,16 +41,18 @@ export const ethereumMainnetConfigs: ChainMap = { }, moonbeam: chainMetadata.moonbeam, gnosis: chainMetadata.gnosis, + mantapacific: chainMetadata.mantapacific, }; // Blessed non-Ethereum chains. -// export const nonEthereumMainnetConfigs: ChainMap = { -// solana: chainMetadata.solana, -// }; +export const nonEthereumMainnetConfigs: ChainMap = { + // solana: chainMetadata.solana, + neutron: chainMetadata.neutron, +}; export const mainnetConfigs: ChainMap = { ...ethereumMainnetConfigs, - // ...nonEthereumMainnetConfigs, + ...nonEthereumMainnetConfigs, }; export type MainnetChains = keyof typeof mainnetConfigs; @@ -63,16 +65,11 @@ export const ethereumChainNames = Object.keys( ethereumMainnetConfigs, ) as MainnetChains[]; -const validatorChainNames = [ - ...supportedChainNames, - // chainMetadata.solana.name, - // chainMetadata.nautilus.name, -]; - -const relayerChainNames = validatorChainNames; - +// Hyperlane & RC context agent chain names. export const agentChainNames: AgentChainNames = { - [Role.Validator]: validatorChainNames, - [Role.Relayer]: relayerChainNames, + // Run validators for all chains. + [Role.Validator]: supportedChainNames, + // Only run relayers for Ethereum chains at the moment. + [Role.Relayer]: ethereumChainNames, [Role.Scraper]: ethereumChainNames, }; diff --git a/typescript/infra/config/environments/mainnet3/core/verification.json b/typescript/infra/config/environments/mainnet3/core/verification.json index 1f81718f07..bee9dfbdf0 100644 --- a/typescript/infra/config/environments/mainnet3/core/verification.json +++ b/typescript/infra/config/environments/mainnet3/core/verification.json @@ -1288,5 +1288,85 @@ "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb", "isProxy": false } + ], + "mantapacific": [ + { + "name": "ProxyAdmin", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000a9", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "ProtocolFee", + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + } ] } diff --git a/typescript/infra/config/environments/mainnet3/gas-oracle.ts b/typescript/infra/config/environments/mainnet3/gas-oracle.ts index c3dc53ec6d..e58dcaffd0 100644 --- a/typescript/infra/config/environments/mainnet3/gas-oracle.ts +++ b/typescript/infra/config/environments/mainnet3/gas-oracle.ts @@ -48,6 +48,8 @@ const gasPrices: ChainMap = { base: ethers.utils.parseUnits('1', 'gwei'), scroll: ethers.utils.parseUnits('1', 'gwei'), polygonzkevm: ethers.utils.parseUnits('2', 'gwei'), + neutron: ethers.utils.parseUnits('1', 'gwei'), + mantapacific: ethers.utils.parseUnits('1', 'gwei'), }; // Accurate from coingecko as of Mar 9, 2023. @@ -83,6 +85,13 @@ const tokenUsdPrices: ChainMap = { '1619.00', TOKEN_EXCHANGE_RATE_DECIMALS, ), + // https://www.coingecko.com/en/coins/neutron + neutron: ethers.utils.parseUnits('0.304396', TOKEN_EXCHANGE_RATE_DECIMALS), + // https://www.coingecko.com/en/coins/ethereum + mantapacific: ethers.utils.parseUnits( + '1619.00', + TOKEN_EXCHANGE_RATE_DECIMALS, + ), }; // Gets the exchange rate of the remote quoted in local tokens diff --git a/typescript/infra/config/environments/mainnet3/infrastructure.ts b/typescript/infra/config/environments/mainnet3/infrastructure.ts index c0d562f91b..f123829482 100644 --- a/typescript/infra/config/environments/mainnet3/infrastructure.ts +++ b/typescript/infra/config/environments/mainnet3/infrastructure.ts @@ -40,6 +40,7 @@ export const infrastructure: InfrastructureConfig = { 'mainnet2-', 'hyperlane-mainnet3-', 'rc-mainnet3-', + 'neutron-mainnet3-', 'mainnet3-', ], }, diff --git a/typescript/infra/config/environments/mainnet3/owners.ts b/typescript/infra/config/environments/mainnet3/owners.ts index 32dae82723..1b4b2a1a0e 100644 --- a/typescript/infra/config/environments/mainnet3/owners.ts +++ b/typescript/infra/config/environments/mainnet3/owners.ts @@ -16,6 +16,7 @@ export const safes: ChainMap
= { base: '', scroll: '', polygonzkevm: '', + mantapacific: '', }; // export const owners = safes; diff --git a/typescript/infra/config/environments/mainnet3/validators.ts b/typescript/infra/config/environments/mainnet3/validators.ts index 28d5e54fd6..61cf6f3f1b 100644 --- a/typescript/infra/config/environments/mainnet3/validators.ts +++ b/typescript/infra/config/environments/mainnet3/validators.ts @@ -22,6 +22,7 @@ export const validatorChainConfig = ( '0x7bf30afcb6a7d92146d5a910ea4c154fba38d25e', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'celo', ), @@ -37,6 +38,7 @@ export const validatorChainConfig = ( '0x749d6e7ad949e522c92181dc77f7bbc1c5d71506', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'ethereum', ), @@ -54,6 +56,7 @@ export const validatorChainConfig = ( [Contexts.ReleaseCandidate]: [ '0x706976391e23dea28152e0207936bd942aba01ce', ], + [Contexts.Neutron]: [], }, 'avalanche', ), @@ -69,6 +72,7 @@ export const validatorChainConfig = ( '0xdbf3666de031bea43ec35822e8c33b9a9c610322', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'polygon', ), @@ -84,6 +88,7 @@ export const validatorChainConfig = ( '0x03047213365800f065356b4a2fe97c3c3a52296a', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'bsc', ), @@ -99,6 +104,7 @@ export const validatorChainConfig = ( '0x3369e12edd52570806f126eb50be269ba5e65843', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'arbitrum', ), @@ -116,6 +122,7 @@ export const validatorChainConfig = ( [Contexts.ReleaseCandidate]: [ '0x60e938bf280bbc21bacfd8bf435459d9003a8f98', ], + [Contexts.Neutron]: [], }, 'optimism', ), @@ -131,6 +138,7 @@ export const validatorChainConfig = ( '0xcc4a78aa162482bea43313cd836ba7b560b44fc4', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'moonbeam', ), @@ -146,6 +154,7 @@ export const validatorChainConfig = ( '0xb93a72cee19402553c9dd7fed2461aebd04e2454', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'gnosis', ), @@ -161,6 +170,7 @@ export const validatorChainConfig = ( '0xb144bb2f599a5af095bc30367856f27ea8a8adc7', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'base', ), @@ -176,6 +186,7 @@ export const validatorChainConfig = ( '0x7210fa0a6be39a75cb14d682ebfb37e2b53ecbe5', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'scroll', ), @@ -191,9 +202,42 @@ export const validatorChainConfig = ( '0x6a1da2e0b7ae26aaece1377c0a4dbe25b85fa3ca', ], [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], }, 'polygonzkevm', ), }, + neutron: { + interval: 5, + reorgPeriod: 0, + validators: validatorsConfig( + { + [Contexts.Hyperlane]: [ + '0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0', + '0x60e890b34cb44ce3fa52f38684f613f31b47a1a6', + '0x7885fae56dbcf5176657f54adbbd881dc6714132', + ], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'neutron', + ), + }, + mantapacific: { + interval: 5, + reorgPeriod: 0, + validators: validatorsConfig( + { + [Contexts.Hyperlane]: [ + '0x8e668c97ad76d0e28375275c41ece4972ab8a5bc', + '0x80afdde2a81f3fb056fd088a97f0af3722dbc4f3', + '0x5dda0c4cf18de3b3ab637f8df82b24921082b54c', + ], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'mantapacific', + ), + }, }; }; diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index cb564c7216..b3bfca2915 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -1,4 +1,5 @@ import { + Chains, GasPaymentEnforcementPolicyType, RpcConsensusType, chainMetadata, @@ -68,6 +69,11 @@ const hyperlane: RootAgentConfig = { repo, tag: '1bee32a-20231121-121303', }, + chainDockerOverrides: { + neutrontestnet: { + tag: '5070398-20231108-172634', + }, + }, chains: validatorChainConfig(Contexts.Hyperlane), }, scraper: { @@ -106,7 +112,28 @@ const releaseCandidate: RootAgentConfig = { }, }; +const neutron: RootAgentConfig = { + ...contextBase, + context: Contexts.Neutron, + rolesWithKeys: [Role.Relayer], + contextChainNames: { + relayer: [Chains.neutrontestnet, Chains.goerli], + validator: [], + scraper: [], + }, + relayer: { + rpcConsensusType: RpcConsensusType.Fallback, + docker: { + repo, + tag: '5070398-20231108-172634', + }, + gasPaymentEnforcement, + transactionGasLimit: 750000, + }, +}; + export const agents = { [Contexts.Hyperlane]: hyperlane, [Contexts.ReleaseCandidate]: releaseCandidate, + [Contexts.Neutron]: neutron, }; diff --git a/typescript/infra/config/environments/testnet4/chains.ts b/typescript/infra/config/environments/testnet4/chains.ts index 64ad786e04..dd9e827ea8 100644 --- a/typescript/infra/config/environments/testnet4/chains.ts +++ b/typescript/infra/config/environments/testnet4/chains.ts @@ -25,13 +25,14 @@ export const ethereumTestnetConfigs: ChainMap = { }; // Blessed non-Ethereum chains. -// export const nonEthereumTestnetConfigs: ChainMap = { -// solanadevnet: chainMetadata.solanadevnet, -// }; +export const nonEthereumTestnetConfigs: ChainMap = { + // solanadevnet: chainMetadata.solanadevnet, + neutrontestnet: chainMetadata.neutrontestnet, +}; export const testnetConfigs: ChainMap = { ...ethereumTestnetConfigs, - // ...nonEthereumTestnetConfigs, + ...nonEthereumTestnetConfigs, }; export type TestnetChains = keyof typeof testnetConfigs; @@ -43,15 +44,12 @@ export const environment = 'testnet4'; export const ethereumChainNames = Object.keys( ethereumTestnetConfigs, ) as TestnetChains[]; -const validatorChainNames = [ - ...supportedChainNames, - // chainMetadata.solanadevnet.name, - // chainMetadata.proteustestnet.name, -]; -const relayerChainNames = validatorChainNames; +// Hyperlane & RC context agent chain names. export const agentChainNames: AgentChainNames = { - [Role.Validator]: validatorChainNames, - [Role.Relayer]: relayerChainNames, + // Run validators for all chains. + [Role.Validator]: supportedChainNames, + // Only run relayers for Ethereum chains at the moment. + [Role.Relayer]: ethereumChainNames, [Role.Scraper]: ethereumChainNames, }; diff --git a/typescript/infra/config/environments/testnet4/gas-oracle.ts b/typescript/infra/config/environments/testnet4/gas-oracle.ts index b173f4d3a5..7fda9ad6b1 100644 --- a/typescript/infra/config/environments/testnet4/gas-oracle.ts +++ b/typescript/infra/config/environments/testnet4/gas-oracle.ts @@ -30,6 +30,7 @@ const gasPrices: ChainMap = { polygonzkevmtestnet: ethers.utils.parseUnits('1', 'gwei'), chiado: ethers.utils.parseUnits('2', 'gwei'), // solanadevnet: ethers.BigNumber.from('28'), + neutrontestnet: ethers.utils.parseUnits('0.1', 'gwei'), }; // Used to categorize rarity of testnet tokens & approximate exchange rates. @@ -64,6 +65,7 @@ const chainTokenRarity: ChainMap = { polygonzkevmtestnet: Rarity.Common, chiado: Rarity.Common, // solanadevnet: Rarity.Common, + neutrontestnet: Rarity.Common, }; // Gets the "value" of a testnet chain diff --git a/typescript/infra/config/environments/testnet4/infrastructure.ts b/typescript/infra/config/environments/testnet4/infrastructure.ts index 9429f4782d..c3367436cb 100644 --- a/typescript/infra/config/environments/testnet4/infrastructure.ts +++ b/typescript/infra/config/environments/testnet4/infrastructure.ts @@ -40,6 +40,7 @@ export const infrastructure: InfrastructureConfig = { 'testnet3-', 'hyperlane-testnet4-', 'rc-testnet4-', + 'neutron-testnet4-', 'testnet4-', ], }, diff --git a/typescript/infra/config/environments/testnet4/validators.ts b/typescript/infra/config/environments/testnet4/validators.ts index 89271fbb54..da7ca5a070 100644 --- a/typescript/infra/config/environments/testnet4/validators.ts +++ b/typescript/infra/config/environments/testnet4/validators.ts @@ -26,6 +26,7 @@ export const validatorChainConfig = ( '0x6c8bfdfb8c40aba10cc9fb2cf0e3e856e0e5dbb3', '0x54c65eb7677e6086cdde3d5ccef89feb2103a11d', ], + [Contexts.Neutron]: [], }, 'alfajores', ), @@ -45,6 +46,7 @@ export const validatorChainConfig = ( '0x36de434527b8f83851d83f1b1d72ec11a5903533', '0x4b65f7527c267e420bf62a0c5a139cb8c3906277', ], + [Contexts.Neutron]: [], }, 'basegoerli', ), @@ -64,6 +66,7 @@ export const validatorChainConfig = ( '0x0a636e76df4124b092cabb4321d6aaef9defb514', '0xbf86037899efe97bca4cea865607e10b849b5878', ], + [Contexts.Neutron]: [], }, 'fuji', ), @@ -121,6 +124,7 @@ export const validatorChainConfig = ( '0x954168cf13faeaa248d412e145a17dc697556636', '0x98a9f2610e44246ac0c749c20a07a6eb192ce9eb', ], + [Contexts.Neutron]: [], }, 'mumbai', ), @@ -140,6 +144,7 @@ export const validatorChainConfig = ( '0xcb5be62b19c52b78cd3993c71c3fa74d821475ae', '0xc50ddb8f03133611853b7f03ffe0a8098e08ae15', ], + [Contexts.Neutron]: [], }, 'bsctestnet', ), @@ -159,6 +164,7 @@ export const validatorChainConfig = ( '0x4711d476a5929840196def397a156c5253b44b96', '0xb0add42f2a4b824ba5fab2628f930dc1dcfc40f8', ], + [Contexts.Neutron]: [], }, 'goerli', ), @@ -178,6 +184,7 @@ export const validatorChainConfig = ( '0x10fa7a657a06a47bcca1bacc436d61619e5d104c', '0xa0f1cf3b23bd0f8a5e2ad438657097b8287816b4', ], + [Contexts.Neutron]: [], }, 'scrollsepolia', ), @@ -197,6 +204,7 @@ export const validatorChainConfig = ( '0x13b51805e9af68e154778d973165f32e10b7446b', '0x7f699c3fc3de4928f1c0abfba1eac3fbb5a00d1b', ], + [Contexts.Neutron]: [], }, 'sepolia', ), @@ -216,6 +224,7 @@ export const validatorChainConfig = ( '0x776623e8be8d7218940b7c77d02162af4ff97985', '0xb4c81facd992a6c7c4a187bcce35a6fc968399a0', ], + [Contexts.Neutron]: [], }, 'moonbasealpha', ), @@ -235,6 +244,7 @@ export const validatorChainConfig = ( '0xec6b5ddfd20ee64ff0dcbc7472ad757dce151685', '0x4acd2983a51f1c33c2ab41669184c7679e0316f1', ], + [Contexts.Neutron]: [], }, 'optimismgoerli', ), @@ -254,6 +264,7 @@ export const validatorChainConfig = ( '0x9be82c7a063b47b2d04c890daabcb666b670a9a4', '0x92c62f4b9cd60a7fe4216d1f12134d34cf827c41', ], + [Contexts.Neutron]: [], }, 'arbitrumgoerli', ), @@ -273,6 +284,7 @@ export const validatorChainConfig = ( '0x989bbbfa753431169556f69be1b0a496b252e8a6', '0x292d5788587bb5efd5c2c911115527e57f50cd05', ], + [Contexts.Neutron]: [], }, 'polygonzkevmtestnet', ), @@ -311,5 +323,21 @@ export const validatorChainConfig = ( // 'solanadevnet', // ), // }, + neutrontestnet: { + interval: 5, + reorgPeriod: chainMetadata.neutrontestnet.blocks!.reorgPeriod!, + validators: validatorsConfig( + { + [Contexts.Hyperlane]: [ + '0x5d2a99d67cd294a821de4fb25da6901ea8f89814', + '0xb57486243ce3bb3c38c50a582b8bbd20cb393589', + '0x661faee997654d14ead4ae48035883f05c3150cf', + ], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'neutrontestnet', + ), + }, }; }; diff --git a/typescript/infra/config/routingIsm.ts b/typescript/infra/config/routingIsm.ts index 71590813f9..6e4d2e8f36 100644 --- a/typescript/infra/config/routingIsm.ts +++ b/typescript/infra/config/routingIsm.ts @@ -43,7 +43,7 @@ export const routingIsm = ( context: Contexts, ): RoutingIsmConfig | string => { const aggregationIsms: ChainMap = chains[environment] - .filter((_) => _ !== local) + .filter((chain) => chain !== local) .reduce( (acc, chain) => ({ ...acc, diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 7673048ca2..486ac63497 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -7,6 +7,7 @@ "@aws-sdk/client-iam": "^3.74.0", "@aws-sdk/client-kms": "3.48.0", "@aws-sdk/client-s3": "^3.74.0", + "@cosmjs/amino": "^0.31.3", "@eth-optimism/sdk": "^1.7.0", "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", diff --git a/typescript/infra/scripts/agents/utils.ts b/typescript/infra/scripts/agents/utils.ts index 607569a660..2bd9a3ad7c 100644 --- a/typescript/infra/scripts/agents/utils.ts +++ b/typescript/infra/scripts/agents/utils.ts @@ -7,6 +7,7 @@ import { import { EnvironmentConfig, RootAgentConfig } from '../../src/config'; import { Role } from '../../src/roles'; import { HelmCommand } from '../../src/utils/helm'; +import { sleep } from '../../src/utils/utils'; import { assertCorrectKubeContext, getArgs, @@ -49,11 +50,10 @@ export class AgentCli { } if (this.dryRun) { - for (const m of Object.values(managers)) { - void m.helmValues().then((v) => { - console.log(JSON.stringify(v, null, 2)); - }); - } + const values = await Promise.all( + Object.values(managers).map(async (m) => m.helmValues()), + ); + console.log('Dry run values:\n', JSON.stringify(values, null, 2)); } for (const m of Object.values(managers)) { @@ -61,21 +61,18 @@ export class AgentCli { } } - protected async init( - argv?: GetConfigsArgv & { role: Role[]; 'dry-run'?: boolean }, - ) { + protected async init() { if (this.initialized) return; - if (!argv) - argv = await withAgentRole(withContext(getArgs())) - .describe('dry-run', 'Run through the steps without making any changes') - .boolean('dry-run').argv; + const argv = await withAgentRole(withContext(getArgs())) + .describe('dry-run', 'Run through the steps without making any changes') + .boolean('dry-run').argv; const { envConfig, agentConfig } = await getConfigsBasedOnArgs(argv); await assertCorrectKubeContext(envConfig); this.roles = argv.role; this.envConfig = envConfig; this.agentConfig = agentConfig; - this.dryRun = argv['dry-run'] || false; + this.dryRun = argv.dryRun || false; this.initialized = true; } } diff --git a/typescript/infra/scripts/announce-validators.ts b/typescript/infra/scripts/announce-validators.ts index 7395f74dcd..542e9de962 100644 --- a/typescript/infra/scripts/announce-validators.ts +++ b/typescript/infra/scripts/announce-validators.ts @@ -95,6 +95,7 @@ async function main() { contracts.mailbox.address, validatorBaseConfig.checkpointSyncer.bucket, validatorBaseConfig.checkpointSyncer.region, + undefined, ); announcements.push({ storageLocation: validator.storageLocation(), diff --git a/typescript/infra/src/agents/aws/s3.ts b/typescript/infra/src/agents/aws/s3.ts index 233b4c24de..2c12cd00c3 100644 --- a/typescript/infra/src/agents/aws/s3.ts +++ b/typescript/infra/src/agents/aws/s3.ts @@ -15,23 +15,26 @@ export class S3Wrapper { private readonly client: S3Client; readonly bucket: string; readonly region: string; + readonly folder: string | undefined; static fromBucketUrl(bucketUrl: string): S3Wrapper { const match = bucketUrl.match(S3_BUCKET_REGEX); if (!match) throw new Error('Could not parse bucket url'); - return new S3Wrapper(match[1], match[2]); + return new S3Wrapper(match[1], match[2], undefined); } - constructor(bucket: string, region: string) { + constructor(bucket: string, region: string, folder: string | undefined) { this.bucket = bucket; this.region = region; + this.folder = folder; this.client = new S3Client({ region }); } async getS3Obj(key: string): Promise | undefined> { + const Key = this.folder ? `${this.folder}/${key}` : key; const command = new GetObjectCommand({ Bucket: this.bucket, - Key: key, + Key, }); try { const response = await this.client.send(command); diff --git a/typescript/infra/src/agents/aws/validator.ts b/typescript/infra/src/agents/aws/validator.ts index fd8ffa4b6f..2cea6b6a8d 100644 --- a/typescript/infra/src/agents/aws/validator.ts +++ b/typescript/infra/src/agents/aws/validator.ts @@ -50,9 +50,10 @@ export class S3Validator extends BaseValidator { mailbox: string, s3Bucket: string, s3Region: string, + s3Folder: string | undefined, ) { super(address, localDomain, mailbox); - this.s3Bucket = new S3Wrapper(s3Bucket, s3Region); + this.s3Bucket = new S3Wrapper(s3Bucket, s3Region, s3Folder); } static async fromStorageLocation( @@ -61,8 +62,8 @@ export class S3Validator extends BaseValidator { if (storageLocation.startsWith(LOCATION_PREFIX)) { const suffix = storageLocation.slice(LOCATION_PREFIX.length); const pieces = suffix.split('/'); - if (pieces.length == 2) { - const s3Bucket = new S3Wrapper(pieces[0], pieces[1]); + if (pieces.length >= 2) { + const s3Bucket = new S3Wrapper(pieces[0], pieces[1], pieces[2]); const announcement = await s3Bucket.getS3Obj(ANNOUNCEMENT_KEY); const address = announcement?.data.value.validator; const mailbox = announcement?.data.value.mailbox_address; @@ -74,6 +75,7 @@ export class S3Validator extends BaseValidator { mailbox, pieces[0], pieces[1], + pieces[2], ); } } diff --git a/typescript/infra/src/agents/gcp.ts b/typescript/infra/src/agents/gcp.ts index a2f6748880..4ba314256e 100644 --- a/typescript/infra/src/agents/gcp.ts +++ b/typescript/infra/src/agents/gcp.ts @@ -1,3 +1,8 @@ +import { + encodeSecp256k1Pubkey, + pubkeyToAddress, + rawSecp256k1PubkeyToRawAddress, +} from '@cosmjs/amino'; import { Keypair } from '@solana/web3.js'; import { Wallet, ethers } from 'ethers'; @@ -101,6 +106,17 @@ export class AgentGCPKey extends CloudAgentKey { return Keypair.fromSeed( Buffer.from(strip0x(this.privateKey), 'hex'), ).publicKey.toBase58(); + case ProtocolType.Cosmos: + const compressedPubkey = ethers.utils.computePublicKey( + this.privateKey, + true, + ); + const encodedPubkey = encodeSecp256k1Pubkey( + new Uint8Array(Buffer.from(strip0x(compressedPubkey), 'hex')), + ); + // TODO support other prefixes? + // https://cosmosdrops.io/en/tools/bech32-converter is useful for converting to other prefixes. + return pubkeyToAddress(encodedPubkey, 'neutron'); default: return undefined; } diff --git a/typescript/infra/src/agents/index.ts b/typescript/infra/src/agents/index.ts index 1d306963cc..8c3d5927a8 100644 --- a/typescript/infra/src/agents/index.ts +++ b/typescript/infra/src/agents/index.ts @@ -22,7 +22,7 @@ import { buildHelmChartDependencies, helmifyValues, } from '../utils/helm'; -import { execCmd } from '../utils/utils'; +import { execCmd, isNotEthereumProtocolChain } from '../utils/utils'; import { AgentGCPKey } from './gcp'; @@ -113,17 +113,27 @@ export abstract class AgentHelmManager { runEnv: this.environment, context: this.context, aws: !!this.config.aws, - chains: this.config.environmentChainNames.map((name) => ({ - name, - disabled: !this.config.contextChainNames[this.role].includes(name), - rpcConsensusType: this.rpcConsensusType(name), - })), + chains: this.config.environmentChainNames.map((chain) => { + const metadata = chainMetadata[chain]; + const reorgPeriod = metadata.blocks?.reorgPeriod; + if (reorgPeriod === undefined) { + throw new Error(`No reorg period found for chain ${chain}`); + } + return { + name: chain, + disabled: !this.config.contextChainNames[this.role].includes(chain), + rpcConsensusType: this.rpcConsensusType(chain), + protocol: metadata.protocol, + blocks: { reorgPeriod }, + }; + }), }, }; } rpcConsensusType(chain: ChainName): RpcConsensusType { - if (chainMetadata[chain].protocol == ProtocolType.Sealevel) { + // Non-Ethereum chains only support Single + if (isNotEthereumProtocolChain(chain)) { return RpcConsensusType.Single; } @@ -195,12 +205,10 @@ export class RelayerHelmManager extends OmniscientAgentHelmManager { }; const signers = await this.config.signers(); - values.hyperlane.relayerChains = this.config.environmentChainNames.map( - (name) => ({ - name, - signer: signers[name], - }), - ); + values.hyperlane.relayerChains = this.config.relayChains.map((name) => ({ + name, + signer: signers[name], + })); return values; } @@ -250,11 +258,6 @@ export class ValidatorHelmManager extends MultichainAgentHelmManager { const helmValues = await super.helmValues(); const cfg = await this.config.buildConfig(); - helmValues.hyperlane.chains.push({ - name: cfg.originChainName, - blocks: { reorgPeriod: cfg.reorgPeriod }, - }); - helmValues.hyperlane.validator = { enabled: true, configs: cfg.validators.map((c) => ({ diff --git a/typescript/infra/src/agents/key-utils.ts b/typescript/infra/src/agents/key-utils.ts index afe3288e44..70705ff711 100644 --- a/typescript/infra/src/agents/key-utils.ts +++ b/typescript/infra/src/agents/key-utils.ts @@ -9,7 +9,7 @@ import { } from '../config'; import { Role } from '../roles'; import { fetchGCPSecret, setGCPSecret } from '../utils/gcloud'; -import { execCmd } from '../utils/utils'; +import { execCmd, isNotEthereumProtocolChain } from '../utils/utils'; import { AgentAwsKey } from './aws/key'; import { AgentGCPKey } from './gcp'; @@ -96,25 +96,52 @@ export function getValidatorCloudAgentKeys( ): Array { // For each chainName, create validatorCount keys if (!agentConfig.validators) return []; + const validators = agentConfig.validators; return agentConfig.contextChainNames[Role.Validator] .filter((chainName) => !!validators.chains[chainName]) .flatMap((chainName) => - validators.chains[chainName].validators.map((_, index) => - getCloudAgentKey(agentConfig, Role.Validator, chainName, index), - ), - ); + validators.chains[chainName].validators.map((_, index) => { + const validatorKeys = []; + + // If AWS is enabled, we want to use AWS keys for the validator signing key + // that actually signs checkpoints. + if (agentConfig.aws) { + validatorKeys.push( + new AgentAwsKey(agentConfig, Role.Validator, chainName, index), + ); + } + + // If the chain is not an EVM chain, we also want to use GCP keys for + // self-announcing. This key won't actually sign checkpoints, just the self-announcement tx. + // We also want to use a GCP key if AWS is not enabled. + if (isNotEthereumProtocolChain(chainName) || !agentConfig.aws) { + validatorKeys.push( + new AgentGCPKey( + agentConfig.runEnv, + agentConfig.context, + Role.Validator, + chainName, + index, + ), + ); + } + + return validatorKeys; + }), + ) + .flat(); } export function getAllCloudAgentKeys( agentConfig: RootAgentConfig, ): Array { const keys = []; - if ((agentConfig.rolesWithKeys ?? []).includes(Role.Relayer)) + if (agentConfig.rolesWithKeys.includes(Role.Relayer)) keys.push(...getRelayerCloudAgentKeys(agentConfig)); - if ((agentConfig.rolesWithKeys ?? []).includes(Role.Validator)) + if (agentConfig.rolesWithKeys.includes(Role.Validator)) keys.push(...getValidatorCloudAgentKeys(agentConfig)); - if ((agentConfig.rolesWithKeys ?? []).includes(Role.Kathy)) + if (agentConfig.rolesWithKeys.includes(Role.Kathy)) keys.push(...getKathyCloudAgentKeys(agentConfig)); for (const role of agentConfig.rolesWithKeys) { @@ -233,8 +260,3 @@ function addressesIdentifier( ) { return `${context}-${environment}-key-addresses`; } - -function isNotEthereumProtocolChain(chainName: ChainName) { - if (!chainMetadata[chainName]) throw new Error(`Unknown chain ${chainName}`); - return chainMetadata[chainName].protocol !== ProtocolType.Ethereum; -} diff --git a/typescript/infra/src/config/agent/agent.ts b/typescript/infra/src/config/agent/agent.ts index af11ea850c..d53e68e056 100644 --- a/typescript/infra/src/config/agent/agent.ts +++ b/typescript/infra/src/config/agent/agent.ts @@ -4,7 +4,9 @@ import { AgentSignerKeyType, ChainName, RpcConsensusType, + chainMetadata, } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { Contexts } from '../../../config/contexts'; import { AgentChainNames, Role } from '../../roles'; @@ -94,7 +96,11 @@ interface AgentRoleConfig { export type AwsKeyConfig = Required; // only require specifying that it's the "hex" type for helm since the hex key will be pulled from secrets. export type HexKeyConfig = { type: AgentSignerKeyType.Hex }; -export type KeyConfig = AwsKeyConfig | HexKeyConfig; +export type CosmosKeyConfig = { + type: AgentSignerKeyType.Cosmos; + prefix: string; +}; +export type KeyConfig = AwsKeyConfig | HexKeyConfig | CosmosKeyConfig; interface IndexingConfig { from: number; @@ -182,3 +188,22 @@ export abstract class AgentConfigHelper export const allAgentChainNames = (agentChainNames: AgentChainNames) => [ ...new Set(Object.values(agentChainNames).reduce((a, b) => a.concat(b), [])), ]; + +// Returns the default KeyConfig for the `chainName`'s chain signer. +// For Ethereum or Sealevel, this is a hexKey, for Cosmos, this is a cosmosKey. +export function defaultChainSignerKeyConfig(chainName: ChainName): KeyConfig { + const metadata = chainMetadata[chainName]; + + switch (metadata?.protocol) { + case ProtocolType.Cosmos: + if (metadata.bech32Prefix === undefined) { + throw new Error(`Bech32 prefix for cosmos chain ${name} is undefined`); + } + return { type: AgentSignerKeyType.Cosmos, prefix: metadata.bech32Prefix }; + // For Ethereum and Sealevel, use a hex key + case ProtocolType.Ethereum: + case ProtocolType.Sealevel: + default: + return { type: AgentSignerKeyType.Hex }; + } +} diff --git a/typescript/infra/src/config/agent/relayer.ts b/typescript/infra/src/config/agent/relayer.ts index a4e4f2a1ee..b7821c47c7 100644 --- a/typescript/infra/src/config/agent/relayer.ts +++ b/typescript/infra/src/config/agent/relayer.ts @@ -16,7 +16,12 @@ import { AgentAwsUser } from '../../agents/aws'; import { Role } from '../../roles'; import { HelmStatefulSetValues } from '../infrastructure'; -import { AgentConfigHelper, KeyConfig, RootAgentConfig } from './agent'; +import { + AgentConfigHelper, + KeyConfig, + RootAgentConfig, + defaultChainSignerKeyConfig, +} from './agent'; export { GasPaymentEnforcement as GasPaymentEnforcementConfig } from '@hyperlane-xyz/sdk'; @@ -60,7 +65,7 @@ export class RelayerConfigHelper extends AgentConfigHelper { const baseConfig = this.#relayerConfig!; const relayerConfig: RelayerConfig = { - relayChains: this.contextChainNames[Role.Relayer].join(','), + relayChains: this.relayChains.join(','), gasPaymentEnforcement: JSON.stringify(baseConfig.gasPaymentEnforcement), }; @@ -84,6 +89,8 @@ export class RelayerConfigHelper extends AgentConfigHelper { // Get the signer configuration for each chain by the chain name. async signers(): Promise> { + let chainSigners: ChainMap = {}; + if (this.aws) { const awsUser = new AgentAwsUser( this.runEnv, @@ -93,25 +100,24 @@ export class RelayerConfigHelper extends AgentConfigHelper { ); await awsUser.createIfNotExists(); const awsKey = (await awsUser.createKeyIfNotExists(this)).keyConfig; - return Object.fromEntries( - this.contextChainNames[Role.Relayer].map((name) => { - const chain = chainMetadata[name]; - // Sealevel chains always use hex keys - if (chain?.protocol == ProtocolType.Sealevel) { - return [name, { type: AgentSignerKeyType.Hex }]; - } else { - return [name, awsKey]; - } - }), - ); - } else { - return Object.fromEntries( - this.contextChainNames[Role.Relayer].map((name) => [ - name, - { type: AgentSignerKeyType.Hex }, - ]), - ); + + // AWS keys only work for Ethereum chains + for (const chainName of this.relayChains) { + if (chainMetadata[chainName].protocol === ProtocolType.Ethereum) { + chainSigners[chainName] = awsKey; + } + } } + + // For any chains that were not configured with AWS keys, fill in the defaults + for (const chainName of this.relayChains) { + if (chainSigners[chainName] !== undefined) { + continue; + } + chainSigners[chainName] = defaultChainSignerKeyConfig(chainName); + } + + return chainSigners; } // Returns whether the relayer requires AWS credentials @@ -130,6 +136,10 @@ export class RelayerConfigHelper extends AgentConfigHelper { get role(): Role { return Role.Relayer; } + + get relayChains(): Array { + return this.contextChainNames[Role.Relayer]; + } } // Create a matching list for the given router addresses diff --git a/typescript/infra/src/config/agent/validator.ts b/typescript/infra/src/config/agent/validator.ts index 380ebef111..cfc8a57d20 100644 --- a/typescript/infra/src/config/agent/validator.ts +++ b/typescript/infra/src/config/agent/validator.ts @@ -4,13 +4,20 @@ import { ValidatorConfig as AgentValidatorConfig, ChainMap, ChainName, + chainMetadata, } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; -import { ValidatorAgentAwsUser } from '../../agents/aws'; +import { AgentAwsUser, ValidatorAgentAwsUser } from '../../agents/aws'; import { Role } from '../../roles'; import { HelmStatefulSetValues } from '../infrastructure'; -import { AgentConfigHelper, KeyConfig, RootAgentConfig } from './agent'; +import { + AgentConfigHelper, + KeyConfig, + RootAgentConfig, + defaultChainSignerKeyConfig, +} from './agent'; // Validator agents for each chain. export type ValidatorBaseChainConfigMap = ChainMap; @@ -33,11 +40,13 @@ export interface ValidatorBaseConfig { export interface ValidatorConfig { interval: number; - reorgPeriod: number; originChainName: ChainName; validators: Array<{ checkpointSyncer: CheckpointSyncerConfig; + // The key that signs checkpoints validator: KeyConfig; + // The key that signs txs (e.g. self-announcements) + chainSigner: KeyConfig | undefined; }>; } @@ -88,7 +97,6 @@ export class ValidatorConfigHelper extends AgentConfigHelper { async buildConfig(): Promise { return { interval: this.#chainConfig.interval, - reorgPeriod: this.#chainConfig.reorgPeriod, originChainName: this.chainName!, validators: await Promise.all( this.#chainConfig.validators.map((val, i) => @@ -110,7 +118,12 @@ export class ValidatorConfigHelper extends AgentConfigHelper { cfg: ValidatorBaseConfig, idx: number, ): Promise { + const metadata = chainMetadata[this.chainName]; + const protocol = metadata.protocol; + let validator: KeyConfig = { type: AgentSignerKeyType.Hex }; + let chainSigner: KeyConfig | undefined = undefined; + if (cfg.checkpointSyncer.type == CheckpointSyncerType.S3) { const awsUser = new ValidatorAgentAwsUser( this.runEnv, @@ -123,17 +136,29 @@ export class ValidatorConfigHelper extends AgentConfigHelper { await awsUser.createIfNotExists(); await awsUser.createBucketIfNotExists(); - if (this.aws) + if (this.aws) { validator = (await awsUser.createKeyIfNotExists(this)).keyConfig; + + // AWS-based chain signer keys are only used for Ethereum + if (protocol === ProtocolType.Ethereum) { + chainSigner = validator; + } + } } else { console.warn( `Validator ${cfg.address}'s checkpoint syncer is not S3-based. Be sure this is a non-k8s-based environment!`, ); } + // If the chainSigner isn't set to the AWS-based key above, then set the default. + if (chainSigner === undefined) { + chainSigner = defaultChainSignerKeyConfig(this.chainName); + } + return { checkpointSyncer: cfg.checkpointSyncer, validator, + chainSigner, }; } diff --git a/typescript/infra/src/utils/utils.ts b/typescript/infra/src/utils/utils.ts index 1a8a79f8e5..92db7a7f56 100644 --- a/typescript/infra/src/utils/utils.ts +++ b/typescript/infra/src/utils/utils.ts @@ -11,7 +11,7 @@ import { CoreChainName, chainMetadata, } from '@hyperlane-xyz/sdk'; -import { objMerge } from '@hyperlane-xyz/utils'; +import { ProtocolType, objMerge } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts'; import { Role } from '../roles'; @@ -273,3 +273,8 @@ export function mustGetChainNativeTokenDecimals(chain: ChainName): number { } return metadata.nativeToken.decimals; } + +export function isNotEthereumProtocolChain(chainName: ChainName) { + if (!chainMetadata[chainName]) throw new Error(`Unknown chain ${chainName}`); + return chainMetadata[chainName].protocol !== ProtocolType.Ethereum; +} diff --git a/typescript/sdk/src/consts/chainMetadata.ts b/typescript/sdk/src/consts/chainMetadata.ts index 4644133b28..4285126c00 100644 --- a/typescript/sdk/src/consts/chainMetadata.ts +++ b/typescript/sdk/src/consts/chainMetadata.ts @@ -807,7 +807,7 @@ export const neutron: ChainMetadata = { ], blocks: { confirmations: 1, - reorgPeriod: 0, + reorgPeriod: 1, estimateBlockTime: 3, }, blockExplorers: [ @@ -1008,6 +1008,31 @@ export const polygonzkevm: ChainMetadata = { }, }; +export const neutrontestnet: ChainMetadata = { + protocol: ProtocolType.Cosmos, + domainId: 33333, + chainId: 'duality-devnet', + name: Chains.neutrontestnet, + displayName: 'Neutron Testnet', + nativeToken: { + name: 'Neutron', + symbol: 'NTRN', + decimals: 6, + }, + blocks: { + confirmations: 1, + reorgPeriod: 1, + estimateBlockTime: 3, + }, + // First URL RPC, second REST + rpcUrls: [ + { http: 'http://54.149.31.83:26657' }, + { http: 'http://54.149.31.83:1317' }, + ], + bech32Prefix: 'dual', + isTestnet: true, +}; + /** * Collection maps * @@ -1051,6 +1076,7 @@ export const chainMetadata: ChainMap = { solanatestnet, solanadevnet, nautilus, + neutrontestnet, }; export const chainIdToMetadata = Object.values(chainMetadata).reduce< diff --git a/typescript/sdk/src/consts/chains.ts b/typescript/sdk/src/consts/chains.ts index b73faf0a0c..65bd49820e 100644 --- a/typescript/sdk/src/consts/chains.ts +++ b/typescript/sdk/src/consts/chains.ts @@ -35,6 +35,7 @@ export enum Chains { proteustestnet = 'proteustestnet', solana = 'solana', solanadevnet = 'solanadevnet', + neutrontestnet = 'neutrontestnet', test1 = 'test1', test2 = 'test2', test3 = 'test3', diff --git a/typescript/sdk/src/consts/environments/mainnet.json b/typescript/sdk/src/consts/environments/mainnet.json index f9dea3adb4..b1f9a4678e 100644 --- a/typescript/sdk/src/consts/environments/mainnet.json +++ b/typescript/sdk/src/consts/environments/mainnet.json @@ -178,5 +178,21 @@ "protocolFee": "0xEc4AdA26E51f2685279F37C8aE62BeAd8212D597", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", "validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f" + }, + "mantapacific": { + "merkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "messageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "aggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "aggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "routingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", + "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F", + "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" } } diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index 7cda9b5e23..695fe2ab45 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -101,12 +101,16 @@ export const defaultMultisigConfigs: ChainMap = { '0x57231619fea13d85270ca6943298046c75a6dd01', // everstake ], }, - solana: { - threshold: 2, + mantapacific: { + threshold: 5, validators: [ - '0x3cd1a081f38874bbb075bf10b62adcb858db864c', // abacus - '0x2b0c45f6111ae1c1684d4287792e3bd6ebd1abcc', // ZKV - '0x7b9ec253a8ba38994457eb9dbe386938d545351a', // everstake + '0x8e668c97ad76d0e28375275c41ece4972ab8a5bc', //abacusworks + '0x521a3e6bf8d24809fde1c1fd3494a859a16f132c', //cosmostation + '0x14025fe092f5f8a401dd9819704d9072196d2125', //p2p + '0x25b9a0961c51e74fd83295293bc029131bf1e05a', //neutron + '0xa0eE95e280D46C14921e524B075d0C341e7ad1C8', //cosmos spaces + '0xcc9a0b6de7fe314bd99223687d784730a75bb957', //dsrv + '0x42b6de2edbaa62c2ea2309ad85d20b3e37d38acf', //sg-1 ], }, // ----------------- Testnets ----------------- @@ -231,4 +235,20 @@ export const defaultMultisigConfigs: ChainMap = { '0x967c5ecdf2625ae86580bd203b630abaaf85cd62', ], }, + neutrontestnet: { + threshold: 2, + validators: [ + '0x5d2a99d67cd294a821de4fb25da6901ea8f89814', + '0xb57486243ce3bb3c38c50a582b8bbd20cb393589', + '0x661faee997654d14ead4ae48035883f05c3150cf', + ], + }, + neutron: { + threshold: 2, + validators: [ + '0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0', + '0x60e890b34cb44ce3fa52f38684f613f31b47a1a6', + '0x7885fae56dbcf5176657f54adbbd881dc6714132', + ], + }, }; diff --git a/typescript/sdk/src/metadata/agentConfig.ts b/typescript/sdk/src/metadata/agentConfig.ts index 8431ea7385..ea4f8270cc 100644 --- a/typescript/sdk/src/metadata/agentConfig.ts +++ b/typescript/sdk/src/metadata/agentConfig.ts @@ -4,6 +4,8 @@ */ import { z } from 'zod'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + import { MultiProvider } from '../providers/MultiProvider'; import { ChainMap, ChainName } from '../types'; @@ -46,6 +48,7 @@ export enum AgentSignerKeyType { Aws = 'aws', Hex = 'hexKey', Node = 'node', + Cosmos = 'cosmosKey', } const AgentSignerHexKeySchema = z @@ -63,6 +66,13 @@ const AgentSignerAwsKeySchema = z .describe( 'An AWS signer. Note that AWS credentials must be inserted into the env separately.', ); +const AgentSignerCosmosKeySchema = z + .object({ + type: z.literal(AgentSignerKeyType.Cosmos), + prefix: z.string().describe('The bech32 prefix for the cosmos address'), + key: ZHash, + }) + .describe('Cosmos key'); const AgentSignerNodeSchema = z .object({ type: z.literal(AgentSignerKeyType.Node), @@ -72,47 +82,82 @@ const AgentSignerNodeSchema = z const AgentSignerSchema = z.union([ AgentSignerHexKeySchema, AgentSignerAwsKeySchema, + AgentSignerCosmosKeySchema, AgentSignerNodeSchema, ]); export type AgentSignerHexKey = z.infer; export type AgentSignerAwsKey = z.infer; +export type AgentSignerCosmosKey = z.infer; export type AgentSignerNode = z.infer; export type AgentSigner = z.infer; export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge( HyperlaneDeploymentArtifactsSchema, -).extend({ - customRpcUrls: z - .string() - .optional() - .describe( - 'Specify a comma seperated list of custom RPC URLs to use for this chain. If not specified, the default RPC urls will be used.', - ), - rpcConsensusType: z - .nativeEnum(RpcConsensusType) - .describe('The consensus type to use when multiple RPCs are configured.') - .optional(), - signer: AgentSignerSchema.optional().describe( - 'The signer to use for this chain', - ), - index: z - .object({ - from: ZUint.optional().describe( - 'The starting block from which to index events.', - ), - chunk: ZNzUint.optional().describe( - 'The number of blocks to index at a time.', +) + .extend({ + customRpcUrls: z + .string() + .optional() + .describe( + 'Specify a comma seperated list of custom RPC URLs to use for this chain. If not specified, the default RPC urls will be used.', ), - mode: z - .nativeEnum(AgentIndexMode) - .optional() - .describe( - 'The indexing method to use for this chain; will attempt to choose a suitable default if not specified.', + rpcConsensusType: z + .nativeEnum(RpcConsensusType) + .describe('The consensus type to use when multiple RPCs are configured.') + .optional(), + signer: AgentSignerSchema.optional().describe( + 'The signer to use for this chain', + ), + index: z + .object({ + from: ZUint.optional().describe( + 'The starting block from which to index events.', ), - }) - .optional(), -}); + chunk: ZNzUint.optional().describe( + 'The number of blocks to index at a time.', + ), + mode: z + .nativeEnum(AgentIndexMode) + .optional() + .describe( + 'The indexing method to use for this chain; will attempt to choose a suitable default if not specified.', + ), + }) + .optional(), + }) + .refine((metadata) => { + // Make sure that the signer is valid for the protocol + + const signerType = metadata.signer?.type; + + // If no signer is specified, no validation is needed + if (signerType === undefined) { + return true; + } + + switch (metadata.protocol) { + case ProtocolType.Ethereum: + return [ + AgentSignerKeyType.Hex, + signerType === AgentSignerKeyType.Aws, + signerType === AgentSignerKeyType.Node, + ].includes(signerType); + + case ProtocolType.Cosmos: + return [AgentSignerKeyType.Cosmos].includes(signerType); + + case ProtocolType.Sealevel: + return [AgentSignerKeyType.Hex].includes(signerType); + + case ProtocolType.Fuel: + return [AgentSignerKeyType.Hex].includes(signerType); + + default: + // Just default to true if we don't know the protocol + return true; + } + }); export type AgentChainMetadata = z.infer; diff --git a/yarn.lock b/yarn.lock index f110662ba6..07216c05e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4335,6 +4335,7 @@ __metadata: "@aws-sdk/client-iam": "npm:^3.74.0" "@aws-sdk/client-kms": "npm:3.48.0" "@aws-sdk/client-s3": "npm:^3.74.0" + "@cosmjs/amino": "npm:^0.31.3" "@eth-optimism/sdk": "npm:^1.7.0" "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0"