Skip to content

Commit

Permalink
feat(l1): add P2P Eth get receipts msg (#1432)
Browse files Browse the repository at this point in the history
**Motivation**

- Support the P2P eth functionality of exchanging transaction receipts.


Closes #386 
Closes #387

---------

Co-authored-by: Martin Paulucci <[email protected]>
Co-authored-by: fmoletta <[email protected]>
Co-authored-by: fmoletta <[email protected]>
  • Loading branch information
4 people authored Dec 19, 2024
1 parent 342e266 commit 3ad1b8a
Show file tree
Hide file tree
Showing 16 changed files with 311 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci_l1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ jobs:
test_pattern: /AccountRange|StorageRanges|ByteCodes|TrieNodes
- name: "Devp2p eth tests"
simulation: devp2p
test_pattern: eth/Status|GetBlockHeaders|SimultaneousRequests|SameRequestID|ZeroRequestID|GetBlockBodies|MaliciousHandshake|MaliciousStatus|Transaction|InvalidTxs|NewPooledTxs
test_pattern: eth/Status|GetBlockHeaders|SimultaneousRequests|SameRequestID|ZeroRequestID|GetBlockBodies|MaliciousHandshake|MaliciousStatus|Transaction|InvalidTxs|NewPooledTxs|GetBlockReceipts
- name: "Engine Auth and EC tests"
simulation: ethereum/engine
test_pattern: engine-(auth|exchange-capabilities)/
Expand Down
6 changes: 3 additions & 3 deletions crates/common/rlp/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,10 +492,10 @@ pub fn is_encoded_as_bytes(rlp: &[u8]) -> Result<bool, RLPDecodeError> {
}

/// Receives an RLP bytes item (prefix between 0xb8 and 0xbf) and returns its payload
pub fn get_rlp_bytes_item_payload(rlp: &[u8]) -> &[u8] {
let prefix = rlp.first().unwrap();
pub fn get_rlp_bytes_item_payload(rlp: &[u8]) -> Result<&[u8], RLPDecodeError> {
let prefix = rlp.first().ok_or(RLPDecodeError::InvalidLength)?;
let offset: usize = (prefix - 0xb8 + 1).into();
&rlp[offset + 1..]
rlp.get(offset + 1..).ok_or(RLPDecodeError::InvalidLength)
}

/// Decodes the payload of an RLP item from a slice of bytes.
Expand Down
2 changes: 1 addition & 1 deletion crates/common/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ pub fn compute_receipts_root(receipts: &[Receipt]) -> H256 {
let iter = receipts
.iter()
.enumerate()
.map(|(idx, receipt)| (idx.encode_to_vec(), receipt.encode_to_vec()));
.map(|(idx, receipt)| (idx.encode_to_vec(), receipt.encode_inner()));
Trie::compute_hash_from_unsorted_iter(iter)
}

Expand Down
221 changes: 188 additions & 33 deletions crates/common/types/receipt.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use bytes::Bytes;
use ethereum_types::{Address, Bloom, BloomInput, H256};
use ethrex_rlp::{
decode::RLPDecode,
decode::{get_rlp_bytes_item_payload, is_encoded_as_bytes, RLPDecode},
encode::RLPEncode,
error::RLPDecodeError,
structs::{Decoder, Encoder},
};
use serde::{Deserialize, Serialize};

use super::TxType;
use crate::types::TxType;
pub type Index = u64;

/// Result of a transaction
Expand All @@ -31,6 +31,79 @@ impl Receipt {
logs,
}
}
// By reading the typed transactions EIP, and some geth code:
// - https://eips.ethereum.org/EIPS/eip-2718
// - https://github.com/ethereum/go-ethereum/blob/330190e476e2a2de4aac712551629a4134f802d5/core/types/receipt.go#L143
// We've noticed the are some subtleties around encoding receipts and transactions.
// First, `encode_inner` will encode a receipt according
// to the RLP of its fields, if typed, the RLP of the fields
// is padded with the byte representing this type.
// For P2P messages, receipts are re-encoded as bytes
// (see the `encode` implementation for receipt).
// For debug and computing receipt roots, the expected
// RLP encodings are the ones returned by `encode_inner`.
// On some documentations, this is also called the `consensus-encoding`
// for a receipt.

/// Encodes Receipts in the following formats:
/// A) Legacy receipts: rlp(receipt)
/// B) Non legacy receipts: tx_type | rlp(receipt).
pub fn encode_inner(&self) -> Vec<u8> {
let mut encode_buff = match self.tx_type {
TxType::Legacy => {
vec![]
}
_ => {
vec![self.tx_type as u8]
}
};
Encoder::new(&mut encode_buff)
.encode_field(&self.succeeded)
.encode_field(&self.cumulative_gas_used)
.encode_field(&self.bloom)
.encode_field(&self.logs)
.finish();
encode_buff
}

/// Decodes Receipts in the following formats:
/// A) Legacy receipts: rlp(receipt)
/// B) Non legacy receipts: tx_type | rlp(receipt).
pub fn decode_inner(rlp: &[u8]) -> Result<Receipt, RLPDecodeError> {
// Obtain TxType
let (tx_type, rlp) = match rlp.first() {
Some(tx_type) if *tx_type < 0x7f => {
let tx_type = match tx_type {
0x0 => TxType::Legacy,
0x1 => TxType::EIP2930,
0x2 => TxType::EIP1559,
0x3 => TxType::EIP4844,
0x7e => TxType::Privileged,
ty => {
return Err(RLPDecodeError::Custom(format!(
"Invalid transaction type: {ty}"
)))
}
};
(tx_type, &rlp[1..])
}
_ => (TxType::Legacy, rlp),
};
let decoder = Decoder::new(rlp)?;
let (succeeded, decoder) = decoder.decode_field("succeeded")?;
let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
let (bloom, decoder) = decoder.decode_field("bloom")?;
let (logs, decoder) = decoder.decode_field("logs")?;
decoder.finish()?;

Ok(Receipt {
tx_type,
succeeded,
cumulative_gas_used,
bloom,
logs,
})
}
}

