Skip to content

Commit

Permalink
feat(client): Account + Account storage hinting in TrieDB
Browse files Browse the repository at this point in the history
Introcues the `TrieDBHinter` trait, which outlines an interface for
sending hints to the host for trie account proofs and storage slot
proofs. This enables `kona-host` to use `eth_getProof` rather than the
very limited `debug_dbGet` for fetching account proofs during L2
execution.
  • Loading branch information
clabby committed Jun 11, 2024
1 parent f5ac81e commit 23b4d17
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 96 deletions.
17 changes: 13 additions & 4 deletions bin/host/src/fetcher/hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ pub enum HintType {
L2BlockHeader,
/// A hint that specifies the transactions of a layer 2 block.
L2Transactions,
/// A hint that specifies the state node in the L2 state trie.
L2StateNode,
/// A hint that specifies the code of a contract on layer 2.
L2Code,
/// A hint that specifies the output root of a block on layer 2.
L2Output,
/// A hint that specifies the state node in the L2 state trie.
L2StateNode,
/// A hint that specifies the proof on the path to an account in the L2 state trie.
L2AccountProof,
/// A hint that specifies the proof on the path to a storage slot in an account within in the
/// L2 state trie.
L2AccountStorageProof,
}

impl TryFrom<&str> for HintType {
Expand All @@ -39,9 +44,11 @@ impl TryFrom<&str> for HintType {
"l1-precompile" => Ok(HintType::L1Precompile),
"l2-block-header" => Ok(HintType::L2BlockHeader),
"l2-transactions" => Ok(HintType::L2Transactions),
"l2-state-node" => Ok(HintType::L2StateNode),
"l2-code" => Ok(HintType::L2Code),
"l2-output" => Ok(HintType::L2Output),
"l2-state-node" => Ok(HintType::L2StateNode),
"l2-account-proof" => Ok(HintType::L2AccountProof),
"l2-account-storage-proof" => Ok(HintType::L2AccountStorageProof),
_ => anyhow::bail!("Invalid hint type: {value}"),
}
}
Expand All @@ -57,9 +64,11 @@ impl From<HintType> for &str {
HintType::L1Precompile => "l1-precompile",
HintType::L2BlockHeader => "l2-block-header",
HintType::L2Transactions => "l2-transactions",
HintType::L2StateNode => "l2-state-node",
HintType::L2Code => "l2-code",
HintType::L2Output => "l2-output",
HintType::L2StateNode => "l2-state-node",
HintType::L2AccountProof => "l2-account-proof",
HintType::L2AccountStorageProof => "l2-account-storage-proof",
}
}
}
Expand Down
65 changes: 64 additions & 1 deletion bin/host/src/fetcher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,72 @@ where
}
HintType::L2BlockHeader => todo!(),
HintType::L2Transactions => todo!(),
HintType::L2StateNode => todo!(),
HintType::L2Code => todo!(),
HintType::L2Output => todo!(),
HintType::L2StateNode => todo!(),
HintType::L2AccountProof => {
if hint_data.len() != 8 + 20 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

let block_number = u64::from_be_bytes(
hint_data.as_ref()[..8]
.try_into()
.map_err(|e| anyhow!("Error converting hint data to u64: {e}"))?,
);
let address = Address::from_slice(&hint_data.as_ref()[8..]);

let proof_response = self
.l2_provider
.get_proof(address, Default::default(), block_number.into())
.await
.map_err(|e| anyhow!("Failed to fetch account proof: {e}"))?;

let mut kv_write_lock = self.kv_store.write().await;

// Write the account proof nodes to the key-value store.
proof_response.account_proof.into_iter().for_each(|node| {
let node_hash = keccak256(node.as_ref());
let key = PreimageKey::new(*node_hash, PreimageKeyType::Keccak256);
kv_write_lock.set(key.into(), node.into());
});
}
HintType::L2AccountStorageProof => {
if hint_data.len() != 8 + 20 + 32 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

let block_number = u64::from_be_bytes(
hint_data.as_ref()[..8]
.try_into()
.map_err(|e| anyhow!("Error converting hint data to u64: {e}"))?,
);
let address = Address::from_slice(&hint_data.as_ref()[8..]);
let slot = B256::from_slice(&hint_data.as_ref()[28..]);

let mut proof_response = self
.l2_provider
.get_proof(address, vec![slot], block_number.into())
.await
.map_err(|e| anyhow!("Failed to fetch account proof: {e}"))?;

let mut kv_write_lock = self.kv_store.write().await;

// Write the account proof nodes to the key-value store.
proof_response.account_proof.into_iter().for_each(|node| {
let node_hash = keccak256(node.as_ref());
let key = PreimageKey::new(*node_hash, PreimageKeyType::Keccak256);
kv_write_lock.set(key.into(), node.into());
});

// Write the storage proof nodes to the key-value store.
let storage_proof = proof_response.storage_proof.remove(0);
storage_proof.proof.into_iter().for_each(|node| {
let node_hash = keccak256(node.as_ref());
let key = PreimageKey::new(*node_hash, PreimageKeyType::Keccak256);
kv_write_lock.set(key.into(), node.into());
});
}
}

