Compare commits

..

354 Commits

Author SHA1 Message Date
jonesmarvin8
feb6cb7f92
feat(wallet): add keycard support for public accounts for public/privacy txs for program facades (#461)
* feat: add basic commands for communicating with keycard

* initialize changes

* reorganization

* add script file for easier wallet access

* update commands

* fixes

* fixed load for non continuous run

* Updates for signatures with keycard

* fix BIP-340 signatures for fixed sized messages

* fmt

* refactor and add pin support to program facades

* fix unit test

* fixes

* Revert "fixes"

This reverts commit 41f34f4ff4145b7abb60fd9bec168ae4b60f23b4.

* fixes

* fixes

* Removed privacy keycard calls

* Revert "Removed privacy keycard calls"

This reverts commit d70ef505a1f40b87159099761f5fce5a31e3f17b.

* Add domain separators

* Removed privacy txs for keycard

* CI fixes

* CI fixes

* addressed some comments

* fix ci

* initialize branch

* ci fixes

* fix integration test issue and updated keycard firmware

* addressed more comments

* fixed deny

* remove keycard-py

* fixed from earlier merge

* add hash_message tests

* add test

* fix deny

* CI fixes

* fixed integration tests

* Update public.rs

* update artifacts

* privacy command fixes

* ci and comments

* addressed comments

* comment fixes

* fixes from merging main

* adding support to other programs

* expanded support

* ci fixes

* ci and add private account keys test

* some fixes and setup notes

* Ci fixes

* ci fixes

* update key paths to avoid collisions in tests

* added separated files for keycard_tests_2.sh

* first round of comments

* Revert "Merge branch 'main' into marvin/keycard-commands"

This reverts commit 3fce53f663a3996938dddf77680854570063ca21, reversing
changes made to e7b42a5177641455a8917bd2e29db20afd9690e5.

* python comments

* addressed comments

* compile error fixed

* fix artifacts

* fix main merge error

* adjust signer logic workflow

* updating logic

* fmt

* refactored

* clippy fix

* minor fix

* addressing comments

* minor fix

* ci fix

* addressed deferred comments

* clean up

* minor cleanup

* ci fixes

* fmt fix

* feat!(wallet): Merged `SigningGroup` with `AccountManager` (#500)

* feat: account manager extension

* feat(wallet): added unified way of sending public transactions to all facades

* fix(wallet): no sign option added

* fix(deny): deny fix

* fix(wallet): suggestion 1

* fix(wallet): suggestion fix 1

* feat!: Add new path for externally provided seed to the circuit.

BREAKING CHANGE: add identity variants to the circuit and change semantics for `Claim::Authorized` for private PDAs

* feat(ci): use separate job per each integration tests module

* feat(ci): cache rust artifacts

* feat(ci): build integration tests binary once and reuse it

* fix(wallet): fmt

* ci: add bench-regression workflow with criterion-compare for crypto_primitives_bench

* fix(wallet): merge postfix

* feat!(wallet): SigningGroup merged with AccountManager

* fix(ci): deny and artifacts fix

* fix(deny): deny fix

* fix keycard and lint

---------

Co-authored-by: Sergio Chouhy <sergio.chouhy@gmail.com>
Co-authored-by: Daniil Polyakov <arjentix@gmail.com>
Co-authored-by: Moudy <m.ellaz@hotmail.com>
Co-authored-by: Sergio Chouhy <41742639+schouhy@users.noreply.github.com>
Co-authored-by: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com>

* addressed comments

* minor comments

* Rebase to main

* CI fixes

---------

Co-authored-by: Pravdyvy <46261001+Pravdyvy@users.noreply.github.com>
Co-authored-by: Sergio Chouhy <sergio.chouhy@gmail.com>
Co-authored-by: Daniil Polyakov <arjentix@gmail.com>
Co-authored-by: Moudy <m.ellaz@hotmail.com>
Co-authored-by: Sergio Chouhy <41742639+schouhy@users.noreply.github.com>
2026-06-05 17:35:10 -04:00
Daniil Polyakov
190d1fe45a
Merge pull request #506 from logos-blockchain/arjentix/crash-tolerant-deposit
feat(sequencer): make bridge deposits fault-tolerant
2026-06-04 16:40:31 +03:00
Daniil Polyakov
7f5590907f feat(sequencer): make bridge deposits fault-tolerant 2026-06-04 15:17:56 +03:00
Daniil Polyakov
bdcf630aa9 feat(sequencer): update to a new zone sdk 2026-06-04 14:19:21 +03:00
jonesmarvin8
2104f71e39
feat(key-protocol/nssa)!: PQ resistance primitives for vsk/vpk for shared key agreement (#474)
* initialize pq encryption changes

* key agreement update

* add test and other fixes

* ci fixes

* fix unit tests

* updates from main and ci

* added updated specs for pq encryption

* addressing comments

* addressed comments

* fix clippy errors from main merge

* Rebased to main
2026-06-03 14:40:06 -04:00
Sergio Chouhy
c64e40590b
Merge pull request #503 from logos-blockchain/schouhy/logos-execution-zone-renaming
refactor!: move all LEZ related crates under a new  directory
2026-06-02 15:58:40 -03:00
Sergio Chouhy
50be74580b refactor!: move LEZ related crates to dedicated directory
BREAKING CHANGE: LEZ crates have been moved from top-level directories into
  a dedicated `lez/` subdirectory. The following crates were relocated:

    common          → lez/common
    indexer         → lez/indexer
    explorer_service→ lez/explorer_service
    keycard_wallet  → lez/keycard_wallet
    mempool         → lez/mempool
    sequencer       → lez/sequencer
    storage         → lez/storage
    testnet_initial_state → lez/testnet_initial_state
    wallet          → lez/wallet
    wallet-ffi      → lez/wallet-ffi

  Any external tooling, scripts, or paths referencing these crates at their
  previous top-level locations must be updated.
2026-06-02 14:17:59 -03:00
Sergio Chouhy
f3ab53460a
Merge pull request #499 from logos-blockchain/schouhy/logos-execution-environment-renaming
refactor!: rename nssa crate to lee
2026-06-02 12:18:04 -03:00
Daniil Polyakov
dda8a71070
Merge pull request #507 from logos-blockchain/arjentix/ci-pin-circuits
chore(ci): pin circuits installation script to a commit
2026-06-02 12:01:34 +03:00
Sergio Chouhy
9f1dc1d24b split indexer integration tests 2026-06-01 23:47:55 -03:00
Sergio Chouhy
4bcffafe27 refactor!: rename nssa crate to lee
BREAKING CHANGE:
- Crate `nssa` renamed to `lee`; update `Cargo.toml` dependencies from `nssa = { workspace = true }` to `lee = { workspace = true }`.
- Crate `nssa_core` renamed to `lee_core`; update similarly.
- Crate `key_protocol` moved under `lee`; update `Cargo.toml` dependencies from `key_protocol = { workspace = true }` to `lee_key_protocol = { workspace = true }`.
- Type `NSSATransaction` (in `common`) renamed to `LeeTransaction`.
- Error type `nssa::error::NssaError` renamed to `lee::error::LeeError`.
- Error type `nssa_core::error::NssaCoreError` renamed to `lee_core::error::LeeCoreError`.
- All `use nssa::` and `use nssa_core::` import paths must be updated to `use lee::` and `use lee_core::` respectively.
- Guest programs must replace `write_nssa_outputs` with `write_lee_outputs`.
- The sequencer RocksDB column family for the chain state was renamed. Existing databases are incompatible and must be wiped before running the new version.
- Domain separators updated: `"NSSA_seed"` → `"LEE_seed"` (key derivation), `"NSSA/v0.2/KDF-SHA256/"` → `"LEE/v0.2/KDF-SHA256/"` (encryption KDF), `"/NSSA/v0.2/AccountId/PDA/"` →
  `"/LEE/v0.2/AccountId/PDA/"` (public PDA address derivation). All previously derived keys, encrypted outputs, and public PDA addresses are invalidated.
2026-06-01 17:11:42 -03:00
Daniil Polyakov
6c0713d70d chore(ci): pin circuits installation script to a commit 2026-06-01 22:55:03 +03:00
Daniil Polyakov
d3390efc6d
Merge pull request #493 from logos-blockchain/arjentix/bridge-deposit
feat: Implement deposit operation for bridge
2026-05-29 23:13:33 +03:00
Daniil Polyakov
3046cccab5 fix(integration_tests/indexer): adjust l2 to l1 timeout to satisfy CI one more time 2026-05-29 21:24:02 +03:00
Daniil Polyakov
d86e02139f chore(faucet): add genesis prefix to instruction names 2026-05-29 20:05:38 +03:00
Daniil Polyakov
0a7cdb1971 fix(integration_tests/indexer): adjust l2 to l1 timeout to satisfy CI 2026-05-29 20:01:24 +03:00
Daniil Polyakov
5e09b81d1c chore(configs): add supply bridge account genesis action 2026-05-29 20:01:24 +03:00
Daniil Polyakov
1322ce0ac4 chore(deny): ignore unmaintained proc-macro-error warning 2026-05-29 20:01:24 +03:00
Daniil Polyakov
913a627198 feat: upgrade to new zone-sdk 2026-05-29 20:01:24 +03:00
Petar Radovic
23fda575ed fix(sequencer): use dummy msg id while waiting for new zone-sdk 2026-05-29 20:01:24 +03:00
Daniil Polyakov
2a2da3726c fix: differentiate between user and sequencer transactions in mempool 2026-05-29 20:01:24 +03:00
Daniil Polyakov
0f347a7614 fix: dedup Deposit events 2026-05-29 20:01:24 +03:00
Daniil Polyakov
78633acdc3 fix: wait for finalized deposit event 2026-05-29 20:01:24 +03:00
Daniil Polyakov
9b8955daf5 feat(sequencer, programs): implement deposit operation for bridge 2026-05-29 20:01:24 +03:00
Sergio Chouhy
6fe965a56f
Merge pull request #504 from logos-blockchain/schouhy/protocol-fixes
fix!: several protocol fixes
2026-05-29 03:01:42 -03:00
Sergio Chouhy
48da4b5119 fix!: protocol fixes
BREAKING CHANGE
  - Nonce init (PrivateAuthorizedInit): the initial nonce for PrivateAuthorizedInit accounts was incorrectly computed. Privacy preserving circuit code changed, as well as its id.
  - Authorization bidirectional check: programs must now set is_authorized = true for every authorized account in pre-states, not just avoid marking unauthorized ones as authorized.
  - Authorization in chained calls: authorized-account set is now the union across the call chain instead of being reset at each hop.
2026-05-29 02:08:14 -03:00
Pravdyvy
b77cfc29c2
Merge pull request #484 from logos-blockchain/Pravdyvy/account-manager-extension
Transaction flows unification
2026-05-28 17:46:53 +03:00
Pravdyvy
251397b291 Merge branch 'main' into Pravdyvy/account-manager-extension 2026-05-27 12:01:17 +03:00
Sergio Chouhy
84bda7be34
Merge pull request #486 from logos-blockchain/schouhy/private-pdas-as-external-input
feat!: Add new path for pda seed input
2026-05-25 23:37:43 -03:00
Daniil Polyakov
18b88a6018
Merge pull request #498 from logos-blockchain/arjentix/speed-up-ci
feat(ci): speed up ci
2026-05-25 23:42:44 +03:00
Sergio Chouhy
71be6bae32 Merge branch 'main' into schouhy/private-pdas-as-external-input 2026-05-25 16:32:11 -03:00
Moudy
867d328cf3
Merge pull request #494 from logos-blockchain/moudy/bench-regression 2026-05-25 16:15:14 +02:00
Moudy
7d69254b3e ci: add bench-regression workflow with criterion-compare for crypto_primitives_bench 2026-05-25 14:00:20 +02:00
Pravdyvy
f3e2c4fc78 fix(wallet): fmt 2026-05-25 14:56:24 +03:00
Pravdyvy
e3b5e4f19a Merge branch 'main' into Pravdyvy/account-manager-extension 2026-05-25 14:41:51 +03:00
Daniil Polyakov
006647bc83
Merge pull request #497 from logos-blockchain/arjentix/expand-contributing-guide
docs(contributing): require fill PR template
2026-05-23 00:45:00 +03:00
Daniil Polyakov
23f427246d
Merge pull request #496 from logos-blockchain/arjentix/fix-risc0-features
fix: apply right features for risc0-zkvm
2026-05-23 00:44:43 +03:00
Daniil Polyakov
ac2d01e1b4 feat(ci): build integration tests binary once and reuse it 2026-05-22 20:09:35 +03:00
Daniil Polyakov
adf0d241c8 feat(ci): cache rust artifacts 2026-05-22 19:28:04 +03:00
Daniil Polyakov
b5cecdebc0 feat(ci): use separate job per each integration tests module 2026-05-22 19:27:47 +03:00
Daniil Polyakov
fa47d471af fix: disable default features for risc0-zkvm for workspace and enable client feature for nssa 2026-05-22 18:23:47 +03:00
Daniil Polyakov
bcd8577370 docs(contributing): add requirement to fill PR template before marking it as ready for review 2026-05-22 18:19:27 +03:00
Daniil Polyakov
7546e22cf6
Merge pull request #495 from logos-blockchain/Pravdyvy/revert-490
revert(490)
2026-05-22 15:47:57 +03:00
Pravdyvy
5f14ac1cfe revert(490): CI test 2026-05-22 07:28:49 +03:00
jonesmarvin8
cf9177a095
feat(wallet): add keycard support for public tx for auth-transfer (#451)
* feat: add basic commands for communicating with keycard

* initialize changes

* reorganization

* add script file for easier wallet access

* update commands

* fixes

* fixed load for non continuous run

* Updates for signatures with keycard

* fix BIP-340 signatures for fixed sized messages

* fmt

* refactor and add pin support to program facades

* fix unit test

* fixes

* Revert "fixes"

This reverts commit 41f34f4ff4145b7abb60fd9bec168ae4b60f23b4.

* fixes

* fixes

* Removed privacy keycard calls

* Revert "Removed privacy keycard calls"

This reverts commit d70ef505a1f40b87159099761f5fce5a31e3f17b.

* Add domain separators

* Removed privacy txs for keycard

* CI fixes

* CI fixes

* addressed some comments

* fix ci

* ci fixes

* fix integration test issue and updated keycard firmware

* addressed more comments

* fixed deny

* remove keycard-py

* fixed from earlier merge

* add hash_message tests

* add test

* fix deny

* CI fixes

* fixed integration tests

* Update public.rs

* update artifacts

* ci and comments

* addressed comments

* comment fixes

* fixes from merging main

* first round of comments

* Revert "Merge branch 'main' into marvin/keycard-commands"

This reverts commit 3fce53f663a3996938dddf77680854570063ca21, reversing
changes made to e7b42a5177641455a8917bd2e29db20afd9690e5.

* python comments

* addressed comments

* compile error fixed

* fix artifacts

* fix main merge error

* adjust signer logic workflow

* fmt

* merge main and shift keycard tests

* deny fix

* artifacts fix

* remove keycard scripts from root

* tps fix

* fmt
2026-05-21 20:46:13 -04:00
Moudy
5543e125ee
Merge pull request #488 from logos-blockchain/moudy/bench-criterion
feat: migrate bench tools to criterion harness
2026-05-21 21:41:25 +02:00
Moudy
ebfc3e5ad2 chore(deny): fix 2026-05-21 16:53:44 +02:00
Moudy
fdec52791d refactor(crypto_primitives_bench): derive account_id from key in encryption bench 2026-05-21 16:44:41 +02:00
Moudy
d064f87ad7 refactor: lift criterion html_reports feature to workspace declaration 2026-05-21 16:44:41 +02:00
Moudy
a9bf3fbfe7 feat(cycle_bench): add criterion verify bench for G_verify 2026-05-21 16:44:40 +02:00
Moudy
fb89e7549b refactor(cycle_bench): split into lib + binary, drop hand-rolled verify timing 2026-05-21 16:44:40 +02:00
Moudy
b608d10ca1 feat(crypto_primitives_bench): migrate to criterion harness 2026-05-21 16:44:40 +02:00
Sergio Chouhy
3c6d623c49 feat!: Add new path for externally provided seed to the circuit.
BREAKING CHANGE: add identity variants to the circuit and change semantics for `Claim::Authorized` for private PDAs
2026-05-21 11:22:31 -03:00
jonesmarvin8
694e484228
fix(nssa): audit 91 issue fix (#489)
* address audit-issue-91

* add privacy test version

* addressed comments
2026-05-21 09:00:27 -04:00
Pravdyvy
ef1e0e0fa4 fix(wallet): suggestion fix 1 2026-05-21 13:02:18 +03:00
Daniil Polyakov
bc852925d4
Merge pull request #490 from ygd58/fix/disable-risc0-zkvm-default-features-v3
fix(workspace): disable risc0-zkvm default features to avoid ring in guest builds
2026-05-21 00:06:09 +03:00
Pravdyvy
97d9188c38 fix(wallet): suggestion 1 2026-05-20 18:55:39 +03:00
Pravdyvy
dc4adfa1a2 Merge branch 'main' into Pravdyvy/account-manager-extension 2026-05-20 18:45:24 +03:00
ygd58
94096bcdc6
fix(workspace): disable risc0-zkvm default features to avoid ring in guest builds
- Cargo.toml: add default-features = false to risc0-zkvm
- nssa/Cargo.toml: add explicit prove feature for ExecutorEnv/default_prover
- Regenerate artifacts

Fixes #468
2026-05-20 17:23:51 +02:00
Moudy
bfdc087680
Merge pull request #487 from logos-blockchain/moudy/e2e-bench-tool
feat!: add integration_bench tool for end-to-end scenario latency, block, and tx-byte measurements
2026-05-20 16:05:52 +02:00
Moudy
715d52f605 chore(workspace): drop integration_tests workspace dep and clean test_fixtures docstring 2026-05-20 16:04:53 +02:00
moudyellaz
33b20bb480 ci(integration_bench): apply nightly rustfmt and drop integration_tests unused deps 2026-05-20 13:08:48 +02:00
moudyellaz
b0a5b3478b docs(integration_bench): add canonical run numbers from docker-compose sweep 2026-05-20 12:58:25 +02:00
moudyellaz
ab77c5d26a refactor(integration_bench): ScenarioOutput::step closure helper 2026-05-20 12:19:43 +02:00
moudyellaz
0119b38c1b refactor(integration_bench)!: pivot to docker-compose via TestContext, share one node per run
BREAKING CHANGE:
- crate renamed e2e_bench → integration_bench. Run via `cargo run -p integration_bench`.
- env vars removed: LEZ_BEDROCK_BIN, LEZ_BEDROCK_CONFIG_DIR, LEZ_BEDROCK_PORT. Replaced by a docker prerequisite (docker-compose Bedrock via test_fixtures::TestContext).
- output filenames: target/e2e_bench_{dev,prove}.json → target/integration_bench_{dev,prove}.json.
- JSON schema: per-scenario `setup_s` field removed; replaced by run-level `shared_setup_s` (one TestContext is shared across all scenarios in a run).
- internal: bedrock_handle.rs and bench_context.rs deleted; placeholder-string config (PLACEHOLDER_CHAIN_START_TIME) gone.
2026-05-20 11:04:06 +02:00
moudyellaz
563a9ce0f7 refactor: extract test_fixtures crate from integration_tests 2026-05-20 10:08:24 +02:00
moudyellaz
932763fcf2 refactor(e2e_bench): rename ScenarioResult to ScenarioOutput 2026-05-19 23:48:05 +02:00
moudyellaz
619db3846d refactor(e2e_bench)!: Duration-typed timings, seconds-float JSON, tokio::timeout
BREAKING CHANGE: bench JSON renames per-step / per-scenario timing fields from *_ms (float milliseconds) to *_s (float seconds). Renames: submit_ms → submit_s, inclusion_ms → inclusion_s, wallet_sync_ms → wallet_sync_s, total_ms → total_s, setup_ms → setup_s, bedrock_finality_ms → bedrock_finality_s, total_wall_seconds → total_wall_s. measure_bedrock_finality timeout floor also shifts slightly: on timeout the field is now ~60.000s rather than "first poll tick past 60s".
2026-05-19 22:59:02 +02:00
Sergio Chouhy
c0e837b65d
Merge pull request #479 from logos-blockchain/schouhy/fix-faucet-account-protection-mechanism
fix: Bug in faucet account protection mechanism
2026-05-19 16:36:29 -03:00
moudyellaz
c3daa9897d docs(e2e_bench): drop machine table and stale benchmark numbers 2026-05-19 18:54:11 +02:00
Pravdyvy
7920b17c6d fix(deny): deny fix 2026-05-19 18:00:03 +03:00
Pravdyvy
43e7fa9be2 fix(wallet): no sign option added 2026-05-19 17:54:25 +03:00
Moudy
832b21f74d fix: cli 2026-05-19 09:59:11 +02:00
Moudy
20b9868ace feat: add e2e_bench tool for end-to-end scenario latency, block, and tx-byte measurements 2026-05-19 09:45:26 +02:00
Daniil Polyakov
534b0f8ee1
Merge pull request #483 from logos-blockchain/arjentix/contributing-guide
docs(contributing): add CONTRIBUTING.md
2026-05-18 22:46:45 +03:00
Moudy
aa53e591d8
Merge pull request #480 from logos-blockchain/moudy/wallet-crypto-bench-tool 2026-05-18 20:06:21 +02:00
Daniil Polyakov
34b6e34642 docs(contributing): add CONTRIBUTING.md 2026-05-18 20:19:11 +03:00
Moudy
0ab3075e78 Merge remote-tracking branch 'origin/main' into moudy/wallet-crypto-bench-tool
# Conflicts:
#	Cargo.toml
#	docs/benchmarks/README.md
2026-05-18 18:25:58 +02:00
Moudy
3877b216e0
Merge pull request #478 from logos-blockchain/moudy/cycle-bench-tool
feat: add cycle_bench tool for executor, prove, PPE, and verify cycle measurements
2026-05-18 17:56:22 +02:00
Moudy
dbe8ac6160 chore(crypto_primitives_bench): switch allow to expect, fix doc_markdown inline 2026-05-18 17:36:07 +02:00
Moudy
8960df04d6 fix: fmt 2026-05-18 17:17:58 +02:00
Moudy
ba65b168dd rename(wallet_crypto_bench): rename to crypto_primitives_bench 2026-05-18 17:13:07 +02:00
Moudy
87170b93b0 refactor(cycle_bench): collapse 9 inline run_case calls into Case struct + iterator
- Introduce `struct Case` holding pre-serialized InstructionData,
  with new<I: Serialize>(...) -> Result<Self> constructor and
  fn run(self, prove, exec_iters) -> Result<BenchResult>.
- Replace 9 inline `run_case(...)?` push calls in main() with
  [Case::new(...)?, ...].into_iter().map(|c| c.run(prove, exec_iters))
  .collect::<Result<Vec<_>>>()?.
- Drop now-unused `needless_pass_by_value` and `too_many_arguments`
  from the crate-level #![expect] block.
2026-05-18 16:48:23 +02:00
Moudy
b84a3e8b44 docs(cycle_bench): document Stats fields and use Display instead of ::format()
- Add /// doc comments on Stats {n, best_ms, mean_ms, stdev_ms}
  clarifying units, semantics, and Bessel's correction.
- Replace pub fn format(&self) -> String with impl fmt::Display for
  Stats, idiomatic and lets println! use {} directly.
- Update three call sites accordingly.
2026-05-18 16:37:11 +02:00
Moudy
28db42315b chore(cycle_bench): tighten lint discipline (allow → expect, fix issues in code)
- Switch crate-level #![allow] to #![expect] in main.rs and prune
  17 entries the compiler reports as unfulfilled or workspace-allowed.
- Fix the underlying issues rather than allow: source ordering
  (mods/uses regrouped), doc_markdown (identifiers backticked),
  redundant_type_annotations, map_unwrap_or, unnecessary_wraps.
- Extract feature-gated mod ppe_impl into its own file
  tools/cycle_bench/src/ppe/ppe_impl.rs so the mod declaration can
  precede the public structs per arbitrary_source_item_ordering.

Net: 35 → 12 expects in main.rs, all load-bearing.
2026-05-18 15:49:24 +02:00
Pravdyvy
0210d70602 feat(wallet): added unified way of sending public transactions to all facades 2026-05-18 15:06:09 +03:00
Pravdyvy
3732f16df9 feat: account manager extension 2026-05-18 13:44:03 +03:00
Sergio Chouhy
518c0e0205 remove test. Now directly modifying faucet account is forbidden 2026-05-15 21:07:17 -03:00
Sergio Chouhy
0e177f1eba replace unit tests with integration tests 2026-05-15 21:04:09 -03:00
Sergio Chouhy
58226fd0f7 fix test 2026-05-15 20:11:11 -03:00
Sergio Chouhy
57173cc140 make authorization propagate transitively through chain calls in the circuit like in the public execution 2026-05-15 17:24:24 -03:00
Moudy
891b23c18a fix: ci 2026-05-15 15:27:30 +02:00
Moudy
4a8825e63c fix: ci 2026-05-15 12:53:04 +02:00
Moudy
9efc26495b refactor: use canonical program IDs from nssa::program_methods 2026-05-15 12:19:49 +02:00
Moudy
84a1fec942 feat: add wallet_crypto_bench tool for wallet-side cryptographic primitives 2026-05-15 10:51:51 +02:00
Sergio Chouhy
2ae9e4da7f add tests and fix mechanism 2026-05-15 00:43:45 -03:00
Daniil Polyakov
4079b0c9c8
Merge pull request #450 from logos-blockchain/arjentix/move-configurable-initial-data-to-genesis
feat: move configurable initial data to genesis
2026-05-15 02:30:57 +03:00
Daniil Polyakov
8c8f5b57af fixup! refactor: use faucet program to manage faucet account 2026-05-15 01:46:39 +03:00
Daniil Polyakov
7e31aa39e3 fix(ci): increase integration-tests timeout 2026-05-15 01:34:07 +03:00
Daniil Polyakov
f721a00bdf fix: proper account authorization propagation 2026-05-15 01:34:07 +03:00
Daniil Polyakov
ee5a98fc48 refactor: use faucet program to manage faucet account 2026-05-15 01:34:07 +03:00
Daniil Polyakov
e359c1abe2 refactor: better check for db existence 2026-05-15 01:34:01 +03:00
Daniil Polyakov
879cd5096a fix: update configuration files 2026-05-15 01:34:01 +03:00
Daniil Polyakov
9075f30f19 refactor: use system faucet and vaults to supply accounts from genesis
Co-authored-by: Copilot <copilot@github.com>
2026-05-15 01:34:01 +03:00
Daniil Polyakov
89d1b95738 refactor: mark WalletFfiError as must_use
Co-authored-by: Copilot <copilot@github.com>
2026-05-15 01:33:50 +03:00
Daniil Polyakov
cff019dca4 fix: increase valid-proof-test timeout 2026-05-15 01:33:50 +03:00
Daniil Polyakov
5f207a3f02 feat: move initial accounts data into genesis 2026-05-15 01:33:50 +03:00
Moudy
e74fe36866
Update README.md 2026-05-15 00:24:56 +02:00
Moudy
75e1cc51d5
Update README.md 2026-05-15 00:24:16 +02:00
Moudy
1e2d41f941
Update cycle_bench.md 2026-05-15 00:23:49 +02:00
Moudy
e998ec7b99
Merge pull request #469 from logos-blockchain/moudy/refactor-ppc-submodules
refactor(privacy_preserving_circuit)!: split guest into bin-dir submodules
2026-05-15 00:18:32 +02:00
moudyellaz
ba84ba60ce fix(privacy_preserving_circuit): refresh artifact 2026-05-14 23:45:52 +02:00
moudyellaz
7679c50ea8 fix(privacy_preserving_circuit): lint 2026-05-14 23:07:15 +02:00
Moudy
2870bc364b fix: allow non_ascii_literal and redundant_type_annotations clippy lints 2026-05-14 18:58:53 +02:00
Moudy
5f30e382d1 fix: satisfy nightly rustfmt and pedantic clippy in cycle_bench 2026-05-14 18:43:26 +02:00
moudyellaz
3c8ff78319 merge: resolve conflicts with main 2026-05-14 18:40:49 +02:00
Moudy
05f41f81e9 feat: add cycle_bench tool for executor, prove, PPE, and verify cycle measurements 2026-05-14 18:14:37 +02:00
moudyellaz
497a8fa761 fix(ci): drop duplicate deny.toml and add hickory advisories to .deny.toml 2026-05-14 16:25:36 +02:00
Pravdyvy
41fa494e32
Merge pull request #477 from logos-blockchain/Pravdyvy/nix-fix
Nix fix
2026-05-14 17:24:18 +03:00
Pravdyvy
36b7228b6d fix: nix pin 2026-05-14 11:56:18 +03:00
Pravdyvy
cfabd91510 fix: nix pin 2026-05-14 11:54:32 +03:00
Pravdyvy
ec74dca893 fix: nix pin 2026-05-14 11:49:29 +03:00
Pravdyvy
904dd56187 fix: nix pin 2026-05-14 11:20:47 +03:00
Pravdyvy
130f35e997 fix(nix): nix fix 2026-05-14 07:52:14 +03:00
Sergio Chouhy
51b1e637a6
Merge pull request #475 from logos-blockchain/schouhy/fix-private-shared-account-preparation
fix: Private shared account preparation
2026-05-13 16:17:37 -03:00
Sergio Chouhy
bc65c877af fix private shared account preparation 2026-05-13 00:03:01 -03:00
Sergio Chouhy
d38ca35337
Merge pull request #464 from logos-blockchain/schouhy/diversify-private-pdas-by-identifier
feat!: Diversify private pdas by identifier
2026-05-12 14:30:49 -03:00
Sergio Chouhy
67b6916b72 clippy 2026-05-12 13:55:30 -03:00
Sergio Chouhy
f3389571f8 use identifier in private gms command 2026-05-12 13:29:06 -03:00
Sergio Chouhy
ee0fecee25 add privatepdashared variant 2026-05-11 20:30:23 -03:00
Sergio Chouhy
b3acd46f11 artifacts 2026-05-11 20:00:20 -03:00
Sergio Chouhy
355fe3842d rename program 2026-05-11 19:38:28 -03:00
Sergio Chouhy
927c24de68 Merge branch 'main' into schouhy/diversify-private-pdas-by-identifier 2026-05-11 18:57:44 -03:00
Sergio Chouhy
a9baf5d3dc rename progam 2026-05-11 17:09:46 -03:00
Sergio Chouhy
1ec145e7da assert equality on duplicate insert 2026-05-11 16:46:18 -03:00
Sergio Chouhy
b8a4d94d96
Merge pull request #473 from logos-blockchain/schouhy/remove-stale-files
chore: Remove stale files
2026-05-11 09:38:09 -03:00
Sergio Chouhy
b05da5b426 remove stale files 2026-05-08 23:30:46 -03:00
Sergio Chouhy
54c039f639 artifacts 2026-05-08 22:31:38 -03:00
Sergio Chouhy
d648505d89 fmt, clippy 2026-05-08 22:15:57 -03:00
Sergio Chouhy
190054c94d use borsh 2026-05-08 22:13:56 -03:00
Sergio Chouhy
e9c0aa0858 handle comments 2026-05-08 21:41:48 -03:00
Moudy
b44551225c
Merge pull request #460 from logos-blockchain/moudy/feat-group-cli
feat(wallet)!: wallet group commands with shared account derivation
2026-05-09 00:28:02 +02:00
Moudy
27e2850b5c refactor: make SealingPublicKey a newtype wrapper 2026-05-08 23:59:08 +02:00
Moudy
6e376900f7 fix: remove export/import commands, rewrite test to use invite/join 2026-05-08 20:28:39 +02:00
Moudy
cf699fde7c refactor: rename private_pda_spender to auth_transfer_proxy 2026-05-08 18:18:40 +02:00
Pravdyvy
fda862f5bc
Merge pull request #456 from logos-blockchain/Pravdyvy/indexer-query-api
Indexer query API
2026-05-08 19:05:09 +03:00
Moudy
1bed9ecef2 fix: resolve shared accounts in all facades, use SealingPublicKey alias, ignore fund test 2026-05-08 17:44:10 +02:00
moudyellaz
06fd4fc12e fix: artifacts and deny
Refs: #454
2026-05-08 17:32:28 +02:00
moudyellaz
3772046a39 refactor(privacy_preserving_circuit): extract execution_state module
Refs: #454
2026-05-08 17:30:55 +02:00
Moudy
2b2275ee74 fix: resolve shared accounts in auth-transfer commands 2026-05-08 11:03:13 +02:00
Moudy
4e7963c655 feat: add dedicated sealing key for GMS distribution 2026-05-08 08:19:55 +02:00
Moudy
4ace6e1570 fix: address review feedback 2026-05-07 22:48:32 +02:00
moudyellaz
01eb4a58b8 fix(privacy_preserving_circuit): satisfy CI lints and refresh artifact for #454
Refs: #454
2026-05-07 21:23:35 +02:00
Sergio Chouhy
89d28fb53e Merge branch 'main' into schouhy/diversify-private-pdas-by-identifier 2026-05-07 15:33:54 -03:00
Sergio Chouhy
75ab606dcf artifacts 2026-05-07 15:28:00 -03:00
Sergio Chouhy
a1a7924c85 ignore advisories since there's no available upgrade 2026-05-07 15:07:32 -03:00
Sergio Chouhy
8f6a519f0e clippy 2026-05-07 13:45:51 -03:00
Daniil Polyakov
f6d56cb3e4
Merge pull request #471 from Thompsonmina/add-getAccountAtBlock-rpc
Add getAccountAtBlock RPC to indexer
2026-05-07 19:26:04 +03:00
Moudy
69b81ea621 fix: address review feedback, persist group data in wallet storage 2026-05-07 17:35:51 +02:00
Sergio Chouhy
2d7d50646d add tests 2026-05-07 12:27:51 -03:00
moudyellaz
bda50f1d2f refactor(privacy_preserving_circuit): extract output module
Refs: #454
2026-05-07 16:54:57 +02:00
Pravdyvy
45ccf14e77 fix(indexer_ffi): move into indexer 2026-05-07 14:32:03 +03:00
Pravdyvy
6054ae113a fix(indexer_ffi): suggestion fix 2026-05-07 14:29:06 +03:00
Sergio Chouhy
f722d257a3 fmt 2026-05-07 01:41:35 -03:00
Sergio Chouhy
d4334c4694 move privateaccountkind to program module 2026-05-07 01:38:41 -03:00
Sergio Chouhy
755b49654e minor change to test 2026-05-07 01:32:37 -03:00
Thompsonmina
7250056f15 feat(indexer): add getAccountAtBlock RPC method 2026-05-07 04:17:51 +01:00
Sergio Chouhy
61dd8ec9b3 fix test 2026-05-07 00:16:28 -03:00
Sergio Chouhy
d24931c643 refactor proxy program 2026-05-06 15:59:42 -03:00
moudyellaz
ce3229f74f refactor(privacy_preserving_circuit)!: scaffold bin-dir layout
BREAKING-CHANGE: PRIVACY_PRESERVING_CIRCUIT_ID changes (one-time). In-flight proofs against the old guest become invalid.
Refs: #454
2026-05-06 19:10:05 +02:00
moudyellaz
fd82d0784d chore: open draft PR for #454
Refs: #454
2026-05-06 17:15:16 +02:00
Moudy
f73cd6738f refactor: delegate to auth-transfer, add shared account test 2026-05-06 14:22:50 +02:00
Moudy
5bf24b191d test: add unit tests for SharedAccountEntry and shared account derivation 2026-05-06 14:22:50 +02:00
Moudy
d0a88e91e1 feat: extend sync to scan shared accounts with GMS-derived keys 2026-05-06 14:22:49 +02:00
Moudy
cd545819e7 feat(wallet)!: add group CLI commands with --for-gms account creation
BREAKING CHANGE: `NewSubcommand::Private` has new required fields (`for_gms`, `pda`, `seed`, `program_id`). Code constructing this variant must include them (use `None`/`false` for defaults). `shared_accounts` value type changed from `Account` to `SharedAccountEntry`.
2026-05-06 14:22:40 +02:00
Moudy
7be0ed926c feat(wallet)!: add derive_keys_for_shared_account and PrivateShared variant
BREAKING CHANGE: `pda_accounts` field in NSSAUserData renamed to `shared_accounts`. `PrivacyPreservingAccount` enum has a new `PrivateShared` variant, exhaustive matches must handle it.
2026-05-06 14:22:10 +02:00
Sergio Chouhy
fb4ddb055a Merge branch 'main' into schouhy/diversify-private-pdas-by-identifier 2026-05-06 00:22:58 -03:00
Sergio Chouhy
1599fc655c add tests 2026-05-05 21:17:15 -03:00
Pravdyvy
0e914adf0a fix: merge fix 2026-05-05 15:45:24 +03:00
Pravdyvy
f3472ce87a
Merge branch 'main' into Pravdyvy/indexer-query-api 2026-05-05 14:39:14 +03:00
Daniil Polyakov
332bd29e93
Merge pull request #389 from logos-blockchain/pg/zone-sdk-lez
feat(sequencer, indexer): Use zone-sdk instead of bedrock client
2026-05-05 14:36:16 +03:00
Moudy
cf6eab2538
Merge pull request #449 from logos-blockchain/moudy/feat-group-key-holder
feat: GroupKeyHolder for GMS key management
2026-05-05 13:09:54 +02:00
Moudy
9e207450d6 fix: resolve merge conflicts with main 2026-05-05 12:37:54 +02:00
Sergio Chouhy
11949e9fa1 use privateaccountkind in storage and fix circuit 2026-05-04 21:40:30 -03:00
Sergio Chouhy
7d5e1492c4 update insert_private_account_data to take account kind instead of identifier 2026-05-04 20:12:54 -03:00
Sergio Chouhy
71ad4e0c85 fix account id computation in wallet to account for pdas 2026-05-04 18:11:38 -03:00
Sergio Chouhy
95afb2065d use privateaccuontkind in privacy circuit 2026-05-04 18:07:56 -03:00
Moudy
51f718d9fb
Merge pull request #462 from logos-blockchain/moudy/feat-strong-type-circuit-input 2026-05-04 20:10:41 +02:00
Petar Radovic
5bcb1d468b unstaged file 2026-05-01 09:25:37 +02:00
Petar Radovic
c263a98231 address comments 2026-05-01 09:12:24 +02:00
Sergio Chouhy
dd4670ab2f encrypt privateaccountkind instead of identifier 2026-05-01 01:21:48 -03:00
Sergio Chouhy
64a8ea5807 add privateaccountkind 2026-05-01 00:49:28 -03:00
Sergio Chouhy
0eb128e515 minor refactor 2026-05-01 00:45:51 -03:00
Sergio Chouhy
8d9fa1224e remove fixed identifier for pdas 2026-05-01 00:26:38 -03:00
Sergio Chouhy
fb48c82717 add identifier to private pda formula 2026-05-01 00:06:23 -03:00
Moudy
8a8bac8b69 ci: address fmt-rs, lint, and unit-tests failures from the merge 2026-04-30 22:16:26 +02:00
jonesmarvin8
f37454ed1e
Refactor signatures (#457)
* fix BIP-340 signatures for fixed sized messages

* fmt

* fix unit test

* Removed privacy keycard calls

* Revert "Removed privacy keycard calls"

This reverts commit d70ef505a1f40b87159099761f5fce5a31e3f17b.

* Add domain separators

* CI fixes

* add hash_message tests

* fix deny

* addressed comments
2026-04-30 14:21:47 -04:00
Moudy
8517906025 Merge branch 'main' into moudy/feat-strong-type-circuit-input 2026-04-30 20:17:47 +02:00
Daniil Polyakov
970696b217
Merge pull request #463 from logos-blockchain/arjentix/indexer-mock-improvements
chore: add new blocks every 30 seconds to mock indexer service
2026-04-30 19:02:42 +03:00
Moudy
4690ca56cc fix: remove epoch and ratcheting, fix PDA identifier 2026-04-30 16:34:26 +02:00
Moudy
b9ceda98cf fix: rebuild artifacts 2026-04-30 16:04:48 +02:00
Moudy
98da9b26cc fix: address PR review feedback
- Rename PrivacyPreservingCircuitInputAccount to InputAccountIdentity (drop the PrivacyPreservingCircuit prefix; add Identity suffix)
- Rename PrivacyPreservingCircuitInput.accounts to account_identities
- Rename AccountManager.accounts() to account_identities() and loop variables to account_identity
- Drop legacy mask-1/2/3 references from variant doc comments and guest comments
- Remove the explanatory comments about deleted parallel-vec tests; moved to the PR description
- Rebake privacy_preserving_circuit and test program artifacts
2026-04-30 15:46:36 +02:00
Moudy
4c28133448 fix: resolve merge conflicts 2026-04-30 15:04:33 +02:00
Petar Radovic
af3a31509e machete 2026-04-30 11:29:49 +02:00
Petar Radovic
0920e086d9 merge main 2026-04-30 11:21:01 +02:00
Petar Radovic
265a269da0 remove indexer dep from sequencer: 2026-04-30 11:13:50 +02:00
Moudy
f375a35929 fix: address PR review feedback
- Add SealingPublicKey/SealingSecretKey type aliases for seal_for/unseal
- Generalize PrivateGroupPda to PrivatePda with pre-resolved keys
- Rename group_pda_spender to private_pda_spender
- Rename group_pda_accounts to pda_accounts with serde alias
- Remove unused storage_mut()
- Remove stale group_pda_router.bin artifact
2026-04-30 09:11:08 +02:00
Sergio Chouhy
9894319389
Merge pull request #447 from logos-blockchain/schouhy/generalize-npk-to-multiple-accounts
Generalize Npk to multiple accounts
2026-04-29 21:40:22 -03:00
Daniil Polyakov
f3ab618268 chore: satisfy cargo deny 2026-04-29 23:58:24 +03:00
Daniil Polyakov
b0a48883e1 chore: add new blocks every 30 seconds to mock indexer service
Co-authored-by: Copilot <copilot@github.com>
2026-04-29 23:58:24 +03:00
Sergio Chouhy
8025780e26 fix deny 2026-04-29 16:46:07 -03:00
Sergio Chouhy
18642f9b6c artifacts 2026-04-29 13:31:22 -03:00
Sergio Chouhy
72756e8622 Merge branch 'main' into schouhy/generalize-npk-to-multiple-accounts 2026-04-29 12:28:05 -03:00
Pravdyvy
06a6983ef3 fix: tests updated 2026-04-29 17:24:00 +03:00
Moudy
c327f592bf merge: resolve Cargo.lock conflict with main 2026-04-29 15:38:09 +02:00
Moudy
27649560bc fix: bump testcontainers to resolve RUSTSEC-2026-0112 and RUSTSEC-2026-0113 2026-04-29 15:34:45 +02:00
Petar Radovic
9f6468682a clippy 2026-04-29 15:26:11 +02:00
Moudy
460d7e7fd2 fix: fmt 2026-04-29 14:54:24 +02:00
Petar Radovic
fede4977b7 clippy 2026-04-29 14:38:20 +02:00
Petar Radovic
798b89839c checkpoints 2026-04-29 14:05:23 +02:00
Petar Radovic
7aecc4faba cargo deny 2026-04-29 13:23:13 +02:00
Petar Radovic
c27e30fe57 tests pass 2026-04-29 13:20:29 +02:00
Pravdyvy
113a68c22c fix: correct free 2026-04-29 13:55:44 +03:00
Petar Radovic
d24313d999 new image 2026-04-29 12:52:38 +02:00
Petar Radovic
7944a77251 use new docker image 2026-04-29 12:50:17 +02:00
Petar Radovic
81a9ec401c indexer stream 2026-04-29 11:06:05 +02:00
Petar Radovic
6bcbe058ed merge master 2026-04-29 10:33:07 +02:00
Moudy
198eac1cf1 refactor: address PR #449 review feedback 2026-04-29 10:11:37 +02:00
Moudy
55a4a1d83b ci: fix fmt-rs, deny advisory, and rebake artifacts 2026-04-29 08:54:19 +02:00
Moudy
f7349656c7 refactor: strong-type PrivacyPreservingCircuitInput with per-account enum 2026-04-29 07:37:30 +02:00
Pravdyvy
1338dec951 Merge branch 'Pravdyvy/indexer-ffi-spawns-rpc-for-communication' into Pravdyvy/indexer-query-api 2026-04-28 19:45:05 +03:00
Pravdyvy
a201fc646c fix: main merge 2026-04-28 19:42:58 +03:00
Pravdyvy
89ea884207 feat: working queries 2026-04-28 19:36:23 +03:00
Sergio Chouhy
ad2b4b66e4 expand docs 2026-04-28 00:28:39 -03:00
Sergio Chouhy
a23e44a8df fmt and clippy 2026-04-28 00:18:57 -03:00
Sergio Chouhy
6738d8ef28 update docs 2026-04-28 00:14:55 -03:00
Sergio Chouhy
06681ef39d add KAT 2026-04-28 00:09:28 -03:00
Sergio Chouhy
aea397565d add test. Remove private sync skip when no private accounts 2026-04-28 00:04:42 -03:00
Sergio Chouhy
f512a3bf0f refactor wallet config to use identifiers instead of the redundant account_id field 2026-04-27 23:12:30 -03:00
Sergio Chouhy
f0b89f8acb use vec<identifiers> in persistentaccountdataprivate to avoid the hacky workaround with identifier=0 for unused accounts 2026-04-27 21:09:33 -03:00
Sergio Chouhy
c3f47f6685 use option<identifier> for all wallet commands 2026-04-27 19:57:25 -03:00
Sergio Chouhy
eb3d3d8a8d simplify insert account logic 2026-04-27 18:48:28 -03:00
Sergio Chouhy
924b30650c change pda reserved identifier 2026-04-27 18:45:19 -03:00
Daniil Polyakov
cf3639d825
Merge pull request #458 from logos-blockchain/arjentix/fix-clock-tx-in-indexer
Fix clock transaction validation in Indexer
2026-04-27 18:48:00 +03:00
Pravdyvy
478ba4c2f2
Merge pull request #427 from logos-blockchain/Pravdyvy/indexer-ffi-spawns-rpc-for-communication
Simple indexer FFI
2026-04-27 17:18:03 +03:00
Pravdyvy
be8f5a6db2 fix: comments 2 2026-04-27 15:44:46 +03:00
Pravdyvy
37f59281c0 fix: all types added 2026-04-27 15:38:06 +03:00
Moudy
9927e6e690 fix: rebuild artifacts 2026-04-27 14:37:15 +02:00
Daniil Polyakov
88102d6964 fix: skip check on state for clock transaction in indexer storage 2026-04-27 15:34:21 +03:00
Pravdyvy
02949e961a
Merge branch 'main' into Pravdyvy/indexer-ffi-spawns-rpc-for-communication 2026-04-27 13:58:10 +03:00
Moudy
636fc9dd30 fix: rebuild artifacts 2026-04-27 02:45:06 +02:00
Moudy
5b9cf95c47 feat: add group PDA test program, unit tests, and integration test 2026-04-27 02:44:16 +02:00
Moudy
48f95b1b7a feat: add GroupKeyHolder storage and PrivateGroupPda wallet variant 2026-04-27 02:43:51 +02:00
Moudy
f3215606fb feat: add GroupKeyHolder with per-PDA derivation, epoch ratchet, and seal/unseal 2026-04-27 02:43:26 +02:00
Sergio Chouhy
85a6763490 artifacts 2026-04-24 19:31:13 -03:00
Sergio Chouhy
e09cb6284e enforce reserved identifier for private pda 2026-04-24 18:00:54 -03:00
Sergio Chouhy
52992a124a fix identifier for pda 2026-04-24 17:04:40 -03:00
Pravdyvy
b736b229ef fix: types halfway done 2026-04-24 17:37:09 +03:00
Pravdyvy
063ad8e476 Merge branch 'main' into Pravdyvy/indexer-query-api 2026-04-24 16:32:06 +03:00
Sergio Chouhy
7c45b5af3c Merge branch 'main' into schouhy/generalize-npk-to-multiple-accounts 2026-04-24 01:04:55 -03:00
Sergio Chouhy
584bfb2052 clippy 2026-04-24 00:42:54 -03:00
Sergio Chouhy
6f9c3b2af3 fmt 2026-04-24 00:37:36 -03:00
Sergio Chouhy
9c90a6d182 remove unused impl 2026-04-24 00:36:25 -03:00
Sergio Chouhy
e19c9ff20a return impl iterator 2026-04-24 00:04:22 -03:00
Sergio Chouhy
4719b1265a replace typedef with struct 2026-04-23 23:44:31 -03:00
Sergio Chouhy
a5565e0875 make identifier random by default for wallet cli send commands 2026-04-23 23:31:21 -03:00
Pravdyvy
9fc2e39c10 feat: first query api 2026-04-23 17:07:19 +03:00
Moudy
00d3140490
Merge pull request #446 from logos-blockchain/moudy/feat-private-pdas
feat: private PDA support in the privacy circuit
2026-04-22 23:07:18 +02:00
Pravdyvy
9880a46bdc feat: indexer client added to ffi 2026-04-22 17:39:27 +03:00
Moudy
86ff3670c0 fix: bump rustls-webpki to 0.103.13 for RUSTSEC-2026-0104
Upstream advisory, reachable panic in certificate revocation list
parsing via `BorrowedCertRevocationList::from_der` /
`OwnedCertRevocationList::from_der`. Unrelated to this PR, dropped
into the advisory DB since the last green CI run and broke the `deny`
job. Fix is the recommended version bump.
2026-04-22 16:02:56 +02:00
Moudy
e5b77a27d5 refactor: localize private_pda_npk_by_position and extract authorization helper
Addresses the following review comments from @Arjentix:

- "I think we can move this into `derive_from_outputs()`"
  (on the position → npk map construction in main())
  I moved the construction inside ExecutionState::derive_from_outputs
  and stored the map as a field of ExecutionState. derive_from_outputs
  now takes `private_account_keys` directly and builds the map as part
  of state initialization. main() no longer owns the intermediate
  structure. validate_and_sync_states reads the npk through
  self.private_pda_npk_by_position.

- "Let's move this whole `is_authorized` computation into a separate
  function. This became really bulky"
  I extracted the caller-seeds resolution, family-binding recording,
  and is_authorized computation into a free function
  `resolve_authorization_and_record_bindings`. It takes the three
  field borrows it needs (`&mut pda_family_binding`, `&mut
  private_pda_bound_positions`, `&private_pda_npk_by_position`), same
  shape as `assert_family_binding`. A method would have conflicted
  with the `&mut self.post_states` borrow held by the Occupied match
  arm; the free function lets rustc split-borrow the self fields.
2026-04-22 15:55:35 +02:00
Moudy
22aa5ef70b refactor: simplify PDA API docs and rename compute_authorized_pdas
Addresses the following review comments from @Arjentix:

- "I think there are too many internal implementation information
  exposed here. This structure is used by our users, program devs. And
  they should not care about distinction between private or public pda
  or different masks"
  (on ChainedCall.pda_seeds, same feedback repeated on Claim::Pda)
  I rewrote both docstrings to drop internal details (visibility masks,
  per-form derivation names, npk handling). Program devs see only that
  they emit a seed and the `AccountId` is derived from
  `(program_id, seed)` regardless of whether the account is public or
  private.

- "Let's reflect the new nuance in the name"
  (on compute_authorized_pdas returning public-form derivations only)
  I renamed the function to `compute_public_authorized_pdas`. After
  the PR #446 rework the function only returns public-form
  derivations, the private-form authorization lives in the circuit
  guest. Updated the call site in nssa/src/validated_state_diff.rs
  and the two unit tests.
2026-04-22 15:34:15 +02:00
Pravdyvy
ad6a55c55d fix: lint fix 1 2026-04-22 07:42:02 +03:00
Sergio Chouhy
9d2abc76a1 fix tests 2026-04-21 22:39:14 -03:00
Sergio Chouhy
145198a078 fix test 2026-04-21 21:32:47 -03:00
Sergio Chouhy
b4d883e275 fix test 2026-04-21 19:34:08 -03:00
Sergio Chouhy
670527c2f1 Merge branch 'main' into schouhy/generalize-npk-to-multiple-accounts 2026-04-21 18:53:24 -03:00
Pravdyvy
33557b122f fix: comments fix 1 2026-04-21 17:46:16 +03:00
Moudy
0183eac5cc refactor: unify PDA AccountId construction via AccountId::for_{public,private}_pda
Addresses the following review comment:

- "I think this should be a constructor `AccountId::for_private_pda`.
  Consider also removing the existing `impl From<(ProgramId, Seed)> for
  AccountId` for public pdas in favor of a `AccountId::for_public_pda`
  to have a unified way of constructing pdas"

I replaced `impl From<(&ProgramId, &PdaSeed)> for AccountId` with
`AccountId::for_public_pda(program_id: &ProgramId, seed: &PdaSeed) ->
Self` and replaced the free function `private_pda_account_id(...)`
with `AccountId::for_private_pda(program_id: &ProgramId, seed:
&PdaSeed, npk: &NullifierPublicKey) -> Self`. Both live in an inherent
`impl AccountId` block in nssa/core/src/program.rs next to the PDA
derivation logic. Migrated all call sites across nssa/core,
nssa/src/state.rs, nssa/src/validated_state_diff.rs,
program_methods/guest/src/bin/privacy_preserving_circuit.rs,
programs/amm/core, programs/associated_token_account/core, the example
tail-call binary, and the ATA tutorial doc. Test function names that
referenced the old free function were also renamed
(private_pda_account_id_* to for_private_pda_*).
2026-04-21 12:35:19 +02:00
Sergio Chouhy
3ec166ff7c bring back new private account command for simplicity 2026-04-21 02:35:50 -03:00
Sergio Chouhy
42842dfbb1 clippy 2026-04-20 22:07:03 -03:00
Moudy
68d43d7f2b test: exercise callee authorization in private-PDA delegation tests
Addresses the following review comments:

- "Shouldn't we use a program that checks authorization in this test as
  callee? If not, I'm not sure if we are fully testing what the test
  docs describe (namely, that the callee got the input account with
  is_authorized=true). Maybe add a variant of the noop that checks the
  input account is authorized."
  I added test_program_methods/guest/src/bin/auth_asserting_noop.rs:
  same shape as noop.rs except it asserts pre.is_authorized == true for
  every pre_state before echoing the post_states. Any unauthorized
  pre_state panics the guest, failing the whole circuit proof. I added
  Program::auth_asserting_noop() as the matching helper. In
  caller_pda_seeds_authorize_private_pda_for_callee and
  caller_pda_seeds_with_wrong_seed_rejects_private_pda_for_callee, I
  swapped Program::noop() for Program::auth_asserting_noop() as the
  callee. The positive test now proves the callee actually sees
  is_authorized=true, not just that the circuit's consistency check did
  not reject. The negative test doubles its evidence, both the
  circuit's authorization reconciliation and the callee guest would now
  reject a wrong-seed delegation.

- "This branching logic is only correct because we are not supporting
  non-authorized private accounts with non-default values. Likely to be
  changed in the future. I'm sure there's use cases for this. For
  example the multisig program if ran completely private it would need
  a private non-default and non-authorized input account."
  Agreed. Supporting this needs wallet-supplied `(seed, owner)` side
  input so the npk-to-account_id binding can be re-verified for an
  existing private PDA without a fresh Claim::Pda or a caller
  pda_seeds match. I handled this in the second PR. I added a
  TODO(private-pdas-pr-2/3) marker on the `else` branch in
  privacy_preserving_circuit.rs:3 => { ... } so the constraint is
  visible to future maintainers, along with a comment noting the
  multisig use case.
2026-04-21 02:08:02 +02:00
Moudy
d22c142a37 fix: rebuild artifacts 2026-04-21 01:44:29 +02:00
Moudy
e8b17eef27 refactor: rename mask3 to private_pda in tests and circuit
Addresses the following review comments:

- "I'd rename all mask_3 references in test names and variables to a
  private pda wording. If in the future we change the mask number for
  the private pda, this naming will silently get outdated."
  I renamed all tests and the local variable mask3_account to
  private_pda_account.

- "Let's use more descriptive names. `mask3` is not very meaningful."
  I renamed all `mask3` into `private_pda`. Panic messages and .expect
  strings updated to match. Doc comments that factually describe the
  encoding (e.g. "mask-3 account" meaning "an account whose visibility
  mask is 3") are left as-is since they are accurate and remain stable
  until the mask value itself changes.

- "..._panics" to "..._fails"
  Covered above. The tests assert Err(CircuitProvingError), so
  execute_and_prove returns an Err, the test process itself never
  panics.

- "we can return `Some((*seed, true, caller))` to avoid having to unwrap
  the `caller_program_id` again in line 290"
  I changed matched_caller_seed from Option<(PdaSeed, bool)> to
  Option<(PdaSeed, bool, ProgramId)>, return the `caller` captured by
  the enclosing and_then from each match arm, and dropped the .expect
  at the consumer site. Bundled with the rename since both touch the
  same branch and a single guest ELF rebuild covers them.
2026-04-21 01:43:57 +02:00
Moudy
00cae12d41 docs: drop "wallet" references from nssa crate
Addresses the following review comments:

- "I'd keep this crate independent of wallet references"
  I replaced all with "supplied npk".

- Rename request on locals attested_keys/wallet_keys
  In mask_3_wallet_npk_mismatch_panics the two key sets play distinct
  roles, one produces the pre_state's account_id (the registered pair)
  and the other is supplied in private_account_keys as the mismatched
  npk. Collapsing both to `keys` would be misleading. I renamed to
  keys_a and keys_b with an inline comment noting which one is the
  registered one and which one is mismatched.
2026-04-21 01:02:22 +02:00
Moudy
34f8b6cac8 docs: split miscoupled private-PDA test docs and clean phrasing
Addresses the following review comments:

- "Isn't two_mask_3_claims_under_same_seed_are_rejected already checking
  that there's a mechanism protecting against this exploit scenario?"
  The doc block at nssa/src/state.rs:2488-2504 mixes three paragraphs,
  one about reuse, one TODO about wallet side input, one exploit pin,
  all attached to two_mask_3_claims_under_same_seed_are_rejected. The
  reuse test below it had no doc at all. I split as follows: the
  exploit-pin paragraph stays on two_mask_3_claims_..., the reuse
  paragraph moves to a fresh docstring on
  mask_3_reuse_across_txs_currently_unsupported.

- "I don't understand this. I think this should fail because ... the
  input pre_state which is marked with is_authorized=true will make
  things fail."
  The reuse test's new docstring cites the actual reject site, the
  post-loop private_pda_bound_positions assertion in
  privacy_preserving_circuit.rs:185-192. At top level the Entry::Vacant
  arm accepts is_authorized=true unconditionally, the rejection comes
  from the bound-positions check firing because noop emits no Claim::Pda
  and there is no caller ChainedCall.pda_seeds.

- "let's dont have this TODO as part of the doc"
  The block is moved out into regular // comments immediately above
  mask_3_reuse_across_txs_currently_unsupported.

- "let's not add implementation details to docs"
  In caller_pda_seeds_authorize_mask_3_private_pda_for_callee's
  docstring, I dropped the parenthetical "(Occupied branch)" and the
  trailing sentence about which validate_and_sync_states code path gets
  exercised.

- "what does \`Claim::Pda(seed)\` / \`pda_seeds\` mean?"
  I rewrote the pda_family_binding docstring at
  privacy_preserving_circuit.rs:33-39: replaced the ambiguous
  "Claim::PrivatePda and ChainedCall's private seeds into plain
  Claim::Pda(seed) / pda_seeds" phrase with "a Claim::Pda(seed) in a
  program's post_state or a caller's ChainedCall.pda_seeds entry".

- Suggestion on nssa/src/validated_state_diff.rs:226 rewriting
  "The public-execution path only sees mask-0 accounts" to
  "The public-execution path only sees public accounts".
  Applied: "The public-execution path only sees public accounts".

- Clarification requested on the private_pda_bound_positions field:
  I expanded the docstring at privacy_preserving_circuit.rs:26-31 to
  state that binding is an idempotent property, not an event, and to
  enumerate the two proof paths that populate it (a Claim::Pda on a
  mask-3 pre_state, or a caller's pda_seeds matching under the private
  derivation).
2026-04-21 00:37:06 +02:00
Sergio Chouhy
7ccd6ae331 wip 2026-04-20 11:27:15 -03:00
Sergio Chouhy
6316f59777 fmt 2026-04-19 23:13:51 -03:00
Sergio Chouhy
c43418721e remove unused methods 2026-04-19 23:13:33 -03:00
Sergio Chouhy
20a068a9e3 update shell completions 2026-04-19 21:16:18 -03:00
Sergio Chouhy
c30d435155 update nonce init formula to depend on account id instead of just npk 2026-04-19 19:28:10 -03:00
Sergio Chouhy
3dfbea9b66 update token transfer tutorial 2026-04-17 19:45:42 -03:00
Sergio Chouhy
a42144cb3c minor refactor. update ffi 2026-04-17 19:45:30 -03:00
Sergio Chouhy
b34e301023 rollback and remove identifier arguments in new private account wallet command 2026-04-17 14:26:50 -03:00
Sergio Chouhy
f28ac0f092 return account id on new private account creation 2026-04-17 12:52:07 -03:00
Pravdyvy
cb2a0ab4ee fix: deny fix 1 2026-04-17 17:58:04 +03:00
Moudy
9e4e546c9e ci: reduce debug info in dev/test profiles to avoid LLD OOM 2026-04-17 16:10:20 +02:00
Moudy
d5f97c3de4 fix: rebuild artifacts 2026-04-17 15:38:50 +02:00
Moudy
d3577f02bc fix: reject multiple family members under same (program, seed) in one tx 2026-04-17 15:36:20 +02:00
Pravdyvy
57831443e2 fix: nix update 2 2026-04-17 14:38:08 +03:00
Pravdyvy
173f7ef58a fix: nix update 1 2026-04-17 14:33:58 +03:00
Moudy
f9a5a7635e refactor: make programs privacy-agnostic in the privacy circuit 2026-04-17 07:29:40 +02:00
Sergio Chouhy
a4e8b53817 fix wallet storage bug 2026-04-17 00:09:32 -03:00
Sergio Chouhy
3cf7972425 add identifier to ciphertext and use it on sync mechanism 2026-04-16 23:22:40 -03:00
Moudy
48478ca21e fix: rebuild artifacts 2026-04-16 20:12:39 +02:00
Moudy
ca5f421188 fix: add private_pda_seeds to ChainedCall literals in example guests 2026-04-16 19:41:26 +02:00
Moudy
4b09aae852 fix: doc backticks and rebuild artifacts 2026-04-16 19:26:27 +02:00
Moudy
390bf59660 fix: nightly fmt 2026-04-16 19:11:46 +02:00
Moudy
d1fd6fe945 fix: clippy issue 2026-04-16 19:07:27 +02:00
Moudy
bbb0aae17a fix: rebuild artifacts 2026-04-16 19:02:54 +02:00
Moudy
ba93ec6f3e fix: clippy issue 2026-04-16 19:00:11 +02:00
Moudy
8da04ac898 fix: nightly fmt 2026-04-16 18:45:19 +02:00
Moudy
93c6921eaf Merge remote-tracking branch 'origin/main' into moudy/feat-private-pdas
# Conflicts:
#	artifacts/program_methods/amm.bin
#	artifacts/program_methods/associated_token_account.bin
#	artifacts/program_methods/authenticated_transfer.bin
#	artifacts/program_methods/clock.bin
#	artifacts/program_methods/pinata.bin
#	artifacts/program_methods/pinata_token.bin
#	artifacts/program_methods/privacy_preserving_circuit.bin
#	artifacts/program_methods/token.bin
#	artifacts/test_program_methods/burner.bin
#	artifacts/test_program_methods/chain_caller.bin
#	artifacts/test_program_methods/changer_claimer.bin
#	artifacts/test_program_methods/claimer.bin
#	artifacts/test_program_methods/clock_chain_caller.bin
#	artifacts/test_program_methods/data_changer.bin
#	artifacts/test_program_methods/extra_output.bin
#	artifacts/test_program_methods/flash_swap_callback.bin
#	artifacts/test_program_methods/flash_swap_initiator.bin
#	artifacts/test_program_methods/malicious_authorization_changer.bin
#	artifacts/test_program_methods/malicious_caller_program_id.bin
#	artifacts/test_program_methods/malicious_self_program_id.bin
#	artifacts/test_program_methods/minter.bin
#	artifacts/test_program_methods/missing_output.bin
#	artifacts/test_program_methods/modified_transfer.bin
#	artifacts/test_program_methods/nonce_changer.bin
#	artifacts/test_program_methods/noop.bin
#	artifacts/test_program_methods/pinata_cooldown.bin
#	artifacts/test_program_methods/program_owner_changer.bin
#	artifacts/test_program_methods/simple_balance_transfer.bin
#	artifacts/test_program_methods/time_locked_transfer.bin
#	artifacts/test_program_methods/validity_window.bin
#	artifacts/test_program_methods/validity_window_chain_caller.bin
#	nssa/core/src/program.rs
#	nssa/src/state.rs
2026-04-16 18:25:57 +02:00
Moudy
ac09e785a9 fix: rebuild artifacts 2026-04-16 18:07:46 +02:00
Moudy
526c3cd978 test: add private PDA circuit tests and two guest programs 2026-04-16 18:07:32 +02:00
Moudy
f1b2c04f3d fix: rebuild artifacts 2026-04-16 17:38:41 +02:00
Moudy
1fd4e4e8d9 test: pin private_pda_account_id formula against hardcoded value 2026-04-16 17:15:34 +02:00
Moudy
661ef7c4e9 fix: rebuild artifacts 2026-04-16 16:54:02 +02:00
Moudy
bda21fb5c5 refactor: move private PDA npk into proven ChainedCall and Claim 2026-04-16 16:53:54 +02:00
Sergio Chouhy
4c050f35dd refactor private key tree to store a vec<(Identifier, AccountId)> 2026-04-16 03:03:13 -03:00
Sergio Chouhy
0ecec7016e refactor key trees 2026-04-15 23:34:49 -03:00
Sergio Chouhy
3a3358e389 adapt wallet ffi 2026-04-15 19:35:48 -03:00
Moudy
e08c8f93b4 fix: rebuild artifacts 2026-04-16 00:23:08 +02:00
Sergio Chouhy
0fd2682d2e add identifier arguments to cli commands 2026-04-15 19:01:58 -03:00
Moudy
b1553109ae fix: validate no duplicate entries in private_pda_info 2026-04-16 00:01:25 +02:00
Moudy
8b9cc5accf fix: rebuild artifacts 2026-04-15 23:29:13 +02:00
Sergio Chouhy
4ab8696d85 update facades to receive identifiers 2026-04-15 17:21:16 -03:00
Moudy
a27da19a45 fix: nightly fmt 2026-04-15 22:10:02 +02:00
Moudy
40a1227871 fix: clippy lint issues 2026-04-15 22:08:26 +02:00
Sergio Chouhy
8fd25bc4bf add identifier to PrivacyPreservingAccount to allow passing different identifiers 2026-04-15 16:51:20 -03:00
Moudy
4bdb1e7a22 fix: rebuild artifacts for CI 2026-04-15 21:31:33 +02:00
Moudy
47843eaa3e fix: nightly fmt and clippy issues (item ordering, doc backticks, integer suffix) 2026-04-15 21:10:22 +02:00
Moudy
b0c10ee5a2 fix: cargo fmt, add #[must_use] to private_pda_account_id, rebuild artifacts 2026-04-15 21:10:22 +02:00
Moudy
7e63f9ddcd test: add unit tests for private PDA AccountId derivation and compute_authorized_pdas 2026-04-15 21:10:21 +02:00
Moudy
cee9608ea7 feat: enable PDA claim verification for private PDA accounts 2026-04-15 21:10:21 +02:00
Moudy
308e1ebebf feat: add mask 3 branch in compute_circuit_output for private PDAs 2026-04-15 21:10:21 +02:00
Moudy
10b26ca223 feat: thread private_pda_info through the privacy circuit and extend compute_authorized_pdas 2026-04-15 21:10:21 +02:00
Moudy
3b78462e2d feat: add private_pda_info field to PrivacyPreservingCircuitInput 2026-04-15 21:10:21 +02:00
Moudy
ac98fba1b1 feat: add private PDA AccountId derivation function 2026-04-15 21:10:21 +02:00
Sergio Chouhy
985f610cea wip 2026-04-15 15:42:04 -03:00
Sergio Chouhy
a4af8da13b replace npk for account id in commitment and init nullifier formulas 2026-04-14 23:45:34 -03:00
Sergio Chouhy
12b8c0ad31 add identifier to account id formula 2026-04-14 22:10:52 -03:00
Sergio Chouhy
dae617c673 wip: add dummy identifier 2026-04-14 18:02:38 -03:00
Pravdyvy
e11d4968d0 fix: postfixes 2026-04-14 16:01:33 +03:00
Pravdyvy
b864ff22d4 Merge branch 'main' into Pravdyvy/indexer-ffi-spawns-rpc-for-communication 2026-04-14 10:58:18 +03:00
Pravdyvy
dd3ac54318 fix: all ffi tests added 2026-04-14 10:51:57 +03:00
Pravdyvy
a921d63750 Merge branch 'main' into Pravdyvy/indexer-ffi-spawns-rpc-for-communication 2026-04-13 16:01:14 +03:00
Pravdyvy
5fc397c2ee fix: ci fix 2026-04-13 15:53:31 +03:00
Pravdyvy
5f86e597d5 fix: tests running 2026-04-13 13:34:01 +03:00
Pravdyvy
1ae7192c7a fix: trying to run tests 2026-04-03 15:50:24 +03:00
Pravdyvy
2cf7f5d724 fix: machete fix 2026-04-02 15:58:26 +03:00
Pravdyvy
cf420291e3 feat: indexer ffi added 2026-04-02 15:49:42 +03:00
Petar Radovic
fee8d1623f lints 2026-03-18 16:57:13 +01:00
Petar Radovic
5191370360 feat(sequencer): use logos-blockchain zone-sdk 2026-03-18 16:24:22 +01:00
481 changed files with 34033 additions and 16567 deletions

View File

@ -13,7 +13,10 @@ ignore = [
{ id = "RUSTSEC-2025-0055", reason = "`tracing-subscriber` v0.2.25 pulled in by ark-relations v0.4.0 - will be addressed before mainnet" },
{ id = "RUSTSEC-2025-0141", reason = "`bincode` is unmaintained but continuing to use it." },
{ id = "RUSTSEC-2023-0089", reason = "atomic-polyfill is pulled transitively via risc0-zkvm; waiting on upstream fix (see https://github.com/risc0/risc0/issues/3453)" },
{ id = "RUSTSEC-2026-0097", reason = "`rand` v0.8.5 is present transitively from logos crates, modification may break integration" },
{ id = "RUSTSEC-2026-0118", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" },
{ id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" },
{ id = "RUSTSEC-2024-0370", reason = "transitive dependency of `logos-blockchain-http-api-common`, can't do anything than wait for upstream fix" },
]
yanked = "deny"
unused-ignored-advisory = "deny"
@ -54,6 +57,7 @@ unused-allowed-license = "deny"
allow-git = [
"https://github.com/EspressoSystems/jellyfish.git",
"https://github.com/logos-blockchain/logos-blockchain.git",
"https://github.com/logos-blockchain/logos-blockchain-circuits.git",
]
unknown-git = "deny"
unknown-registry = "deny"

View File

@ -16,4 +16,4 @@ runs:
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
run: |
curl -sSL https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/main/scripts/setup-logos-blockchain-circuits.sh | bash
curl -sSL https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/6ac348bea4160ca708b70a86b3964e9f1ce82fff/scripts/setup-logos-blockchain-circuits.sh | bash

44
.github/workflows/bench-regression.yml vendored Normal file
View File

@ -0,0 +1,44 @@
on:
pull_request:
paths:
- "tools/crypto_primitives_bench/**"
- "lez/key_protocol/**"
- "lee/core/**"
- ".github/workflows/bench-regression.yml"
permissions:
contents: read
pull-requests: write
name: bench-regression
jobs:
crypto-primitives:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
# criterion-compare-action checks out the base branch in a second
# working tree, so we need the full history.
fetch-depth: 0
- uses: ./.github/actions/install-system-deps
- uses: ./.github/actions/install-risc0
- uses: ./.github/actions/install-logos-blockchain-circuits
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install active toolchain
run: rustup install
- name: Run criterion-compare against base branch
uses: boa-dev/criterion-compare-action@v3
with:
branchName: ${{ github.base_ref }}
cwd: tools/crypto_primitives_bench
benchName: primitives
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -94,6 +94,12 @@ jobs:
- name: Install active toolchain
run: rustup install
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Lint workspace
env:
RISC0_SKIP_BUILD: "1"
@ -123,6 +129,12 @@ jobs:
- name: Install active toolchain
run: rustup install
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
run: cargo install --locked cargo-nextest
@ -132,9 +144,74 @@ jobs:
RUST_LOG: "info"
run: cargo nextest run --workspace --exclude integration_tests --all-features
integration-tests-prebuild:
runs-on: ubuntu-latest
outputs:
targets: ${{ steps.discover-targets.outputs.targets }}
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
- uses: ./.github/actions/install-system-deps
- uses: ./.github/actions/install-risc0
- uses: ./.github/actions/install-logos-blockchain-circuits
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install active toolchain
run: rustup install
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
run: cargo install --locked cargo-nextest
- name: Build integration test archive
env:
RISC0_DEV_MODE: "1"
run: cargo nextest archive -p integration_tests --archive-file integration-tests.tar.zst --no-pager
- name: Upload integration test archive
uses: actions/upload-artifact@v4
with:
name: integration-tests-archive
path: integration-tests.tar.zst
- name: Discover integration test targets from archive
id: discover-targets
run: |
cargo nextest list \
--archive-file integration-tests.tar.zst \
--list-type binaries-only \
--message-format json \
--no-pager > integration-tests-binaries.json
targets_json="$(jq -c '[."rust-binaries" | to_entries[] | select(.value.kind == "test" and .value."binary-name" != "tps") | .value."binary-name"] | sort | unique' integration-tests-binaries.json)"
if [[ "$targets_json" == "[]" ]]; then
echo "No integration test targets were discovered." >&2
exit 1
fi
echo "targets=$targets_json" >> "$GITHUB_OUTPUT"
echo "Discovered integration targets: $targets_json"
integration-tests:
needs: integration-tests-prebuild
runs-on: ubuntu-latest
timeout-minutes: 60
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.integration-tests-prebuild.outputs.targets) }}
name: integration-tests (${{ matrix.target }})
steps:
- uses: actions/checkout@v5
with:
@ -151,33 +228,10 @@ jobs:
- name: Install active toolchain
run: rustup install
- name: Install nextest
run: cargo install --locked cargo-nextest
- name: Run tests
env:
RISC0_DEV_MODE: "1"
RUST_LOG: "info"
run: cargo nextest run -p integration_tests -- --skip tps_test --skip indexer
integration-tests-indexer:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Download integration test archive
uses: actions/download-artifact@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
- uses: ./.github/actions/install-system-deps
- uses: ./.github/actions/install-risc0
- uses: ./.github/actions/install-logos-blockchain-circuits
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install active toolchain
run: rustup install
name: integration-tests-archive
- name: Install nextest
run: cargo install --locked cargo-nextest
@ -186,11 +240,11 @@ jobs:
env:
RISC0_DEV_MODE: "1"
RUST_LOG: "info"
run: cargo nextest run -p integration_tests indexer -- --skip tps_test
run: cargo nextest run --archive-file integration-tests.tar.zst -E "binary(${{ matrix.target }})"
valid-proof-test:
runs-on: ubuntu-latest
timeout-minutes: 60
timeout-minutes: 90
steps:
- uses: actions/checkout@v5
with:
@ -207,6 +261,12 @@ jobs:
- name: Install active toolchain
run: rustup install
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Test valid proof
env:
RUST_LOG: "info"
@ -224,8 +284,14 @@ jobs:
- uses: ./.github/actions/install-risc0
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install just
run: cargo install just
run: cargo install --locked just
- name: Build artifacts
run: just build-artifacts

View File

@ -13,18 +13,18 @@ jobs:
matrix:
include:
- name: sequencer_service
dockerfile: ./sequencer/service/Dockerfile
dockerfile: ./lez/sequencer/service/Dockerfile
build_args: |
STANDALONE=false
- name: sequencer_service-standalone
dockerfile: ./sequencer/service/Dockerfile
dockerfile: ./lez/sequencer/service/Dockerfile
build_args: |
STANDALONE=true
- name: indexer_service
dockerfile: ./indexer/service/Dockerfile
dockerfile: ./lez/indexer/service/Dockerfile
build_args: ""
- name: explorer_service
dockerfile: ./explorer_service/Dockerfile
dockerfile: ./lez/explorer_service/Dockerfile
build_args: ""
steps:
- uses: actions/checkout@v5

3
.gitignore vendored
View File

@ -12,3 +12,6 @@ result
wallet-ffi/wallet_ffi.h
bedrock_signing_key
integration_tests/configs/debug/
venv/
keycard_wallet/python/__pycache__/
keycard_wallet/python/keycard-py/

84
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,84 @@
# Contributing
We're glad you're interested in contributing to Logos Execution Zone!
This document describes the guidelines for contributing to the project. We will be updating it as we grow and we figure out what works best for us.
If you have any questions, come say hi to our [Discord](https://discord.gg/tGJwgGrSPN)!
## Commit title format
We use [Conventional Commits](https://www.conventionalcommits.org/).
Use:
- `type(scope): description`
- `type(scope)!: description` for breaking changes
Allowed `type` values:
- `feat`
- `fix`
- `chore`
- `docs`
- `test`
- `refactor`
- `perf`
- `build`
- `ci`
- `revert`
Examples:
- `feat(lee): add private PDA support`
- `fix(wallet): correct fee calculation`
- `feat(lee)!: rename AccountId::from((prog, seed)) to AccountId::for_public_pda`
Breaking changes:
- Mark with `!` in the title.
`CHANGELOG.md` is generated from these markers on every `v*` tag via `git-cliff`, and GitHub Releases are created from the same content.
## Pull requests
PR titles should follow the same Conventional Commits format:
- `type(scope): description`
- `type(scope)!: description` for breaking changes
Before marking a PR as ready for review:
- Fill out the PR template.
Breaking changes in PRs:
- Optionally add a `BREAKING CHANGE:` footer in the PR body with migration notes.
Before merging a PR, consider squashing non-meaningful commits. E.g.:
```
- refactor(wallet): move user keys to a separate module
- revert(wallet): revert "refactor(wallet): move user keys to a separate module"
```
Could be squashed to an empty commit if they belong to the same PR.
## Branch workflow
When bringing your feature branch up to date, prefer rebasing on top of `main`.
- Preferred: `git rebase main`
- Avoid: `git merge main` in feature branches
This keeps commit history cleaner and makes reviews easier.
## Useful commands
We have [`Justfile`](./Justfile) which contains some useful utilities which may help you.
To list all of them run the command: `just`.
Any change to our core crates may invalidate our RISC0 [`artifacts`](./artifacts/), in that case you're required to run `just build-artifacts` to update them.
## AI-assisted contributions
AI tools are allowed for drafting code, docs, tests, and review suggestions.
Requirements:
- A human author is fully responsible for all submitted code and text.
- The person opening the PR must review, verify, and be able to explain every change.
- Do not open PRs automatically via AI agents or bots. Automatic AI-created PRs are not allowed.

3760
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,14 @@ license = "MIT or Apache-2.0"
resolver = "3"
members = [
"integration_tests",
"storage",
"key_protocol",
"mempool",
"wallet",
"wallet-ffi",
"common",
"nssa",
"nssa/core",
"lez/storage",
"lee/key_protocol",
"lee/state_machine",
"lee/state_machine/core",
"lez/mempool",
"lez/wallet",
"lez/wallet-ffi",
"lez/common",
"programs/amm/core",
"programs/amm",
"programs/clock/core",
@ -20,15 +20,19 @@ members = [
"programs/token",
"programs/associated_token_account/core",
"programs/associated_token_account",
"sequencer/core",
"sequencer/service",
"sequencer/service/protocol",
"sequencer/service/rpc",
"indexer/core",
"indexer/service",
"indexer/service/protocol",
"indexer/service/rpc",
"explorer_service",
"programs/authenticated_transfer/core",
"programs/faucet/core",
"programs/bridge/core",
"programs/vault/core",
"lez/sequencer/core",
"lez/sequencer/service",
"lez/sequencer/service/protocol",
"lez/sequencer/service/rpc",
"lez/indexer/core",
"lez/indexer/service",
"lez/indexer/service/protocol",
"lez/indexer/service/rpc",
"lez/explorer_service",
"program_methods",
"program_methods/guest",
"test_program_methods",
@ -36,27 +40,35 @@ members = [
"examples/program_deployment",
"examples/program_deployment/methods",
"examples/program_deployment/methods/guest",
"bedrock_client",
"testnet_initial_state",
"lez/testnet_initial_state",
"lez/indexer/ffi",
"lez",
"lez/keycard_wallet",
"test_fixtures",
"tools/cycle_bench",
"tools/crypto_primitives_bench",
"tools/integration_bench",
]
[workspace.dependencies]
nssa = { path = "nssa" }
nssa_core = { path = "nssa/core" }
common = { path = "common" }
mempool = { path = "mempool" }
storage = { path = "storage" }
key_protocol = { path = "key_protocol" }
sequencer_core = { path = "sequencer/core" }
sequencer_service_protocol = { path = "sequencer/service/protocol" }
sequencer_service_rpc = { path = "sequencer/service/rpc" }
sequencer_service = { path = "sequencer/service" }
indexer_core = { path = "indexer/core" }
indexer_service = { path = "indexer/service" }
indexer_service_protocol = { path = "indexer/service/protocol" }
indexer_service_rpc = { path = "indexer/service/rpc" }
wallet = { path = "wallet" }
wallet-ffi = { path = "wallet-ffi", default-features = false }
lee = { path = "lee/state_machine" }
lee_core = { path = "lee/state_machine/core" }
common = { path = "lez/common" }
mempool = { path = "lez/mempool" }
storage = { path = "lez/storage" }
key_protocol = { path = "lee/key_protocol" }
sequencer_core = { path = "lez/sequencer/core" }
sequencer_service_protocol = { path = "lez/sequencer/service/protocol" }
sequencer_service_rpc = { path = "lez/sequencer/service/rpc" }
sequencer_service = { path = "lez/sequencer/service" }
indexer_core = { path = "lez/indexer/core" }
indexer_service = { path = "lez/indexer/service" }
indexer_service_protocol = { path = "lez/indexer/service/protocol" }
indexer_service_rpc = { path = "lez/indexer/service/rpc" }
wallet = { path = "lez/wallet" }
wallet-ffi = { path = "lez/wallet-ffi", default-features = false }
indexer_ffi = { path = "lez/indexer/ffi" }
lez = { path = "lez" }
clock_core = { path = "programs/clock/core" }
token_core = { path = "programs/token/core" }
token_program = { path = "programs/token" }
@ -64,9 +76,14 @@ amm_core = { path = "programs/amm/core" }
amm_program = { path = "programs/amm" }
ata_core = { path = "programs/associated_token_account/core" }
ata_program = { path = "programs/associated_token_account" }
authenticated_transfer_core = { path = "programs/authenticated_transfer/core" }
faucet_core = { path = "programs/faucet/core" }
bridge_core = { path = "programs/bridge/core" }
vault_core = { path = "programs/vault/core" }
test_program_methods = { path = "test_program_methods" }
bedrock_client = { path = "bedrock_client" }
testnet_initial_state = { path = "testnet_initial_state" }
testnet_initial_state = { path = "lez/testnet_initial_state" }
keycard_wallet = { path = "lez/keycard_wallet" }
test_fixtures = { path = "test_fixtures" }
tokio = { version = "1.50", features = [
"net",
@ -75,9 +92,10 @@ tokio = { version = "1.50", features = [
"fs",
] }
tokio-util = "0.7.18"
risc0-zkvm = { version = "3.0.5", features = ['std'] }
risc0-zkvm = { version = "3.0.5", default-features = false, features = ['std'] }
risc0-build = "3.0.5"
anyhow = "1.0.98"
derive_more = "2.1.1"
num_cpus = "1.13.1"
openssl = { version = "0.10", features = ["vendored"] }
openssl-probe = { version = "0.1.2" }
@ -120,11 +138,13 @@ tokio-retry = "0.3.0"
schemars = "1.2"
async-stream = "0.3.6"
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" }
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" }
logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" }
logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" }
logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" }
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-zone-sdk = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-http-api-common = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
rocksdb = { version = "0.24.0", default-features = false, features = [
"snappy",
@ -138,12 +158,16 @@ k256 = { version = "0.13.3", features = [
"serde",
"pem",
] }
ml-kem = { version = "0.3", features = ["hazmat"] }
elliptic-curve = { version = "0.13.8", features = ["arithmetic"] }
actix-web = { version = "4.13.0", default-features = false, features = [
"macros",
] }
clap = { version = "4.5.42", features = ["derive", "env"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] }
pyo3 = { version = "0.24", features = ["auto-initialize"] }
zeroize = "1"
criterion = { version = "0.8", features = ["html_reports"] }
# Profile for leptos WASM release builds
[profile.wasm-release]
@ -152,6 +176,14 @@ opt-level = 'z'
lto = true
codegen-units = 1
# Keep backtraces but drop full DWARF type info to avoid LLD OOM/SIGBUS when
# linking large integration-test binaries on resource-constrained CI runners.
[profile.dev]
debug = "line-tables-only"
[profile.test]
debug = "line-tables-only"
[workspace.lints.rust]
warnings = "deny"

View File

@ -23,6 +23,12 @@ test:
@echo "🧪 Running tests"
RISC0_DEV_MODE=1 cargo nextest run --no-fail-fast
# Run criterion benches: fast crypto primitives, then the slow PPE verify (real proving setup).
bench:
@echo "📊 Running criterion benches"
cargo bench -p crypto_primitives_bench --bench primitives
cargo bench -p cycle_bench --features ppe --bench verify
# Run Bedrock node in docker
[working-directory: 'bedrock']
run-bedrock:
@ -30,13 +36,13 @@ run-bedrock:
docker compose up
# Run Sequencer
[working-directory: 'sequencer/service']
[working-directory: 'lez/sequencer/service']
run-sequencer:
@echo "🧠 Running sequencer"
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p sequencer_service configs/debug/sequencer_config.json
# Run Indexer
[working-directory: 'indexer/service']
[working-directory: 'lez/indexer/service']
run-indexer mock="":
@echo "🔍 Running indexer"
@if [ "{{mock}}" = "mock" ]; then \
@ -48,23 +54,23 @@ run-indexer mock="":
fi
# Run Explorer
[working-directory: 'explorer_service']
[working-directory: 'lez/explorer_service']
run-explorer:
@echo "🌐 Running explorer"
RUST_LOG=info cargo leptos serve
# Run Wallet
[working-directory: 'wallet']
[working-directory: 'lez/wallet']
run-wallet +args:
@echo "🔑 Running wallet"
NSSA_WALLET_HOME_DIR=$(pwd)/configs/debug cargo run --release -p wallet -- {{args}}
LEE_WALLET_HOME_DIR=$(pwd)/configs/debug cargo run --release -p wallet -- {{args}}
# Clean runtime data
clean:
@echo "🧹 Cleaning run artifacts"
rm -rf sequencer/service/bedrock_signing_key
rm -rf sequencer/service/rocksdb
rm -rf indexer/service/rocksdb
rm -rf wallet/configs/debug/storage.json
rm -rf lez/sequencer/service/bedrock_signing_key
rm -rf lez/sequencer/service/rocksdb
rm -rf lez/indexer/service/rocksdb
rm -rf lez/wallet/configs/debug/storage.json
rm -rf rocksdb
cd bedrock && docker compose down -v

View File

@ -80,7 +80,7 @@ For each tag we publish docker images of our services.
If you depend on this project you can pin your rust dependency to a git tag like this:
```toml
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.1.0" }
lee_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.1.0" }
```
# Install dependencies
@ -134,7 +134,7 @@ RISC0_DEV_MODE=1 cargo test --release
### Integration tests
```bash
export NSSA_WALLET_HOME_DIR=$(pwd)/integration_tests/configs/debug/wallet/
export LEE_WALLET_HOME_DIR=$(pwd)/integration_tests/configs/debug/wallet/
cd integration_tests
# RISC0_DEV_MODE=1 skips proof generation; RUST_LOG=info enables runtime logs
RUST_LOG=info RISC0_DEV_MODE=1 cargo run $(pwd)/configs/debug all
@ -152,17 +152,17 @@ The sequencer and logos blockchain node can be run locally:
- `cargo build --all-features`
- `./target/debug/logos-blockchain-node --deployment nodes/node/standalone-deployment-config.yaml nodes/node/standalone-node-config.yaml`
- Alternatively (WARNING: This node is outdated) go to `logos-blockchain/lssa/` repo and run the node from docker:
- Alternatively (WARNING: This node is outdated) go to `logos-blockchain/logos-execution-zone/` repo and run the node from docker:
- `cd bedrock`
- Change line 14 of `docker-compose.yml` from `"0:18080/tcp"` into `"8080:18080/tcp"`
- `docker compose up`
2. On another terminal go to the `logos-blockchain/lssa` repo and run indexer service:
- `RUST_LOG=info cargo run -p indexer_service indexer/service/configs/indexer_config.json`
2. On another terminal go to the `logos-blockchain/logos-execution-zone` repo and run indexer service:
- `RUST_LOG=info cargo run -p indexer_service lez/indexer/service/configs/indexer_config.json`
3. On another terminal go to the `logos-blockchain/lssa` repo and run the sequencer:
- `RUST_LOG=info cargo run -p sequencer_service sequencer/service/configs/debug/sequencer_config.json`
4. (To run the explorer): on another terminal go to `logos-blockchain/lssa/explorer_service` and run the following:
3. On another terminal go to the `logos-blockchain/logos-execution-zone` repo and run the sequencer:
- `RUST_LOG=info cargo run -p sequencer_service lez/sequencer/service/configs/debug/sequencer_config.json`
4. (To run the explorer): on another terminal go to `logos-blockchain/logos-execution-zone/lez/explorer_service` and run the following:
- `cargo install cargo-leptos`
- `cargo leptos build --release`
- `cargo leptos serve --release`
@ -171,9 +171,9 @@ The sequencer and logos blockchain node can be run locally:
After stopping services above you need to remove 3 folders to start cleanly:
1. In the `logos-blockchain/logos-blockchain` folder `state` (not needed in case of docker setup)
2. In the `lssa` folder `sequencer/service/rocksdb`
3. In the `lssa` file `sequencer/service/bedrock_signing_key`
4. In the `lssa` folder `indexer/service/rocksdb`
2. In the `logos-execution-zone` folder `lez/sequencer/service/rocksdb`
3. In the `logos-execution-zone` file `lez/sequencer/service/bedrock_signing_key`
4. In the `logos-execution-zone` folder `lez/indexer/service/rocksdb`
### Normal mode (`just` commands)
We provide a `Justfile` for developer and user needs, you can run the whole setup with it. The only difference will be that logos-blockchain (bedrock) will be started from docker.
@ -220,7 +220,7 @@ This will use a wallet binary built from this repo and not the one installed in
### Standalone mode
The sequencer can be run in standalone mode with:
```bash
RUST_LOG=info cargo run --features standalone -p sequencer_service sequencer/service/configs/debug
RUST_LOG=info cargo run --features standalone -p sequencer_service lez/sequencer/service/configs/debug
```
## Running with Docker
@ -231,7 +231,7 @@ You can run the whole setup with Docker:
docker compose up
```
With that you can send transactions from local wallet to the Sequencer running inside Docker using `wallet/configs/debug` as well as exploring blocks by opening `http://localhost:8080`.
With that you can send transactions from local wallet to the Sequencer running inside Docker using `lez/wallet/configs/debug` as well as exploring blocks by opening `http://localhost:8080`.
## Caution for local image builds

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.

Binary file not shown.

View File

@ -39,42 +39,44 @@ cryptarchia:
threshold: 1
timestamp: 0
gossipsub_protocol: /integration/logos-blockchain/cryptarchia/proto/1.0.0
genesis_state:
mantle_tx:
ops:
genesis_block:
header:
version: Bedrock
parent_block: '0000000000000000000000000000000000000000000000000000000000000000'
slot: 0
block_root: b5f8787ac23674822414c70eea15d842da38f2e806ede1a73cf7b5cf0277da07
proof_of_leadership:
proof: '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
entropy_contribution: '0000000000000000000000000000000000000000000000000000000000000000'
leader_key: '0000000000000000000000000000000000000000000000000000000000000000'
voucher_cm: '0000000000000000000000000000000000000000000000000000000000000000'
signature: '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
transactions:
- mantle_tx:
ops:
- opcode: 0
payload:
inputs: [ ]
inputs: []
outputs:
- value: 1
pk: d204000000000000000000000000000000000000000000000000000000000000
- value: 100
pk: 2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26
- value: 1
pk: d204000000000000000000000000000000000000000000000000000000000000
- value: 100
pk: '2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26'
- value: 1
pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717
- opcode: 17
payload:
channel_id: "0000000000000000000000000000000000000000000000000000000000000000"
inscription: [ 103, 101, 110, 101, 115, 105, 115 ] # "genesis" in bytes
parent: "0000000000000000000000000000000000000000000000000000000000000000"
signer: "0000000000000000000000000000000000000000000000000000000000000000"
execution_gas_price: 0
storage_gas_price: 0
ops_proofs:
- !ZkSig
pi_a: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
pi_b: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
pi_c: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
- NoProof
channel_id: '0000000000000000000000000000000000000000000000000000000000000000'
# chain_id_len=12 (u64_le), chain_id=logos-devnet (utf-8),
# genesis_time=2026-01-10T07:47:56Z (u64_le), epoch_nonce=[0u8; 32]
inscription: '0c000000000000006c6f676f732d6465766e65742c046269000000000000000000000000000000000000000000000000000000000000000000000000'
parent: '0000000000000000000000000000000000000000000000000000000000000000'
signer: '0000000000000000000000000000000000000000000000000000000000000000'
execution_gas_price: 0
storage_gas_price: 0
ops_proofs:
- !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
- !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
time:
slot_duration: '1.0'
chain_start_time: PLACEHOLDER_CHAIN_START_TIME

View File

@ -1,7 +1,7 @@
services:
logos-blockchain-node-0:
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:c5243681b353278cabb562a176f0a5cfbefc2056f18cebc47fe0e3720c29fb12
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:f160cfbf898a06554451cc066d84cfd0f8ab62d59bd3e62d9cde3bd5582c12ab
ports:
- "${PORT:-8080}:18080/tcp"
volumes:

View File

@ -1,23 +0,0 @@
[package]
name = "bedrock_client"
version = "0.1.0"
edition = "2024"
license = { workspace = true }
[lints]
workspace = true
[dependencies]
common.workspace = true
reqwest.workspace = true
anyhow.workspace = true
tokio-retry.workspace = true
futures.workspace = true
log.workspace = true
serde.workspace = true
humantime-serde.workspace = true
logos-blockchain-common-http-client.workspace = true
logos-blockchain-core.workspace = true
logos-blockchain-chain-broadcast-service.workspace = true
logos-blockchain-chain-service.workspace = true

View File

@ -1,121 +0,0 @@
use std::time::Duration;
use anyhow::{Context as _, Result};
use common::config::BasicAuth;
use futures::{Stream, TryFutureExt as _};
#[expect(clippy::single_component_path_imports, reason = "Satisfy machete")]
use humantime_serde;
use log::{info, warn};
pub use logos_blockchain_chain_broadcast_service::BlockInfo;
use logos_blockchain_chain_service::CryptarchiaInfo;
pub use logos_blockchain_common_http_client::{CommonHttpClient, Error};
pub use logos_blockchain_core::{block::Block, header::HeaderId, mantle::SignedMantleTx};
use reqwest::{Client, Url};
use serde::{Deserialize, Serialize};
use tokio_retry::Retry;
/// Fibonacci backoff retry strategy configuration.
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct BackoffConfig {
#[serde(with = "humantime_serde")]
pub start_delay: Duration,
pub max_retries: usize,
}
impl Default for BackoffConfig {
fn default() -> Self {
Self {
start_delay: Duration::from_millis(100),
max_retries: 5,
}
}
}
/// Simple wrapper
/// maybe extend in the future for our purposes
/// `Clone` is cheap because `CommonHttpClient` is internally reference counted (`Arc`).
#[derive(Clone)]
pub struct BedrockClient {
http_client: CommonHttpClient,
node_url: Url,
backoff: BackoffConfig,
}
impl BedrockClient {
pub fn new(backoff: BackoffConfig, node_url: Url, auth: Option<BasicAuth>) -> Result<Self> {
info!("Creating Bedrock client with node URL {node_url}");
let client = Client::builder()
//Add more fields if needed
.timeout(std::time::Duration::from_mins(1))
.build()
.context("Failed to build HTTP client")?;
let auth = auth.map(|a| {
logos_blockchain_common_http_client::BasicAuthCredentials::new(a.username, a.password)
});
let http_client = CommonHttpClient::new_with_client(client, auth);
Ok(Self {
http_client,
node_url,
backoff,
})
}
pub async fn post_transaction(&self, tx: SignedMantleTx) -> Result<Result<(), Error>, Error> {
Retry::spawn(self.backoff_strategy(), || async {
match self
.http_client
.post_transaction(self.node_url.clone(), tx.clone())
.await
{
Ok(()) => Ok(Ok(())),
Err(err) => match err {
// Retry arm.
// Retrying only reqwest errors: mainly connected to http.
Error::Request(_) => Err(err),
// Returning non-retryable error
Error::Server(_) | Error::Client(_) | Error::Url(_) => Ok(Err(err)),
},
}
})
.await
}
pub async fn get_lib_stream(&self) -> Result<impl Stream<Item = BlockInfo>, Error> {
self.http_client.get_lib_stream(self.node_url.clone()).await
}
pub async fn get_block_by_id(
&self,
header_id: HeaderId,
) -> Result<Option<Block<SignedMantleTx>>, Error> {
Retry::spawn(self.backoff_strategy(), || {
self.http_client
.get_block_by_id(self.node_url.clone(), header_id)
.inspect_err(|err| warn!("Block fetching failed with error: {err:#}"))
})
.await
}
pub async fn get_consensus_info(&self) -> Result<CryptarchiaInfo, Error> {
Retry::spawn(self.backoff_strategy(), || {
self.http_client
.consensus_info(self.node_url.clone())
.inspect_err(|err| warn!("Block fetching failed with error: {err:#}"))
})
.await
}
fn backoff_strategy(&self) -> impl Iterator<Item = Duration> {
let start_delay_millis = self
.backoff
.start_delay
.as_millis()
.try_into()
.expect("Start delay must be less than u64::MAX milliseconds");
tokio_retry::strategy::FibonacciBackoff::from_millis(start_delay_millis)
.take(self.backoff.max_retries)
}
}

View File

@ -1,85 +0,0 @@
use nssa::AccountId;
use crate::{
HashType,
block::{Block, HashableBlockData},
transaction::NSSATransaction,
};
// Helpers
#[must_use]
pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([37; 32]).unwrap()
}
// Dummy producers
/// Produce dummy block with.
///
/// `id` - block id, provide zero for genesis.
///
/// `prev_hash` - hash of previous block, provide None for genesis.
///
/// `transactions` - vector of `EncodedTransaction` objects.
#[must_use]
pub fn produce_dummy_block(
id: u64,
prev_hash: Option<HashType>,
transactions: Vec<NSSATransaction>,
) -> Block {
let block_data = HashableBlockData {
block_id: id,
prev_block_hash: prev_hash.unwrap_or_default(),
timestamp: id.saturating_mul(100),
transactions,
};
block_data.into_pending_block(&sequencer_sign_key_for_testing(), [0; 32])
}
#[must_use]
pub fn produce_dummy_empty_transaction() -> NSSATransaction {
let program_id = nssa::program::Program::authenticated_transfer_program().id();
let account_ids = vec![];
let nonces = vec![];
let instruction_data: u128 = 0;
let message = nssa::public_transaction::Message::try_new(
program_id,
account_ids,
nonces,
instruction_data,
)
.unwrap();
let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]);
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
NSSATransaction::Public(nssa_tx)
}
#[must_use]
pub fn create_transaction_native_token_transfer(
from: AccountId,
nonce: u128,
to: AccountId,
balance_to_move: u128,
signing_key: &nssa::PrivateKey,
) -> NSSATransaction {
let account_ids = vec![from, to];
let nonces = vec![nonce.into()];
let program_id = nssa::program::Program::authenticated_transfer_program().id();
let message = nssa::public_transaction::Message::try_new(
program_id,
account_ids,
nonces,
balance_to_move,
)
.unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
NSSATransaction::Public(nssa_tx)
}

View File

@ -1,6 +1,6 @@
# Wallet CLI Completion
Completion scripts for the LSSA `wallet` command.
Completion scripts for the LEZ `wallet` command.
## ZSH
@ -93,6 +93,12 @@ Only `Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH` is used for completio
exec zsh
```
> **Note:** After updating the completion script, re-run step 1 to copy the new file, then rebuild the cache:
> ```sh
> cp _wallet ~/.oh-my-zsh/custom/plugins/wallet/
> rm -rf ~/.zcompdump* && exec zsh
> ```
### Requirements
The completion script calls `wallet account list` to dynamically fetch account IDs. Ensure the `wallet` command is in your `$PATH`.
@ -197,8 +203,7 @@ wallet account get --account-id <TAB>
2. Rebuild the completion cache:
```sh
rm -f ~/.zcompdump*
exec zsh
rm -rf ~/.zcompdump* && exec zsh
```
### Account IDs not completing

View File

@ -46,7 +46,7 @@ _wallet() {
cword=$COMP_CWORD
}
local commands="auth-transfer chain-info account pinata token amm check-health config restore-keys deploy-program help"
local commands="auth-transfer chain-info account pinata token amm ata check-health config restore-keys deploy-program help"
# Find the main command and subcommand by scanning words before the cursor.
# Global options that take a value are skipped along with their argument.
@ -127,10 +127,10 @@ _wallet() {
--to-label)
_wallet_complete_account_label "$cur"
;;
--to-npk | --to-vpk | --amount)
--to-npk | --to-vpk | --to-identifier | --amount)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --amount" -- "$cur"))
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --to-identifier --amount" -- "$cur"))
;;
esac
;;
@ -187,11 +187,11 @@ _wallet() {
sync-private)
;; # no options
new)
# `account new` is itself a subcommand: public | private
# `account new` is itself a subcommand: public | private-accounts-key
local new_subcmd=""
for ((i = subcmd_idx + 1; i < cword; i++)); do
case "${words[$i]}" in
public | private)
public | private-accounts-key)
new_subcmd="${words[$i]}"
break
;;
@ -199,13 +199,26 @@ _wallet() {
done
if [[ -z "$new_subcmd" ]]; then
COMPREPLY=($(compgen -W "public private" -- "$cur"))
COMPREPLY=($(compgen -W "public private-accounts-key" -- "$cur"))
else
case "$prev" in
--cci | -l | --label)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--cci -l --label" -- "$cur"))
case "$new_subcmd" in
public)
case "$prev" in
--cci | -l | --label)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--cci -l --label" -- "$cur"))
;;
esac
;;
private-accounts-key)
case "$prev" in
--cci)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--cci" -- "$cur"))
;;
esac
;;
esac
fi
@ -289,10 +302,10 @@ _wallet() {
--to-label)
_wallet_complete_account_label "$cur"
;;
--to-npk | --to-vpk | --amount)
--to-npk | --to-vpk | --to-identifier | --amount)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --amount" -- "$cur"))
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --to-identifier --amount" -- "$cur"))
;;
esac
;;
@ -331,10 +344,10 @@ _wallet() {
--holder-label)
_wallet_complete_account_label "$cur"
;;
--holder-npk | --holder-vpk | --amount)
--holder-npk | --holder-vpk | --holder-identifier | --amount)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--definition --definition-label --holder --holder-label --holder-npk --holder-vpk --amount" -- "$cur"))
COMPREPLY=($(compgen -W "--definition --definition-label --holder --holder-label --holder-npk --holder-vpk --holder-identifier --amount" -- "$cur"))
;;
esac
;;
@ -344,7 +357,7 @@ _wallet() {
amm)
case "$subcmd" in
"")
COMPREPLY=($(compgen -W "new swap add-liquidity remove-liquidity help" -- "$cur"))
COMPREPLY=($(compgen -W "new swap-exact-input swap-exact-output add-liquidity remove-liquidity help" -- "$cur"))
;;
new)
case "$prev" in
@ -373,7 +386,7 @@ _wallet() {
;;
esac
;;
swap)
swap-exact-input)
case "$prev" in
--user-holding-a)
_wallet_complete_account_id "$cur"
@ -394,6 +407,15 @@ _wallet() {
;;
esac
;;
swap-exact-output)
case "$prev" in
--user-holding-a | --user-holding-b | --exact-amount-out | --max-amount-in | --token-definition)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-b --exact-amount-out --max-amount-in --token-definition" -- "$cur"))
;;
esac
;;
add-liquidity)
case "$prev" in
--user-holding-a)
@ -451,6 +473,68 @@ _wallet() {
esac
;;
ata)
case "$subcmd" in
"")
COMPREPLY=($(compgen -W "address create send burn list help" -- "$cur"))
;;
address)
case "$prev" in
--owner | --token-definition)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--owner --token-definition" -- "$cur"))
;;
esac
;;
create)
case "$prev" in
--owner)
_wallet_complete_account_id "$cur"
;;
--token-definition)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--owner --token-definition" -- "$cur"))
;;
esac
;;
send)
case "$prev" in
--from)
_wallet_complete_account_id "$cur"
;;
--to | --token-definition | --amount)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--from --token-definition --to --amount" -- "$cur"))
;;
esac
;;
burn)
case "$prev" in
--holder)
_wallet_complete_account_id "$cur"
;;
--token-definition | --amount)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--holder --token-definition --amount" -- "$cur"))
;;
esac
;;
list)
case "$prev" in
--owner | --token-definition)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--owner --token-definition" -- "$cur"))
;;
esac
;;
esac
;;
config)
case "$subcmd" in
"")

View File

@ -24,6 +24,7 @@ _wallet() {
'pinata:Pinata program interaction subcommand'
'token:Token program interaction subcommand'
'amm:AMM program interaction subcommand'
'ata:Associated Token Account program interaction subcommand'
'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions'
'config:Command to setup config, get and set config fields'
'restore-keys:Restoring keys from given password at given depth'
@ -52,6 +53,9 @@ _wallet() {
amm)
_wallet_amm
;;
ata)
_wallet_ata
;;
config)
_wallet_config
;;
@ -72,7 +76,7 @@ _wallet() {
# auth-transfer subcommand
_wallet_auth_transfer() {
local -a subcommands
_arguments -C \
'1: :->subcommand' \
'*:: :->args'
@ -91,16 +95,17 @@ _wallet_auth_transfer() {
init)
_arguments \
'--account-id[Account ID to initialize]:account_id:_wallet_account_ids' \
'--account-label[Account label (alternative to --account-id)]:label:_wallet_account_labels'
'--account-label[Account label (alternative to --account-id)]:label:'
;;
send)
_arguments \
'--from[Source account ID]:from_account:_wallet_account_ids' \
'--from-label[Source account label (alternative to --from)]:label:_wallet_account_labels' \
'--from-label[From account label (alternative to --from)]:label:' \
'--to[Destination account ID (for owned accounts)]:to_account:_wallet_account_ids' \
'--to-label[Destination account label (alternative to --to)]:label:_wallet_account_labels' \
'--to-label[To account label (alternative to --to)]:label:' \
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
'--to-vpk[Destination viewing public key (for foreign private accounts)]:vpk:' \
'--to-identifier[Identifier for the recipient private account]:identifier:' \
'--amount[Amount of native tokens to send]:amount:'
;;
esac
@ -111,7 +116,7 @@ _wallet_auth_transfer() {
# chain-info subcommand
_wallet_chain_info() {
local -a subcommands
_arguments -C \
'1: :->subcommand' \
'*:: :->args'
@ -144,7 +149,7 @@ _wallet_chain_info() {
# account subcommand
_wallet_account() {
local -a subcommands
_arguments -C \
'1: :->subcommand' \
'*:: :->args'
@ -169,7 +174,7 @@ _wallet_account() {
'(-r --raw)'{-r,--raw}'[Get raw account data]' \
'(-k --keys)'{-k,--keys}'[Display keys (pk for public accounts, npk/vpk for private accounts)]' \
'(-a --account-id)'{-a,--account-id}'[Account ID to query]:account_id:_wallet_account_ids' \
'--account-label[Account label (alternative to --account-id)]:label:_wallet_account_labels'
'--account-label[Account label (alternative to --account-id)]:label:'
;;
list|ls)
_arguments \
@ -181,19 +186,27 @@ _wallet_account() {
'*:: :->new_args'
case $state in
account_type)
compadd public private
compadd public private-accounts-key
;;
new_args)
_arguments \
'--cci[Chain index of a parent node]:chain_index:' \
'(-l --label)'{-l,--label}'[Label to assign to the new account]:label:'
case $line[1] in
public)
_arguments \
'--cci[Chain index of a parent node]:chain_index:' \
'(-l --label)'{-l,--label}'[Label to assign to the new account]:label:'
;;
private-accounts-key)
_arguments \
'--cci[Chain index of a parent node]:chain_index:'
;;
esac
;;
esac
;;
label)
_arguments \
'(-a --account-id)'{-a,--account-id}'[Account ID to label]:account_id:_wallet_account_ids' \
'--account-label[Account label (alternative to --account-id)]:label:_wallet_account_labels' \
'--account-label[Account label (alternative to --account-id)]:label:' \
'(-l --label)'{-l,--label}'[The label to assign to the account]:label:'
;;
esac
@ -204,7 +217,7 @@ _wallet_account() {
# pinata subcommand
_wallet_pinata() {
local -a subcommands
_arguments -C \
'1: :->subcommand' \
'*:: :->args'
@ -222,7 +235,7 @@ _wallet_pinata() {
claim)
_arguments \
'--to[Destination account ID to receive claimed tokens]:to_account:_wallet_account_ids' \
'--to-label[Destination account label (alternative to --to)]:label:_wallet_account_labels'
'--to-label[To account label (alternative to --to)]:label:'
;;
esac
;;
@ -255,36 +268,38 @@ _wallet_token() {
'--name[Token name]:name:' \
'--total-supply[Total supply of tokens to mint]:total_supply:' \
'--definition-account-id[Account ID for token definition]:definition_account:_wallet_account_ids' \
'--definition-account-label[Definition account label (alternative to --definition-account-id)]:label:_wallet_account_labels' \
'--definition-account-label[Definition account label (alternative to --definition-account-id)]:label:' \
'--supply-account-id[Account ID to receive initial supply]:supply_account:_wallet_account_ids' \
'--supply-account-label[Supply account label (alternative to --supply-account-id)]:label:_wallet_account_labels'
'--supply-account-label[Supply account label (alternative to --supply-account-id)]:label:'
;;
send)
_arguments \
'--from[Source holding account ID]:from_account:_wallet_account_ids' \
'--from-label[Source account label (alternative to --from)]:label:_wallet_account_labels' \
'--from-label[From account label (alternative to --from)]:label:' \
'--to[Destination holding account ID (for owned accounts)]:to_account:_wallet_account_ids' \
'--to-label[Destination account label (alternative to --to)]:label:_wallet_account_labels' \
'--to-label[To account label (alternative to --to)]:label:' \
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
'--to-vpk[Destination viewing public key (for foreign private accounts)]:vpk:' \
'--to-identifier[Identifier for the recipient private account]:identifier:' \
'--amount[Amount of tokens to send]:amount:'
;;
burn)
_arguments \
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
'--definition-label[Definition account label (alternative to --definition)]:label:_wallet_account_labels' \
'--definition-label[Definition account label (alternative to --definition)]:label:' \
'--holder[Holder account ID]:holder_account:_wallet_account_ids' \
'--holder-label[Holder account label (alternative to --holder)]:label:_wallet_account_labels' \
'--holder-label[Holder account label (alternative to --holder)]:label:' \
'--amount[Amount of tokens to burn]:amount:'
;;
mint)
_arguments \
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
'--definition-label[Definition account label (alternative to --definition)]:label:_wallet_account_labels' \
'--definition-label[Definition account label (alternative to --definition)]:label:' \
'--holder[Holder account ID (for owned accounts)]:holder_account:_wallet_account_ids' \
'--holder-label[Holder account label (alternative to --holder)]:label:_wallet_account_labels' \
'--holder-label[Holder account label (alternative to --holder)]:label:' \
'--holder-npk[Holder nullifier public key (for foreign private accounts)]:npk:' \
'--holder-vpk[Holder viewing public key (for foreign private accounts)]:vpk:' \
'--holder-identifier[Identifier for the holder private account]:identifier:' \
'--amount[Amount of tokens to mint]:amount:'
;;
esac
@ -295,7 +310,7 @@ _wallet_token() {
# amm subcommand
_wallet_amm() {
local -a subcommands
_arguments -C \
'1: :->subcommand' \
'*:: :->args'
@ -304,7 +319,8 @@ _wallet_amm() {
subcommand)
subcommands=(
'new:Create a new liquidity pool'
'swap:Swap tokens using the AMM'
'swap-exact-input:Swap specifying exact input amount'
'swap-exact-output:Swap specifying exact output amount'
'add-liquidity:Add liquidity to an existing pool'
'remove-liquidity:Remove liquidity from a pool'
'help:Print this message or the help of the given subcommand(s)'
@ -316,32 +332,40 @@ _wallet_amm() {
new)
_arguments \
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
'--user-holding-a-label[User holding A account label (alternative to --user-holding-a)]:label:_wallet_account_labels' \
'--user-holding-a-label[User holding A label (alternative to --user-holding-a)]:label:' \
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
'--user-holding-b-label[User holding B account label (alternative to --user-holding-b)]:label:_wallet_account_labels' \
'--user-holding-b-label[User holding B label (alternative to --user-holding-b)]:label:' \
'--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
'--user-holding-lp-label[User holding LP account label (alternative to --user-holding-lp)]:label:_wallet_account_labels' \
'--user-holding-lp-label[User holding LP label (alternative to --user-holding-lp)]:label:' \
'--balance-a[Amount of token A to deposit]:balance_a:' \
'--balance-b[Amount of token B to deposit]:balance_b:'
;;
swap)
swap-exact-input)
_arguments \
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
'--user-holding-a-label[User holding A account label (alternative to --user-holding-a)]:label:_wallet_account_labels' \
'--user-holding-a-label[User holding A label (alternative to --user-holding-a)]:label:' \
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
'--user-holding-b-label[User holding B account label (alternative to --user-holding-b)]:label:_wallet_account_labels' \
'--user-holding-b-label[User holding B label (alternative to --user-holding-b)]:label:' \
'--amount-in[Amount of tokens to swap]:amount_in:' \
'--min-amount-out[Minimum tokens expected in return]:min_amount_out:' \
'--token-definition[Definition ID of the token being provided]:token_def:'
;;
swap-exact-output)
_arguments \
'--user-holding-a[User token A holding account ID]:holding_a:' \
'--user-holding-b[User token B holding account ID]:holding_b:' \
'--exact-amount-out[Exact amount of tokens expected out]:exact_amount_out:' \
'--max-amount-in[Maximum tokens to spend]:max_amount_in:' \
'--token-definition[Definition ID of the token being provided]:token_def:'
;;
add-liquidity)
_arguments \
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
'--user-holding-a-label[User holding A account label (alternative to --user-holding-a)]:label:_wallet_account_labels' \
'--user-holding-a-label[User holding A label (alternative to --user-holding-a)]:label:' \
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
'--user-holding-b-label[User holding B account label (alternative to --user-holding-b)]:label:_wallet_account_labels' \
'--user-holding-b-label[User holding B label (alternative to --user-holding-b)]:label:' \
'--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
'--user-holding-lp-label[User holding LP account label (alternative to --user-holding-lp)]:label:_wallet_account_labels' \
'--user-holding-lp-label[User holding LP label (alternative to --user-holding-lp)]:label:' \
'--max-amount-a[Maximum amount of token A to deposit]:max_amount_a:' \
'--max-amount-b[Maximum amount of token B to deposit]:max_amount_b:' \
'--min-amount-lp[Minimum LP tokens to receive]:min_amount_lp:'
@ -349,11 +373,11 @@ _wallet_amm() {
remove-liquidity)
_arguments \
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
'--user-holding-a-label[User holding A account label (alternative to --user-holding-a)]:label:_wallet_account_labels' \
'--user-holding-a-label[User holding A label (alternative to --user-holding-a)]:label:' \
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
'--user-holding-b-label[User holding B account label (alternative to --user-holding-b)]:label:_wallet_account_labels' \
'--user-holding-b-label[User holding B label (alternative to --user-holding-b)]:label:' \
'--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
'--user-holding-lp-label[User holding LP account label (alternative to --user-holding-lp)]:label:_wallet_account_labels' \
'--user-holding-lp-label[User holding LP label (alternative to --user-holding-lp)]:label:' \
'--balance-lp[Amount of LP tokens to burn]:balance_lp:' \
'--min-amount-a[Minimum token A to receive]:min_amount_a:' \
'--min-amount-b[Minimum token B to receive]:min_amount_b:'
@ -363,6 +387,61 @@ _wallet_amm() {
esac
}
# ata subcommand
_wallet_ata() {
local -a subcommands
_arguments -C \
'1: :->subcommand' \
'*:: :->args'
case $state in
subcommand)
subcommands=(
'address:Derive and print the Associated Token Account address (local only)'
'create:Create (or idempotently no-op) the Associated Token Account'
'send:Send tokens from owner ATA to a recipient token holding account'
'burn:Burn tokens from holder ATA'
'list:List all ATAs for a given owner across multiple token definitions'
'help:Print this message or the help of the given subcommand(s)'
)
_describe -t subcommands 'ata subcommands' subcommands
;;
args)
case $line[1] in
address)
_arguments \
'--owner[Owner account (no privacy prefix)]:owner:' \
'--token-definition[Token definition account (no privacy prefix)]:token_def:'
;;
create)
_arguments \
'--owner[Owner account with privacy prefix]:owner:_wallet_account_ids' \
'--token-definition[Token definition account (no privacy prefix)]:token_def:'
;;
send)
_arguments \
'--from[Sender account with privacy prefix]:from:_wallet_account_ids' \
'--token-definition[Token definition account (no privacy prefix)]:token_def:' \
'--to[Recipient account (no privacy prefix)]:to:' \
'--amount[Amount of tokens to send]:amount:'
;;
burn)
_arguments \
'--holder[Holder account with privacy prefix]:holder:_wallet_account_ids' \
'--token-definition[Token definition account (no privacy prefix)]:token_def:' \
'--amount[Amount of tokens to burn]:amount:'
;;
list)
_arguments \
'--owner[Owner account (no privacy prefix)]:owner:' \
'--token-definition[Token definition accounts (no privacy prefix)]:token_def:'
;;
esac
;;
esac
}
# config subcommand
_wallet_config() {
local -a subcommands
@ -435,6 +514,7 @@ _wallet_help() {
'pinata:Pinata program interaction subcommand'
'token:Token program interaction subcommand'
'amm:AMM program interaction subcommand'
'ata:Associated Token Account program interaction subcommand'
'check-health:Check the wallet can connect to the node'
'config:Command to setup config, get and set config fields'
'restore-keys:Restoring keys from given password at given depth'
@ -468,25 +548,4 @@ _wallet_account_ids() {
_multi_parts / accounts
}
# Helper function to complete account labels
# Uses `wallet account list` to get available labels
_wallet_account_labels() {
local -a labels
local line
if command -v wallet &>/dev/null; then
while IFS= read -r line; do
local label
# Extract label from [...] at end of line
label="${line##*\[}"
label="${label%\]}"
[[ -n "$label" && "$label" != "$line" ]] && labels+=("$label")
done < <(wallet account list 2>/dev/null)
fi
if (( ${#labels} > 0 )); then
compadd -a labels
fi
}
_wallet "$@"

View File

@ -1,160 +0,0 @@
{
"home": "./indexer/service",
"consensus_info_polling_interval": "1s",
"bedrock_client_config": {
"addr": "http://logos-blockchain-node-0:18080",
"backoff": {
"start_delay": "100ms",
"max_retries": 5
}
},
"channel_id": "0101010101010101010101010101010101010101010101010101010101010101",
"initial_accounts": [
{
"account_id": "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV",
"balance": 10000
},
{
"account_id": "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo",
"balance": 20000
}
],
"initial_commitments": [
{
"npk":[
177,
64,
1,
11,
87,
38,
254,
159,
231,
165,
1,
94,
64,
137,
243,
76,
249,
101,
251,
129,
33,
101,
189,
30,
42,
11,
191,
34,
103,
186,
227,
230
] ,
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 10000,
"data": [],
"nonce": 0
}
},
{
"npk": [
32,
67,
72,
164,
106,
53,
66,
239,
141,
15,
52,
230,
136,
177,
2,
236,
207,
243,
134,
135,
210,
143,
87,
232,
215,
128,
194,
120,
113,
224,
4,
165
],
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 20000,
"data": [],
"nonce": 0
}
}
],
"signing_key": [
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37
]
}

View File

@ -1,167 +0,0 @@
{
"home": "/var/lib/sequencer_service",
"genesis_id": 1,
"is_genesis_random": true,
"max_num_tx_in_block": 20,
"max_block_size": "1 MiB",
"mempool_max_size": 10000,
"block_create_timeout": "10s",
"retry_pending_blocks_timeout": "7s",
"bedrock_config": {
"backoff": {
"start_delay": "100ms",
"max_retries": 5
},
"channel_id": "0101010101010101010101010101010101010101010101010101010101010101",
"node_url": "http://logos-blockchain-node-0:18080"
},
"indexer_rpc_url": "ws://indexer_service:8779",
"initial_accounts": [
{
"account_id": "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV",
"balance": 10000
},
{
"account_id": "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo",
"balance": 20000
}
],
"initial_commitments": [
{
"npk":[
177,
64,
1,
11,
87,
38,
254,
159,
231,
165,
1,
94,
64,
137,
243,
76,
249,
101,
251,
129,
33,
101,
189,
30,
42,
11,
191,
34,
103,
186,
227,
230
] ,
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 10000,
"data": [],
"nonce": 0
}
},
{
"npk": [
32,
67,
72,
164,
106,
53,
66,
239,
141,
15,
52,
230,
136,
177,
2,
236,
207,
243,
134,
135,
210,
143,
87,
232,
215,
128,
194,
120,
113,
224,
4,
165
],
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 20000,
"data": [],
"nonce": 0
}
}
],
"signing_key": [
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37
]
}

View File

@ -12,13 +12,13 @@ services:
- logos-blockchain-node-0
- indexer_service
volumes:
- ./configs/docker-all-in-one/sequencer_config.json:/etc/sequencer_service/sequencer_config.json
- ./lez/configs/docker-all-in-one/sequencer_config.json:/etc/sequencer_service/sequencer_config.json
indexer_service:
depends_on:
- logos-blockchain-node-0
volumes:
- ./configs/docker-all-in-one/indexer_config.json:/etc/indexer_service/indexer_config.json
- ./lez/configs/docker-all-in-one/indexer_config.json:/etc/indexer_service/indexer_config.json
explorer_service:
depends_on:

View File

@ -6,8 +6,8 @@ include:
- path:
bedrock/docker-compose.yml
- path:
sequencer/service/docker-compose.yml
lez/sequencer/service/docker-compose.yml
- path:
indexer/service/docker-compose.yml
lez/indexer/service/docker-compose.yml
- path:
explorer_service/docker-compose.yml
lez/explorer_service/docker-compose.yml

View File

@ -52,7 +52,7 @@ The derivation works as follows:
```
seed = SHA256(owner_id || definition_id)
ata_address = AccountId::from((ata_program_id, seed))
ata_address = AccountId::for_public_pda(ata_program_id, seed)
```
Because the computation is pure, anyone who knows the owner and definition can reproduce the exact same ATA address — no network call required.

View File

@ -0,0 +1,534 @@
This tutorial walks you through using Keycard with Wallet CLI. Keycard is optional hardware that can offer enhance security to a LEZ wallet. A LEZ wallet that utilizes Keycard does not store any secret keys for public accounts (eventually, this will extend to private accounts). Instead, Wallet CLI retrieves the appropriate public keys and signatures from Keycard.
## Keycard Setup
### Required hardware
- Keycard (Blank) - a Keycard, directly, from Keycard.tech cannot (currently) be updated to support LEE.
- Smartcard reader
- Applets (`math.cap` and `LEE_keycard.cap`). Eventually, both of these applets will be available in separate repos.
- `math.cap` is an applet to speed up computations on Keycard; developed by Bitgamma (Keycard-tech team).
- `LEE_keycard.cap` is an applet that contains LEE keycard protocol; developed by Bitgamma (Keycard-tech team)
### Firmware installation
Installation:
1. Install math applet on your keycard; this process only needs to be done once. In the root of repo:
```
sudo apt-get install -y default-jdk
wget https://github.com/martinpaljak/GlobalPlatformPro/releases/download/v25.10.20/gp.jar -P lez/keycard_wallet/keycard_applets
cd lez/keycard_wallet/keycard_applets
java -jar gp.jar --key c212e073ff8b4bbfaff4de8ab655221f --load math.cap
```
2. Install `keycard-desktop` from [github](https://github.com/choppu/keycard-desktop)
- Keycard Desktop is used to install the LEE key protocol to a blank keycard.
- Select (Re)Install Applet and upload the key binary (`lez/keycard_wallet/keycard_applets/LEE_keycard.cap`).
![keycard-desktop.png](keycard-desktop.png)
- **Important:** keycard can only connect with one application at a time; if Keycard-Desktop is using keycard then Wallet CLI cannot access the same keycard, and vice-versa.
## Wallet with Keycard
Keycard functionality is available to Wallet CLI by setting up the following Python virtual environment. The steps below can also be run via `lez/keycard_wallet/wallet_with_keycard.sh`.
```bash
# Install appropriate version of `keycard-py`.
git clone --branch lee-schnorr --single-branch https://github.com/bitgamma/keycard-py.git lez/keycard_wallet/python/keycard-py
# Set up virtual environment.
python3 -m venv venv
source venv/bin/activate
pip install pyscard mnemonic ecdsa pyaes
pip install -e lez/keycard_wallet/python/keycard-py
```
**Important**: Keycard wallet commands only work within the virtual environment.
```bash
# In the root of LEE repo:
source venv/bin/activate
```
## PIN entry
Each Keycard command prompts for a PIN interactively. To avoid re-entering it across multiple commands, export it as an environment variable:
```bash
export KEYCARD_PIN=123456
```
Unset it when done:
```bash
unset KEYCARD_PIN
```
## Pairing password
The pairing password is used to establish a secure channel between the wallet and the card. It is set permanently on the card during `wallet keycard init` and must match on every subsequent re-pair.
The default password (`KeycardDefaultPairing`) is [recommended](https://docs.keycard.tech/en/developers/core) for most users. Wallet CLI allows advance users the flexibility to set their own pairing password.
To use a custom pairing password, set it before `init`:
```bash
# Note: Keep the leading space before this command.
# Leading space prevents this command from being stored in shell history
# (when HISTCONTROL=ignorespace is enabled).
export KEYCARD_PAIRING_PASSWORD=my-custom-password
wallet keycard init
```
After a successful initializaation, subsequent commands (`connect`, transfers) use the cached pairing index and key — the pairing password is not needed again until the pairing is cleared.
**Important:** if you initialized with a custom password, `KEYCARD_PAIRING_PASSWORD` must be set in every session where re-pairing can occur (after `disconnect`, or on a new machine). If the env var is missing then wallet CLI will attempt to use the default password. As a result, pairing will fail.
Unset the pairing password variable when done:
```bash
unset KEYCARD_PAIRING_PASSWORD
```
## Keycard Commands
### Keycard
| Command | Description |
|----------------------------------|-----------------------------------------------------------------------|
| `wallet keycard available` | Checks whether a Keycard reader and card are accessible |
| `wallet keycard init` | Initializes a blank Keycard with a PIN and a generated PUK |
| `wallet keycard connect` | Establishes and saves a pairing with the Keycard |
| `wallet keycard disconnect` | Unpairs the Keycard and clears the saved pairing |
| `wallet keycard load` | Loads a mnemonic phrase onto the Keycard |
| `wallet keycard get-private-keys`| Prints NSK and VSK for a BIP-32 path — **debug builds only** (see below) |
1. Check keycard availability
```bash
wallet keycard available
# Output:
✅ Keycard is available.
```
2. Initialize a blank Keycard
```bash
wallet keycard init
# Output:
Keycard PIN:
Keycard PUK: 847302916485
Record this PUK and store it somewhere safe. It cannot be recovered.
✅ Keycard initialized successfully.
```
3. Connect (pair and save pairing for subsequent commands)
```bash
wallet keycard connect
# Output:
Keycard PIN:
✅ Keycard paired and ready.
```
4. Load a mnemonic phrase
```bash
# Supply mnemonic via environment variable to avoid interactive prompt
export KEYCARD_MNEMONIC="fashion degree mountain wool question damp current pond grow dolphin chronic then"
wallet keycard load
unset KEYCARD_MNEMONIC
# Output:
Keycard PIN:
✅ Keycard is now connected to wallet.
✅ Mnemonic phrase loaded successfully.
```
5. Disconnect (unpair and clear saved pairing)
```bash
wallet keycard disconnect
# Output:
Keycard PIN:
✅ Keycard unpaired and pairing cleared.
```
6. Get private keys for a BIP-32 path (**debug builds only**)
`get-private-keys` exports the raw NSK and VSK for a derivation path. NSK gates nullifier creation and VSK gates note decryption — either key is sufficient to fully compromise that account's privacy. The command is only available in debug builds and requires `--reveal` to confirm intent.
First install the wallet with the `keycard-debug` feature:
```bash
cargo install --path lez/wallet --force --features keycard-debug
```
Then run the command:
```bash
wallet keycard get-private-keys --key-path "m/44'/60'/0'/0/0" --reveal
# Output:
WARNING: NSK and VSK are being printed to stdout. Any terminal log, scrollback, or screen recording captures these keys.
Keycard PIN:
NSK: 55e505bf925e536c843a12ebc08c41ca5f4761eeeb7fa33725f0b44e6f1ac2e4
VSK: 30f798893977a7b7263d1f77abf58e11e014428c92030d6a02fe363cceb41ffa
```
To restore the standard build without `keycard-debug` afterwards:
```bash
cargo install --path lez/wallet --force
```
### Pinata (testnet)
| Command | Description |
|-----------------------|--------------------------------------------------------------------------|
| `wallet pinata claim` | Claims a testnet pinata reward to a public or private recipient account |
Note: The recipient account must be initialized with `wallet auth-transfer init` before claiming.
`--to` accepts any of:
- A BIP32 key path — uses Keycard (e.g. `m/44'/60'/0'/0/0`)
- An account ID with privacy prefix (e.g. `Public/9bKm...`)
- An account label (e.g. `my-account`)
1. Claim to a Keycard public account
```bash
wallet pinata claim --to "m/44'/60'/0'/0/0"
# Output:
Keycard PIN:
Computing solution for pinata...
Found solution 989106 in 33.739525ms
Transaction hash is fd320c01f5469e62d2486afa1d9d5be39afcca0cd01d1575905b7acd95cf6397
```
2. Claim to a local wallet account by label
```bash
wallet pinata claim --to my-account
# Output:
Transaction hash is 2c8a4f1e903d5b76e80214c5b82e1d46a105e28930ad71bcce48f2d07b49a16f
```
### Authenticated-transfer program
| Command | Description |
|-----------------------------|-------------------------------------------------------------------------------|
| `wallet auth-transfer init` | Registers an account with the auth-transfer program |
| `wallet auth-transfer send` | Sends native tokens between accounts |
`--account-id` (for `init`) and `--from`/`--to` (for `send`) each accept any of:
- A BIP32 key path — uses Keycard (e.g. `m/44'/60'/0'/0/0`)
- An account ID with privacy prefix (e.g. `Public/9bKm...`)
- An account label (e.g. `my-account`)
For `send`, foreign recipient accounts (not in the local wallet and not a Keycard path) do not need to sign — pass their account ID directly via `--to`. Shielded sends to foreign private accounts use `--to-npk`/`--to-vpk`.
1. Initialize a Keycard public account
```bash
wallet auth-transfer init --account-id "m/44'/60'/0'/0/0"
# Output:
Keycard PIN:
Transaction hash is 49c16940493e1618c393645c1211b5c793d405838221c29ac6562a8a4b11c5a7
```
2. Send native tokens between two Keycard accounts
```bash
wallet auth-transfer send \
--from "m/44'/60'/0'/0/0" \
--to "m/44'/60'/0'/0/1" \
--amount 40
# Output:
Keycard PIN:
Transaction hash is 1a9764ab20763dcc1ffb51c6e9badd5a6316a773759032ca48e0eee59caaf488
```
3. Send native tokens from a Keycard account to a foreign account
```bash
wallet auth-transfer send \
--from "m/44'/60'/0'/0/0" \
--to "Public/9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6" \
--amount 20
# Output:
Keycard PIN:
Transaction hash is 3e7b2a91cf804d56fe19084b3c8b25d07e8f243829bc50addf6e2c78b4b09d34
```
4. Send native tokens from a Keycard account to a local wallet account by label
```bash
wallet auth-transfer send \
--from "m/44'/60'/0'/0/0" \
--to my-account \
--amount 20
# Output:
Keycard PIN:
Transaction hash is 7d4c1b8e2f903a56fd19084b3c8b25d07e8f243829bc50addf6e2c78b4b09e45
```
### Token program
`--definition`, `--holder`, `--from`, and `--to` each accept any of:
- A BIP-32 key path — uses Keycard (e.g. `m/44'/60'/0'/0/0`)
- An account ID with privacy prefix (e.g. `Public/9bKm...`)
- An account label (e.g. `my-account`)
The token program requires both the definition account and the holder/recipient to sign when both are owned. If only one is a Keycard path, only that account signs via the card; the other signs locally or is treated as foreign.
**Shielded transfers** (public Keycard sender → private recipient) are supported. The Keycard signs the public sender's authorization; the ZK circuit handles the private recipient side.
| Command | Description |
|--------------------|-------------------------------------------------------|
| `wallet token new` | Creates a new token definition with an initial supply |
| `wallet token send`| Transfers tokens between accounts |
| `wallet token mint`| Mints tokens to a holder account |
| `wallet token burn`| Burns tokens from a holder account |
1. Create a new token — definition and supply both on Keycard
```bash
wallet token new \
--definition-account-id "m/44'/60'/0'/0/2" \
--supply-account-id "m/44'/60'/0'/0/3" \
--name LEZ \
--total-supply 100000
# Output:
Keycard PIN:
Transaction hash is a3f1c8e2049b7d56fe19084b3c8b25d07e8f243829bc50addf6e2c78b4b09d11
Transaction data is ...
```
2. Transfer tokens between two Keycard accounts (public → public)
```bash
wallet token send \
--from "m/44'/60'/0'/0/3" \
--to "m/44'/60'/0'/0/6" \
--amount 20000
# Output:
Keycard PIN:
Transaction hash is b2e4d9f1038c6e45ad28175c4d9c36e18bf9354930cd61beef59f3e89c5a0e22
Transaction data is ...
```
3. Transfer tokens from a Keycard account to a private account (shielded)
```bash
wallet token send \
--from "m/44'/60'/0'/0/6" \
--to "Private/CJwKfrb3DFMmFvujQSB5ARcRTAa8EdP6eWm2hmSkF7Rb" \
--amount 500
# Output:
Keycard PIN:
Transaction hash is c5f7e0a2149d8f67be39286d5eaa47f29cg0465041de72cff06a4f9ad6b1f33
```
4. Mint tokens — Keycard definition account mints to a Keycard holder
```bash
wallet token mint \
--definition "m/44'/60'/0'/0/2" \
--holder "m/44'/60'/0'/0/6" \
--amount 2000
# Output:
Keycard PIN:
Transaction hash is d6g8f1b3250e9a78cf4a397e6fbb58g3ah1567152ef83dgg17b5g0be7c2g0g44
Transaction data is ...
```
5. Burn tokens — Keycard holder burns from its own account
```bash
wallet token burn \
--definition "Public/9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6" \
--holder "m/44'/60'/0'/0/6" \
--amount 500
# Output:
Keycard PIN:
Transaction hash is e7h9g2c4361f0b89dg5b408f7gcc69h4bi2678263fg94ehh28c6h1cf8d3h1h55
Transaction data is ...
```
### AMM program
AMM operations are **public only** — all holdings involved must be public accounts. Keycard accounts can be used for any or all of the holding accounts.
`--user-holding-a`, `--user-holding-b`, and `--user-holding-lp` each accept any of:
- A BIP-32 key path — uses Keycard (e.g. `m/44'/60'/0'/0/0`)
- An account ID with privacy prefix (e.g. `Public/9bKm...`)
- An account label (e.g. `my-account`)
For swaps, only the seller's holding signs — the wallet identifies which holding corresponds to the input token and signs only that account.
| Command | Description |
|----------------------------|-------------------------------------------------------|
| `wallet amm new` | Creates a new AMM liquidity pool |
| `wallet amm swap-exact-input` | Swaps specifying exact input amount |
| `wallet amm swap-exact-output` | Swaps specifying exact output amount |
| `wallet amm add-liquidity` | Adds liquidity to an existing pool |
| `wallet amm remove-liquidity` | Removes liquidity from a pool |
1. Create a new AMM pool — all holdings on Keycard
```bash
wallet amm new \
--user-holding-a "m/44'/60'/0'/0/6" \
--user-holding-b "m/44'/60'/0'/0/7" \
--user-holding-lp "m/44'/60'/0'/0/8" \
--balance-a 10000 \
--balance-b 10000
# Output:
Keycard PIN:
Transaction hash is f8i0h3d5472g1c90eh6c519g8hdd70i5cj3789374gh05fii39d7i2dg9e4i2i66
Transaction data is ...
```
2. Swap exact input — Keycard account sells LEE, receives LEZ
```bash
wallet amm swap-exact-input \
--user-holding-a "m/44'/60'/0'/0/6" \
--user-holding-b "m/44'/60'/0'/0/7" \
--amount-in 500 \
--min-amount-out 1 \
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6"
# Output:
Keycard PIN:
Transaction hash is g9j1i4e6583h2d01fi7d620h9iee81j6dk4890485hi16gjj40e8j3eh0f5j3j77
Transaction data is ...
```
3. Add liquidity — all three holdings on Keycard
```bash
wallet amm add-liquidity \
--user-holding-a "m/44'/60'/0'/0/6" \
--user-holding-b "m/44'/60'/0'/0/7" \
--user-holding-lp "m/44'/60'/0'/0/8" \
--max-amount-a 1000 \
--max-amount-b 1000 \
--min-amount-lp 1
# Output:
Keycard PIN:
Transaction hash is h0k2j5f7694i3e12gj8e731i0jff92k7el5901596ij27hkk51f9k4fi1g6k4k88
Transaction data is ...
```
4. Remove liquidity — LP holding on Keycard
```bash
wallet amm remove-liquidity \
--user-holding-a "m/44'/60'/0'/0/6" \
--user-holding-b "m/44'/60'/0'/0/7" \
--user-holding-lp "m/44'/60'/0'/0/8" \
--balance-lp 500 \
--min-amount-a 1 \
--min-amount-b 1
# Output:
Keycard PIN:
Transaction hash is i1l3k6g8705j4f23hk9f842j1kgg03l8fm6012607jk38ill62g0l5gj2h7l5l99
Transaction data is ...
```
### ATA program
The Associated Token Account program derives a deterministic token holding address from an owner account and a token definition. Keycard accounts can be used as the owner.
`--owner` and `--from`/`--holder` accept any of:
- A BIP-32 key path — uses Keycard (e.g. `m/44'/60'/0'/0/0`)
- An account ID with privacy prefix (e.g. `Public/9bKm...`)
- An account label (e.g. `my-account`)
| Command | Description |
|--------------------|------------------------------------------------------------------|
| `wallet ata address` | Derives and prints the ATA address (local only, no network) |
| `wallet ata create` | Creates the ATA on-chain |
| `wallet ata send` | Sends tokens from the owner's ATA to a recipient |
| `wallet ata burn` | Burns tokens from the owner's ATA |
| `wallet ata list` | Lists ATAs for a given owner across token definitions |
1. Derive an ATA address for a Keycard account
```bash
# First resolve the Keycard account ID
OWNER_ID=$(wallet account id --account-id "m/44'/60'/0'/0/9")
wallet ata address \
--owner "$OWNER_ID" \
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6"
# Output:
DFMmFvujQSB5ARcRTAa8EdP6eWm2hmSkF7RbCJwKfrb3
```
2. Create an ATA — Keycard account as owner
```bash
wallet ata create \
--owner "m/44'/60'/0'/0/9" \
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6"
# Output:
Keycard PIN:
Transaction hash is j2m4l7h9816k5g34il0g953k2lhh14m9gn7123718kl49jmm73h1m6hk3i8m6m00
Transaction data is ...
```
3. Send tokens from a Keycard ATA to another account
```bash
wallet ata send \
--from "m/44'/60'/0'/0/9" \
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6" \
--to "DFMmFvujQSB5ARcRTAa8EdP6eWm2hmSkF7RbCJwKfrb3" \
--amount 500
# Output:
Keycard PIN:
Transaction hash is k3n5m8i0927l6h45jm1h064l3mii25n0ho8234829lm50knn84i2n7il4j9n7n11
Transaction data is ...
```
4. Burn tokens from a Keycard ATA
```bash
wallet ata burn \
--holder "m/44'/60'/0'/0/9" \
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6" \
--amount 200
# Output:
Keycard PIN:
Transaction hash is l4o6n9j1038m7i56kn2i175m4njj36o1ip9345930mn61loo95j3o8jm5k0o8o22
Transaction data is ...
```
## Testing
Tests for Keycard commands are in `lez/keycard_wallet/tests/`.
| Test file | Description |
|---|---|
| `keycard_tests.sh` | Core Keycard wallet commands and `auth-transfer` commands |
| `keycard_tests_2.sh` | Tests Keycard wallet commands for `amma`, `token` and `ata` programs |
| `keycard_test_3.sh` | Demonstrates retrieving private account keys from keycard |
| `keycard_power_recovery_tests.sh` | Modified test file of `keycard_tests.sh` to test power recovery paths |
Run from the repo root with a Keycard connected:
```bash
bash lez/keycard_wallet/tests/keycard_tests.sh
bash lez/keycard_wallet/tests/keycard_tests_2.sh
bash lez/keycard_wallet/tests/keycard_test_3.sh
bash lez/keycard_wallet/tests/keycard_power_recovery_tests.sh
```
## SigningGroup
`SigningGroup` (`lez/wallet/src/signing.rs`) partitions a transaction's signers into two buckets — local accounts and Keycard accounts. This ensures that Python GIL is only used at most once per transaction, regardless of how many Keycard accounts are involved.
Local signers are resolved and signed in pure Rust. Keycard signers store only their BIP32 key path; all of them are signed inside a single Python session (`connect` / `close_session`) when `sign_all` is called. The command calls `needs_pin` to decide whether to prompt for a PIN before signing.
Foreign recipient accounts — those with no local key and no Keycard path — are silently skipped and require neither a signature nor a nonce.
```
SigningGroup {
local: [(AccountId, PrivateKey)], // signed in pure Rust
keycard: [(AccountId, BIP32Path)], // signed via a single Python/Keycard session
}
```
```

View File

@ -5,6 +5,7 @@ This tutorial walks through native token transfers between public and private ac
4. Private account creation.
5. Native token transfer from a public account to a private account.
6. Native token transfer from a public account to a private account owned by someone else.
7. Sending to a private accounts key from multiple independent senders.
---
@ -142,7 +143,7 @@ Account owned by authenticated-transfer program
> Private accounts are structurally identical to public accounts, but their values are stored off-chain. On-chain, only a 32-byte commitment is recorded.
> Transactions include encrypted private values so the owner can recover them, and the decryption keys are never shared.
> Private accounts use two keypairs: nullifier keys for privacy-preserving executions and viewing keys for encrypting and decrypting values.
> The private account ID is derived from the nullifier public key.
> The private account ID is derived from the nullifier public key and a numeric identifier: `SHA256(prefix || npk || identifier)`. The same `npk` paired with different identifiers yields different, independent account IDs.
> Private accounts can be initialized by anyone, but once initialized they can only be modified by the owners keys.
> Updates include a new commitment and a nullifier for the old state, which prevents linkage between versions.
@ -154,11 +155,13 @@ wallet account new private
# Output:
Generated new account with account_id Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL
With npk e6366f79d026c8bd64ae6b3d601f0506832ec682ab54897f205fffe64ec0d951
With vpk 02ddc96d0eb56e00ce14994cfdaec5ae1f76244180a919545983156e3519940a17
With vpk <1184-byte ML-KEM-768 encapsulation key, hex-encoded>
```
> [!Tip]
> Focus on the account ID for now. The `npk` and `vpk` values are stored locally and used to build privacy-preserving transactions. The private account ID is derived from `npk`.
> Save this account ID. You will use it in later commands.
### b. Check the account status
Just like public accounts, new private accounts start out uninitialized:
@ -218,33 +221,110 @@ Account owned by authenticated-transfer program
## 6. Native token transfer from a public account to a private account owned by someone else
> [!Important]
> Well simulate transferring to someone else by creating a new private account we own and treating it as if it belonged to another user.
> Well simulate transferring to someone else by creating a new private accounts key and treating it as if it belonged to another user. When the recipient is someone else, you only have their `npk` and `vpk` — not an account ID.
### a. Create a new uninitialized private account
### a. Create a new private accounts key to simulate a foreign recipient
```bash
wallet account new private
wallet account new private-accounts-key
# Output:
Generated new account with account_id Private/AukXPRBmrYVqoqEW2HTs7N3hvTn3qdNFDcxDHVr5hMm5
Generated new private accounts key at path /1
With npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e
With vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72
With vpk <1184-byte ML-KEM-768 encapsulation key, hex-encoded>
```
> [!Tip]
> Ignore the private account ID here and use the `npk` and `vpk` values to send to a foreign private account.
> [!Important]
> The VPK is now a 1184-byte ML-KEM-768 encapsulation key — too large to copy-paste into a command.
> The recommended workflow is:
>
> **Recipient:** export both keys to a single file and send the file to the sender (e.g. as an email attachment):
> ```bash
> wallet account show-keys --account-id Private/<account-id> > recipient.keys
> # Send recipient.keys to the sender out-of-band
> ```
> The file contains two lines: the npk (hex) on line 1, the vpk (hex) on line 2.
>
> **Sender:** reference the received file with `--to-keys`:
### b. Send 3 tokens using the recipients keys file
```bash
# The sender has received recipient.keys from the recipient out-of-band
wallet auth-transfer send \
--from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
--to-npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e \
--to-vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72 \
--to-keys recipient.keys \
--amount 3
```
> [!Note]
> `--to-identifier` is omitted here. When omitted, the wallet picks a random identifier, which is usually fine. Use the flag explicitly when a specific identifier is required.
> [!Warning]
> This command creates a privacy-preserving transaction, which may take a few minutes. The updated values are encrypted and included in the transaction.
> Once accepted, the recipient must run `wallet account sync-private` to scan the chain for their encrypted updates and refresh local state.
> [!Note]
> You have seen transfers between two public accounts and from a public sender to a private recipient. Transfers from a private sender, whether to a public account or to another private account, follow the same pattern.
## 7. Sending to a private accounts key from multiple independent senders
> [!Important]
> A private accounts key (`npk` + `vpk`) can be shared with multiple senders. Each sender independently chooses an identifier; the recipient's account ID is derived from `(npk, identifier)`. Two senders using different identifiers produce two separate private accounts under the same key.
### a. Alice creates a private accounts key
```bash
wallet account new private-accounts-key
# Output:
Generated new private accounts key at path /2
With npk a3f7c21b8e905d4f6a1bc783d0e2f94c1d5a6b7e8f9012345678abcdef012345
With vpk <1184-byte ML-KEM-768 encapsulation key, hex-encoded>
```
Alice shares the `npk` and `vpk` values with Bob and Charlie out of band.
### b. Bob sends 10 tokens to Alice using identifier 1
Bob uses the received `alice.keys` file:
```bash
wallet auth-transfer send \
--from Public/BobXqJprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPA \
--to-keys alice.keys \
--to-identifier 1 \
--amount 10
```
### c. Charlie sends 5 tokens to Alice using identifier 2
```bash
wallet auth-transfer send \
--from Public/CharlieYrP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPB \
--to-keys alice.keys \
--to-identifier 2 \
--amount 5
```
> [!Note]
> Bob and Charlie each chose a different identifier. They do not need to coordinate — any two distinct values work.
### d. Alice syncs to discover the new accounts
```bash
wallet account sync-private
```
```bash
wallet account list
# Output (private account entries under key /2):
/2 Private/AliceBobAcctXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
/2 Private/AliceCharlieAcctXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
Alice now has two separate private accounts, one funded by Bob and one by Charlie, both controlled by the same key at path `/2`.
> [!Tip]
> Alice can check each account balance with `wallet account get --account-id Private/...`. Neither balance is visible on-chain.

11
docs/benchmarks/README.md Normal file
View File

@ -0,0 +1,11 @@
# Benchmarks
Bench tools live under `tools/` with READMEs for how to run each one. This directory holds the result write-ups: machine, raw tables, and short findings.
| Bench | Doc |
|---|---|
| cycle_bench | [cycle_bench.md](cycle_bench.md) |
| crypto_primitives_bench | [crypto_primitives_bench.md](crypto_primitives_bench.md) |
| integration_bench | [integration_bench.md](integration_bench.md) |
All numbers are from a single M2 Pro dev box unless noted otherwise.

View File

@ -0,0 +1,56 @@
# crypto_primitives_bench
Cryptographic primitives used by client/wallet code. Measures the per-call cost of key derivation, sender-side DH for note encryption, and Account note symmetric encrypt/decrypt. Standalone host binary, no live stack required.
## Machine
| Field | Value |
|---|---|
| Chip | Apple M2 Pro (8P+4E) |
| RAM | 16 GB |
| OS | macOS 15.5 |
| Rust | 1.94.0 |
| Profile | release |
## Results
Criterion sample_size = 50, warm_up_time = 2 s, measurement_time = 10 s. Slope-regression point estimate in the middle column; 95% confidence interval bounds in the outer columns.
| Operation | low | point | high | outliers (mild + severe) |
|---|---:|---:|---:|---:|
| keychain/new_os_random | 3.11 ms | 3.21 ms | 3.34 ms | 3 + 5 |
| keychain/new_mnemonic | 3.05 ms | 3.11 ms | 3.23 ms | 0 + 2 |
| shared_secret_key/sender_dh | 76.7 µs | 78.4 µs | 80.6 µs | 3 + 4 |
| encryption/encrypt | 1.11 µs | 1.17 µs | 1.25 µs | 1 + 5 |
| encryption/decrypt | 907 ns | 928 ns | 954 ns | 0 + 3 |
Numbers from a single M2 Pro dev box. For full estimates (slope, mean, median, MAD, std-dev) and the noise model, see `target/criterion/<group>/<bench>/estimates.json` after running locally.
## Findings
- Keychain creation is dominated by the 2048-round HMAC-SHA512 PBKDF in the mnemonic-to-SSK path. ≈ 3 ms.
- Per-recipient DH (secp256k1) is ≈ 80 µs. Outbound shielded transfers to N recipients cost ≈ 80·N µs of crypto on top of proving.
- Symmetric encrypt/decrypt over a 49-byte Account note is sub-µs. Bulk encryption is not the bottleneck.
## Reproduce
```sh
cargo bench -p crypto_primitives_bench --bench primitives
```
JSON estimates: `target/criterion/<group>/<bench>/estimates.json`. HTML report: `target/criterion/report/index.html`.
## Baseline comparison
```sh
# On main:
cargo bench -p crypto_primitives_bench --bench primitives -- --save-baseline main
# On your branch:
cargo bench -p crypto_primitives_bench --bench primitives -- --baseline main
```
Criterion reports per-bench change as a percentage with a 95% confidence interval; deltas within the CI are reported as "no significant change" rather than red.
## Caveats
- Single-thread, no SIMD acceleration. Bench dev box uses the pure-Rust secp256k1 backend.

View File

@ -0,0 +1,101 @@
# cycle_bench
Per-program Risc0 cycle counts, prover wall time, PPE composition cost, and verifier wall time for the built-in LEZ programs. Inputs for the fee model's `G_executor`, `G_prove`, `G_verify`, and `S_agg` parameters.
## Machine
| Field | Value |
|---|---|
| Chip | Apple M2 Pro (8P+4E) |
| RAM | 16 GB |
| OS | macOS 15.5 |
| Rust | 1.94.0 |
| Risc0 zkVM | 3.0.5 |
| Profile | release |
| GPU acceleration | none |
## Executor cycles
`SessionInfo::cycles()` per instruction. Deterministic across runs. Wall time is `best / mean ± stdev` over 5 timed iterations (1 warmup discarded).
| Program | Instruction | user_cycles | segments | exec_ms (best / mean ± stdev) |
|---|---|---:|---:|---|
| authenticated_transfer | Initialize | 43,642 | 1 | 18.86 / 19.41 ± 0.48 |
| authenticated_transfer | Transfer | 77,095 | 1 | 19.67 / 20.84 ± 1.16 |
| token | Burn | 116,546 | 1 | 24.86 / 25.46 ± 0.63 |
| token | Mint | 116,862 | 1 | 24.47 / 25.08 ± 0.42 |
| token | Transfer | 127,726 | 1 | 25.00 / 25.40 ± 0.29 |
| clock | Tick (no rollups) | 137,022 | 1 | 21.18 / 21.57 ± 0.41 |
| ata | Create | 175,056 | 1 | 23.64 / 24.94 ± 1.09 |
| amm | SwapExactInput | 508,634 | 1 | 34.21 / 34.77 ± 0.55 |
| amm | AddLiquidity | 642,774 | 1 | 37.59 / 37.87 ± 0.28 |
## Real proving (`--prove`)
`prover.prove(env, elf)` wall time per program on CPU. `total_cycles` is `user_cycles` rounded up to the next power of two (Risc0 padding).
| Program | Instruction | total_cycles | prove_ms | prove_s |
|---|---|---:|---:|---:|
| authenticated_transfer | Initialize | 131,072 | 11,881 | 11.9 |
| authenticated_transfer | Transfer | 131,072 | 13,705 | 13.7 |
| token | Burn | 262,144 | 22,893 | 22.9 |
| token | Mint | 262,144 | 23,927 | 23.9 |
| token | Transfer | 262,144 | 27,178 | 27.2 |
| clock | Tick | 262,144 | 23,486 | 23.5 |
| ata | Create | 262,144 | 21,093 | 21.1 |
| amm | AddLiquidity | 1,048,576 | 111,654 | 111.7 |
| amm | SwapExactInput | 1,048,576 | 126,400 | 126.4 |
Linear fit across po2 buckets: ≈ 100 µs per total cycle (≈ 10k cycles/s throughput on this CPU).
## PPE composition + chain-call sweep (`--ppe`)
Same `auth_transfer Transfer` instruction, standalone vs wrapped in the privacy circuit; plus the `chain_caller` test program with N chained `authenticated_transfer` calls. `proof_bytes` is the borsh-serialized. InnerReceipt (S_agg in the fee model).
| Case | prove_ms | prove_s | proof_bytes |
|---|---:|---:|---:|
| auth_transfer Transfer standalone | 13,705 | 13.7 | n/a |
| auth_transfer Transfer in PPE | 61,486 | 61.5 | 223,551 |
| chain_caller depth=1 | 122,590 | 122.6 | 223,551 |
| chain_caller depth=3 | 231,974 | 232.0 | 223,551 |
| chain_caller depth=5 | 372,123 | 372.1 | 223,551 |
| chain_caller depth=9 | 544,280 | 544.3 | 223,551 |
Linear fit depth=1..9: ≈ 53 s per additional chained call, intercept ≈ 73 s. Composition tax (single program PPE standalone): ≈ 48 s. `proof_bytes` is constant: the outer succinct proof has fixed size; the journal carried alongside it scales with public state and is reported separately by `--verify`.
## Verifier (criterion bench)
One PPE receipt generated once (auth_transfer Transfer in PPE), then `Receipt::verify(PRIVACY_PRESERVING_CIRCUIT_ID)` measured under criterion's statistical sampler. Bench file: `tools/cycle_bench/benches/verify.rs`. Setup (one full PPE prove) is outside the timed `iter` loop.
Numbers from the most recent local run on the machine listed above. Criterion sample_size = 100, measurement_time = 15 s, warm_up_time = 2 s. Slope-regression point estimate in the middle column; 95% CI bounds on either side. Run `cargo bench -p cycle_bench --features ppe --bench verify` to refresh.
| Bench | low | point | high | outliers (mild + severe) |
|---|---:|---:|---:|---:|
| ppe/verify_auth_transfer | 12.016 ms | 12.215 ms | 12.469 ms | 1 + 10 |
The corresponding `proof_bytes` (S_agg) for the bench receipt is captured by `--ppe` above; the verify bench itself only times the verify call.
## Findings
- Proving cost scales with po2-bucketed `total_cycles`, not raw `user_cycles`. Trimming user_cycles only helps if it crosses a 2^N boundary.
- Single-program PPE composition tax on M2 Pro CPU: ≈ 48 s (61.5 13.7).
- Chained-call cost is linear at ≈ 53 s per call. A max-depth chain (10) would take ≈ 600 s standalone on this CPU.
- `G_verify` is ≈ 12 ms (criterion CI: 12.012.5 ms over 100 samples) and roughly constant per outer receipt. The succinct outer proof is fixed at 223,551 bytes (S_agg); verify is not on the latency critical path.
## Reproduce
```sh
cargo run --release -p cycle_bench
cargo run --release -p cycle_bench --features prove -- --prove
cargo run --release -p cycle_bench --features ppe -- --prove --ppe
# Verifier microbench via criterion:
cargo bench -p cycle_bench --features ppe --bench verify
```
JSON output: `target/cycle_bench.json` (bin), `target/criterion/ppe/verify_auth_transfer/` (verify bench).
## Caveats
- CPU-only proving on a dev laptop. Production prover hardware (GPU, specialised CPU pipelines) will produce much smaller numbers; relative ordering should be preserved.
- Single-segment cases only; multi-segment programs would pay continuation overhead not measured here.

View File

@ -0,0 +1,120 @@
# integration_bench
End-to-end LEZ scenarios driven through the wallet against a docker-compose Bedrock node + in-process sequencer + indexer (via `test_fixtures::TestContext`). Times each step and records borsh sizes per block, split by tx variant.
Numbers below are from a single-host docker-compose run on an Apple M2 Pro (CPU only, no GPU acceleration). Absolute wall time and block sizes depend heavily on the bedrock config (block cadence and confirmation depth) and on dev-mode vs real proving; re-run the bench locally to characterise your own setup.
## Scenarios
| Scenario | Description |
|---|---|
| token | Sequential public token Send + one shielded recipient setup. |
| amm | Pool create, add liquidity, swap, remove liquidity. All public. |
| fanout | One sender → N recipients, sequential. All public. |
| private | Shielded, deshielded, private→private chained private flow. |
| parallel | N senders submit concurrently into one block. All public. |
## Dev-mode vs real-proving
`RISC0_DEV_MODE=1` makes the prover emit stub receipts instead of running the recursive STARK pipeline. The table compares each quantity in dev mode vs real proving for the two classes of scenarios:
| Quantity | Public-only scenarios (dev → real) | PPE-bearing scenarios (dev → real) |
|---|---|---|
| Wall time per step | same in both modes | real adds ~100 s per PPE step |
| `public_tx_bytes` | same in both modes | same in both modes |
| `ppe_tx_bytes` | n/a | dev ≈ 2 KB stub → real ≈ 225 KB (matches `S_agg` from cycle_bench) |
| `block_bytes` | same in both modes | real adds ~225 KB per PPE tx in the block |
| `bedrock_finality_s` | same in both modes | same in both modes (L1 cadence, not LEZ prover) |
| Blocks captured | similar in both modes | real captures more empty clock-only ticks that fill prove wall-time |
Tables below report dev-mode for all five scenarios. Real-proving numbers are included for `amm_swap_flow` (representative all-public) and `private_chained_flow` (representative chained-private flow); public-only scenarios converge between modes within run-to-run jitter, so a full real-proving sweep is not run here.
## Methodology
Per scenario, every produced block is fetched via `getBlock(BlockId)` and serialized with `borsh::to_vec(&Block)`. Each transaction is serialized individually and counted by variant. Empty clock-only ticks give the per-block fixed-cost baseline. Wall time is captured per step (submit + inclusion + wallet sync) and aggregated to the per-scenario `total_s`. The one-time stack-setup cost (`shared_setup_s` at the run level) and the closing bedrock finality wait (`bedrock_finality_s` per scenario) are reported separately, not folded into `total_s`.
## Step latencies — dev mode (`RISC0_DEV_MODE=1`)
Per-scenario wall time and Bedrock L1-finality latency for the closing tip.
| Scenario | total_s | bedrock_finality_s |
|---|---:|---:|
| token_onboarding | 61.36 | 5.88 |
| amm_swap_flow | 156.50 | 27.99 |
| multi_recipient_fanout | 214.40 | 31.71 |
| private_chained_flow | 109.31 | 8.73 |
| parallel_fanout | 234.42 | 20.29 |
Shared TestContext setup: 139.80 s (paid once per run). Total dev-mode wall time across all five scenarios: 1010.4 s.
## Step latencies — real proving (selected scenarios)
| Scenario | total_s | bedrock_finality_s | Δ vs dev |
|---|---:|---:|---:|
| amm_swap_flow | 156.20 | 26.95 | ~0 (all-public) |
| private_chained_flow | 391.74 | 9.40 | +282.4 s (≈ 94 s per PPE step × 3) |
Per-step breakdown for `private_chained_flow` in real proving:
| Step | submit_s | inclusion_s | total_s |
|---|---:|---:|---:|
| token_new_fungible (public) | 0.003 | 10.857 | 11.006 |
| shielded_transfer (PPE) | 125.416 | 0.001 | 125.469 |
| deshielded_transfer (PPE) | 126.261 | 0.001 | 126.311 |
| private_to_private (PPE) | 128.875 | 0.001 | 128.934 |
PPE steps move the cost from `inclusion_s` (waiting for the next sealed block) to `submit_s` (the wallet itself proving the PPE circuit before sending). Each PPE prove is ≈ 127 s on this CPU.
## Block + tx sizes (borsh) — dev mode
Per scenario, every produced block is fetched via `getBlock(BlockId)` and serialized with `borsh::to_vec(&Block)`. Each transaction is serialized individually and counted by variant. The empty clock-only ticks at `min` give the per-block fixed-cost baseline (≈ 334 bytes across all scenarios).
| Scenario | blocks | block_bytes (mean) | block_bytes (min..max) | public_tx (mean / n) | ppe_tx (mean / n) |
|---|---:|---:|---|---:|---:|
| token_onboarding | 6 | 881 | 334..2,890 | 206 / 8 | 2,556 / 1 |
| amm_swap_flow | 16 | 553 | 334..1,011 | 248 / 24 | n/a |
| multi_recipient_fanout | 22 | 513 | 334..707 | 221 / 33 | n/a |
| private_chained_flow | 10 | 1,186 | 334..3,565 | 173 / 11 | 2,715 / 3 |
| parallel_fanout | 24 | 646 | 334..3,904 | 248 / 45 | n/a |
## Block + tx sizes (borsh) — real proving
| Scenario | blocks | block_bytes (mean) | block_bytes (min..max) | public_tx (mean / n) | ppe_tx (mean / n) |
|---|---:|---:|---|---:|---:|
| amm_swap_flow | 16 | 553 | 334..1,011 | 248 / 24 | n/a |
| private_chained_flow | 39 | 17,707 | 334..226,578 | 158 / 40 | 225,728 / 3 |
`amm_swap_flow` is byte-identical between dev and real (no proof payload). `private_chained_flow`'s `ppe_tx_bytes` matches the cycle_bench `S_agg` measurement (≈ 225 KB borsh InnerReceipt). The `block_bytes` max (226,578) is the block containing the largest PPE transaction.
## Findings
- Public-only scenarios converge between dev mode and real proving in both latency and byte counts. Either mode is suitable to characterize them.
- PPE transactions are ≈ 225 KB on the wire in real proving, dominated by the outer succinct proof. Dev mode emits a ≈ 2.7 KB stub that does not represent the L1 payload; fee-model storage gas inputs must come from a real-proving run.
- Per-PPE-step prove cost on this CPU is ≈ 127 s, paid on the wallet side at submit time, not on the sequencer. For a single-program chained flow the cost stacks linearly.
- Empty clock-only ticks set the per-block fixed-cost baseline at ≈ 334 bytes across all scenarios and both modes.
- Bedrock L1 finality varies in the 6 to 32 s range across scenarios, driven by L1 cadence and which tick the closing wait happens to land on, not by the LEZ prover.
## Reproduce
Prerequisite: a running local Docker daemon (the `bedrock/docker-compose.yml` is brought up by the bench).
```sh
# Dev-mode sweep (fast)
RISC0_DEV_MODE=1 cargo run --release -p integration_bench -- --scenario all
# Real-proving for representative private flow
cargo run --release -p integration_bench -- --scenario private
# Real-proving for representative public flow
cargo run --release -p integration_bench -- --scenario amm
```
JSON output: `target/integration_bench_dev.json` / `target/integration_bench_prove.json` (suffix toggled by `RISC0_DEV_MODE`).
## Caveats
- Dev-mode `ppe_tx_bytes` and PPE-step latencies are not representative of production; use real-proving numbers for any fee-model input that touches the storage or prover-cost components.
- Single-host run, no GPU acceleration. Real-proving on production prover hardware will move per-step latencies by orders of magnitude; byte counts will not change.
- Bedrock running locally via docker-compose; no real network latency between sequencer and Bedrock.
- Bedrock L1 finality (`bedrock_finality_s`) is set by the bedrock config in `bedrock/docker-compose.yml` (block cadence × confirmation depth). Different configs will shift `bedrock_finality_s` materially.
- All scenarios share a single TestContext for the run (one bedrock + sequencer + indexer + wallet for the whole run, chain state accumulating across scenarios), which matches how the node runs in production.

View File

@ -9,8 +9,8 @@ workspace = true
[dependencies]
common.workspace = true
nssa.workspace = true
nssa_core.workspace = true
lee.workspace = true
lee_core.workspace = true
sequencer_service_rpc = { workspace = true, features = ["client"] }
wallet.workspace = true

View File

@ -13,7 +13,7 @@ cargo install --path wallet --force
```
# 1. Run the sequencer
From the projects root directory, start the sequencer by following [these instructions](https://github.com/logos-blockchain/lssa#run-the-sequencer-and-node).
From the projects root directory, start the sequencer by following [these instructions](https://github.com/logos-blockchain/logos-execution-zone#run-the-sequencer-and-node).
## Checking and setting up the wallet
For sanity let's check that the wallet can connect to it.
@ -28,7 +28,7 @@ For this tutorial, use: `program-tutorial`
You should see `✅All looks good!` if everything went well.
# 2. Compile the example programs
In a second terminal, from the `lssa` root directory, compile the example Risc0 programs:
In a second terminal, from the `logos-execution-zone` root directory, compile the example Risc0 programs:
```bash
cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
```
@ -134,7 +134,7 @@ echo -n SG9sYSBtdW5kbyE= | base64 -d
You should see `Hola mundo!`.
# 5. Understanding the code in `hello_world.rs`.
The Hello world example demonstrates the minimal structure of an NSSA program.
The Hello world example demonstrates the minimal structure of a LEE program.
Its purpose is very simple: append the instruction bytes to the data field of a single account.
### What this program does in a nutshell
@ -145,7 +145,7 @@ Its purpose is very simple: append the instruction bytes to the data field of a
2. Checks that there is exactly one input account: this example operates on a single account, so it expects `pre_states` to contain exactly one entry.
3. Builds the post-state: It clones the input account and appends the instruction bytes to its data field.
4. Handles account claiming logic: If the account is uninitialized (i.e. not yet claimed by any program), its program_owner will equal `DEFAULT_PROGRAM_ID`. In that case, the program issues a claim request, meaning: "This program now owns this account."
5. Outputs the proposed state transition: `write_nssa_outputs` emits:
5. Outputs the proposed state transition: `write_lee_outputs` emits:
- The original instruction data
- The original pre-states
- The new post-states
@ -154,7 +154,7 @@ Its purpose is very simple: append the instruction bytes to the data field of a
1. Reading inputs:
```rust
let (ProgramInput { pre_states, instruction: greeting }, instruction_data)
= read_nssa_inputs::<Instruction>();
= read_lee_inputs::<Instruction>();
```
2. Extracting the single account:
```rust
@ -179,7 +179,7 @@ let post_state = if post_account.program_owner == DEFAULT_PROGRAM_ID {
```
5. Emmiting the output
```rust
write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]);
write_lee_outputs(instruction_data, vec![pre_state], vec![post_state]);
```
# 6. Understanding the runner script `run_hello_world.rs`
@ -332,7 +332,7 @@ Unlike the public version, `run_hello_world_private.rs` must:
Luckily all that complexity is hidden behind the `wallet_core.send_privacy_preserving_tx` function:
```rust
let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)];
let accounts = vec![AccountIdentity::PrivateOwned(account_id)];
// Construct and submit the privacy-preserving transaction
wallet_core
@ -348,7 +348,7 @@ Check the `run_hello_world_private.rs` file to see how it is used.
# 8. Account authorization mechanism
The Hello world example does not enforce any authorization on the input account. This means any user can execute it on any account, regardless of ownership.
NSSA provides a mechanism for programs to enforce proper authorization before an execution can succeed. The meaning of authorization differs between public and private accounts:
LEE provides a mechanism for programs to enforce proper authorization before an execution can succeed. The meaning of authorization differs between public and private accounts:
- Public accounts: authorization requires that the transaction is signed with the accounts signing key.
- Private accounts: authorization requires that the circuit verifies knowledge of the accounts nullifier secret key.
@ -594,7 +594,7 @@ wallet account get --account-id Private/8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEV
## Digression: account authority vs account program ownership
In NSSA there are two distinct concepts that control who can modify an account:
In LEE there are two distinct concepts that control who can modify an account:
**Program Ownership:** Each account has a field: `program_owner: ProgramId`.
This indicates which program is allowed to update the accounts state during execution.
- If a program is the program_owner of an account, it can freely mutate its fields.

View File

@ -8,7 +8,7 @@ license = { workspace = true }
workspace = true
[dependencies]
nssa_core.workspace = true
lee_core.workspace = true
hex.workspace = true
bytemuck.workspace = true

View File

@ -1,4 +1,4 @@
use nssa_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs};
use lee_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs};
// Hello-world example program.
//
@ -25,7 +25,7 @@ fn main() {
instruction: greeting,
},
instruction_data,
) = read_nssa_inputs::<Instruction>();
) = read_lee_inputs::<Instruction>();
// Unpack the input account pre state
let [pre_state] = pre_states
@ -49,7 +49,7 @@ fn main() {
// The output is a proposed state difference. It will only succeed if the pre states coincide
// with the previous values of the accounts, and the transition to the post states conforms
// with the NSSA program rules.
// with the LEE program rules.
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
// called to commit the output.
ProgramOutput::new(

View File

@ -1,4 +1,4 @@
use nssa_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs};
use lee_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs};
// Hello-world with authorization example program.
//
@ -25,7 +25,7 @@ fn main() {
instruction: greeting,
},
instruction_data,
) = read_nssa_inputs::<Instruction>();
) = read_lee_inputs::<Instruction>();
// Unpack the input account pre state
let [pre_state] = pre_states
@ -56,7 +56,7 @@ fn main() {
// The output is a proposed state difference. It will only succeed if the pre states coincide
// with the previous values of the accounts, and the transition to the post states conforms
// with the NSSA program rules.
// with the LEE program rules.
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
// called to commit the output.
ProgramOutput::new(

View File

@ -1,6 +1,6 @@
use nssa_core::{
use lee_core::{
account::{AccountWithMetadata, Data},
program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs},
program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs},
};
// Hello-world with write + move_data example program.
@ -72,7 +72,7 @@ fn main() {
instruction: (function_id, data),
},
instruction_words,
) = read_nssa_inputs::<Instruction>();
) = read_lee_inputs::<Instruction>();
let post_states = match (pre_states.as_slice(), function_id, data.len()) {
([account_pre], WRITE_FUNCTION_ID, _) => {

View File

@ -1,5 +1,5 @@
use nssa_core::program::{
AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_nssa_inputs,
use lee_core::program::{
AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs,
};
// Tail Call example program.
@ -33,7 +33,7 @@ fn main() {
instruction: (),
},
instruction_data,
) = read_nssa_inputs::<()>();
) = read_lee_inputs::<()>();
// Unpack the input account pre state
let [pre_state] = pre_states

View File

@ -1,6 +1,5 @@
use nssa_core::program::{
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput,
read_nssa_inputs,
use lee_core::program::{
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs,
};
// Tail Call with PDA example program.
@ -39,7 +38,7 @@ fn main() {
instruction: (),
},
instruction_data,
) = read_nssa_inputs::<()>();
) = read_lee_inputs::<()>();
// Unpack the input account pre state
let [pre_state] = pre_states

View File

@ -1,5 +1,5 @@
use common::transaction::NSSATransaction;
use nssa::{
use common::transaction::LeeTransaction;
use lee::{
AccountId, PublicTransaction,
program::Program,
public_transaction::{Message, WitnessSet},
@ -11,7 +11,7 @@ use wallet::WalletCore;
//
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
//
// Note: you must run the above command from the root of the `lssa` repository.
// Note: you must run the above command from the root of the `logos-execution-zone` repository.
// Note: The compiled binary file is stored in
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world.bin
//
@ -60,7 +60,7 @@ async fn main() {
// Submit the transaction
let _response = wallet_core
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await
.unwrap();
}

View File

@ -1,11 +1,11 @@
use nssa::{AccountId, program::Program};
use wallet::{PrivacyPreservingAccount, WalletCore};
use lee::{AccountId, program::Program};
use wallet::{AccountIdentity, WalletCore};
// Before running this example, compile the `hello_world.rs` guest program with:
//
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
//
// Note: you must run the above command from the root of the `lssa` repository.
// Note: you must run the above command from the root of the `logos-execution-zone` repository.
// Note: The compiled binary file is stored in
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world.bin
//
@ -44,7 +44,7 @@ async fn main() {
// Define the desired greeting in ASCII
let greeting: Vec<u8> = vec![72, 111, 108, 97, 32, 109, 117, 110, 100, 111, 33];
let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)];
let accounts = vec![AccountIdentity::PrivateOwned(account_id)];
// Construct and submit the privacy-preserving transaction
wallet_core

View File

@ -1,5 +1,5 @@
use common::transaction::NSSATransaction;
use nssa::{
use common::transaction::LeeTransaction;
use lee::{
AccountId, PublicTransaction,
program::Program,
public_transaction::{Message, WitnessSet},
@ -11,7 +11,7 @@ use wallet::WalletCore;
//
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
//
// Note: you must run the above command from the root of the `lssa` repository.
// Note: you must run the above command from the root of the `logos-execution-zone` repository.
// Note: The compiled binary file is stored in
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin
//
@ -56,7 +56,7 @@ async fn main() {
// Submit the transaction
let _response = wallet_core
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await
.unwrap();
}

View File

@ -1,16 +1,16 @@
use std::collections::HashMap;
use nssa::{
use lee::{
AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies,
program::Program,
};
use wallet::{PrivacyPreservingAccount, WalletCore};
use wallet::{AccountIdentity, WalletCore};
// Before running this example, compile the `simple_tail_call.rs` guest program with:
//
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
//
// Note: you must run the above command from the root of the `lssa` repository.
// Note: you must run the above command from the root of the `logos-execution-zone` repository.
// Note: The compiled binary file is stored in
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin
//
@ -51,7 +51,7 @@ async fn main() {
std::iter::once((hello_world.id(), hello_world)).collect();
let program_with_dependencies = ProgramWithDependencies::new(simple_tail_call, dependencies);
let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)];
let accounts = vec![AccountIdentity::PrivateOwned(account_id)];
// Construct and submit the privacy-preserving transaction
let instruction = ();

View File

@ -1,5 +1,5 @@
use common::transaction::NSSATransaction;
use nssa::{
use common::transaction::LeeTransaction;
use lee::{
AccountId, PublicTransaction,
program::Program,
public_transaction::{Message, WitnessSet},
@ -11,7 +11,7 @@ use wallet::WalletCore;
//
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
//
// Note: you must run the above command from the root of the `lssa` repository.
// Note: you must run the above command from the root of the `logos-execution-zone` repository.
// Note: The compiled binary file is stored in
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world_with_authorization.bin
//
@ -50,8 +50,8 @@ async fn main() {
// Load signing keys to provide authorization
let signing_key = wallet_core
.storage()
.user_data
.get_pub_account_signing_key(account_id)
.key_chain()
.pub_account_signing_key(account_id)
.expect("Input account should be a self owned public account");
// Define the desired greeting in ASCII
@ -73,7 +73,7 @@ async fn main() {
// Submit the transaction
let _response = wallet_core
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await
.unwrap();
}

View File

@ -3,13 +3,13 @@
reason = "This is an example program, it's fine to print to stdout"
)]
use common::transaction::NSSATransaction;
use nssa::{
use common::transaction::LeeTransaction;
use lee::{
AccountId, PublicTransaction,
program::Program,
public_transaction::{Message, WitnessSet},
};
use nssa_core::program::PdaSeed;
use lee_core::program::PdaSeed;
use sequencer_service_rpc::RpcClient as _;
use wallet::WalletCore;
@ -17,7 +17,7 @@ use wallet::WalletCore;
//
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
//
// Note: you must run the above command from the root of the `lssa` repository.
// Note: you must run the above command from the root of the `logos-execution-zone` repository.
// Note: The compiled binary file is stored in
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin
//
@ -46,7 +46,7 @@ async fn main() {
let program = Program::new(bytecode).unwrap();
// Compute the PDA to pass it as input account to the public execution
let pda = AccountId::from((&program.id(), &PDA_SEED));
let pda = AccountId::for_public_pda(&program.id(), &PDA_SEED);
let account_ids = vec![pda];
let instruction_data = ();
let nonces = vec![];
@ -58,7 +58,7 @@ async fn main() {
// Submit the transaction
let _response = wallet_core
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await
.unwrap();

View File

@ -1,14 +1,14 @@
use clap::{Parser, Subcommand};
use common::transaction::NSSATransaction;
use nssa::{PublicTransaction, program::Program, public_transaction};
use common::transaction::LeeTransaction;
use lee::{PublicTransaction, program::Program, public_transaction};
use sequencer_service_rpc::RpcClient as _;
use wallet::{PrivacyPreservingAccount, WalletCore};
use wallet::{AccountIdentity, WalletCore};
// Before running this example, compile the `hello_world_with_move_function.rs` guest program with:
//
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
//
// Note: you must run the above command from the root of the `lssa` repository.
// Note: you must run the above command from the root of the `logos-execution-zone` repository.
// Note: The compiled binary file is stored in
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world_with_move_function.bin
//
@ -89,7 +89,7 @@ async fn main() {
// Submit the transaction
let _response = wallet_core
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await
.unwrap();
}
@ -99,7 +99,7 @@ async fn main() {
} => {
let instruction: Instruction = (WRITE_FUNCTION_ID, greeting.into_bytes());
let account_id = account_id.parse().unwrap();
let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)];
let accounts = vec![AccountIdentity::PrivateOwned(account_id)];
wallet_core
.send_privacy_preserving_tx(
@ -128,7 +128,7 @@ async fn main() {
// Submit the transaction
let _response = wallet_core
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await
.unwrap();
}
@ -138,8 +138,8 @@ async fn main() {
let to = to.parse().unwrap();
let accounts = vec![
PrivacyPreservingAccount::Public(from),
PrivacyPreservingAccount::PrivateOwned(to),
AccountIdentity::Public(from),
AccountIdentity::PrivateOwned(to),
];
wallet_core

24
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": {
"crane": {
"locked": {
"lastModified": 1769737823,
"narHash": "sha256-DrBaNpZ+sJ4stXm+0nBX7zqZT9t9P22zbk6m5YhQxS4=",
"lastModified": 1776396856,
"narHash": "sha256-aRJpIJUlZLaf06ekPvqjuU46zvO9K90IxJGpbqodkPs=",
"owner": "ipetkov",
"repo": "crane",
"rev": "b2f45c3830aa96b7456a4c4bc327d04d7a43e1ba",
"rev": "28462d6d55c33206ffa5a56c7907ca3125ed788f",
"type": "github"
},
"original": {
@ -20,11 +20,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1770979891,
"narHash": "sha256-cvkVnE7btuFLzv70ORAZve9K1Huiplq0iECgXSXb0ZY=",
"lastModified": 1775835011,
"narHash": "sha256-SQDLyyRUa5J9QHjNiHbeZw4rQOZnTEo61TcaUpjtLBs=",
"owner": "logos-blockchain",
"repo": "logos-blockchain-circuits",
"rev": "ec7d298e5a3a0507bb8570df86cdf78dc452d024",
"rev": "d6cf41f66500d4afc157b4f43de0f0d5bfa01443",
"type": "github"
},
"original": {
@ -51,11 +51,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1770019141,
"narHash": "sha256-VKS4ZLNx4PNrABoB0L8KUpc1fE7CLpQXQs985tGfaCU=",
"lastModified": 1776169885,
"narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "cb369ef2efd432b3cdf8622b0ffc0a97a02f3137",
"rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9",
"type": "github"
},
"original": {
@ -80,11 +80,11 @@
]
},
"locked": {
"lastModified": 1770088046,
"narHash": "sha256-4hfYDnUTvL1qSSZEA4CEThxfz+KlwSFQ30Z9jgDguO0=",
"lastModified": 1776395632,
"narHash": "sha256-Mi1uF5f2FsdBIvy+v7MtsqxD3Xjhd0ARJdwoqqqPtJo=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "71f9daa4e05e49c434d08627e755495ae222bc34",
"rev": "8087ff1f47fff983a1fba70fa88b759f2fd8ae97",
"type": "github"
},
"original": {

View File

@ -2,7 +2,9 @@
description = "Logos Execution Zone";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
logos-liblogos.url = "github:logos-co/logos-liblogos";
nixpkgs.follows = "logos-liblogos/nixpkgs";
rust-overlay = {
url = "github:oxalica/rust-overlay";
@ -130,9 +132,26 @@
'';
}
);
indexerFfiPackage = craneLib.buildPackage (
commonArgs
// {
pname = "logos-execution-zone-indexer-ffi";
version = "0.1.0";
cargoExtraArgs = "-p indexer_ffi";
postInstall = ''
mkdir -p $out/include
cp indexer/ffi/indexer_ffi.h $out/include/
''
+ pkgs.lib.optionalString pkgs.stdenv.isDarwin ''
install_name_tool -id @rpath/libindexer_ffi.dylib $out/lib/libindexer_ffi.dylib
'';
}
);
in
{
wallet = walletFfiPackage;
indexer = indexerFfiPackage;
default = walletFfiPackage;
}
);
@ -144,9 +163,14 @@
walletFfiShell = pkgs.mkShell {
inputsFrom = [ walletFfiPackage ];
};
indexerFfiPackage = self.packages.${system}.indexer;
indexerFfiShell = pkgs.mkShell {
inputsFrom = [ indexerFfiPackage ];
};
in
{
wallet = walletFfiShell;
indexer = indexerFfiShell;
default = walletFfiShell;
}
);

View File

@ -1,268 +0,0 @@
use std::{path::Path, sync::Arc};
use anyhow::Result;
use bedrock_client::HeaderId;
use common::{
block::{BedrockStatus, Block},
transaction::{NSSATransaction, clock_invocation},
};
use nssa::{Account, AccountId, V03State};
use nssa_core::BlockId;
use storage::indexer::RocksDBIO;
use tokio::sync::RwLock;
#[derive(Clone)]
pub struct IndexerStore {
dbio: Arc<RocksDBIO>,
current_state: Arc<RwLock<V03State>>,
}
impl IndexerStore {
/// Starting database at the start of new chain.
/// Creates files if necessary.
///
/// ATTENTION: Will overwrite genesis block.
pub fn open_db_with_genesis(
location: &Path,
genesis_block: &Block,
initial_state: &V03State,
) -> Result<Self> {
let dbio = RocksDBIO::open_or_create(location, genesis_block, initial_state)?;
let current_state = dbio.final_state()?;
Ok(Self {
dbio: Arc::new(dbio),
current_state: Arc::new(RwLock::new(current_state)),
})
}
pub fn last_observed_l1_lib_header(&self) -> Result<Option<HeaderId>> {
Ok(self
.dbio
.get_meta_last_observed_l1_lib_header_in_db()?
.map(HeaderId::from))
}
pub fn get_last_block_id(&self) -> Result<u64> {
Ok(self.dbio.get_meta_last_block_in_db()?)
}
pub fn get_block_at_id(&self, id: u64) -> Result<Option<Block>> {
Ok(self.dbio.get_block(id)?)
}
pub fn get_block_batch(&self, before: Option<BlockId>, limit: u64) -> Result<Vec<Block>> {
Ok(self.dbio.get_block_batch(before, limit)?)
}
pub fn get_transaction_by_hash(&self, tx_hash: [u8; 32]) -> Result<Option<NSSATransaction>> {
let Some(block_id) = self.dbio.get_block_id_by_tx_hash(tx_hash)? else {
return Ok(None);
};
let Some(block) = self.get_block_at_id(block_id)? else {
return Ok(None);
};
Ok(block
.body
.transactions
.into_iter()
.find(|enc_tx| enc_tx.hash().0 == tx_hash))
}
pub fn get_block_by_hash(&self, hash: [u8; 32]) -> Result<Option<Block>> {
let Some(id) = self.dbio.get_block_id_by_hash(hash)? else {
return Ok(None);
};
self.get_block_at_id(id)
}
pub fn get_transactions_by_account(
&self,
acc_id: [u8; 32],
offset: u64,
limit: u64,
) -> Result<Vec<NSSATransaction>> {
Ok(self.dbio.get_acc_transactions(acc_id, offset, limit)?)
}
#[must_use]
pub fn genesis_id(&self) -> u64 {
self.dbio
.get_meta_first_block_in_db()
.expect("Must be set at the DB startup")
}
#[must_use]
pub fn last_block(&self) -> u64 {
self.dbio
.get_meta_last_block_in_db()
.expect("Must be set at the DB startup")
}
pub fn get_state_at_block(&self, block_id: u64) -> Result<V03State> {
Ok(self.dbio.calculate_state_for_id(block_id)?)
}
/// Recalculation of final state directly from DB.
///
/// Used for indexer healthcheck.
pub fn recalculate_final_state(&self) -> Result<V03State> {
Ok(self.dbio.final_state()?)
}
pub async fn account_current_state(&self, account_id: &AccountId) -> Result<Account> {
Ok(self
.current_state
.read()
.await
.get_account_by_id(*account_id))
}
pub async fn put_block(&self, mut block: Block, l1_header: HeaderId) -> Result<()> {
{
let mut state_guard = self.current_state.write().await;
let (clock_tx, user_txs) = block
.body
.transactions
.split_last()
.ok_or_else(|| anyhow::anyhow!("Block has no transactions"))?;
anyhow::ensure!(
*clock_tx == NSSATransaction::Public(clock_invocation(block.header.timestamp)),
"Last transaction in block must be the clock invocation for the block timestamp"
);
for transaction in user_txs {
transaction
.clone()
.transaction_stateless_check()?
.execute_check_on_state(
&mut state_guard,
block.header.block_id,
block.header.timestamp,
)?;
}
// Apply the clock invocation directly (it is expected to modify clock accounts).
let NSSATransaction::Public(clock_public_tx) = clock_tx else {
anyhow::bail!("Clock invocation must be a public transaction");
};
state_guard.transition_from_public_transaction(
clock_public_tx,
block.header.block_id,
block.header.timestamp,
)?;
}
// ToDo: Currently we are fetching only finalized blocks
// if it changes, the following lines need to be updated
// to represent correct block finality
block.bedrock_status = BedrockStatus::Finalized;
Ok(self.dbio.put_block(&block, l1_header.into())?)
}
}
#[cfg(test)]
mod tests {
use nssa::{AccountId, PublicKey};
use tempfile::tempdir;
use super::*;
fn genesis_block() -> Block {
common::test_utils::produce_dummy_block(1, None, vec![])
}
fn acc1_sign_key() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([1; 32]).unwrap()
}
fn acc2_sign_key() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([2; 32]).unwrap()
}
fn acc1() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&acc1_sign_key()))
}
fn acc2() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&acc2_sign_key()))
}
#[test]
fn correct_startup() {
let home = tempdir().unwrap();
let storage = IndexerStore::open_db_with_genesis(
home.as_ref(),
&genesis_block(),
&nssa::V03State::new_with_genesis_accounts(
&[(acc1(), 10000), (acc2(), 20000)],
vec![],
0,
),
)
.unwrap();
let block = storage.get_block_at_id(1).unwrap().unwrap();
let final_id = storage.get_last_block_id().unwrap();
assert_eq!(block.header.hash, genesis_block().header.hash);
assert_eq!(final_id, 1);
}
#[tokio::test]
async fn state_transition() {
let home = tempdir().unwrap();
let storage = IndexerStore::open_db_with_genesis(
home.as_ref(),
&genesis_block(),
&nssa::V03State::new_with_genesis_accounts(
&[(acc1(), 10000), (acc2(), 20000)],
vec![],
0,
),
)
.unwrap();
let mut prev_hash = genesis_block().header.hash;
let from = acc1();
let to = acc2();
let sign_key = acc1_sign_key();
for i in 2..10 {
let tx = common::test_utils::create_transaction_native_token_transfer(
from,
i - 2,
to,
10,
&sign_key,
);
let block_id = u64::try_from(i).unwrap();
let block_timestamp = block_id.saturating_mul(100);
let clock_tx = NSSATransaction::Public(clock_invocation(block_timestamp));
let next_block = common::test_utils::produce_dummy_block(
block_id,
Some(prev_hash),
vec![tx, clock_tx],
);
prev_hash = next_block.header.hash;
storage
.put_block(next_block, HeaderId::from([u8::try_from(i).unwrap(); 32]))
.await
.unwrap();
}
let acc1_val = storage.account_current_state(&acc1()).await.unwrap();
let acc2_val = storage.account_current_state(&acc2()).await.unwrap();
assert_eq!(acc1_val.balance, 9920);
assert_eq!(acc2_val.balance, 20080);
}
}

View File

@ -1,380 +0,0 @@
use std::collections::VecDeque;
use anyhow::Result;
use bedrock_client::{BedrockClient, HeaderId};
use common::{
HashType, PINATA_BASE58,
block::{Block, HashableBlockData},
};
use log::{debug, error, info};
use logos_blockchain_core::mantle::{
Op, SignedMantleTx,
ops::channel::{ChannelId, inscribe::InscriptionOp},
};
use nssa::V03State;
use testnet_initial_state::initial_state_testnet;
use crate::{block_store::IndexerStore, config::IndexerConfig};
pub mod block_store;
pub mod config;
#[derive(Clone)]
pub struct IndexerCore {
pub bedrock_client: BedrockClient,
pub config: IndexerConfig,
pub store: IndexerStore,
}
#[derive(Clone)]
/// This struct represents one L1 block data fetched from backfilling.
pub struct BackfillBlockData {
l2_blocks: Vec<Block>,
l1_header: HeaderId,
}
#[derive(Clone)]
/// This struct represents data fetched fom backfilling in one iteration.
pub struct BackfillData {
block_data: VecDeque<BackfillBlockData>,
curr_fin_l1_lib_header: HeaderId,
}
impl IndexerCore {
pub fn new(config: IndexerConfig) -> Result<Self> {
let hashable_data = HashableBlockData {
block_id: 1,
transactions: vec![],
prev_block_hash: HashType([0; 32]),
timestamp: 0,
};
// Genesis creation is fine as it is,
// because it will be overwritten by sequencer.
// Therefore:
// ToDo: remove key from indexer config, use some default.
let signing_key = nssa::PrivateKey::try_new(config.signing_key).unwrap();
let channel_genesis_msg_id = [0; 32];
let genesis_block = hashable_data.into_pending_block(&signing_key, channel_genesis_msg_id);
let initial_private_accounts: Option<Vec<(nssa_core::Commitment, nssa_core::Nullifier)>> =
config.initial_private_accounts.as_ref().map(|accounts| {
accounts
.iter()
.map(|init_comm_data| {
let npk = &init_comm_data.npk;
let mut acc = init_comm_data.account.clone();
acc.program_owner =
nssa::program::Program::authenticated_transfer_program().id();
(
nssa_core::Commitment::new(npk, &acc),
nssa_core::Nullifier::for_account_initialization(npk),
)
})
.collect()
});
let init_accs: Option<Vec<(nssa::AccountId, u128)>> = config
.initial_public_accounts
.as_ref()
.map(|initial_accounts| {
initial_accounts
.iter()
.map(|acc_data| (acc_data.account_id, acc_data.balance))
.collect()
});
// If initial commitments or accounts are present in config, need to construct state from
// them
let state = if initial_private_accounts.is_some() || init_accs.is_some() {
let mut state = V03State::new_with_genesis_accounts(
&init_accs.unwrap_or_default(),
initial_private_accounts.unwrap_or_default(),
genesis_block.header.timestamp,
);
// ToDo: Remove after testnet
state.add_pinata_program(PINATA_BASE58.parse().unwrap());
state
} else {
initial_state_testnet()
};
let home = config.home.join("rocksdb");
Ok(Self {
bedrock_client: BedrockClient::new(
config.bedrock_client_config.backoff,
config.bedrock_client_config.addr.clone(),
config.bedrock_client_config.auth.clone(),
)?,
config,
store: IndexerStore::open_db_with_genesis(&home, &genesis_block, &state)?,
})
}
pub fn subscribe_parse_block_stream(&self) -> impl futures::Stream<Item = Result<Block>> {
async_stream::stream! {
info!("Searching for initial header");
let last_stored_l1_lib_header = self.store.last_observed_l1_lib_header()?;
let mut prev_last_l1_lib_header = if let Some(last_l1_lib_header) = last_stored_l1_lib_header {
info!("Last l1 lib header found: {last_l1_lib_header}");
last_l1_lib_header
} else {
info!("Last l1 lib header not found in DB");
info!("Searching for the start of a channel");
let BackfillData {
block_data: start_buff,
curr_fin_l1_lib_header: last_l1_lib_header,
} = self.search_for_channel_start().await?;
for BackfillBlockData {
l2_blocks: l2_block_vec,
l1_header,
} in start_buff {
let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect();
l2_blocks_parsed_ids.sort_unstable();
info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids);
for l2_block in l2_block_vec {
// TODO: proper fix is to make the sequencer's genesis include a
// trailing `clock_invocation(0)` (and have the indexer's
// `open_db_with_genesis` not pre-apply state transitions) so the
// inscribed genesis can flow through `put_block` like any other
// block. For now we skip re-applying it.
//
// The channel-start (block_id == 1) is the sequencer's genesis
// inscription that we re-discover during initial search. The
// indexer already has its own locally-constructed genesis in
// the store from `open_db_with_genesis`, so re-applying the
// inscribed copy is both redundant and would fail the strict
// block validation in `put_block` (the inscribed genesis lacks
// the trailing clock invocation).
if l2_block.header.block_id != 1 {
self.store.put_block(l2_block.clone(), l1_header).await?;
}
yield Ok(l2_block);
}
}
last_l1_lib_header
};
info!("Searching for initial header finished");
info!("Starting backfilling from {prev_last_l1_lib_header}");
loop {
let BackfillData {
block_data: buff,
curr_fin_l1_lib_header,
} = self
.backfill_to_last_l1_lib_header_id(prev_last_l1_lib_header, &self.config.channel_id)
.await
.inspect_err(|err| error!("Failed to backfill to last l1 lib header id with err {err:#?}"))?;
prev_last_l1_lib_header = curr_fin_l1_lib_header;
for BackfillBlockData {
l2_blocks: l2_block_vec,
l1_header: header,
} in buff {
let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect();
l2_blocks_parsed_ids.sort_unstable();
info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids);
for l2_block in l2_block_vec {
self.store.put_block(l2_block.clone(), header).await?;
yield Ok(l2_block);
}
}
}
}
}
async fn get_lib(&self) -> Result<HeaderId> {
Ok(self.bedrock_client.get_consensus_info().await?.lib)
}
async fn get_next_lib(&self, prev_lib: HeaderId) -> Result<HeaderId> {
loop {
let next_lib = self.get_lib().await?;
if next_lib == prev_lib {
info!(
"Wait {:?} to not spam the node",
self.config.consensus_info_polling_interval
);
tokio::time::sleep(self.config.consensus_info_polling_interval).await;
} else {
break Ok(next_lib);
}
}
}
/// WARNING: depending on channel state,
/// may take indefinite amount of time.
pub async fn search_for_channel_start(&self) -> Result<BackfillData> {
let mut curr_last_l1_lib_header = self.get_lib().await?;
let mut backfill_start = curr_last_l1_lib_header;
// ToDo: How to get root?
let mut backfill_limit = HeaderId::from([0; 32]);
// ToDo: Not scalable, initial buffer should be stored in DB to not run out of memory
// Don't want to complicate DB even more right now.
let mut block_buffer = VecDeque::new();
'outer: loop {
let mut cycle_header = curr_last_l1_lib_header;
loop {
let Some(cycle_block) = self.bedrock_client.get_block_by_id(cycle_header).await?
else {
// First run can reach root easily
// so here we are optimistic about L1
// failing to get parent.
break;
};
// It would be better to have id, but block does not have it, so slot will do.
info!(
"INITIAL SEARCH: Observed L1 block at slot {}",
cycle_block.header().slot().into_inner()
);
debug!(
"INITIAL SEARCH: This block header is {}",
cycle_block.header().id()
);
debug!(
"INITIAL SEARCH: This block parent is {}",
cycle_block.header().parent()
);
let (l2_block_vec, l1_header) =
parse_block_owned(&cycle_block, &self.config.channel_id);
info!("Parsed {} L2 blocks", l2_block_vec.len());
if !l2_block_vec.is_empty() {
block_buffer.push_front(BackfillBlockData {
l2_blocks: l2_block_vec.clone(),
l1_header,
});
}
if let Some(first_l2_block) = l2_block_vec.first()
&& first_l2_block.header.block_id == 1
{
info!("INITIAL_SEARCH: Found channel start");
break 'outer;
}
// Step back to parent
let parent = cycle_block.header().parent();
if parent == backfill_limit {
break;
}
cycle_header = parent;
}
info!("INITIAL_SEARCH: Reached backfill limit, refetching last l1 lib header");
block_buffer.clear();
backfill_limit = backfill_start;
curr_last_l1_lib_header = self.get_next_lib(curr_last_l1_lib_header).await?;
backfill_start = curr_last_l1_lib_header;
}
Ok(BackfillData {
block_data: block_buffer,
curr_fin_l1_lib_header: curr_last_l1_lib_header,
})
}
pub async fn backfill_to_last_l1_lib_header_id(
&self,
last_fin_l1_lib_header: HeaderId,
channel_id: &ChannelId,
) -> Result<BackfillData> {
let curr_fin_l1_lib_header = self.get_next_lib(last_fin_l1_lib_header).await?;
// ToDo: Not scalable, buffer should be stored in DB to not run out of memory
// Don't want to complicate DB even more right now.
let mut block_buffer = VecDeque::new();
let mut cycle_header = curr_fin_l1_lib_header;
loop {
let Some(cycle_block) = self.bedrock_client.get_block_by_id(cycle_header).await? else {
return Err(anyhow::anyhow!("Parent not found"));
};
if cycle_block.header().id() == last_fin_l1_lib_header {
break;
}
// Step back to parent
cycle_header = cycle_block.header().parent();
// It would be better to have id, but block does not have it, so slot will do.
info!(
"Observed L1 block at slot {}",
cycle_block.header().slot().into_inner()
);
let (l2_block_vec, l1_header) = parse_block_owned(&cycle_block, channel_id);
info!("Parsed {} L2 blocks", l2_block_vec.len());
if !l2_block_vec.is_empty() {
block_buffer.push_front(BackfillBlockData {
l2_blocks: l2_block_vec,
l1_header,
});
}
}
Ok(BackfillData {
block_data: block_buffer,
curr_fin_l1_lib_header,
})
}
}
fn parse_block_owned(
l1_block: &bedrock_client::Block<SignedMantleTx>,
decoded_channel_id: &ChannelId,
) -> (Vec<Block>, HeaderId) {
(
#[expect(
clippy::wildcard_enum_match_arm,
reason = "We are only interested in channel inscription ops, so it's fine to ignore the rest"
)]
l1_block
.transactions()
.flat_map(|tx| {
tx.mantle_tx.ops.iter().filter_map(|op| match op {
Op::ChannelInscribe(InscriptionOp {
channel_id,
inscription,
..
}) if channel_id == decoded_channel_id => {
borsh::from_slice::<Block>(inscription)
.inspect_err(|err| {
error!("Failed to deserialize our inscription with err: {err:#?}");
})
.ok()
}
_ => None,
})
})
.collect(),
l1_block.header().id(),
)
}

View File

@ -1,160 +0,0 @@
{
"home": ".",
"consensus_info_polling_interval": "1s",
"bedrock_client_config": {
"addr": "http://localhost:8080",
"backoff": {
"start_delay": "100ms",
"max_retries": 5
}
},
"channel_id": "0101010101010101010101010101010101010101010101010101010101010101",
"initial_accounts": [
{
"account_id": "CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r",
"balance": 10000
},
{
"account_id": "2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2",
"balance": 20000
}
],
"initial_commitments": [
{
"npk": [
139,
19,
158,
11,
155,
231,
85,
206,
132,
228,
220,
114,
145,
89,
113,
156,
238,
142,
242,
74,
182,
91,
43,
100,
6,
190,
31,
15,
31,
88,
96,
204
],
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 10000,
"data": [],
"nonce": 0
}
},
{
"npk": [
173,
134,
33,
223,
54,
226,
10,
71,
215,
254,
143,
172,
24,
244,
243,
208,
65,
112,
118,
70,
217,
240,
69,
100,
129,
3,
121,
25,
213,
132,
42,
45
],
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 20000,
"data": [],
"nonce": 0
}
}
],
"signing_key": [
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37,
37
]
}

View File

@ -1,295 +0,0 @@
#![expect(
clippy::as_conversions,
clippy::arithmetic_side_effects,
clippy::cast_possible_truncation,
clippy::cast_lossless,
clippy::integer_division_remainder_used,
reason = "Mock service uses intentional casts and format patterns for test data generation"
)]
use std::collections::HashMap;
use indexer_service_protocol::{
Account, AccountId, BedrockStatus, Block, BlockBody, BlockHeader, BlockId, Commitment,
CommitmentSetDigest, Data, EncryptedAccountData, HashType, MantleMsgId,
PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage,
ProgramDeploymentTransaction, ProgramId, PublicMessage, PublicTransaction, Signature,
Transaction, ValidityWindow, WitnessSet,
};
use jsonrpsee::{
core::{SubscriptionResult, async_trait},
types::ErrorObjectOwned,
};
/// A mock implementation of the `IndexerService` RPC for testing purposes.
pub struct MockIndexerService {
blocks: Vec<Block>,
accounts: HashMap<AccountId, Account>,
transactions: HashMap<HashType, (Transaction, BlockId)>,
}
impl MockIndexerService {
#[must_use]
pub fn new_with_mock_blocks() -> Self {
let mut blocks = Vec::new();
let mut accounts = HashMap::new();
let mut transactions = HashMap::new();
// Create some mock accounts
let account_ids: Vec<AccountId> = (0..5)
.map(|i| {
let mut value = [0_u8; 32];
value[0] = i;
AccountId { value }
})
.collect();
for (i, account_id) in account_ids.iter().enumerate() {
accounts.insert(
*account_id,
Account {
program_owner: ProgramId([i as u32; 8]),
balance: 1000 * (i as u128 + 1),
data: Data(vec![0xaa, 0xbb, 0xcc]),
nonce: i as u128,
},
);
}
// Create 100 blocks with transactions
let mut prev_hash = HashType([0_u8; 32]);
for block_id in 1..=100 {
let block_hash = {
let mut hash = [0_u8; 32];
hash[0] = block_id as u8;
hash[1] = 0xff;
HashType(hash)
};
// Create 2-4 transactions per block (mix of Public, PrivacyPreserving, and
// ProgramDeployment)
let num_txs = 2 + (block_id % 3);
let mut block_transactions = Vec::new();
for tx_idx in 0..num_txs {
let tx_hash = {
let mut hash = [0_u8; 32];
hash[0] = block_id as u8;
hash[1] = tx_idx as u8;
HashType(hash)
};
// Vary transaction types: Public, PrivacyPreserving, or ProgramDeployment
let tx = match (block_id + tx_idx) % 5 {
// Public transactions (most common)
0 | 1 => Transaction::Public(PublicTransaction {
hash: tx_hash,
message: PublicMessage {
program_id: ProgramId([1_u32; 8]),
account_ids: vec![
account_ids[tx_idx as usize % account_ids.len()],
account_ids[(tx_idx as usize + 1) % account_ids.len()],
],
nonces: vec![block_id as u128, (block_id + 1) as u128],
instruction_data: vec![1, 2, 3, 4],
},
witness_set: WitnessSet {
signatures_and_public_keys: vec![],
proof: None,
},
}),
// PrivacyPreserving transactions
2 | 3 => Transaction::PrivacyPreserving(PrivacyPreservingTransaction {
hash: tx_hash,
message: PrivacyPreservingMessage {
public_account_ids: vec![
account_ids[tx_idx as usize % account_ids.len()],
],
nonces: vec![block_id as u128],
public_post_states: vec![Account {
program_owner: ProgramId([1_u32; 8]),
balance: 500,
data: Data(vec![0xdd, 0xee]),
nonce: block_id as u128,
}],
encrypted_private_post_states: vec![EncryptedAccountData {
ciphertext: indexer_service_protocol::Ciphertext(vec![
0x01, 0x02, 0x03, 0x04,
]),
epk: indexer_service_protocol::EphemeralPublicKey(vec![0xaa; 32]),
view_tag: 42,
}],
new_commitments: vec![Commitment([block_id as u8; 32])],
new_nullifiers: vec![(
indexer_service_protocol::Nullifier([tx_idx as u8; 32]),
CommitmentSetDigest([0xff; 32]),
)],
block_validity_window: ValidityWindow((None, None)),
timestamp_validity_window: ValidityWindow((None, None)),
},
witness_set: WitnessSet {
signatures_and_public_keys: vec![],
proof: Some(indexer_service_protocol::Proof(vec![0; 32])),
},
}),
// ProgramDeployment transactions (rare)
_ => Transaction::ProgramDeployment(ProgramDeploymentTransaction {
hash: tx_hash,
message: ProgramDeploymentMessage {
bytecode: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], /* WASM magic number */
},
}),
};
transactions.insert(tx_hash, (tx.clone(), block_id));
block_transactions.push(tx);
}
let block = Block {
header: BlockHeader {
block_id,
prev_block_hash: prev_hash,
hash: block_hash,
timestamp: 1_704_067_200_000 + (block_id * 12_000), // ~12 seconds per block
signature: Signature([0_u8; 64]),
},
body: BlockBody {
transactions: block_transactions,
},
bedrock_status: match block_id {
0..=5 => BedrockStatus::Finalized,
6..=8 => BedrockStatus::Safe,
_ => BedrockStatus::Pending,
},
bedrock_parent_id: MantleMsgId([0; 32]),
};
prev_hash = block_hash;
blocks.push(block);
}
Self {
blocks,
accounts,
transactions,
}
}
}
#[async_trait]
impl indexer_service_rpc::RpcServer for MockIndexerService {
async fn subscribe_to_finalized_blocks(
&self,
subscription_sink: jsonrpsee::PendingSubscriptionSink,
) -> SubscriptionResult {
let sink = subscription_sink.accept().await?;
for block in self
.blocks
.iter()
.filter(|b| b.bedrock_status == BedrockStatus::Finalized)
{
let json = serde_json::value::to_raw_value(block).unwrap();
sink.send(json).await?;
}
Ok(())
}
async fn get_last_finalized_block_id(&self) -> Result<BlockId, ErrorObjectOwned> {
self.blocks
.last()
.map(|bl| bl.header.block_id)
.ok_or_else(|| {
ErrorObjectOwned::owned(-32001, "Last block not found".to_owned(), None::<()>)
})
}
async fn get_block_by_id(&self, block_id: BlockId) -> Result<Option<Block>, ErrorObjectOwned> {
Ok(self
.blocks
.iter()
.find(|b| b.header.block_id == block_id)
.cloned())
}
async fn get_block_by_hash(
&self,
block_hash: HashType,
) -> Result<Option<Block>, ErrorObjectOwned> {
Ok(self
.blocks
.iter()
.find(|b| b.header.hash == block_hash)
.cloned())
}
async fn get_account(&self, account_id: AccountId) -> Result<Account, ErrorObjectOwned> {
self.accounts
.get(&account_id)
.cloned()
.ok_or_else(|| ErrorObjectOwned::owned(-32001, "Account not found", None::<()>))
}
async fn get_transaction(
&self,
tx_hash: HashType,
) -> Result<Option<Transaction>, ErrorObjectOwned> {
Ok(self.transactions.get(&tx_hash).map(|(tx, _)| tx.clone()))
}
async fn get_blocks(
&self,
before: Option<BlockId>,
limit: u64,
) -> Result<Vec<Block>, ErrorObjectOwned> {
let start_id = before.map_or_else(
|| self.blocks.len(),
|id| usize::try_from(id.saturating_sub(1)).expect("u64 should fit in usize"),
);
let result = (1..=start_id)
.rev()
.take(limit as usize)
.map_while(|block_id| self.blocks.get(block_id - 1).cloned())
.collect();
Ok(result)
}
async fn get_transactions_by_account(
&self,
account_id: AccountId,
offset: u64,
limit: u64,
) -> Result<Vec<Transaction>, ErrorObjectOwned> {
let mut account_txs: Vec<_> = self
.transactions
.values()
.filter(|(tx, _)| match tx {
Transaction::Public(pub_tx) => pub_tx.message.account_ids.contains(&account_id),
Transaction::PrivacyPreserving(priv_tx) => {
priv_tx.message.public_account_ids.contains(&account_id)
}
Transaction::ProgramDeployment(_) => false,
})
.collect();
// Sort by block ID descending (most recent first)
account_txs.sort_by_key(|b| std::cmp::Reverse(b.1));
let start = offset as usize;
if start >= account_txs.len() {
return Ok(Vec::new());
}
let end = (start + limit as usize).min(account_txs.len());
Ok(account_txs[start..end]
.iter()
.map(|(tx, _)| tx.clone())
.collect())
}
async fn healthcheck(&self) -> Result<(), ErrorObjectOwned> {
Ok(())
}
}

Some files were not shown because too many files have changed in this diff Show More