Skip to content

Commit

Permalink
Add anchor checks
Browse files Browse the repository at this point in the history
  • Loading branch information
Brechtpd committed Mar 5, 2024
1 parent c691466 commit aaacc7b
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 77 deletions.
7 changes: 6 additions & 1 deletion lib/src/builder/execute/taiko.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use super::TxExecStrategy;
use crate::{
builder::BlockBuilder,
consts::{self, ChainSpec, GWEI_TO_WEI},
guest_mem_forget,
guest_mem_forget, taiko_utils::check_anchor_tx,
};

/// Minimum supported protocol version: Bedrock (Block no. 105235063).
Expand Down Expand Up @@ -121,11 +121,16 @@ impl TxExecStrategy<EthereumTxEssence> for TkoTxExecStrategy {
{
// anchor transaction must be executed successfully
let is_anchor = tx_no == 0;

// verify the transaction signature
let tx_from = tx
.recover_from()
.with_context(|| anyhow!("Error recovering address for transaction {tx_no}"))?;

if is_anchor {
check_anchor_tx(&block_builder.input, &tx, &tx_from,&block_builder.input.taiko.chain_spec_name).expect("invalid anchor tx");
}

#[cfg(feature = "std")]
{
let tx_hash = tx.hash();
Expand Down
18 changes: 10 additions & 8 deletions lib/src/host/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,18 @@ pub fn taiko_run_preflight(

println!("l1_state_block_no: {:?}", l1_state_block_no);

// Get the L1 state block header so that we can prove the L1 state root
// Fetch the parent block
let l1_state_root_block = tp.l1_provider.get_partial_block(&BlockQuery {
block_no: l1_state_block_no,
})?;
//println!("l1_state_root_block: {:?}", l1_state_root_block);
println!("l1_state_root_block: {:?}", l1_state_root_block);
println!("l1_state_root_block hash: {:?}", l1_state_root_block.hash.unwrap());

/*let l1_propose_block = tp.l1_provider.get_partial_block(&BlockQuery {
block_no: l1_inclusion_block_no,
})?;
println!("l1_propose_block: {:?}", l1_propose_block);*/

// Get the block proposal data
let (proposal_call, proposal_event) = tp.get_proposal(l1_inclusion_block_no, l2_block_no, chain_spec_name)?;
Expand Down Expand Up @@ -109,15 +117,9 @@ pub fn taiko_run_preflight(
);
info!("Transaction count: {:?}", block.transactions.len());

// Get the L1 state block header so that we can prove the L1 state root
// Fetch the parent block
let l1_state_block = tp.l1_provider.get_partial_block(&BlockQuery {
block_no: l1_state_block_no,
})?;

let taiko_sys_info = TaikoSystemInfo {
chain_spec_name: chain_spec_name.to_string(),
l1_header: l1_state_block.try_into().expect("Failed to convert ethers block to zeth block"),
l1_header: l1_state_root_block.try_into().expect("Failed to convert ethers block to zeth block"),
tx_list: proposal_call.txList,
block_proposed: proposal_event,
prover_data,
Expand Down
147 changes: 79 additions & 68 deletions lib/src/taiko_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use alloy_primitives::{uint, Address, U256};
use anyhow::{anyhow, bail, ensure, Context, Result};
use ethers_core::types::{Transaction, U64, U256 as EU256};
use once_cell::unsync::Lazy;
use zeth_primitives::{ethers::{from_ethers_h160, from_ethers_u256}, transactions::{ethereum::EthereumTxEssence, EthereumTransaction}};
use revm::primitives::TransactTo;
use zeth_primitives::{ethers::{from_ethers_h160, from_ethers_u256}, transactions::{ethereum::{EthereumTxEssence, TransactionKind}, EthereumTransaction, TxEssence}, U8};

use crate::input::GuestInput;
use crate::input::{decode_anchor, GuestInput};

pub const ANCHOR_GAS_LIMIT: u64 = 250_000;
pub const MAX_TX_LIST: usize = 79;
Expand Down Expand Up @@ -80,7 +81,7 @@ pub mod internal_devnet_a {
});

pub const L2_SIGNAL_SERVICE: Lazy<Address> = Lazy::new(|| {
Address::from_str("0x1670010000000000000000000000000000000005")
Address::from_str("0x1670010000000000000000000000000000010001")
.expect("invalid l2 signal service")
});
}
Expand Down Expand Up @@ -144,73 +145,83 @@ fn check_anchor_signature(anchor: &EthereumTransaction) -> Result<()> {
}

pub fn check_anchor_tx(
input: GuestInput<EthereumTxEssence>,
anchor: &Transaction,
input: &GuestInput<EthereumTxEssence>,
anchor: &EthereumTransaction,
from: &Address,
chain_name: &str,
) -> Result<()> {
let tx1559_type = U64::from(0x2);
ensure!(
anchor.transaction_type == Some(tx1559_type),
"anchor transaction type mismatch"
);

let tx: EthereumTransaction = anchor
.clone()
.try_into()
.context(anyhow!("failed to decode anchor transaction: {:?}", anchor))?;
check_anchor_signature(&tx).context(anyhow!("failed to check anchor signature"))?;

ensure!(
from_ethers_h160(anchor.from) == *GOLDEN_TOUCH_ACCOUNT,
"anchor transaction from mismatch"
);
ensure!(
from_ethers_h160(anchor.to.unwrap()) == get_contracts(chain_name).unwrap().0,
"anchor transaction to mismatch"
);
ensure!(
anchor.value == EU256::from(0),
"anchor transaction value mismatch"
);
ensure!(
anchor.gas == EU256::from(ANCHOR_GAS_LIMIT),
"anchor transaction gas price mismatch"
);
ensure!(
from_ethers_u256(anchor.max_fee_per_gas.unwrap()) == input.base_fee_per_gas,
"anchor transaction gas mismatch"
);

//TODO(Brecht)
// 1. check l2 parent gas used
/*ensure!(
l2_parent_block.gas_used == ethers_core::types::U256::from(anchor_call.parentGasUsed),
"parentGasUsed mismatch"
);
// 2. check l1 signal root
let Some(l1_signal_service) = tp.l1_signal_service else {
bail!("l1_signal_service not set");
};
let proof = tp.l1_provider.get_proof(&ProofQuery {
block_no: l1_block_no,
address: l1_signal_service.into_array().into(),
indices: Default::default(),
})?;
let l1_signal_root = from_ethers_h256(proof.storage_hash);
ensure!(
l1_signal_root == anchor_call.l1SignalRoot,
"l1SignalRoot mismatch"
);
// 3. check l1 block hash
ensure!(
l1_block.hash.unwrap() == ethers_core::types::H256::from(anchor_call.l1Hash.0),
"l1Hash mismatch"
);*/
// Check the signature
check_anchor_signature(anchor).context(anyhow!("failed to check anchor signature"))?;

// Check the data
match &anchor.essence {
EthereumTxEssence::Eip1559(tx) => {
// Extract the `to` address
let to = if let TransactionKind::Call(to_addr) = tx.to {
to_addr
} else {
panic!("anchor tx not a smart contract call")
};
// Check that it's from the golden touch address
ensure!(
*from == GOLDEN_TOUCH_ACCOUNT.clone(),
"anchor transaction from mismatch"
);
// Check that the L2 contract is being called
ensure!(
to == get_contracts(chain_name).unwrap().1,
"anchor transaction to mismatch"
);
// Tx can't have any ETH attached
ensure!(
tx.value == U256::from(0),
"anchor transaction value mismatch"
);
// Tx needs to have the expected gas limit
ensure!(
tx.gas_limit == U256::from(ANCHOR_GAS_LIMIT),
"anchor transaction gas price mismatch"
);
// Check needs to have the base fee set to the block base fee
ensure!(
tx.max_fee_per_gas == input.base_fee_per_gas,
"anchor transaction gas mismatch"
);

// Okay now let's decode the anchor tx to verify the inputs
let anchor_call = decode_anchor(anchor.essence.data())?;

// TODO(Brecht): somehow l1_header.hash() return the incorrect hash on devnets
// maybe because those are on cancun but shouldn't have an impact on block hash calculation?
println!("anchor: {:?}", anchor_call.l1Hash);
println!("expected: {:?}", input.taiko.l1_header.hash());
if chain_name == "testnet" {
// The L1 blockhash needs to match the expected value
ensure!(
anchor_call.l1Hash == input.taiko.l1_header.hash(),
"L1 hash mismatch"
);
}
if chain_name != "testnet" {
ensure!(
anchor_call.l1SignalRoot == input.taiko.l1_header.state_root,
"L1 state root mismatch"
);
}
ensure!(
anchor_call.l1Height == input.taiko.l1_header.number,
"L1 block number mismatch"
);
// The parent gas used input needs to match the gas used value of the parent block
ensure!(
U256::from(anchor_call.parentGasUsed) == input.parent_header.gas_used,
"parentGasUsed mismatch"
);
}
_ => {
panic!("invalid anchor tx type");
}
}

Ok(())
}

0 comments on commit aaacc7b

Please sign in to comment.