mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-14 12:09:35 +00:00
Merge branch 'main' into marvin/issue-258
This commit is contained in:
commit
bcdb2811e6
44
.github/workflows/ci.yml
vendored
44
.github/workflows/ci.yml
vendored
@ -156,33 +156,35 @@ jobs:
|
|||||||
RUST_LOG: "info"
|
RUST_LOG: "info"
|
||||||
run: cargo nextest run -p integration_tests -- --skip tps_test --skip indexer
|
run: cargo nextest run -p integration_tests -- --skip tps_test --skip indexer
|
||||||
|
|
||||||
integration-tests-indexer:
|
# # TODO: Bring this back once we find the source of the errors.
|
||||||
runs-on: ubuntu-latest
|
# #
|
||||||
timeout-minutes: 60
|
# integration-tests-indexer:
|
||||||
steps:
|
# runs-on: ubuntu-latest
|
||||||
- uses: actions/checkout@v5
|
# timeout-minutes: 60
|
||||||
with:
|
# steps:
|
||||||
ref: ${{ github.head_ref }}
|
# - uses: actions/checkout@v5
|
||||||
|
# with:
|
||||||
|
# ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
- uses: ./.github/actions/install-system-deps
|
# - uses: ./.github/actions/install-system-deps
|
||||||
|
|
||||||
- uses: ./.github/actions/install-risc0
|
# - uses: ./.github/actions/install-risc0
|
||||||
|
|
||||||
- uses: ./.github/actions/install-logos-blockchain-circuits
|
# - uses: ./.github/actions/install-logos-blockchain-circuits
|
||||||
with:
|
# with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Install active toolchain
|
# - name: Install active toolchain
|
||||||
run: rustup install
|
# run: rustup install
|
||||||
|
|
||||||
- name: Install nextest
|
# - name: Install nextest
|
||||||
run: cargo install --locked cargo-nextest
|
# run: cargo install --locked cargo-nextest
|
||||||
|
|
||||||
- name: Run tests
|
# - name: Run tests
|
||||||
env:
|
# env:
|
||||||
RISC0_DEV_MODE: "1"
|
# RISC0_DEV_MODE: "1"
|
||||||
RUST_LOG: "info"
|
# RUST_LOG: "info"
|
||||||
run: cargo nextest run -p integration_tests indexer -- --skip tps_test
|
# 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
|
||||||
|
|||||||
135
Cargo.lock
generated
135
Cargo.lock
generated
@ -629,9 +629,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "astral-tokio-tar"
|
name = "astral-tokio-tar"
|
||||||
version = "0.5.6"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec179a06c1769b1e42e1e2cbe74c7dcdb3d6383c838454d063eaac5bbb7ebbe5"
|
checksum = "3c23f3af104b40a3430ccb90ed5f7bd877a8dc5c26fc92fde51a22b40890dcf9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"filetime",
|
"filetime",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@ -727,6 +727,24 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ata_core"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"nssa_core",
|
||||||
|
"risc0-zkvm",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ata_program"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ata_core",
|
||||||
|
"nssa_core",
|
||||||
|
"token_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic-polyfill"
|
name = "atomic-polyfill"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@ -1504,7 +1522,6 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"logos-blockchain-common-http-client",
|
"logos-blockchain-common-http-client",
|
||||||
"nssa",
|
"nssa",
|
||||||
"nssa_core",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"sha2",
|
"sha2",
|
||||||
@ -3442,6 +3459,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"storage",
|
"storage",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"testnet_initial_state",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@ -3551,6 +3569,7 @@ name = "integration_tests"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"ata_core",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
"common",
|
"common",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -3568,6 +3587,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"testcontainers",
|
"testcontainers",
|
||||||
|
"testnet_initial_state",
|
||||||
"token_core",
|
"token_core",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
@ -5882,6 +5902,8 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"amm_core",
|
"amm_core",
|
||||||
"amm_program",
|
"amm_program",
|
||||||
|
"ata_core",
|
||||||
|
"ata_program",
|
||||||
"nssa_core",
|
"nssa_core",
|
||||||
"risc0-zkvm",
|
"risc0-zkvm",
|
||||||
"serde",
|
"serde",
|
||||||
@ -5930,7 +5952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.14.0",
|
"itertools 0.11.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
@ -5943,7 +5965,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
|
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.14.0",
|
"itertools 0.11.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
@ -6024,7 +6046,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6919,7 +6941,7 @@ dependencies = [
|
|||||||
"security-framework",
|
"security-framework",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"webpki-root-certs 0.26.11",
|
"webpki-root-certs 0.26.11",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6930,9 +6952,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.9"
|
version = "0.103.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
|
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
@ -7137,7 +7159,6 @@ name = "sequencer_core"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base58",
|
|
||||||
"bedrock_client",
|
"bedrock_client",
|
||||||
"borsh",
|
"borsh",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
@ -7157,6 +7178,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"storage",
|
"storage",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"testnet_initial_state",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@ -7842,9 +7864,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "testcontainers"
|
name = "testcontainers"
|
||||||
version = "0.27.1"
|
version = "0.27.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1c0624faaa317c56d6d19136580be889677259caf5c897941c6f446b4655068"
|
checksum = "0bd36b06a2a6c0c3c81a83be1ab05fe86460d054d4d51bf513bc56b3e15bdc22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"astral-tokio-tar",
|
"astral-tokio-tar",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -7873,6 +7895,17 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "testnet_initial_state"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"common",
|
||||||
|
"key_protocol",
|
||||||
|
"nssa",
|
||||||
|
"nssa_core",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@ -8649,6 +8682,7 @@ dependencies = [
|
|||||||
"amm_core",
|
"amm_core",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
|
"ata_core",
|
||||||
"base58",
|
"base58",
|
||||||
"clap",
|
"clap",
|
||||||
"common",
|
"common",
|
||||||
@ -8669,6 +8703,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"testnet_initial_state",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"token_core",
|
"token_core",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -9047,15 +9082,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.60.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.53.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@ -9089,30 +9115,13 @@ dependencies = [
|
|||||||
"windows_aarch64_gnullvm 0.52.6",
|
"windows_aarch64_gnullvm 0.52.6",
|
||||||
"windows_aarch64_msvc 0.52.6",
|
"windows_aarch64_msvc 0.52.6",
|
||||||
"windows_i686_gnu 0.52.6",
|
"windows_i686_gnu 0.52.6",
|
||||||
"windows_i686_gnullvm 0.52.6",
|
"windows_i686_gnullvm",
|
||||||
"windows_i686_msvc 0.52.6",
|
"windows_i686_msvc 0.52.6",
|
||||||
"windows_x86_64_gnu 0.52.6",
|
"windows_x86_64_gnu 0.52.6",
|
||||||
"windows_x86_64_gnullvm 0.52.6",
|
"windows_x86_64_gnullvm 0.52.6",
|
||||||
"windows_x86_64_msvc 0.52.6",
|
"windows_x86_64_msvc 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.53.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
"windows_aarch64_gnullvm 0.53.1",
|
|
||||||
"windows_aarch64_msvc 0.53.1",
|
|
||||||
"windows_i686_gnu 0.53.1",
|
|
||||||
"windows_i686_gnullvm 0.53.1",
|
|
||||||
"windows_i686_msvc 0.53.1",
|
|
||||||
"windows_x86_64_gnu 0.53.1",
|
|
||||||
"windows_x86_64_gnullvm 0.53.1",
|
|
||||||
"windows_x86_64_msvc 0.53.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@ -9125,12 +9134,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@ -9143,12 +9146,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@ -9161,24 +9158,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnullvm"
|
name = "windows_i686_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@ -9191,12 +9176,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@ -9209,12 +9188,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@ -9227,12 +9200,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@ -9245,12 +9212,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.15"
|
version = "0.7.15"
|
||||||
|
|||||||
@ -17,6 +17,8 @@ members = [
|
|||||||
"programs/amm",
|
"programs/amm",
|
||||||
"programs/token/core",
|
"programs/token/core",
|
||||||
"programs/token",
|
"programs/token",
|
||||||
|
"programs/associated_token_account/core",
|
||||||
|
"programs/associated_token_account",
|
||||||
"sequencer/core",
|
"sequencer/core",
|
||||||
"sequencer/service",
|
"sequencer/service",
|
||||||
"sequencer/service/protocol",
|
"sequencer/service/protocol",
|
||||||
@ -34,6 +36,7 @@ members = [
|
|||||||
"examples/program_deployment/methods",
|
"examples/program_deployment/methods",
|
||||||
"examples/program_deployment/methods/guest",
|
"examples/program_deployment/methods/guest",
|
||||||
"bedrock_client",
|
"bedrock_client",
|
||||||
|
"testnet_initial_state",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
@ -57,8 +60,11 @@ 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" }
|
||||||
test_program_methods = { path = "test_program_methods" }
|
test_program_methods = { path = "test_program_methods" }
|
||||||
bedrock_client = { path = "bedrock_client" }
|
bedrock_client = { path = "bedrock_client" }
|
||||||
|
testnet_initial_state = { path = "testnet_initial_state" }
|
||||||
|
|
||||||
tokio = { version = "1.50", features = [
|
tokio = { version = "1.50", features = [
|
||||||
"net",
|
"net",
|
||||||
|
|||||||
Binary file not shown.
BIN
artifacts/program_methods/associated_token_account.bin
Normal file
BIN
artifacts/program_methods/associated_token_account.bin
Normal file
Binary file not shown.
Binary file not shown.
BIN
artifacts/program_methods/clock.bin
Normal file
BIN
artifacts/program_methods/clock.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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.
BIN
artifacts/test_program_methods/validity_window.bin
Normal file
BIN
artifacts/test_program_methods/validity_window.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/validity_window_chain_caller.bin
Normal file
BIN
artifacts/test_program_methods/validity_window_chain_caller.bin
Normal file
Binary file not shown.
@ -46,7 +46,7 @@ impl BedrockClient {
|
|||||||
info!("Creating Bedrock client with node URL {node_url}");
|
info!("Creating Bedrock client with node URL {node_url}");
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
//Add more fields if needed
|
//Add more fields if needed
|
||||||
.timeout(std::time::Duration::from_secs(60))
|
.timeout(std::time::Duration::from_mins(1))
|
||||||
.build()
|
.build()
|
||||||
.context("Failed to build HTTP client")?;
|
.context("Failed to build HTTP client")?;
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nssa.workspace = true
|
nssa.workspace = true
|
||||||
nssa_core.workspace = true
|
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use nssa::AccountId;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest as _, Sha256, digest::FixedOutput as _};
|
use sha2::{Digest as _, Sha256, digest::FixedOutput as _};
|
||||||
|
|
||||||
@ -123,20 +122,6 @@ 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};
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use log::warn;
|
|||||||
use nssa::{AccountId, V03State};
|
use nssa::{AccountId, V03State};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::HashType;
|
use crate::{HashType, block::BlockId};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum NSSATransaction {
|
pub enum NSSATransaction {
|
||||||
@ -68,10 +68,13 @@ impl NSSATransaction {
|
|||||||
pub fn execute_check_on_state(
|
pub fn execute_check_on_state(
|
||||||
self,
|
self,
|
||||||
state: &mut V03State,
|
state: &mut V03State,
|
||||||
|
block_id: BlockId,
|
||||||
) -> Result<Self, nssa::error::NssaError> {
|
) -> Result<Self, nssa::error::NssaError> {
|
||||||
match &self {
|
match &self {
|
||||||
Self::Public(tx) => state.transition_from_public_transaction(tx),
|
Self::Public(tx) => state.transition_from_public_transaction(tx, block_id),
|
||||||
Self::PrivacyPreserving(tx) => state.transition_from_privacy_preserving_transaction(tx),
|
Self::PrivacyPreserving(tx) => {
|
||||||
|
state.transition_from_privacy_preserving_transaction(tx, block_id)
|
||||||
|
}
|
||||||
Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx),
|
Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx),
|
||||||
}
|
}
|
||||||
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
||||||
|
|||||||
369
docs/LEZ testnet v0.1 tutorials/associated-token-accounts.md
Normal file
369
docs/LEZ testnet v0.1 tutorials/associated-token-accounts.md
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
# 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::from((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,5 +1,5 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{
|
||||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hello-world example program.
|
// Hello-world example program.
|
||||||
@ -56,5 +56,7 @@ fn main() {
|
|||||||
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
||||||
// with the previous values of the accounts, and the transition to the post states conforms
|
// with the previous values of the accounts, and the transition to the post states conforms
|
||||||
// with the NSSA program rules.
|
// with the NSSA program rules.
|
||||||
write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]);
|
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
|
||||||
|
// called to commit the output.
|
||||||
|
ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state]).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{
|
||||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hello-world with authorization example program.
|
// Hello-world with authorization example program.
|
||||||
@ -63,5 +63,7 @@ fn main() {
|
|||||||
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
||||||
// with the previous values of the accounts, and the transition to the post states conforms
|
// with the previous values of the accounts, and the transition to the post states conforms
|
||||||
// with the NSSA program rules.
|
// with the NSSA program rules.
|
||||||
write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]);
|
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
|
||||||
|
// called to commit the output.
|
||||||
|
ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state]).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata, Data},
|
account::{Account, AccountWithMetadata, Data},
|
||||||
program::{
|
program::{
|
||||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -95,5 +95,7 @@ fn main() {
|
|||||||
_ => panic!("invalid params"),
|
_ => panic!("invalid params"),
|
||||||
};
|
};
|
||||||
|
|
||||||
write_nssa_outputs(instruction_words, pre_states, post_states);
|
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
|
||||||
|
// called to commit the output.
|
||||||
|
ProgramOutput::new(instruction_words, pre_states, post_states).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{
|
||||||
AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs,
|
AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
write_nssa_outputs_with_chained_call,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tail Call example program.
|
// Tail Call example program.
|
||||||
@ -53,11 +52,10 @@ fn main() {
|
|||||||
pda_seeds: vec![],
|
pda_seeds: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write the outputs
|
// Write the outputs.
|
||||||
write_nssa_outputs_with_chained_call(
|
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
|
||||||
instruction_data,
|
// called to commit the output.
|
||||||
vec![pre_state],
|
ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state])
|
||||||
vec![post_state],
|
.with_chained_calls(vec![chained_call])
|
||||||
vec![chained_call],
|
.write();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{
|
||||||
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs,
|
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput,
|
||||||
write_nssa_outputs_with_chained_call,
|
read_nssa_inputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tail Call with PDA example program.
|
// Tail Call with PDA example program.
|
||||||
@ -65,11 +65,10 @@ fn main() {
|
|||||||
pda_seeds: vec![PDA_SEED],
|
pda_seeds: vec![PDA_SEED],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write the outputs
|
// Write the outputs.
|
||||||
write_nssa_outputs_with_chained_call(
|
// WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be
|
||||||
instruction_data,
|
// called to commit the output.
|
||||||
vec![pre_state],
|
ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state])
|
||||||
vec![post_state],
|
.with_chained_calls(vec![chained_call])
|
||||||
vec![chained_call],
|
.write();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -177,6 +177,7 @@ pub fn TransactionPage() -> impl IntoView {
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments,
|
new_commitments,
|
||||||
new_nullifiers,
|
new_nullifiers,
|
||||||
|
validity_window
|
||||||
} = message;
|
} = message;
|
||||||
let WitnessSet {
|
let WitnessSet {
|
||||||
signatures_and_public_keys: _,
|
signatures_and_public_keys: _,
|
||||||
@ -212,6 +213,10 @@ pub fn TransactionPage() -> impl IntoView {
|
|||||||
<span class="info-label">"Proof Size:"</span>
|
<span class="info-label">"Proof Size:"</span>
|
||||||
<span class="info-value">{format!("{proof_len} bytes")}</span>
|
<span class="info-value">{format!("{proof_len} bytes")}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">"Validity Window:"</span>
|
||||||
|
<span class="info-value">{validity_window.to_string()}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>"Public Accounts"</h3>
|
<h3>"Public Accounts"</h3>
|
||||||
|
|||||||
@ -13,6 +13,7 @@ bedrock_client.workspace = true
|
|||||||
nssa.workspace = true
|
nssa.workspace = true
|
||||||
nssa_core.workspace = true
|
nssa_core.workspace = true
|
||||||
storage.workspace = true
|
storage.workspace = true
|
||||||
|
testnet_initial_state.workspace = true
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|||||||
@ -125,7 +125,7 @@ impl IndexerStore {
|
|||||||
transaction
|
transaction
|
||||||
.clone()
|
.clone()
|
||||||
.transaction_stateless_check()?
|
.transaction_stateless_check()?
|
||||||
.execute_check_on_state(&mut state_guard)?;
|
.execute_check_on_state(&mut state_guard, block.header.block_id)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,13 +7,11 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
pub use bedrock_client::BackoffConfig;
|
pub use bedrock_client::BackoffConfig;
|
||||||
use common::{
|
use common::config::BasicAuth;
|
||||||
block::{AccountInitialData, CommitmentsInitialData},
|
|
||||||
config::BasicAuth,
|
|
||||||
};
|
|
||||||
use humantime_serde;
|
use humantime_serde;
|
||||||
pub use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
pub use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use testnet_initial_state::{PrivateAccountPublicInitialData, PublicAccountPublicInitialData};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -29,16 +27,16 @@ pub struct ClientConfig {
|
|||||||
pub struct IndexerConfig {
|
pub struct IndexerConfig {
|
||||||
/// Home dir of sequencer storage.
|
/// Home dir of sequencer storage.
|
||||||
pub home: PathBuf,
|
pub home: PathBuf,
|
||||||
/// List of initial accounts data.
|
|
||||||
pub initial_accounts: Vec<AccountInitialData>,
|
|
||||||
/// List of initial commitments.
|
|
||||||
pub initial_commitments: Vec<CommitmentsInitialData>,
|
|
||||||
/// Sequencers signing key.
|
/// Sequencers signing key.
|
||||||
pub signing_key: [u8; 32],
|
pub signing_key: [u8; 32],
|
||||||
#[serde(with = "humantime_serde")]
|
#[serde(with = "humantime_serde")]
|
||||||
pub consensus_info_polling_interval: Duration,
|
pub consensus_info_polling_interval: Duration,
|
||||||
pub bedrock_client_config: ClientConfig,
|
pub bedrock_client_config: ClientConfig,
|
||||||
pub channel_id: ChannelId,
|
pub channel_id: ChannelId,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub initial_public_accounts: Option<Vec<PublicAccountPublicInitialData>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub initial_private_accounts: Option<Vec<PrivateAccountPublicInitialData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexerConfig {
|
impl IndexerConfig {
|
||||||
|
|||||||
@ -2,14 +2,17 @@ use std::collections::VecDeque;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bedrock_client::{BedrockClient, HeaderId};
|
use bedrock_client::{BedrockClient, HeaderId};
|
||||||
use common::block::{Block, HashableBlockData};
|
use common::{
|
||||||
// ToDo: Remove after testnet
|
HashType, PINATA_BASE58,
|
||||||
use common::{HashType, PINATA_BASE58};
|
block::{Block, HashableBlockData},
|
||||||
|
};
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use logos_blockchain_core::mantle::{
|
use logos_blockchain_core::mantle::{
|
||||||
Op, SignedMantleTx,
|
Op, SignedMantleTx,
|
||||||
ops::channel::{ChannelId, inscribe::InscriptionOp},
|
ops::channel::{ChannelId, inscribe::InscriptionOp},
|
||||||
};
|
};
|
||||||
|
use nssa::V03State;
|
||||||
|
use testnet_initial_state::initial_state_testnet;
|
||||||
|
|
||||||
use crate::{block_store::IndexerStore, config::IndexerConfig};
|
use crate::{block_store::IndexerStore, config::IndexerConfig};
|
||||||
|
|
||||||
@ -54,36 +57,50 @@ impl IndexerCore {
|
|||||||
let channel_genesis_msg_id = [0; 32];
|
let channel_genesis_msg_id = [0; 32];
|
||||||
let genesis_block = hashable_data.into_pending_block(&signing_key, channel_genesis_msg_id);
|
let genesis_block = hashable_data.into_pending_block(&signing_key, channel_genesis_msg_id);
|
||||||
|
|
||||||
// This is a troubling moment, because changes in key protocol can
|
let initial_commitments: Option<Vec<nssa_core::Commitment>> = config
|
||||||
// affect this. And indexer can not reliably ask this data from sequencer
|
.initial_private_accounts
|
||||||
// because indexer must be independent from it.
|
.as_ref()
|
||||||
// ToDo: move initial state generation into common and use the same method
|
.map(|initial_commitments| {
|
||||||
// for indexer and sequencer. This way both services buit at same version
|
initial_commitments
|
||||||
// could be in sync.
|
.iter()
|
||||||
let initial_commitments: Vec<nssa_core::Commitment> = config
|
.map(|init_comm_data| {
|
||||||
.initial_commitments
|
let npk = &init_comm_data.npk;
|
||||||
.iter()
|
|
||||||
.map(|init_comm_data| {
|
|
||||||
let npk = &init_comm_data.npk;
|
|
||||||
|
|
||||||
let mut acc = init_comm_data.account.clone();
|
let mut acc = init_comm_data.account.clone();
|
||||||
|
|
||||||
acc.program_owner = nssa::program::Program::authenticated_transfer_program().id();
|
acc.program_owner =
|
||||||
|
nssa::program::Program::authenticated_transfer_program().id();
|
||||||
|
|
||||||
nssa_core::Commitment::new(npk, &acc)
|
nssa_core::Commitment::new(npk, &acc)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
let init_accs: Vec<(nssa::AccountId, u128)> = config
|
let init_accs: Option<Vec<(nssa::AccountId, u128)>> = config
|
||||||
.initial_accounts
|
.initial_public_accounts
|
||||||
.iter()
|
.as_ref()
|
||||||
.map(|acc_data| (acc_data.account_id, acc_data.balance))
|
.map(|initial_accounts| {
|
||||||
.collect();
|
initial_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|acc_data| (acc_data.account_id, acc_data.balance))
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
let mut state = nssa::V03State::new_with_genesis_accounts(&init_accs, &initial_commitments);
|
// If initial commitments or accounts are present in config, need to construct state from
|
||||||
|
// them
|
||||||
|
let state = if initial_commitments.is_some() || init_accs.is_some() {
|
||||||
|
let mut state = V03State::new_with_genesis_accounts(
|
||||||
|
&init_accs.unwrap_or_default(),
|
||||||
|
&initial_commitments.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
// ToDo: Remove after testnet
|
// ToDo: Remove after testnet
|
||||||
state.add_pinata_program(PINATA_BASE58.parse().unwrap());
|
state.add_pinata_program(PINATA_BASE58.parse().unwrap());
|
||||||
|
|
||||||
|
state
|
||||||
|
} else {
|
||||||
|
initial_state_testnet()
|
||||||
|
};
|
||||||
|
|
||||||
let home = config.home.join("rocksdb");
|
let home = config.home.join("rocksdb");
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use crate::{
|
|||||||
CommitmentSetDigest, Data, EncryptedAccountData, EphemeralPublicKey, HashType, MantleMsgId,
|
CommitmentSetDigest, Data, EncryptedAccountData, EphemeralPublicKey, HashType, MantleMsgId,
|
||||||
Nullifier, PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage,
|
Nullifier, PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage,
|
||||||
ProgramDeploymentTransaction, ProgramId, Proof, PublicKey, PublicMessage, PublicTransaction,
|
ProgramDeploymentTransaction, ProgramId, Proof, PublicKey, PublicMessage, PublicTransaction,
|
||||||
Signature, Transaction, WitnessSet,
|
Signature, Transaction, ValidityWindow, WitnessSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@ -287,6 +287,7 @@ impl From<nssa::privacy_preserving_transaction::message::Message> for PrivacyPre
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments,
|
new_commitments,
|
||||||
new_nullifiers,
|
new_nullifiers,
|
||||||
|
validity_window,
|
||||||
} = value;
|
} = value;
|
||||||
Self {
|
Self {
|
||||||
public_account_ids: public_account_ids.into_iter().map(Into::into).collect(),
|
public_account_ids: public_account_ids.into_iter().map(Into::into).collect(),
|
||||||
@ -301,12 +302,13 @@ impl From<nssa::privacy_preserving_transaction::message::Message> for PrivacyPre
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(n, d)| (n.into(), d.into()))
|
.map(|(n, d)| (n.into(), d.into()))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
validity_window: validity_window.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<PrivacyPreservingMessage> for nssa::privacy_preserving_transaction::message::Message {
|
impl TryFrom<PrivacyPreservingMessage> for nssa::privacy_preserving_transaction::message::Message {
|
||||||
type Error = nssa_core::account::data::DataTooBigError;
|
type Error = nssa::error::NssaError;
|
||||||
|
|
||||||
fn try_from(value: PrivacyPreservingMessage) -> Result<Self, Self::Error> {
|
fn try_from(value: PrivacyPreservingMessage) -> Result<Self, Self::Error> {
|
||||||
let PrivacyPreservingMessage {
|
let PrivacyPreservingMessage {
|
||||||
@ -316,6 +318,7 @@ impl TryFrom<PrivacyPreservingMessage> for nssa::privacy_preserving_transaction:
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments,
|
new_commitments,
|
||||||
new_nullifiers,
|
new_nullifiers,
|
||||||
|
validity_window,
|
||||||
} = value;
|
} = value;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
public_account_ids: public_account_ids.into_iter().map(Into::into).collect(),
|
public_account_ids: public_account_ids.into_iter().map(Into::into).collect(),
|
||||||
@ -326,7 +329,8 @@ impl TryFrom<PrivacyPreservingMessage> for nssa::privacy_preserving_transaction:
|
|||||||
public_post_states: public_post_states
|
public_post_states: public_post_states
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(TryInto::try_into)
|
.map(TryInto::try_into)
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(|e| nssa::error::NssaError::InvalidInput(format!("{e}")))?,
|
||||||
encrypted_private_post_states: encrypted_private_post_states
|
encrypted_private_post_states: encrypted_private_post_states
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
@ -336,6 +340,9 @@ impl TryFrom<PrivacyPreservingMessage> for nssa::privacy_preserving_transaction:
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(n, d)| (n.into(), d.into()))
|
.map(|(n, d)| (n.into(), d.into()))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
validity_window: validity_window
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e| nssa::error::NssaError::InvalidInput(format!("{e}")))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -479,14 +486,7 @@ impl TryFrom<PrivacyPreservingTransaction> for nssa::PrivacyPreservingTransactio
|
|||||||
witness_set,
|
witness_set,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
Ok(Self::new(
|
Ok(Self::new(message.try_into()?, witness_set.try_into()?))
|
||||||
message
|
|
||||||
.try_into()
|
|
||||||
.map_err(|err: nssa_core::account::data::DataTooBigError| {
|
|
||||||
nssa::error::NssaError::InvalidInput(err.to_string())
|
|
||||||
})?,
|
|
||||||
witness_set.try_into()?,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -687,3 +687,21 @@ impl From<HashType> for common::HashType {
|
|||||||
Self(value.0)
|
Self(value.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ValidityWindow conversions
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
impl From<nssa_core::program::ValidityWindow> for ValidityWindow {
|
||||||
|
fn from(value: nssa_core::program::ValidityWindow) -> Self {
|
||||||
|
Self((value.start(), value.end()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ValidityWindow> for nssa_core::program::ValidityWindow {
|
||||||
|
type Error = nssa_core::program::InvalidWindow;
|
||||||
|
|
||||||
|
fn try_from(value: ValidityWindow) -> Result<Self, Self::Error> {
|
||||||
|
value.0.try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -235,6 +235,7 @@ pub struct PrivacyPreservingMessage {
|
|||||||
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
||||||
pub new_commitments: Vec<Commitment>,
|
pub new_commitments: Vec<Commitment>,
|
||||||
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
||||||
|
pub validity_window: ValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
||||||
@ -300,6 +301,20 @@ pub struct Nullifier(
|
|||||||
pub [u8; 32],
|
pub [u8; 32],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct ValidityWindow(pub (Option<BlockId>, Option<BlockId>));
|
||||||
|
|
||||||
|
impl Display for ValidityWindow {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self.0 {
|
||||||
|
(Some(start), Some(end)) => write!(f, "[{start}, {end})"),
|
||||||
|
(Some(start), None) => write!(f, "[{start}, \u{221e})"),
|
||||||
|
(None, Some(end)) => write!(f, "(-\u{221e}, {end})"),
|
||||||
|
(None, None) => write!(f, "(-\u{221e}, \u{221e})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct CommitmentSetDigest(
|
pub struct CommitmentSetDigest(
|
||||||
#[serde(with = "base64::arr")]
|
#[serde(with = "base64::arr")]
|
||||||
|
|||||||
@ -13,7 +13,7 @@ use indexer_service_protocol::{
|
|||||||
CommitmentSetDigest, Data, EncryptedAccountData, HashType, MantleMsgId,
|
CommitmentSetDigest, Data, EncryptedAccountData, HashType, MantleMsgId,
|
||||||
PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage,
|
PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage,
|
||||||
ProgramDeploymentTransaction, ProgramId, PublicMessage, PublicTransaction, Signature,
|
ProgramDeploymentTransaction, ProgramId, PublicMessage, PublicTransaction, Signature,
|
||||||
Transaction, WitnessSet,
|
Transaction, ValidityWindow, WitnessSet,
|
||||||
};
|
};
|
||||||
use jsonrpsee::{
|
use jsonrpsee::{
|
||||||
core::{SubscriptionResult, async_trait},
|
core::{SubscriptionResult, async_trait},
|
||||||
@ -124,6 +124,7 @@ impl MockIndexerService {
|
|||||||
indexer_service_protocol::Nullifier([tx_idx as u8; 32]),
|
indexer_service_protocol::Nullifier([tx_idx as u8; 32]),
|
||||||
CommitmentSetDigest([0xff; 32]),
|
CommitmentSetDigest([0xff; 32]),
|
||||||
)],
|
)],
|
||||||
|
validity_window: ValidityWindow((None, None)),
|
||||||
},
|
},
|
||||||
witness_set: WitnessSet {
|
witness_set: WitnessSet {
|
||||||
signatures_and_public_keys: vec![],
|
signatures_and_public_keys: vec![],
|
||||||
|
|||||||
@ -18,9 +18,11 @@ key_protocol.workspace = true
|
|||||||
indexer_service.workspace = true
|
indexer_service.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
token_core.workspace = true
|
token_core.workspace = true
|
||||||
|
ata_core.workspace = true
|
||||||
indexer_service_rpc.workspace = true
|
indexer_service_rpc.workspace = true
|
||||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||||
wallet-ffi.workspace = true
|
wallet-ffi.workspace = true
|
||||||
|
testnet_initial_state.workspace = true
|
||||||
|
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
|
|
||||||
|
|||||||
@ -2,16 +2,17 @@ use std::{net::SocketAddr, path::PathBuf, time::Duration};
|
|||||||
|
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
use common::block::{AccountInitialData, CommitmentsInitialData};
|
|
||||||
use indexer_service::{BackoffConfig, ChannelId, ClientConfig, IndexerConfig};
|
use indexer_service::{BackoffConfig, ChannelId, ClientConfig, IndexerConfig};
|
||||||
use key_protocol::key_management::KeyChain;
|
use key_protocol::key_management::KeyChain;
|
||||||
use nssa::{Account, AccountId, PrivateKey, PublicKey};
|
use nssa::{Account, AccountId, PrivateKey, PublicKey};
|
||||||
use nssa_core::{account::Data, program::DEFAULT_PROGRAM_ID};
|
use nssa_core::{account::Data, program::DEFAULT_PROGRAM_ID};
|
||||||
use sequencer_core::config::{BedrockConfig, SequencerConfig};
|
use sequencer_core::config::{BedrockConfig, SequencerConfig};
|
||||||
use url::Url;
|
use testnet_initial_state::{
|
||||||
use wallet::config::{
|
PrivateAccountPrivateInitialData, PrivateAccountPublicInitialData,
|
||||||
InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, WalletConfig,
|
PublicAccountPrivateInitialData, PublicAccountPublicInitialData,
|
||||||
};
|
};
|
||||||
|
use url::Url;
|
||||||
|
use wallet::config::{InitialAccountData, WalletConfig};
|
||||||
|
|
||||||
/// Sequencer config options available for custom changes in integration tests.
|
/// Sequencer config options available for custom changes in integration tests.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@ -102,13 +103,13 @@ impl InitialData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sequencer_initial_accounts(&self) -> Vec<AccountInitialData> {
|
fn sequencer_initial_public_accounts(&self) -> Vec<PublicAccountPublicInitialData> {
|
||||||
self.public_accounts
|
self.public_accounts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(priv_key, balance)| {
|
.map(|(priv_key, balance)| {
|
||||||
let pub_key = PublicKey::new_from_private_key(priv_key);
|
let pub_key = PublicKey::new_from_private_key(priv_key);
|
||||||
let account_id = AccountId::from(&pub_key);
|
let account_id = AccountId::from(&pub_key);
|
||||||
AccountInitialData {
|
PublicAccountPublicInitialData {
|
||||||
account_id,
|
account_id,
|
||||||
balance: *balance,
|
balance: *balance,
|
||||||
}
|
}
|
||||||
@ -116,10 +117,10 @@ impl InitialData {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sequencer_initial_commitments(&self) -> Vec<CommitmentsInitialData> {
|
fn sequencer_initial_private_accounts(&self) -> Vec<PrivateAccountPublicInitialData> {
|
||||||
self.private_accounts
|
self.private_accounts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key_chain, account)| CommitmentsInitialData {
|
.map(|(key_chain, account)| PrivateAccountPublicInitialData {
|
||||||
npk: key_chain.nullifier_public_key.clone(),
|
npk: key_chain.nullifier_public_key.clone(),
|
||||||
account: account.clone(),
|
account: account.clone(),
|
||||||
})
|
})
|
||||||
@ -132,14 +133,14 @@ impl InitialData {
|
|||||||
.map(|(priv_key, _)| {
|
.map(|(priv_key, _)| {
|
||||||
let pub_key = PublicKey::new_from_private_key(priv_key);
|
let pub_key = PublicKey::new_from_private_key(priv_key);
|
||||||
let account_id = AccountId::from(&pub_key);
|
let account_id = AccountId::from(&pub_key);
|
||||||
InitialAccountData::Public(InitialAccountDataPublic {
|
InitialAccountData::Public(PublicAccountPrivateInitialData {
|
||||||
account_id,
|
account_id,
|
||||||
pub_sign_key: priv_key.clone(),
|
pub_sign_key: priv_key.clone(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.chain(self.private_accounts.iter().map(|(key_chain, account)| {
|
.chain(self.private_accounts.iter().map(|(key_chain, account)| {
|
||||||
let account_id = AccountId::from(&key_chain.nullifier_public_key);
|
let account_id = AccountId::from(&key_chain.nullifier_public_key);
|
||||||
InitialAccountData::Private(Box::new(InitialAccountDataPrivate {
|
InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData {
|
||||||
account_id,
|
account_id,
|
||||||
account: account.clone(),
|
account: account.clone(),
|
||||||
key_chain: key_chain.clone(),
|
key_chain: key_chain.clone(),
|
||||||
@ -181,8 +182,8 @@ pub fn indexer_config(
|
|||||||
max_retries: 10,
|
max_retries: 10,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
initial_accounts: initial_data.sequencer_initial_accounts(),
|
initial_public_accounts: Some(initial_data.sequencer_initial_public_accounts()),
|
||||||
initial_commitments: initial_data.sequencer_initial_commitments(),
|
initial_private_accounts: Some(initial_data.sequencer_initial_private_accounts()),
|
||||||
signing_key: [37; 32],
|
signing_key: [37; 32],
|
||||||
channel_id: bedrock_channel_id(),
|
channel_id: bedrock_channel_id(),
|
||||||
})
|
})
|
||||||
@ -210,9 +211,9 @@ pub fn sequencer_config(
|
|||||||
max_block_size,
|
max_block_size,
|
||||||
mempool_max_size,
|
mempool_max_size,
|
||||||
block_create_timeout,
|
block_create_timeout,
|
||||||
retry_pending_blocks_timeout: Duration::from_secs(120),
|
retry_pending_blocks_timeout: Duration::from_mins(2),
|
||||||
initial_accounts: initial_data.sequencer_initial_accounts(),
|
initial_public_accounts: Some(initial_data.sequencer_initial_public_accounts()),
|
||||||
initial_commitments: initial_data.sequencer_initial_commitments(),
|
initial_private_accounts: Some(initial_data.sequencer_initial_private_accounts()),
|
||||||
signing_key: [37; 32],
|
signing_key: [37; 32],
|
||||||
bedrock_config: BedrockConfig {
|
bedrock_config: BedrockConfig {
|
||||||
backoff: BackoffConfig {
|
backoff: BackoffConfig {
|
||||||
@ -240,7 +241,7 @@ pub fn wallet_config(
|
|||||||
seq_tx_poll_max_blocks: 15,
|
seq_tx_poll_max_blocks: 15,
|
||||||
seq_poll_max_retries: 10,
|
seq_poll_max_retries: 10,
|
||||||
seq_block_poll_max_amount: 100,
|
seq_block_poll_max_amount: 100,
|
||||||
initial_accounts: initial_data.wallet_initial_accounts(),
|
initial_accounts: Some(initial_data.wallet_initial_accounts()),
|
||||||
basic_auth: None,
|
basic_auth: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
656
integration_tests/tests/ata.rs
Normal file
656
integration_tests/tests/ata.rs
Normal file
@ -0,0 +1,656 @@
|
|||||||
|
#![expect(
|
||||||
|
clippy::shadow_unrelated,
|
||||||
|
clippy::tests_outside_test_module,
|
||||||
|
reason = "We don't care about these in tests"
|
||||||
|
)]
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use ata_core::{compute_ata_seed, get_associated_token_account_id};
|
||||||
|
use integration_tests::{
|
||||||
|
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id,
|
||||||
|
format_public_account_id, verify_commitment_is_in_state,
|
||||||
|
};
|
||||||
|
use log::info;
|
||||||
|
use nssa::program::Program;
|
||||||
|
use sequencer_service_rpc::RpcClient as _;
|
||||||
|
use token_core::{TokenDefinition, TokenHolding};
|
||||||
|
use tokio::test;
|
||||||
|
use wallet::cli::{
|
||||||
|
Command, SubcommandReturnValue,
|
||||||
|
account::{AccountSubcommand, NewSubcommand},
|
||||||
|
programs::{ata::AtaSubcommand, token::TokenProgramAgnosticSubcommand},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create a public account and return its ID.
|
||||||
|
async fn new_public_account(ctx: &mut TestContext) -> Result<nssa::AccountId> {
|
||||||
|
let result = wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
|
||||||
|
cci: None,
|
||||||
|
label: None,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let SubcommandReturnValue::RegisterAccount { account_id } = result else {
|
||||||
|
anyhow::bail!("Expected RegisterAccount return value");
|
||||||
|
};
|
||||||
|
Ok(account_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a private account and return its ID.
|
||||||
|
async fn new_private_account(ctx: &mut TestContext) -> Result<nssa::AccountId> {
|
||||||
|
let result = wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
|
||||||
|
cci: None,
|
||||||
|
label: None,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let SubcommandReturnValue::RegisterAccount { account_id } = result else {
|
||||||
|
anyhow::bail!("Expected RegisterAccount return value");
|
||||||
|
};
|
||||||
|
Ok(account_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn create_ata_initializes_holding_account() -> Result<()> {
|
||||||
|
let mut ctx = TestContext::new().await?;
|
||||||
|
|
||||||
|
let definition_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let supply_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let owner_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
|
||||||
|
// Create a fungible token
|
||||||
|
let total_supply = 100_u128;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Token(TokenProgramAgnosticSubcommand::New {
|
||||||
|
definition_account_id: format_public_account_id(definition_account_id),
|
||||||
|
supply_account_id: format_public_account_id(supply_account_id),
|
||||||
|
name: "TEST".to_owned(),
|
||||||
|
total_supply,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Create the ATA for owner + definition
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Create {
|
||||||
|
owner: format_public_account_id(owner_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Derive expected ATA address and check on-chain state
|
||||||
|
let ata_program_id = Program::ata().id();
|
||||||
|
let ata_id = get_associated_token_account_id(
|
||||||
|
&ata_program_id,
|
||||||
|
&compute_ata_seed(owner_account_id, definition_account_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
let ata_acc = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account(ata_id)
|
||||||
|
.await
|
||||||
|
.context("ATA account not found")?;
|
||||||
|
|
||||||
|
assert_eq!(ata_acc.program_owner, Program::token().id());
|
||||||
|
let holding = TokenHolding::try_from(&ata_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
definition_id: definition_account_id,
|
||||||
|
balance: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn create_ata_is_idempotent() -> Result<()> {
|
||||||
|
let mut ctx = TestContext::new().await?;
|
||||||
|
|
||||||
|
let definition_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let supply_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let owner_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
|
||||||
|
// Create a fungible token
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Token(TokenProgramAgnosticSubcommand::New {
|
||||||
|
definition_account_id: format_public_account_id(definition_account_id),
|
||||||
|
supply_account_id: format_public_account_id(supply_account_id),
|
||||||
|
name: "TEST".to_owned(),
|
||||||
|
total_supply: 100,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Create the ATA once
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Create {
|
||||||
|
owner: format_public_account_id(owner_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Create the ATA a second time — must succeed (idempotent)
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Create {
|
||||||
|
owner: format_public_account_id(owner_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// State must be unchanged
|
||||||
|
let ata_program_id = Program::ata().id();
|
||||||
|
let ata_id = get_associated_token_account_id(
|
||||||
|
&ata_program_id,
|
||||||
|
&compute_ata_seed(owner_account_id, definition_account_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
let ata_acc = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account(ata_id)
|
||||||
|
.await
|
||||||
|
.context("ATA account not found")?;
|
||||||
|
|
||||||
|
assert_eq!(ata_acc.program_owner, Program::token().id());
|
||||||
|
let holding = TokenHolding::try_from(&ata_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
definition_id: definition_account_id,
|
||||||
|
balance: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn transfer_and_burn_via_ata() -> Result<()> {
|
||||||
|
let mut ctx = TestContext::new().await?;
|
||||||
|
|
||||||
|
let definition_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let supply_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let sender_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let recipient_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
|
||||||
|
let total_supply = 1000_u128;
|
||||||
|
|
||||||
|
// Create a fungible token, supply goes to supply_account_id
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Token(TokenProgramAgnosticSubcommand::New {
|
||||||
|
definition_account_id: format_public_account_id(definition_account_id),
|
||||||
|
supply_account_id: format_public_account_id(supply_account_id),
|
||||||
|
name: "TEST".to_owned(),
|
||||||
|
total_supply,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Derive ATA addresses
|
||||||
|
let ata_program_id = Program::ata().id();
|
||||||
|
let sender_ata_id = get_associated_token_account_id(
|
||||||
|
&ata_program_id,
|
||||||
|
&compute_ata_seed(sender_account_id, definition_account_id),
|
||||||
|
);
|
||||||
|
let recipient_ata_id = get_associated_token_account_id(
|
||||||
|
&ata_program_id,
|
||||||
|
&compute_ata_seed(recipient_account_id, definition_account_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create ATAs for sender and recipient
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Create {
|
||||||
|
owner: format_public_account_id(sender_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Create {
|
||||||
|
owner: format_public_account_id(recipient_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Fund sender's ATA from the supply account (direct token transfer)
|
||||||
|
let fund_amount = 200_u128;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Token(TokenProgramAgnosticSubcommand::Send {
|
||||||
|
from: format_public_account_id(supply_account_id),
|
||||||
|
to: Some(format_public_account_id(sender_ata_id)),
|
||||||
|
to_npk: None,
|
||||||
|
to_vpk: None,
|
||||||
|
amount: fund_amount,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Transfer from sender's ATA to recipient's ATA via the ATA program
|
||||||
|
let transfer_amount = 50_u128;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Send {
|
||||||
|
from: format_public_account_id(sender_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
to: recipient_ata_id.to_string(),
|
||||||
|
amount: transfer_amount,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Verify sender ATA balance decreased
|
||||||
|
let sender_ata_acc = ctx.sequencer_client().get_account(sender_ata_id).await?;
|
||||||
|
let sender_holding = TokenHolding::try_from(&sender_ata_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
sender_holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
definition_id: definition_account_id,
|
||||||
|
balance: fund_amount - transfer_amount,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify recipient ATA balance increased
|
||||||
|
let recipient_ata_acc = ctx.sequencer_client().get_account(recipient_ata_id).await?;
|
||||||
|
let recipient_holding = TokenHolding::try_from(&recipient_ata_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
recipient_holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
definition_id: definition_account_id,
|
||||||
|
balance: transfer_amount,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Burn from sender's ATA
|
||||||
|
let burn_amount = 30_u128;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Burn {
|
||||||
|
holder: format_public_account_id(sender_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
amount: burn_amount,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Verify sender ATA balance after burn
|
||||||
|
let sender_ata_acc = ctx.sequencer_client().get_account(sender_ata_id).await?;
|
||||||
|
let sender_holding = TokenHolding::try_from(&sender_ata_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
sender_holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
definition_id: definition_account_id,
|
||||||
|
balance: fund_amount - transfer_amount - burn_amount,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the token definition total_supply decreased by burn_amount
|
||||||
|
let definition_acc = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account(definition_account_id)
|
||||||
|
.await?;
|
||||||
|
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
token_definition,
|
||||||
|
TokenDefinition::Fungible {
|
||||||
|
name: "TEST".to_owned(),
|
||||||
|
total_supply: total_supply - burn_amount,
|
||||||
|
metadata_id: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn create_ata_with_private_owner() -> Result<()> {
|
||||||
|
let mut ctx = TestContext::new().await?;
|
||||||
|
|
||||||
|
let definition_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let supply_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let owner_account_id = new_private_account(&mut ctx).await?;
|
||||||
|
|
||||||
|
// Create a fungible token
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Token(TokenProgramAgnosticSubcommand::New {
|
||||||
|
definition_account_id: format_public_account_id(definition_account_id),
|
||||||
|
supply_account_id: format_public_account_id(supply_account_id),
|
||||||
|
name: "TEST".to_owned(),
|
||||||
|
total_supply: 100,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Create the ATA for the private owner + definition
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Create {
|
||||||
|
owner: format_private_account_id(owner_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Derive expected ATA address and check on-chain state
|
||||||
|
let ata_program_id = Program::ata().id();
|
||||||
|
let ata_id = get_associated_token_account_id(
|
||||||
|
&ata_program_id,
|
||||||
|
&compute_ata_seed(owner_account_id, definition_account_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
let ata_acc = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account(ata_id)
|
||||||
|
.await
|
||||||
|
.context("ATA account not found")?;
|
||||||
|
|
||||||
|
assert_eq!(ata_acc.program_owner, Program::token().id());
|
||||||
|
let holding = TokenHolding::try_from(&ata_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
definition_id: definition_account_id,
|
||||||
|
balance: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the private owner's commitment is in state
|
||||||
|
let commitment = ctx
|
||||||
|
.wallet()
|
||||||
|
.get_private_account_commitment(owner_account_id)
|
||||||
|
.context("Private owner commitment not found")?;
|
||||||
|
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn transfer_via_ata_private_owner() -> Result<()> {
|
||||||
|
let mut ctx = TestContext::new().await?;
|
||||||
|
|
||||||
|
let definition_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let supply_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let sender_account_id = new_private_account(&mut ctx).await?;
|
||||||
|
let recipient_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
|
||||||
|
let total_supply = 1000_u128;
|
||||||
|
|
||||||
|
// Create a fungible token
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Token(TokenProgramAgnosticSubcommand::New {
|
||||||
|
definition_account_id: format_public_account_id(definition_account_id),
|
||||||
|
supply_account_id: format_public_account_id(supply_account_id),
|
||||||
|
name: "TEST".to_owned(),
|
||||||
|
total_supply,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Derive ATA addresses
|
||||||
|
let ata_program_id = Program::ata().id();
|
||||||
|
let sender_ata_id = get_associated_token_account_id(
|
||||||
|
&ata_program_id,
|
||||||
|
&compute_ata_seed(sender_account_id, definition_account_id),
|
||||||
|
);
|
||||||
|
let recipient_ata_id = get_associated_token_account_id(
|
||||||
|
&ata_program_id,
|
||||||
|
&compute_ata_seed(recipient_account_id, definition_account_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create ATAs for sender (private owner) and recipient (public owner)
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Create {
|
||||||
|
owner: format_private_account_id(sender_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Create {
|
||||||
|
owner: format_public_account_id(recipient_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Fund sender's ATA from the supply account (direct token transfer)
|
||||||
|
let fund_amount = 200_u128;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Token(TokenProgramAgnosticSubcommand::Send {
|
||||||
|
from: format_public_account_id(supply_account_id),
|
||||||
|
to: Some(format_public_account_id(sender_ata_id)),
|
||||||
|
to_npk: None,
|
||||||
|
to_vpk: None,
|
||||||
|
amount: fund_amount,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Transfer from sender's ATA (private owner) to recipient's ATA
|
||||||
|
let transfer_amount = 50_u128;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Send {
|
||||||
|
from: format_private_account_id(sender_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
to: recipient_ata_id.to_string(),
|
||||||
|
amount: transfer_amount,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Verify sender ATA balance decreased
|
||||||
|
let sender_ata_acc = ctx.sequencer_client().get_account(sender_ata_id).await?;
|
||||||
|
let sender_holding = TokenHolding::try_from(&sender_ata_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
sender_holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
definition_id: definition_account_id,
|
||||||
|
balance: fund_amount - transfer_amount,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify recipient ATA balance increased
|
||||||
|
let recipient_ata_acc = ctx.sequencer_client().get_account(recipient_ata_id).await?;
|
||||||
|
let recipient_holding = TokenHolding::try_from(&recipient_ata_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
recipient_holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
definition_id: definition_account_id,
|
||||||
|
balance: transfer_amount,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the private sender's commitment is in state
|
||||||
|
let commitment = ctx
|
||||||
|
.wallet()
|
||||||
|
.get_private_account_commitment(sender_account_id)
|
||||||
|
.context("Private sender commitment not found")?;
|
||||||
|
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn burn_via_ata_private_owner() -> Result<()> {
|
||||||
|
let mut ctx = TestContext::new().await?;
|
||||||
|
|
||||||
|
let definition_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let supply_account_id = new_public_account(&mut ctx).await?;
|
||||||
|
let holder_account_id = new_private_account(&mut ctx).await?;
|
||||||
|
|
||||||
|
let total_supply = 500_u128;
|
||||||
|
|
||||||
|
// Create a fungible token
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Token(TokenProgramAgnosticSubcommand::New {
|
||||||
|
definition_account_id: format_public_account_id(definition_account_id),
|
||||||
|
supply_account_id: format_public_account_id(supply_account_id),
|
||||||
|
name: "TEST".to_owned(),
|
||||||
|
total_supply,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Derive holder's ATA address
|
||||||
|
let ata_program_id = Program::ata().id();
|
||||||
|
let holder_ata_id = get_associated_token_account_id(
|
||||||
|
&ata_program_id,
|
||||||
|
&compute_ata_seed(holder_account_id, definition_account_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create ATA for the private holder
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Create {
|
||||||
|
owner: format_private_account_id(holder_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Fund holder's ATA from the supply account
|
||||||
|
let fund_amount = 300_u128;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Token(TokenProgramAgnosticSubcommand::Send {
|
||||||
|
from: format_public_account_id(supply_account_id),
|
||||||
|
to: Some(format_public_account_id(holder_ata_id)),
|
||||||
|
to_npk: None,
|
||||||
|
to_vpk: None,
|
||||||
|
amount: fund_amount,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Burn from holder's ATA (private owner)
|
||||||
|
let burn_amount = 100_u128;
|
||||||
|
wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Ata(AtaSubcommand::Burn {
|
||||||
|
holder: format_private_account_id(holder_account_id),
|
||||||
|
token_definition: definition_account_id.to_string(),
|
||||||
|
amount: burn_amount,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Waiting for next block creation");
|
||||||
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
|
// Verify holder ATA balance after burn
|
||||||
|
let holder_ata_acc = ctx.sequencer_client().get_account(holder_ata_id).await?;
|
||||||
|
let holder_holding = TokenHolding::try_from(&holder_ata_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
holder_holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
definition_id: definition_account_id,
|
||||||
|
balance: fund_amount - burn_amount,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the token definition total_supply decreased by burn_amount
|
||||||
|
let definition_acc = ctx
|
||||||
|
.sequencer_client()
|
||||||
|
.get_account(definition_account_id)
|
||||||
|
.await?;
|
||||||
|
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||||
|
assert_eq!(
|
||||||
|
token_definition,
|
||||||
|
TokenDefinition::Fungible {
|
||||||
|
name: "TEST".to_owned(),
|
||||||
|
total_supply: total_supply - burn_amount,
|
||||||
|
metadata_id: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the private holder's commitment is in state
|
||||||
|
let commitment = ctx
|
||||||
|
.wallet()
|
||||||
|
.get_private_account_commitment(holder_account_id)
|
||||||
|
.context("Private holder commitment not found")?;
|
||||||
|
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@ -20,17 +20,16 @@ pub struct SeedHolder {
|
|||||||
|
|
||||||
/// Secret spending key object. Can produce `PrivateKeyHolder` objects.
|
/// Secret spending key object. Can produce `PrivateKeyHolder` objects.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SecretSpendingKey(pub(crate) [u8; 32]);
|
pub struct SecretSpendingKey(pub [u8; 32]);
|
||||||
|
|
||||||
pub type ViewingSecretKey = Scalar;
|
pub type ViewingSecretKey = Scalar;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
/// Private key holder. Produces public keys. Can produce `account_id`. Can produce shared secret
|
/// Private key holder. Produces public keys. Can produce `account_id`. Can produce shared secret
|
||||||
/// for recepient.
|
/// for recepient.
|
||||||
#[expect(clippy::partial_pub_fields, reason = "TODO: fix later")]
|
|
||||||
pub struct PrivateKeyHolder {
|
pub struct PrivateKeyHolder {
|
||||||
pub nullifier_secret_key: NullifierSecretKey,
|
pub nullifier_secret_key: NullifierSecretKey,
|
||||||
pub(crate) viewing_secret_key: ViewingSecretKey,
|
pub viewing_secret_key: ViewingSecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SeedHolder {
|
impl SeedHolder {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use crate::{
|
|||||||
NullifierSecretKey, SharedSecretKey,
|
NullifierSecretKey, SharedSecretKey,
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
encryption::Ciphertext,
|
encryption::Ciphertext,
|
||||||
program::{ProgramId, ProgramOutput},
|
program::{ProgramId, ProgramOutput, ValidityWindow},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -36,6 +36,7 @@ pub struct PrivacyPreservingCircuitOutput {
|
|||||||
pub ciphertexts: Vec<Ciphertext>,
|
pub ciphertexts: Vec<Ciphertext>,
|
||||||
pub new_commitments: Vec<Commitment>,
|
pub new_commitments: Vec<Commitment>,
|
||||||
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
||||||
|
pub validity_window: ValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "host")]
|
#[cfg(feature = "host")]
|
||||||
@ -101,6 +102,7 @@ mod tests {
|
|||||||
),
|
),
|
||||||
[0xab; 32],
|
[0xab; 32],
|
||||||
)],
|
)],
|
||||||
|
validity_window: (Some(1), None).try_into().unwrap(),
|
||||||
};
|
};
|
||||||
let bytes = output.to_bytes();
|
let bytes = output.to_bytes();
|
||||||
let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap();
|
let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap();
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "host", test))]
|
||||||
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
|
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -151,15 +153,163 @@ impl AccountPostState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type BlockId = u64;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
any(feature = "host", test),
|
||||||
|
derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)
|
||||||
|
)]
|
||||||
|
pub struct ValidityWindow {
|
||||||
|
from: Option<BlockId>,
|
||||||
|
to: Option<BlockId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValidityWindow {
|
||||||
|
/// Creates a window with no bounds, valid for every block ID.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new_unbounded() -> Self {
|
||||||
|
Self {
|
||||||
|
from: None,
|
||||||
|
to: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `id` falls within the half-open range `[from, to)`.
|
||||||
|
/// A `None` bound on either side is treated as unbounded in that direction.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_valid_for_block_id(&self, id: BlockId) -> bool {
|
||||||
|
self.from.is_none_or(|start| id >= start) && self.to.is_none_or(|end| id < end)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `Err(InvalidWindow)` if both bounds are set and `from >= to`.
|
||||||
|
const fn check_window(&self) -> Result<(), InvalidWindow> {
|
||||||
|
if let (Some(from_id), Some(until_id)) = (self.from, self.to)
|
||||||
|
&& from_id >= until_id
|
||||||
|
{
|
||||||
|
Err(InvalidWindow)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inclusive lower bound. `None` means the window starts at the beginning of the chain.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn start(&self) -> Option<BlockId> {
|
||||||
|
self.from
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exclusive upper bound. `None` means the window has no expiry.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn end(&self) -> Option<BlockId> {
|
||||||
|
self.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<(Option<BlockId>, Option<BlockId>)> for ValidityWindow {
|
||||||
|
type Error = InvalidWindow;
|
||||||
|
|
||||||
|
fn try_from(value: (Option<BlockId>, Option<BlockId>)) -> Result<Self, Self::Error> {
|
||||||
|
let this = Self {
|
||||||
|
from: value.0,
|
||||||
|
to: value.1,
|
||||||
|
};
|
||||||
|
this.check_window()?;
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<std::ops::Range<BlockId>> for ValidityWindow {
|
||||||
|
type Error = InvalidWindow;
|
||||||
|
|
||||||
|
fn try_from(value: std::ops::Range<BlockId>) -> Result<Self, Self::Error> {
|
||||||
|
(Some(value.start), Some(value.end)).try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::ops::RangeFrom<BlockId>> for ValidityWindow {
|
||||||
|
fn from(value: std::ops::RangeFrom<BlockId>) -> Self {
|
||||||
|
Self {
|
||||||
|
from: Some(value.start),
|
||||||
|
to: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::ops::RangeTo<BlockId>> for ValidityWindow {
|
||||||
|
fn from(value: std::ops::RangeTo<BlockId>) -> Self {
|
||||||
|
Self {
|
||||||
|
from: None,
|
||||||
|
to: Some(value.end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::ops::RangeFull> for ValidityWindow {
|
||||||
|
fn from(_: std::ops::RangeFull) -> Self {
|
||||||
|
Self::new_unbounded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[error("Invalid window")]
|
||||||
|
pub struct InvalidWindow;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||||
|
#[must_use = "ProgramOutput does nothing unless written"]
|
||||||
pub struct ProgramOutput {
|
pub struct ProgramOutput {
|
||||||
/// The instruction data the program received to produce this output.
|
/// The instruction data the program received to produce this output.
|
||||||
pub instruction_data: InstructionData,
|
pub instruction_data: InstructionData,
|
||||||
/// The account pre states the program received to produce this output.
|
/// The account pre states the program received to produce this output.
|
||||||
pub pre_states: Vec<AccountWithMetadata>,
|
pub pre_states: Vec<AccountWithMetadata>,
|
||||||
|
/// The account post states the program execution produced.
|
||||||
pub post_states: Vec<AccountPostState>,
|
pub post_states: Vec<AccountPostState>,
|
||||||
|
/// The list of chained calls to other programs.
|
||||||
pub chained_calls: Vec<ChainedCall>,
|
pub chained_calls: Vec<ChainedCall>,
|
||||||
|
/// The window where the program output is valid.
|
||||||
|
pub validity_window: ValidityWindow,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgramOutput {
|
||||||
|
pub const fn new(
|
||||||
|
instruction_data: InstructionData,
|
||||||
|
pre_states: Vec<AccountWithMetadata>,
|
||||||
|
post_states: Vec<AccountPostState>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
instruction_data,
|
||||||
|
pre_states,
|
||||||
|
post_states,
|
||||||
|
chained_calls: Vec::new(),
|
||||||
|
validity_window: ValidityWindow::new_unbounded(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self) {
|
||||||
|
env::commit(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_chained_calls(mut self, chained_calls: Vec<ChainedCall>) -> Self {
|
||||||
|
self.chained_calls = chained_calls;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the validity window from an infallible range conversion (`1..`, `..5`, `..`).
|
||||||
|
pub fn with_validity_window<W: Into<ValidityWindow>>(mut self, window: W) -> Self {
|
||||||
|
self.validity_window = window.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the validity window from a fallible range conversion (`1..5`).
|
||||||
|
/// Returns `Err` if the range is empty.
|
||||||
|
pub fn try_with_validity_window<W: TryInto<ValidityWindow, Error = InvalidWindow>>(
|
||||||
|
mut self,
|
||||||
|
window: W,
|
||||||
|
) -> Result<Self, InvalidWindow> {
|
||||||
|
self.validity_window = window.try_into()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of a number as `lo + hi * 2^128`.
|
/// Representation of a number as `lo + hi * 2^128`.
|
||||||
@ -219,35 +369,6 @@ pub fn read_nssa_inputs<T: DeserializeOwned>() -> (ProgramInput<T>, InstructionD
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_nssa_outputs(
|
|
||||||
instruction_data: InstructionData,
|
|
||||||
pre_states: Vec<AccountWithMetadata>,
|
|
||||||
post_states: Vec<AccountPostState>,
|
|
||||||
) {
|
|
||||||
let output = ProgramOutput {
|
|
||||||
instruction_data,
|
|
||||||
pre_states,
|
|
||||||
post_states,
|
|
||||||
chained_calls: Vec::new(),
|
|
||||||
};
|
|
||||||
env::commit(&output);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_nssa_outputs_with_chained_call(
|
|
||||||
instruction_data: InstructionData,
|
|
||||||
pre_states: Vec<AccountWithMetadata>,
|
|
||||||
post_states: Vec<AccountPostState>,
|
|
||||||
chained_calls: Vec<ChainedCall>,
|
|
||||||
) {
|
|
||||||
let output = ProgramOutput {
|
|
||||||
instruction_data,
|
|
||||||
pre_states,
|
|
||||||
post_states,
|
|
||||||
chained_calls,
|
|
||||||
};
|
|
||||||
env::commit(&output);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validates well-behaved program execution.
|
/// Validates well-behaved program execution.
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
@ -342,6 +463,132 @@ fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> boo
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_unbounded_accepts_any_block() {
|
||||||
|
let w = ValidityWindow::new_unbounded();
|
||||||
|
assert!(w.is_valid_for_block_id(0));
|
||||||
|
assert!(w.is_valid_for_block_id(u64::MAX));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_bounded_range_includes_from_excludes_to() {
|
||||||
|
let w: ValidityWindow = (Some(5), Some(10)).try_into().unwrap();
|
||||||
|
assert!(!w.is_valid_for_block_id(4));
|
||||||
|
assert!(w.is_valid_for_block_id(5));
|
||||||
|
assert!(w.is_valid_for_block_id(9));
|
||||||
|
assert!(!w.is_valid_for_block_id(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_only_from_bound() {
|
||||||
|
let w: ValidityWindow = (Some(5), None).try_into().unwrap();
|
||||||
|
assert!(!w.is_valid_for_block_id(4));
|
||||||
|
assert!(w.is_valid_for_block_id(5));
|
||||||
|
assert!(w.is_valid_for_block_id(u64::MAX));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_only_to_bound() {
|
||||||
|
let w: ValidityWindow = (None, Some(5)).try_into().unwrap();
|
||||||
|
assert!(w.is_valid_for_block_id(0));
|
||||||
|
assert!(w.is_valid_for_block_id(4));
|
||||||
|
assert!(!w.is_valid_for_block_id(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_adjacent_bounds_are_invalid() {
|
||||||
|
// [5, 5) is an empty range — from == to
|
||||||
|
assert!(ValidityWindow::try_from((Some(5), Some(5))).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_inverted_bounds_are_invalid() {
|
||||||
|
assert!(ValidityWindow::try_from((Some(10), Some(5))).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_getters_match_construction() {
|
||||||
|
let w: ValidityWindow = (Some(3), Some(7)).try_into().unwrap();
|
||||||
|
assert_eq!(w.start(), Some(3));
|
||||||
|
assert_eq!(w.end(), Some(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_getters_for_unbounded() {
|
||||||
|
let w = ValidityWindow::new_unbounded();
|
||||||
|
assert_eq!(w.start(), None);
|
||||||
|
assert_eq!(w.end(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_from_range() {
|
||||||
|
let w = ValidityWindow::try_from(5_u64..10).unwrap();
|
||||||
|
assert_eq!(w.start(), Some(5));
|
||||||
|
assert_eq!(w.end(), Some(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_from_range_empty_is_invalid() {
|
||||||
|
assert!(ValidityWindow::try_from(5_u64..5).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_from_range_inverted_is_invalid() {
|
||||||
|
let from = 10_u64;
|
||||||
|
let to = 5_u64;
|
||||||
|
assert!(ValidityWindow::try_from(from..to).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_from_range_from() {
|
||||||
|
let w: ValidityWindow = (5_u64..).into();
|
||||||
|
assert_eq!(w.start(), Some(5));
|
||||||
|
assert_eq!(w.end(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_from_range_to() {
|
||||||
|
let w: ValidityWindow = (..10_u64).into();
|
||||||
|
assert_eq!(w.start(), None);
|
||||||
|
assert_eq!(w.end(), Some(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validity_window_from_range_full() {
|
||||||
|
let w: ValidityWindow = (..).into();
|
||||||
|
assert_eq!(w.start(), None);
|
||||||
|
assert_eq!(w.end(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn program_output_try_with_validity_window_range() {
|
||||||
|
let output = ProgramOutput::new(vec![], vec![], vec![])
|
||||||
|
.try_with_validity_window(10_u64..100)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(output.validity_window.start(), Some(10));
|
||||||
|
assert_eq!(output.validity_window.end(), Some(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn program_output_with_validity_window_range_from() {
|
||||||
|
let output = ProgramOutput::new(vec![], vec![], vec![]).with_validity_window(10_u64..);
|
||||||
|
assert_eq!(output.validity_window.start(), Some(10));
|
||||||
|
assert_eq!(output.validity_window.end(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn program_output_with_validity_window_range_to() {
|
||||||
|
let output = ProgramOutput::new(vec![], vec![], vec![]).with_validity_window(..100_u64);
|
||||||
|
assert_eq!(output.validity_window.start(), None);
|
||||||
|
assert_eq!(output.validity_window.end(), Some(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn program_output_try_with_validity_window_empty_range_fails() {
|
||||||
|
let result = ProgramOutput::new(vec![], vec![], vec![]).try_with_validity_window(5_u64..5);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn post_state_new_with_claim_constructor() {
|
fn post_state_new_with_claim_constructor() {
|
||||||
let account = Account {
|
let account = Account {
|
||||||
|
|||||||
@ -72,6 +72,9 @@ pub enum NssaError {
|
|||||||
|
|
||||||
#[error("Max account nonce reached")]
|
#[error("Max account nonce reached")]
|
||||||
MaxAccountNonceReached,
|
MaxAccountNonceReached,
|
||||||
|
|
||||||
|
#[error("Execution outside of the validity window")]
|
||||||
|
OutOfValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -174,12 +174,13 @@ mod tests {
|
|||||||
#![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")]
|
#![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")]
|
||||||
|
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier,
|
Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, SharedSecretKey,
|
||||||
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::NssaError,
|
||||||
privacy_preserving_transaction::circuit::execute_and_prove,
|
privacy_preserving_transaction::circuit::execute_and_prove,
|
||||||
program::Program,
|
program::Program,
|
||||||
state::{
|
state::{
|
||||||
@ -364,4 +365,46 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(recipient_post, expected_private_account_2);
|
assert_eq!(recipient_post, expected_private_account_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn circuit_fails_when_chained_validity_windows_have_empty_intersection() {
|
||||||
|
let account_keys = test_private_account_keys_1();
|
||||||
|
let pre = AccountWithMetadata::new(
|
||||||
|
Account::default(),
|
||||||
|
false,
|
||||||
|
AccountId::from(&account_keys.npk()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let validity_window_chain_caller = Program::validity_window_chain_caller();
|
||||||
|
let validity_window = Program::validity_window();
|
||||||
|
|
||||||
|
let instruction = Program::serialize_instruction((
|
||||||
|
Some(1_u64),
|
||||||
|
Some(4_u64),
|
||||||
|
validity_window.id(),
|
||||||
|
Some(4_u64),
|
||||||
|
Some(7_u64),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let esk = [3; 32];
|
||||||
|
let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk());
|
||||||
|
|
||||||
|
let program_with_deps = ProgramWithDependencies::new(
|
||||||
|
validity_window_chain_caller,
|
||||||
|
[(validity_window.id(), validity_window)].into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
vec![pre],
|
||||||
|
instruction,
|
||||||
|
vec![2],
|
||||||
|
vec![(account_keys.npk(), shared_secret)],
|
||||||
|
vec![],
|
||||||
|
vec![None],
|
||||||
|
&program_with_deps,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use nssa_core::{
|
|||||||
Commitment, CommitmentSetDigest, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitOutput,
|
Commitment, CommitmentSetDigest, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitOutput,
|
||||||
account::{Account, Nonce},
|
account::{Account, Nonce},
|
||||||
encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey},
|
encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey},
|
||||||
|
program::ValidityWindow,
|
||||||
};
|
};
|
||||||
use sha2::{Digest as _, Sha256};
|
use sha2::{Digest as _, Sha256};
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ pub struct Message {
|
|||||||
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
||||||
pub new_commitments: Vec<Commitment>,
|
pub new_commitments: Vec<Commitment>,
|
||||||
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
||||||
|
pub validity_window: ValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Message {
|
impl std::fmt::Debug for Message {
|
||||||
@ -77,6 +79,7 @@ impl std::fmt::Debug for Message {
|
|||||||
)
|
)
|
||||||
.field("new_commitments", &self.new_commitments)
|
.field("new_commitments", &self.new_commitments)
|
||||||
.field("new_nullifiers", &nullifiers)
|
.field("new_nullifiers", &nullifiers)
|
||||||
|
.field("validity_window", &self.validity_window)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,6 +112,7 @@ impl Message {
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments: output.new_commitments,
|
new_commitments: output.new_commitments,
|
||||||
new_nullifiers: output.new_nullifiers,
|
new_nullifiers: output.new_nullifiers,
|
||||||
|
validity_window: output.validity_window,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,6 +165,7 @@ pub mod tests {
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments,
|
new_commitments,
|
||||||
new_nullifiers,
|
new_nullifiers,
|
||||||
|
validity_window: (None, None).try_into().unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput,
|
Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput,
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
|
program::{BlockId, ValidityWindow},
|
||||||
};
|
};
|
||||||
use sha2::{Digest as _, digest::FixedOutput as _};
|
use sha2::{Digest as _, digest::FixedOutput as _};
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ impl PrivacyPreservingTransaction {
|
|||||||
pub(crate) fn validate_and_produce_public_state_diff(
|
pub(crate) fn validate_and_produce_public_state_diff(
|
||||||
&self,
|
&self,
|
||||||
state: &V03State,
|
state: &V03State,
|
||||||
|
block_id: BlockId,
|
||||||
) -> Result<HashMap<AccountId, Account>, NssaError> {
|
) -> Result<HashMap<AccountId, Account>, NssaError> {
|
||||||
let message = &self.message;
|
let message = &self.message;
|
||||||
let witness_set = &self.witness_set;
|
let witness_set = &self.witness_set;
|
||||||
@ -91,6 +93,11 @@ impl PrivacyPreservingTransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify validity window
|
||||||
|
if !message.validity_window.is_valid_for_block_id(block_id) {
|
||||||
|
return Err(NssaError::OutOfValidityWindow);
|
||||||
|
}
|
||||||
|
|
||||||
// Build pre_states for proof verification
|
// Build pre_states for proof verification
|
||||||
let public_pre_states: Vec<_> = message
|
let public_pre_states: Vec<_> = message
|
||||||
.public_account_ids
|
.public_account_ids
|
||||||
@ -112,6 +119,7 @@ impl PrivacyPreservingTransaction {
|
|||||||
&message.encrypted_private_post_states,
|
&message.encrypted_private_post_states,
|
||||||
&message.new_commitments,
|
&message.new_commitments,
|
||||||
&message.new_nullifiers,
|
&message.new_nullifiers,
|
||||||
|
&message.validity_window,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// 5. Commitment freshness
|
// 5. Commitment freshness
|
||||||
@ -173,6 +181,7 @@ fn check_privacy_preserving_circuit_proof_is_valid(
|
|||||||
encrypted_private_post_states: &[EncryptedAccountData],
|
encrypted_private_post_states: &[EncryptedAccountData],
|
||||||
new_commitments: &[Commitment],
|
new_commitments: &[Commitment],
|
||||||
new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
|
new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
|
||||||
|
validity_window: &ValidityWindow,
|
||||||
) -> Result<(), NssaError> {
|
) -> Result<(), NssaError> {
|
||||||
let output = PrivacyPreservingCircuitOutput {
|
let output = PrivacyPreservingCircuitOutput {
|
||||||
public_pre_states: public_pre_states.to_vec(),
|
public_pre_states: public_pre_states.to_vec(),
|
||||||
@ -184,6 +193,7 @@ fn check_privacy_preserving_circuit_proof_is_valid(
|
|||||||
.collect(),
|
.collect(),
|
||||||
new_commitments: new_commitments.to_vec(),
|
new_commitments: new_commitments.to_vec(),
|
||||||
new_nullifiers: new_nullifiers.to_vec(),
|
new_nullifiers: new_nullifiers.to_vec(),
|
||||||
|
validity_window: validity_window.to_owned(),
|
||||||
};
|
};
|
||||||
proof
|
proof
|
||||||
.is_valid_for(&output)
|
.is_valid_for(&output)
|
||||||
|
|||||||
@ -8,7 +8,9 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::NssaError,
|
error::NssaError,
|
||||||
program_methods::{AMM_ELF, AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF},
|
program_methods::{
|
||||||
|
AMM_ELF, ASSOCIATED_TOKEN_ACCOUNT_ELF, AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Maximum number of cycles for a public execution.
|
/// Maximum number of cycles for a public execution.
|
||||||
@ -105,6 +107,12 @@ impl Program {
|
|||||||
pub fn amm() -> Self {
|
pub fn amm() -> Self {
|
||||||
Self::new(AMM_ELF.to_vec()).expect("The AMM program must be a valid Risc0 program")
|
Self::new(AMM_ELF.to_vec()).expect("The AMM program must be a valid Risc0 program")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn ata() -> Self {
|
||||||
|
Self::new(ASSOCIATED_TOKEN_ACCOUNT_ELF.to_vec())
|
||||||
|
.expect("The ATA program must be a valid Risc0 program")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
||||||
@ -284,6 +292,20 @@ mod tests {
|
|||||||
// `program_methods`
|
// `program_methods`
|
||||||
Self::new(MODIFIED_TRANSFER_ELF.to_vec()).unwrap()
|
Self::new(MODIFIED_TRANSFER_ELF.to_vec()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn validity_window() -> Self {
|
||||||
|
use test_program_methods::VALIDITY_WINDOW_ELF;
|
||||||
|
// This unwrap won't panic since the `VALIDITY_WINDOW_ELF` comes from risc0 build of
|
||||||
|
// `program_methods`
|
||||||
|
Self::new(VALIDITY_WINDOW_ELF.to_vec()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn validity_window_chain_caller() -> Self {
|
||||||
|
use test_program_methods::VALIDITY_WINDOW_CHAIN_CALLER_ELF;
|
||||||
|
Self::new(VALIDITY_WINDOW_CHAIN_CALLER_ELF.to_vec()).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
|||||||
use log::debug;
|
use log::debug;
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountId, AccountWithMetadata},
|
account::{Account, AccountId, AccountWithMetadata},
|
||||||
program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution},
|
program::{BlockId, ChainedCall, DEFAULT_PROGRAM_ID, validate_execution},
|
||||||
};
|
};
|
||||||
use sha2::{Digest as _, digest::FixedOutput as _};
|
use sha2::{Digest as _, digest::FixedOutput as _};
|
||||||
|
|
||||||
@ -70,6 +70,7 @@ impl PublicTransaction {
|
|||||||
pub(crate) fn validate_and_produce_public_state_diff(
|
pub(crate) fn validate_and_produce_public_state_diff(
|
||||||
&self,
|
&self,
|
||||||
state: &V03State,
|
state: &V03State,
|
||||||
|
block_id: BlockId,
|
||||||
) -> Result<HashMap<AccountId, Account>, NssaError> {
|
) -> Result<HashMap<AccountId, Account>, NssaError> {
|
||||||
let message = self.message();
|
let message = self.message();
|
||||||
let witness_set = self.witness_set();
|
let witness_set = self.witness_set();
|
||||||
@ -190,6 +191,14 @@ impl PublicTransaction {
|
|||||||
NssaError::InvalidProgramBehavior
|
NssaError::InvalidProgramBehavior
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Verify validity window
|
||||||
|
ensure!(
|
||||||
|
program_output
|
||||||
|
.validity_window
|
||||||
|
.is_valid_for_block_id(block_id),
|
||||||
|
NssaError::OutOfValidityWindow
|
||||||
|
);
|
||||||
|
|
||||||
for post in program_output
|
for post in program_output
|
||||||
.post_states
|
.post_states
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@ -359,7 +368,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]);
|
let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,7 +388,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,7 +409,7 @@ pub mod tests {
|
|||||||
let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||||
witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]);
|
witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,7 +429,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,7 +445,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
|
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
|
||||||
account::{Account, AccountId, Nonce},
|
account::{Account, AccountId, Nonce},
|
||||||
program::ProgramId,
|
program::{BlockId, ProgramId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -146,6 +146,7 @@ impl V03State {
|
|||||||
this.insert_program(Program::authenticated_transfer_program());
|
this.insert_program(Program::authenticated_transfer_program());
|
||||||
this.insert_program(Program::token());
|
this.insert_program(Program::token());
|
||||||
this.insert_program(Program::amm());
|
this.insert_program(Program::amm());
|
||||||
|
this.insert_program(Program::ata());
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
@ -157,8 +158,9 @@ impl V03State {
|
|||||||
pub fn transition_from_public_transaction(
|
pub fn transition_from_public_transaction(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: &PublicTransaction,
|
tx: &PublicTransaction,
|
||||||
|
block_id: BlockId,
|
||||||
) -> Result<(), NssaError> {
|
) -> Result<(), NssaError> {
|
||||||
let state_diff = tx.validate_and_produce_public_state_diff(self)?;
|
let state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?;
|
||||||
|
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::iter_over_hash_type,
|
clippy::iter_over_hash_type,
|
||||||
@ -181,9 +183,10 @@ impl V03State {
|
|||||||
pub fn transition_from_privacy_preserving_transaction(
|
pub fn transition_from_privacy_preserving_transaction(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: &PrivacyPreservingTransaction,
|
tx: &PrivacyPreservingTransaction,
|
||||||
|
block_id: BlockId,
|
||||||
) -> Result<(), NssaError> {
|
) -> Result<(), NssaError> {
|
||||||
// 1. Verify the transaction satisfies acceptance criteria
|
// 1. Verify the transaction satisfies acceptance criteria
|
||||||
let public_state_diff = tx.validate_and_produce_public_state_diff(self)?;
|
let public_state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?;
|
||||||
|
|
||||||
let message = tx.message();
|
let message = tx.message();
|
||||||
|
|
||||||
@ -338,7 +341,7 @@ pub mod tests {
|
|||||||
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||||
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
||||||
encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey},
|
encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey},
|
||||||
program::{PdaSeed, ProgramId},
|
program::{BlockId, PdaSeed, ProgramId, ValidityWindow},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -373,6 +376,7 @@ pub mod tests {
|
|||||||
self.insert_program(Program::amm());
|
self.insert_program(Program::amm());
|
||||||
self.insert_program(Program::claimer());
|
self.insert_program(Program::claimer());
|
||||||
self.insert_program(Program::changer_claimer());
|
self.insert_program(Program::changer_claimer());
|
||||||
|
self.insert_program(Program::validity_window());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,6 +505,7 @@ pub mod tests {
|
|||||||
);
|
);
|
||||||
this.insert(Program::token().id(), Program::token());
|
this.insert(Program::token().id(), Program::token());
|
||||||
this.insert(Program::amm().id(), Program::amm());
|
this.insert(Program::amm().id(), Program::amm());
|
||||||
|
this.insert(Program::ata().id(), Program::ata());
|
||||||
this
|
this
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -567,7 +572,7 @@ pub mod tests {
|
|||||||
let balance_to_move = 5;
|
let balance_to_move = 5;
|
||||||
|
|
||||||
let tx = transfer_transaction(from, &key, 0, to, balance_to_move);
|
let tx = transfer_transaction(from, &key, 0, to, balance_to_move);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
assert_eq!(state.get_account_by_id(from).balance, 95);
|
assert_eq!(state.get_account_by_id(from).balance, 95);
|
||||||
assert_eq!(state.get_account_by_id(to).balance, 5);
|
assert_eq!(state.get_account_by_id(to).balance, 5);
|
||||||
@ -588,7 +593,7 @@ pub mod tests {
|
|||||||
assert!(state.get_account_by_id(from).balance < balance_to_move);
|
assert!(state.get_account_by_id(from).balance < balance_to_move);
|
||||||
|
|
||||||
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
|
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
|
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
|
||||||
assert_eq!(state.get_account_by_id(from).balance, 100);
|
assert_eq!(state.get_account_by_id(from).balance, 100);
|
||||||
@ -612,7 +617,7 @@ pub mod tests {
|
|||||||
let balance_to_move = 8;
|
let balance_to_move = 8;
|
||||||
|
|
||||||
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
|
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
assert_eq!(state.get_account_by_id(from).balance, 192);
|
assert_eq!(state.get_account_by_id(from).balance, 192);
|
||||||
assert_eq!(state.get_account_by_id(to).balance, 108);
|
assert_eq!(state.get_account_by_id(to).balance, 108);
|
||||||
@ -632,10 +637,10 @@ pub mod tests {
|
|||||||
let balance_to_move = 5;
|
let balance_to_move = 5;
|
||||||
|
|
||||||
let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move);
|
let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
let balance_to_move = 3;
|
let balance_to_move = 3;
|
||||||
let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move);
|
let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
assert_eq!(state.get_account_by_id(account_id1).balance, 95);
|
assert_eq!(state.get_account_by_id(account_id1).balance, 95);
|
||||||
assert_eq!(state.get_account_by_id(account_id2).balance, 2);
|
assert_eq!(state.get_account_by_id(account_id2).balance, 2);
|
||||||
@ -657,7 +662,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -674,7 +679,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -691,7 +696,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -715,7 +720,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -739,7 +744,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -763,7 +768,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -787,7 +792,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -815,7 +820,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -840,7 +845,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -858,7 +863,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -887,7 +892,7 @@ pub mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -1080,7 +1085,7 @@ pub mod tests {
|
|||||||
assert!(!state.private_state.0.contains(&expected_new_commitment));
|
assert!(!state.private_state.0.contains(&expected_new_commitment));
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx)
|
.transition_from_privacy_preserving_transaction(&tx, 1)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let sender_post = state.get_account_by_id(sender_keys.account_id());
|
let sender_post = state.get_account_by_id(sender_keys.account_id());
|
||||||
@ -1150,7 +1155,7 @@ pub mod tests {
|
|||||||
assert!(!state.private_state.1.contains(&expected_new_nullifier));
|
assert!(!state.private_state.1.contains(&expected_new_nullifier));
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx)
|
.transition_from_privacy_preserving_transaction(&tx, 1)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(state.public_state, previous_public_state);
|
assert_eq!(state.public_state, previous_public_state);
|
||||||
@ -1214,7 +1219,7 @@ pub mod tests {
|
|||||||
assert!(!state.private_state.1.contains(&expected_new_nullifier));
|
assert!(!state.private_state.1.contains(&expected_new_nullifier));
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx)
|
.transition_from_privacy_preserving_transaction(&tx, 1)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let recipient_post = state.get_account_by_id(recipient_keys.account_id());
|
let recipient_post = state.get_account_by_id(recipient_keys.account_id());
|
||||||
@ -2142,7 +2147,7 @@ pub mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx)
|
.transition_from_privacy_preserving_transaction(&tx, 1)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let sender_private_account = Account {
|
let sender_private_account = Account {
|
||||||
@ -2160,7 +2165,7 @@ pub mod tests {
|
|||||||
&state,
|
&state,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = state.transition_from_privacy_preserving_transaction(&tx);
|
let result = state.transition_from_privacy_preserving_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
let NssaError::InvalidInput(error_message) = result.err().unwrap() else {
|
let NssaError::InvalidInput(error_message) = result.err().unwrap() else {
|
||||||
@ -2237,7 +2242,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let recipient_post = state.get_account_by_id(to);
|
let recipient_post = state.get_account_by_id(to);
|
||||||
|
|
||||||
@ -2280,7 +2285,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let from_post = state.get_account_by_id(from);
|
let from_post = state.get_account_by_id(from);
|
||||||
let to_post = state.get_account_by_id(to);
|
let to_post = state.get_account_by_id(to);
|
||||||
@ -2320,7 +2325,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
result,
|
result,
|
||||||
Err(NssaError::MaxChainedCallsDepthExceeded)
|
Err(NssaError::MaxChainedCallsDepthExceeded)
|
||||||
@ -2361,7 +2366,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let from_post = state.get_account_by_id(from);
|
let from_post = state.get_account_by_id(from);
|
||||||
let to_post = state.get_account_by_id(to);
|
let to_post = state.get_account_by_id(to);
|
||||||
@ -2417,7 +2422,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let from_post = state.get_account_by_id(from);
|
let from_post = state.get_account_by_id(from);
|
||||||
let to_post = state.get_account_by_id(to);
|
let to_post = state.get_account_by_id(to);
|
||||||
@ -2526,7 +2531,7 @@ pub mod tests {
|
|||||||
let transaction = PrivacyPreservingTransaction::new(message, witness_set);
|
let transaction = PrivacyPreservingTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&transaction)
|
.transition_from_privacy_preserving_transaction(&transaction, 1)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@ -2582,7 +2587,7 @@ pub mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
// Execution of winner's token holding account initialization
|
// Execution of winner's token holding account initialization
|
||||||
let instruction = token_core::Instruction::InitializeAccount;
|
let instruction = token_core::Instruction::InitializeAccount;
|
||||||
@ -2595,7 +2600,7 @@ pub mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
// Submit a solution to the pinata program to claim the prize
|
// Submit a solution to the pinata program to claim the prize
|
||||||
let solution: u128 = 989_106;
|
let solution: u128 = 989_106;
|
||||||
@ -2612,7 +2617,7 @@ pub mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id);
|
let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -2642,7 +2647,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -2688,7 +2693,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let res = state.transition_from_public_transaction(&tx);
|
let res = state.transition_from_public_transaction(&tx, 1);
|
||||||
assert!(matches!(res, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(res, Err(NssaError::InvalidProgramBehavior)));
|
||||||
|
|
||||||
let sender_post = state.get_account_by_id(sender_id);
|
let sender_post = state.get_account_by_id(sender_id);
|
||||||
@ -2757,7 +2762,7 @@ pub mod tests {
|
|||||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||||
|
|
||||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||||
let result = state.transition_from_privacy_preserving_transaction(&tx);
|
let result = state.transition_from_privacy_preserving_transaction(&tx, 1);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
|
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
|
||||||
@ -2810,7 +2815,7 @@ pub mod tests {
|
|||||||
// Claim should succeed
|
// Claim should succeed
|
||||||
assert!(
|
assert!(
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx)
|
.transition_from_privacy_preserving_transaction(&tx, 1)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2859,7 +2864,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
// Should succeed - no changes made, no claim needed
|
// Should succeed - no changes made, no claim needed
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
@ -2884,7 +2889,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx);
|
let result = state.transition_from_public_transaction(&tx, 1);
|
||||||
|
|
||||||
// Should fail - cannot modify data without claiming the account
|
// Should fail - cannot modify data without claiming the account
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
@ -2996,6 +3001,119 @@ pub mod tests {
|
|||||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 2; "inside range")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 0; "below range")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 1; "at lower bound")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 4; "above range")]
|
||||||
|
#[test_case::test_case((Some(1), None), 1; "lower bound only - at bound")]
|
||||||
|
#[test_case::test_case((Some(1), None), 10; "lower bound only - above")]
|
||||||
|
#[test_case::test_case((Some(1), None), 0; "lower bound only - below")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 3; "upper bound only - at bound")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 0; "upper bound only - below")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 4; "upper bound only - above")]
|
||||||
|
#[test_case::test_case((None, None), 0; "no bounds - always valid")]
|
||||||
|
#[test_case::test_case((None, None), 100; "no bounds - always valid 2")]
|
||||||
|
fn validity_window_works_in_public_transactions(
|
||||||
|
validity_window: (Option<BlockId>, Option<BlockId>),
|
||||||
|
block_id: BlockId,
|
||||||
|
) {
|
||||||
|
let validity_window: ValidityWindow = validity_window.try_into().unwrap();
|
||||||
|
let validity_window_program = Program::validity_window();
|
||||||
|
let account_keys = test_public_account_keys_1();
|
||||||
|
let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id());
|
||||||
|
let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs();
|
||||||
|
let tx = {
|
||||||
|
let account_ids = vec![pre.account_id];
|
||||||
|
let nonces = vec![];
|
||||||
|
let program_id = validity_window_program.id();
|
||||||
|
let message = public_transaction::Message::try_new(
|
||||||
|
program_id,
|
||||||
|
account_ids,
|
||||||
|
nonces,
|
||||||
|
validity_window,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
|
PublicTransaction::new(message, witness_set)
|
||||||
|
};
|
||||||
|
let result = state.transition_from_public_transaction(&tx, block_id);
|
||||||
|
let is_inside_validity_window = match (validity_window.start(), validity_window.end()) {
|
||||||
|
(Some(s), Some(e)) => s <= block_id && block_id < e,
|
||||||
|
(Some(s), None) => s <= block_id,
|
||||||
|
(None, Some(e)) => block_id < e,
|
||||||
|
(None, None) => true,
|
||||||
|
};
|
||||||
|
if is_inside_validity_window {
|
||||||
|
assert!(result.is_ok());
|
||||||
|
} else {
|
||||||
|
assert!(matches!(result, Err(NssaError::OutOfValidityWindow)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 2; "inside range")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 0; "below range")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 1; "at lower bound")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 4; "above range")]
|
||||||
|
#[test_case::test_case((Some(1), None), 1; "lower bound only - at bound")]
|
||||||
|
#[test_case::test_case((Some(1), None), 10; "lower bound only - above")]
|
||||||
|
#[test_case::test_case((Some(1), None), 0; "lower bound only - below")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 3; "upper bound only - at bound")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 0; "upper bound only - below")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 4; "upper bound only - above")]
|
||||||
|
#[test_case::test_case((None, None), 0; "no bounds - always valid")]
|
||||||
|
#[test_case::test_case((None, None), 100; "no bounds - always valid 2")]
|
||||||
|
fn validity_window_works_in_privacy_preserving_transactions(
|
||||||
|
validity_window: (Option<BlockId>, Option<BlockId>),
|
||||||
|
block_id: BlockId,
|
||||||
|
) {
|
||||||
|
let validity_window: ValidityWindow = validity_window.try_into().unwrap();
|
||||||
|
let validity_window_program = Program::validity_window();
|
||||||
|
let account_keys = test_private_account_keys_1();
|
||||||
|
let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk());
|
||||||
|
let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs();
|
||||||
|
let tx = {
|
||||||
|
let esk = [3; 32];
|
||||||
|
let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk());
|
||||||
|
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||||
|
|
||||||
|
let (output, proof) = circuit::execute_and_prove(
|
||||||
|
vec![pre],
|
||||||
|
Program::serialize_instruction(validity_window).unwrap(),
|
||||||
|
vec![2],
|
||||||
|
vec![(account_keys.npk(), shared_secret)],
|
||||||
|
vec![],
|
||||||
|
vec![None],
|
||||||
|
&validity_window_program.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let message = Message::try_from_circuit_output(
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
vec![(account_keys.npk(), account_keys.vpk(), epk)],
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||||
|
PrivacyPreservingTransaction::new(message, witness_set)
|
||||||
|
};
|
||||||
|
let result = state.transition_from_privacy_preserving_transaction(&tx, block_id);
|
||||||
|
let is_inside_validity_window = match (validity_window.start(), validity_window.end()) {
|
||||||
|
(Some(s), Some(e)) => s <= block_id && block_id < e,
|
||||||
|
(Some(s), None) => s <= block_id,
|
||||||
|
(None, Some(e)) => block_id < e,
|
||||||
|
(None, None) => true,
|
||||||
|
};
|
||||||
|
if is_inside_validity_window {
|
||||||
|
assert!(result.is_ok());
|
||||||
|
} else {
|
||||||
|
assert!(matches!(result, Err(NssaError::OutOfValidityWindow)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_serialization_roundtrip() {
|
fn state_serialization_roundtrip() {
|
||||||
let account_id_1 = AccountId::new([1; 32]);
|
let account_id_1 = AccountId::new([1; 32]);
|
||||||
|
|||||||
@ -13,5 +13,7 @@ token_core.workspace = true
|
|||||||
token_program.workspace = true
|
token_program.workspace = true
|
||||||
amm_core.workspace = true
|
amm_core.workspace = true
|
||||||
amm_program.workspace = true
|
amm_program.workspace = true
|
||||||
|
ata_core.workspace = true
|
||||||
|
ata_program.workspace = true
|
||||||
risc0-zkvm.workspace = true
|
risc0-zkvm.workspace = true
|
||||||
serde = { workspace = true, default-features = false }
|
serde = { workspace = true, default-features = false }
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
use std::num::NonZero;
|
use std::num::NonZero;
|
||||||
|
|
||||||
use amm_core::Instruction;
|
use amm_core::Instruction;
|
||||||
use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call};
|
use nssa_core::program::{ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let (
|
let (
|
||||||
@ -133,10 +133,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
write_nssa_outputs_with_chained_call(
|
ProgramOutput::new(instruction_words, pre_states_clone, post_states)
|
||||||
instruction_words,
|
.with_chained_calls(chained_calls)
|
||||||
pre_states_clone,
|
.write();
|
||||||
post_states,
|
|
||||||
chained_calls,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
62
program_methods/guest/src/bin/associated_token_account.rs
Normal file
62
program_methods/guest/src/bin/associated_token_account.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use ata_core::Instruction;
|
||||||
|
use nssa_core::program::{ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let (
|
||||||
|
ProgramInput {
|
||||||
|
pre_states,
|
||||||
|
instruction,
|
||||||
|
},
|
||||||
|
instruction_words,
|
||||||
|
) = read_nssa_inputs::<Instruction>();
|
||||||
|
|
||||||
|
let pre_states_clone = pre_states.clone();
|
||||||
|
|
||||||
|
let (post_states, chained_calls) = match instruction {
|
||||||
|
Instruction::Create { ata_program_id } => {
|
||||||
|
let [owner, token_definition, ata_account] = pre_states
|
||||||
|
.try_into()
|
||||||
|
.expect("Create instruction requires exactly three accounts");
|
||||||
|
ata_program::create::create_associated_token_account(
|
||||||
|
owner,
|
||||||
|
token_definition,
|
||||||
|
ata_account,
|
||||||
|
ata_program_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Transfer {
|
||||||
|
ata_program_id,
|
||||||
|
amount,
|
||||||
|
} => {
|
||||||
|
let [owner, sender_ata, recipient] = pre_states
|
||||||
|
.try_into()
|
||||||
|
.expect("Transfer instruction requires exactly three accounts");
|
||||||
|
ata_program::transfer::transfer_from_associated_token_account(
|
||||||
|
owner,
|
||||||
|
sender_ata,
|
||||||
|
recipient,
|
||||||
|
ata_program_id,
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Burn {
|
||||||
|
ata_program_id,
|
||||||
|
amount,
|
||||||
|
} => {
|
||||||
|
let [owner, holder_ata, token_definition] = pre_states
|
||||||
|
.try_into()
|
||||||
|
.expect("Burn instruction requires exactly three accounts");
|
||||||
|
ata_program::burn::burn_from_associated_token_account(
|
||||||
|
owner,
|
||||||
|
holder_ata,
|
||||||
|
token_definition,
|
||||||
|
ata_program_id,
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ProgramOutput::new(instruction_words, pre_states_clone, post_states)
|
||||||
|
.with_chained_calls(chained_calls)
|
||||||
|
.write();
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
program::{
|
program::{
|
||||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,5 +84,5 @@ fn main() {
|
|||||||
_ => panic!("invalid params"),
|
_ => panic!("invalid params"),
|
||||||
};
|
};
|
||||||
|
|
||||||
write_nssa_outputs(instruction_words, pre_states, post_states);
|
ProgramOutput::new(instruction_words, pre_states, post_states).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||||
|
|
||||||
const PRIZE: u128 = 150;
|
const PRIZE: u128 = 150;
|
||||||
@ -78,12 +78,13 @@ fn main() {
|
|||||||
.checked_add(PRIZE)
|
.checked_add(PRIZE)
|
||||||
.expect("Overflow when adding prize to winner");
|
.expect("Overflow when adding prize to winner");
|
||||||
|
|
||||||
write_nssa_outputs(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pinata, winner],
|
vec![pinata, winner],
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new_claimed_if_default(pinata_post),
|
AccountPostState::new_claimed_if_default(pinata_post),
|
||||||
AccountPostState::new(winner_post),
|
AccountPostState::new(winner_post),
|
||||||
],
|
],
|
||||||
);
|
)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::Data,
|
account::Data,
|
||||||
program::{
|
program::{
|
||||||
AccountPostState, ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs,
|
AccountPostState, ChainedCall, PdaSeed, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
write_nssa_outputs_with_chained_call,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||||
@ -97,7 +96,7 @@ fn main() {
|
|||||||
)
|
)
|
||||||
.with_pda_seeds(vec![PdaSeed::new([0; 32])]);
|
.with_pda_seeds(vec![PdaSeed::new([0; 32])]);
|
||||||
|
|
||||||
write_nssa_outputs_with_chained_call(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![
|
vec![
|
||||||
pinata_definition,
|
pinata_definition,
|
||||||
@ -109,6 +108,7 @@ fn main() {
|
|||||||
AccountPostState::new(pinata_token_holding_post),
|
AccountPostState::new(pinata_token_holding_post),
|
||||||
AccountPostState::new(winner_token_holding_post),
|
AccountPostState::new(winner_token_holding_post),
|
||||||
],
|
],
|
||||||
vec![chained_call],
|
)
|
||||||
);
|
.with_chained_calls(vec![chained_call])
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use nssa_core::{
|
|||||||
compute_digest_for_path,
|
compute_digest_for_path,
|
||||||
program::{
|
program::{
|
||||||
AccountPostState, ChainedCall, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, ProgramId,
|
AccountPostState, ChainedCall, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, ProgramId,
|
||||||
ProgramOutput, validate_execution,
|
ProgramOutput, ValidityWindow, validate_execution,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use risc0_zkvm::{guest::env, serde::to_vec};
|
use risc0_zkvm::{guest::env, serde::to_vec};
|
||||||
@ -20,11 +20,31 @@ use risc0_zkvm::{guest::env, serde::to_vec};
|
|||||||
struct ExecutionState {
|
struct ExecutionState {
|
||||||
pre_states: Vec<AccountWithMetadata>,
|
pre_states: Vec<AccountWithMetadata>,
|
||||||
post_states: HashMap<AccountId, Account>,
|
post_states: HashMap<AccountId, Account>,
|
||||||
|
validity_window: ValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionState {
|
impl ExecutionState {
|
||||||
/// Validate program outputs and derive the overall execution state.
|
/// Validate program outputs and derive the overall execution state.
|
||||||
pub fn derive_from_outputs(program_id: ProgramId, program_outputs: Vec<ProgramOutput>) -> Self {
|
pub fn derive_from_outputs(program_id: ProgramId, program_outputs: Vec<ProgramOutput>) -> Self {
|
||||||
|
let valid_from_id = program_outputs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|output| output.validity_window.start())
|
||||||
|
.max();
|
||||||
|
let valid_until_id = program_outputs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|output| output.validity_window.end())
|
||||||
|
.min();
|
||||||
|
|
||||||
|
let validity_window = (valid_from_id, valid_until_id).try_into().expect(
|
||||||
|
"There should be non empty intersection in the program output validity windows",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut execution_state = Self {
|
||||||
|
pre_states: Vec::new(),
|
||||||
|
post_states: HashMap::new(),
|
||||||
|
validity_window,
|
||||||
|
};
|
||||||
|
|
||||||
let Some(first_output) = program_outputs.first() else {
|
let Some(first_output) = program_outputs.first() else {
|
||||||
panic!("No program outputs provided");
|
panic!("No program outputs provided");
|
||||||
};
|
};
|
||||||
@ -37,11 +57,6 @@ impl ExecutionState {
|
|||||||
};
|
};
|
||||||
let mut chained_calls = VecDeque::from_iter([(initial_call, None)]);
|
let mut chained_calls = VecDeque::from_iter([(initial_call, None)]);
|
||||||
|
|
||||||
let mut execution_state = Self {
|
|
||||||
pre_states: Vec::new(),
|
|
||||||
post_states: HashMap::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut program_outputs_iter = program_outputs.into_iter();
|
let mut program_outputs_iter = program_outputs.into_iter();
|
||||||
let mut chain_calls_counter = 0;
|
let mut chain_calls_counter = 0;
|
||||||
|
|
||||||
@ -210,6 +225,7 @@ fn compute_circuit_output(
|
|||||||
ciphertexts: Vec::new(),
|
ciphertexts: Vec::new(),
|
||||||
new_commitments: Vec::new(),
|
new_commitments: Vec::new(),
|
||||||
new_nullifiers: Vec::new(),
|
new_nullifiers: Vec::new(),
|
||||||
|
validity_window: execution_state.validity_window,
|
||||||
};
|
};
|
||||||
|
|
||||||
let states_iter = execution_state.into_states_iter();
|
let states_iter = execution_state.into_states_iter();
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
//! Token program accepts [`Instruction`] as input, refer to the corresponding documentation
|
//! Token program accepts [`Instruction`] as input, refer to the corresponding documentation
|
||||||
//! for more details.
|
//! for more details.
|
||||||
|
|
||||||
use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
use token_program::core::Instruction;
|
use token_program::core::Instruction;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -81,5 +81,5 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
write_nssa_outputs(instruction_words, pre_states_clone, post_states);
|
ProgramOutput::new(instruction_words, pre_states_clone, post_states).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2733,7 +2733,7 @@ fn simple_amm_remove() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -2813,7 +2813,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -2897,7 +2897,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_init_user_lp() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -2969,7 +2969,7 @@ fn simple_amm_new_definition_uninitialized_pool() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -3031,7 +3031,7 @@ fn simple_amm_add() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -3088,7 +3088,7 @@ fn simple_amm_swap_1() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -3138,7 +3138,7 @@ fn simple_amm_swap_2() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx).unwrap();
|
state.transition_from_public_transaction(&tx, 1).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
|
|||||||
10
programs/associated_token_account/Cargo.toml
Normal file
10
programs/associated_token_account/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "ata_program"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = { workspace = true }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nssa_core.workspace = true
|
||||||
|
token_core.workspace = true
|
||||||
|
ata_core.workspace = true
|
||||||
10
programs/associated_token_account/core/Cargo.toml
Normal file
10
programs/associated_token_account/core/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "ata_core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = { workspace = true }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nssa_core.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
risc0-zkvm.workspace = true
|
||||||
82
programs/associated_token_account/core/src/lib.rs
Normal file
82
programs/associated_token_account/core/src/lib.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
pub use nssa_core::program::PdaSeed;
|
||||||
|
use nssa_core::{
|
||||||
|
account::{AccountId, AccountWithMetadata},
|
||||||
|
program::ProgramId,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum Instruction {
|
||||||
|
/// Create the Associated Token Account for (owner, definition).
|
||||||
|
/// Idempotent: no-op if the account already exists.
|
||||||
|
///
|
||||||
|
/// Required accounts (3):
|
||||||
|
/// - Owner account
|
||||||
|
/// - Token definition account
|
||||||
|
/// - Associated token account (default/uninitialized, or already initialized)
|
||||||
|
///
|
||||||
|
/// `token_program_id` is derived from `token_definition.account.program_owner`.
|
||||||
|
Create { ata_program_id: ProgramId },
|
||||||
|
|
||||||
|
/// Transfer tokens FROM owner's ATA to a recipient holding account.
|
||||||
|
/// Uses PDA seeds to authorize the ATA in the chained Token::Transfer call.
|
||||||
|
///
|
||||||
|
/// Required accounts (3):
|
||||||
|
/// - Owner account (authorized)
|
||||||
|
/// - Sender ATA (owner's token holding)
|
||||||
|
/// - Recipient token holding (any account; auto-created if default)
|
||||||
|
///
|
||||||
|
/// `token_program_id` is derived from `sender_ata.account.program_owner`.
|
||||||
|
Transfer {
|
||||||
|
ata_program_id: ProgramId,
|
||||||
|
amount: u128,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Burn tokens FROM owner's ATA.
|
||||||
|
/// Uses PDA seeds to authorize the ATA in the chained Token::Burn call.
|
||||||
|
///
|
||||||
|
/// Required accounts (3):
|
||||||
|
/// - Owner account (authorized)
|
||||||
|
/// - Owner's ATA (the holding to burn from)
|
||||||
|
/// - Token definition account
|
||||||
|
///
|
||||||
|
/// `token_program_id` is derived from `holder_ata.account.program_owner`.
|
||||||
|
Burn {
|
||||||
|
ata_program_id: ProgramId,
|
||||||
|
amount: u128,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute_ata_seed(owner_id: AccountId, definition_id: AccountId) -> PdaSeed {
|
||||||
|
use risc0_zkvm::sha::{Impl, Sha256};
|
||||||
|
let mut bytes = [0u8; 64];
|
||||||
|
bytes[0..32].copy_from_slice(&owner_id.to_bytes());
|
||||||
|
bytes[32..64].copy_from_slice(&definition_id.to_bytes());
|
||||||
|
PdaSeed::new(
|
||||||
|
Impl::hash_bytes(&bytes)
|
||||||
|
.as_bytes()
|
||||||
|
.try_into()
|
||||||
|
.expect("Hash output must be exactly 32 bytes long"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_associated_token_account_id(ata_program_id: &ProgramId, seed: &PdaSeed) -> AccountId {
|
||||||
|
AccountId::from((ata_program_id, seed))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify the ATA's address matches `(ata_program_id, owner, definition)` and return
|
||||||
|
/// the [`PdaSeed`] for use in chained calls.
|
||||||
|
pub fn verify_ata_and_get_seed(
|
||||||
|
ata_account: &AccountWithMetadata,
|
||||||
|
owner: &AccountWithMetadata,
|
||||||
|
definition_id: AccountId,
|
||||||
|
ata_program_id: ProgramId,
|
||||||
|
) -> PdaSeed {
|
||||||
|
let seed = compute_ata_seed(owner.account_id, definition_id);
|
||||||
|
let expected_id = get_associated_token_account_id(&ata_program_id, &seed);
|
||||||
|
assert_eq!(
|
||||||
|
ata_account.account_id, expected_id,
|
||||||
|
"ATA account ID does not match expected derivation"
|
||||||
|
);
|
||||||
|
seed
|
||||||
|
}
|
||||||
39
programs/associated_token_account/src/burn.rs
Normal file
39
programs/associated_token_account/src/burn.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use nssa_core::{
|
||||||
|
account::AccountWithMetadata,
|
||||||
|
program::{AccountPostState, ChainedCall, ProgramId},
|
||||||
|
};
|
||||||
|
use token_core::TokenHolding;
|
||||||
|
|
||||||
|
pub fn burn_from_associated_token_account(
|
||||||
|
owner: AccountWithMetadata,
|
||||||
|
holder_ata: AccountWithMetadata,
|
||||||
|
token_definition: AccountWithMetadata,
|
||||||
|
ata_program_id: ProgramId,
|
||||||
|
amount: u128,
|
||||||
|
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
||||||
|
let token_program_id = holder_ata.account.program_owner;
|
||||||
|
assert!(owner.is_authorized, "Owner authorization is missing");
|
||||||
|
let definition_id = TokenHolding::try_from(&holder_ata.account.data)
|
||||||
|
.expect("Holder ATA must hold a valid token")
|
||||||
|
.definition_id();
|
||||||
|
let seed =
|
||||||
|
ata_core::verify_ata_and_get_seed(&holder_ata, &owner, definition_id, ata_program_id);
|
||||||
|
|
||||||
|
let post_states = vec![
|
||||||
|
AccountPostState::new(owner.account.clone()),
|
||||||
|
AccountPostState::new(holder_ata.account.clone()),
|
||||||
|
AccountPostState::new(token_definition.account.clone()),
|
||||||
|
];
|
||||||
|
let mut holder_ata_auth = holder_ata.clone();
|
||||||
|
holder_ata_auth.is_authorized = true;
|
||||||
|
|
||||||
|
let chained_call = ChainedCall::new(
|
||||||
|
token_program_id,
|
||||||
|
vec![token_definition.clone(), holder_ata_auth],
|
||||||
|
&token_core::Instruction::Burn {
|
||||||
|
amount_to_burn: amount,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_pda_seeds(vec![seed]);
|
||||||
|
(post_states, vec![chained_call])
|
||||||
|
}
|
||||||
44
programs/associated_token_account/src/create.rs
Normal file
44
programs/associated_token_account/src/create.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use nssa_core::{
|
||||||
|
account::{Account, AccountWithMetadata},
|
||||||
|
program::{AccountPostState, ChainedCall, ProgramId},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create_associated_token_account(
|
||||||
|
owner: AccountWithMetadata,
|
||||||
|
token_definition: AccountWithMetadata,
|
||||||
|
ata_account: AccountWithMetadata,
|
||||||
|
ata_program_id: ProgramId,
|
||||||
|
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
||||||
|
// No authorization check needed: create is idempotent, so anyone can call it safely.
|
||||||
|
let token_program_id = token_definition.account.program_owner;
|
||||||
|
ata_core::verify_ata_and_get_seed(
|
||||||
|
&ata_account,
|
||||||
|
&owner,
|
||||||
|
token_definition.account_id,
|
||||||
|
ata_program_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Idempotent: already initialized → no-op
|
||||||
|
if ata_account.account != Account::default() {
|
||||||
|
return (
|
||||||
|
vec![
|
||||||
|
AccountPostState::new_claimed_if_default(owner.account.clone()),
|
||||||
|
AccountPostState::new(token_definition.account.clone()),
|
||||||
|
AccountPostState::new(ata_account.account.clone()),
|
||||||
|
],
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let post_states = vec![
|
||||||
|
AccountPostState::new_claimed_if_default(owner.account.clone()),
|
||||||
|
AccountPostState::new(token_definition.account.clone()),
|
||||||
|
AccountPostState::new(ata_account.account.clone()),
|
||||||
|
];
|
||||||
|
let chained_call = ChainedCall::new(
|
||||||
|
token_program_id,
|
||||||
|
vec![token_definition.clone(), ata_account.clone()],
|
||||||
|
&token_core::Instruction::InitializeAccount,
|
||||||
|
);
|
||||||
|
(post_states, vec![chained_call])
|
||||||
|
}
|
||||||
10
programs/associated_token_account/src/lib.rs
Normal file
10
programs/associated_token_account/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//! The Associated Token Account Program implementation.
|
||||||
|
|
||||||
|
pub use ata_core as core;
|
||||||
|
|
||||||
|
pub mod burn;
|
||||||
|
pub mod create;
|
||||||
|
pub mod transfer;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
153
programs/associated_token_account/src/tests.rs
Normal file
153
programs/associated_token_account/src/tests.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
use ata_core::{compute_ata_seed, get_associated_token_account_id};
|
||||||
|
use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data};
|
||||||
|
use token_core::{TokenDefinition, TokenHolding};
|
||||||
|
|
||||||
|
const ATA_PROGRAM_ID: nssa_core::program::ProgramId = [1u32; 8];
|
||||||
|
const TOKEN_PROGRAM_ID: nssa_core::program::ProgramId = [2u32; 8];
|
||||||
|
|
||||||
|
fn owner_id() -> AccountId {
|
||||||
|
AccountId::new([0x01u8; 32])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn definition_id() -> AccountId {
|
||||||
|
AccountId::new([0x02u8; 32])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ata_id() -> AccountId {
|
||||||
|
get_associated_token_account_id(
|
||||||
|
&ATA_PROGRAM_ID,
|
||||||
|
&compute_ata_seed(owner_id(), definition_id()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn owner_account() -> AccountWithMetadata {
|
||||||
|
AccountWithMetadata {
|
||||||
|
account: Account::default(),
|
||||||
|
is_authorized: true,
|
||||||
|
account_id: owner_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn definition_account() -> AccountWithMetadata {
|
||||||
|
AccountWithMetadata {
|
||||||
|
account: Account {
|
||||||
|
program_owner: TOKEN_PROGRAM_ID,
|
||||||
|
balance: 0,
|
||||||
|
data: Data::from(&TokenDefinition::Fungible {
|
||||||
|
name: "TEST".to_string(),
|
||||||
|
total_supply: 1000,
|
||||||
|
metadata_id: None,
|
||||||
|
}),
|
||||||
|
nonce: nssa_core::account::Nonce(0),
|
||||||
|
},
|
||||||
|
is_authorized: false,
|
||||||
|
account_id: definition_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uninitialized_ata_account() -> AccountWithMetadata {
|
||||||
|
AccountWithMetadata {
|
||||||
|
account: Account::default(),
|
||||||
|
is_authorized: false,
|
||||||
|
account_id: ata_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialized_ata_account() -> AccountWithMetadata {
|
||||||
|
AccountWithMetadata {
|
||||||
|
account: Account {
|
||||||
|
program_owner: TOKEN_PROGRAM_ID,
|
||||||
|
balance: 0,
|
||||||
|
data: Data::from(&TokenHolding::Fungible {
|
||||||
|
definition_id: definition_id(),
|
||||||
|
balance: 100,
|
||||||
|
}),
|
||||||
|
nonce: nssa_core::account::Nonce(0),
|
||||||
|
},
|
||||||
|
is_authorized: false,
|
||||||
|
account_id: ata_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_emits_chained_call_for_uninitialized_ata() {
|
||||||
|
let (post_states, chained_calls) = crate::create::create_associated_token_account(
|
||||||
|
owner_account(),
|
||||||
|
definition_account(),
|
||||||
|
uninitialized_ata_account(),
|
||||||
|
ATA_PROGRAM_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(post_states.len(), 3);
|
||||||
|
assert_eq!(chained_calls.len(), 1);
|
||||||
|
assert_eq!(chained_calls[0].program_id, TOKEN_PROGRAM_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_is_idempotent_for_initialized_ata() {
|
||||||
|
let (post_states, chained_calls) = crate::create::create_associated_token_account(
|
||||||
|
owner_account(),
|
||||||
|
definition_account(),
|
||||||
|
initialized_ata_account(),
|
||||||
|
ATA_PROGRAM_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(post_states.len(), 3);
|
||||||
|
assert!(
|
||||||
|
chained_calls.is_empty(),
|
||||||
|
"Should emit no chained call for already-initialized ATA"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "ATA account ID does not match expected derivation")]
|
||||||
|
fn create_panics_on_wrong_ata_address() {
|
||||||
|
let wrong_ata = AccountWithMetadata {
|
||||||
|
account: Account::default(),
|
||||||
|
is_authorized: false,
|
||||||
|
account_id: AccountId::new([0xFFu8; 32]),
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::create::create_associated_token_account(
|
||||||
|
owner_account(),
|
||||||
|
definition_account(),
|
||||||
|
wrong_ata,
|
||||||
|
ATA_PROGRAM_ID,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_associated_token_account_id_is_deterministic() {
|
||||||
|
let seed = compute_ata_seed(owner_id(), definition_id());
|
||||||
|
let id1 = get_associated_token_account_id(&ATA_PROGRAM_ID, &seed);
|
||||||
|
let id2 = get_associated_token_account_id(&ATA_PROGRAM_ID, &seed);
|
||||||
|
assert_eq!(id1, id2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_associated_token_account_id_differs_by_owner() {
|
||||||
|
let other_owner = AccountId::new([0x99u8; 32]);
|
||||||
|
let id1 = get_associated_token_account_id(
|
||||||
|
&ATA_PROGRAM_ID,
|
||||||
|
&compute_ata_seed(owner_id(), definition_id()),
|
||||||
|
);
|
||||||
|
let id2 = get_associated_token_account_id(
|
||||||
|
&ATA_PROGRAM_ID,
|
||||||
|
&compute_ata_seed(other_owner, definition_id()),
|
||||||
|
);
|
||||||
|
assert_ne!(id1, id2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_associated_token_account_id_differs_by_definition() {
|
||||||
|
let other_def = AccountId::new([0x99u8; 32]);
|
||||||
|
let id1 = get_associated_token_account_id(
|
||||||
|
&ATA_PROGRAM_ID,
|
||||||
|
&compute_ata_seed(owner_id(), definition_id()),
|
||||||
|
);
|
||||||
|
let id2 =
|
||||||
|
get_associated_token_account_id(&ATA_PROGRAM_ID, &compute_ata_seed(owner_id(), other_def));
|
||||||
|
assert_ne!(id1, id2);
|
||||||
|
}
|
||||||
39
programs/associated_token_account/src/transfer.rs
Normal file
39
programs/associated_token_account/src/transfer.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use nssa_core::{
|
||||||
|
account::AccountWithMetadata,
|
||||||
|
program::{AccountPostState, ChainedCall, ProgramId},
|
||||||
|
};
|
||||||
|
use token_core::TokenHolding;
|
||||||
|
|
||||||
|
pub fn transfer_from_associated_token_account(
|
||||||
|
owner: AccountWithMetadata,
|
||||||
|
sender_ata: AccountWithMetadata,
|
||||||
|
recipient: AccountWithMetadata,
|
||||||
|
ata_program_id: ProgramId,
|
||||||
|
amount: u128,
|
||||||
|
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
||||||
|
let token_program_id = sender_ata.account.program_owner;
|
||||||
|
assert!(owner.is_authorized, "Owner authorization is missing");
|
||||||
|
let definition_id = TokenHolding::try_from(&sender_ata.account.data)
|
||||||
|
.expect("Sender ATA must hold a valid token")
|
||||||
|
.definition_id();
|
||||||
|
let seed =
|
||||||
|
ata_core::verify_ata_and_get_seed(&sender_ata, &owner, definition_id, ata_program_id);
|
||||||
|
|
||||||
|
let post_states = vec![
|
||||||
|
AccountPostState::new(owner.account.clone()),
|
||||||
|
AccountPostState::new(sender_ata.account.clone()),
|
||||||
|
AccountPostState::new(recipient.account.clone()),
|
||||||
|
];
|
||||||
|
let mut sender_ata_auth = sender_ata.clone();
|
||||||
|
sender_ata_auth.is_authorized = true;
|
||||||
|
|
||||||
|
let chained_call = ChainedCall::new(
|
||||||
|
token_program_id,
|
||||||
|
vec![sender_ata_auth, recipient.clone()],
|
||||||
|
&token_core::Instruction::Transfer {
|
||||||
|
amount_to_transfer: amount,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_pda_seeds(vec![seed]);
|
||||||
|
(post_states, vec![chained_call])
|
||||||
|
}
|
||||||
@ -14,8 +14,8 @@ common.workspace = true
|
|||||||
storage.workspace = true
|
storage.workspace = true
|
||||||
mempool.workspace = true
|
mempool.workspace = true
|
||||||
bedrock_client.workspace = true
|
bedrock_client.workspace = true
|
||||||
|
testnet_initial_state.workspace = true
|
||||||
|
|
||||||
base58.workspace = true
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|||||||
@ -8,13 +8,11 @@ use std::{
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bedrock_client::BackoffConfig;
|
use bedrock_client::BackoffConfig;
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
use common::{
|
use common::config::BasicAuth;
|
||||||
block::{AccountInitialData, CommitmentsInitialData},
|
|
||||||
config::BasicAuth,
|
|
||||||
};
|
|
||||||
use humantime_serde;
|
use humantime_serde;
|
||||||
use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use testnet_initial_state::{PrivateAccountPublicInitialData, PublicAccountPublicInitialData};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
// TODO: Provide default values
|
// TODO: Provide default values
|
||||||
@ -39,16 +37,16 @@ pub struct SequencerConfig {
|
|||||||
/// Interval in which pending blocks are retried.
|
/// Interval in which pending blocks are retried.
|
||||||
#[serde(with = "humantime_serde")]
|
#[serde(with = "humantime_serde")]
|
||||||
pub retry_pending_blocks_timeout: Duration,
|
pub retry_pending_blocks_timeout: Duration,
|
||||||
/// List of initial accounts data.
|
|
||||||
pub initial_accounts: Vec<AccountInitialData>,
|
|
||||||
/// List of initial commitments.
|
|
||||||
pub initial_commitments: Vec<CommitmentsInitialData>,
|
|
||||||
/// Sequencer own signing key.
|
/// Sequencer own signing key.
|
||||||
pub signing_key: [u8; 32],
|
pub signing_key: [u8; 32],
|
||||||
/// Bedrock configuration options.
|
/// Bedrock configuration options.
|
||||||
pub bedrock_config: BedrockConfig,
|
pub bedrock_config: BedrockConfig,
|
||||||
/// Indexer RPC URL.
|
/// Indexer RPC URL.
|
||||||
pub indexer_rpc_url: Url,
|
pub indexer_rpc_url: Url,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub initial_public_accounts: Option<Vec<PublicAccountPublicInitialData>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub initial_private_accounts: Option<Vec<PrivateAccountPublicInitialData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@ -15,7 +15,9 @@ use logos_blockchain_key_management_system_service::keys::{ED25519_SECRET_KEY_SI
|
|||||||
use mempool::{MemPool, MemPoolHandle};
|
use mempool::{MemPool, MemPoolHandle};
|
||||||
#[cfg(feature = "mock")]
|
#[cfg(feature = "mock")]
|
||||||
pub use mock::SequencerCoreWithMockClients;
|
pub use mock::SequencerCoreWithMockClients;
|
||||||
|
use nssa::V03State;
|
||||||
pub use storage::error::DbError;
|
pub use storage::error::DbError;
|
||||||
|
use testnet_initial_state::initial_state;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block_settlement_client::{BlockSettlementClient, BlockSettlementClientTrait, MsgId},
|
block_settlement_client::{BlockSettlementClient, BlockSettlementClientTrait, MsgId},
|
||||||
@ -98,30 +100,48 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
state
|
state
|
||||||
} else {
|
} else {
|
||||||
info!(
|
info!(
|
||||||
"No database found when starting the sequencer. Creating a fresh new with the initial data in config"
|
"No database found when starting the sequencer. Creating a fresh new with the initial data"
|
||||||
);
|
);
|
||||||
let initial_commitments: Vec<nssa_core::Commitment> = config
|
|
||||||
.initial_commitments
|
|
||||||
.iter()
|
|
||||||
.map(|init_comm_data| {
|
|
||||||
let npk = &init_comm_data.npk;
|
|
||||||
|
|
||||||
let mut acc = init_comm_data.account.clone();
|
let initial_commitments: Option<Vec<nssa_core::Commitment>> = config
|
||||||
|
.initial_private_accounts
|
||||||
|
.clone()
|
||||||
|
.map(|initial_commitments| {
|
||||||
|
initial_commitments
|
||||||
|
.iter()
|
||||||
|
.map(|init_comm_data| {
|
||||||
|
let npk = &init_comm_data.npk;
|
||||||
|
|
||||||
acc.program_owner =
|
let mut acc = init_comm_data.account.clone();
|
||||||
nssa::program::Program::authenticated_transfer_program().id();
|
|
||||||
|
|
||||||
nssa_core::Commitment::new(npk, &acc)
|
acc.program_owner =
|
||||||
})
|
nssa::program::Program::authenticated_transfer_program().id();
|
||||||
.collect();
|
|
||||||
|
|
||||||
let init_accs: Vec<(nssa::AccountId, u128)> = config
|
nssa_core::Commitment::new(npk, &acc)
|
||||||
.initial_accounts
|
})
|
||||||
.iter()
|
.collect()
|
||||||
.map(|acc_data| (acc_data.account_id, acc_data.balance))
|
});
|
||||||
.collect();
|
|
||||||
|
|
||||||
nssa::V03State::new_with_genesis_accounts(&init_accs, &initial_commitments)
|
let init_accs: Option<Vec<(nssa::AccountId, u128)>> = config
|
||||||
|
.initial_public_accounts
|
||||||
|
.clone()
|
||||||
|
.map(|initial_accounts| {
|
||||||
|
initial_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|acc_data| (acc_data.account_id, acc_data.balance))
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
// If initial commitments or accounts are present in config, need to construct state
|
||||||
|
// from them
|
||||||
|
if initial_commitments.is_some() || init_accs.is_some() {
|
||||||
|
V03State::new_with_genesis_accounts(
|
||||||
|
&init_accs.unwrap_or_default(),
|
||||||
|
&initial_commitments.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
initial_state()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "testnet")]
|
#[cfg(feature = "testnet")]
|
||||||
@ -147,10 +167,12 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
tx: NSSATransaction,
|
tx: NSSATransaction,
|
||||||
) -> Result<NSSATransaction, nssa::error::NssaError> {
|
) -> Result<NSSATransaction, nssa::error::NssaError> {
|
||||||
match &tx {
|
match &tx {
|
||||||
NSSATransaction::Public(tx) => self.state.transition_from_public_transaction(tx),
|
NSSATransaction::Public(tx) => self
|
||||||
|
.state
|
||||||
|
.transition_from_public_transaction(tx, self.next_block_id()),
|
||||||
NSSATransaction::PrivacyPreserving(tx) => self
|
NSSATransaction::PrivacyPreserving(tx) => self
|
||||||
.state
|
.state
|
||||||
.transition_from_privacy_preserving_transaction(tx),
|
.transition_from_privacy_preserving_transaction(tx, self.next_block_id()),
|
||||||
NSSATransaction::ProgramDeployment(tx) => self
|
NSSATransaction::ProgramDeployment(tx) => self
|
||||||
.state
|
.state
|
||||||
.transition_from_program_deployment_transaction(tx),
|
.transition_from_program_deployment_transaction(tx),
|
||||||
@ -184,10 +206,7 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
) -> Result<(SignedMantleTx, MsgId)> {
|
) -> Result<(SignedMantleTx, MsgId)> {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
let new_block_height = self
|
let new_block_height = self.next_block_id();
|
||||||
.chain_height
|
|
||||||
.checked_add(1)
|
|
||||||
.with_context(|| format!("Max block height reached: {}", self.chain_height))?;
|
|
||||||
|
|
||||||
let mut valid_transactions = vec![];
|
let mut valid_transactions = vec![];
|
||||||
|
|
||||||
@ -334,6 +353,12 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
pub fn indexer_client(&self) -> IC {
|
pub fn indexer_client(&self) -> IC {
|
||||||
self.indexer_client.clone()
|
self.indexer_client.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_block_id(&self) -> u64 {
|
||||||
|
self.chain_height
|
||||||
|
.checked_add(1)
|
||||||
|
.unwrap_or_else(|| panic!("Max block height reached: {}", self.chain_height))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load signing key from file or generate a new one if it doesn't exist.
|
/// Load signing key from file or generate a new one if it doesn't exist.
|
||||||
@ -363,26 +388,20 @@ fn load_or_create_signing_key(path: &Path) -> Result<Ed25519Key> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
#![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")]
|
#![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")]
|
||||||
|
|
||||||
use std::{pin::pin, str::FromStr as _, time::Duration};
|
use std::{pin::pin, time::Duration};
|
||||||
|
|
||||||
use base58::ToBase58 as _;
|
|
||||||
use bedrock_client::BackoffConfig;
|
use bedrock_client::BackoffConfig;
|
||||||
use common::{
|
use common::{test_utils::sequencer_sign_key_for_testing, transaction::NSSATransaction};
|
||||||
block::AccountInitialData, test_utils::sequencer_sign_key_for_testing,
|
|
||||||
transaction::NSSATransaction,
|
|
||||||
};
|
|
||||||
use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
||||||
use mempool::MemPoolHandle;
|
use mempool::MemPoolHandle;
|
||||||
use nssa::{AccountId, PrivateKey};
|
use testnet_initial_state::{initial_accounts, initial_pub_accounts_private_keys};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{BedrockConfig, SequencerConfig},
|
config::{BedrockConfig, SequencerConfig},
|
||||||
mock::SequencerCoreWithMockClients,
|
mock::SequencerCoreWithMockClients,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn setup_sequencer_config_variable_initial_accounts(
|
fn setup_sequencer_config() -> SequencerConfig {
|
||||||
initial_accounts: Vec<AccountInitialData>,
|
|
||||||
) -> SequencerConfig {
|
|
||||||
let tempdir = tempfile::tempdir().unwrap();
|
let tempdir = tempfile::tempdir().unwrap();
|
||||||
let home = tempdir.path().to_path_buf();
|
let home = tempdir.path().to_path_buf();
|
||||||
|
|
||||||
@ -394,8 +413,6 @@ mod tests {
|
|||||||
max_block_size: bytesize::ByteSize::mib(1),
|
max_block_size: bytesize::ByteSize::mib(1),
|
||||||
mempool_max_size: 10000,
|
mempool_max_size: 10000,
|
||||||
block_create_timeout: Duration::from_secs(1),
|
block_create_timeout: Duration::from_secs(1),
|
||||||
initial_accounts,
|
|
||||||
initial_commitments: vec![],
|
|
||||||
signing_key: *sequencer_sign_key_for_testing().value(),
|
signing_key: *sequencer_sign_key_for_testing().value(),
|
||||||
bedrock_config: BedrockConfig {
|
bedrock_config: BedrockConfig {
|
||||||
backoff: BackoffConfig {
|
backoff: BackoffConfig {
|
||||||
@ -406,43 +423,19 @@ mod tests {
|
|||||||
node_url: "http://not-used-in-unit-tests".parse().unwrap(),
|
node_url: "http://not-used-in-unit-tests".parse().unwrap(),
|
||||||
auth: None,
|
auth: None,
|
||||||
},
|
},
|
||||||
retry_pending_blocks_timeout: Duration::from_secs(60 * 4),
|
retry_pending_blocks_timeout: Duration::from_mins(4),
|
||||||
indexer_rpc_url: "ws://localhost:8779".parse().unwrap(),
|
indexer_rpc_url: "ws://localhost:8779".parse().unwrap(),
|
||||||
|
initial_public_accounts: None,
|
||||||
|
initial_private_accounts: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_sequencer_config() -> SequencerConfig {
|
|
||||||
let acc1_account_id: Vec<u8> = vec![
|
|
||||||
148, 179, 206, 253, 199, 51, 82, 86, 232, 2, 152, 122, 80, 243, 54, 207, 237, 112, 83,
|
|
||||||
153, 44, 59, 204, 49, 128, 84, 160, 227, 216, 149, 97, 102,
|
|
||||||
];
|
|
||||||
|
|
||||||
let acc2_account_id: Vec<u8> = vec![
|
|
||||||
30, 145, 107, 3, 207, 73, 192, 230, 160, 63, 238, 207, 18, 69, 54, 216, 103, 244, 92,
|
|
||||||
94, 124, 248, 42, 16, 141, 19, 119, 18, 14, 226, 140, 204,
|
|
||||||
];
|
|
||||||
|
|
||||||
let initial_acc1 = AccountInitialData {
|
|
||||||
account_id: AccountId::from_str(&acc1_account_id.to_base58()).unwrap(),
|
|
||||||
balance: 10000,
|
|
||||||
};
|
|
||||||
|
|
||||||
let initial_acc2 = AccountInitialData {
|
|
||||||
account_id: AccountId::from_str(&acc2_account_id.to_base58()).unwrap(),
|
|
||||||
balance: 20000,
|
|
||||||
};
|
|
||||||
|
|
||||||
let initial_accounts = vec![initial_acc1, initial_acc2];
|
|
||||||
|
|
||||||
setup_sequencer_config_variable_initial_accounts(initial_accounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_signing_key_for_account1() -> nssa::PrivateKey {
|
fn create_signing_key_for_account1() -> nssa::PrivateKey {
|
||||||
nssa::PrivateKey::try_new([1; 32]).unwrap()
|
initial_pub_accounts_private_keys()[0].pub_sign_key.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_signing_key_for_account2() -> nssa::PrivateKey {
|
fn create_signing_key_for_account2() -> nssa::PrivateKey {
|
||||||
nssa::PrivateKey::try_new([2; 32]).unwrap()
|
initial_pub_accounts_private_keys()[1].pub_sign_key.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn common_setup() -> (SequencerCoreWithMockClients, MemPoolHandle<NSSATransaction>) {
|
async fn common_setup() -> (SequencerCoreWithMockClients, MemPoolHandle<NSSATransaction>) {
|
||||||
@ -475,8 +468,8 @@ mod tests {
|
|||||||
assert_eq!(sequencer.chain_height, config.genesis_id);
|
assert_eq!(sequencer.chain_height, config.genesis_id);
|
||||||
assert_eq!(sequencer.sequencer_config.max_num_tx_in_block, 10);
|
assert_eq!(sequencer.sequencer_config.max_num_tx_in_block, 10);
|
||||||
|
|
||||||
let acc1_account_id = config.initial_accounts[0].account_id;
|
let acc1_account_id = initial_accounts()[0].account_id;
|
||||||
let acc2_account_id = config.initial_accounts[1].account_id;
|
let acc2_account_id = initial_accounts()[1].account_id;
|
||||||
|
|
||||||
let balance_acc_1 = sequencer.state.get_account_by_id(acc1_account_id).balance;
|
let balance_acc_1 = sequencer.state.get_account_by_id(acc1_account_id).balance;
|
||||||
let balance_acc_2 = sequencer.state.get_account_by_id(acc2_account_id).balance;
|
let balance_acc_2 = sequencer.state.get_account_by_id(acc2_account_id).balance;
|
||||||
@ -485,47 +478,6 @@ mod tests {
|
|||||||
assert_eq!(20000, balance_acc_2);
|
assert_eq!(20000, balance_acc_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn start_different_intial_accounts_balances() {
|
|
||||||
let acc1_account_id: Vec<u8> = vec![
|
|
||||||
27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24,
|
|
||||||
52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143,
|
|
||||||
];
|
|
||||||
|
|
||||||
let acc2_account_id: Vec<u8> = vec![
|
|
||||||
77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217, 234,
|
|
||||||
216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102,
|
|
||||||
];
|
|
||||||
|
|
||||||
let initial_acc1 = AccountInitialData {
|
|
||||||
account_id: AccountId::from_str(&acc1_account_id.to_base58()).unwrap(),
|
|
||||||
balance: 10000,
|
|
||||||
};
|
|
||||||
|
|
||||||
let initial_acc2 = AccountInitialData {
|
|
||||||
account_id: AccountId::from_str(&acc2_account_id.to_base58()).unwrap(),
|
|
||||||
balance: 20000,
|
|
||||||
};
|
|
||||||
|
|
||||||
let initial_accounts = vec![initial_acc1, initial_acc2];
|
|
||||||
|
|
||||||
let config = setup_sequencer_config_variable_initial_accounts(initial_accounts);
|
|
||||||
let (sequencer, _mempool_handle) =
|
|
||||||
SequencerCoreWithMockClients::start_from_config(config.clone()).await;
|
|
||||||
|
|
||||||
let acc1_account_id = config.initial_accounts[0].account_id;
|
|
||||||
let acc2_account_id = config.initial_accounts[1].account_id;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
10000,
|
|
||||||
sequencer.state.get_account_by_id(acc1_account_id).balance
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
20000,
|
|
||||||
sequencer.state.get_account_by_id(acc2_account_id).balance
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_pre_check_pass() {
|
fn transaction_pre_check_pass() {
|
||||||
let tx = common::test_utils::produce_dummy_empty_transaction();
|
let tx = common::test_utils::produce_dummy_empty_transaction();
|
||||||
@ -536,10 +488,10 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn transaction_pre_check_native_transfer_valid() {
|
async fn transaction_pre_check_native_transfer_valid() {
|
||||||
let (sequencer, _mempool_handle) = common_setup().await;
|
let (_sequencer, _mempool_handle) = common_setup().await;
|
||||||
|
|
||||||
let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id;
|
let acc1 = initial_accounts()[0].account_id;
|
||||||
let acc2 = sequencer.sequencer_config.initial_accounts[1].account_id;
|
let acc2 = initial_accounts()[1].account_id;
|
||||||
|
|
||||||
let sign_key1 = create_signing_key_for_account1();
|
let sign_key1 = create_signing_key_for_account1();
|
||||||
|
|
||||||
@ -555,8 +507,8 @@ mod tests {
|
|||||||
async fn transaction_pre_check_native_transfer_other_signature() {
|
async fn transaction_pre_check_native_transfer_other_signature() {
|
||||||
let (mut sequencer, _mempool_handle) = common_setup().await;
|
let (mut sequencer, _mempool_handle) = common_setup().await;
|
||||||
|
|
||||||
let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id;
|
let acc1 = initial_accounts()[0].account_id;
|
||||||
let acc2 = sequencer.sequencer_config.initial_accounts[1].account_id;
|
let acc2 = initial_accounts()[1].account_id;
|
||||||
|
|
||||||
let sign_key2 = create_signing_key_for_account2();
|
let sign_key2 = create_signing_key_for_account2();
|
||||||
|
|
||||||
@ -580,8 +532,8 @@ mod tests {
|
|||||||
async fn transaction_pre_check_native_transfer_sent_too_much() {
|
async fn transaction_pre_check_native_transfer_sent_too_much() {
|
||||||
let (mut sequencer, _mempool_handle) = common_setup().await;
|
let (mut sequencer, _mempool_handle) = common_setup().await;
|
||||||
|
|
||||||
let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id;
|
let acc1 = initial_accounts()[0].account_id;
|
||||||
let acc2 = sequencer.sequencer_config.initial_accounts[1].account_id;
|
let acc2 = initial_accounts()[1].account_id;
|
||||||
|
|
||||||
let sign_key1 = create_signing_key_for_account1();
|
let sign_key1 = create_signing_key_for_account1();
|
||||||
|
|
||||||
@ -607,8 +559,8 @@ mod tests {
|
|||||||
async fn transaction_execute_native_transfer() {
|
async fn transaction_execute_native_transfer() {
|
||||||
let (mut sequencer, _mempool_handle) = common_setup().await;
|
let (mut sequencer, _mempool_handle) = common_setup().await;
|
||||||
|
|
||||||
let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id;
|
let acc1 = initial_accounts()[0].account_id;
|
||||||
let acc2 = sequencer.sequencer_config.initial_accounts[1].account_id;
|
let acc2 = initial_accounts()[1].account_id;
|
||||||
|
|
||||||
let sign_key1 = create_signing_key_for_account1();
|
let sign_key1 = create_signing_key_for_account1();
|
||||||
|
|
||||||
@ -669,8 +621,8 @@ mod tests {
|
|||||||
async fn replay_transactions_are_rejected_in_the_same_block() {
|
async fn replay_transactions_are_rejected_in_the_same_block() {
|
||||||
let (mut sequencer, mempool_handle) = common_setup().await;
|
let (mut sequencer, mempool_handle) = common_setup().await;
|
||||||
|
|
||||||
let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id;
|
let acc1 = initial_accounts()[0].account_id;
|
||||||
let acc2 = sequencer.sequencer_config.initial_accounts[1].account_id;
|
let acc2 = initial_accounts()[1].account_id;
|
||||||
|
|
||||||
let sign_key1 = create_signing_key_for_account1();
|
let sign_key1 = create_signing_key_for_account1();
|
||||||
|
|
||||||
@ -702,8 +654,8 @@ mod tests {
|
|||||||
async fn replay_transactions_are_rejected_in_different_blocks() {
|
async fn replay_transactions_are_rejected_in_different_blocks() {
|
||||||
let (mut sequencer, mempool_handle) = common_setup().await;
|
let (mut sequencer, mempool_handle) = common_setup().await;
|
||||||
|
|
||||||
let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id;
|
let acc1 = initial_accounts()[0].account_id;
|
||||||
let acc2 = sequencer.sequencer_config.initial_accounts[1].account_id;
|
let acc2 = initial_accounts()[1].account_id;
|
||||||
|
|
||||||
let sign_key1 = create_signing_key_for_account1();
|
let sign_key1 = create_signing_key_for_account1();
|
||||||
|
|
||||||
@ -739,8 +691,8 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn restart_from_storage() {
|
async fn restart_from_storage() {
|
||||||
let config = setup_sequencer_config();
|
let config = setup_sequencer_config();
|
||||||
let acc1_account_id = config.initial_accounts[0].account_id;
|
let acc1_account_id = initial_accounts()[0].account_id;
|
||||||
let acc2_account_id = config.initial_accounts[1].account_id;
|
let acc2_account_id = initial_accounts()[1].account_id;
|
||||||
let balance_to_move = 13;
|
let balance_to_move = 13;
|
||||||
|
|
||||||
// In the following code block a transaction will be processed that moves `balance_to_move`
|
// In the following code block a transaction will be processed that moves `balance_to_move`
|
||||||
@ -749,7 +701,7 @@ mod tests {
|
|||||||
{
|
{
|
||||||
let (mut sequencer, mempool_handle) =
|
let (mut sequencer, mempool_handle) =
|
||||||
SequencerCoreWithMockClients::start_from_config(config.clone()).await;
|
SequencerCoreWithMockClients::start_from_config(config.clone()).await;
|
||||||
let signing_key = PrivateKey::try_new([1; 32]).unwrap();
|
let signing_key = create_signing_key_for_account1();
|
||||||
|
|
||||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||||
acc1_account_id,
|
acc1_account_id,
|
||||||
@ -781,11 +733,11 @@ mod tests {
|
|||||||
// Balances should be consistent with the stored block
|
// Balances should be consistent with the stored block
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
balance_acc_1,
|
balance_acc_1,
|
||||||
config.initial_accounts[0].balance - balance_to_move
|
initial_accounts()[0].balance - balance_to_move
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
balance_acc_2,
|
balance_acc_2,
|
||||||
config.initial_accounts[1].balance + balance_to_move
|
initial_accounts()[1].balance + balance_to_move
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,15 +784,15 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn produce_block_with_correct_prev_meta_after_restart() {
|
async fn produce_block_with_correct_prev_meta_after_restart() {
|
||||||
let config = setup_sequencer_config();
|
let config = setup_sequencer_config();
|
||||||
let acc1_account_id = config.initial_accounts[0].account_id;
|
let acc1_account_id = initial_accounts()[0].account_id;
|
||||||
let acc2_account_id = config.initial_accounts[1].account_id;
|
let acc2_account_id = initial_accounts()[1].account_id;
|
||||||
|
|
||||||
// Step 1: Create initial database with some block metadata
|
// Step 1: Create initial database with some block metadata
|
||||||
let expected_prev_meta = {
|
let expected_prev_meta = {
|
||||||
let (mut sequencer, mempool_handle) =
|
let (mut sequencer, mempool_handle) =
|
||||||
SequencerCoreWithMockClients::start_from_config(config.clone()).await;
|
SequencerCoreWithMockClients::start_from_config(config.clone()).await;
|
||||||
|
|
||||||
let signing_key = PrivateKey::try_new([1; 32]).unwrap();
|
let signing_key = create_signing_key_for_account1();
|
||||||
|
|
||||||
// Add a transaction and produce a block to set up block metadata
|
// Add a transaction and produce a block to set up block metadata
|
||||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||||
@ -865,7 +817,7 @@ mod tests {
|
|||||||
SequencerCoreWithMockClients::start_from_config(config.clone()).await;
|
SequencerCoreWithMockClients::start_from_config(config.clone()).await;
|
||||||
|
|
||||||
// Step 3: Submit a new transaction
|
// Step 3: Submit a new transaction
|
||||||
let signing_key = PrivateKey::try_new([1; 32]).unwrap();
|
let signing_key = create_signing_key_for_account1();
|
||||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||||
acc1_account_id,
|
acc1_account_id,
|
||||||
1, // Next nonce
|
1, // Next nonce
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
688
storage/src/indexer/mod.rs
Normal file
688
storage/src/indexer/mod.rs
Normal file
@ -0,0 +1,688 @@
|
|||||||
|
use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use common::block::Block;
|
||||||
|
use nssa::V03State;
|
||||||
|
use rocksdb::{
|
||||||
|
BoundColumnFamily, ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::error::DbError;
|
||||||
|
|
||||||
|
pub mod read_multiple;
|
||||||
|
pub mod read_once;
|
||||||
|
pub mod write_atomic;
|
||||||
|
pub mod write_non_atomic;
|
||||||
|
|
||||||
|
/// Maximal size of stored blocks in base.
|
||||||
|
///
|
||||||
|
/// Used to control db size.
|
||||||
|
///
|
||||||
|
/// Currently effectively unbounded.
|
||||||
|
pub const BUFF_SIZE_ROCKSDB: usize = usize::MAX;
|
||||||
|
|
||||||
|
/// Size of stored blocks cache in memory.
|
||||||
|
///
|
||||||
|
/// Keeping small to not run out of memory.
|
||||||
|
pub const CACHE_SIZE: usize = 1000;
|
||||||
|
|
||||||
|
/// Key base for storing metainformation about id of first block in db.
|
||||||
|
pub const DB_META_FIRST_BLOCK_IN_DB_KEY: &str = "first_block_in_db";
|
||||||
|
/// Key base for storing metainformation about id of last current block in db.
|
||||||
|
pub const DB_META_LAST_BLOCK_IN_DB_KEY: &str = "last_block_in_db";
|
||||||
|
/// Key base for storing metainformation about id of last observed L1 lib header in db.
|
||||||
|
pub const DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY: &str =
|
||||||
|
"last_observed_l1_lib_header_in_db";
|
||||||
|
/// Key base for storing metainformation which describe if first block has been set.
|
||||||
|
pub const DB_META_FIRST_BLOCK_SET_KEY: &str = "first_block_set";
|
||||||
|
/// Key base for storing metainformation about the last breakpoint.
|
||||||
|
pub const DB_META_LAST_BREAKPOINT_ID: &str = "last_breakpoint_id";
|
||||||
|
|
||||||
|
/// Interval between state breakpoints.
|
||||||
|
pub const BREAKPOINT_INTERVAL: u8 = 100;
|
||||||
|
|
||||||
|
/// Name of block column family.
|
||||||
|
pub const CF_BLOCK_NAME: &str = "cf_block";
|
||||||
|
/// Name of meta column family.
|
||||||
|
pub const CF_META_NAME: &str = "cf_meta";
|
||||||
|
/// Name of breakpoint column family.
|
||||||
|
pub const CF_BREAKPOINT_NAME: &str = "cf_breakpoint";
|
||||||
|
/// Name of hash to id map column family.
|
||||||
|
pub const CF_HASH_TO_ID: &str = "cf_hash_to_id";
|
||||||
|
/// Name of tx hash to id map column family.
|
||||||
|
pub const CF_TX_TO_ID: &str = "cf_tx_to_id";
|
||||||
|
/// Name of account meta column family.
|
||||||
|
pub const CF_ACC_META: &str = "cf_acc_meta";
|
||||||
|
/// Name of account id to tx hash map column family.
|
||||||
|
pub const CF_ACC_TO_TX: &str = "cf_acc_to_tx";
|
||||||
|
|
||||||
|
pub type DbResult<T> = Result<T, DbError>;
|
||||||
|
|
||||||
|
pub struct RocksDBIO {
|
||||||
|
pub db: DBWithThreadMode<MultiThreaded>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RocksDBIO {
|
||||||
|
pub fn open_or_create(
|
||||||
|
path: &Path,
|
||||||
|
genesis_block: &Block,
|
||||||
|
initial_state: &V03State,
|
||||||
|
) -> DbResult<Self> {
|
||||||
|
let mut cf_opts = Options::default();
|
||||||
|
cf_opts.set_max_write_buffer_number(16);
|
||||||
|
// ToDo: Add more column families for different data
|
||||||
|
let cfb = ColumnFamilyDescriptor::new(CF_BLOCK_NAME, cf_opts.clone());
|
||||||
|
let cfmeta = ColumnFamilyDescriptor::new(CF_META_NAME, cf_opts.clone());
|
||||||
|
let cfbreakpoint = ColumnFamilyDescriptor::new(CF_BREAKPOINT_NAME, cf_opts.clone());
|
||||||
|
let cfhti = ColumnFamilyDescriptor::new(CF_HASH_TO_ID, cf_opts.clone());
|
||||||
|
let cftti = ColumnFamilyDescriptor::new(CF_TX_TO_ID, cf_opts.clone());
|
||||||
|
let cfameta = ColumnFamilyDescriptor::new(CF_ACC_META, cf_opts.clone());
|
||||||
|
let cfatt = ColumnFamilyDescriptor::new(CF_ACC_TO_TX, cf_opts.clone());
|
||||||
|
|
||||||
|
let mut db_opts = Options::default();
|
||||||
|
db_opts.create_missing_column_families(true);
|
||||||
|
db_opts.create_if_missing(true);
|
||||||
|
let db = DBWithThreadMode::<MultiThreaded>::open_cf_descriptors(
|
||||||
|
&db_opts,
|
||||||
|
path,
|
||||||
|
vec![cfb, cfmeta, cfbreakpoint, cfhti, cftti, cfameta, cfatt],
|
||||||
|
)
|
||||||
|
.map_err(|err| DbError::RocksDbError {
|
||||||
|
error: err,
|
||||||
|
additional_info: Some("Failed to open or create DB".to_owned()),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let dbio = Self { db };
|
||||||
|
|
||||||
|
let is_start_set = dbio.get_meta_is_first_block_set()?;
|
||||||
|
if !is_start_set {
|
||||||
|
let block_id = genesis_block.header.block_id;
|
||||||
|
dbio.put_meta_last_block_in_db(block_id)?;
|
||||||
|
dbio.put_meta_first_block_in_db_batch(genesis_block)?;
|
||||||
|
dbio.put_meta_is_first_block_set()?;
|
||||||
|
|
||||||
|
// First breakpoint setup
|
||||||
|
dbio.put_breakpoint(0, initial_state)?;
|
||||||
|
dbio.put_meta_last_breakpoint_id(0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(dbio)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(path: &Path) -> DbResult<()> {
|
||||||
|
let db_opts = Options::default();
|
||||||
|
DBWithThreadMode::<MultiThreaded>::destroy(&db_opts, path)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
|
||||||
|
pub fn meta_column(&self) -> Arc<BoundColumnFamily<'_>> {
|
||||||
|
self.db
|
||||||
|
.cf_handle(CF_META_NAME)
|
||||||
|
.expect("Meta column should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn block_column(&self) -> Arc<BoundColumnFamily<'_>> {
|
||||||
|
self.db
|
||||||
|
.cf_handle(CF_BLOCK_NAME)
|
||||||
|
.expect("Block column should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn breakpoint_column(&self) -> Arc<BoundColumnFamily<'_>> {
|
||||||
|
self.db
|
||||||
|
.cf_handle(CF_BREAKPOINT_NAME)
|
||||||
|
.expect("Breakpoint column should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_to_id_column(&self) -> Arc<BoundColumnFamily<'_>> {
|
||||||
|
self.db
|
||||||
|
.cf_handle(CF_HASH_TO_ID)
|
||||||
|
.expect("Hash to id map column should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tx_hash_to_id_column(&self) -> Arc<BoundColumnFamily<'_>> {
|
||||||
|
self.db
|
||||||
|
.cf_handle(CF_TX_TO_ID)
|
||||||
|
.expect("Tx hash to id map column should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn account_id_to_tx_hash_column(&self) -> Arc<BoundColumnFamily<'_>> {
|
||||||
|
self.db
|
||||||
|
.cf_handle(CF_ACC_TO_TX)
|
||||||
|
.expect("Account id to tx map column should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn account_meta_column(&self) -> Arc<BoundColumnFamily<'_>> {
|
||||||
|
self.db
|
||||||
|
.cf_handle(CF_ACC_META)
|
||||||
|
.expect("Account meta column should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
pub fn calculate_state_for_id(&self, block_id: u64) -> DbResult<V03State> {
|
||||||
|
let last_block = self.get_meta_last_block_in_db()?;
|
||||||
|
|
||||||
|
if block_id <= last_block {
|
||||||
|
let br_id = closest_breakpoint_id(block_id);
|
||||||
|
let mut breakpoint = self.get_breakpoint(br_id)?;
|
||||||
|
|
||||||
|
// ToDo: update it to handle any genesis id
|
||||||
|
// right now works correctly only if genesis_id < BREAKPOINT_INTERVAL
|
||||||
|
let start = if br_id != 0 {
|
||||||
|
u64::from(BREAKPOINT_INTERVAL)
|
||||||
|
.checked_mul(br_id)
|
||||||
|
.expect("Reached maximum breakpoint id")
|
||||||
|
} else {
|
||||||
|
self.get_meta_first_block_in_db()?
|
||||||
|
};
|
||||||
|
|
||||||
|
for block in self.get_block_batch_seq(
|
||||||
|
start.checked_add(1).expect("Will be lesser that u64::MAX")..=block_id,
|
||||||
|
)? {
|
||||||
|
for transaction in block.body.transactions {
|
||||||
|
transaction
|
||||||
|
.transaction_stateless_check()
|
||||||
|
.map_err(|err| {
|
||||||
|
DbError::db_interaction_error(format!(
|
||||||
|
"transaction pre check failed with err {err:?}"
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.execute_check_on_state(&mut breakpoint, block.header.block_id)
|
||||||
|
.map_err(|err| {
|
||||||
|
DbError::db_interaction_error(format!(
|
||||||
|
"transaction execution failed with err {err:?}"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(breakpoint)
|
||||||
|
} else {
|
||||||
|
Err(DbError::db_interaction_error(
|
||||||
|
"Block on this id not found".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn final_state(&self) -> DbResult<V03State> {
|
||||||
|
self.calculate_state_for_id(self.get_meta_last_block_in_db()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn closest_breakpoint_id(block_id: u64) -> u64 {
|
||||||
|
block_id
|
||||||
|
.saturating_sub(1)
|
||||||
|
.checked_div(u64::from(BREAKPOINT_INTERVAL))
|
||||||
|
.expect("Breakpoint interval is not zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::shadow_unrelated, reason = "Fine for tests")]
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use nssa::{AccountId, PublicKey};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn genesis_block() -> Block {
|
||||||
|
common::test_utils::produce_dummy_block(1, None, vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acc1_sign_key() -> nssa::PrivateKey {
|
||||||
|
nssa::PrivateKey::try_new([1; 32]).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acc2_sign_key() -> nssa::PrivateKey {
|
||||||
|
nssa::PrivateKey::try_new([2; 32]).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acc1() -> AccountId {
|
||||||
|
AccountId::from(&PublicKey::new_from_private_key(&acc1_sign_key()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acc2() -> AccountId {
|
||||||
|
AccountId::from(&PublicKey::new_from_private_key(&acc2_sign_key()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start_db() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let temdir_path = temp_dir.path();
|
||||||
|
|
||||||
|
let dbio = RocksDBIO::open_or_create(
|
||||||
|
temdir_path,
|
||||||
|
&genesis_block(),
|
||||||
|
&nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], &[]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let first_id = dbio.get_meta_first_block_in_db().unwrap();
|
||||||
|
let is_first_set = dbio.get_meta_is_first_block_set().unwrap();
|
||||||
|
let last_observed_l1_header = dbio.get_meta_last_observed_l1_lib_header_in_db().unwrap();
|
||||||
|
let last_br_id = dbio.get_meta_last_breakpoint_id().unwrap();
|
||||||
|
let last_block = dbio.get_block(1).unwrap().unwrap();
|
||||||
|
let breakpoint = dbio.get_breakpoint(0).unwrap();
|
||||||
|
let final_state = dbio.final_state().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(last_id, 1);
|
||||||
|
assert_eq!(first_id, 1);
|
||||||
|
assert_eq!(last_observed_l1_header, None);
|
||||||
|
assert!(is_first_set);
|
||||||
|
assert_eq!(last_br_id, 0);
|
||||||
|
assert_eq!(last_block.header.hash, genesis_block().header.hash);
|
||||||
|
assert_eq!(
|
||||||
|
breakpoint.get_account_by_id(acc1()),
|
||||||
|
final_state.get_account_by_id(acc1())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
breakpoint.get_account_by_id(acc2()),
|
||||||
|
final_state.get_account_by_id(acc2())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_block_insertion() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let temdir_path = temp_dir.path();
|
||||||
|
|
||||||
|
let dbio = RocksDBIO::open_or_create(
|
||||||
|
temdir_path,
|
||||||
|
&genesis_block(),
|
||||||
|
&nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], &[]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let prev_hash = genesis_block().header.hash;
|
||||||
|
let from = acc1();
|
||||||
|
let to = acc2();
|
||||||
|
let sign_key = acc1_sign_key();
|
||||||
|
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 0, to, 1, &sign_key);
|
||||||
|
let block = common::test_utils::produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
|
||||||
|
dbio.put_block(&block, [1; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let first_id = dbio.get_meta_first_block_in_db().unwrap();
|
||||||
|
let last_observed_l1_header = dbio
|
||||||
|
.get_meta_last_observed_l1_lib_header_in_db()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let is_first_set = dbio.get_meta_is_first_block_set().unwrap();
|
||||||
|
let last_br_id = dbio.get_meta_last_breakpoint_id().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
let breakpoint = dbio.get_breakpoint(0).unwrap();
|
||||||
|
let final_state = dbio.final_state().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(last_id, 2);
|
||||||
|
assert_eq!(first_id, 1);
|
||||||
|
assert_eq!(last_observed_l1_header, [1; 32]);
|
||||||
|
assert!(is_first_set);
|
||||||
|
assert_eq!(last_br_id, 0);
|
||||||
|
assert_ne!(last_block.header.hash, genesis_block().header.hash);
|
||||||
|
assert_eq!(
|
||||||
|
breakpoint.get_account_by_id(acc1()).balance
|
||||||
|
- final_state.get_account_by_id(acc1()).balance,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
final_state.get_account_by_id(acc2()).balance
|
||||||
|
- breakpoint.get_account_by_id(acc2()).balance,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_breakpoint() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let temdir_path = temp_dir.path();
|
||||||
|
|
||||||
|
let dbio = RocksDBIO::open_or_create(
|
||||||
|
temdir_path,
|
||||||
|
&genesis_block(),
|
||||||
|
&nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], &[]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let from = acc1();
|
||||||
|
let to = acc2();
|
||||||
|
let sign_key = acc1_sign_key();
|
||||||
|
|
||||||
|
for i in 1..=BREAKPOINT_INTERVAL {
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
|
||||||
|
let transfer_tx = common::test_utils::create_transaction_native_token_transfer(
|
||||||
|
from,
|
||||||
|
(i - 1).into(),
|
||||||
|
to,
|
||||||
|
1,
|
||||||
|
&sign_key,
|
||||||
|
);
|
||||||
|
let block = common::test_utils::produce_dummy_block(
|
||||||
|
(i + 1).into(),
|
||||||
|
Some(prev_hash),
|
||||||
|
vec![transfer_tx],
|
||||||
|
);
|
||||||
|
dbio.put_block(&block, [i; 32]).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let first_id = dbio.get_meta_first_block_in_db().unwrap();
|
||||||
|
let is_first_set = dbio.get_meta_is_first_block_set().unwrap();
|
||||||
|
let last_br_id = dbio.get_meta_last_breakpoint_id().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
let prev_breakpoint = dbio.get_breakpoint(0).unwrap();
|
||||||
|
let breakpoint = dbio.get_breakpoint(1).unwrap();
|
||||||
|
let final_state = dbio.final_state().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(last_id, 101);
|
||||||
|
assert_eq!(first_id, 1);
|
||||||
|
assert!(is_first_set);
|
||||||
|
assert_eq!(last_br_id, 1);
|
||||||
|
assert_ne!(last_block.header.hash, genesis_block().header.hash);
|
||||||
|
assert_eq!(
|
||||||
|
prev_breakpoint.get_account_by_id(acc1()).balance
|
||||||
|
- final_state.get_account_by_id(acc1()).balance,
|
||||||
|
100
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
final_state.get_account_by_id(acc2()).balance
|
||||||
|
- prev_breakpoint.get_account_by_id(acc2()).balance,
|
||||||
|
100
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
breakpoint.get_account_by_id(acc1()).balance
|
||||||
|
- final_state.get_account_by_id(acc1()).balance,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
final_state.get_account_by_id(acc2()).balance
|
||||||
|
- breakpoint.get_account_by_id(acc2()).balance,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_maps() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let temdir_path = temp_dir.path();
|
||||||
|
|
||||||
|
let dbio = RocksDBIO::open_or_create(
|
||||||
|
temdir_path,
|
||||||
|
&genesis_block(),
|
||||||
|
&nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], &[]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let from = acc1();
|
||||||
|
let to = acc2();
|
||||||
|
let sign_key = acc1_sign_key();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 0, to, 1, &sign_key);
|
||||||
|
let block = common::test_utils::produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
|
||||||
|
let control_hash1 = block.header.hash;
|
||||||
|
|
||||||
|
dbio.put_block(&block, [1; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 1, to, 1, &sign_key);
|
||||||
|
let block = common::test_utils::produce_dummy_block(3, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
|
||||||
|
let control_hash2 = block.header.hash;
|
||||||
|
|
||||||
|
dbio.put_block(&block, [2; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 2, to, 1, &sign_key);
|
||||||
|
|
||||||
|
let control_tx_hash1 = transfer_tx.hash();
|
||||||
|
|
||||||
|
let block = common::test_utils::produce_dummy_block(4, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
dbio.put_block(&block, [3; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 3, to, 1, &sign_key);
|
||||||
|
|
||||||
|
let control_tx_hash2 = transfer_tx.hash();
|
||||||
|
|
||||||
|
let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
dbio.put_block(&block, [4; 32]).unwrap();
|
||||||
|
|
||||||
|
let control_block_id1 = dbio.get_block_id_by_hash(control_hash1.0).unwrap().unwrap();
|
||||||
|
let control_block_id2 = dbio.get_block_id_by_hash(control_hash2.0).unwrap().unwrap();
|
||||||
|
let control_block_id3 = dbio
|
||||||
|
.get_block_id_by_tx_hash(control_tx_hash1.0)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let control_block_id4 = dbio
|
||||||
|
.get_block_id_by_tx_hash(control_tx_hash2.0)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(control_block_id1, 2);
|
||||||
|
assert_eq!(control_block_id2, 3);
|
||||||
|
assert_eq!(control_block_id3, 4);
|
||||||
|
assert_eq!(control_block_id4, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_batch() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let temdir_path = temp_dir.path();
|
||||||
|
|
||||||
|
let mut block_res = vec![];
|
||||||
|
|
||||||
|
let dbio = RocksDBIO::open_or_create(
|
||||||
|
temdir_path,
|
||||||
|
&genesis_block(),
|
||||||
|
&nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], &[]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let from = acc1();
|
||||||
|
let to = acc2();
|
||||||
|
let sign_key = acc1_sign_key();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 0, to, 1, &sign_key);
|
||||||
|
let block = common::test_utils::produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
|
||||||
|
block_res.push(block.clone());
|
||||||
|
dbio.put_block(&block, [1; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 1, to, 1, &sign_key);
|
||||||
|
let block = common::test_utils::produce_dummy_block(3, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
|
||||||
|
block_res.push(block.clone());
|
||||||
|
dbio.put_block(&block, [2; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 2, to, 1, &sign_key);
|
||||||
|
|
||||||
|
let block = common::test_utils::produce_dummy_block(4, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
block_res.push(block.clone());
|
||||||
|
dbio.put_block(&block, [3; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 3, to, 1, &sign_key);
|
||||||
|
|
||||||
|
let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
block_res.push(block.clone());
|
||||||
|
dbio.put_block(&block, [4; 32]).unwrap();
|
||||||
|
|
||||||
|
let block_hashes_mem: Vec<[u8; 32]> =
|
||||||
|
block_res.into_iter().map(|bl| bl.header.hash.0).collect();
|
||||||
|
|
||||||
|
// Get blocks before ID 6 (i.e., starting from 5 going backwards), limit 4
|
||||||
|
// This should return blocks 5, 4, 3, 2 in descending order
|
||||||
|
let mut batch_res = dbio.get_block_batch(Some(6), 4).unwrap();
|
||||||
|
batch_res.reverse(); // Reverse to match ascending order for comparison
|
||||||
|
|
||||||
|
let block_hashes_db: Vec<[u8; 32]> =
|
||||||
|
batch_res.into_iter().map(|bl| bl.header.hash.0).collect();
|
||||||
|
|
||||||
|
assert_eq!(block_hashes_mem, block_hashes_db);
|
||||||
|
|
||||||
|
let block_hashes_mem_limited = &block_hashes_mem[1..];
|
||||||
|
|
||||||
|
// Get blocks before ID 6, limit 3
|
||||||
|
// This should return blocks 5, 4, 3 in descending order
|
||||||
|
let mut batch_res_limited = dbio.get_block_batch(Some(6), 3).unwrap();
|
||||||
|
batch_res_limited.reverse(); // Reverse to match ascending order for comparison
|
||||||
|
|
||||||
|
let block_hashes_db_limited: Vec<[u8; 32]> = batch_res_limited
|
||||||
|
.into_iter()
|
||||||
|
.map(|bl| bl.header.hash.0)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert_eq!(block_hashes_mem_limited, block_hashes_db_limited.as_slice());
|
||||||
|
|
||||||
|
let block_batch_seq = dbio.get_block_batch_seq(1..=5).unwrap();
|
||||||
|
let block_batch_ids = block_batch_seq
|
||||||
|
.into_iter()
|
||||||
|
.map(|block| block.header.block_id)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(block_batch_ids, vec![1, 2, 3, 4, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn account_map() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let temdir_path = temp_dir.path();
|
||||||
|
|
||||||
|
let dbio = RocksDBIO::open_or_create(
|
||||||
|
temdir_path,
|
||||||
|
&genesis_block(),
|
||||||
|
&nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], &[]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let from = acc1();
|
||||||
|
let to = acc2();
|
||||||
|
let sign_key = acc1_sign_key();
|
||||||
|
|
||||||
|
let mut tx_hash_res = vec![];
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx1 =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 0, to, 1, &sign_key);
|
||||||
|
let transfer_tx2 =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 1, to, 1, &sign_key);
|
||||||
|
tx_hash_res.push(transfer_tx1.hash().0);
|
||||||
|
tx_hash_res.push(transfer_tx2.hash().0);
|
||||||
|
|
||||||
|
let block = common::test_utils::produce_dummy_block(
|
||||||
|
2,
|
||||||
|
Some(prev_hash),
|
||||||
|
vec![transfer_tx1, transfer_tx2],
|
||||||
|
);
|
||||||
|
|
||||||
|
dbio.put_block(&block, [1; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx1 =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 2, to, 1, &sign_key);
|
||||||
|
let transfer_tx2 =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 3, to, 1, &sign_key);
|
||||||
|
tx_hash_res.push(transfer_tx1.hash().0);
|
||||||
|
tx_hash_res.push(transfer_tx2.hash().0);
|
||||||
|
|
||||||
|
let block = common::test_utils::produce_dummy_block(
|
||||||
|
3,
|
||||||
|
Some(prev_hash),
|
||||||
|
vec![transfer_tx1, transfer_tx2],
|
||||||
|
);
|
||||||
|
|
||||||
|
dbio.put_block(&block, [2; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx1 =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 4, to, 1, &sign_key);
|
||||||
|
let transfer_tx2 =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 5, to, 1, &sign_key);
|
||||||
|
tx_hash_res.push(transfer_tx1.hash().0);
|
||||||
|
tx_hash_res.push(transfer_tx2.hash().0);
|
||||||
|
|
||||||
|
let block = common::test_utils::produce_dummy_block(
|
||||||
|
4,
|
||||||
|
Some(prev_hash),
|
||||||
|
vec![transfer_tx1, transfer_tx2],
|
||||||
|
);
|
||||||
|
|
||||||
|
dbio.put_block(&block, [3; 32]).unwrap();
|
||||||
|
|
||||||
|
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||||
|
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||||
|
|
||||||
|
let prev_hash = last_block.header.hash;
|
||||||
|
let transfer_tx =
|
||||||
|
common::test_utils::create_transaction_native_token_transfer(from, 6, to, 1, &sign_key);
|
||||||
|
tx_hash_res.push(transfer_tx.hash().0);
|
||||||
|
|
||||||
|
let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]);
|
||||||
|
|
||||||
|
dbio.put_block(&block, [4; 32]).unwrap();
|
||||||
|
|
||||||
|
let acc1_tx = dbio.get_acc_transactions(*acc1().value(), 0, 7).unwrap();
|
||||||
|
let acc1_tx_hashes: Vec<[u8; 32]> = acc1_tx.into_iter().map(|tx| tx.hash().0).collect();
|
||||||
|
|
||||||
|
assert_eq!(acc1_tx_hashes, tx_hash_res);
|
||||||
|
|
||||||
|
let acc1_tx_limited = dbio.get_acc_transactions(*acc1().value(), 1, 4).unwrap();
|
||||||
|
let acc1_tx_limited_hashes: Vec<[u8; 32]> =
|
||||||
|
acc1_tx_limited.into_iter().map(|tx| tx.hash().0).collect();
|
||||||
|
|
||||||
|
assert_eq!(acc1_tx_limited_hashes.as_slice(), &tx_hash_res[1..5]);
|
||||||
|
}
|
||||||
|
}
|
||||||
209
storage/src/indexer/read_multiple.rs
Normal file
209
storage/src/indexer/read_multiple.rs
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
use common::transaction::NSSATransaction;
|
||||||
|
|
||||||
|
use super::{Block, DbError, DbResult, RocksDBIO};
|
||||||
|
|
||||||
|
#[expect(clippy::multiple_inherent_impl, reason = "Readability")]
|
||||||
|
impl RocksDBIO {
|
||||||
|
pub fn get_block_batch(&self, before: Option<u64>, limit: u64) -> DbResult<Vec<Block>> {
|
||||||
|
let mut seq = vec![];
|
||||||
|
|
||||||
|
// Determine the starting block ID
|
||||||
|
let start_block_id = if let Some(before_id) = before {
|
||||||
|
before_id.saturating_sub(1)
|
||||||
|
} else {
|
||||||
|
// Get the latest block ID
|
||||||
|
self.get_meta_last_block_in_db()?
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 0..limit {
|
||||||
|
let block_id = start_block_id.saturating_sub(i);
|
||||||
|
if block_id == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
seq.push(block_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.get_block_batch_seq(seq.into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get block batch from a sequence.
|
||||||
|
///
|
||||||
|
/// Currently assumes non-decreasing sequence.
|
||||||
|
///
|
||||||
|
/// `ToDo`: Add suport of arbitrary sequences.
|
||||||
|
pub fn get_block_batch_seq(&self, seq: impl Iterator<Item = u64>) -> DbResult<Vec<Block>> {
|
||||||
|
let cf_block = self.block_column();
|
||||||
|
|
||||||
|
// Keys setup
|
||||||
|
let mut keys = vec![];
|
||||||
|
for block_id in seq {
|
||||||
|
keys.push((
|
||||||
|
&cf_block,
|
||||||
|
borsh::to_vec(&block_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize block id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let multi_get_res = self.db.multi_get_cf(keys);
|
||||||
|
|
||||||
|
// Keys parsing
|
||||||
|
let mut block_batch = vec![];
|
||||||
|
for res in multi_get_res {
|
||||||
|
let res = res.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
let block = if let Some(data) = res {
|
||||||
|
Ok(borsh::from_slice::<Block>(&data).map_err(|serr| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
serr,
|
||||||
|
Some("Failed to deserialize block data".to_owned()),
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
// Block not found, assuming that previous one was the last
|
||||||
|
break;
|
||||||
|
}?;
|
||||||
|
|
||||||
|
block_batch.push(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(block_batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get block ids by txs.
|
||||||
|
///
|
||||||
|
/// `ToDo`: There may be multiple transactions in one block
|
||||||
|
/// so this method can take redundant reads.
|
||||||
|
/// Need to update signature and implementation.
|
||||||
|
fn get_block_ids_by_tx_vec(&self, tx_vec: &[[u8; 32]]) -> DbResult<Vec<u64>> {
|
||||||
|
let cf_tti = self.tx_hash_to_id_column();
|
||||||
|
|
||||||
|
// Keys setup
|
||||||
|
let mut keys = vec![];
|
||||||
|
for tx_hash in tx_vec {
|
||||||
|
keys.push((
|
||||||
|
&cf_tti,
|
||||||
|
borsh::to_vec(tx_hash).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(err, Some("Failed to serialize tx_hash".to_owned()))
|
||||||
|
})?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let multi_get_res = self.db.multi_get_cf(keys);
|
||||||
|
|
||||||
|
// Keys parsing
|
||||||
|
let mut block_id_batch = vec![];
|
||||||
|
for res in multi_get_res {
|
||||||
|
let res = res
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
DbError::db_interaction_error(
|
||||||
|
"Tx to block id mapping do not contain transaction from vec".to_owned(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let block_id = {
|
||||||
|
Ok(borsh::from_slice::<u64>(&res).map_err(|serr| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
serr,
|
||||||
|
Some("Failed to deserialize block id".to_owned()),
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
block_id_batch.push(block_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(block_id_batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account
|
||||||
|
|
||||||
|
pub(crate) fn get_acc_transaction_hashes(
|
||||||
|
&self,
|
||||||
|
acc_id: [u8; 32],
|
||||||
|
offset: u64,
|
||||||
|
limit: u64,
|
||||||
|
) -> DbResult<Vec<[u8; 32]>> {
|
||||||
|
let cf_att = self.account_id_to_tx_hash_column();
|
||||||
|
let mut tx_batch = vec![];
|
||||||
|
|
||||||
|
// Keys preparation
|
||||||
|
let mut keys = vec![];
|
||||||
|
for tx_id in offset
|
||||||
|
..offset
|
||||||
|
.checked_add(limit)
|
||||||
|
.expect("Transaction limit should be lesser than u64::MAX")
|
||||||
|
{
|
||||||
|
let mut prefix = borsh::to_vec(&acc_id).map_err(|berr| {
|
||||||
|
DbError::borsh_cast_message(berr, Some("Failed to serialize account id".to_owned()))
|
||||||
|
})?;
|
||||||
|
let suffix = borsh::to_vec(&tx_id).map_err(|berr| {
|
||||||
|
DbError::borsh_cast_message(berr, Some("Failed to serialize tx id".to_owned()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
prefix.extend_from_slice(&suffix);
|
||||||
|
|
||||||
|
keys.push((&cf_att, prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
let multi_get_res = self.db.multi_get_cf(keys);
|
||||||
|
|
||||||
|
for res in multi_get_res {
|
||||||
|
let res = res.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
let tx_hash = if let Some(data) = res {
|
||||||
|
Ok(borsh::from_slice::<[u8; 32]>(&data).map_err(|serr| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
serr,
|
||||||
|
Some("Failed to deserialize tx_hash".to_owned()),
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
// Tx hash not found, assuming that previous one was the last
|
||||||
|
break;
|
||||||
|
}?;
|
||||||
|
|
||||||
|
tx_batch.push(tx_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tx_batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_acc_transactions(
|
||||||
|
&self,
|
||||||
|
acc_id: [u8; 32],
|
||||||
|
offset: u64,
|
||||||
|
limit: u64,
|
||||||
|
) -> DbResult<Vec<NSSATransaction>> {
|
||||||
|
let mut tx_batch = vec![];
|
||||||
|
|
||||||
|
let tx_hashes = self.get_acc_transaction_hashes(acc_id, offset, limit)?;
|
||||||
|
|
||||||
|
let associated_blocks_multi_get = self
|
||||||
|
.get_block_batch_seq(self.get_block_ids_by_tx_vec(&tx_hashes)?.into_iter())?
|
||||||
|
.into_iter()
|
||||||
|
.zip(tx_hashes);
|
||||||
|
|
||||||
|
for (block, tx_hash) in associated_blocks_multi_get {
|
||||||
|
let transaction = block
|
||||||
|
.body
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.find(|tx| tx.hash().0 == tx_hash)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
DbError::db_interaction_error(format!(
|
||||||
|
"Missing transaction in block {} with hash {:#?}",
|
||||||
|
block.header.block_id, tx_hash
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
tx_batch.push(transaction.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tx_batch)
|
||||||
|
}
|
||||||
|
}
|
||||||
272
storage/src/indexer/read_once.rs
Normal file
272
storage/src/indexer/read_once.rs
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
use super::{
|
||||||
|
Block, DB_META_FIRST_BLOCK_IN_DB_KEY, DB_META_FIRST_BLOCK_SET_KEY,
|
||||||
|
DB_META_LAST_BLOCK_IN_DB_KEY, DB_META_LAST_BREAKPOINT_ID,
|
||||||
|
DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY, DbError, DbResult, RocksDBIO, V03State,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[expect(clippy::multiple_inherent_impl, reason = "Readability")]
|
||||||
|
impl RocksDBIO {
|
||||||
|
// Meta
|
||||||
|
|
||||||
|
pub fn get_meta_first_block_in_db(&self) -> DbResult<u64> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
let res = self
|
||||||
|
.db
|
||||||
|
.get_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_FIRST_BLOCK_IN_DB_KEY).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
if let Some(data) = res {
|
||||||
|
Ok(borsh::from_slice::<u64>(&data).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to deserialize first block".to_owned()),
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
Err(DbError::db_interaction_error(
|
||||||
|
"First block not found".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_meta_last_block_in_db(&self) -> DbResult<u64> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
let res = self
|
||||||
|
.db
|
||||||
|
.get_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_LAST_BLOCK_IN_DB_KEY).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
if let Some(data) = res {
|
||||||
|
Ok(borsh::from_slice::<u64>(&data).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to deserialize last block".to_owned()),
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
Err(DbError::db_interaction_error(
|
||||||
|
"Last block not found".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_meta_last_observed_l1_lib_header_in_db(&self) -> DbResult<Option<[u8; 32]>> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
let res = self
|
||||||
|
.db
|
||||||
|
.get_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY).map_err(
|
||||||
|
|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some(
|
||||||
|
"Failed to serialize DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY"
|
||||||
|
.to_owned(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
res.map(|data| {
|
||||||
|
borsh::from_slice::<[u8; 32]>(&data).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to deserialize last l1 lib header".to_owned()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_meta_is_first_block_set(&self) -> DbResult<bool> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
let res = self
|
||||||
|
.db
|
||||||
|
.get_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_FIRST_BLOCK_SET_KEY).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
Ok(res.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_meta_last_breakpoint_id(&self) -> DbResult<u64> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
let res = self
|
||||||
|
.db
|
||||||
|
.get_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_LAST_BREAKPOINT_ID).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_LAST_BREAKPOINT_ID".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
if let Some(data) = res {
|
||||||
|
Ok(borsh::from_slice::<u64>(&data).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to deserialize last breakpoint id".to_owned()),
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
Err(DbError::db_interaction_error(
|
||||||
|
"Last breakpoint id not found".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block
|
||||||
|
|
||||||
|
pub fn get_block(&self, block_id: u64) -> DbResult<Option<Block>> {
|
||||||
|
let cf_block = self.block_column();
|
||||||
|
let res = self
|
||||||
|
.db
|
||||||
|
.get_cf(
|
||||||
|
&cf_block,
|
||||||
|
borsh::to_vec(&block_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize block id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
if let Some(data) = res {
|
||||||
|
Ok(Some(borsh::from_slice::<Block>(&data).map_err(|serr| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
serr,
|
||||||
|
Some("Failed to deserialize block data".to_owned()),
|
||||||
|
)
|
||||||
|
})?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
pub fn get_breakpoint(&self, br_id: u64) -> DbResult<V03State> {
|
||||||
|
let cf_br = self.breakpoint_column();
|
||||||
|
let res = self
|
||||||
|
.db
|
||||||
|
.get_cf(
|
||||||
|
&cf_br,
|
||||||
|
borsh::to_vec(&br_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize breakpoint id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
if let Some(data) = res {
|
||||||
|
Ok(borsh::from_slice::<V03State>(&data).map_err(|serr| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
serr,
|
||||||
|
Some("Failed to deserialize breakpoint data".to_owned()),
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
Err(DbError::db_interaction_error(
|
||||||
|
"Breakpoint on this id not found".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mappings
|
||||||
|
|
||||||
|
pub fn get_block_id_by_hash(&self, hash: [u8; 32]) -> DbResult<Option<u64>> {
|
||||||
|
let cf_hti = self.hash_to_id_column();
|
||||||
|
let res = self
|
||||||
|
.db
|
||||||
|
.get_cf(
|
||||||
|
&cf_hti,
|
||||||
|
borsh::to_vec(&hash).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize block hash".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
if let Some(data) = res {
|
||||||
|
Ok(Some(borsh::from_slice::<u64>(&data).map_err(|serr| {
|
||||||
|
DbError::borsh_cast_message(serr, Some("Failed to deserialize block id".to_owned()))
|
||||||
|
})?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_block_id_by_tx_hash(&self, tx_hash: [u8; 32]) -> DbResult<Option<u64>> {
|
||||||
|
let cf_tti = self.tx_hash_to_id_column();
|
||||||
|
let res = self
|
||||||
|
.db
|
||||||
|
.get_cf(
|
||||||
|
&cf_tti,
|
||||||
|
borsh::to_vec(&tx_hash).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize transaction hash".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
if let Some(data) = res {
|
||||||
|
Ok(Some(borsh::from_slice::<u64>(&data).map_err(|serr| {
|
||||||
|
DbError::borsh_cast_message(serr, Some("Failed to deserialize block id".to_owned()))
|
||||||
|
})?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accounts meta
|
||||||
|
|
||||||
|
pub(crate) fn get_acc_meta_num_tx(&self, acc_id: [u8; 32]) -> DbResult<Option<u64>> {
|
||||||
|
let cf_ameta = self.account_meta_column();
|
||||||
|
let res = self.db.get_cf(&cf_ameta, acc_id).map_err(|rerr| {
|
||||||
|
DbError::rocksdb_cast_message(rerr, Some("Failed to read from acc meta cf".to_owned()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
res.map(|data| {
|
||||||
|
borsh::from_slice::<u64>(&data).map_err(|serr| {
|
||||||
|
DbError::borsh_cast_message(serr, Some("Failed to deserialize num tx".to_owned()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
}
|
||||||
339
storage/src/indexer/write_atomic.rs
Normal file
339
storage/src/indexer/write_atomic.rs
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rocksdb::WriteBatch;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Arc, BREAKPOINT_INTERVAL, Block, BoundColumnFamily, DB_META_FIRST_BLOCK_IN_DB_KEY,
|
||||||
|
DB_META_FIRST_BLOCK_SET_KEY, DB_META_LAST_BLOCK_IN_DB_KEY, DB_META_LAST_BREAKPOINT_ID,
|
||||||
|
DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY, DbError, DbResult, RocksDBIO,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[expect(clippy::multiple_inherent_impl, reason = "Readability")]
|
||||||
|
impl RocksDBIO {
|
||||||
|
// Accounts meta
|
||||||
|
|
||||||
|
pub(crate) fn update_acc_meta_batch(
|
||||||
|
&self,
|
||||||
|
acc_id: [u8; 32],
|
||||||
|
num_tx: u64,
|
||||||
|
write_batch: &mut WriteBatch,
|
||||||
|
) -> DbResult<()> {
|
||||||
|
let cf_ameta = self.account_meta_column();
|
||||||
|
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_ameta,
|
||||||
|
borsh::to_vec(&acc_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(err, Some("Failed to serialize account id".to_owned()))
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(&num_tx).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize acc metadata".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account
|
||||||
|
|
||||||
|
pub fn put_account_transactions(
|
||||||
|
&self,
|
||||||
|
acc_id: [u8; 32],
|
||||||
|
tx_hashes: &[[u8; 32]],
|
||||||
|
) -> DbResult<()> {
|
||||||
|
let acc_num_tx = self.get_acc_meta_num_tx(acc_id)?.unwrap_or(0);
|
||||||
|
let cf_att = self.account_id_to_tx_hash_column();
|
||||||
|
let mut write_batch = WriteBatch::new();
|
||||||
|
|
||||||
|
for (tx_id, tx_hash) in tx_hashes.iter().enumerate() {
|
||||||
|
let put_id = acc_num_tx
|
||||||
|
.checked_add(tx_id.try_into().expect("Must fit into u64"))
|
||||||
|
.expect("Tx count should be lesser that u64::MAX");
|
||||||
|
|
||||||
|
let mut prefix = borsh::to_vec(&acc_id).map_err(|berr| {
|
||||||
|
DbError::borsh_cast_message(berr, Some("Failed to serialize account id".to_owned()))
|
||||||
|
})?;
|
||||||
|
let suffix = borsh::to_vec(&put_id).map_err(|berr| {
|
||||||
|
DbError::borsh_cast_message(berr, Some("Failed to serialize tx id".to_owned()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
prefix.extend_from_slice(&suffix);
|
||||||
|
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_att,
|
||||||
|
prefix,
|
||||||
|
borsh::to_vec(tx_hash).map_err(|berr| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
berr,
|
||||||
|
Some("Failed to serialize tx hash".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_acc_meta_batch(
|
||||||
|
acc_id,
|
||||||
|
acc_num_tx
|
||||||
|
.checked_add(tx_hashes.len().try_into().expect("Must fit into u64"))
|
||||||
|
.expect("Tx count should be lesser that u64::MAX"),
|
||||||
|
&mut write_batch,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.db.write(write_batch).map_err(|rerr| {
|
||||||
|
DbError::rocksdb_cast_message(rerr, Some("Failed to write batch".to_owned()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_account_transactions_dependant(
|
||||||
|
&self,
|
||||||
|
acc_id: [u8; 32],
|
||||||
|
tx_hashes: &[[u8; 32]],
|
||||||
|
write_batch: &mut WriteBatch,
|
||||||
|
) -> DbResult<()> {
|
||||||
|
let acc_num_tx = self.get_acc_meta_num_tx(acc_id)?.unwrap_or(0);
|
||||||
|
let cf_att = self.account_id_to_tx_hash_column();
|
||||||
|
|
||||||
|
for (tx_id, tx_hash) in tx_hashes.iter().enumerate() {
|
||||||
|
let put_id = acc_num_tx
|
||||||
|
.checked_add(tx_id.try_into().expect("Must fit into u64"))
|
||||||
|
.expect("Tx count should be lesser that u64::MAX");
|
||||||
|
|
||||||
|
let mut prefix = borsh::to_vec(&acc_id).map_err(|berr| {
|
||||||
|
DbError::borsh_cast_message(berr, Some("Failed to serialize account id".to_owned()))
|
||||||
|
})?;
|
||||||
|
let suffix = borsh::to_vec(&put_id).map_err(|berr| {
|
||||||
|
DbError::borsh_cast_message(berr, Some("Failed to serialize tx id".to_owned()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
prefix.extend_from_slice(&suffix);
|
||||||
|
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_att,
|
||||||
|
prefix,
|
||||||
|
borsh::to_vec(tx_hash).map_err(|berr| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
berr,
|
||||||
|
Some("Failed to serialize tx hash".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_acc_meta_batch(
|
||||||
|
acc_id,
|
||||||
|
acc_num_tx
|
||||||
|
.checked_add(tx_hashes.len().try_into().expect("Must fit into u64"))
|
||||||
|
.expect("Tx count should be lesser that u64::MAX"),
|
||||||
|
write_batch,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta
|
||||||
|
|
||||||
|
pub fn put_meta_first_block_in_db_batch(&self, block: &Block) -> DbResult<()> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
self.db
|
||||||
|
.put_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_FIRST_BLOCK_IN_DB_KEY).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(&block.header.block_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize first block id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
|
self.put_block(block, [0; 32])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_meta_last_block_in_db_batch(
|
||||||
|
&self,
|
||||||
|
block_id: u64,
|
||||||
|
write_batch: &mut WriteBatch,
|
||||||
|
) -> DbResult<()> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_LAST_BLOCK_IN_DB_KEY).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(&block_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize last block id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_meta_last_observed_l1_lib_header_in_db_batch(
|
||||||
|
&self,
|
||||||
|
l1_lib_header: [u8; 32],
|
||||||
|
write_batch: &mut WriteBatch,
|
||||||
|
) -> DbResult<()> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some(
|
||||||
|
"Failed to serialize DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY"
|
||||||
|
.to_owned(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(&l1_lib_header).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize last l1 block header".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_meta_last_breakpoint_id_batch(
|
||||||
|
&self,
|
||||||
|
br_id: u64,
|
||||||
|
write_batch: &mut WriteBatch,
|
||||||
|
) -> DbResult<()> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_LAST_BREAKPOINT_ID).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_LAST_BREAKPOINT_ID".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(&br_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize last block id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_meta_is_first_block_set_batch(&self, write_batch: &mut WriteBatch) -> DbResult<()> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_FIRST_BLOCK_SET_KEY).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
[1_u8; 1],
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block
|
||||||
|
|
||||||
|
pub fn put_block(&self, block: &Block, l1_lib_header: [u8; 32]) -> DbResult<()> {
|
||||||
|
let cf_block = self.block_column();
|
||||||
|
let cf_hti = self.hash_to_id_column();
|
||||||
|
let cf_tti: Arc<BoundColumnFamily<'_>> = self.tx_hash_to_id_column();
|
||||||
|
let last_curr_block = self.get_meta_last_block_in_db()?;
|
||||||
|
let mut write_batch = WriteBatch::default();
|
||||||
|
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_block,
|
||||||
|
borsh::to_vec(&block.header.block_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(err, Some("Failed to serialize block id".to_owned()))
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(block).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(err, Some("Failed to serialize block data".to_owned()))
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
|
||||||
|
if block.header.block_id > last_curr_block {
|
||||||
|
self.put_meta_last_block_in_db_batch(block.header.block_id, &mut write_batch)?;
|
||||||
|
self.put_meta_last_observed_l1_lib_header_in_db_batch(l1_lib_header, &mut write_batch)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_hti,
|
||||||
|
borsh::to_vec(&block.header.hash).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(err, Some("Failed to serialize block hash".to_owned()))
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(&block.header.block_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(err, Some("Failed to serialize block id".to_owned()))
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut acc_to_tx_map: HashMap<[u8; 32], Vec<[u8; 32]>> = HashMap::new();
|
||||||
|
|
||||||
|
for tx in &block.body.transactions {
|
||||||
|
let tx_hash = tx.hash();
|
||||||
|
|
||||||
|
write_batch.put_cf(
|
||||||
|
&cf_tti,
|
||||||
|
borsh::to_vec(&tx_hash).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(err, Some("Failed to serialize tx hash".to_owned()))
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(&block.header.block_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize block id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let acc_ids = tx
|
||||||
|
.affected_public_account_ids()
|
||||||
|
.into_iter()
|
||||||
|
.map(nssa::AccountId::into_value)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for acc_id in acc_ids {
|
||||||
|
acc_to_tx_map
|
||||||
|
.entry(acc_id)
|
||||||
|
.and_modify(|tx_hashes| tx_hashes.push(tx_hash.into()))
|
||||||
|
.or_insert_with(|| vec![tx_hash.into()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::iter_over_hash_type,
|
||||||
|
reason = "RocksDB will keep ordering persistent"
|
||||||
|
)]
|
||||||
|
for (acc_id, tx_hashes) in acc_to_tx_map {
|
||||||
|
self.put_account_transactions_dependant(acc_id, &tx_hashes, &mut write_batch)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.write(write_batch).map_err(|rerr| {
|
||||||
|
DbError::rocksdb_cast_message(rerr, Some("Failed to write batch".to_owned()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if block
|
||||||
|
.header
|
||||||
|
.block_id
|
||||||
|
.is_multiple_of(BREAKPOINT_INTERVAL.into())
|
||||||
|
{
|
||||||
|
self.put_next_breakpoint()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
147
storage/src/indexer/write_non_atomic.rs
Normal file
147
storage/src/indexer/write_non_atomic.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
use super::{
|
||||||
|
BREAKPOINT_INTERVAL, DB_META_FIRST_BLOCK_SET_KEY, DB_META_LAST_BLOCK_IN_DB_KEY,
|
||||||
|
DB_META_LAST_BREAKPOINT_ID, DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY, DbError,
|
||||||
|
DbResult, RocksDBIO, V03State,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[expect(clippy::multiple_inherent_impl, reason = "Readability")]
|
||||||
|
impl RocksDBIO {
|
||||||
|
// Meta
|
||||||
|
|
||||||
|
pub fn put_meta_last_block_in_db(&self, block_id: u64) -> DbResult<()> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
self.db
|
||||||
|
.put_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_LAST_BLOCK_IN_DB_KEY).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(&block_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize last block id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_meta_last_observed_l1_lib_header_in_db(
|
||||||
|
&self,
|
||||||
|
l1_lib_header: [u8; 32],
|
||||||
|
) -> DbResult<()> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
self.db
|
||||||
|
.put_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY).map_err(
|
||||||
|
|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some(
|
||||||
|
"Failed to serialize DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY"
|
||||||
|
.to_owned(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
borsh::to_vec(&l1_lib_header).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize last l1 block header".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_meta_last_breakpoint_id(&self, br_id: u64) -> DbResult<()> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
self.db
|
||||||
|
.put_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_LAST_BREAKPOINT_ID).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_LAST_BREAKPOINT_ID".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(&br_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize last block id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_meta_is_first_block_set(&self) -> DbResult<()> {
|
||||||
|
let cf_meta = self.meta_column();
|
||||||
|
self.db
|
||||||
|
.put_cf(
|
||||||
|
&cf_meta,
|
||||||
|
borsh::to_vec(&DB_META_FIRST_BLOCK_SET_KEY).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
[1_u8; 1],
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
pub fn put_breakpoint(&self, br_id: u64, breakpoint: &V03State) -> DbResult<()> {
|
||||||
|
let cf_br = self.breakpoint_column();
|
||||||
|
|
||||||
|
self.db
|
||||||
|
.put_cf(
|
||||||
|
&cf_br,
|
||||||
|
borsh::to_vec(&br_id).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize breakpoint id".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
borsh::to_vec(breakpoint).map_err(|err| {
|
||||||
|
DbError::borsh_cast_message(
|
||||||
|
err,
|
||||||
|
Some("Failed to serialize breakpoint data".to_owned()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_next_breakpoint(&self) -> DbResult<()> {
|
||||||
|
let last_block = self.get_meta_last_block_in_db()?;
|
||||||
|
let next_breakpoint_id = self
|
||||||
|
.get_meta_last_breakpoint_id()?
|
||||||
|
.checked_add(1)
|
||||||
|
.expect("Breakpoint Id will be lesser than u64::MAX");
|
||||||
|
let block_to_break_id = next_breakpoint_id
|
||||||
|
.checked_mul(u64::from(BREAKPOINT_INTERVAL))
|
||||||
|
.expect("Reached maximum breakpoint id");
|
||||||
|
|
||||||
|
if block_to_break_id <= last_block {
|
||||||
|
let next_breakpoint = self.calculate_state_for_id(block_to_break_id)?;
|
||||||
|
|
||||||
|
self.put_breakpoint(next_breakpoint_id, &next_breakpoint)?;
|
||||||
|
self.put_meta_last_breakpoint_id(next_breakpoint_id)
|
||||||
|
} else {
|
||||||
|
Err(DbError::db_interaction_error(
|
||||||
|
"Breakpoint not yet achieved".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = u128;
|
type Instruction = u128;
|
||||||
|
|
||||||
@ -19,9 +19,10 @@ fn main() {
|
|||||||
let mut account_post = account_pre.clone();
|
let mut account_post = account_pre.clone();
|
||||||
account_post.balance = account_post.balance.saturating_sub(balance_to_burn);
|
account_post.balance = account_post.balance.saturating_sub(balance_to_burn);
|
||||||
|
|
||||||
write_nssa_outputs(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pre],
|
vec![pre],
|
||||||
vec![AccountPostState::new(account_post)],
|
vec![AccountPostState::new(account_post)],
|
||||||
);
|
)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{
|
||||||
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs,
|
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput,
|
||||||
write_nssa_outputs_with_chained_call,
|
read_nssa_inputs,
|
||||||
};
|
};
|
||||||
use risc0_zkvm::serde::to_vec;
|
use risc0_zkvm::serde::to_vec;
|
||||||
|
|
||||||
@ -54,13 +54,14 @@ fn main() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
write_nssa_outputs_with_chained_call(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![sender_pre.clone(), recipient_pre.clone()],
|
vec![sender_pre.clone(), recipient_pre.clone()],
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new(sender_pre.account),
|
AccountPostState::new(sender_pre.account),
|
||||||
AccountPostState::new(recipient_pre.account),
|
AccountPostState::new(recipient_pre.account),
|
||||||
],
|
],
|
||||||
chained_calls,
|
)
|
||||||
);
|
.with_chained_calls(chained_calls)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = (Option<Vec<u8>>, bool);
|
type Instruction = (Option<Vec<u8>>, bool);
|
||||||
|
|
||||||
@ -33,5 +33,5 @@ fn main() {
|
|||||||
AccountPostState::new(account_post)
|
AccountPostState::new(account_post)
|
||||||
};
|
};
|
||||||
|
|
||||||
write_nssa_outputs(instruction_words, vec![pre], vec![post_state]);
|
ProgramOutput::new(instruction_words, vec![pre], vec![post_state]).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -17,5 +17,5 @@ fn main() {
|
|||||||
|
|
||||||
let account_post = AccountPostState::new_claimed(pre.account.clone());
|
let account_post = AccountPostState::new_claimed(pre.account.clone());
|
||||||
|
|
||||||
write_nssa_outputs(instruction_words, vec![pre], vec![account_post]);
|
ProgramOutput::new(instruction_words, vec![pre], vec![account_post]).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = Vec<u8>;
|
type Instruction = Vec<u8>;
|
||||||
|
|
||||||
@ -22,9 +22,10 @@ fn main() {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.expect("provided data should fit into data limit");
|
.expect("provided data should fit into data limit");
|
||||||
|
|
||||||
write_nssa_outputs(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pre],
|
vec![pre],
|
||||||
vec![AccountPostState::new_claimed(account_post)],
|
vec![AccountPostState::new_claimed(account_post)],
|
||||||
);
|
)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::Account,
|
account::Account,
|
||||||
program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs},
|
||||||
};
|
};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
@ -14,12 +14,13 @@ fn main() {
|
|||||||
|
|
||||||
let account_pre = pre.account.clone();
|
let account_pre = pre.account.clone();
|
||||||
|
|
||||||
write_nssa_outputs(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pre],
|
vec![pre],
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new(account_pre),
|
AccountPostState::new(account_pre),
|
||||||
AccountPostState::new(Account::default()),
|
AccountPostState::new(Account::default()),
|
||||||
],
|
],
|
||||||
);
|
)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::AccountWithMetadata,
|
account::AccountWithMetadata,
|
||||||
program::{
|
program::{
|
||||||
AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs,
|
AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
write_nssa_outputs_with_chained_call,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use risc0_zkvm::serde::to_vec;
|
use risc0_zkvm::serde::to_vec;
|
||||||
@ -40,13 +39,14 @@ fn main() {
|
|||||||
pda_seeds: vec![],
|
pda_seeds: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
write_nssa_outputs_with_chained_call(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![sender.clone(), receiver.clone()],
|
vec![sender.clone(), receiver.clone()],
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new(sender.account),
|
AccountPostState::new(sender.account),
|
||||||
AccountPostState::new(receiver.account),
|
AccountPostState::new(receiver.account),
|
||||||
],
|
],
|
||||||
vec![chained_call],
|
)
|
||||||
);
|
.with_chained_calls(vec![chained_call])
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -16,9 +16,10 @@ fn main() {
|
|||||||
.checked_add(1)
|
.checked_add(1)
|
||||||
.expect("Balance overflow");
|
.expect("Balance overflow");
|
||||||
|
|
||||||
write_nssa_outputs(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pre],
|
vec![pre],
|
||||||
vec![AccountPostState::new(account_post)],
|
vec![AccountPostState::new(account_post)],
|
||||||
);
|
)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -11,9 +11,10 @@ fn main() {
|
|||||||
|
|
||||||
let account_pre1 = pre1.account.clone();
|
let account_pre1 = pre1.account.clone();
|
||||||
|
|
||||||
write_nssa_outputs(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pre1, pre2],
|
vec![pre1, pre2],
|
||||||
vec![AccountPostState::new(account_pre1)],
|
vec![AccountPostState::new(account_pre1)],
|
||||||
);
|
)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs},
|
program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initializes a default account under the ownership of this program.
|
/// Initializes a default account under the ownership of this program.
|
||||||
@ -80,5 +80,5 @@ fn main() {
|
|||||||
}
|
}
|
||||||
_ => panic!("invalid params"),
|
_ => panic!("invalid params"),
|
||||||
};
|
};
|
||||||
write_nssa_outputs(instruction_data, pre_states, post_states);
|
ProgramOutput::new(instruction_data, pre_states, post_states).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -13,9 +13,10 @@ fn main() {
|
|||||||
let mut account_post = account_pre.clone();
|
let mut account_post = account_pre.clone();
|
||||||
account_post.nonce.public_account_nonce_increment();
|
account_post.nonce.public_account_nonce_increment();
|
||||||
|
|
||||||
write_nssa_outputs(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pre],
|
vec![pre],
|
||||||
vec![AccountPostState::new(account_post)],
|
vec![AccountPostState::new(account_post)],
|
||||||
);
|
)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -9,5 +9,5 @@ fn main() {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|account| AccountPostState::new(account.account.clone()))
|
.map(|account| AccountPostState::new(account.account.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
write_nssa_outputs(instruction_words, pre_states, post_states);
|
ProgramOutput::new(instruction_words, pre_states, post_states).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -13,9 +13,10 @@ fn main() {
|
|||||||
let mut account_post = account_pre.clone();
|
let mut account_post = account_pre.clone();
|
||||||
account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7];
|
account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7];
|
||||||
|
|
||||||
write_nssa_outputs(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pre],
|
vec![pre],
|
||||||
vec![AccountPostState::new(account_post)],
|
vec![AccountPostState::new(account_post)],
|
||||||
);
|
)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = u128;
|
type Instruction = u128;
|
||||||
|
|
||||||
@ -26,12 +26,13 @@ fn main() {
|
|||||||
.checked_add(balance)
|
.checked_add(balance)
|
||||||
.expect("Overflow when adding balance");
|
.expect("Overflow when adding balance");
|
||||||
|
|
||||||
write_nssa_outputs(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![sender_pre, receiver_pre],
|
vec![sender_pre, receiver_pre],
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new(sender_post),
|
AccountPostState::new(sender_post),
|
||||||
AccountPostState::new(receiver_post),
|
AccountPostState::new(receiver_post),
|
||||||
],
|
],
|
||||||
);
|
)
|
||||||
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
29
test_program_methods/guest/src/bin/validity_window.rs
Normal file
29
test_program_methods/guest/src/bin/validity_window.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
use nssa_core::program::{
|
||||||
|
AccountPostState, ProgramInput, ProgramOutput, ValidityWindow, read_nssa_inputs,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Instruction = ValidityWindow;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let (
|
||||||
|
ProgramInput {
|
||||||
|
pre_states,
|
||||||
|
instruction: validity_window,
|
||||||
|
},
|
||||||
|
instruction_words,
|
||||||
|
) = read_nssa_inputs::<Instruction>();
|
||||||
|
|
||||||
|
let Ok([pre]) = <[_; 1]>::try_from(pre_states) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let post = pre.account.clone();
|
||||||
|
|
||||||
|
ProgramOutput::new(
|
||||||
|
instruction_words,
|
||||||
|
vec![pre],
|
||||||
|
vec![AccountPostState::new(post)],
|
||||||
|
)
|
||||||
|
.with_validity_window(validity_window)
|
||||||
|
.write();
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
use nssa_core::program::{
|
||||||
|
AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, ValidityWindow,
|
||||||
|
read_nssa_inputs,
|
||||||
|
};
|
||||||
|
use risc0_zkvm::serde::to_vec;
|
||||||
|
|
||||||
|
/// A program that sets a validity window on its output and chains to another program with a
|
||||||
|
/// potentially different validity window.
|
||||||
|
///
|
||||||
|
/// Instruction: (`window`, `chained_program_id`, `chained_window`)
|
||||||
|
/// The initial output uses `window` and chains to `chained_program_id` with `chained_window`.
|
||||||
|
type Instruction = (ValidityWindow, ProgramId, ValidityWindow);
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let (
|
||||||
|
ProgramInput {
|
||||||
|
pre_states,
|
||||||
|
instruction: (validity_window, chained_program_id, chained_validity_window),
|
||||||
|
},
|
||||||
|
instruction_words,
|
||||||
|
) = read_nssa_inputs::<Instruction>();
|
||||||
|
|
||||||
|
let [pre] = <[_; 1]>::try_from(pre_states.clone()).expect("Expected exactly one pre state");
|
||||||
|
let post = pre.account.clone();
|
||||||
|
|
||||||
|
let chained_instruction = to_vec(&chained_validity_window).unwrap();
|
||||||
|
let chained_call = ChainedCall {
|
||||||
|
program_id: chained_program_id,
|
||||||
|
instruction_data: chained_instruction,
|
||||||
|
pre_states,
|
||||||
|
pda_seeds: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
ProgramOutput::new(
|
||||||
|
instruction_words,
|
||||||
|
vec![pre],
|
||||||
|
vec![AccountPostState::new(post)],
|
||||||
|
)
|
||||||
|
.with_validity_window(validity_window)
|
||||||
|
.with_chained_calls(vec![chained_call])
|
||||||
|
.write();
|
||||||
|
}
|
||||||
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