update from main

This commit is contained in:
Marvin Jones 2026-05-29 09:43:30 -04:00
commit 9317c98a90
418 changed files with 5881 additions and 3354 deletions

View File

@ -15,7 +15,11 @@ ignore = [
{ id = "RUSTSEC-2023-0089", reason = "atomic-polyfill is pulled transitively via risc0-zkvm; waiting on upstream fix (see https://github.com/risc0/risc0/issues/3453)" },
{ id = "RUSTSEC-2026-0118", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" },
{ id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" },
{ id = "RUSTSEC-2025-0137", reason = "newest `rint` depends on rustc v1.90.0 and we build artifacts with v1.88.8" },
{ id = "RUSTSEC-2024-0370", reason = "transitive dependency of `logos-blockchain-http-api-common`, can't do anything than wait for upstream fix" },
>>>>>>> refs/rewritten/onto
]
yanked = "deny"
unused-ignored-advisory = "deny"
@ -56,6 +60,7 @@ unused-allowed-license = "deny"
allow-git = [
"https://github.com/EspressoSystems/jellyfish.git",
"https://github.com/logos-blockchain/logos-blockchain.git",
"https://github.com/logos-blockchain/logos-blockchain-circuits.git",
]
unknown-git = "deny"
unknown-registry = "deny"

View File

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

View File

@ -2,8 +2,8 @@ on:
pull_request:
paths:
- "tools/crypto_primitives_bench/**"
- "key_protocol/**"
- "nssa/core/**"
- "lez/key_protocol/**"
- "lee/core/**"
- ".github/workflows/bench-regression.yml"
permissions:

View File

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

View File

@ -27,9 +27,9 @@ Allowed `type` values:
- `revert`
Examples:
- `feat(nssa): add private PDA support`
- `feat(lee): add private PDA support`
- `fix(wallet): correct fee calculation`
- `feat(nssa)!: rename AccountId::from((prog, seed)) to AccountId::for_public_pda`
- `feat(lee)!: rename AccountId::from((prog, seed)) to AccountId::for_public_pda`
Breaking changes:
- Mark with `!` in the title.

1221
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,14 @@ license = "MIT or Apache-2.0"
resolver = "3"
members = [
"integration_tests",
"storage",
"key_protocol",
"mempool",
"wallet",
"wallet-ffi",
"common",
"nssa",
"nssa/core",
"lez/storage",
"lee/key_protocol",
"lee/state_machine",
"lee/state_machine/core",
"lez/mempool",
"lez/wallet",
"lez/wallet-ffi",
"lez/common",
"programs/amm/core",
"programs/amm",
"programs/clock/core",
@ -22,16 +22,17 @@ members = [
"programs/associated_token_account",
"programs/authenticated_transfer/core",
"programs/faucet/core",
"programs/bridge/core",
"programs/vault/core",
"sequencer/core",
"sequencer/service",
"sequencer/service/protocol",
"sequencer/service/rpc",
"indexer/core",
"indexer/service",
"indexer/service/protocol",
"indexer/service/rpc",
"explorer_service",
"lez/sequencer/core",
"lez/sequencer/service",
"lez/sequencer/service/protocol",
"lez/sequencer/service/rpc",
"lez/indexer/core",
"lez/indexer/service",
"lez/indexer/service/protocol",
"lez/indexer/service/rpc",
"lez/explorer_service",
"program_methods",
"program_methods/guest",
"test_program_methods",
@ -39,9 +40,10 @@ members = [
"examples/program_deployment",
"examples/program_deployment/methods",
"examples/program_deployment/methods/guest",
"testnet_initial_state",
"indexer/ffi",
"keycard_wallet",
"lez/testnet_initial_state",
"lez/indexer/ffi",
"lez",
"lez/keycard_wallet",
"test_fixtures",
"tools/cycle_bench",
"tools/crypto_primitives_bench",
@ -49,23 +51,24 @@ members = [
]
[workspace.dependencies]
nssa = { path = "nssa" }
nssa_core = { path = "nssa/core" }
common = { path = "common" }
mempool = { path = "mempool" }
storage = { path = "storage" }
key_protocol = { path = "key_protocol" }
sequencer_core = { path = "sequencer/core" }
sequencer_service_protocol = { path = "sequencer/service/protocol" }
sequencer_service_rpc = { path = "sequencer/service/rpc" }
sequencer_service = { path = "sequencer/service" }
indexer_core = { path = "indexer/core" }
indexer_service = { path = "indexer/service" }
indexer_service_protocol = { path = "indexer/service/protocol" }
indexer_service_rpc = { path = "indexer/service/rpc" }
wallet = { path = "wallet" }
wallet-ffi = { path = "wallet-ffi", default-features = false }
indexer_ffi = { path = "indexer/ffi" }
lee = { path = "lee/state_machine" }
lee_core = { path = "lee/state_machine/core" }
common = { path = "lez/common" }
mempool = { path = "lez/mempool" }
storage = { path = "lez/storage" }
key_protocol = { path = "lee/key_protocol" }
sequencer_core = { path = "lez/sequencer/core" }
sequencer_service_protocol = { path = "lez/sequencer/service/protocol" }
sequencer_service_rpc = { path = "lez/sequencer/service/rpc" }
sequencer_service = { path = "lez/sequencer/service" }
indexer_core = { path = "lez/indexer/core" }
indexer_service = { path = "lez/indexer/service" }
indexer_service_protocol = { path = "lez/indexer/service/protocol" }
indexer_service_rpc = { path = "lez/indexer/service/rpc" }
wallet = { path = "lez/wallet" }
wallet-ffi = { path = "lez/wallet-ffi", default-features = false }
indexer_ffi = { path = "lez/indexer/ffi" }
lez = { path = "lez" }
clock_core = { path = "programs/clock/core" }
token_core = { path = "programs/token/core" }
token_program = { path = "programs/token" }
@ -75,10 +78,11 @@ ata_core = { path = "programs/associated_token_account/core" }
ata_program = { path = "programs/associated_token_account" }
authenticated_transfer_core = { path = "programs/authenticated_transfer/core" }
faucet_core = { path = "programs/faucet/core" }
bridge_core = { path = "programs/bridge/core" }
vault_core = { path = "programs/vault/core" }
test_program_methods = { path = "test_program_methods" }
testnet_initial_state = { path = "testnet_initial_state" }
keycard_wallet = { path = "keycard_wallet" }
testnet_initial_state = { path = "lez/testnet_initial_state" }
keycard_wallet = { path = "lez/keycard_wallet" }
test_fixtures = { path = "test_fixtures" }
tokio = { version = "1.50", features = [
@ -134,12 +138,13 @@ tokio-retry = "0.3.0"
schemars = "1.2"
async-stream = "0.3.6"
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-zone-sdk = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-zone-sdk = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
logos-blockchain-http-api-common = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "db9a8d821c1b20f29b03d02072817150cf969b8e" }
rocksdb = { version = "0.24.0", default-features = false, features = [
"snappy",
@ -153,6 +158,7 @@ k256 = { version = "0.13.3", features = [
"serde",
"pem",
] }
ml-kem = { version = "0.3", features = ["hazmat"] }
elliptic-curve = { version = "0.13.8", features = ["arithmetic"] }
actix-web = { version = "4.13.0", default-features = false, features = [
"macros",

View File

@ -36,13 +36,13 @@ run-bedrock:
docker compose up
# Run Sequencer
[working-directory: 'sequencer/service']
[working-directory: 'lez/sequencer/service']
run-sequencer:
@echo "🧠 Running sequencer"
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p sequencer_service configs/debug/sequencer_config.json
# Run Indexer
[working-directory: 'indexer/service']
[working-directory: 'lez/indexer/service']
run-indexer mock="":
@echo "🔍 Running indexer"
@if [ "{{mock}}" = "mock" ]; then \
@ -54,23 +54,23 @@ run-indexer mock="":
fi
# Run Explorer
[working-directory: 'explorer_service']
[working-directory: 'lez/explorer_service']
run-explorer:
@echo "🌐 Running explorer"
RUST_LOG=info cargo leptos serve
# Run Wallet
[working-directory: 'wallet']
[working-directory: 'lez/wallet']
run-wallet +args:
@echo "🔑 Running wallet"
NSSA_WALLET_HOME_DIR=$(pwd)/configs/debug cargo run --release -p wallet -- {{args}}
LEE_WALLET_HOME_DIR=$(pwd)/configs/debug cargo run --release -p wallet -- {{args}}
# Clean runtime data
clean:
@echo "🧹 Cleaning run artifacts"
rm -rf sequencer/service/bedrock_signing_key
rm -rf sequencer/service/rocksdb
rm -rf indexer/service/rocksdb
rm -rf wallet/configs/debug/storage.json
rm -rf lez/sequencer/service/bedrock_signing_key
rm -rf lez/sequencer/service/rocksdb
rm -rf lez/indexer/service/rocksdb
rm -rf lez/wallet/configs/debug/storage.json
rm -rf rocksdb
cd bedrock && docker compose down -v

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -67,7 +67,9 @@ cryptarchia:
- opcode: 17
payload:
channel_id: '0000000000000000000000000000000000000000000000000000000000000000'
inscription: '67656e65736973'
# chain_id_len=12 (u64_le), chain_id=logos-devnet (utf-8),
# genesis_time=2026-01-10T07:47:56Z (u64_le), epoch_nonce=[0u8; 32]
inscription: '0c000000000000006c6f676f732d6465766e65742c046269000000000000000000000000000000000000000000000000000000000000000000000000'
parent: '0000000000000000000000000000000000000000000000000000000000000000'
signer: '0000000000000000000000000000000000000000000000000000000000000000'
execution_gas_price: 0

View File

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

View File

@ -1,6 +1,6 @@
# Wallet CLI Completion
Completion scripts for the LSSA `wallet` command.
Completion scripts for the LEZ `wallet` command.
## ZSH

View File

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

View File

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

View File

@ -16,28 +16,28 @@ Installation:
1. Install math applet on your keycard; this process only needs to be done once. In the root of repo:
```
sudo apt-get install -y default-jdk
wget https://github.com/martinpaljak/GlobalPlatformPro/releases/download/v25.10.20/gp.jar -P keycard_wallet/keycard_applets
cd keycard_wallet/keycard_applets
wget https://github.com/martinpaljak/GlobalPlatformPro/releases/download/v25.10.20/gp.jar -P lez/keycard_wallet/keycard_applets
cd lez/keycard_wallet/keycard_applets
java -jar gp.jar --key c212e073ff8b4bbfaff4de8ab655221f --load math.cap
```
2. Install `keycard-desktop` from [github](https://github.com/choppu/keycard-desktop)
- Keycard Desktop is used to install the LEE key protocol to a blank keycard.
- Select (Re)Install Applet and upload the key binary (`keycard_wallet/keycard_applets/LEE_keycard.cap`).
- Select (Re)Install Applet and upload the key binary (`lez/keycard_wallet/keycard_applets/LEE_keycard.cap`).
![keycard-desktop.png](keycard-desktop.png)
- **Important:** keycard can only connect with one application at a time; if Keycard-Desktop is using keycard then Wallet CLI cannot access the same keycard, and vice-versa.
## Wallet with Keycard
Keycard functionality is available to Wallet CLI by setting up the following Python virtual environment. The steps below can also be run via `keycard_wallet/wallet_with_keycard.sh`.
Keycard functionality is available to Wallet CLI by setting up the following Python virtual environment. The steps below can also be run via `lez/keycard_wallet/wallet_with_keycard.sh`.
```bash
# Install appropriate version of `keycard-py`.
git clone --branch lee-schnorr --single-branch https://github.com/bitgamma/keycard-py.git keycard_wallet/python/keycard-py
git clone --branch lee-schnorr --single-branch https://github.com/bitgamma/keycard-py.git lez/keycard_wallet/python/keycard-py
# Set up virtual environment.
python3 -m venv venv
source venv/bin/activate
pip install pyscard mnemonic ecdsa pyaes
pip install -e keycard_wallet/python/keycard-py
pip install -e lez/keycard_wallet/python/keycard-py
```
**Important**: Keycard wallet commands only work within the virtual environment.

View File

@ -155,7 +155,7 @@ wallet account new private
# Output:
Generated new account with account_id Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL
With npk e6366f79d026c8bd64ae6b3d601f0506832ec682ab54897f205fffe64ec0d951
With vpk 02ddc96d0eb56e00ce14994cfdaec5ae1f76244180a919545983156e3519940a17
With vpk <1184-byte ML-KEM-768 encapsulation key, hex-encoded>
```
> [!Tip]
@ -231,19 +231,29 @@ wallet account new private-accounts-key
# Output:
Generated new private accounts key at path /1
With npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e
With vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72
With vpk <1184-byte ML-KEM-768 encapsulation key, hex-encoded>
```
> [!Tip]
> Ignore the account ID here and use the `npk` and `vpk` values to send to a foreign private account.
> [!Important]
> The VPK is now a 1184-byte ML-KEM-768 encapsulation key — too large to copy-paste into a command.
> The recommended workflow is:
>
> **Recipient:** export both keys to a single file and send the file to the sender (e.g. as an email attachment):
> ```bash
> wallet account show-keys --account-id Private/<account-id> > recipient.keys
> # Send recipient.keys to the sender out-of-band
> ```
> The file contains two lines: the npk (hex) on line 1, the vpk (hex) on line 2.
>
> **Sender:** reference the received file with `--to-keys`:
### b. Send 3 tokens using the recipients npk and vpk
### b. Send 3 tokens using the recipients keys file
```bash
# The sender has received recipient.keys from the recipient out-of-band
wallet auth-transfer send \
--from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
--to-npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e \
--to-vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72 \
--to-keys recipient.keys \
--amount 3
```
@ -270,18 +280,19 @@ wallet account new private-accounts-key
# Output:
Generated new private accounts key at path /2
With npk a3f7c21b8e905d4f6a1bc783d0e2f94c1d5a6b7e8f9012345678abcdef012345
With vpk 03b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6071819202122232425262728292a2b2c
With vpk <1184-byte ML-KEM-768 encapsulation key, hex-encoded>
```
Alice shares the `npk` and `vpk` values with Bob and Charlie out of band.
### b. Bob sends 10 tokens to Alice using identifier 1
Bob uses the received `alice.keys` file:
```bash
wallet auth-transfer send \
--from Public/BobXqJprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPA \
--to-npk a3f7c21b8e905d4f6a1bc783d0e2f94c1d5a6b7e8f9012345678abcdef012345 \
--to-vpk 03b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6071819202122232425262728292a2b2c \
--to-keys alice.keys \
--to-identifier 1 \
--amount 10
```
@ -291,8 +302,7 @@ wallet auth-transfer send \
```bash
wallet auth-transfer send \
--from Public/CharlieYrP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPB \
--to-npk a3f7c21b8e905d4f6a1bc783d0e2f94c1d5a6b7e8f9012345678abcdef012345 \
--to-vpk 03b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6071819202122232425262728292a2b2c \
--to-keys alice.keys \
--to-identifier 2 \
--amount 5
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
use nssa::{AccountId, program::Program};
use lee::{AccountId, program::Program};
use wallet::{AccountIdentity, WalletCore};
// Before running this example, compile the `hello_world.rs` guest program with:
//
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
//
// Note: you must run the above command from the root of the `lssa` repository.
// Note: you must run the above command from the root of the `logos-execution-zone` repository.
// Note: The compiled binary file is stored in
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world.bin
//

View File

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

View File

@ -1,6 +1,6 @@
use std::collections::HashMap;
use nssa::{
use lee::{
AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies,
program::Program,
};
@ -10,7 +10,7 @@ use wallet::{AccountIdentity, WalletCore};
//
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
//
// Note: you must run the above command from the root of the `lssa` repository.
// Note: you must run the above command from the root of the `logos-execution-zone` repository.
// Note: The compiled binary file is stored in
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin
//

View File

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

View File

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

View File

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

View File

@ -10,8 +10,8 @@ workspace = true
[dependencies]
test_fixtures.workspace = true
nssa_core = { workspace = true, features = ["host"] }
nssa.workspace = true
lee_core = { workspace = true, features = ["host"] }
lee.workspace = true
authenticated_transfer_core.workspace = true
sequencer_core = { workspace = true, features = ["default", "testnet"] }
wallet.workspace = true
@ -22,6 +22,7 @@ token_core.workspace = true
ata_core.workspace = true
vault_core.workspace = true
faucet_core.workspace = true
bridge_core.workspace = true
indexer_service_rpc = { workspace = true, features = ["client"] }
sequencer_service_rpc = { workspace = true, features = ["client"] }
wallet-ffi.workspace = true
@ -34,3 +35,7 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
hex.workspace = true
tempfile.workspace = true
bytesize.workspace = true
reqwest.workspace = true
borsh.workspace = true
logos-blockchain-http-api-common.workspace = true
logos-blockchain-core.workspace = true

View File

@ -3,4 +3,49 @@
//! non-test consumers (e.g. `integration_bench`) can depend on them without
//! pulling in the test files.
use std::time::Duration;
use anyhow::{Context as _, Result};
use log::info;
pub use test_fixtures::*;
/// Maximum time to wait for the indexer to catch up to the sequencer.
pub const L2_TO_L1_TIMEOUT: Duration = Duration::from_mins(6);
/// Poll the indexer until its last finalized block id reaches the sequencer's
/// current last block id or until [`L2_TO_L1_TIMEOUT`] elapses.
/// Returns the last indexer block id observed.
pub async fn wait_for_indexer_to_catch_up(ctx: &TestContext) -> Result<u64> {
use indexer_service_rpc::RpcClient as _;
let block_id_to_catch_up =
sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?;
let mut last_ind: u64 = 1;
let inner = async {
loop {
let ind = ctx
.indexer_client()
.get_last_finalized_block_id()
.await?
.unwrap_or(0);
last_ind = ind;
if ind >= block_id_to_catch_up {
let last_seq =
sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client())
.await?;
info!(
"Indexer caught up. Indexer last block id: {ind}. Current sequencer last block id: {last_seq}"
);
return Ok(ind);
}
tokio::time::sleep(Duration::from_secs(2)).await;
}
};
tokio::time::timeout(L2_TO_L1_TIMEOUT, inner)
.await
.with_context(|| {
format!(
"Indexer failed to catch up within {L2_TO_L1_TIMEOUT:?}. Last indexer block id observed: {last_ind}, but needed to catch up to at least {block_id_to_catch_up}"
)
})?
}

View File

@ -6,9 +6,9 @@
use anyhow::{Context as _, Result};
use integration_tests::{TestContext, private_mention};
use key_protocol::key_management::KeyChain;
use lee::{Data, program::Program};
use lee_core::account::Nonce;
use log::info;
use nssa::{Data, program::Program};
use nssa_core::account::Nonce;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::{
@ -127,8 +127,8 @@ async fn new_public_account_without_label() -> Result<()> {
async fn import_public_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let private_key = nssa::PrivateKey::new_os_random();
let account_id = nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(&private_key));
let private_key = lee::PrivateKey::new_os_random();
let account_id = lee::AccountId::from(&lee::PublicKey::new_from_private_key(&private_key));
let command = Command::Account(AccountSubcommand::Import(ImportSubcommand::Public {
private_key,
@ -156,8 +156,8 @@ async fn import_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let key_chain = KeyChain::new_os_random();
let account_id = nssa::AccountId::from((&key_chain.nullifier_public_key, 0));
let account = nssa::Account {
let account_id = lee::AccountId::from((&key_chain.nullifier_public_key, 0));
let account = lee::Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 777,
data: Data::default(),
@ -213,11 +213,11 @@ async fn import_private_account_second_time_overrides_account_data() -> Result<(
let mut ctx = TestContext::new().await?;
let key_chain = KeyChain::new_os_random();
let account_id = nssa::AccountId::from((&key_chain.nullifier_public_key, 0));
let account_id = lee::AccountId::from((&key_chain.nullifier_public_key, 0));
let key_chain_json =
serde_json::to_string(&key_chain).context("Failed to serialize key chain")?;
let initial_account = nssa::Account {
let initial_account = lee::Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
data: Data::default(),
@ -236,7 +236,7 @@ async fn import_private_account_second_time_overrides_account_data() -> Result<(
)
.await?;
let updated_account = nssa::Account {
let updated_account = lee::Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 999,
data: Data::default(),

View File

@ -132,6 +132,7 @@ async fn amm_public() -> Result<()> {
to: Some(public_mention(recipient_account_id_1)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 7,
};
@ -158,6 +159,7 @@ async fn amm_public() -> Result<()> {
to: Some(public_mention(recipient_account_id_2)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 7,
};
@ -530,6 +532,7 @@ async fn amm_new_pool_using_labels() -> Result<()> {
to: Some(public_mention(holding_a_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 5,
};
@ -551,6 +554,7 @@ async fn amm_new_pool_using_labels() -> Result<()> {
to: Some(public_mention(holding_b_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 5,
};

View File

@ -12,8 +12,8 @@ use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention,
verify_commitment_is_in_state,
};
use lee::program::Program;
use log::info;
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use token_core::{TokenDefinition, TokenHolding};
use tokio::test;
@ -24,7 +24,7 @@ use wallet::cli::{
};
/// Create a public account and return its ID.
async fn new_public_account(ctx: &mut TestContext) -> Result<nssa::AccountId> {
async fn new_public_account(ctx: &mut TestContext) -> Result<lee::AccountId> {
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
@ -40,7 +40,7 @@ async fn new_public_account(ctx: &mut TestContext) -> Result<nssa::AccountId> {
}
/// Create a private account and return its ID.
async fn new_private_account(ctx: &mut TestContext) -> Result<nssa::AccountId> {
async fn new_private_account(ctx: &mut TestContext) -> Result<lee::AccountId> {
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
@ -260,6 +260,7 @@ async fn transfer_and_burn_via_ata() -> Result<()> {
to: Some(public_mention(sender_ata_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: fund_amount,
}),
@ -487,6 +488,7 @@ async fn transfer_via_ata_private_owner() -> Result<()> {
to: Some(public_mention(sender_ata_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: fund_amount,
}),
@ -598,6 +600,7 @@ async fn burn_via_ata_private_owner() -> Result<()> {
to: Some(public_mention(holder_ata_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: fund_amount,
}),

View File

@ -1,14 +1,21 @@
use std::time::Duration;
use anyhow::{Context as _, Result};
use common::transaction::NSSATransaction;
use common::transaction::LeeTransaction;
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx, private_mention,
public_mention, verify_commitment_is_in_state,
};
use lee::{
AccountId, SharedSecretKey, execute_and_prove,
privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
};
use lee_core::{
InputAccountIdentity, NullifierPublicKey,
account::AccountWithMetadata,
encryption::{EphemeralPublicKey, ViewingPublicKey},
};
use log::info;
use nssa::{AccountId, program::Program};
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::{
@ -32,6 +39,7 @@ async fn private_transfer_to_owned_account() -> Result<()> {
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -65,13 +73,14 @@ async fn private_transfer_to_foreign_account() -> Result<()> {
let from: AccountId = ctx.existing_private_accounts()[0];
let to_npk = NullifierPublicKey([42; 32]);
let to_npk_string = hex::encode(to_npk.0);
let to_vpk = Secp256k1Point::from_scalar(to_npk.0);
let to_vpk = ViewingPublicKey::from_seed(&[0_u8; 32], &[1_u8; 32]);
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: private_mention(from),
to: None,
to_npk: Some(to_npk_string),
to_vpk: Some(hex::encode(to_vpk.0)),
to_vpk: Some(hex::encode(to_vpk.to_bytes())),
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -121,6 +130,7 @@ async fn deshielded_transfer_to_public_account() -> Result<()> {
to: Some(public_mention(to)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -183,7 +193,8 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
from: private_mention(from),
to: None,
to_npk: Some(hex::encode(to.key_chain.nullifier_public_key.0)),
to_vpk: Some(hex::encode(&to.key_chain.viewing_public_key.0)),
to_vpk: Some(hex::encode(to.key_chain.viewing_public_key.to_bytes())),
to_keys: None,
to_identifier: Some(to.kind.identifier()),
amount: 100,
});
@ -233,6 +244,7 @@ async fn shielded_transfer_to_owned_private_account() -> Result<()> {
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -268,14 +280,15 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> {
let to_npk = NullifierPublicKey([42; 32]);
let to_npk_string = hex::encode(to_npk.0);
let to_vpk = Secp256k1Point::from_scalar(to_npk.0);
let to_vpk = ViewingPublicKey::from_seed(&[0_u8; 32], &[1_u8; 32]);
let from: AccountId = ctx.existing_public_accounts()[0];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: public_mention(from),
to: None,
to_npk: Some(to_npk_string),
to_vpk: Some(hex::encode(to_vpk.0)),
to_vpk: Some(hex::encode(to_vpk.to_bytes())),
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -345,7 +358,8 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
from: private_mention(from),
to: None,
to_npk: Some(hex::encode(to.key_chain.nullifier_public_key.0)),
to_vpk: Some(hex::encode(&to.key_chain.viewing_public_key.0)),
to_vpk: Some(hex::encode(to.key_chain.viewing_public_key.to_bytes())),
to_keys: None,
to_identifier: Some(to.kind.identifier()),
amount: 100,
});
@ -446,6 +460,7 @@ async fn private_transfer_using_from_label() -> Result<()> {
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -539,7 +554,7 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> {
};
let npk_hex = hex::encode(npk.0);
let vpk_hex = hex::encode(vpk.0);
let vpk_hex = hex::encode(vpk.to_bytes());
let identifier_1 = 1_u128;
let identifier_2 = 2_u128;
@ -554,6 +569,7 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> {
to: None,
to_npk: Some(npk_hex.clone()),
to_vpk: Some(vpk_hex.clone()),
to_keys: None,
to_identifier: Some(identifier_1),
amount: 100,
}),
@ -567,6 +583,7 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> {
to: None,
to_npk: Some(npk_hex),
to_vpk: Some(vpk_hex),
to_keys: None,
to_identifier: Some(identifier_2),
amount: 200,
}),
@ -626,37 +643,31 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> {
}
#[test]
async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> {
use nssa::{
EphemeralPublicKey, SharedSecretKey, execute_and_prove,
privacy_preserving_transaction::{self, circuit::ProgramWithDependencies},
};
use nssa_core::{InputAccountIdentity, account::AccountWithMetadata};
async fn ppt_cant_chain_call_faucet() -> Result<()> {
let ctx = TestContext::new().await?;
let binary = std::fs::read(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../artifacts/test_program_methods/faucet_chain_caller.bin"),
)?;
let deploy_tx = NSSATransaction::ProgramDeployment(nssa::ProgramDeploymentTransaction::new(
nssa::program_deployment_transaction::Message::new(binary.clone()),
let deploy_tx = LeeTransaction::ProgramDeployment(lee::ProgramDeploymentTransaction::new(
lee::program_deployment_transaction::Message::new(binary.clone()),
));
ctx.sequencer_client().send_transaction(deploy_tx).await?;
info!("Waiting for deploy block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let faucet_account_id = nssa::system_faucet_account_id();
let faucet_account_id = lee::system_faucet_account_id();
let attacker_id = ctx.existing_public_accounts()[0];
let faucet_program_id = Program::faucet().id();
let vault_program_id = Program::vault().id();
let auth_transfer_program_id = Program::authenticated_transfer_program().id();
let nsk: nssa_core::NullifierSecretKey = [3; 32];
let nsk: lee_core::NullifierSecretKey = [3; 32];
let npk = NullifierPublicKey::from(&nsk);
let vpk = Secp256k1Point::from_scalar([4; 32]);
let ssk = SharedSecretKey::new([55; 32], &vpk);
let epk = EphemeralPublicKey::from_scalar([55; 32]);
let _vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap();
let ssk = SharedSecretKey([55_u8; 32]);
let _epk = EphemeralPublicKey(vec![55_u8; 1088]);
let attacker_vault_id = {
let seed = vault_core::compute_vault_seed(attacker_id);
AccountId::for_private_pda(&vault_program_id, &seed, &npk, 1337)
@ -695,7 +706,7 @@ async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> {
let instruction =
Program::serialize_instruction((faucet_program_id, vault_program_id, attacker_id, amount))?;
let (output, proof) = execute_and_prove(
let res = execute_and_prove(
vec![faucet_pre, vault_pda_pre],
instruction,
vec![
@ -708,47 +719,9 @@ async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> {
},
],
&program_with_deps,
)?;
);
let message = privacy_preserving_transaction::Message::try_from_circuit_output(
vec![faucet_account_id],
vec![],
vec![(npk, vpk, epk)],
output,
)?;
let witness_set = privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]);
let attack_ppt = NSSATransaction::PrivacyPreserving(nssa::PrivacyPreservingTransaction::new(
message,
witness_set,
));
let faucet_balance_before = ctx
.sequencer_client()
.get_account_balance(faucet_account_id)
.await?;
let vault_balance_before = ctx
.sequencer_client()
.get_account_balance(attacker_vault_id)
.await?;
let tx_hash = ctx.sequencer_client().send_transaction(attack_ppt).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let faucet_balance_after = ctx
.sequencer_client()
.get_account_balance(faucet_account_id)
.await?;
let vault_balance_after = ctx
.sequencer_client()
.get_account_balance(attacker_vault_id)
.await?;
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
assert_eq!(faucet_balance_after, faucet_balance_before);
assert_eq!(vault_balance_after, vault_balance_before);
assert!(tx_on_chain.is_none());
assert!(res.is_err());
Ok(())
}

View File

@ -1,10 +1,10 @@
use std::{path::PathBuf, time::Duration};
use anyhow::Result;
use common::transaction::NSSATransaction;
use common::transaction::LeeTransaction;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, public_mention};
use lee::{program::Program, public_transaction, system_faucet_account_id};
use log::info;
use nssa::{program::Program, public_transaction, system_faucet_account_id};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::{
@ -25,6 +25,7 @@ async fn successful_transfer_to_existing_account() -> Result<()> {
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -83,6 +84,7 @@ pub async fn successful_transfer_to_new_account() -> Result<()> {
to: Some(public_mention(new_persistent_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -120,6 +122,7 @@ async fn failed_transfer_with_insufficient_balance() -> Result<()> {
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 1_000_000,
});
@ -159,6 +162,7 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -192,6 +196,7 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -274,6 +279,7 @@ async fn successful_transfer_using_from_label() -> Result<()> {
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -319,6 +325,7 @@ async fn successful_transfer_using_to_label() -> Result<()> {
to: Some(CliAccountMention::Label(label)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -368,13 +375,13 @@ async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> {
vec![],
authenticated_transfer_core::Instruction::Transfer { amount },
)?;
let tx = nssa::PublicTransaction::new(
let tx = lee::PublicTransaction::new(
message,
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
);
let tx_hash = ctx
.sequencer_client()
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await?;
info!("Waiting for next block creation");
@ -420,19 +427,19 @@ async fn cannot_execute_faucet_program() -> Result<()> {
Program::faucet().id(),
vec![faucet_account_id, recipient_vault_id],
vec![],
faucet_core::Instruction::Transfer {
faucet_core::Instruction::GenesisTransferVault {
vault_program_id,
recipient_id: recipient,
amount,
},
)?;
let tx = nssa::PublicTransaction::new(
let tx = lee::PublicTransaction::new(
message,
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
);
let tx_hash = ctx
.sequencer_client()
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await?;
info!("Waiting for next block creation");
@ -464,8 +471,8 @@ async fn user_tx_that_chain_calls_faucet_is_dropped() -> Result<()> {
.join("../artifacts/test_program_methods/faucet_chain_caller.bin"),
)?;
let faucet_chain_caller_id = Program::new(binary.clone())?.id();
let deploy_tx = NSSATransaction::ProgramDeployment(nssa::ProgramDeploymentTransaction::new(
nssa::program_deployment_transaction::Message::new(binary),
let deploy_tx = LeeTransaction::ProgramDeployment(lee::ProgramDeploymentTransaction::new(
lee::program_deployment_transaction::Message::new(binary),
));
ctx.sequencer_client().send_transaction(deploy_tx).await?;
@ -485,9 +492,9 @@ async fn user_tx_that_chain_calls_faucet_is_dropped() -> Result<()> {
vec![],
(faucet_program_id, vault_program_id, attacker, amount),
)?;
let attack_tx = NSSATransaction::Public(nssa::PublicTransaction::new(
let attack_tx = LeeTransaction::Public(lee::PublicTransaction::new(
message,
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
));
let faucet_balance_before = ctx

View File

@ -8,11 +8,11 @@ use std::time::Duration;
use anyhow::Result;
use bytesize::ByteSize;
use common::transaction::NSSATransaction;
use common::transaction::LeeTransaction;
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, config::SequencerPartialConfig,
};
use nssa::program::Program;
use lee::program::Program;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
@ -33,13 +33,13 @@ async fn reject_oversized_transaction() -> Result<()> {
// Create a 1.1 MiB binary to ensure it exceeds the limit
let oversized_binary = vec![0_u8; 1100 * 1024]; // 1.1 MiB binary
let message = nssa::program_deployment_transaction::Message::new(oversized_binary);
let tx = nssa::ProgramDeploymentTransaction::new(message);
let message = lee::program_deployment_transaction::Message::new(oversized_binary);
let tx = lee::ProgramDeploymentTransaction::new(message);
// Try to submit the transaction and expect an error
let result = ctx
.sequencer_client()
.send_transaction(NSSATransaction::ProgramDeployment(tx))
.send_transaction(LeeTransaction::ProgramDeployment(tx))
.await;
assert!(
@ -74,13 +74,13 @@ async fn accept_transaction_within_limit() -> Result<()> {
// Create a small program deployment that should fit
let small_binary = vec![0_u8; 1024]; // 1 KiB binary
let message = nssa::program_deployment_transaction::Message::new(small_binary);
let tx = nssa::ProgramDeploymentTransaction::new(message);
let message = lee::program_deployment_transaction::Message::new(small_binary);
let tx = lee::ProgramDeploymentTransaction::new(message);
// This should succeed
let result = ctx
.sequencer_client()
.send_transaction(NSSATransaction::ProgramDeployment(tx))
.send_transaction(LeeTransaction::ProgramDeployment(tx))
.await;
assert!(
@ -123,17 +123,17 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> {
// Submit both program deployments
ctx.sequencer_client()
.send_transaction(NSSATransaction::ProgramDeployment(
nssa::ProgramDeploymentTransaction::new(
nssa::program_deployment_transaction::Message::new(burner_bytecode),
.send_transaction(LeeTransaction::ProgramDeployment(
lee::ProgramDeploymentTransaction::new(
lee::program_deployment_transaction::Message::new(burner_bytecode),
),
))
.await?;
ctx.sequencer_client()
.send_transaction(NSSATransaction::ProgramDeployment(
nssa::ProgramDeploymentTransaction::new(
nssa::program_deployment_transaction::Message::new(chain_caller_bytecode),
.send_transaction(LeeTransaction::ProgramDeployment(
lee::ProgramDeploymentTransaction::new(
lee::program_deployment_transaction::Message::new(chain_caller_bytecode),
),
))
.await?;
@ -148,13 +148,13 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> {
.unwrap();
// Check which program is in block 1
let get_program_ids = |block: &common::block::Block| -> Vec<nssa::ProgramId> {
let get_program_ids = |block: &common::block::Block| -> Vec<lee::ProgramId> {
block
.body
.transactions
.iter()
.filter_map(|tx| {
if let NSSATransaction::ProgramDeployment(deployment) = tx {
if let LeeTransaction::ProgramDeployment(deployment) = tx {
let bytecode = deployment.message.clone().into_bytecode();
Program::new(bytecode).ok().map(|p| p.id())
} else {

View File

@ -0,0 +1,454 @@
#![expect(
clippy::tests_outside_test_module,
clippy::arithmetic_side_effects,
reason = "We don't care about these in tests"
)]
use std::time::Duration;
use anyhow::Context as _;
use borsh::BorshSerialize;
use common::transaction::LeeTransaction;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext};
use lee::{
AccountId, execute_and_prove, privacy_preserving_transaction, program::Program,
public_transaction,
};
use lee_core::{InputAccountIdentity, account::AccountWithMetadata};
use log::info;
use logos_blockchain_core::mantle::{Value, ledger::Inputs, ops::channel::deposit::DepositOp};
use logos_blockchain_http_api_common::bodies::{
channel::ChannelDepositRequestBody,
wallet::{
balance::WalletBalanceResponseBody,
transfer_funds::{WalletTransferFundsRequestBody, WalletTransferFundsResponseBody},
},
};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
const TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK: Duration = Duration::from_mins(2);
#[test]
async fn public_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
let ctx = TestContext::new().await?;
let recipient_id = ctx.existing_public_accounts()[0];
let bridge_account_id = lee::system_bridge_account_id();
let vault_program_id = Program::vault().id();
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
let message = public_transaction::Message::try_new(
Program::bridge().id(),
vec![bridge_account_id, recipient_vault_id],
vec![],
bridge_core::Instruction::Deposit {
l1_deposit_op_id: [0_u8; 32],
vault_program_id,
recipient_id,
amount: 1,
},
)
.context("Failed to build public bridge deposit transaction")?;
let attack_tx = LeeTransaction::Public(lee::PublicTransaction::new(
message,
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
));
let bridge_balance_before = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let bridge_balance_after = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_after = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
assert_eq!(bridge_balance_after, bridge_balance_before);
assert_eq!(vault_balance_after, vault_balance_before);
assert!(
tx_on_chain.is_none(),
"Direct public bridge::Deposit invocation should be rejected"
);
Ok(())
}
#[test]
async fn private_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
let ctx = TestContext::new().await?;
let recipient_id = ctx.existing_public_accounts()[0];
let bridge_account_id = lee::system_bridge_account_id();
let vault_program_id = Program::vault().id();
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
// Get pre-state of bridge and vault accounts
let bridge_pre = AccountWithMetadata::new(
ctx.sequencer_client()
.get_account(bridge_account_id)
.await?,
false,
bridge_account_id,
);
let vault_pre = AccountWithMetadata::new(
ctx.sequencer_client()
.get_account(recipient_vault_id)
.await?,
false,
recipient_vault_id,
);
// Create program with dependencies
let program_with_deps =
lee::privacy_preserving_transaction::circuit::ProgramWithDependencies::new(
Program::bridge(),
[
(vault_program_id, Program::vault()),
(
Program::authenticated_transfer_program().id(),
Program::authenticated_transfer_program(),
),
]
.into(),
);
// Serialize the bridge deposit instruction
let instruction = Program::serialize_instruction(bridge_core::Instruction::Deposit {
l1_deposit_op_id: [0_u8; 32],
vault_program_id,
recipient_id,
amount: 1,
})
.context("Failed to serialize bridge deposit instruction")?;
// Execute and prove the bridge deposit
let (output, proof) = execute_and_prove(
vec![bridge_pre.clone(), vault_pre.clone()],
instruction,
vec![InputAccountIdentity::Public, InputAccountIdentity::Public],
&program_with_deps,
)
.context("Failed to execute/prove bridge deposit")?;
// Create privacy-preserving transaction from circuit output
let message = privacy_preserving_transaction::Message::try_from_circuit_output(
vec![bridge_account_id, recipient_vault_id],
vec![bridge_pre.account.nonce, vault_pre.account.nonce],
vec![],
output,
)
.context("Failed to build privacy-preserving bridge deposit message")?;
let witness_set = privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]);
let attack_tx = LeeTransaction::PrivacyPreserving(lee::PrivacyPreservingTransaction::new(
message,
witness_set,
));
let bridge_balance_before = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let bridge_balance_after = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_after = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
assert_eq!(bridge_balance_after, bridge_balance_before);
assert_eq!(vault_balance_after, vault_balance_before);
assert!(
tx_on_chain.is_none(),
"Privacy-preserving bridge::Deposit invocation should be rejected"
);
Ok(())
}
async fn submit_bedrock_deposit(
bedrock_addr: std::net::SocketAddr,
recipient_id: AccountId,
amount: u128,
) -> anyhow::Result<()> {
#[derive(BorshSerialize)]
struct DepositMetadata {
recipient_id: AccountId,
}
// Encode deposit metadata
let metadata = borsh::to_vec(&DepositMetadata { recipient_id })
.context("Failed to encode deposit metadata")?
.try_into()
.context("Encoded metadata is too big")?;
let funding_key = "2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26";
let amount: Value = amount
.try_into()
.context("Deposit amount does not fit Bedrock Value type")?;
let channel_id = integration_tests::config::bedrock_channel_id();
let client = reqwest::Client::new();
let query_balance = || async {
let balance_response = client
.get(format!(
"http://{bedrock_addr}/wallet/{funding_key}/balance"
))
.send()
.await
.context("Failed to query Bedrock wallet balance")?;
let balance_response = check_response_success(balance_response).await?;
balance_response
.json::<WalletBalanceResponseBody>()
.await
.context("Failed to decode Bedrock balance response")
};
let mut balance = query_balance().await?;
info!(
"Queried Bedrock balance for key {funding_key}: {:?}",
balance.balance
);
if balance.balance < amount {
anyhow::bail!(
"Bedrock wallet with key {funding_key} has insufficient balance {:?} for deposit amount {:?}",
balance.balance,
amount
);
}
let mut selected_note_id = balance
.notes
.iter()
.find_map(|(note_id, value)| (*value == amount).then_some(*note_id));
if selected_note_id.is_none() {
let transfer_body = WalletTransferFundsRequestBody {
tip: None,
change_public_key: balance.address,
funding_public_keys: vec![balance.address],
recipient_public_key: balance.address,
amount,
};
let transfer_response = client
.post(format!(
"http://{bedrock_addr}/wallet/transactions/transfer-funds"
))
.json(&transfer_body)
.send()
.await
.context("Failed to submit Bedrock transfer-funds request")?;
let transfer_response = check_response_success(transfer_response).await?;
let transfer: WalletTransferFundsResponseBody = transfer_response
.json()
.await
.context("Failed to decode Bedrock transfer-funds response")?;
info!(
"Submitted transfer-funds to create exact deposit note, tx hash {:?}",
transfer.hash
);
let mut found_note = None;
for _ in 0..20 {
tokio::time::sleep(Duration::from_millis(500)).await;
balance = query_balance().await?;
found_note = balance
.notes
.iter()
.find_map(|(note_id, value)| (*value == amount).then_some(*note_id));
if found_note.is_some() {
break;
}
}
selected_note_id = found_note;
}
let Some(selected_note_id) = selected_note_id else {
anyhow::bail!(
"Failed to locate exact-value note {amount:?} for Bedrock deposit; available notes: {:?}",
balance.notes,
);
};
let body = ChannelDepositRequestBody {
tip: None,
deposit: DepositOp {
channel_id,
inputs: Inputs::new(selected_note_id),
metadata,
},
change_public_key: balance.address,
funding_public_keys: vec![balance.address],
max_tx_fee: 1_000_u64.into(),
};
let response = client
.post(format!("http://{bedrock_addr}/channel/deposit"))
.json(&body)
.send()
.await
.context("Failed to submit Bedrock deposit request")?;
let response = check_response_success(response).await?;
let body_text = response
.text()
.await
.unwrap_or_else(|_| "<failed to decode>".to_owned());
info!(
"Successfully submitted Bedrock deposit request for recipient {recipient_id} and amount {amount}, response body: {body_text}",
);
Ok(())
}
async fn check_response_success(response: reqwest::Response) -> anyhow::Result<reqwest::Response> {
if response.status().is_success() {
Ok(response)
} else {
let status = response.status();
let body_text = response.text().await.unwrap_or_default();
anyhow::bail!("Request failed with status {status} and body {body_text}");
}
}
async fn wait_for_vault_balance(
ctx: &TestContext,
vault_id: AccountId,
expected_balance: u128,
) -> anyhow::Result<()> {
let timeout = TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK
+ Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS);
tokio::time::timeout(timeout, async {
loop {
let balance = ctx.sequencer_client().get_account_balance(vault_id).await?;
if balance == expected_balance {
return Ok(());
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
})
.await
.with_context(|| {
format!("Timed out waiting for vault {vault_id} balance to reach {expected_balance}")
})?
}
#[test]
async fn bedrock_deposit_mints_to_vault_then_claim_succeeds() -> anyhow::Result<()> {
let ctx = TestContext::new().await?;
let recipient_id = ctx.existing_public_accounts()[0];
let vault_program_id = Program::vault().id();
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
let vault_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let recipient_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_id)
.await?;
// Submit deposit to Bedrock
submit_bedrock_deposit(ctx.bedrock_addr(), recipient_id, 1).await?;
// Wait for vault to receive the deposit (minted from bridge to vault)
wait_for_vault_balance(&ctx, recipient_vault_id, vault_balance_before + 1).await?;
// Now claim funds from vault back to recipient
let nonces = ctx
.wallet()
.get_accounts_nonces(vec![recipient_id])
.await
.context("Failed to get nonce for vault claim")?;
let signing_key = ctx
.wallet()
.storage()
.key_chain()
.pub_account_signing_key(recipient_id)
.with_context(|| format!("Missing signing key for account {recipient_id}"))?;
let claim_message = public_transaction::Message::try_new(
vault_program_id,
vec![recipient_id, recipient_vault_id],
nonces,
vault_core::Instruction::Claim { amount: 1 },
)
.context("Failed to build vault claim message")?;
let claim_witness_set =
public_transaction::WitnessSet::for_message(&claim_message, &[signing_key]);
let claim_tx = LeeTransaction::Public(lee::PublicTransaction::new(
claim_message,
claim_witness_set,
));
let claim_hash = ctx.sequencer_client().send_transaction(claim_tx).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let claim_on_chain = ctx.sequencer_client().get_transaction(claim_hash).await?;
let vault_balance_after_claim = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let recipient_balance_after_claim = ctx
.sequencer_client()
.get_account_balance(recipient_id)
.await?;
assert!(
claim_on_chain.is_some(),
"Vault claim transaction must be included on-chain"
);
assert_eq!(
vault_balance_after_claim, vault_balance_before,
"Vault balance should return to initial state after claim"
);
assert_eq!(
recipient_balance_after_claim,
recipient_balance_before + 1,
"Recipient balance should increase by claimed amount"
);
Ok(())
}

View File

@ -1,279 +0,0 @@
#![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 indexer_service_rpc::RpcClient as _;
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention,
verify_commitment_is_in_state,
};
use log::info;
use nssa::AccountId;
use wallet::{
account::Label,
cli::{CliAccountMention, Command, programs::native_token_transfer::AuthTransferSubcommand},
};
/// Maximum time to wait for the indexer to catch up to the sequencer.
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 180_000;
/// Poll the indexer until its last finalized block id reaches the sequencer's
/// current last block id or until [`L2_TO_L1_TIMEOUT_MILLIS`] elapses.
/// Returns the last indexer block id observed.
async fn wait_for_indexer_to_catch_up(ctx: &TestContext) -> Result<u64> {
let timeout = Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS);
let block_id_to_catch_up =
sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?;
let mut last_ind: u64 = 1;
let inner = async {
loop {
let ind = ctx
.indexer_client()
.get_last_finalized_block_id()
.await?
.unwrap_or(0);
last_ind = ind;
if ind >= block_id_to_catch_up {
let last_seq =
sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client())
.await?;
info!(
"Indexer caught up. Indexer last block id: {ind}. Current sequencer last block id: {last_seq}"
);
return Ok(ind);
}
tokio::time::sleep(Duration::from_secs(2)).await;
}
};
tokio::time::timeout(timeout, inner)
.await
.with_context(|| {
format!(
"Indexer failed to catch up within {L2_TO_L1_TIMEOUT_MILLIS} milliseconds. Last indexer block id observed: {last_ind}, but needed to catch up to at least {block_id_to_catch_up}"
)
})?
}
#[tokio::test]
async fn indexer_test_run() -> Result<()> {
let ctx = TestContext::new().await?;
let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await?;
let last_block_seq =
sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?;
info!("Last block on seq now is {last_block_seq}");
info!("Last block on ind now is {last_block_indexer}");
assert!(last_block_indexer > 0);
Ok(())
}
#[tokio::test]
async fn indexer_block_batching() -> Result<()> {
let ctx = TestContext::new().await?;
info!("Waiting for indexer to parse blocks");
let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await?;
info!("Last block on ind now is {last_block_indexer}");
assert!(last_block_indexer > 0);
// Getting wide batch to fit all blocks (from latest backwards)
let mut block_batch = ctx.indexer_client().get_blocks(None, 100).await.unwrap();
// Reverse to check chain consistency from oldest to newest
block_batch.reverse();
// Checking chain consistency
let mut prev_block_hash = block_batch.first().unwrap().header.hash;
for block in &block_batch[1..] {
assert_eq!(block.header.prev_block_hash, prev_block_hash);
info!("Block {} chain-consistent", block.header.block_id);
prev_block_hash = block.header.hash;
}
Ok(())
}
#[tokio::test]
async fn indexer_state_consistency() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: public_mention(ctx.existing_public_accounts()[0]),
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
to_identifier: Some(0),
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move");
let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
.await?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
let from: AccountId = ctx.existing_private_accounts()[0];
let to: AccountId = ctx.existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: private_mention(from),
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
to_identifier: Some(0),
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(from)
.context("Failed to get private account commitment for sender")?;
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(to)
.context("Failed to get private account commitment for receiver")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
info!("Successfully transferred privately to owned account");
info!("Waiting for indexer to parse blocks");
wait_for_indexer_to_catch_up(&ctx).await?;
let acc1_ind_state = ctx
.indexer_client()
.get_account(ctx.existing_public_accounts()[0].into())
.await
.unwrap();
let acc2_ind_state = ctx
.indexer_client()
.get_account(ctx.existing_public_accounts()[1].into())
.await
.unwrap();
info!("Checking correct state transition");
let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
let acc2_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
.await?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
assert_eq!(acc2_ind_state, acc2_seq_state.into());
// ToDo: Check private state transition
Ok(())
}
#[tokio::test]
async fn indexer_state_consistency_with_labels() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Assign labels to both accounts
let from_label = Label::new("idx-sender-label");
let to_label = Label::new("idx-receiver-label");
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: public_mention(ctx.existing_public_accounts()[0]),
label: from_label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?;
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: public_mention(ctx.existing_public_accounts()[1]),
label: to_label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?;
// Send using labels instead of account IDs
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: CliAccountMention::Label(from_label),
to: Some(CliAccountMention::Label(to_label)),
to_npk: None,
to_vpk: None,
to_identifier: Some(0),
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
.await?;
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
info!("Waiting for indexer to parse blocks");
wait_for_indexer_to_catch_up(&ctx).await?;
let acc1_ind_state = ctx
.indexer_client()
.get_account(ctx.existing_public_accounts()[0].into())
.await
.unwrap();
let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
info!("Indexer state is consistent after label-based transfer");
Ok(())
}

View File

@ -0,0 +1,40 @@
#![expect(
clippy::tests_outside_test_module,
reason = "We don't care about these in tests"
)]
use anyhow::Result;
use indexer_service_rpc::RpcClient as _;
use integration_tests::{TestContext, wait_for_indexer_to_catch_up};
use log::info;
#[tokio::test]
async fn indexer_block_batching() -> Result<()> {
let ctx = TestContext::new().await?;
info!("Waiting for indexer to parse blocks");
let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await?;
info!("Last block on ind now is {last_block_indexer}");
assert!(last_block_indexer > 0);
// Getting wide batch to fit all blocks (from latest backwards)
let mut block_batch = ctx.indexer_client().get_blocks(None, 100).await.unwrap();
// Reverse to check chain consistency from oldest to newest
block_batch.reverse();
// Checking chain consistency
let mut prev_block_hash = block_batch.first().unwrap().header.hash;
for block in &block_batch[1..] {
assert_eq!(block.header.prev_block_hash, prev_block_hash);
info!("Block {} chain-consistent", block.header.block_id);
prev_block_hash = block.header.hash;
}
Ok(())
}

View File

@ -1,403 +0,0 @@
#![expect(
clippy::shadow_unrelated,
clippy::tests_outside_test_module,
clippy::undocumented_unsafe_blocks,
reason = "We don't care about these in tests"
)]
use std::{
ffi::{CString, c_char},
fs::File,
io::Write as _,
net::SocketAddr,
};
use anyhow::{Context as _, Result};
use indexer_ffi::{
IndexerServiceFFI, OperationStatus, Runtime,
api::{
PointerResult,
lifecycle::InitializedIndexerServiceFFIResult,
types::{FfiAccountId, FfiOption, FfiVec, account::FfiAccount, block::FfiBlock},
},
};
use integration_tests::{
BlockingTestContext, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention,
public_mention, verify_commitment_is_in_state,
};
use log::{debug, info};
use nssa::AccountId;
use tempfile::TempDir;
use wallet::{
account::Label,
cli::{Command, programs::native_token_transfer::AuthTransferSubcommand},
};
/// Maximum time to wait for the indexer to catch up to the sequencer.
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 180_000;
unsafe extern "C" {
unsafe fn query_last_block(
runtime: *const Runtime,
indexer: *const IndexerServiceFFI,
) -> PointerResult<u64, OperationStatus>;
unsafe fn query_block_vec(
runtime: *const Runtime,
indexer: *const IndexerServiceFFI,
before: FfiOption<u64>,
limit: u64,
) -> PointerResult<FfiVec<FfiBlock>, OperationStatus>;
unsafe fn query_account(
runtime: *const Runtime,
indexer: *const IndexerServiceFFI,
account_id: FfiAccountId,
) -> PointerResult<FfiAccount, OperationStatus>;
unsafe fn start_indexer(
runtime: *const Runtime,
config_path: *const c_char,
port: u16,
) -> InitializedIndexerServiceFFIResult;
}
fn setup_indexer_ffi(
runtime: &Runtime,
bedrock_addr: SocketAddr,
) -> Result<(IndexerServiceFFI, TempDir)> {
let temp_indexer_dir =
tempfile::tempdir().context("Failed to create temp dir for indexer home")?;
debug!(
"Using temp indexer home at {}",
temp_indexer_dir.path().display()
);
let indexer_config =
integration_tests::config::indexer_config(bedrock_addr, temp_indexer_dir.path().to_owned())
.context("Failed to create Indexer config")?;
let config_json = serde_json::to_vec(&indexer_config)?;
let config_path = temp_indexer_dir.path().join("indexer_config.json");
let mut file = File::create(config_path.as_path())?;
file.write_all(&config_json)?;
file.flush()?;
let res =
// SAFETY: lib function ensures validity of value.
unsafe { start_indexer(std::ptr::from_ref(runtime), CString::new(config_path.to_str().unwrap())?.as_ptr(), 0) };
if res.error.is_error() {
anyhow::bail!("Indexer FFI error {:?}", res.error);
}
Ok((
// SAFETY: lib function ensures validity of value.
unsafe { std::ptr::read(res.value) },
temp_indexer_dir,
))
}
/// Prepare setup for tests.
fn setup() -> Result<(BlockingTestContext, IndexerServiceFFI, TempDir)> {
let ctx = TestContext::builder().disable_indexer().build_blocking()?;
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let (indexer_ffi, indexer_dir) = setup_indexer_ffi(&runtime, ctx.ctx().bedrock_addr())?;
Ok((ctx, indexer_ffi, indexer_dir))
}
#[test]
fn indexer_test_run_ffi() -> Result<()> {
let (ctx, indexer_ffi, _indexer_dir) = setup()?;
// RUN OBSERVATION
std::thread::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS));
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let last_block_indexer_ffi_res =
unsafe { query_last_block(&raw const runtime, &raw const indexer_ffi) };
assert!(last_block_indexer_ffi_res.error.is_ok());
let last_block_indexer_ffi = unsafe { *last_block_indexer_ffi_res.value };
info!("Last block on indexer FFI now is {last_block_indexer_ffi}");
assert!(last_block_indexer_ffi > 0);
Ok(())
}
#[test]
fn indexer_ffi_block_batching() -> Result<()> {
let (ctx, indexer_ffi, _indexer_dir) = setup()?;
// WAIT
info!("Waiting for indexer to parse blocks");
std::thread::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS));
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let last_block_indexer_ffi_res =
unsafe { query_last_block(&raw const runtime, &raw const indexer_ffi) };
assert!(last_block_indexer_ffi_res.error.is_ok());
let last_block_indexer = unsafe { *last_block_indexer_ffi_res.value };
info!("Last block on indexer FFI now is {last_block_indexer}");
assert!(last_block_indexer > 0);
let before_ffi = FfiOption::<u64>::from_none();
let limit = 100;
let block_batch_ffi_res = unsafe {
query_block_vec(
&raw const runtime,
&raw const indexer_ffi,
before_ffi,
limit,
)
};
assert!(block_batch_ffi_res.error.is_ok());
let block_batch = unsafe { &*block_batch_ffi_res.value };
let mut last_block_prev_hash = unsafe { block_batch.get(0) }.header.prev_block_hash.data;
for i in 1..block_batch.len {
let block = unsafe { block_batch.get(i) };
assert_eq!(last_block_prev_hash, block.header.hash.data);
info!("Block {} chain-consistent", block.header.block_id);
last_block_prev_hash = block.header.prev_block_hash.data;
}
Ok(())
}
#[test]
fn indexer_ffi_state_consistency() -> Result<()> {
let (mut ctx, indexer_ffi, _indexer_dir) = setup()?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: public_mention(ctx.ctx().existing_public_accounts()[0]),
to: Some(public_mention(ctx.ctx().existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
amount: 100,
to_identifier: Some(0),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
info!("Waiting for next block creation");
std::thread::sleep(std::time::Duration::from_secs(
TIME_TO_WAIT_FOR_BLOCK_SECONDS,
));
info!("Checking correct balance move");
let acc_1_balance = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
})?;
let acc_2_balance = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
})?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
let from: AccountId = ctx.ctx().existing_private_accounts()[0];
let to: AccountId = ctx.ctx().existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: private_mention(from),
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
amount: 100,
to_identifier: Some(0),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
info!("Waiting for next block creation");
std::thread::sleep(std::time::Duration::from_secs(
TIME_TO_WAIT_FOR_BLOCK_SECONDS,
));
let new_commitment1 = ctx
.ctx()
.wallet()
.get_private_account_commitment(from)
.context("Failed to get private account commitment for sender")?;
let commitment_check1 =
ctx.block_on(|ctx| verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()));
assert!(commitment_check1);
let new_commitment2 = ctx
.ctx()
.wallet()
.get_private_account_commitment(to)
.context("Failed to get private account commitment for receiver")?;
let commitment_check2 =
ctx.block_on(|ctx| verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()));
assert!(commitment_check2);
info!("Successfully transferred privately to owned account");
// WAIT
info!("Waiting for indexer to parse blocks");
std::thread::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS));
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let acc1_ind_state_ffi = unsafe {
query_account(
&raw const runtime,
&raw const indexer_ffi,
(&ctx.ctx().existing_public_accounts()[0]).into(),
)
};
assert!(acc1_ind_state_ffi.error.is_ok());
let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value };
let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into();
let acc2_ind_state_ffi = unsafe {
query_account(
&raw const runtime,
&raw const indexer_ffi,
(&ctx.ctx().existing_public_accounts()[1]).into(),
)
};
assert!(acc2_ind_state_ffi.error.is_ok());
let acc2_ind_state_pre = unsafe { &*acc2_ind_state_ffi.value };
let acc2_ind_state: indexer_service_protocol::Account = acc2_ind_state_pre.into();
info!("Checking correct state transition");
let acc1_seq_state = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
})?;
let acc2_seq_state = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
})?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
assert_eq!(acc2_ind_state, acc2_seq_state.into());
// ToDo: Check private state transition
Ok(())
}
#[test]
fn indexer_ffi_state_consistency_with_labels() -> Result<()> {
let (mut ctx, indexer_ffi, _indexer_dir) = setup()?;
// Assign labels to both accounts
let from_label = Label::new("idx-sender-label");
let to_label = Label::new("idx-receiver-label");
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: public_mention(ctx.ctx().existing_public_accounts()[0]),
label: from_label.clone(),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?;
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: public_mention(ctx.ctx().existing_public_accounts()[1]),
label: to_label.clone(),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?;
// Send using labels instead of account IDs
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: from_label.into(),
to: Some(to_label.into()),
to_npk: None,
to_vpk: None,
amount: 100,
to_identifier: Some(0),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
info!("Waiting for next block creation");
std::thread::sleep(std::time::Duration::from_secs(
TIME_TO_WAIT_FOR_BLOCK_SECONDS,
));
let acc_1_balance = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
})?;
let acc_2_balance = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
})?;
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
info!("Waiting for indexer to parse blocks");
std::thread::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS));
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let acc1_ind_state_ffi = unsafe {
query_account(
&raw const runtime,
&raw const indexer_ffi,
(&ctx.ctx().existing_public_accounts()[0]).into(),
)
};
assert!(acc1_ind_state_ffi.error.is_ok());
let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value };
let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into();
let acc1_seq_state = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
})?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
info!("Indexer state is consistent after label-based transfer");
Ok(())
}

View File

@ -0,0 +1,66 @@
#![expect(
clippy::tests_outside_test_module,
clippy::undocumented_unsafe_blocks,
reason = "We don't care about these in tests"
)]
use anyhow::Result;
use indexer_ffi::{Runtime, api::types::FfiOption};
use integration_tests::L2_TO_L1_TIMEOUT;
use log::info;
#[path = "indexer_ffi_helpers/mod.rs"]
mod indexer_ffi_helpers;
#[test]
fn indexer_ffi_block_batching() -> Result<()> {
let (ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?;
// WAIT
info!("Waiting for indexer to parse blocks");
std::thread::sleep(L2_TO_L1_TIMEOUT);
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let last_block_indexer_ffi_res = unsafe {
indexer_ffi_helpers::query_last_block(&raw const runtime, &raw const indexer_ffi)
};
assert!(last_block_indexer_ffi_res.error.is_ok());
let last_block_indexer = unsafe { *last_block_indexer_ffi_res.value };
info!("Last block on indexer FFI now is {last_block_indexer}");
assert!(last_block_indexer > 0);
let before_ffi = FfiOption::<u64>::from_none();
let limit = 100;
let block_batch_ffi_res = unsafe {
indexer_ffi_helpers::query_block_vec(
&raw const runtime,
&raw const indexer_ffi,
before_ffi,
limit,
)
};
assert!(block_batch_ffi_res.error.is_ok());
let block_batch = unsafe { &*block_batch_ffi_res.value };
let mut last_block_prev_hash = unsafe { block_batch.get(0) }.header.prev_block_hash.data;
for i in 1..block_batch.len {
let block = unsafe { block_batch.get(i) };
assert_eq!(last_block_prev_hash, block.header.hash.data);
info!("Block {} chain-consistent", block.header.block_id);
last_block_prev_hash = block.header.prev_block_hash.data;
}
Ok(())
}

View File

@ -0,0 +1,91 @@
#![allow(dead_code, reason = "helper module used only by FFI test binaries")]
use std::{
ffi::{CString, c_char},
fs::File,
io::Write as _,
net::SocketAddr,
};
use anyhow::{Context as _, Result};
use indexer_ffi::{
IndexerServiceFFI, OperationStatus, Runtime,
api::{
PointerResult,
lifecycle::InitializedIndexerServiceFFIResult,
types::{FfiAccountId, FfiOption, FfiVec, account::FfiAccount, block::FfiBlock},
},
};
use integration_tests::{BlockingTestContext, TestContext};
use tempfile::TempDir;
unsafe extern "C" {
pub unsafe fn query_last_block(
runtime: *const Runtime,
indexer: *const IndexerServiceFFI,
) -> PointerResult<u64, OperationStatus>;
pub unsafe fn query_block_vec(
runtime: *const Runtime,
indexer: *const IndexerServiceFFI,
before: FfiOption<u64>,
limit: u64,
) -> PointerResult<FfiVec<FfiBlock>, OperationStatus>;
pub unsafe fn query_account(
runtime: *const Runtime,
indexer: *const IndexerServiceFFI,
account_id: FfiAccountId,
) -> PointerResult<FfiAccount, OperationStatus>;
pub unsafe fn start_indexer(
runtime: *const Runtime,
config_path: *const c_char,
port: u16,
) -> InitializedIndexerServiceFFIResult;
}
pub fn setup_indexer_ffi(
runtime: &Runtime,
bedrock_addr: SocketAddr,
) -> Result<(IndexerServiceFFI, TempDir)> {
let temp_indexer_dir =
tempfile::tempdir().context("Failed to create temp dir for indexer home")?;
log::debug!(
"Using temp indexer home at {}",
temp_indexer_dir.path().display()
);
let indexer_config =
integration_tests::config::indexer_config(bedrock_addr, temp_indexer_dir.path().to_owned())
.context("Failed to create Indexer config")?;
let config_json = serde_json::to_vec(&indexer_config)?;
let config_path = temp_indexer_dir.path().join("indexer_config.json");
let mut file = File::create(config_path.as_path())?;
file.write_all(&config_json)?;
file.flush()?;
let res =
// SAFETY: lib function ensures validity of value.
unsafe { start_indexer(std::ptr::from_ref(runtime), CString::new(config_path.to_str().unwrap())?.as_ptr(), 0) };
if res.error.is_error() {
anyhow::bail!("Indexer FFI error {:?}", res.error);
}
Ok((
// SAFETY: lib function ensures validity of value.
unsafe { std::ptr::read(res.value) },
temp_indexer_dir,
))
}
pub fn setup() -> Result<(BlockingTestContext, IndexerServiceFFI, TempDir)> {
let ctx = TestContext::builder().disable_indexer().build_blocking()?;
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let (indexer_ffi, indexer_dir) = setup_indexer_ffi(&runtime, ctx.ctx().bedrock_addr())?;
Ok((ctx, indexer_ffi, indexer_dir))
}

View File

@ -0,0 +1,153 @@
#![expect(
clippy::shadow_unrelated,
clippy::tests_outside_test_module,
clippy::undocumented_unsafe_blocks,
reason = "We don't care about these in tests"
)]
use std::time::Duration;
use anyhow::{Context as _, Result};
use indexer_ffi::Runtime;
use indexer_service_protocol::Account;
use integration_tests::{
L2_TO_L1_TIMEOUT, TIME_TO_WAIT_FOR_BLOCK_SECONDS, private_mention, public_mention,
verify_commitment_is_in_state,
};
use lee::AccountId;
use log::info;
use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand};
#[path = "indexer_ffi_helpers/mod.rs"]
mod indexer_ffi_helpers;
#[test]
fn indexer_ffi_state_consistency() -> Result<()> {
let (mut ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: public_mention(ctx.ctx().existing_public_accounts()[0]),
to: Some(public_mention(ctx.ctx().existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
to_keys: None,
amount: 100,
to_identifier: Some(0),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
info!("Waiting for next block creation");
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
info!("Checking correct balance move");
let acc_1_balance = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
})?;
let acc_2_balance = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
})?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
let from: AccountId = ctx.ctx().existing_private_accounts()[0];
let to: AccountId = ctx.ctx().existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: private_mention(from),
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
to_keys: None,
amount: 100,
to_identifier: Some(0),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
info!("Waiting for next block creation");
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
let new_commitment1 = ctx
.ctx()
.wallet()
.get_private_account_commitment(from)
.context("Failed to get private account commitment for sender")?;
let commitment_check1 =
ctx.block_on(|ctx| verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()));
assert!(commitment_check1);
let new_commitment2 = ctx
.ctx()
.wallet()
.get_private_account_commitment(to)
.context("Failed to get private account commitment for receiver")?;
let commitment_check2 =
ctx.block_on(|ctx| verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()));
assert!(commitment_check2);
info!("Successfully transferred privately to owned account");
// WAIT
info!("Waiting for indexer to parse blocks");
std::thread::sleep(L2_TO_L1_TIMEOUT);
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let acc1_ind_state_ffi = unsafe {
indexer_ffi_helpers::query_account(
&raw const runtime,
&raw const indexer_ffi,
(&ctx.ctx().existing_public_accounts()[0]).into(),
)
};
assert!(acc1_ind_state_ffi.error.is_ok());
let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value };
let acc1_ind_state: Account = acc1_ind_state_pre.into();
let acc2_ind_state_ffi = unsafe {
indexer_ffi_helpers::query_account(
&raw const runtime,
&raw const indexer_ffi,
(&ctx.ctx().existing_public_accounts()[1]).into(),
)
};
assert!(acc2_ind_state_ffi.error.is_ok());
let acc2_ind_state_pre = unsafe { &*acc2_ind_state_ffi.value };
let acc2_ind_state: Account = acc2_ind_state_pre.into();
info!("Checking correct state transition");
let acc1_seq_state = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
})?;
let acc2_seq_state = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
})?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
assert_eq!(acc2_ind_state, acc2_seq_state.into());
// ToDo: Check private state transition
Ok(())
}

View File

@ -0,0 +1,105 @@
#![expect(
clippy::shadow_unrelated,
clippy::tests_outside_test_module,
clippy::undocumented_unsafe_blocks,
reason = "We don't care about these in tests"
)]
use std::time::Duration;
use anyhow::Result;
use indexer_ffi::Runtime;
use indexer_service_protocol::Account;
use integration_tests::{L2_TO_L1_TIMEOUT, TIME_TO_WAIT_FOR_BLOCK_SECONDS, public_mention};
use log::info;
use wallet::{
account::Label,
cli::{Command, programs::native_token_transfer::AuthTransferSubcommand},
};
#[path = "indexer_ffi_helpers/mod.rs"]
mod indexer_ffi_helpers;
#[test]
fn indexer_ffi_state_consistency_with_labels() -> Result<()> {
let (mut ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?;
// Assign labels to both accounts
let from_label = Label::new("idx-sender-label");
let to_label = Label::new("idx-receiver-label");
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: public_mention(ctx.ctx().existing_public_accounts()[0]),
label: from_label.clone(),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?;
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: public_mention(ctx.ctx().existing_public_accounts()[1]),
label: to_label.clone(),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?;
// Send using labels instead of account IDs
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: from_label.into(),
to: Some(to_label.into()),
to_npk: None,
to_vpk: None,
to_keys: None,
amount: 100,
to_identifier: Some(0),
});
ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
info!("Waiting for next block creation");
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
let acc_1_balance = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
})?;
let acc_2_balance = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
})?;
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
info!("Waiting for indexer to parse blocks");
std::thread::sleep(L2_TO_L1_TIMEOUT);
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let acc1_ind_state_ffi = unsafe {
indexer_ffi_helpers::query_account(
&raw const runtime,
&raw const indexer_ffi,
(&ctx.ctx().existing_public_accounts()[0]).into(),
)
};
assert!(acc1_ind_state_ffi.error.is_ok());
let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value };
let acc1_ind_state: Account = acc1_ind_state_pre.into();
let acc1_seq_state = ctx.block_on(|ctx| {
sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
})?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
info!("Indexer state is consistent after label-based transfer");
Ok(())
}

View File

@ -0,0 +1,120 @@
#![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 indexer_service_rpc::RpcClient as _;
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention,
verify_commitment_is_in_state, wait_for_indexer_to_catch_up,
};
use lee::AccountId;
use log::info;
use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand};
#[tokio::test]
async fn indexer_state_consistency() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: public_mention(ctx.existing_public_accounts()[0]),
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move");
let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
.await?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
let from: AccountId = ctx.existing_private_accounts()[0];
let to: AccountId = ctx.existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: private_mention(from),
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(from)
.context("Failed to get private account commitment for sender")?;
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(to)
.context("Failed to get private account commitment for receiver")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
info!("Successfully transferred privately to owned account");
info!("Waiting for indexer to parse blocks");
wait_for_indexer_to_catch_up(&ctx).await?;
let acc1_ind_state = ctx
.indexer_client()
.get_account(ctx.existing_public_accounts()[0].into())
.await
.unwrap();
let acc2_ind_state = ctx
.indexer_client()
.get_account(ctx.existing_public_accounts()[1].into())
.await
.unwrap();
info!("Checking correct state transition");
let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
let acc2_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
.await?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
assert_eq!(acc2_ind_state, acc2_seq_state.into());
// ToDo: Check private state transition
Ok(())
}

View File

@ -0,0 +1,89 @@
#![expect(
clippy::shadow_unrelated,
clippy::tests_outside_test_module,
reason = "We don't care about these in tests"
)]
use std::time::Duration;
use anyhow::Result;
use indexer_service_rpc::RpcClient as _;
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, public_mention, wait_for_indexer_to_catch_up,
};
use log::info;
use wallet::{
account::Label,
cli::{CliAccountMention, Command, programs::native_token_transfer::AuthTransferSubcommand},
};
#[tokio::test]
async fn indexer_state_consistency_with_labels() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Assign labels to both accounts
let from_label = Label::new("idx-sender-label");
let to_label = Label::new("idx-receiver-label");
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: public_mention(ctx.existing_public_accounts()[0]),
label: from_label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?;
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: public_mention(ctx.existing_public_accounts()[1]),
label: to_label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?;
// Send using labels instead of account IDs
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: CliAccountMention::Label(from_label),
to: Some(CliAccountMention::Label(to_label)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
.await?;
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
info!("Waiting for indexer to parse blocks");
wait_for_indexer_to_catch_up(&ctx).await?;
let acc1_ind_state = ctx
.indexer_client()
.get_account(ctx.existing_public_accounts()[0].into())
.await
.unwrap();
let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
info!("Indexer state is consistent after label-based transfer");
Ok(())
}

