From ab5383af94bf23ef297dc457f6e0599214089f6a Mon Sep 17 00:00:00 2001 From: Roman Proskuryakoff Date: Wed, 27 Mar 2024 22:31:39 +0800 Subject: [PATCH 1/6] Refactor code to create fn ChainwaySequencer::produce_l2_block --- full-node/chainway-sequencer/src/sequencer.rs | 341 +++++++++--------- 1 file changed, 176 insertions(+), 165 deletions(-) diff --git a/full-node/chainway-sequencer/src/sequencer.rs b/full-node/chainway-sequencer/src/sequencer.rs index 4cc28be54..3fa011942 100644 --- a/full-node/chainway-sequencer/src/sequencer.rs +++ b/full-node/chainway-sequencer/src/sequencer.rs @@ -172,6 +172,169 @@ where Ok(()) } + async fn produce_l2_block( + &mut self, + last_finalized_block: ::FilteredBlock, + l1_fee_rate: u64, + rlp_txs: Vec, + ) -> Result<(), anyhow::Error> { + debug!( + "Sequencer: publishing block with {} transactions", + rlp_txs.len() + ); + let batch_info = HookSoftConfirmationInfo { + da_slot_height: last_finalized_block.header().height(), + da_slot_hash: last_finalized_block.header().hash().into(), + pre_state_root: self.state_root.clone().as_ref().to_vec(), + pub_key: self.sov_tx_signer_priv_key.pub_key().try_to_vec().unwrap(), + l1_fee_rate, + }; + let mut signed_batch: SignedSoftConfirmationBatch = batch_info.clone().into(); + // initially create sc info and call begin soft confirmation hook with it + let call_txs = CallMessage { txs: rlp_txs }; + let raw_message = + as EncodeCall>>::encode_call(call_txs); + let signed_blob = self.make_blob(raw_message); + let txs = vec![signed_blob.clone()]; + + let l2_height = match self + .ledger_db + .get_head_soft_batch() + .expect("Sequencer: Failed to get head soft batch") + { + Some((l2_height, _)) => l2_height.0 + 1, + None => 0, + }; + let last_finalized_block = self + .da_service + .get_block_at(last_finalized_block.header().height()) + .await + .unwrap(); + let prestate = self + .storage_manager + .create_storage_on_l2_height(l2_height) + .unwrap(); + + info!( + "Applying soft batch on DA block: {}", + hex::encode(last_finalized_block.header().hash().into()) + ); + + let pub_key = signed_batch.pub_key().clone(); + + match self.stf.begin_soft_batch( + &pub_key, + &self.state_root, + prestate.clone(), + Default::default(), + last_finalized_block.header(), + &mut signed_batch, + ) { + (Ok(()), batch_workspace) => { + let (batch_workspace, tx_receipts) = + self.stf.apply_soft_batch_txs(txs.clone(), batch_workspace); + + // create the unsigned batch with the txs then sign th sc + let unsigned_batch = UnsignedSoftConfirmationBatch::new( + last_finalized_block.header().height(), + last_finalized_block.header().hash().into(), + self.state_root.clone().as_ref().to_vec(), + txs, + l1_fee_rate, + ); + + let mut signed_soft_batch = self.sign_soft_confirmation_batch(unsigned_batch); + + let (batch_receipt, checkpoint) = self.stf.end_soft_batch( + self.sequencer_pub_key.as_ref(), + &mut signed_soft_batch, + tx_receipts, + batch_workspace, + ); + + // Finalize soft confirmation + let slot_result = self.stf.finalize_soft_batch( + batch_receipt, + checkpoint, + prestate, + &mut signed_soft_batch, + ); + + if slot_result.state_root.as_ref() == self.state_root.as_ref() { + debug!("Limiting number is reached for the current L1 block. State root is the same as before, skipping"); + // TODO: Check if below is legit + self.storage_manager + .save_change_set_l2(l2_height, slot_result.change_set)?; + + tracing::debug!("Finalizing l2 height: {:?}", l2_height); + self.storage_manager.finalize_l2(l2_height)?; + return Ok(()); + } + + info!( + "State root after applying slot: {:?}", + slot_result.state_root + ); + + let mut data_to_commit = SlotCommit::new(last_finalized_block.clone()); + for receipt in slot_result.batch_receipts { + data_to_commit.add_batch(receipt); + } + + // TODO: This will be a single receipt once we have apply_soft_batch. + let batch_receipt = data_to_commit.batch_receipts()[0].clone(); + + let next_state_root = slot_result.state_root; + + let soft_batch_receipt = SoftBatchReceipt::<_, _, Da::Spec> { + pre_state_root: self.state_root.as_ref().to_vec(), + post_state_root: next_state_root.as_ref().to_vec(), + phantom_data: PhantomData::, + batch_hash: batch_receipt.batch_hash, + da_slot_hash: last_finalized_block.header().hash(), + da_slot_height: last_finalized_block.header().height(), + tx_receipts: batch_receipt.tx_receipts, + soft_confirmation_signature: signed_soft_batch.signature().to_vec(), + pub_key: signed_soft_batch.pub_key().to_vec(), + l1_fee_rate: signed_soft_batch.l1_fee_rate(), + }; + + // TODO: this will only work for mock da + // when https://github.com/Sovereign-Labs/sovereign-sdk/issues/1218 + // is merged, rpc will access up to date storage then we won't need to finalize rigth away. + // however we need much better DA + finalization logic here + self.storage_manager + .save_change_set_l2(l2_height, slot_result.change_set)?; + + tracing::debug!("Finalizing l2 height: {:?}", l2_height); + self.storage_manager.finalize_l2(l2_height)?; + + self.state_root = next_state_root; + + self.ledger_db.commit_soft_batch(soft_batch_receipt, true)?; + + self.mempool + .remove_transactions(self.db_provider.last_block_tx_hashes()); + + // connect L1 and L2 height + self.ledger_db + .extend_l2_range_of_l1_slot( + SlotNumber(last_finalized_block.header().height()), + BatchNumber(l2_height), + ) + .expect("Sequencer: Failed to set L1 L2 connection"); + } + (Err(err), batch_workspace) => { + warn!( + "Failed to apply soft confirmation hook: {:?} \n reverting batch workspace", + err + ); + batch_workspace.revert(); + } + } + Ok(()) + } + pub async fn run(&mut self) -> Result<(), anyhow::Error> { // TODO: hotfix for mock da self.da_service.get_block_at(1).await.unwrap(); @@ -194,28 +357,6 @@ where BestTransactionsAttributes::base_fee(base_fee), ); - // TODO: implement block builder instead of just including every transaction in order - let rlp_txs: Vec = best_txs_with_base_fee - .into_iter() - .map(|tx| { - tx.to_recovered_transaction() - .into_signed() - .envelope_encoded() - .to_vec() - }) - .map(|rlp| RlpEvmTransaction { rlp }) - .collect(); - - debug!( - "Sequencer: publishing block with {} transactions", - rlp_txs.len() - ); - - let call_txs = CallMessage { txs: rlp_txs }; - let raw_message = - as EncodeCall>>::encode_call(call_txs); - let signed_blob = self.make_blob(raw_message); - let mut prev_l1_height = self .ledger_db .get_head_soft_batch()? @@ -317,150 +458,20 @@ where // TODO: this is where we would include forced transactions from the new L1 block } - let batch_info = HookSoftConfirmationInfo { - da_slot_height: last_finalized_block.header().height(), - da_slot_hash: last_finalized_block.header().hash().into(), - pre_state_root: self.state_root.clone().as_ref().to_vec(), - pub_key: self.sov_tx_signer_priv_key.pub_key().try_to_vec().unwrap(), - l1_fee_rate, - }; - let mut signed_batch: SignedSoftConfirmationBatch = batch_info.clone().into(); - // initially create sc info and call begin soft confirmation hook with it - let txs = vec![signed_blob.clone()]; - - let l2_height = match self - .ledger_db - .get_head_soft_batch() - .expect("Sequencer: Failed to get head soft batch") - { - Some((l2_height, _)) => l2_height.0 + 1, - None => 0, - }; - let last_finalized_block = self - .da_service - .get_block_at(last_finalized_block.header().height()) - .await - .unwrap(); - let prestate = self - .storage_manager - .create_storage_on_l2_height(l2_height) - .unwrap(); - - info!( - "Applying soft batch on DA block: {}", - hex::encode(last_finalized_block.header().hash().into()) - ); - - let pub_key = signed_batch.pub_key().clone(); - - match self.stf.begin_soft_batch( - &pub_key, - &self.state_root, - prestate.clone(), - Default::default(), - last_finalized_block.header(), - &mut signed_batch, - ) { - (Ok(()), batch_workspace) => { - let (batch_workspace, tx_receipts) = - self.stf.apply_soft_batch_txs(txs.clone(), batch_workspace); - - // create the unsigned batch with the txs then sign th sc - let unsigned_batch = UnsignedSoftConfirmationBatch::new( - last_finalized_block.header().height(), - last_finalized_block.header().hash().into(), - self.state_root.clone().as_ref().to_vec(), - txs, - l1_fee_rate, - ); - - let mut signed_soft_batch = - self.sign_soft_confirmation_batch(unsigned_batch); - - let (batch_receipt, checkpoint) = self.stf.end_soft_batch( - self.sequencer_pub_key.as_ref(), - &mut signed_soft_batch, - tx_receipts, - batch_workspace, - ); - - // Finalize soft confirmation - let slot_result = self.stf.finalize_soft_batch( - batch_receipt, - checkpoint, - prestate, - &mut signed_soft_batch, - ); - - if slot_result.state_root.as_ref() == self.state_root.as_ref() { - debug!("Limiting number is reached for the current L1 block. State root is the same as before, skipping"); - // TODO: Check if below is legit - self.storage_manager - .save_change_set_l2(l2_height, slot_result.change_set)?; - - tracing::debug!("Finalizing l2 height: {:?}", l2_height); - self.storage_manager.finalize_l2(l2_height)?; - return Ok(()); - } - - info!( - "State root after applying slot: {:?}", - slot_result.state_root - ); - - let mut data_to_commit = SlotCommit::new(last_finalized_block.clone()); - for receipt in slot_result.batch_receipts { - data_to_commit.add_batch(receipt); - } - - // TODO: This will be a single receipt once we have apply_soft_batch. - let batch_receipt = data_to_commit.batch_receipts()[0].clone(); - - let next_state_root = slot_result.state_root; - - let soft_batch_receipt = SoftBatchReceipt::<_, _, Da::Spec> { - pre_state_root: self.state_root.as_ref().to_vec(), - post_state_root: next_state_root.as_ref().to_vec(), - phantom_data: PhantomData::, - batch_hash: batch_receipt.batch_hash, - da_slot_hash: last_finalized_block.header().hash(), - da_slot_height: last_finalized_block.header().height(), - tx_receipts: batch_receipt.tx_receipts, - soft_confirmation_signature: signed_soft_batch.signature().to_vec(), - pub_key: signed_soft_batch.pub_key().to_vec(), - l1_fee_rate: signed_soft_batch.l1_fee_rate(), - }; - - // TODO: this will only work for mock da - // when https://github.com/Sovereign-Labs/sovereign-sdk/issues/1218 - // is merged, rpc will access up to date storage then we won't need to finalize rigth away. - // however we need much better DA + finalization logic here - self.storage_manager - .save_change_set_l2(l2_height, slot_result.change_set)?; - - tracing::debug!("Finalizing l2 height: {:?}", l2_height); - self.storage_manager.finalize_l2(l2_height)?; - - self.state_root = next_state_root; - - self.ledger_db.commit_soft_batch(soft_batch_receipt, true)?; - - self.mempool - .remove_transactions(self.db_provider.last_block_tx_hashes()); + // TODO: implement block builder instead of just including every transaction in order + let rlp_txs: Vec = best_txs_with_base_fee + .into_iter() + .map(|tx| { + tx.to_recovered_transaction() + .into_signed() + .envelope_encoded() + .to_vec() + }) + .map(|rlp| RlpEvmTransaction { rlp }) + .collect(); - // connect L1 and L2 height - self.ledger_db - .extend_l2_range_of_l1_slot( - SlotNumber(last_finalized_block.header().height()), - BatchNumber(l2_height), - ) - .expect("Sequencer: Failed to set L1 L2 connection"); - } - (Err(err), batch_workspace) => { - warn!("Failed to apply soft confirmation hook: {:?} \n reverting batch workspace", err); - batch_workspace.revert(); - } - } + self.produce_l2_block(last_finalized_block, l1_fee_rate, rlp_txs) + .await?; } } } From 088eab6f406b992c4794bb73f9839a862650e23b Mon Sep 17 00:00:00 2001 From: Roman Proskuryakoff Date: Wed, 27 Mar 2024 22:38:15 +0800 Subject: [PATCH 2/6] Rename last_finalized_block -> da_block --- full-node/chainway-sequencer/src/sequencer.rs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/full-node/chainway-sequencer/src/sequencer.rs b/full-node/chainway-sequencer/src/sequencer.rs index 3fa011942..8296ef343 100644 --- a/full-node/chainway-sequencer/src/sequencer.rs +++ b/full-node/chainway-sequencer/src/sequencer.rs @@ -174,7 +174,7 @@ where async fn produce_l2_block( &mut self, - last_finalized_block: ::FilteredBlock, + da_block: ::FilteredBlock, l1_fee_rate: u64, rlp_txs: Vec, ) -> Result<(), anyhow::Error> { @@ -183,8 +183,8 @@ where rlp_txs.len() ); let batch_info = HookSoftConfirmationInfo { - da_slot_height: last_finalized_block.header().height(), - da_slot_hash: last_finalized_block.header().hash().into(), + da_slot_height: da_block.header().height(), + da_slot_hash: da_block.header().hash().into(), pre_state_root: self.state_root.clone().as_ref().to_vec(), pub_key: self.sov_tx_signer_priv_key.pub_key().try_to_vec().unwrap(), l1_fee_rate, @@ -205,11 +205,6 @@ where Some((l2_height, _)) => l2_height.0 + 1, None => 0, }; - let last_finalized_block = self - .da_service - .get_block_at(last_finalized_block.header().height()) - .await - .unwrap(); let prestate = self .storage_manager .create_storage_on_l2_height(l2_height) @@ -217,7 +212,7 @@ where info!( "Applying soft batch on DA block: {}", - hex::encode(last_finalized_block.header().hash().into()) + hex::encode(da_block.header().hash().into()) ); let pub_key = signed_batch.pub_key().clone(); @@ -227,7 +222,7 @@ where &self.state_root, prestate.clone(), Default::default(), - last_finalized_block.header(), + da_block.header(), &mut signed_batch, ) { (Ok(()), batch_workspace) => { @@ -236,8 +231,8 @@ where // create the unsigned batch with the txs then sign th sc let unsigned_batch = UnsignedSoftConfirmationBatch::new( - last_finalized_block.header().height(), - last_finalized_block.header().hash().into(), + da_block.header().height(), + da_block.header().hash().into(), self.state_root.clone().as_ref().to_vec(), txs, l1_fee_rate, @@ -276,7 +271,7 @@ where slot_result.state_root ); - let mut data_to_commit = SlotCommit::new(last_finalized_block.clone()); + let mut data_to_commit = SlotCommit::new(da_block.clone()); for receipt in slot_result.batch_receipts { data_to_commit.add_batch(receipt); } @@ -291,8 +286,8 @@ where post_state_root: next_state_root.as_ref().to_vec(), phantom_data: PhantomData::, batch_hash: batch_receipt.batch_hash, - da_slot_hash: last_finalized_block.header().hash(), - da_slot_height: last_finalized_block.header().height(), + da_slot_hash: da_block.header().hash(), + da_slot_height: da_block.header().height(), tx_receipts: batch_receipt.tx_receipts, soft_confirmation_signature: signed_soft_batch.signature().to_vec(), pub_key: signed_soft_batch.pub_key().to_vec(), @@ -319,7 +314,7 @@ where // connect L1 and L2 height self.ledger_db .extend_l2_range_of_l1_slot( - SlotNumber(last_finalized_block.header().height()), + SlotNumber(da_block.header().height()), BatchNumber(l2_height), ) .expect("Sequencer: Failed to set L1 L2 connection"); From 73df652e0191139feb87e4b2411cf20c243f6c0c Mon Sep 17 00:00:00 2001 From: Roman Proskuryakoff Date: Wed, 27 Mar 2024 23:17:55 +0800 Subject: [PATCH 3/6] Publish empty L2 blocks for missed L1 blocks --- full-node/chainway-sequencer/src/sequencer.rs | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/full-node/chainway-sequencer/src/sequencer.rs b/full-node/chainway-sequencer/src/sequencer.rs index 8296ef343..ec8d947a6 100644 --- a/full-node/chainway-sequencer/src/sequencer.rs +++ b/full-node/chainway-sequencer/src/sequencer.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::marker::PhantomData; use std::net::SocketAddr; use std::sync::Arc; @@ -383,24 +384,32 @@ where last_finalized_height ); - let last_finalized_block = self - .da_service - .get_block_at(last_finalized_height) - .await - .unwrap(); - let l1_fee_rate = self.da_service.get_fee_rate().await.unwrap(); - - if last_finalized_height != prev_l1_height { - let previous_l1_block = - self.da_service.get_block_at(prev_l1_height).await.unwrap(); - - // Compare if there is no skip - if last_finalized_block.header().prev_hash() - != previous_l1_block.header().hash() - { - // TODO: This shouldn't happen. If it does, then we should produce at least 1 block for the blocks in between + let new_da_block = match last_finalized_height.cmp(&prev_l1_height) { + Ordering::Less => { + panic!("DA L1 height is less than Ledger finalized height"); + } + Ordering::Equal => None, + Ordering::Greater => { + // Compare if there is no skip + if last_finalized_height - prev_l1_height > 1 { + // This shouldn't happen. If it does, then we should produce at least 1 block for the blocks in between + for skipped_height in (prev_l1_height + 1)..last_finalized_height { + debug!( + "Sequencer: publishing empty L2 for skipped L1 block: {:?}", + skipped_height + ); + let da_block = + self.da_service.get_block_at(skipped_height).await.unwrap(); + self.produce_l2_block(da_block, l1_fee_rate, vec![]).await?; + } + } + let prev_l1_height = last_finalized_height - 1; + Some(prev_l1_height) } + }; + + if let Some(prev_l1_height) = new_da_block { debug!("Sequencer: new L1 block, checking if commitment should be submitted"); let commitment_info = commitment_controller::get_commitment_info( @@ -465,6 +474,12 @@ where .map(|rlp| RlpEvmTransaction { rlp }) .collect(); + let last_finalized_block = self + .da_service + .get_block_at(last_finalized_height) + .await + .unwrap(); + self.produce_l2_block(last_finalized_block, l1_fee_rate, rlp_txs) .await?; } From 9f69ace4f708310d236e93d0ecba5993e0b1836b Mon Sep 17 00:00:00 2001 From: Roman Proskuryakoff Date: Thu, 28 Mar 2024 00:11:33 +0800 Subject: [PATCH 4/6] Fix sequencer tests --- examples/demo-rollup/tests/sequencer_commitments/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/demo-rollup/tests/sequencer_commitments/mod.rs b/examples/demo-rollup/tests/sequencer_commitments/mod.rs index 3a76636c2..f46dcbf3e 100644 --- a/examples/demo-rollup/tests/sequencer_commitments/mod.rs +++ b/examples/demo-rollup/tests/sequencer_commitments/mod.rs @@ -73,8 +73,6 @@ async fn sequencer_sends_commitments_to_da_layer() { height += 1; } - da_service.publish_test_block().await.unwrap(); - test_client.send_publish_batch_request().await; da_service.publish_test_block().await.unwrap(); test_client.send_publish_batch_request().await; From 2fe6aeb2c57b4d5be8d937bca61fa45614403932 Mon Sep 17 00:00:00 2001 From: Roman Proskuryakoff Date: Thu, 28 Mar 2024 00:41:29 +0800 Subject: [PATCH 5/6] Check L1 height when producing L2 blocks --- full-node/chainway-sequencer/src/sequencer.rs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/full-node/chainway-sequencer/src/sequencer.rs b/full-node/chainway-sequencer/src/sequencer.rs index ec8d947a6..0a0824b93 100644 --- a/full-node/chainway-sequencer/src/sequencer.rs +++ b/full-node/chainway-sequencer/src/sequencer.rs @@ -183,6 +183,19 @@ where "Sequencer: publishing block with {} transactions", rlp_txs.len() ); + let da_height = da_block.header().height(); + let (l2_height, l1_height) = match self + .ledger_db + .get_head_soft_batch() + .expect("Sequencer: Failed to get head soft batch") + { + Some((l2_height, sb)) => (l2_height.0 + 1, sb.da_slot_height), + None => (0, da_height), + }; + anyhow::ensure!( + l1_height == da_height || l1_height + 1 == da_height, + "Sequencer: L1 height mismatch, expected {da_height} (or {da_height}-1), got {l1_height}", + ); let batch_info = HookSoftConfirmationInfo { da_slot_height: da_block.header().height(), da_slot_hash: da_block.header().hash().into(), @@ -198,14 +211,6 @@ where let signed_blob = self.make_blob(raw_message); let txs = vec![signed_blob.clone()]; - let l2_height = match self - .ledger_db - .get_head_soft_batch() - .expect("Sequencer: Failed to get head soft batch") - { - Some((l2_height, _)) => l2_height.0 + 1, - None => 0, - }; let prestate = self .storage_manager .create_storage_on_l2_height(l2_height) @@ -385,6 +390,7 @@ where ); let l1_fee_rate = self.da_service.get_fee_rate().await.unwrap(); + let new_da_block = match last_finalized_height.cmp(&prev_l1_height) { Ordering::Less => { panic!("DA L1 height is less than Ledger finalized height"); From 00fabb6c0be06ca50ddcfa235e40c038a4b0dd05 Mon Sep 17 00:00:00 2001 From: eyusufatik Date: Thu, 28 Mar 2024 14:43:37 +0300 Subject: [PATCH 6/6] fix merge error --- crates/sequencer/src/sequencer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sequencer/src/sequencer.rs b/crates/sequencer/src/sequencer.rs index e6a6d712d..5e5a543a5 100644 --- a/crates/sequencer/src/sequencer.rs +++ b/crates/sequencer/src/sequencer.rs @@ -207,7 +207,7 @@ where // initially create sc info and call begin soft confirmation hook with it let call_txs = CallMessage { txs: rlp_txs }; let raw_message = - as EncodeCall>>::encode_call(call_txs); + as EncodeCall>>::encode_call(call_txs); let signed_blob = self.make_blob(raw_message); let txs = vec![signed_blob.clone()];