Skip to content

Commit

Permalink
feat: serde for consensus tx types (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
klkvr committed Mar 26, 2024
1 parent 68952c0 commit 7e39c85
Show file tree
Hide file tree
Showing 11 changed files with 250 additions and 9 deletions.
14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ tempfile = "3.10"

# TODO: Remove eventually.
[patch.crates-io]
alloy-sol-macro = { git = "https://github.com/alloy-rs/core", rev = "1bac7678797fcd1bee2f2580825724b4165b12c1" }
alloy-primitives = { git = "https://github.com/alloy-rs/core", rev = "1bac7678797fcd1bee2f2580825724b4165b12c1" }
alloy-sol-types = { git = "https://github.com/alloy-rs/core", rev = "1bac7678797fcd1bee2f2580825724b4165b12c1" }
alloy-json-abi = { git = "https://github.com/alloy-rs/core", rev = "1bac7678797fcd1bee2f2580825724b4165b12c1" }
alloy-dyn-abi = { git = "https://github.com/alloy-rs/core", rev = "1bac7678797fcd1bee2f2580825724b4165b12c1" }
syn-solidity = { git = "https://github.com/alloy-rs/core", rev = "1bac7678797fcd1bee2f2580825724b4165b12c1" }
alloy-core = { git = "https://github.com/alloy-rs/core", rev = "1bac7678797fcd1bee2f2580825724b4165b12c1" }
alloy-sol-macro = { git = "https://github.com/alloy-rs/core", rev = "ff33969" }
alloy-primitives = { git = "https://github.com/alloy-rs/core", rev = "ff33969" }
alloy-sol-types = { git = "https://github.com/alloy-rs/core", rev = "ff33969" }
alloy-json-abi = { git = "https://github.com/alloy-rs/core", rev = "ff33969" }
alloy-dyn-abi = { git = "https://github.com/alloy-rs/core", rev = "ff33969" }
syn-solidity = { git = "https://github.com/alloy-rs/core", rev = "ff33969" }
alloy-core = { git = "https://github.com/alloy-rs/core", rev = "ff33969" }
6 changes: 6 additions & 0 deletions crates/consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ exclude.workspace = true
alloy-primitives = { workspace = true, features = ["rlp"] }
alloy-rlp.workspace = true
alloy-eips.workspace = true
alloy-serde = { workspace = true, optional = true }

sha2 = "0.10"

Expand All @@ -25,13 +26,18 @@ c-kzg = { workspace = true, features = ["std", "serde"], optional = true }
# arbitrary
arbitrary = { workspace = true, features = ["derive"], optional = true }

# serde
serde = { workspace = true, features = ["derive"], optional = true }

[dev-dependencies]
alloy-signer.workspace = true
arbitrary = { workspace = true, features = ["derive"] }
k256.workspace = true
tokio = { workspace = true, features = ["macros"] }
serde_json.workspace = true

[features]
k256 = ["alloy-primitives/k256"]
kzg = ["dep:c-kzg", "dep:thiserror", "alloy-eips/kzg"]
arbitrary = ["dep:arbitrary", "alloy-eips/arbitrary"]
serde = ["dep:serde", "alloy-primitives/serde", "dep:alloy-serde", "alloy-eips/serde"]
3 changes: 3 additions & 0 deletions crates/consensus/src/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ use alloy_primitives::{Signature, B256};

/// A transaction with a signature and hash seal.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Signed<T, Sig = Signature> {
#[cfg_attr(feature = "serde", serde(flatten))]
tx: T,
#[cfg_attr(feature = "serde", serde(flatten))]
signature: Sig,
hash: B256,
}
Expand Down
10 changes: 9 additions & 1 deletion crates/consensus/src/transaction/eip1559.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ use std::mem;

/// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)).
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct TxEip1559 {
/// EIP-155: Simple replay attack protection
pub chain_id: u64,
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u64_hex_or_decimal"))]
pub chain_id: ChainId,
/// A scalar value equal to the number of transactions sent by the sender; formally Tn.
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u64_hex_or_decimal"))]
pub nonce: u64,
/// A scalar value equal to the maximum
/// amount of gas that should be used in executing
/// this transaction. This is paid up-front, before any
/// computation is done and may not be increased
/// later; formally Tg.
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u64_hex_or_decimal"))]
pub gas_limit: u64,
/// A scalar value equal to the maximum
/// amount of gas that should be used in executing
Expand All @@ -28,6 +33,7 @@ pub struct TxEip1559 {
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasFeeCap`
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u128_hex_or_decimal"))]
pub max_fee_per_gas: u128,
/// Max Priority fee that transaction is paying
///
Expand All @@ -36,9 +42,11 @@ pub struct TxEip1559 {
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasTipCap`
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u128_hex_or_decimal"))]
pub max_priority_fee_per_gas: u128,
/// The 160-bit address of the message call’s recipient or, for a contract creation
/// transaction, ∅, used here to denote the only member of B0 ; formally Tt.
#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "TxKind::is_create"))]
pub to: TxKind,
/// A scalar value equal to the number of Wei to
/// be transferred to the message call’s recipient or,
Expand Down
7 changes: 7 additions & 0 deletions crates/consensus/src/transaction/eip2930.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ use std::mem;

/// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)).
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct TxEip2930 {
/// Added as EIP-pub 155: Simple replay attack protection
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u64_hex_or_decimal"))]
pub chain_id: ChainId,
/// A scalar value equal to the number of transactions sent by the sender; formally Tn.
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u64_hex_or_decimal"))]
pub nonce: u64,
/// A scalar value equal to the number of
/// Wei to be paid per unit of gas for all computation
Expand All @@ -18,15 +22,18 @@ pub struct TxEip2930 {
/// As ethereum circulation is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
/// 340282366920938463463374607431768211455
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u128_hex_or_decimal"))]
pub gas_price: u128,
/// A scalar value equal to the maximum
/// amount of gas that should be used in executing
/// this transaction. This is paid up-front, before any
/// computation is done and may not be increased
/// later; formally Tg.
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u64_hex_or_decimal"))]
pub gas_limit: u64,
/// The 160-bit address of the message call’s recipient or, for a contract creation
/// transaction, ∅, used here to denote the only member of B0 ; formally Tt.
#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "TxKind::is_create"))]
pub to: TxKind,
/// A scalar value equal to the number of Wei to
/// be transferred to the message call’s recipient or,
Expand Down
41 changes: 41 additions & 0 deletions crates/consensus/src/transaction/eip4844.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,41 @@ pub enum BlobTransactionValidationError {
/// or a transaction with a sidecar, which is used when submitting a transaction to the network and
/// when receiving and sending transactions during the gossip stage.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum TxEip4844Variant {
/// A standalone transaction with blob hashes and max blob fee.
TxEip4844(TxEip4844),
/// A transaction with a sidecar, which contains the blob data, commitments, and proofs.
TxEip4844WithSidecar(TxEip4844WithSidecar),
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for TxEip4844Variant {
fn deserialize<D>(deserializer: D) -> Result<TxEip4844Variant, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct TxEip4844SerdeHelper {
#[serde(flatten)]
tx: TxEip4844,
#[serde(flatten)]
sidecar: Option<BlobTransactionSidecar>,
}

let tx = TxEip4844SerdeHelper::deserialize(deserializer)?;

if let Some(sidecar) = tx.sidecar {
Ok(TxEip4844Variant::TxEip4844WithSidecar(TxEip4844WithSidecar::from_tx_and_sidecar(
tx.tx, sidecar,
)))
} else {
Ok(TxEip4844Variant::TxEip4844(tx.tx))
}
}
}

impl From<TxEip4844WithSidecar> for TxEip4844Variant {
fn from(tx: TxEip4844WithSidecar) -> Self {
TxEip4844Variant::TxEip4844WithSidecar(tx)
Expand Down Expand Up @@ -286,16 +314,21 @@ impl SignableTransaction<Signature> for TxEip4844Variant {
///
/// A transaction with blob hashes and max blob fee. It does not have the Blob sidecar.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct TxEip4844 {
/// Added as EIP-pub 155: Simple replay attack protection
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u64_hex_or_decimal"))]
pub chain_id: ChainId,
/// A scalar value equal to the number of transactions sent by the sender; formally Tn.
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u64_hex_or_decimal"))]
pub nonce: u64,
/// A scalar value equal to the maximum
/// amount of gas that should be used in executing
/// this transaction. This is paid up-front, before any
/// computation is done and may not be increased
/// later; formally Tg.
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u64_hex_or_decimal"))]
pub gas_limit: u64,
/// A scalar value equal to the maximum
/// amount of gas that should be used in executing
Expand All @@ -308,6 +341,7 @@ pub struct TxEip4844 {
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasFeeCap`
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u128_hex_or_decimal"))]
pub max_fee_per_gas: u128,
/// Max Priority fee that transaction is paying
///
Expand All @@ -316,6 +350,7 @@ pub struct TxEip4844 {
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasTipCap`
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u128_hex_or_decimal"))]
pub max_priority_fee_per_gas: u128,
/// The 160-bit address of the message call’s recipient.
pub to: Address,
Expand All @@ -337,6 +372,7 @@ pub struct TxEip4844 {
/// Max fee per data gas
///
/// aka BlobFeeCap or blobGasFeeCap
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::u128_hex_or_decimal"))]
pub max_fee_per_blob_gas: u128,

/// Input has two uses depending if transaction is Create or Call (if `to` field is None or
Expand Down Expand Up @@ -719,10 +755,14 @@ impl Decodable for TxEip4844 {
/// of a `PooledTransactions` response, and is also used as the format for sending raw transactions
/// through the network (eth_sendRawTransaction/eth_sendTransaction).
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct TxEip4844WithSidecar {
/// The actual transaction.
#[cfg_attr(feature = "serde", serde(flatten))]
pub tx: TxEip4844,
/// The sidecar.
#[cfg_attr(feature = "serde", serde(flatten))]
pub sidecar: BlobTransactionSidecar,
}

Expand Down Expand Up @@ -914,6 +954,7 @@ impl Transaction for TxEip4844WithSidecar {
/// This represents a set of blobs, and its corresponding commitments and proofs.
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlobTransactionSidecar {
/// The blob data.
pub blobs: Vec<Blob>,
Expand Down
116 changes: 116 additions & 0 deletions crates/consensus/src/transaction/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,17 @@ impl TryFrom<u8> for TxType {
///
/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
pub enum TxEnvelope {
/// An untagged [`TxLegacy`].
#[cfg_attr(feature = "serde", serde(rename = "0x00", alias = "0x0"))]
Legacy(Signed<TxLegacy>),
/// A [`TxEip2930`] tagged with type 1.
#[cfg_attr(feature = "serde", serde(rename = "0x01", alias = "0x1"))]
Eip2930(Signed<TxEip2930>),
/// A [`TxEip1559`] tagged with type 2.
#[cfg_attr(feature = "serde", serde(rename = "0x02", alias = "0x2"))]
Eip1559(Signed<TxEip1559>),
/// A TxEip4844 tagged with type 3.
/// An EIP-4844 transaction has two network representations:
Expand All @@ -75,6 +80,7 @@ pub enum TxEnvelope {
///
/// 2 - The transaction with a sidecar, which is the form used to
/// send transactions to the network.
#[cfg_attr(feature = "serde", serde(rename = "0x03", alias = "0x3"))]
Eip4844(Signed<TxEip4844Variant>),
}

Expand Down Expand Up @@ -398,4 +404,114 @@ mod tests {
assert_eq!(encoded, hex_data);
assert_eq!(tx.encode_2718_len(), hex_data.len());
}

#[cfg(feature = "serde")]
fn test_serde_roundtrip<T: SignableTransaction<Signature>>(tx: T)
where
Signed<T>: Into<TxEnvelope>,
{
let signature = Signature::test_signature();
let tx_envelope: TxEnvelope = tx.into_signed(signature).into();

let serialized = serde_json::to_string(&tx_envelope).unwrap();
let deserialized: TxEnvelope = serde_json::from_str(&serialized).unwrap();

assert_eq!(tx_envelope, deserialized);
}

#[test]
#[cfg(feature = "serde")]
fn test_serde_roundtrip_legacy() {
let tx = TxLegacy {
chain_id: Some(1),
nonce: 100,
gas_price: 3_000_000_000,
gas_limit: 50_000,
to: TxKind::Call(Address::default()),
value: U256::from(10e18),
input: Bytes::new(),
};
test_serde_roundtrip(tx);
}

#[test]
#[cfg(feature = "serde")]
fn test_serde_roundtrip_eip1559() {
let tx = TxEip1559 {
chain_id: 1,
nonce: 100,
max_fee_per_gas: 50_000_000_000,
max_priority_fee_per_gas: 1_000_000_000_000,
gas_limit: 1_000_000,
to: TxKind::Create,
value: U256::from(10e18),
input: Bytes::new(),
access_list: AccessList(vec![AccessListItem {
address: Address::random(),
storage_keys: vec![B256::random()],
}]),
};
test_serde_roundtrip(tx);
}

#[test]
#[cfg(feature = "serde")]
fn test_serde_roundtrip_eip2930() {
let tx = TxEip2930 {
chain_id: u64::MAX,
nonce: u64::MAX,
gas_price: u128::MAX,
gas_limit: u64::MAX,
to: TxKind::Call(Address::random()),
value: U256::MAX,
input: Bytes::new(),
access_list: Default::default(),
};
test_serde_roundtrip(tx);
}

#[test]
#[cfg(feature = "serde")]
fn test_serde_roundtrip_eip4844() {
use crate::BlobTransactionSidecar;

let tx = TxEip4844Variant::TxEip4844(TxEip4844 {
chain_id: 1,
nonce: 100,
max_fee_per_gas: 50_000_000_000,
max_priority_fee_per_gas: 1_000_000_000_000,
gas_limit: 1_000_000,
to: Address::random(),
value: U256::from(10e18),
input: Bytes::new(),
access_list: AccessList(vec![AccessListItem {
address: Address::random(),
storage_keys: vec![B256::random()],
}]),
blob_versioned_hashes: vec![B256::random()],
max_fee_per_blob_gas: 0,
});
test_serde_roundtrip(tx);

let tx = TxEip4844Variant::TxEip4844WithSidecar(TxEip4844WithSidecar {
tx: TxEip4844 {
chain_id: 1,
nonce: 100,
max_fee_per_gas: 50_000_000_000,
max_priority_fee_per_gas: 1_000_000_000_000,
gas_limit: 1_000_000,
to: Address::random(),
value: U256::from(10e18),
input: Bytes::new(),
access_list: AccessList(vec![AccessListItem {
address: Address::random(),
storage_keys: vec![B256::random()],
}]),
blob_versioned_hashes: vec![B256::random()],
max_fee_per_blob_gas: 0,
},
sidecar: BlobTransactionSidecar { ..Default::default() },
});
test_serde_roundtrip(tx);
}
}
Loading

0 comments on commit 7e39c85

Please sign in to comment.