From 2e19f940486c445178896b71f7a224b7854ba953 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 17 Oct 2022 16:13:02 +0200 Subject: [PATCH] feat: impl rlp for receipt (#83) * feat: impl rlp for receipt * fix: change to bloom * chore: rustfmt --- Cargo.lock | 29 +++++ crates/common/rlp/Cargo.toml | 2 +- crates/common/rlp/src/decode.rs | 2 +- crates/common/rlp/src/encode.rs | 2 +- crates/primitives/Cargo.toml | 3 +- crates/primitives/src/log.rs | 3 +- crates/primitives/src/receipt.rs | 212 ++++++++++++++++++++++++++++++- 7 files changed, 245 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44e4c6d76..98b83077d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -816,8 +816,10 @@ checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" dependencies = [ "crunchy", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", + "scale-info", "tiny-keccak", ] @@ -829,9 +831,11 @@ checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" dependencies = [ "ethbloom", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", "primitive-types", + "scale-info", "uint", ] @@ -2073,6 +2077,7 @@ dependencies = [ "impl-codec", "impl-rlp", "impl-serde", + "scale-info", "uint", ] @@ -2732,6 +2737,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scale-info" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333af15b02563b8182cd863f925bd31ef8fa86a0e095d30c091956057d436153" +dependencies = [ + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f56acbd0743d29ffa08f911ab5397def774ad01bab3786804cf6ee057fb5e1" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "schannel" version = "0.1.20" diff --git a/crates/common/rlp/Cargo.toml b/crates/common/rlp/Cargo.toml index 6d0c846fd..5f3d46869 100644 --- a/crates/common/rlp/Cargo.toml +++ b/crates/common/rlp/Cargo.toml @@ -11,7 +11,7 @@ arrayvec = { version = "0.7", default-features = false } auto_impl = "1" bytes = { version = "1", default-features = false } ethnum = { version = "1", default-features = false, optional = true } -ethereum-types = { version = "0.13", default-features = false, optional = true } +ethereum-types = { version = "0.13", features = ["codec"], optional = true } reth-rlp-derive = { version = "0.1", path = "../rlp-derive", optional = true } [dev-dependencies] diff --git a/crates/common/rlp/src/decode.rs b/crates/common/rlp/src/decode.rs index e9d4d3eef..ca79616ce 100644 --- a/crates/common/rlp/src/decode.rs +++ b/crates/common/rlp/src/decode.rs @@ -233,7 +233,7 @@ mod ethereum_types_support { fixed_hash_impl!(H256); fixed_hash_impl!(H512); fixed_hash_impl!(H520); - //TODO fixed_hash_impl!(Bloom); + fixed_hash_impl!(Bloom); macro_rules! fixed_uint_impl { ($t:ty, $n_bytes:tt) => { diff --git a/crates/common/rlp/src/encode.rs b/crates/common/rlp/src/encode.rs index 89cf191e0..eea18a567 100644 --- a/crates/common/rlp/src/encode.rs +++ b/crates/common/rlp/src/encode.rs @@ -209,7 +209,7 @@ mod ethereum_types_support { fixed_hash_impl!(H256); fixed_hash_impl!(H512); fixed_hash_impl!(H520); - //TODO fixed_hash_impl!(Bloom); + fixed_hash_impl!(Bloom); macro_rules! fixed_uint_impl { ($t:ty, $n_bytes:tt) => { diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index d2ad32e33..e2d18c20b 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -13,8 +13,7 @@ bytes = "1.2" serde = "1.0" thiserror = "1" - -reth-rlp = { path = "../common/rlp", features = ["derive"]} +reth-rlp = { path = "../common/rlp", features = ["std", "derive", "ethereum-types"]} parity-scale-codec = { version = "3.2.1", features = ["derive", "bytes"] } reth-codecs = { version = "0.1.0", path = "../codecs" } diff --git a/crates/primitives/src/log.rs b/crates/primitives/src/log.rs index 27565c64f..992021cc7 100644 --- a/crates/primitives/src/log.rs +++ b/crates/primitives/src/log.rs @@ -1,9 +1,10 @@ use crate::{Address, H256}; use reth_codecs::main_codec; +use reth_rlp::{RlpDecodable, RlpEncodable}; /// Ethereum Log #[main_codec] -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, RlpDecodable, RlpEncodable)] pub struct Log { /// Contract that emitted this log. pub address: Address, diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index d784c1dd5..c07a7c7af 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -1,5 +1,9 @@ -use crate::{Log, TxType, H256}; +use crate::{Bloom, Log, TxType}; +use bytes::{Buf, BufMut, BytesMut}; +use reth_rlp::{length_of_length, Decodable, Encodable}; + use reth_codecs::main_codec; +use std::cmp::Ordering; /// Receipt containing result of transaction execution. #[main_codec] @@ -8,11 +12,215 @@ pub struct Receipt { /// Receipt type. pub tx_type: TxType, /// If transaction is executed successfully. + /// + /// This is the `statusCode` pub success: bool, /// Gas used pub cumulative_gas_used: u64, /// Bloom filter. - pub bloom: H256, + pub bloom: Bloom, /// Log send from contracts. pub logs: Vec, } + +impl Receipt { + /// Returns the rlp header for the receipt payload. + fn receipt_rlp_header(&self) -> reth_rlp::Header { + let mut rlp_head = reth_rlp::Header { list: true, payload_length: 0 }; + + rlp_head.payload_length += self.success.length(); + rlp_head.payload_length += self.cumulative_gas_used.length(); + rlp_head.payload_length += self.bloom.length(); + rlp_head.payload_length += self.logs.length(); + + rlp_head + } + + /// Encodes the receipt data. + fn encode_receipt(&self, out: &mut dyn BufMut) { + self.receipt_rlp_header().encode(out); + self.success.encode(out); + self.cumulative_gas_used.encode(out); + self.bloom.encode(out); + self.logs.encode(out); + } + + /// Returns the length of the receipt data. + fn receipt_length(&self) -> usize { + let rlp_head = self.receipt_rlp_header(); + length_of_length(rlp_head.payload_length) + rlp_head.payload_length + } + + /// Decodes the receipt payload + fn decode_receipt(buf: &mut &[u8], tx_type: TxType) -> Result { + let b = &mut &**buf; + let rlp_head = reth_rlp::Header::decode(b)?; + if !rlp_head.list { + return Err(reth_rlp::DecodeError::UnexpectedString) + } + let started_len = b.len(); + let this = Self { + tx_type, + success: reth_rlp::Decodable::decode(b)?, + cumulative_gas_used: reth_rlp::Decodable::decode(b)?, + bloom: reth_rlp::Decodable::decode(b)?, + logs: reth_rlp::Decodable::decode(b)?, + }; + let consumed = started_len - b.len(); + if consumed != rlp_head.payload_length { + return Err(reth_rlp::DecodeError::ListLengthMismatch { + expected: rlp_head.payload_length, + got: consumed, + }) + } + *buf = *b; + Ok(this) + } +} + +impl Encodable for Receipt { + fn length(&self) -> usize { + let mut payload_len = self.receipt_length(); + // account for eip-2718 type prefix and set the list + if matches!(self.tx_type, TxType::EIP1559 | TxType::EIP2930) { + payload_len += 1; + // we include a string header for typed receipts, so include the length here + payload_len = length_of_length(payload_len); + } + + payload_len + } + fn encode(&self, out: &mut dyn BufMut) { + if matches!(self.tx_type, TxType::Legacy) { + self.encode_receipt(out); + return + } + + let mut payload = BytesMut::new(); + self.encode_receipt(&mut payload); + let payload_length = payload.len() + 1; + + let header = reth_rlp::Header { list: false, payload_length }; + header.encode(out); + + match self.tx_type { + TxType::EIP2930 => { + out.put_u8(0x01); + } + TxType::EIP1559 => { + out.put_u8(0x02); + } + _ => unreachable!("legacy handled; qed."), + } + + out.put_slice(payload.as_ref()); + } +} + +impl Decodable for Receipt { + fn decode(buf: &mut &[u8]) -> Result { + // a receipt is either encoded as a string (non legacy) or a list (legacy). + // We should not consume the buffer if we are decoding a legacy receipt, so let's + // check if the first byte is between 0x80 and 0xbf. + let rlp_type = *buf + .first() + .ok_or(reth_rlp::DecodeError::Custom("cannot decode a receipt from empty bytes"))?; + + match rlp_type.cmp(&reth_rlp::EMPTY_LIST_CODE) { + Ordering::Less => { + // strip out the string header + let _header = reth_rlp::Header::decode(buf)?; + let receipt_type = *buf.first().ok_or(reth_rlp::DecodeError::Custom( + "typed receipt cannot be decoded from an empty slice", + ))?; + if receipt_type == 0x01 { + buf.advance(1); + Self::decode_receipt(buf, TxType::EIP2930) + } else if receipt_type == 0x02 { + buf.advance(1); + Self::decode_receipt(buf, TxType::EIP1559) + } else { + Err(reth_rlp::DecodeError::Custom("invalid receipt type")) + } + } + Ordering::Equal => { + Err(reth_rlp::DecodeError::Custom("an empty list is not a valid receipt encoding")) + } + Ordering::Greater => Self::decode_receipt(buf, TxType::Legacy), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Address, H256}; + use ethers_core::{types::Bytes, utils::hex}; + use reth_rlp::{Decodable, Encodable}; + use std::str::FromStr; + + #[test] + // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 + fn encode_legacy_receipt() { + let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + + let mut data = vec![]; + let receipt = Receipt { + tx_type: TxType::Legacy, + bloom: [0; 256].into(), + cumulative_gas_used: 0x1u64, + logs: vec![Log { + address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), + topics: vec![ + H256::from_str( + "000000000000000000000000000000000000000000000000000000000000dead", + ) + .unwrap(), + H256::from_str( + "000000000000000000000000000000000000000000000000000000000000beef", + ) + .unwrap(), + ], + data: Bytes::from_str("0100ff").unwrap().0, + }], + success: false, + }; + + receipt.encode(&mut data); + + // check that the rlp length equals the length of the expected rlp + assert_eq!(receipt.length(), expected.len()); + assert_eq!(data, expected); + } + + #[test] + // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 + fn decode_legacy_receipt() { + let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + + // EIP658Receipt + let expected = Receipt { + tx_type: TxType::Legacy, + bloom: [0; 256].into(), + cumulative_gas_used: 0x1u64, + logs: vec![Log { + address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), + topics: vec![ + H256::from_str( + "000000000000000000000000000000000000000000000000000000000000dead", + ) + .unwrap(), + H256::from_str( + "000000000000000000000000000000000000000000000000000000000000beef", + ) + .unwrap(), + ], + data: Bytes::from_str("0100ff").unwrap().0, + }], + success: false, + }; + + let receipt = Receipt::decode(&mut &data[..]).unwrap(); + assert_eq!(receipt, expected); + } +}