Ok(())
Expand Down
17 changes: 10 additions & 7 deletions bin/programs/client/src/l2/executor/canyon.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Contains logic specific to Canyon hardfork activation.
use alloy_consensus::Header;
use alloy_primitives::{address, b256, hex, Address, Bytes, B256};
use anyhow::Result;
use kona_derive::types::RollupConfig;
use kona_mpt::{TrieDB, TrieDBFetcher, TrieDBHinter};
use revm::{
primitives::{Account, Bytecode, HashMap},
DatabaseCommit, State,
Expand All @@ -21,18 +22,20 @@ const CREATE_2_DEPLOYER_BYTECODE: [u8; 1584] = hex!("608060405260043610610043576
/// The Canyon hardfork issues an irregular state transition that force-deploys the create2
/// deployer contract. This is done by directly setting the code of the create2 deployer account
/// prior to executing any transactions on the timestamp activation of the fork.
pub(crate) fn ensure_create2_deployer_canyon<DB>(
db: &mut State<DB>,
pub(crate) fn ensure_create2_deployer_canyon<F, H>(
db: &mut State<TrieDB<F, H>>,
config: &RollupConfig,
timestamp: u64,
parent_header: &Header,
) -> Result<(), DB::Error>
) -> Result<()>
where
DB: revm::Database,
F: TrieDBFetcher,
H: TrieDBHinter,
{
// If the canyon hardfork is active at the current timestamp, and it was not active at the
// previous block timestamp, then we need to force-deploy the create2 deployer contract.
if config.is_canyon_active(timestamp) && !config.is_canyon_active(parent_header.timestamp) {
if config.is_canyon_active(timestamp) &&
!config.is_canyon_active(db.database.parent_block_header().timestamp)
{
// Load the create2 deployer account from the cache.
let acc = db.load_cache_account(CREATE_2_DEPLOYER_ADDR)?;

Expand Down
54 changes: 50 additions & 4 deletions bin/programs/client/src/l2/executor/fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
//!
//! [TrieDB]: kona_mpt::TrieDB
use crate::CachingOracle;
use crate::{CachingOracle, HINT_WRITER};
use alloy_consensus::Header;
use alloy_primitives::{Bytes, B256};
use alloy_primitives::{hex, Address, Bytes, B256};
use alloy_rlp::Decodable;
use anyhow::{anyhow, Result};
use kona_mpt::TrieDBFetcher;
use kona_preimage::{PreimageKey, PreimageKeyType, PreimageOracleClient};
use kona_mpt::{TrieDBFetcher, TrieDBHinter};
use kona_preimage::{HintWriterClient, PreimageKey, PreimageKeyType, PreimageOracleClient};

/// The [TrieDBFetcher] implementation for the block executor's [TrieDB].
///
Expand Down Expand Up @@ -56,3 +56,49 @@ impl<'a, const N: usize> TrieDBFetcher for TrieDBProvider<'a, N> {
})
}
}

/// The [TrieDBHinter] implementation for the block executor's [TrieDB].
///
/// [TrieDB]: kona_mpt::TrieDB
#[derive(Debug)]
pub struct TrieDBHintWriter;

impl TrieDBHinter for TrieDBHintWriter {
fn hint_trie_node(&self, hash: B256) -> Result<()> {
kona_common::block_on(async move {
HINT_WRITER
.write(&alloc::format!("l2-state-node {}", hex::encode(hash.as_slice())))
.await
})
}

fn hint_account_proof(&self, address: Address, block_number: u64) -> Result<()> {
kona_common::block_on(async move {
HINT_WRITER
.write(&alloc::format!(
"l2-account-proof {}{}",
hex::encode(block_number.to_be_bytes()),
hex::encode(address.as_slice())
))
.await
})
}

fn hint_storage_proof(
&self,
address: alloy_primitives::Address,
slot: alloy_primitives::U256,
block_number: u64,
) -> Result<()> {
kona_common::block_on(async move {
HINT_WRITER
.write(&alloc::format!(
"l2-account-storage-proof {}{}{}",
hex::encode(block_number.to_be_bytes()),
hex::encode(address.as_slice()),
hex::encode(slot.to_be_bytes::<32>())
))
.await
})
}
}
Loading

0 comments on commit 23b4d17

Please sign in to comment.