mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-06-08 00:09:32 +00:00
Compare commits
354 Commits
v0.2.0-rc2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
feb6cb7f92 | ||
|
|
190d1fe45a | ||
|
|
7f5590907f | ||
|
|
bdcf630aa9 | ||
|
|
2104f71e39 | ||
|
|
c64e40590b | ||
|
|
50be74580b | ||
|
|
f3ab53460a | ||
|
|
dda8a71070 | ||
|
|
9f1dc1d24b | ||
|
|
4bcffafe27 | ||
|
|
6c0713d70d | ||
|
|
d3390efc6d | ||
|
|
3046cccab5 | ||
|
|
d86e02139f | ||
|
|
0a7cdb1971 | ||
|
|
5e09b81d1c | ||
|
|
1322ce0ac4 | ||
|
|
913a627198 | ||
|
|
23fda575ed | ||
|
|
2a2da3726c | ||
|
|
0f347a7614 | ||
|
|
78633acdc3 | ||
|
|
9b8955daf5 | ||
|
|
6fe965a56f | ||
|
|
48da4b5119 | ||
|
|
b77cfc29c2 | ||
|
|
251397b291 | ||
|
|
84bda7be34 | ||
|
|
18b88a6018 | ||
|
|
71be6bae32 | ||
|
|
867d328cf3 | ||
|
|
7d69254b3e | ||
|
|
f3e2c4fc78 | ||
|
|
e3b5e4f19a | ||
|
|
006647bc83 | ||
|
|
23f427246d | ||
|
|
ac2d01e1b4 | ||
|
|
adf0d241c8 | ||
|
|
b5cecdebc0 | ||
|
|
fa47d471af | ||
|
|
bcd8577370 | ||
|
|
7546e22cf6 | ||
|
|
5f14ac1cfe | ||
|
|
cf9177a095 | ||
|
|
5543e125ee | ||
|
|
ebfc3e5ad2 | ||
|
|
fdec52791d | ||
|
|
d064f87ad7 | ||
|
|
a9bf3fbfe7 | ||
|
|
fb89e7549b | ||
|
|
b608d10ca1 | ||
|
|
3c6d623c49 | ||
|
|
694e484228 | ||
|
|
ef1e0e0fa4 | ||
|
|
bc852925d4 | ||
|
|
97d9188c38 | ||
|
|
dc4adfa1a2 | ||
|
|
94096bcdc6 | ||
|
|
bfdc087680 | ||
|
|
715d52f605 | ||
|
|
33b20bb480 | ||
|
|
b0a5b3478b | ||
|
|
ab77c5d26a | ||
|
|
0119b38c1b | ||
|
|
563a9ce0f7 | ||
|
|
932763fcf2 | ||
|
|
619db3846d | ||
|
|
c0e837b65d | ||
|
|
c3daa9897d | ||
|
|
7920b17c6d | ||
|
|
43e7fa9be2 | ||
|
|
832b21f74d | ||
|
|
20b9868ace | ||
|
|
534b0f8ee1 | ||
|
|
aa53e591d8 | ||
|
|
34b6e34642 | ||
|
|
0ab3075e78 | ||
|
|
3877b216e0 | ||
|
|
dbe8ac6160 | ||
|
|
8960df04d6 | ||
|
|
ba65b168dd | ||
|
|
87170b93b0 | ||
|
|
b84a3e8b44 | ||
|
|
28db42315b | ||
|
|
0210d70602 | ||
|
|
3732f16df9 | ||
|
|
518c0e0205 | ||
|
|
0e177f1eba | ||
|
|
58226fd0f7 | ||
|
|
57173cc140 | ||
|
|
891b23c18a | ||
|
|
4a8825e63c | ||
|
|
9efc26495b | ||
|
|
84a1fec942 | ||
|
|
2ae9e4da7f | ||
|
|
4079b0c9c8 | ||
|
|
8c8f5b57af | ||
|
|
7e31aa39e3 | ||
|
|
f721a00bdf | ||
|
|
ee5a98fc48 | ||
|
|
e359c1abe2 | ||
|
|
879cd5096a | ||
|
|
9075f30f19 | ||
|
|
89d1b95738 | ||
|
|
cff019dca4 | ||
|
|
5f207a3f02 | ||
|
|
e74fe36866 | ||
|
|
75e1cc51d5 | ||
|
|
1e2d41f941 | ||
|
|
e998ec7b99 | ||
|
|
ba84ba60ce | ||
|
|
7679c50ea8 | ||
|
|
2870bc364b | ||
|
|
5f30e382d1 | ||
|
|
3c8ff78319 | ||
|
|
05f41f81e9 | ||
|
|
497a8fa761 | ||
|
|
41fa494e32 | ||
|
|
36b7228b6d | ||
|
|
cfabd91510 | ||
|
|
ec74dca893 | ||
|
|
904dd56187 | ||
|
|
130f35e997 | ||
|
|
51b1e637a6 | ||
|
|
bc65c877af | ||
|
|
d38ca35337 | ||
|
|
67b6916b72 | ||
|
|
f3389571f8 | ||
|
|
ee0fecee25 | ||
|
|
b3acd46f11 | ||
|
|
355fe3842d | ||
|
|
927c24de68 | ||
|
|
a9baf5d3dc | ||
|
|
1ec145e7da | ||
|
|
b8a4d94d96 | ||
|
|
b05da5b426 | ||
|
|
54c039f639 | ||
|
|
d648505d89 | ||
|
|
190054c94d | ||
|
|
e9c0aa0858 | ||
|
|
b44551225c | ||
|
|
27e2850b5c | ||
|
|
6e376900f7 | ||
|
|
cf699fde7c | ||
|
|
fda862f5bc | ||
|
|
1bed9ecef2 | ||
|
|
06fd4fc12e | ||
|
|
3772046a39 | ||
|
|
2b2275ee74 | ||
|
|
4e7963c655 | ||
|
|
4ace6e1570 | ||
|
|
01eb4a58b8 | ||
|
|
89d28fb53e | ||
|
|
75ab606dcf | ||
|
|
a1a7924c85 | ||
|
|
8f6a519f0e | ||
|
|
f6d56cb3e4 | ||
|
|
69b81ea621 | ||
|
|
2d7d50646d | ||
|
|
bda50f1d2f | ||
|
|
45ccf14e77 | ||
|
|
6054ae113a | ||
|
|
f722d257a3 | ||
|
|
d4334c4694 | ||
|
|
755b49654e | ||
|
|
7250056f15 | ||
|
|
61dd8ec9b3 | ||
|
|
d24931c643 | ||
|
|
ce3229f74f | ||
|
|
fd82d0784d | ||
|
|
f73cd6738f | ||
|
|
5bf24b191d | ||
|
|
d0a88e91e1 | ||
|
|
cd545819e7 | ||
|
|
7be0ed926c | ||
|
|
fb4ddb055a | ||
|
|
1599fc655c | ||
|
|
0e914adf0a | ||
|
|
f3472ce87a | ||
|
|
332bd29e93 | ||
|
|
cf6eab2538 | ||
|
|
9e207450d6 | ||
|
|
11949e9fa1 | ||
|
|
7d5e1492c4 | ||
|
|
71ad4e0c85 | ||
|
|
95afb2065d | ||
|
|
51f718d9fb | ||
|
|
5bcb1d468b | ||
|
|
c263a98231 | ||
|
|
dd4670ab2f | ||
|
|
64a8ea5807 | ||
|
|
0eb128e515 | ||
|
|
8d9fa1224e | ||
|
|
fb48c82717 | ||
|
|
8a8bac8b69 | ||
|
|
f37454ed1e | ||
|
|
8517906025 | ||
|
|
970696b217 | ||
|
|
4690ca56cc | ||
|
|
b9ceda98cf | ||
|
|
98da9b26cc | ||
|
|
4c28133448 | ||
|
|
af3a31509e | ||
|
|
0920e086d9 | ||
|
|
265a269da0 | ||
|
|
f375a35929 | ||
|
|
9894319389 | ||
|
|
f3ab618268 | ||
|
|
b0a48883e1 | ||
|
|
8025780e26 | ||
|
|
18642f9b6c | ||
|
|
72756e8622 | ||
|
|
06a6983ef3 | ||
|
|
c327f592bf | ||
|
|
27649560bc | ||
|
|
9f6468682a | ||
|
|
460d7e7fd2 | ||
|
|
fede4977b7 | ||
|
|
798b89839c | ||
|
|
7aecc4faba | ||
|
|
c27e30fe57 | ||
|
|
113a68c22c | ||
|
|
d24313d999 | ||
|
|
7944a77251 | ||
|
|
81a9ec401c | ||
|
|
6bcbe058ed | ||
|
|
198eac1cf1 | ||
|
|
55a4a1d83b | ||
|
|
f7349656c7 | ||
|
|
1338dec951 | ||
|
|
a201fc646c | ||
|
|
89ea884207 | ||
|
|
ad2b4b66e4 | ||
|
|
a23e44a8df | ||
|
|
6738d8ef28 | ||
|
|
06681ef39d | ||
|
|
aea397565d | ||
|
|
f512a3bf0f | ||
|
|
f0b89f8acb | ||
|
|
c3f47f6685 | ||
|
|
eb3d3d8a8d | ||
|
|
924b30650c | ||
|
|
cf3639d825 | ||
|
|
478ba4c2f2 | ||
|
|
be8f5a6db2 | ||
|
|
37f59281c0 | ||
|
|
9927e6e690 | ||
|
|
88102d6964 | ||
|
|
02949e961a | ||
|
|
636fc9dd30 | ||
|
|
5b9cf95c47 | ||
|
|
48f95b1b7a | ||
|
|
f3215606fb | ||
|
|
85a6763490 | ||
|
|
e09cb6284e | ||
|
|
52992a124a | ||
|
|
b736b229ef | ||
|
|
063ad8e476 | ||
|
|
7c45b5af3c | ||
|
|
584bfb2052 | ||
|
|
6f9c3b2af3 | ||
|
|
9c90a6d182 | ||
|
|
e19c9ff20a | ||
|
|
4719b1265a | ||
|
|
a5565e0875 | ||
|
|
9fc2e39c10 | ||
|
|
00d3140490 | ||
|
|
9880a46bdc | ||
|
|
86ff3670c0 | ||
|
|
e5b77a27d5 | ||
|
|
22aa5ef70b | ||
|
|
ad6a55c55d | ||
|
|
9d2abc76a1 | ||
|
|
145198a078 | ||
|
|
b4d883e275 | ||
|
|
670527c2f1 | ||
|
|
33557b122f | ||
|
|
0183eac5cc | ||
|
|
3ec166ff7c | ||
|
|
42842dfbb1 | ||
|
|
68d43d7f2b | ||
|
|
d22c142a37 | ||
|
|
e8b17eef27 | ||
|
|
00cae12d41 | ||
|
|
34f8b6cac8 | ||
|
|
7ccd6ae331 | ||
|
|
6316f59777 | ||
|
|
c43418721e | ||
|
|
20a068a9e3 | ||
|
|
c30d435155 | ||
|
|
3dfbea9b66 | ||
|
|
a42144cb3c | ||
|
|
b34e301023 | ||
|
|
f28ac0f092 | ||
|
|
cb2a0ab4ee | ||
|
|
9e4e546c9e | ||
|
|
d5f97c3de4 | ||
|
|
d3577f02bc | ||
|
|
57831443e2 | ||
|
|
173f7ef58a | ||
|
|
f9a5a7635e | ||
|
|
a4e8b53817 | ||
|
|
3cf7972425 | ||
|
|
48478ca21e | ||
|
|
ca5f421188 | ||
|
|
4b09aae852 | ||
|
|
390bf59660 | ||
|
|
d1fd6fe945 | ||
|
|
bbb0aae17a | ||
|
|
ba93ec6f3e | ||
|
|
8da04ac898 | ||
|
|
93c6921eaf | ||
|
|
ac09e785a9 | ||
|
|
526c3cd978 | ||
|
|
f1b2c04f3d | ||
|
|
1fd4e4e8d9 | ||
|
|
661ef7c4e9 | ||
|
|
bda21fb5c5 | ||
|
|
4c050f35dd | ||
|
|
0ecec7016e | ||
|
|
3a3358e389 | ||
|
|
e08c8f93b4 | ||
|
|
0fd2682d2e | ||
|
|
b1553109ae | ||
|
|
8b9cc5accf | ||
|
|
4ab8696d85 | ||
|
|
a27da19a45 | ||
|
|
40a1227871 | ||
|
|
8fd25bc4bf | ||
|
|
4bdb1e7a22 | ||
|
|
47843eaa3e | ||
|
|
b0c10ee5a2 | ||
|
|
7e63f9ddcd | ||
|
|
cee9608ea7 | ||
|
|
308e1ebebf | ||
|
|
10b26ca223 | ||
|
|
3b78462e2d | ||
|
|
ac98fba1b1 | ||
|
|
985f610cea | ||
|
|
a4af8da13b | ||
|
|
12b8c0ad31 | ||
|
|
dae617c673 | ||
|
|
e11d4968d0 | ||
|
|
b864ff22d4 | ||
|
|
dd3ac54318 | ||
|
|
a921d63750 | ||
|
|
5fc397c2ee | ||
|
|
5f86e597d5 | ||
|
|
1ae7192c7a | ||
|
|
2cf7f5d724 | ||
|
|
cf420291e3 | ||
|
|
fee8d1623f | ||
|
|
5191370360 |
@ -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"
|
||||
|
||||
@ -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
44
.github/workflows/bench-regression.yml
vendored
Normal 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 }}
|
||||
126
.github/workflows/ci.yml
vendored
126
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
||||
8
.github/workflows/publish_images.yml
vendored
8
.github/workflows/publish_images.yml
vendored
@ -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
3
.gitignore
vendored
@ -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
84
CONTRIBUTING.md
Normal 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
3760
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
118
Cargo.toml
118
Cargo.toml
@ -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"
|
||||
|
||||
|
||||
24
Justfile
24
Justfile
@ -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
|
||||
|
||||
26
README.md
26
README.md
@ -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.
BIN
artifacts/program_methods/bridge.bin
Normal file
BIN
artifacts/program_methods/bridge.bin
Normal file
Binary file not shown.
Binary file not shown.
BIN
artifacts/program_methods/faucet.bin
Normal file
BIN
artifacts/program_methods/faucet.bin
Normal file
Binary file not shown.
BIN
artifacts/program_methods/genesis_supply_account.bin
Normal file
BIN
artifacts/program_methods/genesis_supply_account.bin
Normal file
Binary file not shown.
BIN
artifacts/program_methods/genesis_supply_private_account.bin
Normal file
BIN
artifacts/program_methods/genesis_supply_private_account.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
artifacts/program_methods/vault.bin
Normal file
BIN
artifacts/program_methods/vault.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/auth_asserting_noop.bin
Normal file
BIN
artifacts/test_program_methods/auth_asserting_noop.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/auth_transfer_proxy.bin
Normal file
BIN
artifacts/test_program_methods/auth_transfer_proxy.bin
Normal file
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.
BIN
artifacts/test_program_methods/faucet_chain_caller.bin
Normal file
BIN
artifacts/test_program_methods/faucet_chain_caller.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
artifacts/test_program_methods/group_pda_spender.bin
Normal file
BIN
artifacts/test_program_methods/group_pda_spender.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
artifacts/test_program_methods/malicious_injector.bin
Normal file
BIN
artifacts/test_program_methods/malicious_injector.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/malicious_launderer.bin
Normal file
BIN
artifacts/test_program_methods/malicious_launderer.bin
Normal file
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.
BIN
artifacts/test_program_methods/pda_claimer.bin
Normal file
BIN
artifacts/test_program_methods/pda_claimer.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/pda_fund_spend_proxy.bin
Normal file
BIN
artifacts/test_program_methods/pda_fund_spend_proxy.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/pda_spend_proxy.bin
Normal file
BIN
artifacts/test_program_methods/pda_spend_proxy.bin
Normal file
Binary file not shown.
Binary file not shown.
BIN
artifacts/test_program_methods/private_pda_claimer.bin
Normal file
BIN
artifacts/test_program_methods/private_pda_claimer.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/private_pda_delegator.bin
Normal file
BIN
artifacts/test_program_methods/private_pda_delegator.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/private_pda_spender.bin
Normal file
BIN
artifacts/test_program_methods/private_pda_spender.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
artifacts/test_program_methods/two_pda_claimer.bin
Normal file
BIN
artifacts/test_program_methods/two_pda_claimer.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
"")
|
||||
|
||||
@ -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 "$@"
|
||||
|
||||
@ -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
|
||||
]
|
||||
}
|
||||
@ -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
|
||||
]
|
||||
}
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
534
docs/LEZ testnet v0.1 tutorials/keycard.md
Normal file
534
docs/LEZ testnet v0.1 tutorials/keycard.md
Normal 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`).
|
||||

|
||||
- **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
|
||||
}
|
||||
```
|
||||
```
|
||||
@ -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 owner’s 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]
|
||||
> We’ll simulate transferring to someone else by creating a new private account we own and treating it as if it belonged to another user.
|
||||
> We’ll 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 recipient’s 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
11
docs/benchmarks/README.md
Normal 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.
|
||||
56
docs/benchmarks/crypto_primitives_bench.md
Normal file
56
docs/benchmarks/crypto_primitives_bench.md
Normal 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.
|
||||
101
docs/benchmarks/cycle_bench.md
Normal file
101
docs/benchmarks/cycle_bench.md
Normal 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.0–12.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.
|
||||
120
docs/benchmarks/integration_bench.md
Normal file
120
docs/benchmarks/integration_bench.md
Normal 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.
|
||||
@ -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
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ cargo install --path wallet --force
|
||||
```
|
||||
|
||||
# 1. Run the sequencer
|
||||
From the project’s root directory, start the sequencer by following [these instructions](https://github.com/logos-blockchain/lssa#run-the-sequencer-and-node).
|
||||
From the project’s 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 account’s signing key.
|
||||
- Private accounts: authorization requires that the circuit verifies knowledge of the account’s 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 account’s state during execution.
|
||||
- If a program is the program_owner of an account, it can freely mutate its fields.
|
||||
|
||||
@ -8,7 +8,7 @@ license = { workspace = true }
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nssa_core.workspace = true
|
||||
lee_core.workspace = true
|
||||
|
||||
hex.workspace = true
|
||||
bytemuck.workspace = true
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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, _) => {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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 = ();
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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
24
flake.lock
generated
@ -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": {
|
||||
|
||||
26
flake.nix
26
flake.nix
@ -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;
|
||||
}
|
||||
);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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(),
|
||||
)
|
||||
}
|
||||
@ -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
|
||||
]
|
||||
}
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user