Skip to content

Commit

Permalink
Merge pull request #62 from topos-protocol/pass_encoded_txns
Browse files Browse the repository at this point in the history
feat: Pass original txn RLP encodings
  • Loading branch information
Nashtare authored Jun 12, 2024
2 parents 916b682 + 4ddf02b commit 49b6fda
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 188 deletions.
44 changes: 34 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ serde_cbor = "0.11.2"
tokio = { version = "1.28.1" }

# zk-evm dependencies
plonky2 = "0.2.0"
mpt_trie = "0.1.1"
evm_arithmetization = "0.1.1"
plonky2 = "0.2.2"
mpt_trie = "0.3.0"
evm_arithmetization = "0.2.0"

[profile.release]
opt-level = 3
Expand Down
187 changes: 43 additions & 144 deletions eth_test_parser/src/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ use anyhow::Result;
use bytes::Bytes;
use ethereum_types::{Address, H160, H256, U256};
use evm_arithmetization::generation::mpt::transaction_testing::{
AccessListItemRlp, AccessListTransactionRlp, AddressOption, FeeMarketTransactionRlp,
LegacyTransactionRlp,
AddressOption, LegacyTransactionRlp,
};
use hex::FromHex;
use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream};
use rlp::{Decodable, DecoderError, Rlp};
use rlp_derive::RlpDecodable;
use serde::de::MapAccess;
use serde::{
Expand Down Expand Up @@ -109,32 +108,21 @@ pub(crate) struct BlockHeader {
// and hence they require a specific handling.
#[derive(Clone, Debug, RlpDecodable)]
pub struct AccessItemRlp {
pub address: Address,
pub storage_keys: Vec<StorageKey>,
_address: Address,
_storage_keys: Vec<StorageKey>,
}

#[derive(Clone, Debug)]
pub struct StorageKey(pub U256);
pub struct StorageKey;

impl Decodable for StorageKey {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
// Decode the key as a `Vec<u8>` to deal with badly encoded scalars,
// and then convert back to U256.
let key = rlp.as_val::<Vec<u8>>()?;
if key.len() == 1 && key[0] == 0x80 {
return Ok(StorageKey(U256::zero()));
}
// We just need to decode the key as a `Vec<u8>`
// to deal with badly encoded scalars, but we do
// not care about the result.
let _key = rlp.as_val::<Vec<u8>>()?;

Ok(StorageKey(U256::from_big_endian(&key)))
}
}

impl AccessItemRlp {
fn into_regular(self) -> AccessListItemRlp {
AccessListItemRlp {
address: self.address,
storage_keys: self.storage_keys.iter().map(|k| k.0).collect(),
}
Ok(Self)
}
}

Expand Down Expand Up @@ -162,129 +150,55 @@ impl Decodable for Transactions {
// A custom type-1 txn to handle some edge-cases with the access_list field.
#[derive(RlpDecodable, Debug, Clone)]
pub struct CustomAccessListTransactionRlp {
pub chain_id: u64,
pub nonce: U256,
pub gas_price: U256,
pub gas: U256,
pub to: AddressOption,
pub value: U256,
pub data: Bytes,
pub access_list: Vec<AccessItemRlp>,
pub y_parity: U256,
pub r: U256,
pub s: U256,
}

impl CustomAccessListTransactionRlp {
fn into_regular(self) -> AccessListTransactionRlp {
AccessListTransactionRlp {
chain_id: self.chain_id,
nonce: self.nonce,
gas_price: self.gas_price,
gas: self.gas,
to: self.to.clone(),
value: self.value,
data: self.data.clone(),
access_list: self
.access_list
.clone()
.into_iter()
.map(|x| x.into_regular())
.collect(),
y_parity: self.y_parity,
r: self.r,
s: self.s,
}
}
_chain_id: u64,
_nonce: U256,
_gas_price: U256,
_gas: U256,
_to: AddressOption,
_value: U256,
_data: Bytes,
_access_list: Vec<AccessItemRlp>,
_y_parity: U256,
_r: U256,
_s: U256,
}

// A custom type-2 txn to handle some edge-cases with the access_list field.
#[derive(RlpDecodable, Debug, Clone)]
pub struct CustomFeeMarketTransactionRlp {
pub chain_id: u64,
pub nonce: U256,
pub max_priority_fee_per_gas: U256,
pub max_fee_per_gas: U256,
pub gas: U256,
pub to: AddressOption,
pub value: U256,
pub data: Bytes,
pub access_list: Vec<AccessItemRlp>,
pub y_parity: U256,
pub r: U256,
pub s: U256,
}

impl CustomFeeMarketTransactionRlp {
fn into_regular(self) -> FeeMarketTransactionRlp {
FeeMarketTransactionRlp {
chain_id: self.chain_id,
nonce: self.nonce,
max_priority_fee_per_gas: self.max_priority_fee_per_gas,
max_fee_per_gas: self.max_fee_per_gas,
gas: self.gas,
to: self.to.clone(),
value: self.value,
data: self.data.clone(),
access_list: self
.access_list
.clone()
.into_iter()
.map(|x| x.into_regular())
.collect(),
y_parity: self.y_parity,
r: self.r,
s: self.s,
}
}
_chain_id: u64,
_nonce: U256,
_max_priority_fee_per_gas: U256,
_max_fee_per_gas: U256,
_gas: U256,
_to: AddressOption,
_value: U256,
_data: Bytes,
_access_list: Vec<AccessItemRlp>,
_y_parity: U256,
_r: U256,
_s: U256,
}

#[derive(Clone, Debug)]
pub enum Transaction {
Legacy(LegacyTransactionRlp),
AccessList(AccessListTransactionRlp),
FeeMarket(FeeMarketTransactionRlp),
}
pub struct Transaction(pub Vec<u8>);

impl Transaction {
fn decode_actual_rlp(bytes: &[u8]) -> Result<Self, DecoderError> {
let first_byte = bytes.first().ok_or(DecoderError::RlpInvalidLength)?;
match *first_byte {
1 => CustomAccessListTransactionRlp::decode(&Rlp::new(&bytes[1..]))
.map(|tx| Transaction::AccessList(tx.into_regular())),
.map(|_| Self(bytes.to_vec())),
2 => CustomFeeMarketTransactionRlp::decode(&Rlp::new(&bytes[1..]))
.map(|tx| Transaction::FeeMarket(tx.into_regular())),
_ => LegacyTransactionRlp::decode(&Rlp::new(bytes)).map(Transaction::Legacy),
.map(|_| Self(bytes.to_vec())),
_ => LegacyTransactionRlp::decode(&Rlp::new(bytes)).map(|_| Self(bytes.to_vec())),
}
}
}

impl Encodable for Transaction {
fn rlp_append(&self, s: &mut RlpStream) {
match self {
Transaction::Legacy(tx) => s.append(tx),
Transaction::AccessList(tx) => {
s.encoder().encode_value(&[0x01]);
s.append(tx)
}
Transaction::FeeMarket(tx) => {
s.encoder().encode_value(&[0x02]);
s.append(tx)
}
};
}
}

impl Decodable for Transaction {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
let first_byte = rlp.as_raw().first().ok_or(DecoderError::RlpInvalidLength)?;
let attempt = match *first_byte {
1 => CustomAccessListTransactionRlp::decode(&Rlp::new(&rlp.as_raw()[1..]))
.map(|tx| Transaction::AccessList(tx.into_regular())),
2 => CustomFeeMarketTransactionRlp::decode(&Rlp::new(&rlp.as_raw()[1..]))
.map(|tx| Transaction::FeeMarket(tx.into_regular())),
_ => LegacyTransactionRlp::decode(rlp).map(Transaction::Legacy),
};
let attempt = Transaction::decode_actual_rlp(rlp.as_raw());

// Somes tests have a different format and store the RLP encoding of the
// transaction, which needs an additional layer of decoding.
Expand Down Expand Up @@ -429,27 +343,12 @@ impl<'de> Deserialize<'de> for TestFile {
if value.blocks[0].transaction_sequence.is_none() {
let test_body = TestBody::from_parsed_json(&value, key.clone());

// Sanity check: some tests *do not* abide by standard RLP encoding
// rules, therefore causing discrepancies between the "expected"
// encoding of the transaction that is part of the block RLP and used to
// form the transactions trie, and the *regular* encoding computed here
// after deserialization and to be fed to plonky2 zkEVM.
// Ensure that the gas used fits in 32 bits, otherwise the prover will
// abort.
if TryInto::<u32>::try_into(test_body.block.block_header.gas_used)
.is_ok()
{
let rlp = &value.blocks[0].rlp.0;
let encoded_txn = rlp::encode(&test_body.get_tx()).to_vec();
// Ensure that the encoding we will provide the zkEVM prover is in
// the block RLP.
if rlp.windows(encoded_txn.len()).any(|c| c == encoded_txn) {
// Finally, ensure that the gas used fits in 32 bits, otherwise
// the prover will abort.
if TryInto::<u32>::try_into(
test_body.block.block_header.gas_used,
)
.is_ok()
{
map.0.insert(key, test_body);
}
}
map.0.insert(key, test_body);
}
} else {
// Some tests deal with malformed transactions that wouldn't be passed
Expand Down
Loading

0 comments on commit 49b6fda

Please sign in to comment.