New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(state-keeper): Parallel l2 block sealing #1801
base: main
Are you sure you want to change the base?
Conversation
last_in_batch: block.last_batch_miniblock == Some(block.number), | ||
last_in_batch: block.tx_count == 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fictive l2 block sealing and L1 batch sealing are not happening in the same transaction anymore, so the only way to determine if l2 block is the last in batch is to check if it has 0 txs.
if this.pre_insert_txs { | ||
let mut connection = pool.connection_tagged("state_keeper").await?; | ||
let progress = L2_BLOCK_METRICS.start(L2BlockSealStage::PreInsertTxs, is_fictive); | ||
this.insert_transactions(&mut connection) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to merge pre_insert_txs
and mark_txs_as_executed_in_l2_block
in a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at all additional filtering needed to beI wonder whether a potential alternative with primitive L2 block lifecycle would be easier. That is:
- Insert an L2 block header before other data (meaning that foreign keys don't need to be removed)
- ...but mark the L2 block as incomplete until all other data is inserted. Having a new
miniblocks
field looks like an overkill / impractical; another way is to record the latest finished L2 block in a singleton table.
On the one hand, this would allow retaining FK constraints and make filtering out incomplete L2 blocks more explicit. OTOH, it'd require filtering out throughout all queries touching L2 blocks, which is easy to screw up.
core/lib/zksync_core/src/state_keeper/io/seal_logic/l2_block_seal_subtasks.rs
Outdated
Show resolved
Hide resolved
core/lib/zksync_core/src/state_keeper/io/seal_logic/l2_block_seal_subtasks.rs
Outdated
Show resolved
Hide resolved
core/lib/zksync_core/src/state_keeper/io/seal_logic/l2_block_seal_subtasks.rs
Outdated
Show resolved
Hide resolved
Didn't have a deep loop but wanted to respond to @slowli 's comment on
IMO it might be even beneficial to remote foreign keys - this will make insertion even faster. |
My thoughts on alternative approach with inserting header first: |
core/lib/zksync_core/src/state_keeper/io/seal_logic/l2_block_seal_subtasks.rs
Outdated
Show resolved
Hide resolved
No performance difference detected (anymore) |
What ❔
L2 block data (tx results, storage logs, events, l2 to l1 logs, factory deps, tokens) is sealed in parallel using concurrent DB connections. L2 block header is inserted after all l2 block data is inserted and serves as a marker that l2 block is sealed. DB queries are adjusted so L2 block is not accessed before block is sealed. Note, that some of the queries are only queried for resolved block range, i.e. caller never tries to access "pending" data. I haven't adjusted such queries on purpose to keep their performance the same.
Why ❔
Improves L2 block sealing performance
Checklist
zk fmt
andzk lint
.zk spellcheck
.zk linkcheck
.