mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 19:09:54 +00:00
feat: implement network encoding for blob transactions (#4172)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
121
Cargo.lock
generated
121
Cargo.lock
generated
@ -119,9 +119,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c"
|
||||
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -230,9 +230,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.72"
|
||||
version = "1.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
checksum = "8c6f84b74db2535ebae81eede2f39b947dcbf01d093ae5f791e5dd414a1bf289"
|
||||
|
||||
[[package]]
|
||||
name = "aquamarine"
|
||||
@ -932,7 +932,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "c-kzg"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ethereum/c-kzg-4844#3ce8f863415ac1b218bc7d63cc14778b570aa081"
|
||||
source = "git+https://github.com/rjected/c-kzg-4844?branch=dan/add-serde-feature#4c95d6b8850f4f22a25fed0cf207560711cefe2b"
|
||||
dependencies = [
|
||||
"bindgen 0.64.0 (git+https://github.com/rust-lang/rust-bindgen?rev=0de11f0a521611ac8738b7b01d19dddaf3899e66)",
|
||||
"cc",
|
||||
@ -1899,9 +1899,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963"
|
||||
checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"signature",
|
||||
@ -2505,9 +2505,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.26"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
@ -3008,9 +3008,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "human_bytes"
|
||||
@ -3105,7 +3105,7 @@ dependencies = [
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows",
|
||||
"windows 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3957,9 +3957,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "metrics-process"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "006271a8019ad7a9a28cfac2cc40e3ee104d54be763c4a0901e228a63f49d706"
|
||||
checksum = "1c93f6ad342d3f7bc14724147e2dbc6eb6fdbe5a832ace16ea23b73618e8cc17"
|
||||
dependencies = [
|
||||
"libproc",
|
||||
"mach2",
|
||||
@ -3967,7 +3967,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"procfs",
|
||||
"rlimit",
|
||||
"windows",
|
||||
"windows 0.51.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4169,9 +4169,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
|
||||
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
@ -4469,7 +4469,7 @@ dependencies = [
|
||||
"libc",
|
||||
"redox_syscall 0.3.5",
|
||||
"smallvec 1.11.0",
|
||||
"windows-targets 0.48.1",
|
||||
"windows-targets 0.48.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5141,7 +5141,7 @@ version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
|
||||
dependencies = [
|
||||
"aho-corasick 1.0.3",
|
||||
"aho-corasick 1.0.4",
|
||||
"memchr",
|
||||
"regex-automata 0.3.6",
|
||||
"regex-syntax 0.7.4",
|
||||
@ -5162,7 +5162,7 @@ version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
|
||||
dependencies = [
|
||||
"aho-corasick 1.0.3",
|
||||
"aho-corasick 1.0.4",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.4",
|
||||
]
|
||||
@ -6334,9 +6334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rlimit"
|
||||
version = "0.9.1"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a29d87a652dc4d43c586328706bb5cdff211f3f39a530f240b53f7221dab8e"
|
||||
checksum = "3560f70f30a0f16d11d01ed078a07740fe6b489667abc7c7b029155d9f21c3d8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -6754,9 +6754,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.104"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
|
||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@ -8349,7 +8349,26 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.1",
|
||||
"windows-targets 0.48.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9763fb813068e9f4ab70a92a0c6ad61ff6b342f693b1ed0e5387c854386e670"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-targets 0.48.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b81650771e76355778637954dc9d7eb8d991cd89ad64ba26f21eeb3c22d8d836"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -8367,7 +8386,7 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.1",
|
||||
"windows-targets 0.48.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -8387,17 +8406,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.1"
|
||||
version = "0.48.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
|
||||
checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.0",
|
||||
"windows_aarch64_msvc 0.48.0",
|
||||
"windows_i686_gnu 0.48.0",
|
||||
"windows_i686_msvc 0.48.0",
|
||||
"windows_x86_64_gnu 0.48.0",
|
||||
"windows_x86_64_gnullvm 0.48.0",
|
||||
"windows_x86_64_msvc 0.48.0",
|
||||
"windows_aarch64_gnullvm 0.48.2",
|
||||
"windows_aarch64_msvc 0.48.2",
|
||||
"windows_i686_gnu 0.48.2",
|
||||
"windows_i686_msvc 0.48.2",
|
||||
"windows_x86_64_gnu 0.48.2",
|
||||
"windows_x86_64_gnullvm 0.48.2",
|
||||
"windows_x86_64_msvc 0.48.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -8408,9 +8427,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.0"
|
||||
version = "0.48.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@ -8420,9 +8439,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.0"
|
||||
version = "0.48.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
@ -8432,9 +8451,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.0"
|
||||
version = "0.48.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
@ -8444,9 +8463,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.0"
|
||||
version = "0.48.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
@ -8456,9 +8475,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.0"
|
||||
version = "0.48.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
@ -8468,9 +8487,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.0"
|
||||
version = "0.48.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
@ -8480,15 +8499,15 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.0"
|
||||
version = "0.48.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.10"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d"
|
||||
checksum = "1e461589e194280efaa97236b73623445efa195aa633fd7004f39805707a9d53"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
@ -152,4 +152,7 @@ c-kzg = { git = "https://github.com/ethereum/c-kzg-4844" }
|
||||
|
||||
### misc-testing
|
||||
proptest = "1.0"
|
||||
arbitrary = "1.1"
|
||||
arbitrary = "1.1"
|
||||
|
||||
[patch."https://github.com/ethereum/c-kzg-4844"]
|
||||
c-kzg = { git = "https://github.com/rjected/c-kzg-4844", branch = "dan/add-serde-feature" }
|
||||
@ -1,27 +1,11 @@
|
||||
//! Implements the `GetPooledTransactions` and `PooledTransactions` message types.
|
||||
use reth_codecs::{add_arbitrary_tests, derive_arbitrary};
|
||||
use reth_primitives::{
|
||||
kzg::{self, Blob, Bytes48, KzgProof, KzgSettings},
|
||||
TransactionSigned, H256,
|
||||
};
|
||||
use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
|
||||
use reth_codecs::derive_arbitrary;
|
||||
use reth_primitives::{PooledTransactionsElement, TransactionSigned, H256};
|
||||
use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
use proptest::{
|
||||
arbitrary::{any as proptest_any, ParamsFor},
|
||||
collection::vec as proptest_vec,
|
||||
strategy::{BoxedStrategy, Strategy},
|
||||
};
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
use reth_primitives::{
|
||||
constants::eip4844::{FIELD_ELEMENTS_PER_BLOB, KZG_TRUSTED_SETUP},
|
||||
kzg::{KzgCommitment, BYTES_PER_BLOB, BYTES_PER_FIELD_ELEMENT},
|
||||
};
|
||||
|
||||
/// A list of transaction hashes that the peer would like transaction bodies for.
|
||||
#[derive_arbitrary(rlp)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)]
|
||||
@ -47,141 +31,20 @@ where
|
||||
/// as the request's hashes. Hashes may be skipped, and the client should ensure that each body
|
||||
/// corresponds to a requested hash. Hashes may need to be re-requested if the bodies are not
|
||||
/// included in the response.
|
||||
#[derive_arbitrary(rlp, 10)]
|
||||
// #[derive_arbitrary(rlp, 10)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PooledTransactions(
|
||||
/// The transaction bodies, each of which should correspond to a requested hash.
|
||||
pub Vec<TransactionSigned>,
|
||||
pub Vec<PooledTransactionsElement>,
|
||||
);
|
||||
|
||||
impl From<Vec<TransactionSigned>> for PooledTransactions {
|
||||
fn from(txs: Vec<TransactionSigned>) -> Self {
|
||||
PooledTransactions(txs)
|
||||
PooledTransactions(txs.into_iter().map(Into::into).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PooledTransactions> for Vec<TransactionSigned> {
|
||||
fn from(txs: PooledTransactions) -> Self {
|
||||
txs.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A response to [`GetPooledTransactions`] that includes blob data, their commitments, and their
|
||||
/// corresponding proofs.
|
||||
///
|
||||
/// This is defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#networking) as an element
|
||||
/// of a [PooledTransactions] response.
|
||||
#[add_arbitrary_tests(rlp, 20)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
|
||||
pub struct BlobTransaction {
|
||||
/// The transaction payload.
|
||||
pub transaction: TransactionSigned,
|
||||
/// The transaction's blob data.
|
||||
pub blobs: Vec<Blob>,
|
||||
/// The transaction's blob commitments.
|
||||
pub commitments: Vec<Bytes48>,
|
||||
/// The transaction's blob proofs.
|
||||
pub proofs: Vec<Bytes48>,
|
||||
}
|
||||
|
||||
impl BlobTransaction {
|
||||
/// Verifies that the transaction's blob data, commitments, and proofs are all valid.
|
||||
///
|
||||
/// Takes as input the [KzgSettings], which should contain the the parameters derived from the
|
||||
/// KZG trusted setup.
|
||||
///
|
||||
/// This ensures that the blob transaction payload has the same number of blob data elements,
|
||||
/// commitments, and proofs. Each blob data element is verified against its commitment and
|
||||
/// proof.
|
||||
///
|
||||
/// Returns `false` if any blob KZG proof in the response fails to verify.
|
||||
pub fn validate(&self, proof_settings: &KzgSettings) -> Result<bool, kzg::Error> {
|
||||
// Verify as a batch
|
||||
KzgProof::verify_blob_kzg_proof_batch(
|
||||
self.blobs.as_slice(),
|
||||
self.commitments.as_slice(),
|
||||
self.proofs.as_slice(),
|
||||
proof_settings,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl<'a> arbitrary::Arbitrary<'a> for BlobTransaction {
|
||||
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let mut arr = [0u8; BYTES_PER_BLOB];
|
||||
let blobs: Vec<Blob> = (0..u.int_in_range(1..=16)?)
|
||||
.map(|_| {
|
||||
arr = arbitrary::Arbitrary::arbitrary(u).unwrap();
|
||||
|
||||
// Ensure that the blob is canonical by ensuring that
|
||||
// each field element contained in the blob is < BLS_MODULUS
|
||||
for i in 0..(FIELD_ELEMENTS_PER_BLOB as usize) {
|
||||
arr[i * BYTES_PER_FIELD_ELEMENT] = 0;
|
||||
}
|
||||
Blob::from(arr)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(generate_blob_transaction(blobs, TransactionSigned::arbitrary(u)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl proptest::arbitrary::Arbitrary for BlobTransaction {
|
||||
type Parameters = ParamsFor<String>;
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
(
|
||||
proptest_vec(proptest_vec(proptest_any::<u8>(), BYTES_PER_BLOB), 1..=5),
|
||||
proptest_any::<TransactionSigned>(),
|
||||
)
|
||||
.prop_map(move |(blobs, tx)| {
|
||||
let blobs = blobs
|
||||
.into_iter()
|
||||
.map(|mut blob| {
|
||||
let mut arr = [0u8; BYTES_PER_BLOB];
|
||||
|
||||
// Ensure that the blob is canonical by ensuring that
|
||||
// each field element contained in the blob is < BLS_MODULUS
|
||||
for i in 0..(FIELD_ELEMENTS_PER_BLOB as usize) {
|
||||
blob[i * BYTES_PER_FIELD_ELEMENT] = 0;
|
||||
}
|
||||
|
||||
arr.copy_from_slice(blob.as_slice());
|
||||
arr.into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
generate_blob_transaction(blobs, tx)
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
type Strategy = BoxedStrategy<BlobTransaction>;
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
fn generate_blob_transaction(blobs: Vec<Blob>, transaction: TransactionSigned) -> BlobTransaction {
|
||||
let kzg_settings = KZG_TRUSTED_SETUP.clone();
|
||||
|
||||
let commitments: Vec<Bytes48> = blobs
|
||||
.iter()
|
||||
.map(|blob| KzgCommitment::blob_to_kzg_commitment(blob.clone(), &kzg_settings).unwrap())
|
||||
.map(|commitment| commitment.to_bytes())
|
||||
.collect();
|
||||
|
||||
let proofs: Vec<Bytes48> = blobs
|
||||
.iter()
|
||||
.zip(commitments.iter())
|
||||
.map(|(blob, commitment)| {
|
||||
KzgProof::compute_blob_kzg_proof(blob.clone(), *commitment, &kzg_settings).unwrap()
|
||||
})
|
||||
.map(|proof| proof.to_bytes())
|
||||
.collect();
|
||||
|
||||
BlobTransaction { transaction, blobs, commitments, proofs }
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{message::RequestPair, GetPooledTransactions, PooledTransactions};
|
||||
|
||||
1
crates/net/eth-wire/testdata/blob_transaction
vendored
Normal file
1
crates/net/eth-wire/testdata/blob_transaction
vendored
Normal file
File diff suppressed because one or more lines are too long
1
crates/net/eth-wire/testdata/pooled_transactions_with_blob
vendored
Normal file
1
crates/net/eth-wire/testdata/pooled_transactions_with_blob
vendored
Normal file
File diff suppressed because one or more lines are too long
23
crates/net/eth-wire/tests/pooled_transactions.rs
Normal file
23
crates/net/eth-wire/tests/pooled_transactions.rs
Normal file
@ -0,0 +1,23 @@
|
||||
//! Decoding tests for [`PooledTransactions`]
|
||||
use reth_eth_wire::PooledTransactions;
|
||||
use reth_primitives::{hex, PooledTransactionsElement};
|
||||
use reth_rlp::Decodable;
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
#[test]
|
||||
fn decode_pooled_transactions_data() {
|
||||
let network_data_path =
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/pooled_transactions_with_blob");
|
||||
let data = fs::read_to_string(network_data_path).expect("Unable to read file");
|
||||
let hex_data = hex::decode(data.trim()).unwrap();
|
||||
let _txs = PooledTransactions::decode(&mut &hex_data[..]).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_blob_transaction_data() {
|
||||
let network_data_path =
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/blob_transaction");
|
||||
let data = fs::read_to_string(network_data_path).expect("Unable to read file");
|
||||
let hex_data = hex::decode(data.trim()).unwrap();
|
||||
let _txs = PooledTransactionsElement::decode(&mut &hex_data[..]).unwrap();
|
||||
}
|
||||
@ -12,7 +12,7 @@ use reth_eth_wire::{
|
||||
};
|
||||
use reth_interfaces::p2p::error::{RequestError, RequestResult};
|
||||
use reth_primitives::{
|
||||
BlockBody, Bytes, Header, PeerId, ReceiptWithBloom, TransactionSigned, H256,
|
||||
BlockBody, Bytes, Header, PeerId, PooledTransactionsElement, ReceiptWithBloom, H256,
|
||||
};
|
||||
use std::{
|
||||
fmt,
|
||||
@ -199,7 +199,7 @@ impl PeerResponse {
|
||||
pub enum PeerResponseResult {
|
||||
BlockHeaders(RequestResult<Vec<Header>>),
|
||||
BlockBodies(RequestResult<Vec<BlockBody>>),
|
||||
PooledTransactions(RequestResult<Vec<TransactionSigned>>),
|
||||
PooledTransactions(RequestResult<Vec<PooledTransactionsElement>>),
|
||||
NodeData(RequestResult<Vec<Bytes>>),
|
||||
Receipts(RequestResult<Vec<Vec<ReceiptWithBloom>>>),
|
||||
}
|
||||
|
||||
@ -193,7 +193,8 @@ where
|
||||
// we sent a response at which point we assume that the peer is aware of the transaction
|
||||
peer.transactions.extend(transactions.iter().map(|tx| tx.hash()));
|
||||
|
||||
let resp = PooledTransactions(transactions);
|
||||
// TODO: remove this! this will be different when we introduce the blobpool
|
||||
let resp = PooledTransactions(transactions.into_iter().map(Into::into).collect());
|
||||
let _ = response.send(Ok(resp));
|
||||
}
|
||||
}
|
||||
@ -579,7 +580,11 @@ where
|
||||
{
|
||||
match result {
|
||||
Ok(Ok(txs)) => {
|
||||
this.import_transactions(peer_id, txs.0, TransactionSource::Response);
|
||||
// convert all transactions to the inner transaction type, ignoring any
|
||||
// sidecars
|
||||
// TODO: remove this! this will be different when we introduce the blobpool
|
||||
let transactions = txs.0.into_iter().map(|tx| tx.into_transaction()).collect();
|
||||
this.import_transactions(peer_id, transactions, TransactionSource::Response)
|
||||
}
|
||||
Ok(Err(req_err)) => {
|
||||
this.on_request_error(peer_id, req_err);
|
||||
|
||||
@ -67,6 +67,7 @@ pub use compression::*;
|
||||
pub use constants::{
|
||||
DEV_GENESIS, EMPTY_OMMER_ROOT, GOERLI_GENESIS, KECCAK_EMPTY, MAINNET_GENESIS, SEPOLIA_GENESIS,
|
||||
};
|
||||
pub use eip4844::{calculate_excess_blob_gas, kzg_to_versioned_hash};
|
||||
pub use forkid::{ForkFilter, ForkHash, ForkId, ForkTransition, ValidationError};
|
||||
pub use genesis::{Genesis, GenesisAccount};
|
||||
pub use hardfork::Hardfork;
|
||||
@ -89,11 +90,12 @@ pub use serde_helper::JsonU256;
|
||||
pub use storage::StorageEntry;
|
||||
pub use transaction::{
|
||||
util::secp256k1::{public_key_to_address, recover_signer, sign_message},
|
||||
AccessList, AccessListItem, AccessListWithGasUsed, FromRecoveredTransaction,
|
||||
IntoRecoveredTransaction, InvalidTransactionError, Signature, Transaction, TransactionKind,
|
||||
TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash,
|
||||
TxEip1559, TxEip2930, TxEip4844, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
|
||||
EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
|
||||
AccessList, AccessListItem, AccessListWithGasUsed, BlobTransaction, BlobTransactionSidecar,
|
||||
FromRecoveredTransaction, IntoRecoveredTransaction, InvalidTransactionError,
|
||||
PooledTransactionsElement, Signature, Transaction, TransactionKind, TransactionMeta,
|
||||
TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, TxEip1559, TxEip2930,
|
||||
TxEip4844, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID,
|
||||
LEGACY_TX_TYPE_ID,
|
||||
};
|
||||
pub use withdrawal::Withdrawal;
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use super::access_list::AccessList;
|
||||
use crate::{Bytes, ChainId, TransactionKind};
|
||||
use reth_codecs::{main_codec, Compact};
|
||||
use reth_rlp::{Decodable, DecodeError};
|
||||
use std::mem;
|
||||
|
||||
/// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)).
|
||||
@ -82,6 +83,34 @@ impl TxEip1559 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes the inner [TxEip1559] fields from RLP bytes.
|
||||
///
|
||||
/// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
|
||||
/// RLP fields in the following order:
|
||||
///
|
||||
/// - `chain_id`
|
||||
/// - `nonce`
|
||||
/// - `max_priority_fee_per_gas`
|
||||
/// - `max_fee_per_gas`
|
||||
/// - `gas_limit`
|
||||
/// - `to`
|
||||
/// - `value`
|
||||
/// - `data` (`input`)
|
||||
/// - `access_list`
|
||||
pub(crate) fn decode_inner(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
Ok(Self {
|
||||
chain_id: Decodable::decode(buf)?,
|
||||
nonce: Decodable::decode(buf)?,
|
||||
max_priority_fee_per_gas: Decodable::decode(buf)?,
|
||||
max_fee_per_gas: Decodable::decode(buf)?,
|
||||
gas_limit: Decodable::decode(buf)?,
|
||||
to: Decodable::decode(buf)?,
|
||||
value: Decodable::decode(buf)?,
|
||||
input: Bytes(Decodable::decode(buf)?),
|
||||
access_list: Decodable::decode(buf)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculates a heuristic for the in-memory size of the [TxEip1559] transaction.
|
||||
#[inline]
|
||||
pub fn size(&self) -> usize {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use super::access_list::AccessList;
|
||||
use crate::{Bytes, ChainId, TransactionKind};
|
||||
use reth_codecs::{main_codec, Compact};
|
||||
use reth_rlp::{Decodable, DecodeError};
|
||||
use std::mem;
|
||||
|
||||
/// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)).
|
||||
@ -64,6 +65,32 @@ impl TxEip2930 {
|
||||
self.access_list.size() + // access_list
|
||||
self.input.len() // input
|
||||
}
|
||||
|
||||
/// Decodes the inner [TxEip2930] fields from RLP bytes.
|
||||
///
|
||||
/// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
|
||||
/// RLP fields in the following order:
|
||||
///
|
||||
/// - `chain_id`
|
||||
/// - `nonce`
|
||||
/// - `gas_price`
|
||||
/// - `gas_limit`
|
||||
/// - `to`
|
||||
/// - `value`
|
||||
/// - `data` (`input`)
|
||||
/// - `access_list`
|
||||
pub(crate) fn decode_inner(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
Ok(Self {
|
||||
chain_id: Decodable::decode(buf)?,
|
||||
nonce: Decodable::decode(buf)?,
|
||||
gas_price: Decodable::decode(buf)?,
|
||||
gas_limit: Decodable::decode(buf)?,
|
||||
to: Decodable::decode(buf)?,
|
||||
value: Decodable::decode(buf)?,
|
||||
input: Bytes(Decodable::decode(buf)?),
|
||||
access_list: Decodable::decode(buf)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
use super::access_list::AccessList;
|
||||
use crate::{constants::eip4844::DATA_GAS_PER_BLOB, Bytes, ChainId, TransactionKind, H256};
|
||||
use crate::{
|
||||
constants::eip4844::DATA_GAS_PER_BLOB,
|
||||
kzg::{
|
||||
self, Blob, Bytes48, KzgCommitment, KzgProof, KzgSettings, BYTES_PER_BLOB,
|
||||
BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
|
||||
},
|
||||
kzg_to_versioned_hash, Bytes, ChainId, Signature, Transaction, TransactionKind,
|
||||
TransactionSigned, TransactionSignedNoHash, TxType, EIP4844_TX_TYPE_ID, H256,
|
||||
};
|
||||
use reth_codecs::{main_codec, Compact};
|
||||
use std::mem;
|
||||
use reth_rlp::{Decodable, DecodeError, Encodable, Header};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{mem, ops::Deref};
|
||||
|
||||
/// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
|
||||
///
|
||||
@ -100,6 +110,38 @@ impl TxEip4844 {
|
||||
self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
|
||||
}
|
||||
|
||||
/// Decodes the inner [TxEip4844] fields from RLP bytes.
|
||||
///
|
||||
/// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
|
||||
/// RLP fields in the following order:
|
||||
///
|
||||
/// - `chain_id`
|
||||
/// - `nonce`
|
||||
/// - `max_priority_fee_per_gas`
|
||||
/// - `max_fee_per_gas`
|
||||
/// - `gas_limit`
|
||||
/// - `to`
|
||||
/// - `value`
|
||||
/// - `data` (`input`)
|
||||
/// - `access_list`
|
||||
/// - `max_fee_per_blob_gas`
|
||||
/// - `blob_versioned_hashes`
|
||||
pub fn decode_inner(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
Ok(Self {
|
||||
chain_id: Decodable::decode(buf)?,
|
||||
nonce: Decodable::decode(buf)?,
|
||||
max_priority_fee_per_gas: Decodable::decode(buf)?,
|
||||
max_fee_per_gas: Decodable::decode(buf)?,
|
||||
gas_limit: Decodable::decode(buf)?,
|
||||
to: Decodable::decode(buf)?,
|
||||
value: Decodable::decode(buf)?,
|
||||
input: Bytes(Decodable::decode(buf)?),
|
||||
access_list: Decodable::decode(buf)?,
|
||||
max_fee_per_blob_gas: Decodable::decode(buf)?,
|
||||
blob_versioned_hashes: Decodable::decode(buf)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculates a heuristic for the in-memory size of the [TxEip4844] transaction.
|
||||
#[inline]
|
||||
pub fn size(&self) -> usize {
|
||||
@ -116,3 +158,331 @@ impl TxEip4844 {
|
||||
mem::size_of::<u128>() // max_fee_per_data_gas
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that can occur when validating a [BlobTransaction].
|
||||
#[derive(Debug)]
|
||||
pub enum BlobTransactionValidationError {
|
||||
/// An error returned by the [kzg] library
|
||||
KZGError(kzg::Error),
|
||||
/// The inner transaction is not a blob transaction
|
||||
NotBlobTransaction(TxType),
|
||||
}
|
||||
|
||||
impl From<kzg::Error> for BlobTransactionValidationError {
|
||||
fn from(value: kzg::Error) -> Self {
|
||||
Self::KZGError(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A response to `GetPooledTransactions` that includes blob data, their commitments, and their
|
||||
/// corresponding proofs.
|
||||
///
|
||||
/// This is defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#networking) as an element
|
||||
/// of a `PooledTransactions` response.
|
||||
///
|
||||
/// NOTE: This contains a [TransactionSigned], which could be a non-4844 transaction type, even
|
||||
/// though that would not make sense. This type is meant to be constructed using decoding methods,
|
||||
/// which should always construct the [TransactionSigned] with an EIP-4844 transaction.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct BlobTransaction {
|
||||
/// The transaction payload.
|
||||
pub transaction: TransactionSigned,
|
||||
/// The transaction's blob sidecar.
|
||||
pub sidecar: BlobTransactionSidecar,
|
||||
}
|
||||
|
||||
impl BlobTransaction {
|
||||
/// Verifies that the transaction's blob data, commitments, and proofs are all valid.
|
||||
///
|
||||
/// Takes as input the [KzgSettings], which should contain the the parameters derived from the
|
||||
/// KZG trusted setup.
|
||||
///
|
||||
/// This ensures that the blob transaction payload has the same number of blob data elements,
|
||||
/// commitments, and proofs. Each blob data element is verified against its commitment and
|
||||
/// proof.
|
||||
///
|
||||
/// Returns `false` if any blob KZG proof in the response fails to verify, or if the versioned
|
||||
/// hashes in the transaction do not match the actual commitment versioned hashes.
|
||||
pub fn validate(
|
||||
&self,
|
||||
proof_settings: &KzgSettings,
|
||||
) -> Result<bool, BlobTransactionValidationError> {
|
||||
let inner_tx = match &self.transaction.transaction {
|
||||
Transaction::Eip4844(blob_tx) => blob_tx,
|
||||
non_blob_tx => {
|
||||
return Err(BlobTransactionValidationError::NotBlobTransaction(
|
||||
non_blob_tx.tx_type(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure the versioned hashes and commitments have the same length
|
||||
if inner_tx.blob_versioned_hashes.len() != self.sidecar.commitments.len() {
|
||||
return Err(kzg::Error::MismatchLength(format!(
|
||||
"There are {} versioned commitment hashes and {} commitments",
|
||||
inner_tx.blob_versioned_hashes.len(),
|
||||
self.sidecar.commitments.len()
|
||||
))
|
||||
.into())
|
||||
}
|
||||
|
||||
// zip and iterate, calculating versioned hashes
|
||||
for (versioned_hash, commitment) in
|
||||
inner_tx.blob_versioned_hashes.iter().zip(self.sidecar.commitments.iter())
|
||||
{
|
||||
// convert to KzgCommitment
|
||||
let commitment = KzgCommitment::from(*commitment.deref());
|
||||
|
||||
// Calculate the versioned hash
|
||||
//
|
||||
// TODO: should this method distinguish the type of validation failure? For example
|
||||
// whether a certain versioned hash does not match, or whether the blob proof
|
||||
// validation failed?
|
||||
let calculated_versioned_hash = kzg_to_versioned_hash(commitment);
|
||||
if *versioned_hash != calculated_versioned_hash {
|
||||
return Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify as a batch
|
||||
KzgProof::verify_blob_kzg_proof_batch(
|
||||
self.sidecar.blobs.as_slice(),
|
||||
self.sidecar.commitments.as_slice(),
|
||||
self.sidecar.proofs.as_slice(),
|
||||
proof_settings,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Splits the [BlobTransaction] into its [TransactionSigned] and [BlobTransactionSidecar]
|
||||
/// components.
|
||||
pub fn into_parts(self) -> (TransactionSigned, BlobTransactionSidecar) {
|
||||
(self.transaction, self.sidecar)
|
||||
}
|
||||
|
||||
/// Encodes the [BlobTransaction] fields as RLP, with a tx type. If `with_header` is `false`,
|
||||
/// the following will be encoded:
|
||||
/// `tx_type (0x03) || rlp([transaction_payload_body, blobs, commitments, proofs])`
|
||||
///
|
||||
/// If `with_header` is `true`, the following will be encoded:
|
||||
/// `rlp(tx_type (0x03) || rlp([transaction_payload_body, blobs, commitments, proofs]))`
|
||||
///
|
||||
/// NOTE: The header will be a byte string header, not a list header.
|
||||
pub(crate) fn encode_with_type_inner(&self, out: &mut dyn bytes::BufMut, with_header: bool) {
|
||||
// Calculate the length of:
|
||||
// `tx_type || rlp([transaction_payload_body, blobs, commitments, proofs])`
|
||||
//
|
||||
// to construct and encode the string header
|
||||
if with_header {
|
||||
Header {
|
||||
list: false,
|
||||
// add one for the tx type
|
||||
payload_length: 1 + self.payload_len(),
|
||||
}
|
||||
.encode(out);
|
||||
}
|
||||
|
||||
out.put_u8(EIP4844_TX_TYPE_ID);
|
||||
|
||||
// Now we encode the inner blob transaction:
|
||||
self.encode_inner(out);
|
||||
}
|
||||
|
||||
/// Encodes the [BlobTransaction] fields as RLP, with the following format:
|
||||
/// `rlp([transaction_payload_body, blobs, commitments, proofs])`
|
||||
///
|
||||
/// where `transaction_payload_body` is a list:
|
||||
/// `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]`
|
||||
///
|
||||
/// Note: this should be used only when implementing other RLP encoding methods, and does not
|
||||
/// represent the full RLP encoding of the blob transaction.
|
||||
pub(crate) fn encode_inner(&self, out: &mut dyn bytes::BufMut) {
|
||||
// First we construct both required list headers.
|
||||
//
|
||||
// The `transaction_payload_body` length is the length of the fields, plus the length of
|
||||
// its list header.
|
||||
let tx_header = Header {
|
||||
list: true,
|
||||
payload_length: self.transaction.fields_len() +
|
||||
self.transaction.signature.payload_len(),
|
||||
};
|
||||
|
||||
let tx_length = tx_header.length() + tx_header.payload_length;
|
||||
|
||||
// The payload length is the length of the `tranascation_payload_body` list, plus the
|
||||
// length of the blobs, commitments, and proofs.
|
||||
let payload_length = tx_length + self.sidecar.fields_len();
|
||||
|
||||
// First we use the payload len to construct the first list header
|
||||
let blob_tx_header = Header { list: true, payload_length };
|
||||
|
||||
// Encode the blob tx header first
|
||||
blob_tx_header.encode(out);
|
||||
|
||||
// Encode the inner tx list header, then its fields
|
||||
tx_header.encode(out);
|
||||
self.transaction.encode_fields(out);
|
||||
|
||||
// Encode the blobs, commitments, and proofs
|
||||
self.sidecar.encode_inner(out);
|
||||
}
|
||||
|
||||
/// Ouputs the length of the RLP encoding of the blob transaction, including the tx type byte,
|
||||
/// optionally including the length of a wrapping string header. If `with_header` is `false`,
|
||||
/// the length of the following will be calculated:
|
||||
/// `tx_type (0x03) || rlp([transaction_payload_body, blobs, commitments, proofs])`
|
||||
///
|
||||
/// If `with_header` is `true`, the length of the following will be calculated:
|
||||
/// `rlp(tx_type (0x03) || rlp([transaction_payload_body, blobs, commitments, proofs]))`
|
||||
pub(crate) fn payload_len_with_type(&self, with_header: bool) -> usize {
|
||||
if with_header {
|
||||
// Construct a header and use that to calculate the total length
|
||||
let wrapped_header = Header {
|
||||
list: false,
|
||||
// add one for the tx type byte
|
||||
payload_length: 1 + self.payload_len(),
|
||||
};
|
||||
|
||||
// The total length is now the length of the header plus the length of the payload
|
||||
// (which includes the tx type byte)
|
||||
wrapped_header.length() + wrapped_header.payload_length
|
||||
} else {
|
||||
// Just add the length of the tx type to the payload length
|
||||
1 + self.payload_len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Outputs the length of the RLP encoding of the blob transaction with the following format:
|
||||
/// `rlp([transaction_payload_body, blobs, commitments, proofs])`
|
||||
///
|
||||
/// where `transaction_payload_body` is a list:
|
||||
/// `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]`
|
||||
///
|
||||
/// Note: this should be used only when implementing other RLP encoding length methods, and
|
||||
/// does not represent the full RLP encoding of the blob transaction.
|
||||
pub(crate) fn payload_len(&self) -> usize {
|
||||
// The `transaction_payload_body` length is the length of the fields, plus the length of
|
||||
// its list header.
|
||||
let tx_header = Header {
|
||||
list: true,
|
||||
payload_length: self.transaction.fields_len() +
|
||||
self.transaction.signature.payload_len(),
|
||||
};
|
||||
|
||||
let tx_length = tx_header.length() + tx_header.payload_length;
|
||||
|
||||
// The payload length is the length of the `tranascation_payload_body` list, plus the
|
||||
// length of the blobs, commitments, and proofs.
|
||||
tx_length + self.sidecar.fields_len()
|
||||
}
|
||||
|
||||
/// Decodes a [BlobTransaction] from RLP. This expects the encoding to be:
|
||||
/// `rlp([transaction_payload_body, blobs, commitments, proofs])`
|
||||
///
|
||||
/// where `transaction_payload_body` is a list:
|
||||
/// `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]`
|
||||
///
|
||||
/// Note: this should be used only when implementing other RLP decoding methods, and does not
|
||||
/// represent the full RLP decoding of the `PooledTransactionsElement` type.
|
||||
pub(crate) fn decode_inner(data: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
// decode the _first_ list header for the rest of the transaction
|
||||
let header = Header::decode(data)?;
|
||||
if !header.list {
|
||||
return Err(DecodeError::Custom("PooledTransactions blob tx must be encoded as a list"))
|
||||
}
|
||||
|
||||
// Now we need to decode the inner 4844 transaction and its signature:
|
||||
//
|
||||
// `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]`
|
||||
let header = Header::decode(data)?;
|
||||
if !header.list {
|
||||
return Err(DecodeError::Custom(
|
||||
"PooledTransactions inner blob tx must be encoded as a list",
|
||||
))
|
||||
}
|
||||
|
||||
// inner transaction
|
||||
let transaction = Transaction::Eip4844(TxEip4844::decode_inner(data)?);
|
||||
|
||||
// signature
|
||||
let signature = Signature::decode(data)?;
|
||||
|
||||
// construct the tx now that we've decoded the fields in order
|
||||
let tx_no_hash = TransactionSignedNoHash { transaction, signature };
|
||||
|
||||
// All that's left are the blobs, commitments, and proofs
|
||||
let sidecar = BlobTransactionSidecar::decode_inner(data)?;
|
||||
|
||||
// # Calculating the hash
|
||||
//
|
||||
// The full encoding of the `PooledTransaction` response is:
|
||||
// `tx_type (0x03) || rlp([tx_payload_body, blobs, commitments, proofs])`
|
||||
//
|
||||
// The transaction hash however, is:
|
||||
// `keccak256(tx_type (0x03) || rlp(tx_payload_body))`
|
||||
//
|
||||
// Note that this is `tx_payload_body`, not `[tx_payload_body]`, which would be
|
||||
// `[[chain_id, nonce, max_priority_fee_per_gas, ...]]`, i.e. a list within a list.
|
||||
//
|
||||
// Because the pooled transaction encoding is different than the hash encoding for
|
||||
// EIP-4844 transactions, we do not use the original buffer to calculate the hash.
|
||||
//
|
||||
// Instead, we use `TransactionSignedNoHash` which will encode the transaction internally.
|
||||
let signed_tx = tx_no_hash.with_hash();
|
||||
|
||||
Ok(Self { transaction: signed_tx, sidecar })
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents a set of blobs, and its corresponding commitments and proofs.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct BlobTransactionSidecar {
|
||||
/// The blob data.
|
||||
pub blobs: Vec<Blob>,
|
||||
/// The blob commitments.
|
||||
pub commitments: Vec<Bytes48>,
|
||||
/// The blob proofs.
|
||||
pub proofs: Vec<Bytes48>,
|
||||
}
|
||||
|
||||
impl BlobTransactionSidecar {
|
||||
/// Encodes the inner [BlobTransactionSidecar] fields as RLP bytes, without a RLP header.
|
||||
///
|
||||
/// This encodes the fields in the following order:
|
||||
/// - `blobs`
|
||||
/// - `commitments`
|
||||
/// - `proofs`
|
||||
pub(crate) fn encode_inner(&self, out: &mut dyn bytes::BufMut) {
|
||||
// Encode the blobs, commitments, and proofs
|
||||
self.blobs.encode(out);
|
||||
self.commitments.encode(out);
|
||||
self.proofs.encode(out);
|
||||
}
|
||||
|
||||
/// Outputs the RLP length of the [BlobTransactionSidecar] fields, without a RLP header.
|
||||
pub(crate) fn fields_len(&self) -> usize {
|
||||
self.blobs.len() + self.commitments.len() + self.proofs.len()
|
||||
}
|
||||
|
||||
/// Decodes the inner [BlobTransactionSidecar] fields from RLP bytes, without a RLP header.
|
||||
///
|
||||
/// This decodes the fields in the following order:
|
||||
/// - `blobs`
|
||||
/// - `commitments`
|
||||
/// - `proofs`
|
||||
pub(crate) fn decode_inner(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
Ok(Self {
|
||||
blobs: Decodable::decode(buf)?,
|
||||
commitments: Decodable::decode(buf)?,
|
||||
proofs: Decodable::decode(buf)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecar].
|
||||
#[inline]
|
||||
pub fn size(&self) -> usize {
|
||||
self.blobs.len() * BYTES_PER_BLOB + // blobs
|
||||
self.commitments.len() * BYTES_PER_COMMITMENT + // commitments
|
||||
self.proofs.len() * BYTES_PER_PROOF // proofs
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,8 +22,9 @@ pub use tx_type::{
|
||||
|
||||
pub use eip1559::TxEip1559;
|
||||
pub use eip2930::TxEip2930;
|
||||
pub use eip4844::TxEip4844;
|
||||
pub use eip4844::{BlobTransaction, BlobTransactionSidecar, TxEip4844};
|
||||
pub use legacy::TxLegacy;
|
||||
pub use pooled::PooledTransactionsElement;
|
||||
|
||||
mod access_list;
|
||||
mod eip1559;
|
||||
@ -32,6 +33,7 @@ mod eip4844;
|
||||
mod error;
|
||||
mod legacy;
|
||||
mod meta;
|
||||
mod pooled;
|
||||
mod signature;
|
||||
mod tx_type;
|
||||
pub(crate) mod util;
|
||||
@ -343,7 +345,7 @@ impl Transaction {
|
||||
|
||||
/// Outputs the length of the transaction's fields, without a RLP header or length of the
|
||||
/// eip155 fields.
|
||||
pub(crate) fn fields_len(&self) -> usize {
|
||||
pub fn fields_len(&self) -> usize {
|
||||
match self {
|
||||
Transaction::Legacy(TxLegacy {
|
||||
chain_id: _,
|
||||
@ -438,7 +440,7 @@ impl Transaction {
|
||||
}
|
||||
|
||||
/// Encodes only the transaction's fields into the desired buffer, without a RLP header.
|
||||
pub(crate) fn encode_fields(&self, out: &mut dyn bytes::BufMut) {
|
||||
pub fn encode_fields(&self, out: &mut dyn bytes::BufMut) {
|
||||
match self {
|
||||
Transaction::Legacy(TxLegacy {
|
||||
chain_id: _,
|
||||
@ -1059,7 +1061,11 @@ impl TransactionSigned {
|
||||
/// Decodes legacy transaction from the data buffer.
|
||||
///
|
||||
/// This expects `rlp(legacy_tx)`
|
||||
fn decode_rlp_legacy_transaction(data: &mut &[u8]) -> Result<TransactionSigned, DecodeError> {
|
||||
// TODO: make buf advancement semantics consistent with `decode_enveloped_typed_transaction`,
|
||||
// so decoding methods do not need to manually advance the buffer
|
||||
pub fn decode_rlp_legacy_transaction(
|
||||
data: &mut &[u8],
|
||||
) -> Result<TransactionSigned, DecodeError> {
|
||||
// keep this around, so we can use it to calculate the hash
|
||||
let original_encoding = *data;
|
||||
|
||||
@ -1088,7 +1094,7 @@ impl TransactionSigned {
|
||||
/// Decodes en enveloped EIP-2718 typed transaction.
|
||||
///
|
||||
/// CAUTION: this expects that `data` is `[id, rlp(tx)]`
|
||||
fn decode_enveloped_typed_transaction(
|
||||
pub fn decode_enveloped_typed_transaction(
|
||||
data: &mut &[u8],
|
||||
) -> Result<TransactionSigned, DecodeError> {
|
||||
// keep this around so we can use it to calculate the hash
|
||||
@ -1096,6 +1102,7 @@ impl TransactionSigned {
|
||||
|
||||
let tx_type = *data.first().ok_or(DecodeError::InputTooShort)?;
|
||||
data.advance(1);
|
||||
|
||||
// decode the list header for the rest of the transaction
|
||||
let header = Header::decode(data)?;
|
||||
if !header.list {
|
||||
@ -1107,40 +1114,9 @@ impl TransactionSigned {
|
||||
|
||||
// decode common fields
|
||||
let transaction = match tx_type {
|
||||
1 => Transaction::Eip2930(TxEip2930 {
|
||||
chain_id: Decodable::decode(data)?,
|
||||
nonce: Decodable::decode(data)?,
|
||||
gas_price: Decodable::decode(data)?,
|
||||
gas_limit: Decodable::decode(data)?,
|
||||
to: Decodable::decode(data)?,
|
||||
value: Decodable::decode(data)?,
|
||||
input: Bytes(Decodable::decode(data)?),
|
||||
access_list: Decodable::decode(data)?,
|
||||
}),
|
||||
2 => Transaction::Eip1559(TxEip1559 {
|
||||
chain_id: Decodable::decode(data)?,
|
||||
nonce: Decodable::decode(data)?,
|
||||
max_priority_fee_per_gas: Decodable::decode(data)?,
|
||||
max_fee_per_gas: Decodable::decode(data)?,
|
||||
gas_limit: Decodable::decode(data)?,
|
||||
to: Decodable::decode(data)?,
|
||||
value: Decodable::decode(data)?,
|
||||
input: Bytes(Decodable::decode(data)?),
|
||||
access_list: Decodable::decode(data)?,
|
||||
}),
|
||||
3 => Transaction::Eip4844(TxEip4844 {
|
||||
chain_id: Decodable::decode(data)?,
|
||||
nonce: Decodable::decode(data)?,
|
||||
max_priority_fee_per_gas: Decodable::decode(data)?,
|
||||
max_fee_per_gas: Decodable::decode(data)?,
|
||||
gas_limit: Decodable::decode(data)?,
|
||||
to: Decodable::decode(data)?,
|
||||
value: Decodable::decode(data)?,
|
||||
input: Bytes(Decodable::decode(data)?),
|
||||
access_list: Decodable::decode(data)?,
|
||||
max_fee_per_blob_gas: Decodable::decode(data)?,
|
||||
blob_versioned_hashes: Decodable::decode(data)?,
|
||||
}),
|
||||
1 => Transaction::Eip2930(TxEip2930::decode_inner(data)?),
|
||||
2 => Transaction::Eip1559(TxEip1559::decode_inner(data)?),
|
||||
3 => Transaction::Eip4844(TxEip4844::decode_inner(data)?),
|
||||
_ => return Err(DecodeError::Custom("unsupported typed transaction type")),
|
||||
};
|
||||
|
||||
|
||||
178
crates/primitives/src/transaction/pooled.rs
Normal file
178
crates/primitives/src/transaction/pooled.rs
Normal file
@ -0,0 +1,178 @@
|
||||
//! Includes the
|
||||
use crate::{BlobTransaction, Bytes, TransactionSigned, EIP4844_TX_TYPE_ID};
|
||||
use bytes::Buf;
|
||||
use reth_rlp::{Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A response to `GetPooledTransactions`. This can include either a blob transaction, or a
|
||||
/// non-4844 signed transaction.
|
||||
// TODO: redo arbitrary for this encoding - the previous encoding was incorrect
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PooledTransactionsElement {
|
||||
/// A blob transaction, which includes the transaction, blob data, commitments, and proofs.
|
||||
BlobTransaction(BlobTransaction),
|
||||
/// A non-4844 signed transaction.
|
||||
Transaction(TransactionSigned),
|
||||
}
|
||||
|
||||
impl PooledTransactionsElement {
|
||||
/// Decodes the "raw" format of transaction (e.g. `eth_sendRawTransaction`).
|
||||
///
|
||||
/// The raw transaction is either a legacy transaction or EIP-2718 typed transaction
|
||||
/// For legacy transactions, the format is encoded as: `rlp(tx)`
|
||||
/// For EIP-2718 typed transaction, the format is encoded as the type of the transaction
|
||||
/// followed by the rlp of the transaction: `type` + `rlp(tx)`
|
||||
///
|
||||
/// For encoded EIP-4844 transactions, the blob sidecar _must_ be included.
|
||||
pub fn decode_enveloped(tx: Bytes) -> Result<Self, DecodeError> {
|
||||
let mut data = tx.as_ref();
|
||||
|
||||
if data.is_empty() {
|
||||
return Err(DecodeError::InputTooShort)
|
||||
}
|
||||
|
||||
// Check if the tx is a list - tx types are less than EMPTY_LIST_CODE (0xc0)
|
||||
if data[0] >= EMPTY_LIST_CODE {
|
||||
// decode as legacy transaction
|
||||
Ok(Self::Transaction(TransactionSigned::decode_rlp_legacy_transaction(&mut data)?))
|
||||
} else {
|
||||
// decode the type byte, only decode BlobTransaction if it is a 4844 transaction
|
||||
let tx_type = *data.first().ok_or(DecodeError::InputTooShort)?;
|
||||
|
||||
if tx_type == EIP4844_TX_TYPE_ID {
|
||||
// Recall that the blob transaction response `TranactionPayload` is encoded like
|
||||
// this: `rlp([tx_payload_body, blobs, commitments, proofs])`
|
||||
//
|
||||
// Note that `tx_payload_body` is a list:
|
||||
// `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]`
|
||||
//
|
||||
// This makes the full encoding:
|
||||
// `tx_type (0x03) || rlp([[chain_id, nonce, ...], blobs, commitments, proofs])`
|
||||
//
|
||||
// First, we advance the buffer past the type byte
|
||||
data.advance(1);
|
||||
|
||||
// Now, we decode the inner blob transaction:
|
||||
// `rlp([[chain_id, nonce, ...], blobs, commitments, proofs])`
|
||||
let blob_tx = BlobTransaction::decode_inner(&mut data)?;
|
||||
Ok(PooledTransactionsElement::BlobTransaction(blob_tx))
|
||||
} else {
|
||||
// DO NOT advance the buffer for the type, since we want the enveloped decoding to
|
||||
// decode it again and advance the buffer on its own.
|
||||
let typed_tx = TransactionSigned::decode_enveloped_typed_transaction(&mut data)?;
|
||||
Ok(PooledTransactionsElement::Transaction(typed_tx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the inner [TransactionSigned].
|
||||
pub fn into_transaction(self) -> TransactionSigned {
|
||||
match self {
|
||||
Self::Transaction(tx) => tx,
|
||||
Self::BlobTransaction(blob_tx) => blob_tx.transaction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for PooledTransactionsElement {
|
||||
/// Encodes an enveloped post EIP-4844 [PooledTransactionsElement].
|
||||
fn encode(&self, out: &mut dyn bytes::BufMut) {
|
||||
match self {
|
||||
Self::Transaction(tx) => tx.encode(out),
|
||||
Self::BlobTransaction(blob_tx) => {
|
||||
// The inner encoding is used with `with_header` set to true, making the final
|
||||
// encoding:
|
||||
// `rlp(tx_type || rlp([transaction_payload_body, blobs, commitments, proofs]))`
|
||||
blob_tx.encode_with_type_inner(out, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn length(&self) -> usize {
|
||||
match self {
|
||||
Self::Transaction(tx) => tx.length(),
|
||||
Self::BlobTransaction(blob_tx) => {
|
||||
// the encoding uses a header, so we set `with_header` to true
|
||||
blob_tx.payload_len_with_type(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for PooledTransactionsElement {
|
||||
/// Decodes an enveloped post EIP-4844 [PooledTransactionsElement].
|
||||
///
|
||||
/// CAUTION: this expects that `buf` is `[id, rlp(tx)]`
|
||||
fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
// From the EIP-4844 spec:
|
||||
// Blob transactions have two network representations. During transaction gossip responses
|
||||
// (`PooledTransactions`), the EIP-2718 `TransactionPayload` of the blob transaction is
|
||||
// wrapped to become:
|
||||
//
|
||||
// `rlp([tx_payload_body, blobs, commitments, proofs])`
|
||||
//
|
||||
// This means the full wire encoding is:
|
||||
// `rlp(tx_type || rlp([transaction_payload_body, blobs, commitments, proofs]))`
|
||||
//
|
||||
// First, we check whether or not the transaction is a legacy transaction.
|
||||
if buf.is_empty() {
|
||||
return Err(DecodeError::InputTooShort)
|
||||
}
|
||||
|
||||
// keep this around for buffer advancement post-legacy decoding
|
||||
let mut original_encoding = *buf;
|
||||
|
||||
// If the header is a list header, it is a legacy transaction. Otherwise, it is a typed
|
||||
// transaction
|
||||
let header = Header::decode(buf)?;
|
||||
|
||||
// Check if the tx is a list
|
||||
if header.list {
|
||||
// decode as legacy transaction
|
||||
let legacy_tx =
|
||||
TransactionSigned::decode_rlp_legacy_transaction(&mut original_encoding)?;
|
||||
|
||||
// advance the buffer based on how far `decode_rlp_legacy_transaction` advanced the
|
||||
// buffer
|
||||
*buf = original_encoding;
|
||||
|
||||
Ok(PooledTransactionsElement::Transaction(legacy_tx))
|
||||
} else {
|
||||
// decode the type byte, only decode BlobTransaction if it is a 4844 transaction
|
||||
let tx_type = *buf.first().ok_or(DecodeError::InputTooShort)?;
|
||||
|
||||
if tx_type == EIP4844_TX_TYPE_ID {
|
||||
// Recall that the blob transaction response `TranactionPayload` is encoded like
|
||||
// this: `rlp([tx_payload_body, blobs, commitments, proofs])`
|
||||
//
|
||||
// Note that `tx_payload_body` is a list:
|
||||
// `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]`
|
||||
//
|
||||
// This makes the full encoding:
|
||||
// `tx_type (0x03) || rlp([[chain_id, nonce, ...], blobs, commitments, proofs])`
|
||||
//
|
||||
// First, we advance the buffer past the type byte
|
||||
buf.advance(1);
|
||||
|
||||
// Now, we decode the inner blob transaction:
|
||||
// `rlp([[chain_id, nonce, ...], blobs, commitments, proofs])`
|
||||
let blob_tx = BlobTransaction::decode_inner(buf)?;
|
||||
Ok(PooledTransactionsElement::BlobTransaction(blob_tx))
|
||||
} else {
|
||||
// DO NOT advance the buffer for the type, since we want the enveloped decoding to
|
||||
// decode it again and advance the buffer on its own.
|
||||
let typed_tx = TransactionSigned::decode_enveloped_typed_transaction(buf)?;
|
||||
Ok(PooledTransactionsElement::Transaction(typed_tx))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionSigned> for PooledTransactionsElement {
|
||||
/// Converts from a [TransactionSigned] to a [PooledTransactionsElement].
|
||||
///
|
||||
/// NOTE: This will always return a [PooledTransactionsElement::Transaction] variant.
|
||||
fn from(tx: TransactionSigned) -> Self {
|
||||
Self::Transaction(tx)
|
||||
}
|
||||
}
|
||||
@ -93,19 +93,19 @@ impl Signature {
|
||||
}
|
||||
|
||||
/// Output the length of the signature without the length of the RLP header
|
||||
pub(crate) fn payload_len(&self) -> usize {
|
||||
pub fn payload_len(&self) -> usize {
|
||||
self.odd_y_parity.length() + self.r.length() + self.s.length()
|
||||
}
|
||||
|
||||
/// Encode the `odd_y_parity`, `r`, `s` values without a RLP header.
|
||||
pub(crate) fn encode(&self, out: &mut dyn reth_rlp::BufMut) {
|
||||
pub fn encode(&self, out: &mut dyn reth_rlp::BufMut) {
|
||||
self.odd_y_parity.encode(out);
|
||||
self.r.encode(out);
|
||||
self.s.encode(out);
|
||||
}
|
||||
|
||||
/// Decodes the `odd_y_parity`, `r`, `s` values without a RLP header.
|
||||
pub(crate) fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
pub fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
Ok(Signature {
|
||||
odd_y_parity: Decodable::decode(buf)?,
|
||||
r: Decodable::decode(buf)?,
|
||||
|
||||
Reference in New Issue
Block a user