fn bloom_from_logs(logs: &[Log]) -> Bloom {
Expand All @@ -45,55 +118,64 @@ fn bloom_from_logs(logs: &[Log]) -> Bloom {
}

impl RLPEncode for Receipt {
/// Receipts can be encoded in the following formats:
/// A) Legacy receipts: rlp(receipt)
/// B) Non legacy receipts: rlp(Bytes(tx_type | rlp(receipt))).
fn encode(&self, buf: &mut dyn bytes::BufMut) {
// tx_type || RLP(receipt) if tx_type != 0
// RLP(receipt) else
match self.tx_type {
TxType::Legacy => {}
_ => buf.put_u8(self.tx_type as u8),
}
Encoder::new(buf)
.encode_field(&self.succeeded)
.encode_field(&self.cumulative_gas_used)
.encode_field(&self.bloom)
.encode_field(&self.logs)
.finish();
TxType::Legacy => {
let legacy_encoded = self.encode_inner();
buf.put_slice(&legacy_encoded);
}
_ => {
let typed_recepipt_encoded = self.encode_inner();
let bytes = Bytes::from(typed_recepipt_encoded);
bytes.encode(buf);
}
};
}
}

impl RLPDecode for Receipt {
/// Receipts can be encoded in the following formats:
/// A) Legacy receipts: rlp(receipt)
/// B) Non legacy receipts: rlp(Bytes(tx_type | rlp(receipt))).
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
// Decode tx type
let (tx_type, rlp) = match rlp.first() {
Some(tx_type) if *tx_type < 0x7f => match tx_type {
0x0 => (TxType::Legacy, &rlp[1..]),
0x1 => (TxType::EIP2930, &rlp[1..]),
0x2 => (TxType::EIP1559, &rlp[1..]),
0x3 => (TxType::EIP4844, &rlp[1..]),
0x7e => (TxType::Privileged, &rlp[1..]),
let (tx_type, rlp) = if is_encoded_as_bytes(rlp)? {
let payload = get_rlp_bytes_item_payload(rlp)?;
let tx_type = match payload.first().ok_or(RLPDecodeError::InvalidLength)? {
0x0 => TxType::Legacy,
0x1 => TxType::EIP2930,
0x2 => TxType::EIP1559,
0x3 => TxType::EIP4844,
0x7e => TxType::Privileged,
ty => {
return Err(RLPDecodeError::Custom(format!(
"Invalid transaction type: {ty}"
)))
}
},
// Legacy Tx
_ => (TxType::Legacy, rlp),
};
(tx_type, &payload[1..])
} else {
(TxType::Legacy, rlp)
};
// Decode the remaining fields

let decoder = Decoder::new(rlp)?;
let (succeeded, decoder) = decoder.decode_field("succeeded")?;
let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
let (bloom, decoder) = decoder.decode_field("bloom")?;
let (logs, decoder) = decoder.decode_field("logs")?;
let receipt = Receipt {
tx_type,
succeeded,
cumulative_gas_used,
bloom,
logs,
};
Ok((receipt, decoder.finish()?))

Ok((
Receipt {
tx_type,
succeeded,
cumulative_gas_used,
bloom,
logs,
},
decoder.finish()?,
))
}
}

