Skip to content

Commit ee550e0

Browse files
bowenwang1996Bowen Wang
authored andcommitted
fix(chain): do not return error on get_gc_stop_height (#3144)
Currently when we do not have enough blocks for garbage collection, `get_gc_stop_height` will return a `DBNotFoundErr` and the caller handles the error. This caused an incorrect handling of error in `process_block` and we will reject valid blocks if we do not have enough data for garbage collection, which can happen after a state sync. This PR changes `get_gc_stop_height` to return genesis height when we do not have enough data for garbage collection and therefore we don't need to worry about error handling at call site. Test plan --------- * `test_process_block_after_state_sync`
1 parent a1c5d20 commit ee550e0

5 files changed

Lines changed: 80 additions & 35 deletions

File tree

chain/chain/src/chain.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -514,14 +514,7 @@ impl Chain {
514514
) -> Result<(), Error> {
515515
let head = self.store.head()?;
516516
let tail = self.store.tail()?;
517-
let gc_stop_height = match self.runtime_adapter.get_gc_stop_height(&head.last_block_hash) {
518-
Ok(height) => height,
519-
Err(e) => match e.kind() {
520-
// We don't have enough data to garbage collect. Do nothing in this case.
521-
ErrorKind::DBNotFoundErr(_) => return Ok(()),
522-
_ => return Err(e),
523-
},
524-
};
517+
let gc_stop_height = self.runtime_adapter.get_gc_stop_height(&head.last_block_hash);
525518

526519
if gc_stop_height > head.height {
527520
return Err(ErrorKind::GCError(
@@ -2891,7 +2884,7 @@ impl<'a> ChainUpdate<'a> {
28912884
}
28922885

28932886
// Do not accept old forks
2894-
if prev_height < self.runtime_adapter.get_gc_stop_height(&head.last_block_hash)? {
2887+
if prev_height < self.runtime_adapter.get_gc_stop_height(&head.last_block_hash) {
28952888
return Err(ErrorKind::InvalidBlockHeight(prev_height).into());
28962889
}
28972890

chain/chain/src/test_utils.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -854,10 +854,13 @@ impl RuntimeAdapter for KeyValueRuntime {
854854
}
855855
}
856856

857-
fn get_gc_stop_height(&self, block_hash: &CryptoHash) -> Result<BlockHeight, Error> {
858-
let block_height =
859-
self.get_block_header(block_hash)?.map(|h| h.height()).unwrap_or_default();
860-
Ok(block_height.saturating_sub(NUM_EPOCHS_TO_KEEP_STORE_DATA * self.epoch_length))
857+
fn get_gc_stop_height(&self, block_hash: &CryptoHash) -> BlockHeight {
858+
let block_height = self
859+
.get_block_header(block_hash)
860+
.unwrap_or_default()
861+
.map(|h| h.height())
862+
.unwrap_or_default();
863+
block_height.saturating_sub(NUM_EPOCHS_TO_KEEP_STORE_DATA * self.epoch_length)
861864
}
862865

863866
fn epoch_exists(&self, _epoch_id: &EpochId) -> bool {

chain/chain/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ pub trait RuntimeAdapter: Send + Sync {
406406
fn get_epoch_start_height(&self, block_hash: &CryptoHash) -> Result<BlockHeight, Error>;
407407

408408
/// Get the block height for which garbage collection should not go over
409-
fn get_gc_stop_height(&self, block_hash: &CryptoHash) -> Result<BlockHeight, Error>;
409+
fn get_gc_stop_height(&self, block_hash: &CryptoHash) -> BlockHeight;
410410

411411
/// Check if epoch exists.
412412
fn epoch_exists(&self, epoch_id: &EpochId) -> bool;

chain/client/tests/process_blocks.rs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,16 +1238,52 @@ fn test_gc_after_state_sync() {
12381238
assert!(env.clients[1].chain.runtime_adapter.get_epoch_start_height(&block_hash).is_ok());
12391239
}
12401240
env.clients[1].chain.reset_data_pre_state_sync(sync_hash).unwrap();
1241-
assert!(matches!(
1242-
env.clients[1].runtime_adapter.get_gc_stop_height(&sync_hash).unwrap_err().kind(),
1243-
ErrorKind::DBNotFoundErr(_)
1244-
));
1241+
assert_eq!(env.clients[1].runtime_adapter.get_gc_stop_height(&sync_hash), 0);
12451242
// mimic what we do in possible_targets
12461243
assert!(env.clients[1].runtime_adapter.get_epoch_id_from_prev_block(&prev_block_hash).is_ok());
12471244
let tries = env.clients[1].runtime_adapter.get_tries();
12481245
assert!(env.clients[1].chain.clear_data(tries, 2).is_ok());
12491246
}
12501247

1248+
#[test]
1249+
fn test_process_block_after_state_sync() {
1250+
let epoch_length = 1024;
1251+
let mut genesis = Genesis::test(vec!["test0", "test1"], 1);
1252+
genesis.config.epoch_length = epoch_length;
1253+
let runtimes: Vec<Arc<dyn RuntimeAdapter>> = vec![Arc::new(neard::NightshadeRuntime::new(
1254+
Path::new("."),
1255+
create_test_store(),
1256+
Arc::new(genesis.clone()),
1257+
vec![],
1258+
vec![],
1259+
))];
1260+
let mut chain_genesis = ChainGenesis::test();
1261+
chain_genesis.epoch_length = epoch_length;
1262+
let mut env = TestEnv::new_with_runtime(chain_genesis, 1, 1, runtimes);
1263+
for i in 1..epoch_length * 4 + 2 {
1264+
env.produce_block(0, i);
1265+
}
1266+
let sync_height = epoch_length * 4 + 1;
1267+
let sync_block = env.clients[0].chain.get_block_by_height(sync_height).unwrap().clone();
1268+
let sync_hash = *sync_block.hash();
1269+
let chunk_extra = env.clients[0].chain.get_chunk_extra(&sync_hash, 0).unwrap().clone();
1270+
let state_part =
1271+
env.clients[0].runtime_adapter.obtain_state_part(0, &chunk_extra.state_root, 0, 1).unwrap();
1272+
// reset cache
1273+
for i in epoch_length * 3 - 1..sync_height - 1 {
1274+
let block_hash = *env.clients[0].chain.get_block_by_height(i).unwrap().hash();
1275+
assert!(env.clients[0].chain.runtime_adapter.get_epoch_start_height(&block_hash).is_ok());
1276+
}
1277+
env.clients[0].chain.reset_data_pre_state_sync(sync_hash).unwrap();
1278+
env.clients[0]
1279+
.runtime_adapter
1280+
.confirm_state(0, &chunk_extra.state_root, &vec![state_part])
1281+
.unwrap();
1282+
let block = env.clients[0].produce_block(sync_height + 1).unwrap().unwrap();
1283+
let (_, res) = env.clients[0].process_block(block, Provenance::PRODUCED);
1284+
assert!(res.is_ok());
1285+
}
1286+
12511287
#[test]
12521288
fn test_gc_fork_tail() {
12531289
let epoch_length = 101;
@@ -1289,8 +1325,7 @@ fn test_gc_fork_tail() {
12891325
}
12901326
let head = env.clients[1].chain.head().unwrap();
12911327
assert!(
1292-
env.clients[1].runtime_adapter.get_gc_stop_height(&head.last_block_hash).unwrap()
1293-
> epoch_length
1328+
env.clients[1].runtime_adapter.get_gc_stop_height(&head.last_block_hash) > epoch_length
12941329
);
12951330
assert_eq!(env.clients[1].chain.store().fork_tail().unwrap(), 3);
12961331
}

neard/src/runtime.rs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -873,22 +873,36 @@ impl RuntimeAdapter for NightshadeRuntime {
873873
epoch_manager.get_epoch_start_height(block_hash).map_err(Error::from)
874874
}
875875

876-
fn get_gc_stop_height(&self, block_hash: &CryptoHash) -> Result<BlockHeight, Error> {
877-
let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR);
878-
// an epoch must have a first block.
879-
let epoch_first_block = epoch_manager.get_block_info(block_hash)?.epoch_first_block;
880-
let epoch_first_block_info = epoch_manager.get_block_info(&epoch_first_block)?;
881-
// maintain pointers to avoid cloning.
882-
let mut last_block_in_prev_epoch = epoch_first_block_info.prev_hash;
883-
let mut epoch_start_height = epoch_first_block_info.height;
884-
for _ in 0..NUM_EPOCHS_TO_KEEP_STORE_DATA - 1 {
885-
let epoch_first_block =
886-
epoch_manager.get_block_info(&last_block_in_prev_epoch)?.epoch_first_block;
887-
let epoch_first_block_info = epoch_manager.get_block_info(&epoch_first_block)?;
888-
epoch_start_height = epoch_first_block_info.height;
889-
last_block_in_prev_epoch = epoch_first_block_info.prev_hash;
876+
fn get_gc_stop_height(&self, block_hash: &CryptoHash) -> BlockHeight {
877+
let genesis_height = self.genesis_config.genesis_height;
878+
macro_rules! unwrap_result_or_return {
879+
($obj: expr) => {
880+
match $obj {
881+
Ok(value) => value,
882+
Err(_) => {
883+
return genesis_height;
884+
}
885+
}
886+
};
890887
}
891-
Ok(epoch_start_height)
888+
let get_gc_stop_height_inner = || -> Result<BlockHeight, Error> {
889+
let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR);
890+
// an epoch must have a first block.
891+
let epoch_first_block = epoch_manager.get_block_info(block_hash)?.epoch_first_block;
892+
let epoch_first_block_info = epoch_manager.get_block_info(&epoch_first_block)?;
893+
// maintain pointers to avoid cloning.
894+
let mut last_block_in_prev_epoch = epoch_first_block_info.prev_hash;
895+
let mut epoch_start_height = epoch_first_block_info.height;
896+
for _ in 0..NUM_EPOCHS_TO_KEEP_STORE_DATA - 1 {
897+
let epoch_first_block =
898+
epoch_manager.get_block_info(&last_block_in_prev_epoch)?.epoch_first_block;
899+
let epoch_first_block_info = epoch_manager.get_block_info(&epoch_first_block)?;
900+
epoch_start_height = epoch_first_block_info.height;
901+
last_block_in_prev_epoch = epoch_first_block_info.prev_hash;
902+
}
903+
Ok(epoch_start_height)
904+
};
905+
unwrap_result_or_return!(get_gc_stop_height_inner())
892906
}
893907

894908
fn epoch_exists(&self, epoch_id: &EpochId) -> bool {

0 commit comments

Comments
 (0)