mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-23 14:54:45 +00:00
Use the forwards iterator more often (#2376)
## Issue Addressed NA ## Primary Change When investigating memory usage, I noticed that retrieving a block from an early slot (e.g., slot 900) would cause a sharp increase in the memory footprint (from 400mb to 800mb+) which seemed to be ever-lasting. After some investigation, I found that the reverse iteration from the head back to that slot was the likely culprit. To counter this, I've switched the `BeaconChain::block_root_at_slot` to use the forwards iterator, instead of the reverse one. I also noticed that the networking stack is using `BeaconChain::root_at_slot` to check if a peer is relevant (`check_peer_relevance`). Perhaps the steep, seemingly-random-but-consistent increases in memory usage are caused by the use of this function. Using the forwards iterator with the HTTP API alleviated the sharp increases in memory usage. It also made the response much faster (before it felt like to took 1-2s, now it feels instant). ## Additional Changes In the process I also noticed that we have two functions for getting block roots: - `BeaconChain::block_root_at_slot`: returns `None` for a skip slot. - `BeaconChain::root_at_slot`: returns the previous root for a skip slot. I unified these two functions into `block_root_at_slot` and added the `WhenSlotSkipped` enum. Now, the caller must be explicit about the skip-slot behaviour when requesting a root. Additionally, I replaced `vec![]` with `Vec::with_capacity` in `store::chunked_vector::range_query`. I stumbled across this whilst debugging and made this modification to see what effect it would have (not much). It seems like a decent change to keep around, but I'm not concerned either way. Also, `BeaconChain::get_ancestor_block_root` is unused, so I got rid of it 🗑️. ## Additional Info I haven't also done the same for state roots here. Whilst it's possible and a good idea, it's more work since the fwds iterators are presently block-roots-specific. Whilst there's a few places a reverse iteration of state roots could be triggered (e.g., attestation production, HTTP API), they're no where near as common as the `check_peer_relevance` call. As such, I think we should get this PR merged first, then come back for the state root iters. I made an issue here https://github.com/sigp/lighthouse/issues/2377.
This commit is contained in:
@@ -5,7 +5,7 @@ extern crate lazy_static;
|
||||
|
||||
use beacon_chain::{
|
||||
test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy},
|
||||
StateSkipConfig,
|
||||
StateSkipConfig, WhenSlotSkipped,
|
||||
};
|
||||
use store::config::StoreConfig;
|
||||
use tree_hash::TreeHash;
|
||||
@@ -60,7 +60,7 @@ fn produces_attestations() {
|
||||
};
|
||||
|
||||
let block = chain
|
||||
.block_at_slot(block_slot)
|
||||
.block_at_slot(block_slot, WhenSlotSkipped::Prev)
|
||||
.expect("should get block")
|
||||
.expect("block should not be skipped");
|
||||
let block_root = block.message.tree_hash_root();
|
||||
|
||||
@@ -6,7 +6,7 @@ extern crate lazy_static;
|
||||
use beacon_chain::{
|
||||
attestation_verification::Error as AttnError,
|
||||
test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType},
|
||||
BeaconChain, BeaconChainTypes,
|
||||
BeaconChain, BeaconChainTypes, WhenSlotSkipped,
|
||||
};
|
||||
use int_to_bytes::int_to_bytes32;
|
||||
use state_processing::{
|
||||
@@ -912,7 +912,7 @@ fn attestation_that_skips_epochs() {
|
||||
let earlier_slot = (current_epoch - 2).start_slot(MainnetEthSpec::slots_per_epoch());
|
||||
let earlier_block = harness
|
||||
.chain
|
||||
.block_at_slot(earlier_slot)
|
||||
.block_at_slot(earlier_slot, WhenSlotSkipped::Prev)
|
||||
.expect("should not error getting block at slot")
|
||||
.expect("should find block at slot");
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use beacon_chain::{
|
||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
|
||||
OP_POOL_DB_KEY,
|
||||
},
|
||||
WhenSlotSkipped,
|
||||
};
|
||||
use operation_pool::PersistedOperationPool;
|
||||
use state_processing::{
|
||||
@@ -609,3 +610,147 @@ fn produces_and_processes_with_genesis_skip_slots() {
|
||||
run_skip_slot_test(i)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_roots_skip_slot_behaviour() {
|
||||
let harness = get_harness(VALIDATOR_COUNT);
|
||||
|
||||
// Test should be longer than the block roots to ensure a DB lookup is triggered.
|
||||
let chain_length = harness.chain.head().unwrap().beacon_state.block_roots.len() as u64 * 3;
|
||||
|
||||
let skipped_slots = [1, 6, 7, 10, chain_length];
|
||||
|
||||
// Build a chain with some skip slots.
|
||||
for i in 1..=chain_length {
|
||||
if i > 1 {
|
||||
harness.advance_slot();
|
||||
}
|
||||
|
||||
let slot = harness.chain.slot().unwrap().as_u64();
|
||||
|
||||
if !skipped_slots.contains(&slot) {
|
||||
harness.extend_chain(
|
||||
1,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut prev_unskipped_root = None;
|
||||
|
||||
for target_slot in 0..=chain_length {
|
||||
if skipped_slots.contains(&target_slot) {
|
||||
/*
|
||||
* A skip slot
|
||||
*/
|
||||
assert!(
|
||||
harness
|
||||
.chain
|
||||
.block_root_at_slot(target_slot.into(), WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"WhenSlotSkipped::None should return None on a skip slot"
|
||||
);
|
||||
|
||||
let skipped_root = harness
|
||||
.chain
|
||||
.block_root_at_slot(target_slot.into(), WhenSlotSkipped::Prev)
|
||||
.unwrap()
|
||||
.expect("WhenSlotSkipped::Prev should always return Some");
|
||||
|
||||
assert_eq!(
|
||||
skipped_root,
|
||||
prev_unskipped_root.expect("test is badly formed"),
|
||||
"WhenSlotSkipped::Prev should accurately return the prior skipped block"
|
||||
);
|
||||
|
||||
let expected_block = harness.chain.get_block(&skipped_root).unwrap().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
harness
|
||||
.chain
|
||||
.block_at_slot(target_slot.into(), WhenSlotSkipped::Prev)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
expected_block,
|
||||
);
|
||||
|
||||
assert!(
|
||||
harness
|
||||
.chain
|
||||
.block_at_slot(target_slot.into(), WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"WhenSlotSkipped::None should return None on a skip slot"
|
||||
);
|
||||
} else {
|
||||
/*
|
||||
* Not a skip slot
|
||||
*/
|
||||
let skips_none = harness
|
||||
.chain
|
||||
.block_root_at_slot(target_slot.into(), WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.expect("WhenSlotSkipped::None should return Some for non-skipped block");
|
||||
let skips_prev = harness
|
||||
.chain
|
||||
.block_root_at_slot(target_slot.into(), WhenSlotSkipped::Prev)
|
||||
.unwrap()
|
||||
.expect("WhenSlotSkipped::Prev should always return Some");
|
||||
assert_eq!(
|
||||
skips_none, skips_prev,
|
||||
"WhenSlotSkipped::None and WhenSlotSkipped::Prev should be equal on non-skipped slot"
|
||||
);
|
||||
|
||||
let expected_block = harness.chain.get_block(&skips_prev).unwrap().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
harness
|
||||
.chain
|
||||
.block_at_slot(target_slot.into(), WhenSlotSkipped::Prev)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
expected_block
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
harness
|
||||
.chain
|
||||
.block_at_slot(target_slot.into(), WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
expected_block
|
||||
);
|
||||
|
||||
prev_unskipped_root = Some(skips_prev);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A future, non-existent slot.
|
||||
*/
|
||||
|
||||
let future_slot = harness.chain.slot().unwrap() + 1;
|
||||
assert_eq!(
|
||||
harness.chain.head().unwrap().beacon_block.slot(),
|
||||
future_slot - 2,
|
||||
"test precondition"
|
||||
);
|
||||
assert!(
|
||||
harness
|
||||
.chain
|
||||
.block_root_at_slot(future_slot, WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"WhenSlotSkipped::None should return None on a future slot"
|
||||
);
|
||||
assert!(
|
||||
harness
|
||||
.chain
|
||||
.block_root_at_slot(future_slot, WhenSlotSkipped::Prev)
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"WhenSlotSkipped::Prev should return None on a future slot"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user