From 3222a0204bdfacac2c723ce2c78879a7130d6579 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Tue, 28 Nov 2023 10:46:37 +0000 Subject: [PATCH 1/6] Deploy relayer with polygon gas oracle fix (#2966) ### Description using image from https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/2965 ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- typescript/infra/config/environments/mainnet3/agent.ts | 2 +- typescript/infra/config/environments/testnet4/agent.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index f190ad7731..c867c33a19 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -42,7 +42,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '1bee32a-20231121-121303', + tag: '48feaf4-20231122-200632', }, gasPaymentEnforcement, }, diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index 4d1019d648..cb564c7216 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -49,7 +49,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '1bee32a-20231121-121303', + tag: '48feaf4-20231122-200632', }, blacklist: [ ...releaseCandidateHelloworldMatchingList, From 9b8ad55a6a8472c34bd79beb92ba31750fe1f139 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Tue, 28 Nov 2023 11:08:30 +0000 Subject: [PATCH 2/6] Update ethers-rs to use lower max priority fee (#2986) ### Description Cherry-picked the commit from https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/2903, which is now out of date ### Drive-by changes n/a ### Related issues n/a ### Backward compatibility yes ### Testing we're running the relayer with an image off of https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/2903 Co-authored-by: -f --- rust/Cargo.lock | 20 ++++++++++---------- rust/Cargo.toml | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 8d1b909db0..a82f6b9edc 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2616,7 +2616,7 @@ dependencies = [ [[package]] name = "ethers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2630,7 +2630,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "ethers-core", "once_cell", @@ -2641,7 +2641,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -2659,7 +2659,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "Inflector", "cfg-if", @@ -2683,7 +2683,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "ethers-contract-abigen", "ethers-core", @@ -2697,7 +2697,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "arrayvec", "bytes", @@ -2727,7 +2727,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "ethers-core", "getrandom 0.2.11", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "async-trait", "auto_impl 0.5.0", @@ -2789,7 +2789,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "async-trait", "auto_impl 1.1.0", @@ -2825,7 +2825,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-06-01#6c26645cc1707a3d9c987a603a513fb38a5edb3f" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2023-11-08#938e9a68466cb6bae689cdd40746280ec85e4955" dependencies = [ "async-trait", "coins-bip32", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c1ee840430..17a087e8e4 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -183,27 +183,27 @@ cosmwasm-schema = "1.2.7" [workspace.dependencies.ethers] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-08" [workspace.dependencies.ethers-contract] features = ["legacy"] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-08" [workspace.dependencies.ethers-core] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-08" [workspace.dependencies.ethers-providers] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-08" [workspace.dependencies.ethers-signers] features = ["aws"] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2023-06-01" +tag = "2023-11-08" [patch.crates-io.curve25519-dalek] branch = "v3.2.2-relax-zeroize" From 50aed865172364bde600abc7abd8b9ed54e1a3ed Mon Sep 17 00:00:00 2001 From: ByeongSu Hong Date: Tue, 28 Nov 2023 19:02:30 +0700 Subject: [PATCH 3/6] fix: change domain id of cosmos localnet to random (#2977) ### Description We, Mitosis team will be using 26657 as the chain id, so it's good to change now ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- rust/hyperlane-core/src/chain.rs | 8 ++--- rust/utils/run-locally/src/cosmos/link.rs | 6 ++-- rust/utils/run-locally/src/cosmos/mod.rs | 36 +++++++++++++--------- rust/utils/run-locally/src/cosmos/types.rs | 2 ++ 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/rust/hyperlane-core/src/chain.rs b/rust/hyperlane-core/src/chain.rs index 94c2c5c44e..ad28765a5f 100644 --- a/rust/hyperlane-core/src/chain.rs +++ b/rust/hyperlane-core/src/chain.rs @@ -101,8 +101,8 @@ pub enum KnownHyperlaneDomain { ScrollSepolia = 534351, /// Cosmos local chains - CosmosTest26657 = 26657, - CosmosTest26658 = 26658, + CosmosTest99990 = 99990, + CosmosTest99991 = 99991, } #[derive(Clone)] @@ -200,7 +200,7 @@ impl KnownHyperlaneDomain { Goerli, Mumbai, Fuji, ArbitrumGoerli, OptimismGoerli, BinanceSmartChainTestnet, Alfajores, MoonbaseAlpha, Sepolia, PolygonZkEvmTestnet, LineaGoerli, BaseGoerli, ScrollSepolia, Chiado ], - LocalTestChain: [Test1, Test2, Test3, FuelTest1, SealevelTest1, SealevelTest2, CosmosTest26657, CosmosTest26658], + LocalTestChain: [Test1, Test2, Test3, FuelTest1, SealevelTest1, SealevelTest2, CosmosTest99990, CosmosTest99991], }) } @@ -215,7 +215,7 @@ impl KnownHyperlaneDomain { ], HyperlaneDomainProtocol::Fuel: [FuelTest1], HyperlaneDomainProtocol::Sealevel: [SealevelTest1, SealevelTest2], - HyperlaneDomainProtocol::Cosmos: [CosmosTest26657, CosmosTest26658], + HyperlaneDomainProtocol::Cosmos: [CosmosTest99990, CosmosTest99991], }) } } diff --git a/rust/utils/run-locally/src/cosmos/link.rs b/rust/utils/run-locally/src/cosmos/link.rs index 1e65aa4b25..1cd1efe287 100644 --- a/rust/utils/run-locally/src/cosmos/link.rs +++ b/rust/utils/run-locally/src/cosmos/link.rs @@ -93,10 +93,10 @@ fn link_network( ) { let validator_addr = validator.addr(hrp); - let dest_domain = if network.domain == 26657 { - 26658 + let dest_domain = if network.domain == 99990 { + 99991 } else { - 26657 + 99990 }; // hook routing diff --git a/rust/utils/run-locally/src/cosmos/mod.rs b/rust/utils/run-locally/src/cosmos/mod.rs index 1ecb26dc0c..e60b98e01f 100644 --- a/rust/utils/run-locally/src/cosmos/mod.rs +++ b/rust/utils/run-locally/src/cosmos/mod.rs @@ -173,6 +173,7 @@ pub struct CosmosNetwork { pub launch_resp: CosmosResp, pub deployments: Deployments, pub chain_id: String, + pub metrics_port: u32, pub domain: u32, } @@ -182,17 +183,17 @@ impl Drop for CosmosNetwork { } } -impl From<(CosmosResp, Deployments, String, u32)> for CosmosNetwork { - fn from(v: (CosmosResp, Deployments, String, u32)) -> Self { +impl From<(CosmosResp, Deployments, String, u32, u32)> for CosmosNetwork { + fn from(v: (CosmosResp, Deployments, String, u32, u32)) -> Self { Self { launch_resp: v.0, deployments: v.1, chain_id: v.2, - domain: v.3, + metrics_port: v.3, + domain: v.4, } } } - pub struct CosmosHyperlaneStack { pub validators: Vec, pub relayer: AgentHandles, @@ -258,7 +259,7 @@ fn launch_cosmos_validator( .hyp_env("ORIGINCHAINNAME", agent_config.name) .hyp_env("REORGPERIOD", "100") .hyp_env("DB", validator_base_db.to_str().unwrap()) - .hyp_env("METRICSPORT", agent_config.domain_id.to_string()) + .hyp_env("METRICSPORT", agent_config.metrics_port.to_string()) .hyp_env("VALIDATOR_SIGNER_TYPE", agent_config.signer.typ) .hyp_env("VALIDATOR_KEY", agent_config.signer.key.clone()) .hyp_env("VALIDATOR_PREFIX", "osmo") @@ -274,6 +275,7 @@ fn launch_cosmos_validator( fn launch_cosmos_relayer( agent_config_path: PathBuf, relay_chains: Vec, + metrics: u32, debug: bool, ) -> AgentHandles { let relayer_bin = concat_path(format!("../../{AGENT_BIN_PATH}"), "relayer"); @@ -290,7 +292,7 @@ fn launch_cosmos_relayer( .hyp_env("ALLOWLOCALCHECKPOINTSYNCERS", "true") .hyp_env("TRACING_LEVEL", if debug { "debug" } else { "info" }) .hyp_env("GASPAYMENTENFORCEMENT", "[{\"type\": \"none\"}]") - .hyp_env("METRICSPORT", 9093.to_string()) + .hyp_env("METRICSPORT", metrics.to_string()) .spawn("RLY"); relayer @@ -347,7 +349,8 @@ fn run_locally() { }; let port_start = 26600u32; - let domain_start = 26657u32; + let metrics_port_start = 9090u32; + let domain_start = 99990u32; let node_count = 2; let nodes = (0..node_count) @@ -355,10 +358,11 @@ fn run_locally() { ( launch_cosmos_node(CosmosConfig { node_port_base: port_start + (i * 10), - chain_id: format!("cosmos-test-{}", i + 26657), + chain_id: format!("cosmos-test-{}", i + domain_start), ..default_config.clone() }), - format!("cosmos-test-{}", i + 26657), + format!("cosmos-test-{}", i + domain_start), + metrics_port_start + i, domain_start + i, ) }) @@ -371,8 +375,8 @@ fn run_locally() { let nodes = nodes .into_iter() - .map(|v| (v.0.join(), v.1, v.2)) - .map(|(launch_resp, chain_id, domain)| { + .map(|v| (v.0.join(), v.1, v.2, v.3)) + .map(|(launch_resp, chain_id, metrics_port, domain)| { let deployments = deploy_cw_hyperlane( launch_resp.cli(&osmosisd), launch_resp.endpoint.clone(), @@ -381,14 +385,14 @@ fn run_locally() { domain, ); - (launch_resp, deployments, chain_id, domain) + (launch_resp, deployments, chain_id, metrics_port, domain) }) .collect::>(); // nodes with base deployments let nodes = nodes .into_iter() - .map(|v| (v.0, v.1.join(), v.2, v.3)) + .map(|v| (v.0, v.1.join(), v.2, v.3, v.4)) .map(|v| v.into()) .collect::>(); @@ -448,9 +452,11 @@ fn run_locally() { .into_values() .map(|agent_config| launch_cosmos_validator(agent_config, agent_config_path.clone(), debug)) .collect::>(); + let hpl_rly_metrics_port = metrics_port_start + node_count + 1u32; let hpl_rly = launch_cosmos_relayer( agent_config_path, agent_config_out.chains.into_keys().collect::>(), + hpl_rly_metrics_port, debug, ); @@ -516,7 +522,7 @@ fn run_locally() { let mut failure_occurred = false; loop { // look for the end condition. - if termination_invariants_met(dispatched_messages).unwrap_or(false) { + if termination_invariants_met(hpl_rly_metrics_port, dispatched_messages).unwrap_or(false) { // end condition reached successfully break; } else if (Instant::now() - loop_start).as_secs() > TIMEOUT_SECS { @@ -536,7 +542,7 @@ fn run_locally() { } } -fn termination_invariants_met(_messages_expected: u32) -> eyre::Result { +fn termination_invariants_met(_metrics_port: u32, _messages_expected: u32) -> eyre::Result { Ok(true) // TODO: uncomment once CI passes consistently on Ubuntu // let gas_payments_scraped = fetch_metric( diff --git a/rust/utils/run-locally/src/cosmos/types.rs b/rust/utils/run-locally/src/cosmos/types.rs index 8632687b15..138cd3522d 100644 --- a/rust/utils/run-locally/src/cosmos/types.rs +++ b/rust/utils/run-locally/src/cosmos/types.rs @@ -113,6 +113,7 @@ pub struct AgentUrl { pub struct AgentConfig { pub name: String, pub domain_id: u32, + pub metrics_port: u32, pub mailbox: String, pub interchain_gas_paymaster: String, pub validator_announce: String, @@ -144,6 +145,7 @@ impl AgentConfig { AgentConfig { name: format!("cosmostest{}", network.domain), domain_id: network.domain, + metrics_port: network.metrics_port, mailbox: to_hex_addr(&network.deployments.mailbox), interchain_gas_paymaster: to_hex_addr(&network.deployments.igp), validator_announce: to_hex_addr(&network.deployments.va), From df693708b649c483b2b4dba74f4e351c372fac0f Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Tue, 28 Nov 2023 10:40:43 -0500 Subject: [PATCH 4/6] feat:allow full IsmConfig for CLI as an advanced config feature (#2939) ### Description - added `ism-advanced` config which allows you flexibility to define your own IsmConfig shape - configure through `hyperlane config create ism-advanced` - core deployments checks for both `ism-advanced.yaml` and then `ism.yaml` (old multisig config way) ie. precedence is given to the advanced version - support for `Aggregation`, `Routing`, `MerkleRoot`, `MessageId`, and `TestIsm` (with a warning) - instead of routing over just messageId, the simpler `ism` config way is now routing over aggregation of merkle and message matching hyperlane V3 core deployments - also renamed multisig option to ism to not introduce multisig user-side and stick with consistent ism or ism- options. ### Drive-by changes - `ism.yaml` doesn't take in a type since we provide both `merkleRoot` and `messageId` variants by default. ### Related issues - fixes https://github.com/hyperlane-xyz/issues/issues/737 ### Backward compatibility Yes ### Testing Manual --------- Co-authored-by: Yorke Rhodes --- .changeset/tiny-teachers-accept.md | 6 + typescript/cli/ci-test.sh | 2 +- typescript/cli/examples/ism-advanced.yaml | 33 +++ .../examples/{multisig-ism.yaml => ism.yaml} | 8 - typescript/cli/src/commands/config.ts | 53 +++- typescript/cli/src/commands/deploy.ts | 2 +- typescript/cli/src/config/hooks.ts | 4 +- typescript/cli/src/config/ism.ts | 268 ++++++++++++++++++ typescript/cli/src/config/multisig.ts | 46 +-- typescript/cli/src/deploy/core.ts | 119 +++++--- typescript/cli/src/deploy/utils.ts | 21 +- typescript/cli/src/tests/ism.test.ts | 85 ++++++ .../tests/ism/routing-same-chain-fail.yaml | 33 +++ .../cli/src/tests/ism/safe-parse-fail.yaml | 32 +++ .../ism/threshold-gt-modules-length-fail.yaml | 33 +++ .../src/tests/ism/wrong-ism-type-fail.yaml | 33 +++ typescript/cli/src/tests/multisig.test.ts | 41 +++ .../tests/multisig/invalid-address-fail.yaml | 8 + .../src/tests/multisig/safe-parse-fail.yaml | 6 + .../src/tests/multisig/threshold-gt-fail.yaml | 8 + typescript/cli/src/utils/chains.ts | 13 +- typescript/sdk/src/index.ts | 5 +- typescript/sdk/src/ism/multisig.ts | 35 ++- yarn.lock | 24 +- 24 files changed, 832 insertions(+), 86 deletions(-) create mode 100644 .changeset/tiny-teachers-accept.md create mode 100644 typescript/cli/examples/ism-advanced.yaml rename typescript/cli/examples/{multisig-ism.yaml => ism.yaml} (65%) create mode 100644 typescript/cli/src/config/ism.ts create mode 100644 typescript/cli/src/tests/ism.test.ts create mode 100644 typescript/cli/src/tests/ism/routing-same-chain-fail.yaml create mode 100644 typescript/cli/src/tests/ism/safe-parse-fail.yaml create mode 100644 typescript/cli/src/tests/ism/threshold-gt-modules-length-fail.yaml create mode 100644 typescript/cli/src/tests/ism/wrong-ism-type-fail.yaml create mode 100644 typescript/cli/src/tests/multisig.test.ts create mode 100644 typescript/cli/src/tests/multisig/invalid-address-fail.yaml create mode 100644 typescript/cli/src/tests/multisig/safe-parse-fail.yaml create mode 100644 typescript/cli/src/tests/multisig/threshold-gt-fail.yaml diff --git a/.changeset/tiny-teachers-accept.md b/.changeset/tiny-teachers-accept.md new file mode 100644 index 0000000000..010544d76f --- /dev/null +++ b/.changeset/tiny-teachers-accept.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Add support for all ISM types in CLI interactive config creation diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index de3d887cc6..3ae496f278 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -38,7 +38,7 @@ yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --targets anvil1,anvil2 \ --chains ./examples/anvil-chains.yaml \ --artifacts /tmp/empty-artifacts.json \ - --ism ./examples/multisig-ism.yaml \ + --ism ./examples/ism.yaml \ --hook ./examples/hook-config.yaml \ --out /tmp \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ diff --git a/typescript/cli/examples/ism-advanced.yaml b/typescript/cli/examples/ism-advanced.yaml new file mode 100644 index 0000000000..8124102a50 --- /dev/null +++ b/typescript/cli/examples/ism-advanced.yaml @@ -0,0 +1,33 @@ +anvil1: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/ism.yaml similarity index 65% rename from typescript/cli/examples/multisig-ism.yaml rename to typescript/cli/examples/ism.yaml index dd7c65864e..ac292bf687 100644 --- a/typescript/cli/examples/multisig-ism.yaml +++ b/typescript/cli/examples/ism.yaml @@ -1,21 +1,13 @@ # A config for a multisig Interchain Security Module (ISM) # Schema: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/ism/types.ts # -# Valid module types: -# routing -# aggregation -# merkleRootMultisig -# messageIdMultisigIsm -# ism type don't work currently (sets to messageIdMultisigIsm for all) --- anvil1: - type: 'messageIdMultisigIsm' threshold: 1 # Number: Signatures required to approve a message validators: # Array: List of validator addresses - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' anvil2: - type: 'messageIdMultisigIsm' threshold: 1 validators: - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 5ef6e1b508..64bfec5b1b 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -3,6 +3,7 @@ import { CommandModule } from 'yargs'; import { log, logGreen } from '../../logger.js'; import { createChainConfig, readChainConfigs } from '../config/chain.js'; import { createHookConfig } from '../config/hooks.js'; +import { createIsmConfigMap, readIsmConfig } from '../config/ism.js'; import { createMultisigConfig, readMultisigConfig, @@ -40,7 +41,7 @@ const createCommand: CommandModule = { builder: (yargs) => yargs .command(createChainConfigCommand) - .command(createMultisigConfigCommand) + .command(createIsmConfigCommand) .command(createHookConfigCommand) .command(createWarpConfigCommand) .version(false) @@ -64,20 +65,32 @@ const createChainConfigCommand: CommandModule = { }, }; -const createMultisigConfigCommand: CommandModule = { - command: 'multisig', - describe: 'Create a new Multisig ISM config', +const createIsmConfigCommand: CommandModule = { + command: 'ism', + describe: 'Create a basic or advanced ISM config for a validator set', builder: (yargs) => yargs.options({ - output: outputFileOption('./configs/multisig-ism.yaml'), + output: outputFileOption('./configs/ism.yaml'), format: fileFormatOption, chains: chainsCommandOption, + advanced: { + type: 'boolean', + describe: 'Create an advanced ISM configuration', + default: false, + }, }), handler: async (argv: any) => { const format: FileFormat = argv.format; const outPath: string = argv.output; const chainConfigPath: string = argv.chains; - await createMultisigConfig({ format, outPath, chainConfigPath }); + const isAdvanced: boolean = argv.advanced; + + if (isAdvanced) { + await createIsmConfigMap({ format, outPath, chainConfigPath }); + } else { + await createMultisigConfig({ format, outPath, chainConfigPath }); + } + process.exit(0); }, }; @@ -127,7 +140,8 @@ const validateCommand: CommandModule = { builder: (yargs) => yargs .command(validateChainCommand) - .command(validateMultisigCommand) + .command(validateIsmCommand) + .command(validateIsmAdvancedCommand) .command(validateWarpCommand) .version(false) .demandCommand(), @@ -152,9 +166,9 @@ const validateChainCommand: CommandModule = { }, }; -const validateMultisigCommand: CommandModule = { - command: 'multisig', - describe: 'Validate a multisig ism config in a YAML or JSON file', +const validateIsmCommand: CommandModule = { + command: 'ism', + describe: 'Validate the basic ISM config in a YAML or JSON file', builder: (yargs) => yargs.options({ path: { @@ -171,6 +185,25 @@ const validateMultisigCommand: CommandModule = { }, }; +const validateIsmAdvancedCommand: CommandModule = { + command: 'ism-advanced', + describe: 'Validate the advanced ISM config in a YAML or JSON file', + builder: (yargs) => + yargs.options({ + path: { + type: 'string', + description: 'Input file path', + demandOption: true, + }, + }), + handler: async (argv) => { + const path = argv.path as string; + readIsmConfig(path); + logGreen('Config is valid'); + process.exit(0); + }, +}; + const validateWarpCommand: CommandModule = { command: 'warp', describe: 'Validate a Warp Route config in a YAML or JSON file', diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 2e8b9f5148..fd51dcc64e 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -81,7 +81,7 @@ const coreCommand: CommandModule = { ism: { type: 'string', description: - 'A path to a JSON or YAML file with ISM configs (e.g. Multisig)', + 'A path to a JSON or YAML file with basic or advanced ISM configs (e.g. Multisig)', }, hook: { type: 'string', diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index 10f8f12b34..4449e1948f 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -116,7 +116,7 @@ export function presetHookConfigs( export function readHookConfig(filePath: string) { const config = readYamlOrJson(filePath); if (!config) { - logRed(`No multisig config found at ${filePath}`); + logRed(`No hook config found at ${filePath}`); return; } const result = HookConfigMapSchema.safeParse(config); @@ -134,7 +134,7 @@ export function readHookConfig(filePath: string) { type: config.default.type, } as HookConfig), ); - logGreen(`All multisig configs in ${filePath} are valid`); + logGreen(`All hook configs in ${filePath} are valid`); return defaultHook; } diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts new file mode 100644 index 0000000000..ab75581d88 --- /dev/null +++ b/typescript/cli/src/config/ism.ts @@ -0,0 +1,268 @@ +import { confirm, input, select } from '@inquirer/prompts'; +import { z } from 'zod'; + +import { ChainMap, ChainName, IsmType } from '@hyperlane-xyz/sdk'; + +import { errorRed, log, logBlue, logGreen } from '../../logger.js'; +import { runMultiChainSelectionStep } from '../utils/chains.js'; +import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; + +import { readChainConfigsIfExists } from './chain.js'; + +const MultisigIsmConfigSchema = z.object({ + type: z.union([ + z.literal(IsmType.MERKLE_ROOT_MULTISIG), + z.literal(IsmType.MESSAGE_ID_MULTISIG), + ]), + threshold: z.number(), + validators: z.array(z.string()), +}); + +const RoutingIsmConfigSchema: z.ZodSchema = z.lazy(() => + z.object({ + type: z.literal(IsmType.ROUTING), + owner: z.string(), + domains: z.record(IsmConfigSchema), + }), +); + +const AggregationIsmConfigSchema: z.ZodSchema = z + .lazy(() => + z.object({ + type: z.literal(IsmType.AGGREGATION), + modules: z.array(IsmConfigSchema), + threshold: z.number(), + }), + ) + .refine( + // check ig modules.length >= threshold + (ismConfig) => { + return ismConfig.modules.length >= ismConfig.threshold; + }, + { + message: 'Threshold cannot be greater than number of modules', + }, + ); + +const TestIsmConfigSchema = z.object({ + type: z.literal(IsmType.TEST_ISM), +}); + +const IsmConfigSchema = z.union([ + MultisigIsmConfigSchema, + RoutingIsmConfigSchema, + AggregationIsmConfigSchema, + TestIsmConfigSchema, +]); +const IsmConfigMapSchema = z.record(IsmConfigSchema).refine( + (ismConfigMap) => { + // check if any key in IsmConfigMap is found in its own RoutingIsmConfigSchema.domains + for (const [key, config] of Object.entries(ismConfigMap)) { + if (config.type === IsmType.ROUTING) { + if (config.domains && key in config.domains) { + return false; + } + } + } + return true; + }, + { + message: + 'Cannot set RoutingIsm.domain to the same chain you are configuring', + }, +); +export type ZodIsmConfig = z.infer; +export type ZodIsmConfigMap = z.infer; + +export function parseIsmConfig(filePath: string) { + const config = readYamlOrJson(filePath); + if (!config) throw new Error(`No ISM config found at ${filePath}`); + return IsmConfigMapSchema.safeParse(config); +} + +export function readIsmConfig(filePath: string) { + const result = parseIsmConfig(filePath); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid ISM config: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + const parsedConfig = result.data; + return parsedConfig; +} + +export function isValildIsmConfig(config: any) { + return IsmConfigMapSchema.safeParse(config).success; +} + +export async function createIsmConfigMap({ + format, + outPath, + chainConfigPath, +}: { + format: FileFormat; + outPath: string; + chainConfigPath: string; +}) { + logBlue('Creating a new ISM config'); + const customChains = readChainConfigsIfExists(chainConfigPath); + const chains = await runMultiChainSelectionStep(customChains); + + const result: ZodIsmConfigMap = {}; + for (const chain of chains) { + log(`Setting values for chain ${chain}`); + result[chain] = await createIsmConfig(chain, chainConfigPath); + + // TODO consider re-enabling. Disabling based on feedback from @nambrot for now. + // repeat = await confirm({ + // message: 'Use this same config for remaining chains?', + // }); + } + + if (isValildIsmConfig(result)) { + logGreen(`ISM config is valid, writing to file ${outPath}`); + mergeYamlOrJson(outPath, result, format); + } else { + errorRed( + `ISM config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/ism.yaml for an example`, + ); + throw new Error('Invalid ISM config'); + } +} + +export async function createIsmConfig( + chain: ChainName, + chainConfigPath: string, +): Promise { + let lastConfig: ZodIsmConfig; + const moduleType = await select({ + message: 'Select ISM type', + choices: [ + { + value: IsmType.MESSAGE_ID_MULTISIG, + name: IsmType.MESSAGE_ID_MULTISIG, + description: 'Validators need to sign just this messageId', + }, + { + value: IsmType.MERKLE_ROOT_MULTISIG, + name: IsmType.MERKLE_ROOT_MULTISIG, + description: + 'Validators need to sign the root of the merkle tree of all messages from origin chain', + }, + { + value: IsmType.ROUTING, + name: IsmType.ROUTING, + description: + 'Each origin chain can be verified by the specified ISM type via RoutingISM', + }, + { + value: IsmType.AGGREGATION, + name: IsmType.AGGREGATION, + description: + 'You can aggregate multiple ISMs into one ISM via AggregationISM', + }, + { + value: IsmType.TEST_ISM, + name: IsmType.TEST_ISM, + description: + 'ISM where you can deliver messages without any validation (WARNING: only for testing, do not use in production)', + }, + ], + pageSize: 10, + }); + if ( + moduleType === IsmType.MESSAGE_ID_MULTISIG || + moduleType === IsmType.MERKLE_ROOT_MULTISIG + ) { + lastConfig = await createMultisigConfig(moduleType); + } else if (moduleType === IsmType.ROUTING) { + lastConfig = await createRoutingConfig(chain, chainConfigPath); + } else if (moduleType === IsmType.AGGREGATION) { + lastConfig = await createAggregationConfig(chain, chainConfigPath); + } else if (moduleType === IsmType.TEST_ISM) { + lastConfig = { type: IsmType.TEST_ISM }; + } else { + throw new Error(`Invalid ISM type: ${moduleType}}`); + } + return lastConfig; +} + +export async function createMultisigConfig( + type: IsmType.MERKLE_ROOT_MULTISIG | IsmType.MESSAGE_ID_MULTISIG, +): Promise { + const thresholdInput = await input({ + message: 'Enter threshold of signers (number)', + }); + const threshold = parseInt(thresholdInput, 10); + + const validatorsInput = await input({ + message: 'Enter validator addresses (comma separated list)', + }); + const validators = validatorsInput.split(',').map((v) => v.trim()); + return { + type, + threshold, + validators, + }; +} + +export async function createAggregationConfig( + chain: ChainName, + chainConfigPath: string, +): Promise { + const isms = parseInt( + await input({ + message: 'Enter the number of ISMs to aggregate (number)', + }), + 10, + ); + + const threshold = parseInt( + await input({ + message: 'Enter the threshold of ISMs to for verification (number)', + }), + 10, + ); + + const modules: Array = []; + for (let i = 0; i < isms; i++) { + modules.push(await createIsmConfig(chain, chainConfigPath)); + } + return { + type: IsmType.AGGREGATION, + modules, + threshold, + }; +} + +export async function createRoutingConfig( + chain: ChainName, + chainConfigPath: string, +): Promise { + const owner = await input({ + message: 'Enter owner address', + }); + const ownerAddress = owner; + const customChains = readChainConfigsIfExists(chainConfigPath); + delete customChains[chain]; + const chains = await runMultiChainSelectionStep( + customChains, + `Select origin chains to be verified on ${chain}`, + [chain], + ); + + const domainsMap: ChainMap = {}; + for (const chain of chains) { + await confirm({ + message: `You are about to configure ISM from source chain ${chain}. Continue?`, + }); + const config = await createIsmConfig(chain, chainConfigPath); + domainsMap[chain] = config; + } + return { + type: IsmType.ROUTING, + owner: ownerAddress, + domains: domainsMap, + }; +} diff --git a/typescript/cli/src/config/multisig.ts b/typescript/cli/src/config/multisig.ts index c9483ae039..d68497195c 100644 --- a/typescript/cli/src/config/multisig.ts +++ b/typescript/cli/src/config/multisig.ts @@ -1,8 +1,13 @@ -import { input, select } from '@inquirer/prompts'; +import { input } from '@inquirer/prompts'; import { z } from 'zod'; -import { ChainMap, IsmType, MultisigConfig } from '@hyperlane-xyz/sdk'; -import { objMap } from '@hyperlane-xyz/utils'; +import { ChainMap, MultisigConfig } from '@hyperlane-xyz/sdk'; +import { + Address, + isValidAddress, + normalizeAddressEvm, + objMap, +} from '@hyperlane-xyz/utils'; import { errorRed, log, logBlue, logGreen } from '../../logger.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; @@ -12,7 +17,6 @@ import { readChainConfigsIfExists } from './chain.js'; const MultisigConfigMapSchema = z.object({}).catchall( z.object({ - type: z.nativeEnum(IsmType), threshold: z.number(), validators: z.array(z.string()), }), @@ -32,14 +36,24 @@ export function readMultisigConfig(filePath: string) { const parsedConfig = result.data; const formattedConfig: ChainMap = objMap( parsedConfig, - (_, config) => - ({ - type: config.type as IsmType, + (_, config) => { + if (config.threshold > config.validators.length) + throw new Error( + 'Threshold cannot be greater than number of validators', + ); + if (config.threshold < 1) + throw new Error('Threshold must be greater than 0'); + const validators: Address[] = []; + for (const v of config.validators) { + if (isValidAddress(v)) validators.push(normalizeAddressEvm(v)); + else throw new Error(`Invalid address ${v}`); + } + return { threshold: config.threshold, - validators: config.validators, - } as MultisigConfig), + validators: validators, + } as MultisigConfig; + }, ); - logGreen(`All multisig configs in ${filePath} are valid`); return formattedConfig; } @@ -70,17 +84,6 @@ export async function createMultisigConfig({ result[chain] = lastConfig; continue; } - // TODO consider using default and not offering options here - const moduleType = await select({ - message: 'Select multisig type', - choices: [ - // { value: 'routing, name: 'routing' }, // TODO add support - // { value: 'aggregation, name: 'aggregation' }, // TODO add support - { value: IsmType.MERKLE_ROOT_MULTISIG, name: 'merkle root multisig' }, - { value: IsmType.MESSAGE_ID_MULTISIG, name: 'message id multisig' }, - ], - pageSize: 5, - }); const thresholdInput = await input({ message: 'Enter threshold of signers (number)', @@ -92,7 +95,6 @@ export async function createMultisigConfig({ }); const validators = validatorsInput.split(',').map((v) => v.trim()); lastConfig = { - type: moduleType, threshold, validators, }; diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index fd3aa7a6bd..64022f2a47 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -16,13 +16,14 @@ import { HyperlaneIsmFactory, HyperlaneProxyFactoryDeployer, IgpConfig, + IsmConfig, IsmType, MultiProvider, MultisigConfig, RoutingIsmConfig, agentStartBlocks, buildAgentConfig, - buildMultisigIsmConfigs, + buildAggregationIsmConfigs, defaultMultisigConfigs, multisigIsmVerificationCost, serializeContractsMap, @@ -32,6 +33,7 @@ import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; import { log, logBlue, logGray, logGreen, logRed } from '../../logger.js'; import { readDeploymentArtifacts } from '../config/artifacts.js'; import { readHookConfig } from '../config/hooks.js'; +import { readIsmConfig } from '../config/ism.js'; import { readMultisigConfig } from '../config/multisig.js'; import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js'; import { @@ -50,6 +52,7 @@ import { TestRecipientConfig, TestRecipientDeployer, } from './TestRecipientDeployer.js'; +import { isISMConfig, isZODISMConfig } from './utils.js'; import { runPreflightChecksForChains } from './utils.js'; export async function runCoreDeploy({ @@ -83,7 +86,15 @@ export async function runCoreDeploy({ ); } const artifacts = await runArtifactStep(chains, artifactsPath); - const multisigConfig = await runIsmStep(chains, ismConfigPath); + const result = await runIsmStep(chains, ismConfigPath); + // we can either specify the full ISM config or just the multisig config + const isAdvancedIsm = isISMConfig(result); + const ismConfigs = isAdvancedIsm + ? (result as ChainMap) + : undefined; + const multisigConfigs = isAdvancedIsm + ? defaultMultisigConfigs + : (result as ChainMap); // TODO re-enable when hook config is actually used await runHookStep(chains, hookConfigPath); @@ -92,7 +103,8 @@ export async function runCoreDeploy({ signer, multiProvider, artifacts, - multisigConfig, + ismConfigs, + multisigConfigs, outPath, skipConfirmation, }; @@ -129,7 +141,11 @@ async function runArtifactStep( const artifactChains = Object.keys(artifacts).filter((c) => selectedChains.includes(c), ); - log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`); + if (artifactChains.length === 0) { + logGray('No artifacts found for selected chains'); + } else { + log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`); + } return artifacts; } @@ -140,8 +156,7 @@ async function runIsmStep(selectedChains: ChainName[], ismConfigPath?: string) { 'Hyperlane instances requires an Interchain Security Module (ISM).', ); logGray( - 'Note, only Multisig ISM configs are currently supported in the CLI', - 'Example config: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/cli/typescript/cli/examples/multisig-ism.yaml', + 'Example config: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/cli/typescript/cli/examples/ism.yaml', ); ismConfigPath = await runFileSelectionStep( './configs', @@ -149,27 +164,53 @@ async function runIsmStep(selectedChains: ChainName[], ismConfigPath?: string) { 'ism', ); } - // first we check for user provided chains - const multisigConfigs = { - ...defaultMultisigConfigs, - ...readMultisigConfig(ismConfigPath), - } as ChainMap; - const requiredMultisigs = objFilter( - multisigConfigs, - (chain, config): config is MultisigConfig => selectedChains.includes(chain), - ); - // selected chains - (user configs + default configs) = missing config - const missingConfigs = selectedChains.filter( - (c) => !Object.keys(requiredMultisigs).includes(c), - ); - if (missingConfigs.length > 0) { - throw new Error( - `Missing ISM config for one or more chains: ${missingConfigs.join(', ')}`, + const isAdvancedIsm = isZODISMConfig(ismConfigPath); + // separate flow for 'ism' and 'ism-advanced' options + if (isAdvancedIsm) { + const ismConfig = readIsmConfig(ismConfigPath); + const requiredIsms = objFilter( + ismConfig, + (chain, config): config is IsmConfig => selectedChains.includes(chain), ); - } + // selected chains - (user configs + default configs) = missing config + const missingConfigs = selectedChains.filter( + (c) => !Object.keys(ismConfig).includes(c), + ); + if (missingConfigs.length > 0) { + throw new Error( + `Missing advanced ISM config for one or more chains: ${missingConfigs.join( + ', ', + )}`, + ); + } - log(`Found configs for chains: ${selectedChains.join(', ')}`); - return requiredMultisigs; + log(`Found configs for chains: ${selectedChains.join(', ')}`); + return requiredIsms as ChainMap; + } else { + const multisigConfigs = { + ...defaultMultisigConfigs, + ...readMultisigConfig(ismConfigPath), + } as ChainMap; + const requiredMultisigs = objFilter( + multisigConfigs, + (chain, config): config is MultisigConfig => + selectedChains.includes(chain), + ); + // selected chains - (user configs + default configs) = missing config + const missingConfigs = selectedChains.filter( + (c) => !Object.keys(requiredMultisigs).includes(c), + ); + if (missingConfigs.length > 0) { + throw new Error( + `Missing ISM config for one or more chains: ${missingConfigs.join( + ', ', + )}`, + ); + } + + log(`Found configs for chains: ${selectedChains.join(', ')}`); + return requiredMultisigs as ChainMap; + } } async function runHookStep( @@ -201,7 +242,8 @@ interface DeployParams { signer: ethers.Signer; multiProvider: MultiProvider; artifacts?: HyperlaneAddressesMap; - multisigConfig?: ChainMap; + ismConfigs?: ChainMap; + multisigConfigs?: ChainMap; outPath: string; skipConfirmation: boolean; } @@ -237,7 +279,8 @@ async function executeDeploy({ multiProvider, outPath, artifacts = {}, - multisigConfig = {}, + ismConfigs = {}, + multisigConfigs = {}, }: DeployParams) { logBlue('All systems ready, captain! Beginning deployment...'); @@ -280,7 +323,9 @@ async function executeDeploy({ const defaultIsms: ChainMap
= {}; for (const ismOrigin of chains) { logBlue(`Deploying ISM to ${ismOrigin}`); - const ismConfig = buildIsmConfig(owner, ismOrigin, chains, multisigConfig); + const ismConfig = + ismConfigs[ismOrigin] ?? + buildIsmConfig(owner, ismOrigin, chains, multisigConfigs); ismContracts[ismOrigin] = { multisigIsm: await ismFactory.deploy(ismOrigin, ismConfig), }; @@ -297,7 +342,7 @@ async function executeDeploy({ owner, chains, defaultIsms, - multisigConfig, + multisigConfigs ?? defaultMultisigConfigs, // TODO: fix https://github.com/hyperlane-xyz/issues/issues/773 ); const coreContracts = await coreDeployer.deploy(coreConfigs); artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); @@ -333,8 +378,7 @@ function buildIsmConfig( chains: ChainName[], multisigIsmConfigs: ChainMap, ): RoutingIsmConfig { - const multisigConfigs = buildMultisigIsmConfigs( - IsmType.MESSAGE_ID_MULTISIG, + const aggregationIsmConfigs = buildAggregationIsmConfigs( local, chains, multisigIsmConfigs, @@ -342,7 +386,7 @@ function buildIsmConfig( return { owner, type: IsmType.ROUTING, - domains: multisigConfigs, + domains: aggregationIsmConfigs, }; } @@ -410,9 +454,16 @@ function buildIgpConfigMap( const gasOracleType: ChainMap = {}; for (const remote of chains) { if (chain === remote) continue; + // TODO: accurate estimate of gas from ChainMap + const threshold = multisigConfigs[remote] + ? multisigConfigs[remote].threshold + : 2; + const validatorsLength = multisigConfigs[remote] + ? multisigConfigs[remote].validators.length + : 3; overhead[remote] = multisigIsmVerificationCost( - multisigConfigs[chain].threshold, - multisigConfigs[chain].validators.length, + threshold, + validatorsLength, ); gasOracleType[remote] = GasOracleContractType.StorageGasOracle; } diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 871a96c5c6..e239584a4c 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -1,9 +1,16 @@ import { ethers } from 'ethers'; -import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + ChainName, + IsmConfig, + MultiProvider, + MultisigConfig, +} from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; import { log, logGreen } from '../../logger.js'; +import { parseIsmConfig } from '../config/ism.js'; import { assertGasBalances } from '../utils/balances.js'; import { assertSigner } from '../utils/keys.js'; @@ -61,3 +68,15 @@ export async function runPreflightChecksForChains({ await assertGasBalances(multiProvider, signer, chains, minGas); logGreen('Balances are sufficient ✅'); } + +// from parsed types +export function isISMConfig( + config: ChainMap | ChainMap, +): boolean { + return Object.values(config).some((c) => 'type' in c); +} + +// directly from filepath +export function isZODISMConfig(filepath: string): boolean { + return parseIsmConfig(filepath).success; +} diff --git a/typescript/cli/src/tests/ism.test.ts b/typescript/cli/src/tests/ism.test.ts new file mode 100644 index 0000000000..a0b16d5583 --- /dev/null +++ b/typescript/cli/src/tests/ism.test.ts @@ -0,0 +1,85 @@ +import { expect } from 'chai'; + +import { ChainMap, IsmConfig, IsmType } from '@hyperlane-xyz/sdk'; + +import { readIsmConfig } from '../config/ism.js'; + +describe('readIsmConfig', () => { + it('parses and validates example correctly', () => { + const ism = readIsmConfig('examples/ism-advanced.yaml'); + + const exampleIsmConfig: ChainMap = { + anvil1: { + type: IsmType.ROUTING, + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', + domains: { + anvil2: { + type: IsmType.AGGREGATION, + modules: [ + { + type: IsmType.MESSAGE_ID_MULTISIG, + threshold: 1, + validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'], + }, + { + type: IsmType.MERKLE_ROOT_MULTISIG, + threshold: 1, + validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'], + }, + ], + threshold: 1, + }, + }, + }, + anvil2: { + type: IsmType.ROUTING, + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', + domains: { + anvil1: { + type: IsmType.AGGREGATION, + modules: [ + { + type: IsmType.MESSAGE_ID_MULTISIG, + threshold: 1, + validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'], + }, + { + type: IsmType.MERKLE_ROOT_MULTISIG, + threshold: 1, + validators: ['0xa0ee7a142d267c1f36714e4a8f75612f20a79720'], + }, + ], + threshold: 1, + }, + }, + }, + }; + expect(ism).to.deep.equal(exampleIsmConfig); + }); + + it('parsing failure, missing internal key "threshold"', () => { + expect(function () { + readIsmConfig('src/tests/ism/safe-parse-fail.yaml'); + }).to.throw(); + }); + + it('parsing failure, routingIsm.domains includes destination domain', () => { + expect(function () { + readIsmConfig('src/tests/ism/routing-same-chain-fail.yaml'); + }).to.throw( + 'Cannot set RoutingIsm.domain to the same chain you are configuring', + ); + }); + + it('parsing failure, wrong ism type', () => { + expect(function () { + readIsmConfig('src/tests/ism/wrong-ism-type-fail.yaml'); + }).to.throw('Invalid ISM config: anvil2 => Invalid input'); + }); + + it('parsing failure, threshold > modules.length', () => { + expect(function () { + readIsmConfig('src/tests/ism/threshold-gt-modules-length-fail.yaml'); + }).to.throw('Threshold cannot be greater than number of modules'); + }); +}); diff --git a/typescript/cli/src/tests/ism/routing-same-chain-fail.yaml b/typescript/cli/src/tests/ism/routing-same-chain-fail.yaml new file mode 100644 index 0000000000..f78998cc36 --- /dev/null +++ b/typescript/cli/src/tests/ism/routing-same-chain-fail.yaml @@ -0,0 +1,33 @@ +anvil1: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 diff --git a/typescript/cli/src/tests/ism/safe-parse-fail.yaml b/typescript/cli/src/tests/ism/safe-parse-fail.yaml new file mode 100644 index 0000000000..195d2a08b3 --- /dev/null +++ b/typescript/cli/src/tests/ism/safe-parse-fail.yaml @@ -0,0 +1,32 @@ +anvil1: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/src/tests/ism/threshold-gt-modules-length-fail.yaml b/typescript/cli/src/tests/ism/threshold-gt-modules-length-fail.yaml new file mode 100644 index 0000000000..670b560388 --- /dev/null +++ b/typescript/cli/src/tests/ism/threshold-gt-modules-length-fail.yaml @@ -0,0 +1,33 @@ +anvil1: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 3 diff --git a/typescript/cli/src/tests/ism/wrong-ism-type-fail.yaml b/typescript/cli/src/tests/ism/wrong-ism-type-fail.yaml new file mode 100644 index 0000000000..7395c718de --- /dev/null +++ b/typescript/cli/src/tests/ism/wrong-ism-type-fail.yaml @@ -0,0 +1,33 @@ +anvil1: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil2: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: merkleRootMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 + +anvil2: + type: domainRoutingIsm + owner: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + domains: + anvil1: + type: staticAggregationIsm + modules: + - type: messageIdMultisigIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + - type: domainRoutingIsm + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' + threshold: 1 diff --git a/typescript/cli/src/tests/multisig.test.ts b/typescript/cli/src/tests/multisig.test.ts new file mode 100644 index 0000000000..f54bfd5b24 --- /dev/null +++ b/typescript/cli/src/tests/multisig.test.ts @@ -0,0 +1,41 @@ +import { expect } from 'chai'; + +import { ChainMap, MultisigConfig } from '@hyperlane-xyz/sdk'; + +import { readMultisigConfig } from '../config/multisig.js'; + +describe('readMultisigConfig', () => { + it('parses and validates example correctly', () => { + const multisig = readMultisigConfig('examples/ism.yaml'); + + const exampleMultisigConfig: ChainMap = { + anvil1: { + threshold: 1, + validators: ['0xa0Ee7A142d267C1f36714E4a8F75612F20a79720'], + }, + anvil2: { + threshold: 1, + validators: ['0xa0Ee7A142d267C1f36714E4a8F75612F20a79720'], + }, + }; + expect(multisig).to.deep.equal(exampleMultisigConfig); + }); + + it('parsing failure', () => { + expect(function () { + readMultisigConfig('src/tests/multisig/safe-parse-fail.yaml'); + }).to.throw('Invalid multisig config: anvil2,validators => Required'); + }); + + it('threshold cannot be greater than the # of validators', () => { + expect(function () { + readMultisigConfig('src/tests/multisig/threshold-gt-fail.yaml'); + }).to.throw('Threshold cannot be greater than number of validators'); + }); + + it('invalid address', () => { + expect(function () { + readMultisigConfig('src/tests/multisig/invalid-address-fail.yaml'); + }).to.throw('Invalid address 0xa0ee7a142d267c1n36714e4a8f7561f20a79720'); + }); +}); diff --git a/typescript/cli/src/tests/multisig/invalid-address-fail.yaml b/typescript/cli/src/tests/multisig/invalid-address-fail.yaml new file mode 100644 index 0000000000..073a76189a --- /dev/null +++ b/typescript/cli/src/tests/multisig/invalid-address-fail.yaml @@ -0,0 +1,8 @@ +anvil1: + threshold: 1 # Number: Signatures required to approve a message + validators: # Array: List of validator addresses + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' +anvil2: + threshold: 1 + validators: + - '0xa0ee7a142d267c1n36714e4a8f7561f20a79720' diff --git a/typescript/cli/src/tests/multisig/safe-parse-fail.yaml b/typescript/cli/src/tests/multisig/safe-parse-fail.yaml new file mode 100644 index 0000000000..a19e8868c6 --- /dev/null +++ b/typescript/cli/src/tests/multisig/safe-parse-fail.yaml @@ -0,0 +1,6 @@ +anvil1: + threshold: 1 # Number: Signatures required to approve a message + validators: # Array: List of validator addresses + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' +anvil2: + threshold: 1 diff --git a/typescript/cli/src/tests/multisig/threshold-gt-fail.yaml b/typescript/cli/src/tests/multisig/threshold-gt-fail.yaml new file mode 100644 index 0000000000..ae3a3fb001 --- /dev/null +++ b/typescript/cli/src/tests/multisig/threshold-gt-fail.yaml @@ -0,0 +1,8 @@ +anvil1: + threshold: 1 # Number: Signatures required to approve a message + validators: # Array: List of validator addresses + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' +anvil2: + threshold: 3 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index c3a07a7eeb..e824ae57b3 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -5,6 +5,7 @@ import chalk from 'chalk'; import { ChainMap, ChainMetadata, + ChainName, mainnetChainsMetadata, testnetChainsMetadata, } from '@hyperlane-xyz/sdk'; @@ -32,8 +33,9 @@ export async function runSingleChainSelectionStep( export async function runMultiChainSelectionStep( customChains: ChainMap, message = 'Select chains', + chainsToFilterOut: ChainName[] = [], ) { - const choices = getChainChoices(customChains); + const choices = getChainChoices(customChains, chainsToFilterOut); while (true) { logTip('Use SPACE key to select chains, then press ENTER'); const chains = (await checkbox({ @@ -47,9 +49,14 @@ export async function runMultiChainSelectionStep( } } -function getChainChoices(customChains: ChainMap) { +function getChainChoices( + customChains: ChainMap, + chainsToFilterOut: ChainName[] = [], +) { const chainsToChoices = (chains: ChainMetadata[]) => - chains.map((c) => ({ name: c.name, value: c.name })); + chains + .filter((chain) => !chainsToFilterOut.includes(chain.name)) + .map((c) => ({ name: c.name, value: c.name })); const choices: Parameters['0']['choices'] = [ new Separator('--Custom Chains--'), ...chainsToChoices(Object.values(customChains)), diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index cd980c0fbb..07af361d28 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -127,7 +127,10 @@ export { collectValidators, moduleCanCertainlyVerify, } from './ism/HyperlaneIsmFactory'; -export { buildMultisigIsmConfigs } from './ism/multisig'; +export { + buildAggregationIsmConfigs, + buildMultisigIsmConfigs, +} from './ism/multisig'; export { AggregationIsmConfig, DeployedIsm, diff --git a/typescript/sdk/src/ism/multisig.ts b/typescript/sdk/src/ism/multisig.ts index 2846f006ce..370f23221e 100644 --- a/typescript/sdk/src/ism/multisig.ts +++ b/typescript/sdk/src/ism/multisig.ts @@ -2,7 +2,12 @@ import { objFilter, objMap } from '@hyperlane-xyz/utils'; import { ChainMap, ChainName } from '../types'; -import { MultisigConfig, MultisigIsmConfig } from './types'; +import { + AggregationIsmConfig, + IsmType, + MultisigConfig, + MultisigIsmConfig, +} from './types'; // build multisigIsmConfig from multisigConfig // eg. for { sepolia (local), arbitrumsepolia, scrollsepolia } @@ -25,3 +30,31 @@ export const buildMultisigIsmConfigs = ( }), ); }; + +export const buildAggregationIsmConfigs = ( + local: ChainName, + chains: ChainName[], + multisigConfigs: ChainMap, +): ChainMap => { + return objMap( + objFilter( + multisigConfigs, + (chain, config): config is MultisigConfig => + chain !== local && chains.includes(chain), + ), + (_, config) => ({ + type: IsmType.AGGREGATION, + modules: [ + { + ...config, + type: IsmType.MESSAGE_ID_MULTISIG, + }, + { + ...config, + type: IsmType.MERKLE_ROOT_MULTISIG, + }, + ], + threshold: 1, + }), + ) as ChainMap; +}; diff --git a/yarn.lock b/yarn.lock index ec2799f08a..f110662ba6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16953,7 +16953,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.1.6, typescript@npm:^5.1.6": +"typescript@npm:5.1.6": version: 5.1.6 resolution: "typescript@npm:5.1.6" bin: @@ -16963,7 +16963,17 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.1.6#optional!builtin, typescript@patch:typescript@npm%3A^5.1.6#optional!builtin": +"typescript@npm:^5.1.6": + version: 5.3.2 + resolution: "typescript@npm:5.3.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 415e5fb6611f5713e460bad48039f00bcfdbde53a2f911727862d5aa9c5d5edd250059a419df382d8f031709e15a169c41eb62b6a401da5eec7ac0f4e359d6ac + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A5.1.6#optional!builtin": version: 5.1.6 resolution: "typescript@patch:typescript@npm%3A5.1.6#optional!builtin::version=5.1.6&hash=5da071" bin: @@ -16973,6 +16983,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.1.6#optional!builtin": + version: 5.3.2 + resolution: "typescript@patch:typescript@npm%3A5.3.2#optional!builtin::version=5.3.2&hash=e012d7" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 1b45cdfb577a78ae7a9a9d0b77a7b772142cb98ba05e4e5aefba7044a028ded885bcecef63166407a5986645cea816fe4986894336aacd5e791796ea79a6a7ed + languageName: node + linkType: hard + "typical@npm:^4.0.0": version: 4.0.0 resolution: "typical@npm:4.0.0" From df34198d4c230f462673d009646986cf275869fa Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 28 Nov 2023 11:57:31 -0500 Subject: [PATCH 5/6] Make mailbox client upgrade safe (#2984) ### Description Pulls in changes from https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/2904 ### Related issues - Fixes https://github.com/hyperlane-xyz/issues/issues/593 ### Backward compatibility No, existing v3 MailboxClients (Routers) will be storage incompatible ### Testing Unit Tests --------- Co-authored-by: Michalis Kargakis --- .changeset/hip-jobs-smoke.md | 5 +++++ solidity/contracts/client/MailboxClient.sol | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 .changeset/hip-jobs-smoke.md diff --git a/.changeset/hip-jobs-smoke.md b/.changeset/hip-jobs-smoke.md new file mode 100644 index 0000000000..c424dd8b55 --- /dev/null +++ b/.changeset/hip-jobs-smoke.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/core': minor +--- + +Includes storage gap in Mailbox Client for forwards compatibility diff --git a/solidity/contracts/client/MailboxClient.sol b/solidity/contracts/client/MailboxClient.sol index ccbcf41189..d4f1d1c78e 100644 --- a/solidity/contracts/client/MailboxClient.sol +++ b/solidity/contracts/client/MailboxClient.sol @@ -22,6 +22,8 @@ abstract contract MailboxClient is OwnableUpgradeable { IInterchainSecurityModule public interchainSecurityModule; + uint256[48] private __GAP; // gap for upgrade safety + // ============ Modifiers ============ modifier onlyContract(address _contract) { require( From 68d4f2f1493ae65a45a54b3e5c0e9388f1ac013b Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Tue, 28 Nov 2023 12:23:26 -0500 Subject: [PATCH 6/6] 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"