Separate agent chain configurations, re-add some chains to infra, fix eclipsetestnet & solanatestnet configs (#3327)

### Description

- solanatestnet and eclipsetestnet were accidentally moved in the agent
JSON to not be in the `chains` object. This moves them back to the right
place, and adds a test in `infra` to make sure that the agent JSON
chains exactly match the environment chain names. There's opportunity
for future improvement for exact consistency between TS artifacts /
chain metadata and what exists in the agent JSONs, this is tracked here
https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3325
- Adds back eclipsetestnet, solanatestnet, and mumbai to the list of
testnet4 chains. These seem to have been accidentally removed
- I guess we never deployed to mainnet3 since we started writing to
aw-multisig.json. So this writes mainnet chains for the first time
- Made some modest fixes to the address persistence logic:
- Previously, it didn't handle AWS & GCP keys for the same role well.
For non-EVM chains, we have a GCP version of a key for submitting
transactions. These exist for relayers, kathy, and validators. This
resulted in contention for the relayer key - sometimes the GCP key would
get written, sometimes the AWS key. For validators, we'd write both the
validator checkpoint signer (AWS) and the chain signer (GCP). Now we
just flat out prefer the AWS key. We already only wrote the address
persistence with EVM keys in mind, so this isn't a regression
- Moves to very explicitly specifying which chains will be ran on the
`hyperlane` context agents
- Instead of sharing a list of chains for both agents and all contract
management, now it's separate. This avoids us making quick fixes and
removing e.g. non-EVM chains to make ethereum-specific contract tooling
happy, but accidentally removing agent support for these chains
- Each chain must be specified for each role in an `AgentChainConfig`
indicating whether that chain is enabled. This being consistent with the
environment's chain names is enforced at runtime & in a test that's ran
in CI. Explicitly indicating whether a chain is supported or not and
keeping this consistent will hopefully result in fewer unintentional
changes to sets of configured chains
- This also allows us during partner integrations to merge a PR with the
contract deploy but without necessarily running agents yet. We can just
set the chain as disabled to not deploy to it

### Drive-by changes

- Removes the `disabled` property for chains in our infra - this is no
longer a thing and isn't needed anymore

### Related issues

Fixes #3326
Fixes #2572

### Backward compatibility

<!--
Are these changes backward compatible? Are there any infrastructure
implications, e.g. changes that would prohibit deploying older commits
using this infra tooling?

Yes/No
-->

### Testing

<!--
What kind of testing have these changes undergone?

None/Manual/Unit Tests
-->
pull/3069/merge
Trevor Porter 9 months ago committed by GitHub
parent e737d998ff
commit 18d1d0d54e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 84
      rust/config/testnet4_config.json
  2. 4
      rust/helm/hyperlane-agent/templates/external-secret.yaml
  3. 1
      rust/helm/hyperlane-agent/values.yaml
  4. 121
      typescript/infra/config/aw-multisig.json
  5. 93
      typescript/infra/config/environments/mainnet3/agent.ts
  6. 53
      typescript/infra/config/environments/mainnet3/chains.ts
  7. 1
      typescript/infra/config/environments/mainnet3/gasPrices.json
  8. 2
      typescript/infra/config/environments/mainnet3/igp.ts
  9. 82
      typescript/infra/config/environments/testnet4/agent.ts
  10. 27
      typescript/infra/config/environments/testnet4/chains.ts
  11. 2
      typescript/infra/package.json
  12. 4
      typescript/infra/scripts/agent-utils.ts
  13. 3
      typescript/infra/src/agents/index.ts
  14. 27
      typescript/infra/src/agents/key-utils.ts
  15. 44
      typescript/infra/src/config/agent/agent.ts
  16. 25
      typescript/infra/src/config/chain.ts
  17. 8
      typescript/infra/src/deployment/deploy.ts
  18. 47
      typescript/infra/test/agent-configs.test.ts
  19. 0
      typescript/infra/test/cloud-agent-keys.test.ts

@ -583,52 +583,52 @@
"index": {
"from": 4517401
}
}
},
"solanatestnet": {
"name": "solanatestnet",
"chainId": 1399811150,
"domainId": 1399811150,
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3",
"protocol": "sealevel",
"blocks": {
"reorgPeriod": 0,
"confirmations": 0
},
"rpcUrls": [
{
"http": "https://api.testnet.solana.com"
"solanatestnet": {
"name": "solanatestnet",
"chainId": 1399811150,
"domainId": 1399811150,
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3",
"protocol": "sealevel",
"blocks": {
"reorgPeriod": 0,
"confirmations": 0
},
"rpcUrls": [
{
"http": "https://api.testnet.solana.com"
}
],
"index": {
"from": 1,
"mode": "sequence"
}
],
"index": {
"from": 1,
"mode": "sequence"
}
},
"eclipsetestnet": {
"name": "eclipsetestnet",
"chainId": 239092742,
"domainId": 239092742,
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3",
"protocol": "sealevel",
"blocks": {
"reorgPeriod": 0,
"confirmations": 0
},
"rpcUrls": [
{
"http": "https://testnet.dev2.eclipsenetwork.xyz"
"eclipsetestnet": {
"name": "eclipsetestnet",
"chainId": 239092742,
"domainId": 239092742,
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3",
"protocol": "sealevel",
"blocks": {
"reorgPeriod": 0,
"confirmations": 0
},
"rpcUrls": [
{
"http": "https://testnet.dev2.eclipsenetwork.xyz"
}
],
"index": {
"from": 1,
"mode": "sequence"
}
],
"index": {
"from": 1,
"mode": "sequence"
}
},
"defaultRpcConsensusType": "fallback"

@ -26,20 +26,17 @@ spec:
* to replace the correct value in the created secret.
*/}}
{{- 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 }}_CUSTOMGRPCURLS: {{ printf "'{{ .%s_grpcs | mustFromJson | join \",\" }}'" .name }}
{{- end }}
{{- end }}
{{- end }}
data:
{{- /*
* 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 }}
@ -49,4 +46,3 @@ spec:
key: {{ printf "%s-grpc-endpoints-%s" $.Values.hyperlane.runEnv .name }}
{{- end }}
{{- end }}
{{- end }}

@ -50,7 +50,6 @@ hyperlane:
# This should mirror @hyperlane-xyz/sdk AgentChainMetadata
chains:
- name: examplechain
disabled: false
rpcConsensusType: fallback
signer:
type: # aws

@ -6,6 +6,13 @@
"0x86485dcec5f7bb8478dd251676372d054dea6653"
]
},
"arbitrum": {
"validators": [
"0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1",
"0x6333e110b8a261cab28acb43030bcde59f26978a",
"0x3369e12edd52570806f126eb50be269ba5e65843"
]
},
"arbitrumgoerli": {
"validators": [
"0x071c8d135845ae5a2cb73f98d681d519014c0a8b",
@ -13,6 +20,27 @@
"0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d"
]
},
"avalanche": {
"validators": [
"0x3fb8263859843bffb02950c492d492cae169f4cf",
"0xe58c63ad669b946e7c8211299f22679deecc9c83",
"0x6c754f1e9cd8287088b46a7c807303d55d728b49"
]
},
"base": {
"validators": [
"0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9",
"0x4512985a574cb127b2af2d4bb676876ce804e3f8",
"0xb144bb2f599a5af095bc30367856f27ea8a8adc7"
]
},
"bsc": {
"validators": [
"0x570af9b7b36568c8877eebba6c6727aa9dab7268",
"0x7bf928d5d262365d31d64eaa24755d48c3cae313",
"0x03047213365800f065356b4a2fe97c3c3a52296a"
]
},
"bsctestnet": {
"validators": [
"0x242d8a855a8c932dec51f7999ae7d1e48b10c95e",
@ -20,6 +48,23 @@
"0x1f030345963c54ff8229720dd3a711c15c554aeb"
]
},
"celo": {
"validators": [
"0x63478422679303c3e4fc611b771fa4a707ef7f4a",
"0x2f4e808744df049d8acc050628f7bdd8265807f9",
"0x7bf30afcb6a7d92146d5a910ea4c154fba38d25e"
]
},
"eclipsetestnet": {
"validators": ["0xf344f34abca9a444545b5295066348a0ae22dda3"]
},
"ethereum": {
"validators": [
"0x03c842db86a6a3e524d4a6615390c1ea8e2b9541",
"0x4346776b10f5e0d9995d884b7a1dbaee4e24c016",
"0x749d6e7ad949e522c92181dc77f7bbc1c5d71506"
]
},
"fuji": {
"validators": [
"0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e",
@ -27,6 +72,13 @@
"0x43e915573d9f1383cbf482049e4a012290759e7f"
]
},
"gnosis": {
"validators": [
"0xd4df66a859585678f2ea8357161d896be19cc1ca",
"0x06a833508579f8b59d756b3a1e72451fc70840c3",
"0xb93a72cee19402553c9dd7fed2461aebd04e2454"
]
},
"goerli": {
"validators": [
"0x05a9b5efe9f61f9142453d8e9f61565f333c6768",
@ -34,6 +86,34 @@
"0x7940a12c050e24e1839c21ecb12f65afd84e8c5b"
]
},
"inevm": {
"validators": [
"0xf9e35ee88e4448a3673b4676a4e153e3584a08eb",
"0xae3e6bb6b3ece1c425aa6f47adc8cb0453c1f9a2",
"0xd98c9522cd9d3e3e00bee05ff76c34b91b266ec3"
]
},
"injective": {
"validators": [
"0xbfb8911b72cfb138c7ce517c57d9c691535dc517",
"0x6faa139c33a7e6f53cb101f6b2ae392298283ed2",
"0x0115e3a66820fb99da30d30e2ce52a453ba99d92"
]
},
"mantapacific": {
"validators": [
"0x8e668c97ad76d0e28375275c41ece4972ab8a5bc",
"0x80afdde2a81f3fb056fd088a97f0af3722dbc4f3",
"0x5dda0c4cf18de3b3ab637f8df82b24921082b54c"
]
},
"moonbeam": {
"validators": [
"0x2225e2f4e9221049456da93b71d2de41f3b6b2a8",
"0x4fe067bb455358e295bfcfb92519a6f9de94b98e",
"0xcc4a78aa162482bea43313cd836ba7b560b44fc4"
]
},
"mumbai": {
"validators": [
"0xebc301013b6cd2548e347c28d2dc43ec20c068f2",
@ -41,6 +121,20 @@
"0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5"
]
},
"neutron": {
"validators": [
"0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0",
"0x60e890b34cb44ce3fa52f38684f613f31b47a1a6",
"0x7885fae56dbcf5176657f54adbbd881dc6714132"
]
},
"optimism": {
"validators": [
"0x20349eadc6c72e94ce38268b96692b1a5c20de4f",
"0x04d040cee072272789e2d1f29aef73b3ad098db5",
"0x779a17e035018396724a6dec8a59bda1b5adf738"
]
},
"optimismgoerli": {
"validators": [
"0x79e58546e2faca865c6732ad5f6c4951051c4d67",
@ -55,6 +149,20 @@
"0xc906470a73e6b5aad65a4ceb4acd73e3eaf80e2c"
]
},
"polygon": {
"validators": [
"0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac",
"0x8dd8f8d34b5ecaa5f66de24b01acd7b8461c3916",
"0xdbf3666de031bea43ec35822e8c33b9a9c610322"
]
},
"polygonzkevm": {
"validators": [
"0x86f2a44592bb98da766e880cfd70d3bbb295e61a",
"0xc84076030bdabaabb9e61161d833dd84b700afda",
"0x6a1da2e0b7ae26aaece1377c0a4dbe25b85fa3ca"
]
},
"polygonzkevmtestnet": {
"validators": [
"0x3f06b725bc9648917eb11c414e9f8d76fd959550",
@ -62,6 +170,13 @@
"0xd476548222f43206d0abaa30e46e28670aa7859c"
]
},
"scroll": {
"validators": [
"0xad557170a9f2f21c35e03de07cb30dcbcc3dff63",
"0xb37fe43a9f47b7024c2d5ae22526cc66b5261533",
"0x7210fa0a6be39a75cb14d682ebfb37e2b53ecbe5"
]
},
"scrollsepolia": {
"validators": [
"0xbe18dbd758afb367180260b524e6d4bcd1cb6d05",
@ -75,5 +190,11 @@
"0x469f0940684d147defc44f3647146cb90dd0bc8e",
"0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83"
]
},
"solanatestnet": {
"validators": ["0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5"]
},
"viction": {
"validators": ["0x1f87c368f8e05a85ef9126d984a980a20930cb9c"]
}
}

@ -1,11 +1,16 @@
import {
Chains,
GasPaymentEnforcementPolicyType,
RpcConsensusType,
chainMetadata,
getDomainId,
} from '@hyperlane-xyz/sdk';
import { RootAgentConfig, allAgentChainNames } from '../../../src/config';
import {
AgentChainConfig,
RootAgentConfig,
getAgentChainNamesFromConfig,
} from '../../../src/config';
import {
GasPaymentEnforcementConfig,
routerMatchingList,
@ -13,7 +18,7 @@ import {
import { ALL_KEY_ROLES, Role } from '../../../src/roles';
import { Contexts } from '../../contexts';
import { agentChainNames, environment, ethereumChainNames } from './chains';
import { environment, supportedChainNames } from './chains';
import { helloWorld } from './helloworld';
import { validatorChainConfig } from './validators';
import arbitrumTIAAddresses from './warp/arbitrum-TIA-addresses.json';
@ -26,11 +31,85 @@ import mantaTIAAddresses from './warp/manta-TIA-addresses.json';
const repo = 'gcr.io/abacus-labs-dev/hyperlane-agent';
// The chains here must be consistent with the environment's supportedChainNames, which is
// checked / enforced at runtime & in the CI pipeline.
//
// This is intentionally separate and not derived from the environment's supportedChainNames
// to allow for more fine-grained control over which chains are enabled for each agent role.
export const hyperlaneContextAgentChainConfig: AgentChainConfig = {
// Generally, we run all production validators in the Hyperlane context.
[Role.Validator]: {
[Chains.arbitrum]: true,
[Chains.avalanche]: true,
[Chains.bsc]: true,
[Chains.celo]: true,
[Chains.ethereum]: true,
[Chains.neutron]: true,
[Chains.mantapacific]: true,
[Chains.moonbeam]: true,
[Chains.optimism]: true,
[Chains.polygon]: true,
[Chains.gnosis]: true,
[Chains.base]: true,
[Chains.scroll]: true,
[Chains.polygonzkevm]: true,
[Chains.injective]: true,
[Chains.inevm]: true,
[Chains.viction]: true,
},
[Role.Relayer]: {
[Chains.arbitrum]: true,
[Chains.avalanche]: true,
[Chains.bsc]: true,
[Chains.celo]: true,
[Chains.ethereum]: true,
// At the moment, we only relay between Neutron and Manta Pacific on the neutron context.
[Chains.neutron]: false,
[Chains.mantapacific]: false,
[Chains.moonbeam]: true,
[Chains.optimism]: true,
[Chains.polygon]: true,
[Chains.gnosis]: true,
[Chains.base]: true,
[Chains.scroll]: true,
[Chains.polygonzkevm]: true,
[Chains.injective]: true,
[Chains.inevm]: true,
[Chains.viction]: true,
},
[Role.Scraper]: {
[Chains.arbitrum]: true,
[Chains.avalanche]: true,
[Chains.bsc]: true,
[Chains.celo]: true,
[Chains.ethereum]: true,
// Cannot scrape non-EVM chains
[Chains.neutron]: false,
[Chains.mantapacific]: true,
[Chains.moonbeam]: true,
[Chains.optimism]: true,
[Chains.polygon]: true,
[Chains.gnosis]: true,
[Chains.base]: true,
[Chains.scroll]: true,
[Chains.polygonzkevm]: true,
// Cannot scrape non-EVM chains
[Chains.injective]: false,
[Chains.inevm]: true,
// Has RPC non-compliance that breaks scraping.
[Chains.viction]: false,
},
};
export const hyperlaneContextAgentChainNames = getAgentChainNamesFromConfig(
hyperlaneContextAgentChainConfig,
supportedChainNames,
);
const contextBase = {
namespace: environment,
runEnv: environment,
contextChainNames: agentChainNames,
environmentChainNames: allAgentChainNames(agentChainNames),
environmentChainNames: supportedChainNames,
aws: {
region: 'us-east-1',
},
@ -45,6 +124,7 @@ const gasPaymentEnforcement: GasPaymentEnforcementConfig[] = [
const hyperlane: RootAgentConfig = {
...contextBase,
context: Contexts.Hyperlane,
contextChainNames: hyperlaneContextAgentChainNames,
rolesWithKeys: ALL_KEY_ROLES,
relayer: {
rpcConsensusType: RpcConsensusType.Fallback,
@ -86,10 +166,7 @@ const hyperlane: RootAgentConfig = {
const releaseCandidate: RootAgentConfig = {
...contextBase,
context: Contexts.ReleaseCandidate,
contextChainNames: {
...contextBase.contextChainNames,
[Role.Validator]: ethereumChainNames,
},
contextChainNames: hyperlaneContextAgentChainNames,
rolesWithKeys: [Role.Relayer, Role.Kathy, Role.Validator],
relayer: {
rpcConsensusType: RpcConsensusType.Fallback,

@ -1,19 +1,23 @@
import {
ChainMap,
ChainMetadata,
Chains,
Mainnets,
chainMetadata,
} from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { AgentChainNames, Role } from '../../../src/roles';
import { getChainMetadatas } from '../../../src/config/chain';
const defaultEthereumMainnetConfigs = Object.fromEntries(
Mainnets.map((chain) => chainMetadata[chain])
.filter((metadata) => metadata.protocol === ProtocolType.Ethereum)
.map((metadata) => [metadata.name, metadata]),
);
// The `Mainnets` from the SDK are all supported chains for the mainnet3 environment.
// These chains may be any protocol type.
export const supportedChainNames = Mainnets;
export type MainnetChains = (typeof supportedChainNames)[number];
export const environment = 'mainnet3';
const {
ethereumMetadatas: defaultEthereumMainnetConfigs,
nonEthereumMetadatas: nonEthereumMainnetConfigs,
} = getChainMetadatas(supportedChainNames);
export const ethereumMainnetConfigs: ChainMap<ChainMetadata> = {
...defaultEthereumMainnetConfigs,
@ -48,44 +52,11 @@ export const ethereumMainnetConfigs: ChainMap<ChainMetadata> = {
},
};
// Blessed non-Ethereum chains.
export const nonEthereumMainnetConfigs: ChainMap<ChainMetadata> = {
// solana: chainMetadata.solana,
neutron: chainMetadata.neutron,
injective: chainMetadata.injective,
};
export const mainnetConfigs: ChainMap<ChainMetadata> = {
...ethereumMainnetConfigs,
...nonEthereumMainnetConfigs,
};
export type MainnetChains = keyof typeof mainnetConfigs;
export const supportedChainNames = Object.keys(
mainnetConfigs,
) as MainnetChains[];
export const environment = 'mainnet3';
export const ethereumChainNames = Object.keys(
ethereumMainnetConfigs,
) as MainnetChains[];
// Remove mantapacific, as it's not considered a "blessed"
// chain and we don't relay to mantapacific on the Hyperlane or RC contexts.
const relayerHyperlaneContextChains = supportedChainNames.filter(
(chainName) => chainName !== Chains.mantapacific,
);
// Ethereum chains only.
const scraperHyperlaneContextChains = ethereumChainNames.filter(
// Has RPC non-compliance that breaks scraping.
(chainName) => chainName !== Chains.viction,
);
// Hyperlane & RC context agent chain names.
export const agentChainNames: AgentChainNames = {
// Run validators for all chains.
[Role.Validator]: supportedChainNames,
[Role.Relayer]: relayerHyperlaneContextChains,
[Role.Scraper]: scraperHyperlaneContextChains,
};

@ -14,5 +14,6 @@
"polygonzkevm": "3.95",
"inevm": "0.1",
"viction": "0.25",
"neutron": "0.1",
"injective": "0.1"
}

@ -35,7 +35,7 @@ export const igp: ChainMap<IgpConfig> = objMap(owners, (local, owner) => ({
overhead: Object.fromEntries(
exclude(local, supportedChainNames).map((remote) => [
remote,
remoteOverhead(remote),
remoteOverhead(remote as MainnetChains),
]),
),
oracleConfig: storageGasOracleConfig[local],

@ -1,4 +1,5 @@
import {
Chains,
GasPaymentEnforcementPolicyType,
RpcConsensusType,
chainMetadata,
@ -6,15 +7,16 @@ import {
} from '@hyperlane-xyz/sdk';
import {
AgentChainConfig,
RootAgentConfig,
allAgentChainNames,
getAgentChainNamesFromConfig,
routerMatchingList,
} from '../../../src/config';
import { GasPaymentEnforcementConfig } from '../../../src/config/agent/relayer';
import { ALL_KEY_ROLES, Role } from '../../../src/roles';
import { Contexts } from '../../contexts';
import { agentChainNames, environment } from './chains';
import { environment, supportedChainNames } from './chains';
import { helloWorld } from './helloworld';
import { validatorChainConfig } from './validators';
import plumetestnetSepoliaAddresses from './warp/plumetestnet-sepolia-addresses.json';
@ -25,11 +27,70 @@ const releaseCandidateHelloworldMatchingList = routerMatchingList(
const repo = 'gcr.io/abacus-labs-dev/hyperlane-agent';
// The chains here must be consistent with the environment's supportedChainNames, which is
// checked / enforced at runtime & in the CI pipeline.
//
// This is intentionally separate and not derived from the environment's supportedChainNames
// to allow for more fine-grained control over which chains are enabled for each agent role.
export const hyperlaneContextAgentChainConfig: AgentChainConfig = {
[Role.Validator]: {
[Chains.alfajores]: true,
[Chains.arbitrumgoerli]: true,
[Chains.bsctestnet]: true,
[Chains.eclipsetestnet]: true,
[Chains.fuji]: true,
[Chains.goerli]: true,
[Chains.mumbai]: true,
[Chains.optimismgoerli]: true,
[Chains.plumetestnet]: true,
[Chains.polygonzkevmtestnet]: true,
[Chains.scrollsepolia]: true,
[Chains.sepolia]: true,
[Chains.solanatestnet]: true,
},
[Role.Relayer]: {
[Chains.alfajores]: true,
[Chains.arbitrumgoerli]: true,
[Chains.bsctestnet]: true,
[Chains.eclipsetestnet]: true,
[Chains.fuji]: true,
[Chains.goerli]: true,
[Chains.mumbai]: true,
[Chains.optimismgoerli]: true,
[Chains.plumetestnet]: true,
[Chains.polygonzkevmtestnet]: true,
[Chains.scrollsepolia]: true,
[Chains.sepolia]: true,
[Chains.solanatestnet]: true,
},
[Role.Scraper]: {
[Chains.alfajores]: true,
[Chains.arbitrumgoerli]: true,
[Chains.bsctestnet]: true,
// Cannot scrape non-EVM chains
[Chains.eclipsetestnet]: false,
[Chains.fuji]: true,
[Chains.goerli]: true,
[Chains.mumbai]: true,
[Chains.optimismgoerli]: true,
[Chains.plumetestnet]: true,
[Chains.polygonzkevmtestnet]: true,
[Chains.scrollsepolia]: true,
[Chains.sepolia]: true,
// Cannot scrape non-EVM chains
[Chains.solanatestnet]: false,
},
};
export const hyperlaneContextAgentChainNames = getAgentChainNamesFromConfig(
hyperlaneContextAgentChainConfig,
supportedChainNames,
);
const contextBase = {
namespace: environment,
runEnv: environment,
contextChainNames: agentChainNames,
environmentChainNames: allAgentChainNames(agentChainNames),
environmentChainNames: supportedChainNames,
aws: {
region: 'us-east-1',
},
@ -44,14 +105,14 @@ const gasPaymentEnforcement: GasPaymentEnforcementConfig[] = [
const hyperlane: RootAgentConfig = {
...contextBase,
contextChainNames: agentChainNames,
contextChainNames: hyperlaneContextAgentChainNames,
context: Contexts.Hyperlane,
rolesWithKeys: ALL_KEY_ROLES,
relayer: {
rpcConsensusType: RpcConsensusType.Fallback,
docker: {
repo,
tag: 'd1ff3aa-20240226-122224',
tag: 'db9ff2d-20240228-143928',
},
blacklist: [
...releaseCandidateHelloworldMatchingList,
@ -98,7 +159,7 @@ const hyperlane: RootAgentConfig = {
rpcConsensusType: RpcConsensusType.Fallback,
docker: {
repo,
tag: '6b5b324-20240223-122143',
tag: 'db9ff2d-20240228-143928',
},
chains: validatorChainConfig(Contexts.Hyperlane),
},
@ -106,7 +167,7 @@ const hyperlane: RootAgentConfig = {
rpcConsensusType: RpcConsensusType.Fallback,
docker: {
repo,
tag: '6b5b324-20240223-122143',
tag: 'db9ff2d-20240228-143928',
},
},
};
@ -114,12 +175,13 @@ const hyperlane: RootAgentConfig = {
const releaseCandidate: RootAgentConfig = {
...contextBase,
context: Contexts.ReleaseCandidate,
contextChainNames: hyperlaneContextAgentChainNames,
rolesWithKeys: [Role.Relayer, Role.Kathy, Role.Validator],
relayer: {
rpcConsensusType: RpcConsensusType.Fallback,
docker: {
repo,
tag: '95fe655-20240222-183959',
tag: 'db9ff2d-20240228-143928',
},
whitelist: [...releaseCandidateHelloworldMatchingList],
gasPaymentEnforcement,
@ -132,7 +194,7 @@ const releaseCandidate: RootAgentConfig = {
rpcConsensusType: RpcConsensusType.Fallback,
docker: {
repo,
tag: '95fe655-20240222-183959',
tag: 'db9ff2d-20240228-143928',
},
chains: validatorChainConfig(Contexts.ReleaseCandidate),
},

@ -4,26 +4,30 @@ import {
Chains,
chainMetadata,
} from '@hyperlane-xyz/sdk';
import { plumetestnet } from '@hyperlane-xyz/sdk/dist/consts/chainMetadata';
import { AgentChainNames, Role } from '../../../src/roles';
const selectedChains = [
// All supported chains for the testnet4 environment.
// These chains may be any protocol type.
export const supportedChainNames = [
Chains.alfajores,
Chains.arbitrumgoerli,
Chains.bsctestnet,
Chains.eclipsetestnet,
Chains.fuji,
Chains.goerli,
Chains.mumbai,
Chains.optimismgoerli,
Chains.plumetestnet,
Chains.polygonzkevmtestnet,
Chains.scrollsepolia,
Chains.sepolia,
Chains.plumetestnet,
Chains.solanatestnet,
];
export const environment = 'testnet4';
export const testnetConfigs: ChainMap<ChainMetadata> = {
...Object.fromEntries(
selectedChains.map((chain) => [chain, chainMetadata[chain]]),
supportedChainNames.map((chain) => [chain, chainMetadata[chain]]),
),
mumbai: {
...chainMetadata.mumbai,
@ -39,14 +43,3 @@ export const testnetConfigs: ChainMap<ChainMetadata> = {
},
},
};
export const supportedChainNames = Object.keys(testnetConfigs);
export const environment = 'testnet4';
// Hyperlane & RC context agent chain names.
export const agentChainNames: AgentChainNames = {
[Role.Validator]: supportedChainNames,
// Only run relayers for Ethereum chains at the moment.
[Role.Relayer]: supportedChainNames,
[Role.Scraper]: supportedChainNames,
};

@ -70,7 +70,7 @@
"announce": "hardhat announce --network localhost",
"node": "hardhat node",
"prettier": "prettier --write *.ts ./src ./config ./scripts ./test",
"test": "mocha --config ../sdk/.mocharc.json test/agents.test.ts",
"test": "mocha --config ../sdk/.mocharc.json test/**/*.test.ts",
"test:ci": "yarn test"
},
"peerDependencies": {

@ -365,6 +365,10 @@ export function getAgentConfigDirectory() {
return path.join('../../', 'rust', 'config');
}
export function getAgentConfigJsonPath(environment: DeployEnvironment) {
return path.join(getAgentConfigDirectory(), `${environment}_config.json`);
}
export async function assertCorrectKubeContext(coreConfig: EnvironmentConfig) {
const currentKubeContext = await getCurrentKubernetesContext();
if (

@ -112,7 +112,7 @@ export abstract class AgentHelmManager {
runEnv: this.environment,
context: this.context,
aws: !!this.config.aws,
chains: this.config.environmentChainNames.map((chain) => {
chains: this.config.contextChainNames[this.role].map((chain) => {
const metadata = chainMetadata[chain];
const reorgPeriod = metadata.blocks?.reorgPeriod;
if (reorgPeriod === undefined) {
@ -120,7 +120,6 @@ export abstract class AgentHelmManager {
}
return {
name: chain,
disabled: !this.config.contextChainNames[this.role].includes(chain),
rpcConsensusType: this.rpcConsensusType(chain),
protocol: metadata.protocol,
blocks: { reorgPeriod },

@ -385,6 +385,16 @@ async function persistAddressesLocally(
const multisigValidatorKeys: ChainMap<{ validators: Address[] }> = {};
let relayer, kathy;
for (const key of keys) {
// Some types of keys come in an AWS and a GCP variant. We prefer
// to persist the AWS version of the key if AWS is enabled.
// Note this means we prefer EVM addresses here, as even if AWS
// is enabled, we use the GCP address for non-EVM chains because
// only the EVM has the tooling & cryptographic compatibility with
// our AWS KMS keys.
if (agentConfig.aws && !(key instanceof AgentAwsKey)) {
continue;
}
if (key.role === Role.Relayer) {
if (relayer)
throw new Error('More than one Relayer found in gcpCloudAgentKeys');
@ -395,12 +405,17 @@ async function persistAddressesLocally(
throw new Error('More than one Kathy found in gcpCloudAgentKeys');
kathy = key.address;
}
if (!key.chainName) continue;
multisigValidatorKeys[key.chainName] ||= {
validators: [],
};
if (key.chainName)
multisigValidatorKeys[key.chainName].validators.push(key.address);
if (key.chainName) {
multisigValidatorKeys[key.chainName] ||= {
validators: [],
};
// The validator role always has a chainName.
if (key.role === Role.Validator) {
multisigValidatorKeys[key.chainName].validators.push(key.address);
}
}
}
if (!relayer) throw new Error('No Relayer found in awsCloudAgentKeys');
if (!kathy) throw new Error('No Kathy found in awsCloudAgentKeys');

@ -2,14 +2,15 @@ import {
AgentChainMetadata,
AgentSignerAwsKey,
AgentSignerKeyType,
ChainMap,
ChainName,
RpcConsensusType,
chainMetadata,
} from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { ProtocolType, objMap } from '@hyperlane-xyz/utils';
import { Contexts } from '../../../config/contexts';
import { AgentChainNames, Role } from '../../roles';
import { AgentChainNames, AgentRole, Role } from '../../roles';
import { DeployEnvironment } from '../environment';
import { HelmImageValues } from '../infrastructure';
@ -56,7 +57,6 @@ interface HelmHyperlaneValues {
export interface HelmAgentChainOverride
extends DeepPartial<AgentChainMetadata> {
name: AgentChainMetadata['name'];
disabled?: boolean;
}
export interface RootAgentConfig extends AgentContextConfig {
@ -207,3 +207,41 @@ export function defaultChainSignerKeyConfig(chainName: ChainName): KeyConfig {
return { type: AgentSignerKeyType.Hex };
}
}
export type AgentChainConfig = Record<AgentRole, ChainMap<boolean>>;
/// Converts an AgentChainConfig to an AgentChainNames object.
export function getAgentChainNamesFromConfig(
config: AgentChainConfig,
supportedChainNames: ChainName[],
): AgentChainNames {
ensureAgentChainConfigIncludesAllChainNames(config, supportedChainNames);
return objMap(config, (role, roleConfig) =>
Object.entries(roleConfig)
.filter(([_chain, enabled]) => enabled)
.map(([chain]) => chain),
);
}
// Throws if any of the roles in the config do not have all the expected chain names.
export function ensureAgentChainConfigIncludesAllChainNames(
config: AgentChainConfig,
expectedChainNames: ChainName[],
) {
for (const [role, roleConfig] of Object.entries(config)) {
const chainNames = Object.keys(roleConfig);
const missingChainNames = expectedChainNames.filter(
(chainName) => !chainNames.includes(chainName),
);
const unknownChainNames = chainNames.filter(
(chainName) => !expectedChainNames.includes(chainName),
);
if (missingChainNames.length > 0 || unknownChainNames.length > 0) {
throw new Error(
`${role} agent chain config incorrect. Missing chain names: ${missingChainNames}, unknown chain names: ${unknownChainNames}`,
);
}
}
}

@ -1,13 +1,17 @@
import { providers } from 'ethers';
import {
ChainMap,
ChainMetadata,
ChainMetadataManager,
ChainName,
CoreChainName,
HyperlaneSmartProvider,
ProviderRetryOptions,
RpcConsensusType,
chainMetadata,
} from '@hyperlane-xyz/sdk';
import { ProtocolType, objFilter } from '@hyperlane-xyz/utils';
import { getSecretRpcEndpoint } from '../agents';
@ -52,3 +56,24 @@ export async function fetchProvider(
throw Error(`Unsupported connectionType: ${connectionType}`);
}
}
export function getChainMetadatas(chains: Array<CoreChainName>) {
const allMetadatas = Object.fromEntries(
chains
.map((chain) => chainMetadata[chain])
.map((metadata) => [metadata.name, metadata]),
);
const ethereumMetadatas = objFilter(
allMetadatas,
(_, metadata): metadata is ChainMetadata =>
metadata.protocol === ProtocolType.Ethereum,
);
const nonEthereumMetadatas = objFilter(
allMetadatas,
(_, metadata): metadata is ChainMetadata =>
metadata.protocol !== ProtocolType.Ethereum,
);
return { ethereumMetadatas, nonEthereumMetadatas };
}

@ -11,7 +11,7 @@ import {
} from '@hyperlane-xyz/sdk';
import { objMap, objMerge, promiseObjAll } from '@hyperlane-xyz/utils';
import { getAgentConfigDirectory } from '../../scripts/agent-utils';
import { getAgentConfigJsonPath } from '../../scripts/agent-utils';
import { DeployEnvironment } from '../config';
import {
readJSONAtPath,
@ -143,9 +143,5 @@ export async function writeAgentConfig(
addresses as ChainMap<HyperlaneDeploymentArtifacts>,
startBlocks,
);
writeMergedJSON(
getAgentConfigDirectory(),
`${environment}_config.json`,
agentConfig,
);
writeMergedJSONAtPath(getAgentConfigJsonPath(environment), agentConfig);
}

@ -0,0 +1,47 @@
import { expect } from 'chai';
import { hyperlaneContextAgentChainConfig as mainnet3AgentChainConfig } from '../config/environments/mainnet3/agent';
import { supportedChainNames as mainnet3SupportedChainNames } from '../config/environments/mainnet3/chains';
import { hyperlaneContextAgentChainConfig as testnet4AgentChainConfig } from '../config/environments/testnet4/agent';
import { supportedChainNames as testnet4SupportedChainNames } from '../config/environments/testnet4/chains';
import { getAgentConfigJsonPath } from '../scripts/agent-utils';
import { ensureAgentChainConfigIncludesAllChainNames } from '../src/config';
import { readJSONAtPath } from '../src/utils/utils';
const environmentChainConfigs = {
mainnet3: {
agentChainConfig: mainnet3AgentChainConfig,
// We read the agent config from the file system instead of importing
// to get around the agent JSON configs living outside the typescript rootDir
agentJsonConfig: readJSONAtPath(getAgentConfigJsonPath('mainnet3')),
supportedChainNames: mainnet3SupportedChainNames,
},
testnet4: {
agentChainConfig: testnet4AgentChainConfig,
agentJsonConfig: readJSONAtPath(getAgentConfigJsonPath('testnet4')),
supportedChainNames: testnet4SupportedChainNames,
},
};
describe('Agent configs', () => {
Object.entries(environmentChainConfigs).forEach(([environment, config]) => {
describe(`Environment: ${environment}`, () => {
it('AgentChainConfig specifies all chains for each role in the agent chain config', () => {
// This will throw if there are any inconsistencies
ensureAgentChainConfigIncludesAllChainNames(
config.agentChainConfig,
config.supportedChainNames,
);
});
it('Agent JSON config matches environment chains', () => {
const agentJsonConfigChains = Object.keys(
config.agentJsonConfig.chains,
);
expect(agentJsonConfigChains).to.have.members(
config.supportedChainNames,
);
});
});
});
});
Loading…
Cancel
Save