feat: add recovered wrapper type and eth pool conversions (#4267)

Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
This commit is contained in:
Matthias Seitz
2023-08-18 19:57:42 +02:00
committed by GitHub
parent 8516fefa28
commit 6077edf42a
8 changed files with 288 additions and 90 deletions

View File

@ -92,10 +92,10 @@ pub use transaction::{
util::secp256k1::{public_key_to_address, recover_signer, sign_message},
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,
PooledTransactionsElement, PooledTransactionsElementEcRecovered, 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,5 +1,6 @@
use super::access_list::AccessList;
use crate::{Bytes, ChainId, Signature, TransactionKind, TxType};
use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, H256};
use bytes::BytesMut;
use reth_codecs::{main_codec, Compact};
use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header};
use std::mem;
@ -188,6 +189,28 @@ impl TxEip1559 {
self.access_list.size() + // access_list
self.input.len() // input
}
/// Encodes the legacy transaction in RLP for signing.
pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
out.put_u8(self.tx_type() as u8);
Header { list: true, payload_length: self.fields_len() }.encode(out);
self.encode_fields(out);
}
/// Outputs the length of the signature RLP encoding for the transaction.
pub(crate) fn payload_len_for_signature(&self) -> usize {
let payload_length = self.fields_len();
// 'transaction type byte length' + 'header length' + 'payload length'
1 + length_of_length(payload_length) + payload_length
}
/// Outputs the signature hash of the transaction by first encoding without a signature, then
/// hashing.
pub(crate) fn signature_hash(&self) -> H256 {
let mut buf = BytesMut::with_capacity(self.payload_len_for_signature());
self.encode_for_signing(&mut buf);
keccak256(&buf)
}
}
#[cfg(test)]

View File

@ -1,5 +1,6 @@
use super::access_list::AccessList;
use crate::{Bytes, ChainId, Signature, TransactionKind, TxType};
use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, H256};
use bytes::BytesMut;
use reth_codecs::{main_codec, Compact};
use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header};
use std::mem;
@ -153,6 +154,28 @@ impl TxEip2930 {
pub(crate) fn tx_type(&self) -> TxType {
TxType::EIP2930
}
/// Encodes the legacy transaction in RLP for signing.
pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
out.put_u8(self.tx_type() as u8);
Header { list: true, payload_length: self.fields_len() }.encode(out);
self.encode_fields(out);
}
/// Outputs the length of the signature RLP encoding for the transaction.
pub(crate) fn payload_len_for_signature(&self) -> usize {
let payload_length = self.fields_len();
// 'transaction type byte length' + 'header length' + 'payload length'
1 + length_of_length(payload_length) + payload_length
}
/// Outputs the signature hash of the transaction by first encoding without a signature, then
/// hashing.
pub(crate) fn signature_hash(&self) -> H256 {
let mut buf = BytesMut::with_capacity(self.payload_len_for_signature());
self.encode_for_signing(&mut buf);
keccak256(&buf)
}
}
#[cfg(test)]

View File

@ -9,6 +9,7 @@ use crate::{
kzg_to_versioned_hash, Bytes, ChainId, Signature, Transaction, TransactionKind,
TransactionSigned, TxHash, TxType, EIP4844_TX_TYPE_ID, H256,
};
use bytes::BytesMut;
use reth_codecs::{main_codec, Compact};
use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header};
use serde::{Deserialize, Serialize};
@ -226,6 +227,28 @@ impl TxEip4844 {
pub(crate) fn tx_type(&self) -> TxType {
TxType::EIP4844
}
/// Encodes the legacy transaction in RLP for signing.
pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
out.put_u8(self.tx_type() as u8);
Header { list: true, payload_length: self.fields_len() }.encode(out);
self.encode_fields(out);
}
/// Outputs the length of the signature RLP encoding for the transaction.
pub(crate) fn payload_len_for_signature(&self) -> usize {
let payload_length = self.fields_len();
// 'transaction type byte length' + 'header length' + 'payload length'
1 + length_of_length(payload_length) + payload_length
}
/// Outputs the signature hash of the transaction by first encoding without a signature, then
/// hashing.
pub(crate) fn signature_hash(&self) -> H256 {
let mut buf = BytesMut::with_capacity(self.payload_len_for_signature());
self.encode_for_signing(&mut buf);
keccak256(&buf)
}
}
/// An error that can occur when validating a [BlobTransaction].

