Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Einar wip #23

Draft
wants to merge 2 commits into
base: einar-branch-off
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 52 additions & 14 deletions bus-mapping/src/circuit_input_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ mod access;
mod block;
mod call;
mod chunk;
#[cfg(test)]
mod chunk_tests;
mod execution;
mod input_state_ref;
#[cfg(test)]
Expand Down Expand Up @@ -42,6 +44,9 @@ use log::warn;
use std::{collections::HashMap, ops::Deref};
pub use transaction::{Transaction, TransactionContext};

/// Maximum number of transactions in one chunk.
pub const MAX_CHUNK_SIZE: usize = 2;

/// Circuit Setup Parameters
#[derive(Debug, Clone, Copy)]
pub struct FixedCParams {
Expand Down Expand Up @@ -136,22 +141,27 @@ pub struct CircuitInputBuilder<C: CircuitsParams> {
pub circuits_params: C,
/// Block Context
pub block_ctx: BlockContext,
/// Chunk Context
pub chunk_ctx: ChunkContext,
/// Chunk Contexts. This is intended to be mimicking a stack with current
/// context available through `chunk_ctxs.last()` and `chunk_ctxs.last_mut()` .
pub chunk_ctxs: Vec<ChunkContext>,
}

impl<'a, C: CircuitsParams> CircuitInputBuilder<C> {
/// Create a new CircuitInputBuilder from the given `eth_block` and
/// `constants`.
pub fn new(sdb: StateDB, code_db: CodeDB, block: Block, params: C) -> Self {
let total_rw_count: usize = block.txs.iter().map(|tx| tx.steps().len()).sum();
let chunk_size = 42;
let total_chunks = (total_rw_count + chunk_size - 1) / chunk_size; // div_ceil is nightly only.
// This wont work, because we don't really know how many chunks we will have.
let chunk_ctxs = (0..total_chunks).map(|i| ChunkContext::new(i, total_chunks)).collect_vec();
Self {
sdb,
code_db,
block,
circuits_params: params,
block_ctx: BlockContext::new(),
// TODO support multiple chunk
chunk_ctx: ChunkContext::new_one_chunk(),
chunk_ctxs,
}
}

Expand All @@ -168,7 +178,7 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder<C> {
code_db: &mut self.code_db,
block: &mut self.block,
block_ctx: &mut self.block_ctx,
chunk_ctx: &mut self.chunk_ctx,
chunk_ctxs: &mut self.chunk_ctxs,
tx,
tx_ctx,
}
Expand Down Expand Up @@ -237,8 +247,8 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder<C> {
is_last_tx: bool,
tx_index: u64,
) -> Result<(), Error> {
let mut tx = self.new_tx(tx_index, eth_tx, !geth_trace.failed)?;
let mut tx_ctx = TransactionContext::new(eth_tx, geth_trace, is_last_tx)?;
let mut tx: Transaction = self.new_tx(tx_index, eth_tx, !geth_trace.failed)?;
let mut tx_ctx: TransactionContext = TransactionContext::new(eth_tx, geth_trace, is_last_tx)?;

// Generate BeginTx step
let begin_tx_step = gen_associated_steps(
Expand All @@ -247,10 +257,38 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder<C> {
)?;
tx.steps_mut().push(begin_tx_step);

let state_ref = self.state_ref(&mut tx, &mut tx_ctx);
let _block_rwc: usize = state_ref.block_ctx.rwc.into();
let _tx_rwc: usize = tx.steps().last().unwrap().rwc.into();
// let tx_rwc_inner = tx.steps().last().unwrap().rwc_inner_chunk.into();
let _tx_rwc_inner = 42; // tx.steps().last().unwrap().rwc_inner_chunk.into();
//dbg!("handle_tx", block_rwc, tx_rwc, tx_rwc_inner);
// what is the correct chunking condition?
// let chunk_condition = MAX_CHUNK_SIZE < tx_rwc_inner;
let chunk_condition = true;
if chunk_condition {
// Here we are inside the tx where we can inject an `EndChunk` `BeginChunk` pair if
// global `RWCounter` is too high. If we do it before the loop, we are
// always chunking in the beginning of a transaction, which may not be
// optimal.
let mut state_ref = self.state_ref(&mut tx, &mut tx_ctx);
log::trace!("Chunk :: injecting `EndChunk` `BeginChunk` sequence.");
let end_step = state_ref.new_end_chunk_step();
let begin_step = state_ref.new_begin_chunk_step();
state_ref.tx.steps_mut().extend([end_step, begin_step]);

// push chunk_ctx
log::trace!("Chunk :: pushing `ChunkCtx`");
let chunk_index = state_ref.chunk_ctxs.len();
let total_chunks = state_ref.chunk_ctxs.len()+1;
let ctx: ChunkContext = ChunkContext::new(chunk_index, total_chunks);
state_ref.push_chunk_ctx(ctx);
}

for (index, geth_step) in geth_trace.struct_logs.iter().enumerate() {
let mut state_ref = self.state_ref(&mut tx, &mut tx_ctx);
log::trace!("handle {}th opcode {:?} ", index, geth_step.op);
let exec_steps = gen_associated_ops(
let exec_steps: Vec<ExecStep> = gen_associated_ops(
&geth_step.op,
&mut state_ref,
&geth_trace.struct_logs[index..],
Expand Down Expand Up @@ -289,7 +327,7 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder<C> {
.collect::<Vec<RWCounter>>();
// just bump rwc in chunk_ctx as block_ctx rwc to assure same delta apply
let rw_counters_inner_chunk = (0..tags.len())
.map(|_| self.chunk_ctx.rwc.inc_pre())
.map(|_| self.chunk_ctxs.last_mut().unwrap().rwc.inc_pre())
.collect::<Vec<RWCounter>>();
let mut begin_chunk = self.block.block_steps.begin_chunk.clone();
let state = self.state_ref(&mut dummy_tx, &mut dummy_tx_ctx);
Expand Down Expand Up @@ -334,9 +372,9 @@ impl CircuitInputBuilder<FixedCParams> {
let mut end_block_last = self.block.block_steps.end_block_last.clone();
end_block_not_last.rwc = self.block_ctx.rwc;
end_block_last.rwc = self.block_ctx.rwc;
end_block_not_last.rwc_inner_chunk = self.chunk_ctx.rwc;
end_block_last.rwc_inner_chunk = self.chunk_ctx.rwc;
let is_first_chunk = self.chunk_ctx.chunk_index == 0;
end_block_not_last.rwc_inner_chunk = RWCounter::default();
end_block_last.rwc_inner_chunk = RWCounter::default();
let is_first_chunk = self.chunk_ctxs.len()==1;

let mut dummy_tx = Transaction::default();
let mut dummy_tx_ctx = TransactionContext::default();
Expand Down Expand Up @@ -422,7 +460,7 @@ impl<C: CircuitsParams> CircuitInputBuilder<C> {
eth_block: &EthBlock,
geth_traces: &[eth_types::GethExecTrace],
) -> Result<(), Error> {
if self.chunk_ctx.chunk_index > 0 {
if self.chunk_ctxs.len() > 0 {
self.set_begin_chunk();
}

Expand Down Expand Up @@ -516,7 +554,7 @@ impl CircuitInputBuilder<DynamicCParams> {
block: self.block,
circuits_params: c_params,
block_ctx: self.block_ctx,
chunk_ctx: self.chunk_ctx,
chunk_ctxs: self.chunk_ctxs,
};

cib.set_end_block(c_params.max_rws);
Expand Down
6 changes: 5 additions & 1 deletion bus-mapping/src/circuit_input_builder/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ impl Block {
);
}

// let ipb_txs: Vec<super::transaction::Transaction> =
// eth_block.transactions.iter().map(Transaction::from).collect();
let ipb_txs = vec![];

Ok(Self {
chain_id,
history_hashes,
Expand All @@ -130,7 +134,7 @@ impl Block {
base_fee: eth_block.base_fee_per_gas.unwrap_or_default(),
prev_state_root,
container: OperationContainer::new(),
txs: Vec::new(),
txs: ipb_txs,
block_steps: BlockSteps {
begin_chunk: ExecStep {
exec_state: ExecState::BeginChunk,
Expand Down
202 changes: 202 additions & 0 deletions bus-mapping/src/circuit_input_builder/chunk_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
//! Development plan:
//!
//! - [ ] Copmpute the right RWC logic. How should `total_rwc_count` be calcualted?
//! - [ ] Instantiate [`new_begin_chunk_step`] and [`new_end_chunk_step`] with right values.
//! - [ ] Print correct chunk number.
//! - [ ] Express the correct [`chunk_condition`] in [`circuit_input_builder`].
//! - [ ] Enable all disable tests and TODOs and variables starting with underscore.
//! - [ ] `cargo clippy`
//!
//! ------------------------------
//!
//! This module tests the chunking mechanism and produces inspectable print-outs.
//!
//! This test implements (WIP) the following structure:
//!
//! | chunk0 | chunk1 | chunk2 | chunk3 |
//! | tx0 | tx1 | tx2 | tx3 | tx4 | tx5 | tx6 | tx7 | tx8 | tx9 | tx10 | tx11 |
//! | block0 | block1 |
//!
//! To run this test:
//!
//! cargo test -p bus-mapping -- --nocapture test_chunk
//!
//! or:
//!
//! cargo nextest run --no-capture -- test_chunk
//!
//! Properties, that we can test:
//! 1. Chunk boundaries must not coincide with transaction boundaries. This means that we can
//! never have:
//! 1.1. `ExecStep::BeginChunk` followed by `ExecState::BeginTx`(maybe preceded
//! by `ExecState::EndChunk`)
//! 1.2 `ExecState::EndTx` followed by
//! `ExecState::EndChunk` (and then maybe folowed by `ExecState::BeginChunk`).
//! 2. The first (0th) chunk is not initiated with `BeginChunk`.
//! 3. The last (`total_chunk`th) chunk is not terminated by `EndChunk`.
//! 4. COUNT(`BeginChunk`) == COUNT(`EndChunk`) == `total_chunks - 1`.
//! 5. `BeginChunk` and `EndChunk` must alternate, i.e. there must never be two occurences of
//! one of them without the other inbetween.
//! 6. The sequence in 5. must start with `EndChunk` and end with `BeginChunk`.
//! 7. Between any pair of `BeginChunk`and `EndChunk` there is at least one `BeginTx`` and one
//! `EndTx``. (Is this always true or can we have mega transactions that emcompasses a whole
//! chunk? )

use crate::{
circuit_input_builder::{ChunkContext, ExecState, MAX_CHUNK_SIZE},
mock::{self},
};
use ::mock::{eth, TestContext};
use eth_types::{address, bytecode, geth_types::GethData, ToWord, Word};

#[test]
fn test_chunk() {
//! Create single block with 1024 transactions.
//! Check that correctly create four chunks of [300, 300, 300, 124] transactions respectively.
pub const N_TRANSACTIONS: usize = 3;
pub const TOTAL_CHUNK_COUNT: usize = (N_TRANSACTIONS + MAX_CHUNK_SIZE - 1) / MAX_CHUNK_SIZE;
let gas_cost = Word::from(53000);
let required_gas = (gas_cost * N_TRANSACTIONS).try_into().unwrap();

// TODO: The following is just random and may be removed.
let _code_b = bytecode! {
PUSH1(0x10)
JUMP
STOP
};
let code_a = bytecode! {
PUSH1(0x0) // retLength
PUSH1(0x0) // retOffset
PUSH1(0x0) // argsLength
PUSH1(0x0) // argsOffset
PUSH32(address!("0x000000000000000000000000000000000cafe001").to_word()) // addr
PUSH32(0x1_0000) // gas
STATICCALL
PUSH2(0xaa)
};
let test_ctx = TestContext::<2, N_TRANSACTIONS>::new(
None,
|accs| {
accs[0]
.address(address!("0x0000000000000000000000000000000000000000"))
.balance(eth(0))
.code(code_a);
accs[1]
.address(address!("0x000000000000000000000000000000000cafe001"))
.balance(eth(required_gas))
.nonce(100);
// .code(code_b);
},
|txs, accs| {
for tx in txs {
tx.from(accs[1].address)
.to(accs[0].address)
.gas(Word::from(gas_cost));
}
},
|block, _tx| block.number(0xcafeu64),
)
.unwrap();

let block: GethData = test_ctx.into();
let block_data = mock::BlockData::new_from_geth_data(block.clone());
let builder = mock::BlockData::new_circuit_input_builder(&block_data);
let fixed_builder = builder
.handle_block(&block.eth_block, &block.geth_traces)
.unwrap();
let chunk_ctxs: Vec<ChunkContext> = fixed_builder.chunk_ctxs;
let txs = fixed_builder.block.txs();
// This is wrong. How do we distinguish RWops from other ops?
let total_rw_count: usize = txs.iter().map(|tx| tx.steps().len()).sum();

// Verify the amount of chunks generated
assert_ne!(chunk_ctxs.len(), 0, "No `chunk_ctxs` were generated.");
// TODO: assert_eq!(TOTAL_CHUNK_COUNT, chunk_ctxs.len());

// Sanity checks
assert_ne!(txs.len(), 0, "Some transactions needed to generate table.");
assert_ne!(total_rw_count, 0, "Some RW ops needed to chunk.");


// Verify the RWCounter of each block.
for current_chunk_ctx in chunk_ctxs.iter() {
assert!(usize::from(current_chunk_ctx.rwc) < MAX_CHUNK_SIZE);
}

// Create a list of tripples: (op#, tx#, ExecStep)
let exec_steps = txs
.iter()
.enumerate()
.flat_map(|(txN, tx)| tx.steps().iter().map(move |step| (txN, step)))
.enumerate()
.map(|(i, (txN, step))| (i, txN, step));

let curr_tx = txs.iter();
let curr_chunk_ctx = chunk_ctxs.iter();

let mut begin_chunk_count = 0;
let mut end_chunk_count = 0;
// We must start with matching `EndChunk` so we initialize with `BeginChunk`.
let mut prev_chunk_label = ExecState::BeginChunk;

// Print each ExecStep as a row in a table with associated tx number, chunk number and maybe
// rwc, rwc_inner counter.
let es_width = 16;
let width = 13;
let table_header = format!(
"│{est:^width$}│{esta:^es_width$}│{tx:^width$}│{ch:^width$}│{rwc:^width$}│{rwci:^width$}│",
est = "Op#",
esta = "ExecState",
tx = "Transaction",
ch = "Chunk",
rwc = "RWC",
rwci = "RWC_inner",
);
let e = "─".repeat(width);
let table_top = format!(
"╭{e:-^width$}┬{e:─^es_width$}┬{e:─^width$}┬{e:^width$}┬{e:^width$}┬{e:^width$}╮",
);
let table_bottom =
format!("╰{e:-^width$}┴{e:─^es_width$}┴{e:─^width$}┴{e:^width$}┴{e:^width$}┴{e:^width$}╯",);
let table_header_separator =
format!("├{e:-^width$}┼{e:─^es_width$}┼{e:─^width$}┼{e:^width$}┼{e:^width$}┼{e:^width$}┤",);
println!("{table_top}");
println!("{table_header}");
println!("{table_header_separator}");
for (ops,tx, es) in exec_steps {
let exec_str = format!("{:?}", es.exec_state);
println!(
"│{opN:>width$}│{esta:<es_width$}│{tx:>width$}│{ch:>width$}│{rwc:>width$}│{rwci:>width$}│",
opN = ops,
esta = exec_str,
tx = tx,
ch = 42,
rwc = usize::from(es.rwc),
rwci = usize::from(es.rwc_inner_chunk),
);

match (es.exec_state.to_owned(), prev_chunk_label.to_owned()) {
(ExecState::BeginChunk, ExecState::EndChunk) => {
begin_chunk_count += 1;
prev_chunk_label = es.exec_state.to_owned();
}
(ExecState::BeginChunk, _) => assert!(false, "Property 2 or 5 violated."),
(ExecState::EndChunk, ExecState::BeginChunk) => {
begin_chunk_count += 1;
prev_chunk_label = es.exec_state.to_owned();
}
(ExecState::EndChunk, _) => (), // assert!(false, "Property 2 or 5 violated."),
_ => (),
}
}
println!("{table_bottom}");
// assert_eq!(
// prev_chunk_label,
// ExecState::EndChunk,
// "Property 6 violated."
// );
//
// Property 4.
// assert_eq!(begin_chunk_count, TOTAL_CHUNK_COUNT - 1);
// assert_eq!(end_chunk_count, TOTAL_CHUNK_COUNT - 1);
}
Loading
Loading