mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-26 03:41:13 +00:00
refactor(sequencer): some renamings, better key-reuse
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
//! down degrades to L1-only behavior, and a gossip failure after startup
|
||||
//! never halts the node.
|
||||
|
||||
pub use network::{Libp2pNetwork, PeerNetworkTrait, TxPublisher};
|
||||
pub use network::{GossipNetwork, PeerNetworkTrait, TxPublisher};
|
||||
|
||||
pub mod accreditation;
|
||||
pub mod network;
|
||||
|
||||
@@ -15,6 +15,7 @@ use libp2p::{
|
||||
multiaddr::Protocol,
|
||||
swarm::{NetworkBehaviour, Swarm, SwarmEvent},
|
||||
};
|
||||
use logos_blockchain_key_management_system_service::keys::Ed25519Key;
|
||||
use mempool::MemPoolHandle;
|
||||
use tokio::sync::{mpsc, watch};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
@@ -55,7 +56,7 @@ pub trait PeerNetworkTrait {
|
||||
}
|
||||
|
||||
/// Handle to the running gossip network. Dropping it stops the drive task.
|
||||
pub struct Libp2pNetwork {
|
||||
pub struct GossipNetwork {
|
||||
connected_rx: watch::Receiver<Vec<[u8; 32]>>,
|
||||
shutdown: CancellationToken,
|
||||
listen_addrs: Vec<Multiaddr>,
|
||||
@@ -77,7 +78,7 @@ impl TxPublisher {
|
||||
}
|
||||
}
|
||||
|
||||
impl Libp2pNetwork {
|
||||
impl GossipNetwork {
|
||||
/// Builds the swarm, binds `listen_addr`, seeds Kademlia and dials
|
||||
/// bootstrap peers, and spawns the drive task. Fails fast on a bad
|
||||
/// listen/bootstrap multiaddr or bind failure; after that, gossip errors
|
||||
@@ -85,12 +86,15 @@ impl Libp2pNetwork {
|
||||
pub async fn start(
|
||||
config: GossipConfig,
|
||||
channel_id: [u8; 32],
|
||||
secret_key: [u8; 32],
|
||||
signing_key: Ed25519Key,
|
||||
mempool_handle: MemPoolHandle<(TransactionOrigin, LeeTransaction)>,
|
||||
max_block_size: u64,
|
||||
) -> Result<Self> {
|
||||
let mut secret_for_libp2p = secret_key;
|
||||
let keypair = Keypair::ed25519_from_bytes(&mut secret_for_libp2p)
|
||||
// Reuse the node's L1 bedrock signing key as the libp2p identity. The
|
||||
// secret stays in a `Zeroizing` buffer that both `ed25519_from_bytes`
|
||||
// and drop wipe.
|
||||
let mut secret = signing_key.into_unsecured().to_bytes();
|
||||
let keypair = Keypair::ed25519_from_bytes(&mut *secret)
|
||||
.map_err(|err| anyhow!("Invalid bedrock signing key for libp2p identity: {err}"))?;
|
||||
let local_peer_id = keypair.public().to_peer_id();
|
||||
|
||||
@@ -249,7 +253,7 @@ impl Libp2pNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerNetworkTrait for Libp2pNetwork {
|
||||
impl PeerNetworkTrait for GossipNetwork {
|
||||
fn connected_peers(&self) -> Vec<[u8; 32]> {
|
||||
self.connected_rx.borrow().clone()
|
||||
}
|
||||
@@ -259,7 +263,7 @@ impl PeerNetworkTrait for Libp2pNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Libp2pNetwork {
|
||||
impl Drop for GossipNetwork {
|
||||
fn drop(&mut self) {
|
||||
self.shutdown.cancel();
|
||||
}
|
||||
@@ -559,10 +563,10 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_binds_and_reports_listen_addr() {
|
||||
let network = Libp2pNetwork::start(
|
||||
let network = GossipNetwork::start(
|
||||
test_config(),
|
||||
[1; 32],
|
||||
[9; 32],
|
||||
Ed25519Key::from_bytes(&[9; 32]),
|
||||
test_mempool_handle(),
|
||||
TEST_MAX_BLOCK_SIZE,
|
||||
)
|
||||
@@ -581,10 +585,10 @@ mod tests {
|
||||
..test_config()
|
||||
};
|
||||
assert!(
|
||||
Libp2pNetwork::start(
|
||||
GossipNetwork::start(
|
||||
config,
|
||||
[1; 32],
|
||||
[9; 32],
|
||||
Ed25519Key::from_bytes(&[9; 32]),
|
||||
test_mempool_handle(),
|
||||
TEST_MAX_BLOCK_SIZE
|
||||
)
|
||||
@@ -600,10 +604,10 @@ mod tests {
|
||||
..test_config()
|
||||
};
|
||||
assert!(
|
||||
Libp2pNetwork::start(
|
||||
GossipNetwork::start(
|
||||
config,
|
||||
[1; 32],
|
||||
[9; 32],
|
||||
Ed25519Key::from_bytes(&[9; 32]),
|
||||
test_mempool_handle(),
|
||||
TEST_MAX_BLOCK_SIZE
|
||||
)
|
||||
@@ -614,10 +618,10 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn drop_cancels_driver() {
|
||||
let network = Libp2pNetwork::start(
|
||||
let network = GossipNetwork::start(
|
||||
test_config(),
|
||||
[1; 32],
|
||||
[9; 32],
|
||||
Ed25519Key::from_bytes(&[9; 32]),
|
||||
test_mempool_handle(),
|
||||
TEST_MAX_BLOCK_SIZE,
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ use testnet_initial_state::{initial_pub_accounts_private_keys, initial_public_us
|
||||
use crate::{
|
||||
TransactionOrigin,
|
||||
config::GossipConfig,
|
||||
gossip::{Libp2pNetwork, PeerNetworkTrait as _},
|
||||
gossip::{GossipNetwork, PeerNetworkTrait as _},
|
||||
};
|
||||
|
||||
const CHANNEL: [u8; 32] = [1; 32];
|
||||
@@ -46,16 +46,21 @@ fn invalidly_signed_transaction() -> LeeTransaction {
|
||||
async fn start_node(
|
||||
secret: [u8; 32],
|
||||
bootstrap: Vec<String>,
|
||||
) -> (Libp2pNetwork, MemPool<(TransactionOrigin, LeeTransaction)>) {
|
||||
) -> (GossipNetwork, MemPool<(TransactionOrigin, LeeTransaction)>) {
|
||||
let config = GossipConfig {
|
||||
listen_addr: "/ip4/127.0.0.1/udp/0/quic-v1".to_owned(),
|
||||
bootstrap_peers: bootstrap,
|
||||
};
|
||||
let (mempool, mempool_handle) = MemPool::new(1000);
|
||||
let network =
|
||||
Libp2pNetwork::start(config, CHANNEL, secret, mempool_handle, TEST_MAX_BLOCK_SIZE)
|
||||
.await
|
||||
.expect("node should start");
|
||||
let network = GossipNetwork::start(
|
||||
config,
|
||||
CHANNEL,
|
||||
Ed25519Key::from_bytes(&secret),
|
||||
mempool_handle,
|
||||
TEST_MAX_BLOCK_SIZE,
|
||||
)
|
||||
.await
|
||||
.expect("node should start");
|
||||
(network, mempool)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ pub use sequencer_core::config::*;
|
||||
use sequencer_core::{
|
||||
TransactionOrigin,
|
||||
block_publisher::BlockPublisherTrait as _,
|
||||
load_or_create_signing_key,
|
||||
task_group::{StoreRelease, TaskGroup},
|
||||
};
|
||||
use sequencer_service_rpc::RpcServer as _;
|
||||
@@ -43,9 +44,13 @@ pub struct SequencerHandle {
|
||||
/// handle stops, so watching the count go to zero is how shutdown knows the
|
||||
/// database file is actually closed rather than assuming it from drop order.
|
||||
store: StoreRelease,
|
||||
/// Held for its lifetime: dropping it aborts the gossip drive task. `None`
|
||||
/// when gossip is unconfigured.
|
||||
_gossip_network: Option<sequencer_core::gossip::Libp2pNetwork>,
|
||||
/// Held for its lifetime: dropping it stops the gossip drive task.
|
||||
/// `None` when gossip is unconfigured.
|
||||
#[expect(
|
||||
dead_code,
|
||||
reason = "never read; kept alive so drop stops the gossip driver"
|
||||
)]
|
||||
gossip: Option<sequencer_core::gossip::GossipNetwork>,
|
||||
}
|
||||
|
||||
impl SequencerHandle {
|
||||
@@ -56,7 +61,7 @@ impl SequencerHandle {
|
||||
driver_cancellation: CancellationToken,
|
||||
background_tasks: Vec<TaskGroup>,
|
||||
store: StoreRelease,
|
||||
gossip_network: Option<sequencer_core::gossip::Libp2pNetwork>,
|
||||
gossip: Option<sequencer_core::gossip::GossipNetwork>,
|
||||
) -> Self {
|
||||
Self {
|
||||
addr,
|
||||
@@ -65,7 +70,7 @@ impl SequencerHandle {
|
||||
driver_cancellation,
|
||||
background_tasks,
|
||||
store,
|
||||
_gossip_network: gossip_network,
|
||||
gossip,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +123,7 @@ impl SequencerHandle {
|
||||
driver_cancellation,
|
||||
background_tasks: _,
|
||||
store: _,
|
||||
_gossip_network: _,
|
||||
gossip: _,
|
||||
} = self;
|
||||
|
||||
// Cloned rather than taken: `stopped()` consumes a handle, and taking
|
||||
@@ -152,7 +157,7 @@ impl SequencerHandle {
|
||||
driver_cancellation,
|
||||
background_tasks,
|
||||
store: _,
|
||||
_gossip_network: _,
|
||||
gossip: _,
|
||||
} = self;
|
||||
|
||||
let stopped = server_handle.is_stopped()
|
||||
@@ -179,7 +184,7 @@ impl Drop for SequencerHandle {
|
||||
driver_cancellation: _,
|
||||
background_tasks: _,
|
||||
store: _,
|
||||
_gossip_network: _,
|
||||
gossip: _,
|
||||
} = self;
|
||||
|
||||
main_loop_handle.abort();
|
||||
@@ -240,27 +245,14 @@ pub async fn run(config: SequencerConfig, listen_addr: SocketAddr) -> Result<Seq
|
||||
let gossip_network = match gossip_config {
|
||||
None => None,
|
||||
Some(gossip_config) => {
|
||||
// On-disk format matches `sequencer_core::load_or_create_signing_key`
|
||||
// (raw 32-byte secret). `Ed25519Key` has no accessor for the raw
|
||||
// secret outside the `unsafe` feature, and `Libp2pNetwork::start`
|
||||
// needs it directly, so this reads the file itself rather than
|
||||
// going through the KMS type. The node's L1 bedrock signing key is
|
||||
// deliberately reused as the libp2p identity.
|
||||
let key_path = sequencer_home.join("bedrock_signing_key");
|
||||
let secret: [u8; 32] = std::fs::read(&key_path)
|
||||
.with_context(|| format!("Failed to read {}", key_path.display()))?
|
||||
.try_into()
|
||||
.map_err(|bytes: Vec<u8>| {
|
||||
anyhow!(
|
||||
"Bedrock signing key has incorrect length: expected 32 bytes, got {}",
|
||||
bytes.len()
|
||||
)
|
||||
})?;
|
||||
// The node's L1 bedrock signing key is deliberately reused as the
|
||||
// libp2p identity; `GossipNetwork::start` derives the keypair.
|
||||
let signing_key = load_or_create_signing_key(&sequencer_home)?;
|
||||
let channel_id = *bedrock_config.channel_id.as_ref();
|
||||
let network = sequencer_core::gossip::Libp2pNetwork::start(
|
||||
let network = sequencer_core::gossip::GossipNetwork::start(
|
||||
gossip_config,
|
||||
channel_id,
|
||||
secret,
|
||||
signing_key,
|
||||
mempool_handle.clone(),
|
||||
max_block_size.as_u64(),
|
||||
)
|
||||
@@ -272,7 +264,7 @@ pub async fn run(config: SequencerConfig, listen_addr: SocketAddr) -> Result<Seq
|
||||
};
|
||||
let tx_publisher = gossip_network
|
||||
.as_ref()
|
||||
.map(sequencer_core::gossip::Libp2pNetwork::tx_publisher);
|
||||
.map(sequencer_core::gossip::GossipNetwork::tx_publisher);
|
||||
|
||||
let driver_cancellation = sequencer_core.block_publisher().driver_cancellation();
|
||||
// Taken while the core is still owned here: once it is behind the `Arc`
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct SequencerService<BC: BlockPublisherTrait> {
|
||||
sequencer: Arc<Mutex<SequencerCore<BC>>>,
|
||||
mempool_handle: MemPoolHandle<(TransactionOrigin, LeeTransaction)>,
|
||||
max_block_size: u64,
|
||||
tx_publisher: Option<sequencer_core::gossip::network::TxPublisher>,
|
||||
gossip_tx_publisher: Option<sequencer_core::gossip::network::TxPublisher>,
|
||||
}
|
||||
|
||||
impl<BC: BlockPublisherTrait> SequencerService<BC> {
|
||||
@@ -31,13 +31,13 @@ impl<BC: BlockPublisherTrait> SequencerService<BC> {
|
||||
sequencer: Arc<Mutex<SequencerCore<BC>>>,
|
||||
mempool_handle: MemPoolHandle<(TransactionOrigin, LeeTransaction)>,
|
||||
max_block_size: u64,
|
||||
tx_publisher: Option<sequencer_core::gossip::network::TxPublisher>,
|
||||
gossip_tx_publisher: Option<sequencer_core::gossip::network::TxPublisher>,
|
||||
) -> Self {
|
||||
Self {
|
||||
sequencer,
|
||||
mempool_handle,
|
||||
max_block_size,
|
||||
tx_publisher,
|
||||
gossip_tx_publisher,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,11 +105,13 @@ impl<BC: BlockPublisherTrait + Send + Sync + 'static> sequencer_service_rpc::Rpc
|
||||
})?;
|
||||
|
||||
// Publish to the gossip mesh before the (blocking) local mempool push so
|
||||
// a full mempool doesn't delay propagation. Fire-and-forget; the clone
|
||||
// only happens when gossip is enabled.
|
||||
if let Some(publisher) = &self.tx_publisher {
|
||||
// a full mempool doesn't delay propagation.
|
||||
//
|
||||
// TODO: may change with actor-based mempool
|
||||
if let Some(publisher) = &self.gossip_tx_publisher {
|
||||
publisher.publish(authenticated_tx.clone());
|
||||
}
|
||||
|
||||
self.mempool_handle
|
||||
.push((TransactionOrigin::User, authenticated_tx))
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user