From 8dd7a78356cea0b0ea7dd45281020806c27a429c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 25 Jun 2023 15:12:00 +0200 Subject: [PATCH] feat: add transaction validation task (#3358) --- Cargo.lock | 369 +++++------------ Cargo.toml | 4 +- bin/reth/src/node/mod.rs | 20 +- crates/transaction-pool/Cargo.toml | 2 + crates/transaction-pool/src/lib.rs | 4 +- crates/transaction-pool/src/maintain.rs | 28 +- crates/transaction-pool/src/pool/mod.rs | 6 +- crates/transaction-pool/src/validate.rs | 411 ------------------- crates/transaction-pool/src/validate/eth.rs | 306 ++++++++++++++ crates/transaction-pool/src/validate/mod.rs | 225 ++++++++++ crates/transaction-pool/src/validate/task.rs | 62 +++ 11 files changed, 745 insertions(+), 692 deletions(-) delete mode 100644 crates/transaction-pool/src/validate.rs create mode 100644 crates/transaction-pool/src/validate/eth.rs create mode 100644 crates/transaction-pool/src/validate/mod.rs create mode 100644 crates/transaction-pool/src/validate/task.rs diff --git a/Cargo.lock b/Cargo.lock index 847fc9440..6d8e731b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -324,12 +324,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - [[package]] name = "base16ct" version = "0.2.0" @@ -437,9 +431,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded" [[package]] name = "bitvec" @@ -494,9 +488,9 @@ dependencies = [ [[package]] name = "boa_ast" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.2", "boa_interner", "boa_macros", "indexmap", @@ -507,9 +501,9 @@ dependencies = [ [[package]] name = "boa_engine" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.2", "boa_ast", "boa_gc", "boa_icu_provider", @@ -545,7 +539,7 @@ dependencies = [ [[package]] name = "boa_gc" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ "boa_macros", "boa_profiler", @@ -555,7 +549,7 @@ dependencies = [ [[package]] name = "boa_icu_provider" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ "icu_collections", "icu_normalizer", @@ -568,7 +562,7 @@ dependencies = [ [[package]] name = "boa_interner" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ "boa_gc", "boa_macros", @@ -583,7 +577,7 @@ dependencies = [ [[package]] name = "boa_macros" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ "proc-macro2 1.0.60", "quote 1.0.28", @@ -594,9 +588,9 @@ dependencies = [ [[package]] name = "boa_parser" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.2", "boa_ast", "boa_icu_provider", "boa_interner", @@ -614,7 +608,7 @@ dependencies = [ [[package]] name = "boa_profiler" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" [[package]] name = "brotli" @@ -936,7 +930,7 @@ dependencies = [ "digest 0.10.6", "getrandom 0.2.9", "hmac", - "k256 0.13.1", + "k256", "lazy_static", "serde", "sha2 0.10.6", @@ -1212,18 +1206,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-bigint" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-bigint" version = "0.5.1" @@ -1276,13 +1258,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "4.0.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "03d928d978dbec61a1167414f5ec534f24bea0d7a0d24dd9b6233d3d8223e585" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", + "cfg-if", + "digest 0.10.6", + "fiat-crypto", + "packed_simd_2", + "platforms", "subtle", "zeroize", ] @@ -1471,17 +1455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4355c25cbf99edcb6b4a0e906f6bdc6956eda149e84455bea49696429b2f8e8" dependencies = [ "futures", - "tokio-util 0.7.7", -] - -[[package]] -name = "der" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -dependencies = [ - "const-oid", - "zeroize", + "tokio-util", ] [[package]] @@ -1628,14 +1602,14 @@ dependencies = [ [[package]] name = "discv5" -version = "0.2.2" -source = "git+https://github.com/sigp/discv5#d86707d79c1183b14b8cf31ef62a8a74ef9cd3e4" +version = "0.3.0" +source = "git+https://github.com/sigp/discv5#47844ca54e8d22f4fd3db4594645e65afb288bb6" dependencies = [ "aes 0.7.5", "aes-gcm", "arrayvec", "delay_map", - "enr 0.7.0", + "enr", "fnv", "futures", "hashlink", @@ -1650,8 +1624,6 @@ dependencies = [ "smallvec", "socket2", "tokio", - "tokio-stream", - "tokio-util 0.6.10", "tracing", "tracing-subscriber", "uint", @@ -1699,51 +1671,40 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" -[[package]] -name = "ecdsa" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" -dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] - [[package]] name = "ecdsa" version = "0.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a48e5d537b8a30c0b023116d981b16334be1485af7ca68db3a2b7024cbc957fd" dependencies = [ - "der 0.7.3", + "der", "digest 0.10.6", - "elliptic-curve 0.13.4", - "rfc6979 0.4.0", - "signature 2.1.0", + "elliptic-curve", + "rfc6979", + "signature", ] [[package]] name = "ed25519" -version = "1.5.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" dependencies = [ - "signature 1.6.4", + "pkcs8", + "signature", ] [[package]] name = "ed25519-dalek" -version = "1.0.1" +version = "2.0.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +checksum = "798f704d128510932661a3489b08e3f4c934a01d61c5def59ae7b8e48f19665a" dependencies = [ "curve25519-dalek", "ed25519", - "rand 0.7.3", + "rand_core 0.6.4", "serde", - "sha2 0.9.9", + "sha2 0.10.6", "zeroize", ] @@ -1784,41 +1745,21 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" -[[package]] -name = "elliptic-curve" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.6", - "ff 0.12.1", - "generic-array", - "group 0.12.1", - "pkcs8 0.9.0", - "rand_core 0.6.4", - "sec1 0.3.0", - "subtle", - "zeroize", -] - [[package]] name = "elliptic-curve" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7" dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.1", + "base16ct", + "crypto-bigint", "digest 0.10.6", - "ff 0.13.0", + "ff", "generic-array", - "group 0.13.0", - "pkcs8 0.10.2", + "group", + "pkcs8", "rand_core 0.6.4", - "sec1 0.7.2", + "sec1", "subtle", "zeroize", ] @@ -1844,26 +1785,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" -[[package]] -name = "enr" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" -dependencies = [ - "base64 0.13.1", - "bs58", - "bytes", - "ed25519-dalek", - "hex", - "k256 0.11.6", - "log", - "rand 0.8.5", - "rlp", - "serde", - "sha3", - "zeroize", -] - [[package]] name = "enr" version = "0.8.1" @@ -1872,8 +1793,9 @@ checksum = "cf56acd72bb22d2824e66ae8e9e5ada4d0de17a69c7fd35569dde2ada8ec9116" dependencies = [ "base64 0.13.1", "bytes", + "ed25519-dalek", "hex", - "k256 0.13.1", + "k256", "log", "rand 0.8.5", "rlp", @@ -2101,11 +2023,11 @@ dependencies = [ "bytes", "cargo_metadata", "chrono", - "elliptic-curve 0.13.4", + "elliptic-curve", "ethabi", "generic-array", "hex", - "k256 0.13.1", + "k256", "num_enum", "once_cell", "open-fastrlp", @@ -2173,7 +2095,7 @@ dependencies = [ "auto_impl", "base64 0.21.0", "bytes", - "enr 0.8.1", + "enr", "ethers-core", "futures-channel", "futures-core", @@ -2209,7 +2131,7 @@ dependencies = [ "async-trait", "coins-bip32", "coins-bip39", - "elliptic-curve 0.13.4", + "elliptic-curve", "eth-keystore", "ethers-core", "hex", @@ -2265,16 +2187,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "ff" version = "0.13.0" @@ -2285,6 +2197,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + [[package]] name = "findshlibs" version = "0.10.2" @@ -2588,24 +2506,13 @@ dependencies = [ "web-sys", ] -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.0", + "ff", "rand_core 0.6.4", "subtle", ] @@ -2625,7 +2532,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.7", + "tokio-util", "tracing", ] @@ -3322,7 +3229,7 @@ dependencies = [ "thiserror", "tokio", "tokio-rustls", - "tokio-util 0.7.7", + "tokio-util", "tracing", "webpki-roots", ] @@ -3402,7 +3309,7 @@ dependencies = [ "soketto", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tower", "tracing", ] @@ -3458,18 +3365,6 @@ dependencies = [ "simple_asn1", ] -[[package]] -name = "k256" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" -dependencies = [ - "cfg-if", - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.6", -] - [[package]] name = "k256" version = "0.13.1" @@ -3477,11 +3372,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", - "ecdsa 0.16.6", - "elliptic-curve 0.13.4", + "ecdsa", + "elliptic-curve", "once_cell", "sha2 0.10.6", - "signature 2.1.0", + "signature", ] [[package]] @@ -3524,6 +3419,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + [[package]] name = "libm" version = "0.2.6" @@ -3981,7 +3882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", - "libm", + "libm 0.2.6", ] [[package]] @@ -4107,6 +4008,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "packed_simd_2" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" +dependencies = [ + "cfg-if", + "libm 0.1.4", +] + [[package]] name = "page_size" version = "0.4.2" @@ -4336,24 +4247,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der 0.6.1", - "spki 0.6.0", -] - [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.3", - "spki 0.7.1", + "der", + "spki", ] [[package]] @@ -4371,6 +4272,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + [[package]] name = "plotters" version = "0.3.4" @@ -5164,7 +5071,7 @@ name = "reth-discv4" version = "0.1.0-alpha.1" dependencies = [ "discv5", - "enr 0.8.1", + "enr", "generic-array", "hex", "rand 0.8.5", @@ -5188,7 +5095,7 @@ version = "0.1.0-alpha.1" dependencies = [ "async-trait", "data-encoding", - "enr 0.8.1", + "enr", "linked_hash_set", "parking_lot 0.12.1", "reth-net-common", @@ -5227,7 +5134,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tracing", ] @@ -5257,7 +5164,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tracing", "typenum", ] @@ -5291,7 +5198,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tracing", ] @@ -5336,7 +5243,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tower", "tracing", "tracing-test", @@ -5425,7 +5332,7 @@ dependencies = [ "aquamarine", "async-trait", "auto_impl", - "enr 0.8.1", + "enr", "ethers-core", "ethers-middleware", "ethers-providers", @@ -5464,7 +5371,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tracing", ] @@ -5670,7 +5577,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tower", "tracing", "tracing-futures", @@ -5775,7 +5682,7 @@ dependencies = [ "assert_matches", "async-trait", "confy", - "enr 0.8.1", + "enr", "ethers-core", "ethers-middleware", "ethers-providers", @@ -5882,9 +5789,11 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-rlp", + "reth-tasks", "serde", "thiserror", "tokio", + "tokio-stream", "tracing", ] @@ -5934,7 +5843,7 @@ name = "revm-precompile" version = "2.0.3" source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" dependencies = [ - "k256 0.13.1", + "k256", "num", "once_cell", "revm-primitives", @@ -5969,17 +5878,6 @@ dependencies = [ "sha3", ] -[[package]] -name = "rfc6979" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" -dependencies = [ - "crypto-bigint 0.4.9", - "hmac", - "zeroize", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -6290,30 +6188,16 @@ dependencies = [ "untrusted", ] -[[package]] -name = "sec1" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array", - "pkcs8 0.9.0", - "subtle", - "zeroize", -] - [[package]] name = "sec1" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" dependencies = [ - "base16ct 0.2.0", - "der 0.7.3", + "base16ct", + "der", "generic-array", - "pkcs8 0.10.2", + "pkcs8", "subtle", "zeroize", ] @@ -6619,16 +6503,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest 0.10.6", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "2.1.0" @@ -6754,16 +6628,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - [[package]] name = "spki" version = "0.7.1" @@ -6771,7 +6635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" dependencies = [ "base64ct", - "der 0.7.3", + "der", ] [[package]] @@ -7203,7 +7067,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util 0.7.7", + "tokio-util", ] [[package]] @@ -7218,21 +7082,6 @@ dependencies = [ "tungstenite", ] -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "slab", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.7" @@ -7320,7 +7169,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.7", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -7348,7 +7197,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tokio", - "tokio-util 0.7.7", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -7682,9 +7531,9 @@ checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" diff --git a/Cargo.toml b/Cargo.toml index 09bf2988a..6b94e8db0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,10 +114,10 @@ tokio = { version = "1.21", default-features = false } tokio-util = { version = "0.7.4", features = ["codec"] } ## async -async-trait = "0.1.58" +async-trait = "0.1.68" futures = "0.3.26" pin-project = "1.0.12" futures-util = "0.3.25" ## crypto -secp256k1 = { version = "0.27.0", default-features = false, features = ["global-context", "rand-std", "recovery"] } \ No newline at end of file +secp256k1 = { version = "0.27.0", default-features = false, features = ["global-context", "rand-std", "recovery"] } diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 9aa3e8eab..3ee507720 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -206,7 +206,12 @@ impl Command { let blockchain_db = BlockchainProvider::new(factory, blockchain_tree.clone())?; let transaction_pool = reth_transaction_pool::Pool::eth_pool( - EthTransactionValidator::new(blockchain_db.clone(), Arc::clone(&self.chain)), + EthTransactionValidator::new( + blockchain_db.clone(), + Arc::clone(&self.chain), + ctx.task_executor.clone(), + 1, + ), Default::default(), ); info!(target: "reth::cli", "Transaction pool initialized"); @@ -218,14 +223,11 @@ impl Command { let client = blockchain_db.clone(); ctx.task_executor.spawn_critical( "txpool maintenance task", - Box::pin(async move { - reth_transaction_pool::maintain::maintain_transaction_pool( - client, - pool, - chain_events, - ) - .await - }), + reth_transaction_pool::maintain::maintain_transaction_pool_future( + client, + pool, + chain_events, + ), ); debug!(target: "reth::cli", "Spawned txpool maintenance task"); } diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index efc0cda13..c562df71c 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -23,12 +23,14 @@ reth-provider = { workspace = true } reth-interfaces = { workspace = true } reth-rlp = { workspace = true } reth-metrics = { workspace = true } +reth-tasks = { workspace = true } # async/futures async-trait = { workspace = true} futures-util = { workspace = true } parking_lot = "0.12" tokio = { workspace = true, default-features = false, features = ["sync"] } +tokio-stream.workspace = true # misc aquamarine = "0.3.0" diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index d0054c9cc..598cb6714 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -116,7 +116,7 @@ pub mod metrics; mod ordering; pub mod pool; mod traits; -mod validate; +pub mod validate; #[cfg(any(test, feature = "test-utils"))] /// Common test helpers for mocking A pool @@ -222,7 +222,7 @@ where impl Pool, CostOrdering> where - Client: StateProviderFactory, + Client: StateProviderFactory + Clone + 'static, { /// Returns a new [Pool] that uses the default [EthTransactionValidator] when validating /// [PooledTransaction]s and ords via [CostOrdering] diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index dd2cfb111..5805e715a 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -4,7 +4,7 @@ use crate::{ traits::{CanonicalStateUpdate, ChangedAccount}, BlockInfo, Pool, TransactionOrdering, TransactionPool, TransactionValidator, }; -use futures_util::{Stream, StreamExt}; +use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt}; use reth_primitives::{Address, BlockHash, BlockNumberOrTag, FromRecoveredTransaction}; use reth_provider::{BlockProviderIdExt, CanonStateNotification, PostState, StateProviderFactory}; use std::{ @@ -18,6 +18,24 @@ use tracing::debug; /// last_seen.number` const MAX_UPDATE_DEPTH: u64 = 64; +/// Returns a spawnable future for maintaining the state of the transaction pool. +pub fn maintain_transaction_pool_future( + client: Client, + pool: Pool, + events: St, +) -> BoxFuture<'static, ()> +where + Client: StateProviderFactory + BlockProviderIdExt + Send + 'static, + V: TransactionValidator + Send + 'static, + T: TransactionOrdering::Transaction> + Send + 'static, + St: Stream + Send + Unpin + 'static, +{ + async move { + maintain_transaction_pool(client, pool, events).await; + } + .boxed() +} + /// Maintains the state of the transaction pool by handling new blocks and reorgs. /// /// This listens for any new blocks and reorgs and updates the transaction pool's state accordingly @@ -27,10 +45,10 @@ pub async fn maintain_transaction_pool( pool: Pool, mut events: St, ) where - Client: StateProviderFactory + BlockProviderIdExt, - V: TransactionValidator, - T: TransactionOrdering::Transaction>, - St: Stream + Unpin, + Client: StateProviderFactory + BlockProviderIdExt + Send + 'static, + V: TransactionValidator + Send + 'static, + T: TransactionOrdering::Transaction> + Send + 'static, + St: Stream + Send + Unpin + 'static, { // ensure the pool points to latest state if let Ok(Some(latest)) = client.block_by_number_or_tag(BlockNumberOrTag::Latest) { diff --git a/crates/transaction-pool/src/pool/mod.rs b/crates/transaction-pool/src/pool/mod.rs index e4744a296..293e7af22 100644 --- a/crates/transaction-pool/src/pool/mod.rs +++ b/crates/transaction-pool/src/pool/mod.rs @@ -285,10 +285,10 @@ where listener.discarded(tx.hash()); Err(PoolError::InvalidTransaction(*tx.hash(), err)) } - TransactionValidationOutcome::Error(tx, err) => { + TransactionValidationOutcome::Error(tx_hash, err) => { let mut listener = self.event_listener.write(); - listener.discarded(tx.hash()); - Err(PoolError::Other(*tx.hash(), err)) + listener.discarded(&tx_hash); + Err(PoolError::Other(tx_hash, err)) } } } diff --git a/crates/transaction-pool/src/validate.rs b/crates/transaction-pool/src/validate.rs deleted file mode 100644 index 959d5958b..000000000 --- a/crates/transaction-pool/src/validate.rs +++ /dev/null @@ -1,411 +0,0 @@ -//! Transaction validation abstractions. - -use crate::{ - error::InvalidPoolTransactionError, - identifier::{SenderId, TransactionId}, - traits::{PoolTransaction, TransactionOrigin}, - MAX_INIT_CODE_SIZE, TX_MAX_SIZE, -}; -use reth_primitives::{ - constants::ETHEREUM_BLOCK_GAS_LIMIT, Address, ChainSpec, IntoRecoveredTransaction, - InvalidTransactionError, TransactionKind, TransactionSignedEcRecovered, TxHash, - EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, U256, -}; -use reth_provider::{AccountReader, StateProviderFactory}; -use std::{fmt, marker::PhantomData, sync::Arc, time::Instant}; - -/// A Result type returned after checking a transaction's validity. -#[derive(Debug)] -pub enum TransactionValidationOutcome { - /// The transaction is considered _currently_ valid and can be inserted into the pool. - Valid { - /// Balance of the sender at the current point. - balance: U256, - /// Current nonce of the sender. - state_nonce: u64, - /// Validated transaction. - transaction: T, - }, - /// The transaction is considered invalid indefinitely: It violates constraints that prevent - /// this transaction from ever becoming valid. - Invalid(T, InvalidPoolTransactionError), - /// An error occurred while trying to validate the transaction - Error(T, Box), -} - -impl TransactionValidationOutcome { - /// Returns the transaction that was validated. - pub fn transaction(&self) -> &T { - match self { - Self::Valid { transaction, .. } => transaction, - Self::Invalid(transaction, ..) => transaction, - Self::Error(transaction, ..) => transaction, - } - } - - /// Returns the hash of the transactions - pub fn tx_hash(&self) -> TxHash { - *self.transaction().hash() - } -} - -/// Provides support for validating transaction at any given state of the chain -#[async_trait::async_trait] -pub trait TransactionValidator: Send + Sync { - /// The transaction type to validate. - type Transaction: PoolTransaction; - - /// Validates the transaction and returns a [`TransactionValidationOutcome`] describing the - /// validity of the given transaction. - /// - /// This will be used by the transaction-pool to check whether the transaction should be - /// inserted into the pool or discarded right away. - /// - /// Implementers of this trait must ensure that the transaction is well-formed, i.e. that it - /// complies at least all static constraints, which includes checking for: - /// - /// * chain id - /// * gas limit - /// * max cost - /// * nonce >= next nonce of the sender - /// * ... - /// - /// See [InvalidTransactionError](InvalidTransactionError) for common errors variants. - /// - /// The transaction pool makes no additional assumptions about the validity of the transaction - /// at the time of this call before it inserts it into the pool. However, the validity of - /// this transaction is still subject to future (dynamic) changes enforced by the pool, for - /// example nonce or balance changes. Hence, any validation checks must be applied in this - /// function. - /// - /// See [EthTransactionValidator] for a reference implementation. - async fn validate_transaction( - &self, - origin: TransactionOrigin, - transaction: Self::Transaction, - ) -> TransactionValidationOutcome; - - /// Ensure that the code size is not greater than `max_init_code_size`. - /// `max_init_code_size` should be configurable so this will take it as an argument. - fn ensure_max_init_code_size( - &self, - transaction: &Self::Transaction, - max_init_code_size: usize, - ) -> Result<(), InvalidPoolTransactionError> { - if *transaction.kind() == TransactionKind::Create && transaction.size() > max_init_code_size - { - Err(InvalidPoolTransactionError::ExceedsMaxInitCodeSize( - transaction.size(), - max_init_code_size, - )) - } else { - Ok(()) - } - } -} - -/// A [TransactionValidator] implementation that validates ethereum transaction. -#[derive(Debug, Clone)] -pub struct EthTransactionValidator { - /// Spec of the chain - chain_spec: Arc, - /// This type fetches account info from the db - client: Client, - /// Fork indicator whether we are in the Shanghai stage. - shanghai: bool, - /// Fork indicator whether we are using EIP-2718 type transactions. - eip2718: bool, - /// Fork indicator whether we are using EIP-1559 type transactions. - eip1559: bool, - /// The current max gas limit - block_gas_limit: u64, - /// Minimum priority fee to enforce for acceptance into the pool. - minimum_priority_fee: Option, - /// Marker for the transaction type - _marker: PhantomData, -} - -// === impl EthTransactionValidator === - -impl EthTransactionValidator { - /// Creates a new instance for the given [ChainSpec] - pub fn new(client: Client, chain_spec: Arc) -> Self { - // TODO(mattsse): improve these settings by checking against hardfork - // See [reth_consensus::validation::validate_transaction_regarding_header] - Self { - chain_spec, - client, - shanghai: true, - eip2718: true, - eip1559: true, - block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, - minimum_priority_fee: None, - _marker: Default::default(), - } - } - - /// Returns the configured chain id - pub fn chain_id(&self) -> u64 { - self.chain_spec.chain().id() - } -} - -#[async_trait::async_trait] -impl TransactionValidator for EthTransactionValidator -where - Client: StateProviderFactory, - Tx: PoolTransaction, -{ - type Transaction = Tx; - - async fn validate_transaction( - &self, - origin: TransactionOrigin, - transaction: Self::Transaction, - ) -> TransactionValidationOutcome { - // Checks for tx_type - match transaction.tx_type() { - LEGACY_TX_TYPE_ID => { - // Accept legacy transactions - } - EIP2930_TX_TYPE_ID => { - // Accept only legacy transactions until EIP-2718/2930 activates - if !self.eip2718 { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::Eip1559Disabled.into(), - ) - } - } - - EIP1559_TX_TYPE_ID => { - // Reject dynamic fee transactions until EIP-1559 activates. - if !self.eip1559 { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::Eip1559Disabled.into(), - ) - } - } - - _ => { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::TxTypeNotSupported.into(), - ) - } - }; - - // Reject transactions over defined size to prevent DOS attacks - if transaction.size() > TX_MAX_SIZE { - let size = transaction.size(); - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::OversizedData(size, TX_MAX_SIZE), - ) - } - - // Check whether the init code size has been exceeded. - if self.shanghai { - if let Err(err) = self.ensure_max_init_code_size(&transaction, MAX_INIT_CODE_SIZE) { - return TransactionValidationOutcome::Invalid(transaction, err) - } - } - - // Checks for gas limit - if transaction.gas_limit() > self.block_gas_limit { - let gas_limit = transaction.gas_limit(); - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::ExceedsGasLimit(gas_limit, self.block_gas_limit), - ) - } - - // Ensure max_priority_fee_per_gas (if EIP1559) is less than max_fee_per_gas if any. - if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::TipAboveFeeCap.into(), - ) - } - - // Drop non-local transactions with a fee lower than the configured fee for acceptance into - // the pool. - if !origin.is_local() && - transaction.is_eip1559() && - transaction.max_priority_fee_per_gas() < self.minimum_priority_fee - { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Underpriced, - ) - } - - // Checks for chainid - if let Some(chain_id) = transaction.chain_id() { - if chain_id != self.chain_id() { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::ChainIdMismatch.into(), - ) - } - } - - let account = match self - .client - .latest() - .and_then(|state| state.basic_account(transaction.sender())) - { - Ok(account) => account.unwrap_or_default(), - Err(err) => return TransactionValidationOutcome::Error(transaction, Box::new(err)), - }; - - // Signer account shouldn't have bytecode. Presence of bytecode means this is a - // smartcontract. - if account.has_bytecode() { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::SignerAccountHasBytecode.into(), - ) - } - - // Checks for nonce - if transaction.nonce() < account.nonce { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::NonceNotConsistent.into(), - ) - } - - // Checks for max cost - if transaction.cost() > account.balance { - let cost = transaction.cost(); - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::InsufficientFunds { - cost, - available_funds: account.balance, - } - .into(), - ) - } - - // Return the valid transaction - TransactionValidationOutcome::Valid { - balance: account.balance, - state_nonce: account.nonce, - transaction, - } - } -} - -/// A valid transaction in the pool. -pub struct ValidPoolTransaction { - /// The transaction - pub transaction: T, - /// The identifier for this transaction. - pub transaction_id: TransactionId, - /// Whether to propagate the transaction. - pub propagate: bool, - /// Total cost of the transaction: `feeCap x gasLimit + transferredValue`. - pub cost: U256, - /// Timestamp when this was added to the pool. - pub timestamp: Instant, - /// Where this transaction originated from. - pub origin: TransactionOrigin, - /// The length of the rlp encoded transaction (cached) - pub encoded_length: usize, -} - -// === impl ValidPoolTransaction === - -impl ValidPoolTransaction { - /// Returns the hash of the transaction. - pub fn hash(&self) -> &TxHash { - self.transaction.hash() - } - - /// Returns the type identifier of the transaction - pub fn tx_type(&self) -> u8 { - self.transaction.tx_type() - } - - /// Returns the address of the sender - pub fn sender(&self) -> Address { - self.transaction.sender() - } - - /// Returns the internal identifier for the sender of this transaction - pub(crate) fn sender_id(&self) -> SenderId { - self.transaction_id.sender - } - - /// Returns the internal identifier for this transaction. - pub(crate) fn id(&self) -> &TransactionId { - &self.transaction_id - } - - /// Returns the nonce set for this transaction. - pub fn nonce(&self) -> u64 { - self.transaction.nonce() - } - - /// Returns the EIP-1559 Max base fee the caller is willing to pay. - /// - /// For legacy transactions this is gas_price. - pub fn max_fee_per_gas(&self) -> u128 { - self.transaction.max_fee_per_gas() - } - - /// Returns the EIP-1559 Max base fee the caller is willing to pay. - pub fn effective_gas_price(&self) -> u128 { - self.transaction.effective_gas_price() - } - - /// Amount of gas that should be used in executing this transaction. This is paid up-front. - pub fn gas_limit(&self) -> u64 { - self.transaction.gas_limit() - } - - /// Whether the transaction originated locally. - pub fn is_local(&self) -> bool { - self.origin.is_local() - } - - /// The heap allocated size of this transaction. - pub(crate) fn size(&self) -> usize { - self.transaction.size() - } -} - -impl IntoRecoveredTransaction for ValidPoolTransaction { - fn to_recovered_transaction(&self) -> TransactionSignedEcRecovered { - self.transaction.to_recovered_transaction() - } -} - -#[cfg(test)] -impl Clone for ValidPoolTransaction { - fn clone(&self) -> Self { - Self { - transaction: self.transaction.clone(), - transaction_id: self.transaction_id, - propagate: self.propagate, - cost: self.cost, - timestamp: self.timestamp, - origin: self.origin, - encoded_length: self.encoded_length, - } - } -} - -impl fmt::Debug for ValidPoolTransaction { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "Transaction {{ ")?; - write!(fmt, "hash: {:?}, ", &self.transaction.hash())?; - write!(fmt, "provides: {:?}, ", &self.transaction_id)?; - write!(fmt, "raw tx: {:?}", &self.transaction)?; - write!(fmt, "}}")?; - Ok(()) - } -} diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs new file mode 100644 index 000000000..6abe0cb52 --- /dev/null +++ b/crates/transaction-pool/src/validate/eth.rs @@ -0,0 +1,306 @@ +//! Ethereum transaction validator. + +use crate::{ + error::InvalidPoolTransactionError, + traits::{PoolTransaction, TransactionOrigin}, + validate::{task::ValidationJobSender, TransactionValidatorError, ValidationTask}, + TransactionValidationOutcome, TransactionValidator, MAX_INIT_CODE_SIZE, TX_MAX_SIZE, +}; +use reth_primitives::{ + constants::ETHEREUM_BLOCK_GAS_LIMIT, ChainSpec, InvalidTransactionError, EIP1559_TX_TYPE_ID, + EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, +}; +use reth_provider::{AccountReader, StateProviderFactory}; +use reth_tasks::TaskSpawner; +use std::{marker::PhantomData, sync::Arc}; +use tokio::sync::{oneshot, Mutex}; + +/// A [TransactionValidator] implementation that validates ethereum transaction. +/// +/// This validator is non-blocking, all validation work is done in a separate task. +#[derive(Debug, Clone)] +pub struct EthTransactionValidator { + /// The type that performs the actual validation. + inner: Arc>, + /// The sender half to validation tasks that perform the actual validation. + to_validation_task: Arc>, +} + +// === impl EthTransactionValidator === + +impl EthTransactionValidator { + /// Creates a new instance for the given [ChainSpec] + /// + /// This will always spawn a validation tasks that perform the actual validation. A will spawn + /// `num_additional_tasks` additional tasks. + pub fn new( + client: Client, + chain_spec: Arc, + tasks: T, + num_additional_tasks: usize, + ) -> Self + where + T: TaskSpawner, + { + let inner = EthTransactionValidatorInner { + chain_spec, + client, + shanghai: true, + eip2718: true, + eip1559: true, + block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, + minimum_priority_fee: None, + _marker: Default::default(), + }; + + let (tx, task) = ValidationTask::new(); + + // Spawn validation tasks, they are blocking because they perform db lookups + for _ in 0..num_additional_tasks { + let task = task.clone(); + tasks.spawn_blocking(Box::pin(async move { + task.run().await; + })); + } + + tasks.spawn_critical_blocking( + "transaction-validation-service", + Box::pin(async move { + task.run().await; + }), + ); + + let to_validation_task = Arc::new(Mutex::new(tx)); + + Self { inner: Arc::new(inner), to_validation_task } + } + + /// Returns the configured chain id + pub fn chain_id(&self) -> u64 { + self.inner.chain_id() + } +} + +#[async_trait::async_trait] +impl TransactionValidator for EthTransactionValidator +where + Client: StateProviderFactory + Clone + 'static, + Tx: PoolTransaction + 'static, +{ + type Transaction = Tx; + + async fn validate_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> TransactionValidationOutcome { + let hash = *transaction.hash(); + let (tx, rx) = oneshot::channel(); + { + let to_validation_task = self.to_validation_task.clone(); + let to_validation_task = to_validation_task.lock().await; + let validator = Arc::clone(&self.inner); + let res = to_validation_task + .send(Box::pin(async move { + let res = validator.validate_transaction(origin, transaction).await; + let _ = tx.send(res); + })) + .await; + if res.is_err() { + return TransactionValidationOutcome::Error( + hash, + Box::new(TransactionValidatorError::ValidationServiceUnreachable), + ) + } + } + + match rx.await { + Ok(res) => res, + Err(_) => TransactionValidationOutcome::Error( + hash, + Box::new(TransactionValidatorError::ValidationServiceUnreachable), + ), + } + } +} + +/// A [TransactionValidator] implementation that validates ethereum transaction. +#[derive(Debug, Clone)] +struct EthTransactionValidatorInner { + /// Spec of the chain + chain_spec: Arc, + /// This type fetches account info from the db + client: Client, + /// Fork indicator whether we are in the Shanghai stage. + shanghai: bool, + /// Fork indicator whether we are using EIP-2718 type transactions. + eip2718: bool, + /// Fork indicator whether we are using EIP-1559 type transactions. + eip1559: bool, + /// The current max gas limit + block_gas_limit: u64, + /// Minimum priority fee to enforce for acceptance into the pool. + minimum_priority_fee: Option, + /// Marker for the transaction type + _marker: PhantomData, +} + +// === impl EthTransactionValidatorInner === + +impl EthTransactionValidatorInner { + /// Returns the configured chain id + fn chain_id(&self) -> u64 { + self.chain_spec.chain().id() + } +} + +#[async_trait::async_trait] +impl TransactionValidator for EthTransactionValidatorInner +where + Client: StateProviderFactory, + Tx: PoolTransaction, +{ + type Transaction = Tx; + + async fn validate_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> TransactionValidationOutcome { + // Checks for tx_type + match transaction.tx_type() { + LEGACY_TX_TYPE_ID => { + // Accept legacy transactions + } + EIP2930_TX_TYPE_ID => { + // Accept only legacy transactions until EIP-2718/2930 activates + if !self.eip2718 { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::Eip1559Disabled.into(), + ) + } + } + + EIP1559_TX_TYPE_ID => { + // Reject dynamic fee transactions until EIP-1559 activates. + if !self.eip1559 { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::Eip1559Disabled.into(), + ) + } + } + + _ => { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::TxTypeNotSupported.into(), + ) + } + }; + + // Reject transactions over defined size to prevent DOS attacks + if transaction.size() > TX_MAX_SIZE { + let size = transaction.size(); + return TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::OversizedData(size, TX_MAX_SIZE), + ) + } + + // Check whether the init code size has been exceeded. + if self.shanghai { + if let Err(err) = self.ensure_max_init_code_size(&transaction, MAX_INIT_CODE_SIZE) { + return TransactionValidationOutcome::Invalid(transaction, err) + } + } + + // Checks for gas limit + if transaction.gas_limit() > self.block_gas_limit { + let gas_limit = transaction.gas_limit(); + return TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::ExceedsGasLimit(gas_limit, self.block_gas_limit), + ) + } + + // Ensure max_priority_fee_per_gas (if EIP1559) is less than max_fee_per_gas if any. + if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::TipAboveFeeCap.into(), + ) + } + + // Drop non-local transactions with a fee lower than the configured fee for acceptance into + // the pool. + if !origin.is_local() && + transaction.is_eip1559() && + transaction.max_priority_fee_per_gas() < self.minimum_priority_fee + { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::Underpriced, + ) + } + + // Checks for chainid + if let Some(chain_id) = transaction.chain_id() { + if chain_id != self.chain_id() { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::ChainIdMismatch.into(), + ) + } + } + + let account = match self + .client + .latest() + .and_then(|state| state.basic_account(transaction.sender())) + { + Ok(account) => account.unwrap_or_default(), + Err(err) => { + return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err)) + } + }; + + // Signer account shouldn't have bytecode. Presence of bytecode means this is a + // smartcontract. + if account.has_bytecode() { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::SignerAccountHasBytecode.into(), + ) + } + + // Checks for nonce + if transaction.nonce() < account.nonce { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::NonceNotConsistent.into(), + ) + } + + // Checks for max cost + if transaction.cost() > account.balance { + let cost = transaction.cost(); + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::InsufficientFunds { + cost, + available_funds: account.balance, + } + .into(), + ) + } + + // Return the valid transaction + TransactionValidationOutcome::Valid { + balance: account.balance, + state_nonce: account.nonce, + transaction, + } + } +} diff --git a/crates/transaction-pool/src/validate/mod.rs b/crates/transaction-pool/src/validate/mod.rs new file mode 100644 index 000000000..db414530f --- /dev/null +++ b/crates/transaction-pool/src/validate/mod.rs @@ -0,0 +1,225 @@ +//! Transaction validation abstractions. + +use crate::{ + error::InvalidPoolTransactionError, + identifier::{SenderId, TransactionId}, + traits::{PoolTransaction, TransactionOrigin}, +}; +use reth_primitives::{ + Address, IntoRecoveredTransaction, TransactionKind, TransactionSignedEcRecovered, TxHash, U256, +}; +use std::{fmt, time::Instant}; + +mod eth; +mod task; + +/// A [TransactionValidator] implementation that validates ethereum transaction. +pub use eth::EthTransactionValidator; + +/// A spawnable task that performs transaction validation. +pub use task::ValidationTask; + +/// A Result type returned after checking a transaction's validity. +#[derive(Debug)] +pub enum TransactionValidationOutcome { + /// The transaction is considered _currently_ valid and can be inserted into the pool. + Valid { + /// Balance of the sender at the current point. + balance: U256, + /// Current nonce of the sender. + state_nonce: u64, + /// Validated transaction. + transaction: T, + }, + /// The transaction is considered invalid indefinitely: It violates constraints that prevent + /// this transaction from ever becoming valid. + Invalid(T, InvalidPoolTransactionError), + /// An error occurred while trying to validate the transaction + Error(TxHash, Box), +} + +impl TransactionValidationOutcome { + /// Returns the hash of the transactions + pub fn tx_hash(&self) -> TxHash { + match self { + Self::Valid { transaction, .. } => *transaction.hash(), + Self::Invalid(transaction, ..) => *transaction.hash(), + Self::Error(hash, ..) => *hash, + } + } +} + +/// Provides support for validating transaction at any given state of the chain +#[async_trait::async_trait] +pub trait TransactionValidator: Send + Sync { + /// The transaction type to validate. + type Transaction: PoolTransaction; + + /// Validates the transaction and returns a [`TransactionValidationOutcome`] describing the + /// validity of the given transaction. + /// + /// This will be used by the transaction-pool to check whether the transaction should be + /// inserted into the pool or discarded right away. + /// + /// Implementers of this trait must ensure that the transaction is well-formed, i.e. that it + /// complies at least all static constraints, which includes checking for: + /// + /// * chain id + /// * gas limit + /// * max cost + /// * nonce >= next nonce of the sender + /// * ... + /// + /// See [InvalidTransactionError](reth_primitives::InvalidTransactionError) for common errors + /// variants. + /// + /// The transaction pool makes no additional assumptions about the validity of the transaction + /// at the time of this call before it inserts it into the pool. However, the validity of + /// this transaction is still subject to future (dynamic) changes enforced by the pool, for + /// example nonce or balance changes. Hence, any validation checks must be applied in this + /// function. + /// + /// See [EthTransactionValidator] for a reference implementation. + async fn validate_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> TransactionValidationOutcome; + + /// Ensure that the code size is not greater than `max_init_code_size`. + /// `max_init_code_size` should be configurable so this will take it as an argument. + fn ensure_max_init_code_size( + &self, + transaction: &Self::Transaction, + max_init_code_size: usize, + ) -> Result<(), InvalidPoolTransactionError> { + if *transaction.kind() == TransactionKind::Create && transaction.size() > max_init_code_size + { + Err(InvalidPoolTransactionError::ExceedsMaxInitCodeSize( + transaction.size(), + max_init_code_size, + )) + } else { + Ok(()) + } + } +} + +/// A valid transaction in the pool. +pub struct ValidPoolTransaction { + /// The transaction + pub transaction: T, + /// The identifier for this transaction. + pub transaction_id: TransactionId, + /// Whether to propagate the transaction. + pub propagate: bool, + /// Total cost of the transaction: `feeCap x gasLimit + transferredValue`. + pub cost: U256, + /// Timestamp when this was added to the pool. + pub timestamp: Instant, + /// Where this transaction originated from. + pub origin: TransactionOrigin, + /// The length of the rlp encoded transaction (cached) + pub encoded_length: usize, +} + +// === impl ValidPoolTransaction === + +impl ValidPoolTransaction { + /// Returns the hash of the transaction. + pub fn hash(&self) -> &TxHash { + self.transaction.hash() + } + + /// Returns the type identifier of the transaction + pub fn tx_type(&self) -> u8 { + self.transaction.tx_type() + } + + /// Returns the address of the sender + pub fn sender(&self) -> Address { + self.transaction.sender() + } + + /// Returns the internal identifier for the sender of this transaction + pub(crate) fn sender_id(&self) -> SenderId { + self.transaction_id.sender + } + + /// Returns the internal identifier for this transaction. + pub(crate) fn id(&self) -> &TransactionId { + &self.transaction_id + } + + /// Returns the nonce set for this transaction. + pub fn nonce(&self) -> u64 { + self.transaction.nonce() + } + + /// Returns the EIP-1559 Max base fee the caller is willing to pay. + /// + /// For legacy transactions this is gas_price. + pub fn max_fee_per_gas(&self) -> u128 { + self.transaction.max_fee_per_gas() + } + + /// Returns the EIP-1559 Max base fee the caller is willing to pay. + pub fn effective_gas_price(&self) -> u128 { + self.transaction.effective_gas_price() + } + + /// Amount of gas that should be used in executing this transaction. This is paid up-front. + pub fn gas_limit(&self) -> u64 { + self.transaction.gas_limit() + } + + /// Whether the transaction originated locally. + pub fn is_local(&self) -> bool { + self.origin.is_local() + } + + /// The heap allocated size of this transaction. + pub(crate) fn size(&self) -> usize { + self.transaction.size() + } +} + +impl IntoRecoveredTransaction for ValidPoolTransaction { + fn to_recovered_transaction(&self) -> TransactionSignedEcRecovered { + self.transaction.to_recovered_transaction() + } +} + +#[cfg(test)] +impl Clone for ValidPoolTransaction { + fn clone(&self) -> Self { + Self { + transaction: self.transaction.clone(), + transaction_id: self.transaction_id, + propagate: self.propagate, + cost: self.cost, + timestamp: self.timestamp, + origin: self.origin, + encoded_length: self.encoded_length, + } + } +} + +impl fmt::Debug for ValidPoolTransaction { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "Transaction {{ ")?; + write!(fmt, "hash: {:?}, ", &self.transaction.hash())?; + write!(fmt, "provides: {:?}, ", &self.transaction_id)?; + write!(fmt, "raw tx: {:?}", &self.transaction)?; + write!(fmt, "}}")?; + Ok(()) + } +} + +/// Validation Errors that can occur during transaction validation. +#[derive(thiserror::Error, Debug)] +pub enum TransactionValidatorError { + /// Failed to communicate with the validation service. + #[error("Validation service unreachable")] + ValidationServiceUnreachable, +} diff --git a/crates/transaction-pool/src/validate/task.rs b/crates/transaction-pool/src/validate/task.rs new file mode 100644 index 000000000..80c477802 --- /dev/null +++ b/crates/transaction-pool/src/validate/task.rs @@ -0,0 +1,62 @@ +//! A validation service for transactions. + +use crate::validate::TransactionValidatorError; +use futures_util::{lock::Mutex, StreamExt}; +use std::{future::Future, pin::Pin, sync::Arc}; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; + +/// A service that performs validation jobs. +#[derive(Clone)] +pub struct ValidationTask { + #[allow(clippy::type_complexity)] + validation_jobs: Arc + Send>>>>>, +} + +impl ValidationTask { + /// Creates a new clonable task pair + pub fn new() -> (ValidationJobSender, Self) { + let (tx, rx) = mpsc::channel(1); + (ValidationJobSender { tx }, Self::with_receiver(rx)) + } + + /// Creates a new task with the given receiver. + pub fn with_receiver(jobs: mpsc::Receiver + Send>>>) -> Self { + ValidationTask { validation_jobs: Arc::new(Mutex::new(ReceiverStream::new(jobs))) } + } + + /// Executes all new validation jobs that come in. + /// + /// This will run as long as the channel is alive and is expected to be spawned as a task. + pub async fn run(self) { + loop { + let task = self.validation_jobs.lock().await.next().await; + match task { + None => return, + Some(task) => task.await, + } + } + } +} + +impl std::fmt::Debug for ValidationTask { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ValidationTask").field("validation_jobs", &"...").finish() + } +} + +/// A sender new type for sending validation jobs to [ValidationTask]. +#[derive(Debug)] +pub struct ValidationJobSender { + tx: mpsc::Sender + Send>>>, +} + +impl ValidationJobSender { + /// Sends the given job to the validation task. + pub async fn send( + &self, + job: Pin + Send>>, + ) -> Result<(), TransactionValidatorError> { + self.tx.send(job).await.map_err(|_| TransactionValidatorError::ValidationServiceUnreachable) + } +}