Skip to content

Commit

Permalink
added full verification of taiko blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
Brechtpd committed Jun 14, 2024
1 parent 33fa264 commit 527c342
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 65 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ smallvec = "1"
dyn-clone = "1.0.17"
sha2 = { version = "0.10", default-features = false }
paste = "1.0"
lazy_static = "1.4.0"

# proc-macros
proc-macro2 = "1.0"
Expand Down
3 changes: 3 additions & 0 deletions crates/ethereum/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ reth-provider.workspace = true

# Ethereum
revm-primitives.workspace = true
alloy-sol-types.workspace = true

# misc
tracing.workspace = true
anyhow.workspace = true
lazy_static.workspace = true

[dev-dependencies]
reth-revm = { workspace = true, features = ["test-utils"] }
58 changes: 39 additions & 19 deletions crates/ethereum/evm/src/execute.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Ethereum block executor.
use crate::EthEvmConfig;
use crate::{taiko::{check_anchor_tx, TaikoData}, EthEvmConfig};
use reth_evm::{
execute::{
BatchBlockOutput, BatchExecutor, EthBlockExecutionInput, EthBlockOutput, Executor,
Expand All @@ -13,8 +13,7 @@ use reth_interfaces::{
provider::ProviderError,
};
use reth_primitives::{
BlockWithSenders, ChainSpec, GotExpected, Hardfork, Header, PruneModes, Receipt, Receipts,
Withdrawals, U256,
BlockWithSenders, ChainSpec, GotExpected, Hardfork, Header, PruneModes, Receipt, Receipts, Withdrawals, U256
};
use reth_provider::BundleStateWithReceipts;
use reth_revm::{
Expand All @@ -27,14 +26,14 @@ use reth_revm::{
Evm, State,
};
use revm_primitives::{
db::{Database, DatabaseCommit}, Address, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, HashMap, ResultAndState
db::{Database, DatabaseCommit}, Address, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ResultAndState
};
use std::sync::Arc;
use tracing::debug;
use revm_primitives::address;
use revm_primitives::EVMError;
use reth_revm::JournaledState;
use revm_primitives::HashSet;
use anyhow::Result;

/// Provides executors to execute regular ethereum blocks
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -144,6 +143,7 @@ where
block: &BlockWithSenders,
mut evm: Evm<'_, Ext, &mut State<DB>>,
optimistic: bool,
taiko_data: Option<TaikoData>,
) -> Result<(Vec<Receipt>, u64), BlockExecutionError>
where
DB: Database<Error = ProviderError>,
Expand All @@ -162,10 +162,28 @@ where
// execute transactions
let mut cumulative_gas_used = 0;
let mut receipts = Vec::with_capacity(block.body.len());
let mut tx_number = 0;
for (sender, transaction) in block.transactions_with_sender() {
for (idx, (sender, transaction)) in block.transactions_with_sender().enumerate() {
//println!("tx: {:?}", tx_number);

let is_anchor = is_taiko && idx == 0;

// verify the anchor tx
if is_anchor {
check_anchor_tx(transaction, sender, &block.block, taiko_data.clone().unwrap())
.map_err(|e| BlockExecutionError::CanonicalRevert { inner: e.to_string() })?;
}

// if the signature was not valid, the sender address will have been set to zero
if *sender == Address::ZERO {
// Signature can be invalid if not taiko or not the anchor tx
if is_taiko && !is_anchor {
// If the signature is not valid, skip the transaction
continue;
}
// In all other cases, the tx needs to have a valid signature
return Err(BlockExecutionError::CanonicalRevert { inner: "invalid tx".to_string() });
}

// The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior,
// must be no greater than the block’s gasLimit.
let block_available_gas = block.header.gas_limit - cumulative_gas_used;
Expand All @@ -182,14 +200,10 @@ where

EvmConfig::fill_tx_env(evm.tx_mut(), transaction, *sender, ());

let is_anchor = is_taiko && tx_number == 0;

// Set taiko specific data
evm.tx_mut().taiko.is_anchor = is_anchor;
// set the treasury address
//tx_env.taiko.treasury = chain_spec.l2_contract.unwrap_or_default();
evm.tx_mut().taiko.treasury = address!("1670090000000000000000000000000000010001");

tx_number += 1;
evm.tx_mut().taiko.treasury = taiko_data.clone().unwrap().l2_contract;

// Execute transaction.
let res = evm.transact().map_err(move |err| {
Expand All @@ -200,7 +214,6 @@ where
}
});
if res.is_err() {

// Clear the state for the next tx
evm.context.evm.journaled_state = JournaledState::new(evm.context.evm.journaled_state.spec, HashSet::new());

Expand All @@ -216,8 +229,7 @@ where
match res {
Err(BlockValidationError::EVM { hash, error }) => match *error {
EVMError::Transaction(invalid_transaction) => {
//#[cfg(feature = "std")]
println!("Invalid tx at {}: {:?}", tx_number, invalid_transaction);
println!("Invalid tx at {}: {:?}", idx, invalid_transaction);
// skip the tx
continue;
},
Expand Down Expand Up @@ -286,12 +298,14 @@ pub struct EthBlockExecutor<EvmConfig, DB> {
inspector: Option<InspectorStack>,
/// Allows the execution to continue even when a tx is invalid
optimistic: bool,
/// Taiko data
taiko_data: Option<TaikoData>,
}

impl<EvmConfig, DB> EthBlockExecutor<EvmConfig, DB> {
/// Creates a new Ethereum block executor.
pub fn new(chain_spec: Arc<ChainSpec>, evm_config: EvmConfig, state: State<DB>) -> Self {
Self { executor: EthEvmExecutor { chain_spec, evm_config }, state, inspector: None, optimistic: false }
Self { executor: EthEvmExecutor { chain_spec, evm_config }, state, inspector: None, optimistic: false, taiko_data: None }
}

/// Sets the inspector stack for debugging.
Expand All @@ -300,6 +314,12 @@ impl<EvmConfig, DB> EthBlockExecutor<EvmConfig, DB> {
self
}

/// Optimistic execution
pub fn taiko_data(mut self, taiko_data: TaikoData) -> Self {
self.taiko_data = Some(taiko_data);
self
}

/// Optimistic execution
pub fn optimistic(mut self, optimistic: bool) -> Self {
self.optimistic = optimistic;
Expand Down Expand Up @@ -367,11 +387,11 @@ where
env,
inspector,
);
self.executor.execute_pre_and_transactions(block, evm, self.optimistic)?
self.executor.execute_pre_and_transactions(block, evm, self.optimistic, self.taiko_data.clone())?
} else {
let evm = self.executor.evm_config.evm_with_env(&mut self.state, env);

self.executor.execute_pre_and_transactions(block, evm, self.optimistic)?
self.executor.execute_pre_and_transactions(block, evm, self.optimistic, self.taiko_data.clone())?
}
};

Expand Down
1 change: 1 addition & 0 deletions crates/ethereum/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use reth_primitives::{
Address, ChainSpec, Head, Header, Transaction, U256,
};
pub mod execute;
pub mod taiko;

/// Ethereum-related EVM configuration.
#[derive(Debug, Clone, Copy, Default)]
Expand Down
145 changes: 145 additions & 0 deletions crates/ethereum/evm/src/taiko.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! Taiko related functionality for the block executor.
use reth_primitives::{Block, Header, TransactionSigned, TxKind};
use revm_primitives::{alloy_primitives::uint, Address, U256};
use std::str::FromStr;
use lazy_static::lazy_static;
use anyhow::{bail, Context, Result, ensure, anyhow};

#[derive(Clone, Debug, Default)]
pub struct TaikoData {
/// header
pub l1_header: Header,
/// parent L1 header
pub parent_header: Header,
/// L2 contract
pub l2_contract: Address,
}

/// Anchor tx gas limit
pub const ANCHOR_GAS_LIMIT: u64 = 250_000;

lazy_static! {
pub static ref GOLDEN_TOUCH_ACCOUNT: Address = {
Address::from_str("0x0000777735367b36bC9B61C50022d9D0700dB4Ec")
.expect("invalid golden touch account")
};
static ref GX1: U256 =
uint!(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798_U256);
static ref N: U256 =
uint!(0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141_U256);
static ref GX1_MUL_PRIVATEKEY: U256 =
uint!(0x4341adf5a780b4a87939938fd7a032f6e6664c7da553c121d3b4947429639122_U256);
static ref GX2: U256 =
uint!(0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5_U256);
}


/// check the anchor signature with fixed K value
fn check_anchor_signature(anchor: &TransactionSigned) -> Result<()> {
let sign = anchor.signature();
if sign.r == *GX1 {
return Ok(());
}
let msg_hash = anchor.signature_hash();
let msg_hash: U256 = msg_hash.into();
if sign.r == *GX2 {
// when r == GX2 require s == 0 if k == 1
// alias: when r == GX2 require N == msg_hash + *GX1_MUL_PRIVATEKEY
if *N != msg_hash + *GX1_MUL_PRIVATEKEY {
bail!(
"r == GX2, but N != msg_hash + *GX1_MUL_PRIVATEKEY, N: {}, msg_hash: {msg_hash}, *GX1_MUL_PRIVATEKEY: {}",
*N, *GX1_MUL_PRIVATEKEY
);
}
return Ok(());
}
Err(anyhow!(
"r != *GX1 && r != GX2, r: {}, *GX1: {}, GX2: {}",
sign.r,
*GX1,
*GX2
))
}

use alloy_sol_types::{sol, SolCall};

/// Anchor call solidity code
sol! {
/// Anchor call
function anchor(
bytes32 l1Hash,
bytes32 l1StateRoot,
uint64 l1BlockId,
uint32 parentGasUsed
)
external
{}
}

/// Decode anchor tx data
pub fn decode_anchor(bytes: &[u8]) -> Result<anchorCall> {
anchorCall::abi_decode(bytes, true).map_err(|e| anyhow!(e))
// .context("Invalid anchor call")
}

/// Verifies the anchor tx correctness
pub fn check_anchor_tx(tx: &TransactionSigned, from: &Address, block: &Block, taiko_data: TaikoData) -> Result<()> {
let anchor = tx.as_eip1559().context(anyhow!("anchor tx is not an EIP1559 tx"))?;

// Check the signature
check_anchor_signature(tx).context(anyhow!("failed to check anchor signature"))?;

// Extract the `to` address
let TxKind::Call(to) = anchor.to else {
panic!("anchor tx not a smart contract call")
};
// Check that it's from the golden touch address
ensure!(
*from == *GOLDEN_TOUCH_ACCOUNT,
"anchor transaction from mismatch"
);
// Check that the L2 contract is being called
ensure!(
to == taiko_data.l2_contract,
"anchor transaction to mismatch"
);
// Tx can't have any ETH attached
ensure!(
anchor.value == U256::from(0),
"anchor transaction value mismatch"
);
// Tx needs to have the expected gas limit
ensure!(
anchor.gas_limit == ANCHOR_GAS_LIMIT,
"anchor transaction gas price mismatch"
);
// Check needs to have the base fee set to the block base fee
ensure!(
anchor.max_fee_per_gas == block.header.base_fee_per_gas.unwrap().into(),
"anchor transaction gas mismatch"
);

// Okay now let's decode the anchor tx to verify the inputs
let anchor_call = decode_anchor(&anchor.input)?;
// The L1 blockhash needs to match the expected value
ensure!(
anchor_call.l1Hash == taiko_data.l1_header.hash_slow(),
"L1 hash mismatch"
);
ensure!(
anchor_call.l1StateRoot == taiko_data.l1_header.state_root,
"L1 state root mismatch"
);
ensure!(
anchor_call.l1BlockId == taiko_data.l1_header.number,
"L1 block number mismatch"
);
// The parent gas used input needs to match the gas used value of the parent block
ensure!(
anchor_call.parentGasUsed == taiko_data.parent_header.gas_used as u32,
"parentGasUsed mismatch"
);

Ok(())
}
3 changes: 3 additions & 0 deletions crates/interfaces/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ pub enum ProviderError {
/// Consistent view error.
#[error("failed to initialize consistent view: {0}")]
ConsistentView(Box<ConsistentViewError>),
/// RPC error.
#[error("failed to fetch data from RPC: {0}")]
RPC(String),
}

impl From<reth_primitives::fs::FsPathError> for ProviderError {
Expand Down
2 changes: 1 addition & 1 deletion crates/primitives/src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub use info::ChainInfo;
pub use spec::{
AllGenesisFormats, BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder,
DisplayHardforks, ForkBaseFeeParams, ForkCondition, ForkTimestamps, DEV, GOERLI, HOLESKY,
MAINNET, SEPOLIA, TAIKO_A7,
MAINNET, SEPOLIA, TAIKO_A7, TAIKO_MAINNET,
};
#[cfg(feature = "optimism")]
pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA};
Expand Down
Loading

0 comments on commit 527c342

Please sign in to comment.