mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-25 03:11:21 +00:00
refactor(sequencer): rename produce_new_block to run_production_turn
This commit is contained in:
Generated
+1
@@ -10524,6 +10524,7 @@ dependencies = [
|
||||
"sequencer_core",
|
||||
"sequencer_service",
|
||||
"sequencer_service_rpc",
|
||||
"sequencer_stake_core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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<GenesisAction> {
|
||||
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<F, Fut>(what: &str, mut check: F) -> Result<()>
|
||||
where
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -143,10 +143,10 @@ impl<BP: BlockPublisherTrait + Send + 'static> Message<ProduceBlock> 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)?;
|
||||
|
||||
|
||||
@@ -662,8 +662,10 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Produces a new block from mempool transactions and publishes it via zone-sdk.
|
||||
pub async fn produce_new_block(&mut self) -> Result<u64> {
|
||||
/// 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<u64> {
|
||||
let live_accredited_keys = self.live_accredited_sequencer_keys().await;
|
||||
|
||||
let BlockWithMeta {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -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<Vec<GenesisAction>> {
|
||||
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
|
||||
|
||||
+22
-76
@@ -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::<Vec<_>>()
|
||||
);
|
||||
|
||||
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");
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user