mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-06 04:55:00 -05:00
Add TransactionSigned::decode_enveloped (#1186)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de> Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
4b90d42401
commit
e689ba03c4
@@ -3,7 +3,9 @@ pub use access_list::{AccessList, AccessListItem};
|
||||
use bytes::{Buf, BytesMut};
|
||||
use derive_more::{AsRef, Deref};
|
||||
use reth_codecs::{add_arbitrary_tests, main_codec, Compact};
|
||||
use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header, EMPTY_STRING_CODE};
|
||||
use reth_rlp::{
|
||||
length_of_length, Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE,
|
||||
};
|
||||
pub use signature::Signature;
|
||||
pub use tx_type::TxType;
|
||||
|
||||
@@ -587,7 +589,7 @@ impl TransactionSigned {
|
||||
}
|
||||
|
||||
/// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating
|
||||
/// hash that for eip2728 does not require rlp header
|
||||
/// hash that for eip2718 does not require rlp header
|
||||
pub(crate) fn encode_inner(&self, out: &mut dyn bytes::BufMut, with_header: bool) {
|
||||
match self.transaction {
|
||||
Transaction::Legacy(TxLegacy { chain_id, .. }) => {
|
||||
@@ -651,6 +653,110 @@ impl TransactionSigned {
|
||||
initial_tx.hash = initial_tx.recalculate_hash();
|
||||
initial_tx
|
||||
}
|
||||
|
||||
/// Decodes legacy transaction from the data buffer.
|
||||
///
|
||||
/// This expects `rlp(legacy_tx)`
|
||||
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;
|
||||
|
||||
let header = Header::decode(data)?;
|
||||
|
||||
let mut transaction = Transaction::Legacy(TxLegacy {
|
||||
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)?),
|
||||
chain_id: None,
|
||||
});
|
||||
let (signature, extracted_id) = Signature::decode_with_eip155_chain_id(data)?;
|
||||
if let Some(id) = extracted_id {
|
||||
transaction.set_chain_id(id);
|
||||
}
|
||||
|
||||
let tx_length = header.payload_length + header.length();
|
||||
let hash = keccak256(&original_encoding[..tx_length]);
|
||||
let signed = TransactionSigned { transaction, hash, signature };
|
||||
Ok(signed)
|
||||
}
|
||||
|
||||
/// Decodes en enveloped EIP-2718 typed transaction.
|
||||
///
|
||||
/// CAUTION: this expects that `data` is `[id, rlp(tx)]`
|
||||
fn decode_enveloped_typed_transaction(
|
||||
data: &mut &[u8],
|
||||
) -> Result<TransactionSigned, DecodeError> {
|
||||
// keep this around so we can use it to calculate the hash
|
||||
let original_encoding = *data;
|
||||
|
||||
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 {
|
||||
return Err(DecodeError::Custom("typed tx fields must be encoded as a list"))
|
||||
}
|
||||
|
||||
// length of tx encoding = tx type byte (size = 1) + length of header + payload length
|
||||
let tx_length = 1 + header.length() + header.payload_length;
|
||||
|
||||
// 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)?,
|
||||
}),
|
||||
_ => return Err(DecodeError::Custom("unsupported typed transaction type")),
|
||||
};
|
||||
|
||||
let signature = Signature::decode(data)?;
|
||||
|
||||
let hash = keccak256(&original_encoding[..tx_length]);
|
||||
let signed = TransactionSigned { transaction, hash, signature };
|
||||
Ok(signed)
|
||||
}
|
||||
|
||||
/// 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)`
|
||||
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
|
||||
if data[0] >= EMPTY_LIST_CODE {
|
||||
// decode as legacy transaction
|
||||
TransactionSigned::decode_rlp_legacy_transaction(&mut data)
|
||||
} else {
|
||||
TransactionSigned::decode_enveloped_typed_transaction(&mut data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionSignedEcRecovered> for TransactionSigned {
|
||||
@@ -669,77 +775,26 @@ impl Encodable for TransactionSigned {
|
||||
}
|
||||
}
|
||||
|
||||
/// This `Decodable` implementation only supports decoding the transaction format sent over p2p.
|
||||
/// This `Decodable` implementation only supports decoding rlp encoded transactions as it's used by
|
||||
/// p2p.
|
||||
///
|
||||
/// CAUTION: this expects that the given buf contains rlp
|
||||
impl Decodable for TransactionSigned {
|
||||
fn decode(buf: &mut &[u8]) -> Result<Self, DecodeError> {
|
||||
// keep this around so we can use it to calculate the hash
|
||||
let original_encoding = *buf;
|
||||
// decode header
|
||||
let mut original_encoding = *buf;
|
||||
let header = Header::decode(buf)?;
|
||||
|
||||
let first_header = Header::decode(buf)?;
|
||||
// if the transaction is encoded as a string then it is a typed transaction
|
||||
if !first_header.list {
|
||||
// Bytes that are going to be used to create a hash of transaction.
|
||||
// For eip2728 types transaction header is not used inside hash
|
||||
let original_encoding = *buf;
|
||||
|
||||
let tx_type = *buf.first().ok_or(DecodeError::InputTooShort)?;
|
||||
buf.advance(1);
|
||||
// decode the list header for the rest of the transaction
|
||||
let header = Header::decode(buf)?;
|
||||
if !header.list {
|
||||
return Err(DecodeError::Custom("typed tx fields must be encoded as a list"))
|
||||
}
|
||||
|
||||
// decode common fields
|
||||
let transaction = match tx_type {
|
||||
1 => Transaction::Eip2930(TxEip2930 {
|
||||
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)?,
|
||||
}),
|
||||
2 => Transaction::Eip1559(TxEip1559 {
|
||||
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)?,
|
||||
}),
|
||||
_ => return Err(DecodeError::Custom("unsupported typed transaction type")),
|
||||
};
|
||||
|
||||
let signature = Signature::decode(buf)?;
|
||||
|
||||
let hash = keccak256(&original_encoding[..first_header.payload_length]);
|
||||
let signed = TransactionSigned { transaction, hash, signature };
|
||||
Ok(signed)
|
||||
if !header.list {
|
||||
TransactionSigned::decode_enveloped_typed_transaction(buf)
|
||||
} else {
|
||||
let mut transaction = Transaction::Legacy(TxLegacy {
|
||||
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)?),
|
||||
chain_id: None,
|
||||
});
|
||||
let (signature, extracted_id) = Signature::decode_with_eip155_chain_id(buf)?;
|
||||
if let Some(id) = extracted_id {
|
||||
transaction.set_chain_id(id);
|
||||
}
|
||||
let tx = TransactionSigned::decode_rlp_legacy_transaction(&mut original_encoding)?;
|
||||
|
||||
let tx_length = first_header.payload_length + first_header.length();
|
||||
let hash = keccak256(&original_encoding[..tx_length]);
|
||||
let signed = TransactionSigned { transaction, hash, signature };
|
||||
Ok(signed)
|
||||
// advance the buffer based on how far `decode_rlp_legacy_transaction` advanced the
|
||||
// buffer
|
||||
*buf = original_encoding;
|
||||
Ok(tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -747,8 +802,6 @@ impl Decodable for TransactionSigned {
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl proptest::arbitrary::Arbitrary for TransactionSigned {
|
||||
type Parameters = ();
|
||||
type Strategy = proptest::strategy::BoxedStrategy<TransactionSigned>;
|
||||
|
||||
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
|
||||
use proptest::prelude::{any, Strategy};
|
||||
|
||||
@@ -765,6 +818,8 @@ impl proptest::arbitrary::Arbitrary for TransactionSigned {
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
type Strategy = proptest::strategy::BoxedStrategy<TransactionSigned>;
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
@@ -1179,4 +1234,14 @@ mod tests {
|
||||
let encoded = decoded.envelope_encoded();
|
||||
assert_eq!(encoded, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_envelop_decode() {
|
||||
// random tx: <https://etherscan.io/getRawTx?tx=0x9448608d36e721ef403c53b00546068a6474d6cbab6816c3926de449898e7bce>
|
||||
let input = &hex::decode("02f871018302a90f808504890aef60826b6c94ddf4c5025d1a5742cf12f74eec246d4432c295e487e09c3bbcc12b2b80c080a0f21a4eacd0bf8fea9c5105c543be5a1d8c796516875710fafafdf16d16d8ee23a001280915021bb446d1973501a67f93d2b38894a514b976e7b46dc2fe54598d76").unwrap()[..];
|
||||
let decoded = TransactionSigned::decode_enveloped(input.into()).unwrap();
|
||||
|
||||
let encoded = decoded.envelope_encoded();
|
||||
assert_eq!(encoded, input);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user