mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-03-21 11:23:10 +00:00
Merge pull request #394 from logos-blockchain/arjentix/sequencer-rpc-server-refactor
Replace manual Sequencer RPC implementation with jsonrpsee
This commit is contained in:
commit
fe368f2b48
8
.github/workflows/publish_images.yml
vendored
8
.github/workflows/publish_images.yml
vendored
@ -12,12 +12,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: sequencer_runner
|
||||
dockerfile: ./sequencer_runner/Dockerfile
|
||||
- name: sequencer_service
|
||||
dockerfile: ./sequencer/service/Dockerfile
|
||||
build_args: |
|
||||
STANDALONE=false
|
||||
- name: sequencer_runner-standalone
|
||||
dockerfile: ./sequencer_runner/Dockerfile
|
||||
- name: sequencer_service-standalone
|
||||
dockerfile: ./sequencer/service/Dockerfile
|
||||
build_args: |
|
||||
STANDALONE=true
|
||||
- name: indexer_service
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,7 +6,7 @@ data/
|
||||
.idea/
|
||||
.vscode/
|
||||
rocksdb
|
||||
sequencer_runner/data/
|
||||
sequencer/service/data/
|
||||
storage.json
|
||||
result
|
||||
wallet-ffi/wallet_ffi.h
|
||||
|
||||
431
Cargo.lock
generated
431
Cargo.lock
generated
@ -2,229 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "actix"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b"
|
||||
dependencies = [
|
||||
"actix-macros",
|
||||
"actix-rt",
|
||||
"actix_derive",
|
||||
"bitflags 2.11.0",
|
||||
"bytes",
|
||||
"crossbeam-channel",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-codec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-cors"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daa239b93927be1ff123eebada5a3ff23e89f0124ccb8609234e5103d5a5ae6d"
|
||||
dependencies = [
|
||||
"actix-utils",
|
||||
"actix-web",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-http"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e"
|
||||
dependencies = [
|
||||
"actix-codec",
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"bitflags 2.11.0",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"derive_more",
|
||||
"encoding_rs",
|
||||
"foldhash",
|
||||
"futures-core",
|
||||
"http 0.2.12",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"language-tags",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-macros"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-router"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7"
|
||||
dependencies = [
|
||||
"bytestring",
|
||||
"cfg-if",
|
||||
"http 0.2.12",
|
||||
"regex-lite",
|
||||
"serde",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-rt"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-server"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502"
|
||||
dependencies = [
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"mio",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-service"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-utils"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8"
|
||||
dependencies = [
|
||||
"local-waker",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web"
|
||||
version = "4.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf"
|
||||
dependencies = [
|
||||
"actix-codec",
|
||||
"actix-http",
|
||||
"actix-macros",
|
||||
"actix-router",
|
||||
"actix-rt",
|
||||
"actix-server",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"actix-web-codegen",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"cfg-if",
|
||||
"derive_more",
|
||||
"encoding_rs",
|
||||
"foldhash",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"impl-more",
|
||||
"itoa",
|
||||
"language-tags",
|
||||
"log",
|
||||
"mime",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"regex-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"smallvec",
|
||||
"socket2 0.6.3",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-codegen"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8"
|
||||
dependencies = [
|
||||
"actix-router",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix_derive"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addchain"
|
||||
version = "0.2.1"
|
||||
@ -1011,7 +788,7 @@ dependencies = [
|
||||
"axum-core 0.4.5",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
@ -1045,7 +822,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
@ -1080,7 +857,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
@ -1099,7 +876,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
@ -1313,7 +1090,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hex",
|
||||
"home",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-named-pipe",
|
||||
@ -1466,15 +1243,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytestring"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.13+1.0.8"
|
||||
@ -1732,20 +1500,15 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"borsh",
|
||||
"bytesize",
|
||||
"hex",
|
||||
"log",
|
||||
"logos-blockchain-common-http-client",
|
||||
"nssa",
|
||||
"nssa_core",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio-retry",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1877,15 +1640,6 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.11.0"
|
||||
@ -1992,15 +1746,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
@ -2297,7 +2042,6 @@ version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
|
||||
dependencies = [
|
||||
"convert_case 0.10.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
@ -3099,7 +2843,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"gloo-utils",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"js-sys",
|
||||
"pin-project",
|
||||
"serde",
|
||||
@ -3163,7 +2907,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"indexmap 2.13.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
@ -3318,17 +3062,6 @@ dependencies = [
|
||||
"utf8-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.0"
|
||||
@ -3346,7 +3079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3357,7 +3090,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
@ -3432,7 +3165,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
@ -3465,7 +3198,7 @@ version = "0.27.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
||||
dependencies = [
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"log",
|
||||
@ -3516,14 +3249,14 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.3",
|
||||
"socket2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
@ -3684,12 +3417,6 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-more"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
|
||||
|
||||
[[package]]
|
||||
name = "include_bytes_aligned"
|
||||
version = "0.1.4"
|
||||
@ -3725,7 +3452,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"futures",
|
||||
@ -3825,8 +3551,6 @@ name = "integration_tests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"borsh",
|
||||
"bytesize",
|
||||
"common",
|
||||
"env_logger",
|
||||
@ -3839,7 +3563,8 @@ dependencies = [
|
||||
"nssa",
|
||||
"nssa_core",
|
||||
"sequencer_core",
|
||||
"sequencer_runner",
|
||||
"sequencer_service",
|
||||
"sequencer_service_rpc",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"testcontainers",
|
||||
@ -4048,7 +3773,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"gloo-net",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"jsonrpsee-core",
|
||||
"pin-project",
|
||||
"rustls",
|
||||
@ -4073,7 +3798,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"jsonrpsee-types",
|
||||
@ -4134,7 +3859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c51b7c290bb68ce3af2d029648148403863b982f138484a73f02a9dd52dbd7f"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
@ -4160,7 +3885,7 @@ version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5"
|
||||
dependencies = [
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
@ -4184,7 +3909,7 @@ version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b6fceceeb05301cc4c065ab3bd2fa990d41ff4eb44e4ca1b30fa99c057c3e79"
|
||||
dependencies = [
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"jsonrpsee-client-transport",
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-types",
|
||||
@ -4238,12 +3963,6 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||
|
||||
[[package]]
|
||||
name = "lazy-regex"
|
||||
version = "3.6.0"
|
||||
@ -4620,12 +4339,6 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
|
||||
[[package]]
|
||||
name = "local-waker"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
@ -5384,7 +5097,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@ -5398,7 +5110,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"httparse",
|
||||
"memchr",
|
||||
"mime",
|
||||
@ -5545,6 +5257,7 @@ dependencies = [
|
||||
"risc0-zkvm",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"sha2",
|
||||
"test-case",
|
||||
"test_program_methods",
|
||||
@ -6148,8 +5861,10 @@ name = "program_deployment"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"common",
|
||||
"nssa",
|
||||
"nssa_core",
|
||||
"sequencer_service_rpc",
|
||||
"tokio",
|
||||
"wallet",
|
||||
]
|
||||
@ -6270,7 +5985,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.6.3",
|
||||
"socket2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@ -6307,7 +6022,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.6.3",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
@ -6581,12 +6296,6 @@ dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-lite"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
@ -6606,7 +6315,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
@ -7453,47 +7162,43 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequencer_rpc"
|
||||
name = "sequencer_service"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix-cors",
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"base58",
|
||||
"base64 0.22.1",
|
||||
"bedrock_client",
|
||||
"borsh",
|
||||
"bytesize",
|
||||
"common",
|
||||
"futures",
|
||||
"hex",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"mempool",
|
||||
"nssa",
|
||||
"sequencer_core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequencer_runner"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix",
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"clap",
|
||||
"common",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"indexer_service_rpc",
|
||||
"jsonrpsee",
|
||||
"log",
|
||||
"mempool",
|
||||
"nssa",
|
||||
"sequencer_core",
|
||||
"sequencer_rpc",
|
||||
"sequencer_service_protocol",
|
||||
"sequencer_service_rpc",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequencer_service_protocol"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"common",
|
||||
"nssa",
|
||||
"nssa_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequencer_service_rpc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"jsonrpsee",
|
||||
"sequencer_service_protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -7689,7 +7394,7 @@ dependencies = [
|
||||
"const_format",
|
||||
"futures",
|
||||
"gloo-net",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"inventory",
|
||||
@ -7826,16 +7531,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.3"
|
||||
@ -7855,7 +7550,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
@ -8161,7 +7856,7 @@ dependencies = [
|
||||
"etcetera",
|
||||
"ferroid",
|
||||
"futures",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"memchr",
|
||||
@ -8321,7 +8016,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2 0.6.3",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@ -8518,7 +8213,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"h2",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
@ -8526,7 +8221,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"socket2 0.6.3",
|
||||
"socket2",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@ -8576,7 +8271,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"http-range-header",
|
||||
@ -8678,7 +8373,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
@ -8860,7 +8555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"http 1.4.0",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
]
|
||||
@ -8955,8 +8650,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
"base58",
|
||||
"base64 0.22.1",
|
||||
"borsh",
|
||||
"clap",
|
||||
"common",
|
||||
"env_logger",
|
||||
@ -8972,9 +8665,11 @@ dependencies = [
|
||||
"nssa_core",
|
||||
"optfield",
|
||||
"rand 0.8.5",
|
||||
"sequencer_service_rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror 2.0.18",
|
||||
"token_core",
|
||||
"tokio",
|
||||
"url",
|
||||
@ -8985,9 +8680,9 @@ name = "wallet-ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
"common",
|
||||
"nssa",
|
||||
"nssa_core",
|
||||
"sequencer_service_rpc",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"wallet",
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@ -17,9 +17,10 @@ members = [
|
||||
"programs/amm",
|
||||
"programs/token/core",
|
||||
"programs/token",
|
||||
"sequencer_core",
|
||||
"sequencer_rpc",
|
||||
"sequencer_runner",
|
||||
"sequencer/core",
|
||||
"sequencer/service",
|
||||
"sequencer/service/protocol",
|
||||
"sequencer/service/rpc",
|
||||
"indexer/core",
|
||||
"indexer/service",
|
||||
"indexer/service/protocol",
|
||||
@ -42,9 +43,10 @@ common = { path = "common" }
|
||||
mempool = { path = "mempool" }
|
||||
storage = { path = "storage" }
|
||||
key_protocol = { path = "key_protocol" }
|
||||
sequencer_core = { path = "sequencer_core" }
|
||||
sequencer_rpc = { path = "sequencer_rpc" }
|
||||
sequencer_runner = { path = "sequencer_runner" }
|
||||
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" }
|
||||
|
||||
8
Justfile
8
Justfile
@ -30,10 +30,10 @@ run-bedrock:
|
||||
docker compose up
|
||||
|
||||
# Run Sequencer
|
||||
[working-directory: 'sequencer_runner']
|
||||
[working-directory: 'sequencer/service']
|
||||
run-sequencer:
|
||||
@echo "🧠 Running sequencer"
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p sequencer_runner configs/debug
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p sequencer_service configs/debug/sequencer_config.json
|
||||
|
||||
# Run Indexer
|
||||
[working-directory: 'indexer/service']
|
||||
@ -62,8 +62,8 @@ run-wallet +args:
|
||||
# Clean runtime data
|
||||
clean:
|
||||
@echo "🧹 Cleaning run artifacts"
|
||||
rm -rf sequencer_runner/bedrock_signing_key
|
||||
rm -rf sequencer_runner/rocksdb
|
||||
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 rocksdb
|
||||
|
||||
@ -161,7 +161,7 @@ The sequencer and logos blockchain node can be run locally:
|
||||
- `RUST_LOG=info cargo run -p indexer_service indexer/service/configs/indexer_config.json`
|
||||
|
||||
3. On another terminal go to the `logos-blockchain/lssa` repo and run the sequencer:
|
||||
- `RUST_LOG=info cargo run -p sequencer_runner sequencer_runner/configs/debug`
|
||||
- `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:
|
||||
- `cargo install cargo-leptos`
|
||||
- `cargo leptos build --release`
|
||||
@ -171,8 +171,8 @@ 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_runner/rocksdb`
|
||||
3. In the `lssa` file `sequencer_runner/bedrock_signing_key`
|
||||
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`
|
||||
|
||||
### Normal mode (`just` commands)
|
||||
@ -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_runner sequencer_runner/configs/debug
|
||||
RUST_LOG=info cargo run --features standalone -p sequencer_service sequencer/service/configs/debug
|
||||
```
|
||||
|
||||
## Running with Docker
|
||||
|
||||
@ -13,16 +13,11 @@ nssa_core.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
serde_with.workspace = true
|
||||
reqwest.workspace = true
|
||||
base64.workspace = true
|
||||
sha2.workspace = true
|
||||
log.workspace = true
|
||||
hex.workspace = true
|
||||
borsh.workspace = true
|
||||
bytesize.workspace = true
|
||||
base64.workspace = true
|
||||
url.workspace = true
|
||||
logos-blockchain-common-http-client.workspace = true
|
||||
tokio-retry.workspace = true
|
||||
|
||||
@ -60,6 +60,18 @@ pub struct Block {
|
||||
pub bedrock_parent_id: MantleMsgId,
|
||||
}
|
||||
|
||||
impl Serialize for Block {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
crate::borsh_base64::serialize(self, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Block {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
crate::borsh_base64::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
pub struct HashableBlockData {
|
||||
pub block_id: BlockId,
|
||||
|
||||
25
common/src/borsh_base64.rs
Normal file
25
common/src/borsh_base64.rs
Normal file
@ -0,0 +1,25 @@
|
||||
//! This module provides utilities for serializing and deserializing data by combining Borsh and
|
||||
//! Base64 encodings.
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn serialize<T: BorshSerialize, S: serde::Serializer>(
|
||||
value: &T,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let borsh_encoded = borsh::to_vec(value).map_err(serde::ser::Error::custom)?;
|
||||
let base64_encoded = STANDARD.encode(&borsh_encoded);
|
||||
Serialize::serialize(&base64_encoded, serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, T: BorshDeserialize, D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<T, D::Error> {
|
||||
let base64_encoded = <String as Deserialize>::deserialize(deserializer)?;
|
||||
let borsh_encoded = STANDARD
|
||||
.decode(base64_encoded.as_bytes())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
borsh::from_slice(&borsh_encoded).map_err(serde::de::Error::custom)
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
use nssa::AccountId;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::rpc_primitives::errors::RpcError;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SequencerRpcError {
|
||||
pub jsonrpc: String,
|
||||
pub error: RpcError,
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum SequencerClientError {
|
||||
#[error("HTTP error")]
|
||||
HTTPError(#[from] reqwest::Error),
|
||||
#[error("Serde error")]
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
#[error("Internal error: {0:?}")]
|
||||
InternalError(SequencerRpcError),
|
||||
}
|
||||
|
||||
impl From<SequencerRpcError> for SequencerClientError {
|
||||
fn from(value: SequencerRpcError) -> Self {
|
||||
Self::InternalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ExecutionFailureKind {
|
||||
#[error("Failed to get data from sequencer")]
|
||||
SequencerError(#[source] anyhow::Error),
|
||||
#[error("Inputs amounts does not match outputs")]
|
||||
AmountMismatchError,
|
||||
#[error("Accounts key not found")]
|
||||
KeyNotFoundError,
|
||||
#[error("Sequencer client error: {0:?}")]
|
||||
SequencerClientError(#[from] SequencerClientError),
|
||||
#[error("Can not pay for operation")]
|
||||
InsufficientFundsError,
|
||||
#[error("Account {0} data is invalid")]
|
||||
AccountDataError(AccountId),
|
||||
}
|
||||
@ -4,10 +4,8 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
|
||||
pub mod block;
|
||||
mod borsh_base64;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod rpc_primitives;
|
||||
pub mod sequencer_client;
|
||||
pub mod transaction;
|
||||
|
||||
// Module for tests utility functions
|
||||
|
||||
@ -1,194 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use serde_json::{Value, to_value};
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct RpcParseError(pub String);
|
||||
|
||||
/// This struct may be returned from JSON RPC server in case of error.
|
||||
///
|
||||
/// It is expected that that this struct has impls From<_> all other RPC errors
|
||||
/// like [`RpcBlockError`](crate::types::blocks::RpcBlockError).
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RpcError {
|
||||
#[serde(flatten)]
|
||||
pub error_struct: Option<RpcErrorKind>,
|
||||
/// Deprecated please use the `error_struct` instead.
|
||||
pub code: i64,
|
||||
/// Deprecated please use the `error_struct` instead.
|
||||
pub message: String,
|
||||
/// Deprecated please use the `error_struct` instead.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(tag = "name", content = "cause", rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum RpcErrorKind {
|
||||
RequestValidationError(RpcRequestValidationErrorKind),
|
||||
HandlerError(Value),
|
||||
InternalError(Value),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(tag = "name", content = "info", rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum RpcRequestValidationErrorKind {
|
||||
MethodNotFound { method_name: String },
|
||||
ParseError { error_message: String },
|
||||
}
|
||||
|
||||
/// A general Server Error.
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ServerError {
|
||||
Timeout,
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl RpcError {
|
||||
/// A generic constructor.
|
||||
///
|
||||
/// Mostly for completeness, doesn't do anything but filling in the corresponding fields.
|
||||
#[must_use]
|
||||
pub const fn new(code: i64, message: String, data: Option<Value>) -> Self {
|
||||
Self {
|
||||
code,
|
||||
message,
|
||||
data,
|
||||
error_struct: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an Invalid Param error.
|
||||
pub fn invalid_params(data: impl serde::Serialize) -> Self {
|
||||
let value = match to_value(data) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return Self::server_error(Some(format!(
|
||||
"Failed to serialize invalid parameters error: {:?}",
|
||||
err.to_string()
|
||||
)));
|
||||
}
|
||||
};
|
||||
Self::new(-32_602, "Invalid params".to_owned(), Some(value))
|
||||
}
|
||||
|
||||
/// Create a server error.
|
||||
pub fn server_error<E: serde::Serialize>(e: Option<E>) -> Self {
|
||||
Self::new(
|
||||
-32_000,
|
||||
"Server error".to_owned(),
|
||||
e.map(|v| to_value(v).expect("Must be representable in JSON")),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a parse error.
|
||||
#[must_use]
|
||||
pub fn parse_error(e: String) -> Self {
|
||||
Self {
|
||||
code: -32_700,
|
||||
message: "Parse error".to_owned(),
|
||||
data: Some(Value::String(e.clone())),
|
||||
error_struct: Some(RpcErrorKind::RequestValidationError(
|
||||
RpcRequestValidationErrorKind::ParseError { error_message: e },
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn serialization_error(e: &str) -> Self {
|
||||
Self::new_internal_error(Some(Value::String(e.to_owned())), e)
|
||||
}
|
||||
|
||||
/// Helper method to define extract `INTERNAL_ERROR` in separate `RpcErrorKind`
|
||||
/// Returns `HANDLER_ERROR` if the error is not internal one.
|
||||
#[must_use]
|
||||
pub fn new_internal_or_handler_error(error_data: Option<Value>, error_struct: Value) -> Self {
|
||||
if error_struct["name"] == "INTERNAL_ERROR" {
|
||||
let error_message = match error_struct["info"].get("error_message") {
|
||||
Some(Value::String(error_message)) => error_message.as_str(),
|
||||
_ => "InternalError happened during serializing InternalError",
|
||||
};
|
||||
Self::new_internal_error(error_data, error_message)
|
||||
} else {
|
||||
Self::new_handler_error(error_data, error_struct)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new_internal_error(error_data: Option<Value>, info: &str) -> Self {
|
||||
Self {
|
||||
code: -32_000,
|
||||
message: "Server error".to_owned(),
|
||||
data: error_data,
|
||||
error_struct: Some(RpcErrorKind::InternalError(serde_json::json!({
|
||||
"name": "INTERNAL_ERROR",
|
||||
"info": serde_json::json!({"error_message": info})
|
||||
}))),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_handler_error(error_data: Option<Value>, error_struct: Value) -> Self {
|
||||
Self {
|
||||
code: -32_000,
|
||||
message: "Server error".to_owned(),
|
||||
data: error_data,
|
||||
error_struct: Some(RpcErrorKind::HandlerError(error_struct)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a method not found error.
|
||||
#[must_use]
|
||||
pub fn method_not_found(method: String) -> Self {
|
||||
Self {
|
||||
code: -32_601,
|
||||
message: "Method not found".to_owned(),
|
||||
data: Some(Value::String(method.clone())),
|
||||
error_struct: Some(RpcErrorKind::RequestValidationError(
|
||||
RpcRequestValidationErrorKind::MethodNotFound {
|
||||
method_name: method,
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RpcError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpcParseError> for RpcError {
|
||||
fn from(parse_error: RpcParseError) -> Self {
|
||||
Self::parse_error(parse_error.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for RpcError {
|
||||
fn from(_: std::convert::Infallible) -> Self {
|
||||
// SAFETY: Infallible error can never be constructed, so this code can never be reached.
|
||||
unsafe { core::hint::unreachable_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ServerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Timeout => write!(f, "ServerError: Timeout"),
|
||||
Self::Closed => write!(f, "ServerError: Closed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServerError> for RpcError {
|
||||
fn from(e: ServerError) -> Self {
|
||||
let error_data = match to_value(&e) {
|
||||
Ok(value) => value,
|
||||
Err(_err) => {
|
||||
return Self::new_internal_error(None, "Failed to serialize ServerError");
|
||||
}
|
||||
};
|
||||
Self::new_internal_error(Some(error_data), e.to_string().as_str())
|
||||
}
|
||||
}
|
||||
@ -1,588 +0,0 @@
|
||||
// Copyright 2017 tokio-jsonrpc Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
//! JSON-RPC 2.0 messages.
|
||||
//!
|
||||
//! The main entrypoint here is the [Message](enum.Message.html). The others are just building
|
||||
//! blocks and you should generally work with `Message` instead.
|
||||
use std::fmt::{Formatter, Result as FmtResult};
|
||||
|
||||
use serde::{
|
||||
de::{Deserializer, Error, Unexpected, Visitor},
|
||||
ser::{SerializeStruct as _, Serializer},
|
||||
};
|
||||
use serde_json::{Result as JsonResult, Value};
|
||||
|
||||
use super::errors::RpcError;
|
||||
|
||||
pub type Parsed = Result<Message, Broken>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct Version;
|
||||
|
||||
impl serde::Serialize for Version {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str("2.0")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Version {
|
||||
#[expect(
|
||||
clippy::renamed_function_params,
|
||||
reason = "More readable than original serde parameter names"
|
||||
)]
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct VersionVisitor;
|
||||
impl Visitor<'_> for VersionVisitor {
|
||||
type Value = Version;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
|
||||
formatter.write_str("a version string")
|
||||
}
|
||||
|
||||
fn visit_str<E: Error>(self, value: &str) -> Result<Version, E> {
|
||||
match value {
|
||||
"2.0" => Ok(Version),
|
||||
_ => Err(E::invalid_value(Unexpected::Str(value), &"value 2.0")),
|
||||
}
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_str(VersionVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// An RPC request.
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[expect(
|
||||
clippy::partial_pub_fields,
|
||||
reason = "We don't want to allow access to the version, but the others are public for ease of use"
|
||||
)]
|
||||
pub struct Request {
|
||||
jsonrpc: Version,
|
||||
pub method: String,
|
||||
#[serde(default, skip_serializing_if = "Value::is_null")]
|
||||
pub params: Value,
|
||||
pub id: Value,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
#[must_use]
|
||||
pub fn from_payload_version_2_0(method: String, payload: serde_json::Value) -> Self {
|
||||
Self {
|
||||
jsonrpc: Version,
|
||||
method,
|
||||
params: payload,
|
||||
// ToDo: Correct checking of id
|
||||
id: 1.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Answer the request with a (positive) reply.
|
||||
///
|
||||
/// The ID is taken from the request.
|
||||
#[must_use]
|
||||
pub fn reply(&self, reply: Value) -> Message {
|
||||
Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Ok(reply),
|
||||
id: self.id.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Answer the request with an error.
|
||||
#[must_use]
|
||||
pub fn error(&self, error: RpcError) -> Message {
|
||||
Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(error),
|
||||
id: self.id.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A response to an RPC.
|
||||
///
|
||||
/// It is created by the methods on [Request](struct.Request.html).
|
||||
#[expect(
|
||||
clippy::partial_pub_fields,
|
||||
reason = "We don't want to allow access to the version, but the others are public for ease of use"
|
||||
)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Response {
|
||||
jsonrpc: Version,
|
||||
pub result: Result<Value, RpcError>,
|
||||
pub id: Value,
|
||||
}
|
||||
|
||||
impl serde::Serialize for Response {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut sub = serializer.serialize_struct("Response", 3)?;
|
||||
sub.serialize_field("jsonrpc", &self.jsonrpc)?;
|
||||
match &self.result {
|
||||
Ok(value) => sub.serialize_field("result", value),
|
||||
Err(err) => sub.serialize_field("error", err),
|
||||
}?;
|
||||
sub.serialize_field("id", &self.id)?;
|
||||
sub.end()
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trick for deserialization.
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct WireResponse {
|
||||
// It is actually used to eat and sanity check the deserialized text
|
||||
#[serde(rename = "jsonrpc")]
|
||||
_jsonrpc: Version,
|
||||
// Make sure we accept null as Some(Value::Null), instead of going to None
|
||||
#[serde(default, deserialize_with = "some_value")]
|
||||
result: Option<Value>,
|
||||
error: Option<RpcError>,
|
||||
id: Value,
|
||||
}
|
||||
|
||||
// Implementing deserialize is hard. We sidestep the difficulty by deserializing a similar
|
||||
// structure that directly corresponds to whatever is on the wire and then convert it to our more
|
||||
// convenient representation.
|
||||
impl<'de> serde::Deserialize<'de> for Response {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let wr: WireResponse = serde::Deserialize::deserialize(deserializer)?;
|
||||
let result = match (wr.result, wr.error) {
|
||||
(Some(res), None) => Ok(res),
|
||||
(None, Some(err)) => Err(err),
|
||||
_ => {
|
||||
let err = D::Error::custom("Either 'error' or 'result' is expected, but not both");
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
jsonrpc: Version,
|
||||
result,
|
||||
id: wr.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A notification (doesn't expect an answer).
|
||||
#[expect(
|
||||
clippy::partial_pub_fields,
|
||||
reason = "We don't want to allow access to the version, but the others are public for ease of use"
|
||||
)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Notification {
|
||||
jsonrpc: Version,
|
||||
pub method: String,
|
||||
#[serde(default, skip_serializing_if = "Value::is_null")]
|
||||
pub params: Value,
|
||||
}
|
||||
|
||||
/// One message of the JSON RPC protocol.
|
||||
///
|
||||
/// One message, directly mapped from the structures of the protocol. See the
|
||||
/// [specification](http://www.jsonrpc.org/specification) for more details.
|
||||
///
|
||||
/// Since the protocol allows one endpoint to be both client and server at the same time, the
|
||||
/// message can decode and encode both directions of the protocol.
|
||||
///
|
||||
/// The `Batch` variant is supposed to be created directly, without a constructor.
|
||||
///
|
||||
/// The `UnmatchedSub` variant is used when a request is an array and some of the subrequests
|
||||
/// aren't recognized as valid json rpc 2.0 messages. This is never returned as a top-level
|
||||
/// element, it is returned as `Err(Broken::Unmatched)`.
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Message {
|
||||
/// An RPC request.
|
||||
Request(Request),
|
||||
/// A response to a Request.
|
||||
Response(Response),
|
||||
/// A notification.
|
||||
Notification(Notification),
|
||||
/// A batch of more requests or responses.
|
||||
///
|
||||
/// The protocol allows bundling multiple requests, notifications or responses to a single
|
||||
/// message.
|
||||
///
|
||||
/// This variant has no direct constructor and is expected to be constructed manually.
|
||||
Batch(Vec<Self>),
|
||||
/// An unmatched sub entry in a `Batch`.
|
||||
///
|
||||
/// When there's a `Batch` and an element doesn't comform to the JSONRPC 2.0 format, that one
|
||||
/// is represented by this. This is never produced as a top-level value when parsing, the
|
||||
/// `Err(Broken::Unmatched)` is used instead. It is not possible to serialize.
|
||||
#[serde(skip_serializing)]
|
||||
UnmatchedSub(Value),
|
||||
}
|
||||
|
||||
impl Message {
|
||||
/// A constructor for a request.
|
||||
///
|
||||
/// The ID is auto-set to dontcare.
|
||||
#[must_use]
|
||||
pub fn request(method: String, params: Value) -> Self {
|
||||
let id = Value::from("dontcare");
|
||||
Self::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method,
|
||||
params,
|
||||
id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a top-level error (without an ID).
|
||||
#[must_use]
|
||||
pub const fn error(error: RpcError) -> Self {
|
||||
Self::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(error),
|
||||
id: Value::Null,
|
||||
})
|
||||
}
|
||||
|
||||
/// A constructor for a notification.
|
||||
#[must_use]
|
||||
pub const fn notification(method: String, params: Value) -> Self {
|
||||
Self::Notification(Notification {
|
||||
jsonrpc: Version,
|
||||
method,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/// A constructor for a response.
|
||||
#[must_use]
|
||||
pub const fn response(id: Value, result: Result<Value, RpcError>) -> Self {
|
||||
Self::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result,
|
||||
id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns id or Null if there is no id.
|
||||
#[must_use]
|
||||
pub fn id(&self) -> Value {
|
||||
match self {
|
||||
Self::Request(req) => req.id.clone(),
|
||||
Self::Response(response) => response.id.clone(),
|
||||
Self::Notification(_) | Self::Batch(_) | Self::UnmatchedSub(_) => Value::Null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for String {
|
||||
fn from(val: Message) -> Self {
|
||||
::serde_json::ser::to_string(&val).expect("message serialization to json should not fail")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for Vec<u8> {
|
||||
fn from(val: Message) -> Self {
|
||||
::serde_json::ser::to_vec(&val)
|
||||
.expect("message serialization to json bytes should not fail")
|
||||
}
|
||||
}
|
||||
|
||||
/// A broken message.
|
||||
///
|
||||
/// Protocol-level errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Broken {
|
||||
/// It was valid JSON, but doesn't match the form of a JSONRPC 2.0 message.
|
||||
Unmatched(Value),
|
||||
/// Invalid JSON.
|
||||
#[serde(skip_deserializing)]
|
||||
SyntaxError(String),
|
||||
}
|
||||
|
||||
impl Broken {
|
||||
/// Generate an appropriate error message.
|
||||
///
|
||||
/// The error message for these things are specified in the RFC, so this just creates an error
|
||||
/// with the right values.
|
||||
#[must_use]
|
||||
pub fn reply(&self) -> Message {
|
||||
match self {
|
||||
Self::Unmatched(_) => Message::error(RpcError::parse_error(
|
||||
"JSON RPC Request format was expected".to_owned(),
|
||||
)),
|
||||
Self::SyntaxError(e) => Message::error(RpcError::parse_error(e.clone())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trick to easily deserialize and detect valid JSON, but invalid Message.
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum WireMessage {
|
||||
Message(Message),
|
||||
Broken(Broken),
|
||||
}
|
||||
|
||||
pub fn decoded_to_parsed(res: JsonResult<WireMessage>) -> Parsed {
|
||||
match res {
|
||||
Ok(WireMessage::Message(Message::UnmatchedSub(value))) => Err(Broken::Unmatched(value)),
|
||||
Ok(WireMessage::Message(m)) => Ok(m),
|
||||
Ok(WireMessage::Broken(b)) => Err(b),
|
||||
Err(e) => Err(Broken::SyntaxError(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a [Message](enum.Message.html) from a slice.
|
||||
///
|
||||
/// Invalid JSON or JSONRPC messages are reported as [Broken](enum.Broken.html).
|
||||
pub fn from_slice(s: &[u8]) -> Parsed {
|
||||
decoded_to_parsed(::serde_json::de::from_slice(s))
|
||||
}
|
||||
|
||||
/// Read a [Message](enum.Message.html) from a string.
|
||||
///
|
||||
/// Invalid JSON or JSONRPC messages are reported as [Broken](enum.Broken.html).
|
||||
pub fn from_str(s: &str) -> Parsed {
|
||||
from_slice(s.as_bytes())
|
||||
}
|
||||
|
||||
/// Deserializer for `Option<Value>` that produces `Some(Value::Null)`.
|
||||
///
|
||||
/// The usual one produces None in that case. But we need to know the difference between
|
||||
/// `{x: null}` and `{}`.
|
||||
fn some_value<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<Value>, D::Error> {
|
||||
serde::Deserialize::deserialize(deserializer).map(Some)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::{Value, de::from_slice, json, ser::to_vec};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Test serialization and deserialization of the Message.
|
||||
///
|
||||
/// We first deserialize it from a string. That way we check deserialization works.
|
||||
/// But since serialization doesn't have to produce the exact same result (order, spaces, …),
|
||||
/// we then serialize and deserialize the thing again and check it matches.
|
||||
#[test]
|
||||
fn message_serde() {
|
||||
// A helper for running one message test
|
||||
fn one(input: &str, expected: &Message) {
|
||||
let parsed: Message = from_str(input).unwrap();
|
||||
assert_eq!(*expected, parsed);
|
||||
let serialized = to_vec(&parsed).unwrap();
|
||||
let deserialized: Message = from_slice(&serialized).unwrap();
|
||||
assert_eq!(parsed, deserialized);
|
||||
}
|
||||
|
||||
// A request without parameters
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "method": "call", "id": 1}"#,
|
||||
&Message::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method: "call".to_owned(),
|
||||
params: Value::Null,
|
||||
id: json!(1),
|
||||
}),
|
||||
);
|
||||
// A request with parameters
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "method": "call", "params": [1, 2, 3], "id": 2}"#,
|
||||
&Message::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method: "call".to_owned(),
|
||||
params: json!([1, 2, 3]),
|
||||
id: json!(2),
|
||||
}),
|
||||
);
|
||||
// A notification (with parameters)
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "method": "notif", "params": {"x": "y"}}"#,
|
||||
&Message::Notification(Notification {
|
||||
jsonrpc: Version,
|
||||
method: "notif".to_owned(),
|
||||
params: json!({"x": "y"}),
|
||||
}),
|
||||
);
|
||||
// A successful response
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "result": 42, "id": 3}"#,
|
||||
&Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Ok(json!(42)),
|
||||
id: json!(3),
|
||||
}),
|
||||
);
|
||||
// A successful response
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "result": null, "id": 3}"#,
|
||||
&Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Ok(Value::Null),
|
||||
id: json!(3),
|
||||
}),
|
||||
);
|
||||
// An error
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "error": {"code": 42, "message": "Wrong!"}, "id": null}"#,
|
||||
&Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(RpcError::new(42, "Wrong!".to_owned(), None)),
|
||||
id: Value::Null,
|
||||
}),
|
||||
);
|
||||
// A batch
|
||||
one(
|
||||
r#"[
|
||||
{"jsonrpc": "2.0", "method": "notif"},
|
||||
{"jsonrpc": "2.0", "method": "call", "id": 42}
|
||||
]"#,
|
||||
&Message::Batch(vec![
|
||||
Message::Notification(Notification {
|
||||
jsonrpc: Version,
|
||||
method: "notif".to_owned(),
|
||||
params: Value::Null,
|
||||
}),
|
||||
Message::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method: "call".to_owned(),
|
||||
params: Value::Null,
|
||||
id: json!(42),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
// Some handling of broken messages inside a batch
|
||||
let parsed = from_str(
|
||||
r#"[
|
||||
{"jsonrpc": "2.0", "method": "notif"},
|
||||
{"jsonrpc": "2.0", "method": "call", "id": 42},
|
||||
true
|
||||
]"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Message::Batch(vec![
|
||||
Message::Notification(Notification {
|
||||
jsonrpc: Version,
|
||||
method: "notif".to_owned(),
|
||||
params: Value::Null,
|
||||
}),
|
||||
Message::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method: "call".to_owned(),
|
||||
params: Value::Null,
|
||||
id: json!(42),
|
||||
}),
|
||||
Message::UnmatchedSub(Value::Bool(true)),
|
||||
]),
|
||||
parsed
|
||||
);
|
||||
to_vec(&Message::UnmatchedSub(Value::Null)).unwrap_err();
|
||||
}
|
||||
|
||||
/// A helper for the `broken` test.
|
||||
///
|
||||
/// Check that the given JSON string parses, but is not recognized as a valid RPC message.
|
||||
///
|
||||
/// Test things that are almost but not entirely JSONRPC are rejected.
|
||||
///
|
||||
/// The reject is done by returning it as Unmatched.
|
||||
#[test]
|
||||
fn broken() {
|
||||
// A helper with one test
|
||||
fn one(input: &str) {
|
||||
let msg = from_str(input);
|
||||
match msg {
|
||||
Err(Broken::Unmatched(_)) => (),
|
||||
_ => panic!("{input} recognized as an RPC message: {msg:?}!"),
|
||||
}
|
||||
}
|
||||
|
||||
// Missing the version
|
||||
one(r#"{"method": "notif"}"#);
|
||||
// Wrong version
|
||||
one(r#"{"jsonrpc": 2.0, "method": "notif"}"#);
|
||||
// A response with both result and error
|
||||
one(r#"{"jsonrpc": "2.0", "result": 42, "error": {"code": 42, "message": "!"}, "id": 1}"#);
|
||||
// A response without an id
|
||||
one(r#"{"jsonrpc": "2.0", "result": 42}"#);
|
||||
// An extra field
|
||||
one(r#"{"jsonrpc": "2.0", "method": "weird", "params": 42, "others": 43, "id": 2}"#);
|
||||
// Something completely different
|
||||
one(r#"{"x": [1, 2, 3]}"#);
|
||||
|
||||
match from_str("{]") {
|
||||
Err(Broken::SyntaxError(_)) => (),
|
||||
other => panic!("Something unexpected: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Test some non-trivial aspects of the constructors.
|
||||
///
|
||||
/// This doesn't have a full coverage, because there's not much to actually test there.
|
||||
/// Most of it is related to the ids.
|
||||
#[test]
|
||||
#[ignore = "Not a full coverage test"]
|
||||
fn constructors() {
|
||||
let msg1 = Message::request("call".to_owned(), json!([1, 2, 3]));
|
||||
let msg2 = Message::request("call".to_owned(), json!([1, 2, 3]));
|
||||
// They differ, even when created with the same parameters
|
||||
assert_ne!(msg1, msg2);
|
||||
// And, specifically, they differ in the ID's
|
||||
let (req1, req2) = if let (Message::Request(req1), Message::Request(req2)) = (msg1, msg2) {
|
||||
assert_ne!(req1.id, req2.id);
|
||||
assert!(req1.id.is_string());
|
||||
assert!(req2.id.is_string());
|
||||
(req1, req2)
|
||||
} else {
|
||||
panic!("Non-request received");
|
||||
};
|
||||
let id1 = req1.id.clone();
|
||||
// When we answer a message, we get the same ID
|
||||
if let Message::Response(resp) = req1.reply(json!([1, 2, 3])) {
|
||||
assert_eq!(
|
||||
resp,
|
||||
Response {
|
||||
jsonrpc: Version,
|
||||
result: Ok(json!([1, 2, 3])),
|
||||
id: id1
|
||||
}
|
||||
);
|
||||
} else {
|
||||
panic!("Not a response");
|
||||
}
|
||||
let id2 = req2.id.clone();
|
||||
// The same with an error
|
||||
if let Message::Response(resp) = req2.error(RpcError::new(42, "Wrong!".to_owned(), None)) {
|
||||
assert_eq!(
|
||||
resp,
|
||||
Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(RpcError::new(42, "Wrong!".to_owned(), None)),
|
||||
id: id2,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
panic!("Not a response");
|
||||
}
|
||||
// When we have unmatched, we generate a top-level error with Null id.
|
||||
if let Message::Response(resp) =
|
||||
Message::error(RpcError::new(43, "Also wrong!".to_owned(), None))
|
||||
{
|
||||
assert_eq!(
|
||||
resp,
|
||||
Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(RpcError::new(43, "Also wrong!".to_owned(), None)),
|
||||
id: Value::Null,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
panic!("Not a response");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
use bytesize::ByteSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod errors;
|
||||
pub mod message;
|
||||
pub mod parser;
|
||||
pub mod requests;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct RpcLimitsConfig {
|
||||
/// Maximum byte size of the json payload.
|
||||
pub json_payload_max_size: ByteSize,
|
||||
}
|
||||
|
||||
impl Default for RpcLimitsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
json_payload_max_size: ByteSize::mib(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct RpcConfig {
|
||||
pub addr: String,
|
||||
pub cors_allowed_origins: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub limits_config: RpcLimitsConfig,
|
||||
}
|
||||
|
||||
impl Default for RpcConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
addr: "0.0.0.0:3040".to_owned(),
|
||||
cors_allowed_origins: vec!["*".to_owned()],
|
||||
limits_config: RpcLimitsConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcConfig {
|
||||
#[must_use]
|
||||
pub fn new(addr: &str) -> Self {
|
||||
Self {
|
||||
addr: addr.to_owned(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_port(port: u16) -> Self {
|
||||
Self {
|
||||
addr: format!("0.0.0.0:{port}"),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::errors::RpcParseError;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! parse_request {
|
||||
($request_name:ty) => {
|
||||
impl RpcRequest for $request_name {
|
||||
fn parse(value: Option<Value>) -> Result<Self, RpcParseError> {
|
||||
parse_params::<Self>(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait RpcRequest: Sized {
|
||||
fn parse(value: Option<Value>) -> Result<Self, RpcParseError>;
|
||||
}
|
||||
|
||||
pub fn parse_params<T: DeserializeOwned>(value: Option<Value>) -> Result<T, RpcParseError> {
|
||||
value.map_or_else(
|
||||
|| Err(RpcParseError("Require at least one parameter".to_owned())),
|
||||
|value| {
|
||||
serde_json::from_value(value)
|
||||
.map_err(|err| RpcParseError(format!("Failed parsing args: {err}")))
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -1,219 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nssa::AccountId;
|
||||
use nssa_core::program::ProgramId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{
|
||||
errors::RpcParseError,
|
||||
parser::{RpcRequest, parse_params},
|
||||
};
|
||||
use crate::{HashType, parse_request};
|
||||
|
||||
mod base64_deser {
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use serde::{self, Deserialize, Deserializer, Serializer, ser::SerializeSeq as _};
|
||||
|
||||
pub mod vec {
|
||||
use super::*;
|
||||
|
||||
pub fn serialize<S>(bytes_vec: &[Vec<u8>], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(bytes_vec.len()))?;
|
||||
for bytes in bytes_vec {
|
||||
let s = general_purpose::STANDARD.encode(bytes);
|
||||
seq.serialize_element(&s)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let base64_strings: Vec<String> = Deserialize::deserialize(deserializer)?;
|
||||
base64_strings
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
general_purpose::STANDARD
|
||||
.decode(&s)
|
||||
.map_err(serde::de::Error::custom)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let base64_string = general_purpose::STANDARD.encode(bytes);
|
||||
serializer.serialize_str(&base64_string)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let base64_string: String = Deserialize::deserialize(deserializer)?;
|
||||
general_purpose::STANDARD
|
||||
.decode(&base64_string)
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct HelloRequest;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct RegisterAccountRequest {
|
||||
pub account_id: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SendTxRequest {
|
||||
#[serde(with = "base64_deser")]
|
||||
pub transaction: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetBlockDataRequest {
|
||||
pub block_id: u64,
|
||||
}
|
||||
|
||||
/// Get a range of blocks from `start_block_id` to `end_block_id` (inclusive).
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetBlockRangeDataRequest {
|
||||
pub start_block_id: u64,
|
||||
pub end_block_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetGenesisIdRequest;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetLastBlockRequest;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetInitialTestnetAccountsRequest;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountBalanceRequest {
|
||||
pub account_id: AccountId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetTransactionByHashRequest {
|
||||
pub hash: HashType,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountsNoncesRequest {
|
||||
pub account_ids: Vec<AccountId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountRequest {
|
||||
pub account_id: AccountId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetProofForCommitmentRequest {
|
||||
pub commitment: nssa_core::Commitment,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetProgramIdsRequest;
|
||||
|
||||
parse_request!(HelloRequest);
|
||||
parse_request!(RegisterAccountRequest);
|
||||
parse_request!(SendTxRequest);
|
||||
parse_request!(GetBlockDataRequest);
|
||||
parse_request!(GetBlockRangeDataRequest);
|
||||
parse_request!(GetGenesisIdRequest);
|
||||
parse_request!(GetLastBlockRequest);
|
||||
parse_request!(GetInitialTestnetAccountsRequest);
|
||||
parse_request!(GetAccountBalanceRequest);
|
||||
parse_request!(GetTransactionByHashRequest);
|
||||
parse_request!(GetAccountsNoncesRequest);
|
||||
parse_request!(GetProofForCommitmentRequest);
|
||||
parse_request!(GetAccountRequest);
|
||||
parse_request!(GetProgramIdsRequest);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct HelloResponse {
|
||||
pub greeting: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct RegisterAccountResponse {
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SendTxResponse {
|
||||
pub status: String,
|
||||
pub tx_hash: HashType,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetBlockDataResponse {
|
||||
#[serde(with = "base64_deser")]
|
||||
pub block: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetBlockRangeDataResponse {
|
||||
#[serde(with = "base64_deser::vec")]
|
||||
pub blocks: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetGenesisIdResponse {
|
||||
pub genesis_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetLastBlockResponse {
|
||||
pub last_block: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountBalanceResponse {
|
||||
pub balance: u128,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountsNoncesResponse {
|
||||
pub nonces: Vec<u128>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetTransactionByHashResponse {
|
||||
pub transaction: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountResponse {
|
||||
pub account: nssa::Account,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetProofForCommitmentResponse {
|
||||
pub membership_proof: Option<nssa_core::MembershipProof>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetProgramIdsResponse {
|
||||
pub program_ids: HashMap<String, ProgramId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct GetInitialTestnetAccountsResponse {
|
||||
/// Hex encoded account id.
|
||||
pub account_id: String,
|
||||
pub balance: u64,
|
||||
}
|
||||
@ -1,361 +0,0 @@
|
||||
use std::{collections::HashMap, ops::RangeInclusive};
|
||||
|
||||
use anyhow::Result;
|
||||
use nssa::AccountId;
|
||||
use nssa_core::program::ProgramId;
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
|
||||
use super::rpc_primitives::requests::{
|
||||
GetAccountBalanceRequest, GetAccountBalanceResponse, GetBlockDataRequest, GetBlockDataResponse,
|
||||
GetGenesisIdRequest, GetGenesisIdResponse, GetInitialTestnetAccountsRequest,
|
||||
};
|
||||
use crate::{
|
||||
HashType,
|
||||
config::BasicAuth,
|
||||
error::{SequencerClientError, SequencerRpcError},
|
||||
rpc_primitives::{
|
||||
self,
|
||||
requests::{
|
||||
GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest,
|
||||
GetAccountsNoncesResponse, GetBlockRangeDataRequest, GetBlockRangeDataResponse,
|
||||
GetInitialTestnetAccountsResponse, GetLastBlockRequest, GetLastBlockResponse,
|
||||
GetProgramIdsRequest, GetProgramIdsResponse, GetProofForCommitmentRequest,
|
||||
GetProofForCommitmentResponse, GetTransactionByHashRequest,
|
||||
GetTransactionByHashResponse, SendTxRequest, SendTxResponse,
|
||||
},
|
||||
},
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct SequencerRpcResponse {
|
||||
#[serde(rename = "jsonrpc")]
|
||||
_jsonrpc: String,
|
||||
result: serde_json::Value,
|
||||
#[serde(rename = "id")]
|
||||
_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SequencerClient {
|
||||
pub client: reqwest::Client,
|
||||
pub sequencer_addr: Url,
|
||||
pub basic_auth: Option<BasicAuth>,
|
||||
}
|
||||
|
||||
impl SequencerClient {
|
||||
pub fn new(sequencer_addr: Url) -> Result<Self> {
|
||||
Self::new_with_auth(sequencer_addr, None)
|
||||
}
|
||||
|
||||
pub fn new_with_auth(sequencer_addr: Url, basic_auth: Option<BasicAuth>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
client: Client::builder()
|
||||
// Add more fields if needed
|
||||
.timeout(std::time::Duration::from_secs(60))
|
||||
// Should be kept in sync with server keep-alive settings
|
||||
.pool_idle_timeout(std::time::Duration::from_secs(5))
|
||||
.build()?,
|
||||
sequencer_addr,
|
||||
basic_auth,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn call_method_with_payload(
|
||||
&self,
|
||||
method: &str,
|
||||
payload: Value,
|
||||
) -> Result<Value, SequencerClientError> {
|
||||
let request =
|
||||
rpc_primitives::message::Request::from_payload_version_2_0(method.to_owned(), payload);
|
||||
|
||||
log::debug!(
|
||||
"Calling method {method} with payload {request:?} to sequencer at {}",
|
||||
self.sequencer_addr
|
||||
);
|
||||
|
||||
let strategy = tokio_retry::strategy::FixedInterval::from_millis(10000).take(60);
|
||||
|
||||
let response_vall = tokio_retry::Retry::spawn(strategy, || async {
|
||||
let mut call_builder = self.client.post(self.sequencer_addr.clone());
|
||||
|
||||
if let Some(BasicAuth { username, password }) = &self.basic_auth {
|
||||
call_builder = call_builder.basic_auth(username, password.as_deref());
|
||||
}
|
||||
|
||||
let call_res_res = call_builder.json(&request).send().await;
|
||||
|
||||
match call_res_res {
|
||||
Err(err) => Err(err),
|
||||
Ok(call_res) => call_res.json::<Value>().await,
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
if let Ok(response) = serde_json::from_value::<SequencerRpcResponse>(response_vall.clone())
|
||||
{
|
||||
Ok(response.result)
|
||||
} else {
|
||||
let err_resp = serde_json::from_value::<SequencerRpcError>(response_vall)?;
|
||||
|
||||
Err(err_resp.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get block data at `block_id` from sequencer.
|
||||
pub async fn get_block(
|
||||
&self,
|
||||
block_id: u64,
|
||||
) -> Result<GetBlockDataResponse, SequencerClientError> {
|
||||
let block_req = GetBlockDataRequest { block_id };
|
||||
|
||||
let req = serde_json::to_value(block_req)?;
|
||||
|
||||
let resp = self.call_method_with_payload("get_block", req).await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
pub async fn get_block_range(
|
||||
&self,
|
||||
range: RangeInclusive<u64>,
|
||||
) -> Result<GetBlockRangeDataResponse, SequencerClientError> {
|
||||
let block_req = GetBlockRangeDataRequest {
|
||||
start_block_id: *range.start(),
|
||||
end_block_id: *range.end(),
|
||||
};
|
||||
|
||||
let req = serde_json::to_value(block_req)?;
|
||||
|
||||
let resp = self
|
||||
.call_method_with_payload("get_block_range", req)
|
||||
.await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Get last known `blokc_id` from sequencer.
|
||||
pub async fn get_last_block(&self) -> Result<GetLastBlockResponse, SequencerClientError> {
|
||||
let block_req = GetLastBlockRequest {};
|
||||
|
||||
let req = serde_json::to_value(block_req)?;
|
||||
|
||||
let resp = self.call_method_with_payload("get_last_block", req).await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Get account public balance for `account_id`. `account_id` must be a valid hex-string for 32
|
||||
/// bytes.
|
||||
pub async fn get_account_balance(
|
||||
&self,
|
||||
account_id: AccountId,
|
||||
) -> Result<GetAccountBalanceResponse, SequencerClientError> {
|
||||
let block_req = GetAccountBalanceRequest { account_id };
|
||||
|
||||
let req = serde_json::to_value(block_req)?;
|
||||
|
||||
let resp = self
|
||||
.call_method_with_payload("get_account_balance", req)
|
||||
.await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Get accounts nonces for `account_ids`. `account_ids` must be a list of valid hex-strings for
|
||||
/// 32 bytes.
|
||||
pub async fn get_accounts_nonces(
|
||||
&self,
|
||||
account_ids: Vec<AccountId>,
|
||||
) -> Result<GetAccountsNoncesResponse, SequencerClientError> {
|
||||
let block_req = GetAccountsNoncesRequest { account_ids };
|
||||
|
||||
let req = serde_json::to_value(block_req)?;
|
||||
|
||||
let resp = self
|
||||
.call_method_with_payload("get_accounts_nonces", req)
|
||||
.await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
pub async fn get_account(
|
||||
&self,
|
||||
account_id: AccountId,
|
||||
) -> Result<GetAccountResponse, SequencerClientError> {
|
||||
let block_req = GetAccountRequest { account_id };
|
||||
|
||||
let req = serde_json::to_value(block_req)?;
|
||||
|
||||
let resp = self.call_method_with_payload("get_account", req).await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Get transaction details for `hash`.
|
||||
pub async fn get_transaction_by_hash(
|
||||
&self,
|
||||
hash: HashType,
|
||||
) -> Result<GetTransactionByHashResponse, SequencerClientError> {
|
||||
let block_req = GetTransactionByHashRequest { hash };
|
||||
|
||||
let req = serde_json::to_value(block_req)?;
|
||||
|
||||
let resp = self
|
||||
.call_method_with_payload("get_transaction_by_hash", req)
|
||||
.await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Send transaction to sequencer.
|
||||
pub async fn send_tx_public(
|
||||
&self,
|
||||
transaction: nssa::PublicTransaction,
|
||||
) -> Result<SendTxResponse, SequencerClientError> {
|
||||
let transaction = NSSATransaction::Public(transaction);
|
||||
|
||||
let tx_req = SendTxRequest {
|
||||
transaction: borsh::to_vec(&transaction).unwrap(),
|
||||
};
|
||||
|
||||
let req = serde_json::to_value(tx_req)?;
|
||||
|
||||
let resp = self.call_method_with_payload("send_tx", req).await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Send transaction to sequencer.
|
||||
pub async fn send_tx_private(
|
||||
&self,
|
||||
transaction: nssa::PrivacyPreservingTransaction,
|
||||
) -> Result<SendTxResponse, SequencerClientError> {
|
||||
let transaction = NSSATransaction::PrivacyPreserving(transaction);
|
||||
|
||||
let tx_req = SendTxRequest {
|
||||
transaction: borsh::to_vec(&transaction).unwrap(),
|
||||
};
|
||||
|
||||
let req = serde_json::to_value(tx_req)?;
|
||||
|
||||
let resp = self.call_method_with_payload("send_tx", req).await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Get genesis id from sequencer.
|
||||
pub async fn get_genesis_id(&self) -> Result<GetGenesisIdResponse, SequencerClientError> {
|
||||
let genesis_req = GetGenesisIdRequest {};
|
||||
|
||||
let req = serde_json::to_value(genesis_req).unwrap();
|
||||
|
||||
let resp = self
|
||||
.call_method_with_payload("get_genesis", req)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let resp_deser = serde_json::from_value(resp).unwrap();
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Get initial testnet accounts from sequencer.
|
||||
pub async fn get_initial_testnet_accounts(
|
||||
&self,
|
||||
) -> Result<Vec<GetInitialTestnetAccountsResponse>, SequencerClientError> {
|
||||
let acc_req = GetInitialTestnetAccountsRequest {};
|
||||
|
||||
let req = serde_json::to_value(acc_req).unwrap();
|
||||
|
||||
let resp = self
|
||||
.call_method_with_payload("get_initial_testnet_accounts", req)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let resp_deser = serde_json::from_value(resp).unwrap();
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Get proof for commitment.
|
||||
pub async fn get_proof_for_commitment(
|
||||
&self,
|
||||
commitment: nssa_core::Commitment,
|
||||
) -> Result<Option<nssa_core::MembershipProof>, SequencerClientError> {
|
||||
let acc_req = GetProofForCommitmentRequest { commitment };
|
||||
|
||||
let req = serde_json::to_value(acc_req).unwrap();
|
||||
|
||||
let resp = self
|
||||
.call_method_with_payload("get_proof_for_commitment", req)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let resp_deser = serde_json::from_value::<GetProofForCommitmentResponse>(resp)
|
||||
.unwrap()
|
||||
.membership_proof;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
pub async fn send_tx_program(
|
||||
&self,
|
||||
transaction: nssa::ProgramDeploymentTransaction,
|
||||
) -> Result<SendTxResponse, SequencerClientError> {
|
||||
let transaction = NSSATransaction::ProgramDeployment(transaction);
|
||||
|
||||
let tx_req = SendTxRequest {
|
||||
transaction: borsh::to_vec(&transaction).unwrap(),
|
||||
};
|
||||
|
||||
let req = serde_json::to_value(tx_req)?;
|
||||
|
||||
let resp = self.call_method_with_payload("send_tx", req).await?;
|
||||
|
||||
let resp_deser = serde_json::from_value(resp)?;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
|
||||
/// Get Ids of the programs used by the node.
|
||||
pub async fn get_program_ids(
|
||||
&self,
|
||||
) -> Result<HashMap<String, ProgramId>, SequencerClientError> {
|
||||
let acc_req = GetProgramIdsRequest {};
|
||||
|
||||
let req = serde_json::to_value(acc_req).unwrap();
|
||||
|
||||
let resp = self
|
||||
.call_method_with_payload("get_program_ids", req)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let resp_deser = serde_json::from_value::<GetProgramIdsResponse>(resp)
|
||||
.unwrap()
|
||||
.program_ids;
|
||||
|
||||
Ok(resp_deser)
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,18 @@ pub enum NSSATransaction {
|
||||
ProgramDeployment(nssa::ProgramDeploymentTransaction),
|
||||
}
|
||||
|
||||
impl Serialize for NSSATransaction {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
crate::borsh_base64::serialize(self, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NSSATransaction {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
crate::borsh_base64::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl NSSATransaction {
|
||||
#[must_use]
|
||||
pub fn hash(&self) -> HashType {
|
||||
@ -87,7 +99,7 @@ impl From<nssa::ProgramDeploymentTransaction> for NSSATransaction {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize,
|
||||
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
||||
)]
|
||||
pub enum TxKind {
|
||||
Public,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
{
|
||||
"home": "/var/lib/sequencer_runner",
|
||||
"override_rust_log": null,
|
||||
"home": "/var/lib/sequencer_service",
|
||||
"genesis_id": 1,
|
||||
"is_genesis_random": true,
|
||||
"max_num_tx_in_block": 20,
|
||||
@ -8,7 +7,6 @@
|
||||
"mempool_max_size": 10000,
|
||||
"block_create_timeout": "10s",
|
||||
"retry_pending_blocks_timeout": "7s",
|
||||
"port": 3040,
|
||||
"bedrock_config": {
|
||||
"backoff": {
|
||||
"start_delay": "100ms",
|
||||
|
||||
@ -7,12 +7,12 @@ services:
|
||||
environment:
|
||||
- RUST_LOG=error
|
||||
|
||||
sequencer_runner:
|
||||
sequencer_service:
|
||||
depends_on:
|
||||
- logos-blockchain-node-0
|
||||
- indexer_service
|
||||
volumes: !override
|
||||
- ./configs/docker-all-in-one/sequencer:/etc/sequencer_runner
|
||||
- ./configs/docker-all-in-one/sequencer:/etc/sequencer_service
|
||||
|
||||
indexer_service:
|
||||
depends_on:
|
||||
|
||||
@ -6,7 +6,7 @@ include:
|
||||
- path:
|
||||
bedrock/docker-compose.yml
|
||||
- path:
|
||||
sequencer_runner/docker-compose.yml
|
||||
sequencer/service/docker-compose.yml
|
||||
- path:
|
||||
indexer/service/docker-compose.yml
|
||||
- path:
|
||||
|
||||
@ -8,8 +8,10 @@ license = { workspace = true }
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
common.workspace = true
|
||||
nssa.workspace = true
|
||||
nssa_core.workspace = true
|
||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||
wallet.workspace = true
|
||||
|
||||
tokio = { workspace = true, features = ["macros"] }
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::WalletCore;
|
||||
|
||||
// Before running this example, compile the `hello_world.rs` guest program with:
|
||||
@ -58,7 +60,7 @@ async fn main() {
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_tx_public(tx)
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::WalletCore;
|
||||
|
||||
// Before running this example, compile the `simple_tail_call.rs` guest program with:
|
||||
@ -54,7 +56,7 @@ async fn main() {
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_tx_public(tx)
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use nssa_core::account::Nonce;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::WalletCore;
|
||||
|
||||
// Before running this example, compile the `hello_world_with_authorization.rs` guest program with:
|
||||
@ -63,13 +64,7 @@ async fn main() {
|
||||
.await
|
||||
.expect("Node should be reachable to query account data");
|
||||
let signing_keys = [signing_key];
|
||||
let message = Message::try_new(
|
||||
program.id(),
|
||||
vec![account_id],
|
||||
nonces.iter().map(|x| Nonce(*x)).collect(),
|
||||
greeting,
|
||||
)
|
||||
.unwrap();
|
||||
let message = Message::try_new(program.id(), vec![account_id], nonces, greeting).unwrap();
|
||||
// Pass the signing key to sign the message. This will be used by the node
|
||||
// to flag the pre_state as `is_authorized` when executing the program
|
||||
let witness_set = WitnessSet::for_message(&message, &signing_keys);
|
||||
@ -78,7 +73,7 @@ async fn main() {
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_tx_public(tx)
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -3,12 +3,14 @@
|
||||
reason = "This is an example program, it's fine to print to stdout"
|
||||
)]
|
||||
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use nssa_core::program::PdaSeed;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::WalletCore;
|
||||
|
||||
// Before running this example, compile the `simple_tail_call.rs` guest program with:
|
||||
@ -56,7 +58,7 @@ async fn main() {
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_tx_public(tx)
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{PublicTransaction, program::Program, public_transaction};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::{PrivacyPreservingAccount, WalletCore};
|
||||
|
||||
// Before running this example, compile the `hello_world_with_move_function.rs` guest program with:
|
||||
@ -87,7 +89,7 @@ async fn main() {
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_tx_public(tx)
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -126,7 +128,7 @@ async fn main() {
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_tx_public(tx)
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -41,12 +41,12 @@ pub async fn search(query: String) -> Result<SearchResults, ServerFnError> {
|
||||
// Try as hash
|
||||
if let Ok(hash) = HashType::from_str(&query) {
|
||||
// Try as block hash
|
||||
if let Ok(block) = client.get_block_by_hash(hash).await {
|
||||
if let Ok(Some(block)) = client.get_block_by_hash(hash).await {
|
||||
blocks.push(block);
|
||||
}
|
||||
|
||||
// Try as transaction hash
|
||||
if let Ok(tx) = client.get_transaction(hash).await {
|
||||
if let Ok(Some(tx)) = client.get_transaction(hash).await {
|
||||
transactions.push(tx);
|
||||
}
|
||||
}
|
||||
@ -60,7 +60,7 @@ pub async fn search(query: String) -> Result<SearchResults, ServerFnError> {
|
||||
|
||||
// Try as block ID
|
||||
if let Ok(block_id) = query.parse::<u64>()
|
||||
&& let Ok(block) = client.get_block_by_id(block_id).await
|
||||
&& let Ok(Some(block)) = client.get_block_by_id(block_id).await
|
||||
{
|
||||
blocks.push(block);
|
||||
}
|
||||
@ -81,6 +81,7 @@ pub async fn get_block_by_id(block_id: BlockId) -> Result<Block, ServerFnError>
|
||||
.get_block_by_id(block_id)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
.and_then(|opt| opt.ok_or_else(|| ServerFnError::ServerError("Block not found".to_owned())))
|
||||
}
|
||||
|
||||
/// Get latest block ID
|
||||
@ -103,6 +104,7 @@ pub async fn get_block_by_hash(block_hash: HashType) -> Result<Block, ServerFnEr
|
||||
.get_block_by_hash(block_hash)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
.and_then(|opt| opt.ok_or_else(|| ServerFnError::ServerError("Block not found".to_owned())))
|
||||
}
|
||||
|
||||
/// Get transaction by hash
|
||||
@ -114,6 +116,9 @@ pub async fn get_transaction(tx_hash: HashType) -> Result<Transaction, ServerFnE
|
||||
.get_transaction(tx_hash)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
.and_then(|opt| {
|
||||
opt.ok_or_else(|| ServerFnError::ServerError("Transaction not found".to_owned()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get blocks with pagination
|
||||
|
||||
@ -84,7 +84,7 @@ pub fn TransactionPage() -> impl IntoView {
|
||||
} = witness_set;
|
||||
|
||||
let program_id_str = program_id.to_string();
|
||||
let proof_len = proof.0.len();
|
||||
let proof_len = proof.map_or(0, |p| p.0.len());
|
||||
let signatures_count = signatures_and_public_keys.len();
|
||||
|
||||
view! {
|
||||
@ -183,7 +183,7 @@ pub fn TransactionPage() -> impl IntoView {
|
||||
proof,
|
||||
} = witness_set;
|
||||
|
||||
let proof_len = proof.0.len();
|
||||
let proof_len = proof.map_or(0, |p| p.0.len());
|
||||
view! {
|
||||
<div class="transaction-details">
|
||||
<h2>"Privacy-Preserving Transaction Details"</h2>
|
||||
|
||||
@ -46,7 +46,7 @@ impl IndexerStore {
|
||||
Ok(self.dbio.get_meta_last_block_in_db()?)
|
||||
}
|
||||
|
||||
pub fn get_block_at_id(&self, id: u64) -> Result<Block> {
|
||||
pub fn get_block_at_id(&self, id: u64) -> Result<Option<Block>> {
|
||||
Ok(self.dbio.get_block(id)?)
|
||||
}
|
||||
|
||||
@ -54,20 +54,25 @@ impl IndexerStore {
|
||||
Ok(self.dbio.get_block_batch(before, limit)?)
|
||||
}
|
||||
|
||||
pub fn get_transaction_by_hash(&self, tx_hash: [u8; 32]) -> Result<NSSATransaction> {
|
||||
let block = self.get_block_at_id(self.dbio.get_block_id_by_tx_hash(tx_hash)?)?;
|
||||
let transaction = block
|
||||
pub fn get_transaction_by_hash(&self, tx_hash: [u8; 32]) -> Result<Option<NSSATransaction>> {
|
||||
let Some(block_id) = self.dbio.get_block_id_by_tx_hash(tx_hash)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(block) = self.get_block_at_id(block_id)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(block
|
||||
.body
|
||||
.transactions
|
||||
.iter()
|
||||
.find(|enc_tx| enc_tx.hash().0 == tx_hash)
|
||||
.ok_or_else(|| anyhow::anyhow!("Transaction not found in DB"))?;
|
||||
|
||||
Ok(transaction.clone())
|
||||
.into_iter()
|
||||
.find(|enc_tx| enc_tx.hash().0 == tx_hash))
|
||||
}
|
||||
|
||||
pub fn get_block_by_hash(&self, hash: [u8; 32]) -> Result<Block> {
|
||||
self.get_block_at_id(self.dbio.get_block_id_by_hash(hash)?)
|
||||
pub fn get_block_by_hash(&self, hash: [u8; 32]) -> Result<Option<Block>> {
|
||||
let Some(id) = self.dbio.get_block_id_by_hash(hash)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.get_block_at_id(id)
|
||||
}
|
||||
|
||||
pub fn get_transactions_by_account(
|
||||
@ -171,7 +176,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let block = storage.get_block_at_id(1).unwrap();
|
||||
let block = storage.get_block_at_id(1).unwrap().unwrap();
|
||||
let final_id = storage.get_last_block_id().unwrap();
|
||||
|
||||
assert_eq!(block.header.hash, genesis_block().header.hash);
|
||||
|
||||
@ -21,7 +21,6 @@ log.workspace = true
|
||||
jsonrpsee.workspace = true
|
||||
serde_json.workspace = true
|
||||
futures.workspace = true
|
||||
async-trait = "0.1.89"
|
||||
arc-swap = "1.8.1"
|
||||
|
||||
[features]
|
||||
|
||||
@ -359,12 +359,16 @@ impl From<ProgramDeploymentMessage> for nssa::program_deployment_transaction::Me
|
||||
// WitnessSet conversions
|
||||
// ============================================================================
|
||||
|
||||
impl TryFrom<nssa::public_transaction::WitnessSet> for WitnessSet {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(_value: nssa::public_transaction::WitnessSet) -> Result<Self, Self::Error> {
|
||||
// Public transaction witness sets don't have proofs, so we can't convert them directly
|
||||
Err(())
|
||||
impl From<nssa::public_transaction::WitnessSet> for WitnessSet {
|
||||
fn from(value: nssa::public_transaction::WitnessSet) -> Self {
|
||||
Self {
|
||||
signatures_and_public_keys: value
|
||||
.signatures_and_public_keys()
|
||||
.iter()
|
||||
.map(|(sig, pk)| (sig.clone().into(), pk.clone().into()))
|
||||
.collect(),
|
||||
proof: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,7 +380,7 @@ impl From<nssa::privacy_preserving_transaction::witness_set::WitnessSet> for Wit
|
||||
.into_iter()
|
||||
.map(|(sig, pk)| (sig.into(), pk.into()))
|
||||
.collect(),
|
||||
proof: proof.into(),
|
||||
proof: Some(proof.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -396,7 +400,9 @@ impl TryFrom<WitnessSet> for nssa::privacy_preserving_transaction::witness_set::
|
||||
|
||||
Ok(Self::from_raw_parts(
|
||||
signatures_and_public_keys,
|
||||
proof.into(),
|
||||
proof
|
||||
.map(Into::into)
|
||||
.ok_or_else(|| nssa::error::NssaError::InvalidInput("Missing proof".to_owned()))?,
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -416,14 +422,7 @@ impl From<nssa::PublicTransaction> for PublicTransaction {
|
||||
Self {
|
||||
hash,
|
||||
message: message.into(),
|
||||
witness_set: WitnessSet {
|
||||
signatures_and_public_keys: witness_set
|
||||
.signatures_and_public_keys()
|
||||
.iter()
|
||||
.map(|(sig, pk)| (sig.clone().into(), pk.clone().into()))
|
||||
.collect(),
|
||||
proof: Proof(vec![]), // Public transactions don't have proofs
|
||||
},
|
||||
witness_set: witness_set.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,7 +240,7 @@ pub struct PrivacyPreservingMessage {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct WitnessSet {
|
||||
pub signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
||||
pub proof: Proof,
|
||||
pub proof: Option<Proof>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
||||
|
||||
@ -30,16 +30,22 @@ pub trait Rpc {
|
||||
async fn get_last_finalized_block_id(&self) -> Result<BlockId, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getBlockById")]
|
||||
async fn get_block_by_id(&self, block_id: BlockId) -> Result<Block, ErrorObjectOwned>;
|
||||
async fn get_block_by_id(&self, block_id: BlockId) -> Result<Option<Block>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getBlockByHash")]
|
||||
async fn get_block_by_hash(&self, block_hash: HashType) -> Result<Block, ErrorObjectOwned>;
|
||||
async fn get_block_by_hash(
|
||||
&self,
|
||||
block_hash: HashType,
|
||||
) -> Result<Option<Block>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getAccount")]
|
||||
async fn get_account(&self, account_id: AccountId) -> Result<Account, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getTransaction")]
|
||||
async fn get_transaction(&self, tx_hash: HashType) -> Result<Transaction, ErrorObjectOwned>;
|
||||
async fn get_transaction(
|
||||
&self,
|
||||
tx_hash: HashType,
|
||||
) -> Result<Option<Transaction>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getBlocks")]
|
||||
async fn get_blocks(
|
||||
|
||||
@ -3,7 +3,7 @@ use std::net::SocketAddr;
|
||||
use anyhow::{Context as _, Result};
|
||||
pub use indexer_core::config::*;
|
||||
use indexer_service_rpc::RpcServer as _;
|
||||
use jsonrpsee::server::Server;
|
||||
use jsonrpsee::server::{Server, ServerHandle};
|
||||
use log::{error, info};
|
||||
|
||||
pub mod service;
|
||||
@ -13,10 +13,11 @@ pub mod mock_service;
|
||||
|
||||
pub struct IndexerHandle {
|
||||
addr: SocketAddr,
|
||||
server_handle: Option<jsonrpsee::server::ServerHandle>,
|
||||
/// Option because of `Drop` which forbids to simply move out of `self` in `stopped()`.
|
||||
server_handle: Option<ServerHandle>,
|
||||
}
|
||||
impl IndexerHandle {
|
||||
const fn new(addr: SocketAddr, server_handle: jsonrpsee::server::ServerHandle) -> Self {
|
||||
const fn new(addr: SocketAddr, server_handle: ServerHandle) -> Self {
|
||||
Self {
|
||||
addr,
|
||||
server_handle: Some(server_handle),
|
||||
@ -28,6 +29,7 @@ impl IndexerHandle {
|
||||
self.addr
|
||||
}
|
||||
|
||||
/// Wait for all Indexer tasks to stop.
|
||||
pub async fn stopped(mut self) {
|
||||
let handle = self
|
||||
.server_handle
|
||||
@ -37,15 +39,11 @@ impl IndexerHandle {
|
||||
handle.stopped().await;
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::redundant_closure_for_method_calls,
|
||||
reason = "Clippy suggested path jsonrpsee::jsonrpsee_server::ServerHandle is not accessible"
|
||||
)]
|
||||
#[must_use]
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
pub fn is_healthy(&self) -> bool {
|
||||
self.server_handle
|
||||
.as_ref()
|
||||
.is_none_or(|handle| handle.is_stopped())
|
||||
.is_some_and(|handle| !handle.is_stopped())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,10 @@ use indexer_service_protocol::{
|
||||
ProgramDeploymentTransaction, ProgramId, PublicMessage, PublicTransaction, Signature,
|
||||
Transaction, WitnessSet,
|
||||
};
|
||||
use jsonrpsee::{core::SubscriptionResult, types::ErrorObjectOwned};
|
||||
use jsonrpsee::{
|
||||
core::{SubscriptionResult, async_trait},
|
||||
types::ErrorObjectOwned,
|
||||
};
|
||||
|
||||
/// A mock implementation of the `IndexerService` RPC for testing purposes.
|
||||
pub struct MockIndexerService {
|
||||
@ -92,7 +95,7 @@ impl MockIndexerService {
|
||||
},
|
||||
witness_set: WitnessSet {
|
||||
signatures_and_public_keys: vec![],
|
||||
proof: indexer_service_protocol::Proof(vec![0; 32]),
|
||||
proof: None,
|
||||
},
|
||||
}),
|
||||
// PrivacyPreserving transactions
|
||||
@ -124,7 +127,7 @@ impl MockIndexerService {
|
||||
},
|
||||
witness_set: WitnessSet {
|
||||
signatures_and_public_keys: vec![],
|
||||
proof: indexer_service_protocol::Proof(vec![0; 32]),
|
||||
proof: Some(indexer_service_protocol::Proof(vec![0; 32])),
|
||||
},
|
||||
}),
|
||||
// ProgramDeployment transactions (rare)
|
||||
@ -171,7 +174,7 @@ impl MockIndexerService {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[async_trait]
|
||||
impl indexer_service_rpc::RpcServer for MockIndexerService {
|
||||
async fn subscribe_to_finalized_blocks(
|
||||
&self,
|
||||
@ -198,26 +201,23 @@ impl indexer_service_rpc::RpcServer for MockIndexerService {
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_block_by_id(&self, block_id: BlockId) -> Result<Block, ErrorObjectOwned> {
|
||||
self.blocks
|
||||
async fn get_block_by_id(&self, block_id: BlockId) -> Result<Option<Block>, ErrorObjectOwned> {
|
||||
Ok(self
|
||||
.blocks
|
||||
.iter()
|
||||
.find(|b| b.header.block_id == block_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
ErrorObjectOwned::owned(
|
||||
-32001,
|
||||
format!("Block with ID {block_id} not found"),
|
||||
None::<()>,
|
||||
)
|
||||
})
|
||||
.cloned())
|
||||
}
|
||||
|
||||
async fn get_block_by_hash(&self, block_hash: HashType) -> Result<Block, ErrorObjectOwned> {
|
||||
self.blocks
|
||||
async fn get_block_by_hash(
|
||||
&self,
|
||||
block_hash: HashType,
|
||||
) -> Result<Option<Block>, ErrorObjectOwned> {
|
||||
Ok(self
|
||||
.blocks
|
||||
.iter()
|
||||
.find(|b| b.header.hash == block_hash)
|
||||
.cloned()
|
||||
.ok_or_else(|| ErrorObjectOwned::owned(-32001, "Block with hash not found", None::<()>))
|
||||
.cloned())
|
||||
}
|
||||
|
||||
async fn get_account(&self, account_id: AccountId) -> Result<Account, ErrorObjectOwned> {
|
||||
@ -227,11 +227,11 @@ impl indexer_service_rpc::RpcServer for MockIndexerService {
|
||||
.ok_or_else(|| ErrorObjectOwned::owned(-32001, "Account not found", None::<()>))
|
||||
}
|
||||
|
||||
async fn get_transaction(&self, tx_hash: HashType) -> Result<Transaction, ErrorObjectOwned> {
|
||||
self.transactions
|
||||
.get(&tx_hash)
|
||||
.map(|(tx, _)| tx.clone())
|
||||
.ok_or_else(|| ErrorObjectOwned::owned(-32001, "Transaction not found", None::<()>))
|
||||
async fn get_transaction(
|
||||
&self,
|
||||
tx_hash: HashType,
|
||||
) -> Result<Option<Transaction>, ErrorObjectOwned> {
|
||||
Ok(self.transactions.get(&tx_hash).map(|(tx, _)| tx.clone()))
|
||||
}
|
||||
|
||||
async fn get_blocks(
|
||||
|
||||
@ -7,7 +7,7 @@ use indexer_core::{IndexerCore, config::IndexerConfig};
|
||||
use indexer_service_protocol::{Account, AccountId, Block, BlockId, HashType, Transaction};
|
||||
use jsonrpsee::{
|
||||
SubscriptionSink,
|
||||
core::{Serialize, SubscriptionResult},
|
||||
core::{Serialize, SubscriptionResult, async_trait},
|
||||
types::{ErrorCode, ErrorObject, ErrorObjectOwned},
|
||||
};
|
||||
use log::{debug, error, info, warn};
|
||||
@ -30,7 +30,7 @@ impl IndexerService {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[async_trait]
|
||||
impl indexer_service_rpc::RpcServer for IndexerService {
|
||||
async fn subscribe_to_finalized_blocks(
|
||||
&self,
|
||||
@ -52,22 +52,25 @@ impl indexer_service_rpc::RpcServer for IndexerService {
|
||||
self.indexer.store.get_last_block_id().map_err(db_error)
|
||||
}
|
||||
|
||||
async fn get_block_by_id(&self, block_id: BlockId) -> Result<Block, ErrorObjectOwned> {
|
||||
async fn get_block_by_id(&self, block_id: BlockId) -> Result<Option<Block>, ErrorObjectOwned> {
|
||||
Ok(self
|
||||
.indexer
|
||||
.store
|
||||
.get_block_at_id(block_id)
|
||||
.map_err(db_error)?
|
||||
.into())
|
||||
.map(Into::into))
|
||||
}
|
||||
|
||||
async fn get_block_by_hash(&self, block_hash: HashType) -> Result<Block, ErrorObjectOwned> {
|
||||
async fn get_block_by_hash(
|
||||
&self,
|
||||
block_hash: HashType,
|
||||
) -> Result<Option<Block>, ErrorObjectOwned> {
|
||||
Ok(self
|
||||
.indexer
|
||||
.store
|
||||
.get_block_by_hash(block_hash.0)
|
||||
.map_err(db_error)?
|
||||
.into())
|
||||
.map(Into::into))
|
||||
}
|
||||
|
||||
async fn get_account(&self, account_id: AccountId) -> Result<Account, ErrorObjectOwned> {
|
||||
@ -80,13 +83,16 @@ impl indexer_service_rpc::RpcServer for IndexerService {
|
||||
.into())
|
||||
}
|
||||
|
||||
async fn get_transaction(&self, tx_hash: HashType) -> Result<Transaction, ErrorObjectOwned> {
|
||||
async fn get_transaction(
|
||||
&self,
|
||||
tx_hash: HashType,
|
||||
) -> Result<Option<Transaction>, ErrorObjectOwned> {
|
||||
Ok(self
|
||||
.indexer
|
||||
.store
|
||||
.get_transaction_by_hash(tx_hash.0)
|
||||
.map_err(db_error)?
|
||||
.into())
|
||||
.map(Into::into))
|
||||
}
|
||||
|
||||
async fn get_blocks(
|
||||
|
||||
@ -11,7 +11,7 @@ workspace = true
|
||||
nssa_core = { workspace = true, features = ["host"] }
|
||||
nssa.workspace = true
|
||||
sequencer_core = { workspace = true, features = ["default", "testnet"] }
|
||||
sequencer_runner.workspace = true
|
||||
sequencer_service.workspace = true
|
||||
wallet.workspace = true
|
||||
common.workspace = true
|
||||
key_protocol.workspace = true
|
||||
@ -19,6 +19,7 @@ indexer_service.workspace = true
|
||||
serde_json.workspace = true
|
||||
token_core.workspace = true
|
||||
indexer_service_rpc.workspace = true
|
||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||
wallet-ffi.workspace = true
|
||||
|
||||
url.workspace = true
|
||||
@ -26,11 +27,9 @@ url.workspace = true
|
||||
anyhow.workspace = true
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
base64.workspace = true
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
hex.workspace = true
|
||||
tempfile.workspace = true
|
||||
borsh.workspace = true
|
||||
bytesize.workspace = true
|
||||
futures.workspace = true
|
||||
testcontainers = { version = "0.27.0", features = ["docker-compose"] }
|
||||
|
||||
@ -204,7 +204,6 @@ pub fn sequencer_config(
|
||||
|
||||
Ok(SequencerConfig {
|
||||
home,
|
||||
override_rust_log: None,
|
||||
genesis_id: 1,
|
||||
is_genesis_random: true,
|
||||
max_num_tx_in_block,
|
||||
@ -212,7 +211,6 @@ pub fn sequencer_config(
|
||||
mempool_max_size,
|
||||
block_create_timeout,
|
||||
retry_pending_blocks_timeout: Duration::from_secs(120),
|
||||
port: 0,
|
||||
initial_accounts: initial_data.sequencer_initial_accounts(),
|
||||
initial_commitments: initial_data.sequencer_initial_commitments(),
|
||||
signing_key: [37; 32],
|
||||
@ -236,7 +234,6 @@ pub fn wallet_config(
|
||||
initial_data: &InitialData,
|
||||
) -> Result<WalletConfig> {
|
||||
Ok(WalletConfig {
|
||||
override_rust_log: None,
|
||||
sequencer_addr: addr_to_url(UrlProtocol::Http, sequencer_addr)
|
||||
.context("Failed to convert sequencer addr to URL")?,
|
||||
seq_poll_timeout: Duration::from_secs(30),
|
||||
|
||||
@ -3,15 +3,15 @@
|
||||
use std::{net::SocketAddr, path::PathBuf, sync::LazyLock};
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||||
use common::{HashType, sequencer_client::SequencerClient, transaction::NSSATransaction};
|
||||
use common::{HashType, transaction::NSSATransaction};
|
||||
use futures::FutureExt as _;
|
||||
use indexer_service::IndexerHandle;
|
||||
use log::{debug, error, warn};
|
||||
use nssa::{AccountId, PrivacyPreservingTransaction};
|
||||
use nssa_core::Commitment;
|
||||
use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait as _};
|
||||
use sequencer_runner::SequencerHandle;
|
||||
use sequencer_service::SequencerHandle;
|
||||
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
|
||||
use tempfile::TempDir;
|
||||
use testcontainers::compose::DockerCompose;
|
||||
use wallet::{WalletCore, config::WalletConfigOverrides};
|
||||
@ -38,7 +38,8 @@ pub struct TestContext {
|
||||
indexer_client: IndexerClient,
|
||||
wallet: WalletCore,
|
||||
wallet_password: String,
|
||||
sequencer_handle: SequencerHandle,
|
||||
/// Optional to move out value in Drop.
|
||||
sequencer_handle: Option<SequencerHandle>,
|
||||
indexer_handle: IndexerHandle,
|
||||
bedrock_compose: DockerCompose,
|
||||
_temp_indexer_dir: TempDir,
|
||||
@ -90,8 +91,9 @@ impl TestContext {
|
||||
.context("Failed to convert sequencer addr to URL")?;
|
||||
let indexer_url = config::addr_to_url(config::UrlProtocol::Ws, indexer_handle.addr())
|
||||
.context("Failed to convert indexer addr to URL")?;
|
||||
let sequencer_client =
|
||||
SequencerClient::new(sequencer_url).context("Failed to create sequencer client")?;
|
||||
let sequencer_client = SequencerClientBuilder::default()
|
||||
.build(sequencer_url)
|
||||
.context("Failed to create sequencer client")?;
|
||||
let indexer_client = IndexerClient::new(&indexer_url)
|
||||
.await
|
||||
.context("Failed to create indexer client")?;
|
||||
@ -102,7 +104,7 @@ impl TestContext {
|
||||
wallet,
|
||||
wallet_password,
|
||||
bedrock_compose,
|
||||
sequencer_handle,
|
||||
sequencer_handle: Some(sequencer_handle),
|
||||
indexer_handle,
|
||||
_temp_indexer_dir: temp_indexer_dir,
|
||||
_temp_sequencer_dir: temp_sequencer_dir,
|
||||
@ -229,7 +231,7 @@ impl TestContext {
|
||||
)
|
||||
.context("Failed to create Sequencer config")?;
|
||||
|
||||
let sequencer_handle = sequencer_runner::startup_sequencer(config).await?;
|
||||
let sequencer_handle = sequencer_service::run(config, 0).await?;
|
||||
|
||||
Ok((sequencer_handle, temp_sequencer_dir))
|
||||
}
|
||||
@ -333,18 +335,20 @@ impl Drop for TestContext {
|
||||
wallet_password: _,
|
||||
} = self;
|
||||
|
||||
if sequencer_handle.is_finished() {
|
||||
let Err(err) = self
|
||||
.sequencer_handle
|
||||
.run_forever()
|
||||
let sequencer_handle = sequencer_handle
|
||||
.take()
|
||||
.expect("Sequencer handle should be present in TestContext drop");
|
||||
if !sequencer_handle.is_healthy() {
|
||||
let Err(err) = sequencer_handle
|
||||
.failed()
|
||||
.now_or_never()
|
||||
.expect("Future is finished and should be ready");
|
||||
.expect("Sequencer handle should not be running");
|
||||
error!(
|
||||
"Sequencer handle has unexpectedly finished before TestContext drop with error: {err:#}"
|
||||
"Sequencer handle has unexpectedly stopped before TestContext drop with error: {err:#}"
|
||||
);
|
||||
}
|
||||
|
||||
if indexer_handle.is_stopped() {
|
||||
if !indexer_handle.is_healthy() {
|
||||
error!("Indexer handle has unexpectedly stopped before TestContext drop");
|
||||
}
|
||||
|
||||
@ -459,15 +463,8 @@ pub async fn fetch_privacy_preserving_tx(
|
||||
seq_client: &SequencerClient,
|
||||
tx_hash: HashType,
|
||||
) -> PrivacyPreservingTransaction {
|
||||
let transaction_encoded = seq_client
|
||||
.get_transaction_by_hash(tx_hash)
|
||||
.await
|
||||
.unwrap()
|
||||
.transaction
|
||||
.unwrap();
|
||||
let tx = seq_client.get_transaction(tx_hash).await.unwrap().unwrap();
|
||||
|
||||
let tx_bytes = BASE64.decode(transaction_encoded).unwrap();
|
||||
let tx = borsh::from_slice(&tx_bytes).unwrap();
|
||||
match tx {
|
||||
NSSATransaction::PrivacyPreserving(privacy_preserving_transaction) => {
|
||||
privacy_preserving_transaction
|
||||
@ -480,8 +477,10 @@ pub async fn verify_commitment_is_in_state(
|
||||
commitment: Commitment,
|
||||
seq_client: &SequencerClient,
|
||||
) -> bool {
|
||||
matches!(
|
||||
seq_client.get_proof_for_commitment(commitment).await,
|
||||
Ok(Some(_))
|
||||
)
|
||||
seq_client
|
||||
.get_proof_for_commitment(commitment)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ use anyhow::Result;
|
||||
use integration_tests::TestContext;
|
||||
use log::info;
|
||||
use nssa::program::Program;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tokio::test;
|
||||
use wallet::cli::{
|
||||
Command,
|
||||
@ -21,8 +22,7 @@ async fn get_existing_account() -> Result<()> {
|
||||
let account = ctx
|
||||
.sequencer_client()
|
||||
.get_account(ctx.existing_public_accounts()[0])
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
account.program_owner,
|
||||
|
||||
@ -9,6 +9,7 @@ use std::time::Duration;
|
||||
use anyhow::Result;
|
||||
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id};
|
||||
use log::info;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tokio::test;
|
||||
use wallet::cli::{
|
||||
Command, SubcommandReturnValue,
|
||||
@ -194,20 +195,14 @@ async fn amm_public() -> Result<()> {
|
||||
let user_holding_a_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_1)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_b_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_2)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_lp_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(user_holding_lp)
|
||||
.await?
|
||||
.account;
|
||||
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
@ -243,20 +238,14 @@ async fn amm_public() -> Result<()> {
|
||||
let user_holding_a_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_1)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_b_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_2)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_lp_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(user_holding_lp)
|
||||
.await?
|
||||
.account;
|
||||
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
@ -292,20 +281,14 @@ async fn amm_public() -> Result<()> {
|
||||
let user_holding_a_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_1)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_b_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_2)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_lp_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(user_holding_lp)
|
||||
.await?
|
||||
.account;
|
||||
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
@ -342,20 +325,14 @@ async fn amm_public() -> Result<()> {
|
||||
let user_holding_a_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_1)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_b_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_2)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_lp_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(user_holding_lp)
|
||||
.await?
|
||||
.account;
|
||||
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
@ -392,20 +369,14 @@ async fn amm_public() -> Result<()> {
|
||||
let user_holding_a_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_1)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_b_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_2)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
let user_holding_lp_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(user_holding_lp)
|
||||
.await?
|
||||
.account;
|
||||
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
|
||||
|
||||
assert_eq!(
|
||||
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
|
||||
|
||||
@ -8,6 +8,7 @@ use integration_tests::{
|
||||
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::cli::{
|
||||
Command, SubcommandReturnValue,
|
||||
@ -135,7 +136,7 @@ async fn deshielded_transfer_to_public_account() -> Result<()> {
|
||||
let acc_2_balance = ctx.sequencer_client().get_account_balance(to).await?;
|
||||
|
||||
assert_eq!(from_acc.balance, 9900);
|
||||
assert_eq!(acc_2_balance.balance, 20100);
|
||||
assert_eq!(acc_2_balance, 20100);
|
||||
|
||||
info!("Successfully deshielded transfer to public account");
|
||||
|
||||
@ -245,7 +246,7 @@ async fn shielded_transfer_to_owned_private_account() -> Result<()> {
|
||||
|
||||
let acc_from_balance = ctx.sequencer_client().get_account_balance(from).await?;
|
||||
|
||||
assert_eq!(acc_from_balance.balance, 9900);
|
||||
assert_eq!(acc_from_balance, 9900);
|
||||
assert_eq!(acc_to.balance, 20100);
|
||||
|
||||
info!("Successfully shielded transfer to owned private account");
|
||||
@ -290,7 +291,7 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> {
|
||||
.await
|
||||
);
|
||||
|
||||
assert_eq!(acc_1_balance.balance, 9900);
|
||||
assert_eq!(acc_1_balance, 9900);
|
||||
|
||||
info!("Successfully shielded transfer to foreign account");
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ use anyhow::Result;
|
||||
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id};
|
||||
use log::info;
|
||||
use nssa::program::Program;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tokio::test;
|
||||
use wallet::cli::{
|
||||
Command, SubcommandReturnValue,
|
||||
@ -41,8 +42,8 @@ async fn successful_transfer_to_existing_account() -> Result<()> {
|
||||
info!("Balance of sender: {acc_1_balance:#?}");
|
||||
info!("Balance of receiver: {acc_2_balance:#?}");
|
||||
|
||||
assert_eq!(acc_1_balance.balance, 9900);
|
||||
assert_eq!(acc_2_balance.balance, 20100);
|
||||
assert_eq!(acc_1_balance, 9900);
|
||||
assert_eq!(acc_2_balance, 20100);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -97,8 +98,8 @@ pub async fn successful_transfer_to_new_account() -> Result<()> {
|
||||
info!("Balance of sender: {acc_1_balance:#?}");
|
||||
info!("Balance of receiver: {acc_2_balance:#?}");
|
||||
|
||||
assert_eq!(acc_1_balance.balance, 9900);
|
||||
assert_eq!(acc_2_balance.balance, 100);
|
||||
assert_eq!(acc_1_balance, 9900);
|
||||
assert_eq!(acc_2_balance, 100);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -134,8 +135,8 @@ async fn failed_transfer_with_insufficient_balance() -> Result<()> {
|
||||
info!("Balance of sender: {acc_1_balance:#?}");
|
||||
info!("Balance of receiver: {acc_2_balance:#?}");
|
||||
|
||||
assert_eq!(acc_1_balance.balance, 10000);
|
||||
assert_eq!(acc_2_balance.balance, 20000);
|
||||
assert_eq!(acc_1_balance, 10000);
|
||||
assert_eq!(acc_2_balance, 20000);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -171,8 +172,8 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
|
||||
info!("Balance of sender: {acc_1_balance:#?}");
|
||||
info!("Balance of receiver: {acc_2_balance:#?}");
|
||||
|
||||
assert_eq!(acc_1_balance.balance, 9900);
|
||||
assert_eq!(acc_2_balance.balance, 20100);
|
||||
assert_eq!(acc_1_balance, 9900);
|
||||
assert_eq!(acc_2_balance, 20100);
|
||||
|
||||
info!("First TX Success!");
|
||||
|
||||
@ -203,8 +204,8 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
|
||||
info!("Balance of sender: {acc_1_balance:#?}");
|
||||
info!("Balance of receiver: {acc_2_balance:#?}");
|
||||
|
||||
assert_eq!(acc_1_balance.balance, 9800);
|
||||
assert_eq!(acc_2_balance.balance, 20200);
|
||||
assert_eq!(acc_1_balance, 9800);
|
||||
assert_eq!(acc_2_balance, 20200);
|
||||
|
||||
info!("Second TX Success!");
|
||||
|
||||
@ -230,11 +231,7 @@ async fn initialize_public_account() -> Result<()> {
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
info!("Checking correct execution");
|
||||
let account = ctx
|
||||
.sequencer_client()
|
||||
.get_account(account_id)
|
||||
.await?
|
||||
.account;
|
||||
let account = ctx.sequencer_client().get_account(account_id).await?;
|
||||
|
||||
assert_eq!(
|
||||
account.program_owner,
|
||||
|
||||
@ -8,11 +8,12 @@ use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use bytesize::ByteSize;
|
||||
use common::{block::HashableBlockData, transaction::NSSATransaction};
|
||||
use common::transaction::NSSATransaction;
|
||||
use integration_tests::{
|
||||
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, config::SequencerPartialConfig,
|
||||
};
|
||||
use nssa::program::Program;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tokio::test;
|
||||
|
||||
#[test]
|
||||
@ -36,7 +37,10 @@ async fn reject_oversized_transaction() -> Result<()> {
|
||||
let tx = nssa::ProgramDeploymentTransaction::new(message);
|
||||
|
||||
// Try to submit the transaction and expect an error
|
||||
let result = ctx.sequencer_client().send_tx_program(tx).await;
|
||||
let result = ctx
|
||||
.sequencer_client()
|
||||
.send_transaction(NSSATransaction::ProgramDeployment(tx))
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
result.is_err(),
|
||||
@ -74,7 +78,10 @@ async fn accept_transaction_within_limit() -> Result<()> {
|
||||
let tx = nssa::ProgramDeploymentTransaction::new(message);
|
||||
|
||||
// This should succeed
|
||||
let result = ctx.sequencer_client().send_tx_program(tx).await;
|
||||
let result = ctx
|
||||
.sequencer_client()
|
||||
.send_transaction(NSSATransaction::ProgramDeployment(tx))
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
@ -112,33 +119,38 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> {
|
||||
let burner_id = Program::new(burner_bytecode.clone())?.id();
|
||||
let chain_caller_id = Program::new(chain_caller_bytecode.clone())?.id();
|
||||
|
||||
let initial_block_height = ctx.sequencer_client().get_last_block().await?.last_block;
|
||||
let initial_block_height = ctx.sequencer_client().get_last_block_id().await?;
|
||||
|
||||
// Submit both program deployments
|
||||
ctx.sequencer_client()
|
||||
.send_tx_program(nssa::ProgramDeploymentTransaction::new(
|
||||
nssa::program_deployment_transaction::Message::new(burner_bytecode),
|
||||
.send_transaction(NSSATransaction::ProgramDeployment(
|
||||
nssa::ProgramDeploymentTransaction::new(
|
||||
nssa::program_deployment_transaction::Message::new(burner_bytecode),
|
||||
),
|
||||
))
|
||||
.await?;
|
||||
|
||||
ctx.sequencer_client()
|
||||
.send_tx_program(nssa::ProgramDeploymentTransaction::new(
|
||||
nssa::program_deployment_transaction::Message::new(chain_caller_bytecode),
|
||||
.send_transaction(NSSATransaction::ProgramDeployment(
|
||||
nssa::ProgramDeploymentTransaction::new(
|
||||
nssa::program_deployment_transaction::Message::new(chain_caller_bytecode),
|
||||
),
|
||||
))
|
||||
.await?;
|
||||
|
||||
// Wait for first block
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let block1_response = ctx
|
||||
let block1 = ctx
|
||||
.sequencer_client()
|
||||
.get_block(initial_block_height + 1)
|
||||
.await?;
|
||||
let block1: HashableBlockData = borsh::from_slice(&block1_response.block)?;
|
||||
.await?
|
||||
.unwrap();
|
||||
|
||||
// Check which program is in block 1
|
||||
let get_program_ids = |block: &HashableBlockData| -> Vec<nssa::ProgramId> {
|
||||
let get_program_ids = |block: &common::block::Block| -> Vec<nssa::ProgramId> {
|
||||
block
|
||||
.body
|
||||
.transactions
|
||||
.iter()
|
||||
.filter_map(|tx| {
|
||||
@ -168,11 +180,11 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> {
|
||||
// Wait for second block
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let block2_response = ctx
|
||||
let block2 = ctx
|
||||
.sequencer_client()
|
||||
.get_block(initial_block_height + 2)
|
||||
.await?;
|
||||
let block2: HashableBlockData = borsh::from_slice(&block2_response.block)?;
|
||||
.await?
|
||||
.unwrap();
|
||||
let block2_program_ids = get_program_ids(&block2);
|
||||
|
||||
// The other program should be in block 2
|
||||
|
||||
@ -22,12 +22,8 @@ async fn indexer_test_run() -> Result<()> {
|
||||
// RUN OBSERVATION
|
||||
tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await;
|
||||
|
||||
let last_block_seq = ctx
|
||||
.sequencer_client()
|
||||
.get_last_block()
|
||||
.await
|
||||
.unwrap()
|
||||
.last_block;
|
||||
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}");
|
||||
|
||||
@ -100,20 +96,22 @@ async fn indexer_state_consistency() -> Result<()> {
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
info!("Checking correct balance move");
|
||||
let acc_1_balance = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(ctx.existing_public_accounts()[0])
|
||||
.await?;
|
||||
let acc_2_balance = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(ctx.existing_public_accounts()[1])
|
||||
.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?;
|
||||
|
||||
info!("Balance of sender: {acc_1_balance:#?}");
|
||||
info!("Balance of receiver: {acc_2_balance:#?}");
|
||||
|
||||
assert_eq!(acc_1_balance.balance, 9900);
|
||||
assert_eq!(acc_2_balance.balance, 20100);
|
||||
assert_eq!(acc_1_balance, 9900);
|
||||
assert_eq!(acc_2_balance, 20100);
|
||||
|
||||
// WAIT
|
||||
info!("Waiting for indexer to parse blocks");
|
||||
@ -131,16 +129,16 @@ async fn indexer_state_consistency() -> Result<()> {
|
||||
.unwrap();
|
||||
|
||||
info!("Checking correct state transition");
|
||||
let acc1_seq_state = ctx
|
||||
.sequencer_client()
|
||||
.get_account(ctx.existing_public_accounts()[0])
|
||||
.await?
|
||||
.account;
|
||||
let acc2_seq_state = ctx
|
||||
.sequencer_client()
|
||||
.get_account(ctx.existing_public_accounts()[1])
|
||||
.await?
|
||||
.account;
|
||||
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());
|
||||
|
||||
@ -14,6 +14,7 @@ use integration_tests::{
|
||||
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
||||
use log::info;
|
||||
use nssa::{AccountId, program::Program};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tokio::test;
|
||||
use wallet::cli::{
|
||||
Command, SubcommandReturnValue,
|
||||
@ -305,8 +306,8 @@ async fn restore_keys_from_seed() -> Result<()> {
|
||||
.get_account_balance(to_account_id4)
|
||||
.await?;
|
||||
|
||||
assert_eq!(acc3.balance, 91); // 102 - 11
|
||||
assert_eq!(acc4.balance, 114); // 103 + 11
|
||||
assert_eq!(acc3, 91); // 102 - 11
|
||||
assert_eq!(acc4, 114); // 103 + 11
|
||||
|
||||
info!("Successfully restored keys and verified transactions");
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ use integration_tests::{
|
||||
format_public_account_id, verify_commitment_is_in_state,
|
||||
};
|
||||
use log::info;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tokio::test;
|
||||
use wallet::cli::{
|
||||
Command, SubcommandReturnValue,
|
||||
@ -46,8 +47,7 @@ async fn claim_pinata_to_uninitialized_public_account_fails_fast() -> Result<()>
|
||||
let pinata_balance_pre = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
let claim_result = wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
@ -70,8 +70,7 @@ async fn claim_pinata_to_uninitialized_public_account_fails_fast() -> Result<()>
|
||||
let pinata_balance_post = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
assert_eq!(pinata_balance_post, pinata_balance_pre);
|
||||
|
||||
@ -102,8 +101,7 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<()
|
||||
let pinata_balance_pre = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
let claim_result = wallet::cli::execute_subcommand(
|
||||
ctx.wallet_mut(),
|
||||
@ -126,8 +124,7 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<()
|
||||
let pinata_balance_post = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
assert_eq!(pinata_balance_post, pinata_balance_pre);
|
||||
|
||||
@ -146,8 +143,7 @@ async fn claim_pinata_to_existing_public_account() -> Result<()> {
|
||||
let pinata_balance_pre = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
@ -158,14 +154,12 @@ async fn claim_pinata_to_existing_public_account() -> Result<()> {
|
||||
let pinata_balance_post = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
let winner_balance_post = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(ctx.existing_public_accounts()[0])
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
|
||||
assert_eq!(winner_balance_post, 10000 + pinata_prize);
|
||||
@ -187,8 +181,7 @@ async fn claim_pinata_to_existing_private_account() -> Result<()> {
|
||||
let pinata_balance_pre = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = result else {
|
||||
@ -211,8 +204,7 @@ async fn claim_pinata_to_existing_private_account() -> Result<()> {
|
||||
let pinata_balance_post = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
|
||||
|
||||
@ -268,8 +260,7 @@ async fn claim_pinata_to_new_private_account() -> Result<()> {
|
||||
let pinata_balance_pre = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
@ -285,8 +276,7 @@ async fn claim_pinata_to_new_private_account() -> Result<()> {
|
||||
let pinata_balance_post = ctx
|
||||
.sequencer_client()
|
||||
.get_account_balance(PINATA_BASE58.parse().unwrap())
|
||||
.await?
|
||||
.balance;
|
||||
.await?;
|
||||
|
||||
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
|
||||
|
||||
|
||||
@ -6,11 +6,13 @@
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use common::transaction::NSSATransaction;
|
||||
use integration_tests::{
|
||||
NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
|
||||
};
|
||||
use log::info;
|
||||
use nssa::{AccountId, program::Program};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tokio::test;
|
||||
use wallet::cli::Command;
|
||||
|
||||
@ -47,18 +49,17 @@ async fn deploy_and_execute_program() -> Result<()> {
|
||||
)?;
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let transaction = nssa::PublicTransaction::new(message, witness_set);
|
||||
let _response = ctx.sequencer_client().send_tx_public(transaction).await?;
|
||||
let _response = ctx
|
||||
.sequencer_client()
|
||||
.send_transaction(NSSATransaction::Public(transaction))
|
||||
.await?;
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
// Waiting for long time as it may take some time for such a big transaction to be included in a
|
||||
// block
|
||||
tokio::time::sleep(Duration::from_secs(2 * TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
let post_state_account = ctx
|
||||
.sequencer_client()
|
||||
.get_account(account_id)
|
||||
.await?
|
||||
.account;
|
||||
let post_state_account = ctx.sequencer_client().get_account(account_id).await?;
|
||||
|
||||
assert_eq!(post_state_account.program_owner, data_changer.id());
|
||||
assert_eq!(post_state_account.balance, 0);
|
||||
|
||||
@ -14,6 +14,7 @@ use integration_tests::{
|
||||
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
||||
use log::info;
|
||||
use nssa::program::Program;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
use tokio::test;
|
||||
use wallet::cli::{
|
||||
@ -92,8 +93,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
let definition_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(definition_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(definition_acc.program_owner, Program::token().id());
|
||||
@ -110,8 +110,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
let supply_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(supply_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
// The account must be owned by the token program
|
||||
assert_eq!(supply_acc.program_owner, Program::token().id());
|
||||
@ -143,8 +142,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
let supply_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(supply_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
assert_eq!(supply_acc.program_owner, Program::token().id());
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
assert_eq!(
|
||||
@ -159,8 +157,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
let recipient_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
assert_eq!(recipient_acc.program_owner, Program::token().id());
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
assert_eq!(
|
||||
@ -188,8 +185,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
let definition_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(definition_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
@ -205,8 +201,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
let recipient_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
@ -236,8 +231,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
let definition_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(definition_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
@ -253,8 +247,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
|
||||
let recipient_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
@ -341,8 +334,7 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
|
||||
let definition_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(definition_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(definition_acc.program_owner, Program::token().id());
|
||||
@ -405,8 +397,7 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
|
||||
let definition_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(definition_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
@ -506,8 +497,7 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
let supply_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(supply_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
|
||||
assert_eq!(supply_acc.program_owner, Program::token().id());
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
@ -586,8 +576,7 @@ async fn create_token_with_private_definition() -> Result<()> {
|
||||
let recipient_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id_public)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
|
||||
assert_eq!(
|
||||
@ -882,8 +871,7 @@ async fn shielded_token_transfer() -> Result<()> {
|
||||
let supply_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(supply_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
@ -1026,8 +1014,7 @@ async fn deshielded_token_transfer() -> Result<()> {
|
||||
let recipient_acc = ctx
|
||||
.sequencer_client()
|
||||
.get_account(recipient_account_id)
|
||||
.await?
|
||||
.account;
|
||||
.await?;
|
||||
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
|
||||
assert_eq!(
|
||||
token_holding,
|
||||
|
||||
@ -13,6 +13,7 @@ use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::Result;
|
||||
use bytesize::ByteSize;
|
||||
use common::transaction::NSSATransaction;
|
||||
use integration_tests::{
|
||||
TestContext,
|
||||
config::{InitialData, SequencerPartialConfig},
|
||||
@ -30,6 +31,7 @@ use nssa_core::{
|
||||
account::{AccountWithMetadata, Nonce, data::Data},
|
||||
encryption::ViewingPublicKey,
|
||||
};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use tokio::test;
|
||||
|
||||
pub(crate) struct TpsTestManager {
|
||||
@ -153,10 +155,9 @@ pub async fn tps_test() -> Result<()> {
|
||||
for (i, tx) in txs.into_iter().enumerate() {
|
||||
let tx_hash = ctx
|
||||
.sequencer_client()
|
||||
.send_tx_public(tx)
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap()
|
||||
.tx_hash;
|
||||
.unwrap();
|
||||
info!("Sent tx {i}");
|
||||
tx_hashes.push(tx_hash);
|
||||
}
|
||||
@ -170,15 +171,13 @@ pub async fn tps_test() -> Result<()> {
|
||||
|
||||
let tx_obj = ctx
|
||||
.sequencer_client()
|
||||
.get_transaction_by_hash(*tx_hash)
|
||||
.get_transaction(*tx_hash)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
log::warn!("Failed to get transaction by hash {tx_hash} with error: {err:#?}");
|
||||
});
|
||||
|
||||
if let Ok(tx_obj) = tx_obj
|
||||
&& tx_obj.transaction.is_some()
|
||||
{
|
||||
if tx_obj.is_ok_and(|opt| opt.is_some()) {
|
||||
info!("Found tx {i} with hash {tx_hash}");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -19,10 +19,12 @@ serde.workspace = true
|
||||
k256.workspace = true
|
||||
sha2.workspace = true
|
||||
rand.workspace = true
|
||||
base58.workspace = true
|
||||
hex.workspace = true
|
||||
aes-gcm.workspace = true
|
||||
bip39.workspace = true
|
||||
hmac-sha512.workspace = true
|
||||
thiserror.workspace = true
|
||||
itertools.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
base58.workspace = true
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use common::sequencer_client::SequencerClient;
|
||||
use nssa::{Account, AccountId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::key_management::{
|
||||
@ -197,40 +197,6 @@ impl<N: KeyNode> KeyTree<N> {
|
||||
}
|
||||
|
||||
impl KeyTree<ChildKeysPrivate> {
|
||||
/// Cleanup of all non-initialized accounts in a private tree.
|
||||
///
|
||||
/// For given `depth` checks children to a tree such that their `ChainIndex::depth(&self) <
|
||||
/// depth`.
|
||||
///
|
||||
/// If account is default, removes them.
|
||||
///
|
||||
/// Chain must be parsed for accounts beforehand.
|
||||
///
|
||||
/// Fast, leaves gaps between accounts.
|
||||
pub fn cleanup_tree_remove_uninit_for_depth(&mut self, depth: u32) {
|
||||
let mut id_stack = vec![ChainIndex::root()];
|
||||
|
||||
while let Some(curr_id) = id_stack.pop() {
|
||||
if let Some(node) = self.key_map.get(&curr_id)
|
||||
&& node.value.1 == nssa::Account::default()
|
||||
&& curr_id != ChainIndex::root()
|
||||
{
|
||||
let addr = node.account_id();
|
||||
self.remove(addr);
|
||||
}
|
||||
|
||||
let mut next_id = curr_id.nth_child(0);
|
||||
|
||||
while (next_id.depth()) < depth {
|
||||
id_stack.push(next_id.clone());
|
||||
next_id = match next_id.next_in_line() {
|
||||
Some(id) => id,
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cleanup of non-initialized accounts in a private tree.
|
||||
///
|
||||
/// If account is default, removes them, stops at first non-default account.
|
||||
@ -259,56 +225,17 @@ impl KeyTree<ChildKeysPrivate> {
|
||||
}
|
||||
|
||||
impl KeyTree<ChildKeysPublic> {
|
||||
/// Cleanup of all non-initialized accounts in a public tree.
|
||||
///
|
||||
/// For given `depth` checks children to a tree such that their `ChainIndex::depth(&self) <
|
||||
/// depth`.
|
||||
///
|
||||
/// If account is default, removes them.
|
||||
///
|
||||
/// Fast, leaves gaps between accounts.
|
||||
pub async fn cleanup_tree_remove_ininit_for_depth(
|
||||
&mut self,
|
||||
depth: u32,
|
||||
client: Arc<SequencerClient>,
|
||||
) -> Result<()> {
|
||||
let mut id_stack = vec![ChainIndex::root()];
|
||||
|
||||
while let Some(curr_id) = id_stack.pop() {
|
||||
if let Some(node) = self.key_map.get(&curr_id) {
|
||||
let address = node.account_id();
|
||||
let node_acc = client.get_account(address).await?.account;
|
||||
|
||||
if node_acc == nssa::Account::default() && curr_id != ChainIndex::root() {
|
||||
self.remove(address);
|
||||
}
|
||||
}
|
||||
|
||||
let mut next_id = curr_id.nth_child(0);
|
||||
|
||||
while (next_id.depth()) < depth {
|
||||
id_stack.push(next_id.clone());
|
||||
next_id = match next_id.next_in_line() {
|
||||
Some(id) => id,
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cleanup of non-initialized accounts in a public tree.
|
||||
///
|
||||
/// If account is default, removes them, stops at first non-default account.
|
||||
///
|
||||
/// Walks through tree in lairs of same depth using `ChainIndex::chain_ids_at_depth()`.
|
||||
/// Walks through tree in layers of same depth using `ChainIndex::chain_ids_at_depth()`.
|
||||
///
|
||||
/// Slow, maintains tree consistency.
|
||||
pub async fn cleanup_tree_remove_uninit_layered(
|
||||
pub async fn cleanup_tree_remove_uninit_layered<F: Future<Output = Result<Account>>>(
|
||||
&mut self,
|
||||
depth: u32,
|
||||
client: Arc<SequencerClient>,
|
||||
get_account: impl Fn(AccountId) -> F,
|
||||
) -> Result<()> {
|
||||
let depth = usize::try_from(depth).expect("Depth is expected to fit in usize");
|
||||
'outer: for i in (1..depth).rev() {
|
||||
@ -316,7 +243,7 @@ impl KeyTree<ChildKeysPublic> {
|
||||
for id in ChainIndex::chain_ids_at_depth(i) {
|
||||
if let Some(node) = self.key_map.get(&id) {
|
||||
let address = node.account_id();
|
||||
let node_acc = client.get_account(address).await?.account;
|
||||
let node_acc = get_account(address).await?;
|
||||
|
||||
if node_acc == nssa::Account::default() {
|
||||
let addr = node.account_id();
|
||||
|
||||
@ -10,16 +10,16 @@ use sha2::{Digest as _, digest::FixedOutput as _};
|
||||
|
||||
const NSSA_ENTROPY_BYTES: [u8; 32] = [0; 32];
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Seed holder. Non-clonable to ensure that different holders use different seeds.
|
||||
/// Produces `TopSecretKeyHolder` objects.
|
||||
#[derive(Debug)]
|
||||
pub struct SeedHolder {
|
||||
// ToDo: Needs to be vec as serde derives is not implemented for [u8; 64]
|
||||
pub(crate) seed: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
/// Secret spending key object. Can produce `PrivateKeyHolder` objects.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SecretSpendingKey(pub(crate) [u8; 32]);
|
||||
|
||||
pub type ViewingSecretKey = Scalar;
|
||||
|
||||
@ -66,13 +66,13 @@ impl NSSAUserData {
|
||||
) -> Result<Self> {
|
||||
if !Self::valid_public_key_transaction_pairing_check(&default_accounts_keys) {
|
||||
anyhow::bail!(
|
||||
"Key transaction pairing check not satisfied, there is account_ids, which is not derived from keys"
|
||||
"Key transaction pairing check not satisfied, there are public account_ids, which are not derived from keys"
|
||||
);
|
||||
}
|
||||
|
||||
if !Self::valid_private_key_transaction_pairing_check(&default_accounts_key_chains) {
|
||||
anyhow::bail!(
|
||||
"Key transaction pairing check not satisfied, there is account_ids, which is not derived from keys"
|
||||
"Key transaction pairing check not satisfied, there are private account_ids, which are not derived from keys"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
risc0-zkvm.workspace = true
|
||||
serde.workspace = true
|
||||
serde_with.workspace = true
|
||||
sha2.workspace = true
|
||||
rand.workspace = true
|
||||
borsh.workspace = true
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
pub use private_key::PrivateKey;
|
||||
pub use public_key::PublicKey;
|
||||
@ -12,11 +14,27 @@ pub struct Signature {
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Signature {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Signature {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", hex::encode(self.value))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Signature {
|
||||
type Err = hex::FromHexError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut bytes = [0_u8; 64];
|
||||
hex::decode_to_slice(s, &mut bytes)?;
|
||||
Ok(Self { value: bytes })
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
#[must_use]
|
||||
pub fn new(key: &PrivateKey, message: &[u8]) -> Self {
|
||||
|
||||
@ -1,13 +1,37 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use rand::{Rng as _, rngs::OsRng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
|
||||
use crate::error::NssaError;
|
||||
|
||||
// TODO: Remove Debug, Clone, Serialize, Deserialize, PartialEq and Eq for security reasons
|
||||
// TODO: Implement Zeroize
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Clone, SerializeDisplay, DeserializeFromStr, PartialEq, Eq)]
|
||||
pub struct PrivateKey([u8; 32]);
|
||||
|
||||
impl std::fmt::Debug for PrivateKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PrivateKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", hex::encode(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PrivateKey {
|
||||
type Err = NssaError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut bytes = [0_u8; 32];
|
||||
hex::decode_to_slice(s, &mut bytes).map_err(|_err| NssaError::InvalidPrivateKey)?;
|
||||
Self::try_new(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivateKey {
|
||||
#[must_use]
|
||||
pub fn new_os_random() -> Self {
|
||||
|
||||
@ -1,19 +1,38 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use nssa_core::account::AccountId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
use sha2::{Digest as _, Sha256};
|
||||
|
||||
use crate::{PrivateKey, error::NssaError};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, BorshSerialize, Serialize, Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, BorshSerialize, SerializeDisplay, DeserializeFromStr)]
|
||||
pub struct PublicKey([u8; 32]);
|
||||
|
||||
impl std::fmt::Debug for PublicKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PublicKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", hex::encode(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PublicKey {
|
||||
type Err = NssaError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut bytes = [0_u8; 32];
|
||||
hex::decode_to_slice(s, &mut bytes)
|
||||
.map_err(|_err| NssaError::InvalidPublicKey(secp256k1::Error::InvalidPublicKey))?;
|
||||
Self::try_new(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl BorshDeserialize for PublicKey {
|
||||
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
|
||||
let mut buf = [0_u8; 32];
|
||||
|
||||
@ -8,12 +8,9 @@ license = { workspace = true }
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nssa = { workspace = true, optional = true, features = [
|
||||
"test-utils",
|
||||
], default-features = true }
|
||||
nssa_core.workspace = true
|
||||
token_core.workspace = true
|
||||
amm_core.workspace = true
|
||||
|
||||
[features]
|
||||
nssa = ["dep:nssa"]
|
||||
[dev-dependencies]
|
||||
nssa = { workspace = true, features = ["test-utils"] }
|
||||
|
||||
@ -4,7 +4,6 @@ use amm_core::{
|
||||
PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
|
||||
compute_pool_pda, compute_vault_pda, compute_vault_pda_seed,
|
||||
};
|
||||
#[cfg(feature = "nssa")]
|
||||
use nssa::{
|
||||
PrivateKey, PublicKey, PublicTransaction, V02State, program::Program, public_transaction,
|
||||
};
|
||||
@ -25,16 +24,15 @@ struct BalanceForTests;
|
||||
struct ChainedCallForTests;
|
||||
struct IdForTests;
|
||||
struct AccountWithMetadataForTests;
|
||||
#[cfg(feature = "nssa")]
|
||||
|
||||
struct PrivateKeysForTests;
|
||||
#[cfg(feature = "nssa")]
|
||||
|
||||
struct IdForExeTests;
|
||||
#[cfg(feature = "nssa")]
|
||||
|
||||
struct BalanceForExeTests;
|
||||
#[cfg(feature = "nssa")]
|
||||
|
||||
struct AccountsForExeTests;
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
impl PrivateKeysForTests {
|
||||
fn user_token_a_key() -> PrivateKey {
|
||||
PrivateKey::try_new([31; 32]).expect("Keys constructor expects valid private key")
|
||||
@ -1008,7 +1006,6 @@ impl AccountWithMetadataForTests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
impl BalanceForExeTests {
|
||||
fn user_token_a_holding_init() -> u128 {
|
||||
10_000
|
||||
@ -1172,7 +1169,6 @@ impl BalanceForExeTests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
impl IdForExeTests {
|
||||
fn pool_definition_id() -> AccountId {
|
||||
amm_core::compute_pool_pda(
|
||||
@ -1229,7 +1225,6 @@ impl IdForExeTests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
impl AccountsForExeTests {
|
||||
fn user_token_a_holding() -> Account {
|
||||
Account {
|
||||
@ -2641,7 +2636,6 @@ fn new_definition_lp_symmetric_amounts() {
|
||||
assert_eq!(chained_call_lp, expected_lp_call);
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
fn state_for_amm_tests() -> V02State {
|
||||
let initial_data = [];
|
||||
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]);
|
||||
@ -2685,7 +2679,6 @@ fn state_for_amm_tests() -> V02State {
|
||||
state
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
fn state_for_amm_tests_with_new_def() -> V02State {
|
||||
let initial_data = [];
|
||||
let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]);
|
||||
@ -2708,7 +2701,6 @@ fn state_for_amm_tests_with_new_def() -> V02State {
|
||||
state
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
#[test]
|
||||
fn simple_amm_remove() {
|
||||
let mut state = state_for_amm_tests();
|
||||
@ -2768,7 +2760,6 @@ fn simple_amm_remove() {
|
||||
assert_eq!(user_token_lp_post, expected_user_token_lp);
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
#[test]
|
||||
fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() {
|
||||
let mut state = state_for_amm_tests_with_new_def();
|
||||
@ -2849,7 +2840,6 @@ fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() {
|
||||
assert_eq!(user_token_lp_post, expected_user_token_lp);
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
#[test]
|
||||
fn simple_amm_new_definition_inactive_initialized_pool_init_user_lp() {
|
||||
let mut state = state_for_amm_tests_with_new_def();
|
||||
@ -2934,7 +2924,6 @@ fn simple_amm_new_definition_inactive_initialized_pool_init_user_lp() {
|
||||
assert_eq!(user_token_lp_post, expected_user_token_lp);
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
#[test]
|
||||
fn simple_amm_new_definition_uninitialized_pool() {
|
||||
let mut state = state_for_amm_tests_with_new_def();
|
||||
@ -3007,7 +2996,6 @@ fn simple_amm_new_definition_uninitialized_pool() {
|
||||
assert_eq!(user_token_lp_post, expected_user_token_lp);
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
#[test]
|
||||
fn simple_amm_add() {
|
||||
let mut state = state_for_amm_tests();
|
||||
@ -3070,7 +3058,6 @@ fn simple_amm_add() {
|
||||
assert_eq!(user_token_lp_post, expected_user_token_lp);
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
#[test]
|
||||
fn simple_amm_swap_1() {
|
||||
let mut state = state_for_amm_tests();
|
||||
@ -3122,7 +3109,6 @@ fn simple_amm_swap_1() {
|
||||
assert_eq!(user_token_b_post, expected_user_token_b);
|
||||
}
|
||||
|
||||
#[cfg(feature = "nssa")]
|
||||
#[test]
|
||||
fn simple_amm_swap_2() {
|
||||
let mut state = state_for_amm_tests();
|
||||
|
||||
@ -7,7 +7,7 @@ use common::{
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
use nssa::V02State;
|
||||
use storage::sequencer::RocksDBIO;
|
||||
use storage::{error::DbError, sequencer::RocksDBIO};
|
||||
|
||||
pub struct SequencerStore {
|
||||
dbio: RocksDBIO,
|
||||
@ -42,8 +42,8 @@ impl SequencerStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_block_at_id(&self, id: u64) -> Result<Block> {
|
||||
Ok(self.dbio.get_block(id)?)
|
||||
pub fn get_block_at_id(&self, id: u64) -> Result<Option<Block>, DbError> {
|
||||
self.dbio.get_block(id)
|
||||
}
|
||||
|
||||
pub fn delete_block_at_id(&mut self, block_id: u64) -> Result<()> {
|
||||
@ -56,16 +56,20 @@ impl SequencerStore {
|
||||
|
||||
/// Returns the transaction corresponding to the given hash, if it exists in the blockchain.
|
||||
pub fn get_transaction_by_hash(&self, hash: HashType) -> Option<NSSATransaction> {
|
||||
let block_id = self.tx_hash_to_block_map.get(&hash);
|
||||
let block = block_id.map(|&id| self.get_block_at_id(id));
|
||||
if let Some(Ok(block)) = block {
|
||||
for transaction in block.body.transactions {
|
||||
if transaction.hash() == hash {
|
||||
return Some(transaction);
|
||||
}
|
||||
let block_id = *self.tx_hash_to_block_map.get(&hash)?;
|
||||
let block = self
|
||||
.get_block_at_id(block_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("Block should be present since the hash is in the map");
|
||||
for transaction in block.body.transactions {
|
||||
if transaction.hash() == hash {
|
||||
return Some(transaction);
|
||||
}
|
||||
}
|
||||
None
|
||||
panic!(
|
||||
"Transaction hash was in the map but transaction was not found in the block. This should never happen."
|
||||
);
|
||||
}
|
||||
|
||||
pub fn latest_block_meta(&self) -> Result<BlockMeta> {
|
||||
@ -244,7 +248,7 @@ mod tests {
|
||||
node_store.update(&block, [1; 32], &dummy_state).unwrap();
|
||||
|
||||
// Verify initial status is Pending
|
||||
let retrieved_block = node_store.get_block_at_id(block_id).unwrap();
|
||||
let retrieved_block = node_store.get_block_at_id(block_id).unwrap().unwrap();
|
||||
assert!(matches!(
|
||||
retrieved_block.bedrock_status,
|
||||
common::block::BedrockStatus::Pending
|
||||
@ -254,7 +258,7 @@ mod tests {
|
||||
node_store.mark_block_as_finalized(block_id).unwrap();
|
||||
|
||||
// Verify status is now Finalized
|
||||
let finalized_block = node_store.get_block_at_id(block_id).unwrap();
|
||||
let finalized_block = node_store.get_block_at_id(block_id).unwrap().unwrap();
|
||||
assert!(matches!(
|
||||
finalized_block.bedrock_status,
|
||||
common::block::BedrockStatus::Finalized
|
||||
@ -22,8 +22,6 @@ use url::Url;
|
||||
pub struct SequencerConfig {
|
||||
/// Home dir of sequencer storage.
|
||||
pub home: PathBuf,
|
||||
/// Override rust log (env var logging level).
|
||||
pub override_rust_log: Option<String>,
|
||||
/// Genesis id.
|
||||
pub genesis_id: u64,
|
||||
/// If `True`, then adds random sequence of bytes to genesis block.
|
||||
@ -41,8 +39,6 @@ pub struct SequencerConfig {
|
||||
/// Interval in which pending blocks are retried.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub retry_pending_blocks_timeout: Duration,
|
||||
/// Port to listen.
|
||||
pub port: u16,
|
||||
/// List of initial accounts data.
|
||||
pub initial_accounts: Vec<AccountInitialData>,
|
||||
/// List of initial commitments.
|
||||
@ -15,6 +15,7 @@ use logos_blockchain_key_management_system_service::keys::{ED25519_SECRET_KEY_SI
|
||||
use mempool::{MemPool, MemPoolHandle};
|
||||
#[cfg(feature = "mock")]
|
||||
pub use mock::SequencerCoreWithMockClients;
|
||||
pub use storage::error::DbError;
|
||||
|
||||
use crate::{
|
||||
block_settlement_client::{BlockSettlementClient, BlockSettlementClientTrait, MsgId},
|
||||
@ -387,14 +388,12 @@ mod tests {
|
||||
|
||||
SequencerConfig {
|
||||
home,
|
||||
override_rust_log: Some("info".to_owned()),
|
||||
genesis_id: 1,
|
||||
is_genesis_random: false,
|
||||
max_num_tx_in_block: 10,
|
||||
max_block_size: bytesize::ByteSize::mib(1),
|
||||
mempool_max_size: 10000,
|
||||
block_create_timeout: Duration::from_secs(1),
|
||||
port: 8080,
|
||||
initial_accounts,
|
||||
initial_commitments: vec![],
|
||||
signing_key: *sequencer_sign_key_for_testing().value(),
|
||||
@ -475,7 +474,6 @@ mod tests {
|
||||
|
||||
assert_eq!(sequencer.chain_height, config.genesis_id);
|
||||
assert_eq!(sequencer.sequencer_config.max_num_tx_in_block, 10);
|
||||
assert_eq!(sequencer.sequencer_config.port, 8080);
|
||||
|
||||
let acc1_account_id = config.initial_accounts[0].account_id;
|
||||
let acc2_account_id = config.initial_accounts[1].account_id;
|
||||
@ -693,6 +691,7 @@ mod tests {
|
||||
let block = sequencer
|
||||
.store
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// Only one should be included in the block
|
||||
@ -720,6 +719,7 @@ mod tests {
|
||||
let block = sequencer
|
||||
.store
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(block.body.transactions, vec![tx.clone()]);
|
||||
|
||||
@ -731,6 +731,7 @@ mod tests {
|
||||
let block = sequencer
|
||||
.store
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(block.body.transactions.is_empty());
|
||||
}
|
||||
@ -765,6 +766,7 @@ mod tests {
|
||||
let block = sequencer
|
||||
.store
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(block.body.transactions, vec![tx.clone()]);
|
||||
}
|
||||
@ -883,6 +885,7 @@ mod tests {
|
||||
let new_block = sequencer
|
||||
.store
|
||||
.get_block_at_id(sequencer.chain_height)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "sequencer_runner"
|
||||
name = "sequencer_service"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
@ -9,20 +9,25 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
common.workspace = true
|
||||
nssa.workspace = true
|
||||
mempool.workspace = true
|
||||
sequencer_core = { workspace = true, features = ["testnet"] }
|
||||
sequencer_rpc.workspace = true
|
||||
sequencer_service_protocol.workspace = true
|
||||
sequencer_service_rpc = { workspace = true, features = ["server"] }
|
||||
indexer_service_rpc = { workspace = true, features = ["client"] }
|
||||
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
anyhow.workspace = true
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
actix.workspace = true
|
||||
actix-web.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util.workspace = true
|
||||
jsonrpsee.workspace = true
|
||||
futures.workspace = true
|
||||
bytesize.workspace = true
|
||||
borsh.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# Runs the sequencer in standalone mode without depending on Bedrock and Indexer services.
|
||||
standalone = ["sequencer_core/mock", "sequencer_rpc/standalone"]
|
||||
standalone = ["sequencer_core/mock"]
|
||||
@ -40,7 +40,7 @@ RUN r0vm --version
|
||||
# Install logos blockchain circuits
|
||||
RUN curl -sSL https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/main/scripts/setup-logos-blockchain-circuits.sh | bash
|
||||
|
||||
WORKDIR /sequencer_runner
|
||||
WORKDIR /sequencer_service
|
||||
|
||||
# Build argument to enable standalone feature (defaults to false)
|
||||
ARG STANDALONE=false
|
||||
@ -48,17 +48,17 @@ ARG STANDALONE=false
|
||||
# Planner stage - generates dependency recipe
|
||||
FROM chef AS planner
|
||||
COPY . .
|
||||
RUN cargo chef prepare --bin sequencer_runner --recipe-path recipe.json
|
||||
RUN cargo chef prepare --bin sequencer_service --recipe-path recipe.json
|
||||
|
||||
# Builder stage - builds dependencies and application
|
||||
FROM chef AS builder
|
||||
ARG STANDALONE
|
||||
COPY --from=planner /sequencer_runner/recipe.json recipe.json
|
||||
COPY --from=planner /sequencer_service/recipe.json recipe.json
|
||||
# Build dependencies only (this layer will be cached)
|
||||
RUN if [ "$STANDALONE" = "true" ]; then \
|
||||
cargo chef cook --bin sequencer_runner --features standalone --release --recipe-path recipe.json; \
|
||||
cargo chef cook --bin sequencer_service --features standalone --release --recipe-path recipe.json; \
|
||||
else \
|
||||
cargo chef cook --bin sequencer_runner --release --recipe-path recipe.json; \
|
||||
cargo chef cook --bin sequencer_service --release --recipe-path recipe.json; \
|
||||
fi
|
||||
|
||||
# Copy source code
|
||||
@ -66,13 +66,13 @@ COPY . .
|
||||
|
||||
# Build the actual application
|
||||
RUN if [ "$STANDALONE" = "true" ]; then \
|
||||
cargo build --release --features standalone --bin sequencer_runner; \
|
||||
cargo build --release --features standalone --bin sequencer_service; \
|
||||
else \
|
||||
cargo build --release --bin sequencer_runner; \
|
||||
cargo build --release --bin sequencer_service; \
|
||||
fi
|
||||
|
||||
# Strip debug symbols to reduce binary size
|
||||
RUN strip /sequencer_runner/target/release/sequencer_runner
|
||||
RUN strip /sequencer_service/target/release/sequencer_service
|
||||
|
||||
# Runtime stage - minimal image
|
||||
FROM debian:trixie-slim
|
||||
@ -84,11 +84,11 @@ RUN apt-get update \
|
||||
|
||||
# Create non-root user for security
|
||||
RUN useradd -m -u 1000 -s /bin/bash sequencer_user && \
|
||||
mkdir -p /sequencer_runner /etc/sequencer_runner && \
|
||||
chown -R sequencer_user:sequencer_user /sequencer_runner /etc/sequencer_runner
|
||||
mkdir -p /sequencer_service /etc/sequencer_service && \
|
||||
chown -R sequencer_user:sequencer_user /sequencer_service /etc/sequencer_service
|
||||
|
||||
# Copy binary from builder
|
||||
COPY --from=builder --chown=sequencer_user:sequencer_user /sequencer_runner/target/release/sequencer_runner /usr/local/bin/sequencer_runner
|
||||
COPY --from=builder --chown=sequencer_user:sequencer_user /sequencer_service/target/release/sequencer_service /usr/local/bin/sequencer_service
|
||||
|
||||
# Copy r0vm binary from builder
|
||||
COPY --from=builder --chown=sequencer_user:sequencer_user /usr/local/bin/r0vm /usr/local/bin/r0vm
|
||||
@ -97,7 +97,7 @@ COPY --from=builder --chown=sequencer_user:sequencer_user /usr/local/bin/r0vm /u
|
||||
COPY --from=builder --chown=sequencer_user:sequencer_user /root/.logos-blockchain-circuits /home/sequencer_user/.logos-blockchain-circuits
|
||||
|
||||
# Copy entrypoint script
|
||||
COPY sequencer_runner/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY sequencer/service/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
# Expose default port
|
||||
@ -124,5 +124,5 @@ USER root
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
WORKDIR /sequencer_runner
|
||||
CMD ["sequencer_runner", "/etc/sequencer_runner"]
|
||||
WORKDIR /sequencer_service
|
||||
CMD ["sequencer_service", "/etc/sequencer_service/sequencer_config.json"]
|
||||
@ -1,6 +1,5 @@
|
||||
{
|
||||
"home": ".",
|
||||
"override_rust_log": null,
|
||||
"genesis_id": 1,
|
||||
"is_genesis_random": true,
|
||||
"max_num_tx_in_block": 20,
|
||||
@ -8,7 +7,6 @@
|
||||
"mempool_max_size": 1000,
|
||||
"block_create_timeout": "15s",
|
||||
"retry_pending_blocks_timeout": "5s",
|
||||
"port": 3040,
|
||||
"bedrock_config": {
|
||||
"backoff": {
|
||||
"start_delay": "100ms",
|
||||
@ -1,13 +1,11 @@
|
||||
{
|
||||
"home": "/var/lib/sequencer_runner",
|
||||
"override_rust_log": null,
|
||||
"home": "/var/lib/sequencer_service",
|
||||
"genesis_id": 1,
|
||||
"is_genesis_random": true,
|
||||
"max_num_tx_in_block": 20,
|
||||
"max_block_size": "1 MiB",
|
||||
"mempool_max_size": 10000,
|
||||
"block_create_timeout": "10s",
|
||||
"port": 3040,
|
||||
"retry_pending_blocks_timeout": "7s",
|
||||
"bedrock_config": {
|
||||
"backoff": {
|
||||
14
sequencer/service/docker-compose.yml
Normal file
14
sequencer/service/docker-compose.yml
Normal file
@ -0,0 +1,14 @@
|
||||
services:
|
||||
sequencer_service:
|
||||
image: lssa/sequencer_service
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: sequencer/service/Dockerfile
|
||||
container_name: sequencer_service
|
||||
ports:
|
||||
- "3040:3040"
|
||||
volumes:
|
||||
# Mount configuration file
|
||||
- ./configs/docker/sequencer_config.json:/etc/sequencer_service/sequencer_config.json
|
||||
# Mount data folder
|
||||
- ./data:/var/lib/sequencer_service
|
||||
@ -1,11 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This is an entrypoint script for the sequencer_runner Docker container,
|
||||
# This is an entrypoint script for the sequencer_service Docker container,
|
||||
# it's not meant to be executed outside of the container.
|
||||
|
||||
set -e
|
||||
|
||||
CONFIG="/etc/sequencer_runner/sequencer_config.json"
|
||||
CONFIG="/etc/sequencer/service/sequencer_config.json"
|
||||
|
||||
# Check config file exists
|
||||
if [ ! -f "$CONFIG" ]; then
|
||||
13
sequencer/service/protocol/Cargo.toml
Normal file
13
sequencer/service/protocol/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "sequencer_service_protocol"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
common.workspace = true
|
||||
nssa.workspace = true
|
||||
nssa_core.workspace = true
|
||||
9
sequencer/service/protocol/src/lib.rs
Normal file
9
sequencer/service/protocol/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
||||
//! Reexports of types used by sequencer rpc specification.
|
||||
|
||||
pub use common::{
|
||||
HashType,
|
||||
block::{Block, BlockId},
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
pub use nssa::{Account, AccountId, ProgramId};
|
||||
pub use nssa_core::{Commitment, MembershipProof, account::Nonce};
|
||||
17
sequencer/service/rpc/Cargo.toml
Normal file
17
sequencer/service/rpc/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "sequencer_service_rpc"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
sequencer_service_protocol.workspace = true
|
||||
|
||||
jsonrpsee = { workspace = true, features = ["macros"] }
|
||||
|
||||
[features]
|
||||
client = ["jsonrpsee/client"]
|
||||
server = ["jsonrpsee/server"]
|
||||
92
sequencer/service/rpc/src/lib.rs
Normal file
92
sequencer/service/rpc/src/lib.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
#[cfg(feature = "server")]
|
||||
use jsonrpsee::types::ErrorObjectOwned;
|
||||
#[cfg(feature = "client")]
|
||||
pub use jsonrpsee::{core::ClientError, http_client::HttpClientBuilder as SequencerClientBuilder};
|
||||
use sequencer_service_protocol::{
|
||||
Account, AccountId, Block, BlockId, Commitment, HashType, MembershipProof, NSSATransaction,
|
||||
Nonce, ProgramId,
|
||||
};
|
||||
|
||||
#[cfg(all(not(feature = "server"), not(feature = "client")))]
|
||||
compile_error!("At least one of `server` or `client` features must be enabled.");
|
||||
|
||||
/// Type alias for RPC client. Only available when `client` feature is enabled.
|
||||
///
|
||||
/// It's cheap to clone this client, so it can be cloned and shared across the application.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use common::transaction::NSSATransaction;
|
||||
/// use sequencer_service_rpc::{RpcClient as _, SequencerClientBuilder};
|
||||
///
|
||||
/// let url = "http://localhost:3040".parse()?;
|
||||
/// let client = SequencerClientBuilder::default().build(url)?;
|
||||
///
|
||||
/// let tx: NSSATransaction = unimplemented!("Construct your transaction here");
|
||||
/// let tx_hash = client.send_transaction(tx).await?;
|
||||
/// ```
|
||||
#[cfg(feature = "client")]
|
||||
pub type SequencerClient = jsonrpsee::http_client::HttpClient;
|
||||
|
||||
#[cfg_attr(all(feature = "server", not(feature = "client")), rpc(server))]
|
||||
#[cfg_attr(all(feature = "client", not(feature = "server")), rpc(client))]
|
||||
#[cfg_attr(all(feature = "server", feature = "client"), rpc(server, client))]
|
||||
pub trait Rpc {
|
||||
#[method(name = "sendTransaction")]
|
||||
async fn send_transaction(&self, tx: NSSATransaction) -> Result<HashType, ErrorObjectOwned>;
|
||||
|
||||
// TODO: expand healthcheck response into some kind of report
|
||||
#[method(name = "checkHealth")]
|
||||
async fn check_health(&self) -> Result<(), ErrorObjectOwned>;
|
||||
|
||||
// TODO: These functions should be removed after wallet starts using indexer
|
||||
// for this type of queries.
|
||||
//
|
||||
// =============================================================================================
|
||||
|
||||
#[method(name = "getBlock")]
|
||||
async fn get_block(&self, block_id: BlockId) -> Result<Option<Block>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getBlockRange")]
|
||||
async fn get_block_range(
|
||||
&self,
|
||||
start_block_id: BlockId,
|
||||
end_block_id: BlockId,
|
||||
) -> Result<Vec<Block>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getLastBlockId")]
|
||||
async fn get_last_block_id(&self) -> Result<BlockId, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getAccountBalance")]
|
||||
async fn get_account_balance(&self, account_id: AccountId) -> Result<u128, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getTransaction")]
|
||||
async fn get_transaction(
|
||||
&self,
|
||||
tx_hash: HashType,
|
||||
) -> Result<Option<NSSATransaction>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getAccountsNonces")]
|
||||
async fn get_accounts_nonces(
|
||||
&self,
|
||||
account_ids: Vec<AccountId>,
|
||||
) -> Result<Vec<Nonce>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getProofForCommitment")]
|
||||
async fn get_proof_for_commitment(
|
||||
&self,
|
||||
commitment: Commitment,
|
||||
) -> Result<Option<MembershipProof>, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getAccount")]
|
||||
async fn get_account(&self, account_id: AccountId) -> Result<Account, ErrorObjectOwned>;
|
||||
|
||||
#[method(name = "getProgramIds")]
|
||||
async fn get_program_ids(&self) -> Result<BTreeMap<String, ProgramId>, ErrorObjectOwned>;
|
||||
|
||||
// =============================================================================================
|
||||
}
|
||||
@ -1,59 +1,75 @@
|
||||
use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};
|
||||
use std::{net::SocketAddr, sync::Arc, time::Duration};
|
||||
|
||||
use actix_web::dev::ServerHandle;
|
||||
use anyhow::{Context as _, Result};
|
||||
use clap::Parser;
|
||||
use common::rpc_primitives::RpcConfig;
|
||||
use futures::{FutureExt as _, never::Never};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use bytesize::ByteSize;
|
||||
use common::transaction::NSSATransaction;
|
||||
use futures::never::Never;
|
||||
use jsonrpsee::server::ServerHandle;
|
||||
#[cfg(not(feature = "standalone"))]
|
||||
use log::warn;
|
||||
use log::{error, info};
|
||||
use mempool::MemPoolHandle;
|
||||
#[cfg(feature = "standalone")]
|
||||
use sequencer_core::SequencerCoreWithMockClients as SequencerCore;
|
||||
use sequencer_core::config::SequencerConfig;
|
||||
pub use sequencer_core::config::*;
|
||||
#[cfg(not(feature = "standalone"))]
|
||||
use sequencer_core::{SequencerCore, block_settlement_client::BlockSettlementClientTrait as _};
|
||||
use sequencer_rpc::new_http_server;
|
||||
use sequencer_service_rpc::RpcServer as _;
|
||||
use tokio::{sync::Mutex, task::JoinHandle};
|
||||
|
||||
pub const RUST_LOG: &str = "RUST_LOG";
|
||||
pub mod service;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version)]
|
||||
struct Args {
|
||||
/// Path to configs.
|
||||
home_dir: PathBuf,
|
||||
}
|
||||
const REQUEST_BODY_MAX_SIZE: ByteSize = ByteSize::mib(10);
|
||||
|
||||
/// Handle to manage the sequencer and its tasks.
|
||||
///
|
||||
/// Implements `Drop` to ensure all tasks are aborted and the HTTP server is stopped when dropped.
|
||||
/// Implements `Drop` to ensure all tasks are aborted and the RPC server is stopped when dropped.
|
||||
pub struct SequencerHandle {
|
||||
addr: SocketAddr,
|
||||
http_server_handle: ServerHandle,
|
||||
/// Option because of `Drop` which forbids to simply move out of `self` in `stopped()`.
|
||||
server_handle: Option<ServerHandle>,
|
||||
main_loop_handle: JoinHandle<Result<Never>>,
|
||||
retry_pending_blocks_loop_handle: JoinHandle<Result<Never>>,
|
||||
listen_for_bedrock_blocks_loop_handle: JoinHandle<Result<Never>>,
|
||||
}
|
||||
|
||||
impl SequencerHandle {
|
||||
/// Runs the sequencer indefinitely, monitoring its tasks.
|
||||
///
|
||||
/// If no error occurs, this function will never return.
|
||||
const fn new(
|
||||
addr: SocketAddr,
|
||||
server_handle: ServerHandle,
|
||||
main_loop_handle: JoinHandle<Result<Never>>,
|
||||
retry_pending_blocks_loop_handle: JoinHandle<Result<Never>>,
|
||||
listen_for_bedrock_blocks_loop_handle: JoinHandle<Result<Never>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
addr,
|
||||
server_handle: Some(server_handle),
|
||||
main_loop_handle,
|
||||
retry_pending_blocks_loop_handle,
|
||||
listen_for_bedrock_blocks_loop_handle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for any of the sequencer tasks to fail and return the error.
|
||||
#[expect(
|
||||
clippy::integer_division_remainder_used,
|
||||
reason = "Generated by select! macro, can't be easily rewritten to avoid this lint"
|
||||
)]
|
||||
pub async fn run_forever(&mut self) -> Result<Never> {
|
||||
pub async fn failed(mut self) -> Result<Never> {
|
||||
let Self {
|
||||
addr: _,
|
||||
http_server_handle: _,
|
||||
server_handle,
|
||||
main_loop_handle,
|
||||
retry_pending_blocks_loop_handle,
|
||||
listen_for_bedrock_blocks_loop_handle,
|
||||
} = self;
|
||||
} = &mut self;
|
||||
|
||||
let server_handle = server_handle.take().expect("Server handle is set");
|
||||
|
||||
tokio::select! {
|
||||
() = server_handle.stopped() => {
|
||||
Err(anyhow!("RPC Server stopped"))
|
||||
}
|
||||
res = main_loop_handle => {
|
||||
res
|
||||
.context("Main loop task panicked")?
|
||||
@ -72,11 +88,25 @@ impl SequencerHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if all Sequencer tasks are still running.
|
||||
///
|
||||
/// Return `false` if any of the tasks has failed and `true` otherwise.
|
||||
/// Error of the failed task can be retrieved by awaiting on [`Self::failed()`].
|
||||
#[must_use]
|
||||
pub fn is_finished(&self) -> bool {
|
||||
self.main_loop_handle.is_finished()
|
||||
|| self.retry_pending_blocks_loop_handle.is_finished()
|
||||
|| self.listen_for_bedrock_blocks_loop_handle.is_finished()
|
||||
pub fn is_healthy(&self) -> bool {
|
||||
let Self {
|
||||
addr: _,
|
||||
server_handle,
|
||||
main_loop_handle,
|
||||
retry_pending_blocks_loop_handle,
|
||||
listen_for_bedrock_blocks_loop_handle,
|
||||
} = self;
|
||||
|
||||
let stopped = server_handle.as_ref().is_none_or(ServerHandle::is_stopped)
|
||||
|| main_loop_handle.is_finished()
|
||||
|| retry_pending_blocks_loop_handle.is_finished()
|
||||
|| listen_for_bedrock_blocks_loop_handle.is_finished();
|
||||
!stopped
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@ -89,7 +119,7 @@ impl Drop for SequencerHandle {
|
||||
fn drop(&mut self) {
|
||||
let Self {
|
||||
addr: _,
|
||||
http_server_handle,
|
||||
server_handle,
|
||||
main_loop_handle,
|
||||
retry_pending_blocks_loop_handle,
|
||||
listen_for_bedrock_blocks_loop_handle,
|
||||
@ -99,31 +129,35 @@ impl Drop for SequencerHandle {
|
||||
retry_pending_blocks_loop_handle.abort();
|
||||
listen_for_bedrock_blocks_loop_handle.abort();
|
||||
|
||||
// Can't wait here as Drop can't be async, but anyway stop signal should be sent
|
||||
http_server_handle.stop(true).now_or_never();
|
||||
let Some(handle) = server_handle else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(err) = handle.stop() {
|
||||
error!("An error occurred while stopping Sequencer RPC server: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn startup_sequencer(app_config: SequencerConfig) -> Result<SequencerHandle> {
|
||||
let block_timeout = app_config.block_create_timeout;
|
||||
let retry_pending_blocks_timeout = app_config.retry_pending_blocks_timeout;
|
||||
let port = app_config.port;
|
||||
pub async fn run(config: SequencerConfig, port: u16) -> Result<SequencerHandle> {
|
||||
let block_timeout = config.block_create_timeout;
|
||||
let retry_pending_blocks_timeout = config.retry_pending_blocks_timeout;
|
||||
let max_block_size = config.max_block_size;
|
||||
|
||||
let (sequencer_core, mempool_handle) = SequencerCore::start_from_config(app_config).await;
|
||||
let (sequencer_core, mempool_handle) = SequencerCore::start_from_config(config).await;
|
||||
|
||||
info!("Sequencer core set up");
|
||||
|
||||
let seq_core_wrapped = Arc::new(Mutex::new(sequencer_core));
|
||||
|
||||
let (http_server, addr) = new_http_server(
|
||||
RpcConfig::with_port(port),
|
||||
let (server_handle, addr) = run_server(
|
||||
Arc::clone(&seq_core_wrapped),
|
||||
mempool_handle,
|
||||
port,
|
||||
max_block_size.as_u64(),
|
||||
)
|
||||
.await?;
|
||||
info!("HTTP server started");
|
||||
let http_server_handle = http_server.handle();
|
||||
tokio::spawn(http_server);
|
||||
info!("RPC server started");
|
||||
|
||||
#[cfg(not(feature = "standalone"))]
|
||||
{
|
||||
@ -146,13 +180,42 @@ pub async fn startup_sequencer(app_config: SequencerConfig) -> Result<SequencerH
|
||||
let listen_for_bedrock_blocks_loop_handle =
|
||||
tokio::spawn(listen_for_bedrock_blocks_loop(seq_core_wrapped));
|
||||
|
||||
Ok(SequencerHandle {
|
||||
Ok(SequencerHandle::new(
|
||||
addr,
|
||||
http_server_handle,
|
||||
server_handle,
|
||||
main_loop_handle,
|
||||
retry_pending_blocks_loop_handle,
|
||||
listen_for_bedrock_blocks_loop_handle,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
async fn run_server(
|
||||
sequencer: Arc<Mutex<SequencerCore>>,
|
||||
mempool_handle: MemPoolHandle<NSSATransaction>,
|
||||
port: u16,
|
||||
max_block_size: u64,
|
||||
) -> Result<(ServerHandle, SocketAddr)> {
|
||||
let server = jsonrpsee::server::ServerBuilder::with_config(
|
||||
jsonrpsee::server::ServerConfigBuilder::new()
|
||||
.max_request_body_size(
|
||||
u32::try_from(REQUEST_BODY_MAX_SIZE.as_u64())
|
||||
.expect("REQUEST_BODY_MAX_SIZE should be less than u32::MAX"),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.build(SocketAddr::from(([0, 0, 0, 0], port)))
|
||||
.await
|
||||
.context("Failed to build RPC server")?;
|
||||
|
||||
let addr = server
|
||||
.local_addr()
|
||||
.context("Failed to get local address of RPC server")?;
|
||||
|
||||
info!("Starting Sequencer Service RPC server on {addr}");
|
||||
|
||||
let service = service::SequencerService::new(sequencer, mempool_handle, max_block_size);
|
||||
let handle = server.start(service.into_rpc());
|
||||
Ok((handle, addr))
|
||||
}
|
||||
|
||||
async fn main_loop(seq_core: Arc<Mutex<SequencerCore>>, block_timeout: Duration) -> Result<Never> {
|
||||
@ -210,7 +273,7 @@ async fn retry_pending_blocks(seq_core: &Arc<Mutex<SequencerCore>>) -> Result<()
|
||||
.create_inscribe_tx(block)
|
||||
.context("Failed to create inscribe tx for pending block")?;
|
||||
|
||||
debug!(">>>> Create inscribe: {:?}", now.elapsed());
|
||||
debug!("Create inscribe: {:?}", now.elapsed());
|
||||
|
||||
let now = Instant::now();
|
||||
if let Err(e) = block_settlement_client
|
||||
@ -222,7 +285,7 @@ async fn retry_pending_blocks(seq_core: &Arc<Mutex<SequencerCore>>) -> Result<()
|
||||
block.header.block_id
|
||||
);
|
||||
}
|
||||
debug!(">>>> Post: {:?}", now.elapsed());
|
||||
debug!("Post: {:?}", now.elapsed());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -287,33 +350,3 @@ async fn retry_pending_blocks_loop(
|
||||
) -> Result<Never> {
|
||||
std::future::pending::<Result<Never>>().await
|
||||
}
|
||||
|
||||
pub async fn main_runner() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let args = Args::parse();
|
||||
let Args { home_dir } = args;
|
||||
|
||||
let app_config = SequencerConfig::from_path(&home_dir.join("sequencer_config.json"))?;
|
||||
|
||||
if let Some(rust_log) = &app_config.override_rust_log {
|
||||
info!("RUST_LOG env var set to {rust_log:?}");
|
||||
|
||||
// SAFETY: there is no other threads running at this point
|
||||
unsafe {
|
||||
std::env::set_var(RUST_LOG, rust_log);
|
||||
}
|
||||
}
|
||||
|
||||
// ToDo: Add restart on failures
|
||||
let mut sequencer_handle = startup_sequencer(app_config).await?;
|
||||
|
||||
info!("Sequencer running. Monitoring concurrent tasks...");
|
||||
|
||||
let Err(err) = sequencer_handle.run_forever().await;
|
||||
error!("Sequencer failed: {err:#}");
|
||||
|
||||
info!("Shutting down sequencer...");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
60
sequencer/service/src/main.rs
Normal file
60
sequencer/service/src/main.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use log::{error, info};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(version)]
|
||||
struct Args {
|
||||
#[clap(name = "config")]
|
||||
config_path: PathBuf,
|
||||
#[clap(short, long, default_value = "3040")]
|
||||
port: u16,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[expect(
|
||||
clippy::integer_division_remainder_used,
|
||||
reason = "Generated by select! macro, can't be easily rewritten to avoid this lint"
|
||||
)]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let Args { config_path, port } = Args::parse();
|
||||
|
||||
let cancellation_token = listen_for_shutdown_signal();
|
||||
|
||||
let config = sequencer_service::SequencerConfig::from_path(&config_path)?;
|
||||
let sequencer_handle = sequencer_service::run(config, port).await?;
|
||||
|
||||
tokio::select! {
|
||||
() = cancellation_token.cancelled() => {
|
||||
info!("Shutting down sequencer...");
|
||||
}
|
||||
Err(err) = sequencer_handle.failed() => {
|
||||
error!("Sequencer failed unexpectedly: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
info!("Sequencer shutdown complete");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn listen_for_shutdown_signal() -> CancellationToken {
|
||||
let cancellation_token = CancellationToken::new();
|
||||
let cancellation_token_clone = cancellation_token.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = tokio::signal::ctrl_c().await {
|
||||
error!("Failed to listen for Ctrl-C signal: {err}");
|
||||
return;
|
||||
}
|
||||
info!("Received Ctrl-C signal");
|
||||
cancellation_token_clone.cancel();
|
||||
});
|
||||
|
||||
cancellation_token
|
||||
}
|
||||
183
sequencer/service/src/service.rs
Normal file
183
sequencer/service/src/service.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use common::transaction::NSSATransaction;
|
||||
use jsonrpsee::{
|
||||
core::async_trait,
|
||||
types::{ErrorCode, ErrorObjectOwned},
|
||||
};
|
||||
use log::warn;
|
||||
use mempool::MemPoolHandle;
|
||||
use nssa::{self, program::Program};
|
||||
use sequencer_core::{
|
||||
DbError, SequencerCore, block_settlement_client::BlockSettlementClientTrait,
|
||||
indexer_client::IndexerClientTrait,
|
||||
};
|
||||
use sequencer_service_protocol::{
|
||||
Account, AccountId, Block, BlockId, Commitment, HashType, MembershipProof, Nonce, ProgramId,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
const NOT_FOUND_ERROR_CODE: i32 = -31999;
|
||||
|
||||
pub struct SequencerService<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> {
|
||||
sequencer: Arc<Mutex<SequencerCore<BC, IC>>>,
|
||||
mempool_handle: MemPoolHandle<NSSATransaction>,
|
||||
max_block_size: u64,
|
||||
}
|
||||
|
||||
impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerService<BC, IC> {
|
||||
pub const fn new(
|
||||
sequencer: Arc<Mutex<SequencerCore<BC, IC>>>,
|
||||
mempool_handle: MemPoolHandle<NSSATransaction>,
|
||||
max_block_size: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
sequencer,
|
||||
mempool_handle,
|
||||
max_block_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<BC: BlockSettlementClientTrait + Send + 'static, IC: IndexerClientTrait + Send + 'static>
|
||||
sequencer_service_rpc::RpcServer for SequencerService<BC, IC>
|
||||
{
|
||||
async fn send_transaction(&self, tx: NSSATransaction) -> Result<HashType, ErrorObjectOwned> {
|
||||
// Reserve ~200 bytes for block header overhead
|
||||
const BLOCK_HEADER_OVERHEAD: u64 = 200;
|
||||
|
||||
let tx_hash = tx.hash();
|
||||
|
||||
let encoded_tx =
|
||||
borsh::to_vec(&tx).expect("Transaction borsh serialization should not fail");
|
||||
let tx_size = u64::try_from(encoded_tx.len()).expect("Transaction size should fit in u64");
|
||||
|
||||
let max_tx_size = self.max_block_size.saturating_sub(BLOCK_HEADER_OVERHEAD);
|
||||
|
||||
if tx_size > max_tx_size {
|
||||
return Err(ErrorObjectOwned::owned(
|
||||
ErrorCode::InvalidParams.code(),
|
||||
format!("Transaction too large: size {tx_size}, max {max_tx_size}"),
|
||||
None::<()>,
|
||||
));
|
||||
}
|
||||
|
||||
let authenticated_tx = tx
|
||||
.transaction_stateless_check()
|
||||
.inspect_err(|err| warn!("Error at pre_check {err:#?}"))
|
||||
.map_err(|err| {
|
||||
ErrorObjectOwned::owned(
|
||||
ErrorCode::InvalidParams.code(),
|
||||
format!("{err:?}"),
|
||||
None::<()>,
|
||||
)
|
||||
})?;
|
||||
|
||||
self.mempool_handle
|
||||
.push(authenticated_tx)
|
||||
.await
|
||||
.expect("Mempool is closed, this is a bug");
|
||||
|
||||
Ok(tx_hash)
|
||||
}
|
||||
|
||||
async fn check_health(&self) -> Result<(), ErrorObjectOwned> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_block(&self, block_id: BlockId) -> Result<Option<Block>, ErrorObjectOwned> {
|
||||
let sequencer = self.sequencer.lock().await;
|
||||
sequencer
|
||||
.block_store()
|
||||
.get_block_at_id(block_id)
|
||||
.map_err(|err| internal_error(&err))
|
||||
}
|
||||
|
||||
async fn get_block_range(
|
||||
&self,
|
||||
start_block_id: BlockId,
|
||||
end_block_id: BlockId,
|
||||
) -> Result<Vec<Block>, ErrorObjectOwned> {
|
||||
let sequencer = self.sequencer.lock().await;
|
||||
(start_block_id..=end_block_id)
|
||||
.map(|block_id| {
|
||||
let block = sequencer
|
||||
.block_store()
|
||||
.get_block_at_id(block_id)
|
||||
.map_err(|err| internal_error(&err))?;
|
||||
block.ok_or_else(|| {
|
||||
ErrorObjectOwned::owned(
|
||||
NOT_FOUND_ERROR_CODE,
|
||||
format!("Block with id {block_id} not found"),
|
||||
None::<()>,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
async fn get_last_block_id(&self) -> Result<BlockId, ErrorObjectOwned> {
|
||||
let sequencer = self.sequencer.lock().await;
|
||||
Ok(sequencer.chain_height())
|
||||
}
|
||||
|
||||
async fn get_account_balance(&self, account_id: AccountId) -> Result<u128, ErrorObjectOwned> {
|
||||
let sequencer = self.sequencer.lock().await;
|
||||
let account = sequencer.state().get_account_by_id(account_id);
|
||||
Ok(account.balance)
|
||||
}
|
||||
|
||||
async fn get_transaction(
|
||||
&self,
|
||||
tx_hash: HashType,
|
||||
) -> Result<Option<NSSATransaction>, ErrorObjectOwned> {
|
||||
let sequencer = self.sequencer.lock().await;
|
||||
Ok(sequencer.block_store().get_transaction_by_hash(tx_hash))
|
||||
}
|
||||
|
||||
async fn get_accounts_nonces(
|
||||
&self,
|
||||
account_ids: Vec<AccountId>,
|
||||
) -> Result<Vec<Nonce>, ErrorObjectOwned> {
|
||||
let sequencer = self.sequencer.lock().await;
|
||||
let nonces = account_ids
|
||||
.into_iter()
|
||||
.map(|account_id| sequencer.state().get_account_by_id(account_id).nonce)
|
||||
.collect();
|
||||
Ok(nonces)
|
||||
}
|
||||
|
||||
async fn get_proof_for_commitment(
|
||||
&self,
|
||||
commitment: Commitment,
|
||||
) -> Result<Option<MembershipProof>, ErrorObjectOwned> {
|
||||
let sequencer = self.sequencer.lock().await;
|
||||
Ok(sequencer.state().get_proof_for_commitment(&commitment))
|
||||
}
|
||||
|
||||
async fn get_account(&self, account_id: AccountId) -> Result<Account, ErrorObjectOwned> {
|
||||
let sequencer = self.sequencer.lock().await;
|
||||
Ok(sequencer.state().get_account_by_id(account_id))
|
||||
}
|
||||
|
||||
async fn get_program_ids(&self) -> Result<BTreeMap<String, ProgramId>, ErrorObjectOwned> {
|
||||
let mut program_ids = BTreeMap::new();
|
||||
program_ids.insert(
|
||||
"authenticated_transfer".to_owned(),
|
||||
Program::authenticated_transfer_program().id(),
|
||||
);
|
||||
program_ids.insert("token".to_owned(), Program::token().id());
|
||||
program_ids.insert("pinata".to_owned(), Program::pinata().id());
|
||||
program_ids.insert("amm".to_owned(), Program::amm().id());
|
||||
program_ids.insert(
|
||||
"privacy_preserving_circuit".to_owned(),
|
||||
nssa::PRIVACY_PRESERVING_CIRCUIT_ID,
|
||||
);
|
||||
Ok(program_ids)
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_error(err: &DbError) -> ErrorObjectOwned {
|
||||
ErrorObjectOwned::owned(ErrorCode::InternalError.code(), err.to_string(), None::<()>)
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
[package]
|
||||
name = "sequencer_rpc"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nssa.workspace = true
|
||||
common.workspace = true
|
||||
mempool.workspace = true
|
||||
sequencer_core = { workspace = true }
|
||||
bedrock_client.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
serde_json.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
actix-cors.workspace = true
|
||||
futures.workspace = true
|
||||
base58.workspace = true
|
||||
hex.workspace = true
|
||||
tempfile.workspace = true
|
||||
base64.workspace = true
|
||||
itertools.workspace = true
|
||||
actix-web.workspace = true
|
||||
tokio.workspace = true
|
||||
borsh.workspace = true
|
||||
bytesize.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
sequencer_core = { workspace = true, features = ["mock"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# Includes types to run the sequencer in standalone mode
|
||||
standalone = ["sequencer_core/mock"]
|
||||
@ -1,55 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::{
|
||||
rpc_primitives::errors::{RpcError, RpcErrorKind},
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
use mempool::MemPoolHandle;
|
||||
pub use net_utils::*;
|
||||
#[cfg(feature = "standalone")]
|
||||
use sequencer_core::mock::{MockBlockSettlementClient, MockIndexerClient};
|
||||
use sequencer_core::{
|
||||
SequencerCore,
|
||||
block_settlement_client::{BlockSettlementClient, BlockSettlementClientTrait},
|
||||
indexer_client::{IndexerClient, IndexerClientTrait},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use self::types::err_rpc::RpcErr;
|
||||
|
||||
pub mod net_utils;
|
||||
pub mod process;
|
||||
pub mod types;
|
||||
|
||||
#[cfg(feature = "standalone")]
|
||||
pub type JsonHandlerWithMockClients = JsonHandler<MockBlockSettlementClient, MockIndexerClient>;
|
||||
|
||||
// ToDo: Add necessary fields
|
||||
pub struct JsonHandler<
|
||||
BC: BlockSettlementClientTrait = BlockSettlementClient,
|
||||
IC: IndexerClientTrait = IndexerClient,
|
||||
> {
|
||||
sequencer_state: Arc<Mutex<SequencerCore<BC, IC>>>,
|
||||
mempool_handle: MemPoolHandle<NSSATransaction>,
|
||||
max_block_size: usize,
|
||||
}
|
||||
|
||||
fn respond<T: Serialize>(val: T) -> Result<Value, RpcErr> {
|
||||
Ok(serde_json::to_value(val)?)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn rpc_error_responce_inverter(err: RpcError) -> RpcError {
|
||||
let content = err.error_struct.map(|error| match error {
|
||||
RpcErrorKind::HandlerError(val) | RpcErrorKind::InternalError(val) => val,
|
||||
RpcErrorKind::RequestValidationError(vall) => serde_json::to_value(vall).unwrap(),
|
||||
});
|
||||
RpcError {
|
||||
error_struct: None,
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
data: content,
|
||||
}
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
use std::{io, net::SocketAddr, sync::Arc};
|
||||
|
||||
use actix_cors::Cors;
|
||||
use actix_web::{App, Error as HttpError, HttpResponse, HttpServer, http, middleware, web};
|
||||
use common::{
|
||||
rpc_primitives::{RpcConfig, message::Message},
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
use futures::{Future, FutureExt as _};
|
||||
use log::info;
|
||||
use mempool::MemPoolHandle;
|
||||
#[cfg(not(feature = "standalone"))]
|
||||
use sequencer_core::SequencerCore;
|
||||
#[cfg(feature = "standalone")]
|
||||
use sequencer_core::SequencerCoreWithMockClients as SequencerCore;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[cfg(not(feature = "standalone"))]
|
||||
use super::JsonHandler;
|
||||
use crate::process::Process;
|
||||
|
||||
pub const SHUTDOWN_TIMEOUT_SECS: u64 = 10;
|
||||
|
||||
pub const NETWORK: &str = "network";
|
||||
|
||||
#[cfg(feature = "standalone")]
|
||||
type JsonHandler = super::JsonHandlerWithMockClients;
|
||||
|
||||
pub(crate) fn rpc_handler<P: Process>(
|
||||
message: web::Json<Message>,
|
||||
handler: web::Data<P>,
|
||||
) -> impl Future<Output = Result<HttpResponse, HttpError>> {
|
||||
let response = async move {
|
||||
let message = handler.process(message.0).await?;
|
||||
Ok(HttpResponse::Ok().json(&message))
|
||||
};
|
||||
response.boxed()
|
||||
}
|
||||
|
||||
fn get_cors(cors_allowed_origins: &[String]) -> Cors {
|
||||
let mut cors = Cors::permissive();
|
||||
if cors_allowed_origins != ["*".to_owned()] {
|
||||
for origin in cors_allowed_origins {
|
||||
cors = cors.allowed_origin(origin);
|
||||
}
|
||||
}
|
||||
cors.allowed_methods(vec!["GET", "POST"])
|
||||
.allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
|
||||
.allowed_header(http::header::CONTENT_TYPE)
|
||||
.max_age(3600)
|
||||
}
|
||||
|
||||
pub async fn new_http_server(
|
||||
config: RpcConfig,
|
||||
seuquencer_core: Arc<Mutex<SequencerCore>>,
|
||||
mempool_handle: MemPoolHandle<NSSATransaction>,
|
||||
) -> io::Result<(actix_web::dev::Server, SocketAddr)> {
|
||||
let RpcConfig {
|
||||
addr,
|
||||
cors_allowed_origins,
|
||||
limits_config,
|
||||
} = config;
|
||||
info!(target:NETWORK, "Starting HTTP server at {addr}");
|
||||
let max_block_size = seuquencer_core
|
||||
.lock()
|
||||
.await
|
||||
.sequencer_config()
|
||||
.max_block_size
|
||||
.as_u64()
|
||||
.try_into()
|
||||
.expect("`max_block_size` is expected to fit into usize");
|
||||
let handler = web::Data::new(JsonHandler {
|
||||
sequencer_state: Arc::clone(&seuquencer_core),
|
||||
mempool_handle,
|
||||
max_block_size,
|
||||
});
|
||||
|
||||
// HTTP server
|
||||
let http_server = HttpServer::new(move || {
|
||||
let json_limit = limits_config
|
||||
.json_payload_max_size
|
||||
.as_u64()
|
||||
.try_into()
|
||||
.expect("`json_payload_max_size` is expected to fit into usize");
|
||||
App::new()
|
||||
.wrap(get_cors(&cors_allowed_origins))
|
||||
.app_data(handler.clone())
|
||||
.app_data(web::JsonConfig::default().limit(json_limit))
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(web::resource("/").route(web::post().to(rpc_handler::<JsonHandler>)))
|
||||
})
|
||||
.bind(addr)?
|
||||
.shutdown_timeout(SHUTDOWN_TIMEOUT_SECS)
|
||||
.disable_signals();
|
||||
|
||||
let [final_addr] = http_server
|
||||
.addrs()
|
||||
.try_into()
|
||||
.expect("Exactly one address bound is expected for sequencer HTTP server");
|
||||
|
||||
info!(target:NETWORK, "HTTP server started at {final_addr}");
|
||||
|
||||
Ok((http_server.run(), final_addr))
|
||||
}
|
||||
@ -1,786 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use actix_web::Error as HttpError;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use common::{
|
||||
block::{AccountInitialData, HashableBlockData},
|
||||
rpc_primitives::{
|
||||
errors::RpcError,
|
||||
message::{Message, Request},
|
||||
parser::RpcRequest as _,
|
||||
requests::{
|
||||
GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountRequest,
|
||||
GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse,
|
||||
GetBlockDataRequest, GetBlockDataResponse, GetBlockRangeDataRequest,
|
||||
GetBlockRangeDataResponse, GetGenesisIdRequest, GetGenesisIdResponse,
|
||||
GetInitialTestnetAccountsRequest, GetLastBlockRequest, GetLastBlockResponse,
|
||||
GetProgramIdsRequest, GetProgramIdsResponse, GetProofForCommitmentRequest,
|
||||
GetProofForCommitmentResponse, GetTransactionByHashRequest,
|
||||
GetTransactionByHashResponse, HelloRequest, HelloResponse, SendTxRequest,
|
||||
SendTxResponse,
|
||||
},
|
||||
},
|
||||
transaction::{NSSATransaction, TransactionMalformationError},
|
||||
};
|
||||
use itertools::Itertools as _;
|
||||
use log::warn;
|
||||
use nssa::{self, program::Program};
|
||||
use sequencer_core::{
|
||||
block_settlement_client::BlockSettlementClientTrait, indexer_client::IndexerClientTrait,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{JsonHandler, respond, types::err_rpc::RpcErr};
|
||||
|
||||
pub const HELLO: &str = "hello";
|
||||
pub const SEND_TX: &str = "send_tx";
|
||||
pub const GET_BLOCK: &str = "get_block";
|
||||
pub const GET_BLOCK_RANGE: &str = "get_block_range";
|
||||
pub const GET_GENESIS: &str = "get_genesis";
|
||||
pub const GET_LAST_BLOCK: &str = "get_last_block";
|
||||
pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance";
|
||||
pub const GET_TRANSACTION_BY_HASH: &str = "get_transaction_by_hash";
|
||||
pub const GET_ACCOUNTS_NONCES: &str = "get_accounts_nonces";
|
||||
pub const GET_ACCOUNT: &str = "get_account";
|
||||
pub const GET_PROOF_FOR_COMMITMENT: &str = "get_proof_for_commitment";
|
||||
pub const GET_PROGRAM_IDS: &str = "get_program_ids";
|
||||
|
||||
pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER";
|
||||
|
||||
pub const TRANSACTION_SUBMITTED: &str = "Transaction submitted";
|
||||
|
||||
pub const GET_INITIAL_TESTNET_ACCOUNTS: &str = "get_initial_testnet_accounts";
|
||||
|
||||
pub trait Process: Send + Sync + 'static {
|
||||
fn process(&self, message: Message) -> impl Future<Output = Result<Message, HttpError>> + Send;
|
||||
}
|
||||
|
||||
impl<
|
||||
BC: BlockSettlementClientTrait + Send + Sync + 'static,
|
||||
IC: IndexerClientTrait + Send + Sync + 'static,
|
||||
> Process for JsonHandler<BC, IC>
|
||||
{
|
||||
async fn process(&self, message: Message) -> Result<Message, HttpError> {
|
||||
let id = message.id();
|
||||
if let Message::Request(request) = message {
|
||||
let message_inner = self
|
||||
.process_request_internal(request)
|
||||
.await
|
||||
.map_err(|e| e.0);
|
||||
Ok(Message::response(id, message_inner))
|
||||
} else {
|
||||
Ok(Message::error(RpcError::parse_error(
|
||||
"JSON RPC Request format was expected".to_owned(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> JsonHandler<BC, IC> {
|
||||
/// Example of request processing.
|
||||
fn process_temp_hello(request: Request) -> Result<Value, RpcErr> {
|
||||
let _hello_request = HelloRequest::parse(Some(request.params))?;
|
||||
|
||||
let response = HelloResponse {
|
||||
greeting: HELLO_FROM_SEQUENCER.to_owned(),
|
||||
};
|
||||
|
||||
respond(response)
|
||||
}
|
||||
|
||||
async fn process_send_tx(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
// Check transaction size against block size limit
|
||||
// Reserve ~200 bytes for block header overhead
|
||||
const BLOCK_HEADER_OVERHEAD: usize = 200;
|
||||
|
||||
let send_tx_req = SendTxRequest::parse(Some(request.params))?;
|
||||
let tx = borsh::from_slice::<NSSATransaction>(&send_tx_req.transaction).unwrap();
|
||||
|
||||
let tx_hash = tx.hash();
|
||||
|
||||
let tx_size = send_tx_req.transaction.len();
|
||||
|
||||
let max_tx_size = self.max_block_size.saturating_sub(BLOCK_HEADER_OVERHEAD);
|
||||
|
||||
if tx_size > max_tx_size {
|
||||
return Err(TransactionMalformationError::TransactionTooLarge {
|
||||
size: tx_size,
|
||||
max: max_tx_size,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
let authenticated_tx = tx
|
||||
.transaction_stateless_check()
|
||||
.inspect_err(|err| warn!("Error at pre_check {err:#?}"))?;
|
||||
|
||||
// TODO: Do we need a timeout here? It will be usable if we have too many transactions to
|
||||
// process
|
||||
self.mempool_handle
|
||||
.push(authenticated_tx)
|
||||
.await
|
||||
.expect("Mempool is closed, this is a bug");
|
||||
|
||||
let response = SendTxResponse {
|
||||
status: TRANSACTION_SUBMITTED.to_owned(),
|
||||
tx_hash,
|
||||
};
|
||||
|
||||
respond(response)
|
||||
}
|
||||
|
||||
async fn process_get_block_data(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let get_block_req = GetBlockDataRequest::parse(Some(request.params))?;
|
||||
|
||||
let block = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
|
||||
state
|
||||
.block_store()
|
||||
.get_block_at_id(get_block_req.block_id)?
|
||||
};
|
||||
|
||||
let response = GetBlockDataResponse {
|
||||
block: borsh::to_vec(&HashableBlockData::from(block)).unwrap(),
|
||||
};
|
||||
|
||||
respond(response)
|
||||
}
|
||||
|
||||
async fn process_get_block_range_data(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let get_block_req = GetBlockRangeDataRequest::parse(Some(request.params))?;
|
||||
|
||||
let blocks = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
(get_block_req.start_block_id..=get_block_req.end_block_id)
|
||||
.map(|block_id| state.block_store().get_block_at_id(block_id))
|
||||
.map_ok(|block| {
|
||||
borsh::to_vec(&HashableBlockData::from(block))
|
||||
.expect("derived BorshSerialize should never fail")
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
};
|
||||
|
||||
let response = GetBlockRangeDataResponse { blocks };
|
||||
|
||||
respond(response)
|
||||
}
|
||||
|
||||
async fn process_get_genesis(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let _get_genesis_req = GetGenesisIdRequest::parse(Some(request.params))?;
|
||||
|
||||
let genesis_id = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
|
||||
state.block_store().genesis_id()
|
||||
};
|
||||
|
||||
let response = GetGenesisIdResponse { genesis_id };
|
||||
|
||||
respond(response)
|
||||
}
|
||||
|
||||
async fn process_get_last_block(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let _get_last_block_req = GetLastBlockRequest::parse(Some(request.params))?;
|
||||
|
||||
let last_block = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
|
||||
state.chain_height()
|
||||
};
|
||||
|
||||
let response = GetLastBlockResponse { last_block };
|
||||
|
||||
respond(response)
|
||||
}
|
||||
|
||||
/// Returns the initial accounts for testnet.
|
||||
/// `ToDo`: Useful only for testnet and needs to be removed later.
|
||||
async fn get_initial_testnet_accounts(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let _get_initial_testnet_accounts_request =
|
||||
GetInitialTestnetAccountsRequest::parse(Some(request.params))?;
|
||||
|
||||
let initial_accounts: Vec<AccountInitialData> = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
|
||||
state.sequencer_config().initial_accounts.clone()
|
||||
};
|
||||
|
||||
respond(initial_accounts)
|
||||
}
|
||||
|
||||
/// Returns the balance of the account at the given `account_id`.
|
||||
/// The `account_id` must be a valid hex string of the correct length.
|
||||
async fn process_get_account_balance(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let get_account_req = GetAccountBalanceRequest::parse(Some(request.params))?;
|
||||
let account_id = get_account_req.account_id;
|
||||
|
||||
let balance = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
let account = state.state().get_account_by_id(account_id);
|
||||
account.balance
|
||||
};
|
||||
|
||||
let response = GetAccountBalanceResponse { balance };
|
||||
|
||||
respond(response)
|
||||
}
|
||||
|
||||
/// Returns the nonces of the accounts at the given `account_ids`.
|
||||
/// Each `account_id` must be a valid hex string of the correct length.
|
||||
async fn process_get_accounts_nonces(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let get_account_nonces_req = GetAccountsNoncesRequest::parse(Some(request.params))?;
|
||||
let account_ids = get_account_nonces_req.account_ids;
|
||||
|
||||
let nonces = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
|
||||
account_ids
|
||||
.into_iter()
|
||||
.map(|account_id| state.state().get_account_by_id(account_id).nonce.0)
|
||||
.collect()
|
||||
};
|
||||
|
||||
let response = GetAccountsNoncesResponse { nonces };
|
||||
|
||||
respond(response)
|
||||
}
|
||||
|
||||
/// Returns account struct for given `account_id`.
|
||||
/// `AccountId` must be a valid hex string of the correct length.
|
||||
async fn process_get_account(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let get_account_nonces_req = GetAccountRequest::parse(Some(request.params))?;
|
||||
|
||||
let account_id = get_account_nonces_req.account_id;
|
||||
|
||||
let account = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
|
||||
state.state().get_account_by_id(account_id)
|
||||
};
|
||||
|
||||
let response = GetAccountResponse { account };
|
||||
|
||||
respond(response)
|
||||
}
|
||||
|
||||
/// Returns the transaction corresponding to the given hash, if it exists in the blockchain.
|
||||
/// The hash must be a valid hex string of the correct length.
|
||||
async fn process_get_transaction_by_hash(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let get_transaction_req = GetTransactionByHashRequest::parse(Some(request.params))?;
|
||||
let hash = get_transaction_req.hash;
|
||||
|
||||
let transaction = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
state
|
||||
.block_store()
|
||||
.get_transaction_by_hash(hash)
|
||||
.map(|tx| borsh::to_vec(&tx).unwrap())
|
||||
};
|
||||
let base64_encoded = transaction.map(|tx| general_purpose::STANDARD.encode(tx));
|
||||
let response = GetTransactionByHashResponse {
|
||||
transaction: base64_encoded,
|
||||
};
|
||||
respond(response)
|
||||
}
|
||||
|
||||
/// Returns the commitment proof, corresponding to commitment.
|
||||
async fn process_get_proof_by_commitment(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let get_proof_req = GetProofForCommitmentRequest::parse(Some(request.params))?;
|
||||
|
||||
let membership_proof = {
|
||||
let state = self.sequencer_state.lock().await;
|
||||
state
|
||||
.state()
|
||||
.get_proof_for_commitment(&get_proof_req.commitment)
|
||||
};
|
||||
let response = GetProofForCommitmentResponse { membership_proof };
|
||||
respond(response)
|
||||
}
|
||||
|
||||
fn process_get_program_ids(request: Request) -> Result<Value, RpcErr> {
|
||||
let _get_proof_req = GetProgramIdsRequest::parse(Some(request.params))?;
|
||||
|
||||
let mut program_ids = HashMap::new();
|
||||
program_ids.insert(
|
||||
"authenticated_transfer".to_owned(),
|
||||
Program::authenticated_transfer_program().id(),
|
||||
);
|
||||
program_ids.insert("token".to_owned(), Program::token().id());
|
||||
program_ids.insert("pinata".to_owned(), Program::pinata().id());
|
||||
program_ids.insert("amm".to_owned(), Program::amm().id());
|
||||
program_ids.insert(
|
||||
"privacy_preserving_circuit".to_owned(),
|
||||
nssa::PRIVACY_PRESERVING_CIRCUIT_ID,
|
||||
);
|
||||
let response = GetProgramIdsResponse { program_ids };
|
||||
respond(response)
|
||||
}
|
||||
|
||||
pub async fn process_request_internal(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
match request.method.as_ref() {
|
||||
HELLO => Self::process_temp_hello(request),
|
||||
SEND_TX => self.process_send_tx(request).await,
|
||||
GET_BLOCK => self.process_get_block_data(request).await,
|
||||
GET_BLOCK_RANGE => self.process_get_block_range_data(request).await,
|
||||
GET_GENESIS => self.process_get_genesis(request).await,
|
||||
GET_LAST_BLOCK => self.process_get_last_block(request).await,
|
||||
GET_INITIAL_TESTNET_ACCOUNTS => self.get_initial_testnet_accounts(request).await,
|
||||
GET_ACCOUNT_BALANCE => self.process_get_account_balance(request).await,
|
||||
GET_ACCOUNTS_NONCES => self.process_get_accounts_nonces(request).await,
|
||||
GET_ACCOUNT => self.process_get_account(request).await,
|
||||
GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await,
|
||||
GET_PROOF_FOR_COMMITMENT => self.process_get_proof_by_commitment(request).await,
|
||||
GET_PROGRAM_IDS => Self::process_get_program_ids(request),
|
||||
_ => Err(RpcErr(RpcError::method_not_found(request.method))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{str::FromStr as _, sync::Arc, time::Duration};
|
||||
|
||||
use base58::ToBase58 as _;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use bedrock_client::BackoffConfig;
|
||||
use common::{
|
||||
block::AccountInitialData, config::BasicAuth, test_utils::sequencer_sign_key_for_testing,
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
use nssa::AccountId;
|
||||
use sequencer_core::{
|
||||
config::{BedrockConfig, SequencerConfig},
|
||||
mock::{MockBlockSettlementClient, MockIndexerClient, SequencerCoreWithMockClients},
|
||||
};
|
||||
use serde_json::Value;
|
||||
use tempfile::tempdir;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::rpc_handler;
|
||||
|
||||
type JsonHandlerWithMockClients =
|
||||
crate::JsonHandler<MockBlockSettlementClient, MockIndexerClient>;
|
||||
|
||||
fn sequencer_config_for_tests() -> SequencerConfig {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let home = tempdir.path().to_path_buf();
|
||||
let acc1_id: Vec<u8> = vec![
|
||||
148, 179, 206, 253, 199, 51, 82, 86, 232, 2, 152, 122, 80, 243, 54, 207, 237, 112, 83,
|
||||
153, 44, 59, 204, 49, 128, 84, 160, 227, 216, 149, 97, 102,
|
||||
];
|
||||
|
||||
let acc2_id: Vec<u8> = vec![
|
||||
30, 145, 107, 3, 207, 73, 192, 230, 160, 63, 238, 207, 18, 69, 54, 216, 103, 244, 92,
|
||||
94, 124, 248, 42, 16, 141, 19, 119, 18, 14, 226, 140, 204,
|
||||
];
|
||||
|
||||
let initial_acc1 = AccountInitialData {
|
||||
account_id: AccountId::from_str(&acc1_id.to_base58()).unwrap(),
|
||||
balance: 10000,
|
||||
};
|
||||
|
||||
let initial_acc2 = AccountInitialData {
|
||||
account_id: AccountId::from_str(&acc2_id.to_base58()).unwrap(),
|
||||
balance: 20000,
|
||||
};
|
||||
|
||||
let initial_accounts = vec![initial_acc1, initial_acc2];
|
||||
|
||||
SequencerConfig {
|
||||
home,
|
||||
override_rust_log: Some("info".to_owned()),
|
||||
genesis_id: 1,
|
||||
is_genesis_random: false,
|
||||
max_num_tx_in_block: 10,
|
||||
max_block_size: bytesize::ByteSize::mib(1),
|
||||
mempool_max_size: 1000,
|
||||
block_create_timeout: Duration::from_secs(1),
|
||||
port: 8080,
|
||||
initial_accounts,
|
||||
initial_commitments: vec![],
|
||||
signing_key: *sequencer_sign_key_for_testing().value(),
|
||||
retry_pending_blocks_timeout: Duration::from_secs(60 * 4),
|
||||
bedrock_config: BedrockConfig {
|
||||
backoff: BackoffConfig {
|
||||
start_delay: Duration::from_millis(100),
|
||||
max_retries: 5,
|
||||
},
|
||||
channel_id: [42; 32].into(),
|
||||
node_url: "http://localhost:8080".parse().unwrap(),
|
||||
auth: Some(BasicAuth {
|
||||
username: "user".to_owned(),
|
||||
password: None,
|
||||
}),
|
||||
},
|
||||
indexer_rpc_url: "ws://localhost:8779".parse().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn components_for_tests() -> (
|
||||
JsonHandlerWithMockClients,
|
||||
Vec<AccountInitialData>,
|
||||
NSSATransaction,
|
||||
) {
|
||||
let config = sequencer_config_for_tests();
|
||||
|
||||
let (mut sequencer_core, mempool_handle) =
|
||||
SequencerCoreWithMockClients::start_from_config(config).await;
|
||||
let initial_accounts = sequencer_core.sequencer_config().initial_accounts.clone();
|
||||
|
||||
let signing_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
|
||||
let balance_to_move = 10;
|
||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||
AccountId::from_str(
|
||||
&[
|
||||
148, 179, 206, 253, 199, 51, 82, 86, 232, 2, 152, 122, 80, 243, 54, 207, 237,
|
||||
112, 83, 153, 44, 59, 204, 49, 128, 84, 160, 227, 216, 149, 97, 102,
|
||||
]
|
||||
.to_base58(),
|
||||
)
|
||||
.unwrap(),
|
||||
0,
|
||||
AccountId::from_str(&[2; 32].to_base58()).unwrap(),
|
||||
balance_to_move,
|
||||
&signing_key,
|
||||
);
|
||||
|
||||
mempool_handle
|
||||
.push(tx.clone())
|
||||
.await
|
||||
.expect("Mempool is closed, this is a bug");
|
||||
|
||||
sequencer_core
|
||||
.produce_new_block_with_mempool_transactions()
|
||||
.unwrap();
|
||||
|
||||
let max_block_size =
|
||||
usize::try_from(sequencer_core.sequencer_config().max_block_size.as_u64())
|
||||
.expect("`max_block_size` is expected to fit in usize");
|
||||
let sequencer_core = Arc::new(Mutex::new(sequencer_core));
|
||||
|
||||
(
|
||||
JsonHandlerWithMockClients {
|
||||
sequencer_state: sequencer_core,
|
||||
mempool_handle,
|
||||
max_block_size,
|
||||
},
|
||||
initial_accounts,
|
||||
tx,
|
||||
)
|
||||
}
|
||||
|
||||
async fn call_rpc_handler_with_json(
|
||||
handler: JsonHandlerWithMockClients,
|
||||
request_json: Value,
|
||||
) -> Value {
|
||||
use actix_web::{App, test, web};
|
||||
|
||||
let app = test::init_service(App::new().app_data(web::Data::new(handler)).route(
|
||||
"/",
|
||||
web::post().to(rpc_handler::<JsonHandlerWithMockClients>),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/")
|
||||
.set_json(request_json)
|
||||
.to_request();
|
||||
|
||||
let resp = test::call_service(&app, req).await;
|
||||
let body = test::read_body(resp).await;
|
||||
|
||||
serde_json::from_slice(&body).unwrap()
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_account_balance_for_non_existent_account() {
|
||||
let (json_handler, _, _) = components_for_tests().await;
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_account_balance",
|
||||
"params": { "account_id": "11".repeat(16) },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"balance": 0
|
||||
}
|
||||
});
|
||||
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_account_balance_for_invalid_base58() {
|
||||
let (json_handler, _, _) = components_for_tests().await;
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_account_balance",
|
||||
"params": { "account_id": "not_a_valid_base58" },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"error": {
|
||||
"cause": {
|
||||
"info": {
|
||||
"error_message": "Failed parsing args: invalid base58: InvalidBase58Character('_', 3)"
|
||||
},
|
||||
"name": "PARSE_ERROR"
|
||||
},
|
||||
"code": -32700,
|
||||
"data": "Failed parsing args: invalid base58: InvalidBase58Character('_', 3)",
|
||||
"message": "Parse error",
|
||||
"name": "REQUEST_VALIDATION_ERROR"
|
||||
},
|
||||
});
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_account_balance_for_invalid_length() {
|
||||
let (json_handler, _, _) = components_for_tests().await;
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_account_balance",
|
||||
"params": { "account_id": "cafecafe" },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"error": {
|
||||
"cause": {
|
||||
"info": {
|
||||
"error_message": "Failed parsing args: invalid length: expected 32 bytes, got 6"
|
||||
},
|
||||
"name": "PARSE_ERROR"
|
||||
},
|
||||
"code": -32700,
|
||||
"data": "Failed parsing args: invalid length: expected 32 bytes, got 6",
|
||||
"message": "Parse error",
|
||||
"name": "REQUEST_VALIDATION_ERROR"
|
||||
},
|
||||
});
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_account_balance_for_existing_account() {
|
||||
let (json_handler, initial_accounts, _) = components_for_tests().await;
|
||||
|
||||
let acc1_id = initial_accounts[0].account_id;
|
||||
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_account_balance",
|
||||
"params": { "account_id": acc1_id },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"balance": 10000 - 10
|
||||
}
|
||||
});
|
||||
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_accounts_nonces_for_non_existent_account() {
|
||||
let (json_handler, _, _) = components_for_tests().await;
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_accounts_nonces",
|
||||
"params": { "account_ids": ["11".repeat(16)] },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"nonces": [ 0 ]
|
||||
}
|
||||
});
|
||||
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_accounts_nonces_for_existent_account() {
|
||||
let (json_handler, initial_accounts, _) = components_for_tests().await;
|
||||
|
||||
let acc1_id = initial_accounts[0].account_id;
|
||||
let acc2_id = initial_accounts[1].account_id;
|
||||
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_accounts_nonces",
|
||||
"params": { "account_ids": [acc1_id, acc2_id] },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"nonces": [ 1, 0 ]
|
||||
}
|
||||
});
|
||||
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_account_data_for_non_existent_account() {
|
||||
let (json_handler, _, _) = components_for_tests().await;
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_account",
|
||||
"params": { "account_id": "11".repeat(16) },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"account": {
|
||||
"balance": 0,
|
||||
"nonce": 0,
|
||||
"program_owner": [ 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"data": [],
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_transaction_by_hash_for_non_existent_hash() {
|
||||
let (json_handler, _, _) = components_for_tests().await;
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_transaction_by_hash",
|
||||
"params": { "hash": "cafe".repeat(16) },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"transaction": null
|
||||
}
|
||||
});
|
||||
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_transaction_by_hash_for_invalid_hex() {
|
||||
let (json_handler, _, _) = components_for_tests().await;
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_transaction_by_hash",
|
||||
"params": { "hash": "not_a_valid_hex" },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"error": {
|
||||
"cause": {
|
||||
"info": {
|
||||
"error_message": "Failed parsing args: Odd number of digits"
|
||||
},
|
||||
"name": "PARSE_ERROR"
|
||||
},
|
||||
"code": -32700,
|
||||
"data": "Failed parsing args: Odd number of digits",
|
||||
"message": "Parse error",
|
||||
"name": "REQUEST_VALIDATION_ERROR"
|
||||
},
|
||||
});
|
||||
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_transaction_by_hash_for_invalid_length() {
|
||||
let (json_handler, _, _) = components_for_tests().await;
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_transaction_by_hash",
|
||||
"params": { "hash": "cafecafe" },
|
||||
"id": 1
|
||||
});
|
||||
let expected_response = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"error": {
|
||||
"cause": {
|
||||
"info": {
|
||||
"error_message": "Failed parsing args: Invalid string length"
|
||||
},
|
||||
"name": "PARSE_ERROR"
|
||||
},
|
||||
"code": -32700,
|
||||
"data": "Failed parsing args: Invalid string length",
|
||||
"message": "Parse error",
|
||||
"name": "REQUEST_VALIDATION_ERROR"
|
||||
}
|
||||
});
|
||||
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_transaction_by_hash_for_existing_transaction() {
|
||||
let (json_handler, _, tx) = components_for_tests().await;
|
||||
let tx_hash_hex = hex::encode(tx.hash());
|
||||
let expected_base64_encoded = general_purpose::STANDARD.encode(borsh::to_vec(&tx).unwrap());
|
||||
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_transaction_by_hash",
|
||||
"params": { "hash": tx_hash_hex},
|
||||
"id": 1
|
||||
});
|
||||
|
||||
let expected_response = serde_json::json!({
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"transaction": expected_base64_encoded,
|
||||
}
|
||||
});
|
||||
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
use common::{
|
||||
rpc_primitives::errors::{RpcError, RpcParseError},
|
||||
transaction::TransactionMalformationError,
|
||||
};
|
||||
|
||||
macro_rules! standard_rpc_err_kind {
|
||||
($type_name:path) => {
|
||||
impl RpcErrKind for $type_name {
|
||||
fn into_rpc_err(self) -> RpcError {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct RpcErr(pub RpcError);
|
||||
|
||||
pub type RpcErrInternal = anyhow::Error;
|
||||
|
||||
pub trait RpcErrKind: 'static {
|
||||
fn into_rpc_err(self) -> RpcError;
|
||||
}
|
||||
|
||||
impl<T: RpcErrKind> From<T> for RpcErr {
|
||||
fn from(e: T) -> Self {
|
||||
Self(e.into_rpc_err())
|
||||
}
|
||||
}
|
||||
|
||||
standard_rpc_err_kind!(RpcError);
|
||||
standard_rpc_err_kind!(RpcParseError);
|
||||
|
||||
impl RpcErrKind for serde_json::Error {
|
||||
fn into_rpc_err(self) -> RpcError {
|
||||
RpcError::serialization_error(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcErrKind for RpcErrInternal {
|
||||
fn into_rpc_err(self) -> RpcError {
|
||||
RpcError::new_internal_error(None, &format!("{self:#?}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcErrKind for TransactionMalformationError {
|
||||
fn into_rpc_err(self) -> RpcError {
|
||||
RpcError::invalid_params(Some(serde_json::to_value(self).unwrap()))
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
pub mod err_rpc;
|
||||
@ -1,14 +0,0 @@
|
||||
services:
|
||||
sequencer_runner:
|
||||
image: lssa/sequencer_runner
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: sequencer_runner/Dockerfile
|
||||
container_name: sequencer_runner
|
||||
ports:
|
||||
- "3040:3040"
|
||||
volumes:
|
||||
# Mount configuration folder
|
||||
- ./configs/docker:/etc/sequencer_runner
|
||||
# Mount data folder
|
||||
- ./data:/var/lib/sequencer_runner
|
||||
@ -1,16 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use sequencer_runner::main_runner;
|
||||
|
||||
pub const NUM_THREADS: usize = 4;
|
||||
|
||||
// TODO: Why it requires config as a directory and not as a file?
|
||||
fn main() -> Result<()> {
|
||||
actix::System::with_tokio_rt(|| {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(NUM_THREADS)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.block_on(main_runner())
|
||||
}
|
||||
@ -514,7 +514,7 @@ impl RocksDBIO {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_block(&self, block_id: u64) -> DbResult<Block> {
|
||||
pub fn get_block(&self, block_id: u64) -> DbResult<Option<Block>> {
|
||||
let cf_block = self.block_column();
|
||||
let res = self
|
||||
.db
|
||||
@ -530,16 +530,14 @@ impl RocksDBIO {
|
||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||
|
||||
if let Some(data) = res {
|
||||
Ok(borsh::from_slice::<Block>(&data).map_err(|serr| {
|
||||
Ok(Some(borsh::from_slice::<Block>(&data).map_err(|serr| {
|
||||
DbError::borsh_cast_message(
|
||||
serr,
|
||||
Some("Failed to deserialize block data".to_owned()),
|
||||
)
|
||||
})?)
|
||||
})?))
|
||||
} else {
|
||||
Err(DbError::db_interaction_error(
|
||||
"Block on this id not found".to_owned(),
|
||||
))
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@ -618,7 +616,7 @@ impl RocksDBIO {
|
||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))
|
||||
}
|
||||
|
||||
pub fn get_breakpoint(&self, br_id: u64) -> DbResult<V02State> {
|
||||
fn get_breakpoint(&self, br_id: u64) -> DbResult<V02State> {
|
||||
let cf_br = self.breakpoint_column();
|
||||
let res = self
|
||||
.db
|
||||
@ -641,6 +639,8 @@ impl RocksDBIO {
|
||||
)
|
||||
})?)
|
||||
} else {
|
||||
// Note: this is not a `DbError::NotFound` case, because we expect that all searched
|
||||
// breakpoints will be present in db as this is an internal method.
|
||||
Err(DbError::db_interaction_error(
|
||||
"Breakpoint on this id not found".to_owned(),
|
||||
))
|
||||
@ -665,7 +665,9 @@ impl RocksDBIO {
|
||||
};
|
||||
|
||||
for id in start..=block_id {
|
||||
let block = self.get_block(id)?;
|
||||
let block = self.get_block(id)?.ok_or_else(|| {
|
||||
DbError::db_interaction_error(format!("Block with id {id} not found"))
|
||||
})?;
|
||||
|
||||
for transaction in block.body.transactions {
|
||||
transaction
|
||||
@ -686,9 +688,9 @@ impl RocksDBIO {
|
||||
|
||||
Ok(breakpoint)
|
||||
} else {
|
||||
Err(DbError::db_interaction_error(
|
||||
"Block on this id not found".to_owned(),
|
||||
))
|
||||
Err(DbError::db_interaction_error(format!(
|
||||
"Block with id {block_id} not found"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -720,7 +722,7 @@ impl RocksDBIO {
|
||||
|
||||
// Mappings
|
||||
|
||||
pub fn get_block_id_by_hash(&self, hash: [u8; 32]) -> DbResult<u64> {
|
||||
pub fn get_block_id_by_hash(&self, hash: [u8; 32]) -> DbResult<Option<u64>> {
|
||||
let cf_hti = self.hash_to_id_column();
|
||||
let res = self
|
||||
.db
|
||||
@ -736,17 +738,15 @@ impl RocksDBIO {
|
||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||
|
||||
if let Some(data) = res {
|
||||
Ok(borsh::from_slice::<u64>(&data).map_err(|serr| {
|
||||
Ok(Some(borsh::from_slice::<u64>(&data).map_err(|serr| {
|
||||
DbError::borsh_cast_message(serr, Some("Failed to deserialize block id".to_owned()))
|
||||
})?)
|
||||
})?))
|
||||
} else {
|
||||
Err(DbError::db_interaction_error(
|
||||
"Block on this hash not found".to_owned(),
|
||||
))
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_block_id_by_tx_hash(&self, tx_hash: [u8; 32]) -> DbResult<u64> {
|
||||
pub fn get_block_id_by_tx_hash(&self, tx_hash: [u8; 32]) -> DbResult<Option<u64>> {
|
||||
let cf_tti = self.tx_hash_to_id_column();
|
||||
let res = self
|
||||
.db
|
||||
@ -762,13 +762,11 @@ impl RocksDBIO {
|
||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||
|
||||
if let Some(data) = res {
|
||||
Ok(borsh::from_slice::<u64>(&data).map_err(|serr| {
|
||||
Ok(Some(borsh::from_slice::<u64>(&data).map_err(|serr| {
|
||||
DbError::borsh_cast_message(serr, Some("Failed to deserialize block id".to_owned()))
|
||||
})?)
|
||||
})?))
|
||||
} else {
|
||||
Err(DbError::db_interaction_error(
|
||||
"Block for this tx hash not found".to_owned(),
|
||||
))
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@ -921,8 +919,14 @@ impl RocksDBIO {
|
||||
let mut tx_batch = vec![];
|
||||
|
||||
for tx_hash in self.get_acc_transaction_hashes(acc_id, offset, limit)? {
|
||||
let block_id = self.get_block_id_by_tx_hash(tx_hash)?;
|
||||
let block = self.get_block(block_id)?;
|
||||
let block_id = self.get_block_id_by_tx_hash(tx_hash)?.ok_or_else(|| {
|
||||
DbError::db_interaction_error(format!(
|
||||
"Block id not found for tx hash {tx_hash:#?}"
|
||||
))
|
||||
})?;
|
||||
let block = self.get_block(block_id)?.ok_or_else(|| {
|
||||
DbError::db_interaction_error(format!("Block with id {block_id} not found"))
|
||||
})?;
|
||||
|
||||
let transaction = block
|
||||
.body
|
||||
@ -1019,7 +1023,7 @@ mod tests {
|
||||
let first_id = dbio.get_meta_first_block_in_db().unwrap();
|
||||
let is_first_set = dbio.get_meta_is_first_block_set().unwrap();
|
||||
let last_br_id = dbio.get_meta_last_breakpoint_id().unwrap();
|
||||
let last_block = dbio.get_block(1).unwrap();
|
||||
let last_block = dbio.get_block(1).unwrap().unwrap();
|
||||
let breakpoint = dbio.get_breakpoint(0).unwrap();
|
||||
let final_state = dbio.final_state().unwrap();
|
||||
|
||||
@ -1056,7 +1060,7 @@ mod tests {
|
||||
let first_id = dbio.get_meta_first_block_in_db().unwrap();
|
||||
let is_first_set = dbio.get_meta_is_first_block_set().unwrap();
|
||||
let last_br_id = dbio.get_meta_last_breakpoint_id().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
let breakpoint = dbio.get_breakpoint(0).unwrap();
|
||||
let final_state = dbio.final_state().unwrap();
|
||||
|
||||
@ -1087,7 +1091,7 @@ mod tests {
|
||||
|
||||
for i in 1..BREAKPOINT_INTERVAL {
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, u128::from(i - 1), true);
|
||||
@ -1103,7 +1107,7 @@ mod tests {
|
||||
let first_id = dbio.get_meta_first_block_in_db().unwrap();
|
||||
let is_first_set = dbio.get_meta_is_first_block_set().unwrap();
|
||||
let last_br_id = dbio.get_meta_last_breakpoint_id().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
let prev_breakpoint = dbio.get_breakpoint(0).unwrap();
|
||||
let breakpoint = dbio.get_breakpoint(1).unwrap();
|
||||
let final_state = dbio.final_state().unwrap();
|
||||
@ -1142,7 +1146,7 @@ mod tests {
|
||||
RocksDBIO::open_or_create(temdir_path, &genesis_block(), &initial_state()).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 0, true);
|
||||
@ -1153,7 +1157,7 @@ mod tests {
|
||||
dbio.put_block(&block, [1; 32]).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 1, true);
|
||||
@ -1164,7 +1168,7 @@ mod tests {
|
||||
dbio.put_block(&block, [2; 32]).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 2, true);
|
||||
@ -1175,7 +1179,7 @@ mod tests {
|
||||
dbio.put_block(&block, [3; 32]).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 3, true);
|
||||
@ -1185,10 +1189,16 @@ mod tests {
|
||||
let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]);
|
||||
dbio.put_block(&block, [4; 32]).unwrap();
|
||||
|
||||
let control_block_id1 = dbio.get_block_id_by_hash(control_hash1.0).unwrap();
|
||||
let control_block_id2 = dbio.get_block_id_by_hash(control_hash2.0).unwrap();
|
||||
let control_block_id3 = dbio.get_block_id_by_tx_hash(control_tx_hash1.0).unwrap();
|
||||
let control_block_id4 = dbio.get_block_id_by_tx_hash(control_tx_hash2.0).unwrap();
|
||||
let control_block_id1 = dbio.get_block_id_by_hash(control_hash1.0).unwrap().unwrap();
|
||||
let control_block_id2 = dbio.get_block_id_by_hash(control_hash2.0).unwrap().unwrap();
|
||||
let control_block_id3 = dbio
|
||||
.get_block_id_by_tx_hash(control_tx_hash1.0)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let control_block_id4 = dbio
|
||||
.get_block_id_by_tx_hash(control_tx_hash2.0)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(control_block_id1, 2);
|
||||
assert_eq!(control_block_id2, 3);
|
||||
@ -1207,7 +1217,7 @@ mod tests {
|
||||
RocksDBIO::open_or_create(temdir_path, &genesis_block(), &initial_state()).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 0, true);
|
||||
@ -1217,7 +1227,7 @@ mod tests {
|
||||
dbio.put_block(&block, [1; 32]).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 1, true);
|
||||
@ -1227,7 +1237,7 @@ mod tests {
|
||||
dbio.put_block(&block, [2; 32]).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 2, true);
|
||||
@ -1237,7 +1247,7 @@ mod tests {
|
||||
dbio.put_block(&block, [3; 32]).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 3, true);
|
||||
@ -1285,7 +1295,7 @@ mod tests {
|
||||
RocksDBIO::open_or_create(temdir_path, &genesis_block(), &initial_state()).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 0, true);
|
||||
@ -1297,7 +1307,7 @@ mod tests {
|
||||
dbio.put_block(&block, [1; 32]).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 1, true);
|
||||
@ -1309,7 +1319,7 @@ mod tests {
|
||||
dbio.put_block(&block, [2; 32]).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 2, true);
|
||||
@ -1321,7 +1331,7 @@ mod tests {
|
||||
dbio.put_block(&block, [3; 32]).unwrap();
|
||||
|
||||
let last_id = dbio.get_meta_last_block_in_db().unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap();
|
||||
let last_block = dbio.get_block(last_id).unwrap().unwrap();
|
||||
|
||||
let prev_hash = last_block.header.hash;
|
||||
let transfer_tx = transfer(1, 3, true);
|
||||
|
||||
@ -442,7 +442,7 @@ impl RocksDBIO {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_block(&self, block_id: u64) -> DbResult<Block> {
|
||||
pub fn get_block(&self, block_id: u64) -> DbResult<Option<Block>> {
|
||||
let cf_block = self.block_column();
|
||||
let res = self
|
||||
.db
|
||||
@ -458,16 +458,14 @@ impl RocksDBIO {
|
||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||
|
||||
if let Some(data) = res {
|
||||
Ok(borsh::from_slice::<Block>(&data).map_err(|serr| {
|
||||
Ok(Some(borsh::from_slice::<Block>(&data).map_err(|serr| {
|
||||
DbError::borsh_cast_message(
|
||||
serr,
|
||||
Some("Failed to deserialize block data".to_owned()),
|
||||
)
|
||||
})?)
|
||||
})?))
|
||||
} else {
|
||||
Err(DbError::db_interaction_error(
|
||||
"Block on this id not found".to_owned(),
|
||||
))
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@ -495,7 +493,7 @@ impl RocksDBIO {
|
||||
})?)
|
||||
} else {
|
||||
Err(DbError::db_interaction_error(
|
||||
"Block on this id not found".to_owned(),
|
||||
"NSSA state not found".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -512,9 +510,9 @@ impl RocksDBIO {
|
||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?
|
||||
.is_none()
|
||||
{
|
||||
return Err(DbError::db_interaction_error(
|
||||
"Block on this id not found".to_owned(),
|
||||
));
|
||||
return Err(DbError::db_interaction_error(format!(
|
||||
"Block with id {block_id} not found"
|
||||
)));
|
||||
}
|
||||
|
||||
self.db
|
||||
@ -525,7 +523,9 @@ impl RocksDBIO {
|
||||
}
|
||||
|
||||
pub fn mark_block_as_finalized(&self, block_id: u64) -> DbResult<()> {
|
||||
let mut block = self.get_block(block_id)?;
|
||||
let mut block = self.get_block(block_id)?.ok_or_else(|| {
|
||||
DbError::db_interaction_error(format!("Block with id {block_id} not found"))
|
||||
})?;
|
||||
block.bedrock_status = BedrockStatus::Finalized;
|
||||
|
||||
let cf_block = self.block_column();
|
||||
|
||||
@ -13,8 +13,8 @@ crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
[dependencies]
|
||||
wallet.workspace = true
|
||||
nssa.workspace = true
|
||||
common.workspace = true
|
||||
nssa_core.workspace = true
|
||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||
tokio.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use common::error::ExecutionFailureKind;
|
||||
use ::wallet::ExecutionFailureKind;
|
||||
// Re-export public types for cbindgen
|
||||
pub use error::WalletFfiError as FfiError;
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
@ -75,8 +75,8 @@ pub unsafe extern "C" fn wallet_ffi_claim_pinata(
|
||||
let pinata = Pinata(&wallet);
|
||||
|
||||
match block_on(pinata.claim(pinata_id, winner_id, solution)) {
|
||||
Ok(response) => {
|
||||
let tx_hash = CString::new(response.tx_hash.to_string())
|
||||
Ok(tx_hash) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
@ -182,8 +182,8 @@ pub unsafe extern "C" fn wallet_ffi_claim_pinata_private_owned_already_initializ
|
||||
pinata
|
||||
.claim_private_owned_account_already_initialized(pinata_id, winner_id, solution, proof),
|
||||
) {
|
||||
Ok((response, _shared_key)) => {
|
||||
let tx_hash = CString::new(response.tx_hash.to_string())
|
||||
Ok((tx_hash, _shared_key)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
@ -268,8 +268,8 @@ pub unsafe extern "C" fn wallet_ffi_claim_pinata_private_owned_not_initialized(
|
||||
let pinata = Pinata(&wallet);
|
||||
|
||||
match block_on(pinata.claim_private_owned_account(pinata_id, winner_id, solution)) {
|
||||
Ok((response, _shared_key)) => {
|
||||
let tx_hash = CString::new(response.tx_hash.to_string())
|
||||
Ok((tx_hash, _shared_key)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
//! Block synchronization functions.
|
||||
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
|
||||
use crate::{
|
||||
block_on,
|
||||
error::{print_error, WalletFfiError},
|
||||
@ -134,10 +136,10 @@ pub unsafe extern "C" fn wallet_ffi_get_current_block_height(
|
||||
}
|
||||
};
|
||||
|
||||
match block_on(wallet.sequencer_client.get_last_block()) {
|
||||
Ok(response) => {
|
||||
match block_on(wallet.sequencer_client.get_last_block_id()) {
|
||||
Ok(last_block_id) => {
|
||||
unsafe {
|
||||
*out_block_height = response.last_block;
|
||||
*out_block_height = last_block_id;
|
||||
}
|
||||
WalletFfiError::Success
|
||||
}
|
||||
|
||||
@ -73,8 +73,8 @@ pub unsafe extern "C" fn wallet_ffi_transfer_public(
|
||||
let transfer = NativeTokenTransfer(&wallet);
|
||||
|
||||
match block_on(transfer.send_public_transfer(from_id, to_id, amount)) {
|
||||
Ok(response) => {
|
||||
let tx_hash = CString::new(response.tx_hash.to_string())
|
||||
Ok(tx_hash) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
@ -163,8 +163,8 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded(
|
||||
match block_on(
|
||||
transfer.send_shielded_transfer_to_outer_account(from_id, to_npk, to_vpk, amount),
|
||||
) {
|
||||
Ok((response, _shared_key)) => {
|
||||
let tx_hash = CString::new(response.tx_hash)
|
||||
Ok((tx_hash, _shared_key)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
@ -244,8 +244,8 @@ pub unsafe extern "C" fn wallet_ffi_transfer_deshielded(
|
||||
let transfer = NativeTokenTransfer(&wallet);
|
||||
|
||||
match block_on(transfer.send_deshielded_transfer(from_id, to_id, amount)) {
|
||||
Ok((response, _shared_key)) => {
|
||||
let tx_hash = CString::new(response.tx_hash)
|
||||
Ok((tx_hash, _shared_key)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
@ -333,8 +333,8 @@ pub unsafe extern "C" fn wallet_ffi_transfer_private(
|
||||
|
||||
match block_on(transfer.send_private_transfer_to_outer_account(from_id, to_npk, to_vpk, amount))
|
||||
{
|
||||
Ok((response, _shared_key)) => {
|
||||
let tx_hash = CString::new(response.tx_hash)
|
||||
Ok((tx_hash, _shared_key)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
@ -417,8 +417,8 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded_owned(
|
||||
let transfer = NativeTokenTransfer(&wallet);
|
||||
|
||||
match block_on(transfer.send_shielded_transfer(from_id, to_id, amount)) {
|
||||
Ok((response, _shared_key)) => {
|
||||
let tx_hash = CString::new(response.tx_hash)
|
||||
Ok((tx_hash, _shared_key)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
@ -501,8 +501,8 @@ pub unsafe extern "C" fn wallet_ffi_transfer_private_owned(
|
||||
let transfer = NativeTokenTransfer(&wallet);
|
||||
|
||||
match block_on(transfer.send_private_transfer_to_owned_account(from_id, to_id, amount)) {
|
||||
Ok((response, _shared_keys)) => {
|
||||
let tx_hash = CString::new(response.tx_hash)
|
||||
Ok((tx_hash, _shared_keys)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
@ -573,8 +573,8 @@ pub unsafe extern "C" fn wallet_ffi_register_public_account(
|
||||
let transfer = NativeTokenTransfer(&wallet);
|
||||
|
||||
match block_on(transfer.register_account(account_id)) {
|
||||
Ok(response) => {
|
||||
let tx_hash = CString::new(response.tx_hash.to_string())
|
||||
Ok(tx_hash) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
@ -645,8 +645,8 @@ pub unsafe extern "C" fn wallet_ffi_register_private_account(
|
||||
let transfer = NativeTokenTransfer(&wallet);
|
||||
|
||||
match block_on(transfer.register_account_private(account_id)) {
|
||||
Ok((res, _secret)) => {
|
||||
let tx_hash = CString::new(res.tx_hash)
|
||||
Ok((tx_hash, _secret)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map(std::ffi::CString::into_raw)
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
|
||||
@ -12,10 +12,12 @@ nssa_core.workspace = true
|
||||
nssa.workspace = true
|
||||
common.workspace = true
|
||||
key_protocol.workspace = true
|
||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||
token_core.workspace = true
|
||||
amm_core.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
serde_json.workspace = true
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
@ -25,8 +27,6 @@ humantime.workspace = true
|
||||
tokio = { workspace = true, features = ["macros"] }
|
||||
clap.workspace = true
|
||||
base58.workspace = true
|
||||
base64.workspace = true
|
||||
borsh.workspace = true
|
||||
hex.workspace = true
|
||||
rand.workspace = true
|
||||
itertools.workspace = true
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
{
|
||||
"override_rust_log": null,
|
||||
"sequencer_addr": "http://127.0.0.1:3040",
|
||||
"seq_poll_timeout": "30s",
|
||||
"seq_tx_poll_max_blocks": 15,
|
||||
@ -9,84 +8,18 @@
|
||||
{
|
||||
"Public": {
|
||||
"account_id": "CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r",
|
||||
"pub_sign_key": [
|
||||
127,
|
||||
39,
|
||||
48,
|
||||
152,
|
||||
242,
|
||||
91,
|
||||
113,
|
||||
230,
|
||||
192,
|
||||
5,
|
||||
169,
|
||||
81,
|
||||
159,
|
||||
38,
|
||||
120,
|
||||
218,
|
||||
141,
|
||||
28,
|
||||
127,
|
||||
1,
|
||||
246,
|
||||
162,
|
||||
119,
|
||||
120,
|
||||
226,
|
||||
217,
|
||||
148,
|
||||
138,
|
||||
189,
|
||||
249,
|
||||
1,
|
||||
251
|
||||
]
|
||||
"pub_sign_key": "7f273098f25b71e6c005a9519f2678da8d1c7f01f6a27778e2d9948abdf901fb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Public": {
|
||||
"account_id": "2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2",
|
||||
"pub_sign_key": [
|
||||
244,
|
||||
52,
|
||||
248,
|
||||
116,
|
||||
23,
|
||||
32,
|
||||
1,
|
||||
69,
|
||||
134,
|
||||
174,
|
||||
67,
|
||||
53,
|
||||
109,
|
||||
42,
|
||||
236,
|
||||
98,
|
||||
87,
|
||||
218,
|
||||
8,
|
||||
98,
|
||||
34,
|
||||
246,
|
||||
4,
|
||||
221,
|
||||
183,
|
||||
93,
|
||||
105,
|
||||
115,
|
||||
59,
|
||||
134,
|
||||
252,
|
||||
76
|
||||
]
|
||||
"pub_sign_key": "f434f8741720014586ae43356d2aec6257da086222f604ddb75d69733b86fc4c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Private": {
|
||||
"account_id": "HWkW5qd4XK3me6sCAb4bfPj462k33DjtKtEcYpuzNwB",
|
||||
"account_id": "9DGDXnrNo4QhUUb2F8WDuDrPESja3eYDkZG5HkzvAvMC",
|
||||
"account": {
|
||||
"program_owner": [
|
||||
0,
|
||||
@ -104,184 +37,184 @@
|
||||
},
|
||||
"key_chain": {
|
||||
"secret_spending_key": [
|
||||
14,
|
||||
202,
|
||||
241,
|
||||
109,
|
||||
32,
|
||||
181,
|
||||
152,
|
||||
140,
|
||||
76,
|
||||
153,
|
||||
108,
|
||||
57,
|
||||
77,
|
||||
192,
|
||||
181,
|
||||
97,
|
||||
108,
|
||||
75,
|
||||
231,
|
||||
144,
|
||||
122,
|
||||
45,
|
||||
219,
|
||||
165,
|
||||
5,
|
||||
203,
|
||||
193,
|
||||
36,
|
||||
183,
|
||||
237,
|
||||
190,
|
||||
227,
|
||||
238,
|
||||
13,
|
||||
132,
|
||||
39,
|
||||
114,
|
||||
228,
|
||||
172,
|
||||
82,
|
||||
123,
|
||||
83,
|
||||
34,
|
||||
250,
|
||||
214,
|
||||
137,
|
||||
63
|
||||
119,
|
||||
164,
|
||||
233,
|
||||
132,
|
||||
130,
|
||||
224,
|
||||
201,
|
||||
90,
|
||||
200,
|
||||
156,
|
||||
108,
|
||||
199,
|
||||
56,
|
||||
22
|
||||
],
|
||||
"private_key_holder": {
|
||||
"nullifier_secret_key": [
|
||||
174,
|
||||
56,
|
||||
101,
|
||||
30,
|
||||
248,
|
||||
249,
|
||||
100,
|
||||
0,
|
||||
122,
|
||||
199,
|
||||
209,
|
||||
246,
|
||||
58,
|
||||
163,
|
||||
223,
|
||||
146,
|
||||
59,
|
||||
143,
|
||||
78,
|
||||
95,
|
||||
41,
|
||||
186,
|
||||
106,
|
||||
187,
|
||||
53,
|
||||
63,
|
||||
75,
|
||||
244,
|
||||
233,
|
||||
185,
|
||||
110,
|
||||
199
|
||||
212,
|
||||
34,
|
||||
166,
|
||||
184,
|
||||
182,
|
||||
77,
|
||||
127,
|
||||
176,
|
||||
147,
|
||||
68,
|
||||
148,
|
||||
190,
|
||||
41,
|
||||
244,
|
||||
8,
|
||||
202,
|
||||
51,
|
||||
10,
|
||||
44,
|
||||
43,
|
||||
93,
|
||||
41,
|
||||
229,
|
||||
130,
|
||||
54,
|
||||
96,
|
||||
198,
|
||||
242,
|
||||
10,
|
||||
227,
|
||||
119,
|
||||
1
|
||||
],
|
||||
"viewing_secret_key": [
|
||||
251,
|
||||
85,
|
||||
223,
|
||||
73,
|
||||
142,
|
||||
127,
|
||||
134,
|
||||
132,
|
||||
185,
|
||||
210,
|
||||
100,
|
||||
103,
|
||||
198,
|
||||
108,
|
||||
229,
|
||||
80,
|
||||
176,
|
||||
211,
|
||||
249,
|
||||
114,
|
||||
110,
|
||||
7,
|
||||
225,
|
||||
17,
|
||||
7,
|
||||
69,
|
||||
204,
|
||||
32,
|
||||
47,
|
||||
242,
|
||||
103,
|
||||
247
|
||||
205,
|
||||
10,
|
||||
5,
|
||||
19,
|
||||
148,
|
||||
98,
|
||||
49,
|
||||
19,
|
||||
251,
|
||||
186,
|
||||
247,
|
||||
216,
|
||||
75,
|
||||
53,
|
||||
184,
|
||||
36,
|
||||
84,
|
||||
87,
|
||||
236,
|
||||
205,
|
||||
105,
|
||||
217,
|
||||
213,
|
||||
21,
|
||||
61,
|
||||
183,
|
||||
133,
|
||||
174,
|
||||
121,
|
||||
115,
|
||||
51,
|
||||
203
|
||||
]
|
||||
},
|
||||
"nullifier_public_key": [
|
||||
139,
|
||||
19,
|
||||
158,
|
||||
11,
|
||||
155,
|
||||
231,
|
||||
85,
|
||||
206,
|
||||
132,
|
||||
228,
|
||||
220,
|
||||
114,
|
||||
145,
|
||||
89,
|
||||
122,
|
||||
213,
|
||||
113,
|
||||
156,
|
||||
238,
|
||||
142,
|
||||
242,
|
||||
74,
|
||||
182,
|
||||
91,
|
||||
43,
|
||||
8,
|
||||
118,
|
||||
179,
|
||||
235,
|
||||
94,
|
||||
5,
|
||||
219,
|
||||
131,
|
||||
106,
|
||||
246,
|
||||
253,
|
||||
14,
|
||||
204,
|
||||
65,
|
||||
93,
|
||||
0,
|
||||
198,
|
||||
100,
|
||||
108,
|
||||
57,
|
||||
48,
|
||||
6,
|
||||
190,
|
||||
65,
|
||||
183,
|
||||
31,
|
||||
15,
|
||||
31,
|
||||
88,
|
||||
96,
|
||||
204
|
||||
136,
|
||||
86,
|
||||
82,
|
||||
165
|
||||
],
|
||||
"viewing_public_key": [
|
||||
3,
|
||||
136,
|
||||
153,
|
||||
50,
|
||||
191,
|
||||
184,
|
||||
135,
|
||||
36,
|
||||
29,
|
||||
107,
|
||||
57,
|
||||
9,
|
||||
218,
|
||||
135,
|
||||
249,
|
||||
213,
|
||||
118,
|
||||
165,
|
||||
235,
|
||||
215,
|
||||
118,
|
||||
173,
|
||||
30,
|
||||
137,
|
||||
116,
|
||||
77,
|
||||
17,
|
||||
86,
|
||||
62,
|
||||
154,
|
||||
31,
|
||||
173,
|
||||
4,
|
||||
19,
|
||||
45,
|
||||
0,
|
||||
27,
|
||||
18,
|
||||
26,
|
||||
11,
|
||||
226,
|
||||
126,
|
||||
174,
|
||||
144,
|
||||
167,
|
||||
211
|
||||
160,
|
||||
199,
|
||||
14,
|
||||
23,
|
||||
49,
|
||||
163,
|
||||
49,
|
||||
138,
|
||||
129,
|
||||
229,
|
||||
79,
|
||||
9,
|
||||
15,
|
||||
234,
|
||||
30
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Private": {
|
||||
"account_id": "HUpbRQ1vEcZv5y6TDYv9tpt1VA64ji2v4RDLJfK2rpZn",
|
||||
"account_id": "A6AT9UvsgitUi8w4BH43n6DyX1bK37DtSCfjEWXQQUrQ",
|
||||
"account": {
|
||||
"program_owner": [
|
||||
0,
|
||||
@ -299,181 +232,180 @@
|
||||
},
|
||||
"key_chain": {
|
||||
"secret_spending_key": [
|
||||
32,
|
||||
107,
|
||||
49,
|
||||
136,
|
||||
174,
|
||||
162,
|
||||
244,
|
||||
221,
|
||||
2,
|
||||
133,
|
||||
168,
|
||||
107,
|
||||
250,
|
||||
240,
|
||||
52,
|
||||
92,
|
||||
187,
|
||||
157,
|
||||
116,
|
||||
249,
|
||||
203,
|
||||
143,
|
||||
194,
|
||||
214,
|
||||
112,
|
||||
115,
|
||||
142,
|
||||
105,
|
||||
252,
|
||||
146,
|
||||
166,
|
||||
197,
|
||||
163,
|
||||
132,
|
||||
153,
|
||||
78,
|
||||
241,
|
||||
173,
|
||||
103,
|
||||
242,
|
||||
192,
|
||||
196,
|
||||
29,
|
||||
133
|
||||
222,
|
||||
68,
|
||||
17,
|
||||
87,
|
||||
101,
|
||||
22,
|
||||
113,
|
||||
88,
|
||||
97,
|
||||
180,
|
||||
203,
|
||||
139,
|
||||
18,
|
||||
28,
|
||||
62,
|
||||
51,
|
||||
149
|
||||
],
|
||||
"private_key_holder": {
|
||||
"nullifier_secret_key": [
|
||||
188,
|
||||
235,
|
||||
121,
|
||||
54,
|
||||
131,
|
||||
206,
|
||||
7,
|
||||
215,
|
||||
94,
|
||||
231,
|
||||
102,
|
||||
22,
|
||||
12,
|
||||
27,
|
||||
253,
|
||||
161,
|
||||
248,
|
||||
206,
|
||||
41,
|
||||
160,
|
||||
206,
|
||||
149,
|
||||
5,
|
||||
217,
|
||||
127,
|
||||
235,
|
||||
154,
|
||||
230,
|
||||
198,
|
||||
232,
|
||||
102,
|
||||
31
|
||||
219,
|
||||
5,
|
||||
233,
|
||||
185,
|
||||
144,
|
||||
150,
|
||||
100,
|
||||
58,
|
||||
97,
|
||||
5,
|
||||
57,
|
||||
163,
|
||||
110,
|
||||
46,
|
||||
241,
|
||||
216,
|
||||
155,
|
||||
217,
|
||||
100,
|
||||
51,
|
||||
184,
|
||||
21,
|
||||
225,
|
||||
148,
|
||||
198,
|
||||
9,
|
||||
121,
|
||||
239,
|
||||
232,
|
||||
98,
|
||||
22,
|
||||
218
|
||||
],
|
||||
"viewing_secret_key": [
|
||||
89,
|
||||
116,
|
||||
140,
|
||||
122,
|
||||
211,
|
||||
179,
|
||||
190,
|
||||
229,
|
||||
18,
|
||||
94,
|
||||
56,
|
||||
235,
|
||||
48,
|
||||
99,
|
||||
104,
|
||||
228,
|
||||
111,
|
||||
72,
|
||||
231,
|
||||
18,
|
||||
247,
|
||||
97,
|
||||
110,
|
||||
60,
|
||||
238,
|
||||
138,
|
||||
0,
|
||||
25,
|
||||
92,
|
||||
44,
|
||||
30,
|
||||
145
|
||||
35,
|
||||
105,
|
||||
230,
|
||||
121,
|
||||
218,
|
||||
177,
|
||||
21,
|
||||
55,
|
||||
83,
|
||||
80,
|
||||
95,
|
||||
235,
|
||||
161,
|
||||
83,
|
||||
11,
|
||||
221,
|
||||
67,
|
||||
83,
|
||||
1,
|
||||
218,
|
||||
49,
|
||||
242,
|
||||
53,
|
||||
29,
|
||||
26,
|
||||
171,
|
||||
170,
|
||||
144,
|
||||
49,
|
||||
233,
|
||||
159,
|
||||
48
|
||||
]
|
||||
},
|
||||
"nullifier_public_key": [
|
||||
173,
|
||||
134,
|
||||
33,
|
||||
223,
|
||||
54,
|
||||
226,
|
||||
10,
|
||||
71,
|
||||
215,
|
||||
254,
|
||||
143,
|
||||
172,
|
||||
24,
|
||||
244,
|
||||
68,
|
||||
229,
|
||||
154,
|
||||
12,
|
||||
235,
|
||||
210,
|
||||
229,
|
||||
236,
|
||||
144,
|
||||
126,
|
||||
122,
|
||||
58,
|
||||
107,
|
||||
36,
|
||||
58,
|
||||
243,
|
||||
208,
|
||||
65,
|
||||
112,
|
||||
118,
|
||||
70,
|
||||
217,
|
||||
240,
|
||||
69,
|
||||
100,
|
||||
129,
|
||||
3,
|
||||
121,
|
||||
25,
|
||||
213,
|
||||
132,
|
||||
42,
|
||||
45
|
||||
128,
|
||||
174,
|
||||
197,
|
||||
141,
|
||||
137,
|
||||
162,
|
||||
190,
|
||||
155,
|
||||
234,
|
||||
94,
|
||||
156,
|
||||
218,
|
||||
34,
|
||||
13,
|
||||
221
|
||||
],
|
||||
"viewing_public_key": [
|
||||
2,
|
||||
43,
|
||||
42,
|
||||
253,
|
||||
112,
|
||||
83,
|
||||
195,
|
||||
164,
|
||||
26,
|
||||
141,
|
||||
92,
|
||||
28,
|
||||
224,
|
||||
120,
|
||||
155,
|
||||
119,
|
||||
225,
|
||||
1,
|
||||
45,
|
||||
42,
|
||||
245,
|
||||
172,
|
||||
3,
|
||||
122,
|
||||
7,
|
||||
137,
|
||||
250,
|
||||
84,
|
||||
10,
|
||||
85,
|
||||
3,
|
||||
15,
|
||||
134,
|
||||
136,
|
||||
52,
|
||||
183,
|
||||
170,
|
||||
96,
|
||||
115,
|
||||
212,
|
||||
114,
|
||||
250,
|
||||
205,
|
||||
40,
|
||||
126,
|
||||
211,
|
||||
14,
|
||||
120,
|
||||
37
|
||||
15,
|
||||
55,
|
||||
56,
|
||||
214,
|
||||
72,
|
||||
243,
|
||||
83,
|
||||
17,
|
||||
124,
|
||||
242,
|
||||
251,
|
||||
184,
|
||||
174,
|
||||
150,
|
||||
83
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"basic_auth": null
|
||||
]
|
||||
}
|
||||
@ -161,110 +161,48 @@ impl WalletChainStore {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr as _;
|
||||
|
||||
use key_protocol::key_management::key_tree::{
|
||||
keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, traits::KeyNode as _,
|
||||
};
|
||||
use nssa::PrivateKey;
|
||||
|
||||
use super::*;
|
||||
use crate::config::{
|
||||
InitialAccountData, PersistentAccountDataPrivate, PersistentAccountDataPublic,
|
||||
InitialAccountData, InitialAccountDataPublic, PersistentAccountDataPrivate,
|
||||
PersistentAccountDataPublic,
|
||||
};
|
||||
|
||||
fn create_initial_accounts() -> Vec<InitialAccountData> {
|
||||
let initial_acc1 = serde_json::from_str(
|
||||
r#"{
|
||||
"Public": {
|
||||
"account_id": "CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r",
|
||||
"pub_sign_key": [
|
||||
127,
|
||||
39,
|
||||
48,
|
||||
152,
|
||||
242,
|
||||
91,
|
||||
113,
|
||||
230,
|
||||
192,
|
||||
5,
|
||||
169,
|
||||
81,
|
||||
159,
|
||||
38,
|
||||
120,
|
||||
218,
|
||||
141,
|
||||
28,
|
||||
127,
|
||||
1,
|
||||
246,
|
||||
162,
|
||||
119,
|
||||
120,
|
||||
226,
|
||||
217,
|
||||
148,
|
||||
138,
|
||||
189,
|
||||
249,
|
||||
1,
|
||||
251
|
||||
]
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let initial_acc2 = serde_json::from_str(
|
||||
r#"{
|
||||
"Public": {
|
||||
"account_id": "2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2",
|
||||
"pub_sign_key": [
|
||||
244,
|
||||
52,
|
||||
248,
|
||||
116,
|
||||
23,
|
||||
32,
|
||||
1,
|
||||
69,
|
||||
134,
|
||||
174,
|
||||
67,
|
||||
53,
|
||||
109,
|
||||
42,
|
||||
236,
|
||||
98,
|
||||
87,
|
||||
218,
|
||||
8,
|
||||
98,
|
||||
34,
|
||||
246,
|
||||
4,
|
||||
221,
|
||||
183,
|
||||
93,
|
||||
105,
|
||||
115,
|
||||
59,
|
||||
134,
|
||||
252,
|
||||
76
|
||||
]
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let initial_accounts = vec![initial_acc1, initial_acc2];
|
||||
|
||||
initial_accounts
|
||||
vec![
|
||||
InitialAccountData::Public(InitialAccountDataPublic {
|
||||
account_id: nssa::AccountId::from_str(
|
||||
"CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r",
|
||||
)
|
||||
.unwrap(),
|
||||
pub_sign_key: PrivateKey::try_new([
|
||||
127, 39, 48, 152, 242, 91, 113, 230, 192, 5, 169, 81, 159, 38, 120, 218, 141,
|
||||
28, 127, 1, 246, 162, 119, 120, 226, 217, 148, 138, 189, 249, 1, 251,
|
||||
])
|
||||
.unwrap(),
|
||||
}),
|
||||
InitialAccountData::Public(InitialAccountDataPublic {
|
||||
account_id: nssa::AccountId::from_str(
|
||||
"2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2",
|
||||
)
|
||||
.unwrap(),
|
||||
pub_sign_key: PrivateKey::try_new([
|
||||
244, 52, 248, 116, 23, 32, 1, 69, 134, 174, 67, 53, 109, 42, 236, 98, 87, 218,
|
||||
8, 98, 34, 246, 4, 221, 183, 93, 105, 115, 59, 134, 252, 76,
|
||||
])
|
||||
.unwrap(),
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
fn create_sample_wallet_config() -> WalletConfig {
|
||||
WalletConfig {
|
||||
override_rust_log: None,
|
||||
sequencer_addr: "http://127.0.0.1".parse().unwrap(),
|
||||
seq_poll_timeout: std::time::Duration::from_secs(12),
|
||||
seq_tx_poll_max_blocks: 5,
|
||||
|
||||
@ -3,6 +3,7 @@ use clap::Subcommand;
|
||||
use itertools::Itertools as _;
|
||||
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
||||
use nssa::{Account, PublicKey, program::Program};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
use crate::{
|
||||
@ -244,11 +245,7 @@ impl WalletSubcommand for AccountSubcommand {
|
||||
}
|
||||
Self::New(new_subcommand) => new_subcommand.handle_subcommand(wallet_core).await,
|
||||
Self::SyncPrivate => {
|
||||
let curr_last_block = wallet_core
|
||||
.sequencer_client
|
||||
.get_last_block()
|
||||
.await?
|
||||
.last_block;
|
||||
let curr_last_block = wallet_core.sequencer_client.get_last_block_id().await?;
|
||||
|
||||
if wallet_core
|
||||
.storage
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user