diff --git a/bolt-sidecar/Cargo.lock b/bolt-sidecar/Cargo.lock index 4813402bd..00a9f0c5c 100644 --- a/bolt-sidecar/Cargo.lock +++ b/bolt-sidecar/Cargo.lock @@ -132,16 +132,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59febb24956a41c29bb5f450978fbe825bd6456b3f80586c8bd558dc882e7b6a" dependencies = [ "alloy-consensus 0.8.0", + "alloy-contract 0.8.3", "alloy-core", "alloy-eips 0.8.0", "alloy-genesis 0.8.0", "alloy-network 0.8.0", "alloy-provider 0.8.0", + "alloy-pubsub 0.8.3", "alloy-rpc-client 0.8.0", "alloy-rpc-types 0.8.0", "alloy-serde 0.8.0", + "alloy-signer 0.8.0", + "alloy-signer-local 0.8.3", "alloy-transport 0.8.0", "alloy-transport-http 0.8.0", + "alloy-transport-ipc 0.8.3", + "alloy-transport-ws 0.8.3", ] [[package]] @@ -151,22 +157,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbcc41e8a11a4975b18ec6afba2cc48d591fa63336a4c526dacb50479a8d6b35" dependencies = [ "alloy-consensus 0.9.2", - "alloy-contract", + "alloy-contract 0.9.2", "alloy-core", "alloy-eips 0.9.2", "alloy-genesis 0.9.2", "alloy-network 0.9.2", "alloy-provider 0.9.2", - "alloy-pubsub", + "alloy-pubsub 0.9.2", "alloy-rpc-client 0.9.2", "alloy-rpc-types 0.9.2", "alloy-serde 0.9.2", "alloy-signer 0.9.2", - "alloy-signer-local", + "alloy-signer-local 0.9.2", "alloy-transport 0.9.2", "alloy-transport-http 0.9.2", - "alloy-transport-ipc", - "alloy-transport-ws", + "alloy-transport-ipc 0.9.2", + "alloy-transport-ws 0.9.2", ] [[package]] @@ -195,6 +201,7 @@ dependencies = [ "auto_impl", "c-kzg", "derive_more 1.0.0", + "k256 0.13.4", "serde", ] @@ -246,6 +253,27 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-contract" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b668c78c4b1f12f474ede5a85e8ce550d0aa1ef7d49fd1d22855a43b960e725" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network 0.8.0", + "alloy-network-primitives 0.8.0", + "alloy-primitives 0.8.15", + "alloy-provider 0.8.0", + "alloy-pubsub 0.8.3", + "alloy-rpc-types-eth 0.8.0", + "alloy-sol-types", + "alloy-transport 0.8.0", + "futures", + "futures-util", + "thiserror 2.0.9", +] + [[package]] name = "alloy-contract" version = "0.9.2" @@ -258,7 +286,7 @@ dependencies = [ "alloy-network-primitives 0.9.2", "alloy-primitives 0.8.15", "alloy-provider 0.9.2", - "alloy-pubsub", + "alloy-pubsub 0.9.2", "alloy-rpc-types-eth 0.9.2", "alloy-sol-types", "alloy-transport 0.9.2", @@ -332,6 +360,7 @@ dependencies = [ "alloy-primitives 0.8.15", "alloy-rlp", "derive_more 1.0.0", + "k256 0.13.4", "serde", ] @@ -614,14 +643,17 @@ dependencies = [ "alloy-network 0.8.0", "alloy-network-primitives 0.8.0", "alloy-primitives 0.8.15", + "alloy-pubsub 0.8.3", "alloy-rpc-client 0.8.0", "alloy-rpc-types-eth 0.8.0", "alloy-transport 0.8.0", "alloy-transport-http 0.8.0", + "alloy-transport-ipc 0.8.3", + "alloy-transport-ws 0.8.3", "async-stream", "async-trait", "auto_impl", - "dashmap 6.1.0", + "dashmap", "futures", "futures-utils-wasm", "lru", @@ -651,19 +683,19 @@ dependencies = [ "alloy-network 0.9.2", "alloy-network-primitives 0.9.2", "alloy-primitives 0.8.15", - "alloy-pubsub", + "alloy-pubsub 0.9.2", "alloy-rpc-client 0.9.2", "alloy-rpc-types-engine 0.9.2", "alloy-rpc-types-eth 0.9.2", "alloy-rpc-types-trace", "alloy-transport 0.9.2", "alloy-transport-http 0.9.2", - "alloy-transport-ipc", - "alloy-transport-ws", + "alloy-transport-ipc 0.9.2", + "alloy-transport-ws 0.9.2", "async-stream", "async-trait", "auto_impl", - "dashmap 6.1.0", + "dashmap", "futures", "futures-utils-wasm", "lru", @@ -680,6 +712,25 @@ dependencies = [ "wasmtimer", ] +[[package]] +name = "alloy-pubsub" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695809e743628d54510c294ad17a4645bd9f465aeb0d20ee9ce9877c9712dc9c" +dependencies = [ + "alloy-json-rpc 0.8.0", + "alloy-primitives 0.8.15", + "alloy-transport 0.8.0", + "bimap", + "futures", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", +] + [[package]] name = "alloy-pubsub" version = "0.9.2" @@ -729,8 +780,11 @@ checksum = "5c6a0bd0ce5660ac48e4f3bb0c7c5c3a94db287a0be94971599d83928476cbcd" dependencies = [ "alloy-json-rpc 0.8.0", "alloy-primitives 0.8.15", + "alloy-pubsub 0.8.3", "alloy-transport 0.8.0", "alloy-transport-http 0.8.0", + "alloy-transport-ipc 0.8.3", + "alloy-transport-ws 0.8.3", "futures", "pin-project", "reqwest 0.12.9", @@ -752,11 +806,11 @@ checksum = "d06a292b37e182e514903ede6e623b9de96420e8109ce300da288a96d88b7e4b" dependencies = [ "alloy-json-rpc 0.9.2", "alloy-primitives 0.8.15", - "alloy-pubsub", + "alloy-pubsub 0.9.2", "alloy-transport 0.9.2", "alloy-transport-http 0.9.2", - "alloy-transport-ipc", - "alloy-transport-ws", + "alloy-transport-ipc 0.9.2", + "alloy-transport-ws 0.9.2", "futures", "pin-project", "reqwest 0.12.9", @@ -993,6 +1047,22 @@ dependencies = [ "thiserror 2.0.9", ] +[[package]] +name = "alloy-signer-local" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47fababf5a745133490cde927d48e50267f97d3d1209b9fc9f1d1d666964d172" +dependencies = [ + "alloy-consensus 0.8.0", + "alloy-network 0.8.0", + "alloy-primitives 0.8.15", + "alloy-signer 0.8.0", + "async-trait", + "k256 0.13.4", + "rand 0.8.5", + "thiserror 2.0.9", +] + [[package]] name = "alloy-signer-local" version = "0.9.2" @@ -1157,6 +1227,25 @@ dependencies = [ "url", ] +[[package]] +name = "alloy-transport-ipc" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a172a59d24706b26a79a837f86d51745cb26ca6f8524712acd0208a14cff95" +dependencies = [ + "alloy-json-rpc 0.8.0", + "alloy-pubsub 0.8.3", + "alloy-transport 0.8.0", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "alloy-transport-ipc" version = "0.9.2" @@ -1164,7 +1253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4da44bc9a5155ab599666d26decafcf12204b72a80eeaba7c5e234ee8ac205" dependencies = [ "alloy-json-rpc 0.9.2", - "alloy-pubsub", + "alloy-pubsub 0.9.2", "alloy-transport 0.9.2", "bytes", "futures", @@ -1176,13 +1265,31 @@ dependencies = [ "tracing", ] +[[package]] +name = "alloy-transport-ws" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba0e39d181d13c266dbb8ca54ed584a2c66d6e9279afca89c7a6b1825e98abb" +dependencies = [ + "alloy-pubsub 0.8.3", + "alloy-transport 0.8.0", + "futures", + "http 1.2.0", + "rustls 0.23.19", + "serde_json", + "tokio", + "tokio-tungstenite 0.24.0", + "tracing", + "ws_stream_wasm", +] + [[package]] name = "alloy-transport-ws" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58011745b2f17b334db40df9077d75b181f78360a5bc5c35519e15d4bfce15e2" dependencies = [ - "alloy-pubsub", + "alloy-pubsub 0.9.2", "alloy-transport 0.9.2", "futures", "http 1.2.0", @@ -1687,30 +1794,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-extra" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" -dependencies = [ - "axum 0.7.9", - "axum-core 0.4.5", - "bytes", - "fastrand 2.3.0", - "futures-util", - "headers", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "multer", - "pin-project-lite", - "serde", - "tower", - "tower-layer", - "tower-service", -] - [[package]] name = "axum-extra" version = "0.10.0" @@ -2021,7 +2104,7 @@ dependencies = [ "alloy-transport-http 0.9.2", "async-trait", "axum 0.8.1", - "axum-extra 0.10.0", + "axum-extra", "beacon-api-client", "bls 0.2.0 (git+https://github.com/sigp/lighthouse)", "blst", @@ -2029,7 +2112,6 @@ dependencies = [ "bytes", "cb-common", "clap", - "commit-boost", "criterion", "derive_more 1.0.0", "dotenvy", @@ -2161,26 +2243,10 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "cb-cli" -version = "0.4.0" -source = "git+https://github.com/Commit-Boost/commit-boost-client?rev=0f8f69b#0f8f69be3a27e8e095ae4f400afc51d266db790f" -dependencies = [ - "cb-common", - "clap", - "docker-compose-types", - "dotenvy", - "eyre", - "indexmap 2.7.0", - "serde", - "serde_json", - "serde_yaml 0.9.33", -] - [[package]] name = "cb-common" -version = "0.4.0" -source = "git+https://github.com/Commit-Boost/commit-boost-client?rev=0f8f69b#0f8f69be3a27e8e095ae4f400afc51d266db790f" +version = "0.5.0" +source = "git+https://github.com/Commit-Boost/commit-boost-client?tag=v0.5.0#704e9f19719211acfd1697fb8a083c2897aea1a9" dependencies = [ "aes 0.8.4", "alloy 0.8.3", @@ -2215,72 +2281,6 @@ dependencies = [ "url", ] -[[package]] -name = "cb-metrics" -version = "0.4.0" -source = "git+https://github.com/Commit-Boost/commit-boost-client?rev=0f8f69b#0f8f69be3a27e8e095ae4f400afc51d266db790f" -dependencies = [ - "axum 0.7.9", - "cb-common", - "eyre", - "prometheus", - "thiserror 1.0.69", - "tokio", - "tracing", -] - -[[package]] -name = "cb-pbs" -version = "0.4.0" -source = "git+https://github.com/Commit-Boost/commit-boost-client?rev=0f8f69b#0f8f69be3a27e8e095ae4f400afc51d266db790f" -dependencies = [ - "alloy 0.8.3", - "async-trait", - "axum 0.7.9", - "blst", - "cb-common", - "cb-metrics", - "dashmap 5.5.3", - "eyre", - "futures", - "lazy_static", - "parking_lot", - "prometheus", - "reqwest 0.12.9", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tracing", - "url", - "uuid 1.11.0", -] - -[[package]] -name = "cb-signer" -version = "0.4.0" -source = "git+https://github.com/Commit-Boost/commit-boost-client?rev=0f8f69b#0f8f69be3a27e8e095ae4f400afc51d266db790f" -dependencies = [ - "alloy 0.8.3", - "axum 0.7.9", - "axum-extra 0.9.6", - "bimap", - "blst", - "cb-common", - "cb-metrics", - "derive_more 1.0.0", - "eyre", - "headers", - "k256 0.13.4", - "lazy_static", - "prometheus", - "thiserror 1.0.69", - "tokio", - "tracing", - "tree_hash 0.8.0", - "tree_hash_derive", - "uuid 1.11.0", -] - [[package]] name = "cc" version = "1.2.3" @@ -2444,58 +2444,12 @@ dependencies = [ "cc", ] -[[package]] -name = "color-eyre" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" -[[package]] -name = "commit-boost" -version = "0.4.0" -source = "git+https://github.com/Commit-Boost/commit-boost-client?rev=0f8f69b#0f8f69be3a27e8e095ae4f400afc51d266db790f" -dependencies = [ - "cb-cli", - "cb-common", - "cb-metrics", - "cb-pbs", - "cb-signer", - "clap", - "color-eyre", - "eyre", - "tokio", - "tracing", - "tree_hash 0.8.0", - "tree_hash_derive", -] - [[package]] name = "compare_fields" version = "0.2.0" @@ -2940,19 +2894,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "dashmap" version = "6.1.0" @@ -3071,37 +3012,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling 0.20.10", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.90", -] - [[package]] name = "derive_more" version = "0.99.18" @@ -3253,18 +3163,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "docker-compose-types" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9213a368b9c0767c81ef9ced0f712cfd99d27d7de2a22a60e7ac9b1342c8a395" -dependencies = [ - "derive_builder", - "indexmap 2.7.0", - "serde", - "serde_yaml 0.9.33", -] - [[package]] name = "doctest-file" version = "1.0.0" @@ -5637,23 +5535,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 1.2.0", - "httparse", - "memchr", - "mime", - "spin 0.9.8", - "version_check", -] - [[package]] name = "multiaddr" version = "0.14.0" @@ -6035,12 +5916,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "pairing" version = "0.23.0" @@ -8672,16 +8547,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -9055,7 +8920,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom 0.2.15", - "rand 0.8.5", "serde", ] diff --git a/bolt-sidecar/Cargo.toml b/bolt-sidecar/Cargo.toml index fd1bc99f0..dc3cc571f 100644 --- a/bolt-sidecar/Cargo.toml +++ b/bolt-sidecar/Cargo.toml @@ -79,9 +79,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "fmt"] } metrics = "0.23" metrics-exporter-prometheus = { version = "0.15.3", features = ["http-listener"] } -# commit-boost -commit-boost = { git = "https://github.com/Commit-Boost/commit-boost-client", rev = "0f8f69b" } -cb-common = { git = "https://github.com/Commit-Boost/commit-boost-client", rev = "0f8f69b" } +cb-common = { git = "https://github.com/Commit-Boost/commit-boost-client", tag = "v0.5.0" } [dev-dependencies] alloy-node-bindings = "0.8.3" # must match alloy version @@ -116,4 +114,4 @@ unnecessary_self_imports = "warn" use_self = "warn" [features] -keystore-tests = [] +keystore-tests = [] diff --git a/bolt-sidecar/src/api/commitments/firewall/processor.rs b/bolt-sidecar/src/api/commitments/firewall/processor.rs index 8a81fc05c..e91b32cad 100644 --- a/bolt-sidecar/src/api/commitments/firewall/processor.rs +++ b/bolt-sidecar/src/api/commitments/firewall/processor.rs @@ -2,6 +2,7 @@ use futures::{ stream::{FuturesUnordered, SplitSink, SplitStream}, FutureExt, SinkExt, StreamExt, }; +use serde::Serialize; use serde_json::{json, Value}; use std::{collections::VecDeque, future::Future, pin::Pin, task::Poll}; use tokio::{ @@ -23,15 +24,16 @@ use crate::{ api::commitments::{ server::CommitmentEvent, spec::{ - CommitmentError, RejectionError, GET_METADATA_METHOD, GET_VERSION_METHOD, - REQUEST_INCLUSION_METHOD, + CommitmentError, GET_METADATA_METHOD, GET_VERSION_METHOD, REQUEST_INCLUSION_METHOD, }, }, common::BOLT_SIDECAR_VERSION, config::limits::LimitsOpts, primitives::{ commitment::SignedCommitment, - jsonrpc::{JsonResponse, JsonRpcRequestUuid}, + jsonrpc::{ + JsonRpcErrorResponse, JsonRpcRequestUuid, JsonRpcResponse, JsonRpcSuccessResponse, + }, misc::{Identified, IntoIdentified}, CommitmentRequest, InclusionRequest, }, @@ -258,15 +260,14 @@ impl CommitmentRequestProcessor { return; }; - let mut response = - JsonResponse { id: Some(Value::String(id.to_string())), ..Default::default() }; - - match result_commitment { - Ok(commitment) => response.result = json!(commitment), + let response: JsonRpcResponse = match result_commitment { + Ok(commitment) => JsonRpcSuccessResponse::new(json!(commitment)) + .with_id(Value::String(id.to_string())) + .into(), Err(e) => { - response.error = Some(e.into()); + JsonRpcErrorResponse::new(e.into()).with_id(Value::String(id.to_string())).into() } - } + }; let message = Message::Text(serde_json::to_string(&response).expect("to stringify response")); @@ -279,82 +280,82 @@ impl CommitmentRequestProcessor { let rpc_url = self.url.clone(); trace!(?rpc_url, text, "received text message from websocket connection"); - // Create the channel to send and receive the commitment response let (tx, rx) = oneshot::channel(); - let request = serde_json::from_str::(&text).map_err(|e| e.to_string()); - - // FIXME: still too nested, needs to be refactored. - let response = match request { - Err(e) => Err(e), - Ok(request) => { - let id = request.id; - - match request.method.as_str() { - GET_VERSION_METHOD => Ok(JsonResponse { - id: Some(Value::String(id.to_string())), - result: Value::String(BOLT_SIDECAR_VERSION.clone()), - ..Default::default() - }), - GET_METADATA_METHOD => Ok(JsonResponse { - id: Some(Value::String(id.to_string())), - result: serde_json::to_value(self.state.limits).expect("infallible"), - ..Default::default() - }), - REQUEST_INCLUSION_METHOD => { - // Parse the inclusion request from the parameters - let inclusion_request = serde_json::from_value::( - request.params.first().cloned().unwrap_or_default(), - ) - .map_err(RejectionError::Json) - .inspect_err(|err| error!(?err, "Failed to parse inclusion request")); - - match inclusion_request { - Err(e) => Ok(JsonResponse { - id: Some(Value::String(id.to_string())), - error: Some(e.into()), - ..Default::default() - }), - Ok(inclusion_request) => { - let commitment_request = - CommitmentRequest::Inclusion(inclusion_request); - - let commitment_event = - CommitmentEvent { request: commitment_request, response: tx }; - - if let Err(e) = self.api_events_tx.try_send(commitment_event) { - error!(?e, "failed to send commitment event through channel"); - // NOTE: should we return an internal error to the RPC - // here? - return; - } - - // add the pending response to self buffer for later processing - self.pending_commitment_responses - .push(PendingCommitmentResponse::new(rx, id)); - - return; - } - } - } - other => Err(format!("unsupported method: {}", other)), - } + let request = match serde_json::from_str::(&text) { + Ok(req) => req, + Err(e) => { + warn!(?e, ?rpc_url, "failed to parse JSON-RPC request"); + return; } }; - match response { - Ok(json_response) => { - let message = Message::text( - serde_json::to_string(&json_response).expect("to stringify version response"), - ); + let id = request.id; - // Push the message to the outgoing messages queue for later - // processing - self.outgoing_messages.push_back(message); + match request.method.as_str() { + GET_VERSION_METHOD => { + let response = + JsonRpcSuccessResponse::new(Value::String(BOLT_SIDECAR_VERSION.clone())) + .with_uuid(id) + .into(); + self.send_response(response); } - Err(err) => { - warn!(?err, ?rpc_url, "failed to parse JSON-RPC request"); + GET_METADATA_METHOD => { + let response = + JsonRpcSuccessResponse::new(json!(self.state.limits)).with_uuid(id).into(); + self.send_response(response); } - } + REQUEST_INCLUSION_METHOD => { + let Some(param) = request.params.first().cloned() else { + let response: JsonRpcResponse = JsonRpcErrorResponse::new( + CommitmentError::InvalidParams("missing inclusion request".into()).into(), + ) + .with_uuid(id) + .into(); + self.send_response(response); + return; + }; + + let inclusion_request = match serde_json::from_value::(param) { + Ok(req) => req, + Err(e) => { + let msg = format!("failed to parse inclusion request: {}", e); + error!(?e, "failed to parse inclusion request"); + let response: JsonRpcResponse = + JsonRpcErrorResponse::new(CommitmentError::InvalidParams(msg).into()) + .with_uuid(id) + .into(); + self.send_response(response); + return; + } + }; + + let commitment_request = CommitmentRequest::Inclusion(inclusion_request); + let commitment_event = + CommitmentEvent { request: commitment_request, response: tx }; + + if let Err(e) = self.api_events_tx.try_send(commitment_event) { + error!(?e, "failed to send commitment event through channel"); + let response: JsonRpcResponse = + JsonRpcErrorResponse::new(CommitmentError::Internal.into()) + .with_uuid(id) + .into(); + self.send_response(response); + return; + } + + // Push the pending commitment response to the queue + self.pending_commitment_responses.push(PendingCommitmentResponse::new(rx, id)); + } + other => { + warn!(?rpc_url, "unsupported method: {}", other); + } + }; + } + + fn send_response(&mut self, response: JsonRpcResponse) { + let message = + Message::text(serde_json::to_string(&response).expect("to stringify response")); + self.outgoing_messages.push_back(message); } } diff --git a/bolt-sidecar/src/api/commitments/server/handlers.rs b/bolt-sidecar/src/api/commitments/server/handlers.rs index d8caff246..b36fbd996 100644 --- a/bolt-sidecar/src/api/commitments/server/handlers.rs +++ b/bolt-sidecar/src/api/commitments/server/handlers.rs @@ -9,21 +9,21 @@ use axum::{ }; use axum_extra::extract::WithRejection; use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde_json::json; use tracing::{debug, error, info, instrument}; use crate::{ api::commitments::{ server::headers::auth_from_headers, spec::{ - CommitmentError, CommitmentsApi, RejectionError, GET_METADATA_METHOD, - GET_VERSION_METHOD, REQUEST_INCLUSION_METHOD, + CommitmentError, CommitmentsApi, GET_METADATA_METHOD, GET_VERSION_METHOD, + REQUEST_INCLUSION_METHOD, }, }, common::BOLT_SIDECAR_VERSION, config::limits::LimitsOpts, primitives::{ - jsonrpc::{JsonResponse, JsonRpcRequest}, + jsonrpc::{JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse}, signature::SignatureError, InclusionRequest, }, @@ -48,15 +48,18 @@ pub async fn rpc_entrypoint( headers: HeaderMap, State(api): State>, WithRejection(Json(payload), _): WithRejection, CommitmentError>, -) -> Result, CommitmentError> { +) -> Result, CommitmentError> { debug!("Received new request"); match payload.method.as_str() { - GET_VERSION_METHOD => Ok(Json(JsonResponse { - id: payload.id, - result: Value::String(BOLT_SIDECAR_VERSION.clone()), - ..Default::default() - })), + GET_VERSION_METHOD => Ok(Json( + JsonRpcSuccessResponse { + id: payload.id, + result: json!(BOLT_SIDECAR_VERSION.to_string()), + ..Default::default() + } + .into(), + )), GET_METADATA_METHOD => { let metadata = MetadataResponse { @@ -64,12 +67,12 @@ pub async fn rpc_entrypoint( version: BOLT_SIDECAR_VERSION.to_string(), }; - let response = JsonResponse { + let response = JsonRpcSuccessResponse { id: payload.id, - result: serde_json::to_value(metadata) - .expect("infallible - metadata only contains primitive types"), + result: json!(metadata), ..Default::default() - }; + } + .into(); Ok(Json(response)) } @@ -80,12 +83,12 @@ pub async fn rpc_entrypoint( })?; let Some(request_json) = payload.params.first().cloned() else { - return Err(RejectionError::ValidationFailed("Bad params".to_string()).into()); + return Err(CommitmentError::InvalidParams("missing param".to_string())); }; // Parse the inclusion request from the parameters let mut inclusion_request = serde_json::from_value::(request_json) - .map_err(RejectionError::Json) + .map_err(CommitmentError::InvalidJson) .inspect_err(|err| error!(?err, "Failed to parse inclusion request"))?; debug!(?inclusion_request, "New inclusion request"); @@ -113,11 +116,12 @@ pub async fn rpc_entrypoint( let inclusion_commitment = api.request_inclusion(inclusion_request).await?; // Create the JSON-RPC response - let response = JsonResponse { + let response = JsonRpcSuccessResponse { id: payload.id, - result: serde_json::to_value(inclusion_commitment).expect("infallible"), + result: json!(inclusion_commitment), ..Default::default() - }; + } + .into(); Ok(Json(response)) } diff --git a/bolt-sidecar/src/api/commitments/server/mod.rs b/bolt-sidecar/src/api/commitments/server/mod.rs index 8ff329144..d32976168 100644 --- a/bolt-sidecar/src/api/commitments/server/mod.rs +++ b/bolt-sidecar/src/api/commitments/server/mod.rs @@ -177,13 +177,16 @@ fn make_router(state: Arc) -> Router { #[cfg(test)] mod test { - use crate::{api::commitments::spec::SIGNATURE_HEADER, common::BOLT_SIDECAR_VERSION}; + use crate::{ + api::commitments::spec::SIGNATURE_HEADER, common::BOLT_SIDECAR_VERSION, + primitives::jsonrpc::JsonRpcError, + }; use alloy::signers::{k256::SecretKey, local::PrivateKeySigner}; use handlers::MetadataResponse; use serde_json::json; use crate::{ - primitives::{jsonrpc::JsonResponse, signature::ECDSASignatureExt}, + primitives::{jsonrpc::JsonRpcResponse, signature::ECDSASignatureExt}, test_util::{create_signed_inclusion_request, default_test_transaction}, }; @@ -223,12 +226,13 @@ mod test { .send() .await .unwrap() - .json::() + .json::() .await .unwrap(); // Assert unauthorized because of missing signature - assert_eq!(response.error.unwrap().code, -32003); + let expected_error: JsonRpcError = CommitmentError::NoSignature.into(); + assert_eq!(response.into_error().unwrap().code(), expected_error.code); } #[tokio::test] @@ -272,10 +276,10 @@ mod test { .await .unwrap(); - let json = response.json::().await.unwrap(); + let json = response.json::().await.unwrap(); // Assert unauthorized because of missing signature - assert!(json.error.is_none()); + assert!(json.into_success().is_some()); let _ = tx.send(()); }); @@ -316,11 +320,12 @@ mod test { .send() .await .unwrap() - .json::() + .json::() .await .unwrap(); - let metadata: MetadataResponse = serde_json::from_value(response.result).unwrap(); + let metadata: MetadataResponse = + serde_json::from_value(response.into_success().unwrap().result).unwrap(); assert_eq!( metadata.limits.max_committed_gas_per_slot, diff --git a/bolt-sidecar/src/api/commitments/spec.rs b/bolt-sidecar/src/api/commitments/spec.rs index bc0554bdb..b33c4154b 100644 --- a/bolt-sidecar/src/api/commitments/spec.rs +++ b/bolt-sidecar/src/api/commitments/spec.rs @@ -11,7 +11,7 @@ use thiserror::Error; use crate::{ primitives::{ commitment::InclusionCommitment, - jsonrpc::{JsonError, JsonResponse}, + jsonrpc::{JsonRpcError, JsonRpcErrorResponse}, signature::SignatureError, InclusionRequest, }, @@ -31,9 +31,6 @@ pub(super) const MAX_REQUEST_TIMEOUT: std::time::Duration = std::time::Duration: /// Error type for the commitments API. #[derive(Debug, Error)] pub enum CommitmentError { - /// Request rejected. - #[error("Request rejected: {0}")] - Rejected(#[from] RejectionError), /// Consensus validation failed. #[error("Consensus validation error: {0}")] Consensus(#[from] ConsensusError), @@ -63,25 +60,37 @@ pub enum CommitmentError { UnknownMethod, /// Invalid JSON. #[error(transparent)] - InvalidJson(#[from] JsonRejection), + InvalidJson(#[from] serde_json::Error), + /// Invalid JSON-RPC request params. + #[error("Invalid JSON-RPC request params: {0}")] + InvalidParams(String), + /// Invalid JSON. + /// FIXME: (thedevbirb, 2025-13-01) this should be removed because it is dead code, + /// but it allows Rust to pull the correct axum version and not older ones from + /// dependencies (commit-boost). + #[error(transparent)] + RejectedJson(#[from] JsonRejection), } -impl From for JsonError { +impl From for JsonRpcError { fn from(err: CommitmentError) -> Self { + // Reference: https://www.jsonrpc.org/specification#error_object + // TODO: the custom defined ones should be clearly documented. match err { - CommitmentError::Rejected(err) => Self::new(-32000, err.to_string()), CommitmentError::Duplicate => Self::new(-32001, err.to_string()), - CommitmentError::Internal => Self::new(-32002, err.to_string()), - CommitmentError::NoSignature => Self::new(-32003, err.to_string()), - CommitmentError::InvalidSignature(err) => Self::new(-32004, err.to_string()), - CommitmentError::Signature(err) => Self::new(-32005, err.to_string()), - CommitmentError::Consensus(err) => Self::new(-32006, err.to_string()), + CommitmentError::NoSignature => Self::new(-32002, err.to_string()), + CommitmentError::InvalidSignature(err) => Self::new(-32003, err.to_string()), + CommitmentError::Signature(err) => Self::new(-32004, err.to_string()), + CommitmentError::Consensus(err) => Self::new(-32005, err.to_string()), CommitmentError::Validation(err) => Self::new(-32006, err.to_string()), CommitmentError::MalformedHeader => Self::new(-32007, err.to_string()), - CommitmentError::UnknownMethod => Self::new(-32601, err.to_string()), CommitmentError::InvalidJson(err) => { Self::new(-32600, format!("Invalid request: {err}")) } + CommitmentError::UnknownMethod => Self::new(-32601, err.to_string()), + CommitmentError::InvalidParams(err) => Self::new(-32602, err.to_string()), + CommitmentError::Internal => Self::new(-32603, err.to_string()), + CommitmentError::RejectedJson(err) => Self::new(-32604, err.to_string()), } } } @@ -89,16 +98,17 @@ impl From for JsonError { impl From<&CommitmentError> for StatusCode { fn from(err: &CommitmentError) -> Self { match err { - CommitmentError::Rejected(_) | - CommitmentError::Duplicate | - CommitmentError::NoSignature | - CommitmentError::InvalidSignature(_) | - CommitmentError::Signature(_) | - CommitmentError::Consensus(_) | - CommitmentError::Validation(_) | - CommitmentError::MalformedHeader | - CommitmentError::UnknownMethod | - CommitmentError::InvalidJson(_) => Self::BAD_REQUEST, + CommitmentError::Duplicate + | CommitmentError::NoSignature + | CommitmentError::InvalidSignature(_) + | CommitmentError::Signature(_) + | CommitmentError::Consensus(_) + | CommitmentError::Validation(_) + | CommitmentError::MalformedHeader + | CommitmentError::UnknownMethod + | CommitmentError::InvalidParams(_) + | CommitmentError::RejectedJson(_) + | CommitmentError::InvalidJson(_) => Self::BAD_REQUEST, CommitmentError::Internal => Self::INTERNAL_SERVER_ERROR, } } @@ -107,33 +117,13 @@ impl From<&CommitmentError> for StatusCode { impl IntoResponse for CommitmentError { fn into_response(self) -> Response { let status_code = StatusCode::from(&self); - let json = Json(JsonResponse::from_error(self.into())); + let err = JsonRpcError::from(self); + let json = Json(JsonRpcErrorResponse::new(err)); (status_code, json).into_response() } } -/// Error indicating the rejection of a commitment request. This should -/// be returned to the user. -#[derive(Debug, Error)] -pub enum RejectionError { - /// State validation failed for this request. - #[error("Validation failed: {0}")] - ValidationFailed(String), - /// JSON parsing error. - #[error("JSON parsing error: {0}")] - Json(#[from] serde_json::Error), -} - -impl From for JsonError { - fn from(err: RejectionError) -> Self { - match err { - RejectionError::ValidationFailed(err) => Self::new(-32600, err), - RejectionError::Json(err) => Self::new(-32700, err.to_string()), - } - } -} - /// Implements the commitments-API: #[async_trait::async_trait] pub trait CommitmentsApi { diff --git a/bolt-sidecar/src/api/spec.rs b/bolt-sidecar/src/api/spec.rs index 1cd2813df..fdf0d4f2d 100644 --- a/bolt-sidecar/src/api/spec.rs +++ b/bolt-sidecar/src/api/spec.rs @@ -21,7 +21,7 @@ pub const STATUS_PATH: &str = "/eth/v1/builder/status"; /// The path to the builder API register validators endpoint. pub const REGISTER_VALIDATORS_PATH: &str = "/eth/v1/builder/validators"; /// The path to the builder API get header endpoint. -pub const GET_HEADER_PATH: &str = "/eth/v1/builder/header/:slot/:parent_hash/:pubkey"; +pub const GET_HEADER_PATH: &str = "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}"; /// The path to the builder API get payload endpoint. pub const GET_PAYLOAD_PATH: &str = "/eth/v1/builder/blinded_blocks"; /// The path to the constraints API submit constraints endpoint. @@ -86,12 +86,12 @@ pub enum BuilderApiError { impl IntoResponse for BuilderApiError { fn into_response(self) -> Response { match self { - Self::FailedRegisteringValidators(error) | - Self::FailedGettingHeader(error) | - Self::FailedGettingPayload(error) | - Self::FailedSubmittingConstraints(error) | - Self::FailedDelegating(error) | - Self::FailedRevoking(error) => { + Self::FailedRegisteringValidators(error) + | Self::FailedGettingHeader(error) + | Self::FailedGettingPayload(error) + | Self::FailedSubmittingConstraints(error) + | Self::FailedDelegating(error) + | Self::FailedRevoking(error) => { (StatusCode::from_u16(error.code).unwrap(), Json(error)).into_response() } Self::NoBids(_) => (StatusCode::NO_CONTENT, self.to_string()).into_response(), diff --git a/bolt-sidecar/src/primitives/commitment.rs b/bolt-sidecar/src/primitives/commitment.rs index d680d32a2..ccfcb6abf 100644 --- a/bolt-sidecar/src/primitives/commitment.rs +++ b/bolt-sidecar/src/primitives/commitment.rs @@ -82,7 +82,8 @@ impl CommitmentRequest { } /// Request to include a transaction at a specific slot. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] +#[cfg_attr(test, derive(Default))] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct InclusionRequest { /// The consensus slot number at which the transaction should be included. pub slot: u64, diff --git a/bolt-sidecar/src/primitives/jsonrpc.rs b/bolt-sidecar/src/primitives/jsonrpc.rs index e59f9dcf5..b86e76c3c 100644 --- a/bolt-sidecar/src/primitives/jsonrpc.rs +++ b/bolt-sidecar/src/primitives/jsonrpc.rs @@ -1,3 +1,4 @@ +use derive_more::derive::From; use serde::{Deserialize, Serialize}; use serde_json::Value; use uuid::Uuid; @@ -28,44 +29,118 @@ pub struct JsonRpcRequestUuid { pub params: Vec, } -/// A JSON-RPC response +/// A response object for JSON-RPC. +#[derive(Debug, Clone, Serialize, Deserialize, From)] +#[serde(untagged)] +pub enum JsonRpcResponse { + /// A successful response. + Success(JsonRpcSuccessResponse), + /// An error response. + Error(JsonRpcErrorResponse), +} + +impl JsonRpcResponse { + /// Attemps to convert the response into a successful response. + pub fn into_success(self) -> Option { + match self { + Self::Success(success) => Some(success), + _ => None, + } + } + + /// Attemps to convert the response into an error response. + pub fn into_error(self) -> Option { + match self { + Self::Error(error) => Some(error), + _ => None, + } + } +} + +/// A response object for successful JSON-RPC requests. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct JsonRpcSuccessResponse { + /// The JSON-RPC version string. MUST be "2.0". + pub jsonrpc: String, + /// Optional ID. + pub id: Option, + /// The result object. + pub result: T, +} + +impl JsonRpcSuccessResponse { + /// Create a new JSON-RPC success response + pub fn new(result: T) -> Self { + Self { jsonrpc: "2.0".to_string(), id: None, result } + } + + /// Set the ID of the response + pub fn with_id(self, id: Value) -> Self { + Self { id: Some(id), ..self } + } + + /// Set the ID of the response from a UUID + pub fn with_uuid(self, id: Uuid) -> Self { + Self { id: Some(Value::String(id.to_string())), ..self } + } +} + +/// A JSON-RPC error response. +/// +/// Reference: https://www.jsonrpc.org/specification #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JsonResponse { +pub struct JsonRpcErrorResponse { /// The JSON-RPC version string. MUST be "2.0". pub jsonrpc: String, - /// Optional ID. Must be serialized as `null` if not present. + /// Optional ID pub id: Option, - /// The result object. Must be serialized as `null` if an error is present. - #[serde(skip_serializing_if = "Value::is_null", default)] - pub result: Value, - /// The error object. Must be serialized as `null` if no error is present. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, + /// The error object. + pub error: JsonRpcError, } -impl Default for JsonResponse { - fn default() -> Self { - Self { jsonrpc: "2.0".to_string(), id: None, result: Value::Null, error: None } +impl JsonRpcErrorResponse { + /// Create a new JSON-RPC error response + pub fn new(error: JsonRpcError) -> Self { + Self { jsonrpc: "2.0".to_string(), id: None, error } + } + + /// Set the ID of the response. + pub fn with_id(self, id: Value) -> Self { + Self { id: Some(id), ..self } + } + + /// Set the ID of the response from a UUID. + pub fn with_uuid(self, id: Uuid) -> Self { + Self { id: Some(Value::String(id.to_string())), ..self } + } + + /// Returns a clone of the error message. + pub fn message(&self) -> String { + self.error.message.clone() + } + + /// Returns the error code. + pub fn code(&self) -> i32 { + self.error.code } } -impl JsonResponse { - /// Create a new JSON-RPC response with a result - pub fn from_error(error: JsonError) -> Self { - Self { error: Some(error), ..Default::default() } +impl From for JsonRpcErrorResponse { + fn from(error: JsonRpcError) -> Self { + Self::new(error) } } /// A JSON-RPC error object #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JsonError { +pub struct JsonRpcError { /// The error code pub code: i32, /// The error message pub message: String, } -impl JsonError { +impl JsonRpcError { /// Create a new JSON-RPC error object pub fn new(code: i32, message: String) -> Self { Self { code, message } diff --git a/bolt-sidecar/src/signer/commit_boost.rs b/bolt-sidecar/src/signer/commit_boost.rs index 84eebd236..2b1213a7c 100644 --- a/bolt-sidecar/src/signer/commit_boost.rs +++ b/bolt-sidecar/src/signer/commit_boost.rs @@ -9,11 +9,10 @@ use cb_common::{ commit::{ client::SignerClient, error::SignerClientError, - request::{GetPubkeysResponse, SignConsensusRequest}, + request::{GetPubkeysResponse, SignConsensusRequest, SignProxyRequest}, }, signer::EcdsaPublicKey, }; -use commit_boost::prelude::SignProxyRequest; use ethereum_consensus::crypto::bls::PublicKey as BlsPublicKey; use parking_lot::RwLock; use reqwest::Url;