View File

@ -1,4 +1,5 @@
use crate::{Bytes, ChainId, Signature, TransactionKind, TxType};
use crate::{keccak256, Bytes, ChainId, Signature, TransactionKind, TxType, H256};
use bytes::BytesMut;
use reth_codecs::{main_codec, Compact};
use reth_rlp::{length_of_length, Encodable, Header};
use std::mem;
@ -105,6 +106,59 @@ impl TxLegacy {
pub(crate) fn tx_type(&self) -> TxType {
TxType::Legacy
}
/// Encodes EIP-155 arguments into the desired buffer. Only encodes values for legacy
/// transactions.
pub(crate) fn encode_eip155_fields(&self, out: &mut dyn bytes::BufMut) {
// if this is a legacy transaction without a chain ID, it must be pre-EIP-155
// and does not need to encode the chain ID for the signature hash encoding
if let Some(id) = self.chain_id {
// EIP-155 encodes the chain ID and two zeroes
id.encode(out);
0x00u8.encode(out);
0x00u8.encode(out);
}
}
/// Outputs the length of EIP-155 fields. Only outputs a non-zero value for EIP-155 legacy
/// transactions.
pub(crate) fn eip155_fields_len(&self) -> usize {
if let Some(id) = self.chain_id {
// EIP-155 encodes the chain ID and two zeroes, so we add 2 to the length of the chain
// ID to get the length of all 3 fields
// len(chain_id) + (0x00) + (0x00)
id.length() + 2
} else {
// this is either a pre-EIP-155 legacy transaction or a typed transaction
0
}
}
/// Encodes the legacy transaction in RLP for signing, including the EIP-155 fields if possible.
pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
Header { list: true, payload_length: self.fields_len() + self.eip155_fields_len() }
.encode(out);
self.encode_fields(out);
self.encode_eip155_fields(out);
}
/// Outputs the length of the signature RLP encoding for the transaction, including the length
/// of the EIP-155 fields if possible.
pub(crate) fn payload_len_for_signature(&self) -> usize {
let payload_length = self.fields_len() + self.eip155_fields_len();
// 'header length' + 'payload length'
length_of_length(payload_length) + payload_length
}
/// Outputs the signature hash of the transaction by first encoding without a signature, then
/// hashing.
///
/// See [Self::encode_for_signing] for more information on the encoding format.
pub(crate) fn signature_hash(&self) -> H256 {
let mut buf = BytesMut::with_capacity(self.payload_len_for_signature());
self.encode_for_signing(&mut buf);
keccak256(&buf)
}
}
#[cfg(test)]

View File

