diff --git a/.env b/.env index 2e8ab1987e..dee69dea6a 100644 --- a/.env +++ b/.env @@ -49,7 +49,6 @@ ESPRESSO_STATE_RELAY_SERVER_URL=http://state-relay-server:${ESPRESSO_STATE_RELAY # Ethereum accounts (note 11-15 are used by the sequencer nodes) ESPRESSO_SEQUENCER_HOTSHOT_ACCOUNT_INDEX=5 ESPRESSO_SEQUENCER_STATE_PROVER_ACCOUNT_INDEX=7 -ESPRESSO_BUILDER_ETH_ACCOUNT_INDEX=8 ESPRESSO_DEPLOYER_ACCOUNT_INDEX=9 # Contracts diff --git a/builder/src/bin/permissioned-builder.rs b/builder/src/bin/permissioned-builder.rs index c768c92ec2..52bdb48681 100644 --- a/builder/src/bin/permissioned-builder.rs +++ b/builder/src/bin/permissioned-builder.rs @@ -204,9 +204,7 @@ async fn main() -> anyhow::Result<()> { let builder_key_pair = EthKeyPair::from_mnemonic(&opt.eth_mnemonic, opt.eth_account_index)?; let builder_params = BuilderParams { - mnemonic: opt.eth_mnemonic, prefunded_accounts: vec![], - eth_account_index: opt.eth_account_index, }; // Parse supplied Libp2p addresses to their socket form diff --git a/builder/src/bin/permissionless-builder.rs b/builder/src/bin/permissionless-builder.rs index e8675b56fb..d4c5a27c8f 100644 --- a/builder/src/bin/permissionless-builder.rs +++ b/builder/src/bin/permissionless-builder.rs @@ -9,7 +9,7 @@ use hotshot_types::light_client::StateSignKey; use hotshot_types::signature_key::BLSPrivKey; use hotshot_types::traits::node_implementation::ConsensusTime; use sequencer::eth_signature_key::EthKeyPair; -use sequencer::{BuilderParams, L1Params}; +use sequencer::L1Params; use snafu::Snafu; use std::num::NonZeroUsize; use std::{collections::HashMap, path::PathBuf, str::FromStr, time::Duration}; @@ -162,24 +162,12 @@ async fn main() -> anyhow::Result<()> { }; let builder_key_pair = EthKeyPair::from_mnemonic(&opt.eth_mnemonic, opt.eth_account_index)?; - - let builder_params = BuilderParams { - mnemonic: opt.eth_mnemonic, - prefunded_accounts: vec![], - eth_account_index: opt.eth_account_index, - }; - let bootstrapped_view = ViewNumber::new(opt.view_number); let builder_server_url: Url = format!("http://0.0.0.0:{}", opt.port).parse().unwrap(); - let instance_state = build_instance_state( - l1_params, - builder_params, - opt.state_peers, - sequencer_version, - ) - .unwrap(); + let instance_state = + build_instance_state(l1_params, opt.state_peers, sequencer_version).unwrap(); let _builder_config = BuilderConfig::init( builder_key_pair, diff --git a/builder/src/lib.rs b/builder/src/lib.rs index d487dff730..da94cc795e 100644 --- a/builder/src/lib.rs +++ b/builder/src/lib.rs @@ -389,12 +389,9 @@ pub mod testing { _pd: Default::default(), }; - let key = Self::builder_key(i); - tracing::info!("node {i} is builder {:x}", key.address()); let node_state = NodeState::new( ChainConfig::default(), L1Client::new(self.anvil.endpoint().parse().unwrap(), Address::default()), - key, MockStateCatchup::default(), ) .with_genesis(ValidatedState::default()); @@ -418,14 +415,6 @@ pub mod testing { handle } - pub fn builder_key(i: usize) -> EthKeyPair { - EthKeyPair::from_mnemonic( - "test test test test test test test test test test test junk", - i as u32, - ) - .unwrap() - } - // url for the hotshot event streaming api pub fn hotshot_event_streaming_api_url() -> Url { // spawn the event streaming api @@ -537,19 +526,12 @@ pub mod testing { hotshot_builder_api_url: Url, ) -> Self { // setup the instance state - let key = HotShotTestConfig::builder_key(Self::SUBSCRIBED_DA_NODE_ID); - tracing::info!( - "node {} is builder {:x}", - Self::SUBSCRIBED_DA_NODE_ID, - key.address() - ); let node_state = NodeState::new( ChainConfig::default(), L1Client::new( hotshot_test_config.get_anvil().endpoint().parse().unwrap(), Address::default(), ), - key, MockStateCatchup::default(), ) .with_genesis(ValidatedState::default()); @@ -601,19 +583,12 @@ pub mod testing { hotshot_builder_api_url: Url, ) -> Self { // setup the instance state - let key = HotShotTestConfig::builder_key(HotShotTestConfig::NUM_STAKED_NODES); - tracing::info!( - "node {} is builder {:x}", - HotShotTestConfig::NUM_STAKED_NODES, - key.address() - ); let node_state = NodeState::new( ChainConfig::default(), L1Client::new( hotshot_test_config.get_anvil().endpoint().parse().unwrap(), Address::default(), ), - key, MockStateCatchup::default(), ) .with_genesis(ValidatedState::default()); diff --git a/builder/src/non_permissioned.rs b/builder/src/non_permissioned.rs index 163148757f..7f4251f4d5 100644 --- a/builder/src/non_permissioned.rs +++ b/builder/src/non_permissioned.rs @@ -58,22 +58,13 @@ pub struct BuilderConfig { pub fn build_instance_state( l1_params: L1Params, - builder_params: BuilderParams, state_peers: Vec, _: Ver, ) -> anyhow::Result { - // creating the instance state without any builder mnemonic - let builder_key = - EthKeyPair::from_mnemonic(&builder_params.mnemonic, builder_params.eth_account_index)?; - - tracing::info!("Builder account address {:?}", builder_key.address()); - let l1_client = L1Client::new(l1_params.url, Address::default()); - let instance_state = NodeState::new( ChainConfig::default(), l1_client, - builder_key, Arc::new(StatePeers::::from_urls(state_peers)), ); Ok(instance_state) diff --git a/builder/src/permissioned.rs b/builder/src/permissioned.rs index a8e467383a..0959e616c6 100644 --- a/builder/src/permissioned.rs +++ b/builder/src/permissioned.rs @@ -238,11 +238,6 @@ pub async fn init_node::from_urls(network_params.state_peers)), ); diff --git a/docker-compose.yaml b/docker-compose.yaml index 2c1167cc72..e4cca0a186 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -175,8 +175,6 @@ services: - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_0 - ESPRESSO_SEQUENCER_LIBP2P_BIND_ADDRESS=0.0.0.0:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_0 - ESPRESSO_SEQUENCER_LIBP2P_ADVERTISE_ADDRESS=sequencer0:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_0 - - ESPRESSO_SEQUENCER_ETH_MNEMONIC - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=10 - ESPRESSO_SEQUENCER_PREFUNDED_BUILDER_ACCOUNTS - RUST_LOG - RUST_LOG_FORMAT @@ -217,8 +215,6 @@ services: - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_1 - ESPRESSO_SEQUENCER_LIBP2P_BIND_ADDRESS=0.0.0.0:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_1 - ESPRESSO_SEQUENCER_LIBP2P_ADVERTISE_ADDRESS=sequencer1:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_1 - - ESPRESSO_SEQUENCER_ETH_MNEMONIC - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=11 - ESPRESSO_SEQUENCER_PREFUNDED_BUILDER_ACCOUNTS - RUST_LOG - RUST_LOG_FORMAT @@ -254,8 +250,6 @@ services: - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_2 - ESPRESSO_SEQUENCER_LIBP2P_BIND_ADDRESS=0.0.0.0:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_2 - ESPRESSO_SEQUENCER_LIBP2P_ADVERTISE_ADDRESS=sequencer2:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_2 - - ESPRESSO_SEQUENCER_ETH_MNEMONIC - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=12 - ESPRESSO_SEQUENCER_PREFUNDED_BUILDER_ACCOUNTS - RUST_LOG - RUST_LOG_FORMAT @@ -290,8 +284,6 @@ services: - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_3 - ESPRESSO_SEQUENCER_LIBP2P_BIND_ADDRESS=0.0.0.0:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_3 - ESPRESSO_SEQUENCER_LIBP2P_ADVERTISE_ADDRESS=sequencer3:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_3 - - ESPRESSO_SEQUENCER_ETH_MNEMONIC - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=13 - ESPRESSO_SEQUENCER_PREFUNDED_BUILDER_ACCOUNTS - RUST_LOG - RUST_LOG_FORMAT @@ -326,8 +318,6 @@ services: - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_4 - ESPRESSO_SEQUENCER_LIBP2P_BIND_ADDRESS=0.0.0.0:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_4 - ESPRESSO_SEQUENCER_LIBP2P_ADVERTISE_ADDRESS=sequencer4:$ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_4 - - ESPRESSO_SEQUENCER_ETH_MNEMONIC - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=14 - ESPRESSO_SEQUENCER_PREFUNDED_BUILDER_ACCOUNTS - RUST_LOG - RUST_LOG_FORMAT diff --git a/process-compose.yaml b/process-compose.yaml index b83ef97ab4..90c0c38913 100644 --- a/process-compose.yaml +++ b/process-compose.yaml @@ -87,7 +87,6 @@ processes: - ESPRESSO_SEQUENCER_STORAGE_PATH=$ESPRESSO_BASE_STORAGE_PATH/seq0 - ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY=$ESPRESSO_DEMO_SEQUENCER_STAKING_PRIVATE_KEY_0 - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_0 - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=10 - ESPRESSO_SEQUENCER_STAKE_TABLE_CAPACITY=10 depends_on: orchestrator: @@ -127,7 +126,6 @@ processes: - ESPRESSO_SEQUENCER_POSTGRES_PASSWORD=password - ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY=$ESPRESSO_DEMO_SEQUENCER_STAKING_PRIVATE_KEY_1 - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_1 - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=11 - ESPRESSO_SEQUENCER_STAKE_TABLE_CAPACITY=10 depends_on: orchestrator: @@ -164,7 +162,6 @@ processes: - ESPRESSO_SEQUENCER_STORAGE_PATH=$ESPRESSO_BASE_STORAGE_PATH/seq2 - ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY=$ESPRESSO_DEMO_SEQUENCER_STAKING_PRIVATE_KEY_2 - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_2 - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=12 - ESPRESSO_SEQUENCER_STAKE_TABLE_CAPACITY=10 depends_on: orchestrator: @@ -199,7 +196,6 @@ processes: - ESPRESSO_SEQUENCER_STORAGE_PATH=$ESPRESSO_BASE_STORAGE_PATH/seq3 - ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY=$ESPRESSO_DEMO_SEQUENCER_STAKING_PRIVATE_KEY_3 - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_3 - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=13 - ESPRESSO_SEQUENCER_STAKE_TABLE_CAPACITY=10 depends_on: orchestrator: @@ -232,7 +228,6 @@ processes: - ESPRESSO_SEQUENCER_STORAGE_PATH=$ESPRESSO_BASE_STORAGE_PATH/seq4 - ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY=$ESPRESSO_DEMO_SEQUENCER_STAKING_PRIVATE_KEY_4 - ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=$ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_4 - - ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX=14 - ESPRESSO_SEQUENCER_STAKE_TABLE_CAPACITY=10 depends_on: orchestrator: diff --git a/sequencer/src/api.rs b/sequencer/src/api.rs index bc88b0fd97..06d4d0b5cd 100644 --- a/sequencer/src/api.rs +++ b/sequencer/src/api.rs @@ -661,7 +661,6 @@ mod api_tests { #[cfg(test)] mod test { - use self::{ data_source::testing::TestableSequencerDataSource, sql::DataSource as SqlDataSource, }; @@ -689,7 +688,9 @@ mod test { }; use hotshot_types::{ event::LeafInfo, - traits::{block_contents::BlockHeader, metrics::NoMetrics}, + traits::{ + block_contents::BlockHeader, metrics::NoMetrics, node_implementation::ConsensusTime, + }, }; use jf_primitives::merkle_tree::{ prelude::{MerkleProof, Sha3Node}, @@ -755,9 +756,8 @@ mod test { .status(Default::default()), ); - // Populate one account so we have something to look up later. Leave the other accounts - // unpopulated, which proves we can handle state updates even with missing accounts. - let account = TestConfig::builder_key(0).fee_account(); + // Populate the builder account so we have something to look up later. + let account = TestConfig::builder_key().fee_account(); let mut state = ValidatedState::default(); state.prefund_account(account, 1.into()); let mut network = TestNetwork::with_state( @@ -864,23 +864,30 @@ mod test { .await; let mut events = network.server.get_event_stream(); - // Wait for a (non-genesis) block proposed by the lagging node, to prove that it has caught - // up. - let builder = TestConfig::builder_key(TestConfig::NUM_NODES - 1).fee_account(); - 'outer: loop { + // Wait for a (non-genesis) block proposed by each node, to prove that the lagging node has + // caught up and all nodes are in sync. + let mut proposers = [false; TestConfig::NUM_NODES]; + loop { let event = events.next().await.unwrap(); let EventType::Decide { leaf_chain, .. } = event.event else { continue; }; for LeafInfo { leaf, .. } in leaf_chain.iter().rev() { - let height = leaf.get_block_header().height; - let leaf_builder = leaf.get_block_header().fee_info.account(); + let height = leaf.get_height(); + let leaf_builder = + (leaf.get_view_number().get_u64() as usize) % TestConfig::NUM_NODES; + if height == 0 { + continue; + } + tracing::info!( - "waiting for block from {builder}, block {height} is from {leaf_builder}", + "waiting for blocks from {proposers:?}, block {height} is from {leaf_builder}", ); - if height > 1 && leaf_builder == builder { - break 'outer; - } + proposers[leaf_builder] = true; + } + + if proposers.iter().all(|has_proposed| *has_proposed) { + break; } } } @@ -954,24 +961,30 @@ mod test { .await; let mut events = node.get_event_stream(); - // Wait for a (non-genesis) block proposed by the lagging node, to prove that it has caught - // up. - let builder = TestConfig::builder_key(1).fee_account(); - 'outer: loop { + // Wait for a (non-genesis) block proposed by each node, to prove that the lagging node has + // caught up and all nodes are in sync. + let mut proposers = [false; TestConfig::NUM_NODES]; + loop { let event = events.next().await.unwrap(); - tracing::info!(?event, "restarted node got event"); let EventType::Decide { leaf_chain, .. } = event.event else { continue; }; for LeafInfo { leaf, .. } in leaf_chain.iter().rev() { let height = leaf.get_height(); - let leaf_builder = leaf.get_block_header().fee_info.account(); + let leaf_builder = + (leaf.get_view_number().get_u64() as usize) % TestConfig::NUM_NODES; + if height == 0 { + continue; + } + tracing::info!( - "waiting for block from {builder}, block {height} is from {leaf_builder}", + "waiting for blocks from {proposers:?}, block {height} is from {leaf_builder}", ); - if height > 1 && leaf_builder == builder { - break 'outer; - } + proposers[leaf_builder] = true; + } + + if proposers.iter().all(|has_proposed| *has_proposed) { + break; } } } diff --git a/sequencer/src/header.rs b/sequencer/src/header.rs index 3a7c346419..389b82942a 100644 --- a/sequencer/src/header.rs +++ b/sequencer/src/header.rs @@ -1,13 +1,12 @@ use crate::{ block::{entry::TxTableEntryWord, tables::NameSpaceTable, NsTable}, chain_config::ResolvableChainConfig, - eth_signature_key::EthKeyPair, l1_client::L1Snapshot, state::{BlockMerkleCommitment, FeeAccount, FeeAmount, FeeInfo, FeeMerkleCommitment}, ChainConfig, L1BlockInfo, Leaf, NodeState, SeqTypes, ValidatedState, }; +use anyhow::Context; use ark_serialize::CanonicalSerialize; - use committable::{Commitment, Committable, RawCommitmentBuilder}; use ethers::types; use hotshot_query_service::availability::QueryableHeader; @@ -15,14 +14,12 @@ use hotshot_types::{ traits::{ block_contents::{BlockHeader, BlockPayload, BuilderFee}, node_implementation::NodeType, - signature_key::BuilderSignatureKey, ValidatedState as HotShotState, }, utils::BuilderCommitment, vid::VidCommitment, }; use jf_primitives::merkle_tree::prelude::*; - use serde::{Deserialize, Serialize}; use time::OffsetDateTime; @@ -135,19 +132,19 @@ impl Committable for NameSpaceTable { impl Header { #[allow(clippy::too_many_arguments)] - // TODO pub or merely pub(super)? - pub fn from_info( + fn from_info( payload_commitment: VidCommitment, builder_commitment: BuilderCommitment, ns_table: NsTable, parent_leaf: &Leaf, mut l1: L1Snapshot, l1_deposits: &[FeeInfo], + builder_fee: FeeInfo, + builder_signature: types::Signature, mut timestamp: u64, - parent_state: &ValidatedState, - builder_address: &EthKeyPair, + mut state: ValidatedState, chain_config: ChainConfig, - ) -> Self { + ) -> anyhow::Result { // Increment height. let parent_header = parent_leaf.get_block_header(); let height = parent_header.height + 1; @@ -192,33 +189,26 @@ impl Header { } } - let mut state = parent_state.clone(); state .block_merkle_tree .push(parent_header.commit()) - .unwrap(); + .context("missing blocks frontier")?; let block_merkle_tree_root = state.block_merkle_tree.commitment(); // Insert the new L1 deposits for fee_info in l1_deposits { state .insert_fee_deposit(*fee_info) - .expect("fee deposit previously verified"); - // TODO: Check LookupResult + .context(format!("missing fee account {}", fee_info.account()))?; } - // TODO Check that we have the fee to pay for the block. - // We currently can't return an error from Header::new. + // Charge the builder fee. + state + .charge_fee(builder_fee) + .context(format!("invalid builder fee {builder_fee:?}"))?; let fee_merkle_tree_root = state.fee_merkle_tree.commitment(); - - let fee_info = FeeInfo::base_fee(builder_address.address().into()); - let builder_signature = FeeAccount::sign_builder_message( - builder_address, - &fee_message(fee_info.amount(), payload_commitment, &ns_table), - ) - .unwrap(); - Self { + Ok(Self { chain_config: chain_config.commit().into(), height, timestamp, @@ -229,9 +219,9 @@ impl Header { ns_table, fee_merkle_tree_root, block_merkle_tree_root, - fee_info, + fee_info: builder_fee, builder_signature: Some(builder_signature), - } + }) } /// Message authorizing a fee payment for inclusion of a certain payload. @@ -260,11 +250,23 @@ impl BlockHeader for Header { payload_commitment: VidCommitment, builder_commitment: BuilderCommitment, metadata: <::BlockPayload as BlockPayload>::Metadata, - _builder_fee: BuilderFee, + builder_fee: BuilderFee, ) -> Self { let mut validated_state = parent_state.clone(); - let accounts = std::iter::once(FeeAccount::from(instance_state.builder_key.address())); + // Validate the builder's signature, recovering their fee account address so that we can + // fetch the account state if missing. + let fee_msg = fee_message(builder_fee.fee_amount.into(), payload_commitment, &metadata); + let builder_account = FeeAccount::from( + builder_fee + .fee_signature + .recover(fee_msg) + .expect("invalid builder signature"), + ); + + // Figure out which accounts we need to fetch, in case we are missing some state needed to + // construct the header. + let accounts = std::iter::once(builder_account); // Fetch the latest L1 snapshot. let l1_snapshot = instance_state.l1_client().snapshot().await; @@ -330,11 +332,13 @@ impl BlockHeader for Header { parent_leaf, l1_snapshot, &l1_deposits, + FeeInfo::new(builder_account, builder_fee.fee_amount), + builder_fee.fee_signature, OffsetDateTime::now_utc().unix_timestamp() as u64, - &validated_state, - &instance_state.builder_key, + validated_state, instance_state.chain_config, ) + .expect("invalid proposal") } fn genesis( @@ -479,6 +483,18 @@ mod test_headers { fee_merkle_tree, }; + let (fee_account, fee_key) = FeeAccount::generated_from_seed_indexed([0; 32], 0); + let fee_amount = 0.into(); + let fee_signature = FeeAccount::sign_builder_message( + &fee_key, + &fee_message( + fee_amount, + genesis.header.payload_commitment, + &genesis.ns_table, + ), + ) + .unwrap(); + let header = Header::from_info( genesis.header.payload_commitment, genesis.header.builder_commitment, @@ -489,11 +505,13 @@ mod test_headers { finalized: self.l1_finalized, }, &self.l1_deposits, + FeeInfo::new(fee_account, fee_amount), + fee_signature, self.timestamp, - &validated_state, - &genesis.instance_state.builder_key, + validated_state.clone(), genesis.instance_state.chain_config, - ); + ) + .unwrap(); assert_eq!(header.height, parent.height + 1); assert_eq!(header.timestamp, self.expected_timestamp); assert_eq!(header.l1_head, self.expected_l1_head); @@ -847,16 +865,12 @@ mod test_headers { #[test] fn verify_header_signature() { - // easy way to get a wallet: - let state = NodeState::mock(); - // simulate a fixed size hash by padding our message let message = ";)"; let mut commitment = [0u8; 32]; commitment[..message.len()].copy_from_slice(message.as_bytes()); - let key = state.builder_key; - + let key = FeeAccount::generated_from_seed_indexed([0; 32], 0).1; let signature = FeeAccount::sign_builder_message(&key, &commitment).unwrap(); assert!(key .fee_account() diff --git a/sequencer/src/lib.rs b/sequencer/src/lib.rs index 0b1254d867..6a3c51a9fc 100644 --- a/sequencer/src/lib.rs +++ b/sequencer/src/lib.rs @@ -21,7 +21,6 @@ use ethers::types::{Address, U256}; use l1_client::L1Client; -use eth_signature_key::EthKeyPair; use state::FeeAccount; use state_signature::static_stake_table_commitment; use url::Url; @@ -161,14 +160,12 @@ pub struct NodeState { l1_client: L1Client, peers: Arc, genesis_state: ValidatedState, - builder_key: EthKeyPair, } impl NodeState { pub fn new( chain_config: ChainConfig, l1_client: L1Client, - builder_key: EthKeyPair, catchup: impl StateCatchup + 'static, ) -> Self { Self { @@ -176,7 +173,6 @@ impl NodeState { l1_client, peers: Arc::new(catchup), genesis_state: Default::default(), - builder_key, } } @@ -185,7 +181,6 @@ impl NodeState { Self::new( ChainConfig::default(), L1Client::new("http://localhost:3331".parse().unwrap(), Address::default()), - state::FeeAccount::test_key_pair(), catchup::mock::MockStateCatchup::default(), ) } @@ -195,11 +190,6 @@ impl NodeState { self } - pub fn with_builder(mut self, key: EthKeyPair) -> Self { - self.builder_key = key; - self - } - pub fn with_genesis(mut self, state: ValidatedState) -> Self { self.genesis_state = state; self @@ -267,8 +257,6 @@ pub struct NetworkParams { #[derive(Clone, Debug)] pub struct BuilderParams { - pub mnemonic: String, - pub eth_account_index: u32, pub prefunded_accounts: Vec
, } @@ -399,10 +387,6 @@ pub async fn init_node::from_urls(network_params.state_peers)), }; @@ -444,7 +427,10 @@ pub fn empty_builder_commitment() -> BuilderCommitment { #[cfg(any(test, feature = "testing"))] pub mod testing { use super::*; - use crate::{catchup::mock::MockStateCatchup, persistence::no_storage::NoStorage}; + use crate::{ + catchup::mock::MockStateCatchup, eth_signature_key::EthKeyPair, + persistence::no_storage::NoStorage, + }; use committable::Committable; use ethers::utils::{Anvil, AnvilInstance}; use futures::{ @@ -462,11 +448,12 @@ pub mod testing { use hotshot_types::{ event::LeafInfo, light_client::StateKeyPair, - traits::{block_contents::BlockHeader, metrics::NoMetrics}, + traits::{ + block_contents::BlockHeader, metrics::NoMetrics, signature_key::BuilderSignatureKey, + }, ExecutionType, HotShotConfig, PeerConfig, ValidatorConfig, }; use portpicker::pick_unused_port; - use std::time::Duration; const STAKE_TABLE_CAPACITY_FOR_TEST: usize = 10; @@ -492,7 +479,7 @@ pub mod testing { // Generate keys for the nodes. let seed = [0; 32]; let (pub_keys, priv_keys): (Vec<_>, Vec<_>) = (0..num_nodes) - .map(|i| PubKey::generated_from_seed_indexed(seed, i as u64)) + .map(|i| ::generated_from_seed_indexed(seed, i as u64)) .unzip(); let state_key_pairs = (0..num_nodes) .map(|i| StateKeyPair::generate_from_seed_indexed(seed, i as u64)) @@ -616,12 +603,9 @@ pub mod testing { _pd: Default::default(), }; - let key = Self::builder_key(i); - let address = key.address(); let node_state = NodeState::new( ChainConfig::default(), L1Client::new(self.anvil.endpoint().parse().unwrap(), Address::default()), - key, catchup, ) .with_genesis(state); @@ -630,7 +614,6 @@ pub mod testing { i, key = %config.my_own_validator_config.public_key, state_key = %config.my_own_validator_config.state_key_pair.ver_key(), - %address, "starting node", ); SequencerContext::init( @@ -648,12 +631,8 @@ pub mod testing { .unwrap() } - pub fn builder_key(i: usize) -> EthKeyPair { - EthKeyPair::from_mnemonic( - "test test test test test test test test test test test junk", - i as u32, - ) - .unwrap() + pub fn builder_key() -> EthKeyPair { + FeeAccount::generated_from_seed_indexed([1; 32], 0).1 } } diff --git a/sequencer/src/main.rs b/sequencer/src/main.rs index a3dfed4faf..d5443d8df7 100644 --- a/sequencer/src/main.rs +++ b/sequencer/src/main.rs @@ -55,9 +55,7 @@ where url: opt.l1_provider_url, }; let builder_params = BuilderParams { - mnemonic: opt.eth_mnemonic, prefunded_accounts: opt.prefunded_builder_accounts, - eth_account_index: opt.eth_account_index, }; // Parse supplied Libp2p addresses to their socket form diff --git a/sequencer/src/options.rs b/sequencer/src/options.rs index 265538ef63..ee5a6224ea 100644 --- a/sequencer/src/options.rs +++ b/sequencer/src/options.rs @@ -134,21 +134,6 @@ pub struct Options { #[clap(raw = true)] modules: Vec, - /// Mnemonic phrase for builder account. - /// - /// This is the address fees will be charged to. - /// It must be funded with ETH in the Espresso fee ledger - #[clap(long, env = "ESPRESSO_SEQUENCER_ETH_MNEMONIC")] - pub eth_mnemonic: String, - - /// Index of a funded account derived from eth-mnemonic. - #[clap( - long, - env = "ESPRESSO_SEQUENCER_ETH_ACCOUNT_INDEX", - default_value = "8" - )] - pub eth_account_index: u32, - /// Prefunded the builder accounts. Use for demo purposes only. /// /// Comma-separated list of Ethereum addresses. diff --git a/sequencer/src/state.rs b/sequencer/src/state.rs index 4e1e173985..ab9cd2abe7 100644 --- a/sequencer/src/state.rs +++ b/sequencer/src/state.rs @@ -2,7 +2,7 @@ use crate::{ api::endpoints::AccountQueryData, catchup::StateCatchup, eth_signature_key::EthKeyPair, ChainConfig, Header, Leaf, NodeState, SeqTypes, }; -use anyhow::{bail, ensure, Context}; +use anyhow::{anyhow, bail, ensure, Context}; use ark_serialize::{ CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate, }; @@ -141,6 +141,36 @@ impl ValidatedState { Some(balance.cloned().unwrap_or_default().add(fee_info.amount)) }) } + + /// Charge a fee to an account. + pub fn charge_fee(&mut self, fee_info: FeeInfo) -> anyhow::Result<()> { + let FeeInfo { account, amount } = fee_info; + let mut err = None; + let res = self.fee_merkle_tree.update_with(account, |balance| { + let balance = balance.copied(); + let Some(updated) = balance.unwrap_or_default().checked_sub(&amount) else { + // Return an error without updating the account. + err = Some(anyhow!( + "insufficient funds (have {balance:?}, required {amount:?})" + )); + return balance; + }; + if updated == FeeAmount::default() { + // Delete the account from the tree if its balance ended up at 0; this saves some + // space since the account is no longer carrying any information. + None + } else { + // Otherwise store the updated balance. + Some(updated) + } + })?; + // Check if we were unable to do the update because the required Merkle path is missing. + ensure!( + res.expect_not_in_memory().is_err(), + format!("missing account state for {account}") + ); + Ok(()) + } } #[cfg(any(test, feature = "testing"))] @@ -225,48 +255,14 @@ pub fn validate_proposal( Ok(()) } -#[derive(Debug)] -enum ChargeFeeError { - /// Account not in memory, needs to be fetched from peer - NotInMemory, - /// Account exists but has insufficient funds - InsufficientFunds, -} - fn charge_fee( - fee_merkle_tree: &mut FeeMerkleTree, + state: &mut ValidatedState, delta: &mut Delta, fee_info: FeeInfo, -) -> Result<(), ChargeFeeError> { - let FeeInfo { account, amount } = fee_info; - let mut err = None; - let res = fee_merkle_tree - .update_with(account, |balance| { - let balance = balance.copied(); - let Some(updated) = balance.unwrap_or_default().checked_sub(&amount) else { - // Return an error without updating the account. - err = Some(ChargeFeeError::InsufficientFunds); - return balance; - }; - if updated == FeeAmount::default() { - // Delete the account from the tree if its balance ended up at 0; this saves some - // space since the account is no longer carrying any information. - None - } else { - // Otherwise store the updated balance. - Some(updated) - } - }) - .expect("updated succeeds"); - if res.expect_not_in_memory().is_ok() { - return Err(ChargeFeeError::NotInMemory); - } - if let Some(err) = err { - Err(err) - } else { - delta.fees_delta.insert(account); - Ok(()) - } +) -> anyhow::Result<()> { + state.charge_fee(fee_info)?; + delta.fees_delta.insert(fee_info.account); + Ok(()) } /// Validate builder account by verifying signature @@ -665,15 +661,7 @@ impl ValidatedState { let mut validated_state = apply_proposal(&validated_state, &mut delta, parent_leaf, l1_deposits); - if charge_fee( - &mut validated_state.fee_merkle_tree, - &mut delta, - proposed_header.fee_info, - ) - .is_err() - { - bail!("Insufficient funds") - }; + charge_fee(&mut validated_state, &mut delta, proposed_header.fee_info)?; Ok((validated_state, delta)) }