mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-07-24 16:43:15 +00:00
221 lines
7.5 KiB
Rust
221 lines
7.5 KiB
Rust
//! Host-side cross-zone helpers that need program ids (`programs`) or the state
|
|
//! machine (`lee`), kept out of the guest-pure cores. Mirrors `system_accounts`:
|
|
//! it resolves builtin program ids and bakes them into transactions and genesis
|
|
//! accounts for the watcher (sequencer) and verifier (indexer).
|
|
//!
|
|
//! This crate is the reference LEZ-to-LEZ adapter: it re-derives each delivery
|
|
//! byte-for-byte from a peer LEZ zone's finalized blocks, valid only because the
|
|
//! peer runs identical LEZ code. A non-LEZ peer needs a separate adapter with its
|
|
//! 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,
|
|
inbox_seen_shard_account_id,
|
|
};
|
|
use lee_core::{
|
|
account::{Account, AccountId, Balance},
|
|
program::ProgramId,
|
|
};
|
|
use serde::Serialize;
|
|
|
|
/// The cross-zone emission fields a watcher or verifier reads off a source
|
|
/// transaction, common to every emitter program.
|
|
pub struct Emission {
|
|
pub target_zone: ZoneId,
|
|
pub target_program_id: ProgramId,
|
|
pub target_accounts: Vec<[u8; 32]>,
|
|
pub payload: Vec<u8>,
|
|
}
|
|
|
|
/// Whether a program may only be invoked by sequencer-origin transactions.
|
|
///
|
|
/// The cross-zone inbox is injected solely by the watcher; a user-submitted call
|
|
/// must be rejected at ingress, since `TransactionOrigin` is not carried in the
|
|
/// block.
|
|
#[must_use]
|
|
pub fn is_sequencer_only_program(program_id: ProgramId) -> bool {
|
|
program_id == programs::cross_zone_inbox().id()
|
|
}
|
|
|
|
/// Extracts the cross-zone emission from a source transaction.
|
|
///
|
|
/// Recognizes the known emitter programs (`ping_sender`, `bridge_lock`). The
|
|
/// watcher and verifier both use this so they agree on what a given source tx
|
|
/// emits.
|
|
#[must_use]
|
|
pub fn extract_emission(program_id: ProgramId, instruction_data: &[u32]) -> Option<Emission> {
|
|
if program_id == programs::ping_sender().id() {
|
|
let ping_core::SenderInstruction::Send {
|
|
target_zone,
|
|
target_program_id,
|
|
target_accounts,
|
|
payload,
|
|
..
|
|
} = risc0_zkvm::serde::from_slice(instruction_data).ok()?;
|
|
Some(Emission {
|
|
target_zone,
|
|
target_program_id,
|
|
target_accounts,
|
|
payload,
|
|
})
|
|
} else if program_id == programs::bridge_lock().id() {
|
|
let bridge_lock_core::Instruction::Lock {
|
|
target_zone,
|
|
target_program_id,
|
|
target_accounts,
|
|
payload,
|
|
..
|
|
} = risc0_zkvm::serde::from_slice(instruction_data).ok()?;
|
|
Some(Emission {
|
|
target_zone,
|
|
target_program_id,
|
|
target_accounts,
|
|
payload,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Builds the sequencer-origin dispatch transaction. Pure for fixed inputs, so
|
|
/// the watcher's injected tx and the indexer's re-derived tx are byte-identical.
|
|
fn build_inbox_dispatch_tx(
|
|
inbox_id: ProgramId,
|
|
msg: &CrossZoneMessage,
|
|
target_account_ids: Vec<AccountId>,
|
|
) -> lee::PublicTransaction {
|
|
let mut account_ids = Vec::with_capacity(target_account_ids.len().saturating_add(2));
|
|
account_ids.push(inbox_config_account_id(inbox_id));
|
|
account_ids.push(inbox_seen_shard_account_id(
|
|
inbox_id,
|
|
&msg.src_zone,
|
|
msg.src_block_id,
|
|
));
|
|
account_ids.extend(target_account_ids);
|
|
|
|
let message = lee::public_transaction::Message::try_new(
|
|
inbox_id,
|
|
account_ids,
|
|
vec![],
|
|
Instruction::Dispatch(msg.clone()),
|
|
)
|
|
.expect("inbox dispatch instruction must serialize");
|
|
|
|
lee::PublicTransaction::new(
|
|
message,
|
|
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
|
)
|
|
}
|
|
|
|
/// Builds the dispatch transaction for one peer emission.
|
|
///
|
|
/// Both the sequencer's watcher and the indexer's verifier go through this so
|
|
/// their transactions are byte-identical for the same emission (the basis of the
|
|
/// Option B check).
|
|
#[must_use]
|
|
pub fn build_dispatch_from_emission(
|
|
src_zone: ZoneId,
|
|
src_block_id: u64,
|
|
src_tx_index: u32,
|
|
src_program_id: ProgramId,
|
|
target_program_id: ProgramId,
|
|
target_accounts: &[[u8; 32]],
|
|
payload: Vec<u8>,
|
|
) -> lee::PublicTransaction {
|
|
let msg = CrossZoneMessage {
|
|
src_zone,
|
|
src_block_id,
|
|
src_tx_index,
|
|
src_program_id,
|
|
target_program_id,
|
|
payload,
|
|
l1_inclusion_witness: None,
|
|
};
|
|
let target_ids = target_accounts
|
|
.iter()
|
|
.copied()
|
|
.map(AccountId::new)
|
|
.collect();
|
|
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 target
|
|
/// allowlists plus its own zone id.
|
|
fn inbox_config(self_zone: ZoneId, cross_zone: &CrossZoneConfig) -> InboxConfig {
|
|
let mut allowed_targets = BTreeMap::new();
|
|
for peer in &cross_zone.peers {
|
|
allowed_targets.insert(peer.channel_id, peer.allowed_targets.clone());
|
|
}
|
|
InboxConfig {
|
|
self_zone,
|
|
allowed_peers: BTreeMap::new(),
|
|
allowed_targets,
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
#[must_use]
|
|
pub fn build_inbox_init_config_tx(
|
|
self_zone: ZoneId,
|
|
cross_zone: &CrossZoneConfig,
|
|
) -> 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)),
|
|
)
|
|
}
|
|
|
|
/// Builds the genesis holding account funding a holder's bridgeable balance.
|
|
///
|
|
/// A real native balance owned by `bridge_lock`, which can debit it on a lock; it
|
|
/// is conserved like any other balance. Not produced by any transaction, so the
|
|
/// sequencer and indexer both seed it through this one builder.
|
|
#[must_use]
|
|
pub fn build_holding_account(holder: AccountId, amount: Balance) -> (AccountId, Account) {
|
|
let account = Account {
|
|
program_owner: programs::bridge_lock().id(),
|
|
balance: amount,
|
|
..Default::default()
|
|
};
|
|
(holder, account)
|
|
}
|
|
|
|
/// The genesis transaction that pins the cross-zone inbox as the wrapped-token
|
|
/// minter, without importing the inbox id into the guest.
|
|
#[must_use]
|
|
pub fn build_wrapped_token_init_config_tx() -> lee::PublicTransaction {
|
|
let wrapped_token_id = programs::wrapped_token().id();
|
|
genesis_public_tx(
|
|
wrapped_token_id,
|
|
vec![wrapped_token_core::config_account_id(wrapped_token_id)],
|
|
wrapped_token_core::Instruction::InitConfig {
|
|
minter: programs::cross_zone_inbox().id(),
|
|
},
|
|
)
|
|
}
|
|
|
|
/// Builds an unsigned, sequencer-origin genesis transaction invoking `instruction`
|
|
/// on `program_id` over `account_ids`.
|
|
fn genesis_public_tx<I: Serialize>(
|
|
program_id: ProgramId,
|
|
account_ids: Vec<AccountId>,
|
|
instruction: I,
|
|
) -> lee::PublicTransaction {
|
|
let message =
|
|
lee::public_transaction::Message::try_new(program_id, account_ids, vec![], instruction)
|
|
.expect("genesis instruction must serialize");
|
|
lee::PublicTransaction::new(
|
|
message,
|
|
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
|
)
|
|
}
|