mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-26 20:01:16 +00:00
refactor(lee): change programs shape (#720)
* feat(lee): store deployed programs as Account-shaped state, keyed by AccountId Program-as-Account migration, first slice: V03State.programs becomes HashMap<AccountId, Account> instead of HashMap<ProgramId, Program>, with the elf held directly in Account.data. The map key is derived from ProgramId via a new 1:1 From<ProgramId> for AccountId conversion (both types are exactly 32 bytes) rather than a hash, since ProgramId is already content-derived from the elf. Account.program_owner stays ProgramId-typed everywhere - this only changes how deployed programs are stored and looked up host-side, not the dispatch/authorization model any guest program logic depends on. Dispatch resolves a ChainedCall's program_id by converting to AccountId, fetching the Account, and reconstructing a Program via new_unchecked for execution. DATA_MAX_LENGTH is raised from 100 KiB to 700 KiB to fit real program elfs (observed 375 KB-631 KB) directly in Account.data; noted in its docstring as a rough placeholder pending real transaction/block-size budget analysis. * fix(lee): store deployed programs as Account-shaped state, correct SeenShard cap Corrects lee/state_machine internals for the Program-as-Account migration and fixes SeenShard::MAX_DELIVERIES, which was still calibrated for the old 100 KiB DATA_MAX_LENGTH instead of the current 700 KiB cap. Rebuilds program artifacts and the sequencer test fixture to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * address PR #720 review nits - Use FIXME instead of TODO for the temporary ProgramId->AccountId conversion, per review convention for patches guaranteed to be fixed later. - Derive cross_zone_inbox's MAX_DELIVERIES from DATA_MAX_LENGTH instead of a hand-recomputed literal, so it stays in sync automatically the next time the cap changes. * chore: regenerate artifacts after rebasing onto dev Binary program artifacts and the prebuilt sequencer DB dump were left as rebase-conflict placeholders; regenerated via `just build-artifacts` against the fully rebased source. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3a718c32f5
commit
d52c76e2b5
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,9 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use bytesize::ByteSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const DATA_MAX_LENGTH: ByteSize = ByteSize::kib(100);
|
||||
/// TODO: Temporarily raised cap to 700 KiB from 100 KiB. This is a placeholder
|
||||
/// until multiple accounts are used to store the entire elf.
|
||||
pub const DATA_MAX_LENGTH: ByteSize = ByteSize::kib(700);
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)]
|
||||
pub struct Data(Vec<u8>);
|
||||
|
||||
@@ -14,6 +14,19 @@ pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
|
||||
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
|
||||
|
||||
pub type ProgramId = [u32; 8];
|
||||
|
||||
/// FIXME: This is a temporary conversion; will be removed once `Program` to `Account`
|
||||
/// migration is complete.
|
||||
impl From<ProgramId> for AccountId {
|
||||
fn from(program_id: ProgramId) -> Self {
|
||||
let bytes: Vec<u8> = program_id
|
||||
.iter()
|
||||
.flat_map(|word| word.to_le_bytes())
|
||||
.collect();
|
||||
Self::new(bytes.try_into().expect("8 u32 words are exactly 32 bytes"))
|
||||
}
|
||||
}
|
||||
|
||||
pub type InstructionData = Vec<u32>;
|
||||
pub struct ProgramInput<T> {
|
||||
pub self_program_id: ProgramId,
|
||||
|
||||
@@ -4,8 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use lee_core::{
|
||||
BlockId, Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
|
||||
Timestamp,
|
||||
account::{Account, AccountId},
|
||||
program::ProgramId,
|
||||
account::{Account, AccountId, Data},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -114,7 +113,7 @@ impl BorshDeserialize for NullifierSet {
|
||||
pub struct V03State {
|
||||
public_state: HashMap<AccountId, Account>,
|
||||
private_state: (CommitmentSet, NullifierSet),
|
||||
programs: HashMap<ProgramId, Program>,
|
||||
programs: HashMap<AccountId, Account>,
|
||||
}
|
||||
|
||||
impl Default for V03State {
|
||||
@@ -190,13 +189,19 @@ impl V03State {
|
||||
#[must_use]
|
||||
pub fn with_programs(mut self, programs: impl IntoIterator<Item = Program>) -> Self {
|
||||
for program in programs {
|
||||
self.insert_program(program);
|
||||
self.insert_program(&program);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn insert_program(&mut self, program: Program) {
|
||||
self.programs.insert(program.id(), program);
|
||||
pub(crate) fn insert_program(&mut self, program: &Program) {
|
||||
let account_id = AccountId::from(program.id());
|
||||
let account = Account {
|
||||
data: Data::try_from(program.elf().to_vec())
|
||||
.expect("elf must fit under DATA_MAX_LENGTH"),
|
||||
..Account::default()
|
||||
};
|
||||
self.programs.insert(account_id, account);
|
||||
}
|
||||
|
||||
pub fn apply_state_diff(&mut self, diff: ValidatedStateDiff) {
|
||||
@@ -222,7 +227,7 @@ impl V03State {
|
||||
self.private_state.0.extend(&new_commitments);
|
||||
self.private_state.1.extend(&new_nullifiers);
|
||||
if let Some(program) = program {
|
||||
self.insert_program(program);
|
||||
self.insert_program(&program);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +286,7 @@ impl V03State {
|
||||
self.private_state.0.get_proof_for(commitment)
|
||||
}
|
||||
|
||||
pub(crate) const fn programs(&self) -> &HashMap<ProgramId, Program> {
|
||||
pub(crate) const fn programs(&self) -> &HashMap<AccountId, Account> {
|
||||
&self.programs
|
||||
}
|
||||
|
||||
@@ -314,11 +319,12 @@ impl V03State {
|
||||
let mut accounts: Vec<(&AccountId, &Account)> = public_state.iter().collect();
|
||||
accounts.sort_by(|a, b| a.0.as_ref().cmp(b.0.as_ref()));
|
||||
|
||||
let mut program_ids: Vec<ProgramId> = programs.keys().copied().collect();
|
||||
program_ids.sort_unstable();
|
||||
let mut program_accounts: Vec<(&AccountId, &Account)> = programs.iter().collect();
|
||||
program_accounts.sort_by(|a, b| a.0.as_ref().cmp(b.0.as_ref()));
|
||||
|
||||
let account_count = u64::try_from(accounts.len()).expect("account count fits in u64");
|
||||
let program_count = u64::try_from(program_ids.len()).expect("program count fits in u64");
|
||||
let program_count =
|
||||
u64::try_from(program_accounts.len()).expect("program count fits in u64");
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(account_count.to_le_bytes());
|
||||
@@ -330,10 +336,12 @@ impl V03State {
|
||||
hasher.update(&bytes);
|
||||
}
|
||||
hasher.update(program_count.to_le_bytes());
|
||||
for id in program_ids {
|
||||
for word in id {
|
||||
hasher.update(word.to_le_bytes());
|
||||
}
|
||||
for (id, account) in program_accounts {
|
||||
hasher.update(id.as_ref());
|
||||
let bytes = borsh::to_vec(account).expect("Account is BorshSerialize");
|
||||
let len = u64::try_from(bytes.len()).expect("program account encoding fits in u64");
|
||||
hasher.update(len.to_le_bytes());
|
||||
hasher.update(&bytes);
|
||||
}
|
||||
hasher.update(private_state.0.digest());
|
||||
|
||||
|
||||
@@ -67,11 +67,12 @@ fn insert_program() {
|
||||
let mut state = V03State::new();
|
||||
let program_to_insert = crate::test_methods::simple_balance_transfer();
|
||||
let program_id = program_to_insert.id();
|
||||
assert!(!state.programs.contains_key(&program_id));
|
||||
let account_id = lee_core::account::AccountId::from(program_id);
|
||||
assert!(!state.programs.contains_key(&account_id));
|
||||
|
||||
state.insert_program(program_to_insert);
|
||||
state.insert_program(&program_to_insert);
|
||||
|
||||
assert!(state.programs.contains_key(&program_id));
|
||||
assert!(state.programs.contains_key(&account_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -45,36 +45,36 @@ impl V03State {
|
||||
/// Include test programs in the builtin programs map.
|
||||
#[must_use]
|
||||
pub fn with_test_programs(mut self) -> Self {
|
||||
self.insert_program(crate::test_methods::simple_balance_transfer());
|
||||
self.insert_program(crate::test_methods::nonce_changer());
|
||||
self.insert_program(crate::test_methods::extra_output());
|
||||
self.insert_program(crate::test_methods::missing_output());
|
||||
self.insert_program(crate::test_methods::dropped_account());
|
||||
self.insert_program(crate::test_methods::program_owner_changer());
|
||||
self.insert_program(crate::test_methods::data_changer());
|
||||
self.insert_program(crate::test_methods::minter());
|
||||
self.insert_program(crate::test_methods::burner());
|
||||
self.insert_program(crate::test_methods::auth_asserting_noop());
|
||||
self.insert_program(crate::test_methods::private_pda_delegator());
|
||||
self.insert_program(crate::test_methods::pda_claimer());
|
||||
self.insert_program(crate::test_methods::two_pda_claimer());
|
||||
self.insert_program(crate::test_methods::noop());
|
||||
self.insert_program(crate::test_methods::chain_caller());
|
||||
self.insert_program(crate::test_methods::modified_transfer_program());
|
||||
self.insert_program(crate::test_methods::malicious_authorization_changer());
|
||||
self.insert_program(crate::test_methods::validity_window());
|
||||
self.insert_program(crate::test_methods::flash_swap_initiator());
|
||||
self.insert_program(crate::test_methods::flash_swap_callback());
|
||||
self.insert_program(crate::test_methods::malicious_self_program_id());
|
||||
self.insert_program(crate::test_methods::malicious_caller_program_id());
|
||||
self.insert_program(crate::test_methods::pda_spend_proxy());
|
||||
self.insert_program(crate::test_methods::claimer());
|
||||
self.insert_program(crate::test_methods::changer_claimer());
|
||||
self.insert_program(crate::test_methods::validity_window_chain_caller());
|
||||
self.insert_program(crate::test_methods::simple_transfer_proxy());
|
||||
self.insert_program(crate::test_methods::malicious_injector());
|
||||
self.insert_program(crate::test_methods::malicious_launderer());
|
||||
self.insert_program(crate::test_methods::modified_transfer_program());
|
||||
self.insert_program(&crate::test_methods::simple_balance_transfer());
|
||||
self.insert_program(&crate::test_methods::nonce_changer());
|
||||
self.insert_program(&crate::test_methods::extra_output());
|
||||
self.insert_program(&crate::test_methods::missing_output());
|
||||
self.insert_program(&crate::test_methods::dropped_account());
|
||||
self.insert_program(&crate::test_methods::program_owner_changer());
|
||||
self.insert_program(&crate::test_methods::data_changer());
|
||||
self.insert_program(&crate::test_methods::minter());
|
||||
self.insert_program(&crate::test_methods::burner());
|
||||
self.insert_program(&crate::test_methods::auth_asserting_noop());
|
||||
self.insert_program(&crate::test_methods::private_pda_delegator());
|
||||
self.insert_program(&crate::test_methods::pda_claimer());
|
||||
self.insert_program(&crate::test_methods::two_pda_claimer());
|
||||
self.insert_program(&crate::test_methods::noop());
|
||||
self.insert_program(&crate::test_methods::chain_caller());
|
||||
self.insert_program(&crate::test_methods::modified_transfer_program());
|
||||
self.insert_program(&crate::test_methods::malicious_authorization_changer());
|
||||
self.insert_program(&crate::test_methods::validity_window());
|
||||
self.insert_program(&crate::test_methods::flash_swap_initiator());
|
||||
self.insert_program(&crate::test_methods::flash_swap_callback());
|
||||
self.insert_program(&crate::test_methods::malicious_self_program_id());
|
||||
self.insert_program(&crate::test_methods::malicious_caller_program_id());
|
||||
self.insert_program(&crate::test_methods::pda_spend_proxy());
|
||||
self.insert_program(&crate::test_methods::claimer());
|
||||
self.insert_program(&crate::test_methods::changer_claimer());
|
||||
self.insert_program(&crate::test_methods::validity_window_chain_caller());
|
||||
self.insert_program(&crate::test_methods::simple_transfer_proxy());
|
||||
self.insert_program(&crate::test_methods::malicious_injector());
|
||||
self.insert_program(&crate::test_methods::malicious_launderer());
|
||||
self.insert_program(&crate::test_methods::modified_transfer_program());
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
hash::Hash,
|
||||
};
|
||||
@@ -111,10 +112,16 @@ impl ValidatedStateDiff {
|
||||
LeeError::MaxChainedCallsDepthExceeded
|
||||
);
|
||||
|
||||
// Check that the `program_id` corresponds to a deployed program
|
||||
let Some(program) = state.programs().get(&chained_call.program_id) else {
|
||||
let Some(program_account) = state
|
||||
.programs()
|
||||
.get(&AccountId::from(chained_call.program_id))
|
||||
else {
|
||||
return Err(LeeError::InvalidInput("Unknown program".into()));
|
||||
};
|
||||
let program = Program::new_unchecked(
|
||||
chained_call.program_id,
|
||||
Cow::Owned(program_account.data.to_vec()),
|
||||
);
|
||||
|
||||
debug!(
|
||||
"Program {:?} pre_states: {:?}, instruction_data: {:?}",
|
||||
@@ -441,7 +448,10 @@ impl ValidatedStateDiff {
|
||||
) -> Result<Self, LeeError> {
|
||||
// TODO: remove clone
|
||||
let program = Program::new(tx.message.bytecode.clone().into())?;
|
||||
if state.programs().contains_key(&program.id()) {
|
||||
if state
|
||||
.programs()
|
||||
.contains_key(&AccountId::from(program.id()))
|
||||
{
|
||||
return Err(LeeError::ProgramAlreadyExists);
|
||||
}
|
||||
Ok(Self(StateDiff {
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::BTreeSet;
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use lee_core::{
|
||||
account::AccountId,
|
||||
account::{AccountId, data::DATA_MAX_LENGTH},
|
||||
program::{PdaSeed, ProgramId},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -133,13 +133,25 @@ impl SeenShard {
|
||||
/// Deliveries one shard can hold before it exceeds `DATA_MAX_LENGTH`.
|
||||
///
|
||||
/// Borsh is 32 bytes of hash, a 4-byte count, then 4 bytes per index, so
|
||||
/// this is exactly the 100 KiB an account may carry.
|
||||
/// this is exactly the `DATA_MAX_LENGTH` an account may carry.
|
||||
///
|
||||
/// Out of reach only because of the L1 inscription cap: a block inscribes as
|
||||
/// one op near 1.75 MiB and a minimal emitting transaction is about 257
|
||||
/// bytes, capping a peer block near 7,100 deliveries. Raising that L1 cap
|
||||
/// past roughly 6.3 MiB puts this back in reach.
|
||||
pub const MAX_DELIVERIES: usize = 25_591;
|
||||
pub const MAX_DELIVERIES: usize = {
|
||||
let remaining_bytes = DATA_MAX_LENGTH.as_u64() - 36;
|
||||
let count = remaining_bytes
|
||||
.checked_div(4)
|
||||
.expect("division is well-defined");
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
clippy::cast_possible_truncation,
|
||||
reason = "usize::try_from is not yet const-stable; the value is tiny and always fits"
|
||||
)]
|
||||
let count = count as usize;
|
||||
count
|
||||
};
|
||||
|
||||
/// Decodes a shard from account data; empty data is an unclaimed shard.
|
||||
pub fn from_bytes(bytes: &[u8]) -> borsh::io::Result<Self> {
|
||||
@@ -300,8 +312,6 @@ pub fn inbox_source_marker_seed(src_zone: &ZoneId, src_program_id: ProgramId) ->
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lee_core::account::data::DATA_MAX_LENGTH;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn zone(b: u8) -> ZoneId {
|
||||
@@ -379,6 +389,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_full_shard_fits_in_account_data() {
|
||||
// Exact only because `DATA_MAX_LENGTH` is whole KiB, hence a multiple of 4.
|
||||
let mut shard = SeenShard::default();
|
||||
for index in 0..SeenShard::MAX_DELIVERIES {
|
||||
shard.insert([5; 32], u32::try_from(index).expect("index fits"));
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user