mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-06-29 18:39:30 +00:00
test(cross-zone): add two-zone ping round-trip integration test
This commit is contained in:
parent
77cfff5256
commit
b83a3c7556
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -3986,6 +3986,7 @@ dependencies = [
|
||||
"bridge_core",
|
||||
"bytesize",
|
||||
"common",
|
||||
"cross_zone_outbox_core",
|
||||
"faucet_core",
|
||||
"futures",
|
||||
"hex",
|
||||
@ -8865,6 +8866,7 @@ dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
"common",
|
||||
"cross_zone_inbox_core",
|
||||
"faucet_core",
|
||||
"futures",
|
||||
"hex",
|
||||
@ -8878,6 +8880,7 @@ dependencies = [
|
||||
"logos-blockchain-zone-sdk",
|
||||
"mempool",
|
||||
"num-bigint 0.4.6",
|
||||
"ping_core",
|
||||
"rand 0.8.6",
|
||||
"risc0-zkvm",
|
||||
"serde",
|
||||
|
||||
@ -23,6 +23,9 @@ ata_core.workspace = true
|
||||
vault_core.workspace = true
|
||||
faucet_core.workspace = true
|
||||
bridge_core.workspace = true
|
||||
ping_core.workspace = true
|
||||
cross_zone_outbox_core.workspace = true
|
||||
risc0-zkvm.workspace = true
|
||||
indexer_service_rpc = { workspace = true, features = ["client"] }
|
||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||
wallet-ffi.workspace = true
|
||||
|
||||
138
integration_tests/tests/cross_zone_ping.rs
Normal file
138
integration_tests/tests/cross_zone_ping.rs
Normal file
@ -0,0 +1,138 @@
|
||||
#![expect(
|
||||
clippy::tests_outside_test_module,
|
||||
clippy::arithmetic_side_effects,
|
||||
reason = "We don't care about these in tests"
|
||||
)]
|
||||
|
||||
//! End-to-end cross-zone round trip: a ping submitted on zone A is delivered by
|
||||
//! zone B's watcher to `ping_receiver` on zone B, which records the payload.
|
||||
//!
|
||||
//! Two sequencers share one Bedrock node (no indexers): zone A publishes the
|
||||
//! ping to Bedrock, zone B's watcher reads zone A's finalized blocks, injects the
|
||||
//! inbox dispatch, and zone B's sequencer delivers it. This is the M3 milestone,
|
||||
//! sequencer-trusted, with no indexer re-derivation (that is M4).
|
||||
|
||||
use std::{net::SocketAddr, time::Duration};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use common::transaction::LeeTransaction;
|
||||
use cross_zone_outbox_core::outbox_pda;
|
||||
use integration_tests::{
|
||||
config::{self, SequencerPartialConfig},
|
||||
setup::{setup_bedrock_node, setup_sequencer},
|
||||
};
|
||||
use lee::{AccountId, PublicTransaction, program::Program, public_transaction::Message};
|
||||
use lee_core::program::ProgramId;
|
||||
use ping_core::{ReceiverInstruction, SenderInstruction, ping_record_pda};
|
||||
use sequencer_core::config::{CrossZoneConfig, CrossZonePeer};
|
||||
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
|
||||
use tokio::test;
|
||||
|
||||
const DELIVERY_TIMEOUT: Duration = Duration::from_secs(480);
|
||||
const PING_PAYLOAD: &[u8] = b"hello-cross-zone";
|
||||
|
||||
#[test]
|
||||
async fn ping_crosses_from_zone_a_to_zone_b() -> Result<()> {
|
||||
// Declared first so it outlives both zones (drops run in reverse order).
|
||||
let (_bedrock, bedrock_addr) = setup_bedrock_node()
|
||||
.await
|
||||
.context("Failed to set up shared Bedrock node")?;
|
||||
|
||||
let partial = SequencerPartialConfig::default();
|
||||
let channel_a = config::bedrock_channel_id();
|
||||
let channel_b = config::bedrock_channel_id_b();
|
||||
let zone_a: [u8; 32] = *channel_a.as_ref();
|
||||
let zone_b: [u8; 32] = *channel_b.as_ref();
|
||||
|
||||
let receiver_id = Program::ping_receiver().id();
|
||||
|
||||
// Zone B watches zone A and allows delivery only to ping_receiver.
|
||||
let cross_zone = CrossZoneConfig {
|
||||
peers: vec![CrossZonePeer {
|
||||
channel_id: zone_a,
|
||||
allowed_targets: vec![receiver_id],
|
||||
}],
|
||||
};
|
||||
|
||||
let (seq_a, _seq_a_home) = setup_sequencer(partial, bedrock_addr, vec![], channel_a, None)
|
||||
.await
|
||||
.context("Failed to set up zone A sequencer")?;
|
||||
let (seq_b, _seq_b_home) =
|
||||
setup_sequencer(partial, bedrock_addr, vec![], channel_b, Some(cross_zone))
|
||||
.await
|
||||
.context("Failed to set up zone B sequencer")?;
|
||||
|
||||
// Submit the ping on zone A, addressed to ping_receiver on zone B.
|
||||
let ping = build_ping_tx(zone_b, receiver_id);
|
||||
sequencer_client(seq_a.addr())?
|
||||
.send_transaction(ping)
|
||||
.await
|
||||
.context("Failed to submit ping on zone A")?;
|
||||
|
||||
// Wait until zone B's sequencer records the delivered payload.
|
||||
let record_id = ping_record_pda(receiver_id);
|
||||
let delivered = wait_for_delivery(sequencer_client(seq_b.addr())?, record_id).await?;
|
||||
|
||||
assert_eq!(
|
||||
delivered, PING_PAYLOAD,
|
||||
"Zone B must record the payload delivered from zone A"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds a top-level ping_sender transaction that chains into the outbox to emit
|
||||
/// a message carrying a `ping_receiver::Record` instruction for the target zone.
|
||||
fn build_ping_tx(target_zone: [u8; 32], receiver_id: ProgramId) -> LeeTransaction {
|
||||
let outbox_id = Program::cross_zone_outbox().id();
|
||||
let ordinal = 0;
|
||||
|
||||
// The payload is the ping_receiver instruction, serialized as risc0 words in
|
||||
// little-endian bytes (the contract the inbox reverses when forwarding).
|
||||
let words = risc0_zkvm::serde::to_vec(&ReceiverInstruction::Record {
|
||||
payload: PING_PAYLOAD.to_vec(),
|
||||
})
|
||||
.expect("serialize ping instruction");
|
||||
let payload: Vec<u8> = words.iter().flat_map(|word| word.to_le_bytes()).collect();
|
||||
|
||||
let send = SenderInstruction::Send {
|
||||
outbox_program_id: outbox_id,
|
||||
target_zone,
|
||||
target_program_id: receiver_id,
|
||||
target_accounts: vec![ping_record_pda(receiver_id).into_value()],
|
||||
payload,
|
||||
ordinal,
|
||||
};
|
||||
|
||||
let outbox_account = outbox_pda(outbox_id, &target_zone, ordinal);
|
||||
let message = Message::try_new(Program::ping_sender().id(), vec![outbox_account], vec![], send)
|
||||
.expect("build ping message");
|
||||
LeeTransaction::Public(PublicTransaction::new(
|
||||
message,
|
||||
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
||||
))
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
/// Polls zone B's sequencer until the ping record PDA holds a payload.
|
||||
async fn wait_for_delivery(client: SequencerClient, record_id: AccountId) -> Result<Vec<u8>> {
|
||||
let wait = async {
|
||||
loop {
|
||||
let account = client.get_account(record_id).await?;
|
||||
let data = account.data.into_inner();
|
||||
if !data.is_empty() {
|
||||
return Ok::<Vec<u8>, anyhow::Error>(data);
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(3)).await;
|
||||
}
|
||||
};
|
||||
tokio::time::timeout(DELIVERY_TIMEOUT, wait)
|
||||
.await
|
||||
.context("Zone B did not record the cross-zone payload in time")?
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user