use std::collections::BTreeMap;
use super::{
rw::{RwFingerprints, ToVec},
ExecStep, Rw, RwMap, Transaction,
};
use crate::{
evm_circuit::{detect_fixed_table_tags, EvmCircuit},
exp_circuit::param::OFFSET_INCREMENT,
instance::public_data_convert,
table::BlockContextFieldTag,
util::{log2_ceil, unwrap_value, word::WordLoHi, SubCircuit},
witness::Chunk,
};
use bus_mapping::{
circuit_input_builder::{
self, CopyEvent, ExpEvent, FeatureConfig, FixedCParams, PrecompileEvents, Withdrawal,
},
state_db::CodeDB,
Error,
};
use eth_types::{sign_types::SignData, Address, Field, ToScalar, Word, H256};
use gadgets::permutation::get_permutation_fingerprints;
use halo2_proofs::circuit::Value;
use itertools::Itertools;
#[derive(Debug, Clone, Default)]
pub struct Block<F> {
pub randomness: F,
pub txs: Vec<Transaction>,
pub end_block: ExecStep,
pub rws: RwMap,
pub by_address_rws: Vec<Rw>,
pub bytecodes: CodeDB,
pub context: BlockContext,
pub copy_events: Vec<CopyEvent>,
pub exp_events: Vec<ExpEvent>,
pub exp_circuit_pad_to: usize,
pub circuits_params: FixedCParams,
pub feature_config: FeatureConfig,
pub sha3_inputs: Vec<Vec<u8>>,
pub prev_state_root: Word, pub keccak_inputs: Vec<Vec<u8>>,
pub precompile_events: PrecompileEvents,
pub eth_block: eth_types::Block<eth_types::Transaction>,
pub rw_padding_meta: BTreeMap<usize, i32>,
}
impl<F: Field> Block<F> {
#[allow(dead_code, reason = "useful debug function")]
pub(crate) fn debug_print_txs_steps_rw_ops(&self) {
for (tx_idx, tx) in self.txs.iter().enumerate() {
println!("tx {}", tx_idx);
for step in tx.steps() {
println!("> Step {:?}", step.exec_state);
for rw_idx in 0..step.bus_mapping_instance.len() {
let rw = self.get_rws(step, rw_idx);
let rw_str = if rw.is_write() { "WRIT" } else { "READ" };
println!(" {} {} {:?}", rw.rw_counter(), rw_str, rw);
}
}
}
}
pub(crate) fn get_sign_data(&self, padding: bool) -> Vec<SignData> {
let mut signatures: Vec<SignData> = self
.txs
.iter()
.map(|tx| tx.tx.sign_data(self.context.chain_id.as_u64()))
.filter_map(|res| res.ok())
.collect::<Vec<SignData>>();
signatures.extend_from_slice(&self.precompile_events.get_ecrecover_events());
if padding && self.txs.len() < self.circuits_params.max_txs {
signatures.push(
Transaction::dummy()
.sign_data(self.context.chain_id.as_u64())
.unwrap(),
);
}
signatures
}
pub(crate) fn get_rws(&self, step: &ExecStep, index: usize) -> Rw {
self.rws[step.rw_index(index)]
}
pub fn withdrawals(&self) -> Vec<Withdrawal> {
let eth_withdrawals = self.eth_block.withdrawals.clone().unwrap_or_default();
eth_withdrawals
.iter()
.map({
|w| {
Withdrawal::new(
w.index.as_u64(),
w.validator_index.as_u64(),
w.address,
w.amount.as_u64(),
)
.unwrap()
}
})
.collect_vec()
}
pub fn withdrawals_root(&self) -> H256 {
self.eth_block.withdrawals_root.unwrap_or_default()
}
pub fn get_test_degree(&self, chunk: &Chunk<F>) -> u32 {
let num_rows_required_for_execution_steps: usize =
EvmCircuit::<F>::get_num_rows_required(self, chunk);
let num_rows_required_for_rw_table: usize = self.circuits_params.max_rws;
let num_rows_required_for_fixed_table: usize = detect_fixed_table_tags(self)
.iter()
.map(|tag| tag.build::<F>().count())
.sum();
let num_rows_required_for_bytecode_table =
self.bytecodes.num_rows_required_for_bytecode_table();
let num_rows_required_for_copy_table: usize =
self.copy_events.iter().map(|c| c.bytes.len() * 2).sum();
let num_rows_required_for_keccak_table: usize = self.keccak_inputs.len();
let num_rows_required_for_tx_table: usize =
self.txs.iter().map(|tx| 9 + tx.call_data.len()).sum();
let num_rows_required_for_exp_table: usize = self
.exp_events
.iter()
.map(|e| e.steps.len() * OFFSET_INCREMENT)
.sum();
let rows_needed: usize = itertools::max([
num_rows_required_for_execution_steps,
num_rows_required_for_rw_table,
num_rows_required_for_fixed_table,
num_rows_required_for_bytecode_table,
num_rows_required_for_copy_table,
num_rows_required_for_keccak_table,
num_rows_required_for_tx_table,
num_rows_required_for_exp_table,
1 << 16, ])
.unwrap();
let k = log2_ceil(EvmCircuit::<F>::unusable_rows() + rows_needed);
log::debug!(
"num_rows_required_for rw_table={}, fixed_table={}, bytecode_table={}, \
copy_table={}, keccak_table={}, tx_table={}, exp_table={}",
num_rows_required_for_rw_table,
num_rows_required_for_fixed_table,
num_rows_required_for_bytecode_table,
num_rows_required_for_copy_table,
num_rows_required_for_keccak_table,
num_rows_required_for_tx_table,
num_rows_required_for_exp_table
);
log::debug!("evm circuit uses k = {}, rows = {}", k, rows_needed);
k
}
}
#[derive(Debug, Default, Clone)]
pub struct BlockContext {
pub coinbase: Address,
pub gas_limit: u64,
pub number: Word,
pub timestamp: Word,
pub difficulty: Word,
pub base_fee: Word,
pub history_hashes: Vec<Word>,
pub chain_id: Word,
pub withdrawals_root: Word,
}
impl BlockContext {
pub fn table_assignments<F: Field>(&self) -> Vec<[Value<F>; 4]> {
[
vec![
[
Value::known(F::from(BlockContextFieldTag::Coinbase as u64)),
Value::known(F::ZERO),
Value::known(WordLoHi::from(self.coinbase).lo()),
Value::known(WordLoHi::from(self.coinbase).hi()),
],
[
Value::known(F::from(BlockContextFieldTag::Timestamp as u64)),
Value::known(F::ZERO),
Value::known(self.timestamp.to_scalar().unwrap()),
Value::known(F::ZERO),
],
[
Value::known(F::from(BlockContextFieldTag::Number as u64)),
Value::known(F::ZERO),
Value::known(self.number.to_scalar().unwrap()),
Value::known(F::ZERO),
],
[
Value::known(F::from(BlockContextFieldTag::Difficulty as u64)),
Value::known(F::ZERO),
Value::known(WordLoHi::from(self.difficulty).lo()),
Value::known(WordLoHi::from(self.difficulty).hi()),
],
[
Value::known(F::from(BlockContextFieldTag::GasLimit as u64)),
Value::known(F::ZERO),
Value::known(F::from(self.gas_limit)),
Value::known(F::ZERO),
],
[
Value::known(F::from(BlockContextFieldTag::BaseFee as u64)),
Value::known(F::ZERO),
Value::known(WordLoHi::from(self.base_fee).lo()),
Value::known(WordLoHi::from(self.base_fee).hi()),
],
[
Value::known(F::from(BlockContextFieldTag::ChainId as u64)),
Value::known(F::ZERO),
Value::known(WordLoHi::from(self.chain_id).lo()),
Value::known(WordLoHi::from(self.chain_id).hi()),
],
[
Value::known(F::from(BlockContextFieldTag::WithdrawalRoot as u64)),
Value::known(F::ZERO),
Value::known(WordLoHi::from(self.withdrawals_root).lo()),
Value::known(WordLoHi::from(self.withdrawals_root).hi()),
],
],
{
let len_history = self.history_hashes.len();
self.history_hashes
.iter()
.enumerate()
.map(|(idx, hash)| {
[
Value::known(F::from(BlockContextFieldTag::BlockHash as u64)),
Value::known((self.number - len_history + idx).to_scalar().unwrap()),
Value::known(WordLoHi::from(*hash).lo()),
Value::known(WordLoHi::from(*hash).hi()),
]
})
.collect()
},
]
.concat()
}
}
impl From<&circuit_input_builder::Block> for BlockContext {
fn from(block: &circuit_input_builder::Block) -> Self {
Self {
coinbase: block.coinbase,
gas_limit: block.gas_limit,
number: block.number,
timestamp: block.timestamp,
difficulty: block.difficulty,
base_fee: block.base_fee,
history_hashes: block.history_hashes.clone(),
chain_id: block.chain_id,
withdrawals_root: block.withdrawals_root().as_fixed_bytes().into(),
}
}
}
pub fn block_convert<F: Field>(
builder: &circuit_input_builder::CircuitInputBuilder<FixedCParams>,
) -> Result<Block<F>, Error> {
let block = &builder.block;
let code_db = &builder.code_db;
let rws = RwMap::from(&block.container);
let by_address_rws = rws.table_assignments(false);
rws.check_value();
let rw_padding_meta = builder
.chunks
.iter()
.fold(BTreeMap::new(), |mut map, chunk| {
assert!(
chunk.ctx.rwc.0.saturating_sub(1) <= builder.circuits_params.max_rws,
"max_rws size {} must larger than chunk rws size {}",
builder.circuits_params.max_rws,
chunk.ctx.rwc.0.saturating_sub(1),
);
(chunk.ctx.rwc.0..builder.circuits_params.max_rws).for_each(|padding_rw_counter| {
*map.entry(padding_rw_counter).or_insert(0) += 1;
});
map
});
let mut block = Block {
randomness: F::from(0xcafeu64),
context: block.into(),
rws,
by_address_rws,
txs: block.txs().to_vec(),
bytecodes: code_db.clone(),
copy_events: block.copy_events.clone(),
exp_events: block.exp_events.clone(),
sha3_inputs: block.sha3_inputs.clone(),
circuits_params: builder.circuits_params,
feature_config: builder.feature_config,
exp_circuit_pad_to: <usize>::default(),
prev_state_root: block.prev_state_root,
keccak_inputs: circuit_input_builder::keccak_inputs(block, code_db)?,
precompile_events: block.precompile_events.clone(),
eth_block: block.eth_block.clone(),
end_block: block.end_block.clone(),
rw_padding_meta,
};
let public_data = public_data_convert(&block);
let rpi_bytes = public_data.get_pi_bytes(
block.circuits_params.max_txs,
block.circuits_params.max_withdrawals,
block.circuits_params.max_calldata,
);
block.keccak_inputs.extend_from_slice(&[rpi_bytes]);
Ok(block)
}
#[allow(dead_code)]
fn get_rwtable_fingerprints<F: Field>(
alpha: F,
gamma: F,
prev_continuous_fingerprint: F,
rows: &Vec<Rw>,
) -> RwFingerprints<F> {
let x = rows.to2dvec();
let fingerprints = get_permutation_fingerprints(
&x,
Value::known(alpha),
Value::known(gamma),
Value::known(prev_continuous_fingerprint),
);
fingerprints
.first()
.zip(fingerprints.last())
.map(|((first_acc, first_row), (last_acc, last_row))| {
RwFingerprints::new(
unwrap_value(*first_row),
unwrap_value(*last_row),
unwrap_value(*first_acc),
unwrap_value(*last_acc),
)
})
.unwrap_or_default()
}