mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-25 11:21:12 +00:00
fix(cross-zone)!: drop the inbox route allowlist now targets authorize themselves
BREAKING CHANGE: InboxConfig is just self_zone; allowed_routes, InboxConfig::permits and routes_permit are gone, and build_inbox_init_config_tx no longer takes the cross-zone config. The operator's per-peer routes still configure the same thing, fanned out at genesis into each target's own config instead. The watcher keeps only a hygiene filter refusing a peer that names a sequencer-only program, kept host-side so it cannot make the verifier disagree.
This commit is contained in:
@@ -10,10 +10,8 @@
|
||||
//! `outbox::Emit`). Fast, so they pin guest logic before the e2e exercises the
|
||||
//! plumbing. Run with `RISC0_DEV_MODE=1`.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use cross_zone_inbox_core::{
|
||||
CrossZoneMessage, CrossZoneRoute, InboxConfig, Instruction as InboxInstruction, SeenShard,
|
||||
CrossZoneMessage, InboxConfig, Instruction as InboxInstruction, SeenShard,
|
||||
inbox_config_account_id, inbox_seen_shard_account_id, inbox_source_marker_account_id,
|
||||
};
|
||||
use cross_zone_outbox_core::{OutboxRecord, outbox_pda};
|
||||
@@ -45,27 +43,10 @@ fn base_state() -> V03State {
|
||||
])
|
||||
}
|
||||
|
||||
/// Seeds an inbox config (inbox-owned) allowing `src_zone -> target`.
|
||||
fn seed_inbox_config(
|
||||
state: &mut V03State,
|
||||
self_zone: [u8; 32],
|
||||
src_zone: [u8; 32],
|
||||
src_program_id: lee_core::program::ProgramId,
|
||||
target: lee_core::program::ProgramId,
|
||||
) {
|
||||
/// Seeds the inbox config (inbox-owned), which is now just this zone's id.
|
||||
fn seed_inbox_config(state: &mut V03State, self_zone: [u8; 32]) {
|
||||
let inbox_id = programs::cross_zone_inbox().id();
|
||||
let mut allowed_routes = BTreeMap::new();
|
||||
allowed_routes.insert(
|
||||
src_zone,
|
||||
vec![CrossZoneRoute {
|
||||
src_program_id,
|
||||
target_program_id: target,
|
||||
}],
|
||||
);
|
||||
let config = InboxConfig {
|
||||
self_zone,
|
||||
allowed_routes,
|
||||
};
|
||||
let config = InboxConfig { self_zone };
|
||||
*state = std::mem::replace(state, V03State::new()).with_public_accounts([(
|
||||
inbox_config_account_id(inbox_id),
|
||||
Account {
|
||||
@@ -228,13 +209,7 @@ fn dispatch_mint(amount: u128) -> Result<ValidatedStateDiff, lee::error::LeeErro
|
||||
let src_block_id = 5;
|
||||
|
||||
let mut state = base_state();
|
||||
seed_inbox_config(
|
||||
&mut state,
|
||||
self_zone,
|
||||
src_zone,
|
||||
[9_u32; 8],
|
||||
wrapped_token_id,
|
||||
);
|
||||
seed_inbox_config(&mut state, self_zone);
|
||||
seed_wrapped_config(&mut state, vec![(src_zone, [9_u32; 8])]);
|
||||
|
||||
let msg = CrossZoneMessage {
|
||||
@@ -302,7 +277,7 @@ fn inbox_dispatch_delivers_payload_to_ping_receiver() {
|
||||
let src_block_id = 5;
|
||||
|
||||
let mut state = base_state();
|
||||
seed_inbox_config(&mut state, self_zone, src_zone, [9_u32; 8], receiver_id);
|
||||
seed_inbox_config(&mut state, self_zone);
|
||||
seed_receiver_config(&mut state, vec![(src_zone, [9_u32; 8])]);
|
||||
|
||||
// The payload is the ping_receiver instruction, serialized as risc0 words in
|
||||
@@ -987,12 +962,11 @@ fn inbox_dispatch_mints_wrapped_token() {
|
||||
);
|
||||
}
|
||||
|
||||
/// A zone that bridges must allow `wrapped_token` as a target. When that
|
||||
/// allowance was per peer rather than per source program, it was enough for any
|
||||
/// emitter on the peer to reach it, and `ping_sender` lets its caller choose the
|
||||
/// target and payload freely. Any user on the peer could therefore mint wrapped
|
||||
/// tokens with no lock and no escrow behind them, by routing a `Mint` payload
|
||||
/// through the ping emitter. The route is the pair, so this must not execute.
|
||||
/// `ping_sender` lets its caller choose the target and payload freely, so any user
|
||||
/// on a peer can aim a `Mint` payload at `wrapped_token`. The inbox no longer
|
||||
/// refuses it; the token does, because the marker names `ping_sender` and the
|
||||
/// token authorized only the bridge. This is the check that replaced the central
|
||||
/// route table, so it must be the thing that rejects here.
|
||||
#[test]
|
||||
fn a_mint_from_an_unrouted_emitter_is_rejected() {
|
||||
let inbox_id = programs::cross_zone_inbox().id();
|
||||
@@ -1004,13 +978,7 @@ fn a_mint_from_an_unrouted_emitter_is_rejected() {
|
||||
|
||||
let mut state = base_state();
|
||||
// The config a bridging zone writes: the lock program may mint, nothing else.
|
||||
seed_inbox_config(
|
||||
&mut state,
|
||||
self_zone,
|
||||
src_zone,
|
||||
programs::bridge_lock().id(),
|
||||
wrapped_token_id,
|
||||
);
|
||||
seed_inbox_config(&mut state, self_zone);
|
||||
seed_wrapped_config(&mut state, vec![(src_zone, programs::bridge_lock().id())]);
|
||||
|
||||
let msg = CrossZoneMessage {
|
||||
@@ -1057,13 +1025,7 @@ fn a_mint_from_the_routed_emitter_is_accepted() {
|
||||
let src_block_id = 5;
|
||||
|
||||
let mut state = base_state();
|
||||
seed_inbox_config(
|
||||
&mut state,
|
||||
self_zone,
|
||||
src_zone,
|
||||
bridge_lock_id,
|
||||
wrapped_token_id,
|
||||
);
|
||||
seed_inbox_config(&mut state, self_zone);
|
||||
seed_wrapped_config(&mut state, vec![(src_zone, programs::bridge_lock().id())]);
|
||||
|
||||
let msg = CrossZoneMessage {
|
||||
@@ -1111,13 +1073,7 @@ fn mint_replay_rejected() {
|
||||
let src_tx_index = 0;
|
||||
|
||||
let mut state = base_state();
|
||||
seed_inbox_config(
|
||||
&mut state,
|
||||
self_zone,
|
||||
src_zone,
|
||||
[9_u32; 8],
|
||||
wrapped_token_id,
|
||||
);
|
||||
seed_inbox_config(&mut state, self_zone);
|
||||
seed_wrapped_config(&mut state, vec![(src_zone, [9_u32; 8])]);
|
||||
|
||||
// Seed the seen-shard as already holding this delivery, so the inbox takes
|
||||
@@ -1197,7 +1153,7 @@ fn a_delivery_from_a_second_block_at_the_same_id_is_refused() {
|
||||
let other_block_hash = [8_u8; 32];
|
||||
|
||||
let mut state = base_state();
|
||||
seed_inbox_config(&mut state, self_zone, src_zone, [9_u32; 8], receiver_id);
|
||||
seed_inbox_config(&mut state, self_zone);
|
||||
seed_receiver_config(&mut state, vec![(src_zone, [9_u32; 8])]);
|
||||
|
||||
// The shard as the first delivery left it: bound, holding transaction 0.
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
//! own block-reading, emission-extraction, delivery-building, and trust model; a
|
||||
//! shared trait is best lifted from that first real adapter, not from this one.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub use cross_zone_inbox_core::{CrossZoneConfig, CrossZonePeer};
|
||||
use cross_zone_inbox_core::{
|
||||
CrossZoneMessage, InboxConfig, Instruction, ZoneId, inbox_config_account_id,
|
||||
@@ -172,33 +170,18 @@ pub fn build_dispatch_from_emission(
|
||||
build_inbox_dispatch_tx(programs::cross_zone_inbox().id(), &msg, target_ids)
|
||||
}
|
||||
|
||||
/// The inbox config a zone derives from its cross-zone config: the per-peer
|
||||
/// delivery routes plus its own zone id.
|
||||
fn inbox_config(self_zone: ZoneId, cross_zone: &CrossZoneConfig) -> InboxConfig {
|
||||
let mut allowed_routes = BTreeMap::new();
|
||||
for peer in &cross_zone.peers {
|
||||
allowed_routes.insert(peer.channel_id, peer.allowed_routes.clone());
|
||||
}
|
||||
InboxConfig {
|
||||
self_zone,
|
||||
allowed_routes,
|
||||
}
|
||||
}
|
||||
|
||||
/// The genesis transaction that initializes this zone's inbox config PDA.
|
||||
///
|
||||
/// Lets the inbox guest authorize inbound peer messages; replaying it seeds the
|
||||
/// same account on every node, keeping their state consistent.
|
||||
/// The operator's per-peer routes no longer live here. They are fanned out into
|
||||
/// each target program's own config, so all the inbox keeps is its zone id.
|
||||
/// Replaying this seeds the same account on every node.
|
||||
#[must_use]
|
||||
pub fn build_inbox_init_config_tx(
|
||||
self_zone: ZoneId,
|
||||
cross_zone: &CrossZoneConfig,
|
||||
) -> lee::PublicTransaction {
|
||||
pub fn build_inbox_init_config_tx(self_zone: ZoneId) -> lee::PublicTransaction {
|
||||
let inbox_id = programs::cross_zone_inbox().id();
|
||||
genesis_public_tx(
|
||||
inbox_id,
|
||||
vec![inbox_config_account_id(inbox_id)],
|
||||
Instruction::InitConfig(inbox_config(self_zone, cross_zone)),
|
||||
Instruction::InitConfig(InboxConfig { self_zone }),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use lee_core::{
|
||||
@@ -85,32 +85,20 @@ pub struct CrossZoneMessage {
|
||||
pub l1_inclusion_witness: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// Per-peer delivery routes, plus this inbox's own zone id.
|
||||
/// This inbox's own zone id.
|
||||
///
|
||||
/// It no longer decides who may deliver what. Each target program authorizes its
|
||||
/// own sources against the marker the inbox passes, so the only thing the inbox
|
||||
/// still needs to know is which zone it is, to refuse a message addressed to
|
||||
/// itself.
|
||||
#[derive(
|
||||
Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
||||
)]
|
||||
pub struct InboxConfig {
|
||||
pub self_zone: ZoneId,
|
||||
/// Which deliveries each peer may make. A peer absent from this map may
|
||||
/// deliver nothing.
|
||||
pub allowed_routes: BTreeMap<ZoneId, Vec<CrossZoneRoute>>,
|
||||
}
|
||||
|
||||
impl InboxConfig {
|
||||
/// Whether `src_zone` may deliver from `src_program_id` to
|
||||
/// `target_program_id`. A peer with no routes may deliver nothing.
|
||||
#[must_use]
|
||||
pub fn permits(
|
||||
&self,
|
||||
src_zone: &ZoneId,
|
||||
src_program_id: ProgramId,
|
||||
target_program_id: ProgramId,
|
||||
) -> bool {
|
||||
self.allowed_routes
|
||||
.get(src_zone)
|
||||
.is_some_and(|routes| routes_permit(routes, src_program_id, target_program_id))
|
||||
}
|
||||
|
||||
/// Borsh-encoded form stored in the inbox config account.
|
||||
#[must_use]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
@@ -205,25 +193,6 @@ pub enum Instruction {
|
||||
InitConfig(InboxConfig),
|
||||
}
|
||||
|
||||
/// Whether `routes` authorize a delivery from `src_program_id` to
|
||||
/// `target_program_id`.
|
||||
///
|
||||
/// The one place the rule lives. The inbox guest decides with it and the
|
||||
/// sequencer's watcher drops unroutable messages with it, and those two must
|
||||
/// agree: a watcher stricter than the guest loses messages silently, and one
|
||||
/// looser records deliveries the guest will refuse, which production then feeds
|
||||
/// in and gives up on.
|
||||
#[must_use]
|
||||
pub fn routes_permit(
|
||||
routes: &[CrossZoneRoute],
|
||||
src_program_id: ProgramId,
|
||||
target_program_id: ProgramId,
|
||||
) -> bool {
|
||||
routes.iter().any(|route| {
|
||||
route.src_program_id == src_program_id && route.target_program_id == target_program_id
|
||||
})
|
||||
}
|
||||
|
||||
/// Content-addressed replay key for a delivered message.
|
||||
///
|
||||
/// Hashes `(src_zone, src_block_id, src_tx_index)` under a domain separator.
|
||||
@@ -336,63 +305,6 @@ mod tests {
|
||||
[b; 32]
|
||||
}
|
||||
|
||||
fn program(n: u32) -> ProgramId {
|
||||
[n; 8]
|
||||
}
|
||||
|
||||
/// The route is the pair. Two entries that are each reasonable on their own,
|
||||
/// a lock program that may mint and a ping emitter that may reach a
|
||||
/// receiver, must not compose into the lock program's target being
|
||||
/// reachable from the ping emitter: that emitter lets its caller choose the
|
||||
/// target, so it would mint with nothing locked behind it.
|
||||
#[test]
|
||||
fn a_route_authorizes_one_pair_and_does_not_compose() {
|
||||
let lock = program(1);
|
||||
let wrapped_token = program(2);
|
||||
let ping_sender = program(3);
|
||||
let ping_receiver = program(4);
|
||||
|
||||
let mut allowed_routes = BTreeMap::new();
|
||||
allowed_routes.insert(
|
||||
zone(9),
|
||||
vec![
|
||||
CrossZoneRoute {
|
||||
src_program_id: lock,
|
||||
target_program_id: wrapped_token,
|
||||
},
|
||||
CrossZoneRoute {
|
||||
src_program_id: ping_sender,
|
||||
target_program_id: ping_receiver,
|
||||
},
|
||||
],
|
||||
);
|
||||
let config = InboxConfig {
|
||||
self_zone: zone(1),
|
||||
allowed_routes,
|
||||
};
|
||||
|
||||
assert!(config.permits(&zone(9), lock, wrapped_token));
|
||||
assert!(config.permits(&zone(9), ping_sender, ping_receiver));
|
||||
|
||||
assert!(
|
||||
!config.permits(&zone(9), ping_sender, wrapped_token),
|
||||
"an emitter whose caller picks the target must not reach the bridge's target"
|
||||
);
|
||||
assert!(
|
||||
!config.permits(&zone(9), lock, ping_receiver),
|
||||
"a route grants its own target, not every target the peer has"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_peer_with_no_routes_may_deliver_nothing() {
|
||||
let config = InboxConfig {
|
||||
self_zone: zone(1),
|
||||
allowed_routes: BTreeMap::new(),
|
||||
};
|
||||
assert!(!config.permits(&zone(9), program(1), program(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_key_is_stable_and_content_addressed() {
|
||||
assert_eq!(message_key(&zone(1), 7, 3), message_key(&zone(1), 7, 3));
|
||||
|
||||
@@ -95,15 +95,6 @@ fn dispatch(
|
||||
msg.src_zone != cfg.self_zone,
|
||||
"Source zone must not be this zone"
|
||||
);
|
||||
// Checked as a pair. The emitting program is as much a part of the
|
||||
// authorization as the target: an emitter whose caller chooses the target
|
||||
// reaches everything the peer may reach, so a target allowlist on its own
|
||||
// lets any such emitter stand in for every other one.
|
||||
assert!(
|
||||
cfg.permits(&msg.src_zone, msg.src_program_id, msg.target_program_id),
|
||||
"No route from this source program to this target program for this peer"
|
||||
);
|
||||
|
||||
let mut shard =
|
||||
SeenShard::from_bytes(&seen.account.data.clone().into_inner()).expect("seen shard decodes");
|
||||
|
||||
@@ -177,8 +168,7 @@ fn dispatch(
|
||||
.write();
|
||||
}
|
||||
|
||||
/// Writes the inbox config (peer + target allowlists) into the config PDA exactly
|
||||
/// once at genesis.
|
||||
/// Writes the inbox config into the config PDA exactly once at genesis.
|
||||
fn init_config(
|
||||
self_program_id: ProgramId,
|
||||
caller_program_id: Option<ProgramId>,
|
||||
@@ -195,9 +185,8 @@ fn init_config(
|
||||
"account must be the inbox config PDA"
|
||||
);
|
||||
// Init-once, idempotent under genesis replay: a `default` config is a first
|
||||
// init; an already-owned config must already hold exactly these allowlists (the
|
||||
// genesis block is replayed onto seeded state during multi-sequencer
|
||||
// reconstruction), otherwise reject a post-genesis attempt to change them.
|
||||
// init; an already-owned config must already hold exactly this, since genesis
|
||||
// is replayed onto seeded state during multi-sequencer reconstruction.
|
||||
// `new_claimed_if_default` alone would not stop the owning program from
|
||||
// rewriting its own config data on a later call.
|
||||
if config_meta.account != Account::default() {
|
||||
@@ -208,7 +197,7 @@ fn init_config(
|
||||
assert_eq!(
|
||||
config_meta.account.data.clone().into_inner(),
|
||||
config.to_bytes(),
|
||||
"inbox config already initialized with different allowlists"
|
||||
"inbox config already initialized differently"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use common::{HashType, block::Block, transaction::LeeTransaction};
|
||||
use cross_zone::{EmissionSource, build_dispatch_from_emission, extract_emission};
|
||||
use cross_zone_inbox_core::{CrossZoneRoute, message_key, routes_permit};
|
||||
use cross_zone::{
|
||||
EmissionSource, build_dispatch_from_emission, extract_emission, is_sequencer_only_program,
|
||||
};
|
||||
use cross_zone_inbox_core::message_key;
|
||||
use futures::{Stream, StreamExt as _};
|
||||
use lee::{GENESIS_BLOCK_ID, PublicKey};
|
||||
use log::{debug, error, info, warn};
|
||||
@@ -36,7 +38,6 @@ const STUCK_SLOT_ALERT_PASSES: u32 = 20;
|
||||
struct PeerContext {
|
||||
peer_zone: [u8; 32],
|
||||
self_zone: [u8; 32],
|
||||
allowed_routes: Vec<CrossZoneRoute>,
|
||||
expected_pubkey: Option<PublicKey>,
|
||||
}
|
||||
|
||||
@@ -280,7 +281,6 @@ pub fn spawn_watchers(
|
||||
PeerContext {
|
||||
peer_zone: peer.channel_id,
|
||||
self_zone,
|
||||
allowed_routes: peer.allowed_routes,
|
||||
expected_pubkey,
|
||||
},
|
||||
poll_interval,
|
||||
@@ -556,7 +556,6 @@ fn record_block_deliveries(
|
||||
) -> bool {
|
||||
let peer_zone = peer.peer_zone;
|
||||
let self_zone = peer.self_zone;
|
||||
let allowed_routes = peer.allowed_routes.as_slice();
|
||||
// Collected and written once. The pending list is a single value, so a write
|
||||
// per delivery would rewrite the whole list once per message, which is
|
||||
// quadratic in a peer block that carries many of them, on a task holding the
|
||||
@@ -574,16 +573,15 @@ fn record_block_deliveries(
|
||||
if emission.target_zone != self_zone {
|
||||
continue;
|
||||
}
|
||||
// Mirrors the inbox guest, which is the authority. Dropping here keeps
|
||||
// an unroutable message from becoming a record that production would
|
||||
// feed in and give up on three blocks later.
|
||||
if !routes_permit(
|
||||
allowed_routes,
|
||||
message.program_id,
|
||||
emission.target_program_id,
|
||||
) {
|
||||
// Targets authorize their own sources now, so this is not authorization,
|
||||
// it is hygiene: a delivery the zone will certainly refuse still costs a
|
||||
// pending-list slot and three execution attempts before it is dead
|
||||
// lettered. Kept host-side only, never in `extract_emission` or the
|
||||
// verifier's re-derivation, where a check that depends on this build would
|
||||
// make the two disagree and halt ingestion.
|
||||
if is_sequencer_only_program(emission.target_program_id) {
|
||||
warn!(
|
||||
"Watcher dropping message from peer {}: no route from that source program to that target",
|
||||
"Watcher dropping message from peer {}: a peer may not dispatch into a sequencer-only program",
|
||||
hex::encode(peer_zone)
|
||||
);
|
||||
continue;
|
||||
@@ -680,10 +678,6 @@ mod tests {
|
||||
PeerContext {
|
||||
peer_zone: PEER_ZONE,
|
||||
self_zone: SELF_ZONE,
|
||||
allowed_routes: vec![CrossZoneRoute {
|
||||
src_program_id: programs::ping_sender().id(),
|
||||
target_program_id: programs::ping_receiver().id(),
|
||||
}],
|
||||
expected_pubkey: None,
|
||||
}
|
||||
}
|
||||
@@ -1082,13 +1076,49 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_delivery_with_no_route_is_never_recorded() {
|
||||
// The peer is routed to ping_receiver only. A bridging zone would also
|
||||
// route its lock program to wrapped_token, and `ping_sender` lets its
|
||||
// caller name wrapped_token as the target, so without the pair check
|
||||
// this emission would be recorded and delivered, minting with nothing
|
||||
// locked behind it. The guest rejects it too; dropping here keeps it
|
||||
// from becoming a record production feeds in and gives up on.
|
||||
async fn a_delivery_into_a_sequencer_only_program_is_never_recorded() {
|
||||
// Targets authorize their own sources, so the watcher no longer decides
|
||||
// who may reach what. It still refuses to queue a delivery the zone will
|
||||
// certainly refuse: the inbox is injected by this node alone, so a peer
|
||||
// naming it as a target is junk that would cost a pending slot and three
|
||||
// execution attempts.
|
||||
let (_dir, dbio) = store();
|
||||
let mut cursor = None;
|
||||
let mut tip = None;
|
||||
|
||||
let outcome = consume_peer_stream(
|
||||
stream::iter(vec![peer_block_msg_to(
|
||||
1,
|
||||
0,
|
||||
programs::cross_zone_inbox().id(),
|
||||
)]),
|
||||
&peer_context(),
|
||||
&dbio,
|
||||
&mut cursor,
|
||||
&mut tip,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
outcome,
|
||||
PassOutcome::Drained,
|
||||
"a message the watcher drops is not a failure"
|
||||
);
|
||||
assert!(
|
||||
recorded_keys(&dbio).is_empty(),
|
||||
"a message aimed at a sequencer-only program must not be recorded"
|
||||
);
|
||||
assert_eq!(
|
||||
get_cross_zone_peer_floor(&dbio, PEER_ZONE).unwrap(),
|
||||
Some(Slot::from(0)),
|
||||
"the slot was fully read, so the floor still advances"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_delivery_to_an_unrelated_target_is_still_recorded() {
|
||||
// The watcher is not the authorization point any more. A target it knows
|
||||
// nothing about is recorded and delivered, and that target decides.
|
||||
let (_dir, dbio) = store();
|
||||
let mut cursor = None;
|
||||
let mut tip = None;
|
||||
@@ -1106,19 +1136,11 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(outcome, PassOutcome::Drained);
|
||||
assert_eq!(
|
||||
outcome,
|
||||
PassOutcome::Drained,
|
||||
"an unroutable message is not a failure"
|
||||
);
|
||||
assert!(
|
||||
recorded_keys(&dbio).is_empty(),
|
||||
"a message with no route must not be recorded"
|
||||
);
|
||||
assert_eq!(
|
||||
get_cross_zone_peer_floor(&dbio, PEER_ZONE).unwrap(),
|
||||
Some(Slot::from(0)),
|
||||
"the slot was fully read, so the floor still advances"
|
||||
recorded_keys(&dbio).len(),
|
||||
1,
|
||||
"the watcher records it and lets the target refuse it"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1537,9 +1537,9 @@ fn build_genesis_state(config: &SequencerConfig) -> (lee::V03State, Vec<LeeTrans
|
||||
config.cross_zone.as_ref(),
|
||||
));
|
||||
let bridge_lock_config_tx = std::iter::once(cross_zone::build_bridge_lock_init_config_tx());
|
||||
let inbox_config_tx = config.cross_zone.as_ref().map(|cross_zone| {
|
||||
let inbox_config_tx = config.cross_zone.as_ref().map(|_| {
|
||||
let self_zone = *config.bedrock_config.channel_id.as_ref();
|
||||
cross_zone::build_inbox_init_config_tx(self_zone, cross_zone)
|
||||
cross_zone::build_inbox_init_config_tx(self_zone)
|
||||
});
|
||||
let supply_txs = config.genesis.iter().filter_map(|action| match action {
|
||||
GenesisAction::SupplyAccount {
|
||||
|
||||
Reference in New Issue
Block a user