From 52ad3fa1b9106597e53033aeb04068d4f933ee96 Mon Sep 17 00:00:00 2001 From: Alysia Tech Date: Thu, 19 Sep 2024 20:21:00 -0400 Subject: [PATCH] add multisig wallet and prover mode to deployment steps in the deployer utils (#2028) * set the owner of the fee contract to the multisig_address when it is deployed using the deployer util * Make multisig addr optional in deploy script * Set multisig ownership for light client contract * Set permissioned prover address when deploying light client contract * Make multisig optional for dev node --------- Co-authored-by: Jeb Bearer --- .env | 12 +++++-- docker-compose.yaml | 2 ++ sequencer/src/bin/deploy.rs | 26 ++++++++++++++ sequencer/src/bin/espresso-dev-node.rs | 34 ++++++++++++++++-- utils/src/deployer.rs | 48 ++++++++++++++++++++++---- 5 files changed, 110 insertions(+), 12 deletions(-) diff --git a/.env b/.env index a5073aa48..e757443c5 100644 --- a/.env +++ b/.env @@ -58,9 +58,10 @@ ESPRESSO_BUILDER_ETH_ACCOUNT_INDEX=8 ESPRESSO_DEPLOYER_ACCOUNT_INDEX=9 # Contracts -ESPRESSO_SEQUENCER_HOTSHOT_ADDRESS=0xb19b36b1456e65e3a6d514d3f715f204bd59f431 -ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS=0x0c8e79f3534b00d9a3d4a856b665bf4ebc22f2ba +ESPRESSO_SEQUENCER_HOTSHOT_ADDRESS=0x8ce361602b935680e8dec218b820ff5056beb7af +ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS=0xed1db453c3156ff3155a97ad217b3087d5dc5f6e ESPRESSO_SEQUENCER_LIGHTCLIENT_ADDRESS=$ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS +ESPRESSO_SEQUENCER_PERMISSIONED_PROVER=0x14dc79964da2c08b23698b3d3cc7ca32193d9955 # Example sequencer demo private keys ESPRESSO_DEMO_SEQUENCER_STAKING_PRIVATE_KEY_0=BLS_SIGNING_KEY~lNDh4Pn-pTAyzyprOAFdXHwhrKhEwqwtMtkD3CZF4x3o @@ -132,3 +133,10 @@ ESPRESSO_SEQUENCER_FETCH_RATE_LIMIT=25 # Query service stress test ESPRESSO_NASTY_CLIENT_PORT=24011 + +# Test setting upgradable contract address after deployment. +# +# Setting this address to a random address effectively relinuquishes ownership of the contracts, +# which ensures that all services work correctly in the demo without admin access. In a real +# deployment, we would set this to the address of a multisig wallet. +ESPRESSO_SEQUENCER_ETH_MULTISIG_ADDRESS=8626f6940e2eb28930efb4cef49b2d1f2c9c1199 diff --git a/docker-compose.yaml b/docker-compose.yaml index 42af6f41b..3f3e9a23f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -36,7 +36,9 @@ services: - ESPRESSO_SEQUENCER_L1_PROVIDER - ESPRESSO_SEQUENCER_URL - ESPRESSO_SEQUENCER_STAKE_TABLE_CAPACITY + - ESPRESSO_SEQUENCER_PERMISSIONED_PROVER - ESPRESSO_DEPLOYER_ACCOUNT_INDEX + - ESPRESSO_SEQUENCER_ETH_MULTISIG_ADDRESS - RUST_LOG - RUST_LOG_FORMAT - ASYNC_STD_THREAD_COUNT diff --git a/sequencer/src/bin/deploy.rs b/sequencer/src/bin/deploy.rs index 5552ff5d8..bca70a3bd 100644 --- a/sequencer/src/bin/deploy.rs +++ b/sequencer/src/bin/deploy.rs @@ -1,6 +1,7 @@ use std::{fs::File, io::stdout, path::PathBuf}; use clap::Parser; +use ethers::types::Address; use futures::FutureExt; use hotshot_stake_table::config::STAKE_TABLE_CAPACITY; use hotshot_state_prover::service::light_client_genesis; @@ -57,6 +58,19 @@ struct Options { default_value = "test test test test test test test test test test test junk" )] mnemonic: String, + + /// Address for the multisig wallet that will be the admin + /// + /// If provided, this the multisig wallet that will be able to upgrade contracts and execute + /// admin only functions on contracts. If not provided, admin power for all contracts will be + /// held by the account used to deploy the contracts (determined from MNEMONIC, ACCOUNT_INDEX). + #[clap( + long, + name = "MULTISIG_ADDRESS", + env = "ESPRESSO_SEQUENCER_ETH_MULTISIG_ADDRESS" + )] + multisig_address: Option
, + /// Account index in the L1 wallet generated by MNEMONIC to use when deploying the contracts. #[clap( long, @@ -87,6 +101,16 @@ struct Options { #[clap(short, long, env = "ESPRESSO_SEQUENCER_STAKE_TABLE_CAPACITY", default_value_t = STAKE_TABLE_CAPACITY)] pub stake_table_capacity: usize, + /// Permissioned prover address for light client contract. + /// + /// If the light client contract is being deployed and this is set, the prover will be + /// permissioned so that only this address can update the light client state. Otherwise, proving + /// will be permissionless. + /// + /// If the light client contract is not being deployed, this option is ignored. + #[clap(long, env = "ESPRESSO_SEQUENCER_PERMISSIONED_PROVER")] + permissioned_prover: Option
, + #[clap(flatten)] logging: logging::Config, } @@ -106,9 +130,11 @@ async fn main() -> anyhow::Result<()> { opt.rpc_url, opt.mnemonic, opt.account_index, + opt.multisig_address, opt.use_mock_contract, opt.only, genesis, + opt.permissioned_prover, contracts, ) .await?; diff --git a/sequencer/src/bin/espresso-dev-node.rs b/sequencer/src/bin/espresso-dev-node.rs index 035747b61..1ab72046e 100644 --- a/sequencer/src/bin/espresso-dev-node.rs +++ b/sequencer/src/bin/espresso-dev-node.rs @@ -9,7 +9,7 @@ use ethers::{ middleware::{MiddlewareBuilder, SignerMiddleware}, providers::{Http, Middleware, Provider}, signers::{coins_bip39::English, MnemonicBuilder, Signer}, - types::{Address, U256}, + types::{Address, H160, U256}, }; use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt}; use hotshot_state_prover::service::{ @@ -62,6 +62,16 @@ struct Args { )] account_index: u32, + /// Address for the multisig wallet that will be the admin + /// + /// This the multisig wallet that will be upgrade contracts and execute admin only functions on contracts + #[clap( + long, + name = "MULTISIG_ADDRESS", + env = "ESPRESSO_SEQUENCER_ETH_MULTISIG_ADDRESS" + )] + multisig_address: Option, + /// The frequency of updating the light client state, expressed in update interval #[clap( long, value_parser = parse_duration, default_value = "20s", env = "ESPRESSO_STATE_PROVER_UPDATE_INTERVAL")] update_interval: Duration, @@ -87,6 +97,11 @@ struct Args { #[clap(long, env = "ESPRESSO_SEQUENCER_DEPLOYER_ALT_INDICES")] alt_account_indices: Vec, + /// Optional list of multisig addresses for the alternate chains. + /// If there are fewer multisig addresses provided than chains, the base MULTISIG_ADDRESS will be used. + #[arg(long, env = "ESPRESSO_DEPLOYER_ALT_MULTISIG_ADDRESSES", num_args = 1.., value_delimiter = ',')] + alt_multisig_addresses: Vec, + /// The frequency of updating the light client state for alt chains. /// If there are fewer intervals provided than chains, the base update interval will be used. #[clap(long, value_parser = parse_duration, env = "ESPRESSO_STATE_PROVER_ALT_UPDATE_INTERVALS", num_args = 1.., value_delimiter = ',')] @@ -134,9 +149,11 @@ async fn main() -> anyhow::Result<()> { rpc_url, mnemonic, account_index, + multisig_address, alt_chain_providers, alt_mnemonics, alt_account_indices, + alt_multisig_addresses, sequencer_api_port, sequencer_api_max_connections, builder_port, @@ -204,10 +221,11 @@ async fn main() -> anyhow::Result<()> { let mut mock_contracts = BTreeMap::new(); let mut handles = FuturesUnordered::new(); // deploy contract for L1 and each alt chain - for (url, mnemonic, account_index, update_interval, retry_interval) in once(( + for (url, mnemonic, account_index, multisig_address, update_interval, retry_interval) in once(( l1_url.clone(), mnemonic.clone(), account_index, + multisig_address, update_interval, retry_interval, )) @@ -220,6 +238,12 @@ async fn main() -> anyhow::Result<()> { .into_iter() .chain(std::iter::repeat(account_index)), ) + .zip( + alt_multisig_addresses + .into_iter() + .map(Some) + .chain(std::iter::repeat(multisig_address)), + ) .zip( alt_prover_update_intervals .into_iter() @@ -230,7 +254,9 @@ async fn main() -> anyhow::Result<()> { .into_iter() .chain(std::iter::repeat(retry_interval)), ) - .map(|((((url, mnc), idx), update), retry)| (url.clone(), mnc, idx, update, retry)), + .map(|(((((url, mnc), idx), mlts), update), retry)| { + (url.clone(), mnc, idx, mlts, update, retry) + }), ) { tracing::info!("deploying the contract for provider: {url:?}"); @@ -238,9 +264,11 @@ async fn main() -> anyhow::Result<()> { url.clone(), mnemonic.clone(), account_index, + multisig_address, true, None, async { Ok(lc_genesis.clone()) }.boxed(), + None, contracts.clone(), ) .await?; diff --git a/utils/src/deployer.rs b/utils/src/deployer.rs index 79e016ee3..9e906481d 100644 --- a/utils/src/deployer.rs +++ b/utils/src/deployer.rs @@ -311,9 +311,11 @@ pub async fn deploy( l1url: Url, mnemonic: String, account_index: u32, + multisig_address: Option, use_mock_contract: bool, only: Option>, genesis: BoxFuture<'_, anyhow::Result<(ParsedLightClientState, ParsedStakeTableState)>>, + permissioned_prover: Option
, mut contracts: Contracts, ) -> anyhow::Result { let provider = Provider::::try_from(l1url.to_string())?; @@ -323,17 +325,17 @@ pub async fn deploy( .index(account_index)? .build()? .with_chain_id(chain_id); - let owner = wallet.address(); + let deployer = wallet.address(); let l1 = Arc::new(SignerMiddleware::new(provider.clone(), wallet)); // As a sanity check, check that the deployer address has some balance of ETH it can use to pay // gas. - let balance = l1.get_balance(owner, None).await?; + let balance = l1.get_balance(deployer, None).await?; ensure!( balance > 0.into(), - "deployer account {owner:#x} is not funded!" + "deployer account {deployer:#x} is not funded!" ); - tracing::info!(%balance, "deploying from address {owner:#x}"); + tracing::info!(%balance, "deploying from address {deployer:#x}"); // `HotShot.sol` if should_deploy(ContractGroup::HotShot, &only) { @@ -363,7 +365,7 @@ pub async fn deploy( let (genesis_lc, genesis_stake) = genesis.await?.clone(); let data = light_client - .initialize(genesis_lc.into(), genesis_stake.into(), 864000, owner) + .initialize(genesis_lc.into(), genesis_stake.into(), 864000, deployer) .calldata() .context("calldata for initialize transaction not available")?; let light_client_proxy_address = contracts @@ -380,6 +382,25 @@ pub async fn deploy( { panic!("Light Client contract's address is not a proxy"); } + + // Instantiate a wrapper with the proxy address and light client ABI. + let proxy = LightClient::new(light_client_proxy_address, l1.clone()); + + // Perission the light client prover. + if let Some(prover) = permissioned_prover { + tracing::info!(%light_client_proxy_address, %prover, "setting permissioned prover"); + proxy.set_permissioned_prover(prover).send().await?.await?; + } + + // Transfer ownership to the multisig wallet if provided. + if let Some(owner) = multisig_address { + tracing::info!( + %light_client_proxy_address, + %owner, + "transferring light client proxy ownership to multisig", + ); + proxy.transfer_ownership(owner).send().await?.await?; + } } // `FeeContract.sol` @@ -389,7 +410,7 @@ pub async fn deploy( .await?; let fee_contract = FeeContract::new(fee_contract_address, l1.clone()); let data = fee_contract - .initialize(owner) + .initialize(deployer) .calldata() .context("calldata for initialize transaction not available")?; let fee_contract_proxy_address = contracts @@ -406,6 +427,19 @@ pub async fn deploy( { panic!("Fee contract's address is not a proxy"); } + + // Instantiate a wrapper with the proxy address and fee contract ABI. + let proxy = FeeContract::new(fee_contract_proxy_address, l1.clone()); + + // Transfer ownership to the multisig wallet if provided. + if let Some(owner) = multisig_address { + tracing::info!( + %fee_contract_proxy_address, + %owner, + "transferring fee contract proxy ownership to multisig", + ); + proxy.transfer_ownership(owner).send().await?.await?; + } } Ok(contracts) @@ -440,7 +474,7 @@ pub async fn is_proxy_contract( #[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)] pub enum ContractGroup { #[clap(name = "hotshot")] - HotShot, + HotShot, // TODO: confirm whether we keep HotShot in the contract group FeeContract, LightClient, }