Skip to content

Commit 95bfa33

Browse files
Use Batching Query Public Inputs for Tabular Queries + Remove Old Circuits (#417)
This PR cleans up the circuit code in `verifiable-db` crate. In particular - Rather than keeping 2 versions of query circuits (batched and non-batched) in the codebase, the old non-batched circuits, which will become useless when batching circuits will be integrated, are removed - To avoid having to deal with 2 different set of public inputs in query circuits API, the main change of this PR is making tabular queries circuits compatible with the set of public inputs exposed by batching circuits. This will yield some unnecessary public inputs for tabular queries, but it will allow to keep a single API to generate query proofs - As a consequence of the removal of the old non-batched circuits, also the integration test code for queries can be greatly simplified --------- Co-authored-by: T <[email protected]>
1 parent 8c1455d commit 95bfa33

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+3507
-10002
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

groth16-framework/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ itertools.workspace = true
2323
rand.workspace = true
2424
serial_test.workspace = true
2525
sha2.workspace = true
26+
mp2_test = { path = "../mp2-test" }
2627

2728
recursion_framework = { path = "../recursion-framework" }
2829
verifiable-db = { path = "../verifiable-db" }

groth16-framework/tests/common/context.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
use super::{NUM_PREPROCESSING_IO, NUM_QUERY_IO};
44
use groth16_framework::{compile_and_generate_assets, utils::clone_circuit_data};
55
use mp2_common::{C, D, F};
6+
use mp2_test::circuit::TestDummyCircuit;
67
use recursion_framework::framework_testing::TestingRecursiveCircuits;
78
use verifiable_db::{
89
api::WrapCircuitParams,
10+
query::pi_len,
911
revelation::api::Parameters as RevelationParameters,
1012
test_utils::{
1113
INDEX_TREE_MAX_DEPTH, MAX_NUM_COLUMNS, MAX_NUM_ITEMS_PER_OUTPUT, MAX_NUM_OUTPUTS,
@@ -40,6 +42,8 @@ impl TestContext {
4042

4143
// Generate a fake query circuit set.
4244
let query_circuits = TestingRecursiveCircuits::<F, C, D, NUM_QUERY_IO>::default();
45+
let dummy_universal_circuit =
46+
TestDummyCircuit::<{ pi_len::<MAX_NUM_ITEMS_PER_OUTPUT>() }>::build();
4347

4448
// Create the revelation parameters.
4549
let revelation_params = RevelationParameters::<
@@ -53,7 +57,7 @@ impl TestContext {
5357
MAX_NUM_PLACEHOLDERS,
5458
>::build(
5559
query_circuits.get_recursive_circuit_set(), // unused, so we provide a dummy one
56-
query_circuits.get_recursive_circuit_set(),
60+
dummy_universal_circuit.circuit_data().verifier_data(),
5761
preprocessing_circuits.get_recursive_circuit_set(),
5862
preprocessing_circuits
5963
.verifier_data_for_input_proofs::<1>()

groth16-framework/tests/common/query.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,7 @@ impl TestContext {
5656
.unwrap();
5757
let revelation_proof = self
5858
.revelation_params
59-
.generate_proof(
60-
input,
61-
self.query_circuits.get_recursive_circuit_set(),
62-
self.query_circuits.get_recursive_circuit_set(),
63-
None,
64-
)
59+
.generate_proof(input, self.query_circuits.get_recursive_circuit_set(), None)
6560
.unwrap();
6661
let revelation_proof = ProofWithVK::deserialize(&revelation_proof).unwrap();
6762
let (revelation_proof_with_pi, _) = revelation_proof.clone().into();

mp2-common/src/eth.rs

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ mod test {
286286
types::MAX_BLOCK_LEN,
287287
utils::{Endianness, Packer},
288288
};
289-
use mp2_test::eth::{get_mainnet_url, get_sepolia_url};
289+
use mp2_test::eth::get_sepolia_url;
290290

291291
#[tokio::test]
292292
#[ignore]
@@ -426,39 +426,6 @@ mod test {
426426
Ok(())
427427
}
428428

429-
#[tokio::test]
430-
async fn test_pidgy_pinguin_mapping_slot() -> Result<()> {
431-
// first pinguin holder https://dune.com/queries/2450476/4027653
432-
// holder: 0x188b264aa1456b869c3a92eeed32117ebb835f47
433-
// NFT id https://opensea.io/assets/ethereum/0xbd3531da5cf5857e7cfaa92426877b022e612cf8/1116
434-
let mapping_value =
435-
Address::from_str("0x188B264AA1456B869C3a92eeeD32117EbB835f47").unwrap();
436-
let nft_id: u32 = 1116;
437-
let mapping_key = left_pad32(&nft_id.to_be_bytes());
438-
let url = get_mainnet_url();
439-
let provider = ProviderBuilder::new().on_http(url.parse().unwrap());
440-
441-
// extracting from
442-
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
443-
// assuming it's using ERC731Enumerable that inherits ERC721
444-
let mapping_slot = 2;
445-
// pudgy pinguins
446-
let pudgy_address = Address::from_str("0xBd3531dA5CF5857e7CfAA92426877b022e612cf8")?;
447-
let query = ProofQuery::new_mapping_slot(pudgy_address, mapping_slot, mapping_key.to_vec());
448-
let res = query
449-
.query_mpt_proof(&provider, BlockNumberOrTag::Latest)
450-
.await?;
451-
let raw_address = ProofQuery::verify_storage_proof(&res)?;
452-
// the value is actually RLP encoded !
453-
let decoded_address: Vec<u8> = rlp::decode(&raw_address).unwrap();
454-
let leaf_node: Vec<Vec<u8>> = rlp::decode_list(res.storage_proof[0].proof.last().unwrap());
455-
println!("leaf_node[1].len() = {}", leaf_node[1].len());
456-
// this is read in the same order
457-
let found_address = Address::from_slice(&decoded_address.into_iter().collect::<Vec<u8>>());
458-
assert_eq!(found_address, mapping_value);
459-
Ok(())
460-
}
461-
462429
#[tokio::test]
463430
async fn test_kashish_contract_proof_query() -> Result<()> {
464431
// https://sepolia.etherscan.io/address/0xd6a2bFb7f76cAa64Dad0d13Ed8A9EFB73398F39E#code

mp2-v1/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,3 @@ parsil = { path = "../parsil" }
5959

6060
[features]
6161
original_poseidon = ["mp2_common/original_poseidon"]
62-
batching_circuits = ["verifiable-db/batching_circuits"]

mp2-v1/src/query/batching_planner.rs

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ use ryhope::{
1010
storage::{updatetree::UpdateTree, WideLineage},
1111
Epoch,
1212
};
13+
use serde::{Deserialize, Serialize};
1314
use verifiable_db::query::{
14-
batching::{NodePath, RowInput, TreePathInputs},
15+
api::{NodePath, RowInput, TreePathInputs},
1516
computational_hash_ids::ColumnIDs,
1617
universal_circuit::universal_circuit_inputs::{ColumnCell, RowCells},
1718
};
@@ -118,54 +119,62 @@ async fn generate_chunks<const CHUNK_SIZE: usize, C: ContextProvider>(
118119
.cloned()
119120
.collect::<BTreeSet<_>>();
120121

121-
Ok(stream::iter(sorted_index_values.into_iter())
122-
.then(async |index_value| {
123-
let index_path = index_cache
124-
.compute_path(&index_value, current_epoch)
122+
let prove_rows = async |index_value| {
123+
let index_path = index_cache
124+
.compute_path(&index_value, current_epoch)
125+
.await
126+
.unwrap_or_else(|| panic!("node with key {index_value} not found in index tree cache"));
127+
let proven_rows = if let Some(matching_rows) =
128+
row_keys_by_epochs.get(&(index_value as Epoch))
129+
{
130+
let sorted_rows = matching_rows.iter().collect::<BTreeSet<_>>();
131+
stream::iter(sorted_rows.iter())
132+
.then(async |&row_key| {
133+
compute_input_for_row(&row_cache, row_key, index_value, &index_path, column_ids)
134+
.await
135+
})
136+
.collect::<Vec<RowInput>>()
137+
.await
138+
} else {
139+
let proven_node = non_existence_inputs
140+
.find_row_node_for_non_existence(index_value)
125141
.await
126-
.unwrap_or_else(|| {
127-
panic!("node with key {index_value} not found in index tree cache")
142+
.unwrap_or_else(|_| {
143+
panic!("node for non-existence not found for index value {index_value}")
128144
});
129-
let proven_rows =
130-
if let Some(matching_rows) = row_keys_by_epochs.get(&(index_value as Epoch)) {
131-
let sorted_rows = matching_rows.iter().collect::<BTreeSet<_>>();
132-
stream::iter(sorted_rows.iter())
133-
.then(async |&row_key| {
134-
compute_input_for_row(
135-
&row_cache,
136-
row_key,
137-
index_value,
138-
&index_path,
139-
column_ids,
140-
)
141-
.await
142-
})
143-
.collect::<Vec<RowInput>>()
144-
.await
145-
} else {
146-
let proven_node = non_existence_inputs
147-
.find_row_node_for_non_existence(index_value)
148-
.await
149-
.unwrap_or_else(|_| {
150-
panic!("node for non-existence not found for index value {index_value}")
151-
});
152-
let row_input = compute_input_for_row(
153-
non_existence_inputs.row_tree,
154-
&proven_node,
155-
index_value,
156-
&index_path,
157-
column_ids,
158-
)
159-
.await;
160-
vec![row_input]
161-
};
162-
proven_rows
163-
})
164-
.concat()
165-
.await
145+
let row_input = compute_input_for_row(
146+
non_existence_inputs.row_tree,
147+
&proven_node,
148+
index_value,
149+
&index_path,
150+
column_ids,
151+
)
152+
.await;
153+
vec![row_input]
154+
};
155+
proven_rows
156+
};
157+
158+
// TODO: This implementation causes an error in DQ:
159+
// `implementation of `std::marker::Send` is not general enough`
160+
/*
161+
let chunks = stream::iter(sorted_index_values.into_iter())
162+
.then(prove_rows)
163+
.concat()
164+
.await
165+
*/
166+
let mut chunks = vec![];
167+
for index_value in sorted_index_values {
168+
let chunk = prove_rows(index_value).await;
169+
chunks.extend(chunk);
170+
}
171+
172+
let chunks = chunks
166173
.chunks(CHUNK_SIZE)
167174
.map(|chunk| chunk.to_vec())
168-
.collect_vec())
175+
.collect_vec();
176+
177+
Ok(chunks)
169178
}
170179

171180
/// Key for nodes of the `UTForChunks<NUM_CHUNKS>` employed to
@@ -195,8 +204,10 @@ async fn generate_chunks<const CHUNK_SIZE: usize, C: ContextProvider>(
195204
///
196205
/// (2,0) (2,1) (2,2) (2,3) (2,4)
197206
/// ```
198-
#[derive(Clone, Debug, Hash, Eq, PartialEq, Default)]
199-
pub struct UTKey<const ARITY: usize>((usize, usize));
207+
#[derive(
208+
Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash, Serialize, Deserialize,
209+
)]
210+
pub struct UTKey<const ARITY: usize>(pub (usize, usize));
200211

201212
impl<const ARITY: usize> UTKey<ARITY> {
202213
/// Compute the key of the child node of `self` that has `num_left_siblings`
@@ -318,15 +329,13 @@ impl<const ARITY: usize> ProvingTree<ARITY> {
318329
let num_childrens = parent_node.children_keys.len();
319330
let new_child_key = parent_key.children_key(num_childrens);
320331
let child_node = ProvingTreeNode {
321-
parent_key: Some(parent_key.clone()),
332+
parent_key: Some(*parent_key),
322333
children_keys: vec![],
323334
};
324335
// insert new child in the set of children of the parent
325-
parent_node.children_keys.push(new_child_key.clone());
336+
parent_node.children_keys.push(new_child_key);
326337
assert!(
327-
self.nodes
328-
.insert(new_child_key.clone(), child_node)
329-
.is_none(),
338+
self.nodes.insert(new_child_key, child_node).is_none(),
330339
"Node with key {:?} already found in the tree",
331340
new_child_key
332341
);
@@ -339,7 +348,7 @@ impl<const ARITY: usize> ProvingTree<ARITY> {
339348
};
340349
let root_key = UTKey((0, 0));
341350
assert!(
342-
self.nodes.insert(root_key.clone(), root).is_none(),
351+
self.nodes.insert(root_key, root).is_none(),
343352
"Error: root node inserted multiple times"
344353
);
345354
root_key
@@ -412,7 +421,7 @@ impl<const ARITY: usize> ProvingTree<ARITY> {
412421
while node_key.is_some() {
413422
// place node key in the path
414423
let key = node_key.unwrap();
415-
path.push(key.clone());
424+
path.push(*key);
416425
// fetch key of the parent node, if any
417426
node_key = self
418427
.nodes
@@ -449,7 +458,7 @@ impl<const NUM_CHUNKS: usize> UTForChunksBuilder<NUM_CHUNKS> {
449458
let path = tree.compute_path_for_leaf(node_index);
450459
(
451460
(
452-
path.last().unwrap().clone(), // chunk node is always a leaf of the tree, so it is the last node
461+
*path.last().unwrap(), // chunk node is always a leaf of the tree, so it is the last node
453462
// in the path
454463
chunk,
455464
),

mp2-v1/src/query/planner.rs

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use ryhope::{
1919
use std::{fmt::Debug, future::Future};
2020
use tokio_postgres::{row::Row as PsqlRow, types::ToSql, NoTls};
2121
use verifiable_db::query::{
22-
aggregation::{ChildPosition, NodeInfo, QueryBounds},
23-
batching::TreePathInputs,
22+
api::TreePathInputs,
23+
utils::{ChildPosition, NodeInfo, QueryBounds},
2424
};
2525

2626
use crate::indexing::{
@@ -65,7 +65,7 @@ impl<'a, C: ContextProvider> NonExistenceInput<'a, C> {
6565
}
6666
}
6767

68-
pub(crate) async fn find_row_node_for_non_existence(
68+
pub async fn find_row_node_for_non_existence(
6969
&self,
7070
primary: BlockPrimaryIndex,
7171
) -> anyhow::Result<RowTreeKey> {
@@ -375,50 +375,6 @@ impl<
375375
}
376376
}
377377

378-
/// Returns the proving plan to prove the non existence of node of the query in this row tree at
379-
/// the epoch primary. It also returns the leaf node chosen.
380-
///
381-
/// The row tree is given and specialized to psql storage since that is the only official storage
382-
/// supported.
383-
/// The `table_name` must be the one given to parsil settings, it is the human friendly table
384-
/// name, i.e. the vTable name.
385-
/// The pool is to issue specific query
386-
/// Primary is indicating the primary index over which this row tree is looked at.
387-
/// Settings are the parsil settings corresponding to the current SQL and current table looked at.
388-
/// Pis contain the bounds and placeholders values.
389-
/// TODO: we should extend ryhope to offer this API directly on the tree since it's very related.
390-
pub async fn proving_plan_for_non_existence<C>(
391-
row_tree: &MerkleTreeKvDb<RowTree, RowPayload<BlockPrimaryIndex>, DBRowStorage>,
392-
table_name: String,
393-
pool: &DBPool,
394-
primary: BlockPrimaryIndex,
395-
settings: &ParsilSettings<C>,
396-
bounds: &QueryBounds,
397-
) -> anyhow::Result<(RowTreeKey, UpdateTree<RowTreeKey>)>
398-
where
399-
C: ContextProvider,
400-
{
401-
let to_be_proven_node = {
402-
let input = NonExistenceInput {
403-
row_tree,
404-
table_name,
405-
pool,
406-
settings,
407-
bounds: bounds.clone(),
408-
};
409-
input.find_row_node_for_non_existence(primary).await
410-
}?;
411-
412-
let path = row_tree
413-
// since the epoch starts at genesis we can directly give the block number !
414-
.lineage_at(&to_be_proven_node, primary as Epoch)
415-
.await
416-
.expect("node doesn't have a lineage?")
417-
.into_full_path()
418-
.collect_vec();
419-
let proving_tree = UpdateTree::from_paths([path], primary as Epoch);
420-
Ok((to_be_proven_node.clone(), proving_tree))
421-
}
422378
/// Fetch a key `k` from a tree, assuming that the key is in the
423379
/// tree. Therefore, it handles differently the case when `k` is not found:
424380
/// - If `T::WIDE_LINEAGE` is true, then `k` might not be found because the

mp2-v1/tests/common/cases/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use super::table::Table;
1111

1212
pub mod contract;
1313
pub mod indexing;
14-
pub mod planner;
1514
pub mod query;
1615
pub mod table_source;
1716

0 commit comments

Comments
 (0)