Repository: deltadevsde/prism
Pull request #318: fix(prover/syncer)!: race condition for fullnode sync
Author: login: distractedm1nd
Created: 2025-05-28T11:28:24Z | Merged: 2025-05-30T10:48:59Z

Previously, `FinalizedEpochs` did not track the height of the DA layer at the time the epoch began being finalized. This led to the case that, transactions posted between the beginning of finalization and the posting of the epoch, were queued incorrectly by other fullnodes to be executed too early.

To fix this, I added a field to `FinalizedEpoch` that tracks this, and changed the transaction buffer to be a custom type that now tracks the height information as well for correct execution

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added configurable delays for block and epoch posting in the in-memory data availability layer.
  - Introduced a transaction buffer for improved transaction management and synchronization.
  - Added a new test to verify commitment synchronization with concurrent transactions.

- **Improvements**
  - Enhanced documentation for epoch-related data structures.
  - Updated method signatures to include data availability height for better tracking.
  - Improved logging for data availability and epoch processing.

- **Bug Fixes**
  - Ensured signature verification covers all relevant epoch fields.

- **Chores**
  - Updated verification keys for enhanced security.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/crates/cli/src/cfg.rs b/crates/cli/src/cfg.rs
index 07bb8724..f73dbdca 100644
--- a/crates/cli/src/cfg.rs
+++ b/crates/cli/src/cfg.rs
@@ -16,7 +16,7 @@ use prism_storage::{
 };
 use prism_telemetry::config::{TelemetryConfig, get_default_telemetry_config};
 use serde::{Deserialize, Serialize};
-use std::{fs, path::Path, str::FromStr, sync::Arc};
+use std::{fs, path::Path, str::FromStr, sync::Arc, time::Duration};
 use tracing::{error, info, warn};
 
 use prism_da::{
@@ -368,7 +368,8 @@ pub async fn initialize_da_layer(
             unreachable!() // This line should never be reached due to the return in the last iteration
         }
         DALayerOption::InMemory => {
-            let (da_layer, _height_rx, _block_rx) = InMemoryDataAvailabilityLayer::new(30);
+            let (da_layer, _height_rx, _block_rx) =
+                InMemoryDataAvailabilityLayer::new(Duration::from_secs(10));
             Ok(Arc::new(da_layer) as Arc<dyn DataAvailabilityLayer + 'static>)
         }
     }
@@ -405,7 +406,8 @@ pub async fn initialize_light_da_layer(
                 >)
         }
         DALayerOption::InMemory => {
-            let (da_layer, _height_rx, _block_rx) = InMemoryDataAvailabilityLayer::new(30);
+            let (da_layer, _height_rx, _block_rx) =
+                InMemoryDataAvailabilityLayer::new(Duration::from_secs(10));
             Ok(Arc::new(da_layer))
         }
     }
