chore: restructure transaction impl order (#1166)

This commit is contained in:
Matthias Seitz
2023-02-04 01:16:17 +01:00
committed by GitHub
parent e7cc50038f
commit 689ba28caf

View File

@@ -172,12 +172,6 @@ pub enum Transaction {
Eip1559(TxEip1559),
}
impl Default for Transaction {
fn default() -> Self {
Self::Legacy(TxLegacy::default())
}
}
impl Transaction {
/// Heavy operation that return signature hash over rlp encoded transaction.
/// It is only for signature signing or signer recovery.
@@ -428,24 +422,15 @@ impl Transaction {
}
}
impl Default for Transaction {
fn default() -> Self {
Self::Legacy(TxLegacy::default())
}
}
/// This encodes the transaction _without_ the signature, and is only suitable for creating a hash
/// intended for signing.
impl Encodable for Transaction {
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
}
}
}
fn encode(&self, out: &mut dyn bytes::BufMut) {
match self {
Transaction::Legacy { .. } => {
@@ -461,6 +446,21 @@ impl Encodable for Transaction {
}
}
}
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
}
}
}
}
/// Whether or not the transaction is a contract creation.
@@ -475,18 +475,18 @@ pub enum TransactionKind {
}
impl Encodable for TransactionKind {
fn length(&self) -> usize {
match self {
TransactionKind::Call(to) => to.length(),
TransactionKind::Create => 1, // EMPTY_STRING_CODE is a single byte
}
}
fn encode(&self, out: &mut dyn reth_rlp::BufMut) {
match self {
TransactionKind::Call(to) => to.encode(out),
TransactionKind::Create => out.put_u8(EMPTY_STRING_CODE),
}
}
fn length(&self) -> usize {
match self {
TransactionKind::Call(to) => to.length(),
TransactionKind::Create => 1, // EMPTY_STRING_CODE is a single byte
}
}
}
impl Decodable for TransactionKind {
@@ -520,143 +520,7 @@ pub struct TransactionSigned {
pub transaction: Transaction,
}
#[cfg(any(test, feature = "arbitrary"))]
use proptest::{
prelude::{any, Strategy},
strategy::BoxedStrategy,
};
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for TransactionSigned {
type Parameters = ();
type Strategy = BoxedStrategy<TransactionSigned>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
any::<(Transaction, Signature)>()
.prop_map(move |(mut transaction, sig)| {
if let Some(chain_id) = transaction.chain_id().cloned() {
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
let mut tx =
TransactionSigned { hash: Default::default(), signature: sig, transaction };
tx.hash = tx.recalculate_hash();
tx
})
.boxed()
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut transaction = Transaction::arbitrary(u)?;
if let Some(chain_id) = transaction.chain_id().cloned() {
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
let mut tx = TransactionSigned {
hash: Default::default(),
signature: Signature::arbitrary(u)?,
transaction,
};
tx.hash = tx.recalculate_hash();
Ok(tx)
}
}
impl From<TransactionSignedEcRecovered> for TransactionSigned {
fn from(recovered: TransactionSignedEcRecovered) -> Self {
recovered.signed_transaction
}
}
impl Encodable for TransactionSigned {
fn encode(&self, out: &mut dyn bytes::BufMut) {
self.encode_inner(out, true);
}
fn length(&self) -> usize {
self.payload_len_inner()
}
}
/// This `Decodable` implementation only supports decoding the transaction format sent over p2p.
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;
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)
} 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_length = first_header.payload_length + first_header.length();
let hash = keccak256(&original_encoding[..tx_length]);
let signed = TransactionSigned { transaction, hash, signature };
Ok(signed)
}
}
}
// === impl TransactionSigned ===
impl TransactionSigned {
/// Transaction signature.
@@ -758,6 +622,140 @@ impl TransactionSigned {
}
}
impl From<TransactionSignedEcRecovered> for TransactionSigned {
fn from(recovered: TransactionSignedEcRecovered) -> Self {
recovered.signed_transaction
}
}
impl Encodable for TransactionSigned {
fn encode(&self, out: &mut dyn bytes::BufMut) {
self.encode_inner(out, true);
}
fn length(&self) -> usize {
self.payload_len_inner()
}
}
/// This `Decodable` implementation only supports decoding the transaction format sent over p2p.
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;
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)
} 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_length = first_header.payload_length + first_header.length();
let hash = keccak256(&original_encoding[..tx_length]);
let signed = TransactionSigned { transaction, hash, signature };
Ok(signed)
}
}
}
#[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};
any::<(Transaction, Signature)>()
.prop_map(move |(mut transaction, sig)| {
if let Some(chain_id) = transaction.chain_id().cloned() {
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
let mut tx =
TransactionSigned { hash: Default::default(), signature: sig, transaction };
tx.hash = tx.recalculate_hash();
tx
})
.boxed()
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut transaction = Transaction::arbitrary(u)?;
if let Some(chain_id) = transaction.chain_id().cloned() {
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
let mut tx = TransactionSigned {
hash: Default::default(),
signature: Signature::arbitrary(u)?,
transaction,
};
tx.hash = tx.recalculate_hash();
Ok(tx)
}
}
/// Signed transaction with recovered signer.
#[main_codec]
#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Default)]
@@ -770,16 +768,6 @@ pub struct TransactionSignedEcRecovered {
signed_transaction: TransactionSigned,
}
impl Encodable for TransactionSignedEcRecovered {
fn length(&self) -> usize {
self.signed_transaction.length()
}
fn encode(&self, out: &mut dyn bytes::BufMut) {
self.signed_transaction.encode(out)
}
}
impl TransactionSignedEcRecovered {
/// Signer of transaction recovered from signature
pub fn signer(&self) -> Address {
@@ -815,6 +803,16 @@ impl FromRecoveredTransaction for TransactionSignedEcRecovered {
}
}
impl Encodable for TransactionSignedEcRecovered {
fn encode(&self, out: &mut dyn bytes::BufMut) {
self.signed_transaction.encode(out)
}
fn length(&self) -> usize {
self.signed_transaction.length()
}
}
/// The inverse of [`FromRecoveredTransaction`] that ensure the transaction can be sent over the
/// network
pub trait IntoRecoveredTransaction {