mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-26 18:09:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff36f7f76b |
@ -13,9 +13,6 @@ 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-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-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-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" },
|
|
||||||
]
|
]
|
||||||
yanked = "deny"
|
yanked = "deny"
|
||||||
unused-ignored-advisory = "deny"
|
unused-ignored-advisory = "deny"
|
||||||
|
|||||||
@ -26,20 +26,11 @@ Thumbs.db
|
|||||||
ci_scripts/
|
ci_scripts/
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
docs/
|
|
||||||
*.md
|
*.md
|
||||||
!README.md
|
!README.md
|
||||||
|
|
||||||
# Non-build project files
|
# Configs (copy selectively if needed)
|
||||||
completions/
|
|
||||||
configs/
|
configs/
|
||||||
Justfile
|
|
||||||
clippy.toml
|
|
||||||
rustfmt.toml
|
|
||||||
flake.nix
|
|
||||||
flake.lock
|
|
||||||
LICENSE
|
|
||||||
|
|
||||||
# Docker compose files (not needed inside build)
|
# License
|
||||||
docker-compose*.yml
|
LICENSE
|
||||||
**/docker-compose*.yml
|
|
||||||
|
|||||||
44
.github/workflows/bench-regression.yml
vendored
44
.github/workflows/bench-regression.yml
vendored
@ -1,44 +0,0 @@
|
|||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "tools/crypto_primitives_bench/**"
|
|
||||||
- "key_protocol/**"
|
|
||||||
- "nssa/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 }}
|
|
||||||
150
.github/workflows/ci.yml
vendored
150
.github/workflows/ci.yml
vendored
@ -11,10 +11,6 @@ on:
|
|||||||
- "**.md"
|
- "**.md"
|
||||||
- "!.github/workflows/*.yml"
|
- "!.github/workflows/*.yml"
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
|
|
||||||
name: General
|
name: General
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -23,7 +19,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- name: Install nightly toolchain for rustfmt
|
- name: Install nightly toolchain for rustfmt
|
||||||
run: rustup install nightly --profile minimal --component rustfmt
|
run: rustup install nightly --profile minimal --component rustfmt
|
||||||
@ -36,7 +32,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- name: Install taplo-cli
|
- name: Install taplo-cli
|
||||||
run: cargo install --locked taplo-cli
|
run: cargo install --locked taplo-cli
|
||||||
@ -49,7 +45,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- name: Install active toolchain
|
- name: Install active toolchain
|
||||||
run: rustup install
|
run: rustup install
|
||||||
@ -65,7 +61,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- name: Install cargo-deny
|
- name: Install cargo-deny
|
||||||
run: cargo install --locked cargo-deny
|
run: cargo install --locked cargo-deny
|
||||||
@ -81,7 +77,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- uses: ./.github/actions/install-system-deps
|
- uses: ./.github/actions/install-system-deps
|
||||||
|
|
||||||
@ -94,12 +90,6 @@ jobs:
|
|||||||
- name: Install active toolchain
|
- name: Install active toolchain
|
||||||
run: rustup install
|
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
|
- name: Lint workspace
|
||||||
env:
|
env:
|
||||||
RISC0_SKIP_BUILD: "1"
|
RISC0_SKIP_BUILD: "1"
|
||||||
@ -116,7 +106,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- uses: ./.github/actions/install-system-deps
|
- uses: ./.github/actions/install-system-deps
|
||||||
|
|
||||||
@ -129,12 +119,6 @@ jobs:
|
|||||||
- name: Install active toolchain
|
- name: Install active toolchain
|
||||||
run: rustup install
|
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
|
- name: Install nextest
|
||||||
run: cargo install --locked cargo-nextest
|
run: cargo install --locked cargo-nextest
|
||||||
|
|
||||||
@ -142,80 +126,15 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RISC0_DEV_MODE: "1"
|
RISC0_DEV_MODE: "1"
|
||||||
RUST_LOG: "info"
|
RUST_LOG: "info"
|
||||||
run: cargo nextest run --workspace --exclude integration_tests --all-features
|
run: cargo nextest run --workspace --exclude integration_tests
|
||||||
|
|
||||||
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:
|
integration-tests:
|
||||||
needs: integration-tests-prebuild
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 90
|
timeout-minutes: 60
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target: ${{ fromJson(needs.integration-tests-prebuild.outputs.targets) }}
|
|
||||||
name: integration-tests (${{ matrix.target }})
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- uses: ./.github/actions/install-system-deps
|
- uses: ./.github/actions/install-system-deps
|
||||||
|
|
||||||
@ -228,10 +147,33 @@ jobs:
|
|||||||
- name: Install active toolchain
|
- name: Install active toolchain
|
||||||
run: rustup install
|
run: rustup install
|
||||||
|
|
||||||
- name: Download integration test archive
|
- name: Install nextest
|
||||||
uses: actions/download-artifact@v4
|
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
|
||||||
with:
|
with:
|
||||||
name: integration-tests-archive
|
ref: ${{ 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: Install nextest
|
- name: Install nextest
|
||||||
run: cargo install --locked cargo-nextest
|
run: cargo install --locked cargo-nextest
|
||||||
@ -240,15 +182,15 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RISC0_DEV_MODE: "1"
|
RISC0_DEV_MODE: "1"
|
||||||
RUST_LOG: "info"
|
RUST_LOG: "info"
|
||||||
run: cargo nextest run --archive-file integration-tests.tar.zst -E "binary(${{ matrix.target }})"
|
run: cargo nextest run -p integration_tests indexer -- --skip tps_test
|
||||||
|
|
||||||
valid-proof-test:
|
valid-proof-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 90
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- uses: ./.github/actions/install-system-deps
|
- uses: ./.github/actions/install-system-deps
|
||||||
|
|
||||||
@ -261,12 +203,6 @@ jobs:
|
|||||||
- name: Install active toolchain
|
- name: Install active toolchain
|
||||||
run: rustup install
|
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
|
- name: Test valid proof
|
||||||
env:
|
env:
|
||||||
RUST_LOG: "info"
|
RUST_LOG: "info"
|
||||||
@ -280,18 +216,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- uses: ./.github/actions/install-risc0
|
- 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
|
- name: Install just
|
||||||
run: cargo install --locked just
|
run: cargo install just
|
||||||
|
|
||||||
- name: Build artifacts
|
- name: Build artifacts
|
||||||
run: just build-artifacts
|
run: just build-artifacts
|
||||||
|
|||||||
@ -2,9 +2,6 @@ name: Publish Docker Images
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
@ -12,12 +9,12 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- name: sequencer_service
|
- name: sequencer_runner
|
||||||
dockerfile: ./sequencer/service/Dockerfile
|
dockerfile: ./sequencer_runner/Dockerfile
|
||||||
build_args: |
|
build_args: |
|
||||||
STANDALONE=false
|
STANDALONE=false
|
||||||
- name: sequencer_service-standalone
|
- name: sequencer_runner-standalone
|
||||||
dockerfile: ./sequencer/service/Dockerfile
|
dockerfile: ./sequencer_runner/Dockerfile
|
||||||
build_args: |
|
build_args: |
|
||||||
STANDALONE=true
|
STANDALONE=true
|
||||||
- name: indexer_service
|
- name: indexer_service
|
||||||
@ -45,12 +42,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
images: ${{ secrets.DOCKER_REGISTRY }}/${{ github.repository }}/${{ matrix.name }}
|
images: ${{ secrets.DOCKER_REGISTRY }}/${{ github.repository }}/${{ matrix.name }}
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=tag
|
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=sha,prefix=sha-
|
type=sha,prefix={{branch}}-
|
||||||
type=raw,value=latest,enable={{is_default_branch}}
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,7 +6,7 @@ data/
|
|||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
rocksdb
|
rocksdb
|
||||||
sequencer/service/data/
|
sequencer_runner/data/
|
||||||
storage.json
|
storage.json
|
||||||
result
|
result
|
||||||
wallet-ffi/wallet_ffi.h
|
wallet-ffi/wallet_ffi.h
|
||||||
|
|||||||
@ -1,84 +0,0 @@
|
|||||||
# 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(nssa): add private PDA support`
|
|
||||||
- `fix(wallet): correct fee calculation`
|
|
||||||
- `feat(nssa)!: 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.
|
|
||||||
3640
Cargo.lock
generated
3640
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
218
Cargo.toml
218
Cargo.toml
@ -15,18 +15,11 @@ members = [
|
|||||||
"nssa/core",
|
"nssa/core",
|
||||||
"programs/amm/core",
|
"programs/amm/core",
|
||||||
"programs/amm",
|
"programs/amm",
|
||||||
"programs/clock/core",
|
|
||||||
"programs/token/core",
|
"programs/token/core",
|
||||||
"programs/token",
|
"programs/token",
|
||||||
"programs/associated_token_account/core",
|
"sequencer_core",
|
||||||
"programs/associated_token_account",
|
"sequencer_rpc",
|
||||||
"programs/authenticated_transfer/core",
|
"sequencer_runner",
|
||||||
"programs/faucet/core",
|
|
||||||
"programs/vault/core",
|
|
||||||
"sequencer/core",
|
|
||||||
"sequencer/service",
|
|
||||||
"sequencer/service/protocol",
|
|
||||||
"sequencer/service/rpc",
|
|
||||||
"indexer/core",
|
"indexer/core",
|
||||||
"indexer/service",
|
"indexer/service",
|
||||||
"indexer/service/protocol",
|
"indexer/service/protocol",
|
||||||
@ -39,13 +32,7 @@ members = [
|
|||||||
"examples/program_deployment",
|
"examples/program_deployment",
|
||||||
"examples/program_deployment/methods",
|
"examples/program_deployment/methods",
|
||||||
"examples/program_deployment/methods/guest",
|
"examples/program_deployment/methods/guest",
|
||||||
"testnet_initial_state",
|
"bedrock_client",
|
||||||
"indexer/ffi",
|
|
||||||
"keycard_wallet",
|
|
||||||
"test_fixtures",
|
|
||||||
"tools/cycle_bench",
|
|
||||||
"tools/crypto_primitives_bench",
|
|
||||||
"tools/integration_bench",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
@ -55,43 +42,32 @@ common = { path = "common" }
|
|||||||
mempool = { path = "mempool" }
|
mempool = { path = "mempool" }
|
||||||
storage = { path = "storage" }
|
storage = { path = "storage" }
|
||||||
key_protocol = { path = "key_protocol" }
|
key_protocol = { path = "key_protocol" }
|
||||||
sequencer_core = { path = "sequencer/core" }
|
sequencer_core = { path = "sequencer_core" }
|
||||||
sequencer_service_protocol = { path = "sequencer/service/protocol" }
|
sequencer_rpc = { path = "sequencer_rpc" }
|
||||||
sequencer_service_rpc = { path = "sequencer/service/rpc" }
|
sequencer_runner = { path = "sequencer_runner" }
|
||||||
sequencer_service = { path = "sequencer/service" }
|
|
||||||
indexer_core = { path = "indexer/core" }
|
indexer_core = { path = "indexer/core" }
|
||||||
indexer_service = { path = "indexer/service" }
|
indexer_service = { path = "indexer/service" }
|
||||||
indexer_service_protocol = { path = "indexer/service/protocol" }
|
indexer_service_protocol = { path = "indexer/service/protocol" }
|
||||||
indexer_service_rpc = { path = "indexer/service/rpc" }
|
indexer_service_rpc = { path = "indexer/service/rpc" }
|
||||||
wallet = { path = "wallet" }
|
wallet = { path = "wallet" }
|
||||||
wallet-ffi = { path = "wallet-ffi", default-features = false }
|
wallet-ffi = { path = "wallet-ffi", default-features = false }
|
||||||
indexer_ffi = { path = "indexer/ffi" }
|
|
||||||
clock_core = { path = "programs/clock/core" }
|
|
||||||
token_core = { path = "programs/token/core" }
|
token_core = { path = "programs/token/core" }
|
||||||
token_program = { path = "programs/token" }
|
token_program = { path = "programs/token" }
|
||||||
amm_core = { path = "programs/amm/core" }
|
amm_core = { path = "programs/amm/core" }
|
||||||
amm_program = { path = "programs/amm" }
|
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" }
|
|
||||||
vault_core = { path = "programs/vault/core" }
|
|
||||||
test_program_methods = { path = "test_program_methods" }
|
test_program_methods = { path = "test_program_methods" }
|
||||||
testnet_initial_state = { path = "testnet_initial_state" }
|
bedrock_client = { path = "bedrock_client" }
|
||||||
keycard_wallet = { path = "keycard_wallet" }
|
|
||||||
test_fixtures = { path = "test_fixtures" }
|
|
||||||
|
|
||||||
tokio = { version = "1.50", features = [
|
tokio = { version = "1.28.2", features = [
|
||||||
"net",
|
"net",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"sync",
|
"sync",
|
||||||
"fs",
|
"fs",
|
||||||
] }
|
] }
|
||||||
tokio-util = "0.7.18"
|
tokio-util = "0.7.18"
|
||||||
risc0-zkvm = { version = "3.0.5", default-features = false, features = ['std'] }
|
risc0-zkvm = { version = "3.0.5", features = ['std'] }
|
||||||
risc0-build = "3.0.5"
|
risc0-build = "3.0.5"
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
derive_more = "2.1.1"
|
|
||||||
num_cpus = "1.13.1"
|
num_cpus = "1.13.1"
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
openssl-probe = { version = "0.1.2" }
|
openssl-probe = { version = "0.1.2" }
|
||||||
@ -99,15 +75,15 @@ serde = { version = "1.0.60", default-features = false, features = ["derive"] }
|
|||||||
serde_json = "1.0.81"
|
serde_json = "1.0.81"
|
||||||
serde_with = "3.16.1"
|
serde_with = "3.16.1"
|
||||||
actix = "0.13.0"
|
actix = "0.13.0"
|
||||||
actix-cors = "0.7.1"
|
actix-cors = "0.6.1"
|
||||||
jsonrpsee = "0.26.0"
|
jsonrpsee = "0.26.0"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
actix-rt = "*"
|
actix-rt = "*"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
env_logger = "0.11"
|
env_logger = "0.10"
|
||||||
log = "0.4.28"
|
log = "0.4.28"
|
||||||
lru = "0.16.3"
|
lru = "0.7.8"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0.12"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
bytemuck = "1.24.0"
|
bytemuck = "1.24.0"
|
||||||
@ -115,7 +91,7 @@ bytesize = { version = "2.3.1", features = ["serde"] }
|
|||||||
humantime-serde = "1.1"
|
humantime-serde = "1.1"
|
||||||
humantime = "2.1"
|
humantime = "2.1"
|
||||||
aes-gcm = "0.10.3"
|
aes-gcm = "0.10.3"
|
||||||
toml = "0.9.8"
|
toml = "0.7.4"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
tempfile = "3.14.0"
|
tempfile = "3.14.0"
|
||||||
light-poseidon = "0.3.0"
|
light-poseidon = "0.3.0"
|
||||||
@ -131,16 +107,14 @@ base58 = "0.2.0"
|
|||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
url = { version = "2.5.4", features = ["serde"] }
|
url = { version = "2.5.4", features = ["serde"] }
|
||||||
tokio-retry = "0.3.0"
|
tokio-retry = "0.3.0"
|
||||||
schemars = "1.2"
|
schemars = "1.2.0"
|
||||||
async-stream = "0.3.6"
|
async-stream = "0.3.6"
|
||||||
criterion = { version = "0.8", features = ["html_reports"] }
|
|
||||||
|
|
||||||
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||||
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||||
logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||||
logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||||
logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||||
logos-blockchain-zone-sdk = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
|
||||||
|
|
||||||
rocksdb = { version = "0.24.0", default-features = false, features = [
|
rocksdb = { version = "0.24.0", default-features = false, features = [
|
||||||
"snappy",
|
"snappy",
|
||||||
@ -155,12 +129,11 @@ k256 = { version = "0.13.3", features = [
|
|||||||
"pem",
|
"pem",
|
||||||
] }
|
] }
|
||||||
elliptic-curve = { version = "0.13.8", features = ["arithmetic"] }
|
elliptic-curve = { version = "0.13.8", features = ["arithmetic"] }
|
||||||
actix-web = { version = "4.13.0", default-features = false, features = [
|
actix-web = { version = "=4.1.0", default-features = false, features = [
|
||||||
"macros",
|
"macros",
|
||||||
] }
|
] }
|
||||||
clap = { version = "4.5.42", features = ["derive", "env"] }
|
clap = { version = "4.5.42", features = ["derive", "env"] }
|
||||||
reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] }
|
reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] }
|
||||||
pyo3 = { version = "0.24", features = ["auto-initialize"] }
|
|
||||||
|
|
||||||
# Profile for leptos WASM release builds
|
# Profile for leptos WASM release builds
|
||||||
[profile.wasm-release]
|
[profile.wasm-release]
|
||||||
@ -168,150 +141,3 @@ inherits = "release"
|
|||||||
opt-level = 'z'
|
opt-level = 'z'
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
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"
|
|
||||||
|
|
||||||
[workspace.lints]
|
|
||||||
clippy.all = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Pedantic
|
|
||||||
clippy.pedantic = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Reason: documenting every function returning Result is too verbose and doesn't add much value when you have good error types.
|
|
||||||
clippy.missing-errors-doc = "allow"
|
|
||||||
# Reason: most of the panics are internal and not part of the public API, so documenting them is not necessary.
|
|
||||||
clippy.missing-panics-doc = "allow"
|
|
||||||
# Reason: this isn't always bad and actually works well for our financial and cryptography code.
|
|
||||||
clippy.similar-names = "allow"
|
|
||||||
# Reason: this lint is too strict and hard to fix.
|
|
||||||
clippy.too-many-lines = "allow"
|
|
||||||
# Reason: std hasher is fine for us in public functions.
|
|
||||||
clippy.implicit-hasher = "allow"
|
|
||||||
|
|
||||||
# Restriction
|
|
||||||
clippy.restriction = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Reason: we deny the whole `restriction` group but we allow things that don't make sense for us.
|
|
||||||
# That way we can still benefit from new lints added to the `restriction` group without having to
|
|
||||||
# explicitly allow them.
|
|
||||||
# As a downside our contributors don't know if some lint was enabled intentionally or just no one
|
|
||||||
# else faced it before to allow it but we can handle this during code reviews.
|
|
||||||
clippy.blanket-clippy-restriction-lints = "allow"
|
|
||||||
# Reason: we can't avoid using unwrap for now.
|
|
||||||
clippy.unwrap-used = "allow"
|
|
||||||
# Reason: we can't avoid using expect for now.
|
|
||||||
clippy.expect-used = "allow"
|
|
||||||
# Reason: unreachable is good in many cases.
|
|
||||||
clippy.unreachable = "allow"
|
|
||||||
# Reason: this is ridiculous strict in our codebase and doesn't add any value.
|
|
||||||
clippy.single-call-fn = "allow"
|
|
||||||
# Reason: we use panic in some places and it's okay.
|
|
||||||
clippy.panic = "allow"
|
|
||||||
# Reason: shadowing is good most of the times.
|
|
||||||
clippy.shadow-reuse = "allow"
|
|
||||||
# Reason: implicit return is good.
|
|
||||||
clippy.implicit-return = "allow"
|
|
||||||
# Reason: std is fine for us, we don't need to use core.
|
|
||||||
clippy.std-instead-of-core = "allow"
|
|
||||||
# Reason: std is fine for us, we don't need to use alloc.
|
|
||||||
clippy.std-instead-of-alloc = "allow"
|
|
||||||
# Reason: default methods are good most of the time.
|
|
||||||
clippy.missing-trait-methods = "allow"
|
|
||||||
# Reason: this is too verbose and doesn't help much if you have rust analyzer.
|
|
||||||
clippy.pattern-type-mismatch = "allow"
|
|
||||||
# Reason: decreases readability.
|
|
||||||
clippy.assertions-on-result-states = "allow"
|
|
||||||
# Reason: documenting every assert is too verbose.
|
|
||||||
clippy.missing-assert-message = "allow"
|
|
||||||
# Reason: documenting private items is too verbose and doesn't add much value.
|
|
||||||
clippy.missing-docs-in-private-items = "allow"
|
|
||||||
# Reason: we use separated suffix style.
|
|
||||||
clippy.separated_literal_suffix = "allow"
|
|
||||||
# Reason: sometimes absolute paths are more readable.
|
|
||||||
clippy.absolute-paths = "allow"
|
|
||||||
# Reason: sometimes it's as readable as full variable naming.
|
|
||||||
clippy.min-ident-chars = "allow"
|
|
||||||
# Reason: it's very common and handy.
|
|
||||||
clippy.indexing-slicing = "allow"
|
|
||||||
# Reason: we use little endian style.
|
|
||||||
clippy.little-endian-bytes = "allow"
|
|
||||||
# Reason: we use this style of pub visibility.
|
|
||||||
clippy.pub-with-shorthand = "allow"
|
|
||||||
# Reason: question mark operator is very cool.
|
|
||||||
clippy.question-mark-used = "allow"
|
|
||||||
# Reason: it's fine to panic in tests and some functions where it makes sense.
|
|
||||||
clippy.panic-in-result-fn = "allow"
|
|
||||||
# Reason: we don't care that much about inlining and LTO should take care of it.
|
|
||||||
clippy.missing_inline_in_public_items = "allow"
|
|
||||||
# Reason: it's okay for us.
|
|
||||||
clippy.default-numeric-fallback = "allow"
|
|
||||||
# Reason: this is fine for us.
|
|
||||||
clippy.exhaustive-enums = "allow"
|
|
||||||
# Reason: this is fine for us.
|
|
||||||
clippy.exhaustive-structs = "allow"
|
|
||||||
# Reason: this helps readability when item is imported in other modules.
|
|
||||||
clippy.module-name-repetitions = "allow"
|
|
||||||
# Reason: mostly historical reasons, maybe we'll address this in future.
|
|
||||||
clippy.mod-module-files = "allow"
|
|
||||||
# Reason: named module files is our preferred way.
|
|
||||||
clippy.self-named-module-files = "allow"
|
|
||||||
# Reason: this is actually quite handy.
|
|
||||||
clippy.impl-trait-in-params = "allow"
|
|
||||||
# Reason: this is often useful.
|
|
||||||
clippy.use-debug = "allow"
|
|
||||||
# Reason: this is sometimes useful.
|
|
||||||
clippy.field-scoped-visibility-modifiers = "allow"
|
|
||||||
# Reason: `pub use` is good for re-exports and hiding unnecessary details.
|
|
||||||
clippy.pub-use = "allow"
|
|
||||||
# Reason: we prefer semicolons inside blocks.
|
|
||||||
clippy.semicolon-outside-block = "allow"
|
|
||||||
# Reason: we don't do it blindly, this is mostly internal constraints checks.
|
|
||||||
clippy.unwrap-in-result = "allow"
|
|
||||||
# Reason: we don't see any problems with that.
|
|
||||||
clippy.shadow-same = "allow"
|
|
||||||
# Reason: this lint is too verbose.
|
|
||||||
clippy.let-underscore-untyped = "allow"
|
|
||||||
# Reason: this lint is actually bad as it forces to use wildcard `..` instead of
|
|
||||||
# field-by-field `_` which may lead to subtle bugs when new fields are added to the struct.
|
|
||||||
clippy.unneeded-field-pattern = "allow"
|
|
||||||
|
|
||||||
# Nursery
|
|
||||||
clippy.nursery = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Reason: this is okay if it compiles.
|
|
||||||
clippy.future-not-send = "allow"
|
|
||||||
# Reason: this is actually a good lint, but currently it gives a lot of false-positives.
|
|
||||||
clippy.significant-drop-tightening = "allow"
|
|
||||||
|
|
||||||
# Correctness
|
|
||||||
clippy.correctness = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Complexity
|
|
||||||
clippy.complexity = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Perf
|
|
||||||
clippy.perf = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Suspicious
|
|
||||||
clippy.suspicious = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Style
|
|
||||||
clippy.style = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Cargo
|
|
||||||
clippy.cargo = { level = "deny", priority = -1 }
|
|
||||||
|
|
||||||
# Reason: we're not at this stage yet and it will be a pain to create a new crate.
|
|
||||||
clippy.cargo-common-metadata = "allow"
|
|
||||||
# Reason: hard to address right now and mostly comes from dependencies
|
|
||||||
# so the fix would be just a long list of exceptions.
|
|
||||||
clippy.multiple-crate-versions = "allow"
|
|
||||||
|
|||||||
27
Justfile
27
Justfile
@ -23,12 +23,6 @@ test:
|
|||||||
@echo "🧪 Running tests"
|
@echo "🧪 Running tests"
|
||||||
RISC0_DEV_MODE=1 cargo nextest run --no-fail-fast
|
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
|
# Run Bedrock node in docker
|
||||||
[working-directory: 'bedrock']
|
[working-directory: 'bedrock']
|
||||||
run-bedrock:
|
run-bedrock:
|
||||||
@ -36,22 +30,16 @@ run-bedrock:
|
|||||||
docker compose up
|
docker compose up
|
||||||
|
|
||||||
# Run Sequencer
|
# Run Sequencer
|
||||||
[working-directory: 'sequencer/service']
|
[working-directory: 'sequencer_runner']
|
||||||
run-sequencer:
|
run-sequencer:
|
||||||
@echo "🧠 Running sequencer"
|
@echo "🧠 Running sequencer"
|
||||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p sequencer_service configs/debug/sequencer_config.json
|
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p sequencer_runner configs/debug
|
||||||
|
|
||||||
# Run Indexer
|
# Run Indexer
|
||||||
[working-directory: 'indexer/service']
|
[working-directory: 'indexer/service']
|
||||||
run-indexer mock="":
|
run-indexer:
|
||||||
@echo "🔍 Running indexer"
|
@echo "🔍 Running indexer"
|
||||||
@if [ "{{mock}}" = "mock" ]; then \
|
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p indexer_service configs/indexer_config.json
|
||||||
echo "🧪 Using mock data"; \
|
|
||||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release --features mock-responses -p indexer_service configs/indexer_config.json; \
|
|
||||||
else \
|
|
||||||
echo "🚀 Using real data"; \
|
|
||||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p indexer_service configs/indexer_config.json; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run Explorer
|
# Run Explorer
|
||||||
[working-directory: 'explorer_service']
|
[working-directory: 'explorer_service']
|
||||||
@ -68,9 +56,6 @@ run-wallet +args:
|
|||||||
# Clean runtime data
|
# Clean runtime data
|
||||||
clean:
|
clean:
|
||||||
@echo "🧹 Cleaning run artifacts"
|
@echo "🧹 Cleaning run artifacts"
|
||||||
rm -rf sequencer/service/bedrock_signing_key
|
rm -rf sequencer_runner/bedrock_signing_key
|
||||||
rm -rf sequencer/service/rocksdb
|
rm -rf sequencer_runner/rocksdb
|
||||||
rm -rf indexer/service/rocksdb
|
|
||||||
rm -rf wallet/configs/debug/storage.json
|
rm -rf wallet/configs/debug/storage.json
|
||||||
rm -rf rocksdb
|
|
||||||
cd bedrock && docker compose down -v
|
|
||||||
|
|||||||
51
README.md
51
README.md
@ -12,7 +12,7 @@ Public accounts are stored on-chain as a visible map from IDs to account states,
|
|||||||
|
|
||||||
### Programmability and selective privacy
|
### Programmability and selective privacy
|
||||||
|
|
||||||
LEZ aims to deliver full programmability in a hybrid public/private model, with the same flexibility and composability as public blockchains. Developers write and deploy programs in LEZ without addressing privacy concerns. The protocol automatically supports executions that involve any combination of public and private accounts. From the program’s perspective, all accounts look the same, and privacy is enforced transparently. This lets developers focus on business logic while the system guarantees privacy and correctness.
|
LEZ aims to deliver full programmability in a hybrid public/private model, with the same flexibility and composability as public blockchains. Developers write and deploy programs in LEZ just as they would elsewhere. The protocol automatically supports executions that involve any combination of public and private accounts. From the program’s perspective, all accounts look the same, and privacy is enforced transparently. This lets developers focus on business logic while the system guarantees privacy and correctness.
|
||||||
|
|
||||||
To our knowledge, this design is unique to LEZ. Other privacy-focused programmable blockchains often require developers to explicitly handle private inputs inside their app logic. In LEZ, privacy is protocol-level: programs do not change, accounts are treated uniformly, and private execution works out of the box.
|
To our knowledge, this design is unique to LEZ. Other privacy-focused programmable blockchains often require developers to explicitly handle private inputs inside their app logic. In LEZ, privacy is protocol-level: programs do not change, accounts are treated uniformly, and private execution works out of the box.
|
||||||
|
|
||||||
@ -71,17 +71,6 @@ This design keeps public transactions as fast as any RISC-V–based VM and makes
|
|||||||
|
|
||||||
---
|
---
|
||||||
---
|
---
|
||||||
---
|
|
||||||
|
|
||||||
# Versioning
|
|
||||||
|
|
||||||
We release versions as git tags (e.g. `v0.1.0`). If no critical issues with version is found you can expect it to be immutable. All further features and fixes will be a part of the next tag. As the project is in active development we don't provide backward compatibility yet.
|
|
||||||
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" }
|
|
||||||
```
|
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
### Install build dependencies
|
### Install build dependencies
|
||||||
@ -141,38 +130,36 @@ RUST_LOG=info RISC0_DEV_MODE=1 cargo run $(pwd)/configs/debug all
|
|||||||
```
|
```
|
||||||
|
|
||||||
# Run the sequencer and node
|
# Run the sequencer and node
|
||||||
|
|
||||||
|
|
||||||
## Running Manually
|
## Running Manually
|
||||||
### Normal mode
|
### Normal mode
|
||||||
The sequencer and logos blockchain node can be run locally:
|
The sequencer and logos blockchain node can be run locally:
|
||||||
1. On one terminal go to the `logos-blockchain/logos-blockchain` repo and run a local logos blockchain node:
|
1. On one terminal go to the `logos-blockchain/logos-blockchain` repo and run a local logos blockchain node:
|
||||||
- `git checkout master; git pull`
|
- `git checkout master; git pull`
|
||||||
- `cargo clean`
|
- `cargo clean`
|
||||||
- `rm -r ~/.logos-blockchain-circuits`
|
- `rm -r ~/.logos-blockchain-circuits`
|
||||||
- `./scripts/setup-logos-blockchain-circuits.sh`
|
- `./scripts/setup-logos-blockchain-circuits.sh`
|
||||||
- `cargo build --all-features`
|
- `cargo build --all-features`
|
||||||
- `./target/debug/logos-blockchain-node --deployment nodes/node/standalone-deployment-config.yaml nodes/node/standalone-node-config.yaml`
|
- `./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:
|
2. Alternatively (WARNING: This node is outdated) go to ``logos-blockchain/lssa/` repo and run the node from docker:
|
||||||
- `cd bedrock`
|
- `cd bedrock`
|
||||||
- Change line 14 of `docker-compose.yml` from `"0:18080/tcp"` into `"8080:18080/tcp"`
|
- Change line 14 of `docker-compose.yml` from `"0:18080/tcp"` into `"8080:18080/tcp"`
|
||||||
- `docker compose up`
|
- `docker compose up`
|
||||||
|
|
||||||
2. On another terminal go to the `logos-blockchain/lssa` repo and run indexer service:
|
3. 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`
|
- `RUST_LOG=info cargo run -p indexer_service indexer/service/configs/indexer_config.json`
|
||||||
|
|
||||||
3. On another terminal go to the `logos-blockchain/lssa` repo and run the sequencer:
|
4. 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`
|
- `RUST_LOG=info cargo run -p sequencer_runner sequencer_runner/configs/debug`
|
||||||
4. (To run the explorer): on another terminal go to `logos-blockchain/lssa/explorer_service` and run the following:
|
|
||||||
- `cargo install cargo-leptos`
|
|
||||||
- `cargo leptos build --release`
|
|
||||||
- `cargo leptos serve --release`
|
|
||||||
|
|
||||||
### Notes on cleanup
|
### Notes on cleanup
|
||||||
|
|
||||||
After stopping services above you need to remove 3 folders to start cleanly:
|
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)
|
1. In the `logos-blockchain/logos-blockchain` folder `state` (not needed in case of docker setup)
|
||||||
2. In the `lssa` folder `sequencer/service/rocksdb`
|
2. In the `lssa` folder `sequencer_runner/rocksdb`
|
||||||
3. In the `lssa` file `sequencer/service/bedrock_signing_key`
|
3. In the `lssa` file `sequencer_runner/bedrock_signing_key`
|
||||||
4. In the `lssa` folder `indexer/service/rocksdb`
|
4. In the `lssa` folder `indexer/service/rocksdb`
|
||||||
|
|
||||||
### Normal mode (`just` commands)
|
### Normal mode (`just` commands)
|
||||||
@ -220,7 +207,7 @@ This will use a wallet binary built from this repo and not the one installed in
|
|||||||
### Standalone mode
|
### Standalone mode
|
||||||
The sequencer can be run in standalone mode with:
|
The sequencer can be run in standalone mode with:
|
||||||
```bash
|
```bash
|
||||||
RUST_LOG=info cargo run --features standalone -p sequencer_service sequencer/service/configs/debug
|
RUST_LOG=info cargo run --features standalone -p sequencer_runner sequencer_runner/configs/debug
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running with Docker
|
## Running with Docker
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
12
bedrock/cfgsync.yaml
Normal file
12
bedrock/cfgsync.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
port: 4400
|
||||||
|
n_hosts: 4
|
||||||
|
timeout: 10
|
||||||
|
|
||||||
|
# Tracing
|
||||||
|
tracing_settings:
|
||||||
|
logger: Stdout
|
||||||
|
tracing: None
|
||||||
|
filter: None
|
||||||
|
metrics: None
|
||||||
|
console: None
|
||||||
|
level: DEBUG
|
||||||
@ -1,82 +0,0 @@
|
|||||||
blend:
|
|
||||||
common:
|
|
||||||
num_blend_layers: 3
|
|
||||||
minimum_network_size: 30
|
|
||||||
protocol_name: /blend/integration-tests
|
|
||||||
data_replication_factor: 0
|
|
||||||
core:
|
|
||||||
scheduler:
|
|
||||||
cover:
|
|
||||||
message_frequency_per_round: 1.0
|
|
||||||
intervals_for_safety_buffer: 100
|
|
||||||
delayer:
|
|
||||||
maximum_release_delay_in_rounds: 3
|
|
||||||
minimum_messages_coefficient: 1
|
|
||||||
normalization_constant: 1.03
|
|
||||||
activity_threshold_sensitivity: 1
|
|
||||||
network:
|
|
||||||
kademlia_protocol_name: /integration/logos-blockchain/kad/1.0.0
|
|
||||||
identify_protocol_name: /integration/logos-blockchain/identify/1.0.0
|
|
||||||
chain_sync_protocol_name: /integration/logos-blockchain/chainsync/1.0.0
|
|
||||||
cryptarchia:
|
|
||||||
epoch_config:
|
|
||||||
epoch_stake_distribution_stabilization: 3
|
|
||||||
epoch_period_nonce_buffer: 3
|
|
||||||
epoch_period_nonce_stabilization: 4
|
|
||||||
security_param: 10
|
|
||||||
slot_activation_coeff:
|
|
||||||
numerator: 1
|
|
||||||
denominator: 2
|
|
||||||
learning_rate: 0.1
|
|
||||||
sdp_config:
|
|
||||||
service_params:
|
|
||||||
BN:
|
|
||||||
lock_period: 10
|
|
||||||
inactivity_period: 1
|
|
||||||
retention_period: 1
|
|
||||||
timestamp: 0
|
|
||||||
min_stake:
|
|
||||||
threshold: 1
|
|
||||||
timestamp: 0
|
|
||||||
gossipsub_protocol: /integration/logos-blockchain/cryptarchia/proto/1.0.0
|
|
||||||
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: []
|
|
||||||
outputs:
|
|
||||||
- value: 1
|
|
||||||
pk: d204000000000000000000000000000000000000000000000000000000000000
|
|
||||||
- value: 100
|
|
||||||
pk: '2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26'
|
|
||||||
- value: 1
|
|
||||||
pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717
|
|
||||||
- opcode: 17
|
|
||||||
payload:
|
|
||||||
channel_id: '0000000000000000000000000000000000000000000000000000000000000000'
|
|
||||||
inscription: '67656e65736973'
|
|
||||||
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
|
|
||||||
mempool:
|
|
||||||
pubsub_topic: mantle_e2e_tests
|
|
||||||
@ -1,12 +1,46 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
|
cfgsync:
|
||||||
|
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||||
|
volumes:
|
||||||
|
- ./scripts:/etc/logos-blockchain/scripts
|
||||||
|
- ./cfgsync.yaml:/etc/logos-blockchain/cfgsync.yaml:z
|
||||||
|
entrypoint: /etc/logos-blockchain/scripts/run_cfgsync.sh
|
||||||
|
|
||||||
logos-blockchain-node-0:
|
logos-blockchain-node-0:
|
||||||
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:9f1829dea335c56f6ff68ae37ea872ed5313b96b69e8ffe143c02b7217de85fc
|
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||||
ports:
|
ports:
|
||||||
- "${PORT:-8080}:18080/tcp"
|
- "${PORT:-8080}:18080/tcp"
|
||||||
volumes:
|
volumes:
|
||||||
- ./scripts:/etc/logos-blockchain/scripts
|
- ./scripts:/etc/logos-blockchain/scripts
|
||||||
- ./kzgrs_test_params:/kzgrs_test_params:z
|
- ./kzgrs_test_params:/kzgrs_test_params:z
|
||||||
- ./node-config.yaml:/etc/logos-blockchain/node-config.yaml:z
|
depends_on:
|
||||||
- ./deployment-settings.yaml:/etc/logos-blockchain/deployment-settings.yaml:z
|
- cfgsync
|
||||||
|
entrypoint: /etc/logos-blockchain/scripts/run_logos_blockchain_node.sh
|
||||||
|
|
||||||
|
logos-blockchain-node-1:
|
||||||
|
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||||
|
volumes:
|
||||||
|
- ./scripts:/etc/logos-blockchain/scripts
|
||||||
|
- ./kzgrs_test_params:/kzgrs_test_params:z
|
||||||
|
depends_on:
|
||||||
|
- cfgsync
|
||||||
|
entrypoint: /etc/logos-blockchain/scripts/run_logos_blockchain_node.sh
|
||||||
|
|
||||||
|
logos-blockchain-node-2:
|
||||||
|
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||||
|
volumes:
|
||||||
|
- ./scripts:/etc/logos-blockchain/scripts
|
||||||
|
- ./kzgrs_test_params:/kzgrs_test_params:z
|
||||||
|
depends_on:
|
||||||
|
- cfgsync
|
||||||
|
entrypoint: /etc/logos-blockchain/scripts/run_logos_blockchain_node.sh
|
||||||
|
|
||||||
|
logos-blockchain-node-3:
|
||||||
|
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||||
|
volumes:
|
||||||
|
- ./scripts:/etc/logos-blockchain/scripts
|
||||||
|
- ./kzgrs_test_params:/kzgrs_test_params:z
|
||||||
|
depends_on:
|
||||||
|
- cfgsync
|
||||||
entrypoint: /etc/logos-blockchain/scripts/run_logos_blockchain_node.sh
|
entrypoint: /etc/logos-blockchain/scripts/run_logos_blockchain_node.sh
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
blend:
|
|
||||||
non_ephemeral_signing_key_id: 86c8519f00178e9eb1fe5f4247e4bed77d4c9f6da2fb10e8a1fdd7ba6bc79fa0
|
|
||||||
core:
|
|
||||||
zk:
|
|
||||||
secret_key_kms_id: 64249c75c2cb813578b75d05b215fc95f67cea5862fff047228183f22e63460e
|
|
||||||
cryptarchia:
|
|
||||||
service:
|
|
||||||
bootstrap:
|
|
||||||
prolonged_bootstrap_period: '1.000000000'
|
|
||||||
network:
|
|
||||||
network:
|
|
||||||
max_connected_peers_to_try_download: 16
|
|
||||||
max_discovered_peers_to_try_download: 16
|
|
||||||
leader:
|
|
||||||
wallet:
|
|
||||||
funding_pk: "2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26"
|
|
||||||
sdp:
|
|
||||||
wallet:
|
|
||||||
funding_pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717
|
|
||||||
kms:
|
|
||||||
backend:
|
|
||||||
keys:
|
|
||||||
2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26: !Zk 6c645cd4636d9c4c36a37a9aeabcaa3300000000000000000000000000000000
|
|
||||||
64249c75c2cb813578b75d05b215fc95f67cea5862fff047228183f22e63460e: !Zk 83c851cf4436e8d2fdac33d56d2b235f66431be97e2a20bf241d431713dc720f
|
|
||||||
ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717: !Zk 7364705cd4636d9c4c36a37a9aeabcaa00000000000000000000000000000000
|
|
||||||
86c8519f00178e9eb1fe5f4247e4bed77d4c9f6da2fb10e8a1fdd7ba6bc79fa0: !Ed25519 5cd4636d9c4c36a37a9aeabcaa332c3ec796226af0af48a0b2e70167205af749
|
|
||||||
wallet:
|
|
||||||
known_keys:
|
|
||||||
2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26: "2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26"
|
|
||||||
ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717: "ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717"
|
|
||||||
voucher_master_key_id: 2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26
|
|
||||||
|
|
||||||
api:
|
|
||||||
backend:
|
|
||||||
listen_address: 0.0.0.0:18080
|
|
||||||
cors_origins: []
|
|
||||||
timeout: 30
|
|
||||||
max_body_size: 10485760
|
|
||||||
max_concurrent_requests: 500
|
|
||||||
|
|
||||||
tracing:
|
|
||||||
logger:
|
|
||||||
stdout: true
|
|
||||||
stderr: false
|
|
||||||
file:
|
|
||||||
directory: "./state/logs"
|
|
||||||
otlp: null
|
|
||||||
loki: null
|
|
||||||
gelf: null
|
|
||||||
tracing: None
|
|
||||||
filter: None
|
|
||||||
metrics: None
|
|
||||||
console: None
|
|
||||||
level: "INFO"
|
|
||||||
@ -2,19 +2,12 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
export POL_PROOF_DEV_MODE=true
|
export CFG_FILE_PATH="/config.yaml" \
|
||||||
|
CFG_SERVER_ADDR="http://cfgsync:4400" \
|
||||||
|
CFG_HOST_IP=$(hostname -i) \
|
||||||
|
CFG_HOST_IDENTIFIER="validator-$(hostname -i)" \
|
||||||
|
LOG_LEVEL="INFO" \
|
||||||
|
POL_PROOF_DEV_MODE=true
|
||||||
|
|
||||||
# Use static configs mounted from host. Both node-config.yaml and
|
/usr/bin/logos-blockchain-cfgsync-client && \
|
||||||
# deployment-settings.yaml have matching validator keys so the node
|
exec /usr/bin/logos-blockchain-node /config.yaml
|
||||||
# can produce blocks as a single-validator network.
|
|
||||||
# Copy deployment-settings to a writable path because sed -i can't
|
|
||||||
# rename on a bind-mounted file.
|
|
||||||
cp /etc/logos-blockchain/deployment-settings.yaml /deployment-settings.yaml
|
|
||||||
|
|
||||||
# Set chain_start_time to "now" so the chain starts immediately.
|
|
||||||
sed -i "s/PLACEHOLDER_CHAIN_START_TIME/$(date -u '+%Y-%m-%d %H:%M:%S.000000 +00:00:00')/" \
|
|
||||||
/deployment-settings.yaml
|
|
||||||
|
|
||||||
exec /usr/bin/logos-blockchain-node \
|
|
||||||
/etc/logos-blockchain/node-config.yaml \
|
|
||||||
--deployment /deployment-settings.yaml
|
|
||||||
|
|||||||
20
bedrock_client/Cargo.toml
Normal file
20
bedrock_client/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "bedrock_client"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = { 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
|
||||||
104
bedrock_client/src/lib.rs
Normal file
104
bedrock_client/src/lib.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use common::config::BasicAuth;
|
||||||
|
use futures::{Stream, TryFutureExt};
|
||||||
|
#[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_secs(60))
|
||||||
|
.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<(), Error> {
|
||||||
|
Retry::spawn(self.backoff_strategy(), || {
|
||||||
|
self.http_client
|
||||||
|
.post_transaction(self.node_url.clone(), tx.clone())
|
||||||
|
})
|
||||||
|
.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> {
|
||||||
|
tokio_retry::strategy::FibonacciBackoff::from_millis(
|
||||||
|
self.backoff.start_delay.as_millis() as u64
|
||||||
|
)
|
||||||
|
.take(self.backoff.max_retries)
|
||||||
|
}
|
||||||
|
}
|
||||||
54
clippy.toml
54
clippy.toml
@ -1,54 +0,0 @@
|
|||||||
module-item-order-groupings = [
|
|
||||||
[
|
|
||||||
"use",
|
|
||||||
[
|
|
||||||
"use",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"modules",
|
|
||||||
[
|
|
||||||
"extern_crate",
|
|
||||||
"mod",
|
|
||||||
"foreign_mod",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"macros",
|
|
||||||
[
|
|
||||||
"macro",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"global_asm",
|
|
||||||
[
|
|
||||||
"global_asm",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"UPPER_SNAKE_CASE",
|
|
||||||
[
|
|
||||||
"static",
|
|
||||||
"const",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"PascalCase",
|
|
||||||
[
|
|
||||||
"ty_alias",
|
|
||||||
"enum",
|
|
||||||
"struct",
|
|
||||||
"union",
|
|
||||||
"trait",
|
|
||||||
"trait_alias",
|
|
||||||
"impl",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"lower_snake_case",
|
|
||||||
[
|
|
||||||
"fn",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
source-item-ordering = ["module"]
|
|
||||||
@ -4,22 +4,21 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nssa.workspace = true
|
nssa.workspace = true
|
||||||
nssa_core.workspace = true
|
nssa_core.workspace = true
|
||||||
authenticated_transfer_core.workspace = true
|
|
||||||
clock_core.workspace = true
|
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_with.workspace = true
|
serde_with.workspace = true
|
||||||
base64.workspace = true
|
reqwest.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
borsh.workspace = true
|
borsh.workspace = true
|
||||||
|
bytesize.workspace = true
|
||||||
|
base64.workspace = true
|
||||||
|
url.workspace = true
|
||||||
logos-blockchain-common-http-client.workspace = true
|
logos-blockchain-common-http-client.workspace = true
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use nssa_core::BlockId;
|
use nssa::AccountId;
|
||||||
pub use nssa_core::Timestamp;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest as _, Sha256, digest::FixedOutput as _};
|
use sha2::{Digest, Sha256, digest::FixedOutput};
|
||||||
|
|
||||||
use crate::{HashType, transaction::NSSATransaction};
|
use crate::{HashType, transaction::NSSATransaction};
|
||||||
|
|
||||||
pub type MantleMsgId = [u8; 32];
|
pub type MantleMsgId = [u8; 32];
|
||||||
pub type BlockHash = HashType;
|
pub type BlockHash = HashType;
|
||||||
|
pub type BlockId = u64;
|
||||||
|
pub type TimeStamp = u64;
|
||||||
|
|
||||||
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
||||||
pub struct BlockMeta {
|
pub struct BlockMeta {
|
||||||
@ -18,7 +20,7 @@ pub struct BlockMeta {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
/// Our own hasher.
|
/// Our own hasher.
|
||||||
/// Currently it is SHA256 hasher wrapper. May change in a future.
|
/// Currently it is SHA256 hasher wrapper. May change in a future.
|
||||||
pub struct OwnHasher;
|
pub struct OwnHasher {}
|
||||||
|
|
||||||
impl OwnHasher {
|
impl OwnHasher {
|
||||||
fn hash(data: &[u8]) -> HashType {
|
fn hash(data: &[u8]) -> HashType {
|
||||||
@ -34,7 +36,7 @@ pub struct BlockHeader {
|
|||||||
pub block_id: BlockId,
|
pub block_id: BlockId,
|
||||||
pub prev_block_hash: BlockHash,
|
pub prev_block_hash: BlockHash,
|
||||||
pub hash: BlockHash,
|
pub hash: BlockHash,
|
||||||
pub timestamp: Timestamp,
|
pub timestamp: TimeStamp,
|
||||||
pub signature: nssa::Signature,
|
pub signature: nssa::Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,47 +60,23 @@ pub struct Block {
|
|||||||
pub bedrock_parent_id: MantleMsgId,
|
pub bedrock_parent_id: MantleMsgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Block {
|
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
crate::borsh_base64::serialize(self, serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Block {
|
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
||||||
crate::borsh_base64::deserialize(deserializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||||
pub struct HashableBlockData {
|
pub struct HashableBlockData {
|
||||||
pub block_id: BlockId,
|
pub block_id: BlockId,
|
||||||
pub prev_block_hash: BlockHash,
|
pub prev_block_hash: BlockHash,
|
||||||
pub timestamp: Timestamp,
|
pub timestamp: TimeStamp,
|
||||||
pub transactions: Vec<NSSATransaction>,
|
pub transactions: Vec<NSSATransaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HashableBlockData {
|
impl HashableBlockData {
|
||||||
#[must_use]
|
|
||||||
pub fn into_pending_block(
|
pub fn into_pending_block(
|
||||||
self,
|
self,
|
||||||
signing_key: &nssa::PrivateKey,
|
signing_key: &nssa::PrivateKey,
|
||||||
bedrock_parent_id: MantleMsgId,
|
bedrock_parent_id: MantleMsgId,
|
||||||
) -> Block {
|
) -> Block {
|
||||||
const PREFIX: &[u8; 32] = b"/LEE/v0.3/Message/Block/\x00\x00\x00\x00\x00\x00\x00\x00";
|
|
||||||
|
|
||||||
let data_bytes = borsh::to_vec(&self).unwrap();
|
let data_bytes = borsh::to_vec(&self).unwrap();
|
||||||
let mut bytes = Vec::with_capacity(
|
let signature = nssa::Signature::new(signing_key, &data_bytes);
|
||||||
PREFIX
|
let hash = OwnHasher::hash(&data_bytes);
|
||||||
.len()
|
|
||||||
.checked_add(data_bytes.len())
|
|
||||||
.expect("length overflow"),
|
|
||||||
);
|
|
||||||
bytes.extend_from_slice(PREFIX);
|
|
||||||
bytes.extend_from_slice(&data_bytes);
|
|
||||||
|
|
||||||
let hash = OwnHasher::hash(&bytes);
|
|
||||||
let signature = nssa::Signature::new(signing_key, &hash.0);
|
|
||||||
Block {
|
Block {
|
||||||
header: BlockHeader {
|
header: BlockHeader {
|
||||||
block_id: self.block_id,
|
block_id: self.block_id,
|
||||||
@ -114,6 +92,10 @@ impl HashableBlockData {
|
|||||||
bedrock_parent_id,
|
bedrock_parent_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn block_hash(&self) -> BlockHash {
|
||||||
|
OwnHasher::hash(&borsh::to_vec(&self).unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Block> for HashableBlockData {
|
impl From<Block> for HashableBlockData {
|
||||||
@ -127,12 +109,26 @@ impl From<Block> for HashableBlockData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper struct for account (de-)serialization
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AccountInitialData {
|
||||||
|
pub account_id: AccountId,
|
||||||
|
pub balance: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper struct to (de-)serialize initial commitments
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CommitmentsInitialData {
|
||||||
|
pub npk: nssa_core::NullifierPublicKey,
|
||||||
|
pub account: nssa_core::account::Account,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{HashType, block::HashableBlockData, test_utils};
|
use crate::{HashType, block::HashableBlockData, test_utils};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn encoding_roundtrip() {
|
fn test_encoding_roundtrip() {
|
||||||
let transactions = vec![test_utils::produce_dummy_empty_transaction()];
|
let transactions = vec![test_utils::produce_dummy_empty_transaction()];
|
||||||
let block = test_utils::produce_dummy_block(1, Some(HashType([1; 32])), transactions);
|
let block = test_utils::produce_dummy_block(1, Some(HashType([1; 32])), transactions);
|
||||||
let hashable = HashableBlockData::from(block);
|
let hashable = HashableBlockData::from(block);
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
//! This module provides utilities for serializing and deserializing data by combining Borsh and
|
|
||||||
//! Base64 encodings.
|
|
||||||
|
|
||||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
pub fn serialize<T: BorshSerialize, S: serde::Serializer>(
|
|
||||||
value: &T,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error> {
|
|
||||||
let borsh_encoded = borsh::to_vec(value).map_err(serde::ser::Error::custom)?;
|
|
||||||
let base64_encoded = STANDARD.encode(&borsh_encoded);
|
|
||||||
Serialize::serialize(&base64_encoded, serializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<'de, T: BorshDeserialize, D: serde::Deserializer<'de>>(
|
|
||||||
deserializer: D,
|
|
||||||
) -> Result<T, D::Error> {
|
|
||||||
let base64_encoded = <String as Deserialize>::deserialize(deserializer)?;
|
|
||||||
let borsh_encoded = STANDARD
|
|
||||||
.decode(base64_encoded.as_bytes())
|
|
||||||
.map_err(serde::de::Error::custom)?;
|
|
||||||
borsh::from_slice(&borsh_encoded).map_err(serde::de::Error::custom)
|
|
||||||
}
|
|
||||||
@ -42,14 +42,14 @@ impl FromStr for BasicAuth {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
username: username.to_owned(),
|
username: username.to_string(),
|
||||||
password: password.map(std::string::ToString::to_string),
|
password: password.map(|p| p.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BasicAuth> for BasicAuthCredentials {
|
impl From<BasicAuth> for BasicAuthCredentials {
|
||||||
fn from(value: BasicAuth) -> Self {
|
fn from(value: BasicAuth) -> Self {
|
||||||
Self::new(value.username, value.password)
|
BasicAuthCredentials::new(value.username, value.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
common/src/error.rs
Normal file
43
common/src/error.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use nssa::AccountId;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::rpc_primitives::errors::RpcError;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct SequencerRpcError {
|
||||||
|
pub jsonrpc: String,
|
||||||
|
pub error: RpcError,
|
||||||
|
pub id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum SequencerClientError {
|
||||||
|
#[error("HTTP error")]
|
||||||
|
HTTPError(#[from] reqwest::Error),
|
||||||
|
#[error("Serde error")]
|
||||||
|
SerdeError(#[from] serde_json::Error),
|
||||||
|
#[error("Internal error: {0:?}")]
|
||||||
|
InternalError(SequencerRpcError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SequencerRpcError> for SequencerClientError {
|
||||||
|
fn from(value: SequencerRpcError) -> Self {
|
||||||
|
SequencerClientError::InternalError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ExecutionFailureKind {
|
||||||
|
#[error("Failed to get account data from sequencer")]
|
||||||
|
SequencerError,
|
||||||
|
#[error("Inputs amounts does not match outputs")]
|
||||||
|
AmountMismatchError,
|
||||||
|
#[error("Accounts key not found")]
|
||||||
|
KeyNotFoundError,
|
||||||
|
#[error("Sequencer client error: {0:?}")]
|
||||||
|
SequencerClientError(#[from] SequencerClientError),
|
||||||
|
#[error("Can not pay for operation")]
|
||||||
|
InsufficientFundsError,
|
||||||
|
#[error("Account {0} data is invalid")]
|
||||||
|
AccountDataError(AccountId),
|
||||||
|
}
|
||||||
@ -4,8 +4,10 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
|||||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||||
|
|
||||||
pub mod block;
|
pub mod block;
|
||||||
mod borsh_base64;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod error;
|
||||||
|
pub mod rpc_primitives;
|
||||||
|
pub mod sequencer_client;
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
|
|
||||||
// Module for tests utility functions
|
// Module for tests utility functions
|
||||||
@ -15,6 +17,7 @@ pub mod test_utils;
|
|||||||
pub const PINATA_BASE58: &str = "EfQhKQAkX2FJiwNii2WFQsGndjvF1Mzd7RuVe7QdPLw7";
|
pub const PINATA_BASE58: &str = "EfQhKQAkX2FJiwNii2WFQsGndjvF1Mzd7RuVe7QdPLw7";
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
Debug,
|
||||||
Default,
|
Default,
|
||||||
Copy,
|
Copy,
|
||||||
Clone,
|
Clone,
|
||||||
@ -34,19 +37,13 @@ impl Display for HashType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for HashType {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", hex::encode(self.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for HashType {
|
impl FromStr for HashType {
|
||||||
type Err = hex::FromHexError;
|
type Err = hex::FromHexError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let mut bytes = [0_u8; 32];
|
let mut bytes = [0u8; 32];
|
||||||
hex::decode_to_slice(s, &mut bytes)?;
|
hex::decode_to_slice(s, &mut bytes)?;
|
||||||
Ok(Self(bytes))
|
Ok(HashType(bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +61,7 @@ impl From<HashType> for [u8; 32] {
|
|||||||
|
|
||||||
impl From<[u8; 32]> for HashType {
|
impl From<[u8; 32]> for HashType {
|
||||||
fn from(bytes: [u8; 32]) -> Self {
|
fn from(bytes: [u8; 32]) -> Self {
|
||||||
Self(bytes)
|
HashType(bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +69,7 @@ impl TryFrom<Vec<u8>> for HashType {
|
|||||||
type Error = <[u8; 32] as TryFrom<Vec<u8>>>::Error;
|
type Error = <[u8; 32] as TryFrom<Vec<u8>>>::Error;
|
||||||
|
|
||||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
Ok(Self(value.try_into()?))
|
Ok(HashType(value.try_into()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +85,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serialization_roundtrip() {
|
fn serialization_roundtrip() {
|
||||||
let original = HashType([1_u8; 32]);
|
let original = HashType([1u8; 32]);
|
||||||
let serialized = original.to_string();
|
let serialized = original.to_string();
|
||||||
let deserialized = HashType::from_str(&serialized).unwrap();
|
let deserialized = HashType::from_str(&serialized).unwrap();
|
||||||
assert_eq!(original, deserialized);
|
assert_eq!(original, deserialized);
|
||||||
|
|||||||
187
common/src/rpc_primitives/errors.rs
Normal file
187
common/src/rpc_primitives/errors.rs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use serde_json::{Value, to_value};
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct RpcParseError(pub String);
|
||||||
|
|
||||||
|
#[allow(clippy::too_long_first_doc_paragraph)]
|
||||||
|
/// This struct may be returned from JSON RPC server in case of error
|
||||||
|
/// It is expected that that this struct has impls From<_> all other RPC errors
|
||||||
|
/// like [`RpcBlockError`](crate::types::blocks::RpcBlockError)
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct RpcError {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub error_struct: Option<RpcErrorKind>,
|
||||||
|
/// Deprecated please use the `error_struct` instead
|
||||||
|
pub code: i64,
|
||||||
|
/// Deprecated please use the `error_struct` instead
|
||||||
|
pub message: String,
|
||||||
|
/// Deprecated please use the `error_struct` instead
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub data: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(tag = "name", content = "cause", rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum RpcErrorKind {
|
||||||
|
RequestValidationError(RpcRequestValidationErrorKind),
|
||||||
|
HandlerError(Value),
|
||||||
|
InternalError(Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(tag = "name", content = "info", rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum RpcRequestValidationErrorKind {
|
||||||
|
MethodNotFound { method_name: String },
|
||||||
|
ParseError { error_message: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A general Server Error
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum ServerError {
|
||||||
|
Timeout,
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcError {
|
||||||
|
/// A generic constructor.
|
||||||
|
///
|
||||||
|
/// Mostly for completeness, doesn't do anything but filling in the corresponding fields.
|
||||||
|
pub fn new(code: i64, message: String, data: Option<Value>) -> Self {
|
||||||
|
RpcError {
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
data,
|
||||||
|
error_struct: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an Invalid Param error.
|
||||||
|
pub fn invalid_params(data: impl serde::Serialize) -> Self {
|
||||||
|
let value = match to_value(data) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) => {
|
||||||
|
return Self::server_error(Some(format!(
|
||||||
|
"Failed to serialize invalid parameters error: {:?}",
|
||||||
|
err.to_string()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
RpcError::new(-32_602, "Invalid params".to_owned(), Some(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a server error.
|
||||||
|
pub fn server_error<E: serde::Serialize>(e: Option<E>) -> Self {
|
||||||
|
RpcError::new(
|
||||||
|
-32_000,
|
||||||
|
"Server error".to_owned(),
|
||||||
|
e.map(|v| to_value(v).expect("Must be representable in JSON")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a parse error.
|
||||||
|
pub fn parse_error(e: String) -> Self {
|
||||||
|
RpcError {
|
||||||
|
code: -32_700,
|
||||||
|
message: "Parse error".to_owned(),
|
||||||
|
data: Some(Value::String(e.clone())),
|
||||||
|
error_struct: Some(RpcErrorKind::RequestValidationError(
|
||||||
|
RpcRequestValidationErrorKind::ParseError { error_message: e },
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialization_error(e: &str) -> Self {
|
||||||
|
RpcError::new_internal_error(Some(Value::String(e.to_owned())), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method to define extract `INTERNAL_ERROR` in separate `RpcErrorKind`
|
||||||
|
/// Returns `HANDLER_ERROR` if the error is not internal one
|
||||||
|
pub fn new_internal_or_handler_error(error_data: Option<Value>, error_struct: Value) -> Self {
|
||||||
|
if error_struct["name"] == "INTERNAL_ERROR" {
|
||||||
|
let error_message = match error_struct["info"].get("error_message") {
|
||||||
|
Some(Value::String(error_message)) => error_message.as_str(),
|
||||||
|
_ => "InternalError happened during serializing InternalError",
|
||||||
|
};
|
||||||
|
Self::new_internal_error(error_data, error_message)
|
||||||
|
} else {
|
||||||
|
Self::new_handler_error(error_data, error_struct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_internal_error(error_data: Option<Value>, info: &str) -> Self {
|
||||||
|
RpcError {
|
||||||
|
code: -32_000,
|
||||||
|
message: "Server error".to_owned(),
|
||||||
|
data: error_data,
|
||||||
|
error_struct: Some(RpcErrorKind::InternalError(serde_json::json!({
|
||||||
|
"name": "INTERNAL_ERROR",
|
||||||
|
"info": serde_json::json!({"error_message": info})
|
||||||
|
}))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_handler_error(error_data: Option<Value>, error_struct: Value) -> Self {
|
||||||
|
RpcError {
|
||||||
|
code: -32_000,
|
||||||
|
message: "Server error".to_owned(),
|
||||||
|
data: error_data,
|
||||||
|
error_struct: Some(RpcErrorKind::HandlerError(error_struct)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a method not found error.
|
||||||
|
pub fn method_not_found(method: String) -> Self {
|
||||||
|
RpcError {
|
||||||
|
code: -32_601,
|
||||||
|
message: "Method not found".to_owned(),
|
||||||
|
data: Some(Value::String(method.clone())),
|
||||||
|
error_struct: Some(RpcErrorKind::RequestValidationError(
|
||||||
|
RpcRequestValidationErrorKind::MethodNotFound {
|
||||||
|
method_name: method,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RpcError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RpcParseError> for RpcError {
|
||||||
|
fn from(parse_error: RpcParseError) -> Self {
|
||||||
|
Self::parse_error(parse_error.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::convert::Infallible> for RpcError {
|
||||||
|
fn from(_: std::convert::Infallible) -> Self {
|
||||||
|
unsafe { core::hint::unreachable_unchecked() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ServerError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ServerError::Timeout => write!(f, "ServerError: Timeout"),
|
||||||
|
ServerError::Closed => write!(f, "ServerError: Closed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ServerError> for RpcError {
|
||||||
|
fn from(e: ServerError) -> RpcError {
|
||||||
|
let error_data = match to_value(&e) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(_err) => {
|
||||||
|
return RpcError::new_internal_error(None, "Failed to serialize ServerError");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
RpcError::new_internal_error(Some(error_data), e.to_string().as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
567
common/src/rpc_primitives/message.rs
Normal file
567
common/src/rpc_primitives/message.rs
Normal file
@ -0,0 +1,567 @@
|
|||||||
|
// Copyright 2017 tokio-jsonrpc Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||||
|
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||||
|
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||||
|
// copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
//! JSON-RPC 2.0 messages.
|
||||||
|
//!
|
||||||
|
//! The main entrypoint here is the [Message](enum.Message.html). The others are just building
|
||||||
|
//! blocks and you should generally work with `Message` instead.
|
||||||
|
use std::fmt::{Formatter, Result as FmtResult};
|
||||||
|
|
||||||
|
use serde::{
|
||||||
|
de::{Deserializer, Error, Unexpected, Visitor},
|
||||||
|
ser::{SerializeStruct, Serializer},
|
||||||
|
};
|
||||||
|
use serde_json::{Result as JsonResult, Value};
|
||||||
|
|
||||||
|
use super::errors::RpcError;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
struct Version;
|
||||||
|
|
||||||
|
impl serde::Serialize for Version {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.serialize_str("2.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for Version {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
struct VersionVisitor;
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
|
impl<'de> Visitor<'de> for VersionVisitor {
|
||||||
|
type Value = Version;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
formatter.write_str("a version string")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: Error>(self, value: &str) -> Result<Version, E> {
|
||||||
|
match value {
|
||||||
|
"2.0" => Ok(Version),
|
||||||
|
_ => Err(E::invalid_value(Unexpected::Str(value), &"value 2.0")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deserializer.deserialize_str(VersionVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An RPC request.
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Request {
|
||||||
|
jsonrpc: Version,
|
||||||
|
pub method: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Value::is_null")]
|
||||||
|
pub params: Value,
|
||||||
|
pub id: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
pub fn from_payload_version_2_0(method: String, payload: serde_json::Value) -> Self {
|
||||||
|
Self {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method,
|
||||||
|
params: payload,
|
||||||
|
// ToDo: Correct checking of id
|
||||||
|
id: 1.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answer the request with a (positive) reply.
|
||||||
|
///
|
||||||
|
/// The ID is taken from the request.
|
||||||
|
pub fn reply(&self, reply: Value) -> Message {
|
||||||
|
Message::Response(Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result: Ok(reply),
|
||||||
|
id: self.id.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Answer the request with an error.
|
||||||
|
pub fn error(&self, error: RpcError) -> Message {
|
||||||
|
Message::Response(Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result: Err(error),
|
||||||
|
id: self.id.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A response to an RPC.
|
||||||
|
///
|
||||||
|
/// It is created by the methods on [Request](struct.Request.html).
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
pub result: Result<Value, RpcError>,
|
||||||
|
pub id: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for Response {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
let mut sub = serializer.serialize_struct("Response", 3)?;
|
||||||
|
sub.serialize_field("jsonrpc", &self.jsonrpc)?;
|
||||||
|
match self.result {
|
||||||
|
Ok(ref value) => sub.serialize_field("result", value),
|
||||||
|
Err(ref err) => sub.serialize_field("error", err),
|
||||||
|
}?;
|
||||||
|
sub.serialize_field("id", &self.id)?;
|
||||||
|
sub.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializer for `Option<Value>` that produces `Some(Value::Null)`.
|
||||||
|
///
|
||||||
|
/// The usual one produces None in that case. But we need to know the difference between
|
||||||
|
/// `{x: null}` and `{}`.
|
||||||
|
fn some_value<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<Value>, D::Error> {
|
||||||
|
serde::Deserialize::deserialize(deserializer).map(Some)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper trick for deserialization.
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct WireResponse {
|
||||||
|
// It is actually used to eat and sanity check the deserialized text
|
||||||
|
#[allow(dead_code)]
|
||||||
|
jsonrpc: Version,
|
||||||
|
// Make sure we accept null as Some(Value::Null), instead of going to None
|
||||||
|
#[serde(default, deserialize_with = "some_value")]
|
||||||
|
result: Option<Value>,
|
||||||
|
error: Option<RpcError>,
|
||||||
|
id: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementing deserialize is hard. We sidestep the difficulty by deserializing a similar
|
||||||
|
// structure that directly corresponds to whatever is on the wire and then convert it to our more
|
||||||
|
// convenient representation.
|
||||||
|
impl<'de> serde::Deserialize<'de> for Response {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
let wr: WireResponse = serde::Deserialize::deserialize(deserializer)?;
|
||||||
|
let result = match (wr.result, wr.error) {
|
||||||
|
(Some(res), None) => Ok(res),
|
||||||
|
(None, Some(err)) => Err(err),
|
||||||
|
_ => {
|
||||||
|
let err = D::Error::custom("Either 'error' or 'result' is expected, but not both");
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result,
|
||||||
|
id: wr.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A notification (doesn't expect an answer).
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Notification {
|
||||||
|
jsonrpc: Version,
|
||||||
|
pub method: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Value::is_null")]
|
||||||
|
pub params: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One message of the JSON RPC protocol.
|
||||||
|
///
|
||||||
|
/// One message, directly mapped from the structures of the protocol. See the
|
||||||
|
/// [specification](http://www.jsonrpc.org/specification) for more details.
|
||||||
|
///
|
||||||
|
/// Since the protocol allows one endpoint to be both client and server at the same time, the
|
||||||
|
/// message can decode and encode both directions of the protocol.
|
||||||
|
///
|
||||||
|
/// The `Batch` variant is supposed to be created directly, without a constructor.
|
||||||
|
///
|
||||||
|
/// The `UnmatchedSub` variant is used when a request is an array and some of the subrequests
|
||||||
|
/// aren't recognized as valid json rpc 2.0 messages. This is never returned as a top-level
|
||||||
|
/// element, it is returned as `Err(Broken::Unmatched)`.
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Message {
|
||||||
|
/// An RPC request.
|
||||||
|
Request(Request),
|
||||||
|
/// A response to a Request.
|
||||||
|
Response(Response),
|
||||||
|
/// A notification.
|
||||||
|
Notification(Notification),
|
||||||
|
/// A batch of more requests or responses.
|
||||||
|
///
|
||||||
|
/// The protocol allows bundling multiple requests, notifications or responses to a single
|
||||||
|
/// message.
|
||||||
|
///
|
||||||
|
/// This variant has no direct constructor and is expected to be constructed manually.
|
||||||
|
Batch(Vec<Message>),
|
||||||
|
/// An unmatched sub entry in a `Batch`.
|
||||||
|
///
|
||||||
|
/// When there's a `Batch` and an element doesn't comform to the JSONRPC 2.0 format, that one
|
||||||
|
/// is represented by this. This is never produced as a top-level value when parsing, the
|
||||||
|
/// `Err(Broken::Unmatched)` is used instead. It is not possible to serialize.
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
UnmatchedSub(Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message {
|
||||||
|
/// A constructor for a request.
|
||||||
|
///
|
||||||
|
/// The ID is auto-set to dontcare.
|
||||||
|
pub fn request(method: String, params: Value) -> Self {
|
||||||
|
let id = Value::from("dontcare");
|
||||||
|
Message::Request(Request {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a top-level error (without an ID).
|
||||||
|
pub fn error(error: RpcError) -> Self {
|
||||||
|
Message::Response(Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result: Err(error),
|
||||||
|
id: Value::Null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A constructor for a notification.
|
||||||
|
pub fn notification(method: String, params: Value) -> Self {
|
||||||
|
Message::Notification(Notification {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A constructor for a response.
|
||||||
|
pub fn response(id: Value, result: Result<Value, RpcError>) -> Self {
|
||||||
|
Message::Response(Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result,
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns id or Null if there is no id.
|
||||||
|
pub fn id(&self) -> Value {
|
||||||
|
match self {
|
||||||
|
Message::Request(req) => req.id.clone(),
|
||||||
|
_ => Value::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A broken message.
|
||||||
|
///
|
||||||
|
/// Protocol-level errors.
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Broken {
|
||||||
|
/// It was valid JSON, but doesn't match the form of a JSONRPC 2.0 message.
|
||||||
|
Unmatched(Value),
|
||||||
|
/// Invalid JSON.
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
SyntaxError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Broken {
|
||||||
|
/// Generate an appropriate error message.
|
||||||
|
///
|
||||||
|
/// The error message for these things are specified in the RFC, so this just creates an error
|
||||||
|
/// with the right values.
|
||||||
|
pub fn reply(&self) -> Message {
|
||||||
|
match *self {
|
||||||
|
Broken::Unmatched(_) => Message::error(RpcError::parse_error(
|
||||||
|
"JSON RPC Request format was expected".to_owned(),
|
||||||
|
)),
|
||||||
|
Broken::SyntaxError(ref e) => Message::error(RpcError::parse_error(e.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trick to easily deserialize and detect valid JSON, but invalid Message.
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum WireMessage {
|
||||||
|
Message(Message),
|
||||||
|
Broken(Broken),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decoded_to_parsed(res: JsonResult<WireMessage>) -> Parsed {
|
||||||
|
match res {
|
||||||
|
Ok(WireMessage::Message(Message::UnmatchedSub(value))) => Err(Broken::Unmatched(value)),
|
||||||
|
Ok(WireMessage::Message(m)) => Ok(m),
|
||||||
|
Ok(WireMessage::Broken(b)) => Err(b),
|
||||||
|
Err(e) => Err(Broken::SyntaxError(e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Parsed = Result<Message, Broken>;
|
||||||
|
|
||||||
|
/// Read a [Message](enum.Message.html) from a slice.
|
||||||
|
///
|
||||||
|
/// Invalid JSON or JSONRPC messages are reported as [Broken](enum.Broken.html).
|
||||||
|
pub fn from_slice(s: &[u8]) -> Parsed {
|
||||||
|
decoded_to_parsed(::serde_json::de::from_slice(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a [Message](enum.Message.html) from a string.
|
||||||
|
///
|
||||||
|
/// Invalid JSON or JSONRPC messages are reported as [Broken](enum.Broken.html).
|
||||||
|
pub fn from_str(s: &str) -> Parsed {
|
||||||
|
from_slice(s.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Message> for String {
|
||||||
|
fn from(val: Message) -> Self {
|
||||||
|
::serde_json::ser::to_string(&val).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Message> for Vec<u8> {
|
||||||
|
fn from(val: Message) -> Self {
|
||||||
|
::serde_json::ser::to_vec(&val).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde_json::{Value, de::from_slice, json, ser::to_vec};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Test serialization and deserialization of the Message
|
||||||
|
///
|
||||||
|
/// We first deserialize it from a string. That way we check deserialization works.
|
||||||
|
/// But since serialization doesn't have to produce the exact same result (order, spaces, …),
|
||||||
|
/// we then serialize and deserialize the thing again and check it matches.
|
||||||
|
#[test]
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn message_serde() {
|
||||||
|
// A helper for running one message test
|
||||||
|
fn one(input: &str, expected: &Message) {
|
||||||
|
let parsed: Message = from_str(input).unwrap();
|
||||||
|
assert_eq!(*expected, parsed);
|
||||||
|
let serialized = to_vec(&parsed).unwrap();
|
||||||
|
let deserialized: Message = from_slice(&serialized).unwrap();
|
||||||
|
assert_eq!(parsed, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A request without parameters
|
||||||
|
one(
|
||||||
|
r#"{"jsonrpc": "2.0", "method": "call", "id": 1}"#,
|
||||||
|
&Message::Request(Request {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method: "call".to_owned(),
|
||||||
|
params: Value::Null,
|
||||||
|
id: json!(1),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// A request with parameters
|
||||||
|
one(
|
||||||
|
r#"{"jsonrpc": "2.0", "method": "call", "params": [1, 2, 3], "id": 2}"#,
|
||||||
|
&Message::Request(Request {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method: "call".to_owned(),
|
||||||
|
params: json!([1, 2, 3]),
|
||||||
|
id: json!(2),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// A notification (with parameters)
|
||||||
|
one(
|
||||||
|
r#"{"jsonrpc": "2.0", "method": "notif", "params": {"x": "y"}}"#,
|
||||||
|
&Message::Notification(Notification {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method: "notif".to_owned(),
|
||||||
|
params: json!({"x": "y"}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// A successful response
|
||||||
|
one(
|
||||||
|
r#"{"jsonrpc": "2.0", "result": 42, "id": 3}"#,
|
||||||
|
&Message::Response(Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result: Ok(json!(42)),
|
||||||
|
id: json!(3),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// A successful response
|
||||||
|
one(
|
||||||
|
r#"{"jsonrpc": "2.0", "result": null, "id": 3}"#,
|
||||||
|
&Message::Response(Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result: Ok(Value::Null),
|
||||||
|
id: json!(3),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// An error
|
||||||
|
one(
|
||||||
|
r#"{"jsonrpc": "2.0", "error": {"code": 42, "message": "Wrong!"}, "id": null}"#,
|
||||||
|
&Message::Response(Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result: Err(RpcError::new(42, "Wrong!".to_owned(), None)),
|
||||||
|
id: Value::Null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// A batch
|
||||||
|
one(
|
||||||
|
r#"[
|
||||||
|
{"jsonrpc": "2.0", "method": "notif"},
|
||||||
|
{"jsonrpc": "2.0", "method": "call", "id": 42}
|
||||||
|
]"#,
|
||||||
|
&Message::Batch(vec![
|
||||||
|
Message::Notification(Notification {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method: "notif".to_owned(),
|
||||||
|
params: Value::Null,
|
||||||
|
}),
|
||||||
|
Message::Request(Request {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method: "call".to_owned(),
|
||||||
|
params: Value::Null,
|
||||||
|
id: json!(42),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
// Some handling of broken messages inside a batch
|
||||||
|
let parsed = from_str(
|
||||||
|
r#"[
|
||||||
|
{"jsonrpc": "2.0", "method": "notif"},
|
||||||
|
{"jsonrpc": "2.0", "method": "call", "id": 42},
|
||||||
|
true
|
||||||
|
]"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
Message::Batch(vec![
|
||||||
|
Message::Notification(Notification {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method: "notif".to_owned(),
|
||||||
|
params: Value::Null,
|
||||||
|
}),
|
||||||
|
Message::Request(Request {
|
||||||
|
jsonrpc: Version,
|
||||||
|
method: "call".to_owned(),
|
||||||
|
params: Value::Null,
|
||||||
|
id: json!(42),
|
||||||
|
}),
|
||||||
|
Message::UnmatchedSub(Value::Bool(true)),
|
||||||
|
]),
|
||||||
|
parsed
|
||||||
|
);
|
||||||
|
to_vec(&Message::UnmatchedSub(Value::Null)).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper for the `broken` test.
|
||||||
|
///
|
||||||
|
/// Check that the given JSON string parses, but is not recognized as a valid RPC message.
|
||||||
|
///
|
||||||
|
/// Test things that are almost but not entirely JSONRPC are rejected
|
||||||
|
///
|
||||||
|
/// The reject is done by returning it as Unmatched.
|
||||||
|
#[test]
|
||||||
|
#[allow(clippy::panic)]
|
||||||
|
fn broken() {
|
||||||
|
// A helper with one test
|
||||||
|
fn one(input: &str) {
|
||||||
|
let msg = from_str(input);
|
||||||
|
match msg {
|
||||||
|
Err(Broken::Unmatched(_)) => (),
|
||||||
|
_ => panic!("{input} recognized as an RPC message: {msg:?}!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Missing the version
|
||||||
|
one(r#"{"method": "notif"}"#);
|
||||||
|
// Wrong version
|
||||||
|
one(r#"{"jsonrpc": 2.0, "method": "notif"}"#);
|
||||||
|
// A response with both result and error
|
||||||
|
one(r#"{"jsonrpc": "2.0", "result": 42, "error": {"code": 42, "message": "!"}, "id": 1}"#);
|
||||||
|
// A response without an id
|
||||||
|
one(r#"{"jsonrpc": "2.0", "result": 42}"#);
|
||||||
|
// An extra field
|
||||||
|
one(r#"{"jsonrpc": "2.0", "method": "weird", "params": 42, "others": 43, "id": 2}"#);
|
||||||
|
// Something completely different
|
||||||
|
one(r#"{"x": [1, 2, 3]}"#);
|
||||||
|
|
||||||
|
match from_str(r#"{]"#) {
|
||||||
|
Err(Broken::SyntaxError(_)) => (),
|
||||||
|
other => panic!("Something unexpected: {other:?}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test some non-trivial aspects of the constructors
|
||||||
|
///
|
||||||
|
/// This doesn't have a full coverage, because there's not much to actually test there.
|
||||||
|
/// Most of it is related to the ids.
|
||||||
|
#[test]
|
||||||
|
#[allow(clippy::panic)]
|
||||||
|
#[ignore]
|
||||||
|
fn constructors() {
|
||||||
|
let msg1 = Message::request("call".to_owned(), json!([1, 2, 3]));
|
||||||
|
let msg2 = Message::request("call".to_owned(), json!([1, 2, 3]));
|
||||||
|
// They differ, even when created with the same parameters
|
||||||
|
assert_ne!(msg1, msg2);
|
||||||
|
// And, specifically, they differ in the ID's
|
||||||
|
let (req1, req2) = if let (Message::Request(req1), Message::Request(req2)) = (msg1, msg2) {
|
||||||
|
assert_ne!(req1.id, req2.id);
|
||||||
|
assert!(req1.id.is_string());
|
||||||
|
assert!(req2.id.is_string());
|
||||||
|
(req1, req2)
|
||||||
|
} else {
|
||||||
|
panic!("Non-request received");
|
||||||
|
};
|
||||||
|
let id1 = req1.id.clone();
|
||||||
|
// When we answer a message, we get the same ID
|
||||||
|
if let Message::Response(ref resp) = req1.reply(json!([1, 2, 3])) {
|
||||||
|
assert_eq!(
|
||||||
|
*resp,
|
||||||
|
Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result: Ok(json!([1, 2, 3])),
|
||||||
|
id: id1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("Not a response");
|
||||||
|
}
|
||||||
|
let id2 = req2.id.clone();
|
||||||
|
// The same with an error
|
||||||
|
if let Message::Response(ref resp) =
|
||||||
|
req2.error(RpcError::new(42, "Wrong!".to_owned(), None))
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
*resp,
|
||||||
|
Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result: Err(RpcError::new(42, "Wrong!".to_owned(), None)),
|
||||||
|
id: id2,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("Not a response");
|
||||||
|
}
|
||||||
|
// When we have unmatched, we generate a top-level error with Null id.
|
||||||
|
if let Message::Response(ref resp) =
|
||||||
|
Message::error(RpcError::new(43, "Also wrong!".to_owned(), None))
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
*resp,
|
||||||
|
Response {
|
||||||
|
jsonrpc: Version,
|
||||||
|
result: Err(RpcError::new(43, "Also wrong!".to_owned(), None)),
|
||||||
|
id: Value::Null,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("Not a response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
common/src/rpc_primitives/mod.rs
Normal file
55
common/src/rpc_primitives/mod.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use bytesize::ByteSize;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod errors;
|
||||||
|
pub mod message;
|
||||||
|
pub mod parser;
|
||||||
|
pub mod requests;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct RpcLimitsConfig {
|
||||||
|
/// Maximum byte size of the json payload.
|
||||||
|
pub json_payload_max_size: ByteSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RpcLimitsConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
json_payload_max_size: ByteSize::mib(10),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct RpcConfig {
|
||||||
|
pub addr: String,
|
||||||
|
pub cors_allowed_origins: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub limits_config: RpcLimitsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RpcConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
RpcConfig {
|
||||||
|
addr: "0.0.0.0:3040".to_owned(),
|
||||||
|
cors_allowed_origins: vec!["*".to_owned()],
|
||||||
|
limits_config: RpcLimitsConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcConfig {
|
||||||
|
pub fn new(addr: &str) -> Self {
|
||||||
|
RpcConfig {
|
||||||
|
addr: addr.to_owned(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_port(port: u16) -> Self {
|
||||||
|
RpcConfig {
|
||||||
|
addr: format!("0.0.0.0:{port}"),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
common/src/rpc_primitives/parser.rs
Normal file
27
common/src/rpc_primitives/parser.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use super::errors::RpcParseError;
|
||||||
|
|
||||||
|
pub trait RpcRequest: Sized {
|
||||||
|
fn parse(value: Option<Value>) -> Result<Self, RpcParseError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_params<T: DeserializeOwned>(value: Option<Value>) -> Result<T, RpcParseError> {
|
||||||
|
if let Some(value) = value {
|
||||||
|
serde_json::from_value(value)
|
||||||
|
.map_err(|err| RpcParseError(format!("Failed parsing args: {err}")))
|
||||||
|
} else {
|
||||||
|
Err(RpcParseError("Require at least one parameter".to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! parse_request {
|
||||||
|
($request_name:ty) => {
|
||||||
|
impl RpcRequest for $request_name {
|
||||||
|
fn parse(value: Option<Value>) -> Result<Self, RpcParseError> {
|
||||||
|
parse_params::<Self>(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
219
common/src/rpc_primitives/requests.rs
Normal file
219
common/src/rpc_primitives/requests.rs
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use nssa::AccountId;
|
||||||
|
use nssa_core::program::ProgramId;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
errors::RpcParseError,
|
||||||
|
parser::{RpcRequest, parse_params},
|
||||||
|
};
|
||||||
|
use crate::{HashType, parse_request};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct HelloRequest {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct RegisterAccountRequest {
|
||||||
|
pub account_id: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct SendTxRequest {
|
||||||
|
#[serde(with = "base64_deser")]
|
||||||
|
pub transaction: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetBlockDataRequest {
|
||||||
|
pub block_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a range of blocks from `start_block_id` to `end_block_id` (inclusive)
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetBlockRangeDataRequest {
|
||||||
|
pub start_block_id: u64,
|
||||||
|
pub end_block_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetGenesisIdRequest {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetLastBlockRequest {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetInitialTestnetAccountsRequest {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetAccountBalanceRequest {
|
||||||
|
pub account_id: AccountId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetTransactionByHashRequest {
|
||||||
|
pub hash: HashType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetAccountsNoncesRequest {
|
||||||
|
pub account_ids: Vec<AccountId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetAccountRequest {
|
||||||
|
pub account_id: AccountId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetProofForCommitmentRequest {
|
||||||
|
pub commitment: nssa_core::Commitment,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetProgramIdsRequest {}
|
||||||
|
|
||||||
|
parse_request!(HelloRequest);
|
||||||
|
parse_request!(RegisterAccountRequest);
|
||||||
|
parse_request!(SendTxRequest);
|
||||||
|
parse_request!(GetBlockDataRequest);
|
||||||
|
parse_request!(GetBlockRangeDataRequest);
|
||||||
|
parse_request!(GetGenesisIdRequest);
|
||||||
|
parse_request!(GetLastBlockRequest);
|
||||||
|
parse_request!(GetInitialTestnetAccountsRequest);
|
||||||
|
parse_request!(GetAccountBalanceRequest);
|
||||||
|
parse_request!(GetTransactionByHashRequest);
|
||||||
|
parse_request!(GetAccountsNoncesRequest);
|
||||||
|
parse_request!(GetProofForCommitmentRequest);
|
||||||
|
parse_request!(GetAccountRequest);
|
||||||
|
parse_request!(GetProgramIdsRequest);
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct HelloResponse {
|
||||||
|
pub greeting: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct RegisterAccountResponse {
|
||||||
|
pub status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct SendTxResponse {
|
||||||
|
pub status: String,
|
||||||
|
pub tx_hash: HashType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetBlockDataResponse {
|
||||||
|
#[serde(with = "base64_deser")]
|
||||||
|
pub block: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetBlockRangeDataResponse {
|
||||||
|
#[serde(with = "base64_deser::vec")]
|
||||||
|
pub blocks: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod base64_deser {
|
||||||
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
use serde::{self, Deserialize, Deserializer, Serializer, ser::SerializeSeq as _};
|
||||||
|
|
||||||
|
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let base64_string = general_purpose::STANDARD.encode(bytes);
|
||||||
|
serializer.serialize_str(&base64_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let base64_string: String = Deserialize::deserialize(deserializer)?;
|
||||||
|
general_purpose::STANDARD
|
||||||
|
.decode(&base64_string)
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod vec {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn serialize<S>(bytes_vec: &[Vec<u8>], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut seq = serializer.serialize_seq(Some(bytes_vec.len()))?;
|
||||||
|
for bytes in bytes_vec {
|
||||||
|
let s = general_purpose::STANDARD.encode(bytes);
|
||||||
|
seq.serialize_element(&s)?;
|
||||||
|
}
|
||||||
|
seq.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let base64_strings: Vec<String> = Deserialize::deserialize(deserializer)?;
|
||||||
|
base64_strings
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| {
|
||||||
|
general_purpose::STANDARD
|
||||||
|
.decode(&s)
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetGenesisIdResponse {
|
||||||
|
pub genesis_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetLastBlockResponse {
|
||||||
|
pub last_block: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetAccountBalanceResponse {
|
||||||
|
pub balance: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetAccountsNoncesResponse {
|
||||||
|
pub nonces: Vec<u128>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetTransactionByHashResponse {
|
||||||
|
pub transaction: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetAccountResponse {
|
||||||
|
pub account: nssa::Account,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetProofForCommitmentResponse {
|
||||||
|
pub membership_proof: Option<nssa_core::MembershipProof>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetProgramIdsResponse {
|
||||||
|
pub program_ids: HashMap<String, ProgramId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct GetInitialTestnetAccountsResponse {
|
||||||
|
/// Hex encoded account id
|
||||||
|
pub account_id: String,
|
||||||
|
pub balance: u64,
|
||||||
|
}
|
||||||
351
common/src/sequencer_client.rs
Normal file
351
common/src/sequencer_client.rs
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
use std::{collections::HashMap, ops::RangeInclusive};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use nssa::AccountId;
|
||||||
|
use nssa_core::program::ProgramId;
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::Value;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use super::rpc_primitives::requests::{
|
||||||
|
GetAccountBalanceRequest, GetAccountBalanceResponse, GetBlockDataRequest, GetBlockDataResponse,
|
||||||
|
GetGenesisIdRequest, GetGenesisIdResponse, GetInitialTestnetAccountsRequest,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
HashType,
|
||||||
|
config::BasicAuth,
|
||||||
|
error::{SequencerClientError, SequencerRpcError},
|
||||||
|
rpc_primitives::{
|
||||||
|
self,
|
||||||
|
requests::{
|
||||||
|
GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest,
|
||||||
|
GetAccountsNoncesResponse, GetBlockRangeDataRequest, GetBlockRangeDataResponse,
|
||||||
|
GetInitialTestnetAccountsResponse, GetLastBlockRequest, GetLastBlockResponse,
|
||||||
|
GetProgramIdsRequest, GetProgramIdsResponse, GetProofForCommitmentRequest,
|
||||||
|
GetProofForCommitmentResponse, GetTransactionByHashRequest,
|
||||||
|
GetTransactionByHashResponse, SendTxRequest, SendTxResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transaction::NSSATransaction,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SequencerClient {
|
||||||
|
pub client: reqwest::Client,
|
||||||
|
pub sequencer_addr: Url,
|
||||||
|
pub basic_auth: Option<BasicAuth>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SequencerClient {
|
||||||
|
pub fn new(sequencer_addr: Url) -> Result<Self> {
|
||||||
|
Self::new_with_auth(sequencer_addr, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_auth(sequencer_addr: Url, basic_auth: Option<BasicAuth>) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
client: Client::builder()
|
||||||
|
// Add more fields if needed
|
||||||
|
.timeout(std::time::Duration::from_secs(60))
|
||||||
|
// Should be kept in sync with server keep-alive settings
|
||||||
|
.pool_idle_timeout(std::time::Duration::from_secs(5))
|
||||||
|
.build()?,
|
||||||
|
sequencer_addr,
|
||||||
|
basic_auth,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn call_method_with_payload(
|
||||||
|
&self,
|
||||||
|
method: &str,
|
||||||
|
payload: Value,
|
||||||
|
) -> Result<Value, SequencerClientError> {
|
||||||
|
let request =
|
||||||
|
rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload);
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"Calling method {method} with payload {request:?} to sequencer at {}",
|
||||||
|
self.sequencer_addr
|
||||||
|
);
|
||||||
|
let mut call_builder = self.client.post(self.sequencer_addr.clone());
|
||||||
|
|
||||||
|
if let Some(BasicAuth { username, password }) = &self.basic_auth {
|
||||||
|
call_builder = call_builder.basic_auth(username, password.as_deref());
|
||||||
|
}
|
||||||
|
|
||||||
|
let call_res = call_builder.json(&request).send().await?;
|
||||||
|
|
||||||
|
let response_vall = call_res.json::<Value>().await?;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct SequencerRpcResponse {
|
||||||
|
pub jsonrpc: String,
|
||||||
|
pub result: serde_json::Value,
|
||||||
|
pub id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(response) = serde_json::from_value::<SequencerRpcResponse>(response_vall.clone())
|
||||||
|
{
|
||||||
|
Ok(response.result)
|
||||||
|
} else {
|
||||||
|
let err_resp = serde_json::from_value::<SequencerRpcError>(response_vall)?;
|
||||||
|
|
||||||
|
Err(err_resp.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get block data at `block_id` from sequencer
|
||||||
|
pub async fn get_block(
|
||||||
|
&self,
|
||||||
|
block_id: u64,
|
||||||
|
) -> Result<GetBlockDataResponse, SequencerClientError> {
|
||||||
|
let block_req = GetBlockDataRequest { block_id };
|
||||||
|
|
||||||
|
let req = serde_json::to_value(block_req)?;
|
||||||
|
|
||||||
|
let resp = self.call_method_with_payload("get_block", req).await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_block_range(
|
||||||
|
&self,
|
||||||
|
range: RangeInclusive<u64>,
|
||||||
|
) -> Result<GetBlockRangeDataResponse, SequencerClientError> {
|
||||||
|
let block_req = GetBlockRangeDataRequest {
|
||||||
|
start_block_id: *range.start(),
|
||||||
|
end_block_id: *range.end(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = serde_json::to_value(block_req)?;
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.call_method_with_payload("get_block_range", req)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get last known `blokc_id` from sequencer
|
||||||
|
pub async fn get_last_block(&self) -> Result<GetLastBlockResponse, SequencerClientError> {
|
||||||
|
let block_req = GetLastBlockRequest {};
|
||||||
|
|
||||||
|
let req = serde_json::to_value(block_req)?;
|
||||||
|
|
||||||
|
let resp = self.call_method_with_payload("get_last_block", req).await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get account public balance for `account_id`. `account_id` must be a valid hex-string for 32
|
||||||
|
/// bytes.
|
||||||
|
pub async fn get_account_balance(
|
||||||
|
&self,
|
||||||
|
account_id: AccountId,
|
||||||
|
) -> Result<GetAccountBalanceResponse, SequencerClientError> {
|
||||||
|
let block_req = GetAccountBalanceRequest { account_id };
|
||||||
|
|
||||||
|
let req = serde_json::to_value(block_req)?;
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.call_method_with_payload("get_account_balance", req)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get accounts nonces for `account_ids`. `account_ids` must be a list of valid hex-strings for
|
||||||
|
/// 32 bytes.
|
||||||
|
pub async fn get_accounts_nonces(
|
||||||
|
&self,
|
||||||
|
account_ids: Vec<AccountId>,
|
||||||
|
) -> Result<GetAccountsNoncesResponse, SequencerClientError> {
|
||||||
|
let block_req = GetAccountsNoncesRequest { account_ids };
|
||||||
|
|
||||||
|
let req = serde_json::to_value(block_req)?;
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.call_method_with_payload("get_accounts_nonces", req)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_account(
|
||||||
|
&self,
|
||||||
|
account_id: AccountId,
|
||||||
|
) -> Result<GetAccountResponse, SequencerClientError> {
|
||||||
|
let block_req = GetAccountRequest { account_id };
|
||||||
|
|
||||||
|
let req = serde_json::to_value(block_req)?;
|
||||||
|
|
||||||
|
let resp = self.call_method_with_payload("get_account", req).await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get transaction details for `hash`.
|
||||||
|
pub async fn get_transaction_by_hash(
|
||||||
|
&self,
|
||||||
|
hash: HashType,
|
||||||
|
) -> Result<GetTransactionByHashResponse, SequencerClientError> {
|
||||||
|
let block_req = GetTransactionByHashRequest { hash };
|
||||||
|
|
||||||
|
let req = serde_json::to_value(block_req)?;
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.call_method_with_payload("get_transaction_by_hash", req)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send transaction to sequencer
|
||||||
|
pub async fn send_tx_public(
|
||||||
|
&self,
|
||||||
|
transaction: nssa::PublicTransaction,
|
||||||
|
) -> Result<SendTxResponse, SequencerClientError> {
|
||||||
|
let transaction = NSSATransaction::Public(transaction);
|
||||||
|
|
||||||
|
let tx_req = SendTxRequest {
|
||||||
|
transaction: borsh::to_vec(&transaction).unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = serde_json::to_value(tx_req)?;
|
||||||
|
|
||||||
|
let resp = self.call_method_with_payload("send_tx", req).await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send transaction to sequencer
|
||||||
|
pub async fn send_tx_private(
|
||||||
|
&self,
|
||||||
|
transaction: nssa::PrivacyPreservingTransaction,
|
||||||
|
) -> Result<SendTxResponse, SequencerClientError> {
|
||||||
|
let transaction = NSSATransaction::PrivacyPreserving(transaction);
|
||||||
|
|
||||||
|
let tx_req = SendTxRequest {
|
||||||
|
transaction: borsh::to_vec(&transaction).unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = serde_json::to_value(tx_req)?;
|
||||||
|
|
||||||
|
let resp = self.call_method_with_payload("send_tx", req).await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get genesis id from sequencer
|
||||||
|
pub async fn get_genesis_id(&self) -> Result<GetGenesisIdResponse, SequencerClientError> {
|
||||||
|
let genesis_req = GetGenesisIdRequest {};
|
||||||
|
|
||||||
|
let req = serde_json::to_value(genesis_req).unwrap();
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.call_method_with_payload("get_genesis", req)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp).unwrap();
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get initial testnet accounts from sequencer
|
||||||
|
pub async fn get_initial_testnet_accounts(
|
||||||
|
&self,
|
||||||
|
) -> Result<Vec<GetInitialTestnetAccountsResponse>, SequencerClientError> {
|
||||||
|
let acc_req = GetInitialTestnetAccountsRequest {};
|
||||||
|
|
||||||
|
let req = serde_json::to_value(acc_req).unwrap();
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.call_method_with_payload("get_initial_testnet_accounts", req)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp).unwrap();
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get proof for commitment
|
||||||
|
pub async fn get_proof_for_commitment(
|
||||||
|
&self,
|
||||||
|
commitment: nssa_core::Commitment,
|
||||||
|
) -> Result<Option<nssa_core::MembershipProof>, SequencerClientError> {
|
||||||
|
let acc_req = GetProofForCommitmentRequest { commitment };
|
||||||
|
|
||||||
|
let req = serde_json::to_value(acc_req).unwrap();
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.call_method_with_payload("get_proof_for_commitment", req)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value::<GetProofForCommitmentResponse>(resp)
|
||||||
|
.unwrap()
|
||||||
|
.membership_proof;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_tx_program(
|
||||||
|
&self,
|
||||||
|
transaction: nssa::ProgramDeploymentTransaction,
|
||||||
|
) -> Result<SendTxResponse, SequencerClientError> {
|
||||||
|
let transaction = NSSATransaction::ProgramDeployment(transaction);
|
||||||
|
|
||||||
|
let tx_req = SendTxRequest {
|
||||||
|
transaction: borsh::to_vec(&transaction).unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = serde_json::to_value(tx_req)?;
|
||||||
|
|
||||||
|
let resp = self.call_method_with_payload("send_tx", req).await?;
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value(resp)?;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get Ids of the programs used by the node
|
||||||
|
pub async fn get_program_ids(
|
||||||
|
&self,
|
||||||
|
) -> Result<HashMap<String, ProgramId>, SequencerClientError> {
|
||||||
|
let acc_req = GetProgramIdsRequest {};
|
||||||
|
|
||||||
|
let req = serde_json::to_value(acc_req).unwrap();
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.call_method_with_payload("get_program_ids", req)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp_deser = serde_json::from_value::<GetProgramIdsResponse>(resp)
|
||||||
|
.unwrap()
|
||||||
|
.program_ids;
|
||||||
|
|
||||||
|
Ok(resp_deser)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,55 +3,49 @@ use nssa::AccountId;
|
|||||||
use crate::{
|
use crate::{
|
||||||
HashType,
|
HashType,
|
||||||
block::{Block, HashableBlockData},
|
block::{Block, HashableBlockData},
|
||||||
transaction::{NSSATransaction, clock_invocation},
|
transaction::NSSATransaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
|
pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
|
||||||
nssa::PrivateKey::try_new([37; 32]).unwrap()
|
nssa::PrivateKey::try_new([37; 32]).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dummy producers
|
// Dummy producers
|
||||||
|
|
||||||
/// Produce dummy block with provided transactions + clock transaction an the end.
|
/// Produce dummy block with
|
||||||
///
|
///
|
||||||
/// `id` - block id, provide zero for genesis.
|
/// `id` - block id, provide zero for genesis
|
||||||
///
|
///
|
||||||
/// `prev_hash` - hash of previous block, provide None for genesis.
|
/// `prev_hash` - hash of previous block, provide None for genesis
|
||||||
///
|
///
|
||||||
/// `transactions` - vector of `EncodedTransaction` objects.
|
/// `transactions` - vector of `EncodedTransaction` objects
|
||||||
#[must_use]
|
|
||||||
pub fn produce_dummy_block(
|
pub fn produce_dummy_block(
|
||||||
id: u64,
|
id: u64,
|
||||||
prev_hash: Option<HashType>,
|
prev_hash: Option<HashType>,
|
||||||
mut transactions: Vec<NSSATransaction>,
|
transactions: Vec<NSSATransaction>,
|
||||||
) -> Block {
|
) -> Block {
|
||||||
transactions.push(NSSATransaction::Public(clock_invocation(
|
|
||||||
id.saturating_mul(100),
|
|
||||||
)));
|
|
||||||
|
|
||||||
let block_data = HashableBlockData {
|
let block_data = HashableBlockData {
|
||||||
block_id: id,
|
block_id: id,
|
||||||
prev_block_hash: prev_hash.unwrap_or_default(),
|
prev_block_hash: prev_hash.unwrap_or_default(),
|
||||||
timestamp: id.saturating_mul(100),
|
timestamp: id * 100,
|
||||||
transactions,
|
transactions,
|
||||||
};
|
};
|
||||||
|
|
||||||
block_data.into_pending_block(&sequencer_sign_key_for_testing(), [0; 32])
|
block_data.into_pending_block(&sequencer_sign_key_for_testing(), [0; 32])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn produce_dummy_empty_transaction() -> NSSATransaction {
|
pub fn produce_dummy_empty_transaction() -> NSSATransaction {
|
||||||
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
||||||
let account_ids = vec![];
|
let account_ids = vec![];
|
||||||
let nonces = vec![];
|
let nonces = vec![];
|
||||||
|
let instruction_data: u128 = 0;
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
program_id,
|
program_id,
|
||||||
account_ids,
|
account_ids,
|
||||||
nonces,
|
nonces,
|
||||||
authenticated_transfer_core::Instruction::Initialize,
|
instruction_data,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
|
let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
|
||||||
@ -62,27 +56,24 @@ pub fn produce_dummy_empty_transaction() -> NSSATransaction {
|
|||||||
NSSATransaction::Public(nssa_tx)
|
NSSATransaction::Public(nssa_tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn create_transaction_native_token_transfer(
|
pub fn create_transaction_native_token_transfer(
|
||||||
from: AccountId,
|
from: AccountId,
|
||||||
nonce: u128,
|
nonce: u128,
|
||||||
to: AccountId,
|
to: AccountId,
|
||||||
balance_to_move: u128,
|
balance_to_move: u128,
|
||||||
signing_key: &nssa::PrivateKey,
|
signing_key: nssa::PrivateKey,
|
||||||
) -> NSSATransaction {
|
) -> NSSATransaction {
|
||||||
let account_ids = vec![from, to];
|
let account_ids = vec![from, to];
|
||||||
let nonces = vec![nonce.into()];
|
let nonces = vec![nonce];
|
||||||
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
program_id,
|
program_id,
|
||||||
account_ids,
|
account_ids,
|
||||||
nonces,
|
nonces,
|
||||||
authenticated_transfer_core::Instruction::Transfer {
|
balance_to_move,
|
||||||
amount: balance_to_move,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]);
|
||||||
|
|
||||||
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
|
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use nssa::{AccountId, V03State, ValidatedStateDiff};
|
use nssa::{AccountId, V02State};
|
||||||
use nssa_core::{BlockId, Timestamp};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::HashType;
|
use crate::HashType;
|
||||||
@ -13,34 +12,20 @@ pub enum NSSATransaction {
|
|||||||
ProgramDeployment(nssa::ProgramDeploymentTransaction),
|
ProgramDeployment(nssa::ProgramDeploymentTransaction),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for NSSATransaction {
|
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
crate::borsh_base64::serialize(self, serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for NSSATransaction {
|
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
||||||
crate::borsh_base64::deserialize(deserializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NSSATransaction {
|
impl NSSATransaction {
|
||||||
#[must_use]
|
|
||||||
pub fn hash(&self) -> HashType {
|
pub fn hash(&self) -> HashType {
|
||||||
HashType(match self {
|
HashType(match self {
|
||||||
Self::Public(tx) => tx.hash(),
|
NSSATransaction::Public(tx) => tx.hash(),
|
||||||
Self::PrivacyPreserving(tx) => tx.hash(),
|
NSSATransaction::PrivacyPreserving(tx) => tx.hash(),
|
||||||
Self::ProgramDeployment(tx) => tx.hash(),
|
NSSATransaction::ProgramDeployment(tx) => tx.hash(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
|
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
|
||||||
match self {
|
match self {
|
||||||
Self::ProgramDeployment(tx) => tx.affected_public_account_ids(),
|
NSSATransaction::ProgramDeployment(tx) => tx.affected_public_account_ids(),
|
||||||
Self::Public(tx) => tx.affected_public_account_ids(),
|
NSSATransaction::Public(tx) => tx.affected_public_account_ids(),
|
||||||
Self::PrivacyPreserving(tx) => tx.affected_public_account_ids(),
|
NSSATransaction::PrivacyPreserving(tx) => tx.affected_public_account_ids(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,85 +33,39 @@ impl NSSATransaction {
|
|||||||
pub fn transaction_stateless_check(self) -> Result<Self, TransactionMalformationError> {
|
pub fn transaction_stateless_check(self) -> Result<Self, TransactionMalformationError> {
|
||||||
// Stateless checks here
|
// Stateless checks here
|
||||||
match self {
|
match self {
|
||||||
Self::Public(tx) => {
|
NSSATransaction::Public(tx) => {
|
||||||
if tx.witness_set().is_valid_for(tx.message()) {
|
if tx.witness_set().is_valid_for(tx.message()) {
|
||||||
Ok(Self::Public(tx))
|
Ok(NSSATransaction::Public(tx))
|
||||||
} else {
|
} else {
|
||||||
Err(TransactionMalformationError::InvalidSignature)
|
Err(TransactionMalformationError::InvalidSignature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::PrivacyPreserving(tx) => {
|
NSSATransaction::PrivacyPreserving(tx) => {
|
||||||
if tx.witness_set().signatures_are_valid_for(tx.message()) {
|
if tx.witness_set().signatures_are_valid_for(tx.message()) {
|
||||||
Ok(Self::PrivacyPreserving(tx))
|
Ok(NSSATransaction::PrivacyPreserving(tx))
|
||||||
} else {
|
} else {
|
||||||
Err(TransactionMalformationError::InvalidSignature)
|
Err(TransactionMalformationError::InvalidSignature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::ProgramDeployment(tx) => Ok(Self::ProgramDeployment(tx)),
|
NSSATransaction::ProgramDeployment(tx) => Ok(NSSATransaction::ProgramDeployment(tx)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates the transaction against the current state and returns the resulting diff
|
|
||||||
/// without applying it. Rejects transactions that modify clock or faucet system accounts,
|
|
||||||
/// whether directly or indirectly via chain calls.
|
|
||||||
///
|
|
||||||
/// This check is required for all user transactions. Only sequencer transactions may bypass
|
|
||||||
/// this check.
|
|
||||||
pub fn validate_on_state(
|
|
||||||
&self,
|
|
||||||
state: &V03State,
|
|
||||||
block_id: BlockId,
|
|
||||||
timestamp: Timestamp,
|
|
||||||
) -> Result<ValidatedStateDiff, nssa::error::NssaError> {
|
|
||||||
let diff = match self {
|
|
||||||
Self::Public(tx) => {
|
|
||||||
ValidatedStateDiff::from_public_transaction(tx, state, block_id, timestamp)
|
|
||||||
}
|
|
||||||
Self::PrivacyPreserving(tx) => ValidatedStateDiff::from_privacy_preserving_transaction(
|
|
||||||
tx, state, block_id, timestamp,
|
|
||||||
),
|
|
||||||
Self::ProgramDeployment(tx) => {
|
|
||||||
ValidatedStateDiff::from_program_deployment_transaction(tx, state)
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let public_diff = diff.public_diff();
|
|
||||||
let touches_clock = nssa::CLOCK_PROGRAM_ACCOUNT_IDS.iter().any(|id| {
|
|
||||||
public_diff
|
|
||||||
.get(id)
|
|
||||||
.is_some_and(|post| *post != state.get_account_by_id(*id))
|
|
||||||
});
|
|
||||||
if touches_clock {
|
|
||||||
return Err(nssa::error::NssaError::InvalidInput(
|
|
||||||
"Transaction modifies system clock accounts".into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let faucet_id = nssa::system_faucet_account_id();
|
|
||||||
if public_diff
|
|
||||||
.get(&faucet_id)
|
|
||||||
.is_some_and(|post| *post != state.get_account_by_id(faucet_id))
|
|
||||||
{
|
|
||||||
return Err(nssa::error::NssaError::InvalidInput(
|
|
||||||
"Transaction modifies system faucet account".into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(diff)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validates the transaction against the current state, rejects modifications to clock
|
|
||||||
/// system accounts, and applies the resulting diff to the state.
|
|
||||||
pub fn execute_check_on_state(
|
pub fn execute_check_on_state(
|
||||||
self,
|
self,
|
||||||
state: &mut V03State,
|
state: &mut V02State,
|
||||||
block_id: BlockId,
|
|
||||||
timestamp: Timestamp,
|
|
||||||
) -> Result<Self, nssa::error::NssaError> {
|
) -> Result<Self, nssa::error::NssaError> {
|
||||||
let diff = self
|
match &self {
|
||||||
.validate_on_state(state, block_id, timestamp)
|
NSSATransaction::Public(tx) => state.transition_from_public_transaction(tx),
|
||||||
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
NSSATransaction::PrivacyPreserving(tx) => {
|
||||||
state.apply_state_diff(diff);
|
state.transition_from_privacy_preserving_transaction(tx)
|
||||||
|
}
|
||||||
|
NSSATransaction::ProgramDeployment(tx) => {
|
||||||
|
state.transition_from_program_deployment_transaction(tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,7 +89,7 @@ impl From<nssa::ProgramDeploymentTransaction> for NSSATransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize,
|
||||||
)]
|
)]
|
||||||
pub enum TxKind {
|
pub enum TxKind {
|
||||||
Public,
|
Public,
|
||||||
@ -158,7 +97,7 @@ pub enum TxKind {
|
|||||||
ProgramDeployment,
|
ProgramDeployment,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, thiserror::Error)]
|
||||||
pub enum TransactionMalformationError {
|
pub enum TransactionMalformationError {
|
||||||
#[error("Invalid signature(-s)")]
|
#[error("Invalid signature(-s)")]
|
||||||
InvalidSignature,
|
InvalidSignature,
|
||||||
@ -167,20 +106,3 @@ pub enum TransactionMalformationError {
|
|||||||
#[error("Transaction size {size} exceeds maximum allowed size of {max} bytes")]
|
#[error("Transaction size {size} exceeds maximum allowed size of {max} bytes")]
|
||||||
TransactionTooLarge { size: usize, max: usize },
|
TransactionTooLarge { size: usize, max: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the canonical Clock Program invocation transaction for the given block timestamp.
|
|
||||||
/// Every valid block must end with exactly one occurrence of this transaction.
|
|
||||||
#[must_use]
|
|
||||||
pub fn clock_invocation(timestamp: clock_core::Instruction) -> nssa::PublicTransaction {
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
nssa::program::Program::clock().id(),
|
|
||||||
clock_core::CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(),
|
|
||||||
vec![],
|
|
||||||
timestamp,
|
|
||||||
)
|
|
||||||
.expect("Clock invocation message should always be constructable");
|
|
||||||
nssa::PublicTransaction::new(
|
|
||||||
message,
|
|
||||||
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Wallet CLI Completion
|
# Wallet CLI Completion
|
||||||
|
|
||||||
Completion scripts for the LSSA `wallet` command.
|
Completion scripts for the LSSA `wallet` command.
|
||||||
|
|
||||||
## ZSH
|
## ZSH
|
||||||
|
|
||||||
@ -19,9 +19,9 @@ Preconfigured accounts and accounts only with `/` (no number) are not completed.
|
|||||||
e.g.:
|
e.g.:
|
||||||
|
|
||||||
```
|
```
|
||||||
▶ wallet account list
|
▶ wallet account list
|
||||||
Preconfigured Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo,
|
Preconfigured Public/Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw,
|
||||||
Preconfigured Public/6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV,
|
Preconfigured Public/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy,
|
||||||
Preconfigured Private/3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw,
|
Preconfigured Private/3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw,
|
||||||
Preconfigured Private/AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX,
|
Preconfigured Private/AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX,
|
||||||
/ Public/8DstRgMQrB2N9a7ymv98RDDbt8nctrP9ZzaNRSpKDZSu,
|
/ Public/8DstRgMQrB2N9a7ymv98RDDbt8nctrP9ZzaNRSpKDZSu,
|
||||||
@ -93,83 +93,6 @@ Only `Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH` is used for completio
|
|||||||
exec zsh
|
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`.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Main commands
|
|
||||||
wallet <TAB>
|
|
||||||
|
|
||||||
# Account subcommands
|
|
||||||
wallet account <TAB>
|
|
||||||
|
|
||||||
# Options for auth-transfer send
|
|
||||||
wallet auth-transfer send --<TAB>
|
|
||||||
|
|
||||||
# Account types when creating
|
|
||||||
wallet account new <TAB>
|
|
||||||
# Shows: public private
|
|
||||||
|
|
||||||
# Account IDs (fetched dynamically)
|
|
||||||
wallet account get --account-id <TAB>
|
|
||||||
# Shows: Public/... Private/...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Bash
|
|
||||||
|
|
||||||
Works with bash 4+. The `bash-completion` package is required for auto-sourcing from
|
|
||||||
`/etc/bash_completion.d/`; without it, source the file directly from `~/.bashrc` instead.
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Full completion for all wallet subcommands
|
|
||||||
- Contextual option completion for each command
|
|
||||||
- Dynamic account ID completion via `wallet account list`
|
|
||||||
- Falls back to `Public/` / `Private/` prefixes when no accounts are available
|
|
||||||
|
|
||||||
Note that only accounts created by the user auto-complete (same filtering as zsh — see above).
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
#### Option A — source directly from `~/.bashrc` (works everywhere)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
echo "source $(pwd)/completions/bash/wallet" >> ~/.bashrc
|
|
||||||
exec bash
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Option B — system-wide via `bash-completion`
|
|
||||||
|
|
||||||
1. Copy the file:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cp ./bash/wallet /etc/bash_completion.d/wallet
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Ensure `bash-completion` is initialised in every interactive shell. On many Linux
|
|
||||||
distributions (e.g. Fedora) it is only sourced for **login** shells via
|
|
||||||
`/etc/profile.d/bash_completion.sh`. For non-login shells (e.g. a bash session started
|
|
||||||
inside zsh), add this to `~/.bashrc`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
[[ -f /usr/share/bash-completion/bash_completion ]] && source /usr/share/bash-completion/bash_completion
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Reload your shell:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
exec bash
|
|
||||||
```
|
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
The completion script calls `wallet account list` to dynamically fetch account IDs. Ensure the `wallet` command is in your `$PATH`.
|
The completion script calls `wallet account list` to dynamically fetch account IDs. Ensure the `wallet` command is in your `$PATH`.
|
||||||
@ -197,13 +120,14 @@ wallet account get --account-id <TAB>
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Zsh completions not appearing
|
### Completions not appearing
|
||||||
|
|
||||||
1. Check that `compinit` is called in your `.zshrc`
|
1. Check that `compinit` is called in your `.zshrc`
|
||||||
2. Rebuild the completion cache:
|
2. Rebuild the completion cache:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
rm -rf ~/.zcompdump* && exec zsh
|
rm -f ~/.zcompdump*
|
||||||
|
exec zsh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Account IDs not completing
|
### Account IDs not completing
|
||||||
|
|||||||
@ -1,591 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Bash completion script for the wallet CLI
|
|
||||||
# See instructions in ../README.md
|
|
||||||
|
|
||||||
# Helper function to complete account IDs
|
|
||||||
# Uses `wallet account list` to get available accounts
|
|
||||||
# Only includes accounts with /N prefix (where N is a number)
|
|
||||||
_wallet_complete_account_id() {
|
|
||||||
local cur="$1"
|
|
||||||
local accounts
|
|
||||||
|
|
||||||
if command -v wallet &>/dev/null; then
|
|
||||||
accounts=$(wallet account list 2>/dev/null | grep '^/[0-9]' | awk '{print $2}' | tr -d ',')
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$accounts" ]]; then
|
|
||||||
COMPREPLY=($(compgen -W "$accounts" -- "$cur"))
|
|
||||||
else
|
|
||||||
COMPREPLY=($(compgen -W "Public/ Private/" -- "$cur"))
|
|
||||||
compopt -o nospace 2>/dev/null
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Helper function to complete account labels
|
|
||||||
_wallet_complete_account_label() {
|
|
||||||
local cur="$1"
|
|
||||||
local labels
|
|
||||||
|
|
||||||
if command -v wallet &>/dev/null; then
|
|
||||||
labels=$(wallet account list 2>/dev/null | grep -o '\[.*\]' | sed 's/^\[//;s/\]$//')
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$labels" ]]; then
|
|
||||||
COMPREPLY=($(compgen -W "$labels" -- "$cur"))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
_wallet() {
|
|
||||||
local cur prev words cword
|
|
||||||
_init_completion 2>/dev/null || {
|
|
||||||
COMPREPLY=()
|
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
||||||
words=("${COMP_WORDS[@]}")
|
|
||||||
cword=$COMP_CWORD
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
local cmd="" subcmd=""
|
|
||||||
local cmd_idx=0 subcmd_idx=0
|
|
||||||
local i
|
|
||||||
for ((i = 1; i < cword; i++)); do
|
|
||||||
local w="${words[$i]}"
|
|
||||||
case "$w" in
|
|
||||||
--auth)
|
|
||||||
((i++)) # skip the auth value
|
|
||||||
;;
|
|
||||||
-c | --continuous-run)
|
|
||||||
# boolean flag, no value
|
|
||||||
;;
|
|
||||||
-*)
|
|
||||||
# unrecognised option, skip
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
if [[ -z "$cmd" ]]; then
|
|
||||||
cmd="$w"
|
|
||||||
cmd_idx=$i
|
|
||||||
elif [[ -z "$subcmd" ]]; then
|
|
||||||
subcmd="$w"
|
|
||||||
subcmd_idx=$i
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
local config_keys="override_rust_log sequencer_addr seq_poll_timeout seq_tx_poll_max_blocks seq_poll_max_retries seq_block_poll_max_amount initial_accounts basic_auth"
|
|
||||||
|
|
||||||
case "$cmd" in
|
|
||||||
"")
|
|
||||||
# Completing the main command or a global option
|
|
||||||
if [[ "$prev" == "--auth" ]]; then
|
|
||||||
return # completing the --auth value; no suggestions
|
|
||||||
fi
|
|
||||||
case "$cur" in
|
|
||||||
-*)
|
|
||||||
COMPREPLY=($(compgen -W "-c --continuous-run --auth" -- "$cur"))
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
|
|
||||||
auth-transfer)
|
|
||||||
case "$subcmd" in
|
|
||||||
"")
|
|
||||||
COMPREPLY=($(compgen -W "init send help" -- "$cur"))
|
|
||||||
;;
|
|
||||||
init)
|
|
||||||
case "$prev" in
|
|
||||||
--account-id)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--account-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--account-id --account-label" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
send)
|
|
||||||
case "$prev" in
|
|
||||||
--from)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--from-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--to)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--to-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--to-npk | --to-vpk | --to-identifier | --amount)
|
|
||||||
;; # no specific completion
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --to-identifier --amount" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
|
|
||||||
chain-info)
|
|
||||||
case "$subcmd" in
|
|
||||||
"")
|
|
||||||
COMPREPLY=($(compgen -W "current-block-id block transaction help" -- "$cur"))
|
|
||||||
;;
|
|
||||||
block)
|
|
||||||
case "$prev" in
|
|
||||||
-i | --id)
|
|
||||||
;; # no specific completion for block ID
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "-i --id" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
transaction)
|
|
||||||
case "$prev" in
|
|
||||||
-t | --hash)
|
|
||||||
;; # no specific completion for tx hash
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "-t --hash" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
|
|
||||||
account)
|
|
||||||
case "$subcmd" in
|
|
||||||
"")
|
|
||||||
COMPREPLY=($(compgen -W "get new sync-private list ls label help" -- "$cur"))
|
|
||||||
;;
|
|
||||||
get)
|
|
||||||
case "$prev" in
|
|
||||||
-a | --account-id)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--account-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "-r --raw -k --keys -a --account-id --account-label" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
list | ls)
|
|
||||||
COMPREPLY=($(compgen -W "-l --long" -- "$cur"))
|
|
||||||
;;
|
|
||||||
sync-private)
|
|
||||||
;; # no options
|
|
||||||
new)
|
|
||||||
# `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-accounts-key)
|
|
||||||
new_subcmd="${words[$i]}"
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -z "$new_subcmd" ]]; then
|
|
||||||
COMPREPLY=($(compgen -W "public private-accounts-key" -- "$cur"))
|
|
||||||
else
|
|
||||||
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
|
|
||||||
;;
|
|
||||||
label)
|
|
||||||
case "$prev" in
|
|
||||||
-a | --account-id)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--account-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
-l | --label)
|
|
||||||
;; # no specific completion for label value
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "-a --account-id --account-label -l --label" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
|
|
||||||
pinata)
|
|
||||||
case "$subcmd" in
|
|
||||||
"")
|
|
||||||
COMPREPLY=($(compgen -W "claim help" -- "$cur"))
|
|
||||||
;;
|
|
||||||
claim)
|
|
||||||
case "$prev" in
|
|
||||||
--to)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--to-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--to --to-label" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
|
|
||||||
token)
|
|
||||||
case "$subcmd" in
|
|
||||||
"")
|
|
||||||
COMPREPLY=($(compgen -W "new send burn mint help" -- "$cur"))
|
|
||||||
;;
|
|
||||||
new)
|
|
||||||
case "$prev" in
|
|
||||||
--definition-account-id)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--definition-account-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--supply-account-id)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--supply-account-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
-n | --name | -t | --total-supply)
|
|
||||||
;; # no specific completion
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--definition-account-id --definition-account-label --supply-account-id --supply-account-label -n --name -t --total-supply" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
send)
|
|
||||||
case "$prev" in
|
|
||||||
--from)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--from-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--to)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--to-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--to-npk | --to-vpk | --to-identifier | --amount)
|
|
||||||
;; # no specific completion
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--from --from-label --to --to-label --to-npk --to-vpk --to-identifier --amount" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
burn)
|
|
||||||
case "$prev" in
|
|
||||||
--definition)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--definition-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--holder)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--holder-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--amount)
|
|
||||||
;; # no specific completion
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--definition --definition-label --holder --holder-label --amount" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
mint)
|
|
||||||
case "$prev" in
|
|
||||||
--definition)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--definition-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--holder)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--holder-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--holder-npk | --holder-vpk | --holder-identifier | --amount)
|
|
||||||
;; # no specific completion
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--definition --definition-label --holder --holder-label --holder-npk --holder-vpk --holder-identifier --amount" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
|
|
||||||
amm)
|
|
||||||
case "$subcmd" in
|
|
||||||
"")
|
|
||||||
COMPREPLY=($(compgen -W "new swap-exact-input swap-exact-output add-liquidity remove-liquidity help" -- "$cur"))
|
|
||||||
;;
|
|
||||||
new)
|
|
||||||
case "$prev" in
|
|
||||||
--user-holding-a)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-a-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-b)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-b-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-lp)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-lp-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--balance-a | --balance-b)
|
|
||||||
;; # no specific completion
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-a-label --user-holding-b --user-holding-b-label --user-holding-lp --user-holding-lp-label --balance-a --balance-b" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
swap-exact-input)
|
|
||||||
case "$prev" in
|
|
||||||
--user-holding-a)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-a-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-b)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-b-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--amount-in | --min-amount-out | --token-definition)
|
|
||||||
;; # no specific completion
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-a-label --user-holding-b --user-holding-b-label --amount-in --min-amount-out --token-definition" -- "$cur"))
|
|
||||||
;;
|
|
||||||
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)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-a-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-b)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-b-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-lp)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-lp-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--max-amount-a | --max-amount-b | --min-amount-lp)
|
|
||||||
;; # no specific completion
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-a-label --user-holding-b --user-holding-b-label --user-holding-lp --user-holding-lp-label --max-amount-a --max-amount-b --min-amount-lp" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
remove-liquidity)
|
|
||||||
case "$prev" in
|
|
||||||
--user-holding-a)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-a-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-b)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-b-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-lp)
|
|
||||||
_wallet_complete_account_id "$cur"
|
|
||||||
;;
|
|
||||||
--user-holding-lp-label)
|
|
||||||
_wallet_complete_account_label "$cur"
|
|
||||||
;;
|
|
||||||
--balance-lp | --min-amount-a | --min-amount-b)
|
|
||||||
;; # no specific completion
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-a-label --user-holding-b --user-holding-b-label --user-holding-lp --user-holding-lp-label --balance-lp --min-amount-a --min-amount-b" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
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
|
|
||||||
"")
|
|
||||||
COMPREPLY=($(compgen -W "get set description help" -- "$cur"))
|
|
||||||
;;
|
|
||||||
get)
|
|
||||||
# Accepts optional -a/--all flag and an optional positional key
|
|
||||||
COMPREPLY=($(compgen -W "--all -a $config_keys" -- "$cur"))
|
|
||||||
;;
|
|
||||||
set)
|
|
||||||
# set <key> <value> — only complete the key; no completion for the value
|
|
||||||
local set_args=0
|
|
||||||
for ((i = subcmd_idx + 1; i < cword; i++)); do
|
|
||||||
[[ "${words[$i]}" != -* ]] && ((set_args++))
|
|
||||||
done
|
|
||||||
if [[ $set_args -eq 0 ]]; then
|
|
||||||
COMPREPLY=($(compgen -W "$config_keys" -- "$cur"))
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
description)
|
|
||||||
# description <key> — only complete if no key provided yet
|
|
||||||
local has_key=false
|
|
||||||
for ((i = subcmd_idx + 1; i < cword; i++)); do
|
|
||||||
[[ "${words[$i]}" != -* ]] && has_key=true && break
|
|
||||||
done
|
|
||||||
if ! $has_key; then
|
|
||||||
COMPREPLY=($(compgen -W "$config_keys" -- "$cur"))
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
|
|
||||||
restore-keys)
|
|
||||||
case "$prev" in
|
|
||||||
-d | --depth)
|
|
||||||
;; # no specific completion for depth value
|
|
||||||
*)
|
|
||||||
COMPREPLY=($(compgen -W "-d --depth" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
|
|
||||||
deploy-program)
|
|
||||||
COMPREPLY=($(compgen -f -- "$cur"))
|
|
||||||
compopt -o filenames 2>/dev/null
|
|
||||||
;;
|
|
||||||
|
|
||||||
help)
|
|
||||||
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
complete -F _wallet wallet
|
|
||||||
@ -24,7 +24,6 @@ _wallet() {
|
|||||||
'pinata:Pinata program interaction subcommand'
|
'pinata:Pinata program interaction subcommand'
|
||||||
'token:Token program interaction subcommand'
|
'token:Token program interaction subcommand'
|
||||||
'amm:AMM 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'
|
'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'
|
'config:Command to setup config, get and set config fields'
|
||||||
'restore-keys:Restoring keys from given password at given depth'
|
'restore-keys:Restoring keys from given password at given depth'
|
||||||
@ -53,9 +52,6 @@ _wallet() {
|
|||||||
amm)
|
amm)
|
||||||
_wallet_amm
|
_wallet_amm
|
||||||
;;
|
;;
|
||||||
ata)
|
|
||||||
_wallet_ata
|
|
||||||
;;
|
|
||||||
config)
|
config)
|
||||||
_wallet_config
|
_wallet_config
|
||||||
;;
|
;;
|
||||||
@ -76,7 +72,7 @@ _wallet() {
|
|||||||
# auth-transfer subcommand
|
# auth-transfer subcommand
|
||||||
_wallet_auth_transfer() {
|
_wallet_auth_transfer() {
|
||||||
local -a subcommands
|
local -a subcommands
|
||||||
|
|
||||||
_arguments -C \
|
_arguments -C \
|
||||||
'1: :->subcommand' \
|
'1: :->subcommand' \
|
||||||
'*:: :->args'
|
'*:: :->args'
|
||||||
@ -94,18 +90,14 @@ _wallet_auth_transfer() {
|
|||||||
case $line[1] in
|
case $line[1] in
|
||||||
init)
|
init)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--account-id[Account ID to initialize]:account_id:_wallet_account_ids' \
|
'--account-id[Account ID to initialize]:account_id:_wallet_account_ids'
|
||||||
'--account-label[Account label (alternative to --account-id)]:label:'
|
|
||||||
;;
|
;;
|
||||||
send)
|
send)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--from[Source account ID]:from_account:_wallet_account_ids' \
|
'--from[Source account ID]:from_account:_wallet_account_ids' \
|
||||||
'--from-label[From account label (alternative to --from)]:label:' \
|
|
||||||
'--to[Destination account ID (for owned accounts)]:to_account:_wallet_account_ids' \
|
'--to[Destination account ID (for owned accounts)]:to_account:_wallet_account_ids' \
|
||||||
'--to-label[To account label (alternative to --to)]:label:' \
|
|
||||||
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
|
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
|
||||||
'--to-vpk[Destination viewing public key (for foreign private accounts)]:vpk:' \
|
'--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:'
|
'--amount[Amount of native tokens to send]:amount:'
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -116,7 +108,7 @@ _wallet_auth_transfer() {
|
|||||||
# chain-info subcommand
|
# chain-info subcommand
|
||||||
_wallet_chain_info() {
|
_wallet_chain_info() {
|
||||||
local -a subcommands
|
local -a subcommands
|
||||||
|
|
||||||
_arguments -C \
|
_arguments -C \
|
||||||
'1: :->subcommand' \
|
'1: :->subcommand' \
|
||||||
'*:: :->args'
|
'*:: :->args'
|
||||||
@ -149,7 +141,7 @@ _wallet_chain_info() {
|
|||||||
# account subcommand
|
# account subcommand
|
||||||
_wallet_account() {
|
_wallet_account() {
|
||||||
local -a subcommands
|
local -a subcommands
|
||||||
|
|
||||||
_arguments -C \
|
_arguments -C \
|
||||||
'1: :->subcommand' \
|
'1: :->subcommand' \
|
||||||
'*:: :->args'
|
'*:: :->args'
|
||||||
@ -173,8 +165,7 @@ _wallet_account() {
|
|||||||
_arguments \
|
_arguments \
|
||||||
'(-r --raw)'{-r,--raw}'[Get raw account data]' \
|
'(-r --raw)'{-r,--raw}'[Get raw account data]' \
|
||||||
'(-k --keys)'{-k,--keys}'[Display keys (pk for public accounts, npk/vpk for private accounts)]' \
|
'(-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' \
|
'(-a --account-id)'{-a,--account-id}'[Account ID to query]:account_id:_wallet_account_ids'
|
||||||
'--account-label[Account label (alternative to --account-id)]:label:'
|
|
||||||
;;
|
;;
|
||||||
list|ls)
|
list|ls)
|
||||||
_arguments \
|
_arguments \
|
||||||
@ -186,27 +177,17 @@ _wallet_account() {
|
|||||||
'*:: :->new_args'
|
'*:: :->new_args'
|
||||||
case $state in
|
case $state in
|
||||||
account_type)
|
account_type)
|
||||||
compadd public private-accounts-key
|
compadd public private
|
||||||
;;
|
;;
|
||||||
new_args)
|
new_args)
|
||||||
case $line[1] in
|
_arguments \
|
||||||
public)
|
'--cci[Chain index of a parent node]:chain_index:'
|
||||||
_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
|
esac
|
||||||
;;
|
;;
|
||||||
label)
|
label)
|
||||||
_arguments \
|
_arguments \
|
||||||
'(-a --account-id)'{-a,--account-id}'[Account ID to label]:account_id:_wallet_account_ids' \
|
'(-a --account-id)'{-a,--account-id}'[Account ID to label]:account_id:_wallet_account_ids' \
|
||||||
'--account-label[Account label (alternative to --account-id)]:label:' \
|
|
||||||
'(-l --label)'{-l,--label}'[The label to assign to the account]:label:'
|
'(-l --label)'{-l,--label}'[The label to assign to the account]:label:'
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -217,7 +198,7 @@ _wallet_account() {
|
|||||||
# pinata subcommand
|
# pinata subcommand
|
||||||
_wallet_pinata() {
|
_wallet_pinata() {
|
||||||
local -a subcommands
|
local -a subcommands
|
||||||
|
|
||||||
_arguments -C \
|
_arguments -C \
|
||||||
'1: :->subcommand' \
|
'1: :->subcommand' \
|
||||||
'*:: :->args'
|
'*:: :->args'
|
||||||
@ -234,8 +215,7 @@ _wallet_pinata() {
|
|||||||
case $line[1] in
|
case $line[1] in
|
||||||
claim)
|
claim)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--to[Destination account ID to receive claimed tokens]:to_account:_wallet_account_ids' \
|
'--to[Destination account ID to receive claimed tokens]:to_account:_wallet_account_ids'
|
||||||
'--to-label[To account label (alternative to --to)]:label:'
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
@ -268,38 +248,28 @@ _wallet_token() {
|
|||||||
'--name[Token name]:name:' \
|
'--name[Token name]:name:' \
|
||||||
'--total-supply[Total supply of tokens to mint]:total_supply:' \
|
'--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-id[Account ID for token definition]:definition_account:_wallet_account_ids' \
|
||||||
'--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-id[Account ID to receive initial supply]:supply_account:_wallet_account_ids' \
|
|
||||||
'--supply-account-label[Supply account label (alternative to --supply-account-id)]:label:'
|
|
||||||
;;
|
;;
|
||||||
send)
|
send)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--from[Source holding account ID]:from_account:_wallet_account_ids' \
|
'--from[Source holding account ID]:from_account:_wallet_account_ids' \
|
||||||
'--from-label[From account label (alternative to --from)]:label:' \
|
|
||||||
'--to[Destination holding account ID (for owned accounts)]:to_account:_wallet_account_ids' \
|
'--to[Destination holding account ID (for owned accounts)]:to_account:_wallet_account_ids' \
|
||||||
'--to-label[To account label (alternative to --to)]:label:' \
|
|
||||||
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
|
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
|
||||||
'--to-vpk[Destination viewing public key (for foreign private accounts)]:vpk:' \
|
'--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:'
|
'--amount[Amount of tokens to send]:amount:'
|
||||||
;;
|
;;
|
||||||
burn)
|
burn)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
|
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
|
||||||
'--definition-label[Definition account label (alternative to --definition)]:label:' \
|
|
||||||
'--holder[Holder account ID]:holder_account:_wallet_account_ids' \
|
'--holder[Holder account ID]:holder_account:_wallet_account_ids' \
|
||||||
'--holder-label[Holder account label (alternative to --holder)]:label:' \
|
|
||||||
'--amount[Amount of tokens to burn]:amount:'
|
'--amount[Amount of tokens to burn]:amount:'
|
||||||
;;
|
;;
|
||||||
mint)
|
mint)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
|
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
|
||||||
'--definition-label[Definition account label (alternative to --definition)]:label:' \
|
|
||||||
'--holder[Holder account ID (for owned accounts)]:holder_account:_wallet_account_ids' \
|
'--holder[Holder account ID (for owned accounts)]:holder_account:_wallet_account_ids' \
|
||||||
'--holder-label[Holder account label (alternative to --holder)]:label:' \
|
|
||||||
'--holder-npk[Holder nullifier public key (for foreign private accounts)]:npk:' \
|
'--holder-npk[Holder nullifier public key (for foreign private accounts)]:npk:' \
|
||||||
'--holder-vpk[Holder viewing public key (for foreign private accounts)]:vpk:' \
|
'--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:'
|
'--amount[Amount of tokens to mint]:amount:'
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -310,7 +280,7 @@ _wallet_token() {
|
|||||||
# amm subcommand
|
# amm subcommand
|
||||||
_wallet_amm() {
|
_wallet_amm() {
|
||||||
local -a subcommands
|
local -a subcommands
|
||||||
|
|
||||||
_arguments -C \
|
_arguments -C \
|
||||||
'1: :->subcommand' \
|
'1: :->subcommand' \
|
||||||
'*:: :->args'
|
'*:: :->args'
|
||||||
@ -319,8 +289,7 @@ _wallet_amm() {
|
|||||||
subcommand)
|
subcommand)
|
||||||
subcommands=(
|
subcommands=(
|
||||||
'new:Create a new liquidity pool'
|
'new:Create a new liquidity pool'
|
||||||
'swap-exact-input:Swap specifying exact input amount'
|
'swap:Swap tokens using the AMM'
|
||||||
'swap-exact-output:Swap specifying exact output amount'
|
|
||||||
'add-liquidity:Add liquidity to an existing pool'
|
'add-liquidity:Add liquidity to an existing pool'
|
||||||
'remove-liquidity:Remove liquidity from a pool'
|
'remove-liquidity:Remove liquidity from a pool'
|
||||||
'help:Print this message or the help of the given subcommand(s)'
|
'help:Print this message or the help of the given subcommand(s)'
|
||||||
@ -332,40 +301,24 @@ _wallet_amm() {
|
|||||||
new)
|
new)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||||
'--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[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||||
'--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[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
|
||||||
'--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-a[Amount of token A to deposit]:balance_a:' \
|
||||||
'--balance-b[Amount of token B to deposit]:balance_b:'
|
'--balance-b[Amount of token B to deposit]:balance_b:'
|
||||||
;;
|
;;
|
||||||
swap-exact-input)
|
swap)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||||
'--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[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||||
'--user-holding-b-label[User holding B label (alternative to --user-holding-b)]:label:' \
|
|
||||||
'--amount-in[Amount of tokens to swap]:amount_in:' \
|
'--amount-in[Amount of tokens to swap]:amount_in:' \
|
||||||
'--min-amount-out[Minimum tokens expected in return]:min_amount_out:' \
|
'--min-amount-out[Minimum tokens expected in return]:min_amount_out:' \
|
||||||
'--token-definition[Definition ID of the token being provided]:token_def:'
|
'--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)
|
add-liquidity)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||||
'--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[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||||
'--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[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
|
||||||
'--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-a[Maximum amount of token A to deposit]:max_amount_a:' \
|
||||||
'--max-amount-b[Maximum amount of token B to deposit]:max_amount_b:' \
|
'--max-amount-b[Maximum amount of token B to deposit]:max_amount_b:' \
|
||||||
'--min-amount-lp[Minimum LP tokens to receive]:min_amount_lp:'
|
'--min-amount-lp[Minimum LP tokens to receive]:min_amount_lp:'
|
||||||
@ -373,11 +326,8 @@ _wallet_amm() {
|
|||||||
remove-liquidity)
|
remove-liquidity)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||||
'--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[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||||
'--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[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
|
||||||
'--user-holding-lp-label[User holding LP label (alternative to --user-holding-lp)]:label:' \
|
|
||||||
'--balance-lp[Amount of LP tokens to burn]:balance_lp:' \
|
'--balance-lp[Amount of LP tokens to burn]:balance_lp:' \
|
||||||
'--min-amount-a[Minimum token A to receive]:min_amount_a:' \
|
'--min-amount-a[Minimum token A to receive]:min_amount_a:' \
|
||||||
'--min-amount-b[Minimum token B to receive]:min_amount_b:'
|
'--min-amount-b[Minimum token B to receive]:min_amount_b:'
|
||||||
@ -387,67 +337,13 @@ _wallet_amm() {
|
|||||||
esac
|
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
|
# config subcommand
|
||||||
_wallet_config() {
|
_wallet_config() {
|
||||||
local -a subcommands
|
local -a subcommands
|
||||||
local -a config_keys
|
local -a config_keys
|
||||||
|
|
||||||
config_keys=(
|
config_keys=(
|
||||||
|
'all'
|
||||||
'override_rust_log'
|
'override_rust_log'
|
||||||
'sequencer_addr'
|
'sequencer_addr'
|
||||||
'seq_poll_timeout'
|
'seq_poll_timeout'
|
||||||
@ -474,12 +370,7 @@ _wallet_config() {
|
|||||||
;;
|
;;
|
||||||
args)
|
args)
|
||||||
case $line[1] in
|
case $line[1] in
|
||||||
get)
|
get|description)
|
||||||
_arguments \
|
|
||||||
'(-a --all)'{-a,--all}'[Print all config fields]' \
|
|
||||||
'::key:compadd -a config_keys'
|
|
||||||
;;
|
|
||||||
description)
|
|
||||||
compadd -a config_keys
|
compadd -a config_keys
|
||||||
;;
|
;;
|
||||||
set)
|
set)
|
||||||
@ -514,7 +405,6 @@ _wallet_help() {
|
|||||||
'pinata:Pinata program interaction subcommand'
|
'pinata:Pinata program interaction subcommand'
|
||||||
'token:Token program interaction subcommand'
|
'token:Token program interaction subcommand'
|
||||||
'amm:AMM 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'
|
'check-health:Check the wallet can connect to the node'
|
||||||
'config:Command to setup config, get and set config fields'
|
'config:Command to setup config, get and set config fields'
|
||||||
'restore-keys:Restoring keys from given password at given depth'
|
'restore-keys:Restoring keys from given password at given depth'
|
||||||
@ -529,7 +419,7 @@ _wallet_help() {
|
|||||||
_wallet_account_ids() {
|
_wallet_account_ids() {
|
||||||
local -a accounts
|
local -a accounts
|
||||||
local line
|
local line
|
||||||
|
|
||||||
# Try to get accounts from wallet account list command
|
# Try to get accounts from wallet account list command
|
||||||
# Filter to lines starting with /N (numbered accounts) and extract the account ID
|
# Filter to lines starting with /N (numbered accounts) and extract the account ID
|
||||||
if command -v wallet &>/dev/null; then
|
if command -v wallet &>/dev/null; then
|
||||||
@ -538,13 +428,13 @@ _wallet_account_ids() {
|
|||||||
[[ -n "$line" ]] && accounts+=("${line%,}")
|
[[ -n "$line" ]] && accounts+=("${line%,}")
|
||||||
done < <(wallet account list 2>/dev/null | grep '^/[0-9]' | awk '{print $2}')
|
done < <(wallet account list 2>/dev/null | grep '^/[0-9]' | awk '{print $2}')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Provide type prefixes as fallback if command fails or returns nothing
|
# Provide type prefixes as fallback if command fails or returns nothing
|
||||||
if (( ${#accounts} == 0 )); then
|
if (( ${#accounts} == 0 )); then
|
||||||
compadd -S '' -- 'Public/' 'Private/'
|
compadd -S '' -- 'Public/' 'Private/'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_multi_parts / accounts
|
_multi_parts / accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
configs/docker-all-in-one/indexer/indexer_config.json
Normal file
11
configs/docker-all-in-one/indexer/indexer_config.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"resubscribe_interval": "1s",
|
||||||
|
"bedrock_client_config": {
|
||||||
|
"addr": "http://logos-blockchain-node-0:18080",
|
||||||
|
"backoff": {
|
||||||
|
"start_delay": "100ms",
|
||||||
|
"max_retries": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channel_id": "0101010101010101010101010101010101010101010101010101010101010101"
|
||||||
|
}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"home": "./indexer/service",
|
|
||||||
"consensus_info_polling_interval": "1s",
|
|
||||||
"bedrock_config": {
|
|
||||||
"addr": "http://logos-blockchain-node-0:18080"
|
|
||||||
},
|
|
||||||
"channel_id": "0101010101010101010101010101010101010101010101010101010101010101"
|
|
||||||
}
|
|
||||||
103
configs/docker-all-in-one/sequencer/sequencer_config.json
Normal file
103
configs/docker-all-in-one/sequencer/sequencer_config.json
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"home": "/var/lib/sequencer_runner",
|
||||||
|
"override_rust_log": null,
|
||||||
|
"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",
|
||||||
|
"port": 3040,
|
||||||
|
"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": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
|
||||||
|
"balance": 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account_id": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
|
||||||
|
"balance": 20000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"initial_commitments": [
|
||||||
|
{
|
||||||
|
"npk": [13, 25, 40, 5, 198, 248, 210, 248, 237, 121, 124, 145, 186, 142, 253, 216, 236, 69, 193, 32, 166, 167, 49, 133, 172, 111, 159, 46, 84, 17, 157, 23],
|
||||||
|
"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,77 +0,0 @@
|
|||||||
{
|
|
||||||
"home": "/var/lib/sequencer_service",
|
|
||||||
"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",
|
|
||||||
"genesis": [
|
|
||||||
{
|
|
||||||
"supply_account": {
|
|
||||||
"account_id": "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV",
|
|
||||||
"balance": 10000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"supply_account": {
|
|
||||||
"account_id": "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo",
|
|
||||||
"balance": 20000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"supply_account": {
|
|
||||||
"account_id": "61EsoYN6gvTLkveh1YSTMG3yJkncpHy5EGmxhSK4ew29",
|
|
||||||
"balance": 10000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"supply_account": {
|
|
||||||
"account_id": "3m6HQmCgmAvsxZtxAHPqqEqoBG4335fCG8TzxigyW7rE",
|
|
||||||
"balance": 20000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -7,21 +7,21 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- RUST_LOG=error
|
- RUST_LOG=error
|
||||||
|
|
||||||
sequencer_service:
|
sequencer_runner:
|
||||||
depends_on:
|
depends_on:
|
||||||
- logos-blockchain-node-0
|
- logos-blockchain-node-0
|
||||||
- indexer_service
|
- indexer_service
|
||||||
volumes:
|
volumes: !override
|
||||||
- ./configs/docker-all-in-one/sequencer_config.json:/etc/sequencer_service/sequencer_config.json
|
- ./configs/docker-all-in-one/sequencer:/etc/sequencer_runner
|
||||||
|
|
||||||
indexer_service:
|
indexer_service:
|
||||||
depends_on:
|
depends_on:
|
||||||
- logos-blockchain-node-0
|
- logos-blockchain-node-0
|
||||||
volumes:
|
volumes:
|
||||||
- ./configs/docker-all-in-one/indexer_config.json:/etc/indexer_service/indexer_config.json
|
- ./configs/docker-all-in-one/indexer/indexer_config.json:/etc/indexer_service/indexer_config.json
|
||||||
|
|
||||||
explorer_service:
|
explorer_service:
|
||||||
depends_on:
|
depends_on:
|
||||||
- indexer_service
|
- indexer_service
|
||||||
environment:
|
environment:
|
||||||
- INDEXER_RPC_URL=http://indexer_service:8779
|
- INDEXER_RPC_URL=http://indexer_service:8779
|
||||||
@ -6,7 +6,7 @@ include:
|
|||||||
- path:
|
- path:
|
||||||
bedrock/docker-compose.yml
|
bedrock/docker-compose.yml
|
||||||
- path:
|
- path:
|
||||||
sequencer/service/docker-compose.yml
|
sequencer_runner/docker-compose.yml
|
||||||
- path:
|
- path:
|
||||||
indexer/service/docker-compose.yml
|
indexer/service/docker-compose.yml
|
||||||
- path:
|
- path:
|
||||||
|
|||||||
@ -1,369 +0,0 @@
|
|||||||
# Associated Token Accounts (ATAs)
|
|
||||||
|
|
||||||
This tutorial covers Associated Token Accounts (ATAs). An ATA lets you derive a unique token holding address from an owner account and a token definition — no need to create and track holding accounts manually. Given the same inputs, anyone can compute the same ATA address without a network call. By the end, you will have practiced:
|
|
||||||
|
|
||||||
1. Deriving ATA addresses locally.
|
|
||||||
2. Creating an ATA.
|
|
||||||
3. Sending tokens via ATAs.
|
|
||||||
4. Burning tokens from an ATA.
|
|
||||||
5. Listing ATAs across multiple token definitions.
|
|
||||||
6. Creating an ATA with a private owner.
|
|
||||||
7. Sending tokens from a private owner's ATA.
|
|
||||||
8. Burning tokens from a private owner's ATA.
|
|
||||||
|
|
||||||
> [!Important]
|
|
||||||
> This tutorial assumes you have completed the [wallet-setup](wallet-setup.md) and [custom-tokens](custom-tokens.md) tutorials. You need a running wallet with accounts and at least one token definition.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
### Deploy the ATA program
|
|
||||||
|
|
||||||
Unlike the Token program (which is built-in), the ATA program must be deployed before you can use it. The pre-built binary is included in the repository:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet deploy-program artifacts/program_methods/associated_token_account.bin
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!Note]
|
|
||||||
> Program deployment is idempotent — if the ATA program has already been deployed (e.g. by another user on the same network), the command is a no-op.
|
|
||||||
|
|
||||||
You can verify the deployment succeeded by running any `wallet ata` command. If the program is not deployed, commands that submit transactions will fail.
|
|
||||||
|
|
||||||
The CLI provides commands to work with the ATA program. Run `wallet ata` to see the options:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Commands:
|
|
||||||
address Derive and print the Associated Token Account address (local only, no network)
|
|
||||||
create Create (or idempotently no-op) the Associated Token Account
|
|
||||||
send Send tokens from owner's ATA to a recipient
|
|
||||||
burn Burn tokens from holder's 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)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1. How ATA addresses work
|
|
||||||
|
|
||||||
An ATA address is deterministically derived from two inputs:
|
|
||||||
|
|
||||||
1. The **owner** account ID.
|
|
||||||
2. The **token definition** account ID.
|
|
||||||
|
|
||||||
The derivation works as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
seed = SHA256(owner_id || definition_id)
|
|
||||||
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.
|
|
||||||
|
|
||||||
> [!Note]
|
|
||||||
> All ATA commands that submit transactions accept a privacy prefix on the owner/holder argument — `Public/` for public accounts and `Private/` for private accounts. Using `Private/` generates a zero-knowledge proof locally and submits only the proof to the sequencer, keeping the owner's identity off-chain.
|
|
||||||
|
|
||||||
## 2. Deriving an ATA address (`wallet ata address`)
|
|
||||||
|
|
||||||
The `address` subcommand computes the ATA address locally without submitting a transaction.
|
|
||||||
|
|
||||||
### a. Set up an owner and token definition
|
|
||||||
|
|
||||||
If you already have a public account and a token definition from the custom-tokens tutorial, you can reuse them. Otherwise, create them now:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account new public
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Generated new account with account_id Public/5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account new public
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Generated new account with account_id Public/3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet token new \
|
|
||||||
--name MYTOKEN \
|
|
||||||
--total-supply 10000 \
|
|
||||||
--definition-account-id Public/3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4 \
|
|
||||||
--supply-account-id Public/5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB
|
|
||||||
```
|
|
||||||
|
|
||||||
### b. Derive the ATA address
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata address \
|
|
||||||
--owner 5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB \
|
|
||||||
--token-definition 3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
7a2Bf9cKLm3XpRtH1wDqZs8vYjN4eU6gAoFxW5kMnE2R
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!Note]
|
|
||||||
> This is a pure computation — no transaction is submitted and no network connection is needed. The same inputs will always produce the same output.
|
|
||||||
|
|
||||||
## 3. Creating an ATA (`wallet ata create`)
|
|
||||||
|
|
||||||
Before an ATA can hold tokens it must be created on-chain. The `create` subcommand submits a transaction that initializes the ATA. If it already exists, the operation is a no-op.
|
|
||||||
|
|
||||||
### a. Create the ATA
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata create \
|
|
||||||
--owner Public/5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB \
|
|
||||||
--token-definition 3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4
|
|
||||||
```
|
|
||||||
|
|
||||||
### b. Inspect the ATA
|
|
||||||
|
|
||||||
Use the ATA address derived in the previous section:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account get --account-id Public/7a2Bf9cKLm3XpRtH1wDqZs8vYjN4eU6gAoFxW5kMnE2R
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Holding account owned by ata program
|
|
||||||
{"account_type":"Token holding","definition_id":"3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4","balance":0}
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!Tip]
|
|
||||||
> Creation is idempotent — running the same command again is a no-op.
|
|
||||||
|
|
||||||
## 4. Sending tokens via ATA (`wallet ata send`)
|
|
||||||
|
|
||||||
The `send` subcommand transfers tokens from the owner's ATA to a recipient account.
|
|
||||||
|
|
||||||
### a. Fund the ATA
|
|
||||||
|
|
||||||
First, move tokens into the ATA from the supply account created earlier:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet token send \
|
|
||||||
--from Public/5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB \
|
|
||||||
--to Public/7a2Bf9cKLm3XpRtH1wDqZs8vYjN4eU6gAoFxW5kMnE2R \
|
|
||||||
--amount 5000
|
|
||||||
```
|
|
||||||
|
|
||||||
### b. Create a recipient account
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account new public
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Generated new account with account_id Public/9Ht4Kv8pYmW2rXjN6dFcQsA7bEoLf3gUZx1wDnR5eTi
|
|
||||||
```
|
|
||||||
|
|
||||||
### c. Send tokens from the ATA to the recipient
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata send \
|
|
||||||
--from Public/5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB \
|
|
||||||
--token-definition 3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4 \
|
|
||||||
--to 9Ht4Kv8pYmW2rXjN6dFcQsA7bEoLf3gUZx1wDnR5eTi \
|
|
||||||
--amount 2000
|
|
||||||
```
|
|
||||||
|
|
||||||
### d. Verify balances
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account get --account-id Public/7a2Bf9cKLm3XpRtH1wDqZs8vYjN4eU6gAoFxW5kMnE2R
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Holding account owned by ata program
|
|
||||||
{"account_type":"Token holding","definition_id":"3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4","balance":3000}
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account get --account-id Public/9Ht4Kv8pYmW2rXjN6dFcQsA7bEoLf3gUZx1wDnR5eTi
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Holding account owned by token program
|
|
||||||
{"account_type":"Token holding","definition_id":"3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4","balance":2000}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Burning tokens from an ATA (`wallet ata burn`)
|
|
||||||
|
|
||||||
The `burn` subcommand destroys tokens held in the owner's ATA, reducing the token's total supply.
|
|
||||||
|
|
||||||
### a. Burn tokens
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata burn \
|
|
||||||
--holder Public/5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB \
|
|
||||||
--token-definition 3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4 \
|
|
||||||
--amount 500
|
|
||||||
```
|
|
||||||
|
|
||||||
### b. Verify the reduced balance
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account get --account-id Public/7a2Bf9cKLm3XpRtH1wDqZs8vYjN4eU6gAoFxW5kMnE2R
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Holding account owned by ata program
|
|
||||||
{"account_type":"Token holding","definition_id":"3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4","balance":2500}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. Listing ATAs (`wallet ata list`)
|
|
||||||
|
|
||||||
The `list` subcommand queries ATAs for a given owner across one or more token definitions.
|
|
||||||
|
|
||||||
### a. Create a second token and ATA
|
|
||||||
|
|
||||||
Create a second token definition so there are multiple ATAs to list:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account new public
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Generated new account with account_id Public/BxR3Lm7YkWp9vNs2hD4qJcTfA8eUoZ6gKn1wXjM5rFi
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account new public
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Generated new account with account_id Public/Ck8mVp4YhWn2rXjD6dFsQtA7bEoLf3gUZx1wDnR9eTs
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet token new \
|
|
||||||
--name OTHERTOKEN \
|
|
||||||
--total-supply 5000 \
|
|
||||||
--definition-account-id Public/BxR3Lm7YkWp9vNs2hD4qJcTfA8eUoZ6gKn1wXjM5rFi \
|
|
||||||
--supply-account-id Public/Ck8mVp4YhWn2rXjD6dFsQtA7bEoLf3gUZx1wDnR9eTs
|
|
||||||
```
|
|
||||||
|
|
||||||
Create an ATA for the second token:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata create \
|
|
||||||
--owner Public/5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB \
|
|
||||||
--token-definition BxR3Lm7YkWp9vNs2hD4qJcTfA8eUoZ6gKn1wXjM5rFi
|
|
||||||
```
|
|
||||||
|
|
||||||
### b. List ATAs for both token definitions
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata list \
|
|
||||||
--owner 5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB \
|
|
||||||
--token-definition \
|
|
||||||
3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4 \
|
|
||||||
BxR3Lm7YkWp9vNs2hD4qJcTfA8eUoZ6gKn1wXjM5rFi
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
ATA 7a2Bf9cKLm3XpRtH1wDqZs8vYjN4eU6gAoFxW5kMnE2R (definition 3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4): balance 2500
|
|
||||||
ATA 4nPxKd8YmW7rVsH2jDfQcA9bEoLf6gUZx3wTnR1eMs5 (definition BxR3Lm7YkWp9vNs2hD4qJcTfA8eUoZ6gKn1wXjM5rFi): balance 0
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!Note]
|
|
||||||
> The `list` command derives each ATA address locally and fetches its on-chain state. If an ATA has not been created for a given definition, it prints "No ATA for definition ..." instead.
|
|
||||||
|
|
||||||
## 7. Private owner operations
|
|
||||||
|
|
||||||
All three ATA operations — `create`, `send`, and `burn` — support private owner accounts. Passing a `Private/` prefix on the owner argument switches the wallet into privacy-preserving mode:
|
|
||||||
|
|
||||||
1. The wallet builds the transaction locally.
|
|
||||||
2. The ATA program is executed inside the RISC0 ZK VM to generate a proof.
|
|
||||||
3. The proof, the updated ATA state (in plaintext), and an encrypted update for the owner's private account are submitted to the sequencer.
|
|
||||||
4. The sequencer verifies the proof, writes the ATA state change to the public chain, and records the owner's new commitment in the nullifier set.
|
|
||||||
|
|
||||||
The result is that the ATA account and its token balance are **fully public** — anyone can see them. What stays private is the link between the ATA and its owner: the proof demonstrates that someone with the correct private key authorized the operation, but reveals nothing about which account that was.
|
|
||||||
|
|
||||||
> [!Note]
|
|
||||||
> The ATA address is derived from `SHA256(owner_id || definition_id)`. Because SHA256 is one-way, the ATA address does not reveal the owner's identity. However, if the owner's account ID becomes known for any other reason, all of their ATAs across every token definition can be enumerated by anyone.
|
|
||||||
|
|
||||||
### a. Create a private account
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account new private
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Generated new account with account_id Private/HkR7Lm2YnWp4vNs8hD3qJcTfA6eUoZ9gKn5wXjM1rFi
|
|
||||||
```
|
|
||||||
|
|
||||||
### b. Create the ATA for the private owner
|
|
||||||
|
|
||||||
Pass `Private/` on `--owner`. The token definition account has no privacy prefix — it is always a public account.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata create \
|
|
||||||
--owner Private/HkR7Lm2YnWp4vNs8hD3qJcTfA6eUoZ9gKn5wXjM1rFi \
|
|
||||||
--token-definition 3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!Note]
|
|
||||||
> Proof generation runs locally in the RISC0 ZK VM and can take up to a minute on first run.
|
|
||||||
|
|
||||||
### c. Verify the ATA was created
|
|
||||||
|
|
||||||
Derive the ATA address using the raw account ID (no privacy prefix):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata address \
|
|
||||||
--owner HkR7Lm2YnWp4vNs8hD3qJcTfA6eUoZ9gKn5wXjM1rFi \
|
|
||||||
--token-definition 3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
2pQxNf7YkWm3rVsH8jDcQaA4bEoLf9gUZx6wTnR2eMs1
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account get --account-id Public/2pQxNf7YkWm3rVsH8jDcQaA4bEoLf9gUZx6wTnR2eMs1
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Holding account owned by ata program
|
|
||||||
{"account_type":"Token holding","definition_id":"3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4","balance":0}
|
|
||||||
```
|
|
||||||
|
|
||||||
### d. Fund the ATA
|
|
||||||
|
|
||||||
The ATA is a public account. Fund it with a direct token transfer from any public holding account:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet token send \
|
|
||||||
--from Public/5FkBei8HYoSUNqh9rWCrJDnSZE5FJfGiWmTvhgBx3qTB \
|
|
||||||
--to Public/2pQxNf7YkWm3rVsH8jDcQaA4bEoLf9gUZx6wTnR2eMs1 \
|
|
||||||
--amount 500
|
|
||||||
```
|
|
||||||
|
|
||||||
### e. Send tokens from the private owner's ATA
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata send \
|
|
||||||
--from Private/HkR7Lm2YnWp4vNs8hD3qJcTfA6eUoZ9gKn5wXjM1rFi \
|
|
||||||
--token-definition 3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4 \
|
|
||||||
--to 9Ht4Kv8pYmW2rXjN6dFcQsA7bEoLf3gUZx1wDnR5eTi \
|
|
||||||
--amount 200
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify the ATA balance decreased:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account get --account-id Public/2pQxNf7YkWm3rVsH8jDcQaA4bEoLf9gUZx6wTnR2eMs1
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Holding account owned by ata program
|
|
||||||
{"account_type":"Token holding","definition_id":"3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4","balance":300}
|
|
||||||
```
|
|
||||||
|
|
||||||
### f. Burn tokens from the private owner's ATA
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet ata burn \
|
|
||||||
--holder Private/HkR7Lm2YnWp4vNs8hD3qJcTfA6eUoZ9gKn5wXjM1rFi \
|
|
||||||
--token-definition 3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4 \
|
|
||||||
--amount 100
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify the balance and token supply:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet account get --account-id Public/2pQxNf7YkWm3rVsH8jDcQaA4bEoLf9gUZx6wTnR2eMs1
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
Holding account owned by ata program
|
|
||||||
{"account_type":"Token holding","definition_id":"3YpK8RvVzWm6Q4h2nDAbxJfLmuRqkEkFP9C7UwTdGvE4","balance":200}
|
|
||||||
```
|
|
||||||
@ -1,237 +0,0 @@
|
|||||||
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 keycard_wallet/keycard_applets
|
|
||||||
cd 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 (`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 `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 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 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
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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 |
|
|
||||||
|
|
||||||
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.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Tests for Keycard commands are in `keycard_wallet/tests/keycard_tests.sh`. Run from the repo root with a Keycard connected:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash keycard_wallet/tests/keycard_tests.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## SigningGroups
|
|
||||||
|
|
||||||
`SigningGroups` (`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.
|
|
||||||
|
|
||||||
```
|
|
||||||
SigningGroups {
|
|
||||||
local: [(AccountId, PrivateKey)], // signed in pure Rust
|
|
||||||
keycard: [(AccountId, BIP32Path)], // signed via a single Python/Keycard session
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@ -5,7 +5,6 @@ This tutorial walks through native token transfers between public and private ac
|
|||||||
4. Private account creation.
|
4. Private account creation.
|
||||||
5. Native token transfer from a public account to a private account.
|
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.
|
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -143,7 +142,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.
|
> 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.
|
> 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.
|
> 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 and a numeric identifier: `SHA256(prefix || npk || identifier)`. The same `npk` paired with different identifiers yields different, independent account IDs.
|
> The private account ID is derived from the nullifier public key.
|
||||||
> Private accounts can be initialized by anyone, but once initialized they can only be modified by the owner’s keys.
|
> 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.
|
> Updates include a new commitment and a nullifier for the old state, which prevents linkage between versions.
|
||||||
|
|
||||||
@ -159,9 +158,7 @@ With vpk 02ddc96d0eb56e00ce14994cfdaec5ae1f76244180a919545983156e3519940a17
|
|||||||
```
|
```
|
||||||
|
|
||||||
> [!Tip]
|
> [!Tip]
|
||||||
> Save this account ID. You will use it in later commands.
|
> 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`.
|
||||||
|
|
||||||
### b. Check the account status
|
|
||||||
|
|
||||||
Just like public accounts, new private accounts start out uninitialized:
|
Just like public accounts, new private accounts start out uninitialized:
|
||||||
|
|
||||||
@ -221,23 +218,21 @@ Account owned by authenticated-transfer program
|
|||||||
## 6. Native token transfer from a public account to a private account owned by someone else
|
## 6. Native token transfer from a public account to a private account owned by someone else
|
||||||
|
|
||||||
> [!Important]
|
> [!Important]
|
||||||
> 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.
|
> 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.
|
||||||
|
|
||||||
### a. Create a new private accounts key to simulate a foreign recipient
|
### a. Create a new uninitialized private account
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wallet account new private-accounts-key
|
wallet account new private
|
||||||
|
|
||||||
# Output:
|
# Output:
|
||||||
Generated new private accounts key at path /1
|
Generated new account with account_id Private/AukXPRBmrYVqoqEW2HTs7N3hvTn3qdNFDcxDHVr5hMm5
|
||||||
With npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e
|
With npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e
|
||||||
With vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72
|
With vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!Tip]
|
> [!Tip]
|
||||||
> Ignore the account ID here and use the `npk` and `vpk` values to send to a foreign private account.
|
> Ignore the private account ID here and use the `npk` and `vpk` values to send to a foreign private account.
|
||||||
|
|
||||||
### b. Send 3 tokens using the recipient’s npk and vpk
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wallet auth-transfer send \
|
wallet auth-transfer send \
|
||||||
@ -247,74 +242,9 @@ wallet auth-transfer send \
|
|||||||
--amount 3
|
--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]
|
> [!Warning]
|
||||||
> This command creates a privacy-preserving transaction, which may take a few minutes. The updated values are encrypted and included in the transaction.
|
> 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.
|
> Once accepted, the recipient must run `wallet account sync-private` to scan the chain for their encrypted updates and refresh local state.
|
||||||
|
|
||||||
> [!Note]
|
> [!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.
|
> 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 03b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6071819202122232425262728292a2b2c
|
|
||||||
```
|
|
||||||
|
|
||||||
Alice shares the `npk` and `vpk` values with Bob and Charlie out of band.
|
|
||||||
|
|
||||||
### b. Bob sends 10 tokens to Alice using identifier 1
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet auth-transfer send \
|
|
||||||
--from Public/BobXqJprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPA \
|
|
||||||
--to-npk a3f7c21b8e905d4f6a1bc783d0e2f94c1d5a6b7e8f9012345678abcdef012345 \
|
|
||||||
--to-vpk 03b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6071819202122232425262728292a2b2c \
|
|
||||||
--to-identifier 1 \
|
|
||||||
--amount 10
|
|
||||||
```
|
|
||||||
|
|
||||||
### c. Charlie sends 5 tokens to Alice using identifier 2
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wallet auth-transfer send \
|
|
||||||
--from Public/CharlieYrP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPB \
|
|
||||||
--to-npk a3f7c21b8e905d4f6a1bc783d0e2f94c1d5a6b7e8f9012345678abcdef012345 \
|
|
||||||
--to-vpk 03b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6071819202122232425262728292a2b2c \
|
|
||||||
--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.
|
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -4,14 +4,9 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common.workspace = true
|
|
||||||
nssa.workspace = true
|
nssa.workspace = true
|
||||||
nssa_core.workspace = true
|
nssa_core.workspace = true
|
||||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
|
||||||
wallet.workspace = true
|
wallet.workspace = true
|
||||||
|
|
||||||
tokio = { workspace = true, features = ["macros"] }
|
tokio = { workspace = true, features = ["macros"] }
|
||||||
|
|||||||
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