View File

@ -0,0 +1,25 @@
#![expect(
clippy::tests_outside_test_module,
reason = "We don't care about these in tests"
)]
use anyhow::Result;
use integration_tests::{TestContext, wait_for_indexer_to_catch_up};
use log::info;
#[tokio::test]
async fn indexer_test_run() -> Result<()> {
let ctx = TestContext::new().await?;
let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await?;
let last_block_seq =
sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?;
info!("Last block on seq now is {last_block_seq}");
info!("Last block on ind now is {last_block_indexer}");
assert!(last_block_indexer > 0);
Ok(())
}

View File

@ -0,0 +1,37 @@
#![expect(
clippy::tests_outside_test_module,
clippy::undocumented_unsafe_blocks,
reason = "We don't care about these in tests"
)]
use anyhow::Result;
use indexer_ffi::Runtime;
use integration_tests::L2_TO_L1_TIMEOUT;
use log::info;
#[path = "indexer_ffi_helpers/mod.rs"]
mod indexer_ffi_helpers;
#[test]
fn indexer_test_run_ffi() -> Result<()> {
let (ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?;
// RUN OBSERVATION
std::thread::sleep(L2_TO_L1_TIMEOUT);
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let last_block_indexer_ffi_res = unsafe {
indexer_ffi_helpers::query_last_block(&raw const runtime, &raw const indexer_ffi)
};
assert!(last_block_indexer_ffi_res.error.is_ok());
let last_block_indexer_ffi = unsafe { *last_block_indexer_ffi_res.value };
info!("Last block on indexer FFI now is {last_block_indexer_ffi}");
assert!(last_block_indexer_ffi > 0);
Ok(())
}

View File

@ -12,8 +12,8 @@ use integration_tests::{
public_mention, verify_commitment_is_in_state,
};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use lee::{AccountId, program::Program};
use log::info;
use nssa::{AccountId, program::Program};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
@ -71,7 +71,10 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
from: private_mention(from),
to: None,
to_npk: Some(hex::encode(to_account.key_chain.nullifier_public_key.0)),
to_vpk: Some(hex::encode(&to_account.key_chain.viewing_public_key.0)),
to_vpk: Some(hex::encode(
to_account.key_chain.viewing_public_key.to_bytes(),
)),
to_keys: None,
to_identifier: Some(to_account.kind.identifier()),
amount: 100,
});
@ -147,6 +150,7 @@ async fn restore_keys_from_seed() -> Result<()> {
to: Some(private_mention(to_account_id1)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 100,
});
@ -158,6 +162,7 @@ async fn restore_keys_from_seed() -> Result<()> {
to: Some(private_mention(to_account_id2)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 101,
});
@ -197,6 +202,7 @@ async fn restore_keys_from_seed() -> Result<()> {
to: Some(public_mention(to_account_id3)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 102,
});
@ -208,6 +214,7 @@ async fn restore_keys_from_seed() -> Result<()> {
to: Some(public_mention(to_account_id4)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 103,
});
@ -268,6 +275,7 @@ async fn restore_keys_from_seed() -> Result<()> {
to: Some(private_mention(to_account_id2)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 10,
});
@ -278,6 +286,7 @@ async fn restore_keys_from_seed() -> Result<()> {
to: Some(public_mention(to_account_id4)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: 11,
});