Expand Down Expand Up @@ -129,3 +211,76 @@ impl RLPDecode for Log {
Ok((log, decoder.finish()?))
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_encode_decode_receipt_legacy() {
let receipt = Receipt {
tx_type: TxType::Legacy,
succeeded: true,
cumulative_gas_used: 1200,
bloom: Bloom::random(),
logs: vec![Log {
address: Address::random(),
topics: vec![],
data: Bytes::from_static(b"foo"),
}],
};
let encoded_receipt = receipt.encode_to_vec();
assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
}

#[test]
fn test_encode_decode_receipt_non_legacy() {
let receipt = Receipt {
tx_type: TxType::EIP4844,
succeeded: true,
cumulative_gas_used: 1500,
bloom: Bloom::random(),
logs: vec![Log {
address: Address::random(),
topics: vec![],
data: Bytes::from_static(b"bar"),
}],
};
let encoded_receipt = receipt.encode_to_vec();
assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
}

#[test]
fn test_encode_decode_inner_receipt_legacy() {
let receipt = Receipt {
tx_type: TxType::Legacy,
succeeded: true,
cumulative_gas_used: 1200,
bloom: Bloom::random(),
logs: vec![Log {
address: Address::random(),
topics: vec![],
data: Bytes::from_static(b"foo"),
}],
};
let encoded_receipt = receipt.encode_inner();
assert_eq!(receipt, Receipt::decode_inner(&encoded_receipt).unwrap())
}

#[test]
fn test_encode_decode_receipt_inner_non_legacy() {
let receipt = Receipt {
tx_type: TxType::EIP4844,
succeeded: true,
cumulative_gas_used: 1500,
bloom: Bloom::random(),
logs: vec![Log {
address: Address::random(),
topics: vec![],
data: Bytes::from_static(b"bar"),
}],
};
let encoded_receipt = receipt.encode_inner();
assert_eq!(receipt, Receipt::decode_inner(&encoded_receipt).unwrap())
}
}
6 changes: 3 additions & 3 deletions crates/common/types/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,9 @@ impl RLPDecode for Transaction {
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
if is_encoded_as_bytes(rlp)? {
// Adjust the encoding to get the payload
let payload = get_rlp_bytes_item_payload(rlp);
let tx_type = payload.first().unwrap();
let tx_encoding = &payload[1..];
let payload = get_rlp_bytes_item_payload(rlp)?;
let tx_type = payload.first().ok_or(RLPDecodeError::InvalidLength)?;
let tx_encoding = &payload.get(1..).ok_or(RLPDecodeError::InvalidLength)?;
// Look at the first byte to check if it corresponds to a TransactionType
match *tx_type {
// Legacy
Expand Down
13 changes: 13 additions & 0 deletions crates/networking/p2p/rlpx/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
eth::{
backend,
blocks::{BlockBodies, BlockHeaders},
receipts::Receipts,
transactions::Transactions,
},
handshake::encode_ack_message,
Expand All @@ -22,6 +23,7 @@ use crate::{

use super::{
error::RLPxError,
eth::receipts::GetReceipts,
eth::transactions::GetPooledTransactions,
frame,
handshake::{decode_ack_message, decode_auth_message, encode_auth_message},
Expand Down Expand Up @@ -379,6 +381,17 @@ impl<S: AsyncWrite + AsyncRead + std::marker::Unpin> RLPxConnection<S> {
};
self.send(Message::BlockBodies(response)).await?;
}
Message::GetReceipts(GetReceipts { id, block_hashes }) if peer_supports_eth => {
let receipts: Result<_, _> = block_hashes
.iter()
.map(|hash| self.storage.get_receipts_for_block(hash))
.collect();
let response = Receipts {
id,
receipts: receipts?,
};
self.send(Message::Receipts(response)).await?;
}
Message::NewPooledTransactionHashes(new_pooled_transaction_hashes)
if peer_supports_eth =>
{
Expand Down
8 changes: 4 additions & 4 deletions crates/networking/p2p/rlpx/eth/receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use ethrex_rlp::{
pub(crate) struct GetReceipts {
// id is a u64 chosen by the requesting peer, the responding peer must mirror the value for the response
// https://github.com/ethereum/devp2p/blob/master/caps/eth.md#protocol-messages
id: u64,
block_hashes: Vec<BlockHash>,
pub id: u64,
pub block_hashes: Vec<BlockHash>,
}

impl GetReceipts {
Expand Down Expand Up @@ -52,8 +52,8 @@ impl RLPxMessage for GetReceipts {
pub(crate) struct Receipts {
// id is a u64 chosen by the requesting peer, the responding peer must mirror the value for the response
// https://github.com/ethereum/devp2p/blob/master/caps/eth.md#protocol-messages
id: u64,
receipts: Vec<Vec<Receipt>>,
pub id: u64,
pub receipts: Vec<Vec<Receipt>>,
}

impl Receipts {
Expand Down
Loading

0 comments on commit 3ad1b8a

Please sign in to comment.