use axiom_codec::{utils::native::encode_addr_to_field, HiLo};
use axiom_eth::{
halo2_base::{
gates::{flex_gate::threads::parallelize_core, GateInstructions},
safe_types::SafeTypeChip,
AssignedValue, Context,
},
halo2_proofs::plonk::ConstraintSystem,
keccak::{types::ComponentTypeKeccak, KeccakChip},
mpt::MPTChip,
rlc::circuit::builder::RlcCircuitBuilder,
rlc::circuit::builder::RlcContextPair,
rlp::RlpChip,
storage::{EthStorageChip, EthStorageWitness},
utils::{
build_utils::aggregation::CircuitMetadata,
circuit_utils::bytes::{
pack_bytes_to_hilo, safe_bytes32_to_hi_lo, unsafe_mpt_root_to_hi_lo,
},
component::{
circuit::{
ComponentBuilder, ComponentCircuitImpl, CoreBuilder, CoreBuilderOutput,
CoreBuilderOutputParams, CoreBuilderParams,
},
promise_collector::PromiseCaller,
promise_loader::{combo::PromiseBuilderCombo, single::PromiseLoader},
types::LogicalEmpty,
utils::create_hasher,
LogicalResult,
},
constrain_vec_equal, unsafe_bytes_to_assigned,
},
};
use serde::{Deserialize, Serialize};
use crate::{
components::subqueries::{
account::{
types::{ComponentTypeAccountSubquery, FieldAccountSubqueryCall},
STORAGE_ROOT_INDEX,
},
common::{extract_logical_results, extract_virtual_table},
},
utils::codec::{
AssignedAccountSubquery, AssignedStorageSubquery, AssignedStorageSubqueryResult,
},
Field,
};
use super::types::{
CircuitInputStorageShard, CircuitInputStorageSubquery, ComponentTypeStorageSubquery,
};
pub struct CoreBuilderStorageSubquery<F: Field> {
input: Option<CircuitInputStorageShard<F>>,
params: CoreParamsStorageSubquery,
payload: Option<(KeccakChip<F>, Vec<PayloadStorageSubquery<F>>)>,
}
#[derive(Clone, Default, Serialize, Deserialize)]
pub struct CoreParamsStorageSubquery {
pub capacity: usize,
pub max_trie_depth: usize,
}
impl CoreBuilderParams for CoreParamsStorageSubquery {
fn get_output_params(&self) -> CoreBuilderOutputParams {
CoreBuilderOutputParams::new(vec![self.capacity])
}
}
type CKeccak<F> = ComponentTypeKeccak<F>;
type CAccount<F> = ComponentTypeAccountSubquery<F>;
pub type PromiseLoaderStorageSubquery<F> =
PromiseBuilderCombo<F, PromiseLoader<F, CKeccak<F>>, PromiseLoader<F, CAccount<F>>>;
pub type ComponentCircuitStorageSubquery<F> =
ComponentCircuitImpl<F, CoreBuilderStorageSubquery<F>, PromiseLoaderStorageSubquery<F>>;
impl<F: Field> CircuitMetadata for CoreBuilderStorageSubquery<F> {
const HAS_ACCUMULATOR: bool = false;
fn num_instance(&self) -> Vec<usize> {
unreachable!()
}
}
impl<F: Field> ComponentBuilder<F> for CoreBuilderStorageSubquery<F> {
type Params = CoreParamsStorageSubquery;
fn new(params: Self::Params) -> Self {
Self { input: None, params, payload: None }
}
fn get_params(&self) -> Self::Params {
self.params.clone()
}
fn clear_witnesses(&mut self) {
self.payload = None;
}
fn calculate_params(&mut self) -> Self::Params {
self.params.clone()
}
fn configure_with_params(_: &mut ConstraintSystem<F>, _: Self::Params) {}
}
impl<F: Field> CoreBuilder<F> for CoreBuilderStorageSubquery<F> {
type CompType = ComponentTypeStorageSubquery<F>;
type PublicInstanceValue = LogicalEmpty<F>;
type PublicInstanceWitness = LogicalEmpty<AssignedValue<F>>;
type CoreInput = CircuitInputStorageShard<F>;
fn feed_input(&mut self, input: Self::CoreInput) -> anyhow::Result<()> {
for r in &input.requests {
if r.proof.storage_pfs.len() != 1 {
anyhow::bail!(
"InvalidInput: each storage proof input must have exactly one storage proof"
);
}
if r.proof.storage_pfs[0].2.max_depth != self.params.max_trie_depth {
anyhow::bail!("StorageSubquery: request MPT max depth {} does not match configured max depth {}", r.proof.storage_pfs[0].2.max_depth, self.params.max_trie_depth);
}
}
self.input = Some(input);
Ok(())
}
fn virtual_assign_phase0(
&mut self,
builder: &mut RlcCircuitBuilder<F>,
promise_caller: PromiseCaller<F>,
) -> CoreBuilderOutput<F, Self::CompType> {
let keccak =
KeccakChip::new_with_promise_collector(builder.range_chip(), promise_caller.clone());
let range_chip = keccak.range();
let rlp = RlpChip::new(range_chip, None);
let mut poseidon = create_hasher();
poseidon.initialize_consts(builder.base.main(0), keccak.gate());
let input = self.input.as_ref().unwrap();
let mpt = MPTChip::new(rlp, &keccak);
let chip = EthStorageChip::new(&mpt, None);
let pool = &mut builder.base.pool(0);
let payload = parallelize_core(pool, input.requests.clone(), |ctx, subquery| {
handle_single_storage_subquery_phase0(ctx, &chip, &subquery)
});
let vt = extract_virtual_table(payload.iter().map(|p| p.output));
let lr: Vec<LogicalResult<F, Self::CompType>> =
extract_logical_results(payload.iter().map(|p| p.output));
let ctx = pool.main();
let account_storage_hash_idx = ctx.load_constant(F::from(STORAGE_ROOT_INDEX as u64));
for p in payload.iter() {
let block_number = p.output.subquery.block_number;
let addr = p.output.subquery.addr;
let storage_root = p.storage_root;
let account_subquery =
AssignedAccountSubquery { block_number, addr, field_idx: account_storage_hash_idx };
let promise_storage_root = promise_caller
.call::<FieldAccountSubqueryCall<F>, ComponentTypeAccountSubquery<F>>(
ctx,
FieldAccountSubqueryCall(account_subquery),
)
.unwrap();
constrain_vec_equal(ctx, &storage_root.hi_lo(), &promise_storage_root.hi_lo());
}
self.payload = Some((keccak, payload));
CoreBuilderOutput { public_instances: vec![], virtual_table: vt, logical_results: lr }
}
fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder<F>) {
let (keccak, payload) = self.payload.take().unwrap();
let range_chip = keccak.range();
let rlc_chip = builder.rlc_chip(&range_chip.gate);
let rlp = RlpChip::new(range_chip, Some(&rlc_chip));
let mpt = MPTChip::new(rlp, &keccak);
let chip = EthStorageChip::new(&mpt, None);
builder.parallelize_phase1(payload, |(ctx_gate, ctx_rlc), payload| {
handle_single_storage_subquery_phase1((ctx_gate, ctx_rlc), &chip, payload)
});
}
}
pub struct PayloadStorageSubquery<F: Field> {
pub storage_witness: EthStorageWitness<F>,
pub storage_root: HiLo<AssignedValue<F>>,
pub output: AssignedStorageSubqueryResult<F>,
}
pub fn handle_single_storage_subquery_phase0<F: Field>(
ctx: &mut Context<F>,
chip: &EthStorageChip<F>,
subquery: &CircuitInputStorageSubquery,
) -> PayloadStorageSubquery<F> {
let gate = chip.gate();
let range = chip.range();
let safe = SafeTypeChip::new(range);
let addr = ctx.load_witness(encode_addr_to_field(&subquery.proof.addr));
let (slot, _value, mpt_proof) = subquery.proof.storage_pfs[0].clone();
let unsafe_slot = unsafe_bytes_to_assigned(ctx, slot.as_bytes());
let slot_bytes = safe.raw_bytes_to(ctx, unsafe_slot);
let slot = safe_bytes32_to_hi_lo(ctx, gate, &slot_bytes);
let mpt_proof = mpt_proof.assign(ctx);
let storage_root = unsafe_mpt_root_to_hi_lo(ctx, gate, &mpt_proof);
let storage_witness = chip.parse_storage_proof_phase0(ctx, slot_bytes, mpt_proof);
let value = {
let w = storage_witness.value_witness();
let inputs = w.field_cells.clone();
let len = w.field_len;
let var_len_bytes = SafeTypeChip::unsafe_to_var_len_bytes_vec(inputs, len, 32);
let fixed_bytes = var_len_bytes.left_pad_to_fixed(ctx, gate);
pack_bytes_to_hilo(ctx, gate, fixed_bytes.bytes())
};
let slot_is_empty = storage_witness.mpt_witness().slot_is_empty;
let value = HiLo::from_hi_lo(value.hi_lo().map(|x| gate.mul_not(ctx, slot_is_empty, x)));
let block_number = ctx.load_witness(F::from(subquery.block_number));
PayloadStorageSubquery {
storage_witness,
storage_root,
output: AssignedStorageSubqueryResult {
subquery: AssignedStorageSubquery { block_number, addr, slot },
value,
},
}
}
pub fn handle_single_storage_subquery_phase1<F: Field>(
ctx: RlcContextPair<F>,
chip: &EthStorageChip<F>,
payload: PayloadStorageSubquery<F>,
) {
chip.parse_storage_proof_phase1(ctx, payload.storage_witness);
}