diff --git a/rust/agents/validator/src/submit.rs b/rust/agents/validator/src/submit.rs index d9b3e7b99..f16154c2c 100644 --- a/rust/agents/validator/src/submit.rs +++ b/rust/agents/validator/src/submit.rs @@ -46,9 +46,10 @@ impl ValidatorSubmitter { async fn main_task(self) -> Result<()> { // Sign and post the validator announcement let announcement = Announcement { + validator: self.signer.eth_address(), mailbox_address: self.mailbox.address(), mailbox_domain: self.mailbox.domain().id(), - storage_metadata: self.checkpoint_syncer.announcement_metadata(), + storage_location: self.checkpoint_syncer.announcement_location(), }; let signed_announcement = self.signer.sign(announcement).await?; self.checkpoint_syncer diff --git a/rust/config/test/test_config.json b/rust/config/test/test_config.json index d3ca42d93..93b191a47 100644 --- a/rust/config/test/test_config.json +++ b/rust/config/test/test_config.json @@ -6,7 +6,8 @@ "domain": "13371", "addresses": { "mailbox": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", - "interchainGasPaymaster": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "interchainGasPaymaster": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", + "validatorAnnounce": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, "signer": null, "protocol": "ethereum", @@ -23,8 +24,9 @@ "name": "test2", "domain": "13372", "addresses": { - "mailbox": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "interchainGasPaymaster": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "mailbox": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", + "interchainGasPaymaster": "0x9A676e781A523b5d0C0e43731313A708CB607508", + "validatorAnnounce": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" }, "signer": null, "protocol": "ethereum", @@ -34,15 +36,16 @@ "url": "" }, "index": { - "from": "12" + "from": "13" } }, "test3": { "name": "test3", "domain": "13373", "addresses": { - "mailbox": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "interchainGasPaymaster": "0x59b670e9fA9D0A427751Af201D676719a970857b" + "mailbox": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "interchainGasPaymaster": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "validatorAnnounce": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F" }, "signer": null, "protocol": "ethereum", @@ -52,7 +55,7 @@ "url": "" }, "index": { - "from": "20" + "from": "23" } } }, @@ -61,4 +64,4 @@ "level": "debug", "fmt": "json" } -} \ No newline at end of file +} diff --git a/rust/hyperlane-base/src/traits/checkpoint_syncer.rs b/rust/hyperlane-base/src/traits/checkpoint_syncer.rs index 88d8f1bf6..d3d5e002b 100644 --- a/rust/hyperlane-base/src/traits/checkpoint_syncer.rs +++ b/rust/hyperlane-base/src/traits/checkpoint_syncer.rs @@ -16,6 +16,6 @@ pub trait CheckpointSyncer: Debug + Send + Sync { async fn write_checkpoint(&self, signed_checkpoint: &SignedCheckpoint) -> Result<()>; /// Write the signed announcement to this syncer async fn write_announcement(&self, signed_announcement: &SignedAnnouncement) -> Result<()>; - /// Return the announcement storage metadata for this syncer - fn announcement_metadata(&self) -> String; + /// Return the announcement storage location for this syncer + fn announcement_location(&self) -> String; } diff --git a/rust/hyperlane-base/src/types/local_storage.rs b/rust/hyperlane-base/src/types/local_storage.rs index 7b78e48a2..1039b03f4 100644 --- a/rust/hyperlane-base/src/types/local_storage.rs +++ b/rust/hyperlane-base/src/types/local_storage.rs @@ -101,9 +101,9 @@ impl CheckpointSyncer for LocalStorage { Ok(()) } - fn announcement_metadata(&self) -> String { - let mut metadata: String = "file://".to_owned(); - metadata.push_str(self.announcement_file_path().as_ref()); - metadata + fn announcement_location(&self) -> String { + let mut location: String = "file://".to_owned(); + location.push_str(self.announcement_file_path().as_ref()); + location } } diff --git a/rust/hyperlane-base/src/types/s3_storage.rs b/rust/hyperlane-base/src/types/s3_storage.rs index 6875138f6..d0e061bca 100644 --- a/rust/hyperlane-base/src/types/s3_storage.rs +++ b/rust/hyperlane-base/src/types/s3_storage.rs @@ -188,11 +188,11 @@ impl CheckpointSyncer for S3Storage { .await?; Ok(()) } - fn announcement_metadata(&self) -> String { - let mut metadata: String = "s3://".to_owned(); - metadata.push_str(self.bucket.as_ref()); - metadata.push('/'); - metadata.push_str(self.region.name()); - metadata + fn announcement_location(&self) -> String { + let mut location: String = "s3://".to_owned(); + location.push_str(self.bucket.as_ref()); + location.push('/'); + location.push_str(self.region.name()); + location } } diff --git a/rust/hyperlane-core/src/types/announcement.rs b/rust/hyperlane-core/src/types/announcement.rs index 57868abf5..cc46b3491 100644 --- a/rust/hyperlane-core/src/types/announcement.rs +++ b/rust/hyperlane-core/src/types/announcement.rs @@ -2,25 +2,27 @@ use async_trait::async_trait; use serde::{Deserialize, Serialize}; use sha3::{digest::Update, Digest, Keccak256}; -use crate::{utils::domain_hash, Signable, SignedType, H256}; +use crate::{utils::domain_hash, Signable, SignedType, H160, H256}; /// An Hyperlane checkpoint #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct Announcement { + /// The validator address + pub validator: H160, /// The mailbox address pub mailbox_address: H256, /// The mailbox chain pub mailbox_domain: u32, - /// The checkpointed root - pub storage_metadata: String, + /// The location of signed checkpoints + pub storage_location: String, } impl std::fmt::Display for Announcement { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "Announcement(domain: {}, mailbox: {:x}, metadata: {})", - self.mailbox_domain, self.mailbox_address, self.storage_metadata + "Announcement(domain: {}, mailbox: {:x}, location: {})", + self.mailbox_domain, self.mailbox_address, self.storage_location ) } } @@ -33,7 +35,7 @@ impl Signable for Announcement { H256::from_slice( Keccak256::new() .chain(domain_hash(self.mailbox_address, self.mailbox_domain)) - .chain(&self.storage_metadata) + .chain(&self.storage_location) .finalize() .as_slice(), ) diff --git a/rust/utils/run-locally/src/main.rs b/rust/utils/run-locally/src/main.rs index 0114c03f7..694d79f36 100644 --- a/rust/utils/run-locally/src/main.rs +++ b/rust/utils/run-locally/src/main.rs @@ -313,6 +313,31 @@ fn main() -> ExitCode { })); state.validator = Some(validator); + // Rebuild the SDK to pick up the deployed contracts + println!("Rebuilding sdk..."); + build_cmd( + &["yarn", "build"], + &build_log, + log_all, + Some("../typescript/sdk"), + ); + + // Register the validator announcement + println!("Announcing validator..."); + let mut announce = Command::new("yarn"); + announce.arg("announce"); + announce.args([ + "--checkpointsdir", + checkpoints_dir.path().to_str().unwrap(), + "--chain", + "test1", + ]); + announce + .current_dir("../typescript/infra") + .stdout(Stdio::piped()) + .spawn() + .expect("Failed to announce validator"); + println!("Setup complete! Agents running in background..."); println!("Ctrl+C to end execution..."); diff --git a/typescript/infra/hardhat.config.ts b/typescript/infra/hardhat.config.ts index cc2a5ab3a..42694202c 100644 --- a/typescript/infra/hardhat.config.ts +++ b/typescript/infra/hardhat.config.ts @@ -1,7 +1,10 @@ import '@nomiclabs/hardhat-etherscan'; import '@nomiclabs/hardhat-waffle'; +import { ethers } from 'ethers'; +import { readFileSync } from 'fs'; import { task } from 'hardhat/config'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import * as path from 'path'; import { TestSendReceiver__factory } from '@hyperlane-xyz/core'; import { @@ -35,6 +38,45 @@ const chainSummary = async ( return summary; }; +task('announce', 'Registers validator announcement') + .addParam('checkpointsdir', 'Directory containing announcement json file') + .addParam('chain', 'Chain to announce on') + .setAction( + async ( + taskArgs: { checkpointsdir: string; chain: ChainName }, + hre: HardhatRuntimeEnvironment, + ) => { + const environment = 'test'; + const config = getCoreEnvironmentConfig(environment); + const [signer] = await hre.ethers.getSigners(); + const multiProvider = getTestMultiProvider( + signer, + config.transactionConfigs, + ); + const core = HyperlaneCore.fromEnvironment(environment, multiProvider); + const announcementFilepath = path.join( + taskArgs.checkpointsdir, + 'announcement.json', + ); + const announcement = JSON.parse( + readFileSync(announcementFilepath, 'utf-8'), + ); + const signature = ethers.utils.hexConcat([ + announcement.signature.r, + announcement.signature.s, + ethers.utils.hexValue(announcement.signature.v), + ]); + const tx = await core + .getContracts(taskArgs.chain) + .validatorAnnounce.announce( + announcement.announcement.validator, + announcement.announcement.storage_location, + signature, + ); + await tx.wait(); + }, + ); + task('kathy', 'Dispatches random hyperlane messages') .addParam( 'rounds', diff --git a/typescript/infra/package.json b/typescript/infra/package.json index b0df28384..c5ed4aae9 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -62,6 +62,7 @@ "clean": "rm -rf ./dist ./cache", "check": "tsc --noEmit", "kathy": "hardhat kathy --network localhost", + "announce": "hardhat announce --network localhost", "node": "hardhat node", "prettier": "prettier --write *.ts ./src ./config ./scripts ./test", "test": "hardhat test" diff --git a/typescript/infra/src/config/agent.ts b/typescript/infra/src/config/agent.ts index 78c5395c6..235b0dfa1 100644 --- a/typescript/infra/src/config/agent.ts +++ b/typescript/infra/src/config/agent.ts @@ -251,6 +251,7 @@ export type RustConnection = export type RustCoreAddresses = { mailbox: types.Address; interchainGasPaymaster: types.Address; + validatorAnnounce: types.Address; }; export type RustChainSetup = { diff --git a/typescript/infra/src/core/deploy.ts b/typescript/infra/src/core/deploy.ts index 479137327..7ff62a20e 100644 --- a/typescript/infra/src/core/deploy.ts +++ b/typescript/infra/src/core/deploy.ts @@ -4,6 +4,7 @@ import { InterchainGasPaymaster, Mailbox, ProxyAdmin, + ValidatorAnnounce, } from '@hyperlane-xyz/core'; import { ChainMap, @@ -70,6 +71,19 @@ export class HyperlaneCoreInfraDeployer< ); } + async deployValidatorAnnounce( + chain: LocalChain, + mailboxAddress: types.Address, + ): Promise { + const deployOpts = { + create2Salt: ethers.utils.solidityKeccak256( + ['string', 'string', 'uint8'], + [this.environment, 'validatorAnnounce', 1], + ), + }; + return super.deployValidatorAnnounce(chain, mailboxAddress, deployOpts); + } + writeRustConfigs(directory: string) { const rustConfig: RustConfig = { environment: this.environment, @@ -88,7 +102,7 @@ export class HyperlaneCoreInfraDeployer< contracts == undefined || contracts.mailbox == undefined || contracts.interchainGasPaymaster == undefined || - contracts.multisigIsm == undefined + contracts.validatorAnnounce == undefined ) { return; } @@ -99,6 +113,7 @@ export class HyperlaneCoreInfraDeployer< addresses: { mailbox: contracts.mailbox.contract.address, interchainGasPaymaster: contracts.interchainGasPaymaster.address, + validatorAnnounce: contracts.validatorAnnounce.address, }, signer: null, protocol: 'ethereum', diff --git a/typescript/sdk/src/consts/environments/test.json b/typescript/sdk/src/consts/environments/test.json index d03e1bf65..f82547683 100644 --- a/typescript/sdk/src/consts/environments/test.json +++ b/typescript/sdk/src/consts/environments/test.json @@ -1,5 +1,6 @@ { "test1": { + "validatorAnnounce": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", "proxyAdmin": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", "interchainGasPaymaster": { "kind": "Transparent", @@ -14,31 +15,33 @@ "multisigIsm": "0x5FbDB2315678afecb367f032d93F642f64180aa3" }, "test2": { - "proxyAdmin": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + "validatorAnnounce": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", + "proxyAdmin": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", "interchainGasPaymaster": { "kind": "Transparent", - "proxy": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "implementation": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + "proxy": "0x9A676e781A523b5d0C0e43731313A708CB607508", + "implementation": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" }, "mailbox": { "kind": "Transparent", - "proxy": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "implementation": "0x9A676e781A523b5d0C0e43731313A708CB607508" + "proxy": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", + "implementation": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" }, - "multisigIsm": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + "multisigIsm": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, "test3": { - "proxyAdmin": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", + "validatorAnnounce": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F", + "proxyAdmin": "0x59b670e9fA9D0A427751Af201D676719a970857b", "interchainGasPaymaster": { "kind": "Transparent", - "proxy": "0x59b670e9fA9D0A427751Af201D676719a970857b", - "implementation": "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d" + "proxy": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "implementation": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1" }, "mailbox": { "kind": "Transparent", - "proxy": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "implementation": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1" + "proxy": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "implementation": "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f" }, - "multisigIsm": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + "multisigIsm": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" } } diff --git a/typescript/sdk/src/core/contracts.ts b/typescript/sdk/src/core/contracts.ts index 544e07dc9..6baeaed19 100644 --- a/typescript/sdk/src/core/contracts.ts +++ b/typescript/sdk/src/core/contracts.ts @@ -10,6 +10,8 @@ import { MultisigIsm__factory, ProxyAdmin, ProxyAdmin__factory, + ValidatorAnnounce, + ValidatorAnnounce__factory, } from '@hyperlane-xyz/core'; import { ProxiedContract, TransparentProxyAddresses } from '../proxy'; @@ -25,11 +27,13 @@ export type CoreContracts = ConnectionClientContracts & { mailbox: ProxiedContract; multisigIsm: MultisigIsm; proxyAdmin: ProxyAdmin; + validatorAnnounce: ValidatorAnnounce; }; export const coreFactories = { interchainAccountRouter: new InterchainAccountRouter__factory(), interchainQueryRouter: new InterchainQueryRouter__factory(), + validatorAnnounce: new ValidatorAnnounce__factory(), create2Factory: new Create2Factory__factory(), proxyAdmin: new ProxyAdmin__factory(), interchainGasPaymaster: new InterchainGasPaymaster__factory(), diff --git a/typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts b/typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts index 7702d56c4..77930da36 100644 --- a/typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts +++ b/typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts @@ -7,6 +7,7 @@ import { MultisigIsm, Ownable, ProxyAdmin, + ValidatorAnnounce, } from '@hyperlane-xyz/core'; import type { types } from '@hyperlane-xyz/utils'; @@ -79,6 +80,20 @@ export class HyperlaneCoreDeployer< return mailbox; } + async deployValidatorAnnounce( + chain: LocalChain, + mailboxAddress: string, + deployOpts?: DeployOptions, + ): Promise { + const validatorAnnounce = await this.deployContract( + chain, + 'validatorAnnounce', + [mailboxAddress], + deployOpts, + ); + return validatorAnnounce; + } + async deployMultisigIsm( chain: LocalChain, ): Promise { @@ -173,11 +188,16 @@ export class HyperlaneCoreDeployer< multisigIsm.address, proxyAdmin, ); + const validatorAnnounce = await this.deployValidatorAnnounce( + chain, + mailbox.address, + ); // Mailbox ownership is transferred upon initialization. const ownables: Ownable[] = [multisigIsm, proxyAdmin]; await this.transferOwnershipOfContracts(chain, ownables); return { + validatorAnnounce, proxyAdmin, interchainGasPaymaster, mailbox,