use std::cmp::max;
use ethers_core::{
types::{Block, Chain, Transaction, H256},
utils::hex::FromHex,
};
use halo2_base::{
gates::{
flex_gate::threads::parallelize_core, GateChip, GateInstructions, RangeChip,
RangeInstructions,
},
AssignedValue, Context,
QuantumCell::{Constant, Existing},
};
use itertools::Itertools;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use crate::{
block_header::{
get_block_header_rlp_max_lens, EthBlockHeaderChip, EthBlockHeaderTrace,
EthBlockHeaderWitness,
},
keccak::KeccakChip,
mpt::{MPTChip, MPTInput, MPTProof, MPTProofWitness},
rlc::{
chip::RlcChip,
circuit::builder::{RlcCircuitBuilder, RlcContextPair},
FIRST_PHASE,
},
rlp::{evaluate_byte_array, RlpChip},
utils::circuit_utils::constrain_no_leading_zeros,
Field,
};
#[cfg(all(test, feature = "providers"))]
mod tests;
mod types;
pub use types::*;
lazy_static! {
static ref KECCAK_RLP_EMPTY_STRING: Vec<u8> =
Vec::from_hex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").unwrap();
}
pub const TRANSACTION_MAX_FIELDS: usize = 12;
pub(crate) const TRANSACTION_TYPE_0_FIELDS_MAX_BYTES: [usize; TRANSACTION_MAX_FIELDS] =
[32, 32, 32, 20, 32, 0, 8, 32, 32, 1, 1, 1];
pub(crate) const TRANSACTION_TYPE_1_FIELDS_MAX_BYTES: [usize; TRANSACTION_MAX_FIELDS] =
[8, 32, 32, 32, 20, 32, 0, 0, 1, 32, 32, 1];
pub(crate) const TRANSACTION_TYPE_2_FIELDS_MAX_BYTES: [usize; TRANSACTION_MAX_FIELDS] =
[8, 8, 8, 8, 32, 20, 32, 0, 0, 1, 32, 32];
pub(crate) const TRANSACTION_IDX_MAX_LEN: usize = 2;
pub const TX_IDX_MAX_BYTES: usize = TRANSACTION_IDX_MAX_LEN;
pub fn calc_max_val_len(
max_data_byte_len: usize,
max_access_list_len: usize,
enable_types: [bool; 3],
) -> usize {
let mut t0_len = 2;
let prefix_tot_max = 1 + 4 + 10 + 4 * 2;
let mut field_len_sum = max_data_byte_len;
for field_len in TRANSACTION_TYPE_0_FIELDS_MAX_BYTES {
field_len_sum += field_len;
}
if enable_types[0] {
t0_len = max(t0_len, prefix_tot_max + field_len_sum);
}
field_len_sum = max_data_byte_len + max_access_list_len;
for field_len in TRANSACTION_TYPE_1_FIELDS_MAX_BYTES {
field_len_sum += field_len;
}
if enable_types[1] {
t0_len = max(t0_len, prefix_tot_max + field_len_sum);
}
field_len_sum = max_data_byte_len + max_access_list_len;
for field_len in TRANSACTION_TYPE_2_FIELDS_MAX_BYTES {
field_len_sum += field_len;
}
if enable_types[2] {
t0_len = max(t0_len, prefix_tot_max + field_len_sum);
}
t0_len
}
fn calc_max_field_len(
max_data_byte_len: usize,
max_access_list_len: usize,
enable_types: [bool; 3],
) -> Vec<usize> {
let mut base = vec![0; TRANSACTION_MAX_FIELDS];
base[0] = 1;
for i in 0..TRANSACTION_MAX_FIELDS {
if enable_types[0] {
if i == 5 {
base[i] = max(base[i], max_data_byte_len);
} else {
base[i] = max(base[i], TRANSACTION_TYPE_0_FIELDS_MAX_BYTES[i]);
}
}
if enable_types[1] {
if i == 6 {
base[i] = max(base[i], max_data_byte_len);
} else if i == 7 {
base[i] = max(base[i], max_access_list_len);
} else {
base[i] = max(base[i], TRANSACTION_TYPE_1_FIELDS_MAX_BYTES[i]);
}
}
if enable_types[2] {
if i == 7 {
base[i] = max(base[i], max_data_byte_len);
} else if i == 8 {
base[i] = max(base[i], max_access_list_len);
} else {
base[i] = max(base[i], TRANSACTION_TYPE_2_FIELDS_MAX_BYTES[i]);
}
}
}
base
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Hash, Default)]
pub struct EthTransactionChipParams {
pub max_data_byte_len: usize,
pub max_access_list_len: usize,
pub enable_types: [bool; 3],
pub network: Option<Chain>,
}
#[derive(Clone, Debug)]
pub struct EthTransactionChip<'chip, F: Field> {
pub mpt: &'chip MPTChip<'chip, F>,
pub params: EthTransactionChipParams,
}
impl<'chip, F: Field> EthTransactionChip<'chip, F> {
pub fn new(mpt: &'chip MPTChip<'chip, F>, params: EthTransactionChipParams) -> Self {
Self { mpt, params }
}
pub fn gate(&self) -> &GateChip<F> {
self.mpt.gate()
}
pub fn range(&self) -> &RangeChip<F> {
self.mpt.range()
}
pub fn rlc(&self) -> &RlcChip<F> {
self.mpt.rlc()
}
pub fn rlp(&self) -> RlpChip<F> {
self.mpt.rlp()
}
pub fn keccak(&self) -> &KeccakChip<F> {
self.mpt.keccak()
}
pub fn mpt(&self) -> &'chip MPTChip<'chip, F> {
self.mpt
}
pub fn network(&self) -> Option<Chain> {
self.params.network
}
pub fn block_header_chip(&self) -> EthBlockHeaderChip<F> {
EthBlockHeaderChip::new_from_network(
self.rlp(),
self.network().expect("Must provide network to access block header chip"),
)
}
pub fn parse_transaction_proof_phase0(
&self,
ctx: &mut Context<F>,
input: EthTransactionInputAssigned<F>,
) -> EthTransactionWitness<F> {
let EthTransactionChipParams {
max_data_byte_len, max_access_list_len, enable_types, ..
} = self.params;
let EthTransactionInputAssigned { transaction_index, proof } = input;
let slot_is_empty = proof.slot_is_empty;
let all_disabled = !(enable_types[0] || enable_types[1] || enable_types[2]);
let idx_witness = self.rlp().decompose_rlp_field_phase0(
ctx,
proof.key_bytes.clone(),
TRANSACTION_IDX_MAX_LEN,
);
let tx_idx =
evaluate_byte_array(ctx, self.gate(), &idx_witness.field_cells, idx_witness.field_len);
ctx.constrain_equal(&tx_idx, &transaction_index);
constrain_no_leading_zeros(
ctx,
self.gate(),
&idx_witness.field_cells,
idx_witness.field_len,
);
let mpt_witness = self.mpt.parse_mpt_inclusion_phase0(ctx, proof);
let max_field_lens =
calc_max_field_len(max_data_byte_len, max_access_list_len, enable_types);
if all_disabled {
let one = ctx.load_constant(F::ONE);
ctx.constrain_equal(&slot_is_empty, &one);
}
let type_is_not_zero =
self.range().is_less_than(ctx, mpt_witness.value_bytes[0], Constant(F::from(128)), 8);
let mut tx_type = self.gate().mul(ctx, type_is_not_zero, mpt_witness.value_bytes[0]);
let max_val_len = if all_disabled {
2_usize
} else {
calc_max_val_len(max_data_byte_len, max_access_list_len, enable_types)
};
debug_assert!(max_val_len > 1);
let mut new_value_witness = Vec::with_capacity(max_val_len);
let slot_is_full = self.gate().not(ctx, slot_is_empty);
tx_type = self.gate().mul(ctx, tx_type, slot_is_full);
tx_type = self.gate().sub(ctx, tx_type, slot_is_empty);
for i in 0..max_val_len {
let mut val_byte = self.gate().select(
ctx,
mpt_witness
.value_bytes
.get(i + 1)
.map(|a| Existing(*a))
.unwrap_or(Constant(F::ZERO)),
mpt_witness.value_bytes[i],
type_is_not_zero,
);
val_byte = if i == 0 {
self.gate().select(ctx, val_byte, Constant(F::from(0xc1)), slot_is_full)
} else {
self.gate().mul(ctx, val_byte, slot_is_full)
};
new_value_witness.push(val_byte);
}
let value_witness =
self.rlp().decompose_rlp_array_phase0(ctx, new_value_witness, &max_field_lens, true);
EthTransactionWitness {
transaction_type: tx_type,
idx: tx_idx,
idx_witness,
value_witness,
mpt_witness,
}
}
pub fn parse_transaction_proof_phase1(
&self,
(ctx_gate, ctx_rlc): RlcContextPair<F>,
witness: EthTransactionWitness<F>,
) -> EthTransactionTrace<F> {
self.rlp().decompose_rlp_field_phase1((ctx_gate, ctx_rlc), witness.idx_witness);
self.mpt.parse_mpt_inclusion_phase1((ctx_gate, ctx_rlc), witness.mpt_witness);
let value_trace =
self.rlp().decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.value_witness, true);
let value_trace = value_trace.field_trace;
EthTransactionTrace { transaction_type: witness.transaction_type, value_trace }
}
pub fn parse_transaction_proofs_phase0(
&self,
builder: &mut RlcCircuitBuilder<F>,
input: Vec<EthTransactionInputAssigned<F>>,
) -> Vec<EthTransactionWitness<F>> {
parallelize_core(builder.base.pool(0), input, |ctx, input| {
self.parse_transaction_proof_phase0(ctx, input)
})
}
pub fn parse_transaction_proofs_phase1(
&self,
builder: &mut RlcCircuitBuilder<F>,
transaction_witness: Vec<EthTransactionWitness<F>>,
) -> Vec<EthTransactionTrace<F>> {
builder.parallelize_phase1(transaction_witness, |(ctx_gate, ctx_rlc), witness| {
self.parse_transaction_proof_phase1((ctx_gate, ctx_rlc), witness)
})
}
pub fn parse_transaction_proofs_from_block_phase0(
&self,
builder: &mut RlcCircuitBuilder<F>,
input: EthBlockTransactionsInputAssigned<F>,
) -> EthBlockTransactionsWitness<F> {
let block_witness = {
let ctx = builder.base.main(FIRST_PHASE);
let block_header = input.block_header;
self.block_header_chip().decompose_block_header_phase0(
ctx,
self.keccak(),
&block_header,
)
};
let transactions_root = &block_witness.get_transactions_root().field_cells;
let transaction_witness = {
parallelize_core(builder.base.pool(FIRST_PHASE), input.tx_inputs, |ctx, input| {
let witness = self.parse_transaction_proof_phase0(ctx, input);
for (pf_byte, byte) in
witness.mpt_witness.root_hash_bytes.iter().zip_eq(transactions_root.iter())
{
ctx.constrain_equal(pf_byte, byte);
}
witness
})
};
let (len, len_witness) = if let Some(len_proof) = input.len_proof {
let ctx = builder.base.main(FIRST_PHASE);
let one = ctx.load_constant(F::ONE);
let is_empty = {
let mut is_empty = one;
let mut empty_hash = Vec::with_capacity(32);
for i in 0..32 {
empty_hash.push(ctx.load_constant(F::from(KECCAK_RLP_EMPTY_STRING[i] as u64)));
}
for (pf_byte, byte) in empty_hash.iter().zip(transactions_root.iter()) {
let byte_match = self.gate().is_equal(ctx, *pf_byte, *byte);
is_empty = self.gate().and(ctx, is_empty, byte_match);
}
is_empty
};
let inclusion_idx = len_proof[0].transaction_index;
let noninclusion_idx = len_proof[1].transaction_index;
let diff = self.gate().sub(ctx, noninclusion_idx, inclusion_idx);
let correct_diff = self.gate().is_equal(ctx, diff, one);
let correct_empty = self.gate().is_zero(ctx, noninclusion_idx);
let correct = self.gate().or(ctx, correct_diff, correct_empty);
ctx.constrain_equal(&correct, &one);
let slot_is_full = self.gate().not(ctx, len_proof[0].proof.slot_is_empty);
let inclusion_constraint =
self.gate().select(ctx, correct_empty, slot_is_full, is_empty);
ctx.constrain_equal(&inclusion_constraint, &one);
ctx.constrain_equal(&len_proof[1].proof.slot_is_empty, &one);
(
Some(noninclusion_idx),
Some(len_proof.map(|tx_input| {
let witness = self.parse_transaction_proof_phase0(ctx, tx_input);
for (pf_byte, byte) in
witness.mpt_witness.root_hash_bytes.iter().zip(transactions_root.iter())
{
ctx.constrain_equal(pf_byte, byte);
}
witness
})),
)
} else {
(None, None)
};
EthBlockTransactionsWitness { block_witness, transaction_witness, len, len_witness }
}
pub fn parse_transaction_proofs_from_block_phase1(
&self,
builder: &mut RlcCircuitBuilder<F>,
witness: EthBlockTransactionsWitness<F>,
) -> EthBlockTransactionsTrace<F> {
let block_trace = self
.block_header_chip()
.decompose_block_header_phase1(builder.rlc_ctx_pair(), witness.block_witness);
let transaction_trace =
self.parse_transaction_proofs_phase1(builder, witness.transaction_witness);
let len_trace = witness
.len_witness
.map(|len_witness| self.parse_transaction_proofs_phase1(builder, len_witness.to_vec()));
EthBlockTransactionsTrace { block_trace, transaction_trace, len: witness.len, len_trace }
}
pub fn parse_transaction_proof_from_block_phase0(
&self,
ctx: &mut Context<F>,
block_header: &[AssignedValue<F>],
tx_input: EthTransactionInputAssigned<F>,
) -> (EthBlockHeaderWitness<F>, EthTransactionWitness<F>) {
let block_witness = self.block_header_chip().decompose_block_header_phase0(
ctx,
self.keccak(),
block_header,
);
let transactions_root = &block_witness.get_transactions_root().field_cells;
let transaction_witness = {
let witness = self.parse_transaction_proof_phase0(ctx, tx_input);
for (pf_byte, byte) in
witness.mpt_witness.root_hash_bytes.iter().zip_eq(transactions_root.iter())
{
ctx.constrain_equal(pf_byte, byte);
}
witness
};
(block_witness, transaction_witness)
}
pub fn parse_transaction_proof_from_block_phase1(
&self,
(ctx_gate, ctx_rlc): RlcContextPair<F>,
block_witness: EthBlockHeaderWitness<F>,
tx_witness: EthTransactionWitness<F>,
) -> (EthBlockHeaderTrace<F>, EthTransactionTrace<F>) {
let block_trace = self
.block_header_chip()
.decompose_block_header_phase1((ctx_gate, ctx_rlc), block_witness);
let transaction_trace =
self.parse_transaction_proof_phase1((ctx_gate, ctx_rlc), tx_witness);
(block_trace, transaction_trace)
}
pub fn extract_field(
&self,
ctx: &mut Context<F>,
witness: EthTransactionWitness<F>,
field_idx: AssignedValue<F>,
) -> EthTransactionFieldWitness<F> {
let field_witness = &witness.value_witness.field_witness;
let slot_is_empty = witness.mpt_witness.slot_is_empty;
let ans_len = field_witness.iter().map(|w| w.field_cells.len()).max().unwrap();
let indicator = self.gate().idx_to_indicator(ctx, field_idx, TRANSACTION_MAX_FIELDS);
assert_eq!(field_witness.len(), TRANSACTION_MAX_FIELDS);
let zero = ctx.load_zero();
ctx.constrain_equal(&slot_is_empty, &zero);
let mut field_bytes = Vec::with_capacity(ans_len);
for i in 0..ans_len {
let entries = field_witness.iter().map(|w| *w.field_cells.get(i).unwrap_or(&zero));
let field_byte = self.gate().select_by_indicator(ctx, entries, indicator.clone());
field_bytes.push(field_byte);
}
let lens = field_witness.iter().map(|w| w.field_len);
let len = self.gate().select_by_indicator(ctx, lens, indicator);
EthTransactionFieldWitness {
transaction_type: witness.transaction_type,
transaction_witness: witness,
field_idx,
field_bytes,
len,
max_len: ans_len,
}
}
}