Skip to content

Commit

Permalink
Merge branch 'dvush-add-last-transfer' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
dvush committed Feb 15, 2021
2 parents 7bac7c5 + cbdd0c8 commit f90a2ea
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 27 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl OperationNotifier {
block.block_transactions.clone(),
action,
block.block_number,
)?;
);

let updated_accounts: Vec<AccountId> = block
.block_transactions
Expand Down Expand Up @@ -124,7 +124,7 @@ impl OperationNotifier {
ops: Vec<ExecutedOperations>,
action: ActionType,
block_number: BlockNumber,
) -> Result<(), anyhow::Error> {
) {
let start = Instant::now();
for tx in ops {
match tx {
Expand Down Expand Up @@ -157,7 +157,6 @@ impl OperationNotifier {
}
}
metrics::histogram!("api.notifier.handle_executed_operations", start.elapsed());
Ok(())
}

/// More convenient alias for `handle_executed_operations`.
Expand All @@ -169,7 +168,8 @@ impl OperationNotifier {
exec_batch.operations,
ActionType::COMMIT,
exec_batch.block_number,
)
);
Ok(())
}

/// Removes provided subscription from the list.
Expand Down
1 change: 1 addition & 0 deletions core/bin/zksync_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub async fn run_core(
config.chain.state_keeper.block_chunk_sizes.clone(),
config.chain.state_keeper.miniblock_iterations as usize,
config.chain.state_keeper.fast_block_miniblock_iterations as usize,
config.chain.state_keeper.last_tx_signer_data(),
);
let state_keeper_task = start_state_keeper(state_keeper, pending_block);

