logos-execution-zone/integration_tests/tests/cross_zone_ingress_guard.rs
moudyellaz a16e22c46c refactor!(cross-zone): deploy programs at genesis instead of builtins
Cross-zone builtin programs are no longer registered in the production
genesis. A zone that participates declares the ones it uses via a new
GenesisAction::DeployProgram (sequencer) and a matching deploy_programs
list (indexer), both resolved through CrossZoneProgram and registered
with with_programs. Cross-zone genesis accounts (inbox config,
wrapped-token config) are seeded through the state constructor for a
receiving zone, and bridge-lock holdings are seeded from their actions
regardless of receiving config, dropping V03State::insert_genesis_account.
GenesisAction amounts now use the Balance alias. Documents cross_zone as
the reference LEZ adapter and the bridge demo as not production-safe.

The sequencer's DeployProgram set and the indexer's deploy_programs are
configured separately, so both nodes now log a deterministic genesis
fingerprint (V03State::genesis_fingerprint) at startup: equal values
confirm the two genesis states agree, a mismatch flags a divergent
deploy set.

BREAKING CHANGE: the genesis state root changes (cross-zone builtins are
out of production genesis) and the sequencer/indexer configs gain the
DeployProgram / deploy_programs list that cross-zone-participating zones
must set.
2026-07-13 19:12:24 +02:00

87 lines
2.9 KiB
Rust

#![expect(
clippy::tests_outside_test_module,
reason = "We don't care about these in tests"
)]
//! M6 ingress guard: the cross-zone inbox is sequencer-only. Only the watcher
//! injects inbox dispatches; a user must not be able to invoke the inbox through
//! the public RPC, or anyone could forge an inbound cross-zone delivery. The
//! inbox guest's caller-is-none assertion passes for a top-level user tx, so the
//! sequencer ingress guard is the only thing that stops this.
use std::net::SocketAddr;
use anyhow::{Context as _, Result};
use common::transaction::LeeTransaction;
use cross_zone_inbox_core::{
CrossZoneMessage, Instruction, inbox_config_account_id, inbox_seen_shard_account_id,
};
use integration_tests::{
config::{self, SequencerPartialConfig},
setup::{setup_bedrock_node, setup_sequencer},
};
use lee::{
PublicTransaction,
public_transaction::{Message, WitnessSet},
};
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
use tokio::test;
#[test]
async fn user_origin_inbox_call_rejected() -> Result<()> {
let (_bedrock, bedrock_addr) = setup_bedrock_node()
.await
.context("Failed to set up Bedrock node")?;
let partial = SequencerPartialConfig::default();
let channel = config::bedrock_channel_id();
let (seq, _seq_home) = setup_sequencer(
partial,
bedrock_addr,
config::cross_zone_deploy_actions(),
channel,
None,
)
.await
.context("Failed to set up sequencer")?;
// A user hand-builds a top-level inbox Dispatch and submits it via RPC.
let inbox_id = programs::cross_zone_inbox().id();
let msg = CrossZoneMessage {
src_zone: [2; 32],
src_block_id: 1,
src_tx_index: 0,
src_program_id: [9; 8],
target_program_id: programs::ping_receiver().id(),
payload: vec![],
l1_inclusion_witness: None,
};
let seen_id = inbox_seen_shard_account_id(inbox_id, &msg.src_zone, msg.src_block_id);
let message = Message::try_new(
inbox_id,
vec![inbox_config_account_id(inbox_id), seen_id],
vec![],
Instruction::Dispatch(msg),
)
.expect("build dispatch message");
let tx = LeeTransaction::Public(PublicTransaction::new(
message,
WitnessSet::from_raw_parts(vec![]),
));
let result = sequencer_client(seq.addr())?.send_transaction(tx).await;
let err = result.expect_err("the sequencer must reject a user-origin inbox call");
assert!(
err.to_string().contains("sequencer-only"),
"rejection should cite the sequencer-only guard, got: {err}"
);
Ok(())
}
fn sequencer_client(addr: SocketAddr) -> Result<SequencerClient> {
let url = config::addr_to_url(config::UrlProtocol::Http, addr)
.context("Failed to build sequencer URL")?;
SequencerClientBuilder::default()
.build(url)
.context("Failed to build sequencer client")
}