- RESERVED_DEPLOYMENT_PROGRAM_ACCOUNT_ID: restore the SHA256 derivation
docstring that explains the constant instead of leaving it as an opaque
byte array, and switch the array itself to a hex literal (hex-literal,
already a workspace dependency) for readability.
- Rename the loader/loader_core crate to program_loader/program_loader_core
to disambiguate it, across the directory, package name, workspace
members/dependency alias, both dependent crates, and every call site.
deploy_pda_seed's first paragraph just restated its parameter names;
kept only the domain-separation rationale. chained_call_forwarder.rs's
doc comment was floating over an unrelated type alias instead of being
a module-level //! doc.
Removed explanatory comments that restated context better left to commit
history/PR description across the deploy-dispatch and program-storage code.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deploy is not intended to ever run privately, and public dispatch
already always takes the native fast-path for it, so the guest ELF
had no real execution path left. Removes lez/programs/loader's guest
binary crate, its [[bin]] wiring, programs::loader(), and the
now-pointless guest-vs-native equivalence test along with the
test-only Program::execute_for_test it depended on.
loader_core (Instruction, ProgramData, execute_deploy) stays — native
dispatch still calls it directly. Deploy is now honestly just native
dispatch logic with a program-shaped interface, not a program with a
guest binary nobody executes.
Introduces a new loader_program/loader_core crate pair implementing a
Deploy instruction that claims a program's ProgramData PDA account
(image_id, segment_number, update_auth, elf_segment), unifying
deployment with ordinary PublicTransaction dispatch instead of the
separate ProgramDeploymentTransaction path.
Measured against every real program in this repo, computing a
program's image_id inside the zkVM costs ~1,400-1,500 cycles per byte
of deployed bytecode, pushing real deployments to 500M-900M cycles
against the 32M public-execution cap (vs. ~27ms natively, since
ProgramDeploymentTransaction's equivalent check runs as a plain host
function today). To keep the unified dispatch path viable, Deploy is
special-cased in from_public_transaction: calls targeting the reserved
RESERVED_DEPLOYMENT_PROGRAM_ACCOUNT_ID run loader_core::execute_deploy
natively instead of through the interpreted guest executor, wrapped in
catch_unwind since the shared execute_deploy logic validates via
assert!/expect() like every other guest program, relying on that
boundary instead of the zkVM's own panic-to-Result conversion.
The loader guest binary is kept buildable and covered by a test that
runs it for real and asserts its output matches the native path
exactly, so the two can't silently drift apart.
ping_receiver's renounce_authority/update_sources, wrapped_token's
renounce_authority/update_sources, authority_proxy, and
cross_zone_state_machine integration tests all landed on dev after this
branch's self/caller-identity and ChainedCall/Message AccountId migrations,
so they still referenced the old ProgramId-based fields.
dev grew new self_program_id/caller_program_id and ChainedCall.program_id
call sites (sequencer_stake's rewritten main.rs, gossip validation,
cross_zone's shared ping_emission test helper, and three guest test
programs) after this branch's ProgramId->AccountId migration commits
were originally written, so they predated the self_account_id/
caller_account_id/program_account_id rename and didn't conflict during
the rebase - they just still assumed the old ProgramId-typed fields.
Converts all of them, recovering ProgramId via the existing bijection
only where the RISC0 image id is actually needed, and rebuilds
artifacts against the fixed source.
ProgramInput/ProgramOutput.self_program_id/caller_program_id, and the
dispatcher's CallerData.program_id, now carry AccountId (renamed to
self_account_id/caller_account_id) instead of ProgramId. These fields
are self-reported/cross-checked dispatch bookkeeping, not RISC0 image
identity, and AccountId already crosses the guest/host boundary this
way via every pre_state.account_id.
ProgramId is now confined to what's actually image-id-keyed:
env::verify, Program.id (from compute_image_id()), and the
for_public_pda/for_private_pda derivation formulas, each recovering
the real ProgramId from AccountId via the existing bijection exactly
where needed.
Rebuilds artifacts and the prebuilt sequencer db fixture to match.
Chained-call and public-transaction dispatch now address the target
program directly by AccountId instead of routing through ProgramId and
converting internally, closing the gap that blocked PDA-addressed
program invocation. The field is named program_account_id (not
account_id) to stay unambiguous next to the account_ids list it sits
beside in the same structs.
Execution/PDA-derivation logic that fundamentally needs the RISC0 image
id (self_program_id, caller_program_id, env::verify, PDA seed
derivation) stays ProgramId-typed, recovering it from the dispatched
AccountId via the existing bijection where needed.
DATA_MAX_LENGTH_BYTES is a real const (ByteSize::as_u64 is const fn,
so no fallible conversion needed), replacing the private
data_max_length() helper and every duplicate
usize::try_from(DATA_MAX_LENGTH.as_u64()).expect(...) call site
across data.rs and its two external consumers, per PR #756 review nit.
BREAKING CHANGE: CrossZonePeer.expected_block_signing_pubkey (single optional
key) is renamed to expected_block_signing_pubkeys (a list, empty = unchecked),
and cross-zone config now refuses unknown fields at startup.
* 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.
* feat(lee): migrate Account.program_owner from ProgramId to AccountId
Account.program_owner is now AccountId-typed instead of ProgramId,
via a new bijective From<ProgramId> for AccountId / From<AccountId>
for ProgramId conversion pair (pure byte reinterpretation, not a
hash - both types are exactly 32 bytes). Adds DEFAULT_PROGRAM_OWNER
as the AccountId-typed counterpart to DEFAULT_PROGRAM_ID, used at
every program_owner comparison/claim site instead of an inline
AccountId::default().
Touches every call site across lee_core, lee (including the
guest-side privacy-preserving circuit), all 16 deployed guest
programs, wallet/wallet-ffi, indexer_ffi/indexer_service/
indexer_service_protocol, sequencer_core, testnet_initial_state,
system_accounts, cross_zone, storage, cycle_bench, and
integration_tests - mostly mechanical .into() conversions, plus two
simplifications: wallet's manual base58 encode/decode of
program_owner was dead code once it's AccountId (which already has
Display/FromStr), and the FFI crates' program_owner field now reuses
the existing generic FfiBytes32 wrapper instead of the now-unused
FfiProgramId one.
Rebuilds every guest ELF artifact and the prebuilt sequencer test
fixture via just build-artifacts, since execute_and_prove runs
against the checked-in precompiled privacy_preserving_circuit.bin,
which isn't rebuilt automatically by cargo test/check.
* chore(lee): rebuild artifacts after rebase, drop unused base58 dep
Rebases marvin/program-as-account-2 onto the updated
marvin/program-as-account (SeenShard cap fix), regenerating program
and circuit artifacts plus the sequencer test fixture to match.
Also removes lez/wallet's now-unused base58 dependency, dead since
AccountId gained its own Display/FromStr base58 encoding.
* docs(lee): trim DEFAULT_PROGRAM_OWNER and From<AccountId> for ProgramId docs
* test(lee): add known-answer tests for ProgramId/AccountId conversion, rebuild artifacts
* fix(lee): apply program_owner AccountId migration to code added after rebase
dev grew new program_owner call sites (sequencer_stake genesis/config
handling, committee_discovery, a new selective_pda_delegator test
program, and related tests) after this branch's ProgramId->AccountId
migration commit was originally written, so they predated the .into()
sweep and didn't conflict during the rebase - they just still assumed
the old ProgramId-typed field. Converts all of them, fixes a stray
unseparated hex literal clippy caught along the way, and rebuilds
artifacts against the fixed source.
* chore(lee): regenerate test fixture after rebasing onto dev
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* 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>
BREAKING CHANGE: WrappedTokenConfig and ReceiverConfig gain a governance field and CrossZoneConfig gains source_governance, so the config layouts change and every program image id moves.
BREAKING CHANGE: WrappedTokenConfig and ReceiverConfig gain an authority field and CrossZoneConfig gains source_authority, so the config layouts change and every program image id moves.
Every guest moved, not only the ones whose source changed: adding real
dependencies (borsh to two cores, cross_zone_inbox_core to two targets) changes
the build graph, and with it the metadata hash of every crate in the guest build.
Piece A moved only three because it added a dev-dependency, which the guest build
never sees.
BREAKING CHANGE: InboxConfig is just self_zone; allowed_routes, InboxConfig::permits
and routes_permit are gone, and build_inbox_init_config_tx no longer takes the
cross-zone config. The operator's per-peer routes still configure the same thing,
fanned out at genesis into each target's own config instead. The watcher keeps
only a hygiene filter refusing a peer that names a sequencer-only program, kept
host-side so it cannot make the verifier disagree.
BREAKING CHANGE: ping_receiver gains a config PDA holding the deliverer and the
peer sources it accepts, ReceiverInstruction gains InitConfig, and Record takes
[source marker, config, record] instead of the record alone. Every emitter aimed
at ping_receiver must name its config account in target_accounts. The
ping_receiver and ping_sender image ids move, and a fresh genesis is required.
BREAKING CHANGE: the wrapped-token config PDA holds a borsh WrappedTokenConfig
(minter plus authorized source pairs) rather than a bare minter id, and
Instruction::InitConfig takes it as a single field. Mint now refuses a source the
token has not authorized, checked against the inbox's source marker. The
wrapped_token image id moves, and bridge_lock's with it, since it links
wrapped_token_core.
BREAKING CHANGE: an inbox dispatch now declares [config, seen shard, source
marker, ...target accounts], and a target's chained call receives the marker at
position 0, so wrapped_token::Mint and ping_receiver::Record each take one more
leading account. The inbox, wrapped_token and ping_receiver image ids move,
relocating their PDAs and requiring a fresh genesis.
BREAKING CHANGE: Instruction::Lock drops outbox_program_id and takes the
bridge-lock config PDA as its first account, changing both the instruction
encoding and the account list. bridge_lock's image id moves, relocating its
escrow and config PDAs and requiring a fresh genesis. Sequencer, indexer and
every peer zone must upgrade together: a stale decoder re-derives a different
dispatch and reports Forged.
BREAKING CHANGE: SenderInstruction::Send drops outbox_program_id and takes the
sender config PDA as its first account, changing both the instruction encoding
and the account list. ping_sender's image id moves, relocating its PDAs and
requiring a fresh genesis. Sequencer, indexer and every peer zone must upgrade
together: a stale decoder re-derives a different dispatch and reports Forged.
Closes#675.
BREAKING CHANGE: an outbox message PDA is now derived from the emitting program
as well as the destination zone and ordinal, and the seed domain moves to
/00001/, so every outbox record address changes. `OutboxRecord` gains `emitter`
and `ordinal`, changing its borsh layout, so records written by earlier code are
orphaned at their old addresses and no longer decode. A second `Emit` at the same
(emitter, target_zone, ordinal) now fails the transaction instead of silently
overwriting the record, so a caller must choose an ordinal the chain does not
already hold rather than counting from zero. A fresh genesis is required.
Closes#678.
BREAKING CHANGE: `wrapped_token` and `bridge_lock` image ids move, relocating the
wrapped-token config and every holding PDA. A lock above the cap is now refused
at the source rather than escrowing balance the destination will not mint.
Closes#676.
BREAKING CHANGE: the seen-shard PDA address and its data layout both change, so
every message delivered under the old layout becomes deliverable again at the new
address, and the old shards are orphaned. A fresh genesis is required.
BREAKING CHANGE: `CrossZoneMessage` gains `src_block_hash`, changing the risc0
encoding of `cross_zone_inbox::Instruction::Dispatch` and the inbox image id,
and with it the inbox config and seen-shard PDA addresses. A pending dispatch
record written by earlier code no longer names a registered program, so it fails
production and is dead-lettered rather than delivered. Drain the pending list
before upgrading, or accept that in-flight deliveries are lost.
BREAKING CHANGE: `CrossZonePeer.allowed_targets` is replaced by `allowed_routes`,
a list of `{src_program_id, target_program_id}` pairs. `InboxConfig.allowed_peers`
is removed.
Integrates origin/main (per-program-crate refactor) and re-homes the cross-zone messaging feature onto it.
BREAKING CHANGE: Genesis state root changes. This registers six builtin programs
(cross_zone_outbox, cross_zone_inbox, ping_sender, ping_receiver, bridge_lock,
wrapped_token) and seeds their genesis accounts (the wrapped_token authorized
minter config, the per-zone inbox config, and optional bridge-lock holdings).
Building the new cores alongside the existing builtins also enables serde/alloc
for the shared programs build, regenerating every builtin program image id. All
nodes must upgrade together.