Skip to content

Commit

Permalink
fix: era1 typed-(transaction/receipt) encode/decode (#1234)
Browse files Browse the repository at this point in the history
  • Loading branch information
KolbyML authored Apr 5, 2024
1 parent 9eb99d7 commit c9744bd
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 45 deletions.
113 changes: 109 additions & 4 deletions ethportal-api/src/types/execution/block_body.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{sync::Arc, vec};

use alloy_primitives::B256;
use alloy_rlp::{Decodable, Encodable, Error as RlpError, RlpDecodable, RlpEncodable};
use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header as RlpHeader};
use anyhow::{anyhow, bail};
use eth_trie::{EthTrie, MemoryDB, Trie};
use serde::Deserialize;
Expand Down Expand Up @@ -169,12 +169,56 @@ impl BlockBody {
}
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, RlpEncodable, RlpDecodable)]
fn rlp_encode_transaction_list(out: &mut dyn bytes::BufMut, txs: &[Transaction]) {
let mut transactions_list = Vec::<u8>::new();
for tx in txs {
tx.encode_with_envelope(&mut transactions_list, true);
}
let header = RlpHeader {
list: true,
payload_length: transactions_list.len(),
};
header.encode(out);
out.put_slice(transactions_list.as_slice());
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct BlockBodyLegacy {
pub txs: Vec<Transaction>,
pub uncles: Vec<Header>,
}

impl Encodable for BlockBodyLegacy {
fn encode(&self, out: &mut dyn bytes::BufMut) {
let mut list = Vec::<u8>::new();
rlp_encode_transaction_list(&mut list, &self.txs);
self.uncles.encode(&mut list);
let header = RlpHeader {
list: true,
payload_length: list.len(),
};
header.encode(out);
out.put_slice(list.as_slice());
}
}

impl Decodable for BlockBodyLegacy {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let header = RlpHeader::decode(buf)?;
if !header.list {
return Err(RlpError::UnexpectedString);
}
let mut bytes = RlpHeader::decode_bytes(buf, true)?;
let mut txs: Vec<Transaction> = vec![];
let payload_view = &mut bytes;
while !payload_view.is_empty() {
txs.push(Transaction::decode_enveloped_transactions(payload_view)?);
}
let uncles: Vec<Header> = Decodable::decode(buf)?;
Ok(Self { txs, uncles })
}
}

impl ssz::Encode for BlockBodyLegacy {
// note: MAX_LENGTH attributes (defined in portal history spec) are not currently enforced
fn is_ssz_fixed_len() -> bool {
Expand Down Expand Up @@ -227,11 +271,41 @@ impl ssz::Decode for BlockBodyLegacy {
}
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, RlpEncodable, RlpDecodable)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct BlockBodyMerge {
pub txs: Vec<Transaction>,
}

impl Encodable for BlockBodyMerge {
fn encode(&self, out: &mut dyn bytes::BufMut) {
let mut list = Vec::<u8>::new();
rlp_encode_transaction_list(&mut list, &self.txs);
let header = RlpHeader {
list: true,
payload_length: list.len(),
};
header.encode(out);
out.put_slice(list.as_slice());
}
}

impl Decodable for BlockBodyMerge {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let header = RlpHeader::decode(buf)?;
if !header.list {
return Err(RlpError::UnexpectedString);
}
let mut bytes = RlpHeader::decode_bytes(buf, true)?;
let mut txs: Vec<Transaction> = vec![];
let payload_view = &mut bytes;
while !payload_view.is_empty() {
txs.push(Transaction::decode_enveloped_transactions(payload_view)?);
}

Ok(Self { txs })
}
}

impl ssz::Encode for BlockBodyMerge {
// note: MAX_LENGTH attributes (defined in portal history spec) are not currently enforced
fn is_ssz_fixed_len() -> bool {
Expand Down Expand Up @@ -287,14 +361,45 @@ impl ssz::Decode for BlockBodyMerge {
}
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, RlpEncodable, RlpDecodable)]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct BlockBodyShanghai {
pub txs: Vec<Transaction>,
// post-shanghai block bodies are expected to have empty uncles, but we skip that here
// and simply encode an empty list during the encoding/decoding process
pub withdrawals: Vec<Withdrawal>,
}

impl Encodable for BlockBodyShanghai {
fn encode(&self, out: &mut dyn bytes::BufMut) {
let mut list = Vec::<u8>::new();
rlp_encode_transaction_list(&mut list, &self.txs);
self.withdrawals.encode(&mut list);
let header = RlpHeader {
list: true,
payload_length: list.len(),
};
header.encode(out);
out.put_slice(list.as_slice());
}
}

impl Decodable for BlockBodyShanghai {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let header = RlpHeader::decode(buf)?;
if !header.list {
return Err(RlpError::UnexpectedString);
}
let mut bytes = RlpHeader::decode_bytes(buf, true)?;
let mut txs: Vec<Transaction> = vec![];
let payload_view = &mut bytes;
while !payload_view.is_empty() {
txs.push(Transaction::decode_enveloped_transactions(payload_view)?);
}
let withdrawals: Vec<Withdrawal> = Decodable::decode(buf)?;
Ok(Self { txs, withdrawals })
}
}

