├── .gitignore ├── README.md ├── artifacts └── agent_config.json ├── constants.star ├── kurtosis-package-icon.png ├── kurtosis.yml ├── main.star ├── relayer.star ├── utils.star └── validator.star /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | .idea 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hyperlane package 2 | ================= 3 | 4 | # Getting started 5 | 6 | You'll need the following set of args, which you can write to a JSON file like `args.json` 7 | ``` 8 | { 9 | "origin_chain_url": "https://base-goerli.gateway.tenderly.co", 10 | "origin_chain_name": "base", 11 | "validator_key": "", 12 | "aws_access_key_id": "", 13 | "aws_secret_access_key": "", 14 | "aws_bucket_region": "us-east-1", 15 | "aws_bucket_name": "", 16 | "aws_bucket_folder":"", 17 | "remote_chains": { 18 | "goerli":"https://goerli-rollup.arbitrum.io/rpc" 19 | }, 20 | "agent_config_json": "" 21 | } 22 | ``` 23 | 24 | The origin chain and remote chains should reference the ones listed in [agent_config.json](./artifacts/agent_config.json). If you want to use custom ones, be sure to add their info into this file also. Alternativelly, you can place its contents inside `agent_config_json` parameter to your `args.json` with the JSON string containing the chain information. 25 | 26 | An AWS user key and S3 bucket is required when running locally so that the validator service can persist data to it. When running the package on Kurtosis Cloud, the `aws_env` part becomes optional because Kurtosis Cloud automatically provides this information to the package via its environment variable. 27 | 28 | With the arguments JSON file, the package can be run with: 29 | ``` 30 | kurtosis run ./ "$(cat args.json)" 31 | ``` 32 | -------------------------------------------------------------------------------- /artifacts/agent_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "chains": { 3 | "base": { 4 | "name": "base", 5 | "domain": 84531, 6 | "addresses": { 7 | "mailbox": "0x88Fcc0A8308665368De695F9f22501aD8b80E724", 8 | "interchainGasPaymaster": "0xc454bda985B7476B4AF0244a83F1a17FfB685364", 9 | "validatorAnnounce": "0x14952f942b0C211Bdab6D18A9Ea56E515Dfc026E" 10 | }, 11 | "protocol": "ethereum", 12 | "finalityBlocks": 1, 13 | "index": { 14 | "from": 8646417 15 | } 16 | }, 17 | "goerli": { 18 | "name": "goerli", 19 | "domain": 5, 20 | "addresses": { 21 | "mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", 22 | "interchainGasPaymaster": "0xe5553193B13c8e6C98a0C5ABb540770Be9723f5b", 23 | "validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" 24 | }, 25 | "protocol": "ethereum", 26 | "finalityBlocks": 2, 27 | "index": { 28 | "from": 8039005 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /constants.star: -------------------------------------------------------------------------------- 1 | CONFIG_FILE_FOLDER = "/config/" 2 | CONFIG_FILE_NAME = "agent_config.json" 3 | 4 | DB_FOLDER = "/data/" 5 | 6 | DEFAULT_ORIGIN_CHAIN_URL = "HYP_CHAINS_%s_CONNECTION_URL" 7 | -------------------------------------------------------------------------------- /kurtosis-package-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-package/753badfc72316093f155a0d9c9e94976f512ac1f/kurtosis-package-icon.png -------------------------------------------------------------------------------- /kurtosis.yml: -------------------------------------------------------------------------------- 1 | name: github.com/kurtosis-tech/hyperlane-package 2 | description: | 3 | Hyperlane 4 | ======== 5 | This Kurtosis package instantiates an environment containing a Hyperlane validator and a Hyperlane relayer. 6 | 7 | Prerequisites 8 | ------------- 9 | You'll need to have the Hyperlane contracts deployed on both a source blockchain and a deatination blockahin. Instructions for doing so can be found [here](https://docs.hyperlane.xyz/docs/deploy/deploy-hyperlane#2.-deploy-contracts). Deploying the contracts will produce an `agent_config.json` file, which you'll need for configuring this environment. 10 | 11 | Configuration 12 | ------------- 13 | To configure this package, you'll need to provide the following information: 14 | - `origin_chain_name`: this corresponds to the value of the `--local` flag you supplied when running the deploy script. 15 | - `origin_chain_url`: the RPC URL for origin chain you provided via the `--local` flag in the deploy script. This URL can be found inside the `chain.ts` you provided when running the deploy script (e.g. `https://base-goerli.gateway.tenderly.co`). 16 | - `validator_key`: the private key corresponding to the public key that you slotted into the `validators` key of the `multisig_ism.ts` file used when you ran the deploy script. For example, if your `multisig_ism.ts` file had the following, you would provide the private key corresponding to public key `0xd12C119d4548B2E401ec82CeC8c27752d7d31C1C`: 17 | ``` 18 | linea: { 19 | type: ModuleType.LEGACY_MULTISIG, 20 | threshold: 1, 21 | validators: [ 22 | '0xd12C119d4548B2E401ec82CeC8c27752d7d31C1C', 23 | ], 24 | }, 25 | ``` 26 | - `remote_chains`: a map, whose keys correspond to the chains specified in the `--remotes` flag during deploy, and whose values are the RPC URLs where those chains can be reached. For example: 27 | ``` 28 | { 29 | "goerli": "https://ethereum-goerli.publicnode.com" 30 | } 31 | ``` 32 | - `agent_config_json`: the contents of the `agent_config.json` file produced by running the deploy script. 33 | -------------------------------------------------------------------------------- /main.star: -------------------------------------------------------------------------------- 1 | validator = import_module("./validator.star") 2 | relayer = import_module("./relayer.star") 3 | utils = import_module("./utils.star") 4 | 5 | 6 | def run( 7 | plan, 8 | origin_chain_name, 9 | validator_key, 10 | agent_config_json, 11 | relay_chains, 12 | rpc_urls={}, 13 | custom_validator_image="gcr.io/abacus-labs-dev/hyperlane-agent:a888cf7-20231214-180118", 14 | custom_relayer_image="gcr.io/abacus-labs-dev/hyperlane-agent:a888cf7-20231214-180118", 15 | log_level="info", 16 | aws_access_key_id="", 17 | aws_secret_access_key="", 18 | aws_bucket_region="", 19 | aws_bucket_name="", 20 | aws_bucket_folder="", 21 | ): 22 | """Runs a Hyperlane validator and relayer 23 | 24 | Args: 25 | origin_chain_name (string): The name of the origin chain 26 | origin_chain_url (string): The RPC url of the origin chain 27 | validator_key (string): The private key (0x-prefixed) to be used by the validator 28 | relay_chains (string): comma separated list of chains to relay between 29 | agent_config_json: The agent config used by Hyperlane validator and relayer 30 | rpc_urls (dict[string, string]): Mapping of chainName => rpcURL 31 | custom_validator_image (string): A custom image to use to run the validator 32 | custom_relayer_image (string): A custom image to use to run the relayer 33 | log_level (string): Custom logging level. Defaults to "info" 34 | aws_access_key_id (string): A custom AWS access key ID to use for accessing the AWS bucket 35 | aws_secret_access_key (string): A custom AWS secret access key to use for accessing the AWS bucket 36 | aws_bucket_region (string): The region of the custom bucket to be used 37 | aws_bucket_name (string): The name of the custom bucket to be used 38 | aws_bucket_folder (string): The folder inside the bucket to use 39 | """ 40 | aws_env = {} 41 | if len(aws_access_key_id) > 0: 42 | aws_env["aws_access_key_id"] = aws_access_key_id 43 | 44 | if len(aws_secret_access_key) > 0: 45 | aws_env["aws_secret_access_key"] = aws_secret_access_key 46 | 47 | if len(aws_bucket_region) > 0: 48 | aws_env["aws_bucket_region"] = aws_bucket_region 49 | 50 | if len(aws_bucket_name) > 0: 51 | aws_env["aws_bucket_name"] = aws_bucket_name 52 | 53 | if len(aws_bucket_folder) > 0: 54 | aws_env["aws_bucket_user_folder"] = aws_bucket_folder 55 | 56 | relay_chains = [chain.strip() for chain in relay_chains.split(',')] 57 | if len(relay_chains) < 2: 58 | fail("At least two chains must be provided to relay between") 59 | 60 | if len(validator_key) < 64: 61 | fail("Invalid validator key provided") 62 | validator_key = validator_key if validator_key.startswith("0x") else "0x" + validator_key 63 | 64 | env_aws = get_aws_user_info(plan, aws_env) 65 | 66 | origin_chain = { 67 | "chain_name": origin_chain_name, 68 | "signer_id": validator_key, 69 | } 70 | 71 | # ONCE THE BUG IS FIXED, I WILL REMOVE THIS 72 | if log_level == "": 73 | log_level = "info" 74 | 75 | # ADD DEPLOY STEP HERE 76 | config_file = utils.get_agent_config_artifact(plan, agent_config_json) 77 | validator.run(plan, config_file, origin_chain, rpc_urls, env_aws, custom_validator_image, log_level) 78 | relayer.run(plan, config_file, relay_chains, validator_key, rpc_urls, env_aws, custom_relayer_image, log_level) 79 | 80 | 81 | def get_aws_user_info(plan, aws_env): 82 | if len(aws_env) > 0: 83 | # use this instead of potential values passed via the `kurtosis` module 84 | return struct( 85 | access_key_id=aws_env["aws_access_key_id"], 86 | secret_access_key=aws_env["aws_secret_access_key"], 87 | bucket_region=aws_env["aws_bucket_region"], 88 | bucket_name=aws_env["aws_bucket_name"], 89 | bucket_user_folder=aws_env["aws_bucket_user_folder"] if "aws_bucket_user_folder" in aws_env else "" 90 | ) 91 | else: 92 | # the AWS values should be provided as env variables to the package. Otherwise this package cannot run 93 | return struct( 94 | access_key_id=kurtosis.aws_access_key_id, 95 | secret_access_key=kurtosis.aws_secret_access_key, 96 | bucket_region=kurtosis.aws_bucket_region, 97 | bucket_name=kurtosis.aws_bucket_name, 98 | bucket_user_folder=kurtosis.aws_bucket_user_folder 99 | ) 100 | -------------------------------------------------------------------------------- /relayer.star: -------------------------------------------------------------------------------- 1 | constants = import_module("./constants.star") 2 | 3 | def run(plan, config_file, relay_chains, relayer_key, rpc_urls, aws_env, relayer_image, log_level): 4 | env_vars = {} 5 | 6 | for chain in rpc_urls: 7 | env_var_name = constants.DEFAULT_ORIGIN_CHAIN_URL % chain 8 | env_vars[env_var_name] = rpc_urls[chain] 9 | 10 | env_vars["HYP_DEFAULTSIGNER_KEY"] = relayer_key 11 | plan.print(relay_chains) 12 | env_vars["HYP_RELAYCHAINS"]=",".join(relay_chains) 13 | env_vars["HYP_DB"] = constants.DB_FOLDER 14 | env_vars["CONFIG_FILES"] = "{}{}".format(constants.CONFIG_FILE_FOLDER, constants.CONFIG_FILE_NAME) 15 | env_vars["AWS_ACCESS_KEY_ID"] = aws_env.access_key_id 16 | env_vars["AWS_SECRET_ACCESS_KEY"] = aws_env.secret_access_key 17 | env_vars["HYP_TRACING_LEVEL"] = log_level 18 | 19 | relay_service_config = ServiceConfig( 20 | image=relayer_image, 21 | env_vars=env_vars, 22 | entrypoint=["/bin/sh", "-c", "./relayer"], 23 | files={ 24 | constants.CONFIG_FILE_FOLDER: config_file, 25 | constants.DB_FOLDER: Directory( 26 | persistent_key="relayer-db" 27 | ), 28 | } 29 | ) 30 | 31 | plan.add_service(name="relayer", config=relay_service_config) 32 | -------------------------------------------------------------------------------- /utils.star: -------------------------------------------------------------------------------- 1 | def get_agent_config_artifact(plan, agent_config_json): 2 | if len(agent_config_json) == 0: 3 | return plan.upload_files( 4 | src="./artifacts/agent_config.json", 5 | name="config_file", 6 | ) 7 | else: 8 | return plan.render_templates(config={ 9 | "agent_config.json": struct( 10 | template=json.encode(agent_config_json), 11 | data={}, 12 | ), 13 | }, 14 | name="config_file", 15 | ) -------------------------------------------------------------------------------- /validator.star: -------------------------------------------------------------------------------- 1 | constants = import_module("./constants.star") 2 | 3 | DEFAULT_SIGNER_KEY = "HYP_CHAINS_%s_SIGNER_KEY" 4 | ORIGIN_CHAIN = "origin_chain" 5 | 6 | def run(plan, config_file, origin_chain, rpc_urls, aws_env, validator_image, log_level): 7 | env_vars = { 8 | "HYP_GASPAYMENTENFORCEMENT": '[{"type": "none"}]' 9 | } 10 | 11 | chain = origin_chain["chain_name"] 12 | signer_id = origin_chain["signer_id"] 13 | 14 | env_vars[DEFAULT_SIGNER_KEY % chain] = signer_id 15 | 16 | if chain in rpc_urls: 17 | env_vars[constants.DEFAULT_ORIGIN_CHAIN_URL % chain] = rpc_urls[chain] 18 | 19 | env_vars["HYP_CHECKPOINTSYNCER_TYPE"] = "s3" 20 | env_vars["HYP_CHECKPOINTSYNCER_REGION"] = aws_env.bucket_region 21 | env_vars["HYP_CHECKPOINTSYNCER_BUCKET"] = aws_env.bucket_name 22 | env_vars["HYP_CHECKPOINTSYNCER_FOLDER"] = aws_env.bucket_user_folder 23 | env_vars["HYP_VALIDATOR_KEY"] = signer_id 24 | env_vars["HYP_ORIGINCHAINNAME"] = chain 25 | env_vars["HYP_VALIDATOR_REORGPERIOD"] = "1" 26 | env_vars["HYP_DB"] = constants.DB_FOLDER 27 | env_vars["CONFIG_FILES"] = "{}{}".format(constants.CONFIG_FILE_FOLDER, constants.CONFIG_FILE_NAME) 28 | env_vars["AWS_ACCESS_KEY_ID"] = aws_env.access_key_id 29 | env_vars["AWS_SECRET_ACCESS_KEY"] = aws_env.secret_access_key 30 | env_vars["HYP_TRACING_LEVEL"] = log_level 31 | 32 | validator_service_config = ServiceConfig( 33 | image=validator_image, 34 | env_vars=env_vars, 35 | entrypoint=["/bin/sh", "-c", "./validator"], 36 | files={ 37 | constants.CONFIG_FILE_FOLDER: config_file, 38 | constants.DB_FOLDER: Directory( 39 | persistent_key="validator-db" 40 | ), 41 | } 42 | ) 43 | 44 | plan.add_service(name="validator", config=validator_service_config) 45 | --------------------------------------------------------------------------------