use anyhow::Context as _;
use zksync_concurrency::{ctx, error::Wrap as _};
use zksync_consensus_roles::validator;
use zksync_dal::consensus_dal::Payload;
use zksync_l1_contract_interface::i_executor;
use zksync_metadata_calculator::api_server::{TreeApiClient, TreeEntryWithProof};
use zksync_system_constants as constants;
use zksync_types::{
abi,
block::{unpack_block_info, L2BlockHasher},
AccountTreeId, L1BatchNumber, L2BlockNumber, ProtocolVersionId, StorageKey, Transaction, H256,
U256,
};
use zksync_utils::{h256_to_u256, u256_to_h256};
use crate::storage::ConnectionPool;
pub(crate) struct LastBlockCommit {
pub(crate) info: H256,
}
pub(crate) struct LastBlockWitness {
info: i_executor::structures::StoredBatchInfo,
protocol_version: ProtocolVersionId,
current_l2_block_info: TreeEntryWithProof,
tx_rolling_hash: TreeEntryWithProof,
l2_block_hash_entry: TreeEntryWithProof,
}
pub(crate) struct L1BatchCommit {
pub(crate) number: L1BatchNumber,
pub(crate) this_batch: LastBlockCommit,
pub(crate) prev_batch: LastBlockCommit,
}
pub struct L1BatchWithWitness {
pub(crate) blocks: Vec<Payload>,
pub(crate) this_batch: LastBlockWitness,
pub(crate) prev_batch: LastBlockWitness,
}
impl LastBlockWitness {
fn system_context_addr() -> AccountTreeId {
AccountTreeId::new(constants::SYSTEM_CONTEXT_ADDRESS)
}
fn current_l2_block_info_key() -> U256 {
StorageKey::new(
Self::system_context_addr(),
constants::SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION,
)
.hashed_key_u256()
}
fn tx_rolling_hash_key() -> U256 {
StorageKey::new(
Self::system_context_addr(),
constants::SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION,
)
.hashed_key_u256()
}
fn l2_block_hash_entry_key(i: L2BlockNumber) -> U256 {
let key = h256_to_u256(constants::SYSTEM_CONTEXT_CURRENT_L2_BLOCK_HASHES_POSITION)
+ U256::from(i.0) % U256::from(constants::SYSTEM_CONTEXT_STORED_L2_BLOCK_HASHES);
StorageKey::new(Self::system_context_addr(), u256_to_h256(key)).hashed_key_u256()
}
async fn load(
ctx: &ctx::Ctx,
n: L1BatchNumber,
pool: &ConnectionPool,
tree: &dyn TreeApiClient,
) -> ctx::Result<Self> {
let mut conn = pool.connection(ctx).await.wrap("pool.connection()")?;
let batch = conn
.batch(ctx, n)
.await
.wrap("batch()")?
.context("batch not in storage")?;
let proofs = tree
.get_proofs(
n,
vec![
Self::current_l2_block_info_key(),
Self::tx_rolling_hash_key(),
],
)
.await
.context("get_proofs()")?;
if proofs.len() != 2 {
return Err(anyhow::format_err!("proofs.len()!=2").into());
}
let current_l2_block_info = proofs[0].clone();
let tx_rolling_hash = proofs[1].clone();
let (block_number, _) = unpack_block_info(current_l2_block_info.value.as_bytes().into());
let prev = L2BlockNumber(
block_number
.checked_sub(1)
.context("L2BlockNumber underflow")?
.try_into()
.context("L2BlockNumber overflow")?,
);
let proofs = tree
.get_proofs(n, vec![Self::l2_block_hash_entry_key(prev)])
.await
.context("get_proofs()")?;
if proofs.len() != 1 {
return Err(anyhow::format_err!("proofs.len()!=1").into());
}
let l2_block_hash_entry = proofs[0].clone();
Ok(Self {
info: i_executor::structures::StoredBatchInfo::from(&batch),
protocol_version: batch
.header
.protocol_version
.context("missing protocol_version")?,
current_l2_block_info,
tx_rolling_hash,
l2_block_hash_entry,
})
}
pub(crate) fn verify(&self, comm: &LastBlockCommit) -> anyhow::Result<(L2BlockNumber, H256)> {
anyhow::ensure!(comm.info == self.info.hash());
anyhow::ensure!(
self.protocol_version >= ProtocolVersionId::Version13,
"unsupported protocol version"
);
let (block_number, block_timestamp) =
unpack_block_info(self.current_l2_block_info.value.as_bytes().into());
let prev = L2BlockNumber(
block_number
.checked_sub(1)
.context("L2BlockNumber underflow")?
.try_into()
.context("L2BlockNumber overflow")?,
);
self.current_l2_block_info
.verify(Self::current_l2_block_info_key(), self.info.batch_hash)
.context("invalid merkle path for current_l2_block_info")?;
self.tx_rolling_hash
.verify(Self::tx_rolling_hash_key(), self.info.batch_hash)
.context("invalid merkle path for tx_rolling_hash")?;
self.l2_block_hash_entry
.verify(Self::l2_block_hash_entry_key(prev), self.info.batch_hash)
.context("invalid merkle path for l2_block_hash entry")?;
let block_number = L2BlockNumber(block_number.try_into().context("block_number overflow")?);
Ok((
block_number,
L2BlockHasher::hash(
block_number,
block_timestamp,
self.l2_block_hash_entry.value,
self.tx_rolling_hash.value,
self.protocol_version,
),
))
}
pub fn last_block(&self) -> validator::BlockNumber {
let (n, _) = unpack_block_info(self.current_l2_block_info.value.as_bytes().into());
validator::BlockNumber(n)
}
}
impl L1BatchWithWitness {
pub(crate) async fn load(
ctx: &ctx::Ctx,
number: L1BatchNumber,
pool: &ConnectionPool,
tree: &dyn TreeApiClient,
) -> ctx::Result<Self> {
let prev_batch = LastBlockWitness::load(ctx, number - 1, pool, tree)
.await
.with_wrap(|| format!("LastBlockWitness::make({})", number - 1))?;
let this_batch = LastBlockWitness::load(ctx, number, pool, tree)
.await
.with_wrap(|| format!("LastBlockWitness::make({number})"))?;
let mut conn = pool.connection(ctx).await.wrap("connection()")?;
let this = Self {
blocks: conn
.payloads(
ctx,
std::ops::Range {
start: prev_batch.last_block() + 1,
end: this_batch.last_block() + 1,
},
)
.await
.wrap("payloads()")?,
prev_batch,
this_batch,
};
Ok(this)
}
pub(crate) fn verify(&self, comm: &L1BatchCommit) -> anyhow::Result<()> {
let (last_number, last_hash) = self.this_batch.verify(&comm.this_batch)?;
let (mut prev_number, mut prev_hash) = self.prev_batch.verify(&comm.prev_batch)?;
anyhow::ensure!(
self.prev_batch
.info
.batch_number
.checked_add(1)
.context("batch_number overflow")?
== u64::from(comm.number.0)
);
anyhow::ensure!(self.this_batch.info.batch_number == u64::from(comm.number.0));
for (i, b) in self.blocks.iter().enumerate() {
anyhow::ensure!(b.l1_batch_number == comm.number);
anyhow::ensure!(b.protocol_version == self.this_batch.protocol_version);
anyhow::ensure!(b.last_in_batch == (i + 1 == self.blocks.len()));
prev_number += 1;
let mut hasher = L2BlockHasher::new(prev_number, b.timestamp, prev_hash);
for t in &b.transactions {
let t2: Transaction = abi::Transaction::try_from(t.clone())?.try_into()?;
anyhow::ensure!(t == &t2);
hasher.push_tx_hash(t.hash());
}
prev_hash = hasher.finalize(self.this_batch.protocol_version);
anyhow::ensure!(prev_hash == b.hash);
}
anyhow::ensure!(prev_hash == last_hash);
anyhow::ensure!(prev_number == last_number);
Ok(())
}
}