Expand Down
149 changes: 130 additions & 19 deletions core/bin/zksync_core/src/state_keeper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ use futures::{
use itertools::Itertools;
use tokio::task::JoinHandle;
// Workspace uses
use zksync_crypto::ff;
use zksync_crypto::ff::{PrimeField, PrimeFieldRepr};
use zksync_crypto::{
convert::FeConvert,
ff::{self, PrimeField, PrimeFieldRepr},
params::ETH_TOKEN_ID,
PrivateKey,
};
use zksync_state::state::{CollectedFee, OpSuccess, ZkSyncState};
use zksync_storage::ConnectionPool;
use zksync_types::{
Expand All @@ -20,10 +24,11 @@ use zksync_types::{
PendingBlock as SendablePendingBlock,
},
gas_counter::GasCounter,
helpers::reverse_updates,
mempool::SignedTxVariant,
tx::{TxHash, ZkSyncTx},
Account, AccountId, AccountTree, AccountUpdate, AccountUpdates, Address, BlockNumber,
PriorityOp, SignedZkSyncTx, H256,
PriorityOp, SignedZkSyncTx, Transfer, TransferOp, H256,
};
// Local uses
use crate::{
Expand Down Expand Up @@ -72,10 +77,16 @@ struct PendingBlock {
impl PendingBlock {
fn new(
unprocessed_priority_op_before: u64,
chunks_left: usize,
available_chunks_sizes: &[usize],
previous_block_root_hash: H256,
timestamp: u64,
) -> Self {
// TransferOp chunks are subtracted to reserve space for last transfer.
let chunks_left = *available_chunks_sizes
.iter()
.max()
.expect("Expected at least one block chunks size")
- TransferOp::CHUNKS;
Self {
success_operations: Vec::new(),
failed_txs: Vec::new(),
Expand Down Expand Up @@ -124,6 +135,9 @@ pub struct ZkSyncStateKeeper {
success_txs_pending_len: usize,
/// Amount of failed transactions in the pending block at the last pending block synchronization step.
failed_txs_pending_len: usize,

/// ZK sync account that is used to create last transfer before sealing block (e.g. to change block hash)
tx_signer: Option<(Address, PrivateKey)>,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -359,6 +373,7 @@ impl ZkSyncStateKeeper {
available_block_chunk_sizes: Vec<usize>,
max_miniblock_iterations: usize,
fast_miniblock_iterations: usize,
tx_signer: Option<(Address, PrivateKey)>,
) -> Self {
assert!(!available_block_chunk_sizes.is_empty());

Expand All @@ -378,7 +393,7 @@ impl ZkSyncStateKeeper {
.get_account_by_address(&fee_account_address)
.expect("Fee account should be present in the account tree");
// Keeper starts with the NEXT block
let max_block_size = *available_block_chunk_sizes.iter().max().unwrap();
// we leave space for last tx
let mut be_bytes = [0u8; 32];
state
.root_hash()
Expand All @@ -394,7 +409,7 @@ impl ZkSyncStateKeeper {
tx_for_commitments,
pending_block: PendingBlock::new(
initial_state.unprocessed_priority_op,
max_block_size,
&available_block_chunk_sizes,
previous_root_hash,
system_time_timestamp(),
),
Expand All @@ -404,6 +419,7 @@ impl ZkSyncStateKeeper {

success_txs_pending_len: 0,
failed_txs_pending_len: 0,
tx_signer,
};

let root = keeper.state.root_hash();
Expand Down Expand Up @@ -928,31 +944,32 @@ impl ZkSyncStateKeeper {
/// Finalizes the pending block, transforming it into a full block.
async fn seal_pending_block(&mut self) {
let start = Instant::now();

// Apply fees of pending block
let fee_updates = self
.state
.collect_fee(&self.pending_block.collected_fees, self.fee_account_id);
self.pending_block
.account_updates
.extend(fee_updates.into_iter());

// This last tx does not pay any fee
if let Err(e) = self.execute_transfer_to_change_block_hash() {
vlog::error!("Failed to execute transfer to change block hash: {}", e);
}
let mut pending_block = std::mem::replace(
&mut self.pending_block,
PendingBlock::new(
self.current_unprocessed_priority_op,
*self
.available_block_chunk_sizes
.last()
.expect("failed to get max block size"),
&self.available_block_chunk_sizes,
H256::default(),
system_time_timestamp(),
),
);

// Once block is sealed, we refresh the counters for the next block.
self.success_txs_pending_len = 0;
self.failed_txs_pending_len = 0;

// Apply fees of pending block
let fee_updates = self
.state
.collect_fee(&pending_block.collected_fees, self.fee_account_id);
pending_block
.account_updates
.extend(fee_updates.into_iter());

let mut block_transactions = pending_block.success_operations;
block_transactions.extend(
pending_block
Expand Down Expand Up @@ -1079,6 +1096,100 @@ impl ZkSyncStateKeeper {
unprocessed_priority_op: self.current_unprocessed_priority_op,
}
}

/// Should be applied after fee is collected when block is being sealed.
fn execute_transfer_to_change_block_hash(&mut self) -> anyhow::Result<()> {
let (signer_id, signer_account, signer_pk) = {
let (address, pk) = if let Some((address, pk)) = self.tx_signer.as_ref() {
(address, pk)
} else {
return Ok(());
};
let (id, account) = self.state.get_account_by_address(&address).ok_or_else(|| {
anyhow::format_err!("Signer account is not in the tree: {:?}", address)
})?;
(id, account, pk)
};
let (target_id, target_account) = {
(
self.fee_account_id,
self.state
.get_account(self.fee_account_id)
.expect("Fee account must be present in the tree"),
)
};

let mut tx_value = 0u32;
let mut first_byte = self.state.root_hash().to_bytes()[0];
while first_byte > 0x1F {
tx_value += 1;
anyhow::ensure!(
signer_account.get_balance(ETH_TOKEN_ID) > tx_value.into(),
"Not enough balance on signer account"
);

let expected_updates = vec![
(
signer_id,
AccountUpdate::UpdateBalance {
old_nonce: signer_account.nonce,
new_nonce: signer_account.nonce + 1,
balance_update: (
ETH_TOKEN_ID,
signer_account.get_balance(ETH_TOKEN_ID),
signer_account.get_balance(ETH_TOKEN_ID) - tx_value,
),
},
),
(
target_id,
AccountUpdate::UpdateBalance {
old_nonce: target_account.nonce,
new_nonce: target_account.nonce,
balance_update: (
ETH_TOKEN_ID,
target_account.get_balance(ETH_TOKEN_ID),
target_account.get_balance(ETH_TOKEN_ID) + tx_value,
),
},
),
];
self.state.apply_account_updates(expected_updates.clone());

first_byte = self.state.root_hash().to_bytes()[0];

let reverse_updates = {
let mut rev_updates = expected_updates;
reverse_updates(&mut rev_updates);
rev_updates
};
self.state.apply_account_updates(reverse_updates);
}

if tx_value == 0 {
return Ok(());
}

let transfer = Transfer::new_signed(
signer_id,
signer_account.address,
target_account.address,
ETH_TOKEN_ID,
tx_value.into(),
0u32.into(),
signer_account.nonce,
Default::default(),
&signer_pk,
)?;

self.apply_tx(&SignedZkSyncTx {
tx: transfer.into(),
eth_sign_data: None,
})
.map_err(|_| anyhow::format_err!("Transaction execution failed"))?;

Ok(())
}
}

#[must_use]
Expand Down
2 changes: 2 additions & 0 deletions core/bin/zksync_core/src/state_keeper/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ impl StateKeeperTester {
vec![available_chunk_size],
max_iterations,
fast_iterations,
None,
);

Self {
Expand Down Expand Up @@ -252,6 +253,7 @@ fn test_create_incorrect_state_keeper() {
vec![1, 2, 2], // `available_block_chunk_sizes` must be strictly increasing.
MAX_ITERATIONS,
FAST_ITERATIONS,
None,
);
}

Expand Down
1 change: 1 addition & 0 deletions core/lib/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ categories = ["cryptography"]
[dependencies]
zksync_types = { path = "../types", version = "1.0" }
zksync_utils = { path = "../utils", version = "1.0" }
zksync_crypto = { path = "../crypto", version = "1.0" }
chrono = "0.4"
url = "2.1"
tracing = "0.1.22"
Expand Down
21 changes: 21 additions & 0 deletions core/lib/config/src/configs/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use serde::Deserialize;
/// Built-in uses
use std::time::Duration;
// Local uses
use zksync_crypto::{convert::FeConvert, priv_key_from_fs, Fs, PrivateKey};
use zksync_types::network::Network;
use zksync_types::Address;

Expand Down Expand Up @@ -88,6 +89,9 @@ pub struct StateKeeper {
pub block_prove_deadline: u64,
pub block_execute_deadline: u64,
pub max_aggregated_tx_gas: usize,
pub last_tx_signer_used: bool,
pub last_tx_signer_address: Address,
pub last_tx_signer_private_key: String,
}

impl StateKeeper {
Expand All @@ -107,6 +111,17 @@ impl StateKeeper {
pub fn block_execute_deadline(&self) -> Duration {
Duration::from_secs(self.block_execute_deadline)
}

pub fn last_tx_signer_data(&self) -> Option<(Address, PrivateKey)> {
if self.last_tx_signer_used {
let fs = Fs::from_hex(&self.last_tx_signer_private_key)
.expect("failed to parse private key");
let pk = priv_key_from_fs(fs);
Some((self.last_tx_signer_address, pk))
} else {
None
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -141,6 +156,9 @@ mod tests {
block_prove_deadline: 3_000,
block_execute_deadline: 4_000,
max_aggregated_tx_gas: 4_000_000,
last_tx_signer_used: false,
last_tx_signer_private_key: "0xaabbeecc".into(),
last_tx_signer_address: addr("da03a0b5963f75f1c8485b355ff6d30f3093bde7"),
},
}
}
Expand Down Expand Up @@ -169,6 +187,9 @@ CHAIN_STATE_KEEPER_BLOCK_COMMIT_DEADLINE="300"
CHAIN_STATE_KEEPER_BLOCK_PROVE_DEADLINE="3000"
CHAIN_STATE_KEEPER_BLOCK_EXECUTE_DEADLINE="4000"
CHAIN_STATE_KEEPER_MAX_AGGREGATED_TX_GAS="4000000"
CHAIN_STATE_KEEPER_LAST_TX_SIGNER_USED="false"
CHAIN_STATE_KEEPER_LAST_TX_SIGNER_ADDRESS="0xda03a0b5963f75f1c8485b355ff6d30f3093bde7"
CHAIN_STATE_KEEPER_LAST_TX_SIGNER_PRIVATE_KEY="0xaabbeecc"
"#;
set_env(config);

Expand Down
5 changes: 1 addition & 4 deletions core/lib/types/src/tx/primitives/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::str::FromStr;
// External uses
use anyhow::Result;
// Workspace uses
use zksync_basic_types::Address;
use zksync_utils::format_units;
Expand Down Expand Up @@ -57,7 +56,7 @@ fn test_empty_batch() {

/// Checks the correctness of the message `EthBatchSignData::new()` returns.
#[test]
fn test_batch_message() -> Result<()> {
fn test_batch_message() {
let token = Token::new(TokenId(0), Default::default(), "ETH", 18);
let transfer = get_transfer();
let withdraw = get_withdraw();
Expand Down Expand Up @@ -130,6 +129,4 @@ fn test_batch_message() -> Result<()> {

let message = EthBatchSignData::get_batch_sign_message(txs);
assert_eq!(message, expected.into_bytes());

Ok(())
}
1 change: 1 addition & 0 deletions core/tests/testkit/src/state_keeper_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub fn spawn_state_keeper(
block_chunks_sizes,
max_miniblock_iterations,
max_miniblock_iterations,
None,
);

let (stop_state_keeper_sender, stop_state_keeper_receiver) = oneshot::channel::<()>();
Expand Down
Loading

0 comments on commit f90a2ea

Please sign in to comment.