From be94e133fa773c5ccc373ceb937a38d75fec1769 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Thu, 12 Mar 2026 15:43:18 +0300 Subject: [PATCH] feat: refactor sequencer RPC server-side --- Cargo.lock | 411 ++------- Cargo.toml | 10 +- common/src/block.rs | 12 +- common/src/error.rs | 43 - common/src/lib.rs | 3 - common/src/rpc_primitives/errors.rs | 194 ----- common/src/rpc_primitives/message.rs | 588 ------------- common/src/rpc_primitives/mod.rs | 57 -- common/src/rpc_primitives/parser.rs | 29 - common/src/rpc_primitives/requests.rs | 219 ----- common/src/sequencer_client.rs | 361 -------- common/src/transaction.rs | 4 +- indexer/service/Cargo.toml | 1 - indexer/service/protocol/src/convert.rs | 31 +- indexer/service/protocol/src/lib.rs | 2 +- indexer/service/src/lib.rs | 8 +- indexer/service/src/mock_service.rs | 11 +- indexer/service/src/service.rs | 4 +- nssa/Cargo.toml | 2 + nssa/src/base64.rs | 14 + nssa/src/lib.rs | 1 + .../privacy_preserving_transaction/circuit.rs | 5 +- .../privacy_preserving_transaction/message.rs | 5 +- .../transaction.rs | 3 +- .../witness_set.rs | 3 +- .../program_deployment_transaction/message.rs | 4 +- .../transaction.rs | 3 +- nssa/src/public_transaction/message.rs | 4 +- nssa/src/public_transaction/transaction.rs | 3 +- nssa/src/public_transaction/witness_set.rs | 3 +- nssa/src/signature/mod.rs | 23 +- nssa/src/signature/private_key.rs | 28 +- nssa/src/signature/public_key.rs | 23 +- sequencer/core/src/block_store.rs | 6 +- sequencer/core/src/config.rs | 4 - sequencer/core/src/lib.rs | 3 +- sequencer/service/Cargo.toml | 13 +- sequencer/service/rpc/Cargo.toml | 32 +- sequencer/service/rpc/src/lib.rs | 111 ++- sequencer/service/rpc/src/net_utils.rs | 104 --- sequencer/service/rpc/src/process.rs | 786 ------------------ sequencer/service/rpc/src/types/err_rpc.rs | 49 -- sequencer/service/rpc/src/types/mod.rs | 1 - sequencer/service/src/lib.rs | 161 ++-- sequencer/service/src/main.rs | 70 +- sequencer/service/src/service.rs | 202 +++++ storage/src/error.rs | 7 + storage/src/indexer.rs | 32 +- storage/src/sequencer.rs | 24 +- wallet/configs/debug/wallet_config.json | 70 +- wallet/src/lib.rs | 16 + 51 files changed, 677 insertions(+), 3126 deletions(-) delete mode 100644 common/src/error.rs delete mode 100644 common/src/rpc_primitives/errors.rs delete mode 100644 common/src/rpc_primitives/message.rs delete mode 100644 common/src/rpc_primitives/mod.rs delete mode 100644 common/src/rpc_primitives/parser.rs delete mode 100644 common/src/rpc_primitives/requests.rs delete mode 100644 common/src/sequencer_client.rs create mode 100644 nssa/src/base64.rs delete mode 100644 sequencer/service/rpc/src/net_utils.rs delete mode 100644 sequencer/service/rpc/src/process.rs delete mode 100644 sequencer/service/rpc/src/types/err_rpc.rs delete mode 100644 sequencer/service/rpc/src/types/mod.rs create mode 100644 sequencer/service/src/service.rs diff --git a/Cargo.lock b/Cargo.lock index d8b51cbc..1695180d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" @@ -1877,15 +1645,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 +1751,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 +2047,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 +2848,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http 1.4.0", + "http", "js-sys", "pin-project", "serde", @@ -3163,7 +2912,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http", "indexmap 2.13.0", "slab", "tokio", @@ -3318,17 +3067,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 +3084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -3357,7 +3095,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http", "http-body", "pin-project-lite", ] @@ -3432,7 +3170,7 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http 1.4.0", + "http", "http-body", "httparse", "httpdate", @@ -3465,7 +3203,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 +3254,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 +3422,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 +3457,6 @@ version = "0.1.0" dependencies = [ "anyhow", "arc-swap", - "async-trait", "clap", "env_logger", "futures", @@ -4048,7 +3779,7 @@ dependencies = [ "futures-channel", "futures-util", "gloo-net", - "http 1.4.0", + "http", "jsonrpsee-core", "pin-project", "rustls", @@ -4073,7 +3804,7 @@ dependencies = [ "bytes", "futures-timer", "futures-util", - "http 1.4.0", + "http", "http-body", "http-body-util", "jsonrpsee-types", @@ -4134,7 +3865,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 +3891,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 +3915,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 +3969,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 +4345,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 +5103,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", - "log", "wasi", "windows-sys 0.61.2", ] @@ -5398,7 +5116,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.4.0", + "http", "httparse", "memchr", "mime", @@ -5533,6 +5251,7 @@ name = "nssa" version = "0.1.0" dependencies = [ "anyhow", + "base64 0.22.1", "borsh", "env_logger", "hex", @@ -5545,6 +5264,7 @@ dependencies = [ "risc0-zkvm", "secp256k1", "serde", + "serde_with", "sha2", "test-case", "test_program_methods", @@ -6270,7 +5990,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2", "thiserror 2.0.18", "tokio", "tracing", @@ -6307,7 +6027,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2", "tracing", "windows-sys 0.60.2", ] @@ -6581,12 +6301,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 +6320,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 1.4.0", + "http", "http-body", "http-body-util", "hyper", @@ -7452,48 +7166,39 @@ dependencies = [ "url", ] -[[package]] -name = "sequencer_rpc" -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_service" version = "0.1.0" dependencies = [ - "actix", - "actix-web", "anyhow", + "borsh", + "bytesize", "clap", "common", "env_logger", "futures", "indexer_service_rpc", + "jsonrpsee", "log", + "mempool", + "nssa", + "nssa_core", "sequencer_core", - "sequencer_rpc", + "sequencer_service_rpc", "tokio", + "tokio-util", +] + +[[package]] +name = "sequencer_service_rpc" +version = "0.1.0" +dependencies = [ + "common", + "jsonrpsee", + "nssa", + "nssa_core", + "schemars 1.2.1", + "serde_json", ] [[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", ] diff --git a/Cargo.toml b/Cargo.toml index 78d5adb7..abe84745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,9 @@ members = [ "programs/amm", "programs/token/core", "programs/token", - "sequencer_core", - "sequencer_rpc", - "sequencer_service", + "sequencer/core", + "sequencer/service", + "sequencer/service/rpc", "indexer/core", "indexer/service", "indexer/service/protocol", @@ -42,8 +42,8 @@ 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_core = { path = "sequencer/core" } +sequencer_service_rpc = { path = "sequencer/service/rpc" } sequencer_service = { path = "sequencer/service" } indexer_core = { path = "indexer/core" } indexer_service = { path = "indexer/service" } diff --git a/common/src/block.rs b/common/src/block.rs index 8ef2eb0c..7759333a 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -10,7 +10,7 @@ pub type BlockHash = HashType; pub type BlockId = u64; pub type TimeStamp = u64; -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct BlockMeta { pub id: BlockId, pub hash: BlockHash, @@ -31,7 +31,7 @@ impl OwnHasher { } } -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct BlockHeader { pub block_id: BlockId, pub prev_block_hash: BlockHash, @@ -40,19 +40,19 @@ pub struct BlockHeader { pub signature: nssa::Signature, } -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct BlockBody { pub transactions: Vec, } -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub enum BedrockStatus { Pending, Safe, Finalized, } -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct Block { pub header: BlockHeader, pub body: BlockBody, @@ -60,7 +60,7 @@ pub struct Block { pub bedrock_parent_id: MantleMsgId, } -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct HashableBlockData { pub block_id: BlockId, pub prev_block_hash: BlockHash, diff --git a/common/src/error.rs b/common/src/error.rs deleted file mode 100644 index 1e348a32..00000000 --- a/common/src/error.rs +++ /dev/null @@ -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 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), -} diff --git a/common/src/lib.rs b/common/src/lib.rs index da07a602..57d70b64 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -5,9 +5,6 @@ use serde_with::{DeserializeFromStr, SerializeDisplay}; pub mod block; pub mod config; -pub mod error; -pub mod rpc_primitives; -pub mod sequencer_client; pub mod transaction; // Module for tests utility functions diff --git a/common/src/rpc_primitives/errors.rs b/common/src/rpc_primitives/errors.rs deleted file mode 100644 index 28ec0b63..00000000 --- a/common/src/rpc_primitives/errors.rs +++ /dev/null @@ -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, - /// 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, -} - -#[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) -> 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: Option) -> 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, 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, 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, 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 for RpcError { - fn from(parse_error: RpcParseError) -> Self { - Self::parse_error(parse_error.0) - } -} - -impl From 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 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()) - } -} diff --git a/common/src/rpc_primitives/message.rs b/common/src/rpc_primitives/message.rs deleted file mode 100644 index de7f132e..00000000 --- a/common/src/rpc_primitives/message.rs +++ /dev/null @@ -1,588 +0,0 @@ -// Copyright 2017 tokio-jsonrpc Developers -// -// Licensed under the Apache License, Version 2.0, or the MIT license , 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; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct Version; - -impl serde::Serialize for Version { - fn serialize(&self, serializer: S) -> Result { - 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>(deserializer: D) -> Result { - 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(self, value: &str) -> Result { - 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, - pub id: Value, -} - -impl serde::Serialize for Response { - fn serialize(&self, serializer: S) -> Result { - 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, - error: Option, - 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>(deserializer: D) -> Result { - 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), - /// 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) -> 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 for String { - fn from(val: Message) -> Self { - ::serde_json::ser::to_string(&val).expect("message serialization to json should not fail") - } -} - -impl From for Vec { - 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) -> 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` 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, 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"); - } - } -} diff --git a/common/src/rpc_primitives/mod.rs b/common/src/rpc_primitives/mod.rs deleted file mode 100644 index cd643712..00000000 --- a/common/src/rpc_primitives/mod.rs +++ /dev/null @@ -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, - #[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() - } - } -} diff --git a/common/src/rpc_primitives/parser.rs b/common/src/rpc_primitives/parser.rs deleted file mode 100644 index 0b918c94..00000000 --- a/common/src/rpc_primitives/parser.rs +++ /dev/null @@ -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) -> Result { - parse_params::(value) - } - } - }; -} - -pub trait RpcRequest: Sized { - fn parse(value: Option) -> Result; -} - -pub fn parse_params(value: Option) -> Result { - 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}"))) - }, - ) -} diff --git a/common/src/rpc_primitives/requests.rs b/common/src/rpc_primitives/requests.rs deleted file mode 100644 index fd566c89..00000000 --- a/common/src/rpc_primitives/requests.rs +++ /dev/null @@ -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(bytes_vec: &[Vec], serializer: S) -> Result - 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>, D::Error> - where - D: Deserializer<'de>, - { - let base64_strings: Vec = Deserialize::deserialize(deserializer)?; - base64_strings - .into_iter() - .map(|s| { - general_purpose::STANDARD - .decode(&s) - .map_err(serde::de::Error::custom) - }) - .collect() - } - } - - pub fn serialize(bytes: &[u8], serializer: S) -> Result - where - S: Serializer, - { - let base64_string = general_purpose::STANDARD.encode(bytes); - serializer.serialize_str(&base64_string) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, 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, -} - -#[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, -} - -#[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, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GetBlockRangeDataResponse { - #[serde(with = "base64_deser::vec")] - pub blocks: Vec>, -} - -#[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, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GetTransactionByHashResponse { - pub transaction: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GetAccountResponse { - pub account: nssa::Account, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GetProofForCommitmentResponse { - pub membership_proof: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GetProgramIdsResponse { - pub program_ids: HashMap, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct GetInitialTestnetAccountsResponse { - /// Hex encoded account id. - pub account_id: String, - pub balance: u64, -} diff --git a/common/src/sequencer_client.rs b/common/src/sequencer_client.rs deleted file mode 100644 index d52e4585..00000000 --- a/common/src/sequencer_client.rs +++ /dev/null @@ -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, -} - -impl SequencerClient { - pub fn new(sequencer_addr: Url) -> Result { - Self::new_with_auth(sequencer_addr, None) - } - - pub fn new_with_auth(sequencer_addr: Url, basic_auth: Option) -> Result { - 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 { - 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::().await, - } - }) - .await?; - - if let Ok(response) = serde_json::from_value::(response_vall.clone()) - { - Ok(response.result) - } else { - let err_resp = serde_json::from_value::(response_vall)?; - - Err(err_resp.into()) - } - } - - /// Get block data at `block_id` from sequencer. - pub async fn get_block( - &self, - block_id: u64, - ) -> Result { - 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, - ) -> Result { - 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 { - 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 { - 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, - ) -> Result { - 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 { - 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 { - 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 { - 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 { - 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 { - 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, 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, 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::(resp) - .unwrap() - .membership_proof; - - Ok(resp_deser) - } - - pub async fn send_tx_program( - &self, - transaction: nssa::ProgramDeploymentTransaction, - ) -> Result { - 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, 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::(resp) - .unwrap() - .program_ids; - - Ok(resp_deser) - } -} diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 8fdc2074..e11b91c3 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use crate::HashType; -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub enum NSSATransaction { Public(nssa::PublicTransaction), PrivacyPreserving(nssa::PrivacyPreservingTransaction), @@ -87,7 +87,7 @@ impl From 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, diff --git a/indexer/service/Cargo.toml b/indexer/service/Cargo.toml index 911121fd..a07a2285 100644 --- a/indexer/service/Cargo.toml +++ b/indexer/service/Cargo.toml @@ -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] diff --git a/indexer/service/protocol/src/convert.rs b/indexer/service/protocol/src/convert.rs index 499baa4c..dd01e28a 100644 --- a/indexer/service/protocol/src/convert.rs +++ b/indexer/service/protocol/src/convert.rs @@ -359,12 +359,16 @@ impl From for nssa::program_deployment_transaction::Me // WitnessSet conversions // ============================================================================ -impl TryFrom for WitnessSet { - type Error = (); - - fn try_from(_value: nssa::public_transaction::WitnessSet) -> Result { - // Public transaction witness sets don't have proofs, so we can't convert them directly - Err(()) +impl From 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 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 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_string()))?, )) } } @@ -416,14 +422,7 @@ impl From 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(), } } } diff --git a/indexer/service/protocol/src/lib.rs b/indexer/service/protocol/src/lib.rs index 98ef5650..d61f62a6 100644 --- a/indexer/service/protocol/src/lib.rs +++ b/indexer/service/protocol/src/lib.rs @@ -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, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] diff --git a/indexer/service/src/lib.rs b/indexer/service/src/lib.rs index 1f87e929..48e4f876 100644 --- a/indexer/service/src/lib.rs +++ b/indexer/service/src/lib.rs @@ -13,6 +13,7 @@ pub mod mock_service; pub struct IndexerHandle { addr: SocketAddr, + /// Option because of `Drop` which forbids to simply move out of `self` in `stopped()`. server_handle: Option, } impl IndexerHandle { @@ -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 { self.server_handle .as_ref() - .is_none_or(|handle| handle.is_stopped()) + .is_none_or(ServerHandle::is_stopped) } } diff --git a/indexer/service/src/mock_service.rs b/indexer/service/src/mock_service.rs index bc131740..b52123bc 100644 --- a/indexer/service/src/mock_service.rs +++ b/indexer/service/src/mock_service.rs @@ -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, diff --git a/indexer/service/src/service.rs b/indexer/service/src/service.rs index 256ef33d..049d4a0c 100644 --- a/indexer/service/src/service.rs +++ b/indexer/service/src/service.rs @@ -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, diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index b50f189b..89dae607 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -14,10 +14,12 @@ 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 hex.workspace = true +base64.workspace = true secp256k1 = "0.31.1" risc0-binfmt = "3.0.2" log.workspace = true diff --git a/nssa/src/base64.rs b/nssa/src/base64.rs new file mode 100644 index 00000000..cc782e9c --- /dev/null +++ b/nssa/src/base64.rs @@ -0,0 +1,14 @@ +use base64::prelude::{BASE64_STANDARD, Engine as _}; +use serde::{Deserialize as _, Deserializer, Serialize as _, Serializer}; + +pub fn serialize(v: &[u8], s: S) -> Result { + let base64 = BASE64_STANDARD.encode(v); + String::serialize(&base64, s) +} + +pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { + let base64 = String::deserialize(d)?; + BASE64_STANDARD + .decode(base64.as_bytes()) + .map_err(serde::de::Error::custom) +} diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index bc7cf121..973433ee 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -18,6 +18,7 @@ pub use public_transaction::PublicTransaction; pub use signature::{PrivateKey, PublicKey, Signature}; pub use state::V02State; +mod base64; pub mod encoding; pub mod error; mod merkle_tree; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 2ab141a3..9f0bf83f 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -8,6 +8,7 @@ use nssa_core::{ program::{ChainedCall, InstructionData, ProgramId, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, ProverOpts, Receipt, default_prover}; +use serde::{Deserialize, Serialize}; use crate::{ error::NssaError, @@ -17,8 +18,8 @@ use crate::{ }; /// Proof of the privacy preserving execution circuit. -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct Proof(pub(crate) Vec); +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +pub struct Proof(#[serde(with = "crate::base64")] pub(crate) Vec); impl Proof { #[must_use] diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 4b93e820..a2d6812e 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -4,13 +4,14 @@ use nssa_core::{ account::{Account, Nonce}, encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey}, }; +use serde::{Deserialize, Serialize}; use sha2::{Digest as _, Sha256}; use crate::{AccountId, error::NssaError}; pub type ViewTag = u8; -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct EncryptedAccountData { pub ciphertext: Ciphertext, pub epk: EphemeralPublicKey, @@ -44,7 +45,7 @@ impl EncryptedAccountData { } } -#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct Message { pub public_account_ids: Vec, pub nonces: Vec, diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 2b268c07..012b3bd5 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -8,6 +8,7 @@ use nssa_core::{ Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput, account::{Account, AccountWithMetadata}, }; +use serde::{Deserialize, Serialize}; use sha2::{Digest as _, digest::FixedOutput as _}; use super::{message::Message, witness_set::WitnessSet}; @@ -17,7 +18,7 @@ use crate::{ privacy_preserving_transaction::{circuit::Proof, message::EncryptedAccountData}, }; -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct PrivacyPreservingTransaction { pub message: Message, pub witness_set: WitnessSet, diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 373bbc9c..4f570a22 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -1,11 +1,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; use crate::{ PrivateKey, PublicKey, Signature, privacy_preserving_transaction::{circuit::Proof, message::Message}, }; -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct WitnessSet { pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>, pub(crate) proof: Proof, diff --git a/nssa/src/program_deployment_transaction/message.rs b/nssa/src/program_deployment_transaction/message.rs index a51e4149..d4758ae2 100644 --- a/nssa/src/program_deployment_transaction/message.rs +++ b/nssa/src/program_deployment_transaction/message.rs @@ -1,7 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct Message { + #[serde(with = "crate::base64")] pub(crate) bytecode: Vec, } diff --git a/nssa/src/program_deployment_transaction/transaction.rs b/nssa/src/program_deployment_transaction/transaction.rs index 1e53388d..939d81be 100644 --- a/nssa/src/program_deployment_transaction/transaction.rs +++ b/nssa/src/program_deployment_transaction/transaction.rs @@ -1,12 +1,13 @@ use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::account::AccountId; +use serde::{Deserialize, Serialize}; use sha2::{Digest as _, digest::FixedOutput as _}; use crate::{ V02State, error::NssaError, program::Program, program_deployment_transaction::message::Message, }; -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct ProgramDeploymentTransaction { pub message: Message, } diff --git a/nssa/src/public_transaction/message.rs b/nssa/src/public_transaction/message.rs index d4838b87..e3651ba3 100644 --- a/nssa/src/public_transaction/message.rs +++ b/nssa/src/public_transaction/message.rs @@ -3,11 +3,11 @@ use nssa_core::{ account::Nonce, program::{InstructionData, ProgramId}, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use crate::{AccountId, error::NssaError, program::Program}; -#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct Message { pub program_id: ProgramId, pub account_ids: Vec, diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 8c84d83c..00d29a05 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -6,6 +6,7 @@ use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution}, }; +use serde::{Deserialize, Serialize}; use sha2::{Digest as _, digest::FixedOutput as _}; use crate::{ @@ -15,7 +16,7 @@ use crate::{ state::MAX_NUMBER_CHAINED_CALLS, }; -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct PublicTransaction { pub message: Message, pub witness_set: WitnessSet, diff --git a/nssa/src/public_transaction/witness_set.rs b/nssa/src/public_transaction/witness_set.rs index d6b32891..e796fbfe 100644 --- a/nssa/src/public_transaction/witness_set.rs +++ b/nssa/src/public_transaction/witness_set.rs @@ -1,8 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; use crate::{PrivateKey, PublicKey, Signature, public_transaction::Message}; -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct WitnessSet { pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>, } diff --git a/nssa/src/signature/mod.rs b/nssa/src/signature/mod.rs index 63377f15..f0e5a863 100644 --- a/nssa/src/signature/mod.rs +++ b/nssa/src/signature/mod.rs @@ -1,22 +1,43 @@ +use std::str::FromStr; + use borsh::{BorshDeserialize, BorshSerialize}; pub use private_key::PrivateKey; pub use public_key::PublicKey; use rand::{RngCore as _, rngs::OsRng}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; mod private_key; mod public_key; -#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, PartialEq, Eq, SerializeDisplay, DeserializeFromStr, BorshSerialize, BorshDeserialize, +)] pub struct Signature { pub value: [u8; 64], } 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 { + 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 { diff --git a/nssa/src/signature/private_key.rs b/nssa/src/signature/private_key.rs index d8ece0e0..e73e0e4f 100644 --- a/nssa/src/signature/private_key.rs +++ b/nssa/src/signature/private_key.rs @@ -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 { + 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 { diff --git a/nssa/src/signature/public_key.rs b/nssa/src/signature/public_key.rs index 9cdac761..ee0f5dbc 100644 --- a/nssa/src/signature/public_key.rs +++ b/nssa/src/signature/public_key.rs @@ -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 { + 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(reader: &mut R) -> std::io::Result { let mut buf = [0_u8; 32]; diff --git a/sequencer/core/src/block_store.rs b/sequencer/core/src/block_store.rs index eb541188..51355f5f 100644 --- a/sequencer/core/src/block_store.rs +++ b/sequencer/core/src/block_store.rs @@ -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 { - Ok(self.dbio.get_block(id)?) + pub fn get_block_at_id(&self, id: u64) -> Result { + self.dbio.get_block(id) } pub fn delete_block_at_id(&mut self, block_id: u64) -> Result<()> { diff --git a/sequencer/core/src/config.rs b/sequencer/core/src/config.rs index 097d1391..9dd10680 100644 --- a/sequencer/core/src/config.rs +++ b/sequencer/core/src/config.rs @@ -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, /// 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, /// List of initial commitments. diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index c844c193..7f58faf4 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -27,6 +27,8 @@ pub mod block_store; pub mod config; pub mod indexer_client; +pub use storage::error::DbError; + #[cfg(feature = "mock")] pub mod mock; @@ -394,7 +396,6 @@ mod tests { 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(), diff --git a/sequencer/service/Cargo.toml b/sequencer/service/Cargo.toml index 235b4d71..3efb380d 100644 --- a/sequencer/service/Cargo.toml +++ b/sequencer/service/Cargo.toml @@ -9,20 +9,25 @@ workspace = true [dependencies] common.workspace = true +nssa.workspace = true +nssa_core.workspace = true +mempool.workspace = true +borsh.workspace = true sequencer_core = { workspace = true, features = ["testnet"] } -sequencer_rpc.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 [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"] diff --git a/sequencer/service/rpc/Cargo.toml b/sequencer/service/rpc/Cargo.toml index 5c76ba34..1ced7997 100644 --- a/sequencer/service/rpc/Cargo.toml +++ b/sequencer/service/rpc/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sequencer_rpc" +name = "sequencer_service_rpc" version = "0.1.0" edition = "2024" license = { workspace = true } @@ -8,32 +8,14 @@ license = { workspace = true } workspace = true [dependencies] -nssa.workspace = true common.workspace = true -mempool.workspace = true -sequencer_core = { workspace = true } -bedrock_client.workspace = true +nssa.workspace = true +nssa_core.workspace = true -anyhow.workspace = true +jsonrpsee = { workspace = true, features = ["macros"] } 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"] } +schemars.workspace = true [features] -default = [] -# Includes types to run the sequencer in standalone mode -standalone = ["sequencer_core/mock"] +client = ["jsonrpsee/client"] +server = ["jsonrpsee/server"] diff --git a/sequencer/service/rpc/src/lib.rs b/sequencer/service/rpc/src/lib.rs index 47e4fa75..0ff68bfe 100644 --- a/sequencer/service/rpc/src/lib.rs +++ b/sequencer/service/rpc/src/lib.rs @@ -1,55 +1,74 @@ -use std::sync::Arc; +use std::collections::BTreeMap; use common::{ - rpc_primitives::errors::{RpcError, RpcErrorKind}, + HashType, + block::{Block, BlockId}, 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 jsonrpsee::proc_macros::rpc; +#[cfg(feature = "server")] +use jsonrpsee::types::ErrorObjectOwned; +use nssa::{Account, AccountId, ProgramId}; +use nssa_core::{Commitment, MembershipProof, account::Nonce}; -use self::types::err_rpc::RpcErr; +#[cfg(all(not(feature = "server"), not(feature = "client")))] +compile_error!("At least one of `server` or `client` features must be enabled."); -pub mod net_utils; -pub mod process; -pub mod types; +#[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; -#[cfg(feature = "standalone")] -pub type JsonHandlerWithMockClients = JsonHandler; + // TODO: expand healthcheck response into some kind of report + #[method(name = "checkHealth")] + async fn check_health(&self) -> Result<(), ErrorObjectOwned>; -// ToDo: Add necessary fields -pub struct JsonHandler< - BC: BlockSettlementClientTrait = BlockSettlementClient, - IC: IndexerClientTrait = IndexerClient, -> { - sequencer_state: Arc>>, - mempool_handle: MemPoolHandle, - max_block_size: usize, -} - -fn respond(val: T) -> Result { - 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, - } + // TODO: These functions should be removed after wallet starts using indexer + // for this type of queries. + // + // ============================================================================================= + + #[method(name = "getBlockData")] + async fn get_block_data(&self, block_id: BlockId) -> Result; + + #[method(name = "getBlockRangeData")] + async fn get_block_range_data( + &self, + start_block_id: BlockId, + end_block_id: BlockId, + ) -> Result, ErrorObjectOwned>; + + #[method(name = "getLastBlockId")] + async fn get_last_block_id(&self) -> Result; + + #[method(name = "getAccountBalance")] + async fn get_account_balance(&self, account_id: AccountId) -> Result; + + #[method(name = "getTransactionByHash")] + async fn get_transaction_by_hash( + &self, + hash: HashType, + ) -> Result; + + #[method(name = "getAccountsNonces")] + async fn get_accounts_nonces( + &self, + account_ids: Vec, + ) -> Result, ErrorObjectOwned>; + + #[method(name = "getProofForCommitment")] + async fn get_proof_for_commitment( + &self, + commitment: Commitment, + ) -> Result; + + #[method(name = "getAccount")] + async fn get_account(&self, account_id: AccountId) -> Result; + + #[method(name = "getProgramIds")] + async fn get_program_ids(&self) -> Result, ErrorObjectOwned>; + + // ============================================================================================= } diff --git a/sequencer/service/rpc/src/net_utils.rs b/sequencer/service/rpc/src/net_utils.rs deleted file mode 100644 index e306ec0e..00000000 --- a/sequencer/service/rpc/src/net_utils.rs +++ /dev/null @@ -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( - message: web::Json, - handler: web::Data

, -) -> impl Future> { - 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>, - mempool_handle: MemPoolHandle, -) -> 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::))) - }) - .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)) -} diff --git a/sequencer/service/rpc/src/process.rs b/sequencer/service/rpc/src/process.rs deleted file mode 100644 index 17c46f03..00000000 --- a/sequencer/service/rpc/src/process.rs +++ /dev/null @@ -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> + Send; -} - -impl< - BC: BlockSettlementClientTrait + Send + Sync + 'static, - IC: IndexerClientTrait + Send + Sync + 'static, -> Process for JsonHandler -{ - async fn process(&self, message: Message) -> Result { - 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 JsonHandler { - /// Example of request processing. - fn process_temp_hello(request: Request) -> Result { - 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 { - // 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::(&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 { - 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 { - 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::, _>>()? - }; - - let response = GetBlockRangeDataResponse { blocks }; - - respond(response) - } - - async fn process_get_genesis(&self, request: Request) -> Result { - 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 { - 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 { - let _get_initial_testnet_accounts_request = - GetInitialTestnetAccountsRequest::parse(Some(request.params))?; - - let initial_accounts: Vec = { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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; - - fn sequencer_config_for_tests() -> SequencerConfig { - let tempdir = tempdir().unwrap(); - let home = tempdir.path().to_path_buf(); - let acc1_id: Vec = 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 = 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, - 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::), - )) - .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); - } -} diff --git a/sequencer/service/rpc/src/types/err_rpc.rs b/sequencer/service/rpc/src/types/err_rpc.rs deleted file mode 100644 index 4cb75606..00000000 --- a/sequencer/service/rpc/src/types/err_rpc.rs +++ /dev/null @@ -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 From 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())) - } -} diff --git a/sequencer/service/rpc/src/types/mod.rs b/sequencer/service/rpc/src/types/mod.rs deleted file mode 100644 index 0b78fea1..00000000 --- a/sequencer/service/rpc/src/types/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod err_rpc; diff --git a/sequencer/service/src/lib.rs b/sequencer/service/src/lib.rs index a17ecbf9..547f58d2 100644 --- a/sequencer/service/src/lib.rs +++ b/sequencer/service/src/lib.rs @@ -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, main_loop_handle: JoinHandle>, retry_pending_blocks_loop_handle: JoinHandle>, listen_for_bedrock_blocks_loop_handle: JoinHandle>, } impl SequencerHandle { - /// Runs the sequencer indefinitely, monitoring its tasks. - /// - /// If no error occurs, this function will never return. + fn new( + addr: SocketAddr, + server_handle: ServerHandle, + main_loop_handle: JoinHandle>, + retry_pending_blocks_loop_handle: JoinHandle>, + listen_for_bedrock_blocks_loop_handle: JoinHandle>, + ) -> Self { + Self { + addr, + server_handle: Some(server_handle), + main_loop_handle, + retry_pending_blocks_loop_handle, + listen_for_bedrock_blocks_loop_handle, + } + } + + /// Wait for all Sequencer tasks to stop. #[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 { + pub async fn stopped(mut self) -> Result { 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")? @@ -89,7 +105,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 +115,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 { - 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 { + 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 +166,42 @@ pub async fn startup_sequencer(app_config: SequencerConfig) -> Result>, + mempool_handle: MemPoolHandle, + 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>, block_timeout: Duration) -> Result { @@ -210,7 +259,7 @@ async fn retry_pending_blocks(seq_core: &Arc>) -> 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 +271,7 @@ async fn retry_pending_blocks(seq_core: &Arc>) -> Result<() block.header.block_id ); } - debug!(">>>> Post: {:?}", now.elapsed()); + debug!("Post: {:?}", now.elapsed()); } Ok(()) } @@ -287,33 +336,3 @@ async fn retry_pending_blocks_loop( ) -> Result { std::future::pending::>().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(()) -} diff --git a/sequencer/service/src/main.rs b/sequencer/service/src/main.rs index d0c51073..326ded70 100644 --- a/sequencer/service/src/main.rs +++ b/sequencer/service/src/main.rs @@ -1,16 +1,60 @@ +use std::path::PathBuf; + use anyhow::Result; -use sequencer_service::main_runner; +use clap::Parser; +use log::{error, info}; +use tokio_util::sync::CancellationToken; -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()) +#[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.stopped() => { + error!("Sequencer stopped 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 } diff --git a/sequencer/service/src/service.rs b/sequencer/service/src/service.rs new file mode 100644 index 00000000..d1b21f69 --- /dev/null +++ b/sequencer/service/src/service.rs @@ -0,0 +1,202 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use common::{ + HashType, + block::{Block, BlockId}, + transaction::NSSATransaction, +}; +use jsonrpsee::{ + core::async_trait, + types::{ErrorCode, ErrorObjectOwned}, +}; +use log::warn; +use mempool::MemPoolHandle; +use nssa::{self, program::Program}; +use nssa_core::{ + Commitment, MembershipProof, + account::{Account, AccountId, Nonce}, + program::ProgramId, +}; +use sequencer_core::{ + DbError, SequencerCore, block_settlement_client::BlockSettlementClientTrait, + indexer_client::IndexerClientTrait, +}; +use tokio::sync::Mutex; + +pub struct SequencerService { + sequencer: Arc>>, + mempool_handle: MemPoolHandle, + max_block_size: u64, +} + +impl SequencerService { + pub fn new( + sequencer: Arc>>, + mempool_handle: MemPoolHandle, + max_block_size: u64, + ) -> Self { + Self { + sequencer, + mempool_handle, + max_block_size, + } + } +} + +#[async_trait] +impl + sequencer_service_rpc::RpcServer for SequencerService +{ + async fn send_transaction(&self, tx: NSSATransaction) -> Result { + // Reserve ~200 bytes for block header overhead + const BLOCK_HEADER_OVERHEAD: u64 = 200; + + let tx_hash = tx.hash(); + + let tx_size = u64::try_from( + borsh::to_vec(&tx) + .expect("NSSATransaction BorshSerialize should never fail") + .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_data(&self, block_id: BlockId) -> Result { + let sequencer = self.sequencer.lock().await; + sequencer + .block_store() + .get_block_at_id(block_id) + .map_err(|err| db_error_to_rpc_error(&err)) + } + + async fn get_block_range_data( + &self, + start_block_id: BlockId, + end_block_id: BlockId, + ) -> Result, ErrorObjectOwned> { + let sequencer = self.sequencer.lock().await; + (start_block_id..=end_block_id) + .map(|block_id| sequencer.block_store().get_block_at_id(block_id)) + .collect::, _>>() + .map_err(|err| db_error_to_rpc_error(&err)) + } + + async fn get_last_block_id(&self) -> Result { + let sequencer = self.sequencer.lock().await; + Ok(sequencer.chain_height()) + } + + async fn get_account_balance(&self, account_id: AccountId) -> Result { + let sequencer = self.sequencer.lock().await; + let account = sequencer.state().get_account_by_id(account_id); + Ok(account.balance) + } + + async fn get_transaction_by_hash( + &self, + hash: HashType, + ) -> Result { + let sequencer = self.sequencer.lock().await; + sequencer + .block_store() + .get_transaction_by_hash(hash) + .ok_or_else(|| { + ErrorObjectOwned::owned(NOT_FOUND_ERROR_CODE, "Transaction not found", None::<()>) + }) + } + + async fn get_accounts_nonces( + &self, + account_ids: Vec, + ) -> Result, 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 { + let sequencer = self.sequencer.lock().await; + sequencer + .state() + .get_proof_for_commitment(&commitment) + .ok_or_else(|| { + ErrorObjectOwned::owned( + NOT_FOUND_ERROR_CODE, + "Proof for commitment not found", + None::<()>, + ) + }) + } + + async fn get_account(&self, account_id: AccountId) -> Result { + let sequencer = self.sequencer.lock().await; + Ok(sequencer.state().get_account_by_id(account_id)) + } + + async fn get_program_ids(&self) -> Result, 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) + } +} + +const NOT_FOUND_ERROR_CODE: i32 = -31999; + +fn db_error_to_rpc_error(err: &DbError) -> ErrorObjectOwned { + match err { + DbError::NotFound { entity } => ErrorObjectOwned::owned( + NOT_FOUND_ERROR_CODE, + format!("{entity} not found"), + None::<()>, + ), + _ => ErrorObjectOwned::owned(ErrorCode::InternalError.code(), err.to_string(), None::<()>), + } +} diff --git a/storage/src/error.rs b/storage/src/error.rs index 3056e09b..3e0f88ba 100644 --- a/storage/src/error.rs +++ b/storage/src/error.rs @@ -14,6 +14,8 @@ pub enum DbError { }, #[error("Logic Error: {additional_info}")] DbInteractionError { additional_info: String }, + #[error("{entity} not found")] + NotFound { entity: String }, } impl DbError { @@ -39,4 +41,9 @@ impl DbError { additional_info: message, } } + + #[must_use] + pub const fn not_found(entity: String) -> Self { + Self::NotFound { entity } + } } diff --git a/storage/src/indexer.rs b/storage/src/indexer.rs index 534a1c0b..c9b3b358 100644 --- a/storage/src/indexer.rs +++ b/storage/src/indexer.rs @@ -180,9 +180,7 @@ impl RocksDBIO { ) })?) } else { - Err(DbError::db_interaction_error( - "First block not found".to_owned(), - )) + Err(DbError::not_found("First block".to_owned())) } } @@ -209,9 +207,7 @@ impl RocksDBIO { ) })?) } else { - Err(DbError::db_interaction_error( - "Last block not found".to_owned(), - )) + Err(DbError::not_found("Last block".to_owned())) } } @@ -286,9 +282,7 @@ impl RocksDBIO { ) })?) } else { - Err(DbError::db_interaction_error( - "Last breakpoint id not found".to_owned(), - )) + Err(DbError::not_found("Last breakpoint id".to_owned())) } } @@ -537,9 +531,7 @@ impl RocksDBIO { ) })?) } else { - Err(DbError::db_interaction_error( - "Block on this id not found".to_owned(), - )) + Err(DbError::not_found(format!("Block with id {block_id}"))) } } @@ -618,7 +610,7 @@ impl RocksDBIO { .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None)) } - pub fn get_breakpoint(&self, br_id: u64) -> DbResult { + fn get_breakpoint(&self, br_id: u64) -> DbResult { let cf_br = self.breakpoint_column(); let res = self .db @@ -641,6 +633,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(), )) @@ -686,9 +680,7 @@ impl RocksDBIO { Ok(breakpoint) } else { - Err(DbError::db_interaction_error( - "Block on this id not found".to_owned(), - )) + Err(DbError::not_found(format!("Block with id {block_id}"))) } } @@ -740,9 +732,7 @@ impl RocksDBIO { 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(), - )) + Err(DbError::not_found("Block with given hash".to_owned())) } } @@ -766,9 +756,7 @@ impl RocksDBIO { 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(), - )) + Err(DbError::not_found("Block for given tx hash".to_owned())) } } diff --git a/storage/src/sequencer.rs b/storage/src/sequencer.rs index 8d072a52..3d0190d6 100644 --- a/storage/src/sequencer.rs +++ b/storage/src/sequencer.rs @@ -142,9 +142,7 @@ impl RocksDBIO { ) })?) } else { - Err(DbError::db_interaction_error( - "First block not found".to_owned(), - )) + Err(DbError::not_found("First block".to_owned())) } } @@ -171,9 +169,7 @@ impl RocksDBIO { ) })?) } else { - Err(DbError::db_interaction_error( - "Last block not found".to_owned(), - )) + Err(DbError::not_found("Last block".to_owned())) } } @@ -399,9 +395,7 @@ impl RocksDBIO { ) })?) } else { - Err(DbError::db_interaction_error( - "Latest block meta not found".to_owned(), - )) + Err(DbError::not_found("Latest block meta".to_owned())) } } @@ -465,9 +459,7 @@ impl RocksDBIO { ) })?) } else { - Err(DbError::db_interaction_error( - "Block on this id not found".to_owned(), - )) + Err(DbError::not_found(format!("Block with id {block_id}"))) } } @@ -494,9 +486,7 @@ impl RocksDBIO { ) })?) } else { - Err(DbError::db_interaction_error( - "Block on this id not found".to_owned(), - )) + Err(DbError::not_found("NSSA state".to_owned())) } } @@ -512,9 +502,7 @@ 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::not_found(format!("Block with id {block_id}"))); } self.db diff --git a/wallet/configs/debug/wallet_config.json b/wallet/configs/debug/wallet_config.json index 1835c88a..d279c0cb 100644 --- a/wallet/configs/debug/wallet_config.json +++ b/wallet/configs/debug/wallet_config.json @@ -9,79 +9,13 @@ { "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" } }, { diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 79729fc4..d873b357 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -51,6 +51,22 @@ pub enum AccDecodeData { Decode(nssa_core::SharedSecretKey, AccountId), } +#[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), +} + #[expect(clippy::partial_pub_fields, reason = "TODO: make all fields private")] pub struct WalletCore { config_path: PathBuf,