mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: feature gate c-kzg in reth-primitives (#5240)
This commit is contained in:
@ -23,7 +23,7 @@ ethers-core = { workspace = true, default-features = false, optional = true }
|
||||
secp256k1 = { workspace = true, features = ["global-context", "recovery"] }
|
||||
|
||||
# for eip-4844
|
||||
c-kzg = { workspace = true, features = ["serde"] }
|
||||
c-kzg = { workspace = true, features = ["serde"], optional = true }
|
||||
|
||||
# used for forkid
|
||||
crc = "3"
|
||||
@ -88,8 +88,9 @@ criterion = "0.5"
|
||||
pprof = { version = "0.12", features = ["flamegraph", "frame-pointer", "criterion"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["c-kzg"]
|
||||
arbitrary = ["revm-primitives/arbitrary", "reth-rpc-types/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive"]
|
||||
c-kzg = ["revm-primitives/c-kzg", "dep:c-kzg"]
|
||||
test-utils = ["dep:plain_hasher", "dep:hash-db", "dep:ethers-core"]
|
||||
# value-256 controls whether transaction Value fields are DB-encoded as 256 bits instead of the
|
||||
# default of 128 bits.
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
//! [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#parameters) protocol constants and utils for shard Blob Transactions.
|
||||
|
||||
use crate::kzg::KzgSettings;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{io::Write, sync::Arc};
|
||||
#[cfg(feature = "c-kzg")]
|
||||
pub use trusted_setup::*;
|
||||
|
||||
/// Size a single field element in bytes.
|
||||
pub const FIELD_ELEMENT_BYTES: u64 = 32;
|
||||
@ -34,45 +32,55 @@ pub const BLOB_TX_MIN_BLOB_GASPRICE: u128 = 1u128;
|
||||
/// Commitment version of a KZG commitment
|
||||
pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
|
||||
|
||||
/// KZG trusted setup
|
||||
pub static MAINNET_KZG_TRUSTED_SETUP: Lazy<Arc<KzgSettings>> = Lazy::new(|| {
|
||||
Arc::new(
|
||||
c_kzg::KzgSettings::load_trusted_setup(
|
||||
&revm_primitives::kzg::G1_POINTS.0,
|
||||
&revm_primitives::kzg::G2_POINTS.0,
|
||||
#[cfg(feature = "c-kzg")]
|
||||
mod trusted_setup {
|
||||
use crate::kzg::KzgSettings;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{io::Write, sync::Arc};
|
||||
|
||||
/// KZG trusted setup
|
||||
pub static MAINNET_KZG_TRUSTED_SETUP: Lazy<Arc<KzgSettings>> = Lazy::new(|| {
|
||||
Arc::new(
|
||||
c_kzg::KzgSettings::load_trusted_setup(
|
||||
&revm_primitives::kzg::G1_POINTS.0,
|
||||
&revm_primitives::kzg::G2_POINTS.0,
|
||||
)
|
||||
.expect("failed to load trusted setup"),
|
||||
)
|
||||
.expect("failed to load trusted setup"),
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
/// Loads the trusted setup parameters from the given bytes and returns the [KzgSettings].
|
||||
///
|
||||
/// This creates a temp file to store the bytes and then loads the [KzgSettings] from the file via
|
||||
/// [KzgSettings::load_trusted_setup_file].
|
||||
pub fn load_trusted_setup_from_bytes(bytes: &[u8]) -> Result<KzgSettings, LoadKzgSettingsError> {
|
||||
let mut file = tempfile::NamedTempFile::new().map_err(LoadKzgSettingsError::TempFileErr)?;
|
||||
file.write_all(bytes).map_err(LoadKzgSettingsError::TempFileErr)?;
|
||||
KzgSettings::load_trusted_setup_file(file.path()).map_err(LoadKzgSettingsError::KzgError)
|
||||
}
|
||||
/// Loads the trusted setup parameters from the given bytes and returns the [KzgSettings].
|
||||
///
|
||||
/// This creates a temp file to store the bytes and then loads the [KzgSettings] from the file
|
||||
/// via [KzgSettings::load_trusted_setup_file].
|
||||
pub fn load_trusted_setup_from_bytes(
|
||||
bytes: &[u8],
|
||||
) -> Result<KzgSettings, LoadKzgSettingsError> {
|
||||
let mut file = tempfile::NamedTempFile::new().map_err(LoadKzgSettingsError::TempFileErr)?;
|
||||
file.write_all(bytes).map_err(LoadKzgSettingsError::TempFileErr)?;
|
||||
KzgSettings::load_trusted_setup_file(file.path()).map_err(LoadKzgSettingsError::KzgError)
|
||||
}
|
||||
|
||||
/// Error type for loading the trusted setup.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LoadKzgSettingsError {
|
||||
/// Failed to create temp file to store bytes for loading [KzgSettings] via
|
||||
/// [KzgSettings::load_trusted_setup_file].
|
||||
#[error("failed to setup temp file: {0}")]
|
||||
TempFileErr(#[from] std::io::Error),
|
||||
/// Kzg error
|
||||
#[error("KZG error: {0:?}")]
|
||||
KzgError(#[from] c_kzg::Error),
|
||||
}
|
||||
/// Error type for loading the trusted setup.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LoadKzgSettingsError {
|
||||
/// Failed to create temp file to store bytes for loading [KzgSettings] via
|
||||
/// [KzgSettings::load_trusted_setup_file].
|
||||
#[error("failed to setup temp file: {0}")]
|
||||
TempFileErr(#[from] std::io::Error),
|
||||
/// Kzg error
|
||||
#[error("KZG error: {0:?}")]
|
||||
KzgError(#[from] c_kzg::Error),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn ensure_load_kzg_settings() {
|
||||
let _settings = Arc::clone(&MAINNET_KZG_TRUSTED_SETUP);
|
||||
#[test]
|
||||
fn ensure_load_kzg_settings() {
|
||||
let _settings = Arc::clone(&MAINNET_KZG_TRUSTED_SETUP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
//! Helpers for working with EIP-4844 blob fee
|
||||
use crate::{
|
||||
constants::eip4844::{TARGET_DATA_GAS_PER_BLOCK, VERSIONED_HASH_VERSION_KZG},
|
||||
kzg::KzgCommitment,
|
||||
B256,
|
||||
};
|
||||
use crate::constants::eip4844::TARGET_DATA_GAS_PER_BLOCK;
|
||||
#[cfg(feature = "c-kzg")]
|
||||
use crate::{constants::eip4844::VERSIONED_HASH_VERSION_KZG, kzg::KzgCommitment, B256};
|
||||
#[cfg(feature = "c-kzg")]
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
// re-exports from revm for calculating blob fee
|
||||
@ -12,6 +11,7 @@ pub use revm_primitives::calc_blob_gasprice;
|
||||
/// Calculates the versioned hash for a KzgCommitment
|
||||
///
|
||||
/// Specified in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension)
|
||||
#[cfg(feature = "c-kzg")]
|
||||
pub fn kzg_to_versioned_hash(commitment: KzgCommitment) -> B256 {
|
||||
let mut res = Sha256::digest(commitment.as_slice());
|
||||
res[0] = VERSIONED_HASH_VERSION_KZG;
|
||||
|
||||
@ -64,6 +64,7 @@ pub use constants::{
|
||||
DEV_GENESIS_HASH, EMPTY_OMMER_ROOT_HASH, GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH,
|
||||
KECCAK_EMPTY, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH,
|
||||
};
|
||||
#[cfg(feature = "c-kzg")]
|
||||
pub use eip4844::{calculate_excess_blob_gas, kzg_to_versioned_hash};
|
||||
pub use forkid::{ForkFilter, ForkHash, ForkId, ForkTransition, ValidationError};
|
||||
pub use genesis::{Genesis, GenesisAccount};
|
||||
@ -84,12 +85,18 @@ pub use receipt::{Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts};
|
||||
pub use serde_helper::JsonU256;
|
||||
pub use snapshot::SnapshotSegment;
|
||||
pub use storage::StorageEntry;
|
||||
|
||||
#[cfg(feature = "c-kzg")]
|
||||
pub use transaction::{
|
||||
BlobTransaction, BlobTransactionSidecar, BlobTransactionValidationError,
|
||||
FromRecoveredPooledTransaction, PooledTransactionsElement,
|
||||
PooledTransactionsElementEcRecovered,
|
||||
};
|
||||
|
||||
pub use transaction::{
|
||||
util::secp256k1::{public_key_to_address, recover_signer, sign_message},
|
||||
AccessList, AccessListItem, BlobTransaction, BlobTransactionSidecar,
|
||||
BlobTransactionValidationError, FromRecoveredPooledTransaction, FromRecoveredTransaction,
|
||||
IntoRecoveredTransaction, InvalidTransactionError, PooledTransactionsElement,
|
||||
PooledTransactionsElementEcRecovered, Signature, Transaction, TransactionKind, TransactionMeta,
|
||||
AccessList, AccessListItem, FromRecoveredTransaction, IntoRecoveredTransaction,
|
||||
InvalidTransactionError, Signature, Transaction, TransactionKind, TransactionMeta,
|
||||
TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, TxEip1559, TxEip2930,
|
||||
TxEip4844, TxHashOrNumber, TxLegacy, TxType, TxValue, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
|
||||
EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
|
||||
@ -125,6 +132,7 @@ pub type H64 = B64;
|
||||
pub use arbitrary;
|
||||
|
||||
/// EIP-4844 + KZG helpers
|
||||
#[cfg(feature = "c-kzg")]
|
||||
pub mod kzg {
|
||||
pub use c_kzg::*;
|
||||
}
|
||||
|
||||
@ -1,32 +1,23 @@
|
||||
use super::access_list::AccessList;
|
||||
use crate::{
|
||||
constants::eip4844::DATA_GAS_PER_BLOB,
|
||||
keccak256,
|
||||
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, TxHash, TxType, TxValue, B256, EIP4844_TX_TYPE_ID,
|
||||
constants::eip4844::DATA_GAS_PER_BLOB, keccak256, Bytes, ChainId, Signature, TransactionKind,
|
||||
TxType, TxValue, B256,
|
||||
};
|
||||
use alloy_rlp::{length_of_length, Decodable, Encodable, Error as RlpError, Header};
|
||||
|
||||
#[cfg(feature = "c-kzg")]
|
||||
use crate::transaction::sidecar::*;
|
||||
|
||||
#[cfg(feature = "c-kzg")]
|
||||
use crate::kzg_to_versioned_hash;
|
||||
|
||||
#[cfg(feature = "c-kzg")]
|
||||
use crate::kzg::{self, KzgCommitment, KzgProof, KzgSettings};
|
||||
use alloy_rlp::{length_of_length, Decodable, Encodable, Header};
|
||||
use bytes::BytesMut;
|
||||
use reth_codecs::{main_codec, Compact};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{mem, ops::Deref};
|
||||
|
||||
#[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 crate::{
|
||||
constants::eip4844::{FIELD_ELEMENTS_PER_BLOB, MAINNET_KZG_TRUSTED_SETUP},
|
||||
kzg::BYTES_PER_FIELD_ELEMENT,
|
||||
};
|
||||
use std::mem;
|
||||
#[cfg(feature = "c-kzg")]
|
||||
use std::ops::Deref;
|
||||
|
||||
/// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
|
||||
///
|
||||
@ -127,6 +118,7 @@ impl TxEip4844 {
|
||||
/// Returns [BlobTransactionValidationError::InvalidProof] 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.
|
||||
#[cfg(feature = "c-kzg")]
|
||||
pub fn validate_blob(
|
||||
&self,
|
||||
sidecar: &BlobTransactionSidecar,
|
||||
@ -326,431 +318,3 @@ impl TxEip4844 {
|
||||
keccak256(&buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that can occur when validating a [BlobTransaction].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BlobTransactionValidationError {
|
||||
/// Proof validation failed.
|
||||
#[error("invalid KZG proof")]
|
||||
InvalidProof,
|
||||
/// An error returned by [`kzg`].
|
||||
#[error("KZG error: {0:?}")]
|
||||
KZGError(#[from] kzg::Error),
|
||||
/// The inner transaction is not a blob transaction.
|
||||
#[error("unable to verify proof for non blob transaction: {0}")]
|
||||
NotBlobTransaction(u8),
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct BlobTransaction {
|
||||
/// The transaction hash.
|
||||
pub hash: TxHash,
|
||||
/// The transaction payload.
|
||||
pub transaction: TxEip4844,
|
||||
/// The transaction signature.
|
||||
pub signature: Signature,
|
||||
/// The transaction's blob sidecar.
|
||||
pub sidecar: BlobTransactionSidecar,
|
||||
}
|
||||
|
||||
impl BlobTransaction {
|
||||
/// Constructs a new [BlobTransaction] from a [TransactionSigned] and a
|
||||
/// [BlobTransactionSidecar].
|
||||
///
|
||||
/// Returns an error if the signed transaction is not [TxEip4844]
|
||||
pub fn try_from_signed(
|
||||
tx: TransactionSigned,
|
||||
sidecar: BlobTransactionSidecar,
|
||||
) -> Result<Self, (TransactionSigned, BlobTransactionSidecar)> {
|
||||
let TransactionSigned { transaction, signature, hash } = tx;
|
||||
match transaction {
|
||||
Transaction::Eip4844(transaction) => Ok(Self { hash, transaction, signature, sidecar }),
|
||||
transaction => {
|
||||
let tx = TransactionSigned { transaction, signature, hash };
|
||||
Err((tx, sidecar))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies that the transaction's blob data, commitments, and proofs are all valid.
|
||||
///
|
||||
/// See also [TxEip4844::validate_blob]
|
||||
pub fn validate(
|
||||
&self,
|
||||
proof_settings: &KzgSettings,
|
||||
) -> Result<(), BlobTransactionValidationError> {
|
||||
self.transaction.validate_blob(&self.sidecar, proof_settings)
|
||||
}
|
||||
|
||||
/// Splits the [BlobTransaction] into its [TransactionSigned] and [BlobTransactionSidecar]
|
||||
/// components.
|
||||
pub fn into_parts(self) -> (TransactionSigned, BlobTransactionSidecar) {
|
||||
let transaction = TransactionSigned {
|
||||
transaction: Transaction::Eip4844(self.transaction),
|
||||
hash: self.hash,
|
||||
signature: self.signature,
|
||||
};
|
||||
|
||||
(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.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 signature
|
||||
self.signature.encode(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.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();
|
||||
|
||||
// We use the calculated payload len to construct the first list header, which encompasses
|
||||
// everything in the tx - the length of the second, inner list header is part of
|
||||
// payload_length
|
||||
let blob_tx_header = Header { list: true, payload_length };
|
||||
|
||||
// The final length is the length of:
|
||||
// * the outer blob tx header +
|
||||
// * the inner tx header +
|
||||
// * the inner tx fields +
|
||||
// * the signature fields +
|
||||
// * the sidecar fields
|
||||
blob_tx_header.length() + blob_tx_header.payload_length
|
||||
}
|
||||
|
||||
/// 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]) -> alloy_rlp::Result<Self> {
|
||||
// decode the _first_ list header for the rest of the transaction
|
||||
let header = Header::decode(data)?;
|
||||
if !header.list {
|
||||
return Err(RlpError::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(RlpError::Custom(
|
||||
"PooledTransactions inner blob tx must be encoded as a list",
|
||||
))
|
||||
}
|
||||
|
||||
// inner transaction
|
||||
let transaction = TxEip4844::decode_inner(data)?;
|
||||
|
||||
// signature
|
||||
let signature = Signature::decode(data)?;
|
||||
|
||||
// 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 `encode_with_signature`, which RLP encodes the transaction with a
|
||||
// signature for hashing without a header. We then hash the result.
|
||||
let mut buf = Vec::new();
|
||||
transaction.encode_with_signature(&signature, &mut buf, false);
|
||||
let hash = keccak256(&buf);
|
||||
|
||||
Ok(Self { transaction, hash, signature, sidecar })
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents a set of blobs, and its corresponding commitments and proofs.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
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 {
|
||||
/// Creates a new [BlobTransactionSidecar] using the given blobs, commitments, and proofs.
|
||||
pub fn new(blobs: Vec<Blob>, commitments: Vec<Bytes48>, proofs: Vec<Bytes48>) -> Self {
|
||||
Self { blobs, commitments, proofs }
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
BlobTransactionSidecarRlp::wrap_ref(self).encode(out);
|
||||
}
|
||||
|
||||
/// Outputs the RLP length of the [BlobTransactionSidecar] fields, without a RLP header.
|
||||
pub fn fields_len(&self) -> usize {
|
||||
BlobTransactionSidecarRlp::wrap_ref(self).fields_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]) -> alloy_rlp::Result<Self> {
|
||||
Ok(BlobTransactionSidecarRlp::decode(buf)?.unwrap())
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for c-kzg rlp
|
||||
#[repr(C)]
|
||||
struct BlobTransactionSidecarRlp {
|
||||
blobs: Vec<[u8; c_kzg::BYTES_PER_BLOB]>,
|
||||
commitments: Vec<[u8; 48]>,
|
||||
proofs: Vec<[u8; 48]>,
|
||||
}
|
||||
|
||||
const _: [(); std::mem::size_of::<BlobTransactionSidecar>()] =
|
||||
[(); std::mem::size_of::<BlobTransactionSidecarRlp>()];
|
||||
|
||||
impl BlobTransactionSidecarRlp {
|
||||
fn wrap_ref(other: &BlobTransactionSidecar) -> &Self {
|
||||
// SAFETY: Same repr and size
|
||||
unsafe { &*(other as *const BlobTransactionSidecar).cast::<Self>() }
|
||||
}
|
||||
|
||||
fn unwrap(self) -> BlobTransactionSidecar {
|
||||
// SAFETY: Same repr and size
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
|
||||
fn encode(&self, out: &mut dyn bytes::BufMut) {
|
||||
// Encode the blobs, commitments, and proofs
|
||||
self.blobs.encode(out);
|
||||
self.commitments.encode(out);
|
||||
self.proofs.encode(out);
|
||||
}
|
||||
|
||||
fn fields_len(&self) -> usize {
|
||||
self.blobs.length() + self.commitments.length() + self.proofs.length()
|
||||
}
|
||||
|
||||
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||
Ok(Self {
|
||||
blobs: Decodable::decode(buf)?,
|
||||
commitments: Decodable::decode(buf)?,
|
||||
proofs: Decodable::decode(buf)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl<'a> arbitrary::Arbitrary<'a> for BlobTransactionSidecar {
|
||||
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 each blob is cacnonical by ensuring 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_sidecar(blobs))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl proptest::arbitrary::Arbitrary for BlobTransactionSidecar {
|
||||
type Parameters = ParamsFor<String>;
|
||||
type Strategy = BoxedStrategy<BlobTransactionSidecar>;
|
||||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
proptest_vec(proptest_vec(proptest_any::<u8>(), BYTES_PER_BLOB), 1..=5)
|
||||
.prop_map(move |blobs| {
|
||||
let blobs = blobs
|
||||
.into_iter()
|
||||
.map(|mut blob| {
|
||||
let mut arr = [0u8; BYTES_PER_BLOB];
|
||||
|
||||
// Ensure that each blob is cacnonical by ensuring 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_sidecar(blobs)
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
fn generate_blob_sidecar(blobs: Vec<Blob>) -> BlobTransactionSidecar {
|
||||
let kzg_settings = MAINNET_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, commitment, &kzg_settings).unwrap()
|
||||
})
|
||||
.map(|proof| proof.to_bytes())
|
||||
.collect();
|
||||
|
||||
BlobTransactionSidecar { blobs, commitments, proofs }
|
||||
}
|
||||
|
||||
@ -16,13 +16,15 @@ use std::mem;
|
||||
pub use access_list::{AccessList, AccessListItem};
|
||||
pub use eip1559::TxEip1559;
|
||||
pub use eip2930::TxEip2930;
|
||||
pub use eip4844::{
|
||||
BlobTransaction, BlobTransactionSidecar, BlobTransactionValidationError, TxEip4844,
|
||||
};
|
||||
pub use eip4844::TxEip4844;
|
||||
|
||||
pub use error::InvalidTransactionError;
|
||||
pub use legacy::TxLegacy;
|
||||
pub use meta::TransactionMeta;
|
||||
#[cfg(feature = "c-kzg")]
|
||||
pub use pooled::{PooledTransactionsElement, PooledTransactionsElementEcRecovered};
|
||||
#[cfg(feature = "c-kzg")]
|
||||
pub use sidecar::{BlobTransaction, BlobTransactionSidecar, BlobTransactionValidationError};
|
||||
pub use signature::Signature;
|
||||
pub use tx_type::{
|
||||
TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
|
||||
@ -37,7 +39,10 @@ mod eip4844;
|
||||
mod error;
|
||||
mod legacy;
|
||||
mod meta;
|
||||
#[cfg(feature = "c-kzg")]
|
||||
mod pooled;
|
||||
#[cfg(feature = "c-kzg")]
|
||||
mod sidecar;
|
||||
mod signature;
|
||||
mod tx_type;
|
||||
mod tx_value;
|
||||
@ -1286,6 +1291,7 @@ impl FromRecoveredTransaction for TransactionSignedEcRecovered {
|
||||
///
|
||||
/// This is a conversion trait that'll ensure transactions received via P2P can be converted to the
|
||||
/// transaction type that the transaction pool uses.
|
||||
#[cfg(feature = "c-kzg")]
|
||||
pub trait FromRecoveredPooledTransaction {
|
||||
/// Converts to this type from the given [`PooledTransactionsElementEcRecovered`].
|
||||
fn from_recovered_transaction(tx: PooledTransactionsElementEcRecovered) -> Self;
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
//! Defines the types for blob transactions, legacy, and other EIP-2718 transactions included in a
|
||||
//! response to `GetPooledTransactions`.
|
||||
#![cfg(feature = "c-kzg")]
|
||||
#![cfg_attr(docsrs, doc(cfg(feature = "c-kzg")))]
|
||||
|
||||
use crate::{
|
||||
Address, BlobTransaction, Bytes, Signature, Transaction, TransactionSigned,
|
||||
TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxHash, TxLegacy, B256, EIP4844_TX_TYPE_ID,
|
||||
|
||||
454
crates/primitives/src/transaction/sidecar.rs
Normal file
454
crates/primitives/src/transaction/sidecar.rs
Normal file
@ -0,0 +1,454 @@
|
||||
#![cfg(feature = "c-kzg")]
|
||||
#![cfg_attr(docsrs, doc(cfg(feature = "c-kzg")))]
|
||||
|
||||
use crate::{
|
||||
keccak256, Signature, Transaction, TransactionSigned, TxEip4844, TxHash, EIP4844_TX_TYPE_ID,
|
||||
};
|
||||
|
||||
use crate::kzg::{
|
||||
self, Blob, Bytes48, KzgSettings, BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
|
||||
};
|
||||
use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header};
|
||||
|
||||
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 crate::{
|
||||
constants::eip4844::{FIELD_ELEMENTS_PER_BLOB, MAINNET_KZG_TRUSTED_SETUP},
|
||||
kzg::{KzgCommitment, KzgProof, BYTES_PER_FIELD_ELEMENT},
|
||||
};
|
||||
|
||||
/// An error that can occur when validating a [BlobTransaction].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BlobTransactionValidationError {
|
||||
/// Proof validation failed.
|
||||
#[error("invalid KZG proof")]
|
||||
InvalidProof,
|
||||
/// An error returned by [`kzg`].
|
||||
#[error("KZG error: {0:?}")]
|
||||
KZGError(#[from] kzg::Error),
|
||||
/// The inner transaction is not a blob transaction.
|
||||
#[error("unable to verify proof for non blob transaction: {0}")]
|
||||
NotBlobTransaction(u8),
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct BlobTransaction {
|
||||
/// The transaction hash.
|
||||
pub hash: TxHash,
|
||||
/// The transaction payload.
|
||||
pub transaction: TxEip4844,
|
||||
/// The transaction signature.
|
||||
pub signature: Signature,
|
||||
/// The transaction's blob sidecar.
|
||||
pub sidecar: BlobTransactionSidecar,
|
||||
}
|
||||
|
||||
impl BlobTransaction {
|
||||
/// Constructs a new [BlobTransaction] from a [TransactionSigned] and a
|
||||
/// [BlobTransactionSidecar].
|
||||
///
|
||||
/// Returns an error if the signed transaction is not [TxEip4844]
|
||||
pub fn try_from_signed(
|
||||
tx: TransactionSigned,
|
||||
sidecar: BlobTransactionSidecar,
|
||||
) -> Result<Self, (TransactionSigned, BlobTransactionSidecar)> {
|
||||
let TransactionSigned { transaction, signature, hash } = tx;
|
||||
match transaction {
|
||||
Transaction::Eip4844(transaction) => Ok(Self { hash, transaction, signature, sidecar }),
|
||||
transaction => {
|
||||
let tx = TransactionSigned { transaction, signature, hash };
|
||||
Err((tx, sidecar))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies that the transaction's blob data, commitments, and proofs are all valid.
|
||||
///
|
||||
/// See also [TxEip4844::validate_blob]
|
||||
pub fn validate(
|
||||
&self,
|
||||
proof_settings: &KzgSettings,
|
||||
) -> Result<(), BlobTransactionValidationError> {
|
||||
self.transaction.validate_blob(&self.sidecar, proof_settings)
|
||||
}
|
||||
|
||||
/// Splits the [BlobTransaction] into its [TransactionSigned] and [BlobTransactionSidecar]
|
||||
/// components.
|
||||
pub fn into_parts(self) -> (TransactionSigned, BlobTransactionSidecar) {
|
||||
let transaction = TransactionSigned {
|
||||
transaction: Transaction::Eip4844(self.transaction),
|
||||
hash: self.hash,
|
||||
signature: self.signature,
|
||||
};
|
||||
|
||||
(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.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 signature
|
||||
self.signature.encode(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.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();
|
||||
|
||||
// We use the calculated payload len to construct the first list header, which encompasses
|
||||
// everything in the tx - the length of the second, inner list header is part of
|
||||
// payload_length
|
||||
let blob_tx_header = Header { list: true, payload_length };
|
||||
|
||||
// The final length is the length of:
|
||||
// * the outer blob tx header +
|
||||
// * the inner tx header +
|
||||
// * the inner tx fields +
|
||||
// * the signature fields +
|
||||
// * the sidecar fields
|
||||
blob_tx_header.length() + blob_tx_header.payload_length
|
||||
}
|
||||
|
||||
/// 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]) -> alloy_rlp::Result<Self> {
|
||||
// decode the _first_ list header for the rest of the transaction
|
||||
let header = Header::decode(data)?;
|
||||
if !header.list {
|
||||
return Err(RlpError::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(RlpError::Custom(
|
||||
"PooledTransactions inner blob tx must be encoded as a list",
|
||||
))
|
||||
}
|
||||
|
||||
// inner transaction
|
||||
let transaction = TxEip4844::decode_inner(data)?;
|
||||
|
||||
// signature
|
||||
let signature = Signature::decode(data)?;
|
||||
|
||||
// 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 `encode_with_signature`, which RLP encodes the transaction with a
|
||||
// signature for hashing without a header. We then hash the result.
|
||||
let mut buf = Vec::new();
|
||||
transaction.encode_with_signature(&signature, &mut buf, false);
|
||||
let hash = keccak256(&buf);
|
||||
|
||||
Ok(Self { transaction, hash, signature, sidecar })
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents a set of blobs, and its corresponding commitments and proofs.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
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 {
|
||||
/// Creates a new [BlobTransactionSidecar] using the given blobs, commitments, and proofs.
|
||||
pub fn new(blobs: Vec<Blob>, commitments: Vec<Bytes48>, proofs: Vec<Bytes48>) -> Self {
|
||||
Self { blobs, commitments, proofs }
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
BlobTransactionSidecarRlp::wrap_ref(self).encode(out);
|
||||
}
|
||||
|
||||
/// Outputs the RLP length of the [BlobTransactionSidecar] fields, without a RLP header.
|
||||
pub fn fields_len(&self) -> usize {
|
||||
BlobTransactionSidecarRlp::wrap_ref(self).fields_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]) -> alloy_rlp::Result<Self> {
|
||||
Ok(BlobTransactionSidecarRlp::decode(buf)?.unwrap())
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for c-kzg rlp
|
||||
#[repr(C)]
|
||||
struct BlobTransactionSidecarRlp {
|
||||
blobs: Vec<[u8; c_kzg::BYTES_PER_BLOB]>,
|
||||
commitments: Vec<[u8; 48]>,
|
||||
proofs: Vec<[u8; 48]>,
|
||||
}
|
||||
|
||||
const _: [(); std::mem::size_of::<BlobTransactionSidecar>()] =
|
||||
[(); std::mem::size_of::<BlobTransactionSidecarRlp>()];
|
||||
|
||||
impl BlobTransactionSidecarRlp {
|
||||
fn wrap_ref(other: &BlobTransactionSidecar) -> &Self {
|
||||
// SAFETY: Same repr and size
|
||||
unsafe { &*(other as *const BlobTransactionSidecar).cast::<Self>() }
|
||||
}
|
||||
|
||||
fn unwrap(self) -> BlobTransactionSidecar {
|
||||
// SAFETY: Same repr and size
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
|
||||
fn encode(&self, out: &mut dyn bytes::BufMut) {
|
||||
// Encode the blobs, commitments, and proofs
|
||||
self.blobs.encode(out);
|
||||
self.commitments.encode(out);
|
||||
self.proofs.encode(out);
|
||||
}
|
||||
|
||||
fn fields_len(&self) -> usize {
|
||||
self.blobs.length() + self.commitments.length() + self.proofs.length()
|
||||
}
|
||||
|
||||
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||
Ok(Self {
|
||||
blobs: Decodable::decode(buf)?,
|
||||
commitments: Decodable::decode(buf)?,
|
||||
proofs: Decodable::decode(buf)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl<'a> arbitrary::Arbitrary<'a> for BlobTransactionSidecar {
|
||||
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 each blob is cacnonical by ensuring 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_sidecar(blobs))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl proptest::arbitrary::Arbitrary for BlobTransactionSidecar {
|
||||
type Parameters = ParamsFor<String>;
|
||||
type Strategy = BoxedStrategy<BlobTransactionSidecar>;
|
||||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
proptest_vec(proptest_vec(proptest_any::<u8>(), BYTES_PER_BLOB), 1..=5)
|
||||
.prop_map(move |blobs| {
|
||||
let blobs = blobs
|
||||
.into_iter()
|
||||
.map(|mut blob| {
|
||||
let mut arr = [0u8; BYTES_PER_BLOB];
|
||||
|
||||
// Ensure that each blob is cacnonical by ensuring 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_sidecar(blobs)
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
fn generate_blob_sidecar(blobs: Vec<Blob>) -> BlobTransactionSidecar {
|
||||
let kzg_settings = MAINNET_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, commitment, &kzg_settings).unwrap()
|
||||
})
|
||||
.map(|proof| proof.to_bytes())
|
||||
.collect();
|
||||
|
||||
BlobTransactionSidecar { blobs, commitments, proofs }
|
||||
}
|
||||
Reference in New Issue
Block a user