Skip to content

Commit ede0766

Browse files
authored
Merge pull request #198 from decipherhub/fix/parent-hash-bug
fix: restore last_block_hash on node restart
2 parents 0401725 + c0fc679 commit ede0766

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

crates/node/src/execution_bridge.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,39 @@ impl ExecutionBridge {
279279
}
280280
}
281281

282+
/// Restore the last block hash from storage on node restart.
283+
///
284+
/// This MUST be called after node restart when blocks already exist in storage.
285+
/// It ensures that the next block's parent_hash correctly references the latest
286+
/// stored block's hash, maintaining proper chain connectivity.
287+
///
288+
/// # Arguments
289+
///
290+
/// * `latest_block_hash` - The hash of the latest block in storage
291+
///
292+
/// # Note
293+
///
294+
/// This method should be called AFTER `set_genesis_block_hash` during node
295+
/// initialization. If there are blocks beyond genesis in storage, this method
296+
/// should be called to update `last_block_hash` to the latest block's hash.
297+
pub fn restore_last_block_hash(&self, latest_block_hash: B256) {
298+
match self.last_block_hash.write() {
299+
Ok(mut guard) => {
300+
*guard = latest_block_hash;
301+
info!(
302+
latest_block_hash = %latest_block_hash,
303+
"Execution bridge restored last block hash from storage (node restart recovery)"
304+
);
305+
}
306+
Err(e) => {
307+
warn!(
308+
error = %e,
309+
"Failed to restore last block hash - lock poisoned, next block may have incorrect parent_hash"
310+
);
311+
}
312+
}
313+
}
314+
282315
/// Validate a transaction for mempool CheckTx
283316
///
284317
/// This is called by workers before accepting transactions into batches.
@@ -830,4 +863,47 @@ mod tests {
830863

831864
assert_eq!(bridge.gas_limit(), 50_000_000);
832865
}
866+
867+
#[tokio::test]
868+
async fn test_restore_last_block_hash() {
869+
let (bridge, _temp_dir) = create_default_bridge().unwrap();
870+
871+
// Initially should be B256::ZERO
872+
let initial_hash = bridge.last_block_hash.read().map(|guard| *guard).unwrap();
873+
assert_eq!(initial_hash, B256::ZERO);
874+
875+
// Restore to a specific block hash (simulating node restart recovery)
876+
let latest_block_hash = B256::from([0xABu8; 32]);
877+
bridge.restore_last_block_hash(latest_block_hash);
878+
879+
// Should now be updated to the restored hash
880+
let restored_hash = bridge.last_block_hash.read().map(|guard| *guard).unwrap();
881+
assert_eq!(
882+
restored_hash, latest_block_hash,
883+
"restore_last_block_hash should update last_block_hash to the latest block's hash"
884+
);
885+
}
886+
887+
#[tokio::test]
888+
async fn test_restore_overrides_genesis_hash() {
889+
let (bridge, _temp_dir) = create_default_bridge().unwrap();
890+
891+
// First set genesis hash (simulating initial node startup)
892+
let genesis_hash = B256::from([0x01u8; 32]);
893+
bridge.set_genesis_block_hash(genesis_hash);
894+
895+
let after_genesis = bridge.last_block_hash.read().map(|guard| *guard).unwrap();
896+
assert_eq!(after_genesis, genesis_hash);
897+
898+
// Now restore to latest block hash (simulating node restart with existing blocks)
899+
let latest_block_hash = B256::from([0xFFu8; 32]);
900+
bridge.restore_last_block_hash(latest_block_hash);
901+
902+
// Should be updated to the latest, not genesis
903+
let after_restore = bridge.last_block_hash.read().map(|guard| *guard).unwrap();
904+
assert_eq!(
905+
after_restore, latest_block_hash,
906+
"restore_last_block_hash should override any previously set hash"
907+
);
908+
}
833909
}

crates/node/src/node.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,32 @@ impl Node {
11761176
if let Ok(Some(latest)) = storage.block_store().get_latest_block_number().await {
11771177
storage.set_latest_block(latest);
11781178
info!("Initialized RPC latest_block from storage: {}", latest);
1179+
1180+
// CRITICAL: Restore last_block_hash for correct parent_hash in next block
1181+
// On node restart, the ExecutionBridge's in-memory last_block_hash is lost.
1182+
// We must restore it from the latest block in storage to ensure the next
1183+
// block's parent_hash correctly references the latest block's hash.
1184+
if latest > 0 {
1185+
if let Some(ref bridge) = self.execution_bridge {
1186+
match storage.block_store().get_block_by_number(latest).await {
1187+
Ok(Some(latest_block)) => {
1188+
bridge.restore_last_block_hash(B256::from(latest_block.hash));
1189+
}
1190+
Ok(None) => {
1191+
warn!(
1192+
"Latest block {} not found in storage despite get_latest_block_number returning it",
1193+
latest
1194+
);
1195+
}
1196+
Err(e) => {
1197+
warn!(
1198+
"Failed to retrieve latest block {} for hash recovery: {}",
1199+
latest, e
1200+
);
1201+
}
1202+
}
1203+
}
1204+
}
11791205
}
11801206

11811207
Some(storage)

0 commit comments

Comments
 (0)