Skip to content

Commit

Permalink
[FEAT] support mainnet block in mainnet.rs (#814) (#1004)
Browse files Browse the repository at this point in the history
* patch for L2 node rpc

* constrain max_txs to pass pi circuit

* revert get access set to the same implement as upstream

* update for using prestateTracer for state

* fix coinbase issue

* apply mock fill root

* better form for mock filling mpt

* dep issue

* some trivial optimizations

* grep mpt standlone circuit from test

* update poseidon row params

* fix issues in old_root setting

* update ethblock for some fields

* update more fields

* update keccak inputs

* update prev state root

* update more fields for state root

* update code size

* fix storage issue on missing field

* optimize the pre state handling

* induce relax mode to raise some restrict

* picking coinbase/difficulity from block instead of default constants

* custom diff/coinbase in padding

* fix the max_txs issue in pi_circuit

* clippy and fmt

* support single tx test

* adjust params

* rebuild mpt state in mock tx proven

* fmt

* resume some fields in prestate trace

* test creating supercircuit from dummy witness block

* revert most params

* mitigate an issue in prestate tracer

* add timeout spec for robust API call

* fmt

* add readme for test

* add more comment according to review

---------

Co-authored-by: Steven <asongala@163.com>
  • Loading branch information
noel2004 and silathdiir authored Nov 20, 2023
1 parent b02de5d commit 0c3b39f
Show file tree
Hide file tree
Showing 19 changed files with 530 additions and 214 deletions.
205 changes: 150 additions & 55 deletions bus-mapping/src/circuit_input_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
precompile::is_precompiled,
rpc::GethClient,
state_db::{self, CodeDB, StateDB},
util::{hash_code_keccak, KECCAK_CODE_HASH_EMPTY},
};
pub use access::{Access, AccessSet, AccessValue, CodeSource};
pub use block::{Block, BlockContext};
Expand Down Expand Up @@ -298,14 +299,25 @@ impl<'a> CircuitInputBuilder {
for (tx_index, tx) in eth_block.transactions.iter().enumerate() {
let chunk_tx_idx = self.block.txs.len();
if self.block.txs.len() >= self.block.circuits_params.max_txs {
log::error!(
"tx num overflow, MAX_TX limit {}, {}th tx(inner idx: {}) {:?}",
self.block.circuits_params.max_txs,
chunk_tx_idx,
tx.transaction_index.unwrap_or_default(),
tx.hash
);
return Err(Error::InternalError("tx num overflow"));
if self.block.is_relaxed() {
log::warn!(
"tx num overflow, MAX_TX limit {}, {}th tx(inner idx: {}) {:?}, would process for partial block",
self.block.circuits_params.max_txs,
chunk_tx_idx,
tx.transaction_index.unwrap_or_default(),
tx.hash
);
break;
} else {
log::error!(
"tx num overflow, MAX_TX limit {}, {}th tx(inner idx: {}) {:?}",
self.block.circuits_params.max_txs,
chunk_tx_idx,
tx.transaction_index.unwrap_or_default(),
tx.hash
);
return Err(Error::InternalError("tx num overflow"));
}
}
let geth_trace = &geth_traces[tx_index];
log::info!(
Expand Down Expand Up @@ -709,6 +721,12 @@ impl CircuitInputBuilder {
.iter()
.any(|tx| tx.has_l2_different_evm_behaviour_step())
}

/// enable relax mode for testing
pub fn enable_relax_mode(mut self) -> Self {
self.block = self.block.relax();
self
}
}

/// Return all the keccak inputs used during the processing of the current
Expand Down Expand Up @@ -950,7 +968,7 @@ pub struct BuilderClient<P: JsonRpcClient> {
pub fn get_state_accesses(
eth_block: &EthBlock,
geth_traces: &[eth_types::GethExecTrace],
) -> Result<Vec<Access>, Error> {
) -> Result<AccessSet, Error> {
let mut block_access_trace = vec![Access::new(
None,
RW::WRITE,
Expand All @@ -966,7 +984,7 @@ pub fn get_state_accesses(
block_access_trace.extend(tx_access_trace);
}

Ok(block_access_trace)
Ok(AccessSet::from(block_access_trace))
}

/// Build a partial StateDB from step 3
Expand Down Expand Up @@ -1060,26 +1078,11 @@ impl<P: JsonRpcClient> BuilderClient<P> {
}

/// Step 2. Get State Accesses from TxExecTraces
pub async fn get_state_accesses(&self, eth_block: &EthBlock) -> Result<AccessSet, Error> {
let mut access_set = AccessSet::default();
access_set.add_account(
eth_block
.author
.ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?,
);
let traces = self
.cli
.trace_block_prestate_by_hash(
eth_block
.hash
.ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?,
)
.await?;
for trace in traces.into_iter() {
access_set.extend_from_traces(&trace);
}

Ok(access_set)
pub fn get_state_accesses(
eth_block: &EthBlock,
geth_traces: &[eth_types::GethExecTrace],
) -> Result<AccessSet, Error> {
get_state_accesses(eth_block, geth_traces)
}

/// Step 3. Query geth for all accounts, storage keys, and codes from
Expand Down Expand Up @@ -1118,6 +1121,117 @@ impl<P: JsonRpcClient> BuilderClient<P> {
Ok((proofs, codes))
}

/// Yet-another Step 3. Get the account state and codes from pre-state tracing
/// the account state is limited since proof is not included,
/// but it is enough to build the sdb/cdb
/// if a hash for tx is provided, would return the prestate for this tx
pub async fn get_pre_state(
&self,
eth_block: &EthBlock,
tx_hash: Option<H256>,
) -> Result<
(
Vec<eth_types::EIP1186ProofResponse>,
HashMap<Address, Vec<u8>>,
),
Error,
> {
let traces = if let Some(tx_hash) = tx_hash {
vec![self.cli.trace_tx_prestate_by_hash(tx_hash).await?]
} else {
self.cli
.trace_block_prestate_by_hash(
eth_block
.hash
.ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?,
)
.await?
};

let mut account_set =
HashMap::<Address, (eth_types::EIP1186ProofResponse, HashMap<Word, Word>)>::new();
let mut code_set = HashMap::new();

for trace in traces.into_iter() {
for (addr, prestate) in trace.into_iter() {
let (_, storages) = account_set.entry(addr).or_insert_with(|| {
let code_size =
Word::from(prestate.code.as_ref().map(|bt| bt.len()).unwrap_or(0));
let (code_hash, keccak_code_hash) = if let Some(bt) = prestate.code {
let h = CodeDB::hash(&bt);
// only require for L2
let keccak_h = if cfg!(feature = "scroll") {
hash_code_keccak(&bt)
} else {
h
};
code_set.insert(addr, Vec::from(bt.as_ref()));
(h, keccak_h)
} else {
(CodeDB::empty_code_hash(), *KECCAK_CODE_HASH_EMPTY)
};

(
eth_types::EIP1186ProofResponse {
address: addr,
balance: prestate.balance.unwrap_or_default(),
nonce: prestate.nonce.unwrap_or_default().into(),
code_hash,
keccak_code_hash,
code_size,
..Default::default()
},
HashMap::new(),
)
});

if let Some(stg) = prestate.storage {
for (k, v) in stg {
storages.entry(k).or_insert(v);
}
}
}
}

// a hacking? since the coinbase address is not touch in prestate
let coinbase_addr = eth_block
.author
.ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?;
let block_num = eth_block
.number
.ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?;
assert_ne!(
block_num.as_u64(),
0,
"is not expected to access genesis block"
);
if let std::collections::hash_map::Entry::Vacant(e) = account_set.entry(coinbase_addr) {
let coinbase_proof = self
.cli
.get_proof(coinbase_addr, Vec::new(), (block_num - 1).into())
.await?;
e.insert((coinbase_proof, HashMap::new()));
}

Ok((
account_set
.into_iter()
.map(|(_, (mut acc_resp, storage_proofs))| {
acc_resp.storage_proof = storage_proofs
.into_iter()
.map(|(key, value)| eth_types::StorageProof {
key,
value,
..Default::default()
})
.collect();
acc_resp
})
.collect::<Vec<_>>(),
code_set,
))
}

/// Step 4. Build a partial StateDB from step 3
pub fn build_state_code_db(
proofs: Vec<eth_types::EIP1186ProofResponse>,
Expand Down Expand Up @@ -1184,8 +1298,8 @@ impl<P: JsonRpcClient> BuilderClient<P> {
> {
let (mut eth_block, mut geth_traces, history_hashes, prev_state_root) =
self.get_block(block_num).await?;
let access_set = self.get_state_accesses(&eth_block).await?;
let (proofs, codes) = self.get_state(block_num, access_set).await?;
//let access_set = Self::get_state_accesses(&eth_block, &geth_traces)?;
let (proofs, codes) = self.get_pre_state(&eth_block, None).await?;
let (state_db, code_db) = Self::build_state_code_db(proofs, codes);
if eth_block.transactions.len() > self.circuits_params.max_txs {
log::error!(
Expand Down Expand Up @@ -1220,7 +1334,7 @@ impl<P: JsonRpcClient> BuilderClient<P> {
let mut access_set = AccessSet::default();
for block_num in block_num_begin..block_num_end {
let (eth_block, geth_traces, _, _) = self.get_block(block_num).await?;
let mut access_list = self.get_state_accesses(&eth_block).await?;
let mut access_list = Self::get_state_accesses(&eth_block, &geth_traces)?;
access_set.extend(&mut access_list);
blocks_and_traces.push((eth_block, geth_traces));
}
Expand All @@ -1243,40 +1357,21 @@ impl<P: JsonRpcClient> BuilderClient<P> {

let mut tx: eth_types::Transaction = self.cli.get_tx_by_hash(tx_hash).await?;
tx.transaction_index = Some(0.into());
let geth_traces = self.cli.trace_tx_by_hash(tx_hash).await?;
let geth_trace = self.cli.trace_tx_by_hash(tx_hash).await?;
let mut eth_block = self
.cli
.get_block_by_number(tx.block_number.unwrap().into())
.await?;

eth_block.transactions = vec![tx.clone()];

let mut block_access_trace = vec![Access::new(
None,
RW::WRITE,
AccessValue::Account {
address: eth_block.author.unwrap(),
},
)];
let geth_trace = &geth_traces[0];
let tx_access_trace = gen_state_access_trace(
&eth_types::Block::<eth_types::Transaction>::default(),
&tx,
geth_trace,
)?;
block_access_trace.extend(tx_access_trace);

let access_set = AccessSet::from(block_access_trace);

let (proofs, codes) = self
.get_state(tx.block_number.unwrap().as_u64(), access_set)
.await?;
let (proofs, codes) = self.get_pre_state(&eth_block, Some(tx_hash)).await?;
let (state_db, code_db) = Self::build_state_code_db(proofs, codes);
let builder = self.gen_inputs_from_state(
state_db,
code_db,
&eth_block,
&geth_traces,
&[geth_trace],
Default::default(),
Default::default(),
)?;
Expand Down
16 changes: 16 additions & 0 deletions bus-mapping/src/circuit_input_builder/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ pub struct Block {
pub precompile_events: PrecompileEvents,
/// circuit capacity counter
copy_counter: usize,
/// relax mode indicate builder and circuit would skip
/// some sanity check, used by testing and debugging
relax_mode: bool,
}

impl Block {
Expand Down Expand Up @@ -311,6 +314,11 @@ impl Block {
self.chain_id
}

/// Return if the relax mode
pub fn is_relaxed(&self) -> bool {
self.relax_mode
}

/// ..
pub fn end_state_root(&self) -> Word {
self.headers
Expand All @@ -323,6 +331,14 @@ impl Block {
pub fn txs_mut(&mut self) -> &mut Vec<Transaction> {
&mut self.txs
}

/// switch to relax mode (used by testing and debugging,
/// see the note in defination of `relax_mode`)
#[cfg(feature = "test")]
pub fn relax(mut self) -> Self {
self.relax_mode = true;
self
}
}

impl Block {
Expand Down
24 changes: 19 additions & 5 deletions bus-mapping/src/evm/opcodes/begin_end_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,25 @@ pub fn gen_begin_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep,
if state.tx.is_create()
&& ((!account_code_hash_is_empty_or_zero) || !callee_account.nonce.is_zero())
{
unimplemented!(
"deployment collision at {:?}, account {:?}",
call.address,
callee_account
);
// since there is a bug in the prestate
// tracer: https://github.com/ethereum/go-ethereum/issues/28439
// which may also act as the data source for our statedb,
// we have to relax the constarint a bit and fix it silently
if account_code_hash_is_empty_or_zero && callee_account.nonce == 1.into() {
log::warn!(
"fix deployment nonce for {:?} silently for the prestate tracer",
call.address,
);
let mut fixed_account = callee_account.clone();
fixed_account.nonce = Word::zero();
state.sdb.set_account(&call.address, fixed_account);
} else {
unimplemented!(
"deployment collision at {:?}, account {:?}",
call.address,
callee_account
);
}
}

// Transfer with fee
Expand Down
3 changes: 1 addition & 2 deletions bus-mapping/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ impl BlockData {

let access_set: AccessSet =
get_state_accesses(&geth_data.eth_block, &geth_data.geth_traces)
.expect("state accesses")
.into();
.expect("state accesses");
// Initialize all accesses accounts to zero
for addr in access_set.state.keys() {
sdb.set_account(addr, state_db::Account::zero());
Expand Down
Loading

0 comments on commit 0c3b39f

Please sign in to comment.