diff --git a/magicblock-aperture/src/geyser.rs b/magicblock-aperture/src/geyser.rs index 4e3bbf6fb..f245e126f 100644 --- a/magicblock-aperture/src/geyser.rs +++ b/magicblock-aperture/src/geyser.rs @@ -154,6 +154,7 @@ impl GeyserPluginManager { ) -> Result<(), GeyserPluginError> { check_if_enabled!(self); let blockhash = block.blockhash.to_string(); + let parent_blockhash = block.parent_blockhash.to_string(); let rewards = solana_transaction_status::RewardsAndNumPartitions { rewards: Vec::new(), num_partitions: None, @@ -165,8 +166,7 @@ impl GeyserPluginManager { block_height: Some(block.slot), rewards: &rewards, block_time: Some(block.clock.unix_timestamp), - // TODO(bmuddha): register proper values with the new ledger - parent_blockhash: "11111111111111111111111111111111", + parent_blockhash: &parent_blockhash, executed_transaction_count: 0, entry_count: 0, }; diff --git a/magicblock-ledger/src/lib.rs b/magicblock-ledger/src/lib.rs index 2828c0fa2..bc0250a21 100644 --- a/magicblock-ledger/src/lib.rs +++ b/magicblock-ledger/src/lib.rs @@ -15,6 +15,7 @@ pub struct LatestBlockInner { pub slot: u64, pub blockhash: Hash, pub clock: Clock, + pub parent_blockhash: Hash, } /// Atomically updated, shared, latest block information @@ -45,6 +46,26 @@ impl LatestBlockInner { slot, blockhash, clock, + parent_blockhash: Hash::default(), + } + } + + pub fn new_with_parent( + slot: u64, + blockhash: Hash, + timestamp: i64, + parent_blockhash: Hash, + ) -> Self { + let clock = Clock { + slot: slot + 1, + unix_timestamp: timestamp, + ..Default::default() + }; + Self { + slot, + blockhash, + clock, + parent_blockhash, } } } diff --git a/magicblock-ledger/tests/get_block.rs b/magicblock-ledger/tests/get_block.rs index 7fa743c3b..952fbc51e 100644 --- a/magicblock-ledger/tests/get_block.rs +++ b/magicblock-ledger/tests/get_block.rs @@ -44,6 +44,39 @@ fn test_get_block_meta() { assert_eq!(slot_2_block.blockhash, slot_2_hash.to_string()); } +#[test] +fn test_write_block_stores_parent_blockhash() { + init_logger!(); + let ledger = setup(); + + let slot_0_hash = Hash::new_unique(); + ledger + .write_block(LatestBlockInner::new(0, slot_0_hash, 1)) + .unwrap(); + assert_eq!( + ledger.latest_block().load().parent_blockhash, + Hash::default() + ); + + let slot_1_hash = Hash::new_unique(); + ledger + .write_block(LatestBlockInner::new_with_parent( + 1, + slot_1_hash, + 2, + slot_0_hash, + )) + .unwrap(); + + let latest = ledger.latest_block().load(); + assert_eq!(latest.slot, 1); + assert_eq!(latest.blockhash, slot_1_hash); + assert_eq!(latest.parent_blockhash, slot_0_hash); + + let slot_1_block = get_block(&ledger, 1); + assert_eq!(slot_1_block.previous_blockhash, slot_0_hash.to_string()); +} + #[test] fn test_get_block_transactions() { init_logger!(); diff --git a/magicblock-processor/src/scheduler/mod.rs b/magicblock-processor/src/scheduler/mod.rs index 8dc5ab2c5..5f3c3bf8a 100644 --- a/magicblock-processor/src/scheduler/mod.rs +++ b/magicblock-processor/src/scheduler/mod.rs @@ -391,8 +391,18 @@ impl TransactionScheduler { .await .map_err(|_| "scheduler semaphore closed".to_string())?; - let block = - LatestBlockInner::new(block.slot, block.hash, block.timestamp); + // once write_block runs, latest_block is overwritten with the block that's being applied + let parent_blockhash = Self::initial_blockhash( + &self.accountsdb, + &self.ledger.latest_block(), + ); + let block = LatestBlockInner::new_with_parent( + block.slot, + block.hash, + block.timestamp, + parent_blockhash, + ); + self.verify_block_as_replica(&block); self.ledger .write_block(block.clone()) @@ -575,6 +585,10 @@ impl TransactionScheduler { /// Prepares block as primary: computes blockhash and broadcasts to replicas. async fn prepare_block_as_primary(&mut self) -> LatestBlockInner { let blockhash = (*self.hasher.finalize().as_bytes()).into(); + let parent_blockhash = Self::initial_blockhash( + &self.accountsdb, + &self.ledger.latest_block(), + ); // NOTE: // As we have a single node network, we have no // option but to use the time from host machine @@ -584,7 +598,12 @@ impl TransactionScheduler { // NOTE: since we can tick very frequently, a lot // of blocks might have identical timestamps .as_secs() as i64; - let block = LatestBlockInner::new(self.slot, blockhash, timestamp); + let block = LatestBlockInner::new_with_parent( + self.slot, + blockhash, + timestamp, + parent_blockhash, + ); let msg = Message::Block(replication::Block { slot: block.slot, hash: block.blockhash, diff --git a/magicblock-processor/tests/replica_ordering.rs b/magicblock-processor/tests/replica_ordering.rs index de72f249d..373c91045 100644 --- a/magicblock-processor/tests/replica_ordering.rs +++ b/magicblock-processor/tests/replica_ordering.rs @@ -226,7 +226,10 @@ async fn test_replay_block_waits_for_queued_transactions() { .expect("failed to apply replayed block"); assert_eq!(env.get_account(acc).data()[0], 77); - assert_eq!(env.ledger.latest_block().load().slot, slot); + let latest = env.ledger.latest_block().load(); + assert_eq!(latest.slot, slot); + assert_eq!(latest.blockhash, hash); + assert_eq!(latest.parent_blockhash, prev_hash); assert_eq!(env.accountsdb.slot(), slot); } @@ -255,6 +258,11 @@ async fn test_replica_replayed_superblock_takes_snapshot_without_publishing() { .await .expect("failed to apply replayed superblock boundary"); + let latest = env.ledger.latest_block().load(); + assert_eq!(latest.slot, slot); + assert_eq!(latest.blockhash, hash); + assert_eq!(latest.parent_blockhash, prev_hash); + timeout(Duration::from_secs(5), async { while !snapshot.exists() { sleep(Duration::from_millis(10)).await; diff --git a/magicblock-processor/tests/scheduling.rs b/magicblock-processor/tests/scheduling.rs index 70e1896e3..7f0c035f8 100644 --- a/magicblock-processor/tests/scheduling.rs +++ b/magicblock-processor/tests/scheduling.rs @@ -527,3 +527,27 @@ async fn test_wait_for_idle_coordination() { .await .expect("should acquire permit quickly when scheduler is idle"); } + +#[tokio::test] +async fn test_primary_slot_transition_sets_parent_blockhash() { + let mut env = setup_env(1); + let parent_hash = env.ledger.latest_block().load().blockhash; + let initial_slot = env.ledger.latest_block().load().slot; + + env.run_scheduler(); + env.yield_to_scheduler().await; + + // Wait for the first primary slot tick to finalize the in-progress slot. + timeout(TIMEOUT, async { + loop { + let latest = env.ledger.latest_block().load(); + if latest.slot > initial_slot { + assert_eq!(latest.parent_blockhash, parent_hash); + return; + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .expect("timed out waiting for primary slot transition"); +}