feat: implement network encoding for blob transactions (#4172)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Dan Cline
2023-08-16 19:10:33 -04:00
committed by GitHub
parent 45db5a6368
commit 40f9576c3a
15 changed files with 745 additions and 248 deletions

121
Cargo.lock generated
View File

@ -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",
]

View File

@ -153,3 +153,6 @@ c-kzg = { git = "https://github.com/ethereum/c-kzg-4844" }
### misc-testing
proptest = "1.0"
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" }

View File

@ -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};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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();
}

View File

@ -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>>>),
}

View File

@ -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);

View File

@ -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;

View File

@ -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 {

View File

@ -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)]

View File

@ -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
}
}

View File

@ -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")),
};

View 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)
}
}

View File

@ -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)?,