diff --git a/Cargo.lock b/Cargo.lock index 73ebd75d4..35bfc584d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10524,6 +10524,7 @@ dependencies = [ "sequencer_core", "sequencer_service", "sequencer_service_rpc", + "sequencer_stake_core", "serde", "serde_json", "tempfile", diff --git a/artifacts/lez/programs/amm.bin b/artifacts/lez/programs/amm.bin index 2fdf142e7..0d9e312c7 100644 Binary files a/artifacts/lez/programs/amm.bin and b/artifacts/lez/programs/amm.bin differ diff --git a/artifacts/lez/programs/associated_token_account.bin b/artifacts/lez/programs/associated_token_account.bin index 60edcbbdc..e541359e5 100644 Binary files a/artifacts/lez/programs/associated_token_account.bin and b/artifacts/lez/programs/associated_token_account.bin differ diff --git a/artifacts/lez/programs/authenticated_transfer.bin b/artifacts/lez/programs/authenticated_transfer.bin index 216282765..532694b14 100644 Binary files a/artifacts/lez/programs/authenticated_transfer.bin and b/artifacts/lez/programs/authenticated_transfer.bin differ diff --git a/artifacts/lez/programs/bridge.bin b/artifacts/lez/programs/bridge.bin index c1b8d9884..789a70005 100644 Binary files a/artifacts/lez/programs/bridge.bin and b/artifacts/lez/programs/bridge.bin differ diff --git a/artifacts/lez/programs/bridge_lock.bin b/artifacts/lez/programs/bridge_lock.bin index ddadd4ed5..3b832058a 100644 Binary files a/artifacts/lez/programs/bridge_lock.bin and b/artifacts/lez/programs/bridge_lock.bin differ diff --git a/artifacts/lez/programs/clock.bin b/artifacts/lez/programs/clock.bin index c47419be3..aabe59be9 100644 Binary files a/artifacts/lez/programs/clock.bin and b/artifacts/lez/programs/clock.bin differ diff --git a/artifacts/lez/programs/cross_zone_inbox.bin b/artifacts/lez/programs/cross_zone_inbox.bin index ec1da83fa..649ff500b 100644 Binary files a/artifacts/lez/programs/cross_zone_inbox.bin and b/artifacts/lez/programs/cross_zone_inbox.bin differ diff --git a/artifacts/lez/programs/cross_zone_outbox.bin b/artifacts/lez/programs/cross_zone_outbox.bin index ba3649f6b..ef51d4858 100644 Binary files a/artifacts/lez/programs/cross_zone_outbox.bin and b/artifacts/lez/programs/cross_zone_outbox.bin differ diff --git a/artifacts/lez/programs/faucet.bin b/artifacts/lez/programs/faucet.bin index 399826ed6..9debd7146 100644 Binary files a/artifacts/lez/programs/faucet.bin and b/artifacts/lez/programs/faucet.bin differ diff --git a/artifacts/lez/programs/pinata.bin b/artifacts/lez/programs/pinata.bin index febc6cd86..360a7b2da 100644 Binary files a/artifacts/lez/programs/pinata.bin and b/artifacts/lez/programs/pinata.bin differ diff --git a/artifacts/lez/programs/pinata_token.bin b/artifacts/lez/programs/pinata_token.bin index d7a6844eb..699fcd7c0 100644 Binary files a/artifacts/lez/programs/pinata_token.bin and b/artifacts/lez/programs/pinata_token.bin differ diff --git a/artifacts/lez/programs/ping_receiver.bin b/artifacts/lez/programs/ping_receiver.bin index dc2c06472..cd242c58e 100644 Binary files a/artifacts/lez/programs/ping_receiver.bin and b/artifacts/lez/programs/ping_receiver.bin differ diff --git a/artifacts/lez/programs/ping_sender.bin b/artifacts/lez/programs/ping_sender.bin index 0ae3175bd..c7935ee7f 100644 Binary files a/artifacts/lez/programs/ping_sender.bin and b/artifacts/lez/programs/ping_sender.bin differ diff --git a/artifacts/lez/programs/sequencer_stake.bin b/artifacts/lez/programs/sequencer_stake.bin index 0e17bedb7..c179dfbb7 100644 Binary files a/artifacts/lez/programs/sequencer_stake.bin and b/artifacts/lez/programs/sequencer_stake.bin differ diff --git a/artifacts/lez/programs/token.bin b/artifacts/lez/programs/token.bin index d91927d3e..aba29e5c2 100644 Binary files a/artifacts/lez/programs/token.bin and b/artifacts/lez/programs/token.bin differ diff --git a/artifacts/lez/programs/vault.bin b/artifacts/lez/programs/vault.bin index 5ce29680c..608c9dee4 100644 Binary files a/artifacts/lez/programs/vault.bin and b/artifacts/lez/programs/vault.bin differ diff --git a/artifacts/lez/programs/wrapped_token.bin b/artifacts/lez/programs/wrapped_token.bin index 6633ec468..ce3ea46aa 100644 Binary files a/artifacts/lez/programs/wrapped_token.bin and b/artifacts/lez/programs/wrapped_token.bin differ diff --git a/integration_tests/tests/multi_sequencer.rs b/integration_tests/tests/multi_sequencer.rs index 14d50f23b..11211e072 100644 --- a/integration_tests/tests/multi_sequencer.rs +++ b/integration_tests/tests/multi_sequencer.rs @@ -3,9 +3,9 @@ reason = "top-level test functions are conventional for integration tests" )] -//! Two sequencers share one channel: both are staked at genesis, so A creates -//! the channel already accrediting `[A, B]`, B joins and syncs, both produce on -//! their turns, and A, B and an indexer converge on the same chain. +//! Two sequencers share one channel: both are staked at genesis, so the channel +//! is created already accrediting `[A, B]`, B syncs the chain A began, both +//! produce on their turns, and A, B and an indexer converge on the same chain. use std::time::Duration; @@ -14,75 +14,68 @@ use indexer_service_rpc::RpcClient as _; use integration_tests::{ config::{self, SequencerPartialConfig}, init_logger, - setup::{SequencerSetup, indexer_client, sequencer_client, setup_bedrock_node, setup_indexer}, }; -use logos_blockchain_key_management_system_service::keys::{ED25519_SECRET_KEY_SIZE, Ed25519Key}; +use logos_blockchain_key_management_system_service::keys::Ed25519Key; use sequencer_core::{ block_publisher::{Ed25519PublicKey, read_channel_state}, - config::{BedrockConfig, GenesisAction}, - sign_genesis_stake, + config::BedrockConfig, }; use sequencer_service_rpc::{RpcClient as _, SequencerClient}; +use test_fixtures::{ + MultiZoneTestContextBuilder, ZoneTestContextBuilder, config::MultiNodeTestContextConfig, +}; use testnet_initial_state::{initial_pub_accounts_private_keys, initial_public_user_accounts}; use tokio::test; const PHASE_TIMEOUT: Duration = Duration::from_secs(360); const POLL_INTERVAL: Duration = Duration::from_secs(2); const TRANSFER_AMOUNT: u128 = 10; -/// ≈4 turn windows past B's join, at `DEFAULT_SEQUENCER_POSTING_TIMEFRAME` -/// (20 slots ≈ 20 s) and 5 s blocks. +/// ≈4 turn windows, at the `system_accounts` posting timeframe and 5 s blocks. const ROTATION_BLOCKS: u64 = 8; #[test] async fn multi_sequencer_committee_converges() -> Result<()> { init_logger(); - let (_bedrock, bedrock_addr) = setup_bedrock_node() - .await - .context("Failed to set up Bedrock node")?; - - // Fixed seeds so both keys can be staked in genesis before either starts. - let key_a = [0xA1_u8; ED25519_SECRET_KEY_SIZE]; - let key_b = [0xB2_u8; ED25519_SECRET_KEY_SIZE]; - let pub_a = Ed25519Key::from_bytes(&key_a).public_key(); - let pub_b = Ed25519Key::from_bytes(&key_b).public_key(); - - // Each operator signs its own stake offchain. - let genesis = vec![ - founding_stake(0, stake_key(&pub_a), [0x51_u8; 32])?, - founding_stake(1, stake_key(&pub_b), [0x52_u8; 32])?, - ]; - - let bedrock_config = BedrockConfig { - channel_id: config::bedrock_channel_id(), - node_url: config::addr_to_url(config::UrlProtocol::Http, bedrock_addr)?, - funding_key: config::bedrock_funding_key(), - auth: None, - priority_fee: sequencer_core::config::default_priority_fee(), - }; - + let channel = config::bedrock_channel_id(); let partial = SequencerPartialConfig { block_create_timeout: Duration::from_secs(5), ..SequencerPartialConfig::default() }; - // Phase 1: A solo (it creates the channel), plus an indexer. - let (seq_a, _a_home) = SequencerSetup::new(partial, bedrock_addr) - .with_genesis(genesis.clone()) - .with_bedrock_signing_key(key_a) - .setup() + let ctx = MultiZoneTestContextBuilder::default() + .with_zone( + ZoneTestContextBuilder::new(MultiNodeTestContextConfig { + num_nodes: 2, + bedrock_channel: channel, + }) + .disable_wallet() + .with_sequencer_partial_config(partial), + ) + .build() .await - .context("Failed to set up sequencer A")?; - let a = sequencer_client(seq_a.addr())?; - let (idx, _idx_home) = setup_indexer(bedrock_addr, config::bedrock_channel_id(), None) - .await - .context("Failed to set up indexer")?; - let indexer = indexer_client(idx.addr()).await?; + .context("Failed to build the two-sequencer test context")?; - wait_for_height(&a, 2, "sequencer A to produce past genesis").await?; + let a = ctx + .sequencer_client_by_node_ids(channel, 0) + .context("Missing sequencer A")?; + let b = ctx + .sequencer_client_by_node_ids(channel, 1) + .context("Missing sequencer B")?; + let indexer = ctx.indexer_client(); - // Phase 2: the creating tx carried the committee, so both keys are accredited - // from block 1 and discovery has nothing to reconcile. + let pub_a = Ed25519Key::from_bytes(&config::SEQUENCER_SIGNING_KEY).public_key(); + let pub_b = Ed25519Key::from_bytes(&config::sequencer_signing_key_from_seed(1)).public_key(); + + let bedrock_config = BedrockConfig { + channel_id: channel, + node_url: config::addr_to_url(config::UrlProtocol::Http, ctx.bedrock_addr())?, + funding_key: config::bedrock_funding_key(), + auth: None, + priority_fee: sequencer_core::config::default_priority_fee(), + }; + + // Phase 1: both keys accredited from channel creation. let mut want = vec![pub_a.to_bytes(), pub_b.to_bytes()]; want.sort_unstable(); wait_until("Bedrock to accredit both staked keys", || async { @@ -90,35 +83,27 @@ async fn multi_sequencer_committee_converges() -> Result<()> { }) .await?; - // Phase 3: B joins live and syncs the existing chain. - let (seq_b, _b_home) = SequencerSetup::new(partial, bedrock_addr) - .with_genesis(genesis) - .with_bedrock_signing_key(key_b) - .setup() - .await - .context("Failed to set up sequencer B")?; - let b = sequencer_client(seq_b.addr())?; + // Phase 2: B follows the chain A began. + let join_height = a.get_last_block_id().await?.max(1); + wait_for_height(b, join_height, "B to sync to A's height").await?; - let join_height = a.get_last_block_id().await?; - wait_for_height(&b, join_height, "B to sync to A's height at join").await?; - - // Phase 4: rotation + convergence over ≈4 turn windows. Without the turn - // check, a chain A produces alone satisfies every assertion below. + // Phase 3: rotation + convergence; without the turn check, a chain A + // produces alone satisfies everything below. let rotation_target = join_height + ROTATION_BLOCKS; wait_for_height( - &a, + a, rotation_target, "the chain to advance across turn windows", ) .await?; - wait_for_height(&b, rotation_target, "B to follow across turn windows").await?; + wait_for_height(b, rotation_target, "B to follow across turn windows").await?; wait_until("the round-robin turn to reach B", || async { Ok(committee(&bedrock_config).await?.1 == Some(pub_b)) }) .await?; - assert_same_chain(&a, &b).await?; + assert_same_chain(a, b).await?; - // Phase 5: a tx submitted only to B is included by B and visible on A. + // Phase 4: a tx submitted only to B is included by B and visible on A. let accounts = initial_public_user_accounts(); let from = accounts[0].account_id; let to = accounts[1].account_id; @@ -143,7 +128,7 @@ async fn multi_sequencer_committee_converges() -> Result<()> { }) .await?; - // Phase 6: the indexer finalizes the same chain, with no stall. + // Phase 5: the indexer finalizes the same chain, with no stall. wait_until("the indexer to finalize", || async { Ok(indexer.get_last_finalized_block_id().await?.unwrap_or(0) >= join_height) }) @@ -173,26 +158,6 @@ async fn multi_sequencer_committee_converges() -> Result<()> { Ok(()) } -fn stake_key(key: &Ed25519PublicKey) -> sequencer_stake_core::SequencerKey { - sequencer_stake_core::SequencerKey::new(key.to_bytes()) - .expect("a Bedrock key is a valid Ed25519 public key") -} - -/// Builds a founding-sequencer genesis entry, signing its stake the way an -/// operator would before handing the entry over. -fn founding_stake( - index: usize, - sequencer_key: sequencer_stake_core::SequencerKey, - owner_seed: [u8; 32], -) -> Result { - let owner = lee::PrivateKey::try_new(owner_seed)?; - Ok(GenesisAction::StakeSequencer { - sequencer_key, - ownership_public_key: lee::PublicKey::new_from_private_key(&owner), - stake_signature: sign_genesis_stake(index, sequencer_key, &owner), - }) -} - /// Polls `check` until it reports ready, failing with `what` on timeout. async fn wait_until(what: &str, mut check: F) -> Result<()> where diff --git a/integration_tests/tests/sequencer_stake_demo.rs b/integration_tests/tests/sequencer_stake_demo.rs index c540cca44..9c5733a32 100644 --- a/integration_tests/tests/sequencer_stake_demo.rs +++ b/integration_tests/tests/sequencer_stake_demo.rs @@ -18,12 +18,14 @@ use logos_blockchain_zone_sdk::{ adapter::{Node as _, NodeHttpClient}, }; use sequencer_core::config::GenesisAction; +use sequencer_service_rpc::RpcClient as _; use test_fixtures::{ MultiZoneTestContextBuilder, TestContext, ZoneTestContextBuilder, config::{ MultiNodeTestContextConfig, SequencerPartialConfig, UrlProtocol, addr_to_url, bedrock_channel_id, }, + setup::{SequencerSetup, sequencer_client}, }; use tokio::test; use wallet::AccountIdentity; @@ -31,6 +33,9 @@ use wallet::AccountIdentity; /// Comfortably above `system_accounts::DEFAULT_MINIMUM_SEQUENCER_STAKE`. const FUNDING_BALANCE: u128 = 2 * system_accounts::DEFAULT_MINIMUM_SEQUENCER_STAKE; +/// Bedrock signing key of the sequencer that stakes its way in. +const JOINER_SIGNING_KEY: [u8; 32] = [0x42; 32]; + /// Short block cadence for the demo. fn fast_blocks() -> SequencerPartialConfig { SequencerPartialConfig { @@ -41,8 +46,7 @@ fn fast_blocks() -> SequencerPartialConfig { #[test] async fn stake_transaction_joins_the_bedrock_committee() -> Result<()> { - // Placeholder sequencer identity; must be a genuine Ed25519 point. - let demo_sequencer_key = Ed25519Key::from_bytes(&[0x42; 32]).public_key(); + let demo_sequencer_key = Ed25519Key::from_bytes(&JOINER_SIGNING_KEY).public_key(); let demo_stake_key = sequencer_stake_core::SequencerKey::new(demo_sequencer_key.to_bytes()) .expect("a Bedrock key is a valid Ed25519 public key"); @@ -194,6 +198,67 @@ async fn stake_transaction_joins_the_bedrock_committee() -> Result<()> { channel_state.accredited_keys.len() ); + // Only now start a node behind the key, against a channel that already has a chain. + let (joiner, _joiner_home) = SequencerSetup::new(fast_blocks(), ctx.bedrock_addr()) + .with_channel_id(bedrock_channel_id()) + .with_bedrock_signing_key(JOINER_SIGNING_KEY) + .joining_existing_channel() + .setup() + .await + .context("Failed to start the joining sequencer")?; + let joiner_client = sequencer_client(joiner.addr())?; + + let joined_at = ctx.sequencer_client().get_last_block_id().await?; + poll_until("the joining sequencer to sync the existing chain", 120, { + let joiner_client = &joiner_client; + move || async move { Ok(joiner_client.get_last_block_id().await? >= joined_at) } + }) + .await?; + info!("Joining sequencer synced to block {joined_at}"); + + // A tip past `joined_at` under the demo key is a block this node built. + poll_until("the joining sequencer to build a block on its turn", 180, { + let node = &node; + let ctx = &ctx; + move || async move { + let Some(state) = node.channel_state(bedrock_channel_id()).await? else { + return Ok(false); + }; + let turn = state + .accredited_keys + .get(usize::from(state.tip_sequencer)) + .copied(); + Ok(turn == Some(demo_sequencer_key) + && ctx.sequencer_client().get_last_block_id().await? > joined_at) + } + }) + .await?; + info!("Joining sequencer produced a block on its round-robin turn"); + + // Both nodes agree, block for block, over everything they share. + let leader_client = ctx.sequencer_client(); + let common = leader_client + .get_last_block_id() + .await? + .min(joiner_client.get_last_block_id().await?); + for id in 1..=common { + let leader_block = leader_client + .get_block(id) + .await? + .with_context(|| format!("Leader is missing block {id}"))?; + let joiner_block = joiner_client + .get_block(id) + .await? + .with_context(|| format!("Joining sequencer is missing block {id}"))?; + anyhow::ensure!( + leader_block.header.hash == joiner_block.header.hash, + "Chain divergence at block {id}: leader {:?} vs joiner {:?}", + leader_block.header.hash, + joiner_block.header.hash + ); + } + info!("Leader and joining sequencer agree on all {common} shared blocks"); + // Exit flow: full UnstakeRequest, wait for the committee removal to land on // Bedrock, then check the sequencer's own FinalizeUnstake releases the stake. // diff --git a/lez/sequencer/actors/executor/src/actor.rs b/lez/sequencer/actors/executor/src/actor.rs index e05c1869a..96dc9f039 100644 --- a/lez/sequencer/actors/executor/src/actor.rs +++ b/lez/sequencer/actors/executor/src/actor.rs @@ -143,10 +143,10 @@ impl Message for Executo return Ok(()); } - info!("Our turn: collecting transactions from mempool, creating block"); + info!("Our turn: producing a block and any committee update"); let id = self .sequencer - .produce_new_block() + .run_production_turn() .await .map_err(Error::BlockProductionFailed)?; diff --git a/lez/sequencer/core/src/lib.rs b/lez/sequencer/core/src/lib.rs index d15d24786..1199aa705 100644 --- a/lez/sequencer/core/src/lib.rs +++ b/lez/sequencer/core/src/lib.rs @@ -662,8 +662,10 @@ impl SequencerCore { }) } - /// Produces a new block from mempool transactions and publishes it via zone-sdk. - pub async fn produce_new_block(&mut self) -> Result { + /// Runs everything this sequencer owes its turn: builds a block from + /// mempool transactions, publishes it via zone-sdk, and submits any + /// committee-config update the new state calls for. + pub async fn run_production_turn(&mut self) -> Result { let live_accredited_keys = self.live_accredited_sequencer_keys().await; let BlockWithMeta { diff --git a/lez/sequencer/core/src/tests.rs b/lez/sequencer/core/src/tests.rs index c4f28f788..d3727cf05 100644 --- a/lez/sequencer/core/src/tests.rs +++ b/lez/sequencer/core/src/tests.rs @@ -191,7 +191,7 @@ async fn common_setup_with_config( .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); (sequencer, mempool_handle) } @@ -425,7 +425,7 @@ async fn unfulfilled_deposit_events_are_drained_from_the_store_on_production() { "deposit mints are drained from the store, never queued in the mempool" ); - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let block = sequencer .store .get_block_at_id(block_id) @@ -477,8 +477,8 @@ async fn a_drained_deposit_is_not_minted_twice_across_turns() { }) .unwrap(); - let first = sequencer.produce_new_block().await.unwrap(); - let second = sequencer.produce_new_block().await.unwrap(); + let first = sequencer.run_production_turn().await.unwrap(); + let second = sequencer.run_production_turn().await.unwrap(); let minted_in = |block_id: u64| { sequencer @@ -527,7 +527,7 @@ async fn an_orphaned_deposit_is_reminted_exactly_once_in_the_replacement() { .unwrap(); // Produce the block that mints the deposit; its receipt marks it minted. - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let minted_block = sequencer.store.get_block_at_id(2).unwrap().unwrap(); assert!( sequencer.with_state(|s| deposit_already_minted(s, HashType(deposit_op_id))), @@ -554,7 +554,7 @@ async fn an_orphaned_deposit_is_reminted_exactly_once_in_the_replacement() { // Next turn: the still-pending record is drained and re-minted on the new // head, exactly once. - let replacement = sequencer.produce_new_block().await.unwrap(); + let replacement = sequencer.run_production_turn().await.unwrap(); let mints = sequencer .store .get_block_at_id(replacement) @@ -657,7 +657,7 @@ async fn recorded_dispatches_are_drained_from_the_store_on_production() { "deliveries are drained from the store, never queued in the mempool" ); - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let block = sequencer .store .get_block_at_id(block_id) @@ -704,8 +704,8 @@ async fn a_delivered_dispatch_is_skipped_on_the_next_turn() { .add_pending_cross_zone_dispatches(vec![record]) .unwrap(); - let first = sequencer.produce_new_block().await.unwrap(); - let second = sequencer.produce_new_block().await.unwrap(); + let first = sequencer.run_production_turn().await.unwrap(); + let second = sequencer.run_production_turn().await.unwrap(); let delivered_in = |block_id: u64| { dispatches_in( @@ -747,7 +747,7 @@ async fn a_dispatch_that_never_executes_is_given_up_on_after_repeated_failures() .unwrap(); for attempt in 1..RETIRE_DISPATCH_AFTER_FAILURES { - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let block = sequencer .store .get_block_at_id(block_id) @@ -770,7 +770,7 @@ async fn a_dispatch_that_never_executes_is_given_up_on_after_repeated_failures() // pending list. Anything else leaves an entry no later block can ever // remove, which is how a peer that can make deliveries fail would grow this // list without bound. - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); assert!( pending_dispatches(&sequencer).is_empty(), "giving up on a delivery must take its record out of the pending list" @@ -806,7 +806,7 @@ async fn a_dispatch_that_never_executes_is_given_up_on_after_repeated_failures() assert_eq!(retained, dead_letters); // And nothing re-feeds it, so it stops costing a guest execution per block. - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let block = sequencer.store.get_block_at_id(block_id).unwrap().unwrap(); assert!(dispatches_in(&block).is_empty()); assert!(pending_dispatches(&sequencer).is_empty()); @@ -830,7 +830,7 @@ async fn a_redelivered_record_is_dropped_once_its_delivery_is_irreversible() { .add_pending_cross_zone_dispatches(vec![record.clone()]) .unwrap(); - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let delivery_block = sequencer.store.get_block_at_id(block_id).unwrap().unwrap(); assert_eq!(dispatches_in(&delivery_block), vec![key]); @@ -853,7 +853,7 @@ async fn a_redelivered_record_is_dropped_once_its_delivery_is_irreversible() { .unwrap(); assert_eq!(pending_dispatches(&sequencer).len(), 1); - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let block = sequencer.store.get_block_at_id(block_id).unwrap().unwrap(); assert!( dispatches_in(&block).is_empty(), @@ -882,8 +882,8 @@ async fn a_delivery_still_reversible_keeps_its_record() { .add_pending_cross_zone_dispatches(vec![record]) .unwrap(); - sequencer.produce_new_block().await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); assert_eq!( pending_dispatches(&sequencer) @@ -963,7 +963,7 @@ async fn a_delivery_too_large_for_any_block_does_not_stall_production() { .unwrap(); // Production must get past it to the mempool in the very first block. - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let block = sequencer.store.get_block_at_id(block_id).unwrap().unwrap(); assert!( block.body.transactions.contains(&user_tx), @@ -973,7 +973,7 @@ async fn a_delivery_too_large_for_any_block_does_not_stall_production() { // And it is given up on rather than retried for ever. for _ in 1..RETIRE_DISPATCH_AFTER_FAILURES { - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); } assert!( pending_dispatches(&sequencer).is_empty(), @@ -1005,7 +1005,7 @@ async fn a_delivery_backlog_is_spread_across_blocks() { .add_pending_cross_zone_dispatches(records) .unwrap(); - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let block = sequencer.store.get_block_at_id(block_id).unwrap().unwrap(); assert_eq!( dispatches_in(&block).len(), @@ -1014,7 +1014,7 @@ async fn a_delivery_backlog_is_spread_across_blocks() { ); // Deferred, not dropped: the rest go in the next block. - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let block = sequencer.store.get_block_at_id(block_id).unwrap().unwrap(); assert_eq!(dispatches_in(&block).len(), 3); } @@ -1163,7 +1163,7 @@ async fn push_tx_into_mempool_blocks_until_mempool_is_full() { assert!(poll.is_pending()); // Empty the mempool by producing a block - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); // Resolve the pending push assert!(push_fut.await.is_ok()); @@ -1246,7 +1246,7 @@ async fn replay_transactions_are_rejected_in_the_same_block() { .unwrap(); // Create block - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block = sequencer .store .get_block_at_id(sequencer.chain_height()) @@ -1281,7 +1281,7 @@ async fn replay_transactions_are_rejected_in_different_blocks() { .push((TransactionOrigin::User, tx.clone())) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block = sequencer .store .get_block_at_id(sequencer.chain_height()) @@ -1300,7 +1300,7 @@ async fn replay_transactions_are_rejected_in_different_blocks() { .push((TransactionOrigin::User, tx.clone())) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block = sequencer .store .get_block_at_id(sequencer.chain_height()) @@ -1342,7 +1342,7 @@ async fn restart_from_storage() { .push((TransactionOrigin::User, tx.clone())) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block = sequencer .store .get_block_at_id(sequencer.chain_height()) @@ -1380,9 +1380,9 @@ async fn get_pending_blocks() { let config = setup_sequencer_config(); let (mut sequencer, _mempool_handle) = SequencerCoreWithMockClients::start_from_config(config).await; - sequencer.produce_new_block().await.unwrap(); - sequencer.produce_new_block().await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); assert_eq!(sequencer.get_pending_blocks().unwrap().len(), 4); } @@ -1391,9 +1391,9 @@ async fn delete_blocks() { let config = setup_sequencer_config(); let (mut sequencer, _mempool_handle) = SequencerCoreWithMockClients::start_from_config(config).await; - sequencer.produce_new_block().await.unwrap(); - sequencer.produce_new_block().await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let last_finalized_block = 3; sequencer @@ -1429,7 +1429,7 @@ async fn produce_block_with_correct_prev_meta_after_restart() { .push((TransactionOrigin::User, tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); // Get the metadata of the last block produced sequencer.store.latest_block_meta().unwrap().unwrap() @@ -1455,7 +1455,7 @@ async fn produce_block_with_correct_prev_meta_after_restart() { .unwrap(); // Step 4: Produce new block - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); // Step 5: Verify the new block has correct previous block metadata let new_block = sequencer @@ -1508,7 +1508,7 @@ async fn transactions_touching_clock_account_are_dropped_from_block() { .push((TransactionOrigin::User, crafted_clock_tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block = sequencer .store @@ -1538,7 +1538,7 @@ async fn user_tx_that_chain_calls_clock_is_dropped() { .push((TransactionOrigin::User, deploy_tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); // Build a user transaction that invokes clock_chain_caller, which in turn chain-calls the // clock program with the clock accounts. The sequencer should detect that the resulting @@ -1563,7 +1563,7 @@ async fn user_tx_that_chain_calls_clock_is_dropped() { .push((TransactionOrigin::User, user_tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block = sequencer .store @@ -1603,7 +1603,7 @@ async fn block_production_aborts_when_clock_account_data_is_corrupted() { .unwrap(); // Block production must fail because the appended clock tx cannot execute. - let result = sequencer.produce_new_block().await; + let result = sequencer.run_production_turn().await; assert!( result.is_err(), "Block production should abort when clock account data is corrupted" @@ -2154,8 +2154,8 @@ async fn head_rewound_below_published_height_blocks_production() { let (mut sequencer, mempool_handle) = SequencerCoreWithMockClients::start_from_config(config).await; - let first = sequencer.produce_new_block().await.unwrap(); - let published_tip = sequencer.produce_new_block().await.unwrap(); + let first = sequencer.run_production_turn().await.unwrap(); + let published_tip = sequencer.run_production_turn().await.unwrap(); assert_eq!( sequencer.store.published_high_water().unwrap(), Some(published_tip), @@ -2312,7 +2312,7 @@ async fn follow_redelivery_of_own_block_is_deduped() { .push((TransactionOrigin::User, tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block2 = sequencer.store.get_block_at_id(2).unwrap().unwrap(); // The channel redelivers our own block under the MsgId the mock publisher @@ -2354,7 +2354,7 @@ async fn follow_orphan_reverts_head_and_requeues_user_txs() { .push((TransactionOrigin::User, tx.clone())) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block2 = sequencer.store.get_block_at_id(2).unwrap().unwrap(); apply_follow_update( @@ -2410,7 +2410,7 @@ async fn follow_orphan_of_a_finalized_block_requeues_nothing() { .push((TransactionOrigin::User, tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block2 = sequencer.store.get_block_at_id(2).unwrap().unwrap(); apply_follow_update( @@ -2459,7 +2459,7 @@ async fn follow_finalized_own_block_moves_final_tier_and_marks_store() { .push((TransactionOrigin::User, tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block2 = sequencer.store.get_block_at_id(2).unwrap().unwrap(); apply_follow_update( @@ -2503,7 +2503,7 @@ async fn follow_finalized_delivery_drops_its_pending_record() { .add_pending_cross_zone_dispatches(vec![record]) .unwrap(); - let block_id = sequencer.produce_new_block().await.unwrap(); + let block_id = sequencer.run_production_turn().await.unwrap(); let delivery_block = sequencer.store.get_block_at_id(block_id).unwrap().unwrap(); assert_eq!(dispatches_in(&delivery_block), vec![key]); assert_eq!( @@ -2551,7 +2551,7 @@ async fn a_parked_finalized_block_does_not_drop_a_dispatch_record() { .push((TransactionOrigin::User, tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); // A skip-ahead block carrying the same delivery: not in head and linking to // nothing we hold, so the final tier parks it instead of applying it. @@ -2630,7 +2630,7 @@ async fn parked_finalized_block_neither_sweeps_the_store_nor_drops_its_deposit_r .push((TransactionOrigin::User, tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let deposit_op_id = HashType([21; 32]); let record = PendingDepositEventRecord { @@ -2709,7 +2709,7 @@ async fn restart_restores_head_tier_and_recovers_from_orphan() { .push((TransactionOrigin::User, tx.clone())) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); (tx, sequencer.store.get_block_at_id(2).unwrap().unwrap()) }; @@ -2774,7 +2774,7 @@ async fn restart_reanchors_on_the_persisted_final_snapshot() { .push((TransactionOrigin::User, tx)) .await .unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer.run_production_turn().await.unwrap(); let block2 = sequencer.store.get_block_at_id(2).unwrap().unwrap(); apply_follow_update( &sequencer.store.dbio(), diff --git a/lez/sequencer/core/src/tests/reconstruction.rs b/lez/sequencer/core/src/tests/reconstruction.rs index 596c1c26a..f2802ec0b 100644 --- a/lez/sequencer/core/src/tests/reconstruction.rs +++ b/lez/sequencer/core/src/tests/reconstruction.rs @@ -58,8 +58,8 @@ async fn reconstructs_missing_channel_blocks_into_fresh_store() { let config_a = setup_sequencer_config(); let (mut seq_a, _handle_a) = SequencerCoreWithMockClients::start_from_config(config_a.clone()).await; - seq_a.produce_new_block().await.unwrap(); - seq_a.produce_new_block().await.unwrap(); + seq_a.run_production_turn().await.unwrap(); + seq_a.run_production_turn().await.unwrap(); let tip_a = seq_a.block_store().latest_block_meta().unwrap().unwrap(); let messages = channel_from_store(seq_a.block_store(), 10); @@ -196,8 +196,8 @@ async fn fails_when_a_below_tip_channel_block_does_not_validate() { // A sequencer that committed blocks past genesis but never recorded an anchor. let config = setup_sequencer_config(); let (mut seq, _handle) = SequencerCoreWithMockClients::start_from_config(config.clone()).await; - seq.produce_new_block().await.unwrap(); - seq.produce_new_block().await.unwrap(); + seq.run_production_turn().await.unwrap(); + seq.run_production_turn().await.unwrap(); // A below-tip block re-served with a corrupted hash. Holding a different // block at that id is not itself grounds to abort — the head tier is @@ -291,7 +291,7 @@ async fn reconstruction_ignores_a_duplicate_height_the_final_tier_settled() { let config_a = setup_sequencer_config(); let (mut seq_a, _mempool_a) = SequencerCoreWithMockClients::start_from_config(config_a.clone()).await; - seq_a.produce_new_block().await.unwrap(); + seq_a.run_production_turn().await.unwrap(); let tip_a = seq_a.block_store().latest_block_meta().unwrap().unwrap(); let mut messages = channel_from_store(seq_a.block_store(), 10); let settled_slot = messages.last().unwrap().1; @@ -374,7 +374,7 @@ async fn reconstruction_replaces_a_conflicting_head_block_with_finalized_history let config_a = setup_sequencer_config(); let (mut seq_a, _mempool_a) = SequencerCoreWithMockClients::start_from_config(config_a.clone()).await; - seq_a.produce_new_block().await.unwrap(); + seq_a.run_production_turn().await.unwrap(); let tip_a = seq_a.block_store().latest_block_meta().unwrap().unwrap(); let messages = channel_from_store(seq_a.block_store(), 10); let tip_slot = messages.last().unwrap().1; @@ -508,7 +508,7 @@ fn deposit_event_record( // .push((TransactionOrigin::Sequencer, deposit_tx)) // .await // .unwrap(); -// seq_a.produce_new_block().await.unwrap(); +// seq_a.run_production_turn().await.unwrap(); // let withdraw_tx = build_public_withdraw_tx( // recipient, @@ -521,7 +521,7 @@ fn deposit_event_record( // .push((TransactionOrigin::User, withdraw_tx.clone())) // .await // .unwrap(); -// seq_a.produce_new_block().await.unwrap(); +// seq_a.run_production_turn().await.unwrap(); // let tip_a = seq_a.block_store().latest_block_meta().unwrap().unwrap(); // let messages = channel_from_store(seq_a.block_store(), 10); @@ -571,7 +571,7 @@ fn deposit_event_record( // "reconstruction must drop the re-delivered pending deposit record" // ); -// seq_b.produce_new_block().await.unwrap(); +// seq_b.run_production_turn().await.unwrap(); // let vault_id = vault_core::compute_vault_account_id(programs::vault().id(), recipient); // let bridge_id = system_accounts::bridge_account_id(); @@ -643,7 +643,7 @@ fn deposit_event_record( // .push((TransactionOrigin::User, withdraw_tx.clone())) // .await // .unwrap(); -// seq_a.produce_new_block().await.unwrap(); +// seq_a.run_production_turn().await.unwrap(); // let key = produced_withdraw_key(&withdraw_tx); // // Producing the withdraw counts it as unseen, awaiting its L1 event. @@ -696,7 +696,7 @@ async fn reconstruction_reconciles_already_finished_deposit() { .push((TransactionOrigin::Sequencer, deposit_tx)) .await .unwrap(); - seq_a.produce_new_block().await.unwrap(); + seq_a.run_production_turn().await.unwrap(); let messages = channel_from_store(seq_a.block_store(), 10); let tip_slot = messages.last().unwrap().1; @@ -768,7 +768,7 @@ async fn reconstructed_delivery_settles_its_pending_record() { .dbio() .add_pending_cross_zone_dispatches(vec![record.clone()]) .unwrap(); - seq_a.produce_new_block().await.unwrap(); + seq_a.run_production_turn().await.unwrap(); let tip_a = seq_a.block_store().latest_block_meta().unwrap().unwrap(); let messages = channel_from_store(seq_a.block_store(), 10); @@ -818,7 +818,7 @@ async fn reconstructed_delivery_settles_its_pending_record() { payload, "the reconstructed delivery must reach its target program" ); - seq_b.produce_new_block().await.unwrap(); + seq_b.run_production_turn().await.unwrap(); let produced = seq_b .block_store() .get_block_at_id(tip_b.id + 1) @@ -847,7 +847,7 @@ async fn a_verified_own_block_settles_its_delivery_records() { .add_pending_cross_zone_dispatches(vec![record]) .unwrap(); - let block_id = seq.produce_new_block().await.unwrap(); + let block_id = seq.run_production_turn().await.unwrap(); let block = seq .block_store() .get_block_at_id(block_id) @@ -898,8 +898,8 @@ async fn committed_local_against_missing_channel_fails_without_anchor() { { let (mut seq, _handle) = SequencerCoreWithMockClients::start_from_config(config.clone()).await; - seq.produce_new_block().await.unwrap(); - seq.produce_new_block().await.unwrap(); + seq.run_production_turn().await.unwrap(); + seq.run_production_turn().await.unwrap(); assert!(seq.block_store().latest_block_meta().unwrap().unwrap().id > 1); } // drop releases the store so we can reopen it diff --git a/lez/sequencer/service/Cargo.toml b/lez/sequencer/service/Cargo.toml index 6425ffac2..c8edaec26 100644 --- a/lez/sequencer/service/Cargo.toml +++ b/lez/sequencer/service/Cargo.toml @@ -45,7 +45,7 @@ mdns = ["sequencer_core/mdns"] # Needed only by the submit_stake bin. submit_stake = [ "dep:lee", - "dep:programs", + "programs", "dep:system_accounts", "dep:wallet", "dep:sequencer_stake_core", diff --git a/test_fixtures/Cargo.toml b/test_fixtures/Cargo.toml index 41e82bda7..4d9982123 100644 --- a/test_fixtures/Cargo.toml +++ b/test_fixtures/Cargo.toml @@ -16,6 +16,7 @@ lee.workspace = true lee_core = { workspace = true, features = ["host"] } sequencer_core = { workspace = true, features = ["default", "testnet"] } sequencer_service.workspace = true +sequencer_stake_core.workspace = true sequencer_service_rpc = { workspace = true, features = ["client"] } wallet.workspace = true programs.workspace = true diff --git a/test_fixtures/fixtures/prebuilt_sequencer_db.dump b/test_fixtures/fixtures/prebuilt_sequencer_db.dump index 0fe43eded..dc32e1d86 100644 Binary files a/test_fixtures/fixtures/prebuilt_sequencer_db.dump and b/test_fixtures/fixtures/prebuilt_sequencer_db.dump differ diff --git a/test_fixtures/src/config.rs b/test_fixtures/src/config.rs index a068c7ad2..9513dd509 100644 --- a/test_fixtures/src/config.rs +++ b/test_fixtures/src/config.rs @@ -6,11 +6,13 @@ use indexer_service::{ChannelId, ClientConfig, IndexerConfig}; use key_protocol::key_management::{KeyChain, secret_holders::SeedHolder}; use lee::{AccountId, PrivateKey, PublicKey}; use lee_core::Identifier; -use logos_blockchain_key_management_system_service::keys::ZkPublicKey; +use logos_blockchain_key_management_system_service::keys::{Ed25519Key, ZkPublicKey}; use num_bigint::BigUint; -use sequencer_core::config::{ - BedrockConfig, CrossZoneConfig, GenesisAction, GossipConfig, SequencerConfig, +use sequencer_core::{ + config::{BedrockConfig, CrossZoneConfig, GenesisAction, GossipConfig, SequencerConfig}, + sign_genesis_stake, }; +use sequencer_stake_core::SequencerKey; use url::Url; use wallet::config::{MultiSequencerClientConfig, SequencerConnectionData, WalletConfig}; @@ -315,6 +317,37 @@ pub fn sequencer_signing_key_from_seed(seed: u32) -> [u8; 32] { .unwrap_or_else(|_| unreachable!()) } +/// Seed of the account owning sequencer `index`'s founding stake. +fn founding_stake_owner_seed(index: usize) -> [u8; 32] { + if index == 0 { + return SEQUENCER_STAKE_KEY; + } + let mut seed = [0x70; 32]; + seed[0] = u8::try_from(index).expect("Test contexts never run enough sequencers to overflow"); + seed +} + +/// Genesis entries staking every sequencer in `sequencer_signing_keys`, so the +/// creator opens the channel already accrediting all of them. +pub fn genesis_sequencer_stakes(sequencer_signing_keys: &[[u8; 32]]) -> Result> { + sequencer_signing_keys + .iter() + .enumerate() + .map(|(index, signing_key)| { + let public_key = Ed25519Key::from_bytes(signing_key).public_key(); + let sequencer_key = SequencerKey::new(public_key.to_bytes()) + .context("Sequencer signing key is not a valid Ed25519 point")?; + let owner = PrivateKey::try_new(founding_stake_owner_seed(index)) + .context("Failed to build the founding stake ownership key")?; + Ok(GenesisAction::StakeSequencer { + sequencer_key, + ownership_public_key: PublicKey::new_from_private_key(&owner), + stake_signature: sign_genesis_stake(index, sequencer_key, &owner), + }) + }) + .collect() +} + /// Generate bedrock channel id from `u32` number via repeating le bytes 8 times. /// /// Counting from the end of `u32` to guarantee, that it is different from diff --git a/test_fixtures/src/lib.rs b/test_fixtures/src/lib.rs index bc67fbc80..b6c31f87a 100644 --- a/test_fixtures/src/lib.rs +++ b/test_fixtures/src/lib.rs @@ -10,13 +10,8 @@ use indexer_service::{ChannelId, IndexerHandle}; use lee::{AccountId, PrivacyPreservingTransaction, PrivateKey}; use lee_core::Commitment; use log::{debug, error}; -use sequencer_core::{ - block_publisher::{Ed25519Key, post_channel_config}, - config::GenesisAction, -}; -use sequencer_service::{ - BedrockConfig, CrossZoneConfig, GossipConfig, SequencerHandle, default_priority_fee, -}; +use sequencer_core::config::GenesisAction; +use sequencer_service::{CrossZoneConfig, GossipConfig, SequencerHandle}; use sequencer_service_rpc::{RpcClient as _, SequencerClient}; use serde::Serialize; use tempfile::TempDir; @@ -27,10 +22,7 @@ use wallet::{ }; use crate::{ - config::{ - InitialPrivateAccountForWallet, MultiNodeTestContextConfig, SequencerPartialConfig, - bedrock_funding_key, - }, + config::{InitialPrivateAccountForWallet, MultiNodeTestContextConfig, SequencerPartialConfig}, indexer_client::IndexerClient, setup::{ SequencerSetup, setup_bedrock_node, setup_indexer, @@ -45,10 +37,6 @@ pub mod setup; // TODO: Remove this and control time from tests pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; -/// 1 s bedrock slots: rotate the turn every ~20 s of tenure; steal a stalled -/// turn after ~30 s (bounds the stall while B is accredited but not started). -const POSTING_TIMEFRAME_SLOTS: u32 = 20; -const POSTING_TIMEOUT_SLOTS: u32 = 30; pub(crate) const BEDROCK_SERVICE_WITH_OPEN_PORT: &str = "logos-blockchain-node-0"; pub(crate) const BEDROCK_SERVICE_PORT: u16 = 18080; @@ -641,6 +629,24 @@ impl ZoneTestContextBuilder { debug!("Test context setup"); + let mut sequencer_keys = vec![config::SEQUENCER_SIGNING_KEY]; + sequencer_keys.extend((1..mn_config.num_nodes).map(|i| { + config::sequencer_signing_key_from_seed( + u32::try_from(i).expect("Not being able to fit is realistically impossible"), + ) + })); + + let genesis_transactions = if mn_config.num_nodes == 1 { + genesis_transactions + } else { + let mut actions = config::genesis_sequencer_stakes(&sequencer_keys) + .context("Failed to build the founding sequencer stakes")?; + actions.extend(genesis_transactions.unwrap_or_default()); + // Returning Some() forces a live build below: the prebuilt dump stakes only one + // sequencer. + Some(actions) + }; + // The fixture bakes in the default accounts + genesis, so custom genesis / from_scratch // must build live. Otherwise load the fixture (fails if it is missing). let use_prebuilt = !from_scratch && genesis_transactions.is_none(); @@ -673,16 +679,6 @@ impl ZoneTestContextBuilder { let mut sequencer_addrs = vec![]; let mut sequencer_components = vec![]; - let mut sequencer_keys = vec![]; - - sequencer_keys.push(config::SEQUENCER_SIGNING_KEY); - - sequencer_keys.extend((1..mn_config.num_nodes).map(|i| { - config::sequencer_signing_key_from_seed( - u32::try_from(i).expect("Not being able to fit is realistically impossible"), - ) - })); - // First, need to start a leader. let leader_gossip = enable_gossip.then(|| GossipConfig { listen_addr: "/ip4/127.0.0.1/udp/0/quic-v1" @@ -727,16 +723,7 @@ impl ZoneTestContextBuilder { sequencer_addrs.push(leader_addr); sequencer_components.push(leader_components); - // Skip posting chain config with just one node. - if mn_config.num_nodes != 1 { - post_chain_config_with_default_parameters( - mn_config.bedrock_channel, - bedrock_addr, - sequencer_keys.clone(), - ) - .await?; - } - + // Followers are already accredited by their genesis stakes. for sequencer_key in sequencer_keys.into_iter().skip(1) { let (sequencer_addr, sequencer_component) = build_sequencer_components( follower_sequencer_partial_config.unwrap_or(partial_config), @@ -1070,47 +1057,6 @@ fn dir_size_bytes(path: &Path) -> u64 { total } -async fn post_chain_config_with_default_parameters( - channel_id: ChannelId, - bedrock_addr: SocketAddr, - sequencer_keys: Vec<[u8; 32]>, -) -> Result<()> { - log::info!( - "Sequencer committee is {:?} at {channel_id}", - sequencer_keys - .iter() - .map(|key| Ed25519Key::from_bytes(key).public_key().as_bytes().to_vec()) - .map(hex::encode) - .collect::>() - ); - - post_channel_config( - &BedrockConfig { - channel_id, - node_url: config::addr_to_url(config::UrlProtocol::Http, bedrock_addr)?, - auth: None, - funding_key: bedrock_funding_key(), - priority_fee: default_priority_fee(), - }, - &Ed25519Key::from_bytes( - sequencer_keys - .first() - .expect("Must be at least one sequencer"), - ), - sequencer_keys - .clone() - .into_iter() - .map(|key| Ed25519Key::from_bytes(&key).public_key()) - .collect(), - POSTING_TIMEFRAME_SLOTS, - POSTING_TIMEOUT_SLOTS, - 1, - 1, - ) - .await - .context("Failed to configure the channel committee") -} - async fn wait_until_genesis(client: &SequencerClient) -> Result<()> { log::info!("Waiting for leader to send genesis"); diff --git a/test_fixtures/src/setup.rs b/test_fixtures/src/setup.rs index 7880fe354..6844d4b6a 100644 --- a/test_fixtures/src/setup.rs +++ b/test_fixtures/src/setup.rs @@ -77,6 +77,14 @@ impl SequencerSetup { self } + /// Build a sequencer that joins a channel another node already created, + /// replaying its genesis from the channel instead of the prebuilt dump. + #[must_use] + pub fn joining_existing_channel(mut self) -> Self { + self.genesis_transactions = Some(Vec::new()); + self + } + /// Pre-write a bedrock (Ed25519, 32-byte seed) signing key into the home /// before boot, so tests know the sequencer's public key in advance (e.g. /// to accredit a committee member that has not started yet).