mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-26 11:51:13 +00:00
feat(lee): fold program storage into public_state, drop the separate programs map
V03State.programs is gone; deployed programs now live directly in public_state, keyed by AccountId::from(program_id) same as any other account. insert_program sets program_owner to a new reserved sentinel, PROGRAM_STORAGE_OWNER, instead of leaving it at the default. That ownership choice is load-bearing now in a way it wasn't before: once program accounts share the same map as everything else, they're reachable through ordinary dispatch, so program_owner determines whether they're claimable/writable. Left unclaimed, a program invocation could legitimately claim a program's storage account via the normal claim path and then rewrite its elf; self-ownership has the same flaw, since it authorizes exactly the program whose own invocation would touch its own storage account. The reserved sentinel makes every program account unwritable by construction, since no real chained_call.program_id will ever derive to it. programs() is removed; dispatch and the deployment-existence check go through get_account_by_id_ref like any other account lookup. genesis_fingerprint drops its separate program-hashing loop, since program accounts now fall out of the existing public_state loop. Rebuilt all guest artifacts and the test fixture via just build-artifacts as a precaution, since V03State's Borsh shape changed even though Account's did not.
This commit is contained in:
@@ -15,6 +15,25 @@ pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
|
||||
/// TODO: Placeholder `program_owner` for uninitialized `Account`.
|
||||
pub const DEFAULT_PROGRAM_OWNER: AccountId = AccountId::new([0; 32]);
|
||||
|
||||
/// Sentinel `AccountId` that owns every deployed program's storage account.
|
||||
///
|
||||
/// Now that program accounts live directly in `public_state` (Program-as-Account), a real
|
||||
/// owner value matters: `DEFAULT_PROGRAM_OWNER` would leave them unclaimed, letting an
|
||||
/// ordinary program invocation "claim" one via the normal claim path and then legitimately
|
||||
/// rewrite its `data` (the elf) as if it were the program's own account — corrupting deployed
|
||||
/// bytecode through completely ordinary dispatch. Self-ownership (`program_owner` equal to the
|
||||
/// account's own id) is just as broken: it authorizes exactly the program whose invocation
|
||||
/// would touch its own storage account.
|
||||
///
|
||||
/// This value is reserved instead — no real `ProgramId`'s image id will ever equal it (it's a
|
||||
/// fixed, distinctive bit pattern, not a hash, so collision with a real cryptographic digest is
|
||||
/// as implausible as with `DEFAULT_PROGRAM_ID`), and it is a sentinel `Program::execute`s never
|
||||
/// executes at, so no ordinary transaction can ever supply a `chained_call.program_id` whose
|
||||
/// derived owner matches it. That makes every program's storage account unwritable via normal
|
||||
/// dispatch, by construction, until a real loader program is designed and registered to hold
|
||||
/// this authority deliberately.
|
||||
pub const PROGRAM_STORAGE_OWNER: AccountId = AccountId::new([0xFF; 32]);
|
||||
|
||||
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
|
||||
|
||||
pub type ProgramId = [u32; 8];
|
||||
|
||||
@@ -5,6 +5,7 @@ use lee_core::{
|
||||
BlockId, Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
|
||||
Timestamp,
|
||||
account::{Account, AccountId, Data},
|
||||
program::PROGRAM_STORAGE_OWNER,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -111,9 +112,12 @@ impl BorshDeserialize for NullifierSet {
|
||||
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct V03State {
|
||||
/// Deployed programs live here too, as `Account`s keyed by `AccountId::from(program_id)`
|
||||
/// (see that impl's doc comment), with the elf held in `Account.data` and `program_owner`
|
||||
/// set to the reserved `PROGRAM_STORAGE_OWNER` (see its doc comment for why that ownership
|
||||
/// choice is load-bearing now that these accounts are reachable via ordinary dispatch).
|
||||
public_state: HashMap<AccountId, Account>,
|
||||
private_state: (CommitmentSet, NullifierSet),
|
||||
programs: HashMap<AccountId, Account>,
|
||||
}
|
||||
|
||||
impl Default for V03State {
|
||||
@@ -126,7 +130,6 @@ impl Default for V03State {
|
||||
Self {
|
||||
public_state: HashMap::default(),
|
||||
private_state,
|
||||
programs: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,11 +200,12 @@ impl V03State {
|
||||
pub(crate) fn insert_program(&mut self, program: &Program) {
|
||||
let account_id = AccountId::from(program.id());
|
||||
let account = Account {
|
||||
program_owner: PROGRAM_STORAGE_OWNER,
|
||||
data: Data::try_from(program.elf().to_vec())
|
||||
.expect("elf must fit under DATA_MAX_LENGTH"),
|
||||
..Account::default()
|
||||
};
|
||||
self.programs.insert(account_id, account);
|
||||
self.public_state.insert(account_id, account);
|
||||
}
|
||||
|
||||
pub fn apply_state_diff(&mut self, diff: ValidatedStateDiff) {
|
||||
@@ -286,17 +290,13 @@ impl V03State {
|
||||
self.private_state.0.get_proof_for(commitment)
|
||||
}
|
||||
|
||||
pub(crate) const fn programs(&self) -> &HashMap<AccountId, Account> {
|
||||
&self.programs
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn commitment_set_digest(&self) -> CommitmentSetDigest {
|
||||
self.private_state.0.digest()
|
||||
}
|
||||
|
||||
/// Order-independent fingerprint of the genesis-relevant state: the public
|
||||
/// account set, the deployed program set, and the commitment-set digest.
|
||||
/// Order-independent fingerprint of the genesis-relevant state: the public account set
|
||||
/// (which includes deployed programs' storage accounts) and the commitment-set digest.
|
||||
///
|
||||
/// The sequencer and the indexer build the directly-seeded part of genesis
|
||||
/// (base builtins plus any directly-seeded accounts) separately from their own
|
||||
@@ -313,18 +313,11 @@ impl V03State {
|
||||
let Self {
|
||||
public_state,
|
||||
private_state,
|
||||
programs,
|
||||
} = self;
|
||||
|
||||
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_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_accounts.len()).expect("program count fits in u64");
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(account_count.to_le_bytes());
|
||||
@@ -335,14 +328,6 @@ impl V03State {
|
||||
hasher.update(len.to_le_bytes());
|
||||
hasher.update(&bytes);
|
||||
}
|
||||
hasher.update(program_count.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());
|
||||
|
||||
let mut out = [0_u8; 32];
|
||||
|
||||
@@ -24,13 +24,10 @@ fn new_works() {
|
||||
);
|
||||
this
|
||||
};
|
||||
let expected_builtin_programs = HashMap::new();
|
||||
|
||||
let state =
|
||||
V03State::new().with_public_account_balances([(addr1, 100_u128), (addr2, 151_u128)]);
|
||||
|
||||
assert_eq!(state.public_state, expected_public_state);
|
||||
assert_eq!(state.programs, expected_builtin_programs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -68,11 +65,11 @@ fn insert_program() {
|
||||
let program_to_insert = crate::test_methods::simple_balance_transfer();
|
||||
let program_id = program_to_insert.id();
|
||||
let account_id = lee_core::account::AccountId::from(program_id);
|
||||
assert!(!state.programs.contains_key(&account_id));
|
||||
assert!(!state.public_state.contains_key(&account_id));
|
||||
|
||||
state.insert_program(&program_to_insert);
|
||||
|
||||
assert!(state.programs.contains_key(&account_id));
|
||||
assert!(state.public_state.contains_key(&account_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -106,15 +103,6 @@ fn get_account_by_account_id_default_account() {
|
||||
assert_eq!(account, expected_account);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_programs_getter() {
|
||||
let state = V03State::new();
|
||||
|
||||
let builtin_programs = state.programs();
|
||||
|
||||
assert_eq!(builtin_programs, &state.programs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_serialization_roundtrip() {
|
||||
let account_id_1 = AccountId::new([1; 32]);
|
||||
|
||||
@@ -112,9 +112,13 @@ impl ValidatedStateDiff {
|
||||
LeeError::MaxChainedCallsDepthExceeded
|
||||
);
|
||||
|
||||
let Some(program_account) = state
|
||||
.programs()
|
||||
.get(&AccountId::from(chained_call.program_id))
|
||||
// Check that the `program_id` corresponds to a deployed program. Deployed programs
|
||||
// live in `public_state`, keyed by `AccountId::from(program_id)` (see that impl's
|
||||
// doc comment), holding the elf as a plain `Account`; reconstruct a `Program` from
|
||||
// it via `new_unchecked` to execute, skipping a redundant image-id recomputation
|
||||
// since the id/elf pairing was already validated once, at deployment time.
|
||||
let Some(program_account) =
|
||||
state.get_account_by_id_ref(AccountId::from(chained_call.program_id))
|
||||
else {
|
||||
return Err(LeeError::InvalidInput("Unknown program".into()));
|
||||
};
|
||||
@@ -452,8 +456,8 @@ impl ValidatedStateDiff {
|
||||
// TODO: remove clone
|
||||
let program = Program::new(tx.message.bytecode.clone().into())?;
|
||||
if state
|
||||
.programs()
|
||||
.contains_key(&AccountId::from(program.id()))
|
||||
.get_account_by_id_ref(AccountId::from(program.id()))
|
||||
.is_some()
|
||||
{
|
||||
return Err(LeeError::ProgramAlreadyExists);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user