impl ssz::Encode for BlockBodyShanghai {
// note: MAX_LENGTH attributes (defined in portal history spec) are not currently enforced
fn is_ssz_fixed_len() -> bool {
Expand Down
83 changes: 63 additions & 20 deletions ethportal-api/src/types/execution/receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use alloy_rlp::{
RlpEncodable,
};
use anyhow::anyhow;
use bytes::{BufMut, Bytes};
use bytes::{Buf, BufMut, Bytes};
use eth_trie::{EthTrie, MemoryDB, Trie};
use serde::{Deserialize, Deserializer};
use serde_json::Value;
Expand Down Expand Up @@ -44,6 +44,33 @@ impl Receipts {
}
}

impl Encodable for Receipts {
fn encode(&self, out: &mut dyn BufMut) {
let mut list = Vec::<u8>::new();
for receipt in &self.receipt_list {
receipt.encode_with_envelope(&mut list, true);
}
let header = RlpHeader {
list: true,
payload_length: list.len(),
};
header.encode(out);
out.put_slice(list.as_slice());
}
}

impl Decodable for Receipts {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let mut bytes = RlpHeader::decode_bytes(buf, true)?;
let mut receipt_list: Vec<Receipt> = vec![];
let payload_view = &mut bytes;
while !payload_view.is_empty() {
receipt_list.push(Receipt::decode_enveloped_transactions(payload_view)?);
}
Ok(Self { receipt_list })
}
}

impl ssz::Encode for Receipts {
// note: MAX_LENGTH attributes (defined in portal history spec) are not currently enforced
fn is_ssz_fixed_len() -> bool {
Expand Down Expand Up @@ -304,21 +331,6 @@ impl Encodable for LegacyReceipt {
}
}

impl Encodable for Receipts {
fn encode(&self, out: &mut dyn BufMut) {
let mut list = Vec::<u8>::new();
for receipt in self.receipt_list.clone() {
receipt.encode(&mut list);
}
let header = RlpHeader {
list: true,
payload_length: list.len(),
};
header.encode(out);
out.put_slice(list.as_slice());
}
}

#[derive(Eq, Hash, Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
/// The typed transaction ID
Expand Down Expand Up @@ -398,11 +410,8 @@ impl Receipt {
Self::Blob(receipt) => receipt,
}
}
}

impl Encodable for Receipt {
fn encode(&self, out: &mut dyn bytes::BufMut) {
let with_header = false;
fn encode_with_envelope(&self, out: &mut dyn bytes::BufMut, with_header: bool) {
match self {
Self::Legacy(receipt) => {
receipt.encode(out);
Expand Down Expand Up @@ -445,6 +454,40 @@ impl Encodable for Receipt {
}
}
}

pub fn decode_enveloped_transactions(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
// at least one byte needs to be present
if buf.is_empty() {
return Err(RlpError::InputTooShort);
}
let original_encoding = *buf;
let header = RlpHeader::decode(buf)?;
let value = &mut &buf[..header.payload_length];
buf.advance(header.payload_length);
if !header.list {
let id = TransactionId::try_from(value[0])
.map_err(|_| RlpError::Custom("Unknown transaction id"))?;
value.advance(1);
match id {
TransactionId::Legacy => {
unreachable!("Legacy receipts should be wrapped in a list")
}
TransactionId::AccessList => Ok(Self::AccessList(Decodable::decode(value)?)),
TransactionId::EIP1559 => Ok(Self::EIP1559(Decodable::decode(value)?)),
TransactionId::Blob => Ok(Self::Blob(Decodable::decode(value)?)),
}
} else {
Ok(Self::Legacy(Decodable::decode(
&mut &original_encoding[..(header.payload_length + header.length())],
)?))
}
}
}

impl Encodable for Receipt {
fn encode(&self, out: &mut dyn bytes::BufMut) {
self.encode_with_envelope(out, false)
}
}

impl Decodable for Receipt {
Expand Down
42 changes: 37 additions & 5 deletions ethportal-api/src/types/execution/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,8 @@ impl Transaction {
pub fn hash(&self) -> B256 {
keccak256(alloy_rlp::encode(self))
}
}

impl Encodable for Transaction {
fn encode(&self, out: &mut dyn bytes::BufMut) {
// we don't wrap versioned transactions with a string header
let with_header = false;
pub fn encode_with_envelope(&self, out: &mut dyn bytes::BufMut, with_header: bool) {
match self {
Self::Legacy(tx) => tx.encode(out),
Self::AccessList(tx) => {
Expand Down Expand Up @@ -69,6 +65,42 @@ impl Encodable for Transaction {
}
}
}

pub fn decode_enveloped_transactions(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
// at least one byte needs to be present
if buf.is_empty() {
return Err(RlpError::InputTooShort);
}
let original_encoding = *buf;
let header = RlpHeader::decode(buf)?;
let value = &mut &buf[..header.payload_length];
buf.advance(header.payload_length);
if !header.list {
let id = TransactionId::try_from(value[0])
.map_err(|_| RlpError::Custom("Unknown transaction id"))?;
value.advance(1);
match id {
TransactionId::EIP1559 => Ok(Self::EIP1559(EIP1559Transaction::decode(value)?)),
TransactionId::AccessList => {
Ok(Self::AccessList(AccessListTransaction::decode(value)?))
}
TransactionId::Legacy => {
unreachable!("Legacy transactions should be wrapped in a list")
}
TransactionId::Blob => Ok(Self::Blob(BlobTransaction::decode(value)?)),
}
} else {
Ok(Self::Legacy(LegacyTransaction::decode(
&mut &original_encoding[..(header.payload_length + header.length())],
)?))
}
}
}

impl Encodable for Transaction {
fn encode(&self, out: &mut dyn bytes::BufMut) {
self.encode_with_envelope(out, false)
}
}

impl Decodable for Transaction {
Expand Down
Loading

0 comments on commit c9744bd

Please sign in to comment.