From e4d556973a08dbb50acfb089ea128d7ba4195b2a Mon Sep 17 00:00:00 2001 From: Vitaly Drogan Date: Thu, 3 Dec 2020 18:42:56 +0200 Subject: [PATCH] seems to work --- Cargo.lock | 3 +- contracts/contracts/ZkSync.sol | 14 +- core/bin/prover/src/bin/dummy_prover.rs | 22 ++- core/bin/prover/src/lib.rs | 4 +- core/bin/zksync_core/src/committer.rs | 62 ++++++--- core/bin/zksync_eth_sender/src/lib.rs | 3 +- core/bin/zksync_witness_generator/src/lib.rs | 22 ++- core/lib/prover_utils/Cargo.toml | 1 + core/lib/prover_utils/src/fs_utils.rs | 15 +++ core/lib/storage/sqlx-data.json | 127 ++++++++---------- core/lib/storage/src/prover/mod.rs | 28 ++-- core/lib/types/src/aggregated_operations.rs | 22 +-- core/lib/types/src/prover.rs | 4 +- .../tests/testkit/src/bin/block_sizes_test.rs | 5 +- core/tests/testkit/src/test_setup.rs | 1 - ...nk-8c6e12e4c-account-32_-balance-11.tar.gz | Bin 15340 -> 18408 bytes 16 files changed, 191 insertions(+), 142 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60e295bfdc..84d52441c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1739,7 +1739,7 @@ dependencies = [ [[package]] name = "franklin-crypto" version = "0.0.5" -source = "git+https://github.com/matter-labs/franklin-crypto.git?branch=beta#40437704ce4557a40e4669b0a768c1d122903ec6" +source = "git+https://github.com/matter-labs/franklin-crypto.git?branch=beta#b39a82a4539f389cf056b3f7cc916f1b7df3447d" dependencies = [ "bellman_ce", "bit-vec", @@ -6115,6 +6115,7 @@ dependencies = [ "log 0.4.11", "reqwest", "serde", + "serde_json", "zksync_basic_types", "zksync_circuit", "zksync_crypto", diff --git a/contracts/contracts/ZkSync.sol b/contracts/contracts/ZkSync.sol index df4dc59d86..fb4c16d375 100644 --- a/contracts/contracts/ZkSync.sol +++ b/contracts/contracts/ZkSync.sol @@ -429,12 +429,7 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { /// @notice Blocks commitment verification. /// @notice Only verifies block commitments without any other processing - function proofBlocks( - StoredBlockInfo[] memory _committedBlocks, - uint256[] memory _commitmentIdxs, - ProofInput memory _proof - ) external nonReentrant { - require(_committedBlocks.length == _commitmentIdxs.length, "pbl1"); + function proofBlocks(StoredBlockInfo[] memory _committedBlocks, ProofInput memory _proof) external nonReentrant { uint32 currentTotalBlocksProofed = totalBlocksProofed; for (uint256 i = 0; i < _committedBlocks.length; ++i) { require( @@ -444,10 +439,7 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { ++currentTotalBlocksProofed; uint256 mask = (~uint256(0)) >> 3; - require( - _proof.commitments[_commitmentIdxs[i]] & mask == uint256(_committedBlocks[i].commitment) & mask, - "pbl3" - ); // incorrect block commitment in proof + require(_proof.commitments[i] & mask == uint256(_committedBlocks[i].commitment) & mask, "pbl3"); // incorrect block commitment in proof } bool success = @@ -485,7 +477,7 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard { totalBlocksCommitted = blocksCommitted; totalCommittedPriorityRequests -= revertedPriorityRequests; - if (totalBlocksCommitted > totalBlocksProofed) { + if (totalBlocksCommitted < totalBlocksProofed) { totalBlocksProofed = totalBlocksCommitted; } diff --git a/core/bin/prover/src/bin/dummy_prover.rs b/core/bin/prover/src/bin/dummy_prover.rs index 6d63b7c2ea..623e740bc3 100644 --- a/core/bin/prover/src/bin/dummy_prover.rs +++ b/core/bin/prover/src/bin/dummy_prover.rs @@ -4,6 +4,7 @@ use std::time::Duration; use zksync_prover::cli_utils::main_for_prover_impl; use zksync_prover::{ApiClient, ProverConfig, ProverImpl}; use zksync_prover_utils::api::{JobRequestData, JobResultData}; +use zksync_prover_utils::fs_utils::{load_correct_aggregated_proof, load_correct_single_proof}; use zksync_utils::get_env; #[derive(Debug)] @@ -36,10 +37,25 @@ impl ProverImpl for DummyProver { fn create_proof(&self, data: JobRequestData) -> Result { let empty_proof = match data { - JobRequestData::AggregatedBlockProof(_) => { - JobResultData::AggregatedBlockProof(Default::default()) + JobRequestData::AggregatedBlockProof(single_proofs) => { + let mut aggregated_proof = load_correct_aggregated_proof() + .expect("Failed to load correct aggregated proof"); + aggregated_proof.individual_vk_inputs = Vec::new(); + for (single_proof, _) in single_proofs { + aggregated_proof + .individual_vk_inputs + .push(single_proof.0.input_values[0]); + aggregated_proof.individual_vk_idxs.push(0); + } + + JobResultData::AggregatedBlockProof(aggregated_proof) + } + JobRequestData::BlockProof(prover_data, _) => { + let mut single_proof = + load_correct_single_proof().expect("Failed to load correct single proof"); + single_proof.0.input_values[0] = prover_data.public_data_commitment; + JobResultData::BlockProof(single_proof) } - JobRequestData::BlockProof(..) => JobResultData::BlockProof(Default::default()), }; Ok(empty_proof) } diff --git a/core/bin/prover/src/lib.rs b/core/bin/prover/src/lib.rs index fd2559f64a..6445008308 100644 --- a/core/bin/prover/src/lib.rs +++ b/core/bin/prover/src/lib.rs @@ -106,9 +106,9 @@ async fn compute_proof_no_blocking( where PROVER: ProverImpl + Send + Sync + 'static, { + let (result_sender, result_receiver) = oneshot::channel(); + let (panic_sender, panic_receiver) = oneshot::channel(); let (mut result_receiver, mut panic_receiver) = { - let (result_sender, result_receiver) = oneshot::channel(); - let (panic_sender, panic_receiver) = oneshot::channel(); std::thread::spawn(move || { // TODO: panic sender should work // std::panic::set_hook(Box::new(|panic_info| { diff --git a/core/bin/zksync_core/src/committer.rs b/core/bin/zksync_core/src/committer.rs index 5a1c1bcdaf..55a18594d5 100644 --- a/core/bin/zksync_core/src/committer.rs +++ b/core/bin/zksync_core/src/committer.rs @@ -8,10 +8,11 @@ use serde::{Deserialize, Serialize}; use tokio::{task::JoinHandle, time}; // Workspace uses use crate::mempool::MempoolRequest; +use zksync_crypto::params::RECURSIVE_CIRCUIT_SIZES; use zksync_storage::{ConnectionPool, StorageProcessor}; use zksync_types::aggregated_operations::{ AggregatedActionType, AggregatedOperation, BlockExecuteOperationArg, BlocksCommitOperation, - BlocksExecuteOperation, BlocksProofOperation, + BlocksCreateProofOperation, BlocksExecuteOperation, BlocksProofOperation, }; use zksync_types::{ block::{Block, ExecutedOperations, PendingBlock}, @@ -286,25 +287,46 @@ async fn create_aggregated_operations(storage: &mut StorageProcessor<'_>) -> any } if last_committed_block > last_aggregate_create_proof_block { - let mut proofs_exits = true; + let mut consecutive_proofs = Vec::new(); for block_number in last_aggregate_create_proof_block + 1..=last_committed_block { - proofs_exits = proofs_exits - && storage - .prover_schema() - .load_proof(block_number) - .await? - .is_some(); - if !proofs_exits { + let proof_exists = storage + .prover_schema() + .load_proof(block_number) + .await? + .is_some(); + if proof_exists { + consecutive_proofs.push(block_number); + } else { break; } } - if proofs_exits { + if consecutive_proofs.len() > 0 { + let aggregate_sizes = RECURSIVE_CIRCUIT_SIZES + .iter() + .map(|(proofs, _)| *proofs) + .collect::>(); + let max_agg_size = *aggregate_sizes + .iter() + .max() + .expect("should be at least one recursive size"); + let agg_size = aggregate_sizes + .into_iter() + .find(|agg_size| *agg_size >= consecutive_proofs.len()) + .unwrap_or(max_agg_size); + let mut block_numbers = Vec::new(); let mut blocks = Vec::new(); let mut block_idxs_in_proof = Vec::new(); + let proofs_to_pad = if agg_size > consecutive_proofs.len() { + agg_size - consecutive_proofs.len() + } else { + 0 + }; let mut idx = 0; - for block_number in last_aggregate_create_proof_block + 1..=last_committed_block { + for block_number in last_aggregate_create_proof_block + 1 + ..=last_aggregate_create_proof_block + consecutive_proofs.len() as u32 + { let block = storage .chain() .block_schema() @@ -317,7 +339,11 @@ async fn create_aggregated_operations(storage: &mut StorageProcessor<'_>) -> any idx += 1; } - let aggregated_op_create = AggregatedOperation::CreateProofBlocks(block_numbers); + let aggregated_op_create = + AggregatedOperation::CreateProofBlocks(BlocksCreateProofOperation { + blocks: block_numbers, + proofs_to_pad, + }); storage .chain() @@ -335,12 +361,15 @@ async fn create_aggregated_operations(storage: &mut StorageProcessor<'_>) -> any if last_aggregate_create_proof_block > last_aggregate_publish_proof_block { let create_proof_blocks = - if let Some(AggregatedOperation::CreateProofBlocks(create_proof_blocks)) = storage + if let Some(AggregatedOperation::CreateProofBlocks(BlocksCreateProofOperation { + blocks: create_proof_blocks, + .. + })) = storage .chain() .operations_schema() .get_aggregated_op_that_affects_block( AggregatedActionType::CreateProofBlocks, - last_aggregate_create_proof_block + 1, + last_aggregate_publish_proof_block + 1, ) .await? { @@ -359,8 +388,7 @@ async fn create_aggregated_operations(storage: &mut StorageProcessor<'_>) -> any if let Some(proof) = proof { let proof = proof.serialize_aggregated_proof(); let mut blocks = Vec::new(); - let mut block_idxs_in_proof = Vec::new(); - for (idx, block_number) in create_proof_blocks.into_iter().enumerate() { + for block_number in create_proof_blocks { let block = storage .chain() .block_schema() @@ -368,14 +396,12 @@ async fn create_aggregated_operations(storage: &mut StorageProcessor<'_>) -> any .await? .expect("Failed to get last committed block from db"); blocks.push(block); - block_idxs_in_proof.push(idx); } let aggregated_op_publish = AggregatedOperation::PublishProofBlocksOnchain(BlocksProofOperation { blocks, proof, - block_idxs_in_proof, }); storage .chain() diff --git a/core/bin/zksync_eth_sender/src/lib.rs b/core/bin/zksync_eth_sender/src/lib.rs index f1b502bd33..6e41cff47d 100644 --- a/core/bin/zksync_eth_sender/src/lib.rs +++ b/core/bin/zksync_eth_sender/src/lib.rs @@ -722,8 +722,7 @@ impl ETHSender { } // not for eth sender AggregatedOperation::PublishProofBlocksOnchain(operation) => { let args = operation.get_eth_tx_args(); - self.ethereum - .encode_tx_data("verifyCommitments", args.as_slice()) + self.ethereum.encode_tx_data("proofBlocks", args.as_slice()) } AggregatedOperation::ExecuteBlocks(operation) => { let args = operation.get_eth_tx_args(); diff --git a/core/bin/zksync_witness_generator/src/lib.rs b/core/bin/zksync_witness_generator/src/lib.rs index 724fec4d72..8094e83a5e 100644 --- a/core/bin/zksync_witness_generator/src/lib.rs +++ b/core/bin/zksync_witness_generator/src/lib.rs @@ -17,7 +17,9 @@ use zksync_prover_utils::api::{ JobRequestData, JobResultData, ProverInputRequest, ProverInputResponse, ProverOutputRequest, WorkingOn, }; -use zksync_types::aggregated_operations::{AggregatedActionType, AggregatedOperation}; +use zksync_types::aggregated_operations::{ + AggregatedActionType, AggregatedOperation, BlocksCreateProofOperation, +}; use zksync_types::prover::{ ProverJobType, AGGREGATED_PROOF_JOB_PRIORITY, SINGLE_PROOF_JOB_PRIORITY, }; @@ -150,19 +152,29 @@ async fn publish( data: web::Data, r: web::Json, ) -> actix_web::Result { - log::info!("Received a proof for job: {}", r.job_id); let mut storage = data .access_storage() .await .map_err(actix_web::error::ErrorInternalServerError)?; let storage_result = match &r.data { JobResultData::BlockProof(single_proof) => { + log::info!( + "Received a proof for job: {}, single block: {}", + r.job_id, + r.first_block + ); storage .prover_schema() .store_proof(r.job_id, r.first_block, single_proof) .await } JobResultData::AggregatedBlockProof(aggregated_proof) => { + log::info!( + "Received a proof for job: {}, aggregated blocks: [{},{}]", + r.job_id, + r.first_block, + r.last_block + ); storage .prover_schema() .store_aggregated_proof(r.job_id, r.first_block, r.last_block, aggregated_proof) @@ -294,7 +306,11 @@ async fn update_prover_job_queue(storage: &mut StorageProcessor<'_>) -> anyhow:: next_aggregated_proof_block, ) .await?; - if let Some(AggregatedOperation::CreateProofBlocks(blocks)) = create_block_proof_action { + if let Some(AggregatedOperation::CreateProofBlocks(BlocksCreateProofOperation { + blocks, + .. + })) = create_block_proof_action + { let first_block = *blocks.first().expect("should have 1 block"); let last_block = *blocks.last().expect("should have 1 block"); let mut data = Vec::new(); diff --git a/core/lib/prover_utils/Cargo.toml b/core/lib/prover_utils/Cargo.toml index 187d1989d5..83a624bbc7 100644 --- a/core/lib/prover_utils/Cargo.toml +++ b/core/lib/prover_utils/Cargo.toml @@ -21,3 +21,4 @@ log = "0.4" backoff = "0.1.6" reqwest = { version = "0.10.6", features = ["blocking"] } serde = "1.0" +serde_json = "1.0" diff --git a/core/lib/prover_utils/src/fs_utils.rs b/core/lib/prover_utils/src/fs_utils.rs index 1cfddd728d..233c0a9ed5 100644 --- a/core/lib/prover_utils/src/fs_utils.rs +++ b/core/lib/prover_utils/src/fs_utils.rs @@ -5,6 +5,7 @@ use std::io::BufReader; use std::path::PathBuf; use zksync_crypto::bellman::kate_commitment::{Crs, CrsForLagrangeForm, CrsForMonomialForm}; use zksync_crypto::params::{account_tree_depth, balance_tree_depth}; +use zksync_crypto::proof::{AggregatedProof, SingleProof}; use zksync_crypto::Engine; pub fn get_keys_root_dir() -> PathBuf { @@ -97,3 +98,17 @@ pub fn get_recursive_verification_key_path(number_of_proofs: usize) -> PathBuf { key.push(&format!("recursive_{}.key", number_of_proofs)); key } + +pub fn load_correct_aggregated_proof() -> anyhow::Result { + let mut path = get_keys_root_dir(); + path.push("zksync-aggregated-1.json"); + let file = File::open(path)?; + Ok(serde_json::from_reader(file)?) +} + +pub fn load_correct_single_proof() -> anyhow::Result { + let mut path = get_keys_root_dir(); + path.push("zksync-6-chunks.json"); + let file = File::open(path)?; + Ok(serde_json::from_reader(file)?) +} diff --git a/core/lib/storage/sqlx-data.json b/core/lib/storage/sqlx-data.json index f4b1955be8..abdf1fc8dc 100644 --- a/core/lib/storage/sqlx-data.json +++ b/core/lib/storage/sqlx-data.json @@ -496,19 +496,6 @@ "nullable": [] } }, - "21f5974c8b44b476e606833f96353a6eac4f832665dc4161ca17c6dc9ecff805": { - "query": "\n UPDATE prover_job_queue \n SET (job_status, updated_at, updated_by) = ($1, now(), 'server')\n WHERE id = $2;\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4", - "Int4" - ] - }, - "nullable": [] - } - }, "222e3946401772e3f6e0d9ce9909e8e7ac2dc830c5ecfcd522f56b3bf70fd679": { "query": "INSERT INTO data_restore_storage_state_update (storage_state) VALUES ($1)", "describe": { @@ -1519,20 +1506,6 @@ "nullable": [] } }, - "5e787ba690db443e308db72a6cb29b8c51d57f09ea21391a9ee64630cea34036": { - "query": "UPDATE prover_job_queue\n SET (updated_at, job_status) = (now(), $1)\n WHERE first_block = $2 and last_block = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4", - "Int8", - "Int8" - ] - }, - "nullable": [] - } - }, "60a2be4d7162d73b929f7ab712d01404d45bcc128e2b03ad3ff853a645e2fb5c": { "query": "\n WITH eth_ops AS (\n SELECT DISTINCT ON (block_number, action_type)\n operations.block_number,\n eth_tx_hashes.tx_hash,\n operations.action_type,\n operations.created_at,\n confirmed\n FROM operations\n left join eth_ops_binding on eth_ops_binding.op_id = operations.id\n left join eth_tx_hashes on eth_tx_hashes.eth_op_id = eth_ops_binding.eth_op_id\n ORDER BY block_number desc, action_type, confirmed\n )\n SELECT\n blocks.number AS \"block_number!\",\n blocks.root_hash AS \"new_state_root!\",\n blocks.block_size AS \"block_size!\",\n committed.tx_hash AS \"commit_tx_hash?\",\n verified.tx_hash AS \"verify_tx_hash?\",\n committed.created_at AS \"committed_at!\",\n verified.created_at AS \"verified_at?\"\n FROM blocks\n INNER JOIN eth_ops committed ON\n committed.block_number = blocks.number AND committed.action_type = 'COMMIT' AND committed.confirmed = true\n LEFT JOIN eth_ops verified ON\n verified.block_number = blocks.number AND verified.action_type = 'VERIFY' AND verified.confirmed = true\n WHERE false\n OR committed.tx_hash = $1\n OR verified.tx_hash = $1\n OR blocks.root_hash = $1\n OR blocks.number = $2\n ORDER BY blocks.number DESC\n LIMIT 1;\n ", "describe": { @@ -1694,6 +1667,23 @@ "nullable": [] } }, + "64e87611fad53aaa3b5cddddd468045d5870603f705ec1652eafb536e4a0658f": { + "query": "\n WITH job_values as (\n SELECT $1::int4, $2::int4, $3::text, 'server_add_job', $4::int8, $5::int8, $6::jsonb\n WHERE NOT EXISTS (SELECT * FROM prover_job_queue WHERE first_block = $4 and last_block = $5 and job_type = $3 LIMIT 1)\n ) \n INSERT INTO prover_job_queue (job_status, job_priority, job_type, updated_by, first_block, last_block, job_data) \n SELECT * from job_values\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int4", + "Text", + "Int8", + "Int8", + "Jsonb" + ] + }, + "nullable": [] + } + }, "681359f99d0e4bafdd3109f67c7af4d235dc1197ba88cd0d6148f632ae0cdf8f": { "query": "SELECT * FROM aggregated_proofs WHERE first_block = $1 and last_block = $2", "describe": { @@ -1733,6 +1723,19 @@ ] } }, + "6bd51c16a66835305c8fa763966bbfef13199924cbe1c97b7d7b840edea4217a": { + "query": "UPDATE prover_job_queue\n SET (updated_at, job_status, updated_by) = (now(), $1, 'server_finish_job')\n WHERE id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int4" + ] + }, + "nullable": [] + } + }, "6d676581f14d0935983aca496bc37b58206b90320058290809020a2604b11df3": { "query": "SELECT max(number) FROM blocks", "describe": { @@ -2453,6 +2456,19 @@ "nullable": [] } }, + "a77668a3dce7f7cd1f45816f932eea685d429c3d75b40ea8e1a1bb9fc29f11c6": { + "query": "UPDATE prover_job_queue SET (job_status, updated_at, updated_by) = ($1, now(), 'server_clean_idle')\n WHERE job_status = $2 and (now() - updated_at) >= interval '120 seconds'", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int4" + ] + }, + "nullable": [] + } + }, "aaaf2bcea738151db11f6152772516a46ef7d23ae885936094226b837369ee3c": { "query": "DELETE FROM mempool_txs\n WHERE tx_hash = ANY($1)", "describe": { @@ -2644,19 +2660,6 @@ ] } }, - "b6ec588f73141845615fcc324cdbe69ce043049c94bd52b417cad6c290a78700": { - "query": "UPDATE prover_job_queue\n SET (updated_at, job_status) = (now(), $1)\n WHERE first_block = $2 and last_block = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4", - "Int8" - ] - }, - "nullable": [] - } - }, "ba7b1041b86ef292034c6e53ee6347c458fea2d19c91b060179e7f01f7ea60fc": { "query": "UPDATE prover_job_queue \n SET (updated_at, updated_by) = (now(), $1)\n WHERE id = $2", "describe": { @@ -2702,19 +2705,6 @@ ] } }, - "bbe27e525e6d5969c09657bfcffee0a81098c01013d989d7688e783c0c533d63": { - "query": "UPDATE prover_job_queue SET job_status = $1\n WHERE job_status = $2 and (now() - updated_at) < interval '120 seconds'", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4", - "Int4" - ] - }, - "nullable": [] - } - }, "bbf6839d81439b9760bea580b95a044cfb2b418aa385e051295252ea7a0d60dd": { "query": "SELECT * FROM data_restore_storage_state_update\n LIMIT 1", "describe": { @@ -3102,6 +3092,19 @@ ] } }, + "c623ce8cb2f6e17285af5aa0fe4fddbb302f7dda343778690f299d49b832e273": { + "query": "\n UPDATE prover_job_queue \n SET (job_status, updated_at, updated_by) = ($1, now(), 'server_give_job')\n WHERE id = $2;\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int4" + ] + }, + "nullable": [] + } + }, "c7bc91425f35b3a77be36fe8ba80030445051a0bc2536fa4a0def7ac498fc5c2": { "query": "INSERT INTO mempool_txs (tx_hash, tx, created_at, eth_sign_data)\n VALUES ($1, $2, $3, $4)", "describe": { @@ -3816,24 +3819,6 @@ ] } }, - "fbcf404f1a662c9f093c14a49a497dd9dc21c68dc60ac9c192e0930a9bebc6a1": { - "query": "\n WITH job_values as (\n SELECT $1::int4, $2::int4, $3::text, $4::text, $5::int8, $6::int8, $7::jsonb\n WHERE NOT EXISTS (SELECT * FROM prover_job_queue WHERE first_block = $5 and last_block = $6 LIMIT 1)\n ) \n INSERT INTO prover_job_queue (job_status, job_priority, job_type, updated_by, first_block, last_block, job_data) \n SELECT * from job_values\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4", - "Int4", - "Text", - "Text", - "Int8", - "Int8", - "Jsonb" - ] - }, - "nullable": [] - } - }, "fd16aadbd04d4a48332d59c77290a588f1a33922418b55a08c656a44ff75b8e8": { "query": "SELECT * FROM account_balance_updates WHERE block_number = $1", "describe": { diff --git a/core/lib/storage/src/prover/mod.rs b/core/lib/storage/src/prover/mod.rs index d75308ec1d..a0ea8e6639 100644 --- a/core/lib/storage/src/prover/mod.rs +++ b/core/lib/storage/src/prover/mod.rs @@ -48,8 +48,8 @@ impl<'a, 'c> ProverSchema<'a, 'c> { sqlx::query!( " WITH job_values as ( - SELECT $1::int4, $2::int4, $3::text, $4::text, $5::int8, $6::int8, $7::jsonb - WHERE NOT EXISTS (SELECT * FROM prover_job_queue WHERE first_block = $5 and last_block = $6 LIMIT 1) + SELECT $1::int4, $2::int4, $3::text, 'server_add_job', $4::int8, $5::int8, $6::jsonb + WHERE NOT EXISTS (SELECT * FROM prover_job_queue WHERE first_block = $4 and last_block = $5 and job_type = $3 LIMIT 1) ) INSERT INTO prover_job_queue (job_status, job_priority, job_type, updated_by, first_block, last_block, job_data) SELECT * from job_values @@ -57,7 +57,6 @@ impl<'a, 'c> ProverSchema<'a, 'c> { ProverJobStatus::Idle.to_number(), job_priority, job_type.to_string(), - "server".to_string(), i64::from(first_block), i64::from(last_block), job_data, @@ -67,8 +66,8 @@ impl<'a, 'c> ProverSchema<'a, 'c> { // pub async fn mark_stale_jobs_as_idle(&mut self) -> QueryResult<()> { sqlx::query!( - "UPDATE prover_job_queue SET job_status = $1 - WHERE job_status = $2 and (now() - updated_at) < interval '120 seconds'", + "UPDATE prover_job_queue SET (job_status, updated_at, updated_by) = ($1, now(), 'server_clean_idle') + WHERE job_status = $2 and (now() - updated_at) >= interval '120 seconds'", ProverJobStatus::Idle.to_number(), ProverJobStatus::InProgress.to_number(), ) @@ -102,10 +101,10 @@ impl<'a, 'c> ProverSchema<'a, 'c> { sqlx::query!( r#" UPDATE prover_job_queue - SET (job_status, updated_at, updated_by) = ($1, now(), 'server') + SET (job_status, updated_at, updated_by) = ($1, now(), 'server_give_job') WHERE id = $2; "#, - ProverJobStatus::Idle.to_number(), + ProverJobStatus::InProgress.to_number(), job.id, ) .execute(transaction.conn()) @@ -174,10 +173,10 @@ impl<'a, 'c> ProverSchema<'a, 'c> { let mut transaction = self.0.start_transaction().await?; sqlx::query!( "UPDATE prover_job_queue - SET (updated_at, job_status) = (now(), $1) - WHERE first_block = $2 and last_block = $2", + SET (updated_at, job_status, updated_by) = (now(), $1, 'server_finish_job') + WHERE id = $2", ProverJobStatus::Done.to_number(), - i64::from(block_number), + job_id, ) .execute(transaction.conn()) .await?; @@ -190,6 +189,7 @@ impl<'a, 'c> ProverSchema<'a, 'c> { .execute(transaction.conn()) .await? .rows_affected() as usize; + transaction.commit().await?; metrics::histogram!("sql", start.elapsed(), "prover" => "store_proof"); Ok(updated_rows) @@ -207,11 +207,10 @@ impl<'a, 'c> ProverSchema<'a, 'c> { let mut transaction = self.0.start_transaction().await?; sqlx::query!( "UPDATE prover_job_queue - SET (updated_at, job_status) = (now(), $1) - WHERE first_block = $2 and last_block = $3", + SET (updated_at, job_status, updated_by) = (now(), $1, 'server_finish_job') + WHERE id = $2", ProverJobStatus::Done.to_number(), - i64::from(first_block), - i64::from(last_block), + job_id ) .execute(transaction.conn()) .await?; @@ -225,6 +224,7 @@ impl<'a, 'c> ProverSchema<'a, 'c> { .execute(transaction.conn()) .await? .rows_affected() as usize; + transaction.commit().await?; metrics::histogram!("sql", start.elapsed(), "prover" => "store_aggregated_proof"); Ok(updated_rows) diff --git a/core/lib/types/src/aggregated_operations.rs b/core/lib/types/src/aggregated_operations.rs index c3ecc3d259..9cefbc7999 100644 --- a/core/lib/types/src/aggregated_operations.rs +++ b/core/lib/types/src/aggregated_operations.rs @@ -60,27 +60,25 @@ impl BlocksCommitOperation { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BlocksCreateProofOperation { + pub blocks: Vec, + pub proofs_to_pad: usize, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BlocksProofOperation { pub blocks: Vec, pub proof: EncodedAggregatedProof, - pub block_idxs_in_proof: Vec, } impl BlocksProofOperation { pub fn get_eth_tx_args(&self) -> Vec { let blocks_arg = Token::Array(self.blocks.iter().map(|b| stored_block_info(b)).collect()); - let committed_idxs = Token::Array( - self.block_idxs_in_proof - .iter() - .map(|idx| Token::Uint(U256::from(*idx))) - .collect(), - ); - let proof = self.proof.get_eth_tx_args(); - vec![blocks_arg, committed_idxs, proof] + vec![blocks_arg, proof] } } @@ -158,7 +156,7 @@ impl std::str::FromStr for AggregatedActionType { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum AggregatedOperation { CommitBlocks(BlocksCommitOperation), - CreateProofBlocks(Vec), + CreateProofBlocks(BlocksCreateProofOperation), PublishProofBlocksOnchain(BlocksProofOperation), ExecuteBlocks(BlocksExecuteOperation), } @@ -181,7 +179,9 @@ impl AggregatedOperation { blocks.first().map(|b| b.block_number).unwrap_or_default(), blocks.last().map(|b| b.block_number).unwrap_or_default(), ), - AggregatedOperation::CreateProofBlocks(blocks) => ( + AggregatedOperation::CreateProofBlocks(BlocksCreateProofOperation { + blocks, .. + }) => ( blocks.first().cloned().unwrap_or_default(), blocks.last().cloned().unwrap_or_default(), ), diff --git a/core/lib/types/src/prover.rs b/core/lib/types/src/prover.rs index 08c1fbf605..d917e7819a 100644 --- a/core/lib/types/src/prover.rs +++ b/core/lib/types/src/prover.rs @@ -26,8 +26,8 @@ impl ProverJobStatus { } } -pub const SINGLE_PROOF_JOB_PRIORITY: i32 = 0; -pub const AGGREGATED_PROOF_JOB_PRIORITY: i32 = 1; +pub const SINGLE_PROOF_JOB_PRIORITY: i32 = 1; +pub const AGGREGATED_PROOF_JOB_PRIORITY: i32 = 0; #[derive(Debug, Clone)] pub struct ProverJob { diff --git a/core/tests/testkit/src/bin/block_sizes_test.rs b/core/tests/testkit/src/bin/block_sizes_test.rs index 3ca33f7313..45a3b13849 100644 --- a/core/tests/testkit/src/bin/block_sizes_test.rs +++ b/core/tests/testkit/src/bin/block_sizes_test.rs @@ -1,6 +1,7 @@ //! Block sizes test is used to create blocks of all available sizes, make proofs of them and verify onchain use log::info; +use std::io::Write; use std::time::Instant; use web3::transports::Http; use zksync_circuit::witness::utils::build_block_witness; @@ -84,7 +85,7 @@ async fn main() { accounts, &contracts, commit_account, - Default::default(), + genesis_root, ); let account_state = test_setup.get_accounts_state().await; @@ -153,7 +154,6 @@ async fn main() { let proof_op = BlocksProofOperation { blocks: vec![block], proof: aggreagated_proof.serialize_aggregated_proof(), - block_idxs_in_proof: vec![0], }; test_setup .execute_verify_commitments(proof_op) @@ -228,7 +228,6 @@ async fn main() { let proof_op = BlocksProofOperation { blocks, proof: aggreagated_proof.serialize_aggregated_proof(), - block_idxs_in_proof, }; test_setup .execute_verify_commitments(proof_op) diff --git a/core/tests/testkit/src/test_setup.rs b/core/tests/testkit/src/test_setup.rs index 86417f793d..71a9132165 100644 --- a/core/tests/testkit/src/test_setup.rs +++ b/core/tests/testkit/src/test_setup.rs @@ -695,7 +695,6 @@ impl TestSetup { let block_proof_op = BlocksProofOperation { blocks: vec![new_block.clone()], proof, - block_idxs_in_proof: vec![0], }; let verify_result = self .commit_account diff --git a/keys/packed/verify-keys-plonk-8c6e12e4c-account-32_-balance-11.tar.gz b/keys/packed/verify-keys-plonk-8c6e12e4c-account-32_-balance-11.tar.gz index 9993ca1f041f1aba7b962080580d577ddc1d4ea1..8f5744f1872b7cac87260d1bb3958dfbcc3c8048 100644 GIT binary patch literal 18408 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 P|2=;Lrz;>i0LTCUx&cF1 literal 15340 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 zD-uY_0000000000000000000000000000000002szx)np K4#t20$N&KRusF#8