@ -10,9 +10,7 @@ pub use meta::TransactionMeta;
use once_cell::sync::Lazy;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use reth_codecs::{add_arbitrary_tests, derive_arbitrary, Compact};
use reth_rlp::{
length_of_length, Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE,
};
use reth_rlp::{Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE};
use serde::{Deserialize, Serialize};
pub use signature::Signature;
use std::mem;
@ -24,7 +22,7 @@ pub use eip1559::TxEip1559;
pub use eip2930::TxEip2930;
pub use eip4844::{BlobTransaction, BlobTransactionSidecar, TxEip4844};
pub use legacy::TxLegacy;
pub use pooled::PooledTransactionsElement;
pub use pooled::{PooledTransactionsElement, PooledTransactionsElementEcRecovered};
mod access_list;
mod eip1559;
@ -100,9 +98,12 @@ impl Transaction {
/// Heavy operation that return signature hash over rlp encoded transaction.
/// It is only for signature signing or signer recovery.
pub fn signature_hash(&self) -> H256 {
let mut buf = BytesMut::new();
self.encode(&mut buf);
keccak256(&buf)
match self {
Transaction::Legacy(tx) => tx.signature_hash(),
Transaction::Eip2930(tx) => tx.signature_hash(),
Transaction::Eip1559(tx) => tx.signature_hash(),
Transaction::Eip4844(tx) => tx.signature_hash(),
}
}
/// Get chain_id.
@ -316,54 +317,6 @@ impl Transaction {
}
}
/// Encodes EIP-155 arguments into the desired buffer. Only encodes values for legacy
/// transactions.
pub(crate) fn encode_eip155_fields(&self, out: &mut dyn bytes::BufMut) {
// if this is a legacy transaction without a chain ID, it must be pre-EIP-155
// and does not need to encode the chain ID for the signature hash encoding
if let Transaction::Legacy(TxLegacy { chain_id: Some(id), .. }) = self {
// EIP-155 encodes the chain ID and two zeroes
id.encode(out);
0x00u8.encode(out);
0x00u8.encode(out);
}
}
/// Outputs the length of EIP-155 fields. Only outputs a non-zero value for EIP-155 legacy
/// transactions.
pub(crate) fn eip155_fields_len(&self) -> usize {
if let Transaction::Legacy(TxLegacy { chain_id: Some(id), .. }) = self {
// EIP-155 encodes the chain ID and two zeroes, so we add 2 to the length of the chain
// ID to get the length of all 3 fields
// len(chain_id) + (0x00) + (0x00)
id.length() + 2
} else {
// this is either a pre-EIP-155 legacy transaction or a typed transaction
0
}
}
/// 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 {
match self {
Transaction::Legacy(legacy_tx) => legacy_tx.fields_len(),
Transaction::Eip2930(access_list_tx) => access_list_tx.fields_len(),
Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.fields_len(),
Transaction::Eip4844(blob_tx) => blob_tx.fields_len(),
}
}
/// 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) {
match self {
Transaction::Legacy(legacy_tx) => legacy_tx.encode_fields(out),
Transaction::Eip2930(access_list_tx) => access_list_tx.encode_fields(out),
Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.encode_fields(out),
Transaction::Eip4844(blob_tx) => blob_tx.encode_fields(out),
}
}
/// This encodes the transaction _without_ the signature, and is only suitable for creating a
/// hash intended for signing.
pub fn encode_without_signature(&self, out: &mut dyn bytes::BufMut) {
@ -496,32 +449,27 @@ impl Default for Transaction {
impl Encodable for Transaction {
fn encode(&self, out: &mut dyn bytes::BufMut) {
match self {
Transaction::Legacy { .. } => {
Header { list: true, payload_length: self.fields_len() + self.eip155_fields_len() }
.encode(out);
self.encode_fields(out);
self.encode_eip155_fields(out);
Transaction::Legacy(legacy_tx) => {
legacy_tx.encode_for_signing(out);
}
_ => {
out.put_u8(self.tx_type() as u8);
Header { list: true, payload_length: self.fields_len() }.encode(out);
self.encode_fields(out);
Transaction::Eip2930(access_list_tx) => {
access_list_tx.encode_for_signing(out);
}
Transaction::Eip1559(dynamic_fee_tx) => {
dynamic_fee_tx.encode_for_signing(out);
}
Transaction::Eip4844(blob_tx) => {
blob_tx.encode_for_signing(out);
}
}
}
fn length(&self) -> usize {
match self {
Transaction::Legacy { .. } => {
let payload_length = self.fields_len() + self.eip155_fields_len();
// 'header length' + 'payload length'
length_of_length(payload_length) + payload_length
}
_ => {
let payload_length = self.fields_len();
// 'transaction type byte length' + 'header length' + 'payload length'
1 + length_of_length(payload_length) + payload_length
}
Transaction::Legacy(legacy_tx) => legacy_tx.payload_len_for_signature(),
Transaction::Eip2930(access_list_tx) => access_list_tx.payload_len_for_signature(),
Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.payload_len_for_signature(),
Transaction::Eip4844(blob_tx) => blob_tx.payload_len_for_signature(),
}
}
}

View File

@ -1,10 +1,11 @@
//! Defines the types for blob transactions, legacy, and other EIP-2718 transactions included in a
//! response to `GetPooledTransactions`.
use crate::{
BlobTransaction, Bytes, Signature, Transaction, TransactionSigned, TxEip1559, TxEip2930,
TxHash, TxLegacy, EIP4844_TX_TYPE_ID,
Address, BlobTransaction, Bytes, Signature, Transaction, TransactionSigned,
TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxHash, TxLegacy, EIP4844_TX_TYPE_ID, H256,
};
use bytes::Buf;
use derive_more::{AsRef, Deref};
use reth_rlp::{Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE};
use serde::{Deserialize, Serialize};
@ -45,6 +46,46 @@ pub enum PooledTransactionsElement {
}
impl PooledTransactionsElement {
/// Heavy operation that return signature hash over rlp encoded transaction.
/// It is only for signature signing or signer recovery.
pub fn signature_hash(&self) -> H256 {
match self {
Self::Legacy { transaction, .. } => transaction.signature_hash(),
Self::Eip2930 { transaction, .. } => transaction.signature_hash(),
Self::Eip1559 { transaction, .. } => transaction.signature_hash(),
Self::BlobTransaction(blob_tx) => blob_tx.transaction.signature_hash(),
}
}
/// Returns the signature of the transaction.
pub fn signature(&self) -> &Signature {
match self {
Self::Legacy { signature, .. } => signature,
Self::Eip2930 { signature, .. } => signature,
Self::Eip1559 { signature, .. } => signature,
Self::BlobTransaction(blob_tx) => &blob_tx.signature,
}
}
/// Recover signer from signature and hash.
///
/// Returns `None` if the transaction's signature is invalid, see also [Self::recover_signer].
pub fn recover_signer(&self) -> Option<Address> {
let signature_hash = self.signature_hash();
self.signature().recover_signer(signature_hash)
}
/// Tries to recover signer and return [`PooledTransactionsElementEcRecovered`].
///
/// Returns `Err(Self)` if the transaction's signature is invalid, see also
/// [Self::recover_signer].
pub fn try_into_ecrecovered(self) -> Result<PooledTransactionsElementEcRecovered, Self> {
match self.recover_signer() {
None => Err(self),
Some(signer) => Ok(PooledTransactionsElementEcRecovered { transaction: self, signer }),
}
}
/// Decodes the "raw" format of transaction (e.g. `eth_sendRawTransaction`).
///
/// The raw transaction is either a legacy transaction or EIP-2718 typed transaction
@ -117,6 +158,12 @@ impl PooledTransactionsElement {
}
}
/// Create [`TransactionSignedEcRecovered`] by converting this transaction into
/// [`TransactionSigned`] and [`Address`] of the signer.
pub fn into_ecrecovered_transaction(self, signer: Address) -> TransactionSignedEcRecovered {
TransactionSignedEcRecovered::from_signed_transaction(self.into_transaction(), signer)
}
/// Returns the inner [TransactionSigned].
pub fn into_transaction(self) -> TransactionSigned {
match self {
@ -301,3 +348,42 @@ impl From<TransactionSigned> for PooledTransactionsElement {
}
}
}
/// A signed pooled transaction with recovered signer.
#[derive(Debug, Clone, PartialEq, Eq, AsRef, Deref)]
pub struct PooledTransactionsElementEcRecovered {
/// Signer of the transaction
signer: Address,
/// Signed transaction
#[deref]
#[as_ref]
transaction: PooledTransactionsElement,
}
// === impl PooledTransactionsElementEcRecovered ===
impl PooledTransactionsElementEcRecovered {
/// Signer of transaction recovered from signature
pub fn signer(&self) -> Address {
self.signer
}
/// Transform back to [`PooledTransactionsElement`]
pub fn into_transaction(self) -> PooledTransactionsElement {
self.transaction
}
/// Desolve Self to its component
pub fn into_components(self) -> (PooledTransactionsElement, Address) {
(self.transaction, self.signer)
}
/// Create [`TransactionSignedEcRecovered`] from [`PooledTransactionsElement`] and [`Address`]
/// of the signer.
pub fn from_signed_transaction(
transaction: PooledTransactionsElement,
signer: Address,
) -> Self {
Self { transaction, signer }
}
}

View File

@ -6,9 +6,9 @@ use crate::{
};
use futures_util::{ready, Stream};
use reth_primitives::{
Address, FromRecoveredTransaction, IntoRecoveredTransaction, PeerId, Transaction,
TransactionKind, TransactionSignedEcRecovered, TxHash, EIP1559_TX_TYPE_ID, EIP4844_TX_TYPE_ID,
H256, U256,
Address, BlobTransactionSidecar, FromRecoveredTransaction, IntoRecoveredTransaction, PeerId,
PooledTransactionsElement, PooledTransactionsElementEcRecovered, Transaction, TransactionKind,
TransactionSignedEcRecovered, TxHash, EIP1559_TX_TYPE_ID, EIP4844_TX_TYPE_ID, H256, U256,
};
use reth_rlp::Encodable;
use std::{
@ -569,7 +569,7 @@ pub trait PoolTransaction:
///
/// This type is essentially a wrapper around [TransactionSignedEcRecovered] with additional fields
/// derived from the transaction that are frequently used by the pools for ordering.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EthPooledTransaction {
/// EcRecovered transaction info
pub(crate) transaction: TransactionSignedEcRecovered,
@ -577,21 +577,41 @@ pub struct EthPooledTransaction {
/// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`.
/// For legacy transactions: `gas_price * gas_limit + tx_value`.
pub(crate) cost: U256,
// TODO optional sidecar
/// The blob side car this transaction
pub(crate) blob_sidecar: EthBlobTransactionSidecar,
}
/// Represents the blob sidecar of the [EthPooledTransaction].
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum EthBlobTransactionSidecar {
/// This transaction does not have a blob sidecar
None,
/// This transaction has a blob sidecar (EIP-4844) but it is missing
///
/// It was either extracted after being inserted into the pool or re-injected after reorg
/// without the blob sidecar
Missing,
/// The eip-4844 transaction was pulled from the network and still has its blob sidecar
Present(BlobTransactionSidecar),
}
impl EthPooledTransaction {
/// Create new instance of [Self].
pub fn new(transaction: TransactionSignedEcRecovered) -> Self {
let mut blob_sidecar = EthBlobTransactionSidecar::None;
let gas_cost = match &transaction.transaction {
Transaction::Legacy(t) => U256::from(t.gas_price) * U256::from(t.gas_limit),
Transaction::Eip2930(t) => U256::from(t.gas_price) * U256::from(t.gas_limit),
Transaction::Eip1559(t) => U256::from(t.max_fee_per_gas) * U256::from(t.gas_limit),
Transaction::Eip4844(t) => U256::from(t.max_fee_per_gas) * U256::from(t.gas_limit),
Transaction::Eip4844(t) => {
blob_sidecar = EthBlobTransactionSidecar::Missing;
U256::from(t.max_fee_per_gas) * U256::from(t.gas_limit)
}
};
let cost = gas_cost + U256::from(transaction.value());
Self { transaction, cost }
Self { transaction, cost, blob_sidecar }
}
/// Return the reference to the underlying transaction.
@ -600,6 +620,27 @@ impl EthPooledTransaction {
}
}
/// Conversion from the network transaction type to the pool transaction type.
impl From<PooledTransactionsElementEcRecovered> for EthPooledTransaction {
fn from(tx: PooledTransactionsElementEcRecovered) -> Self {
let (tx, signer) = tx.into_components();
match tx {
PooledTransactionsElement::BlobTransaction(tx) => {
// include the blob sidecar
let (tx, blob) = tx.into_parts();
let tx = TransactionSignedEcRecovered::from_signed_transaction(tx, signer);
let mut pooled = EthPooledTransaction::new(tx);
pooled.blob_sidecar = EthBlobTransactionSidecar::Present(blob);
pooled
}
tx => {
// no blob sidecar
EthPooledTransaction::new(tx.into_ecrecovered_transaction(signer))
}
}
}
}
impl PoolTransaction for EthPooledTransaction {
/// Returns hash of the transaction.
fn hash(&self) -> &TxHash {