View File

@ -7,14 +7,13 @@ use std::{path::PathBuf, time::Duration};
use anyhow::{Context as _, Result};
use authenticated_transfer_core::Instruction as AuthTransferInstruction;
use common::transaction::NSSATransaction;
use common::transaction::LeeTransaction;
use integration_tests::{
NSSA_PROGRAM_FOR_TEST_PDA_SPEND_PROXY, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
LEE_PROGRAM_FOR_TEST_PDA_SPEND_PROXY, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
verify_commitment_is_in_state,
};
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use log::info;
use nssa::{
use lee::{
AccountId, PrivacyPreservingTransaction, ProgramId,
privacy_preserving_transaction::{
circuit::{ProgramWithDependencies, execute_and_prove},
@ -23,12 +22,13 @@ use nssa::{
},
program::Program,
};
use nssa_core::{
use lee_core::{
InputAccountIdentity, NullifierPublicKey,
account::{Account, AccountWithMetadata},
encryption::ViewingPublicKey,
program::PdaSeed,
};
use log::info;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::{
@ -64,9 +64,9 @@ async fn fund_private_pda(
let sender_pre = AccountWithMetadata::new(sender_account.clone(), true, sender);
let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_account_id);
let eph_holder = EphemeralKeyHolder::new(&npk);
let ssk = eph_holder.calculate_shared_secret_sender(&vpk);
let epk = eph_holder.generate_ephemeral_public_key();
let eph_holder = EphemeralKeyHolder::new(&vpk);
let ssk = eph_holder.calculate_shared_secret_sender();
let epk = eph_holder.ephemeral_public_key().clone();
let instruction = Program::serialize_instruction(AuthTransferInstruction::Transfer { amount })
.context("failed to serialize auth_transfer instruction")?;
@ -102,7 +102,7 @@ async fn fund_private_pda(
wallet
.sequencer_client
.send_transaction(NSSATransaction::PrivacyPreserving(tx))
.send_transaction(LeeTransaction::PrivacyPreserving(tx))
.await
.map_err(|e| anyhow::anyhow!("send transaction failed: {e}"))?;
@ -170,7 +170,7 @@ async fn private_pda_family_members_receive_and_spend() -> Result<()> {
let proxy = {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../artifacts/test_program_methods")
.join(NSSA_PROGRAM_FOR_TEST_PDA_SPEND_PROXY);
.join(LEE_PROGRAM_FOR_TEST_PDA_SPEND_PROXY);
Program::new(std::fs::read(&path).with_context(|| format!("reading {path:?}"))?)
.context("invalid pda_spend_proxy binary")?
};
@ -272,10 +272,10 @@ async fn private_pda_family_members_receive_and_spend() -> Result<()> {
// Fresh recipients — hardcoded npks not in any wallet.
let recipient_npk_0 = NullifierPublicKey([0xAA; 32]);
let recipient_vpk_0 = ViewingPublicKey::from_scalar(recipient_npk_0.0);
let recipient_vpk_0 = ViewingPublicKey::from_seed(&[0_u8; 32], &[1_u8; 32]);
let recipient_npk_1 = NullifierPublicKey([0xBB; 32]);
let recipient_vpk_1 = ViewingPublicKey::from_scalar(recipient_npk_1.0);
let recipient_vpk_1 = ViewingPublicKey::from_seed(&[2_u8; 32], &[3_u8; 32]);
let amount_spend_0: u128 = 13;
let amount_spend_1: u128 = 37;

View File

@ -6,12 +6,12 @@
use std::{path::PathBuf, time::Duration};
use anyhow::Result;
use common::transaction::NSSATransaction;
use common::transaction::LeeTransaction;
use integration_tests::{
NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
LEE_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
};
use lee::program::Program;
use log::info;
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
@ -26,7 +26,7 @@ async fn deploy_and_execute_program() -> Result<()> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let binary_filepath: PathBuf = PathBuf::from(manifest_dir)
.join("../artifacts/test_program_methods")
.join(NSSA_PROGRAM_FOR_TEST_DATA_CHANGER);
.join(LEE_PROGRAM_FOR_TEST_DATA_CHANGER);
let command = Command::DeployProgram {
binary_filepath: binary_filepath.clone(),
@ -39,7 +39,7 @@ async fn deploy_and_execute_program() -> Result<()> {
// The program is the data changer and takes one account as input.
// We pass an uninitialized account and we expect after execution to be owned by the data
// changer program (NSSA account claiming mechanism) with data equal to [0] (due to program
// changer program (LEE account claiming mechanism) with data equal to [0] (due to program
// logic)
let bytecode = std::fs::read(binary_filepath)?;
let data_changer = Program::new(bytecode)?;
@ -61,17 +61,17 @@ async fn deploy_and_execute_program() -> Result<()> {
.wallet()
.get_account_public_signing_key(account_id)
.unwrap();
let message = nssa::public_transaction::Message::try_new(
let message = lee::public_transaction::Message::try_new(
data_changer.id(),
vec![account_id],
nonces,
vec![0],
)?;
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[private_key]);
let transaction = nssa::PublicTransaction::new(message, witness_set);
let witness_set = lee::public_transaction::WitnessSet::for_message(&message, &[private_key]);
let transaction = lee::PublicTransaction::new(message, witness_set);
let _response = ctx
.sequencer_client()
.send_transaction(NSSATransaction::Public(transaction))
.send_transaction(LeeTransaction::Public(transaction))
.await?;
info!("Waiting for next block creation");

View File

@ -107,8 +107,11 @@ async fn group_invite_join_key_agreement() -> Result<()> {
.key_chain()
.sealing_secret_key()
.context("Sealing key not found")?;
let sealing_pk =
key_protocol::key_management::group_key_holder::SealingPublicKey::from_scalar(sealing_sk);
let sealing_pk = key_protocol::key_management::group_key_holder::SealingPublicKey::from_bytes(
lee_core::encryption::ViewingPublicKey::from_seed(&sealing_sk.d, &sealing_sk.z)
.to_bytes()
.to_vec(),
);
let holder = ctx
.wallet()
@ -204,6 +207,7 @@ async fn fund_shared_account_from_public() -> Result<()> {
to: Some(private_mention(shared_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: None,
amount: 100,
});

View File

@ -12,8 +12,8 @@ use integration_tests::{
verify_commitment_is_in_state,
};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use lee::program::Program;
use log::info;
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use token_core::{TokenDefinition, TokenHolding};
use tokio::test;
@ -133,6 +133,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
to: Some(public_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: transfer_amount,
};
@ -223,6 +224,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
holder: Some(public_mention(recipient_account_id)),
holder_npk: None,
holder_vpk: None,
holder_keys: None,
holder_identifier: None,
amount: mint_amount,
};
@ -365,6 +367,7 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
to: Some(private_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: transfer_amount,
};
@ -554,6 +557,7 @@ async fn create_token_with_private_definition() -> Result<()> {
holder: Some(public_mention(recipient_account_id_public)),
holder_npk: None,
holder_vpk: None,
holder_keys: None,
holder_identifier: None,
amount: mint_amount_public,
};
@ -601,6 +605,7 @@ async fn create_token_with_private_definition() -> Result<()> {
holder: Some(private_mention(recipient_account_id_private)),
holder_npk: None,
holder_vpk: None,
holder_keys: None,
holder_identifier: None,
amount: mint_amount_private,
};
@ -740,6 +745,7 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
to: Some(private_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: transfer_amount,
};
@ -868,6 +874,7 @@ async fn shielded_token_transfer() -> Result<()> {
to: Some(private_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: transfer_amount,
};
@ -991,6 +998,7 @@ async fn deshielded_token_transfer() -> Result<()> {
to: Some(public_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: transfer_amount,
};
@ -1124,7 +1132,8 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
definition: private_mention(definition_account_id),
holder: None,
holder_npk: Some(hex::encode(holder_keys.nullifier_public_key.0)),
holder_vpk: Some(hex::encode(&holder_keys.viewing_public_key.0)),
holder_vpk: Some(hex::encode(holder_keys.viewing_public_key.to_bytes())),
holder_keys: None,
holder_identifier: Some(holder_identifier),
amount: mint_amount,
};
@ -1323,6 +1332,7 @@ async fn transfer_token_using_from_label() -> Result<()> {
to: Some(public_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
amount: transfer_amount,
};

View File

@ -13,21 +13,21 @@ use std::time::{Duration, Instant};
use anyhow::{Context as _, Result};
use bytesize::ByteSize;
use common::transaction::NSSATransaction;
use common::transaction::LeeTransaction;
use integration_tests::{TestContext, config::SequencerPartialConfig};
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use log::info;
use nssa::{
use lee::{
Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction,
privacy_preserving_transaction::{self as pptx, circuit},
program::Program,
public_transaction as putx,
};
use nssa_core::{
use lee_core::{
InputAccountIdentity, MembershipProof, NullifierPublicKey,
account::{AccountWithMetadata, Nonce, data::Data},
encryption::ViewingPublicKey,
};
use log::info;
use sequencer_core::config::GenesisAction;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
@ -90,16 +90,16 @@ impl TpsTestManager {
)
.context("Failed to build vault claim message")?;
let witness_set =
nssa::public_transaction::WitnessSet::for_message(&message, &[private_key]);
lee::public_transaction::WitnessSet::for_message(&message, &[private_key]);
let tx = PublicTransaction::new(message, witness_set);
let hash = sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await
.context("Failed to submit vault claim")?;
tx_hashes.push(hash);
}
let deadline = Instant::now() + Duration::from_secs(300);
let deadline = Instant::now() + Duration::from_mins(5);
for (i, tx_hash) in tx_hashes.iter().enumerate() {
loop {
anyhow::ensure!(
@ -140,7 +140,7 @@ impl TpsTestManager {
)
.unwrap();
let witness_set =
nssa::public_transaction::WitnessSet::for_message(&message, &[&pair[0].0]);
lee::public_transaction::WitnessSet::for_message(&message, &[&pair[0].0]);
PublicTransaction::new(message, witness_set)
})
.collect();
@ -202,7 +202,7 @@ pub async fn tps_test() -> Result<()> {
for (i, tx) in txs.into_iter().enumerate() {
let tx_hash = ctx
.sequencer_client()
.send_transaction(NSSATransaction::Public(tx))
.send_transaction(LeeTransaction::Public(tx))
.await
.unwrap();
info!("Sent tx {i}");
@ -256,8 +256,7 @@ pub async fn tps_test() -> Result<()> {
fn build_privacy_transaction() -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
let sender_nsk = [1; 32];
let sender_vsk = [99; 32];
let sender_vpk = ViewingPublicKey::from_scalar(sender_vsk);
let sender_vpk = ViewingPublicKey::from_seed(&[99_u8; 32], &[100_u8; 32]);
let sender_npk = NullifierPublicKey::from(&sender_nsk);
let sender_pre = AccountWithMetadata::new(
Account {
@ -270,8 +269,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
AccountId::for_regular_private_account(&sender_npk, 0),
);
let recipient_nsk = [2; 32];
let recipient_vsk = [99; 32];
let recipient_vpk = ViewingPublicKey::from_scalar(recipient_vsk);
let recipient_vpk = ViewingPublicKey::from_seed(&[101_u8; 32], &[102_u8; 32]);
let recipient_npk = NullifierPublicKey::from(&recipient_nsk);
let recipient_pre = AccountWithMetadata::new(
Account::default(),
@ -279,13 +277,13 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
AccountId::for_regular_private_account(&recipient_npk, 0),
);
let eph_holder_from = EphemeralKeyHolder::new(&sender_npk);
let sender_ss = eph_holder_from.calculate_shared_secret_sender(&sender_vpk);
let sender_epk = eph_holder_from.generate_ephemeral_public_key();
let eph_holder_from = EphemeralKeyHolder::new(&sender_vpk);
let sender_ss = eph_holder_from.calculate_shared_secret_sender();
let sender_epk = eph_holder_from.ephemeral_public_key().clone();
let eph_holder_to = EphemeralKeyHolder::new(&recipient_npk);
let recipient_ss = eph_holder_to.calculate_shared_secret_sender(&recipient_vpk);
let recipient_epk = eph_holder_from.generate_ephemeral_public_key();
let eph_holder_to = EphemeralKeyHolder::new(&recipient_vpk);
let recipient_ss = eph_holder_to.calculate_shared_secret_sender();
let recipient_epk = eph_holder_to.ephemeral_public_key().clone();
let balance_to_move: u128 = 1;
let proof: MembershipProof = (

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