diff --git a/crates/da/src/lib.rs b/crates/da/src/lib.rs
index 710ae94d..7db8ad77 100644
--- a/crates/da/src/lib.rs
+++ b/crates/da/src/lib.rs
@@ -35,13 +35,30 @@ type CompressedProof = SP1ProofWithPublicValues;
 // FinalizedEpoch is the data structure that represents the finalized epoch data, and is posted to the DA layer.
 #[derive(Serialize, Deserialize, Clone, Debug)]
 pub struct FinalizedEpoch {
+    /// The epoch height.
     pub height: u64,
+
+    /// Commitment of the last epoch.
     pub prev_commitment: Digest,
+
+    /// Commitment after the state transition to the current epoch.
     pub current_commitment: Digest,
+
+    /// Groth16 proof of the state transition.
     pub proof: Groth16Proof,
-    pub compressed_proof: CompressedProof,
+    /// Auxillary data for WASM arch to read the public values of the proof.
     pub public_values: Vec<u8>,
+
+    /// Compressed proof of the state transition, stored for cheaper recursive proving.
+    pub compressed_proof: CompressedProof,
+
+    /// The signature of this struct by the prover, with the signature field set to `None`.
     pub signature: Option<String>,
+
+    /// The tip of the DA layer at the time of the epoch; All transactions in
+    /// this epoch are from the DA blocks [previous_epoch.tip_da_height,
+    /// current_epoch.tip_da_height).
+    pub tip_da_height: u64,
 }
 
 impl FinalizedEpoch {
@@ -61,6 +78,7 @@ impl FinalizedEpoch {
             compressed_proof: self.compressed_proof.clone(),
             public_values: self.public_values.clone(),
             signature: None,
+            tip_da_height: self.tip_da_height,
         };
 
         let message = epoch_without_signature
diff --git a/crates/da/src/memory.rs b/crates/da/src/memory.rs
index 1b147d7b..984c3ba7 100644
--- a/crates/da/src/memory.rs
+++ b/crates/da/src/memory.rs
@@ -26,11 +26,39 @@ pub struct InMemoryDataAvailabilityLayer {
     latest_height: Arc<RwLock<u64>>,
     height_update_tx: broadcast::Sender<u64>,
     block_update_tx: broadcast::Sender<Block>,
-    block_time: u64,
+    block_time: Duration,
+
+    // For testing: Because mock proofs are generated very quickly, it is
+    // helpful to delay the posting of the epoch to test some latency scenarios.
+    epoch_posting_delay: Option<Duration>,
 }
 
 impl InMemoryDataAvailabilityLayer {
-    pub fn new(block_time: u64) -> (Self, broadcast::Receiver<u64>, broadcast::Receiver<Block>) {
+    pub fn new(
+        block_time: Duration,
+    ) -> (Self, broadcast::Receiver<u64>, broadcast::Receiver<Block>) {
+        let (height_tx, height_rx) = broadcast::channel(100);
+        let (block_tx, block_rx) = broadcast::channel(100);
+        (
+            Self {
+                blocks: Arc::new(RwLock::new(Vec::new())),
+                pending_transactions: Arc::new(RwLock::new(Vec::new())),
+                pending_epochs: Arc::new(RwLock::new(VecDeque::new())),
+                latest_height: Arc::new(RwLock::new(0)),
+                height_update_tx: height_tx,
+                block_update_tx: block_tx,
+                block_time,
+                epoch_posting_delay: None,
+            },
+            height_rx,
+            block_rx,
+        )
+    }
+
+    pub fn new_with_epoch_delay(
+        block_time: Duration,
+        epoch_delay: Duration,
+    ) -> (Self, broadcast::Receiver<u64>, broadcast::Receiver<Block>) {
         let (height_tx, height_rx) = broadcast::channel(100);
         let (block_tx, block_rx) = broadcast::channel(100);
         (
@@ -42,6 +70,7 @@ impl InMemoryDataAvailabilityLayer {
                 height_update_tx: height_tx,
                 block_update_tx: block_tx,
                 block_time,
+                epoch_posting_delay: Some(epoch_delay),
             },
             height_rx,
             block_rx,
@@ -49,7 +78,7 @@ impl InMemoryDataAvailabilityLayer {
     }
 
     async fn produce_blocks(self: Arc<Self>) {
-        let mut interval = interval(Duration::from_secs(self.block_time));
+        let mut interval = interval(self.block_time);
         loop {
             interval.tick().await;
             let mut blocks = self.blocks.write().await;
@@ -119,6 +148,11 @@ impl DataAvailabilityLayer for InMemoryDataAvailabilityLayer {
     }
 
     async fn submit_finalized_epoch(&self, epoch: FinalizedEpoch) -> Result<u64> {
+        // wait for epoch posting delay
+        if let Some(delay) = self.epoch_posting_delay {
+            tokio::time::sleep(delay).await;
+        }
+
         let mut pending_epochs = self.pending_epochs.write().await;
         pending_epochs.push_back(epoch);
         let height = self.get_latest_height().await?;
diff --git a/crates/node_types/prover/src/lib.rs b/crates/node_types/prover/src/lib.rs
index 252ed42a..3a025659 100644
--- a/crates/node_types/prover/src/lib.rs
+++ b/crates/node_types/prover/src/lib.rs
@@ -1,10 +1,11 @@
 pub mod prover;
-pub mod webserver;
 pub mod prover_engine;
 pub mod sequencer;
 pub mod syncer;
+mod tx_buffer;
+pub mod webserver;
 
-pub use prover::{Config, Prover, SyncerConfig, SequencerConfig, ProverEngineConfig};
+pub use prover::{Config, Prover, ProverEngineConfig, SequencerConfig, SyncerConfig};
 pub use webserver::WebServerConfig;
 
 #[macro_use]
diff --git a/crates/node_types/prover/src/prover/mod.rs b/crates/node_types/prover/src/prover/mod.rs
index 7cebe34d..b2174f64 100644
--- a/crates/node_types/prover/src/prover/mod.rs
+++ b/crates/node_types/prover/src/prover/mod.rs
@@ -169,8 +169,9 @@ impl Prover {
         &self,
         epoch_height: u64,
         transactions: Vec<Transaction>,
+        tip_da_height: u64,
     ) -> Result<u64> {
-        self.sequencer.finalize_new_epoch(epoch_height, transactions, &self.prover_engine).await
+        self.sequencer.finalize_new_epoch(epoch_height, transactions, &self.prover_engine, tip_da_height).await
     }
 
     pub async fn validate_and_queue_update(&self, transaction: Transaction) -> Result<()> {
@@ -192,7 +193,6 @@ impl Prover {
         self.syncer.get_da()
     }
 
-
     pub async fn run(self: Arc<Self>) -> Result<()> {
         let mut futures = JoinSet::new();
 
diff --git a/crates/node_types/prover/src/prover/tests.rs b/crates/node_types/prover/src/prover/tests.rs
index 64c19acf..13747450 100644
--- a/crates/node_types/prover/src/prover/tests.rs
+++ b/crates/node_types/prover/src/prover/tests.rs
@@ -11,7 +11,7 @@ use prism_storage::inmemory::InMemoryDatabase;
 
 // Helper function to create a test prover instance
 async fn create_test_prover(algorithm: CryptoAlgorithm) -> Arc<Prover> {
-    let (da_layer, _rx, _brx) = InMemoryDataAvailabilityLayer::new(1);
+    let (da_layer, _rx, _brx) = InMemoryDataAvailabilityLayer::new(Duration::from_millis(500));
     let da_layer = Arc::new(da_layer);
     let db: Arc<Box<dyn Database>> = Arc::new(Box::new(InMemoryDatabase::new()));
     let mut cfg = Config::default_with_key_algorithm(algorithm).unwrap();
@@ -62,7 +62,7 @@ async fn test_posts_epoch_after_max_gap(algorithm: CryptoAlgorithm) {
 
     // Verify commitment changes after epoch finalization
     let commitment_before_epoch = prover.get_commitment().await.unwrap();
-    let initial_epoch_height = prover.finalize_new_epoch(0, test_transactions).await.unwrap();
+    let initial_epoch_height = prover.finalize_new_epoch(0, test_transactions, 0).await.unwrap();
     let commitment_after_epoch = prover.get_commitment().await.unwrap();
     assert_ne!(
         commitment_before_epoch, commitment_after_epoch,
@@ -76,7 +76,8 @@ async fn test_posts_epoch_after_max_gap(algorithm: CryptoAlgorithm) {
             break;
         }
     }
-    let initial_epoch = prover.get_da().get_finalized_epoch(initial_epoch_height).await.unwrap().unwrap();
+    let initial_epoch =
+        prover.get_da().get_finalized_epoch(initial_epoch_height).await.unwrap().unwrap();
 
     // Wait for gap length
     loop {
@@ -96,7 +97,8 @@ async fn test_posts_epoch_after_max_gap(algorithm: CryptoAlgorithm) {
         }
     }
     // Verify gap proof contents
-    let gap_proof = prover.get_da().get_finalized_epoch(current_epoch_height).await.unwrap().unwrap();
+    let gap_proof =
+        prover.get_da().get_finalized_epoch(current_epoch_height).await.unwrap().unwrap();
     assert_eq!(
         gap_proof.height,
         initial_epoch.height + 1,
@@ -201,19 +203,27 @@ async fn test_finalize_new_epoch(algorithm: CryptoAlgorithm) {
     let transactions = create_mock_transactions(algorithm, "test_service".to_string());
 
     let prev_commitment = prover.get_commitment().await.unwrap();
-    prover.finalize_new_epoch(0, transactions).await.unwrap();
+    prover.finalize_new_epoch(0, transactions, 0).await.unwrap();
 
     let new_commitment = prover.get_commitment().await.unwrap();
     assert_ne!(prev_commitment, new_commitment);
 }
 
 async fn test_restart_sync_from_scratch(algorithm: CryptoAlgorithm) {
-    let (da_layer, _rx, mut brx) = InMemoryDataAvailabilityLayer::new(1);
+    let (da_layer, _rx, mut brx) = InMemoryDataAvailabilityLayer::new(Duration::from_millis(200));
     let da_layer = Arc::new(da_layer);
     let db1: Arc<Box<dyn Database>> = Arc::new(Box::new(InMemoryDatabase::new()));
     let db2: Arc<Box<dyn Database>> = Arc::new(Box::new(InMemoryDatabase::new()));
     let cfg = Config::default_with_key_algorithm(algorithm).unwrap();
-    let prover = Arc::new(Prover::new(db1.clone(), da_layer.clone(), &cfg, CancellationToken::new()).unwrap());
+    let prover = Arc::new(
+        Prover::new(
+            db1.clone(),
+            da_layer.clone(),
+            &cfg,
+            CancellationToken::new(),
+        )
+        .unwrap(),
+    );
 
     let runner = prover.clone();
     spawn(async move {
@@ -233,7 +243,15 @@ async fn test_restart_sync_from_scratch(algorithm: CryptoAlgorithm) {
 
     assert_eq!(prover.get_db().get_latest_epoch_height().unwrap(), 3);
 
-    let prover2 = Arc::new(Prover::new(db2.clone(), da_layer.clone(), &cfg, CancellationToken::new()).unwrap());
+    let prover2 = Arc::new(
+        Prover::new(
+            db2.clone(),
+            da_layer.clone(),
+            &cfg,
+            CancellationToken::new(),
+        )
+        .unwrap(),
+    );
     let runner = prover2.clone();
     spawn(async move { runner.run().await.unwrap() });
 
@@ -250,12 +268,143 @@ async fn test_restart_sync_from_scratch(algorithm: CryptoAlgorithm) {
     }
 }
 
+async fn test_prover_fullnode_commitment_sync_with_racing_transactions(algorithm: CryptoAlgorithm) {
+    // Setup shared DA layer
+    let (da_layer, _rx, mut brx) = InMemoryDataAvailabilityLayer::new_with_epoch_delay(
+        Duration::from_millis(200),
+        Duration::from_secs(1),
+    );
+    let da_layer = Arc::new(da_layer);
+
+    // Setup prover (with prover enabled)
+    let prover_db: Arc<Box<dyn Database>> = Arc::new(Box::new(InMemoryDatabase::new()));
+    let mut prover_cfg = Config::default_with_key_algorithm(algorithm).unwrap();
+    prover_cfg.syncer.prover_enabled = true;
+    let prover = Arc::new(
+        Prover::new(
+            prover_db.clone(),
+            da_layer.clone(),
+            &prover_cfg,
+            CancellationToken::new(),
+        )
+        .unwrap(),
+    );
+
+    // Setup fullnode (with prover disabled) - use same verifying key as prover
+    let fullnode_db: Arc<Box<dyn Database>> = Arc::new(Box::new(InMemoryDatabase::new()));
+    let mut fullnode_cfg = Config::default_with_key_algorithm(algorithm).unwrap();
+    fullnode_cfg.syncer.prover_enabled = false;
+    fullnode_cfg.syncer.verifying_key = prover_cfg.syncer.verifying_key.clone();
+    let fullnode = Arc::new(
+        Prover::new(
+            fullnode_db.clone(),
+            da_layer.clone(),
+            &fullnode_cfg,
+            CancellationToken::new(),
+        )
+        .unwrap(),
+    );
+
+    // Start both nodes
+    let prover_handle = prover.clone();
+    spawn(async move {
+        prover_handle.run().await.unwrap();
+    });
+
+    let fullnode_handle = fullnode.clone();
+    spawn(async move {
+        fullnode_handle.run().await.unwrap();
+    });
+
+    // Wait for both nodes to boot up
+    tokio::time::sleep(Duration::from_millis(1000)).await;
+
+    // Ensure both nodes are at the same height before proceeding
+    let mut prover_synced = false;
+    let mut fullnode_synced = false;
+
+    for _ in 0..50 {
+        // 5 second timeout
+        let prover_height = prover.get_db().get_last_synced_height().unwrap_or(0);
+        let fullnode_height = fullnode.get_db().get_last_synced_height().unwrap_or(0);
+        let da_height = da_layer.get_latest_height().await.unwrap();
+
+        if prover_height >= da_height && prover_height > 0 {
+            prover_synced = true;
+        }
+        if fullnode_height >= da_height && fullnode_height > 0 {
+            fullnode_synced = true;
+        }
+
+        if prover_synced && fullnode_synced {
+            break;
+        }
+
+        tokio::time::sleep(Duration::from_millis(100)).await;
+    }
+
+    assert!(
+        prover_synced && fullnode_synced,
+        "Nodes failed to sync before test"
+    );
+
+    // Create all transactions and split them
+    let all_transactions = create_mock_transactions(algorithm, "test_service".to_string());
+    let (initial_transactions, racing_transactions) = all_transactions.split_at(1);
+
+    // Submit initial transaction for prover to process
+    for transaction in initial_transactions {
+        da_layer.submit_transactions(vec![transaction.clone()]).await.unwrap();
+    }
+
+    // Wait a bit for transactions to be processed
+    tokio::time::sleep(Duration::from_millis(100)).await;
+
+    // Submit racing transactions that arrive while prover is creating proof
+    for transaction in racing_transactions {
+        da_layer.submit_transactions(vec![transaction.clone()]).await.unwrap();
+    }
+
+    // Wait for the prover to create and publish an epoch (this should happen with the racing transactions buffered)
+    let mut epoch_found = false;
+    while let Ok(new_block) = brx.recv().await {
+        if new_block.epoch.is_some() {
+            epoch_found = true;
+            break;
+        }
+    }
+    assert!(epoch_found, "Prover should have created an epoch");
+
+    // Wait for fullnode to sync the epoch
+    // If this test flakes, it could be because it needs a tiny bit more time here
+    tokio::time::sleep(Duration::from_millis(5000)).await;
+
+    // Both nodes should have the same commitment despite racing transactions
+    let prover_commitment = prover.get_commitment().await.unwrap();
+    let fullnode_commitment = fullnode.get_commitment().await.unwrap();
+
+    assert_eq!(
+        prover_commitment, fullnode_commitment,
+        "Prover and fullnode should have matching commitments even with racing transactions"
+    );
+
+    // Verify they are at the same epoch height
+    let prover_epoch = prover.get_db().get_latest_epoch_height().unwrap();
+    let fullnode_epoch = fullnode.get_db().get_latest_epoch_height().unwrap();
+    assert_eq!(
+        prover_epoch, fullnode_epoch,
+        "Both nodes should be at the same epoch height"
+    );
+}
+
 async fn test_load_persisted_state(algorithm: CryptoAlgorithm) {
-    let (da_layer, _rx, mut brx) = InMemoryDataAvailabilityLayer::new(1);
+    let (da_layer, _rx, mut brx) = InMemoryDataAvailabilityLayer::new(Duration::from_millis(500));
     let da_layer = Arc::new(da_layer);
     let db: Arc<Box<dyn Database>> = Arc::new(Box::new(InMemoryDatabase::new()));
     let cfg = Config::default_with_key_algorithm(algorithm).unwrap();
-    let prover = Arc::new(Prover::new(db.clone(), da_layer.clone(), &cfg, CancellationToken::new()).unwrap());
+    let prover = Arc::new(
+        Prover::new(db.clone(), da_layer.clone(), &cfg, CancellationToken::new()).unwrap(),
+    );
 
     let runner = prover.clone();
     spawn(async move {
@@ -275,7 +424,9 @@ async fn test_load_persisted_state(algorithm: CryptoAlgorithm) {
 
     assert_eq!(prover.get_db().get_latest_epoch_height().unwrap(), 3);
 
-    let prover2 = Arc::new(Prover::new(db.clone(), da_layer.clone(), &cfg, CancellationToken::new()).unwrap());
+    let prover2 = Arc::new(
+        Prover::new(db.clone(), da_layer.clone(), &cfg, CancellationToken::new()).unwrap(),
+    );
     let runner = prover2.clone();
     spawn(async move { runner.run().await.unwrap() });
     let epoch = prover2.get_db().get_latest_epoch_height().unwrap();
@@ -315,3 +466,4 @@ generate_algorithm_tests!(test_finalize_new_epoch);
 generate_algorithm_tests!(test_restart_sync_from_scratch);
 generate_algorithm_tests!(test_load_persisted_state);
 generate_algorithm_tests!(test_posts_epoch_after_max_gap);
+generate_algorithm_tests!(test_prover_fullnode_commitment_sync_with_racing_transactions);
diff --git a/crates/node_types/prover/src/sequencer.rs b/crates/node_types/prover/src/sequencer.rs
index 97c3eb3c..f8267420 100644
--- a/crates/node_types/prover/src/sequencer.rs
+++ b/crates/node_types/prover/src/sequencer.rs
@@ -115,6 +115,7 @@ impl Sequencer {
         epoch_height: u64,
         transactions: Vec<Transaction>,
         prover_engine: &Arc<ProverEngine>,
+        tip_da_height: u64,
     ) -> Result<u64> {
         let mut tree = self.tree.write().await;
         let batch = tree.process_batch(transactions)?;
@@ -133,11 +134,17 @@ impl Sequencer {
             compressed_proof,
             public_values,
             signature: None,
+            tip_da_height,
         };
 
         epoch_json.insert_signature(&self.signing_key)?;
 
+        debug!("Submitting finalized epoch height {} to DA", epoch_height);
         let da_height = self.da.submit_finalized_epoch(epoch_json.clone()).await?;
+        debug!(
+            "Finalized epoch height {} submitted to DA at height {}",
+            epoch_height, da_height
+        );
         let mut latest_da_height = self.latest_epoch_da_height.write().await;
         *latest_da_height = da_height;
 
diff --git a/crates/node_types/prover/src/syncer.rs b/crates/node_types/prover/src/syncer.rs
index 95943604..dc923f2e 100644
--- a/crates/node_types/prover/src/syncer.rs
+++ b/crates/node_types/prover/src/syncer.rs
@@ -4,16 +4,17 @@ use prism_da::{DataAvailabilityLayer, FinalizedEpoch};
 use prism_keys::VerifyingKey;
 use prism_storage::database::Database;
 use prism_telemetry_registry::metrics_registry::get_metrics;
-use std::{collections::VecDeque, sync::Arc};
+use std::sync::Arc;
 use tokio::sync::{RwLock, broadcast};
 use tokio_util::sync::CancellationToken;
 
-use crate::{prover_engine::ProverEngine, sequencer::Sequencer};
+use crate::{prover_engine::ProverEngine, sequencer::Sequencer, tx_buffer::TxBuffer};
 
 #[derive(Clone)]
 pub struct Syncer {
     da: Arc<dyn DataAvailabilityLayer>,
     db: Arc<Box<dyn Database>>,
+    tx_buffer: Arc<RwLock<TxBuffer>>,
     verifying_key: VerifyingKey,
     max_epochless_gap: u64,
     latest_epoch_da_height: Arc<RwLock<u64>>,
@@ -35,6 +36,7 @@ impl Syncer {
         Self {
             da,
             db,
+            tx_buffer: Arc::new(RwLock::new(TxBuffer::new())),
             verifying_key: config.verifying_key.clone(),
             max_epochless_gap: config.max_epochless_gap,
             latest_epoch_da_height,
@@ -83,14 +85,12 @@ impl Syncer {
         mut incoming_heights: broadcast::Receiver<u64>,
         cancellation_token: CancellationToken,
     ) -> Result<()> {
-        let mut buffered_transactions: VecDeque<Transaction> = VecDeque::new();
         let mut current_height = start_height;
 
         while current_height <= end_height {
             tokio::select! {
                 result = self.process_da_height(
                     current_height,
-                    &mut buffered_transactions,
                     false,
                 ) => {
                     result?;
@@ -122,7 +122,6 @@ impl Syncer {
                     }
                     self.process_da_height(
                         height,
-                        &mut buffered_transactions,
                         true,
                     ).await?;
                     current_height += 1;
@@ -136,12 +135,7 @@ impl Syncer {
         }
     }
 
-    async fn process_da_height(
-        &self,
-        height: u64,
-        buffered_transactions: &mut VecDeque<Transaction>,
-        is_real_time: bool,
-    ) -> Result<()> {
+    async fn process_da_height(&self, height: u64, is_real_time: bool) -> Result<()> {
         let next_epoch_height = match self.db.get_latest_epoch_height() {
             Ok(height) => height + 1,
             Err(_) => 0,
@@ -150,6 +144,13 @@ impl Syncer {
         let transactions = self.da.get_transactions(height).await?;
         let epoch_result = self.da.get_finalized_epoch(height).await?;
 
+        trace!(
+            "DA query at height {}: {} transactions, epoch present: {}",
+            height,
+            transactions.len(),
+            epoch_result.is_some()
+        );
+
         debug!(
             "processing {} height {}, next_epoch_height: {}",
             if is_real_time { "new" } else { "old" },
@@ -158,20 +159,36 @@ impl Syncer {
         );
 
         if let Some(epoch) = epoch_result {
-            self.process_epoch(epoch, buffered_transactions).await?;
+            debug!(
+                "Found finalized epoch {} at height {}",
+                epoch.height, height
+            );
+            self.process_epoch(epoch).await?;
         } else {
-            debug!("No transactions to process at height {}", height);
+            debug!("No epoch found at height {}", height);
         }
 
-        if is_real_time && !buffered_transactions.is_empty() && self.is_prover_enabled {
-            let all_transactions: Vec<Transaction> = buffered_transactions.drain(..).collect();
+        let mut tx_buffer = self.tx_buffer.write().await;
+        if is_real_time && tx_buffer.contains_pending() && self.is_prover_enabled {
+            let all_transactions: Vec<Transaction> = tx_buffer.take_to_range(height);
+            debug!(
+                "Starting epoch {} finalization with {} transactions at DA height {}",
+                next_epoch_height,
+                all_transactions.len(),
+                height
+            );
             self.sequencer
-                .finalize_new_epoch(next_epoch_height, all_transactions, &self.prover_engine)
+                .finalize_new_epoch(
+                    next_epoch_height,
+                    all_transactions,
+                    &self.prover_engine,
+                    height,
+                )
                 .await?;
         }
 
         if !transactions.is_empty() {
-            buffered_transactions.extend(transactions);
+            tx_buffer.insert_at_height(height, transactions);
             return Ok(());
         }
 
@@ -181,7 +198,7 @@ impl Syncer {
             && height.saturating_sub(latest_epoch_height) >= self.max_epochless_gap
         {
             self.sequencer
-                .finalize_new_epoch(next_epoch_height, Vec::new(), &self.prover_engine)
+                .finalize_new_epoch(next_epoch_height, Vec::new(), &self.prover_engine, height)
                 .await?;
         }
 
@@ -193,11 +210,7 @@ impl Syncer {
         Ok(())
     }
 
-    async fn process_epoch(
-        &self,
-        epoch: FinalizedEpoch,
-        buffered_transactions: &mut VecDeque<Transaction>,
-    ) -> Result<()> {
+    async fn process_epoch(&self, epoch: FinalizedEpoch) -> Result<()> {
         let current_epoch = match self.db.get_latest_epoch_height() {
             Ok(height) => height + 1,
             Err(_) => 0,
@@ -234,9 +247,12 @@ impl Syncer {
             ));
         }
 
-        let all_transactions: Vec<Transaction> = buffered_transactions.drain(..).collect();
-        if !all_transactions.is_empty() {
-            self.sequencer.execute_block(all_transactions).await?;
+        // Only execute transactions up to the tip DA height that the prover used
+        let mut tx_buffer = self.tx_buffer.write().await;
+        let transactions_to_execute = tx_buffer.take_to_range(epoch.tip_da_height);
+
+        if !transactions_to_execute.is_empty() {
+            self.sequencer.execute_block(transactions_to_execute).await?;
         }
 
         let new_commitment = self.sequencer.get_commitment().await?;
diff --git a/crates/node_types/prover/src/tx_buffer.rs b/crates/node_types/prover/src/tx_buffer.rs
new file mode 100644
index 00000000..534515dc
--- /dev/null
+++ b/crates/node_types/prover/src/tx_buffer.rs
@@ -0,0 +1,28 @@
+use prism_common::transaction::Transaction;
+use std::collections::BTreeMap;
+
+pub struct TxBuffer {
+    transactions: BTreeMap<u64, Vec<Transaction>>,
+}
+
+impl TxBuffer {
+    pub fn new() -> Self {
+        TxBuffer {
+            transactions: BTreeMap::new(),
+        }
+    }
+
+    pub fn take_to_range(&mut self, end: u64) -> Vec<Transaction> {
+        let keys: Vec<u64> = self.transactions.range(..=end).map(|(&k, _)| k).collect();
+
+        keys.into_iter().filter_map(|k| self.transactions.remove(&k)).flatten().collect()
+    }
+
+    pub fn contains_pending(&self) -> bool {
+        !self.transactions.is_empty()
+    }
+
+    pub fn insert_at_height(&mut self, height: u64, txs: Vec<Transaction>) {
+        self.transactions.entry(height).or_default().extend(txs);
+    }
+}
diff --git a/elf/base-riscv32im-succinct-zkvm-elf b/elf/base-riscv32im-succinct-zkvm-elf
index 2e682c66..70435df5 100755
Binary files a/elf/base-riscv32im-succinct-zkvm-elf and b/elf/base-riscv32im-succinct-zkvm-elf differ
diff --git a/elf/recursive-riscv32im-succinct-zkvm-elf b/elf/recursive-riscv32im-succinct-zkvm-elf
index b0b55862..99ef32fe 100755
Binary files a/elf/recursive-riscv32im-succinct-zkvm-elf and b/elf/recursive-riscv32im-succinct-zkvm-elf differ
diff --git a/verification_keys/keys.json b/verification_keys/keys.json
index 1c9e1a86..372c7eeb 100644
--- a/verification_keys/keys.json
+++ b/verification_keys/keys.json
@@ -1 +1 @@
-{"base_vk": "0x00e04725fb33de4f7d318466938f0a9faee9470faeff656efb475043c00db0b8", "recursive_vk": "0x008189efbfb0bfef2e1a457ffdc7528c1bf3d9d6a0d9905db5d21e81924be035"}
+{"base_vk": "0x005da3d30457bcedfc5a8996c04a67a64dc5f4c50298d286d093c6024a44b26d", "recursive_vk": "0x000d5942dc98176862d1991a1b755e04ed395f022447007abb9d5a52db5d0b08"}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>crates/node_types/prover/src/prover/tests.rs (1)</summary><blockquote>

`206-206`: **Same concern about hardcoded tip_da_height value.**

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>crates/node_types/prover/src/prover/tests.rs (1)</summary><blockquote>

`271-398`: **Consider making the test more robust.**

The test implementation looks good overall and validates the race condition fix. However, there are a few concerns:

1. The 5-second sleep at line 380 could make the test flaky. Consider using a more deterministic approach to wait for synchronization.
2. The comment at line 379 acknowledges potential flakiness.


Consider replacing the fixed sleep with a polling mechanism:

```diff
-    // Wait for fullnode to sync the epoch
-    // If this test flakes, it could be because it needs a tiny bit more time here
-    tokio::time::sleep(Duration::from_millis(5000)).await;
+    // Wait for fullnode to sync the epoch with timeout
+    let timeout = Duration::from_secs(10);
+    let start = tokio::time::Instant::now();
+    loop {
+        let fullnode_epoch = fullnode.get_db().get_latest_epoch_height();
+        if fullnode_epoch.is_ok() && fullnode_epoch.unwrap() > 0 {
+            break;
+        }
+        if start.elapsed() > timeout {
+            panic!("Fullnode failed to sync epoch within timeout");
+        }
+        tokio::time::sleep(Duration::from_millis(100)).await;
+    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c2dcdf24f87b5d10f6bd60bc68324298ce1b4381 and e135ef27f9b36b2c8e14b1afba4c504602e3caf8.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `crates/cli/src/cfg.rs` (3 hunks)
* `crates/da/src/lib.rs` (2 hunks)
* `crates/da/src/memory.rs` (3 hunks)
* `crates/node_types/prover/src/lib.rs` (1 hunks)
* `crates/node_types/prover/src/prover/mod.rs` (1 hunks)
* `crates/node_types/prover/src/prover/tests.rs` (9 hunks)
* `crates/node_types/prover/src/sequencer.rs` (2 hunks)
* `crates/node_types/prover/src/syncer.rs` (8 hunks)
* `crates/node_types/prover/src/tx_buffer.rs` (1 hunks)
* `verification_keys/keys.json` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>crates/cli/src/cfg.rs (1)</summary><blockquote>

<details>
<summary>crates/da/src/memory.rs (1)</summary>

* `new` (37-56)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: clippy
* GitHub Check: unused dependencies
* GitHub Check: unit-test
* GitHub Check: integration-test
* GitHub Check: build-and-push-image

</details>

<details>
<summary>🔇 Additional comments (28)</summary><blockquote>

<details>
<summary>crates/da/src/memory.rs (5)</summary>

`29-34`: **Good improvement using Duration for type safety.**

Converting `block_time` from `u64` to `Duration` improves type safety and makes the timing semantics clearer. The addition of `epoch_posting_delay` provides valuable testing capabilities for timing-sensitive scenarios, which aligns with the race condition fix objectives.

---

`37-56`: **Constructor correctly updated for Duration.**

The constructor properly accepts `Duration` for `block_time` and initializes the `epoch_posting_delay` to `None` by default. The broadcast channel setup and field initialization are consistent.

---

`58-78`: **Well-designed constructor for testing scenarios.**

The `new_with_epoch_delay` constructor provides a clean way to configure epoch posting delays for testing timing-sensitive behavior. This is particularly valuable for testing the race condition scenarios mentioned in the PR objectives.

---

`81-81`: **Correct usage of Duration in interval timing.**

The `produce_blocks` method now correctly uses the `Duration` directly with `interval()`, which is the proper way to handle timing intervals.

---

`151-154`: **Epoch posting delay implementation is correct.**

The conditional delay before posting epochs allows testing of race conditions where transactions might be submitted between epoch finalization start and actual posting. This directly addresses the synchronization issues mentioned in the PR objectives.

</details>
<details>
<summary>verification_keys/keys.json (1)</summary>

`1-1`: <details>
<summary>✅ Verification successful</summary>

**Verification keys updated for new epoch finalization logic.**

The verification keys have been completely updated, which is expected when the circuit logic changes to include the new `tip_da_height` parameter in epoch finalization. Ensure these keys are properly generated and correspond to the updated circuit that includes DA height tracking.




Let me verify the hex string format is valid:

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Verify the verification keys have valid hexadecimal format

# Extract and validate the hex strings
base_vk=$(jq -r '.base_vk' verification_keys/keys.json)
recursive_vk=$(jq -r '.recursive_vk' verification_keys/keys.json)

echo "Base VK: $base_vk"
echo "Recursive VK: $recursive_vk"

# Check if they are valid 32-byte hex strings (64 hex characters + 0x prefix)
if [[ $base_vk =~ ^0x[0-9a-fA-F]{64}$ ]]; then
    echo "Base VK format is valid"
else
    echo "Base VK format is invalid"
fi

if [[ $recursive_vk =~ ^0x[0-9a-fA-F]{64}$ ]]; then
    echo "Recursive VK format is valid"
else
    echo "Recursive VK format is invalid"
fi
```

Length of output: 907

---



</details>

**Verification keys validated and ready**  

Both `base_vk` and `recursive_vk` in `verification_keys/keys.json` are valid 32-byte hex strings and were correctly regenerated to reflect the new `tip_da_height` parameter in the circuit. Approving these updates.

</details>
<details>
<summary>crates/cli/src/cfg.rs (3)</summary>

`19-19`: **Duration import correctly added.**

The `Duration` import is properly added to support the updated DA layer initialization calls.

---

`371-372`: **DA layer initialization updated correctly.**

The `initialize_da_layer` function now correctly passes `Duration::from_secs(10)` to match the updated `InMemoryDataAvailabilityLayer::new()` constructor signature that expects a `Duration` parameter.

---

`409-410`: **Light DA layer initialization updated correctly.**

The `initialize_light_da_layer` function now correctly passes `Duration::from_secs(10)` to match the updated constructor signature, maintaining consistency with the main DA layer initialization.

</details>
<details>
<summary>crates/node_types/prover/src/lib.rs (2)</summary>

`5-6`: **New transaction buffer module added correctly.**

The addition of the private `tx_buffer` module supports the enhanced transaction buffering functionality mentioned in the PR objectives. Making it private is appropriate since it appears to be an internal implementation detail for the syncer.

---

`8-8`: **Export reordering is acceptable.**

The reordering of exports in the `pub use` statement doesn't change functionality and maintains all the same public exports.

</details>
<details>
<summary>crates/node_types/prover/src/prover/mod.rs (1)</summary>

`172-174`: **LGTM: Correct parameter threading for DA height tracking.**

The addition of `tip_da_height` parameter and its forwarding to the sequencer is correctly implemented and aligns with the race condition fix described in the PR objectives.

</details>
<details>
<summary>crates/da/src/lib.rs (2)</summary>

`38-61`: **Excellent documentation and field addition.**

The comprehensive documentation for each field in `FinalizedEpoch` greatly improves code readability. The new `tip_da_height` field is well-documented and clearly explains its purpose in defining the DA block range for transactions, which directly addresses the race condition fix.

---

`81-81`: **Correct integration of new field in signature verification.**

The `tip_da_height` field is properly included in the signature verification process, maintaining the security integrity of the `FinalizedEpoch` structure.

</details>
<details>
<summary>crates/node_types/prover/src/sequencer.rs (3)</summary>

`118-118`: **Correct parameter addition for DA height tracking.**

The `tip_da_height` parameter addition is consistent with the race condition fix implementation across the codebase.

---

`137-137`: **Proper assignment of DA height to FinalizedEpoch.**

The `tip_da_height` is correctly assigned to the `FinalizedEpoch` struct, ensuring the DA layer height is tracked at epoch finalization time.

---

`142-147`: **Good addition of debug logging for observability.**

The debug logging provides valuable traceability for epoch submission, which will help with troubleshooting synchronization issues and monitoring the race condition fix.

</details>
<details>
<summary>crates/node_types/prover/src/tx_buffer.rs (1)</summary>

`1-30`: **Well-implemented transaction buffer for DA height tracking.**

The `TxBuffer` implementation is solid and supports the race condition fix by enabling proper transaction buffering by DA height. Key strengths:

- **Good data structure choice**: `BTreeMap` provides ordered operations by height
- **Efficient methods**: `take_to_range` correctly uses inclusive range and efficiently drains transactions
- **Clean API**: Methods are well-named and provide necessary functionality for transaction management

The implementation correctly supports the transaction buffering refactor described in the PR objectives.

</details>
<details>
<summary>crates/node_types/prover/src/prover/tests.rs (4)</summary>

`14-14`: **LGTM!**

The Duration type change aligns with the updated DA layer API.

---

`65-66`: **Verify the hardcoded tip_da_height value of 0.**

The `0` value for `tip_da_height` might not accurately represent the current DA height at the time of epoch finalization in this test. Consider retrieving the actual DA height to ensure the test accurately reflects production behavior.

---

`213-226`: **LGTM!**

Consistent Duration type update and improved formatting.

---

`469-469`: **LGTM!**

Correctly adds the new test to the algorithm test suite.

</details>
<details>
<summary>crates/node_types/prover/src/syncer.rs (6)</summary>

`7-11`: **LGTM!**

Appropriate imports for the new TxBuffer integration.

---

`17-17`: **Good architectural improvement!**

The shared TxBuffer properly centralizes transaction management and addresses the race condition mentioned in the PR objectives.



Also applies to: 39-39

---

`147-152`: **LGTM!**

Helpful trace logging for debugging DA layer queries.

---

`162-169`: **LGTM!**

Good addition of debug logging for epoch tracking.

---

`201-201`: **LGTM!**

Consistent update to include DA height for gap epochs.

---

`251-256`: **Excellent fix for the race condition!**

This change correctly implements the solution described in the PR objectives. By using `take_to_range(epoch.tip_da_height)`, it ensures that only transactions posted before the epoch began finalization are executed, preventing the race condition where transactions posted during finalization were incorrectly queued.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T09:10:36Z
user: login: distractedm1nd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T09:50:06Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T09:50:30Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>crates/node_types/prover/src/prover/tests.rs (1)</summary><blockquote>

`271-398`: **Excellent comprehensive race condition test!**

This new test effectively addresses the core PR objective by testing race conditions during fullnode synchronization. The test design is solid:

✅ **Strengths:**
- Tests the exact race condition scenario described in the PR
- Uses `new_with_epoch_delay` to simulate realistic timing conditions
- Properly sets up prover-enabled and prover-disabled nodes
- Includes synchronization checks before testing
- Verifies both commitment and epoch height consistency

⚠️ **Areas for improvement:**
- The hardcoded 5-second sleep (line 380) could be flaky in slower environments
- The 50-iteration sync loop might be insufficient under high load




Consider making the timing more robust:

```diff
-    // Wait for fullnode to sync the epoch
-    // If this test flakes, it could be because it needs a tiny bit more time here
-    tokio::time::sleep(Duration::from_millis(5000)).await;
+    // Wait for fullnode to sync the epoch with timeout
+    let mut synced = false;
+    for _ in 0..100 { // 10 second timeout
+        let prover_epoch = prover.get_db().get_latest_epoch_height().unwrap_or(0);
+        let fullnode_epoch = fullnode.get_db().get_latest_epoch_height().unwrap_or(0);
+        
+        if prover_epoch > 0 && prover_epoch == fullnode_epoch {
+            synced = true;
+            break;
+        }
+        tokio::time::sleep(Duration::from_millis(100)).await;
+    }
+    assert!(synced, "Fullnode failed to sync with prover within timeout");
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ca988a0583b44f1c616d0aed57722737d0153a14 and e0a400bb754ee985a676e8d1fb1e43c3de8aabb3.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `crates/cli/src/cfg.rs` (3 hunks)
* `crates/da/src/lib.rs` (2 hunks)
* `crates/da/src/memory.rs` (3 hunks)
* `crates/node_types/prover/src/lib.rs` (1 hunks)
* `crates/node_types/prover/src/prover/mod.rs` (1 hunks)
* `crates/node_types/prover/src/prover/tests.rs` (9 hunks)
* `crates/node_types/prover/src/sequencer.rs` (2 hunks)
* `crates/node_types/prover/src/syncer.rs` (8 hunks)
* `crates/node_types/prover/src/tx_buffer.rs` (1 hunks)
* `verification_keys/keys.json` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* crates/node_types/prover/src/prover/mod.rs

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (8)</summary>

* verification_keys/keys.json
* crates/cli/src/cfg.rs
* crates/node_types/prover/src/lib.rs
* crates/node_types/prover/src/tx_buffer.rs
* crates/da/src/lib.rs
* crates/node_types/prover/src/sequencer.rs
* crates/da/src/memory.rs
* crates/node_types/prover/src/syncer.rs

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: build-and-push-image
* GitHub Check: unused dependencies
* GitHub Check: clippy
* GitHub Check: integration-test
* GitHub Check: unit-test

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>crates/node_types/prover/src/prover/tests.rs (9)</summary>

`14-14`: **LGTM! API change correctly implemented.**

The change from integer milliseconds to `Duration::from_millis(500)` is consistent with the updated `InMemoryDataAvailabilityLayer` API and improves type safety.

---

`79-80`: **Good formatting improvements.**

The multi-line formatting of the assignment makes the code more readable and easier to maintain.



Also applies to: 100-101

---

`206-206`: **Consistent parameter addition.**

Same concern as line 65 - verify that the hardcoded `0` value for `tip_da_height` is appropriate for this test context.

---

`213-213`: **Consistent Duration usage.**

The change to `Duration::from_millis(200)` is consistent with the API update. The shorter duration (200ms vs 500ms) is appropriate for this sync test scenario.

---

`218-226`: **Improved code formatting.**

The multi-line formatting of `Prover::new` calls enhances readability and makes the parameter structure clearer.



Also applies to: 246-254

---

`401-401`: **Consistent API usage.**

The Duration parameter change is correctly applied here as well.

---

`405-407`: **Consistent formatting improvements.**

The multi-line formatting matches the pattern used throughout the file and improves readability.



Also applies to: 427-429

---

`469-469`: **Proper test integration.**

The new race condition test is correctly added to the macro-generated test suite, ensuring it runs for all supported cryptographic algorithms.

---

`65-65`: <details>
<summary>✅ Verification successful</summary>

**Verify the new tip_da_height parameter usage.**

The addition of the third parameter `0` to `finalize_new_epoch` aligns with the new signature that includes `tip_da_height: u64`. However, using `0` as a hardcoded value might not be appropriate for all test scenarios.


Consider whether the hardcoded `0` value is appropriate or if it should be dynamically determined based on the test context:

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Check the finalize_new_epoch method signature and usage patterns
ast-grep --pattern 'finalize_new_epoch($_, $_, $_)'
```

Length of output: 53

---

🏁 Script executed:

```shell
#!/bin/bash
set -e

# 1. Inspect the signature of finalize_new_epoch
echo "== Signature of finalize_new_epoch =="
rg -n "fn finalize_new_epoch" -C3

# 2. List all calls to finalize_new_epoch in the repo
echo
echo "== Usages of finalize_new_epoch =="
rg -n "finalize_new_epoch\(" -C1
```

Length of output: 3355

---



</details>

**tip_da_height parameter usage in tests is correct**  

The wrapper `finalize_new_epoch(epoch_height, transactions, tip_da_height: u64)` now requires a DA‐tip height. In both test calls:

- `crates/node_types/prover/src/prover/tests.rs:65`  
- `crates/node_types/prover/src/prover/tests.rs:206`  

passing `0` correctly represents the initial‐epoch DA state. No changes are necessary here.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T10:10:28Z
user: login: coderabbitai[bot]
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T10:10:31Z
user: login: coderabbitai[bot]
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T10:10:32Z

Review comments:
diff_hunk: @@ -158,20 +159,36 @@ impl Syncer {
         );
 
         if let Some(epoch) = epoch_result {
-            self.process_epoch(epoch, buffered_transactions).await?;
+            debug!(
+                "Found finalized epoch {} at height {}",
+                epoch.height, height
+            );
+            self.process_epoch(epoch).await?;
         } else {
-            debug!("No transactions to process at height {}", height);
+            debug!("No epoch found at height {}", height);
         }
 
-        if is_real_time && !buffered_transactions.is_empty() && self.is_prover_enabled {
-            let all_transactions: Vec<Transaction> = buffered_transactions.drain(..).collect();
+        let mut tx_buffer = self.tx_buffer.write().await;
+        if is_real_time && tx_buffer.contains_pending() && self.is_prover_enabled {
+            let all_transactions: Vec<Transaction> = tx_buffer.take_all();
+            debug!(
+                "Starting epoch {} finalization with {} transactions at DA height {}",
+                next_epoch_height,
+                all_transactions.len(),
+                height
+            );
             self.sequencer
-                .finalize_new_epoch(next_epoch_height, all_transactions, &self.prover_engine)
+                .finalize_new_epoch(
+                    next_epoch_height,
+                    all_transactions,
+                    &self.prover_engine,
+                    height,
+                )
                 .await?;
         }
 
         if !transactions.is_empty() {
-            buffered_transactions.extend(transactions);
+            tx_buffer.insert_at_height(height, transactions);
             return Ok(());
path: crates/node_types/prover/src/syncer.rs
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Verify the transaction collection logic.**

The use of `take_all()` at line 173 collects all buffered transactions regardless of their DA height. This might include transactions from different heights. Ensure this aligns with the intended behavior, especially since the PR objectives mention tracking DA height for proper transaction ordering.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check the TxBuffer implementation to understand take_all behavior
fd -e rs tx_buffer.rs --exec cat {}
```

Length of output: 884

---



</details>

**Filter buffered transactions by DA height before finalizing the epoch**

Using `take_all()` will clear and return transactions from *all* heights, mixing future‐height transactions into the current epoch. To preserve ordering and keep future transactions buffered, use `take_to_range(height)` instead:

• Location: `crates/node_types/prover/src/syncer.rs` (around line 173)  
• Suggested change:
```diff
- let all_transactions: Vec<Transaction> = tx_buffer.take_all();
+ let all_transactions: Vec<Transaction> = tx_buffer.take_to_range(height);
```
This ensures only transactions up to the current DA height are included in `finalize_new_epoch`, leaving later‐height transactions for subsequent epochs.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In crates/node_types/prover/src/syncer.rs around lines 171 to 192, the current
use of tx_buffer.take_all() collects all buffered transactions regardless of
their DA height, which can mix transactions from future heights into the current
epoch finalization. To fix this, replace take_all() with take_to_range(height)
so that only transactions up to the current DA height are included in
finalize_new_epoch, preserving proper transaction ordering and leaving future
transactions buffered for later epochs.
```

</details>

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commits a9ccebf to e0a400b

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-30T09:10:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,20 +159,36 @@ impl Syncer {
         );
 
         if let Some(epoch) = epoch_result {
-            self.process_epoch(epoch, buffered_transactions).await?;
+            debug!(
+                "Found finalized epoch {} at height {}",
+                epoch.height, height
+            );
+            self.process_epoch(epoch).await?;
         } else {
-            debug!("No transactions to process at height {}", height);
+            debug!("No epoch found at height {}", height);
         }
 
-        if is_real_time && !buffered_transactions.is_empty() && self.is_prover_enabled {
-            let all_transactions: Vec<Transaction> = buffered_transactions.drain(..).collect();
+        let mut tx_buffer = self.tx_buffer.write().await;
+        if is_real_time && tx_buffer.contains_pending() && self.is_prover_enabled {
+            let all_transactions: Vec<Transaction> = tx_buffer.take_all();
+            debug!(
+                "Starting epoch {} finalization with {} transactions at DA height {}",
+                next_epoch_height,
+                all_transactions.len(),
+                height
+            );
             self.sequencer
-                .finalize_new_epoch(next_epoch_height, all_transactions, &self.prover_engine)
+                .finalize_new_epoch(
+                    next_epoch_height,
+                    all_transactions,
+                    &self.prover_engine,
+                    height,
+                )
                 .await?;
         }
 
         if !transactions.is_empty() {
-            buffered_transactions.extend(transactions);
+            tx_buffer.insert_at_height(height, transactions);
             return Ok(());
path: crates/node_types/prover/src/syncer.rs
user: login: distractedm1nd
body: This is actually true
created_at: 2025-05-30T09:50:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,20 +159,36 @@ impl Syncer {
         );
 
         if let Some(epoch) = epoch_result {
-            self.process_epoch(epoch, buffered_transactions).await?;
+            debug!(
+                "Found finalized epoch {} at height {}",
+                epoch.height, height
+            );
+            self.process_epoch(epoch).await?;
         } else {
-            debug!("No transactions to process at height {}", height);
+            debug!("No epoch found at height {}", height);
         }
 
-        if is_real_time && !buffered_transactions.is_empty() && self.is_prover_enabled {
-            let all_transactions: Vec<Transaction> = buffered_transactions.drain(..).collect();
+        let mut tx_buffer = self.tx_buffer.write().await;
+        if is_real_time && tx_buffer.contains_pending() && self.is_prover_enabled {
+            let all_transactions: Vec<Transaction> = tx_buffer.take_all();
+            debug!(
+                "Starting epoch {} finalization with {} transactions at DA height {}",
+                next_epoch_height,
+                all_transactions.len(),
+                height
+            );
             self.sequencer
-                .finalize_new_epoch(next_epoch_height, all_transactions, &self.prover_engine)
+                .finalize_new_epoch(
+                    next_epoch_height,
+                    all_transactions,
+                    &self.prover_engine,
+                    height,
+                )
                 .await?;
         }
 
         if !transactions.is_empty() {
-            buffered_transactions.extend(transactions);
+            tx_buffer.insert_at_height(height, transactions);
             return Ok(());
path: crates/node_types/prover/src/syncer.rs
user: login: coderabbitai[bot]
body: @distractedm1nd Thank you for confirming! This could indeed cause race conditions where transactions intended for future epochs get processed prematurely. 

Would you like me to help implement this fix by changing `take_all()` to `take_to_range(height)` in the syncer, or would you prefer to create an issue to track this for a follow-up PR?


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@distractedm1nd Thank you for confirming! This could indeed cause race conditions where transactions intended for future epochs get processed prematurely. 

Would you like me to help implement this fix by changing `take_all()` to `take_to_range(height)` in the syncer, or would you prefer to create an issue to track this for a follow-up PR?</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-30T09:50:30Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-28T11:28:28Z
body: [vc]: #fqBGcnJSBBTNhMtD6MJ5uANKn+WKf/+gMBpLn6dMoc4=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJwcmlzbSIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2RlbHRhZGV2cy9wcmlzbS80cGtFRDVpalhLY21rVGQxNDl5RkNrS2hUZlBOIiwibmV4dENvbW1pdFN0YXR1cyI6IklHTk9SRUQiLCJyb290RGlyZWN0b3J5IjoiZG9jLyIsInByZXZpZXdVcmwiOiJwcmlzbS1naXQtc3luY2VyLWZpeC1kZWx0YWRldnMudmVyY2VsLmFwcCJ9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **prism** | ⬜️ Ignored ([Inspect](https://vercel.com/deltadevs/prism/4pkED5ijXKcmkTd149yFCkKhTfPN)) | [Visit Preview](https://prism-git-syncer-fix-deltadevs.vercel.app) |  | May 30, 2025 10:06am |
</details>


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-28T11:28:31Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes introduce a new transaction buffering module and refactor the prover and syncer logic to use a shared asynchronous buffer for transaction management. Data availability layer timing is now controlled with `Duration` types and supports configurable epoch posting delays. The finalized epoch structure and related methods are updated to include the tip DA height. Tests are updated and extended to cover new synchronization scenarios.

## Changes

| File(s)                                                                 | Change Summary                                                                                                                        |
|-------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
| crates/cli/src/cfg.rs                                                   | Updated `InMemoryDataAvailabilityLayer` initialization to use `Duration` instead of integer for timing.                              |
| crates/da/src/lib.rs                                                    | Added `tip_da_height` to `FinalizedEpoch`; documented fields; updated signature verification to include new field.                   |
| crates/da/src/memory.rs                                                 | Changed `block_time` to `Duration`; added optional `epoch_posting_delay`; new constructors for timing; updated async methods.        |
| crates/node_types/prover/src/lib.rs                                     | Added private `tx_buffer` module; reordered module declarations and public re-exports.                                               |
| crates/node_types/prover/src/prover/mod.rs                              | Updated `finalize_new_epoch` to accept `tip_da_height` parameter and pass it to sequencer.                                           |
| crates/node_types/prover/src/prover/tests.rs                            | Changed DA layer timing to `Duration`; updated test calls for new parameters; added async test for commitment sync with racing txs.  |
| crates/node_types/prover/src/sequencer.rs                               | Updated `finalize_new_epoch` to accept and assign `tip_da_height`; added debug logs for epoch submission.                            |
| crates/node_types/prover/src/syncer.rs                                  | Refactored to use shared `TxBuffer` for transaction management; updated methods to use buffer; enhanced logging.                     |
| crates/node_types/prover/src/tx_buffer.rs                               | New module: defines `TxBuffer` struct for managing buffered transactions by height with insert and drain methods.                    |
| verification_keys/keys.json                                             | Replaced "base_vk" and "recursive_vk" hex strings with new values.                                                                   |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant Prover
    participant Sequencer
    participant TxBuffer
    participant DA as DataAvailabilityLayer

    User->>Prover: Submit Transaction(s)
    Prover->>TxBuffer: Insert transactions at current DA height
    Prover->>Sequencer: finalize_new_epoch(epoch_height, txs, tip_da_height)
    Sequencer->>DA: Submit finalized epoch (includes tip_da_height)
    DA-->>Sequencer: Confirm submission
    Sequencer-->>Prover: Return next height
```

```mermaid
sequenceDiagram
    participant Syncer
    participant TxBuffer
    participant DA as DataAvailabilityLayer
    participant Sequencer

    loop On new DA height
        Syncer->>DA: Query for finalized epoch and transactions
        alt Finalized epoch found
            Syncer->>Sequencer: process_epoch(epoch, txs from TxBuffer up to tip_da_height)
        else No epoch, but pending txs
            Syncer->>TxBuffer: Check for pending txs
            alt Pending txs exist
                Syncer->>TxBuffer: Drain all txs
                Syncer->>Sequencer: finalize_new_epoch(epoch_height, drained_txs, DA height)
            end
        end
    end
```

## Suggested reviewers

- sebasti810

## Poem

> A buffer for every bunny hop,  
> Transactions queued, they never stop.  
> With epochs tall and DA tips in sight,  
> We sync and race through day and night.  
> New keys, new flows, new tests to run—  
> The code leaps forward, job well done!  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=deltadevsde/prism&utm_content=318):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR