From 81b5fbf57307b6eefe708c71d7d59e713dc70101 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 24 Jun 2024 14:40:35 +0100 Subject: [PATCH] feat(examples): remote exex (#8890) --- .github/workflows/integration.yml | 2 +- .github/workflows/lint.yml | 3 + .github/workflows/unit.yml | 3 +- Cargo.lock | 301 +++++++- crates/primitives/src/lib.rs | 2 +- examples/exex/remote/Cargo.toml | 42 ++ examples/exex/remote/bin/consumer.rs | 32 + examples/exex/remote/bin/exex.rs | 77 +++ examples/exex/remote/build.rs | 4 + examples/exex/remote/proto/exex.proto | 279 ++++++++ examples/exex/remote/src/codec.rs | 961 ++++++++++++++++++++++++++ examples/exex/remote/src/lib.rs | 4 + 12 files changed, 1690 insertions(+), 20 deletions(-) create mode 100644 examples/exex/remote/Cargo.toml create mode 100644 examples/exex/remote/bin/consumer.rs create mode 100644 examples/exex/remote/bin/exex.rs create mode 100644 examples/exex/remote/build.rs create mode 100644 examples/exex/remote/proto/exex.proto create mode 100644 examples/exex/remote/src/codec.rs create mode 100644 examples/exex/remote/src/lib.rs diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 8bd42c8bb31d..a57411744939 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -41,7 +41,7 @@ jobs: run: | cargo nextest run \ --locked --features "asm-keccak ${{ matrix.network }}" \ - --workspace --exclude ef-tests \ + --workspace --exclude example-exex-remote --exclude ef-tests \ -E "kind(test)" - if: matrix.network == 'optimism' name: Run tests diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c07cee38830b..c758f6945a05 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -41,6 +41,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - uses: arduino/setup-protoc@v3 - run: cargo clippy --workspace --lib --examples --tests --benches --all-features --locked env: RUSTFLAGS: -D warnings @@ -70,6 +71,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - uses: arduino/setup-protoc@v3 - run: cargo hack check msrv: @@ -105,6 +107,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - uses: arduino/setup-protoc@v3 - run: cargo docs --document-private-items env: # Keep in sync with ./book.yml:jobs.build diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index a6663aea8843..a5d42a85d208 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -39,7 +39,7 @@ jobs: run: | cargo nextest run \ --locked --features "asm-keccak ${{ matrix.network }}" \ - --workspace --exclude ef-tests \ + --workspace --exclude example-exex-remote --exclude ef-tests \ --partition hash:${{ matrix.partition }}/2 \ -E "!kind(test)" @@ -84,6 +84,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - uses: arduino/setup-protoc@v3 - name: Run doctests run: cargo test --doc --workspace --features "${{ matrix.network }}" diff --git a/Cargo.lock b/Cargo.lock index b9923d34a8e8..4e26b254f381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1064,6 +1064,51 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backon" version = "0.4.4" @@ -2876,6 +2921,25 @@ dependencies = [ "tokio", ] +[[package]] +name = "example-exex-remote" +version = "0.0.0" +dependencies = [ + "bincode", + "eyre", + "prost", + "reth", + "reth-exex", + "reth-exex-test-utils", + "reth-node-api", + "reth-node-ethereum", + "reth-tracing", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", +] + [[package]] name = "example-exex-rollup" version = "0.0.0" @@ -3129,6 +3193,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.30" @@ -3417,6 +3487,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.5" @@ -3617,6 +3706,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.0" @@ -3636,7 +3736,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "pin-project-lite", ] @@ -3700,6 +3800,30 @@ dependencies = [ "serde", ] +[[package]] +name = "hyper" +version = "0.14.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.3.1" @@ -3709,9 +3833,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", + "h2 0.4.5", "http 1.1.0", - "http-body", + "http-body 1.0.0", "httparse", "httpdate", "itoa", @@ -3729,7 +3853,7 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http 1.1.0", - "hyper", + "hyper 1.3.1", "hyper-util", "log", "rustls", @@ -3741,6 +3865,18 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.29", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-util" version = "0.1.5" @@ -3751,8 +3887,8 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.3.1", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -4287,7 +4423,7 @@ dependencies = [ "futures-timer", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", "jsonrpsee-types", "parking_lot 0.12.3", @@ -4311,8 +4447,8 @@ checksum = "fb25cab482c8512c4f3323a5c90b95a3b8f7c90681a87bf7a68b942d52f08933" dependencies = [ "async-trait", "base64 0.22.1", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.3.1", "hyper-rustls", "hyper-util", "jsonrpsee-core", @@ -4350,9 +4486,9 @@ dependencies = [ "anyhow", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", + "hyper 1.3.1", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -4779,6 +4915,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.4" @@ -5018,6 +5160,12 @@ dependencies = [ "unsigned-varint 0.7.2", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "multistream-select" version = "0.13.0" @@ -5426,6 +5574,16 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.2.6", +] + [[package]] name = "ph" version = "0.8.3" @@ -5801,6 +5959,59 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.67", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.67", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + [[package]] name = "quanta" version = "0.12.3" @@ -6141,9 +6352,9 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", + "hyper 1.3.1", "hyper-rustls", "hyper-util", "ipnet", @@ -6161,7 +6372,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tokio-rustls", "tokio-util", @@ -7871,8 +8082,8 @@ dependencies = [ "dyn-clone", "futures", "http 1.1.0", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.3.1", "jsonrpsee", "jsonwebtoken", "metrics", @@ -9470,6 +9681,12 @@ dependencies = [ "syn 2.0.67", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" @@ -9778,6 +9995,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.3.0" @@ -9888,6 +10115,46 @@ dependencies = [ "winnow 0.6.13", ] +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.67", +] + [[package]] name = "tower" version = "0.4.13" @@ -9922,7 +10189,7 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", "http-range-header", "httpdate", diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index eff4b7dcfacd..59fec9702991 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -56,7 +56,7 @@ pub use receipt::{ gas_spent_by_transactions, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts, }; pub use reth_primitives_traits::{ - logs_bloom, Account, Bytecode, GotExpected, GotExpectedBoxed, Log, Request, Requests, + logs_bloom, Account, Bytecode, GotExpected, GotExpectedBoxed, Log, LogData, Request, Requests, StorageEntry, Withdrawal, Withdrawals, }; pub use static_file::StaticFileSegment; diff --git a/examples/exex/remote/Cargo.toml b/examples/exex/remote/Cargo.toml new file mode 100644 index 000000000000..634b9a7fef7e --- /dev/null +++ b/examples/exex/remote/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "example-exex-remote" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + +[dependencies] +reth.workspace = true +reth-exex = { workspace = true, features = ["serde"] } +reth-node-api.workspace = true +reth-node-ethereum.workspace = true +reth-tracing.workspace = true + +eyre.workspace = true + +tonic = "0.11" +prost = "0.12" +tokio = { version = "1.0", features = ["full"] } +tokio-stream = "0.1" + +bincode = "1.3" + +[build-dependencies] +tonic-build = "0.11" + +[dev-dependencies] +reth-exex-test-utils.workspace = true + +tokio.workspace = true + +[features] +default = [] +optimism = ["reth/optimism"] + +[[bin]] +name = "exex" +path = "bin/exex.rs" + +[[bin]] +name = "consumer" +path = "bin/consumer.rs" diff --git a/examples/exex/remote/bin/consumer.rs b/examples/exex/remote/bin/consumer.rs new file mode 100644 index 000000000000..71d4ebbe6ed6 --- /dev/null +++ b/examples/exex/remote/bin/consumer.rs @@ -0,0 +1,32 @@ +use example_exex_remote::proto::{remote_ex_ex_client::RemoteExExClient, SubscribeRequest}; +use reth_exex::ExExNotification; +use reth_tracing::{tracing::info, RethTracer, Tracer}; + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let _ = RethTracer::new().init()?; + + let mut client = RemoteExExClient::connect("http://[::1]:10000") + .await? + .max_encoding_message_size(usize::MAX) + .max_decoding_message_size(usize::MAX); + + let mut stream = client.subscribe(SubscribeRequest {}).await?.into_inner(); + while let Some(notification) = stream.message().await? { + let notification = ExExNotification::try_from(¬ification)?; + + match notification { + ExExNotification::ChainCommitted { new } => { + info!(committed_chain = ?new.range(), "Received commit"); + } + ExExNotification::ChainReorged { old, new } => { + info!(from_chain = ?old.range(), to_chain = ?new.range(), "Received reorg"); + } + ExExNotification::ChainReverted { old } => { + info!(reverted_chain = ?old.range(), "Received revert"); + } + }; + } + + Ok(()) +} diff --git a/examples/exex/remote/bin/exex.rs b/examples/exex/remote/bin/exex.rs new file mode 100644 index 000000000000..ed1e5ec1e8c4 --- /dev/null +++ b/examples/exex/remote/bin/exex.rs @@ -0,0 +1,77 @@ +use example_exex_remote::proto::{ + remote_ex_ex_server::{RemoteExEx, RemoteExExServer}, + ExExNotification as ProtoExExNotification, SubscribeRequest as ProtoSubscribeRequest, +}; +use reth_exex::{ExExContext, ExExEvent, ExExNotification}; +use reth_node_api::FullNodeComponents; +use reth_node_ethereum::EthereumNode; +use tokio::sync::{broadcast, mpsc}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{transport::Server, Request, Response, Status}; + +#[derive(Debug)] +struct ExExService { + notifications: broadcast::Sender, +} + +#[tonic::async_trait] +impl RemoteExEx for ExExService { + type SubscribeStream = ReceiverStream>; + + async fn subscribe( + &self, + _request: Request, + ) -> Result, Status> { + let (tx, rx) = mpsc::channel(1); + + let mut notifications = self.notifications.subscribe(); + tokio::spawn(async move { + while let Ok(notification) = notifications.recv().await { + tx.send(Ok((¬ification).try_into().expect("failed to encode"))) + .await + .expect("failed to send notification to client"); + } + }); + + Ok(Response::new(ReceiverStream::new(rx))) + } +} + +async fn exex( + mut ctx: ExExContext, + notifications: broadcast::Sender, +) -> eyre::Result<()> { + while let Some(notification) = ctx.notifications.recv().await { + if let Some(committed_chain) = notification.committed_chain() { + ctx.events.send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; + } + + let _ = notifications.send(notification); + } + + Ok(()) +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let notifications = broadcast::channel(1).0; + + let server = Server::builder() + .add_service(RemoteExExServer::new(ExExService { + notifications: notifications.clone(), + })) + .serve("[::1]:10000".parse().unwrap()); + + let handle = builder + .node(EthereumNode::default()) + .install_exex("Remote", |ctx| async move { Ok(exex(ctx, notifications)) }) + .launch() + .await?; + + handle.node.task_executor.spawn_critical("gRPC server", async move { + server.await.expect("gRPC server crashed") + }); + + handle.wait_for_node_exit().await + }) +} diff --git a/examples/exex/remote/build.rs b/examples/exex/remote/build.rs new file mode 100644 index 000000000000..9e70bbe9b31f --- /dev/null +++ b/examples/exex/remote/build.rs @@ -0,0 +1,4 @@ +fn main() { + tonic_build::compile_protos("proto/exex.proto") + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); +} diff --git a/examples/exex/remote/proto/exex.proto b/examples/exex/remote/proto/exex.proto new file mode 100644 index 000000000000..17620b6802ab --- /dev/null +++ b/examples/exex/remote/proto/exex.proto @@ -0,0 +1,279 @@ +syntax = "proto3"; + +package exex; + +import "google/protobuf/empty.proto"; + +service RemoteExEx { + rpc Subscribe(SubscribeRequest) returns (stream ExExNotification) {} +} + +message SubscribeRequest {} + +message ExExNotification { + oneof notification { + ChainCommitted chain_committed = 1; + ChainReorged chain_reorged = 2; + ChainReverted chain_reverted = 3; + } +} + +message ChainCommitted { + Chain new = 1; +} + +message ChainReorged { + Chain old = 1; + Chain new = 2; +} + +message ChainReverted { + Chain old = 1; +} + +message Chain { + repeated Block blocks = 1; + ExecutionOutcome execution_outcome = 2; +} + +message Block { + SealedHeader header = 1; + repeated Transaction body = 2; + repeated Header ommers = 3; + repeated bytes senders = 4; + // TODO: add withdrawals and requests +} + +message SealedHeader { + bytes hash = 1; + Header header = 2; +} + +message Header { + bytes parent_hash = 1; + bytes ommers_hash = 2; + bytes beneficiary = 3; + bytes state_root = 4; + bytes transactions_root = 5; + bytes receipts_root = 6; + optional bytes withdrawals_root = 7; + bytes logs_bloom = 8; + bytes difficulty = 9; + uint64 number = 10; + uint64 gas_limit = 11; + uint64 gas_used = 12; + uint64 timestamp = 13; + bytes mix_hash = 14; + uint64 nonce = 15; + optional uint64 base_fee_per_gas = 16; + optional uint64 blob_gas_used = 17; + optional uint64 excess_blob_gas = 18; + optional bytes parent_beacon_block_root = 19; + // TODO: add requests_root + bytes extra_data = 20; +} + +message Transaction { + bytes hash = 1; + Signature signature = 2; + oneof transaction { + TransactionLegacy legacy = 3; + TransactionEip2930 eip2930 = 4; + TransactionEip1559 eip1559 = 5; + TransactionEip4844 eip4844 = 6; + } +} + +message Signature { + bytes r = 1; + bytes s = 2; + bool odd_y_parity = 3; +} + +message TransactionLegacy { + optional uint64 chain_id = 1; + uint64 nonce = 2; + bytes gas_price = 3; + uint64 gas_limit = 4; + TxKind to = 5; + bytes value = 6; + bytes input = 7; +} + +message TransactionEip2930 { + uint64 chain_id = 1; + uint64 nonce = 2; + bytes gas_price = 3; + uint64 gas_limit = 4; + TxKind to = 5; + bytes value = 6; + repeated AccessListItem access_list = 7; + bytes input = 8; +} + +message TransactionEip1559 { + uint64 chain_id = 1; + uint64 nonce = 2; + uint64 gas_limit = 3; + bytes max_fee_per_gas = 4; + bytes max_priority_fee_per_gas = 5; + TxKind to = 6; + bytes value = 7; + repeated AccessListItem access_list = 8; + bytes input = 9; +} + +message TransactionEip4844 { + uint64 chain_id = 1; + uint64 nonce = 2; + uint64 gas_limit = 3; + bytes max_fee_per_gas = 4; + bytes max_priority_fee_per_gas = 5; + bytes to = 6; + bytes value = 7; + repeated AccessListItem access_list = 8; + repeated bytes blob_versioned_hashes = 9; + bytes max_fee_per_blob_gas = 10; + bytes input = 11; +} + +message TxKind { + oneof kind { + google.protobuf.Empty create = 1; + bytes call = 2; + } +} + +message AccessListItem { + bytes address = 1; + repeated bytes storage_keys = 2; +} + +message ExecutionOutcome { + BundleState bundle = 1; + repeated BlockReceipts receipts = 2; + uint64 first_block = 3; + // TODO: add requests +} + +message BundleState { + repeated BundleAccount state = 1; + repeated ContractBytecode contracts = 2; + repeated BlockReverts reverts = 3; + uint64 state_size = 4; + uint64 reverts_size = 5; +} + +message BundleAccount { + bytes address = 1; + AccountInfo info = 2; + AccountInfo original_info = 3; + repeated StorageSlot storage = 4; + AccountStatus status = 5; +} + +message AccountInfo { + bytes balance = 1; + uint64 nonce = 2; + bytes code_hash = 3; + Bytecode code = 4; +} + +message StorageSlot { + bytes key = 1; + bytes previous_or_original_value = 2; + bytes present_value = 3; +} + +enum AccountStatus { + LOADED_NOT_EXISTING = 0; + LOADED = 1; + LOADED_EMPTY_EIP161 = 2; + IN_MEMORY_CHANGE = 3; + CHANGED = 4; + DESTROYED = 5; + DESTROYED_CHANGED = 6; + DESTROYED_AGAIN = 7; +} + +message ContractBytecode { + bytes hash = 1; + Bytecode bytecode = 2; +} + +message Bytecode { + oneof bytecode { + bytes legacy_raw = 1; + LegacyAnalyzedBytecode legacy_analyzed = 2; + // TODO: add EOF + } +} + +message LegacyAnalyzedBytecode { + bytes bytecode = 1; + uint64 original_len = 2; + repeated uint32 jump_table = 3; +} + +message BlockReverts { + repeated Revert reverts = 1; +} + +message Revert { + bytes address = 1; + AccountInfoRevert account = 2; + repeated RevertToSlot storage = 3; + AccountStatus previous_status = 4; + bool wipe_storage = 5; +} + +message AccountInfoRevert { + oneof revert { + google.protobuf.Empty do_nothing = 1; + google.protobuf.Empty delete_it = 2; + AccountInfo revert_to = 3; + } +} + +message RevertToSlot { + bytes key = 1; + oneof revert { + bytes some = 2; + google.protobuf.Empty destroyed = 3; + } +} + +message BlockReceipts { + repeated Receipt receipts = 1; +} + +message Receipt { + oneof receipt { + google.protobuf.Empty empty = 1; + NonEmptyReceipt non_empty = 2; + } +} + +message NonEmptyReceipt { + TxType tx_type = 1; + bool success = 2; + uint64 cumulative_gas_used = 3; + repeated Log logs = 4; +} + +enum TxType { + LEGACY = 0; + EIP2930 = 1; + EIP1559 = 2; + EIP4844 = 3; +} + +message Log { + bytes address = 1; + LogData data = 2; +} + +message LogData { + repeated bytes topics = 1; + bytes data = 2; +} diff --git a/examples/exex/remote/src/codec.rs b/examples/exex/remote/src/codec.rs new file mode 100644 index 000000000000..d36b60b92a5c --- /dev/null +++ b/examples/exex/remote/src/codec.rs @@ -0,0 +1,961 @@ +use std::sync::Arc; + +use eyre::OptionExt; +use reth::primitives::{Address, BlockHash, Bloom, TxHash, B256, U256}; + +use crate::proto; + +impl TryFrom<&reth_exex::ExExNotification> for proto::ExExNotification { + type Error = eyre::Error; + + fn try_from(notification: &reth_exex::ExExNotification) -> Result { + let notification = match notification { + reth_exex::ExExNotification::ChainCommitted { new } => { + proto::ex_ex_notification::Notification::ChainCommitted(proto::ChainCommitted { + new: Some(new.as_ref().try_into()?), + }) + } + reth_exex::ExExNotification::ChainReorged { old, new } => { + proto::ex_ex_notification::Notification::ChainReorged(proto::ChainReorged { + old: Some(old.as_ref().try_into()?), + new: Some(new.as_ref().try_into()?), + }) + } + reth_exex::ExExNotification::ChainReverted { old } => { + proto::ex_ex_notification::Notification::ChainReverted(proto::ChainReverted { + old: Some(old.as_ref().try_into()?), + }) + } + }; + + Ok(proto::ExExNotification { notification: Some(notification) }) + } +} + +impl TryFrom<&reth::providers::Chain> for proto::Chain { + type Error = eyre::Error; + + fn try_from(chain: &reth::providers::Chain) -> Result { + let bundle_state = chain.execution_outcome().state(); + Ok(proto::Chain { + blocks: chain + .blocks_iter() + .map(|block| { + Ok(proto::Block { + header: Some(proto::SealedHeader { + hash: block.header.hash().to_vec(), + header: Some(block.header.header().into()), + }), + body: block + .transactions() + .map(TryInto::try_into) + .collect::>()?, + ommers: block.ommers.iter().map(Into::into).collect(), + senders: block.senders.iter().map(|sender| sender.to_vec()).collect(), + }) + }) + .collect::>()?, + execution_outcome: Some(proto::ExecutionOutcome { + bundle: Some(proto::BundleState { + state: bundle_state + .state + .iter() + .map(|(address, account)| (*address, account).try_into()) + .collect::>()?, + contracts: bundle_state + .contracts + .iter() + .map(|(hash, bytecode)| { + Ok(proto::ContractBytecode { + hash: hash.to_vec(), + bytecode: Some(bytecode.try_into()?), + }) + }) + .collect::>()?, + reverts: bundle_state + .reverts + .iter() + .map(|block_reverts| { + Ok(proto::BlockReverts { + reverts: block_reverts + .iter() + .map(|(address, revert)| (*address, revert).try_into()) + .collect::>()?, + }) + }) + .collect::>()?, + state_size: bundle_state.state_size as u64, + reverts_size: bundle_state.reverts_size as u64, + }), + receipts: chain + .execution_outcome() + .receipts() + .iter() + .map(|block_receipts| { + Ok(proto::BlockReceipts { + receipts: block_receipts + .iter() + .map(TryInto::try_into) + .collect::>()?, + }) + }) + .collect::>()?, + first_block: chain.execution_outcome().first_block, + }), + }) + } +} + +impl From<&reth::primitives::Header> for proto::Header { + fn from(header: &reth::primitives::Header) -> Self { + proto::Header { + parent_hash: header.parent_hash.to_vec(), + ommers_hash: header.ommers_hash.to_vec(), + beneficiary: header.beneficiary.to_vec(), + state_root: header.state_root.to_vec(), + transactions_root: header.transactions_root.to_vec(), + receipts_root: header.receipts_root.to_vec(), + withdrawals_root: header.withdrawals_root.map(|root| root.to_vec()), + logs_bloom: header.logs_bloom.to_vec(), + difficulty: header.difficulty.to_le_bytes_vec(), + number: header.number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + mix_hash: header.mix_hash.to_vec(), + nonce: header.nonce, + base_fee_per_gas: header.base_fee_per_gas, + blob_gas_used: header.blob_gas_used, + excess_blob_gas: header.excess_blob_gas, + parent_beacon_block_root: header.parent_beacon_block_root.map(|root| root.to_vec()), + extra_data: header.extra_data.to_vec(), + } + } +} + +impl TryFrom<&reth::primitives::TransactionSigned> for proto::Transaction { + type Error = eyre::Error; + + fn try_from(transaction: &reth::primitives::TransactionSigned) -> Result { + let hash = transaction.hash().to_vec(); + let signature = proto::Signature { + r: transaction.signature.r.to_le_bytes_vec(), + s: transaction.signature.s.to_le_bytes_vec(), + odd_y_parity: transaction.signature.odd_y_parity, + }; + let transaction = match &transaction.transaction { + reth::primitives::Transaction::Legacy(reth::primitives::TxLegacy { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + input, + }) => proto::transaction::Transaction::Legacy(proto::TransactionLegacy { + chain_id: *chain_id, + nonce: *nonce, + gas_price: gas_price.to_le_bytes().to_vec(), + gas_limit: *gas_limit, + to: Some(to.into()), + value: value.to_le_bytes_vec(), + input: input.to_vec(), + }), + reth::primitives::Transaction::Eip2930(reth::primitives::TxEip2930 { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + access_list, + input, + }) => proto::transaction::Transaction::Eip2930(proto::TransactionEip2930 { + chain_id: *chain_id, + nonce: *nonce, + gas_price: gas_price.to_le_bytes().to_vec(), + gas_limit: *gas_limit, + to: Some(to.into()), + value: value.to_le_bytes_vec(), + access_list: access_list.iter().map(Into::into).collect(), + input: input.to_vec(), + }), + reth::primitives::Transaction::Eip1559(reth::primitives::TxEip1559 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value, + access_list, + input, + }) => proto::transaction::Transaction::Eip1559(proto::TransactionEip1559 { + chain_id: *chain_id, + nonce: *nonce, + gas_limit: *gas_limit, + max_fee_per_gas: max_fee_per_gas.to_le_bytes().to_vec(), + max_priority_fee_per_gas: max_priority_fee_per_gas.to_le_bytes().to_vec(), + to: Some(to.into()), + value: value.to_le_bytes_vec(), + access_list: access_list.iter().map(Into::into).collect(), + input: input.to_vec(), + }), + reth::primitives::Transaction::Eip4844(reth::primitives::TxEip4844 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + placeholder: _, + to, + value, + access_list, + blob_versioned_hashes, + max_fee_per_blob_gas, + input, + }) => proto::transaction::Transaction::Eip4844(proto::TransactionEip4844 { + chain_id: *chain_id, + nonce: *nonce, + gas_limit: *gas_limit, + max_fee_per_gas: max_fee_per_gas.to_le_bytes().to_vec(), + max_priority_fee_per_gas: max_priority_fee_per_gas.to_le_bytes().to_vec(), + to: to.to_vec(), + value: value.to_le_bytes_vec(), + access_list: access_list.iter().map(Into::into).collect(), + blob_versioned_hashes: blob_versioned_hashes + .iter() + .map(|hash| hash.to_vec()) + .collect(), + max_fee_per_blob_gas: max_fee_per_blob_gas.to_le_bytes().to_vec(), + input: input.to_vec(), + }), + #[cfg(feature = "optimism")] + reth::primitives::Transaction::Deposit(_) => { + eyre::bail!("deposit transaction not supported") + } + }; + + Ok(proto::Transaction { hash, signature: Some(signature), transaction: Some(transaction) }) + } +} + +impl From<&reth::primitives::TxKind> for proto::TxKind { + fn from(kind: &reth::primitives::TxKind) -> Self { + proto::TxKind { + kind: match kind { + reth::primitives::TxKind::Create => Some(proto::tx_kind::Kind::Create(())), + reth::primitives::TxKind::Call(address) => { + Some(proto::tx_kind::Kind::Call(address.to_vec())) + } + }, + } + } +} + +impl From<&reth::primitives::AccessListItem> for proto::AccessListItem { + fn from(item: &reth::primitives::AccessListItem) -> Self { + proto::AccessListItem { + address: item.address.to_vec(), + storage_keys: item.storage_keys.iter().map(|key| key.to_vec()).collect(), + } + } +} + +impl TryFrom<(Address, &reth::revm::db::BundleAccount)> for proto::BundleAccount { + type Error = eyre::Error; + + fn try_from( + (address, account): (Address, &reth::revm::db::BundleAccount), + ) -> Result { + Ok(proto::BundleAccount { + address: address.to_vec(), + info: account.info.as_ref().map(TryInto::try_into).transpose()?, + original_info: account.original_info.as_ref().map(TryInto::try_into).transpose()?, + storage: account + .storage + .iter() + .map(|(key, slot)| proto::StorageSlot { + key: key.to_le_bytes_vec(), + previous_or_original_value: slot.previous_or_original_value.to_le_bytes_vec(), + present_value: slot.present_value.to_le_bytes_vec(), + }) + .collect(), + status: proto::AccountStatus::from(account.status) as i32, + }) + } +} + +impl TryFrom<&reth::revm::primitives::AccountInfo> for proto::AccountInfo { + type Error = eyre::Error; + + fn try_from(account_info: &reth::revm::primitives::AccountInfo) -> Result { + Ok(proto::AccountInfo { + balance: account_info.balance.to_le_bytes_vec(), + nonce: account_info.nonce, + code_hash: account_info.code_hash.to_vec(), + code: account_info.code.as_ref().map(TryInto::try_into).transpose()?, + }) + } +} + +impl TryFrom<&reth::revm::primitives::Bytecode> for proto::Bytecode { + type Error = eyre::Error; + + fn try_from(bytecode: &reth::revm::primitives::Bytecode) -> Result { + let bytecode = match bytecode { + reth::revm::primitives::Bytecode::LegacyRaw(code) => { + proto::bytecode::Bytecode::LegacyRaw(code.to_vec()) + } + reth::revm::primitives::Bytecode::LegacyAnalyzed(legacy_analyzed) => { + proto::bytecode::Bytecode::LegacyAnalyzed(proto::LegacyAnalyzedBytecode { + bytecode: legacy_analyzed.bytecode().to_vec(), + original_len: legacy_analyzed.original_len() as u64, + jump_table: legacy_analyzed + .jump_table() + .0 + .iter() + .by_vals() + .map(|x| x.into()) + .collect(), + }) + } + reth::revm::primitives::Bytecode::Eof(_) => { + eyre::bail!("EOF bytecode not supported"); + } + }; + Ok(proto::Bytecode { bytecode: Some(bytecode) }) + } +} + +impl From for proto::AccountStatus { + fn from(status: reth::revm::db::AccountStatus) -> Self { + match status { + reth::revm::db::AccountStatus::LoadedNotExisting => { + proto::AccountStatus::LoadedNotExisting + } + reth::revm::db::AccountStatus::Loaded => proto::AccountStatus::Loaded, + reth::revm::db::AccountStatus::LoadedEmptyEIP161 => { + proto::AccountStatus::LoadedEmptyEip161 + } + reth::revm::db::AccountStatus::InMemoryChange => proto::AccountStatus::InMemoryChange, + reth::revm::db::AccountStatus::Changed => proto::AccountStatus::Changed, + reth::revm::db::AccountStatus::Destroyed => proto::AccountStatus::Destroyed, + reth::revm::db::AccountStatus::DestroyedChanged => { + proto::AccountStatus::DestroyedChanged + } + reth::revm::db::AccountStatus::DestroyedAgain => proto::AccountStatus::DestroyedAgain, + } + } +} + +impl TryFrom<(Address, &reth::revm::db::states::reverts::AccountRevert)> for proto::Revert { + type Error = eyre::Error; + + fn try_from( + (address, revert): (Address, &reth::revm::db::states::reverts::AccountRevert), + ) -> Result { + Ok(proto::Revert { + address: address.to_vec(), + account: Some(proto::AccountInfoRevert { + revert: Some(match &revert.account { + reth::revm::db::states::reverts::AccountInfoRevert::DoNothing => { + proto::account_info_revert::Revert::DoNothing(()) + } + reth::revm::db::states::reverts::AccountInfoRevert::DeleteIt => { + proto::account_info_revert::Revert::DeleteIt(()) + } + reth::revm::db::states::reverts::AccountInfoRevert::RevertTo(account_info) => { + proto::account_info_revert::Revert::RevertTo(account_info.try_into()?) + } + }), + }), + storage: revert + .storage + .iter() + .map(|(key, slot)| { + Ok(proto::RevertToSlot { + key: key.to_le_bytes_vec(), + revert: Some(match slot { + reth::revm::db::RevertToSlot::Some(value) => { + proto::revert_to_slot::Revert::Some(value.to_le_bytes_vec()) + } + reth::revm::db::RevertToSlot::Destroyed => { + proto::revert_to_slot::Revert::Destroyed(()) + } + }), + }) + }) + .collect::>()?, + previous_status: proto::AccountStatus::from(revert.previous_status) as i32, + wipe_storage: revert.wipe_storage, + }) + } +} + +impl TryFrom<&Option> for proto::Receipt { + type Error = eyre::Error; + + fn try_from(receipt: &Option) -> Result { + Ok(proto::Receipt { + receipt: Some( + receipt + .as_ref() + .map_or(eyre::Ok(proto::receipt::Receipt::Empty(())), |receipt| { + Ok(proto::receipt::Receipt::NonEmpty(receipt.try_into()?)) + })?, + ), + }) + } +} + +impl TryFrom<&reth::primitives::Receipt> for proto::NonEmptyReceipt { + type Error = eyre::Error; + + fn try_from(receipt: &reth::primitives::Receipt) -> Result { + Ok(proto::NonEmptyReceipt { + tx_type: match receipt.tx_type { + reth::primitives::TxType::Legacy => proto::TxType::Legacy, + reth::primitives::TxType::Eip2930 => proto::TxType::Eip2930, + reth::primitives::TxType::Eip1559 => proto::TxType::Eip1559, + reth::primitives::TxType::Eip4844 => proto::TxType::Eip4844, + #[cfg(feature = "optimism")] + reth::primitives::TxType::Deposit => { + eyre::bail!("deposit transaction not supported") + } + } as i32, + success: receipt.success, + cumulative_gas_used: receipt.cumulative_gas_used, + logs: receipt + .logs + .iter() + .map(|log| proto::Log { + address: log.address.to_vec(), + data: Some(proto::LogData { + topics: log.data.topics().iter().map(|topic| topic.to_vec()).collect(), + data: log.data.data.to_vec(), + }), + }) + .collect(), + }) + } +} + +impl TryFrom<&proto::ExExNotification> for reth_exex::ExExNotification { + type Error = eyre::Error; + + fn try_from(notification: &proto::ExExNotification) -> Result { + Ok(match notification.notification.as_ref().ok_or_eyre("no notification")? { + proto::ex_ex_notification::Notification::ChainCommitted(proto::ChainCommitted { + new, + }) => reth_exex::ExExNotification::ChainCommitted { + new: Arc::new(new.as_ref().ok_or_eyre("no new chain")?.try_into()?), + }, + proto::ex_ex_notification::Notification::ChainReorged(proto::ChainReorged { + old, + new, + }) => reth_exex::ExExNotification::ChainReorged { + old: Arc::new(old.as_ref().ok_or_eyre("no old chain")?.try_into()?), + new: Arc::new(new.as_ref().ok_or_eyre("no new chain")?.try_into()?), + }, + proto::ex_ex_notification::Notification::ChainReverted(proto::ChainReverted { + old, + }) => reth_exex::ExExNotification::ChainReverted { + old: Arc::new(old.as_ref().ok_or_eyre("no old chain")?.try_into()?), + }, + }) + } +} + +impl TryFrom<&proto::Chain> for reth::providers::Chain { + type Error = eyre::Error; + + fn try_from(chain: &proto::Chain) -> Result { + let execution_outcome = + chain.execution_outcome.as_ref().ok_or_eyre("no execution outcome")?; + let bundle = execution_outcome.bundle.as_ref().ok_or_eyre("no bundle")?; + Ok(reth::providers::Chain::new( + chain.blocks.iter().map(TryInto::try_into).collect::>>()?, + reth::providers::ExecutionOutcome { + bundle: reth::revm::db::BundleState { + state: bundle + .state + .iter() + .map(TryInto::try_into) + .collect::>()?, + contracts: bundle + .contracts + .iter() + .map(|contract| { + Ok(( + B256::try_from(contract.hash.as_slice())?, + contract.bytecode.as_ref().ok_or_eyre("no bytecode")?.try_into()?, + )) + }) + .collect::>()?, + reverts: reth::revm::db::states::reverts::Reverts::new( + bundle + .reverts + .iter() + .map(|block_reverts| { + block_reverts + .reverts + .iter() + .map(TryInto::try_into) + .collect::>() + }) + .collect::>()?, + ), + state_size: bundle.state_size as usize, + reverts_size: bundle.reverts_size as usize, + }, + receipts: reth::primitives::Receipts::from_iter( + execution_outcome + .receipts + .iter() + .map(|block_receipts| { + block_receipts + .receipts + .iter() + .map(TryInto::try_into) + .collect::>() + }) + .collect::>>()?, + ), + first_block: execution_outcome.first_block, + requests: Default::default(), + }, + None, + )) + } +} + +impl TryFrom<&proto::Block> for reth::primitives::SealedBlockWithSenders { + type Error = eyre::Error; + + fn try_from(block: &proto::Block) -> Result { + let sealed_header = block.header.as_ref().ok_or_eyre("no sealed header")?; + let header = sealed_header.header.as_ref().ok_or_eyre("no header")?.try_into()?; + let sealed_header = reth::primitives::SealedHeader::new( + header, + BlockHash::try_from(sealed_header.hash.as_slice())?, + ); + + let transactions = block.body.iter().map(TryInto::try_into).collect::>()?; + let ommers = block.ommers.iter().map(TryInto::try_into).collect::>()?; + let senders = block + .senders + .iter() + .map(|sender| Address::try_from(sender.as_slice())) + .collect::>()?; + + reth::primitives::SealedBlockWithSenders::new( + reth::primitives::SealedBlock::new( + sealed_header, + reth::primitives::BlockBody { + transactions, + ommers, + withdrawals: Default::default(), + requests: Default::default(), + }, + ), + senders, + ) + .ok_or_eyre("senders do not match transactions") + } +} + +impl TryFrom<&proto::Header> for reth::primitives::Header { + type Error = eyre::Error; + + fn try_from(header: &proto::Header) -> Result { + Ok(reth::primitives::Header { + parent_hash: B256::try_from(header.parent_hash.as_slice())?, + ommers_hash: B256::try_from(header.ommers_hash.as_slice())?, + beneficiary: Address::try_from(header.beneficiary.as_slice())?, + state_root: B256::try_from(header.state_root.as_slice())?, + transactions_root: B256::try_from(header.transactions_root.as_slice())?, + receipts_root: B256::try_from(header.receipts_root.as_slice())?, + withdrawals_root: header + .withdrawals_root + .as_ref() + .map(|root| B256::try_from(root.as_slice())) + .transpose()?, + logs_bloom: Bloom::try_from(header.logs_bloom.as_slice())?, + difficulty: U256::try_from_le_slice(&header.difficulty) + .ok_or_eyre("failed to parse difficulty")?, + number: header.number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + mix_hash: B256::try_from(header.mix_hash.as_slice())?, + nonce: header.nonce, + base_fee_per_gas: header.base_fee_per_gas, + blob_gas_used: header.blob_gas_used, + excess_blob_gas: header.excess_blob_gas, + parent_beacon_block_root: header + .parent_beacon_block_root + .as_ref() + .map(|root| B256::try_from(root.as_slice())) + .transpose()?, + requests_root: None, + extra_data: header.extra_data.as_slice().to_vec().into(), + }) + } +} + +impl TryFrom<&proto::Transaction> for reth::primitives::TransactionSigned { + type Error = eyre::Error; + + fn try_from(transaction: &proto::Transaction) -> Result { + let hash = TxHash::try_from(transaction.hash.as_slice())?; + let signature = transaction.signature.as_ref().ok_or_eyre("no signature")?; + let signature = reth::primitives::Signature { + r: U256::try_from_le_slice(signature.r.as_slice()).ok_or_eyre("failed to parse r")?, + s: U256::try_from_le_slice(signature.s.as_slice()).ok_or_eyre("failed to parse s")?, + odd_y_parity: signature.odd_y_parity, + }; + let transaction = match transaction.transaction.as_ref().ok_or_eyre("no transaction")? { + proto::transaction::Transaction::Legacy(proto::TransactionLegacy { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + input, + }) => reth::primitives::Transaction::Legacy(reth::primitives::TxLegacy { + chain_id: *chain_id, + nonce: *nonce, + gas_price: u128::from_le_bytes(gas_price.as_slice().try_into()?), + gas_limit: *gas_limit, + to: to.as_ref().ok_or_eyre("no to")?.try_into()?, + value: U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse value")?, + input: input.to_vec().into(), + }), + proto::transaction::Transaction::Eip2930(proto::TransactionEip2930 { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + access_list, + input, + }) => reth::primitives::Transaction::Eip2930(reth::primitives::TxEip2930 { + chain_id: *chain_id, + nonce: *nonce, + gas_price: u128::from_le_bytes(gas_price.as_slice().try_into()?), + gas_limit: *gas_limit, + to: to.as_ref().ok_or_eyre("no to")?.try_into()?, + value: U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse value")?, + access_list: access_list + .iter() + .map(TryInto::try_into) + .collect::>>()? + .into(), + input: input.to_vec().into(), + }), + proto::transaction::Transaction::Eip1559(proto::TransactionEip1559 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value, + access_list, + input, + }) => reth::primitives::Transaction::Eip1559(reth::primitives::TxEip1559 { + chain_id: *chain_id, + nonce: *nonce, + gas_limit: *gas_limit, + max_fee_per_gas: u128::from_le_bytes(max_fee_per_gas.as_slice().try_into()?), + max_priority_fee_per_gas: u128::from_le_bytes( + max_priority_fee_per_gas.as_slice().try_into()?, + ), + to: to.as_ref().ok_or_eyre("no to")?.try_into()?, + value: U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse value")?, + access_list: access_list + .iter() + .map(TryInto::try_into) + .collect::>>()? + .into(), + input: input.to_vec().into(), + }), + proto::transaction::Transaction::Eip4844(proto::TransactionEip4844 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value, + access_list, + blob_versioned_hashes, + max_fee_per_blob_gas, + input, + }) => reth::primitives::Transaction::Eip4844(reth::primitives::TxEip4844 { + chain_id: *chain_id, + nonce: *nonce, + gas_limit: *gas_limit, + max_fee_per_gas: u128::from_le_bytes(max_fee_per_gas.as_slice().try_into()?), + max_priority_fee_per_gas: u128::from_le_bytes( + max_priority_fee_per_gas.as_slice().try_into()?, + ), + placeholder: None, + to: Address::try_from(to.as_slice())?, + value: U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse value")?, + access_list: access_list + .iter() + .map(TryInto::try_into) + .collect::>>()? + .into(), + blob_versioned_hashes: blob_versioned_hashes + .iter() + .map(|hash| B256::try_from(hash.as_slice())) + .collect::>()?, + max_fee_per_blob_gas: u128::from_le_bytes( + max_fee_per_blob_gas.as_slice().try_into()?, + ), + input: input.to_vec().into(), + }), + }; + + Ok(reth::primitives::TransactionSigned { hash, signature, transaction }) + } +} + +impl TryFrom<&proto::TxKind> for reth::primitives::TxKind { + type Error = eyre::Error; + + fn try_from(tx_kind: &proto::TxKind) -> Result { + Ok(match tx_kind.kind.as_ref().ok_or_eyre("no kind")? { + proto::tx_kind::Kind::Create(()) => reth::primitives::TxKind::Create, + proto::tx_kind::Kind::Call(address) => { + reth::primitives::TxKind::Call(Address::try_from(address.as_slice())?) + } + }) + } +} + +impl TryFrom<&proto::AccessListItem> for reth::primitives::AccessListItem { + type Error = eyre::Error; + + fn try_from(item: &proto::AccessListItem) -> Result { + Ok(reth::primitives::AccessListItem { + address: Address::try_from(item.address.as_slice())?, + storage_keys: item + .storage_keys + .iter() + .map(|key| B256::try_from(key.as_slice())) + .collect::>()?, + }) + } +} + +impl TryFrom<&proto::AccountInfo> for reth::revm::primitives::AccountInfo { + type Error = eyre::Error; + + fn try_from(account_info: &proto::AccountInfo) -> Result { + Ok(reth::revm::primitives::AccountInfo { + balance: U256::try_from_le_slice(account_info.balance.as_slice()) + .ok_or_eyre("failed to parse balance")?, + nonce: account_info.nonce, + code_hash: B256::try_from(account_info.code_hash.as_slice())?, + code: account_info.code.as_ref().map(TryInto::try_into).transpose()?, + }) + } +} + +impl TryFrom<&proto::Bytecode> for reth::revm::primitives::Bytecode { + type Error = eyre::Error; + + fn try_from(bytecode: &proto::Bytecode) -> Result { + Ok(match bytecode.bytecode.as_ref().ok_or_eyre("no bytecode")? { + proto::bytecode::Bytecode::LegacyRaw(code) => { + reth::revm::primitives::Bytecode::LegacyRaw(code.clone().into()) + } + proto::bytecode::Bytecode::LegacyAnalyzed(legacy_analyzed) => { + reth::revm::primitives::Bytecode::LegacyAnalyzed( + reth::revm::primitives::LegacyAnalyzedBytecode::new( + legacy_analyzed.bytecode.clone().into(), + legacy_analyzed.original_len as usize, + reth::revm::primitives::JumpTable::from_slice( + legacy_analyzed + .jump_table + .iter() + .map(|dest| *dest as u8) + .collect::>() + .as_slice(), + ), + ), + ) + } + }) + } +} + +impl From for reth::revm::db::AccountStatus { + fn from(status: proto::AccountStatus) -> Self { + match status { + proto::AccountStatus::LoadedNotExisting => { + reth::revm::db::AccountStatus::LoadedNotExisting + } + proto::AccountStatus::Loaded => reth::revm::db::AccountStatus::Loaded, + proto::AccountStatus::LoadedEmptyEip161 => { + reth::revm::db::AccountStatus::LoadedEmptyEIP161 + } + proto::AccountStatus::InMemoryChange => reth::revm::db::AccountStatus::InMemoryChange, + proto::AccountStatus::Changed => reth::revm::db::AccountStatus::Changed, + proto::AccountStatus::Destroyed => reth::revm::db::AccountStatus::Destroyed, + proto::AccountStatus::DestroyedChanged => { + reth::revm::db::AccountStatus::DestroyedChanged + } + proto::AccountStatus::DestroyedAgain => reth::revm::db::AccountStatus::DestroyedAgain, + } + } +} + +impl TryFrom<&proto::BundleAccount> for (Address, reth::revm::db::BundleAccount) { + type Error = eyre::Error; + + fn try_from(account: &proto::BundleAccount) -> Result { + Ok(( + Address::try_from(account.address.as_slice())?, + reth::revm::db::BundleAccount { + info: account.info.as_ref().map(TryInto::try_into).transpose()?, + original_info: account.original_info.as_ref().map(TryInto::try_into).transpose()?, + storage: account + .storage + .iter() + .map(|slot| { + Ok(( + U256::try_from_le_slice(slot.key.as_slice()) + .ok_or_eyre("failed to parse key")?, + reth::revm::db::states::StorageSlot { + previous_or_original_value: U256::try_from_le_slice( + slot.previous_or_original_value.as_slice(), + ) + .ok_or_eyre("failed to parse previous or original value")?, + present_value: U256::try_from_le_slice( + slot.present_value.as_slice(), + ) + .ok_or_eyre("failed to parse present value")?, + }, + )) + }) + .collect::>()?, + status: proto::AccountStatus::try_from(account.status)?.into(), + }, + )) + } +} + +impl TryFrom<&proto::Revert> for (Address, reth::revm::db::states::reverts::AccountRevert) { + type Error = eyre::Error; + + fn try_from(revert: &proto::Revert) -> Result { + Ok(( + Address::try_from(revert.address.as_slice())?, + reth::revm::db::states::reverts::AccountRevert { + account: match revert + .account + .as_ref() + .ok_or_eyre("no revert account")? + .revert + .as_ref() + .ok_or_eyre("no revert account revert")? + { + proto::account_info_revert::Revert::DoNothing(()) => { + reth::revm::db::states::reverts::AccountInfoRevert::DoNothing + } + proto::account_info_revert::Revert::DeleteIt(()) => { + reth::revm::db::states::reverts::AccountInfoRevert::DeleteIt + } + proto::account_info_revert::Revert::RevertTo(account_info) => { + reth::revm::db::states::reverts::AccountInfoRevert::RevertTo( + account_info.try_into()?, + ) + } + }, + storage: revert + .storage + .iter() + .map(|slot| { + Ok(( + U256::try_from_le_slice(slot.key.as_slice()) + .ok_or_eyre("failed to parse slot key")?, + match slot.revert.as_ref().ok_or_eyre("no slot revert")? { + proto::revert_to_slot::Revert::Some(value) => { + reth::revm::db::states::reverts::RevertToSlot::Some( + U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse slot revert")?, + ) + } + proto::revert_to_slot::Revert::Destroyed(()) => { + reth::revm::db::states::reverts::RevertToSlot::Destroyed + } + }, + )) + }) + .collect::>()?, + previous_status: proto::AccountStatus::try_from(revert.previous_status)?.into(), + wipe_storage: revert.wipe_storage, + }, + )) + } +} + +impl TryFrom<&proto::Receipt> for Option { + type Error = eyre::Error; + + fn try_from(receipt: &proto::Receipt) -> Result { + Ok(match receipt.receipt.as_ref().ok_or_eyre("no receipt")? { + proto::receipt::Receipt::Empty(()) => None, + proto::receipt::Receipt::NonEmpty(receipt) => Some(receipt.try_into()?), + }) + } +} + +impl TryFrom<&proto::NonEmptyReceipt> for reth::primitives::Receipt { + type Error = eyre::Error; + + fn try_from(receipt: &proto::NonEmptyReceipt) -> Result { + Ok(reth::primitives::Receipt { + tx_type: match proto::TxType::try_from(receipt.tx_type)? { + proto::TxType::Legacy => reth::primitives::TxType::Legacy, + proto::TxType::Eip2930 => reth::primitives::TxType::Eip2930, + proto::TxType::Eip1559 => reth::primitives::TxType::Eip1559, + proto::TxType::Eip4844 => reth::primitives::TxType::Eip4844, + }, + success: receipt.success, + cumulative_gas_used: receipt.cumulative_gas_used, + logs: receipt + .logs + .iter() + .map(|log| { + let data = log.data.as_ref().ok_or_eyre("no log data")?; + Ok(reth::primitives::Log { + address: Address::try_from(log.address.as_slice())?, + data: reth::primitives::LogData::new_unchecked( + data.topics + .iter() + .map(|topic| Ok(B256::try_from(topic.as_slice())?)) + .collect::>()?, + data.data.clone().into(), + ), + }) + }) + .collect::>()?, + #[cfg(feature = "optimism")] + deposit_nonce: None, + #[cfg(feature = "optimism")] + deposit_receipt_version: None, + }) + } +} diff --git a/examples/exex/remote/src/lib.rs b/examples/exex/remote/src/lib.rs new file mode 100644 index 000000000000..9b8aa5781a8f --- /dev/null +++ b/examples/exex/remote/src/lib.rs @@ -0,0 +1,4 @@ +pub mod codec; +pub mod proto { + tonic::include_proto!("exex"); +}