From 61962898e207e03bb2826f44fd994986c9bfaa20 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 24 Apr 2024 11:22:36 +1000 Subject: [PATCH] In-memory tree states (#5533) * Consensus changes * EF tests * lcli * common and watch * account manager * cargo * fork choice * promise cache * beacon chain * interop genesis * http api * lighthouse * op pool * beacon chain misc * parallel state cache * store * fix issues in store * IT COMPILES * Remove some unnecessary module qualification * Revert Arced pubkey optimization (#5536) * Merge remote-tracking branch 'origin/unstable' into tree-states-memory * Fix caching, rebasing and some tests * Remove unused deps * Merge remote-tracking branch 'origin/unstable' into tree-states-memory * Small cleanups * Revert shuffling cache/promise cache changes * Fix state advance bugs * Fix shuffling tests * Remove some resolved FIXMEs * Remove StateProcessingStrategy * Optimise withdrawals calculation * Don't reorg if state cache is missed * Remove inconsistent state func * Fix beta compiler * Rebase early, rebase often * Fix state caching behaviour * Update to milhouse release * Fix on-disk consensus context format * Merge remote-tracking branch 'origin/unstable' into tree-states-memory * Squashed commit of the following: commit 3a16649023e2d6d40d704dbe958cec712544aba9 Author: Michael Sproul Date: Thu Apr 18 14:26:09 2024 +1000 Fix on-disk consensus context format * Keep indexed attestations, thanks Sean * Merge branch 'on-disk-consensus-context' into tree-states-memory * Merge branch 'unstable' into tree-states-memory * Address half of Sean's review * More simplifications from Sean's review * Cache state after get_advanced_hot_state --- Cargo.lock | 86 +- Cargo.toml | 12 +- beacon_node/beacon_chain/Cargo.toml | 78 +- beacon_node/beacon_chain/src/beacon_chain.rs | 325 +++----- .../beacon_chain/src/beacon_proposer_cache.rs | 8 +- .../beacon_chain/src/beacon_snapshot.rs | 25 +- .../beacon_chain/src/blob_verification.rs | 125 +-- .../beacon_chain/src/block_verification.rs | 152 ++-- beacon_node/beacon_chain/src/builder.rs | 19 +- .../beacon_chain/src/canonical_head.rs | 114 +-- beacon_node/beacon_chain/src/chain_config.rs | 3 - .../overflow_lru_cache.rs | 2 +- .../state_lru_cache.rs | 3 +- beacon_node/beacon_chain/src/errors.rs | 4 + beacon_node/beacon_chain/src/eth1_chain.rs | 29 +- beacon_node/beacon_chain/src/fork_revert.rs | 3 +- beacon_node/beacon_chain/src/lib.rs | 1 - beacon_node/beacon_chain/src/metrics.rs | 17 +- .../beacon_chain/src/shuffling_cache.rs | 2 +- .../beacon_chain/src/state_advance_timer.rs | 185 ++--- .../src/sync_committee_rewards.rs | 42 +- beacon_node/beacon_chain/src/test_utils.rs | 17 +- .../src/validator_pubkey_cache.rs | 5 +- .../beacon_chain/tests/block_verification.rs | 5 +- .../tests/payload_invalidation.rs | 4 +- beacon_node/beacon_chain/tests/rewards.rs | 28 +- beacon_node/beacon_chain/tests/store_tests.rs | 72 +- beacon_node/genesis/src/interop.rs | 10 +- beacon_node/http_api/src/attester_duties.rs | 7 +- .../http_api/src/block_packing_efficiency.rs | 2 +- beacon_node/http_api/src/lib.rs | 14 +- beacon_node/http_api/src/proposer_duties.rs | 5 +- beacon_node/http_api/src/state_id.rs | 5 +- beacon_node/http_api/tests/fork_tests.rs | 5 +- beacon_node/http_api/tests/tests.rs | 24 +- beacon_node/operation_pool/src/lib.rs | 8 +- beacon_node/src/config.rs | 7 +- beacon_node/store/Cargo.toml | 4 + beacon_node/store/src/chunked_vector.rs | 68 +- beacon_node/store/src/config.rs | 4 + beacon_node/store/src/errors.rs | 16 +- beacon_node/store/src/hot_cold_store.rs | 333 +++++--- beacon_node/store/src/impls/beacon_state.rs | 4 +- beacon_node/store/src/iter.rs | 10 +- beacon_node/store/src/lib.rs | 1 + beacon_node/store/src/partial_beacon_state.rs | 33 +- beacon_node/store/src/reconstruct.rs | 3 +- beacon_node/store/src/state_cache.rs | 303 +++++++ common/task_executor/Cargo.toml | 1 + common/task_executor/src/test_utils.rs | 3 +- consensus/state_processing/src/all_caches.rs | 6 +- .../state_processing/src/block_replayer.rs | 100 +-- .../src/common/initiate_validator_exit.rs | 3 +- consensus/state_processing/src/genesis.rs | 6 +- consensus/state_processing/src/lib.rs | 2 +- .../src/per_block_processing.rs | 6 +- .../src/per_block_processing/errors.rs | 7 + .../process_operations.rs | 7 +- .../src/per_block_processing/tests.rs | 7 +- .../altair/participation_flag_updates.rs | 9 +- .../capella/historical_summaries_update.rs | 3 + .../effective_balance_updates.rs | 6 +- .../epoch_processing_summary.rs | 16 +- .../src/per_epoch_processing/errors.rs | 9 +- .../historical_roots_update.rs | 2 +- .../src/per_epoch_processing/resets.rs | 4 +- .../src/per_epoch_processing/single_pass.rs | 66 +- .../state_processing/src/upgrade/altair.rs | 9 +- .../state_processing/src/upgrade/capella.rs | 5 +- .../state_processing/src/upgrade/deneb.rs | 1 - .../state_processing/src/upgrade/electra.rs | 1 - .../state_processing/src/upgrade/merge.rs | 1 - consensus/types/Cargo.toml | 4 +- consensus/types/benches/benches.rs | 46 +- consensus/types/examples/clone_state.rs | 51 -- consensus/types/examples/ssz_encode_state.rs | 54 -- consensus/types/examples/tree_hash_state.rs | 56 -- consensus/types/src/beacon_state.rs | 777 ++++++++++++------ .../types/src/beacon_state/clone_config.rs | 47 -- .../types/src/beacon_state/committee_cache.rs | 7 +- .../src/beacon_state/committee_cache/tests.rs | 2 +- .../types/src/beacon_state/exit_cache.rs | 11 +- consensus/types/src/beacon_state/iter.rs | 4 +- .../types/src/beacon_state/pubkey_cache.rs | 16 +- .../types/src/beacon_state/slashings_cache.rs | 6 +- consensus/types/src/beacon_state/tests.rs | 240 +----- .../types/src/beacon_state/tree_hash_cache.rs | 645 --------------- consensus/types/src/blob_sidecar.rs | 3 +- .../types/src/execution_payload_header.rs | 24 +- consensus/types/src/historical_batch.rs | 6 +- consensus/types/src/lib.rs | 6 +- consensus/types/src/light_client_bootstrap.rs | 7 +- .../types/src/light_client_finality_update.rs | 1 + consensus/types/src/light_client_update.rs | 7 + consensus/types/src/test_utils/test_random.rs | 2 +- consensus/types/src/tree_hash_impls.rs | 165 ---- lcli/src/new_testnet.rs | 2 +- lcli/src/replace_state_pubkeys.rs | 7 +- lcli/src/skip_slots.rs | 4 +- lcli/src/transition_blocks.rs | 7 +- lighthouse/tests/beacon_node.rs | 20 - testing/ef_tests/src/case_result.rs | 3 + .../src/cases/merkle_proof_validity.rs | 5 +- testing/ef_tests/src/cases/sanity_blocks.rs | 4 +- testing/ef_tests/src/cases/ssz_generic.rs | 28 +- testing/ef_tests/src/cases/ssz_static.rs | 1 - testing/ef_tests/src/cases/transition.rs | 3 +- testing/state_transition_vectors/src/exit.rs | 5 +- 108 files changed, 2032 insertions(+), 2756 deletions(-) create mode 100644 beacon_node/store/src/state_cache.rs delete mode 100644 consensus/types/examples/clone_state.rs delete mode 100644 consensus/types/examples/ssz_encode_state.rs delete mode 100644 consensus/types/examples/tree_hash_state.rs delete mode 100644 consensus/types/src/beacon_state/clone_config.rs delete mode 100644 consensus/types/src/beacon_state/tree_hash_cache.rs delete mode 100644 consensus/types/src/tree_hash_impls.rs diff --git a/Cargo.lock b/Cargo.lock index 4e3557c7efb..d63773d9a94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,15 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "archery" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a8da9bc4c4053ee067669762bcaeea6e241841295a2b6c948312dad6ef4cc02" +dependencies = [ + "static_assertions", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -2810,13 +2819,13 @@ dependencies = [ [[package]] name = "ethereum_hashing" -version = "1.0.0-beta.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233dc6f434ce680dbabf4451ee3380cec46cb3c45d66660445a435619710dd35" +checksum = "6ea7b408432c13f71af01197b1d3d0069c48a27bfcfbe72a81fc346e47f6defb" dependencies = [ "cpufeatures", "lazy_static", - "ring 0.16.20", + "ring 0.17.8", "sha2 0.10.8", ] @@ -5456,6 +5465,29 @@ dependencies = [ "quote", ] +[[package]] +name = "milhouse" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3826d3602a3674b07e080ce1982350e454ec253d73f156bd927ac1b652293f4d" +dependencies = [ + "arbitrary", + "derivative", + "ethereum-types 0.14.1", + "ethereum_hashing", + "ethereum_ssz", + "ethereum_ssz_derive", + "itertools", + "parking_lot 0.12.1", + "rayon", + "serde", + "smallvec", + "tree_hash", + "triomphe", + "typenum", + "vec_map", +] + [[package]] name = "mime" version = "0.3.17" @@ -7109,6 +7141,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "rpds" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ef5140bcb576bfd6d56cd2de709a7d17851ac1f3805e67fe9d99e42a11821f" +dependencies = [ + "archery", +] + [[package]] name = "rtnetlink" version = "0.10.1" @@ -8123,9 +8164,9 @@ dependencies = [ [[package]] name = "ssz_types" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382939886cb24ee8ac885d09116a60f6262d827c7a9e36012b4f6d3d0116d0b3" +checksum = "625b20de2d4b3891e6972f4ce5061cb11bd52b3479270c4b177c134b571194a9" dependencies = [ "arbitrary", "derivative", @@ -8139,6 +8180,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "state_processing" version = "0.2.0" @@ -8189,6 +8236,7 @@ name = "store" version = "0.2.0" dependencies = [ "beacon_chain", + "bls", "db-key", "directory", "ethereum_ssz", @@ -8197,11 +8245,14 @@ dependencies = [ "lazy_static", "leveldb", "lighthouse_metrics", + "logging", "lru", "parking_lot 0.12.1", + "safe_arith", "serde", "slog", "sloggers", + "smallvec", "state_processing", "strum", "tempfile", @@ -8267,9 +8318,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "superstruct" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b9e5728aa1a87141cefd4e7509903fc01fa0dcb108022b1e841a67c5159fc5" +checksum = "6f4e1f478a7728f8855d7e620e9a152cf8932c6614f86564c886f9b8141f3201" dependencies = [ "darling 0.13.4", "itertools", @@ -8415,6 +8466,7 @@ dependencies = [ "futures", "lazy_static", "lighthouse_metrics", + "logging", "slog", "sloggers", "tokio", @@ -8955,9 +9007,9 @@ dependencies = [ [[package]] name = "tree_hash" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c998ac5fe2b07c025444bdd522e6258110b63861c6698eedc610c071980238d" +checksum = "134d6b24a5b829f30b5ee7de05ba7384557f5f6b00e29409cdf2392f93201bfa" dependencies = [ "ethereum-types 0.14.1", "ethereum_hashing", @@ -8966,9 +9018,9 @@ dependencies = [ [[package]] name = "tree_hash_derive" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84303a9c7cda5f085a3ed9cd241d1e95e04d88aab1d679b02f212e653537ba86" +checksum = "9ce7bccc538359a213436af7bc95804bdbf1c2a21d80e22953cbe9e096837ff1" dependencies = [ "darling 0.13.4", "quote", @@ -8985,6 +9037,16 @@ dependencies = [ "rlp", ] +[[package]] +name = "triomphe" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee8098afad3fb0c54a9007aab6804558410503ad676d4633f9c2559a00ac0f" +dependencies = [ + "serde", + "stable_deref_trait", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -9024,12 +9086,14 @@ dependencies = [ "maplit", "merkle_proof", "metastruct", + "milhouse", "parking_lot 0.12.1", "paste", "rand", "rand_xorshift", "rayon", "regex", + "rpds", "rusqlite", "safe_arith", "serde", diff --git a/Cargo.toml b/Cargo.toml index 69f6835f575..296dadc1f66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,7 +114,7 @@ discv5 = { version = "0.4.1", features = ["libp2p"] } env_logger = "0.9" error-chain = "0.12" ethereum-types = "0.14" -ethereum_hashing = "1.0.0-beta.2" +ethereum_hashing = "0.6.0" ethereum_serde_utils = "0.5.2" ethereum_ssz = "0.5" ethereum_ssz_derive = "0.5" @@ -132,6 +132,7 @@ libsecp256k1 = "0.7" log = "0.4" lru = "0.12" maplit = "1" +milhouse = "0.1" num_cpus = "1" parking_lot = "0.12" paste = "1" @@ -144,6 +145,7 @@ rayon = "1.7" regex = "1" reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "stream", "rustls-tls", "native-tls-vendored"] } ring = "0.16" +rpds = "0.11" rusqlite = { version = "0.28", features = ["bundled"] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -156,9 +158,9 @@ slog-term = "2" sloggers = { version = "2", features = ["json"] } smallvec = "1.11.2" snap = "1" -ssz_types = "0.5" +ssz_types = "0.6" strum = { version = "0.24", features = ["derive"] } -superstruct = "0.6" +superstruct = "0.7" syn = "1" sysinfo = "0.26" tempfile = "3" @@ -170,8 +172,8 @@ tracing-appender = "0.2" tracing-core = "0.1" tracing-log = "0.2" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tree_hash = "0.5" -tree_hash_derive = "0.5" +tree_hash = "0.6" +tree_hash_derive = "0.6" url = "2" uuid = { version = "0.8", features = ["serde", "v4"] } warp = { version = "0.3.7", default-features = false, features = ["tls"] } diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 43c2c896f71..9c7c7febc5f 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -19,58 +19,58 @@ environment = { workspace = true } serde_json = { workspace = true } [dependencies] -serde_json = { workspace = true } +bitvec = { workspace = true } +bls = { workspace = true } +derivative = { workspace = true } +eth1 = { workspace = true } +eth2 = { workspace = true } eth2_network_config = { workspace = true } -merkle_proof = { workspace = true } -store = { workspace = true } -parking_lot = { workspace = true } +ethereum_hashing = { workspace = true } +ethereum_serde_utils = { workspace = true } +ethereum_ssz = { workspace = true } +ethereum_ssz_derive = { workspace = true } +execution_layer = { workspace = true } +fork_choice = { workspace = true } +futures = { workspace = true } +genesis = { workspace = true } +hex = { workspace = true } +int_to_bytes = { workspace = true } +itertools = { workspace = true } +kzg = { workspace = true } lazy_static = { workspace = true } -smallvec = { workspace = true } lighthouse_metrics = { workspace = true } +logging = { workspace = true } +lru = { workspace = true } +merkle_proof = { workspace = true } +oneshot_broadcast = { path = "../../common/oneshot_broadcast/" } operation_pool = { workspace = true } +parking_lot = { workspace = true } +proto_array = { workspace = true } +rand = { workspace = true } rayon = { workspace = true } +safe_arith = { workspace = true } +sensitive_url = { workspace = true } serde = { workspace = true } -ethereum_serde_utils = { workspace = true } +serde_json = { workspace = true } +slasher = { workspace = true } slog = { workspace = true } +slog-async = { workspace = true } +slog-term = { workspace = true } sloggers = { workspace = true } slot_clock = { workspace = true } -ethereum_hashing = { workspace = true } -ethereum_ssz = { workspace = true } +smallvec = { workspace = true } ssz_types = { workspace = true } -ethereum_ssz_derive = { workspace = true } state_processing = { workspace = true } -tree_hash_derive = { workspace = true } -tree_hash = { workspace = true } -types = { workspace = true } -tokio = { workspace = true } -tokio-stream = { workspace = true } -eth1 = { workspace = true } -futures = { workspace = true } -genesis = { workspace = true } -int_to_bytes = { workspace = true } -rand = { workspace = true } -proto_array = { workspace = true } -lru = { workspace = true } -tempfile = { workspace = true } -bitvec = { workspace = true } -bls = { workspace = true } -kzg = { workspace = true } -safe_arith = { workspace = true } -fork_choice = { workspace = true } -task_executor = { workspace = true } -derivative = { workspace = true } -itertools = { workspace = true } -slasher = { workspace = true } -eth2 = { workspace = true } +store = { workspace = true } strum = { workspace = true } -logging = { workspace = true } -execution_layer = { workspace = true } -sensitive_url = { workspace = true } superstruct = { workspace = true } -hex = { workspace = true } -oneshot_broadcast = { path = "../../common/oneshot_broadcast/" } -slog-term = { workspace = true } -slog-async = { workspace = true } +task_executor = { workspace = true } +tempfile = { workspace = true } +tokio = { workspace = true } +tokio-stream = { workspace = true } +tree_hash = { workspace = true } +tree_hash_derive = { workspace = true } +types = { workspace = true } [[test]] name = "beacon_chain_tests" diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index c59c5e8ed10..c65dfdb449f 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -58,7 +58,6 @@ use crate::persisted_beacon_chain::{PersistedBeaconChain, DUMMY_CANONICAL_HEAD_B use crate::persisted_fork_choice::PersistedForkChoice; use crate::pre_finalization_cache::PreFinalizationBlockCache; use crate::shuffling_cache::{BlockShufflingIds, ShufflingCache}; -use crate::snapshot_cache::{BlockProductionPreState, SnapshotCache}; use crate::sync_committee_verification::{ Error as SyncCommitteeError, VerifiedSyncCommitteeMessage, VerifiedSyncContribution, }; @@ -103,8 +102,7 @@ use state_processing::{ }, per_slot_processing, state_advance::{complete_state_advance, partial_state_advance}, - BlockSignatureStrategy, ConsensusContext, SigVerifiedOp, StateProcessingStrategy, - VerifyBlockRoot, VerifyOperation, + BlockSignatureStrategy, ConsensusContext, SigVerifiedOp, VerifyBlockRoot, VerifyOperation, }; use std::borrow::Cow; use std::cmp::Ordering; @@ -130,9 +128,6 @@ pub type ForkChoiceError = fork_choice::Error; /// Alias to appease clippy. type HashBlockTuple = (Hash256, RpcBlock); -/// The time-out before failure during an operation to take a read/write RwLock on the block -/// processing cache. -pub const BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT: Duration = Duration::from_secs(1); /// The time-out before failure during an operation to take a read/write RwLock on the /// attestation cache. pub const ATTESTATION_CACHE_LOCK_TIMEOUT: Duration = Duration::from_secs(1); @@ -176,6 +171,7 @@ pub const INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON: &str = "Finalized merge transition block is invalid."; /// Defines the behaviour when a block/block-root for a skipped slot is requested. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WhenSlotSkipped { /// If the slot is a skip slot, return `None`. /// @@ -450,8 +446,6 @@ pub struct BeaconChain { pub event_handler: Option>, /// Used to track the heads of the beacon chain. pub(crate) head_tracker: Arc, - /// A cache dedicated to block processing. - pub(crate) snapshot_cache: TimeoutRwLock>, /// Caches the attester shuffling for a given epoch and shuffling key root. pub shuffling_cache: TimeoutRwLock, /// A cache of eth1 deposit data at epoch boundaries for deposit finalization @@ -492,11 +486,6 @@ pub struct BeaconChain { pub data_availability_checker: Arc>, /// The KZG trusted setup used by this chain. pub kzg: Option>, - /// State with complete tree hash cache, ready for block production. - /// - /// NB: We can delete this once we have tree-states. - #[allow(clippy::type_complexity)] - pub block_production_state: Arc)>>>, } pub enum BeaconBlockResponseWrapper { @@ -773,7 +762,7 @@ impl BeaconChain { let iter = self.store.forwards_block_roots_iterator( start_slot, - local_head.beacon_state.clone_with(CloneConfig::none()), + local_head.beacon_state.clone(), local_head.beacon_block_root, &self.spec, )?; @@ -803,12 +792,7 @@ impl BeaconChain { let iter = self.store.forwards_block_roots_iterator_until( start_slot, end_slot, - || { - Ok(( - head.beacon_state.clone_with_only_committee_caches(), - head.beacon_block_root, - )) - }, + || Ok((head.beacon_state.clone(), head.beacon_block_root)), &self.spec, )?; Ok(iter @@ -879,7 +863,7 @@ impl BeaconChain { let iter = self.store.forwards_state_roots_iterator( start_slot, local_head.beacon_state_root(), - local_head.beacon_state.clone_with(CloneConfig::none()), + local_head.beacon_state.clone(), &self.spec, )?; @@ -900,12 +884,7 @@ impl BeaconChain { let iter = self.store.forwards_state_roots_iterator_until( start_slot, end_slot, - || { - Ok(( - head.beacon_state.clone_with_only_committee_caches(), - head.beacon_state_root(), - )) - }, + || Ok((head.beacon_state.clone(), head.beacon_state_root())), &self.spec, )?; Ok(iter @@ -3561,29 +3540,6 @@ impl BeaconChain { }); } - self.snapshot_cache - .try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .ok_or(Error::SnapshotCacheLockTimeout) - .map(|mut snapshot_cache| { - snapshot_cache.insert( - BeaconSnapshot { - beacon_state: state, - beacon_block: signed_block.clone(), - beacon_block_root: block_root, - }, - None, - &self.spec, - ) - }) - .unwrap_or_else(|e| { - error!( - self.log, - "Failed to insert snapshot"; - "error" => ?e, - "task" => "process block" - ); - }); - self.head_tracker .register_block(block_root, parent_root, slot); @@ -4145,22 +4101,22 @@ impl BeaconChain { self.wait_for_fork_choice_before_block_production(slot)?; drop(fork_choice_timer); - // Producing a block requires the tree hash cache, so clone a full state corresponding to - // the head from the snapshot cache. Unfortunately we can't move the snapshot out of the - // cache (which would be fast), because we need to re-process the block after it has been - // signed. If we miss the cache or we're producing a block that conflicts with the head, - // fall back to getting the head from `slot - 1`. let state_load_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_STATE_LOAD_TIMES); // Atomically read some values from the head whilst avoiding holding cached head `Arc` any // longer than necessary. - let (head_slot, head_block_root) = { + let (head_slot, head_block_root, head_state_root) = { let head = self.canonical_head.cached_head(); - (head.head_slot(), head.head_block_root()) + ( + head.head_slot(), + head.head_block_root(), + head.head_state_root(), + ) }; let (state, state_root_opt) = if head_slot < slot { // Attempt an aggressive re-org if configured and the conditions are right. - if let Some(re_org_state) = self.get_state_for_re_org(slot, head_slot, head_block_root) + if let Some((re_org_state, re_org_state_root)) = + self.get_state_for_re_org(slot, head_slot, head_block_root) { info!( self.log, @@ -4168,37 +4124,16 @@ impl BeaconChain { "slot" => slot, "head_to_reorg" => %head_block_root, ); - (re_org_state.pre_state, re_org_state.state_root) - } - // Normal case: proposing a block atop the current head using the cache. - else if let Some((_, cached_state)) = - self.get_state_from_block_production_cache(head_block_root) - { - (cached_state.pre_state, cached_state.state_root) - } - // Fall back to a direct read of the snapshot cache. - else if let Some(pre_state) = - self.get_state_from_snapshot_cache_for_block_production(head_block_root) - { - warn!( - self.log, - "Block production cache miss"; - "message" => "falling back to snapshot cache clone", - "slot" => slot - ); - (pre_state.pre_state, pre_state.state_root) + (re_org_state, Some(re_org_state_root)) } else { - warn!( - self.log, - "Block production cache miss"; - "message" => "this block is more likely to be orphaned", - "slot" => slot, - ); - let state = self - .state_at_slot(slot - 1, StateSkipConfig::WithStateRoots) - .map_err(|_| BlockProductionError::UnableToProduceAtSlot(slot))?; - - (state, None) + // Fetch the head state advanced through to `slot`, which should be present in the + // state cache thanks to the state advance timer. + let (state_root, state) = self + .store + .get_advanced_hot_state(head_block_root, slot, head_state_root) + .map_err(BlockProductionError::FailedToLoadState)? + .ok_or(BlockProductionError::UnableToProduceAtSlot(slot))?; + (state, Some(state_root)) } } else { warn!( @@ -4219,40 +4154,6 @@ impl BeaconChain { Ok((state, state_root_opt)) } - /// Get the state cached for block production *if* it matches `head_block_root`. - /// - /// This will clear the cache regardless of whether the block root matches, so only call this if - /// you think the `head_block_root` is likely to match! - fn get_state_from_block_production_cache( - &self, - head_block_root: Hash256, - ) -> Option<(Hash256, BlockProductionPreState)> { - // Take care to drop the lock as quickly as possible. - let mut lock = self.block_production_state.lock(); - let result = lock - .take() - .filter(|(cached_block_root, _)| *cached_block_root == head_block_root); - drop(lock); - result - } - - /// Get a state for block production from the snapshot cache. - fn get_state_from_snapshot_cache_for_block_production( - &self, - head_block_root: Hash256, - ) -> Option> { - if let Some(lock) = self - .snapshot_cache - .try_read_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - { - let result = lock.get_state_for_block_production(head_block_root); - drop(lock); - result - } else { - None - } - } - /// Fetch the beacon state to use for producing a block if a 1-slot proposer re-org is viable. /// /// This function will return `None` if proposer re-orgs are disabled. @@ -4261,7 +4162,7 @@ impl BeaconChain { slot: Slot, head_slot: Slot, canonical_head: Hash256, - ) -> Option> { + ) -> Option<(BeaconState, Hash256)> { let re_org_head_threshold = self.config.re_org_head_threshold?; let re_org_parent_threshold = self.config.re_org_parent_threshold?; @@ -4345,26 +4246,14 @@ impl BeaconChain { drop(proposer_head_timer); let re_org_parent_block = proposer_head.parent_node.root; - // Only attempt a re-org if we hit the block production cache or snapshot cache. - let pre_state = self - .get_state_from_block_production_cache(re_org_parent_block) - .map(|(_, state)| state) + let (state_root, state) = self + .store + .get_advanced_hot_state_from_cache(re_org_parent_block, slot) .or_else(|| { warn!( - self.log, - "Block production cache miss"; - "message" => "falling back to snapshot cache during re-org", - "slot" => slot, - "block_root" => ?re_org_parent_block - ); - self.get_state_from_snapshot_cache_for_block_production(re_org_parent_block) - }) - .or_else(|| { - debug!( self.log, "Not attempting re-org"; - "reason" => "missed snapshot cache", - "parent_block" => ?re_org_parent_block, + "reason" => "no state in cache" ); None })?; @@ -4378,7 +4267,7 @@ impl BeaconChain { "threshold_weight" => proposer_head.re_org_head_weight_threshold ); - Some(pre_state) + Some((state, state_root)) } /// Get the proposer index and `prev_randao` value for a proposal at slot `proposal_slot`. @@ -4506,20 +4395,6 @@ impl BeaconChain { let (unadvanced_state, unadvanced_state_root) = if cached_head.head_block_root() == parent_block_root { (Cow::Borrowed(head_state), cached_head.head_state_root()) - } else if let Some(snapshot) = self - .snapshot_cache - .try_read_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .ok_or(Error::SnapshotCacheLockTimeout)? - .get_cloned(parent_block_root, CloneConfig::none()) - { - debug!( - self.log, - "Hit snapshot cache during withdrawals calculation"; - "slot" => proposal_slot, - "parent_block_root" => ?parent_block_root, - ); - let state_root = snapshot.beacon_state_root(); - (Cow::Owned(snapshot.beacon_state), state_root) } else { info!( self.log, @@ -4530,10 +4405,11 @@ impl BeaconChain { let block = self .get_blinded_block(&parent_block_root)? .ok_or(Error::MissingBeaconBlock(parent_block_root))?; - let state = self - .get_state(&block.state_root(), Some(block.slot()))? + let (state_root, state) = self + .store + .get_advanced_hot_state(parent_block_root, proposal_slot, block.state_root())? .ok_or(Error::MissingBeaconState(block.state_root()))?; - (Cow::Owned(state), block.state_root()) + (Cow::Owned(state), state_root) }; // Parent state epoch is the same as the proposal, we don't need to advance because the @@ -4910,6 +4786,7 @@ impl BeaconChain { drop(slot_timer); state.build_committee_cache(RelativeEpoch::Current, &self.spec)?; + state.apply_pending_mutations()?; let parent_root = if state.slot() > 0 { *state @@ -5383,7 +5260,6 @@ impl BeaconChain { &mut state, &block, signature_strategy, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &self.spec, @@ -6308,6 +6184,17 @@ impl BeaconChain { "head_block_root" => head_block_root.to_string(), ); + // If the block's state will be so far ahead of `shuffling_epoch` that even its + // previous epoch committee cache will be too new, then error. Callers of this function + // shouldn't be requesting such old shufflings for this `head_block_root`. + let head_block_epoch = head_block.slot.epoch(T::EthSpec::slots_per_epoch()); + if head_block_epoch > shuffling_epoch + 1 { + return Err(Error::InvalidStateForShuffling { + state_epoch: head_block_epoch, + shuffling_epoch, + }); + } + let state_read_timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_READ_TIMES); @@ -6318,71 +6205,52 @@ impl BeaconChain { // to copy the head is liable to race-conditions. let head_state_opt = self.with_head(|head| { if head.beacon_block_root == head_block_root { - Ok(Some(( - head.beacon_state - .clone_with(CloneConfig::committee_caches_only()), - head.beacon_state_root(), - ))) + Ok(Some((head.beacon_state.clone(), head.beacon_state_root()))) } else { Ok::<_, Error>(None) } })?; + // Compute the `target_slot` to advance the block's state to. + // + // Since there's a one-epoch look-ahead on the attester shuffling, it suffices to + // only advance into the first slot of the epoch prior to `shuffling_epoch`. + // + // If the `head_block` is already ahead of that slot, then we should load the state + // at that slot, as we've determined above that the `shuffling_epoch` cache will + // not be too far in the past. + let target_slot = std::cmp::max( + shuffling_epoch + .saturating_sub(1_u64) + .start_slot(T::EthSpec::slots_per_epoch()), + head_block.slot, + ); + // If the head state is useful for this request, use it. Otherwise, read a state from - // disk. + // disk that is advanced as close as possible to `target_slot`. let (mut state, state_root) = if let Some((state, state_root)) = head_state_opt { (state, state_root) } else { - let block_state_root = head_block.state_root; - let max_slot = shuffling_epoch.start_slot(T::EthSpec::slots_per_epoch()); let (state_root, state) = self .store - .get_inconsistent_state_for_attestation_verification_only( - &head_block_root, - max_slot, - block_state_root, - )? - .ok_or(Error::MissingBeaconState(block_state_root))?; + .get_advanced_hot_state(head_block_root, target_slot, head_block.state_root)? + .ok_or(Error::MissingBeaconState(head_block.state_root))?; (state, state_root) }; - /* - * IMPORTANT - * - * Since it's possible that - * `Store::get_inconsistent_state_for_attestation_verification_only` was used to obtain - * the state, we cannot rely upon the following fields: - * - * - `state.state_roots` - * - `state.block_roots` - * - * These fields should not be used for the rest of this function. - */ - metrics::stop_timer(state_read_timer); let state_skip_timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_STATE_SKIP_TIMES); - // If the state is in an earlier epoch, advance it. If it's from a later epoch, reject - // it. + // If the state is still in an earlier epoch, advance it to the `target_slot` so + // that its next epoch committee cache matches the `shuffling_epoch`. if state.current_epoch() + 1 < shuffling_epoch { - // Since there's a one-epoch look-ahead on the attester shuffling, it suffices to - // only advance into the slot prior to the `shuffling_epoch`. - let target_slot = shuffling_epoch - .saturating_sub(1_u64) - .start_slot(T::EthSpec::slots_per_epoch()); - - // Advance the state into the required slot, using the "partial" method since the state - // roots are not relevant for the shuffling. + // Advance the state into the required slot, using the "partial" method since the + // state roots are not relevant for the shuffling. partial_state_advance(&mut state, Some(state_root), target_slot, &self.spec)?; - } else if state.current_epoch() > shuffling_epoch { - return Err(Error::InvalidStateForShuffling { - state_epoch: state.current_epoch(), - shuffling_epoch, - }); } - metrics::stop_timer(state_skip_timer); + let committee_building_timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_COMMITTEE_BUILDING_TIMES); @@ -6391,8 +6259,7 @@ impl BeaconChain { state.build_committee_cache(relative_epoch, &self.spec)?; - let committee_cache = state.take_committee_cache(relative_epoch)?; - let committee_cache = Arc::new(committee_cache); + let committee_cache = state.committee_cache(relative_epoch)?.clone(); let shuffling_decision_block = shuffling_id.shuffling_decision_block; self.shuffling_cache @@ -6412,29 +6279,27 @@ impl BeaconChain { /// /// This could be a very expensive operation and should only be done in testing/analysis /// activities. + /// + /// This dump function previously used a backwards iterator but has been swapped to a forwards + /// iterator as it allows for MUCH better caching and rebasing. Memory usage of some tests went + /// from 5GB per test to 90MB. #[allow(clippy::type_complexity)] pub fn chain_dump( &self, ) -> Result>>, Error> { let mut dump = vec![]; - let mut last_slot = { - let head = self.canonical_head.cached_head(); - BeaconSnapshot { - beacon_block: Arc::new(head.snapshot.beacon_block.clone_as_blinded()), - beacon_block_root: head.snapshot.beacon_block_root, - beacon_state: head.snapshot.beacon_state.clone(), - } - }; - - dump.push(last_slot.clone()); + let mut prev_block_root = None; + let mut prev_beacon_state = None; - loop { - let beacon_block_root = last_slot.beacon_block.parent_root(); + for res in self.forwards_iter_block_roots(Slot::new(0))? { + let (beacon_block_root, _) = res?; - if beacon_block_root == Hash256::zero() { - break; // Genesis has been reached. + // Do not include snapshots at skipped slots. + if Some(beacon_block_root) == prev_block_root { + continue; } + prev_block_root = Some(beacon_block_root); let beacon_block = self .store @@ -6443,25 +6308,31 @@ impl BeaconChain { Error::DBInconsistent(format!("Missing block {}", beacon_block_root)) })?; let beacon_state_root = beacon_block.state_root(); - let beacon_state = self + + let mut beacon_state = self .store .get_state(&beacon_state_root, Some(beacon_block.slot()))? .ok_or_else(|| { Error::DBInconsistent(format!("Missing state {:?}", beacon_state_root)) })?; - let slot = BeaconSnapshot { + // This beacon state might come from the freezer DB, which means it could have pending + // updates or lots of untethered memory. We rebase it on the previous state in order to + // address this. + beacon_state.apply_pending_mutations()?; + if let Some(prev) = prev_beacon_state { + beacon_state.rebase_on(&prev, &self.spec)?; + } + beacon_state.build_caches(&self.spec)?; + prev_beacon_state = Some(beacon_state.clone()); + + let snapshot = BeaconSnapshot { beacon_block: Arc::new(beacon_block), beacon_block_root, beacon_state, }; - - dump.push(slot.clone()); - last_slot = slot; + dump.push(snapshot); } - - dump.reverse(); - Ok(dump) } @@ -6696,6 +6567,10 @@ impl BeaconChain { self.data_availability_checker.data_availability_boundary() } + pub fn logger(&self) -> &Logger { + &self.log + } + /// Gets the `LightClientBootstrap` object for a requested block root. /// /// Returns `None` when the state or block is not found in the database. diff --git a/beacon_node/beacon_chain/src/beacon_proposer_cache.rs b/beacon_node/beacon_chain/src/beacon_proposer_cache.rs index ca390712b13..d10bbfbbc5f 100644 --- a/beacon_node/beacon_chain/src/beacon_proposer_cache.rs +++ b/beacon_node/beacon_chain/src/beacon_proposer_cache.rs @@ -17,8 +17,7 @@ use std::cmp::Ordering; use std::num::NonZeroUsize; use types::non_zero_usize::new_non_zero_usize; use types::{ - BeaconState, BeaconStateError, ChainSpec, CloneConfig, Epoch, EthSpec, Fork, Hash256, Slot, - Unsigned, + BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, Fork, Hash256, Slot, Unsigned, }; /// The number of sets of proposer indices that should be cached. @@ -145,10 +144,7 @@ pub fn compute_proposer_duties_from_head( let (mut state, head_state_root, head_block_root) = { let head = chain.canonical_head.cached_head(); // Take a copy of the head state. - let head_state = head - .snapshot - .beacon_state - .clone_with(CloneConfig::committee_caches_only()); + let head_state = head.snapshot.beacon_state.clone(); let head_state_root = head.head_state_root(); let head_block_root = head.head_block_root(); (head_state, head_state_root, head_block_root) diff --git a/beacon_node/beacon_chain/src/beacon_snapshot.rs b/beacon_node/beacon_chain/src/beacon_snapshot.rs index afb13247766..e9fde48ac67 100644 --- a/beacon_node/beacon_chain/src/beacon_snapshot.rs +++ b/beacon_node/beacon_chain/src/beacon_snapshot.rs @@ -1,8 +1,8 @@ use serde::Serialize; use std::sync::Arc; use types::{ - beacon_state::CloneConfig, AbstractExecPayload, BeaconState, EthSpec, FullPayload, Hash256, - SignedBeaconBlock, + AbstractExecPayload, BeaconState, EthSpec, FullPayload, Hash256, SignedBeaconBlock, + SignedBlindedBeaconBlock, }; /// Represents some block and its associated state. Generally, this will be used for tracking the @@ -14,6 +14,19 @@ pub struct BeaconSnapshot = FullPayl pub beacon_state: BeaconState, } +/// This snapshot is to be used for verifying a child of `self.beacon_block`. +#[derive(Debug)] +pub struct PreProcessingSnapshot { + /// This state is equivalent to the `self.beacon_block.state_root()` state that has been + /// advanced forward one slot using `per_slot_processing`. This state is "primed and ready" for + /// the application of another block. + pub pre_state: BeaconState, + /// This value is only set to `Some` if the `pre_state` was *not* advanced forward. + pub beacon_state_root: Option, + pub beacon_block: SignedBlindedBeaconBlock, + pub beacon_block_root: Hash256, +} + impl> BeaconSnapshot { /// Create a new checkpoint. pub fn new( @@ -48,12 +61,4 @@ impl> BeaconSnapshot { self.beacon_block_root = beacon_block_root; self.beacon_state = beacon_state; } - - pub fn clone_with(&self, clone_config: CloneConfig) -> Self { - Self { - beacon_block: self.beacon_block.clone(), - beacon_block_root: self.beacon_block_root, - beacon_state: self.beacon_state.clone_with(clone_config), - } - } } diff --git a/beacon_node/beacon_chain/src/blob_verification.rs b/beacon_node/beacon_chain/src/blob_verification.rs index a1ae260d930..263b9f9e013 100644 --- a/beacon_node/beacon_chain/src/blob_verification.rs +++ b/beacon_node/beacon_chain/src/blob_verification.rs @@ -2,7 +2,7 @@ use derivative::Derivative; use slot_clock::SlotClock; use std::sync::Arc; -use crate::beacon_chain::{BeaconChain, BeaconChainTypes, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT}; +use crate::beacon_chain::{BeaconChain, BeaconChainTypes}; use crate::block_verification::{ cheap_state_advance_to_obtain_committees, get_validator_pubkey_cache, process_block_slash_info, BlockSlashInfo, @@ -11,15 +11,13 @@ use crate::kzg_utils::{validate_blob, validate_blobs}; use crate::{metrics, BeaconChainError}; use kzg::{Error as KzgError, Kzg, KzgCommitment}; use merkle_proof::MerkleTreeError; -use slog::{debug, warn}; +use slog::debug; use ssz_derive::{Decode, Encode}; use ssz_types::VariableList; use std::time::Duration; use tree_hash::TreeHash; use types::blob_sidecar::BlobIdentifier; -use types::{ - BeaconStateError, BlobSidecar, CloneConfig, EthSpec, Hash256, SignedBeaconBlockHeader, Slot, -}; +use types::{BeaconStateError, BlobSidecar, EthSpec, Hash256, SignedBeaconBlockHeader, Slot}; /// An error occurred while validating a gossip blob. #[derive(Debug)] @@ -514,98 +512,43 @@ pub fn validate_blob_sidecar_for_gossip( "block_root" => %block_root, "index" => %blob_index, ); - if let Some(mut snapshot) = chain - .snapshot_cache - .try_read_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .and_then(|snapshot_cache| { - snapshot_cache.get_cloned(block_parent_root, CloneConfig::committee_caches_only()) - }) - { - if snapshot.beacon_state.slot() == blob_slot { - debug!( - chain.log, - "Cloning snapshot cache state for blob verification"; - "block_root" => %block_root, - "index" => %blob_index, - ); - ( - snapshot - .beacon_state - .get_beacon_proposer_index(blob_slot, &chain.spec)?, - snapshot.beacon_state.fork(), - ) - } else { - debug!( - chain.log, - "Cloning and advancing snapshot cache state for blob verification"; - "block_root" => %block_root, - "index" => %blob_index, - ); - let state = - cheap_state_advance_to_obtain_committees::<_, GossipBlobError>( - &mut snapshot.beacon_state, - Some(snapshot.beacon_block_root), - blob_slot, - &chain.spec, - )?; - ( - state.get_beacon_proposer_index(blob_slot, &chain.spec)?, - state.fork(), - ) - } - } - // Need to advance the state to get the proposer index - else { - warn!( - chain.log, - "Snapshot cache miss for blob verification"; - "block_root" => %block_root, - "index" => %blob_index, - ); - - let parent_block = chain - .get_blinded_block(&block_parent_root) - .map_err(GossipBlobError::BeaconChainError)? - .ok_or_else(|| { - GossipBlobError::from(BeaconChainError::MissingBeaconBlock(block_parent_root)) - })?; - - let mut parent_state = chain - .get_state(&parent_block.state_root(), Some(parent_block.slot()))? - .ok_or_else(|| { - BeaconChainError::DBInconsistent(format!( - "Missing state {:?}", - parent_block.state_root() - )) - })?; - let state = cheap_state_advance_to_obtain_committees::<_, GossipBlobError>( - &mut parent_state, - Some(parent_block.state_root()), - blob_slot, - &chain.spec, - )?; - - let proposers = state.get_beacon_proposer_indices(&chain.spec)?; - let proposer_index = *proposers - .get(blob_slot.as_usize() % T::EthSpec::slots_per_epoch() as usize) - .ok_or_else(|| BeaconChainError::NoProposerForSlot(blob_slot))?; - - let fork = state.fork(); - // Prime the proposer shuffling cache with the newly-learned value. - chain.beacon_proposer_cache.lock().insert( - blob_epoch, - proposer_shuffling_root, - proposers, - fork, - )?; - (proposer_index, fork) - } + let (parent_state_root, mut parent_state) = chain + .store + .get_advanced_hot_state(block_parent_root, blob_slot, parent_block.state_root) + .map_err(|e| GossipBlobError::BeaconChainError(e.into()))? + .ok_or_else(|| { + BeaconChainError::DBInconsistent(format!( + "Missing state for parent block {block_parent_root:?}", + )) + })?; + + let state = cheap_state_advance_to_obtain_committees::<_, GossipBlobError>( + &mut parent_state, + Some(parent_state_root), + blob_slot, + &chain.spec, + )?; + + let proposers = state.get_beacon_proposer_indices(&chain.spec)?; + let proposer_index = *proposers + .get(blob_slot.as_usize() % T::EthSpec::slots_per_epoch() as usize) + .ok_or_else(|| BeaconChainError::NoProposerForSlot(blob_slot))?; + + // Prime the proposer shuffling cache with the newly-learned value. + chain.beacon_proposer_cache.lock().insert( + blob_epoch, + proposer_shuffling_root, + proposers, + state.fork(), + )?; + (proposer_index, state.fork()) }; // Signature verify the signed block header. let signature_is_valid = { let pubkey_cache = get_validator_pubkey_cache(chain).map_err(|_| GossipBlobError::PubkeyCacheTimeout)?; + let pubkey = pubkey_cache .get(proposer_index) .ok_or_else(|| GossipBlobError::UnknownValidator(proposer_index as u64))?; diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 38648949f9c..866dde5a763 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -48,6 +48,7 @@ // returned alongside. #![allow(clippy::result_large_err)] +use crate::beacon_snapshot::PreProcessingSnapshot; use crate::blob_verification::{GossipBlobError, GossipVerifiedBlob}; use crate::block_verification_types::{ AsBlock, BlockContentsError, BlockImportData, GossipVerifiedBlockContents, RpcBlock, @@ -59,14 +60,10 @@ use crate::execution_payload::{ AllowOptimisticImport, NotifyExecutionLayer, PayloadNotifier, }; use crate::observed_block_producers::SeenBlock; -use crate::snapshot_cache::PreProcessingSnapshot; use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS; use crate::validator_pubkey_cache::ValidatorPubkeyCache; use crate::{ - beacon_chain::{ - BeaconForkChoice, ForkChoiceError, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT, - VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT, - }, + beacon_chain::{BeaconForkChoice, ForkChoiceError, VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT}, metrics, BeaconChain, BeaconChainError, BeaconChainTypes, }; use derivative::Derivative; @@ -86,22 +83,21 @@ use state_processing::{ block_signature_verifier::{BlockSignatureVerifier, Error as BlockSignatureVerifierError}, per_block_processing, per_slot_processing, state_advance::partial_state_advance, - BlockProcessingError, BlockSignatureStrategy, ConsensusContext, SlotProcessingError, - StateProcessingStrategy, VerifyBlockRoot, + AllCaches, BlockProcessingError, BlockSignatureStrategy, ConsensusContext, SlotProcessingError, + VerifyBlockRoot, }; use std::borrow::Cow; use std::fmt::Debug; use std::fs; use std::io::Write; use std::sync::Arc; -use std::time::Duration; use store::{Error as DBError, HotStateSummary, KeyValueStore, StoreOp}; use task_executor::JoinHandle; use tree_hash::TreeHash; use types::{ - BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, CloneConfig, Epoch, EthSpec, - ExecutionBlockHash, Hash256, InconsistentFork, PublicKey, PublicKeyBytes, RelativeEpoch, - SignedBeaconBlock, SignedBeaconBlockHeader, Slot, + BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, ExecutionBlockHash, + Hash256, InconsistentFork, PublicKey, PublicKeyBytes, RelativeEpoch, SignedBeaconBlock, + SignedBeaconBlockHeader, Slot, }; use types::{BlobSidecar, ExecPayload}; @@ -578,7 +574,7 @@ pub fn signature_verify_chain_segment( } let (first_root, first_block) = chain_segment.remove(0); - let (mut parent, first_block) = load_parent(first_root, first_block, chain)?; + let (mut parent, first_block) = load_parent(first_block, chain)?; let slot = first_block.slot(); chain_segment.insert(0, (first_root, first_block)); @@ -893,7 +889,7 @@ impl GossipVerifiedBlock { } else { // The proposer index was *not* cached and we must load the parent in order to determine // the proposer index. - let (mut parent, block) = load_parent(block_root, block, chain)?; + let (mut parent, block) = load_parent(block, chain)?; debug!( chain.log, @@ -1042,7 +1038,7 @@ impl SignatureVerifiedBlock { // Check the anchor slot before loading the parent, to avoid spurious lookups. check_block_against_anchor_slot(block.message(), chain)?; - let (mut parent, block) = load_parent(block_root, block, chain)?; + let (mut parent, block) = load_parent(block, chain)?; let state = cheap_state_advance_to_obtain_committees::<_, BlockError>( &mut parent.pre_state, @@ -1092,7 +1088,7 @@ impl SignatureVerifiedBlock { let (mut parent, block) = if let Some(parent) = from.parent { (parent, from.block) } else { - load_parent(from.block_root, from.block, chain)? + load_parent(from.block, chain)? }; let state = cheap_state_advance_to_obtain_committees::<_, BlockError>( @@ -1154,7 +1150,7 @@ impl IntoExecutionPendingBlock for SignatureVerifiedBloc let (parent, block) = if let Some(parent) = self.parent { (parent, self.block) } else { - load_parent(self.block_root, self.block, chain) + load_parent(self.block, chain) .map_err(|e| BlockSlashInfo::SignatureValid(header.clone(), e))? }; @@ -1386,8 +1382,18 @@ impl ExecutionPendingBlock { let catchup_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_CATCHUP_STATE); // Stage a batch of operations to be completed atomically if this block is imported - // successfully. - let mut confirmed_state_roots = vec![]; + // successfully. We include the state root of the pre-state, which may be an advanced state + // that was stored in the DB with a `temporary` flag. + let mut state = parent.pre_state; + + let mut confirmed_state_roots = if state.slot() > parent.beacon_block.slot() { + // Advanced pre-state. Delete its temporary flag. + let pre_state_root = state.update_tree_hash_cache()?; + vec![pre_state_root] + } else { + // Pre state is parent state. It is already stored in the DB without temporary status. + vec![] + }; // The block must have a higher slot than its parent. if block.slot() <= parent.beacon_block.slot() { @@ -1397,14 +1403,6 @@ impl ExecutionPendingBlock { }); } - let mut summaries = vec![]; - - // Transition the parent state to the block slot. - // - // It is important to note that we're using a "pre-state" here, one that has potentially - // been advanced one slot forward from `parent.beacon_block.slot`. - let mut state = parent.pre_state; - // Perform a sanity check on the pre-state. let parent_slot = parent.beacon_block.slot(); if state.slot() < parent_slot || state.slot() > block.slot() { @@ -1423,6 +1421,12 @@ impl ExecutionPendingBlock { eth1_deposit_index: state.eth1_deposit_index(), }; + // Transition the parent state to the block slot. + // + // It is important to note that we're using a "pre-state" here, one that has potentially + // been advanced one slot forward from `parent.beacon_block.slot`. + let mut summaries = vec![]; + let distance = block.slot().as_u64().saturating_sub(state.slot().as_u64()); for _ in 0..distance { let state_root = if parent.beacon_block.slot() == state.slot() { @@ -1522,8 +1526,7 @@ impl ExecutionPendingBlock { let committee_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_COMMITTEE); - state.build_committee_cache(RelativeEpoch::Previous, &chain.spec)?; - state.build_committee_cache(RelativeEpoch::Current, &chain.spec)?; + state.build_all_committee_caches(&chain.spec)?; metrics::stop_timer(committee_timer); @@ -1564,7 +1567,6 @@ impl ExecutionPendingBlock { block.as_block(), // Signatures were verified earlier in this function. BlockSignatureStrategy::NoVerification, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut consensus_context, &chain.spec, @@ -1839,12 +1841,9 @@ fn verify_parent_block_is_known( /// whilst attempting the operation. #[allow(clippy::type_complexity)] fn load_parent>( - block_root: Hash256, block: B, chain: &BeaconChain, ) -> Result<(PreProcessingSnapshot, B), BlockError> { - let spec = &chain.spec; - // Reject any block if its parent is not known to fork choice. // // A block that is not in fork choice is either: @@ -1863,45 +1862,10 @@ fn load_parent>( return Err(BlockError::ParentUnknown(block.into_rpc_block())); } - let block_delay = chain - .block_times_cache - .read() - .get_block_delays( - block_root, - chain - .slot_clock - .start_of(block.slot()) - .unwrap_or_else(|| Duration::from_secs(0)), - ) - .observed; - let db_read_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DB_READ); - let result = if let Some((snapshot, cloned)) = chain - .snapshot_cache - .try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .and_then(|mut snapshot_cache| { - snapshot_cache.get_state_for_block_processing( - block.parent_root(), - block.slot(), - block_delay, - spec, - ) - }) { - if cloned { - metrics::inc_counter(&metrics::BLOCK_PROCESSING_SNAPSHOT_CACHE_CLONES); - debug!( - chain.log, - "Cloned snapshot for late block/skipped slot"; - "slot" => %block.slot(), - "parent_slot" => %snapshot.beacon_block.slot(), - "parent_root" => ?block.parent_root(), - "block_delay" => ?block_delay, - ); - } - Ok((snapshot, block)) - } else { - // Load the blocks parent block from the database, returning invalid if that block is not + let result = { + // Load the block's parent block from the database, returning invalid if that block is not // found. // // We don't return a DBInconsistent error here since it's possible for a block to @@ -1925,7 +1889,7 @@ fn load_parent>( // Retrieve any state that is advanced through to at most `block.slot()`: this is // particularly important if `block` descends from the finalized/split block, but at a slot // prior to the finalized slot (which is invalid and inaccessible in our DB schema). - let (parent_state_root, parent_state) = chain + let (parent_state_root, state) = chain .store .get_advanced_hot_state(root, block.slot(), parent_block.state_root())? .ok_or_else(|| { @@ -1934,22 +1898,46 @@ fn load_parent>( ) })?; - metrics::inc_counter(&metrics::BLOCK_PROCESSING_SNAPSHOT_CACHE_MISSES); - debug!( - chain.log, - "Missed snapshot cache"; - "slot" => block.slot(), - "parent_slot" => parent_block.slot(), - "parent_root" => ?block.parent_root(), - "block_delay" => ?block_delay, - ); + if !state.all_caches_built() { + debug!( + chain.log, + "Parent state lacks built caches"; + "block_slot" => block.slot(), + "state_slot" => state.slot(), + ); + } + + if block.slot() != state.slot() { + debug!( + chain.log, + "Parent state is not advanced"; + "block_slot" => block.slot(), + "state_slot" => state.slot(), + ); + } + + let beacon_state_root = if state.slot() == parent_block.slot() { + // Sanity check. + if parent_state_root != parent_block.state_root() { + return Err(BeaconChainError::DBInconsistent(format!( + "Parent state at slot {} has the wrong state root: {:?} != {:?}", + state.slot(), + parent_state_root, + parent_block.state_root() + )) + .into()); + } + Some(parent_block.state_root()) + } else { + None + }; Ok(( PreProcessingSnapshot { beacon_block: parent_block, beacon_block_root: root, - pre_state: parent_state, - beacon_state_root: Some(parent_state_root), + pre_state: state, + beacon_state_root, }, block, )) @@ -2030,7 +2018,7 @@ pub fn cheap_state_advance_to_obtain_committees<'a, E: EthSpec, Err: BlockBlobEr } else if state.slot() > block_slot { Err(Err::not_later_than_parent_error(block_slot, state.slot())) } else { - let mut state = state.clone_with(CloneConfig::committee_caches_only()); + let mut state = state.clone(); let target_slot = block_epoch.start_slot(E::slots_per_epoch()); // Advance the state into the same epoch as the block. Use the "partial" method since state diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 56381a7806f..be6b1f9b2bf 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -12,7 +12,6 @@ use crate::light_client_server_cache::LightClientServerCache; use crate::migrate::{BackgroundMigrator, MigratorConfig}; use crate::persisted_beacon_chain::PersistedBeaconChain; use crate::shuffling_cache::{BlockShufflingIds, ShufflingCache}; -use crate::snapshot_cache::SnapshotCache; use crate::timeout_rw_lock::TimeoutRwLock; use crate::validator_monitor::{ValidatorMonitor, ValidatorMonitorConfig}; use crate::validator_pubkey_cache::ValidatorPubkeyCache; @@ -32,7 +31,7 @@ use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold}; use slasher::Slasher; use slog::{crit, debug, error, info, o, Logger}; use slot_clock::{SlotClock, TestingSlotClock}; -use state_processing::per_slot_processing; +use state_processing::{per_slot_processing, AllCaches}; use std::marker::PhantomData; use std::sync::Arc; use std::time::Duration; @@ -462,7 +461,7 @@ where // Prime all caches before storing the state in the database and computing the tree hash // root. weak_subj_state - .build_caches(&self.spec) + .build_all_caches(&self.spec) .map_err(|e| format!("Error building caches on checkpoint state: {e:?}"))?; let weak_subj_state_root = weak_subj_state .update_tree_hash_cache() @@ -537,6 +536,13 @@ where // Write the state, block and blobs non-atomically, it doesn't matter if they're forgotten // about on a crash restart. + store + .update_finalized_state( + weak_subj_state_root, + weak_subj_block_root, + weak_subj_state.clone(), + ) + .map_err(|e| format!("Failed to set checkpoint state as finalized state: {:?}", e))?; store .put_state(&weak_subj_state_root, &weak_subj_state) .map_err(|e| format!("Failed to store weak subjectivity state: {e:?}"))?; @@ -851,10 +857,8 @@ where let genesis_validators_root = head_snapshot.beacon_state.genesis_validators_root(); let genesis_time = head_snapshot.beacon_state.genesis_time(); - let head_for_snapshot_cache = head_snapshot.clone(); let canonical_head = CanonicalHead::new(fork_choice, Arc::new(head_snapshot)); let shuffling_cache_size = self.chain_config.shuffling_cache_size; - let snapshot_cache_size = self.chain_config.snapshot_cache_size; // Calculate the weak subjectivity point in which to backfill blocks to. let genesis_backfill_slot = if self.chain_config.genesis_backfill { @@ -930,10 +934,6 @@ where fork_choice_signal_rx, event_handler: self.event_handler, head_tracker, - snapshot_cache: TimeoutRwLock::new(SnapshotCache::new( - snapshot_cache_size, - head_for_snapshot_cache, - )), shuffling_cache: TimeoutRwLock::new(ShufflingCache::new( shuffling_cache_size, head_shuffling_ids, @@ -962,7 +962,6 @@ where .map_err(|e| format!("Error initializing DataAvailabiltyChecker: {:?}", e))?, ), kzg: self.kzg.clone(), - block_production_state: Arc::new(Mutex::new(None)), }; let head = beacon_chain.head_snapshot(); diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 734575d2c0d..a84cfab298d 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -35,10 +35,7 @@ use crate::beacon_chain::ATTESTATION_CACHE_LOCK_TIMEOUT; use crate::persisted_fork_choice::PersistedForkChoice; use crate::shuffling_cache::BlockShufflingIds; use crate::{ - beacon_chain::{ - BeaconForkChoice, BeaconStore, OverrideForkchoiceUpdate, - BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT, FORK_CHOICE_DB_KEY, - }, + beacon_chain::{BeaconForkChoice, BeaconStore, OverrideForkchoiceUpdate, FORK_CHOICE_DB_KEY}, block_times_cache::BlockTimesCache, events::ServerSentEventHandler, metrics, @@ -54,6 +51,7 @@ use itertools::process_results; use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; use slog::{crit, debug, error, warn, Logger}; use slot_clock::SlotClock; +use state_processing::AllCaches; use std::sync::Arc; use std::time::Duration; use store::{iter::StateRootsIterator, KeyValueStoreOp, StoreItem}; @@ -466,9 +464,7 @@ impl BeaconChain { pub fn head_beacon_state_cloned(&self) -> BeaconState { // Don't clone whilst holding the read-lock, take an Arc-clone to reduce lock contention. let snapshot: Arc<_> = self.head_snapshot(); - snapshot - .beacon_state - .clone_with(CloneConfig::committee_caches_only()) + snapshot.beacon_state.clone() } /// Execute the fork choice algorithm and enthrone the result as the canonical head. @@ -652,48 +648,31 @@ impl BeaconChain { let new_cached_head = if new_view.head_block_root != old_view.head_block_root { metrics::inc_counter(&metrics::FORK_CHOICE_CHANGED_HEAD); - // Try and obtain the snapshot for `beacon_block_root` from the snapshot cache, falling - // back to a database read if that fails. - let new_snapshot = self - .snapshot_cache - .try_read_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .and_then(|snapshot_cache| { - snapshot_cache.get_cloned( + let mut new_snapshot = { + let beacon_block = self + .store + .get_full_block(&new_view.head_block_root)? + .ok_or(Error::MissingBeaconBlock(new_view.head_block_root))?; + + let (_, beacon_state) = self + .store + .get_advanced_hot_state( new_view.head_block_root, - CloneConfig::committee_caches_only(), - ) - }) - .map::, _>(Ok) - .unwrap_or_else(|| { - let beacon_block = self - .store - .get_full_block(&new_view.head_block_root)? - .ok_or(Error::MissingBeaconBlock(new_view.head_block_root))?; - - let (_, beacon_state) = self - .store - .get_advanced_hot_state( - new_view.head_block_root, - current_slot, - beacon_block.state_root(), - )? - .ok_or(Error::MissingBeaconState(beacon_block.state_root()))?; - - Ok(BeaconSnapshot { - beacon_block: Arc::new(beacon_block), - beacon_block_root: new_view.head_block_root, - beacon_state, - }) - }) - .and_then(|mut snapshot| { - // Regardless of where we got the state from, attempt to build the committee - // caches. - snapshot - .beacon_state - .build_all_committee_caches(&self.spec) - .map_err(Into::into) - .map(|()| snapshot) - })?; + current_slot, + beacon_block.state_root(), + )? + .ok_or(Error::MissingBeaconState(beacon_block.state_root()))?; + + BeaconSnapshot { + beacon_block: Arc::new(beacon_block), + beacon_block_root: new_view.head_block_root, + beacon_state, + } + }; + + // Regardless of where we got the state from, attempt to build all the + // caches except the tree hash cache. + new_snapshot.beacon_state.build_all_caches(&self.spec)?; let new_cached_head = CachedHead { snapshot: Arc::new(new_snapshot), @@ -834,25 +813,6 @@ impl BeaconChain { .beacon_state .attester_shuffling_decision_root(self.genesis_block_root, RelativeEpoch::Current); - // Update the snapshot cache with the latest head value. - // - // This *could* be done inside `recompute_head`, however updating the head on the snapshot - // cache is not critical so we avoid placing it on a critical path. Note that this function - // will not return an error if the update fails, it will just log an error. - self.snapshot_cache - .try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .map(|mut snapshot_cache| { - snapshot_cache.update_head(new_snapshot.beacon_block_root); - }) - .unwrap_or_else(|| { - error!( - self.log, - "Failed to obtain cache write lock"; - "lock" => "snapshot_cache", - "task" => "update head" - ); - }); - match BlockShufflingIds::try_from_head( new_snapshot.beacon_block_root, &new_snapshot.beacon_state, @@ -998,26 +958,6 @@ impl BeaconChain { .start_slot(T::EthSpec::slots_per_epoch()), ); - self.snapshot_cache - .try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .map(|mut snapshot_cache| { - snapshot_cache.prune(new_view.finalized_checkpoint.epoch); - debug!( - self.log, - "Snapshot cache pruned"; - "new_len" => snapshot_cache.len(), - "remaining_roots" => ?snapshot_cache.beacon_block_roots(), - ); - }) - .unwrap_or_else(|| { - error!( - self.log, - "Failed to obtain cache write lock"; - "lock" => "snapshot_cache", - "task" => "prune" - ); - }); - self.attester_cache .prune_below(new_view.finalized_checkpoint.epoch); diff --git a/beacon_node/beacon_chain/src/chain_config.rs b/beacon_node/beacon_chain/src/chain_config.rs index 0772aff6710..255b8f00497 100644 --- a/beacon_node/beacon_chain/src/chain_config.rs +++ b/beacon_node/beacon_chain/src/chain_config.rs @@ -75,8 +75,6 @@ pub struct ChainConfig { pub optimistic_finalized_sync: bool, /// The size of the shuffling cache, pub shuffling_cache_size: usize, - /// The size of the snapshot cache. - pub snapshot_cache_size: usize, /// If using a weak-subjectivity sync, whether we should download blocks all the way back to /// genesis. pub genesis_backfill: bool, @@ -116,7 +114,6 @@ impl Default for ChainConfig { // This value isn't actually read except in tests. optimistic_finalized_sync: true, shuffling_cache_size: crate::shuffling_cache::DEFAULT_CACHE_SIZE, - snapshot_cache_size: crate::snapshot_cache::DEFAULT_SNAPSHOT_CACHE_SIZE, genesis_backfill: false, always_prepare_payload: false, epochs_per_migration: crate::migrate::DEFAULT_EPOCHS_PER_MIGRATION, diff --git a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs index f4c1bc308c0..3a8bccbad5c 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs @@ -1059,7 +1059,7 @@ mod test { let chain = &harness.chain; let log = chain.log.clone(); let head = chain.head_snapshot(); - let parent_state = head.beacon_state.clone_with_only_committee_caches(); + let parent_state = head.beacon_state.clone(); let target_slot = chain.slot().expect("should get slot") + 1; let parent_root = head.beacon_block_root; diff --git a/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs index b6dbf2b952f..f8a243bd9e8 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs @@ -8,7 +8,7 @@ use crate::{ use lru::LruCache; use parking_lot::RwLock; use ssz_derive::{Decode, Encode}; -use state_processing::{BlockReplayer, StateProcessingStrategy}; +use state_processing::BlockReplayer; use std::sync::Arc; use store::OnDiskConsensusContext; use types::beacon_block_body::KzgCommitments; @@ -189,7 +189,6 @@ impl StateLRUCache { let block_replayer: BlockReplayer<'_, T::EthSpec, AvailabilityCheckError, _> = BlockReplayer::new(parent_state, &self.spec) .no_signature_verification() - .state_processing_strategy(StateProcessingStrategy::Accurate) .state_root_iter(state_roots.into_iter()) .minimal_block_root_verification(); diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 9c82e964cc0..340f1f9f797 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -31,6 +31,7 @@ use state_processing::{ use std::time::Duration; use task_executor::ShutdownReason; use tokio::task::JoinError; +use types::milhouse::Error as MilhouseError; use types::*; macro_rules! easy_from_to { @@ -224,6 +225,7 @@ pub enum BeaconChainError { AvailabilityCheckError(AvailabilityCheckError), LightClientError(LightClientError), UnsupportedFork, + MilhouseError(MilhouseError), } easy_from_to!(SlotProcessingError, BeaconChainError); @@ -253,6 +255,7 @@ easy_from_to!(InconsistentFork, BeaconChainError); easy_from_to!(AvailabilityCheckError, BeaconChainError); easy_from_to!(EpochCacheError, BeaconChainError); easy_from_to!(LightClientError, BeaconChainError); +easy_from_to!(MilhouseError, BeaconChainError); #[derive(Debug)] pub enum BlockProductionError { @@ -279,6 +282,7 @@ pub enum BlockProductionError { TerminalPoWBlockLookupFailed(execution_layer::Error), GetPayloadFailed(execution_layer::Error), FailedToReadFinalizedBlock(store::Error), + FailedToLoadState(store::Error), MissingFinalizedBlock(Hash256), BlockTooLarge(usize), ShuttingDown, diff --git a/beacon_node/beacon_chain/src/eth1_chain.rs b/beacon_node/beacon_chain/src/eth1_chain.rs index 3ec39f9d192..31297244e3e 100644 --- a/beacon_node/beacon_chain/src/eth1_chain.rs +++ b/beacon_node/beacon_chain/src/eth1_chain.rs @@ -1020,6 +1020,7 @@ mod test { mod collect_valid_votes { use super::*; + use types::List; fn get_eth1_data_vec(n: u64, block_number_offset: u64) -> Vec<(Eth1Data, BlockNumber)> { (0..n) @@ -1067,12 +1068,14 @@ mod test { let votes_to_consider = get_eth1_data_vec(slots, 0); - *state.eth1_data_votes_mut() = votes_to_consider[0..slots as usize / 4] - .iter() - .map(|(eth1_data, _)| eth1_data) - .cloned() - .collect::>() - .into(); + *state.eth1_data_votes_mut() = List::new( + votes_to_consider[0..slots as usize / 4] + .iter() + .map(|(eth1_data, _)| eth1_data) + .cloned() + .collect::>(), + ) + .unwrap(); let votes = collect_valid_votes(&state, &votes_to_consider.clone().into_iter().collect()); @@ -1096,12 +1099,14 @@ mod test { .expect("should have some eth1 data") .clone(); - *state.eth1_data_votes_mut() = vec![duplicate_eth1_data.clone(); 4] - .iter() - .map(|(eth1_data, _)| eth1_data) - .cloned() - .collect::>() - .into(); + *state.eth1_data_votes_mut() = List::new( + vec![duplicate_eth1_data.clone(); 4] + .iter() + .map(|(eth1_data, _)| eth1_data) + .cloned() + .collect::>(), + ) + .unwrap(); let votes = collect_valid_votes(&state, &votes_to_consider.into_iter().collect()); assert_votes!( diff --git a/beacon_node/beacon_chain/src/fork_revert.rs b/beacon_node/beacon_chain/src/fork_revert.rs index 084ae95e096..8d1c29f46f6 100644 --- a/beacon_node/beacon_chain/src/fork_revert.rs +++ b/beacon_node/beacon_chain/src/fork_revert.rs @@ -5,7 +5,7 @@ use slog::{info, warn, Logger}; use state_processing::state_advance::complete_state_advance; use state_processing::{ per_block_processing, per_block_processing::BlockSignatureStrategy, ConsensusContext, - StateProcessingStrategy, VerifyBlockRoot, + VerifyBlockRoot, }; use std::sync::Arc; use std::time::Duration; @@ -175,7 +175,6 @@ pub fn reset_fork_choice_to_finalization, Cold: It &mut state, &block, BlockSignatureStrategy::NoVerification, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, spec, diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 71c96d0fd55..c1df9ede87a 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -51,7 +51,6 @@ mod pre_finalization_cache; pub mod proposer_prep_service; pub mod schema_change; pub mod shuffling_cache; -pub mod snapshot_cache; pub mod state_advance_timer; pub mod sync_committee_rewards; pub mod sync_committee_verification; diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index df718413cc0..fc3f032cdc3 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -4,12 +4,8 @@ use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; use lazy_static::lazy_static; pub use lighthouse_metrics::*; use slot_clock::SlotClock; -use std::time::Duration; use types::{BeaconState, Epoch, EthSpec, Hash256, Slot}; -/// The maximum time to wait for the snapshot cache lock during a metrics scrape. -const SNAPSHOT_CACHE_TIMEOUT: Duration = Duration::from_millis(100); - // Attestation simulator metrics pub const VALIDATOR_MONITOR_ATTESTATION_SIMULATOR_HEAD_ATTESTER_HIT_TOTAL: &str = "validator_monitor_attestation_simulator_head_attester_hit_total"; @@ -1204,15 +1200,10 @@ pub fn scrape_for_metrics(beacon_chain: &BeaconChain) { let attestation_stats = beacon_chain.op_pool.attestation_stats(); - if let Some(snapshot_cache) = beacon_chain - .snapshot_cache - .try_write_for(SNAPSHOT_CACHE_TIMEOUT) - { - set_gauge( - &BLOCK_PROCESSING_SNAPSHOT_CACHE_SIZE, - snapshot_cache.len() as i64, - ) - } + set_gauge_by_usize( + &BLOCK_PROCESSING_SNAPSHOT_CACHE_SIZE, + beacon_chain.store.state_cache_len(), + ); set_gauge_by_usize( &BEACON_REQRESP_PRE_IMPORT_CACHE_SIZE, diff --git a/beacon_node/beacon_chain/src/shuffling_cache.rs b/beacon_node/beacon_chain/src/shuffling_cache.rs index b3de6f91c92..04d58882639 100644 --- a/beacon_node/beacon_chain/src/shuffling_cache.rs +++ b/beacon_node/beacon_chain/src/shuffling_cache.rs @@ -339,7 +339,7 @@ mod test { .clone(); let committee_b = state.committee_cache(RelativeEpoch::Next).unwrap().clone(); assert!(committee_a != committee_b); - (Arc::new(committee_a), Arc::new(committee_b)) + (committee_a, committee_b) } /// Builds a deterministic but incoherent shuffling ID from a `u64`. diff --git a/beacon_node/beacon_chain/src/state_advance_timer.rs b/beacon_node/beacon_chain/src/state_advance_timer.rs index 39d35f81113..1f928a16e42 100644 --- a/beacon_node/beacon_chain/src/state_advance_timer.rs +++ b/beacon_node/beacon_chain/src/state_advance_timer.rs @@ -15,9 +15,7 @@ //! 2. There's a possibility that the head block is never built upon, causing wasted CPU cycles. use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS; use crate::{ - beacon_chain::{ATTESTATION_CACHE_LOCK_TIMEOUT, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT}, - chain_config::FORK_CHOICE_LOOKAHEAD_FACTOR, - snapshot_cache::StateAdvance, + beacon_chain::ATTESTATION_CACHE_LOCK_TIMEOUT, chain_config::FORK_CHOICE_LOOKAHEAD_FACTOR, BeaconChain, BeaconChainError, BeaconChainTypes, }; use slog::{debug, error, warn, Logger}; @@ -27,9 +25,10 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; +use store::KeyValueStore; use task_executor::TaskExecutor; use tokio::time::{sleep, sleep_until, Instant}; -use types::{AttestationShufflingId, EthSpec, Hash256, RelativeEpoch, Slot}; +use types::{AttestationShufflingId, BeaconStateError, EthSpec, Hash256, RelativeEpoch, Slot}; /// If the head slot is more than `MAX_ADVANCE_DISTANCE` from the current slot, then don't perform /// the state advancement. @@ -45,14 +44,13 @@ const MAX_ADVANCE_DISTANCE: u64 = 4; /// impact whilst having 8 epochs without a block is a comfortable grace period. const MAX_FORK_CHOICE_DISTANCE: u64 = 256; -/// Drop any unused block production state cache after this many slots. -const MAX_BLOCK_PRODUCTION_CACHE_DISTANCE: u64 = 4; - #[derive(Debug)] enum Error { BeaconChain(BeaconChainError), // We don't use the inner value directly, but it's used in the Debug impl. HeadMissingFromSnapshotCache(#[allow(dead_code)] Hash256), + BeaconState(#[allow(dead_code)] BeaconStateError), + Store(#[allow(dead_code)] store::Error), MaxDistanceExceeded { current_slot: Slot, head_slot: Slot, @@ -72,6 +70,18 @@ impl From for Error { } } +impl From for Error { + fn from(e: BeaconStateError) -> Self { + Self::BeaconState(e) + } +} + +impl From for Error { + fn from(e: store::Error) -> Self { + Self::Store(e) + } +} + /// Provides a simple thread-safe lock to be used for task co-ordination. Practically equivalent to /// `Mutex<()>`. #[derive(Clone)] @@ -231,7 +241,7 @@ async fn state_advance_timer( // Prepare proposers so that the node can send payload attributes in the case where // it decides to abandon a proposer boost re-org. - let proposer_head = beacon_chain + beacon_chain .prepare_beacon_proposer(current_slot) .await .unwrap_or_else(|e| { @@ -248,56 +258,6 @@ async fn state_advance_timer( // in `ForkChoiceSignalTx`. beacon_chain.task_executor.clone().spawn_blocking( move || { - // If we're proposing, clone the head state preemptively so that it isn't on - // the hot path of proposing. We can delete this once we have tree-states. - if let Some(proposer_head) = proposer_head { - let mut cache = beacon_chain.block_production_state.lock(); - - // Avoid holding two states in memory. It's OK to hold the lock because - // we always lock the block production cache before the snapshot cache - // and we prefer for block production to wait for the block production - // cache if a clone is in-progress. - if cache - .as_ref() - .map_or(false, |(cached_head, _)| *cached_head != proposer_head) - { - drop(cache.take()); - } - if let Some(proposer_state) = beacon_chain - .snapshot_cache - .try_read_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .and_then(|snapshot_cache| { - snapshot_cache.get_state_for_block_production(proposer_head) - }) - { - *cache = Some((proposer_head, proposer_state)); - debug!( - log, - "Cloned state ready for block production"; - "head_block_root" => ?proposer_head, - "slot" => next_slot - ); - } else { - warn!( - log, - "Block production state missing from snapshot cache"; - "head_block_root" => ?proposer_head, - "slot" => next_slot - ); - } - } else { - // If we aren't proposing, drop any old block production cache to save - // memory. - let mut cache = beacon_chain.block_production_state.lock(); - if let Some((_, state)) = &*cache { - if state.pre_state.slot() + MAX_BLOCK_PRODUCTION_CACHE_DISTANCE - <= current_slot - { - drop(cache.take()); - } - } - } - // Signal block proposal for the next slot (if it happens to be waiting). if let Some(tx) = &beacon_chain.fork_choice_signal_tx { if let Err(e) = tx.notify_fork_choice_complete(next_slot) { @@ -318,9 +278,9 @@ async fn state_advance_timer( } } -/// Reads the `snapshot_cache` from the `beacon_chain` and attempts to take a clone of the +/// Reads the `state_cache` from the `beacon_chain` and attempts to take a clone of the /// `BeaconState` of the head block. If it obtains this clone, the state will be advanced a single -/// slot then placed back in the `snapshot_cache` to be used for block verification. +/// slot then placed in the `state_cache` to be used for block verification. /// /// See the module-level documentation for rationale. fn advance_head( @@ -345,46 +305,42 @@ fn advance_head( } } - let head_root = beacon_chain.head_beacon_block_root(); + let (head_block_root, head_block_state_root) = { + let snapshot = beacon_chain.head_snapshot(); + (snapshot.beacon_block_root, snapshot.beacon_state_root()) + }; - let (head_slot, head_state_root, mut state) = match beacon_chain - .snapshot_cache - .try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .ok_or(BeaconChainError::SnapshotCacheLockTimeout)? - .get_for_state_advance(head_root) - { - StateAdvance::AlreadyAdvanced => { + let (head_state_root, mut state) = beacon_chain + .store + .get_advanced_hot_state(head_block_root, current_slot, head_block_state_root)? + .ok_or(Error::HeadMissingFromSnapshotCache(head_block_root))?; + + // Protect against advancing a state more than a single slot. + // + // Advancing more than one slot without storing the intermediate state would corrupt the + // database. Future works might store temporary, intermediate states inside this function. + match state.slot().cmp(&state.latest_block_header().slot) { + std::cmp::Ordering::Equal => (), + std::cmp::Ordering::Greater => { return Err(Error::StateAlreadyAdvanced { - block_root: head_root, - }) + block_root: head_block_root, + }); } - StateAdvance::BlockNotFound => return Err(Error::HeadMissingFromSnapshotCache(head_root)), - StateAdvance::State { - state, - state_root, - block_slot, - } => (block_slot, state_root, *state), - }; + std::cmp::Ordering::Less => { + return Err(Error::BadStateSlot { + _block_slot: state.latest_block_header().slot, + _state_slot: state.slot(), + }); + } + } let initial_slot = state.slot(); let initial_epoch = state.current_epoch(); - let state_root = if state.slot() == head_slot { - Some(head_state_root) - } else { - // Protect against advancing a state more than a single slot. - // - // Advancing more than one slot without storing the intermediate state would corrupt the - // database. Future works might store temporary, intermediate states inside this function. - return Err(Error::BadStateSlot { - _block_slot: head_slot, - _state_slot: state.slot(), - }); - }; - // Advance the state a single slot. - if let Some(summary) = per_slot_processing(&mut state, state_root, &beacon_chain.spec) - .map_err(BeaconChainError::from)? + if let Some(summary) = + per_slot_processing(&mut state, Some(head_state_root), &beacon_chain.spec) + .map_err(BeaconChainError::from)? { // Expose Prometheus metrics. if let Err(e) = summary.observe_metrics() { @@ -418,7 +374,7 @@ fn advance_head( debug!( log, "Advanced head state one slot"; - "head_root" => ?head_root, + "head_block_root" => ?head_block_root, "state_slot" => state.slot(), "current_slot" => current_slot, ); @@ -437,14 +393,14 @@ fn advance_head( if initial_epoch < state.current_epoch() { // Update the proposer cache. // - // We supply the `head_root` as the decision block since the prior `if` statement guarantees + // We supply the `head_block_root` as the decision block since the prior `if` statement guarantees // the head root is the latest block from the prior epoch. beacon_chain .beacon_proposer_cache .lock() .insert( state.current_epoch(), - head_root, + head_block_root, state .get_beacon_proposer_indices(&beacon_chain.spec) .map_err(BeaconChainError::from)?, @@ -453,8 +409,9 @@ fn advance_head( .map_err(BeaconChainError::from)?; // Update the attester cache. - let shuffling_id = AttestationShufflingId::new(head_root, &state, RelativeEpoch::Next) - .map_err(BeaconChainError::from)?; + let shuffling_id = + AttestationShufflingId::new(head_block_root, &state, RelativeEpoch::Next) + .map_err(BeaconChainError::from)?; let committee_cache = state .committee_cache(RelativeEpoch::Next) .map_err(BeaconChainError::from)?; @@ -467,7 +424,7 @@ fn advance_head( debug!( log, "Primed proposer and attester caches"; - "head_root" => ?head_root, + "head_block_root" => ?head_block_root, "next_epoch_shuffling_root" => ?shuffling_id.shuffling_decision_block, "state_epoch" => state.current_epoch(), "current_epoch" => current_slot.epoch(T::EthSpec::slots_per_epoch()), @@ -477,22 +434,13 @@ fn advance_head( // Apply the state to the attester cache, if the cache deems it interesting. beacon_chain .attester_cache - .maybe_cache_state(&state, head_root, &beacon_chain.spec) + .maybe_cache_state(&state, head_block_root, &beacon_chain.spec) .map_err(BeaconChainError::from)?; let final_slot = state.slot(); - // Insert the advanced state back into the snapshot cache. - beacon_chain - .snapshot_cache - .try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT) - .ok_or(BeaconChainError::SnapshotCacheLockTimeout)? - .update_pre_state(head_root, state) - .ok_or(Error::HeadMissingFromSnapshotCache(head_root))?; - // If we have moved into the next slot whilst processing the state then this function is going - // to become ineffective and likely become a hindrance as we're stealing the tree hash cache - // from the snapshot cache (which may force the next block to rebuild a new one). + // to become ineffective. // // If this warning occurs very frequently on well-resourced machines then we should consider // starting it earlier in the slot. Otherwise, it's a good indication that the machine is too @@ -503,7 +451,7 @@ fn advance_head( warn!( log, "State advance too slow"; - "head_root" => %head_root, + "head_block_root" => %head_block_root, "advanced_slot" => final_slot, "current_slot" => current_slot, "starting_slot" => starting_slot, @@ -511,10 +459,25 @@ fn advance_head( ); } + // Write the advanced state to the database with a temporary flag that will be deleted when + // a block is imported on top of this state. We should delete this once we bring in the DB + // changes from tree-states that allow us to prune states without temporary flags. + let advanced_state_root = state.update_tree_hash_cache()?; + let txn_lock = beacon_chain.store.hot_db.begin_rw_transaction(); + let state_already_exists = beacon_chain + .store + .load_hot_state_summary(&advanced_state_root)? + .is_some(); + let temporary = !state_already_exists; + beacon_chain + .store + .put_state_possibly_temporary(&advanced_state_root, &state, temporary)?; + drop(txn_lock); + debug!( log, "Completed state advance"; - "head_root" => ?head_root, + "head_block_root" => ?head_block_root, "advanced_slot" => final_slot, "initial_slot" => initial_slot, ); diff --git a/beacon_node/beacon_chain/src/sync_committee_rewards.rs b/beacon_node/beacon_chain/src/sync_committee_rewards.rs index 2221aa1d5eb..9b35cff9432 100644 --- a/beacon_node/beacon_chain/src/sync_committee_rewards.rs +++ b/beacon_node/beacon_chain/src/sync_committee_rewards.rs @@ -38,9 +38,26 @@ impl BeaconChain { })?; let mut balances = HashMap::::new(); + for &validator_index in &sync_committee_indices { + balances.insert( + validator_index, + *state + .balances() + .get(validator_index) + .ok_or(BeaconChainError::SyncCommitteeRewardsSyncError)?, + ); + } + + let proposer_index = block.proposer_index() as usize; + balances.insert( + proposer_index, + *state + .balances() + .get(proposer_index) + .ok_or(BeaconChainError::SyncCommitteeRewardsSyncError)?, + ); let mut total_proposer_rewards = 0; - let proposer_index = state.get_beacon_proposer_index(block.slot(), spec)?; // Apply rewards to participant balances. Keep track of proposer rewards for (validator_index, participant_bit) in sync_committee_indices @@ -48,15 +65,15 @@ impl BeaconChain { .zip(sync_aggregate.sync_committee_bits.iter()) { let participant_balance = balances - .entry(*validator_index) - .or_insert_with(|| state.balances()[*validator_index]); + .get_mut(validator_index) + .ok_or(BeaconChainError::SyncCommitteeRewardsSyncError)?; if participant_bit { participant_balance.safe_add_assign(participant_reward_value)?; balances - .entry(proposer_index) - .or_insert_with(|| state.balances()[proposer_index]) + .get_mut(&proposer_index) + .ok_or(BeaconChainError::SyncCommitteeRewardsSyncError)? .safe_add_assign(proposer_reward_per_bit)?; total_proposer_rewards.safe_add_assign(proposer_reward_per_bit)?; @@ -67,18 +84,17 @@ impl BeaconChain { Ok(balances .iter() - .filter_map(|(i, new_balance)| { - let reward = if *i != proposer_index { - *new_balance as i64 - state.balances()[*i] as i64 - } else if sync_committee_indices.contains(i) { - *new_balance as i64 - - state.balances()[*i] as i64 - - total_proposer_rewards as i64 + .filter_map(|(&i, &new_balance)| { + let initial_balance = *state.balances().get(i)? as i64; + let reward = if i != proposer_index { + new_balance as i64 - initial_balance + } else if sync_committee_indices.contains(&i) { + new_balance as i64 - initial_balance - total_proposer_rewards as i64 } else { return None; }; Some(SyncCommitteeReward { - validator_index: *i as u64, + validator_index: i as u64, reward, }) }) diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index debc4881a60..d3eeff8a7d9 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -46,10 +46,7 @@ use slog_async::Async; use slog_term::{FullFormat, TermDecorator}; use slot_clock::{SlotClock, TestingSlotClock}; use state_processing::per_block_processing::compute_timestamp_at_slot; -use state_processing::{ - state_advance::{complete_state_advance, partial_state_advance}, - StateProcessingStrategy, -}; +use state_processing::state_advance::complete_state_advance; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::fmt; @@ -756,10 +753,7 @@ where pub fn get_current_state_and_root(&self) -> (BeaconState, Hash256) { let head = self.chain.head_snapshot(); let state_root = head.beacon_state_root(); - ( - head.beacon_state.clone_with_only_committee_caches(), - state_root, - ) + (head.beacon_state.clone(), state_root) } pub fn head_slot(&self) -> Slot { @@ -802,8 +796,9 @@ where pub fn get_hot_state(&self, state_hash: BeaconStateHash) -> Option> { self.chain .store - .load_hot_state(&state_hash.into(), StateProcessingStrategy::Accurate) + .load_hot_state(&state_hash.into()) .unwrap() + .map(|(state, _)| state) } pub fn get_cold_state(&self, state_hash: BeaconStateHash) -> Option> { @@ -1017,9 +1012,7 @@ where return Err(BeaconChainError::CannotAttestToFutureState); } else if state.current_epoch() < epoch { let mut_state = state.to_mut(); - // Only perform a "partial" state advance since we do not require the state roots to be - // accurate. - partial_state_advance( + complete_state_advance( mut_state, Some(state_root), epoch.start_slot(E::slots_per_epoch()), diff --git a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs index 2cf0c326158..e1b50706286 100644 --- a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs +++ b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs @@ -81,8 +81,9 @@ impl ValidatorPubkeyCache { ) -> Result>, BeaconChainError> { if state.validators().len() > self.pubkeys.len() { self.import( - state.validators()[self.pubkeys.len()..] - .iter() + state + .validators() + .iter_from(self.pubkeys.len())? .map(|v| v.pubkey), ) } else { diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 4d37557f0d1..98a112daffe 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -15,8 +15,7 @@ use slasher::{Config as SlasherConfig, Slasher}; use state_processing::{ common::get_indexed_attestation, per_block_processing::{per_block_processing, BlockSignatureStrategy}, - per_slot_processing, BlockProcessingError, ConsensusContext, StateProcessingStrategy, - VerifyBlockRoot, + per_slot_processing, BlockProcessingError, ConsensusContext, VerifyBlockRoot, }; use std::marker::PhantomData; use std::sync::Arc; @@ -1309,7 +1308,6 @@ async fn add_base_block_to_altair_chain() { &mut state, &base_block, BlockSignatureStrategy::NoVerification, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &harness.chain.spec, @@ -1445,7 +1443,6 @@ async fn add_altair_block_to_base_chain() { &mut state, &altair_block, BlockSignatureStrategy::NoVerification, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &harness.chain.spec, diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index f1262596f70..0ef348319af 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -223,7 +223,7 @@ impl InvalidPayloadRig { let mock_execution_layer = self.harness.mock_execution_layer.as_ref().unwrap(); let head = self.harness.chain.head_snapshot(); - let state = head.beacon_state.clone_with_only_committee_caches(); + let state = head.beacon_state.clone(); let slot = slot_override.unwrap_or(state.slot() + 1); let ((block, blobs), post_state) = self.harness.make_block(state, slot).await; let block_root = block.canonical_root(); @@ -2048,7 +2048,7 @@ async fn weights_after_resetting_optimistic_status() { .fork_choice_read_lock() .get_block_weight(&head.head_block_root()) .unwrap(), - head.snapshot.beacon_state.validators()[0].effective_balance, + head.snapshot.beacon_state.validators().get(0).unwrap().effective_balance, "proposer boost should be removed from the head block and the vote of a single validator applied" ); diff --git a/beacon_node/beacon_chain/tests/rewards.rs b/beacon_node/beacon_chain/tests/rewards.rs index a78463ef5d7..1c80525223a 100644 --- a/beacon_node/beacon_chain/tests/rewards.rs +++ b/beacon_node/beacon_chain/tests/rewards.rs @@ -105,8 +105,8 @@ async fn test_sync_committee_rewards() { .get_validator_index(&validator.pubkey) .unwrap() .unwrap(); - let pre_state_balance = parent_state.balances()[validator_index]; - let post_state_balance = state.balances()[validator_index]; + let pre_state_balance = *parent_state.balances().get(validator_index).unwrap(); + let post_state_balance = *state.balances().get(validator_index).unwrap(); let sync_committee_reward = rewards.get(&(validator_index as u64)).unwrap_or(&0); if validator_index == proposer_index { @@ -141,7 +141,7 @@ async fn test_verify_attestation_rewards_base() { ) .await; - let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + let initial_balances: Vec = harness.get_current_state().balances().to_vec(); // extend slots to beginning of epoch N + 2 harness.extend_slots(E::slots_per_epoch() as usize).await; @@ -163,7 +163,7 @@ async fn test_verify_attestation_rewards_base() { let expected_balances = apply_attestation_rewards(&initial_balances, total_rewards); // verify expected balances against actual balances - let balances: Vec = harness.get_current_state().balances().clone().into(); + let balances: Vec = harness.get_current_state().balances().to_vec(); assert_eq!(expected_balances, balances); } @@ -185,7 +185,7 @@ async fn test_verify_attestation_rewards_base_inactivity_leak() { AttestationStrategy::SomeValidators(half_validators.clone()), ) .await; - let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + let initial_balances: Vec = harness.get_current_state().balances().to_vec(); // extend slots to beginning of epoch N + 2 harness.advance_slot(); @@ -215,7 +215,7 @@ async fn test_verify_attestation_rewards_base_inactivity_leak() { let expected_balances = apply_attestation_rewards(&initial_balances, total_rewards); // verify expected balances against actual balances - let balances: Vec = harness.get_current_state().balances().clone().into(); + let balances: Vec = harness.get_current_state().balances().to_vec(); assert_eq!(expected_balances, balances); } @@ -241,7 +241,7 @@ async fn test_verify_attestation_rewards_base_inactivity_leak_justification_epoc // advance to create first justification epoch and get initial balances harness.extend_slots(E::slots_per_epoch() as usize).await; target_epoch += 1; - let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + let initial_balances: Vec = harness.get_current_state().balances().to_vec(); //assert previous_justified_checkpoint matches 0 as we were in inactivity leak from beginning assert_eq!( @@ -284,7 +284,7 @@ async fn test_verify_attestation_rewards_base_inactivity_leak_justification_epoc let expected_balances = apply_attestation_rewards(&initial_balances, total_rewards); // verify expected balances against actual balances - let balances: Vec = harness.get_current_state().balances().clone().into(); + let balances: Vec = harness.get_current_state().balances().to_vec(); assert_eq!(expected_balances, balances); } @@ -298,7 +298,7 @@ async fn test_verify_attestation_rewards_altair() { harness .extend_slots((E::slots_per_epoch() * (target_epoch + 1)) as usize) .await; - let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + let initial_balances: Vec = harness.get_current_state().balances().to_vec(); // advance until epoch N + 2 and build proposal rewards map let mut proposal_rewards_map: HashMap = HashMap::new(); @@ -364,7 +364,7 @@ async fn test_verify_attestation_rewards_altair() { apply_sync_committee_rewards(&sync_committee_rewards_map, expected_balances); // verify expected balances against actual balances - let balances: Vec = harness.get_current_state().balances().clone().into(); + let balances: Vec = harness.get_current_state().balances().to_vec(); assert_eq!(expected_balances, balances); } @@ -386,7 +386,7 @@ async fn test_verify_attestation_rewards_altair_inactivity_leak() { half_validators.clone(), ) .await; - let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + let initial_balances: Vec = harness.get_current_state().balances().to_vec(); // advance until epoch N + 2 and build proposal rewards map let mut proposal_rewards_map: HashMap = HashMap::new(); @@ -458,7 +458,7 @@ async fn test_verify_attestation_rewards_altair_inactivity_leak() { apply_sync_committee_rewards(&sync_committee_rewards_map, expected_balances); // verify expected balances against actual balances - let balances: Vec = harness.get_current_state().balances().clone().into(); + let balances: Vec = harness.get_current_state().balances().to_vec(); assert_eq!(expected_balances, balances); } @@ -492,7 +492,7 @@ async fn test_verify_attestation_rewards_altair_inactivity_leak_justification_ep // advance for first justification epoch and get balances harness.extend_slots(E::slots_per_epoch() as usize).await; target_epoch += 1; - let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + let initial_balances: Vec = harness.get_current_state().balances().to_vec(); // advance until epoch N + 2 and build proposal rewards map let mut proposal_rewards_map: HashMap = HashMap::new(); @@ -568,7 +568,7 @@ async fn test_verify_attestation_rewards_altair_inactivity_leak_justification_ep apply_sync_committee_rewards(&sync_committee_rewards_map, expected_balances); // verify expected balances against actual balances - let balances: Vec = harness.get_current_state().balances().clone().into(); + let balances: Vec = harness.get_current_state().balances().to_vec(); assert_eq!(expected_balances, balances); } diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 51d62cf8a19..ba8a6bf7016 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -719,52 +719,6 @@ async fn forwards_iter_block_and_state_roots_until() { test_range(Slot::new(0), head_state.slot()); } -#[tokio::test] -async fn block_replay_with_inaccurate_state_roots() { - let num_blocks_produced = E::slots_per_epoch() * 3 + 31; - let db_path = tempdir().unwrap(); - let store = get_store(&db_path); - let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); - let chain = &harness.chain; - - harness - .extend_chain( - num_blocks_produced as usize, - BlockStrategy::OnCanonicalHead, - AttestationStrategy::AllValidators, - ) - .await; - - // Slot must not be 0 mod 32 or else no blocks will be replayed. - let (mut head_state, head_state_root) = harness.get_current_state_and_root(); - let head_block_root = harness.head_block_root(); - assert_ne!(head_state.slot() % 32, 0); - - let (_, mut fast_head_state) = store - .get_inconsistent_state_for_attestation_verification_only( - &head_block_root, - head_state.slot(), - head_state_root, - ) - .unwrap() - .unwrap(); - assert_eq!(head_state.validators(), fast_head_state.validators()); - - head_state.build_all_committee_caches(&chain.spec).unwrap(); - fast_head_state - .build_all_committee_caches(&chain.spec) - .unwrap(); - - assert_eq!( - head_state - .get_cached_active_validator_indices(RelativeEpoch::Current) - .unwrap(), - fast_head_state - .get_cached_active_validator_indices(RelativeEpoch::Current) - .unwrap() - ); -} - #[tokio::test] async fn block_replayer_hooks() { let db_path = tempdir().unwrap(); @@ -795,7 +749,7 @@ async fn block_replayer_hooks() { let mut post_block_slots = vec![]; let mut replay_state = BlockReplayer::::new(state, &chain.spec) - .pre_slot_hook(Box::new(|state| { + .pre_slot_hook(Box::new(|_, state| { pre_slots.push(state.slot()); Ok(()) })) @@ -834,6 +788,8 @@ async fn block_replayer_hooks() { assert_eq!(post_block_slots, block_slots); // States match. + end_state.apply_pending_mutations().unwrap(); + replay_state.apply_pending_mutations().unwrap(); end_state.drop_all_caches().unwrap(); replay_state.drop_all_caches().unwrap(); assert_eq!(end_state, replay_state); @@ -1219,9 +1175,17 @@ fn check_shuffling_compatible( |committee_cache, _| { let state_cache = head_state.committee_cache(RelativeEpoch::Current).unwrap(); if current_epoch_shuffling_is_compatible { - assert_eq!(committee_cache, state_cache, "block at slot {slot}"); + assert_eq!( + committee_cache, + state_cache.as_ref(), + "block at slot {slot}" + ); } else { - assert_ne!(committee_cache, state_cache, "block at slot {slot}"); + assert_ne!( + committee_cache, + state_cache.as_ref(), + "block at slot {slot}" + ); } Ok(()) }, @@ -1251,9 +1215,9 @@ fn check_shuffling_compatible( |committee_cache, _| { let state_cache = head_state.committee_cache(RelativeEpoch::Previous).unwrap(); if previous_epoch_shuffling_is_compatible { - assert_eq!(committee_cache, state_cache); + assert_eq!(committee_cache, state_cache.as_ref()); } else { - assert_ne!(committee_cache, state_cache); + assert_ne!(committee_cache, state_cache.as_ref()); } Ok(()) }, @@ -3605,16 +3569,16 @@ fn check_split_slot(harness: &TestHarness, store: Arc, L /// Check that all the states in a chain dump have the correct tree hash. fn check_chain_dump(harness: &TestHarness, expected_len: u64) { - let chain_dump = harness.chain.chain_dump().unwrap(); + let mut chain_dump = harness.chain.chain_dump().unwrap(); let split_slot = harness.chain.store.get_split_slot(); assert_eq!(chain_dump.len() as u64, expected_len); - for checkpoint in &chain_dump { + for checkpoint in &mut chain_dump { // Check that the tree hash of the stored state is as expected assert_eq!( checkpoint.beacon_state_root(), - checkpoint.beacon_state.tree_hash_root(), + checkpoint.beacon_state.update_tree_hash_cache().unwrap(), "tree hash of stored state is incorrect" ); diff --git a/beacon_node/genesis/src/interop.rs b/beacon_node/genesis/src/interop.rs index b4753e92f1f..4c78b8efd8f 100644 --- a/beacon_node/genesis/src/interop.rs +++ b/beacon_node/genesis/src/interop.rs @@ -178,13 +178,14 @@ mod test { } for v in state.validators() { - let creds = v.withdrawal_credentials.as_bytes(); + let creds = v.withdrawal_credentials; assert_eq!( - creds[0], spec.bls_withdrawal_prefix_byte, + creds.as_bytes()[0], + spec.bls_withdrawal_prefix_byte, "first byte of withdrawal creds should be bls prefix" ); assert_eq!( - &creds[1..], + &creds.as_bytes()[1..], &hash(&v.pubkey.as_ssz_bytes())[1..], "rest of withdrawal creds should be pubkey hash" ) @@ -240,7 +241,8 @@ mod test { } for (index, v) in state.validators().iter().enumerate() { - let creds = v.withdrawal_credentials.as_bytes(); + let withdrawal_credientials = v.withdrawal_credentials; + let creds = withdrawal_credientials.as_bytes(); if index % 2 == 0 { assert_eq!( creds[0], spec.bls_withdrawal_prefix_byte, diff --git a/beacon_node/http_api/src/attester_duties.rs b/beacon_node/http_api/src/attester_duties.rs index f3242a2b374..6c7dc3348c1 100644 --- a/beacon_node/http_api/src/attester_duties.rs +++ b/beacon_node/http_api/src/attester_duties.rs @@ -5,9 +5,7 @@ use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes}; use eth2::types::{self as api_types}; use slot_clock::SlotClock; use state_processing::state_advance::partial_state_advance; -use types::{ - AttestationDuty, BeaconState, ChainSpec, CloneConfig, Epoch, EthSpec, Hash256, RelativeEpoch, -}; +use types::{AttestationDuty, BeaconState, ChainSpec, Epoch, EthSpec, Hash256, RelativeEpoch}; /// The struct that is returned to the requesting HTTP client. type ApiDuties = api_types::DutiesResponse>; @@ -90,8 +88,7 @@ fn compute_historic_attester_duties( if head.beacon_state.current_epoch() <= request_epoch { Some(( head.beacon_state_root(), - head.beacon_state - .clone_with(CloneConfig::committee_caches_only()), + head.beacon_state.clone(), execution_status.is_optimistic_or_invalid(), )) } else { diff --git a/beacon_node/http_api/src/block_packing_efficiency.rs b/beacon_node/http_api/src/block_packing_efficiency.rs index d78f1f7c66e..f105fdf0a7d 100644 --- a/beacon_node/http_api/src/block_packing_efficiency.rs +++ b/beacon_node/http_api/src/block_packing_efficiency.rs @@ -279,7 +279,7 @@ pub fn get_block_packing_efficiency( )); let pre_slot_hook = - |state: &mut BeaconState| -> Result<(), PackingEfficiencyError> { + |_, state: &mut BeaconState| -> Result<(), PackingEfficiencyError> { // Add attestations to `available_attestations`. handler.lock().add_attestations(state.slot())?; Ok(()) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index cc117c3fb92..46d3ad7569b 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -61,7 +61,6 @@ use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use ssz::Encode; pub use state_id::StateId; -use std::borrow::Cow; use std::future::Future; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; @@ -864,10 +863,10 @@ pub fn serve( None }; - let committee_cache = if let Some(ref shuffling) = + let committee_cache = if let Some(shuffling) = maybe_cached_shuffling { - Cow::Borrowed(&**shuffling) + shuffling } else { let possibly_built_cache = match RelativeEpoch::from_epoch(current_epoch, epoch) { @@ -876,16 +875,13 @@ pub fn serve( relative_epoch, ) => { - state - .committee_cache(relative_epoch) - .map(Cow::Borrowed) + state.committee_cache(relative_epoch).cloned() } _ => CommitteeCache::initialized( state, epoch, &chain.spec, - ) - .map(Cow::Owned), + ), } .map_err(|e| { match e { @@ -933,7 +929,7 @@ pub fn serve( { cache_write.insert_committee_cache( shuffling_id, - &*possibly_built_cache, + &possibly_built_cache, ); } } diff --git a/beacon_node/http_api/src/proposer_duties.rs b/beacon_node/http_api/src/proposer_duties.rs index c31dd9b1faa..ab8952976c8 100644 --- a/beacon_node/http_api/src/proposer_duties.rs +++ b/beacon_node/http_api/src/proposer_duties.rs @@ -10,7 +10,7 @@ use safe_arith::SafeArith; use slog::{debug, Logger}; use slot_clock::SlotClock; use std::cmp::Ordering; -use types::{CloneConfig, Epoch, EthSpec, Hash256, Slot}; +use types::{Epoch, EthSpec, Hash256, Slot}; /// The struct that is returned to the requesting HTTP client. type ApiDuties = api_types::DutiesResponse>; @@ -192,8 +192,7 @@ fn compute_historic_proposer_duties( if head.beacon_state.current_epoch() <= epoch { Some(( head.beacon_state_root(), - head.beacon_state - .clone_with(CloneConfig::committee_caches_only()), + head.beacon_state.clone(), execution_status.is_optimistic_or_invalid(), )) } else { diff --git a/beacon_node/http_api/src/state_id.rs b/beacon_node/http_api/src/state_id.rs index 1a76333e2d4..fdc99fa954e 100644 --- a/beacon_node/http_api/src/state_id.rs +++ b/beacon_node/http_api/src/state_id.rs @@ -178,10 +178,7 @@ impl StateId { .head_and_execution_status() .map_err(warp_utils::reject::beacon_chain_error)?; return Ok(( - cached_head - .snapshot - .beacon_state - .clone_with_only_committee_caches(), + cached_head.snapshot.beacon_state.clone(), execution_status.is_optimistic_or_invalid(), false, )); diff --git a/beacon_node/http_api/tests/fork_tests.rs b/beacon_node/http_api/tests/fork_tests.rs index 74b26475639..ad32ff1d579 100644 --- a/beacon_node/http_api/tests/fork_tests.rs +++ b/beacon_node/http_api/tests/fork_tests.rs @@ -128,17 +128,18 @@ async fn attestations_across_fork_with_skip_slots() { let all_validators = harness.get_all_validators(); let fork_slot = fork_epoch.start_slot(E::slots_per_epoch()); - let fork_state = harness + let mut fork_state = harness .chain .state_at_slot(fork_slot, StateSkipConfig::WithStateRoots) .unwrap(); + let fork_state_root = fork_state.update_tree_hash_cache().unwrap(); harness.set_current_slot(fork_slot); let attestations = harness.make_attestations( &all_validators, &fork_state, - fork_state.canonical_root(), + fork_state_root, (*fork_state.get_block_root(fork_slot - 1).unwrap()).into(), fork_slot, ); diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index e4580e4ffdb..d44b9a688ce 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -806,7 +806,7 @@ impl ApiTester { let state_opt = state_id.state(&self.chain).ok(); let validators: Vec = match state_opt.as_ref() { Some((state, _execution_optimistic, _finalized)) => { - state.validators().clone().into() + state.validators().clone().to_vec() } None => vec![], }; @@ -822,7 +822,7 @@ impl ApiTester { ValidatorId::PublicKey( validators .get(i as usize) - .map_or(PublicKeyBytes::empty(), |val| val.pubkey.clone()), + .map_or(PublicKeyBytes::empty(), |val| val.pubkey), ) }) .collect::>(); @@ -865,7 +865,7 @@ impl ApiTester { if i < state.balances().len() as u64 { validators.push(ValidatorBalanceData { index: i as u64, - balance: state.balances()[i as usize], + balance: *state.balances().get(i as usize).unwrap(), }); } } @@ -892,7 +892,7 @@ impl ApiTester { .ok() .map(|(state, _execution_optimistic, _finalized)| state); let validators: Vec = match state_opt.as_ref() { - Some(state) => state.validators().clone().into(), + Some(state) => state.validators().to_vec(), None => vec![], }; let validator_index_ids = validator_indices @@ -907,7 +907,7 @@ impl ApiTester { ValidatorId::PublicKey( validators .get(i as usize) - .map_or(PublicKeyBytes::empty(), |val| val.pubkey.clone()), + .map_or(PublicKeyBytes::empty(), |val| val.pubkey), ) }) .collect::>(); @@ -955,7 +955,7 @@ impl ApiTester { if i >= state.validators().len() as u64 { continue; } - let validator = state.validators()[i as usize].clone(); + let validator = state.validators().get(i as usize).unwrap().clone(); let status = ValidatorStatus::from_validator( &validator, epoch, @@ -967,7 +967,7 @@ impl ApiTester { { validators.push(ValidatorData { index: i as u64, - balance: state.balances()[i as usize], + balance: *state.balances().get(i as usize).unwrap(), status, validator, }); @@ -995,13 +995,13 @@ impl ApiTester { .ok() .map(|(state, _execution_optimistic, _finalized)| state); let validators = match state_opt.as_ref() { - Some(state) => state.validators().clone().into(), + Some(state) => state.validators().to_vec(), None => vec![], }; for (i, validator) in validators.into_iter().enumerate() { let validator_ids = &[ - ValidatorId::PublicKey(validator.pubkey.clone()), + ValidatorId::PublicKey(validator.pubkey), ValidatorId::Index(i as u64), ]; @@ -1025,7 +1025,7 @@ impl ApiTester { ValidatorData { index: i as u64, - balance: state.balances()[i], + balance: *state.balances().get(i).unwrap(), status: ValidatorStatus::from_validator( &validator, epoch, @@ -2360,7 +2360,7 @@ impl ApiTester { .unwrap() { let expected = AttesterData { - pubkey: state.validators()[i as usize].pubkey.clone().into(), + pubkey: state.validators().get(i as usize).unwrap().pubkey, validator_index: i, committees_at_slot: duty.committees_at_slot, committee_index: duty.index, @@ -2465,7 +2465,7 @@ impl ApiTester { let index = state .get_beacon_proposer_index(slot, &self.chain.spec) .unwrap(); - let pubkey = state.validators()[index].pubkey.clone().into(); + let pubkey = state.validators().get(index).unwrap().pubkey; ProposerData { pubkey, diff --git a/beacon_node/operation_pool/src/lib.rs b/beacon_node/operation_pool/src/lib.rs index 7cd40a4bb60..6e744ccf62a 100644 --- a/beacon_node/operation_pool/src/lib.rs +++ b/beacon_node/operation_pool/src/lib.rs @@ -1272,7 +1272,7 @@ mod release_tests { // Each validator will have a multiple of 1_000_000_000 wei. // Safe from overflow unless there are about 18B validators (2^64 / 1_000_000_000). for i in 0..state.validators().len() { - state.validators_mut()[i].effective_balance = 1_000_000_000 * i as u64; + state.validators_mut().get_mut(i).unwrap().effective_balance = 1_000_000_000 * i as u64; } let num_validators = num_committees @@ -1530,9 +1530,9 @@ mod release_tests { let spec = &harness.spec; let mut state = harness.get_current_state(); let op_pool = OperationPool::::new(); - state.validators_mut()[1].effective_balance = 17_000_000_000; - state.validators_mut()[2].effective_balance = 17_000_000_000; - state.validators_mut()[3].effective_balance = 17_000_000_000; + state.validators_mut().get_mut(1).unwrap().effective_balance = 17_000_000_000; + state.validators_mut().get_mut(2).unwrap().effective_balance = 17_000_000_000; + state.validators_mut().get_mut(3).unwrap().effective_balance = 17_000_000_000; let slashing_1 = harness.make_attester_slashing(vec![1, 2, 3]); let slashing_2 = harness.make_attester_slashing(vec![4, 5, 6]); diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index fd2cf473cb3..d31b576989c 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -179,9 +179,6 @@ pub fn get_config( if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? { client_config.chain.shuffling_cache_size = cache_size; } - if let Some(cache_size) = clap_utils::parse_optional(cli_args, "state-cache-size")? { - client_config.chain.snapshot_cache_size = cache_size; - } /* * Prometheus metrics HTTP server @@ -408,6 +405,10 @@ pub fn get_config( .map_err(|_| "block-cache-size is not a valid integer".to_string())?; } + if let Some(cache_size) = clap_utils::parse_optional(cli_args, "state-cache-size")? { + client_config.store.state_cache_size = cache_size; + } + if let Some(historic_state_cache_size) = cli_args.value_of("historic-state-cache-size") { client_config.store.historic_state_cache_size = historic_state_cache_size .parse() diff --git a/beacon_node/store/Cargo.toml b/beacon_node/store/Cargo.toml index 7bf1ef76bef..b7822670078 100644 --- a/beacon_node/store/Cargo.toml +++ b/beacon_node/store/Cargo.toml @@ -25,3 +25,7 @@ lru = { workspace = true } sloggers = { workspace = true } directory = { workspace = true } strum = { workspace = true } +safe_arith = { workspace = true } +bls = { workspace = true } +smallvec = { workspace = true } +logging = { workspace = true } diff --git a/beacon_node/store/src/chunked_vector.rs b/beacon_node/store/src/chunked_vector.rs index a0c50e5a2b5..4450989d590 100644 --- a/beacon_node/store/src/chunked_vector.rs +++ b/beacon_node/store/src/chunked_vector.rs @@ -1,6 +1,6 @@ //! Space-efficient storage for `BeaconState` vector fields. //! -//! This module provides logic for splitting the `FixedVector` fields of a `BeaconState` into +//! This module provides logic for splitting the `Vector` fields of a `BeaconState` into //! chunks, and storing those chunks in contiguous ranges in the on-disk database. The motiviation //! for doing this is avoiding massive duplication in every on-disk state. For example, rather than //! storing the whole `historical_roots` vector, which is updated once every couple of thousand @@ -60,12 +60,13 @@ fn genesis_value_key() -> [u8; 8] { /// type-level. We require their value-level witnesses to be `Copy` so that we can avoid the /// turbofish when calling functions like `store_updated_vector`. pub trait Field: Copy { - /// The type of value stored in this field: the `T` from `FixedVector`. + /// The type of value stored in this field: the `T` from `Vector`. /// /// The `Default` impl will be used to fill extra vector entries. - type Value: Decode + Encode + Default + Clone + PartialEq + std::fmt::Debug; + type Value: Default + std::fmt::Debug + milhouse::Value; + // Decode + Encode + Default + Clone + PartialEq + std::fmt::Debug - /// The length of this field: the `N` from `FixedVector`. + /// The length of this field: the `N` from `Vector`. type Length: Unsigned; /// The database column where the integer-indexed chunks for this field should be stored. @@ -273,10 +274,10 @@ pub trait Field: Copy { } } -/// Marker trait for fixed-length fields (`FixedVector`). +/// Marker trait for fixed-length fields (`Vector`). pub trait FixedLengthField: Field {} -/// Marker trait for variable-length fields (`VariableList`). +/// Marker trait for variable-length fields (`List`). pub trait VariableLengthField: Field {} /// Macro to implement the `Field` trait on a new unit struct type. @@ -331,7 +332,7 @@ field!( activation_slot: Some(Slot::new(0)), deactivation_slot: None }, - |state: &BeaconState<_>, index, _| safe_modulo_index(state.block_roots(), index) + |state: &BeaconState<_>, index, _| safe_modulo_vector_index(state.block_roots(), index) ); field!( @@ -345,7 +346,7 @@ field!( activation_slot: Some(Slot::new(0)), deactivation_slot: None, }, - |state: &BeaconState<_>, index, _| safe_modulo_index(state.state_roots(), index) + |state: &BeaconState<_>, index, _| safe_modulo_vector_index(state.state_roots(), index) ); field!( @@ -361,7 +362,7 @@ field!( .capella_fork_epoch .map(|fork_epoch| fork_epoch.start_slot(E::slots_per_epoch())), }, - |state: &BeaconState<_>, index, _| safe_modulo_index(state.historical_roots(), index) + |state: &BeaconState<_>, index, _| safe_modulo_list_index(state.historical_roots(), index) ); field!( @@ -371,7 +372,7 @@ field!( E::EpochsPerHistoricalVector, DBColumn::BeaconRandaoMixes, |_| OncePerEpoch { lag: 1 }, - |state: &BeaconState<_>, index, _| safe_modulo_index(state.randao_mixes(), index) + |state: &BeaconState<_>, index, _| safe_modulo_vector_index(state.randao_mixes(), index) ); field!( @@ -387,7 +388,7 @@ field!( .map(|fork_epoch| fork_epoch.start_slot(E::slots_per_epoch())), deactivation_slot: None, }, - |state: &BeaconState<_>, index, _| safe_modulo_index( + |state: &BeaconState<_>, index, _| safe_modulo_list_index( state .historical_summaries() .map_err(|_| ChunkError::InvalidFork)?, @@ -565,7 +566,7 @@ pub fn load_vector_from_db, E: EthSpec, S: KeyValueStore< store: &S, slot: Slot, spec: &ChainSpec, -) -> Result, Error> { +) -> Result, Error> { // Do a range query let chunk_size = F::chunk_size(); let (start_vindex, end_vindex) = F::start_and_end_vindex(slot, spec); @@ -589,7 +590,7 @@ pub fn load_vector_from_db, E: EthSpec, S: KeyValueStore< default, )?; - Ok(result.into()) + Ok(Vector::new(result).map_err(ChunkError::Milhouse)?) } /// The historical roots are stored in vector chunks, despite not actually being a vector. @@ -597,7 +598,7 @@ pub fn load_variable_list_from_db, E: EthSpec, S: KeyV store: &S, slot: Slot, spec: &ChainSpec, -) -> Result, Error> { +) -> Result, Error> { let chunk_size = F::chunk_size(); let (start_vindex, end_vindex) = F::start_and_end_vindex(slot, spec); let start_cindex = start_vindex / chunk_size; @@ -617,15 +618,35 @@ pub fn load_variable_list_from_db, E: EthSpec, S: KeyV } } - Ok(result.into()) + Ok(List::new(result).map_err(ChunkError::Milhouse)?) } -/// Index into a field of the state, avoiding out of bounds and division by 0. -fn safe_modulo_index(values: &[T], index: u64) -> Result { +/// Index into a `List` field of the state, avoiding out of bounds and division by 0. +fn safe_modulo_list_index( + values: &List, + index: u64, +) -> Result { + if values.is_empty() { + Err(ChunkError::ZeroLengthList) + } else { + values + .get(index as usize % values.len()) + .copied() + .ok_or(ChunkError::IndexOutOfBounds { index }) + } +} + +fn safe_modulo_vector_index( + values: &Vector, + index: u64, +) -> Result { if values.is_empty() { Err(ChunkError::ZeroLengthVector) } else { - Ok(values[index as usize % values.len()]) + values + .get(index as usize % values.len()) + .copied() + .ok_or(ChunkError::IndexOutOfBounds { index }) } } @@ -712,6 +733,10 @@ where #[derive(Debug, PartialEq)] pub enum ChunkError { ZeroLengthVector, + ZeroLengthList, + IndexOutOfBounds { + index: u64, + }, InvalidSize { chunk_index: usize, expected: usize, @@ -744,6 +769,13 @@ pub enum ChunkError { length: usize, }, InvalidFork, + Milhouse(milhouse::Error), +} + +impl From for ChunkError { + fn from(e: milhouse::Error) -> ChunkError { + Self::Milhouse(e) + } } #[cfg(test)] diff --git a/beacon_node/store/src/config.rs b/beacon_node/store/src/config.rs index 681d424e282..d43999d8220 100644 --- a/beacon_node/store/src/config.rs +++ b/beacon_node/store/src/config.rs @@ -9,6 +9,7 @@ use types::{EthSpec, MinimalEthSpec}; pub const PREV_DEFAULT_SLOTS_PER_RESTORE_POINT: u64 = 2048; pub const DEFAULT_SLOTS_PER_RESTORE_POINT: u64 = 8192; pub const DEFAULT_BLOCK_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(5); +pub const DEFAULT_STATE_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(128); pub const DEFAULT_HISTORIC_STATE_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(1); pub const DEFAULT_EPOCHS_PER_BLOB_PRUNE: u64 = 1; pub const DEFAULT_BLOB_PUNE_MARGIN_EPOCHS: u64 = 0; @@ -22,6 +23,8 @@ pub struct StoreConfig { pub slots_per_restore_point_set_explicitly: bool, /// Maximum number of blocks to store in the in-memory block cache. pub block_cache_size: NonZeroUsize, + /// Maximum number of states to store in the in-memory state cache. + pub state_cache_size: NonZeroUsize, /// Maximum number of states from freezer database to store in the in-memory state cache. pub historic_state_cache_size: NonZeroUsize, /// Whether to compact the database on initialization. @@ -57,6 +60,7 @@ impl Default for StoreConfig { slots_per_restore_point: MinimalEthSpec::slots_per_historical_root() as u64, slots_per_restore_point_set_explicitly: false, block_cache_size: DEFAULT_BLOCK_CACHE_SIZE, + state_cache_size: DEFAULT_STATE_CACHE_SIZE, historic_state_cache_size: DEFAULT_HISTORIC_STATE_CACHE_SIZE, compact_on_init: false, compact_on_prune: true, diff --git a/beacon_node/store/src/errors.rs b/beacon_node/store/src/errors.rs index 96e02b80ff8..91e6a920ba3 100644 --- a/beacon_node/store/src/errors.rs +++ b/beacon_node/store/src/errors.rs @@ -3,7 +3,7 @@ use crate::config::StoreConfigError; use crate::hot_cold_store::HotColdDBError; use ssz::DecodeError; use state_processing::BlockReplayError; -use types::{BeaconStateError, Hash256, InconsistentFork, Slot}; +use types::{BeaconStateError, EpochCacheError, Hash256, InconsistentFork, Slot}; pub type Result = std::result::Result; @@ -49,6 +49,14 @@ pub enum Error { InvalidBytes, UnableToDowngrade, InconsistentFork(InconsistentFork), + CacheBuildError(EpochCacheError), + RandaoMixOutOfBounds, + FinalizedStateDecreasingSlot, + FinalizedStateUnaligned, + StateForCacheHasPendingUpdates { + state_root: Hash256, + slot: Slot, + }, } pub trait HandleUnavailable { @@ -113,6 +121,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: EpochCacheError) -> Error { + Error::CacheBuildError(e) + } +} + #[derive(Debug)] pub struct DBError { pub message: String, diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 70e02164e08..484a1139bf9 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -17,6 +17,7 @@ use crate::metadata::{ PRUNING_CHECKPOINT_KEY, SCHEMA_VERSION_KEY, SPLIT_KEY, STATE_UPPER_LIMIT_NO_RETAIN, }; use crate::metrics; +use crate::state_cache::{PutStateOutcome, StateCache}; use crate::{ get_key_for_col, ChunkWriter, DBColumn, DatabaseBlock, Error, ItemStore, KeyValueStoreOp, PartialBeaconState, StoreItem, StoreOp, @@ -30,7 +31,8 @@ use slog::{debug, error, info, trace, warn, Logger}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use state_processing::{ - BlockProcessingError, BlockReplayer, SlotProcessingError, StateProcessingStrategy, + block_replayer::PreSlotHook, AllCaches, BlockProcessingError, BlockReplayer, + SlotProcessingError, }; use std::cmp::min; use std::marker::PhantomData; @@ -66,12 +68,16 @@ pub struct HotColdDB, Cold: ItemStore> { pub hot_db: Hot, /// LRU cache of deserialized blocks and blobs. Updated whenever a block or blob is loaded. block_cache: Mutex>, + /// Cache of beacon states. + /// + /// LOCK ORDERING: this lock must always be locked *after* the `split` if both are required. + state_cache: Mutex>, /// LRU cache of replayed states. - state_cache: Mutex>>, + historic_state_cache: Mutex>>, /// Chain spec. pub(crate) spec: ChainSpec, /// Logger. - pub(crate) log: Logger, + pub log: Logger, /// Mere vessel for E. _phantom: PhantomData, } @@ -178,7 +184,8 @@ impl HotColdDB, MemoryStore> { blobs_db: MemoryStore::open(), hot_db: MemoryStore::open(), block_cache: Mutex::new(BlockCache::new(config.block_cache_size)), - state_cache: Mutex::new(LruCache::new(config.historic_state_cache_size)), + state_cache: Mutex::new(StateCache::new(config.state_cache_size)), + historic_state_cache: Mutex::new(LruCache::new(config.historic_state_cache_size)), config, spec, log, @@ -192,8 +199,6 @@ impl HotColdDB, MemoryStore> { impl HotColdDB, LevelDB> { /// Open a new or existing database, with the given paths to the hot and cold DBs. /// - /// The `slots_per_restore_point` parameter must be a divisor of `SLOTS_PER_HISTORICAL_ROOT`. - /// /// The `migrate_schema` function is passed in so that the parent `BeaconChain` can provide /// context and access `BeaconChain`-level code without creating a circular dependency. pub fn open( @@ -215,7 +220,8 @@ impl HotColdDB, LevelDB> { blobs_db: LevelDB::open(blobs_db_path)?, hot_db: LevelDB::open(hot_path)?, block_cache: Mutex::new(BlockCache::new(config.block_cache_size)), - state_cache: Mutex::new(LruCache::new(config.historic_state_cache_size)), + state_cache: Mutex::new(StateCache::new(config.state_cache_size)), + historic_state_cache: Mutex::new(LruCache::new(config.historic_state_cache_size)), config, spec, log, @@ -352,6 +358,21 @@ impl HotColdDB, LevelDB> { } impl, Cold: ItemStore> HotColdDB { + pub fn update_finalized_state( + &self, + state_root: Hash256, + block_root: Hash256, + state: BeaconState, + ) -> Result<(), Error> { + self.state_cache + .lock() + .update_finalized_state(state_root, block_root, state) + } + + pub fn state_cache_len(&self) -> usize { + self.state_cache.lock().len() + } + /// Store a block and update the LRU cache. pub fn put_block( &self, @@ -615,11 +636,26 @@ impl, Cold: ItemStore> HotColdDB /// Store a state in the store. pub fn put_state(&self, state_root: &Hash256, state: &BeaconState) -> Result<(), Error> { + self.put_state_possibly_temporary(state_root, state, false) + } + + /// Store a state in the store. + /// + /// The `temporary` flag indicates whether this state should be considered canonical. + pub fn put_state_possibly_temporary( + &self, + state_root: &Hash256, + state: &BeaconState, + temporary: bool, + ) -> Result<(), Error> { let mut ops: Vec = Vec::new(); if state.slot() < self.get_split_slot() { self.store_cold_state(state_root, state, &mut ops)?; self.cold_db.do_atomically(ops) } else { + if temporary { + ops.push(TemporaryFlag.as_kv_store_op(*state_root)); + } self.store_hot_state(state_root, state, &mut ops)?; self.hot_db.do_atomically(ops) } @@ -648,45 +684,16 @@ impl, Cold: ItemStore> HotColdDB // chain. This way we avoid returning a state that doesn't match `state_root`. self.load_cold_state(state_root) } else { - self.load_hot_state(state_root, StateProcessingStrategy::Accurate) + self.get_hot_state(state_root) } } else { - match self.load_hot_state(state_root, StateProcessingStrategy::Accurate)? { + match self.get_hot_state(state_root)? { Some(state) => Ok(Some(state)), None => self.load_cold_state(state_root), } } } - /// Fetch a state from the store, but don't compute all of the values when replaying blocks - /// upon that state (e.g., state roots). Additionally, only states from the hot store are - /// returned. - /// - /// See `Self::get_advanced_hot_state` for information about `max_slot`. - /// - /// ## Warning - /// - /// The returned state **is not a valid beacon state**, it can only be used for obtaining - /// shuffling to process attestations. At least the following components of the state will be - /// broken/invalid: - /// - /// - `state.state_roots` - /// - `state.block_roots` - pub fn get_inconsistent_state_for_attestation_verification_only( - &self, - block_root: &Hash256, - max_slot: Slot, - state_root: Hash256, - ) -> Result)>, Error> { - metrics::inc_counter(&metrics::BEACON_STATE_GET_COUNT); - self.get_advanced_hot_state_with_strategy( - *block_root, - max_slot, - state_root, - StateProcessingStrategy::Inconsistent, - ) - } - /// Get a state with `latest_block_root == block_root` advanced through to at most `max_slot`. /// /// The `state_root` argument is used to look up the block's un-advanced state in case an @@ -697,35 +704,29 @@ impl, Cold: ItemStore> HotColdDB /// - `result_state_root == state.canonical_root()` /// - `state.slot() <= max_slot` /// - `state.get_latest_block_root(result_state_root) == block_root` - /// - /// Presently this is only used to avoid loading the un-advanced split state, but in future will - /// be expanded to return states from an in-memory cache. pub fn get_advanced_hot_state( &self, block_root: Hash256, max_slot: Slot, state_root: Hash256, ) -> Result)>, Error> { - self.get_advanced_hot_state_with_strategy( - block_root, - max_slot, - state_root, - StateProcessingStrategy::Accurate, - ) - } + if let Some(cached) = self.get_advanced_hot_state_from_cache(block_root, max_slot) { + return Ok(Some(cached)); + } - /// Same as `get_advanced_hot_state` but taking a `StateProcessingStrategy`. - pub fn get_advanced_hot_state_with_strategy( - &self, - block_root: Hash256, - max_slot: Slot, - state_root: Hash256, - state_processing_strategy: StateProcessingStrategy, - ) -> Result)>, Error> { // Hold a read lock on the split point so it can't move while we're trying to load the // state. let split = self.split.read_recursive(); + if state_root != split.state_root { + warn!( + self.log, + "State cache missed"; + "state_root" => ?state_root, + "block_root" => ?block_root, + ); + } + // Sanity check max-slot against the split slot. if max_slot < split.slot { return Err(HotColdDBError::FinalizedStateNotInHotDatabase { @@ -741,11 +742,40 @@ impl, Cold: ItemStore> HotColdDB } else { state_root }; - let state = self - .load_hot_state(&state_root, state_processing_strategy)? - .map(|state| (state_root, state)); + let mut opt_state = self + .load_hot_state(&state_root)? + .map(|(state, _block_root)| (state_root, state)); + + if let Some((state_root, state)) = opt_state.as_mut() { + state.update_tree_hash_cache()?; + state.build_all_caches(&self.spec)?; + self.state_cache + .lock() + .put_state(*state_root, block_root, state)?; + debug!( + self.log, + "Cached state"; + "state_root" => ?state_root, + "slot" => state.slot(), + ); + } drop(split); - Ok(state) + Ok(opt_state) + } + + /// Same as `get_advanced_hot_state` but will return `None` if no compatible state is cached. + /// + /// If this function returns `Some(state)` then that `state` will always have + /// `latest_block_header` matching `block_root` but may not be advanced all the way through to + /// `max_slot`. + pub fn get_advanced_hot_state_from_cache( + &self, + block_root: Hash256, + max_slot: Slot, + ) -> Option<(Hash256, BeaconState)> { + self.state_cache + .lock() + .get_by_block_root(block_root, max_slot) } /// Delete a state, ensuring it is removed from the LRU cache, as well as from on-disk. @@ -755,17 +785,10 @@ impl, Cold: ItemStore> HotColdDB /// (which are frozen, and won't be deleted), or valid descendents of the finalized checkpoint /// (which will be deleted by this function but shouldn't be). pub fn delete_state(&self, state_root: &Hash256, slot: Slot) -> Result<(), Error> { - // Delete the state summary. - self.hot_db - .key_delete(DBColumn::BeaconStateSummary.into(), state_root.as_bytes())?; - - // Delete the full state if it lies on an epoch boundary. - if slot % E::slots_per_epoch() == 0 { - self.hot_db - .key_delete(DBColumn::BeaconState.into(), state_root.as_bytes())?; - } - - Ok(()) + self.do_atomically_with_block_and_blobs_cache(vec![StoreOp::DeleteState( + *state_root, + Some(slot), + )]) } pub fn forwards_block_roots_iterator( @@ -833,17 +856,9 @@ impl, Cold: ItemStore> HotColdDB }) = self.load_hot_state_summary(state_root)? { // NOTE: minor inefficiency here because we load an unnecessary hot state summary - // - // `StateProcessingStrategy` should be irrelevant here since we never replay blocks for an epoch - // boundary state in the hot DB. - let state = self - .load_hot_state( - &epoch_boundary_state_root, - StateProcessingStrategy::Accurate, - )? - .ok_or(HotColdDBError::MissingEpochBoundaryState( - epoch_boundary_state_root, - ))?; + let (state, _) = self.load_hot_state(&epoch_boundary_state_root)?.ok_or( + HotColdDBError::MissingEpochBoundaryState(epoch_boundary_state_root), + )?; Ok(Some(state)) } else { // Try the cold DB @@ -1029,11 +1044,14 @@ impl, Cold: ItemStore> HotColdDB StoreOp::DeleteBlock(block_root) => { guard.delete_block(&block_root); + self.state_cache.lock().delete_block_states(&block_root); } - StoreOp::DeleteBlobs(_) => (), + StoreOp::DeleteState(state_root, _) => { + self.state_cache.lock().delete_state(&state_root) + } - StoreOp::DeleteState(_, _) => (), + StoreOp::DeleteBlobs(_) => (), StoreOp::DeleteExecutionPayload(_) => (), @@ -1070,6 +1088,26 @@ impl, Cold: ItemStore> HotColdDB state: &BeaconState, ops: &mut Vec, ) -> Result<(), Error> { + // Put the state in the cache. + let block_root = state.get_latest_block_root(*state_root); + + // Avoid storing states in the database if they already exist in the state cache. + // The exception to this is the finalized state, which must exist in the cache before it + // is stored on disk. + if let PutStateOutcome::Duplicate = + self.state_cache + .lock() + .put_state(*state_root, block_root, state)? + { + debug!( + self.log, + "Skipping storage of cached state"; + "slot" => state.slot(), + "state_root" => ?state_root + ); + return Ok(()); + } + // On the epoch boundary, store the full state. if state.slot() % E::slots_per_epoch() == 0 { trace!( @@ -1091,14 +1129,51 @@ impl, Cold: ItemStore> HotColdDB Ok(()) } + /// Get a post-finalization state from the database or store. + pub fn get_hot_state(&self, state_root: &Hash256) -> Result>, Error> { + if let Some(state) = self.state_cache.lock().get_by_state_root(*state_root) { + return Ok(Some(state)); + } + + if *state_root != self.get_split_info().state_root { + // Do not warn on start up when loading the split state. + warn!( + self.log, + "State cache missed"; + "state_root" => ?state_root, + ); + } + + let state_from_disk = self.load_hot_state(state_root)?; + + if let Some((mut state, block_root)) = state_from_disk { + state.update_tree_hash_cache()?; + state.build_all_caches(&self.spec)?; + self.state_cache + .lock() + .put_state(*state_root, block_root, &state)?; + debug!( + self.log, + "Cached state"; + "state_root" => ?state_root, + "slot" => state.slot(), + ); + Ok(Some(state)) + } else { + Ok(None) + } + } + /// Load a post-finalization state from the hot database. /// /// Will replay blocks from the nearest epoch boundary. + /// + /// Return the `(state, latest_block_root)` where `latest_block_root` is the root of the last + /// block applied to `state`. pub fn load_hot_state( &self, state_root: &Hash256, - state_processing_strategy: StateProcessingStrategy, - ) -> Result>, Error> { + ) -> Result, Hash256)>, Error> { metrics::inc_counter(&metrics::BEACON_STATE_HOT_GET_COUNT); // If the state is marked as temporary, do not return it. It will become visible @@ -1113,16 +1188,47 @@ impl, Cold: ItemStore> HotColdDB epoch_boundary_state_root, }) = self.load_hot_state_summary(state_root)? { - let boundary_state = + let mut boundary_state = get_full_state(&self.hot_db, &epoch_boundary_state_root, &self.spec)?.ok_or( HotColdDBError::MissingEpochBoundaryState(epoch_boundary_state_root), )?; + // Immediately rebase the state from disk on the finalized state so that we can reuse + // parts of the tree for state root calculation in `replay_blocks`. + self.state_cache + .lock() + .rebase_on_finalized(&mut boundary_state, &self.spec)?; + // Optimization to avoid even *thinking* about replaying blocks if we're already // on an epoch boundary. - let state = if slot % E::slots_per_epoch() == 0 { + let mut state = if slot % E::slots_per_epoch() == 0 { boundary_state } else { + // Cache ALL intermediate states that are reached during block replay. We may want + // to restrict this in future to only cache epoch boundary states. At worst we will + // cache up to 32 states for each state loaded, which should not flush out the cache + // entirely. + let state_cache_hook = |state_root, state: &mut BeaconState| { + // Ensure all caches are built before attempting to cache. + state.update_tree_hash_cache()?; + state.build_all_caches(&self.spec)?; + + let latest_block_root = state.get_latest_block_root(state_root); + let state_slot = state.slot(); + if let PutStateOutcome::New = + self.state_cache + .lock() + .put_state(state_root, latest_block_root, state)? + { + debug!( + self.log, + "Cached ancestor state"; + "state_root" => ?state_root, + "slot" => state_slot, + ); + } + Ok(()) + }; let blocks = self.load_blocks_to_replay(boundary_state.slot(), slot, latest_block_root)?; self.replay_blocks( @@ -1130,11 +1236,12 @@ impl, Cold: ItemStore> HotColdDB blocks, slot, no_state_root_iter(), - state_processing_strategy, + Some(Box::new(state_cache_hook)), )? }; + state.apply_pending_mutations()?; - Ok(Some(state)) + Ok(Some((state, latest_block_root))) } else { Ok(None) } @@ -1233,7 +1340,9 @@ impl, Cold: ItemStore> HotColdDB partial_state.load_randao_mixes(&self.cold_db, &self.spec)?; partial_state.load_historical_summaries(&self.cold_db, &self.spec)?; - partial_state.try_into() + let mut state: BeaconState = partial_state.try_into()?; + state.apply_pending_mutations()?; + Ok(state) } /// Load a restore point state by its `restore_point_index`. @@ -1247,7 +1356,7 @@ impl, Cold: ItemStore> HotColdDB /// Load a frozen state that lies between restore points. fn load_cold_intermediate_state(&self, slot: Slot) -> Result, Error> { - if let Some(state) = self.state_cache.lock().get(&slot) { + if let Some(state) = self.historic_state_cache.lock().get(&slot) { return Ok(state.clone()); } @@ -1261,7 +1370,7 @@ impl, Cold: ItemStore> HotColdDB let mut low_state: Option> = None; // Try to get a more recent state from the cache to avoid massive blocks replay. - for (s, state) in self.state_cache.lock().iter() { + for (s, state) in self.historic_state_cache.lock().iter() { if s.as_u64() / self.config.slots_per_restore_point == low_restore_point_idx && *s < slot && low_slot < *s @@ -1299,16 +1408,11 @@ impl, Cold: ItemStore> HotColdDB &self.spec, )?; - let state = self.replay_blocks( - low_state, - blocks, - slot, - Some(state_root_iter), - StateProcessingStrategy::Accurate, - )?; + let mut state = self.replay_blocks(low_state, blocks, slot, Some(state_root_iter), None)?; + state.apply_pending_mutations()?; // If state is not error, put it in the cache. - self.state_cache.lock().put(slot, state.clone()); + self.historic_state_cache.lock().put(slot, state.clone()); Ok(state) } @@ -1390,16 +1494,15 @@ impl, Cold: ItemStore> HotColdDB /// /// Will skip slots as necessary. The returned state is not guaranteed /// to have any caches built, beyond those immediately required by block processing. - fn replay_blocks( + pub fn replay_blocks( &self, state: BeaconState, blocks: Vec>>, target_slot: Slot, state_root_iter: Option>>, - state_processing_strategy: StateProcessingStrategy, + pre_slot_hook: Option>, ) -> Result, Error> { let mut block_replayer = BlockReplayer::new(state, &self.spec) - .state_processing_strategy(state_processing_strategy) .no_signature_verification() .minimal_block_root_verification(); @@ -1408,17 +1511,20 @@ impl, Cold: ItemStore> HotColdDB block_replayer = block_replayer.state_root_iter(state_root_iter); } + if let Some(pre_slot_hook) = pre_slot_hook { + block_replayer = block_replayer.pre_slot_hook(pre_slot_hook); + } + block_replayer .apply_blocks(blocks, Some(target_slot)) .map(|block_replayer| { if have_state_root_iterator && block_replayer.state_root_miss() { warn!( self.log, - "State root iterator miss"; + "State root cache miss during block replay"; "slot" => target_slot, ); } - block_replayer.into_state() }) } @@ -2213,7 +2319,7 @@ impl, Cold: ItemStore> HotColdDB } /// This function fills in missing block roots between last restore point slot and split - /// slot, if any. + /// slot, if any. pub fn heal_freezer_block_roots_at_split(&self) -> Result<(), Error> { let split = self.get_split_info(); let last_restore_point_slot = (split.slot - 1) / self.config.slots_per_restore_point @@ -2528,15 +2634,22 @@ pub fn migrate_database, Cold: ItemStore>( }; store.hot_db.put_sync(&SPLIT_KEY, &split)?; - // Split point is now persisted in the hot database on disk. The in-memory split point - // hasn't been modified elsewhere since we keep a write lock on it. It's safe to update + // Split point is now persisted in the hot database on disk. The in-memory split point + // hasn't been modified elsewhere since we keep a write lock on it. It's safe to update // the in-memory split point now. *split_guard = split; } - // Delete the states from the hot database if we got this far. + // Delete the blocks and states from the hot database if we got this far. store.do_atomically_with_block_and_blobs_cache(hot_db_ops)?; + // Update the cache's view of the finalized state. + store.update_finalized_state( + finalized_state_root, + finalized_block_root, + finalized_state.clone(), + )?; + debug!( store.log, "Freezer migration complete"; diff --git a/beacon_node/store/src/impls/beacon_state.rs b/beacon_node/store/src/impls/beacon_state.rs index d08bf564927..f752bf39795 100644 --- a/beacon_node/store/src/impls/beacon_state.rs +++ b/beacon_node/store/src/impls/beacon_state.rs @@ -46,14 +46,14 @@ pub fn get_full_state, E: EthSpec>( #[derive(Encode)] pub struct StorageContainer { state: BeaconState, - committee_caches: Vec, + committee_caches: Vec>, } impl StorageContainer { /// Create a new instance for storing a `BeaconState`. pub fn new(state: &BeaconState) -> Self { Self { - state: state.clone_with(CloneConfig::none()), + state: state.clone(), committee_caches: state.committee_caches().to_vec(), } } diff --git a/beacon_node/store/src/iter.rs b/beacon_node/store/src/iter.rs index e459c1c3575..03090ca14c5 100644 --- a/beacon_node/store/src/iter.rs +++ b/beacon_node/store/src/iter.rs @@ -412,15 +412,16 @@ mod test { let mut hashes = (0..).map(Hash256::from_low_u64_be); let roots_a = state_a.block_roots_mut(); for i in 0..roots_a.len() { - roots_a[i] = hashes.next().unwrap() + *roots_a.get_mut(i).unwrap() = hashes.next().unwrap(); } let roots_b = state_b.block_roots_mut(); for i in 0..roots_b.len() { - roots_b[i] = hashes.next().unwrap() + *roots_b.get_mut(i).unwrap() = hashes.next().unwrap(); } let state_a_root = hashes.next().unwrap(); - state_b.state_roots_mut()[0] = state_a_root; + *state_b.state_roots_mut().get_mut(0).unwrap() = state_a_root; + state_a.apply_pending_mutations().unwrap(); store.put_state(&state_a_root, &state_a).unwrap(); let iter = BlockRootsIterator::new(&store, &state_b); @@ -472,6 +473,9 @@ mod test { let state_a_root = Hash256::from_low_u64_be(slots_per_historical_root as u64); let state_b_root = Hash256::from_low_u64_be(slots_per_historical_root as u64 * 2); + state_a.apply_pending_mutations().unwrap(); + state_b.apply_pending_mutations().unwrap(); + store.put_state(&state_a_root, &state_a).unwrap(); store.put_state(&state_b_root, &state_b).unwrap(); diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index c3136a910db..66032d89c52 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -26,6 +26,7 @@ pub mod metadata; pub mod metrics; mod partial_beacon_state; pub mod reconstruct; +pub mod state_cache; pub mod iter; diff --git a/beacon_node/store/src/partial_beacon_state.rs b/beacon_node/store/src/partial_beacon_state.rs index 4e5a2b8e64b..25438fc7e0a 100644 --- a/beacon_node/store/src/partial_beacon_state.rs +++ b/beacon_node/store/src/partial_beacon_state.rs @@ -34,42 +34,42 @@ where pub latest_block_header: BeaconBlockHeader, #[ssz(skip_serializing, skip_deserializing)] - pub block_roots: Option>, + pub block_roots: Option>, #[ssz(skip_serializing, skip_deserializing)] - pub state_roots: Option>, + pub state_roots: Option>, #[ssz(skip_serializing, skip_deserializing)] - pub historical_roots: Option>, + pub historical_roots: Option>, // Ethereum 1.0 chain data pub eth1_data: Eth1Data, - pub eth1_data_votes: VariableList, + pub eth1_data_votes: List, pub eth1_deposit_index: u64, // Registry - pub validators: VariableList, - pub balances: VariableList, + pub validators: List, + pub balances: List, // Shuffling /// Randao value from the current slot, for patching into the per-epoch randao vector. pub latest_randao_value: Hash256, #[ssz(skip_serializing, skip_deserializing)] - pub randao_mixes: Option>, + pub randao_mixes: Option>, // Slashings - slashings: FixedVector, + slashings: Vector, // Attestations (genesis fork only) #[superstruct(only(Base))] - pub previous_epoch_attestations: VariableList, E::MaxPendingAttestations>, + pub previous_epoch_attestations: List, E::MaxPendingAttestations>, #[superstruct(only(Base))] - pub current_epoch_attestations: VariableList, E::MaxPendingAttestations>, + pub current_epoch_attestations: List, E::MaxPendingAttestations>, // Participation (Altair and later) #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] - pub previous_epoch_participation: VariableList, + pub previous_epoch_participation: List, #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] - pub current_epoch_participation: VariableList, + pub current_epoch_participation: List, // Finality pub justification_bits: BitVector, @@ -79,7 +79,7 @@ where // Inactivity #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] - pub inactivity_scores: VariableList, + pub inactivity_scores: List, // Light-client sync committees #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] @@ -117,7 +117,7 @@ where #[ssz(skip_serializing, skip_deserializing)] #[superstruct(only(Capella, Deneb, Electra))] - pub historical_summaries: Option>, + pub historical_summaries: Option>, } /// Implement the conversion function from BeaconState -> PartialBeaconState. @@ -369,7 +369,9 @@ impl PartialBeaconState { // Patch the value for the current slot into the index for the current epoch let current_epoch = self.slot().epoch(E::slots_per_epoch()); let len = randao_mixes.len(); - randao_mixes[current_epoch.as_usize() % len] = *self.latest_randao_value(); + *randao_mixes + .get_mut(current_epoch.as_usize() % len) + .ok_or(Error::RandaoMixOutOfBounds)? = *self.latest_randao_value(); *self.randao_mixes_mut() = Some(randao_mixes) } @@ -422,7 +424,6 @@ macro_rules! impl_try_into_beacon_state { exit_cache: <_>::default(), slashings_cache: <_>::default(), epoch_cache: <_>::default(), - tree_hash_cache: <_>::default(), // Variant-specific fields $( diff --git a/beacon_node/store/src/reconstruct.rs b/beacon_node/store/src/reconstruct.rs index 8fe13777ac4..8ef4886565c 100644 --- a/beacon_node/store/src/reconstruct.rs +++ b/beacon_node/store/src/reconstruct.rs @@ -5,7 +5,7 @@ use itertools::{process_results, Itertools}; use slog::info; use state_processing::{ per_block_processing, per_slot_processing, BlockSignatureStrategy, ConsensusContext, - StateProcessingStrategy, VerifyBlockRoot, + VerifyBlockRoot, }; use std::sync::Arc; use types::{EthSpec, Hash256}; @@ -94,7 +94,6 @@ where &mut state, &block, BlockSignatureStrategy::NoVerification, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &self.spec, diff --git a/beacon_node/store/src/state_cache.rs b/beacon_node/store/src/state_cache.rs new file mode 100644 index 00000000000..5c1faa7f2fd --- /dev/null +++ b/beacon_node/store/src/state_cache.rs @@ -0,0 +1,303 @@ +use crate::Error; +use lru::LruCache; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::num::NonZeroUsize; +use types::{BeaconState, ChainSpec, Epoch, EthSpec, Hash256, Slot}; + +/// Fraction of the LRU cache to leave intact during culling. +const CULL_EXEMPT_NUMERATOR: usize = 1; +const CULL_EXEMPT_DENOMINATOR: usize = 10; + +/// States that are less than or equal to this many epochs old *could* become finalized and will not +/// be culled from the cache. +const EPOCH_FINALIZATION_LIMIT: u64 = 4; + +#[derive(Debug)] +pub struct FinalizedState { + state_root: Hash256, + state: BeaconState, +} + +/// Map from block_root -> slot -> state_root. +#[derive(Debug, Default)] +pub struct BlockMap { + blocks: HashMap, +} + +/// Map from slot -> state_root. +#[derive(Debug, Default)] +pub struct SlotMap { + slots: BTreeMap, +} + +#[derive(Debug)] +pub struct StateCache { + finalized_state: Option>, + states: LruCache>, + block_map: BlockMap, + max_epoch: Epoch, +} + +#[derive(Debug)] +pub enum PutStateOutcome { + Finalized, + Duplicate, + New, +} + +#[allow(clippy::len_without_is_empty)] +impl StateCache { + pub fn new(capacity: NonZeroUsize) -> Self { + StateCache { + finalized_state: None, + states: LruCache::new(capacity), + block_map: BlockMap::default(), + max_epoch: Epoch::new(0), + } + } + + pub fn len(&self) -> usize { + self.states.len() + } + + pub fn capacity(&self) -> usize { + self.states.cap().get() + } + + pub fn update_finalized_state( + &mut self, + state_root: Hash256, + block_root: Hash256, + state: BeaconState, + ) -> Result<(), Error> { + if state.slot() % E::slots_per_epoch() != 0 { + return Err(Error::FinalizedStateUnaligned); + } + + if self + .finalized_state + .as_ref() + .map_or(false, |finalized_state| { + state.slot() < finalized_state.state.slot() + }) + { + return Err(Error::FinalizedStateDecreasingSlot); + } + + // Add to block map. + self.block_map.insert(block_root, state.slot(), state_root); + + // Prune block map. + let state_roots_to_prune = self.block_map.prune(state.slot()); + + // Delete states. + for state_root in state_roots_to_prune { + self.states.pop(&state_root); + } + + // Update finalized state. + self.finalized_state = Some(FinalizedState { state_root, state }); + Ok(()) + } + + /// Rebase the given state on the finalized state in order to reduce its memory consumption. + /// + /// This function should only be called on states that are likely not to already share tree + /// nodes with the finalized state, e.g. states loaded from disk. + /// + /// If the finalized state is not initialized this function is a no-op. + pub fn rebase_on_finalized( + &self, + state: &mut BeaconState, + spec: &ChainSpec, + ) -> Result<(), Error> { + if let Some(finalized_state) = &self.finalized_state { + state.rebase_on(&finalized_state.state, spec)?; + } + Ok(()) + } + + /// Return a status indicating whether the state already existed in the cache. + pub fn put_state( + &mut self, + state_root: Hash256, + block_root: Hash256, + state: &BeaconState, + ) -> Result { + if self + .finalized_state + .as_ref() + .map_or(false, |finalized_state| { + finalized_state.state_root == state_root + }) + { + return Ok(PutStateOutcome::Finalized); + } + + if self.states.peek(&state_root).is_some() { + return Ok(PutStateOutcome::Duplicate); + } + + // Refuse states with pending mutations: we want cached states to be as small as possible + // i.e. stored entirely as a binary merkle tree with no updates overlaid. + if state.has_pending_mutations() { + return Err(Error::StateForCacheHasPendingUpdates { + state_root, + slot: state.slot(), + }); + } + + // Update the cache's idea of the max epoch. + self.max_epoch = std::cmp::max(state.current_epoch(), self.max_epoch); + + // If the cache is full, use the custom cull routine to make room. + if let Some(over_capacity) = self.len().checked_sub(self.capacity()) { + self.cull(over_capacity + 1); + } + + // Insert the full state into the cache. + self.states.put(state_root, state.clone()); + + // Record the connection from block root and slot to this state. + let slot = state.slot(); + self.block_map.insert(block_root, slot, state_root); + + Ok(PutStateOutcome::New) + } + + pub fn get_by_state_root(&mut self, state_root: Hash256) -> Option> { + if let Some(ref finalized_state) = self.finalized_state { + if state_root == finalized_state.state_root { + return Some(finalized_state.state.clone()); + } + } + self.states.get(&state_root).cloned() + } + + pub fn get_by_block_root( + &mut self, + block_root: Hash256, + slot: Slot, + ) -> Option<(Hash256, BeaconState)> { + let slot_map = self.block_map.blocks.get(&block_root)?; + + // Find the state at `slot`, or failing that the most recent ancestor. + let state_root = slot_map + .slots + .iter() + .rev() + .find_map(|(ancestor_slot, state_root)| { + (*ancestor_slot <= slot).then_some(*state_root) + })?; + + let state = self.get_by_state_root(state_root)?; + Some((state_root, state)) + } + + pub fn delete_state(&mut self, state_root: &Hash256) { + self.states.pop(state_root); + self.block_map.delete(state_root); + } + + pub fn delete_block_states(&mut self, block_root: &Hash256) { + if let Some(slot_map) = self.block_map.delete_block_states(block_root) { + for state_root in slot_map.slots.values() { + self.states.pop(state_root); + } + } + } + + /// Cull approximately `count` states from the cache. + /// + /// States are culled LRU, with the following extra order imposed: + /// + /// - Advanced states. + /// - Mid-epoch unadvanced states. + /// - Epoch-boundary states that are too old to be finalized. + /// - Epoch-boundary states that could be finalized. + pub fn cull(&mut self, count: usize) { + let cull_exempt = std::cmp::max( + 1, + self.len() * CULL_EXEMPT_NUMERATOR / CULL_EXEMPT_DENOMINATOR, + ); + + // Stage 1: gather states to cull. + let mut advanced_state_roots = vec![]; + let mut mid_epoch_state_roots = vec![]; + let mut old_boundary_state_roots = vec![]; + let mut good_boundary_state_roots = vec![]; + for (&state_root, state) in self.states.iter().skip(cull_exempt) { + let is_advanced = state.slot() > state.latest_block_header().slot; + let is_boundary = state.slot() % E::slots_per_epoch() == 0; + let could_finalize = + (self.max_epoch - state.current_epoch()) <= EPOCH_FINALIZATION_LIMIT; + + if is_boundary { + if could_finalize { + good_boundary_state_roots.push(state_root); + } else { + old_boundary_state_roots.push(state_root); + } + } else if is_advanced { + advanced_state_roots.push(state_root); + } else { + mid_epoch_state_roots.push(state_root); + } + + // Terminate early in the common case where we've already found enough junk to cull. + if advanced_state_roots.len() == count { + break; + } + } + + // Stage 2: delete. + // This could probably be more efficient in how it interacts with the block map. + for state_root in advanced_state_roots + .iter() + .chain(mid_epoch_state_roots.iter()) + .chain(old_boundary_state_roots.iter()) + .chain(good_boundary_state_roots.iter()) + .take(count) + { + self.delete_state(state_root); + } + } +} + +impl BlockMap { + fn insert(&mut self, block_root: Hash256, slot: Slot, state_root: Hash256) { + let slot_map = self.blocks.entry(block_root).or_default(); + slot_map.slots.insert(slot, state_root); + } + + fn prune(&mut self, finalized_slot: Slot) -> HashSet { + let mut pruned_states = HashSet::new(); + + self.blocks.retain(|_, slot_map| { + slot_map.slots.retain(|slot, state_root| { + let keep = *slot >= finalized_slot; + if !keep { + pruned_states.insert(*state_root); + } + keep + }); + + !slot_map.slots.is_empty() + }); + + pruned_states + } + + fn delete(&mut self, state_root_to_delete: &Hash256) { + self.blocks.retain(|_, slot_map| { + slot_map + .slots + .retain(|_, state_root| state_root != state_root_to_delete); + !slot_map.slots.is_empty() + }); + } + + fn delete_block_states(&mut self, block_root: &Hash256) -> Option { + self.blocks.remove(block_root) + } +} diff --git a/common/task_executor/Cargo.toml b/common/task_executor/Cargo.toml index b3d58fa5ea8..cc9a2c5097b 100644 --- a/common/task_executor/Cargo.toml +++ b/common/task_executor/Cargo.toml @@ -12,3 +12,4 @@ futures = { workspace = true } lazy_static = { workspace = true } lighthouse_metrics = { workspace = true } sloggers = { workspace = true } +logging = { workspace = true } diff --git a/common/task_executor/src/test_utils.rs b/common/task_executor/src/test_utils.rs index 6e372d97575..ec8f45d850e 100644 --- a/common/task_executor/src/test_utils.rs +++ b/common/task_executor/src/test_utils.rs @@ -1,4 +1,5 @@ use crate::TaskExecutor; +use logging::test_logger; use slog::Logger; use sloggers::{null::NullLoggerBuilder, Build}; use std::sync::Arc; @@ -26,7 +27,7 @@ impl Default for TestRuntime { fn default() -> Self { let (runtime_shutdown, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let log = null_logger().unwrap(); + let log = test_logger(); let (runtime, handle) = if let Ok(handle) = runtime::Handle::try_current() { (None, handle) diff --git a/consensus/state_processing/src/all_caches.rs b/consensus/state_processing/src/all_caches.rs index 106692c63aa..b915091405b 100644 --- a/consensus/state_processing/src/all_caches.rs +++ b/consensus/state_processing/src/all_caches.rs @@ -9,12 +9,14 @@ use types::{BeaconState, ChainSpec, EpochCacheError, EthSpec, Hash256, RelativeE pub trait AllCaches { /// Build all caches. /// - /// Note that this excludes the tree-hash cache. That needs to be managed separately. + /// Note that this excludes milhouse's intrinsic tree-hash cache. That needs to be managed + /// separately. fn build_all_caches(&mut self, spec: &ChainSpec) -> Result<(), EpochCacheError>; /// Return true if all caches are built. /// - /// Note that this excludes the tree-hash cache. That needs to be managed separately. + /// Note that this excludes milhouse's intrinsic tree-hash cache. That needs to be managed + /// separately. fn all_caches_built(&self) -> bool; } diff --git a/consensus/state_processing/src/block_replayer.rs b/consensus/state_processing/src/block_replayer.rs index f502d7f692c..d7621ebf18b 100644 --- a/consensus/state_processing/src/block_replayer.rs +++ b/consensus/state_processing/src/block_replayer.rs @@ -6,19 +6,23 @@ use crate::{ use itertools::Itertools; use std::iter::Peekable; use std::marker::PhantomData; -use types::{BeaconState, BlindedPayload, ChainSpec, EthSpec, Hash256, SignedBeaconBlock, Slot}; +use types::{ + BeaconState, BeaconStateError, BlindedPayload, ChainSpec, EthSpec, Hash256, SignedBeaconBlock, + Slot, +}; -type PreBlockHook<'a, E, Error> = Box< +pub type PreBlockHook<'a, E, Error> = Box< dyn FnMut(&mut BeaconState, &SignedBeaconBlock>) -> Result<(), Error> + 'a, >; -type PostBlockHook<'a, E, Error> = PreBlockHook<'a, E, Error>; -type PreSlotHook<'a, E, Error> = Box) -> Result<(), Error> + 'a>; -type PostSlotHook<'a, E, Error> = Box< +pub type PostBlockHook<'a, E, Error> = PreBlockHook<'a, E, Error>; +pub type PreSlotHook<'a, E, Error> = + Box) -> Result<(), Error> + 'a>; +pub type PostSlotHook<'a, E, Error> = Box< dyn FnMut(&mut BeaconState, Option>, bool) -> Result<(), Error> + 'a, >; -type StateRootIterDefault = std::iter::Empty>; +pub type StateRootIterDefault = std::iter::Empty>; /// Efficiently apply blocks to a state while configuring various parameters. /// @@ -31,7 +35,6 @@ pub struct BlockReplayer< > { state: BeaconState, spec: &'a ChainSpec, - state_processing_strategy: StateProcessingStrategy, block_sig_strategy: BlockSignatureStrategy, verify_block_root: Option, pre_block_hook: Option>, @@ -45,9 +48,9 @@ pub struct BlockReplayer< #[derive(Debug)] pub enum BlockReplayError { - NoBlocks, SlotProcessing(SlotProcessingError), BlockProcessing(BlockProcessingError), + BeaconState(BeaconStateError), } impl From for BlockReplayError { @@ -62,14 +65,10 @@ impl From for BlockReplayError { } } -/// Defines how state roots should be computed and whether to perform all state transitions during block replay. -#[derive(PartialEq, Clone, Copy)] -pub enum StateProcessingStrategy { - /// Perform all transitions faithfully to the specification. - Accurate, - /// Don't compute state roots and process withdrawals, eventually computing an invalid beacon - /// state that can only be used for obtaining shuffling. - Inconsistent, +impl From for BlockReplayError { + fn from(e: BeaconStateError) -> Self { + Self::BeaconState(e) + } } impl<'a, E, Error, StateRootIter> BlockReplayer<'a, E, Error, StateRootIter> @@ -89,7 +88,6 @@ where Self { state, spec, - state_processing_strategy: StateProcessingStrategy::Accurate, block_sig_strategy: BlockSignatureStrategy::VerifyBulk, verify_block_root: Some(VerifyBlockRoot::True), pre_block_hook: None, @@ -102,18 +100,6 @@ where } } - /// Set the replayer's state processing strategy different from the default. - pub fn state_processing_strategy( - mut self, - state_processing_strategy: StateProcessingStrategy, - ) -> Self { - if state_processing_strategy == StateProcessingStrategy::Inconsistent { - self.verify_block_root = None; - } - self.state_processing_strategy = state_processing_strategy; - self - } - /// Set the replayer's block signature verification strategy. pub fn block_signature_strategy(mut self, block_sig_strategy: BlockSignatureStrategy) -> Self { self.block_sig_strategy = block_sig_strategy; @@ -175,21 +161,24 @@ where self } - /// Compute the state root for `slot` as efficiently as possible. + /// Compute the state root for `self.state` as efficiently as possible. + /// + /// This function MUST only be called when `self.state` is a post-state, i.e. it MUST not be + /// called between advancing a state with `per_slot_processing` and applying the block for that + /// slot. /// /// The `blocks` should be the full list of blocks being applied and `i` should be the index of /// the next block that will be applied, or `blocks.len()` if all blocks have already been /// applied. + /// + /// If the state root is not available from the state root iterator or the blocks then it will + /// be computed from `self.state` and a state root iterator miss will be recorded. fn get_state_root( &mut self, - slot: Slot, blocks: &[SignedBeaconBlock>], i: usize, - ) -> Result, Error> { - // If we don't care about state roots then return immediately. - if self.state_processing_strategy == StateProcessingStrategy::Inconsistent { - return Ok(Some(Hash256::zero())); - } + ) -> Result { + let slot = self.state.slot(); // If a state root iterator is configured, use it to find the root. if let Some(ref mut state_root_iter) = self.state_root_iter { @@ -199,7 +188,7 @@ where .transpose()?; if let Some((root, _)) = opt_root { - return Ok(Some(root)); + return Ok(root); } } @@ -207,13 +196,17 @@ where if let Some(prev_i) = i.checked_sub(1) { if let Some(prev_block) = blocks.get(prev_i) { if prev_block.slot() == slot { - return Ok(Some(prev_block.state_root())); + return Ok(prev_block.state_root()); } } } self.state_root_miss = true; - Ok(None) + let state_root = self + .state + .update_tree_hash_cache() + .map_err(BlockReplayError::from)?; + Ok(state_root) } /// Apply `blocks` atop `self.state`, taking care of slot processing. @@ -232,12 +225,13 @@ where } while self.state.slot() < block.slot() { + let state_root = self.get_state_root(&blocks, i)?; + if let Some(ref mut pre_slot_hook) = self.pre_slot_hook { - pre_slot_hook(&mut self.state)?; + pre_slot_hook(state_root, &mut self.state)?; } - let state_root = self.get_state_root(self.state.slot(), &blocks, i)?; - let summary = per_slot_processing(&mut self.state, state_root, self.spec) + let summary = per_slot_processing(&mut self.state, Some(state_root), self.spec) .map_err(BlockReplayError::from)?; if let Some(ref mut post_slot_hook) = self.post_slot_hook { @@ -250,15 +244,11 @@ where pre_block_hook(&mut self.state, block)?; } - let verify_block_root = self.verify_block_root.unwrap_or_else(|| { - // If no explicit policy is set, verify only the first 1 or 2 block roots if using - // accurate state roots. Inaccurate state roots require block root verification to - // be off. - if i <= 1 && self.state_processing_strategy == StateProcessingStrategy::Accurate { - VerifyBlockRoot::True - } else { - VerifyBlockRoot::False - } + // If no explicit policy is set, verify only the first 1 or 2 block roots. + let verify_block_root = self.verify_block_root.unwrap_or(if i <= 1 { + VerifyBlockRoot::True + } else { + VerifyBlockRoot::False }); // Proposer index was already checked when this block was originally processed, we // can omit recomputing it during replay. @@ -268,7 +258,6 @@ where &mut self.state, block, self.block_sig_strategy, - self.state_processing_strategy, verify_block_root, &mut ctxt, self.spec, @@ -282,12 +271,13 @@ where if let Some(target_slot) = target_slot { while self.state.slot() < target_slot { + let state_root = self.get_state_root(&blocks, blocks.len())?; + if let Some(ref mut pre_slot_hook) = self.pre_slot_hook { - pre_slot_hook(&mut self.state)?; + pre_slot_hook(state_root, &mut self.state)?; } - let state_root = self.get_state_root(self.state.slot(), &blocks, blocks.len())?; - let summary = per_slot_processing(&mut self.state, state_root, self.spec) + let summary = per_slot_processing(&mut self.state, Some(state_root), self.spec) .map_err(BlockReplayError::from)?; if let Some(ref mut post_slot_hook) = self.post_slot_hook { diff --git a/consensus/state_processing/src/common/initiate_validator_exit.rs b/consensus/state_processing/src/common/initiate_validator_exit.rs index 84656d9c890..a40a9dfd398 100644 --- a/consensus/state_processing/src/common/initiate_validator_exit.rs +++ b/consensus/state_processing/src/common/initiate_validator_exit.rs @@ -30,13 +30,14 @@ pub fn initiate_validator_exit( exit_queue_epoch.safe_add_assign(1)?; } - let validator = state.get_validator_mut(index)?; + let validator = state.get_validator_cow(index)?; // Return if the validator already initiated exit if validator.exit_epoch != spec.far_future_epoch { return Ok(()); } + let validator = validator.into_mut()?; validator.exit_epoch = exit_queue_epoch; validator.withdrawable_epoch = exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?; diff --git a/consensus/state_processing/src/genesis.rs b/consensus/state_processing/src/genesis.rs index 036ab23498c..5ab9a99a3b1 100644 --- a/consensus/state_processing/src/genesis.rs +++ b/consensus/state_processing/src/genesis.rs @@ -28,7 +28,7 @@ pub fn initialize_beacon_state_from_eth1( let mut state = BeaconState::new(genesis_time, eth1_data, spec); // Seed RANDAO with Eth1 entropy - state.fill_randao_mixes_with(eth1_block_hash); + state.fill_randao_mixes_with(eth1_block_hash)?; let mut deposit_tree = DepositDataTree::create(&[], 0, DEPOSIT_TREE_DEPTH); @@ -152,7 +152,9 @@ pub fn process_activations( spec: &ChainSpec, ) -> Result<(), Error> { let (validators, balances, _) = state.validators_and_balances_and_progressive_balances_mut(); - for (index, validator) in validators.iter_mut().enumerate() { + let mut validators_iter = validators.iter_cow(); + while let Some((index, validator)) = validators_iter.next_cow() { + let validator = validator.into_mut()?; let balance = balances .get(index) .copied() diff --git a/consensus/state_processing/src/lib.rs b/consensus/state_processing/src/lib.rs index 7d84c426e8c..74f9d84bb11 100644 --- a/consensus/state_processing/src/lib.rs +++ b/consensus/state_processing/src/lib.rs @@ -30,7 +30,7 @@ pub mod upgrade; pub mod verify_operation; pub use all_caches::AllCaches; -pub use block_replayer::{BlockReplayError, BlockReplayer, StateProcessingStrategy}; +pub use block_replayer::{BlockReplayError, BlockReplayer}; pub use consensus_context::{ConsensusContext, ContextError}; pub use genesis::{ eth2_genesis_time, initialize_beacon_state_from_eth1, is_valid_genesis_state, diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index b370ec6216b..a8447b7714a 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -40,7 +40,6 @@ mod verify_exit; mod verify_proposer_slashing; use crate::common::decrease_balance; -use crate::StateProcessingStrategy; use crate::common::update_progressive_balances_cache::{ initialize_progressive_balances_cache, update_progressive_balances_metrics, @@ -102,7 +101,6 @@ pub fn per_block_processing>( state: &mut BeaconState, signed_block: &SignedBeaconBlock, block_signature_strategy: BlockSignatureStrategy, - state_processing_strategy: StateProcessingStrategy, verify_block_root: VerifyBlockRoot, ctxt: &mut ConsensusContext, spec: &ChainSpec, @@ -172,9 +170,7 @@ pub fn per_block_processing>( // previous block. if is_execution_enabled(state, block.body()) { let body = block.body(); - if state_processing_strategy == StateProcessingStrategy::Accurate { - process_withdrawals::(state, body.execution_payload()?, spec)?; - } + process_withdrawals::(state, body.execution_payload()?, spec)?; process_execution_payload::(state, body, spec)?; } diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index 28d36dbc518..336895514f9 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -82,6 +82,7 @@ pub enum BlockProcessingError { }, ExecutionInvalid, ConsensusContext(ContextError), + MilhouseError(milhouse::Error), EpochCacheError(EpochCacheError), WithdrawalsRootMismatch { expected: Hash256, @@ -138,6 +139,12 @@ impl From for BlockProcessingError { } } +impl From for BlockProcessingError { + fn from(e: milhouse::Error) -> Self { + Self::MilhouseError(e) + } +} + impl From> for BlockProcessingError { fn from(e: BlockOperationError) -> BlockProcessingError { match e { diff --git a/consensus/state_processing/src/per_block_processing/process_operations.rs b/consensus/state_processing/src/per_block_processing/process_operations.rs index 63b7c9e01fb..d812306a0f7 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -128,7 +128,7 @@ pub mod altair_deneb { let previous_epoch = ctxt.previous_epoch; let current_epoch = ctxt.current_epoch; - let attesting_indices = &verify_attestation_for_block_inclusion( + let attesting_indices = verify_attestation_for_block_inclusion( state, attestation, ctxt, @@ -136,7 +136,8 @@ pub mod altair_deneb { spec, ) .map_err(|e| e.into_with_index(att_index))? - .attesting_indices; + .attesting_indices + .clone(); // Matching roots, participation flag indices let data = &attestation.data; @@ -146,7 +147,7 @@ pub mod altair_deneb { // Update epoch participation flags. let mut proposer_reward_numerator = 0; - for index in attesting_indices { + for index in &attesting_indices { let index = *index as usize; let validator_effective_balance = state.epoch_cache().get_effective_balance(index)?; diff --git a/consensus/state_processing/src/per_block_processing/tests.rs b/consensus/state_processing/src/per_block_processing/tests.rs index 2a2b67e30da..f0055fa80dd 100644 --- a/consensus/state_processing/src/per_block_processing/tests.rs +++ b/consensus/state_processing/src/per_block_processing/tests.rs @@ -5,7 +5,7 @@ use crate::per_block_processing::errors::{ DepositInvalid, HeaderInvalid, IndexedAttestationInvalid, IntoWithIndex, ProposerSlashingInvalid, }; -use crate::{per_block_processing, BlockReplayError, BlockReplayer, StateProcessingStrategy}; +use crate::{per_block_processing, BlockReplayError, BlockReplayer}; use crate::{ per_block_processing::{process_operations, verify_exit::verify_exit}, BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, VerifySignatures, @@ -72,7 +72,6 @@ async fn valid_block_ok() { &mut state, &block, BlockSignatureStrategy::VerifyIndividual, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &spec, @@ -98,7 +97,6 @@ async fn invalid_block_header_state_slot() { &mut state, &SignedBeaconBlock::from_block(block, signature), BlockSignatureStrategy::VerifyIndividual, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &spec, @@ -131,7 +129,6 @@ async fn invalid_parent_block_root() { &mut state, &SignedBeaconBlock::from_block(block, signature), BlockSignatureStrategy::VerifyIndividual, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &spec, @@ -165,7 +162,6 @@ async fn invalid_block_signature() { &mut state, &SignedBeaconBlock::from_block(block, Signature::empty()), BlockSignatureStrategy::VerifyIndividual, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &spec, @@ -199,7 +195,6 @@ async fn invalid_randao_reveal_signature() { &mut state, &signed_block, BlockSignatureStrategy::VerifyIndividual, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &spec, diff --git a/consensus/state_processing/src/per_epoch_processing/altair/participation_flag_updates.rs b/consensus/state_processing/src/per_epoch_processing/altair/participation_flag_updates.rs index dd1b2dfcd86..fc55fb11144 100644 --- a/consensus/state_processing/src/per_epoch_processing/altair/participation_flag_updates.rs +++ b/consensus/state_processing/src/per_epoch_processing/altair/participation_flag_updates.rs @@ -2,17 +2,14 @@ use crate::EpochProcessingError; use types::beacon_state::BeaconState; use types::eth_spec::EthSpec; use types::participation_flags::ParticipationFlags; -use types::VariableList; +use types::List; pub fn process_participation_flag_updates( state: &mut BeaconState, ) -> Result<(), EpochProcessingError> { *state.previous_epoch_participation_mut()? = std::mem::take(state.current_epoch_participation_mut()?); - *state.current_epoch_participation_mut()? = VariableList::new(vec![ - ParticipationFlags::default( - ); - state.validators().len() - ])?; + *state.current_epoch_participation_mut()? = + List::repeat(ParticipationFlags::default(), state.validators().len())?; Ok(()) } diff --git a/consensus/state_processing/src/per_epoch_processing/capella/historical_summaries_update.rs b/consensus/state_processing/src/per_epoch_processing/capella/historical_summaries_update.rs index 7490f276567..00adabdcfe9 100644 --- a/consensus/state_processing/src/per_epoch_processing/capella/historical_summaries_update.rs +++ b/consensus/state_processing/src/per_epoch_processing/capella/historical_summaries_update.rs @@ -13,6 +13,9 @@ pub fn process_historical_summaries_update( .safe_rem((E::slots_per_historical_root() as u64).safe_div(E::slots_per_epoch())?)? == 0 { + // We need to flush any pending mutations before hashing. + state.block_roots_mut().apply_updates()?; + state.state_roots_mut().apply_updates()?; let summary = HistoricalSummary::new(state); return state .historical_summaries_mut()? diff --git a/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs b/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs index 7bd62c40816..73881e932b7 100644 --- a/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs +++ b/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs @@ -21,7 +21,9 @@ pub fn process_effective_balance_updates( let downward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_downward_multiplier)?; let upward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_upward_multiplier)?; let (validators, balances, _) = state.validators_and_balances_and_progressive_balances_mut(); - for (index, validator) in validators.iter_mut().enumerate() { + let mut validators_iter = validators.iter_cow(); + + while let Some((index, validator)) = validators_iter.next_cow() { let balance = balances .get(index) .copied() @@ -44,7 +46,7 @@ pub fn process_effective_balance_updates( } if new_effective_balance != validator.effective_balance { - validator.effective_balance = new_effective_balance; + validator.into_mut()?.effective_balance = new_effective_balance; } } diff --git a/consensus/state_processing/src/per_epoch_processing/epoch_processing_summary.rs b/consensus/state_processing/src/per_epoch_processing/epoch_processing_summary.rs index 65a946e7bff..6f48050e161 100644 --- a/consensus/state_processing/src/per_epoch_processing/epoch_processing_summary.rs +++ b/consensus/state_processing/src/per_epoch_processing/epoch_processing_summary.rs @@ -3,8 +3,8 @@ use crate::metrics; use std::sync::Arc; use types::{ consts::altair::{TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX}, - BeaconStateError, Epoch, EthSpec, ParticipationFlags, ProgressiveBalancesCache, SyncCommittee, - Validator, VariableList, + BeaconStateError, Epoch, EthSpec, List, ParticipationFlags, ProgressiveBalancesCache, + SyncCommittee, Validator, }; /// Provides a summary of validator participation during the epoch. @@ -25,20 +25,20 @@ pub enum EpochProcessingSummary { #[derive(PartialEq, Debug)] pub struct ParticipationEpochSummary { /// Copy of the validator registry prior to mutation. - validators: VariableList, + validators: List, /// Copy of the participation flags for the previous epoch. - previous_epoch_participation: VariableList, + previous_epoch_participation: List, /// Copy of the participation flags for the current epoch. - current_epoch_participation: VariableList, + current_epoch_participation: List, previous_epoch: Epoch, current_epoch: Epoch, } impl ParticipationEpochSummary { pub fn new( - validators: VariableList, - previous_epoch_participation: VariableList, - current_epoch_participation: VariableList, + validators: List, + previous_epoch_participation: List, + current_epoch_participation: List, previous_epoch: Epoch, current_epoch: Epoch, ) -> Self { diff --git a/consensus/state_processing/src/per_epoch_processing/errors.rs b/consensus/state_processing/src/per_epoch_processing/errors.rs index c18e1303b26..de481ec6767 100644 --- a/consensus/state_processing/src/per_epoch_processing/errors.rs +++ b/consensus/state_processing/src/per_epoch_processing/errors.rs @@ -1,4 +1,4 @@ -use types::{BeaconStateError, EpochCacheError, InconsistentFork}; +use types::{milhouse, BeaconStateError, EpochCacheError, InconsistentFork}; #[derive(Debug, PartialEq)] pub enum EpochProcessingError { @@ -23,6 +23,7 @@ pub enum EpochProcessingError { InconsistentStateFork(InconsistentFork), InvalidJustificationBit(ssz_types::Error), InvalidFlagIndex(usize), + MilhouseError(milhouse::Error), EpochCache(EpochCacheError), } @@ -50,6 +51,12 @@ impl From for EpochProcessingError { } } +impl From for EpochProcessingError { + fn from(e: milhouse::Error) -> Self { + Self::MilhouseError(e) + } +} + impl From for EpochProcessingError { fn from(e: EpochCacheError) -> Self { EpochProcessingError::EpochCache(e) diff --git a/consensus/state_processing/src/per_epoch_processing/historical_roots_update.rs b/consensus/state_processing/src/per_epoch_processing/historical_roots_update.rs index 6d06b4d7ca5..7686932192f 100644 --- a/consensus/state_processing/src/per_epoch_processing/historical_roots_update.rs +++ b/consensus/state_processing/src/per_epoch_processing/historical_roots_update.rs @@ -14,7 +14,7 @@ pub fn process_historical_roots_update( .safe_rem(E::SlotsPerHistoricalRoot::to_u64().safe_div(E::slots_per_epoch())?)? == 0 { - let historical_batch = state.historical_batch(); + let historical_batch = state.historical_batch()?; state .historical_roots_mut() .push(historical_batch.tree_hash_root())?; diff --git a/consensus/state_processing/src/per_epoch_processing/resets.rs b/consensus/state_processing/src/per_epoch_processing/resets.rs index d577c52e6a5..c9f69c3c95e 100644 --- a/consensus/state_processing/src/per_epoch_processing/resets.rs +++ b/consensus/state_processing/src/per_epoch_processing/resets.rs @@ -2,7 +2,7 @@ use super::errors::EpochProcessingError; use safe_arith::SafeArith; use types::beacon_state::BeaconState; use types::eth_spec::EthSpec; -use types::{Unsigned, VariableList}; +use types::{List, Unsigned}; pub fn process_eth1_data_reset( state: &mut BeaconState, @@ -13,7 +13,7 @@ pub fn process_eth1_data_reset( .safe_rem(E::SlotsPerEth1VotingPeriod::to_u64())? == 0 { - *state.eth1_data_votes_mut() = VariableList::empty(); + *state.eth1_data_votes_mut() = List::empty(); } Ok(()) } diff --git a/consensus/state_processing/src/per_epoch_processing/single_pass.rs b/consensus/state_processing/src/per_epoch_processing/single_pass.rs index 380484046c3..7a95de3317e 100644 --- a/consensus/state_processing/src/per_epoch_processing/single_pass.rs +++ b/consensus/state_processing/src/per_epoch_processing/single_pass.rs @@ -12,6 +12,7 @@ use types::{ NUM_FLAG_INDICES, PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX, WEIGHT_DENOMINATOR, }, + milhouse::Cow, ActivationQueue, BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, ExitCache, ForkName, ParticipationFlags, ProgressiveBalancesCache, RelativeEpoch, Unsigned, Validator, }; @@ -173,9 +174,9 @@ pub fn process_epoch_single_pass( let effective_balances_ctxt = &EffectiveBalancesContext::new(spec)?; // Iterate over the validators and related fields in one pass. - let mut validators_iter = validators.iter_mut(); - let mut balances_iter = balances.iter_mut(); - let mut inactivity_scores_iter = inactivity_scores.iter_mut(); + let mut validators_iter = validators.iter_cow(); + let mut balances_iter = balances.iter_cow(); + let mut inactivity_scores_iter = inactivity_scores.iter_cow(); // Values computed for the next epoch transition. let mut next_epoch_total_active_balance = 0; @@ -186,14 +187,14 @@ pub fn process_epoch_single_pass( previous_epoch_participation.iter(), current_epoch_participation.iter(), ) { - let validator = validators_iter - .next() + let (_, mut validator) = validators_iter + .next_cow() .ok_or(BeaconStateError::UnknownValidator(index))?; - let balance = balances_iter - .next() + let (_, mut balance) = balances_iter + .next_cow() .ok_or(BeaconStateError::UnknownValidator(index))?; - let inactivity_score = inactivity_scores_iter - .next() + let (_, mut inactivity_score) = inactivity_scores_iter + .next_cow() .ok_or(BeaconStateError::UnknownValidator(index))?; let is_active_current_epoch = validator.is_active_at(current_epoch); @@ -223,7 +224,7 @@ pub fn process_epoch_single_pass( // `process_inactivity_updates` if conf.inactivity_updates { process_single_inactivity_update( - inactivity_score, + &mut inactivity_score, validator_info, state_ctxt, spec, @@ -233,8 +234,8 @@ pub fn process_epoch_single_pass( // `process_rewards_and_penalties` if conf.rewards_and_penalties { process_single_reward_and_penalty( - balance, - inactivity_score, + &mut balance, + &inactivity_score, validator_info, rewards_ctxt, state_ctxt, @@ -246,7 +247,7 @@ pub fn process_epoch_single_pass( // `process_registry_updates` if conf.registry_updates { process_single_registry_update( - validator, + &mut validator, validator_info, exit_cache, activation_queue, @@ -258,14 +259,14 @@ pub fn process_epoch_single_pass( // `process_slashings` if conf.slashings { - process_single_slashing(balance, validator, slashings_ctxt, state_ctxt, spec)?; + process_single_slashing(&mut balance, &validator, slashings_ctxt, state_ctxt, spec)?; } // `process_effective_balance_updates` if conf.effective_balance_updates { process_single_effective_balance_update( *balance, - validator, + &mut validator, validator_info, &mut next_epoch_total_active_balance, &mut next_epoch_cache, @@ -290,7 +291,7 @@ pub fn process_epoch_single_pass( } fn process_single_inactivity_update( - inactivity_score: &mut u64, + inactivity_score: &mut Cow, validator_info: &ValidatorInfo, state_ctxt: &StateContext, spec: &ChainSpec, @@ -303,25 +304,27 @@ fn process_single_inactivity_update( if validator_info.is_unslashed_participating_index(TIMELY_TARGET_FLAG_INDEX)? { // Avoid mutating when the inactivity score is 0 and can't go any lower -- the common // case. - if *inactivity_score == 0 { + if **inactivity_score == 0 { return Ok(()); } - inactivity_score.safe_sub_assign(1)?; + inactivity_score.make_mut()?.safe_sub_assign(1)?; } else { - inactivity_score.safe_add_assign(spec.inactivity_score_bias)?; + inactivity_score + .make_mut()? + .safe_add_assign(spec.inactivity_score_bias)?; } // Decrease the score of all validators for forgiveness when not during a leak if !state_ctxt.is_in_inactivity_leak { - let deduction = min(spec.inactivity_score_recovery_rate, *inactivity_score); - inactivity_score.safe_sub_assign(deduction)?; + let deduction = min(spec.inactivity_score_recovery_rate, **inactivity_score); + inactivity_score.make_mut()?.safe_sub_assign(deduction)?; } Ok(()) } fn process_single_reward_and_penalty( - balance: &mut u64, + balance: &mut Cow, inactivity_score: &u64, validator_info: &ValidatorInfo, rewards_ctxt: &RewardsAndPenaltiesContext, @@ -351,6 +354,7 @@ fn process_single_reward_and_penalty( )?; if delta.rewards != 0 || delta.penalties != 0 { + let balance = balance.make_mut()?; balance.safe_add_assign(delta.rewards)?; *balance = balance.saturating_sub(delta.penalties); } @@ -452,7 +456,7 @@ impl RewardsAndPenaltiesContext { } fn process_single_registry_update( - validator: &mut Validator, + validator: &mut Cow, validator_info: &ValidatorInfo, exit_cache: &mut ExitCache, activation_queue: &BTreeSet, @@ -463,7 +467,7 @@ fn process_single_registry_update( let current_epoch = state_ctxt.current_epoch; if validator.is_eligible_for_activation_queue(spec) { - validator.activation_eligibility_epoch = current_epoch.safe_add(1)?; + validator.make_mut()?.activation_eligibility_epoch = current_epoch.safe_add(1)?; } if validator.is_active_at(current_epoch) && validator.effective_balance <= spec.ejection_balance @@ -472,7 +476,8 @@ fn process_single_registry_update( } if activation_queue.contains(&validator_info.index) { - validator.activation_epoch = spec.compute_activation_exit_epoch(current_epoch)?; + validator.make_mut()?.activation_epoch = + spec.compute_activation_exit_epoch(current_epoch)?; } // Caching: add to speculative activation queue for next epoch. @@ -487,7 +492,7 @@ fn process_single_registry_update( } fn initiate_validator_exit( - validator: &mut Validator, + validator: &mut Cow, exit_cache: &mut ExitCache, state_ctxt: &StateContext, spec: &ChainSpec, @@ -508,6 +513,7 @@ fn initiate_validator_exit( exit_queue_epoch.safe_add_assign(1)?; } + let validator = validator.make_mut()?; validator.exit_epoch = exit_queue_epoch; validator.withdrawable_epoch = exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?; @@ -540,7 +546,7 @@ impl SlashingsContext { } fn process_single_slashing( - balance: &mut u64, + balance: &mut Cow, validator: &Validator, slashings_ctxt: &SlashingsContext, state_ctxt: &StateContext, @@ -557,7 +563,7 @@ fn process_single_slashing( .safe_div(state_ctxt.total_active_balance)? .safe_mul(increment)?; - *balance = balance.saturating_sub(penalty); + *balance.make_mut()? = balance.saturating_sub(penalty); } Ok(()) } @@ -581,7 +587,7 @@ impl EffectiveBalancesContext { #[allow(clippy::too_many_arguments)] fn process_single_effective_balance_update( balance: u64, - validator: &mut Validator, + validator: &mut Cow, validator_info: &ValidatorInfo, next_epoch_total_active_balance: &mut u64, next_epoch_cache: &mut PreEpochCache, @@ -611,7 +617,7 @@ fn process_single_effective_balance_update( } if new_effective_balance != old_effective_balance { - validator.effective_balance = new_effective_balance; + validator.make_mut()?.effective_balance = new_effective_balance; // Update progressive balances cache for the *current* epoch, which will soon become the // previous epoch once the epoch transition completes. diff --git a/consensus/state_processing/src/upgrade/altair.rs b/consensus/state_processing/src/upgrade/altair.rs index cfbc6eba9e9..872560db3df 100644 --- a/consensus/state_processing/src/upgrade/altair.rs +++ b/consensus/state_processing/src/upgrade/altair.rs @@ -4,13 +4,13 @@ use std::mem; use std::sync::Arc; use types::{ BeaconState, BeaconStateAltair, BeaconStateError as Error, ChainSpec, EpochCache, EthSpec, - Fork, ParticipationFlags, PendingAttestation, RelativeEpoch, SyncCommittee, VariableList, + Fork, List, ParticipationFlags, PendingAttestation, RelativeEpoch, SyncCommittee, }; /// Translate the participation information from the epoch prior to the fork into Altair's format. pub fn translate_participation( state: &mut BeaconState, - pending_attestations: &VariableList, E::MaxPendingAttestations>, + pending_attestations: &List, E::MaxPendingAttestations>, spec: &ChainSpec, ) -> Result<(), Error> { // Previous epoch committee cache is required for `get_attesting_indices`. @@ -51,8 +51,8 @@ pub fn upgrade_to_altair( let pre = pre_state.as_base_mut()?; let default_epoch_participation = - VariableList::new(vec![ParticipationFlags::default(); pre.validators.len()])?; - let inactivity_scores = VariableList::new(vec![0; pre.validators.len()])?; + List::new(vec![ParticipationFlags::default(); pre.validators.len()])?; + let inactivity_scores = List::new(vec![0; pre.validators.len()])?; let temp_sync_committee = Arc::new(SyncCommittee::temporary()); @@ -108,7 +108,6 @@ pub fn upgrade_to_altair( exit_cache: mem::take(&mut pre.exit_cache), slashings_cache: mem::take(&mut pre.slashings_cache), epoch_cache: EpochCache::default(), - tree_hash_cache: mem::take(&mut pre.tree_hash_cache), }); // Fill in previous epoch participation from the pre state's pending attestations. diff --git a/consensus/state_processing/src/upgrade/capella.rs b/consensus/state_processing/src/upgrade/capella.rs index 87b40abebdd..51e29d10f3c 100644 --- a/consensus/state_processing/src/upgrade/capella.rs +++ b/consensus/state_processing/src/upgrade/capella.rs @@ -1,7 +1,7 @@ use std::mem; use types::{ BeaconState, BeaconStateCapella, BeaconStateError as Error, ChainSpec, EpochCache, EthSpec, - Fork, VariableList, + Fork, List, }; /// Transform a `Merge` state into an `Capella` state. @@ -61,7 +61,7 @@ pub fn upgrade_to_capella( // Capella next_withdrawal_index: 0, next_withdrawal_validator_index: 0, - historical_summaries: VariableList::default(), + historical_summaries: List::default(), // Caches total_active_balance: pre.total_active_balance, progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache), @@ -70,7 +70,6 @@ pub fn upgrade_to_capella( exit_cache: mem::take(&mut pre.exit_cache), slashings_cache: mem::take(&mut pre.slashings_cache), epoch_cache: EpochCache::default(), - tree_hash_cache: mem::take(&mut pre.tree_hash_cache), }); *pre_state = post; diff --git a/consensus/state_processing/src/upgrade/deneb.rs b/consensus/state_processing/src/upgrade/deneb.rs index 43fe5d9dc3d..c21e1361a5a 100644 --- a/consensus/state_processing/src/upgrade/deneb.rs +++ b/consensus/state_processing/src/upgrade/deneb.rs @@ -71,7 +71,6 @@ pub fn upgrade_to_deneb( exit_cache: mem::take(&mut pre.exit_cache), slashings_cache: mem::take(&mut pre.slashings_cache), epoch_cache: EpochCache::default(), - tree_hash_cache: mem::take(&mut pre.tree_hash_cache), }); *pre_state = post; diff --git a/consensus/state_processing/src/upgrade/electra.rs b/consensus/state_processing/src/upgrade/electra.rs index a37d0fc3beb..f64228f050b 100644 --- a/consensus/state_processing/src/upgrade/electra.rs +++ b/consensus/state_processing/src/upgrade/electra.rs @@ -70,7 +70,6 @@ pub fn upgrade_to_electra( exit_cache: mem::take(&mut pre.exit_cache), slashings_cache: mem::take(&mut pre.slashings_cache), epoch_cache: EpochCache::default(), - tree_hash_cache: mem::take(&mut pre.tree_hash_cache), }); *pre_state = post; diff --git a/consensus/state_processing/src/upgrade/merge.rs b/consensus/state_processing/src/upgrade/merge.rs index 147c97ac29e..02705743ceb 100644 --- a/consensus/state_processing/src/upgrade/merge.rs +++ b/consensus/state_processing/src/upgrade/merge.rs @@ -66,7 +66,6 @@ pub fn upgrade_to_bellatrix( exit_cache: mem::take(&mut pre.exit_cache), slashings_cache: mem::take(&mut pre.slashings_cache), epoch_cache: EpochCache::default(), - tree_hash_cache: mem::take(&mut pre.tree_hash_cache), }); *pre_state = post; diff --git a/consensus/types/Cargo.toml b/consensus/types/Cargo.toml index db15f53537e..4b7d9f2b98d 100644 --- a/consensus/types/Cargo.toml +++ b/consensus/types/Cargo.toml @@ -52,6 +52,8 @@ serde_json = { workspace = true } smallvec = { workspace = true } maplit = { workspace = true } strum = { workspace = true } +milhouse = { workspace = true } +rpds = { workspace = true } [dev-dependencies] criterion = { workspace = true } @@ -68,4 +70,4 @@ sqlite = [] # The `arbitrary-fuzz` feature is a no-op provided for backwards compatibility. # For simplicity `Arbitrary` is now derived regardless of the feature's presence. arbitrary-fuzz = [] -portable = ["bls/supranational-portable"] \ No newline at end of file +portable = ["bls/supranational-portable"] diff --git a/consensus/types/benches/benches.rs b/consensus/types/benches/benches.rs index bb2b527109f..5c1036a4c5a 100644 --- a/consensus/types/benches/benches.rs +++ b/consensus/types/benches/benches.rs @@ -2,6 +2,7 @@ use criterion::Criterion; use criterion::{black_box, criterion_group, criterion_main, Benchmark}; +use milhouse::List; use rayon::prelude::*; use ssz::Encode; use std::sync::Arc; @@ -27,21 +28,23 @@ fn get_state(validator_count: usize) -> BeaconState { .expect("should add balance"); } - *state.validators_mut() = (0..validator_count) - .collect::>() - .par_iter() - .map(|&i| Validator { - pubkey: generate_deterministic_keypair(i).pk.into(), - withdrawal_credentials: Hash256::from_low_u64_le(i as u64), - effective_balance: spec.max_effective_balance, - slashed: false, - activation_eligibility_epoch: Epoch::new(0), - activation_epoch: Epoch::new(0), - exit_epoch: Epoch::from(u64::max_value()), - withdrawable_epoch: Epoch::from(u64::max_value()), - }) - .collect::>() - .into(); + *state.validators_mut() = List::new( + (0..validator_count) + .collect::>() + .par_iter() + .map(|&i| Validator { + pubkey: generate_deterministic_keypair(i).pk.compress(), + withdrawal_credentials: Hash256::from_low_u64_le(i as u64), + effective_balance: spec.max_effective_balance, + slashed: false, + activation_eligibility_epoch: Epoch::new(0), + activation_epoch: Epoch::new(0), + exit_epoch: Epoch::from(u64::max_value()), + withdrawable_epoch: Epoch::from(u64::max_value()), + }) + .collect(), + ) + .unwrap(); state } @@ -96,19 +99,6 @@ fn all_benches(c: &mut Criterion) { .sample_size(10), ); - let inner_state = state.clone(); - c.bench( - &format!("{}_validators", validator_count), - Benchmark::new("clone/tree_hash_cache", move |b| { - b.iter_batched_ref( - || inner_state.clone(), - |state| black_box(state.tree_hash_cache().clone()), - criterion::BatchSize::SmallInput, - ) - }) - .sample_size(10), - ); - let inner_state = state.clone(); c.bench( &format!("{}_validators", validator_count), diff --git a/consensus/types/examples/clone_state.rs b/consensus/types/examples/clone_state.rs deleted file mode 100644 index a7e80cf4078..00000000000 --- a/consensus/types/examples/clone_state.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! These examples only really exist so we can use them for flamegraph. If they get annoying to -//! maintain, feel free to delete. - -use types::{ - test_utils::generate_deterministic_keypair, BeaconState, Eth1Data, EthSpec, Hash256, - MinimalEthSpec, Validator, -}; - -type E = MinimalEthSpec; - -fn get_state(validator_count: usize) -> BeaconState { - let spec = &E::default_spec(); - let eth1_data = Eth1Data { - deposit_root: Hash256::zero(), - deposit_count: 0, - block_hash: Hash256::zero(), - }; - - let mut state = BeaconState::new(0, eth1_data, spec); - - for i in 0..validator_count { - state - .balances_mut() - .push(i as u64) - .expect("should add balance"); - state - .validators_mut() - .push(Validator { - pubkey: generate_deterministic_keypair(i).pk.into(), - withdrawal_credentials: Hash256::from_low_u64_le(i as u64), - effective_balance: i as u64, - slashed: i % 2 == 0, - activation_eligibility_epoch: i.into(), - activation_epoch: i.into(), - exit_epoch: i.into(), - withdrawable_epoch: i.into(), - }) - .expect("should add validator"); - } - - state -} - -fn main() { - let validator_count = 1_024; - let state = get_state(validator_count); - - for _ in 0..100_000 { - let _ = state.clone(); - } -} diff --git a/consensus/types/examples/ssz_encode_state.rs b/consensus/types/examples/ssz_encode_state.rs deleted file mode 100644 index 5d0a2db17c7..00000000000 --- a/consensus/types/examples/ssz_encode_state.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! These examples only really exist so we can use them for flamegraph. If they get annoying to -//! maintain, feel free to delete. - -use ssz::Encode; -use types::{ - test_utils::generate_deterministic_keypair, BeaconState, Eth1Data, EthSpec, Hash256, - MinimalEthSpec, Validator, -}; - -type E = MinimalEthSpec; - -fn get_state(validator_count: usize) -> BeaconState { - let spec = &E::default_spec(); - let eth1_data = Eth1Data { - deposit_root: Hash256::zero(), - deposit_count: 0, - block_hash: Hash256::zero(), - }; - - let mut state = BeaconState::new(0, eth1_data, spec); - - for i in 0..validator_count { - state - .balances_mut() - .push(i as u64) - .expect("should add balance"); - state - .validators_mut() - .push(Validator { - pubkey: generate_deterministic_keypair(i).pk.into(), - withdrawal_credentials: Hash256::from_low_u64_le(i as u64), - effective_balance: i as u64, - slashed: i % 2 == 0, - activation_eligibility_epoch: i.into(), - activation_epoch: i.into(), - exit_epoch: i.into(), - withdrawable_epoch: i.into(), - }) - .expect("should add validator"); - } - - state -} - -fn main() { - let validator_count = 1_024; - let state = get_state(validator_count); - - for _ in 0..1_024 { - let state_bytes = state.as_ssz_bytes(); - let _: BeaconState = - BeaconState::from_ssz_bytes(&state_bytes, &E::default_spec()).expect("should decode"); - } -} diff --git a/consensus/types/examples/tree_hash_state.rs b/consensus/types/examples/tree_hash_state.rs deleted file mode 100644 index 26777b25912..00000000000 --- a/consensus/types/examples/tree_hash_state.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! These examples only really exist so we can use them for flamegraph. If they get annoying to -//! maintain, feel free to delete. - -use types::{ - test_utils::generate_deterministic_keypair, BeaconState, Eth1Data, EthSpec, Hash256, - MinimalEthSpec, Validator, -}; - -type E = MinimalEthSpec; - -fn get_state(validator_count: usize) -> BeaconState { - let spec = &E::default_spec(); - let eth1_data = Eth1Data { - deposit_root: Hash256::zero(), - deposit_count: 0, - block_hash: Hash256::zero(), - }; - - let mut state = BeaconState::new(0, eth1_data, spec); - - for i in 0..validator_count { - state - .balances_mut() - .push(i as u64) - .expect("should add balance"); - state - .validators_mut() - .push(Validator { - pubkey: generate_deterministic_keypair(i).pk.into(), - withdrawal_credentials: Hash256::from_low_u64_le(i as u64), - effective_balance: i as u64, - slashed: i % 2 == 0, - activation_eligibility_epoch: i.into(), - activation_epoch: i.into(), - exit_epoch: i.into(), - withdrawable_epoch: i.into(), - }) - .expect("should add validator"); - } - - state -} - -fn main() { - let validator_count = 1_024; - let mut state = get_state(validator_count); - state.update_tree_hash_cache().expect("should update cache"); - - actual_thing::(&mut state); -} - -fn actual_thing(state: &mut BeaconState) { - for _ in 0..200_024 { - let _ = state.update_tree_hash_cache().expect("should update cache"); - } -} diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 02572b0efbd..5da81f6a752 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -7,6 +7,7 @@ use compare_fields_derive::CompareFields; use derivative::Derivative; use ethereum_hashing::hash; use int_to_bytes::{int_to_bytes4, int_to_bytes8}; +use metastruct::{metastruct, NumFields}; pub use pubkey_cache::PubkeyCache; use safe_arith::{ArithError, SafeArith}; use serde::{Deserialize, Serialize}; @@ -28,28 +29,25 @@ pub use crate::beacon_state::balance::Balance; pub use crate::beacon_state::exit_cache::ExitCache; pub use crate::beacon_state::progressive_balances_cache::*; pub use crate::beacon_state::slashings_cache::SlashingsCache; -pub use clone_config::CloneConfig; pub use eth_spec::*; pub use iter::BlockRootsIter; -pub use tree_hash_cache::BeaconTreeHashCache; +pub use milhouse::{interface::Interface, List, Vector}; #[macro_use] mod committee_cache; mod balance; -mod clone_config; mod exit_cache; mod iter; mod progressive_balances_cache; mod pubkey_cache; mod slashings_cache; mod tests; -mod tree_hash_cache; pub const CACHED_EPOCHS: usize = 3; const MAX_RANDOM_BYTE: u64 = (1 << 8) - 1; -pub type Validators = VariableList::ValidatorRegistryLimit>; -pub type Balances = VariableList::ValidatorRegistryLimit>; +pub type Validators = List::ValidatorRegistryLimit>; +pub type Balances = List::ValidatorRegistryLimit>; #[derive(Debug, PartialEq, Clone)] pub enum Error { @@ -144,6 +142,20 @@ pub enum Error { current_epoch: Epoch, epoch: Epoch, }, + MilhouseError(milhouse::Error), + CommitteeCacheDiffInvalidEpoch { + prev_current_epoch: Epoch, + current_epoch: Epoch, + }, + CommitteeCacheDiffUninitialized { + expected_epoch: Epoch, + }, + DiffAcrossFork { + prev_fork: ForkName, + current_fork: ForkName, + }, + TotalActiveBalanceDiffUninitialized, + MissingImmutableValidator(usize), IndexNotSupported(usize), InvalidFlagIndex(usize), MerkleTreeError(merkle_proof::MerkleTreeError), @@ -207,16 +219,105 @@ impl From for Hash256 { TreeHash, TestRandom, CompareFields, - arbitrary::Arbitrary + arbitrary::Arbitrary, ), serde(bound = "E: EthSpec", deny_unknown_fields), arbitrary(bound = "E: EthSpec"), derivative(Clone), ), + specific_variant_attributes( + Base(metastruct( + mappings( + map_beacon_state_base_fields(), + map_beacon_state_base_tree_list_fields(mutable, fallible, groups(tree_lists)), + map_beacon_state_base_tree_list_fields_immutable(groups(tree_lists)), + ), + bimappings(bimap_beacon_state_base_tree_list_fields( + other_type = "BeaconStateBase", + self_mutable, + fallible, + groups(tree_lists) + )), + num_fields(all()), + )), + Altair(metastruct( + mappings( + map_beacon_state_altair_fields(), + map_beacon_state_altair_tree_list_fields(mutable, fallible, groups(tree_lists)), + map_beacon_state_altair_tree_list_fields_immutable(groups(tree_lists)), + ), + bimappings(bimap_beacon_state_altair_tree_list_fields( + other_type = "BeaconStateAltair", + self_mutable, + fallible, + groups(tree_lists) + )), + num_fields(all()), + )), + Merge(metastruct( + mappings( + map_beacon_state_bellatrix_fields(), + map_beacon_state_bellatrix_tree_list_fields(mutable, fallible, groups(tree_lists)), + map_beacon_state_bellatrix_tree_list_fields_immutable(groups(tree_lists)), + ), + bimappings(bimap_beacon_state_bellatrix_tree_list_fields( + other_type = "BeaconStateMerge", + self_mutable, + fallible, + groups(tree_lists) + )), + num_fields(all()), + )), + Capella(metastruct( + mappings( + map_beacon_state_capella_fields(), + map_beacon_state_capella_tree_list_fields(mutable, fallible, groups(tree_lists)), + map_beacon_state_capella_tree_list_fields_immutable(groups(tree_lists)), + ), + bimappings(bimap_beacon_state_capella_tree_list_fields( + other_type = "BeaconStateCapella", + self_mutable, + fallible, + groups(tree_lists) + )), + num_fields(all()), + )), + Deneb(metastruct( + mappings( + map_beacon_state_deneb_fields(), + map_beacon_state_deneb_tree_list_fields(mutable, fallible, groups(tree_lists)), + map_beacon_state_deneb_tree_list_fields_immutable(groups(tree_lists)), + ), + bimappings(bimap_beacon_state_deneb_tree_list_fields( + other_type = "BeaconStateDeneb", + self_mutable, + fallible, + groups(tree_lists) + )), + num_fields(all()), + )), + Electra(metastruct( + mappings( + map_beacon_state_electra_fields(), + map_beacon_state_electra_tree_list_fields(mutable, fallible, groups(tree_lists)), + map_beacon_state_electra_tree_list_fields_immutable(groups(tree_lists)), + ), + bimappings(bimap_beacon_state_electra_tree_list_fields( + other_type = "BeaconStateElectra", + self_mutable, + fallible, + groups(tree_lists) + )), + num_fields(all()), + )) + ), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), - partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") + partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant"), + map_ref_mut_into(BeaconStateRef) +)] +#[derive( + Debug, PartialEq, Clone, Serialize, Deserialize, Encode, TreeHash, arbitrary::Arbitrary, )] -#[derive(Debug, PartialEq, Serialize, Deserialize, Encode, TreeHash, arbitrary::Arbitrary)] #[serde(untagged)] #[serde(bound = "E: EthSpec")] #[arbitrary(bound = "E: EthSpec")] @@ -228,76 +329,102 @@ where { // Versioning #[superstruct(getter(copy))] + #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub genesis_time: u64, #[superstruct(getter(copy))] + #[metastruct(exclude_from(tree_lists))] pub genesis_validators_root: Hash256, #[superstruct(getter(copy))] + #[metastruct(exclude_from(tree_lists))] pub slot: Slot, #[superstruct(getter(copy))] + #[metastruct(exclude_from(tree_lists))] pub fork: Fork, // History + #[metastruct(exclude_from(tree_lists))] pub latest_block_header: BeaconBlockHeader, - #[compare_fields(as_slice)] - pub block_roots: FixedVector, - #[compare_fields(as_slice)] - pub state_roots: FixedVector, + #[test_random(default)] + #[compare_fields(as_iter)] + pub block_roots: Vector, + #[test_random(default)] + #[compare_fields(as_iter)] + pub state_roots: Vector, // Frozen in Capella, replaced by historical_summaries - pub historical_roots: VariableList, + #[test_random(default)] + #[compare_fields(as_iter)] + pub historical_roots: List, // Ethereum 1.0 chain data + #[metastruct(exclude_from(tree_lists))] pub eth1_data: Eth1Data, - pub eth1_data_votes: VariableList, + #[test_random(default)] + pub eth1_data_votes: List, #[superstruct(getter(copy))] + #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] pub eth1_deposit_index: u64, // Registry - #[compare_fields(as_slice)] - pub validators: VariableList, - #[compare_fields(as_slice)] + #[test_random(default)] + pub validators: List, #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] - pub balances: VariableList, + #[compare_fields(as_iter)] + #[test_random(default)] + pub balances: List, // Randomness - pub randao_mixes: FixedVector, + #[test_random(default)] + pub randao_mixes: Vector, // Slashings + #[test_random(default)] #[serde(with = "ssz_types::serde_utils::quoted_u64_fixed_vec")] - pub slashings: FixedVector, + pub slashings: Vector, // Attestations (genesis fork only) #[superstruct(only(Base))] - pub previous_epoch_attestations: VariableList, E::MaxPendingAttestations>, + #[test_random(default)] + pub previous_epoch_attestations: List, E::MaxPendingAttestations>, #[superstruct(only(Base))] - pub current_epoch_attestations: VariableList, E::MaxPendingAttestations>, + #[test_random(default)] + pub current_epoch_attestations: List, E::MaxPendingAttestations>, // Participation (Altair and later) #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] - pub previous_epoch_participation: VariableList, + #[test_random(default)] + pub previous_epoch_participation: List, #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] - pub current_epoch_participation: VariableList, + #[test_random(default)] + pub current_epoch_participation: List, // Finality #[test_random(default)] + #[metastruct(exclude_from(tree_lists))] pub justification_bits: BitVector, #[superstruct(getter(copy))] + #[metastruct(exclude_from(tree_lists))] pub previous_justified_checkpoint: Checkpoint, #[superstruct(getter(copy))] + #[metastruct(exclude_from(tree_lists))] pub current_justified_checkpoint: Checkpoint, #[superstruct(getter(copy))] + #[metastruct(exclude_from(tree_lists))] pub finalized_checkpoint: Checkpoint, // Inactivity #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] - pub inactivity_scores: VariableList, + #[test_random(default)] + pub inactivity_scores: List, // Light-client sync committees #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] + #[metastruct(exclude_from(tree_lists))] pub current_sync_committee: Arc>, #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] + #[metastruct(exclude_from(tree_lists))] pub next_sync_committee: Arc>, // Execution @@ -305,89 +432,85 @@ where only(Merge), partial_getter(rename = "latest_execution_payload_header_merge") )] + #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_header: ExecutionPayloadHeaderMerge, #[superstruct( only(Capella), partial_getter(rename = "latest_execution_payload_header_capella") )] + #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_header: ExecutionPayloadHeaderCapella, #[superstruct( only(Deneb), partial_getter(rename = "latest_execution_payload_header_deneb") )] + #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_header: ExecutionPayloadHeaderDeneb, #[superstruct( only(Electra), partial_getter(rename = "latest_execution_payload_header_electra") )] + #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_header: ExecutionPayloadHeaderElectra, // Capella #[superstruct(only(Capella, Deneb, Electra), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] + #[metastruct(exclude_from(tree_lists))] pub next_withdrawal_index: u64, #[superstruct(only(Capella, Deneb, Electra), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] + #[metastruct(exclude_from(tree_lists))] pub next_withdrawal_validator_index: u64, // Deep history valid from Capella onwards. #[superstruct(only(Capella, Deneb, Electra))] - pub historical_summaries: VariableList, + #[test_random(default)] + pub historical_summaries: List, // Caching (not in the spec) #[serde(skip_serializing, skip_deserializing)] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] #[test_random(default)] - #[derivative(Clone(clone_with = "clone_default"))] + #[metastruct(exclude)] pub total_active_balance: Option<(Epoch, u64)>, #[serde(skip_serializing, skip_deserializing)] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] #[test_random(default)] - #[derivative(Clone(clone_with = "clone_default"))] - pub progressive_balances_cache: ProgressiveBalancesCache, + #[metastruct(exclude)] + pub committee_caches: [Arc; CACHED_EPOCHS], #[serde(skip_serializing, skip_deserializing)] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] #[test_random(default)] - #[derivative(Clone(clone_with = "clone_default"))] - pub committee_caches: [CommitteeCache; CACHED_EPOCHS], + #[metastruct(exclude)] + pub progressive_balances_cache: ProgressiveBalancesCache, #[serde(skip_serializing, skip_deserializing)] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] #[test_random(default)] - #[derivative(Clone(clone_with = "clone_default"))] + #[metastruct(exclude)] pub pubkey_cache: PubkeyCache, #[serde(skip_serializing, skip_deserializing)] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] #[test_random(default)] - #[derivative(Clone(clone_with = "clone_default"))] + #[metastruct(exclude)] pub exit_cache: ExitCache, #[serde(skip_serializing, skip_deserializing)] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] #[test_random(default)] - #[derivative(Clone(clone_with = "clone_default"))] + #[metastruct(exclude)] pub slashings_cache: SlashingsCache, /// Epoch cache of values that are useful for block processing that are static over an epoch. #[serde(skip_serializing, skip_deserializing)] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] #[test_random(default)] + #[metastruct(exclude)] pub epoch_cache: EpochCache, - #[serde(skip_serializing, skip_deserializing)] - #[ssz(skip_serializing, skip_deserializing)] - #[tree_hash(skip_hashing)] - #[test_random(default)] - #[derivative(Clone(clone_with = "clone_default"))] - pub tree_hash_cache: BeaconTreeHashCache, -} - -impl Clone for BeaconState { - fn clone(&self) -> Self { - self.clone_with(CloneConfig::all()) - } } impl BeaconState { @@ -395,6 +518,7 @@ impl BeaconState { /// /// Not a complete genesis state, see `initialize_beacon_state_from_eth1`. pub fn new(genesis_time: u64, eth1_data: Eth1Data, spec: &ChainSpec) -> Self { + let default_committee_cache = Arc::new(CommitteeCache::default()); BeaconState::Base(BeaconStateBase { // Versioning genesis_time, @@ -408,28 +532,28 @@ impl BeaconState { // History latest_block_header: BeaconBlock::::empty(spec).temporary_block_header(), - block_roots: FixedVector::from_elem(Hash256::zero()), - state_roots: FixedVector::from_elem(Hash256::zero()), - historical_roots: VariableList::empty(), + block_roots: Vector::default(), + state_roots: Vector::default(), + historical_roots: List::default(), // Eth1 eth1_data, - eth1_data_votes: VariableList::empty(), + eth1_data_votes: List::default(), eth1_deposit_index: 0, // Validator registry - validators: VariableList::empty(), // Set later. - balances: VariableList::empty(), // Set later. + validators: List::default(), // Set later. + balances: List::default(), // Set later. // Randomness - randao_mixes: FixedVector::from_elem(Hash256::zero()), + randao_mixes: Vector::default(), // Slashings - slashings: FixedVector::from_elem(0), + slashings: Vector::default(), // Attestations - previous_epoch_attestations: VariableList::empty(), - current_epoch_attestations: VariableList::empty(), + previous_epoch_attestations: List::default(), + current_epoch_attestations: List::default(), // Finality justification_bits: BitVector::new(), @@ -441,15 +565,14 @@ impl BeaconState { total_active_balance: None, progressive_balances_cache: <_>::default(), committee_caches: [ - CommitteeCache::default(), - CommitteeCache::default(), - CommitteeCache::default(), + default_committee_cache.clone(), + default_committee_cache.clone(), + default_committee_cache, ], pubkey_cache: PubkeyCache::default(), exit_cache: ExitCache::default(), slashings_cache: SlashingsCache::default(), epoch_cache: EpochCache::default(), - tree_hash_cache: <_>::default(), }) } @@ -485,30 +608,6 @@ impl BeaconState { } } - /// Specialised deserialisation method that uses the `ChainSpec` as context. - #[allow(clippy::arithmetic_side_effects)] - pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result { - // Slot is after genesis_time (u64) and genesis_validators_root (Hash256). - let slot_start = ::ssz_fixed_len() + ::ssz_fixed_len(); - let slot_end = slot_start + ::ssz_fixed_len(); - - let slot_bytes = bytes - .get(slot_start..slot_end) - .ok_or(DecodeError::InvalidByteLength { - len: bytes.len(), - expected: slot_end, - })?; - - let slot = Slot::from_ssz_bytes(slot_bytes)?; - let fork_at_slot = spec.fork_name_at_slot::(slot); - - Ok(map_fork_name!( - fork_at_slot, - Self, - <_>::from_ssz_bytes(bytes)? - )) - } - /// Returns the `tree_hash_root` of the state. /// /// Spec v0.12.1 @@ -516,11 +615,15 @@ impl BeaconState { Hash256::from_slice(&self.tree_hash_root()[..]) } - pub fn historical_batch(&self) -> HistoricalBatch { - HistoricalBatch { + pub fn historical_batch(&mut self) -> Result, Error> { + // Updating before cloning makes the clone cheap and saves repeated hashing. + self.block_roots_mut().apply_updates()?; + self.state_roots_mut().apply_updates()?; + + Ok(HistoricalBatch { block_roots: self.block_roots().clone(), state_roots: self.state_roots().clone(), - } + }) } /// This method ensures the state's pubkey cache is fully up-to-date before checking if the validator @@ -1079,8 +1182,9 @@ impl BeaconState { } /// Fill `randao_mixes` with - pub fn fill_randao_mixes_with(&mut self, index_root: Hash256) { - *self.randao_mixes_mut() = FixedVector::from_elem(index_root); + pub fn fill_randao_mixes_with(&mut self, index_root: Hash256) -> Result<(), Error> { + *self.randao_mixes_mut() = Vector::from_elem(index_root)?; + Ok(()) } /// Safely obtains the index for `randao_mixes` @@ -1213,7 +1317,7 @@ impl BeaconState { } /// Get a reference to the entire `slashings` vector. - pub fn get_all_slashings(&self) -> &[u64] { + pub fn get_all_slashings(&self) -> &Vector { self.slashings() } @@ -1237,45 +1341,25 @@ impl BeaconState { } /// Convenience accessor for validators and balances simultaneously. - pub fn validators_and_balances_and_progressive_balances_mut( - &mut self, + pub fn validators_and_balances_and_progressive_balances_mut<'a>( + &'a mut self, ) -> ( - &mut Validators, - &mut Balances, - &mut ProgressiveBalancesCache, + &'a mut Validators, + &'a mut Balances, + &'a mut ProgressiveBalancesCache, ) { - match self { - BeaconState::Base(state) => ( - &mut state.validators, - &mut state.balances, - &mut state.progressive_balances_cache, - ), - BeaconState::Altair(state) => ( - &mut state.validators, - &mut state.balances, - &mut state.progressive_balances_cache, - ), - BeaconState::Merge(state) => ( - &mut state.validators, - &mut state.balances, - &mut state.progressive_balances_cache, - ), - BeaconState::Capella(state) => ( - &mut state.validators, - &mut state.balances, - &mut state.progressive_balances_cache, - ), - BeaconState::Deneb(state) => ( - &mut state.validators, - &mut state.balances, - &mut state.progressive_balances_cache, - ), - BeaconState::Electra(state) => ( - &mut state.validators, - &mut state.balances, - &mut state.progressive_balances_cache, - ), - } + map_beacon_state_ref_mut_into_beacon_state_ref!(&'a _, self.to_mut(), |inner, cons| { + if false { + cons(&*inner); + unreachable!() + } else { + ( + &mut inner.validators, + &mut inner.balances, + &mut inner.progressive_balances_cache, + ) + } + }) } #[allow(clippy::type_complexity)] @@ -1285,9 +1369,9 @@ impl BeaconState { ( &mut Validators, &mut Balances, - &VariableList, - &VariableList, - &mut VariableList, + &List, + &List, + &mut List, &mut ProgressiveBalancesCache, &mut ExitCache, &mut EpochCache, @@ -1349,6 +1433,13 @@ impl BeaconState { } } + /// Get a mutable reference to the balance of a single validator. + pub fn get_balance_mut(&mut self, validator_index: usize) -> Result<&mut u64, Error> { + self.balances_mut() + .get_mut(validator_index) + .ok_or(Error::BalancesOutOfBounds(validator_index)) + } + /// Generate a seed for the given `epoch`. pub fn get_seed( &self, @@ -1398,6 +1489,16 @@ impl BeaconState { .ok_or(Error::UnknownValidator(validator_index)) } + /// Safe copy-on-write accessor for the `validators` list. + pub fn get_validator_cow( + &mut self, + validator_index: usize, + ) -> Result, Error> { + self.validators_mut() + .get_cow(validator_index) + .ok_or(Error::UnknownValidator(validator_index)) + } + /// Return the effective balance for a validator with the given `validator_index`. pub fn get_effective_balance(&self, validator_index: usize) -> Result { self.get_validator(validator_index) @@ -1423,13 +1524,6 @@ impl BeaconState { .ok_or(Error::InactivityScoresOutOfBounds(validator_index)) } - /// Get a mutable reference to the balance of a single validator. - pub fn get_balance_mut(&mut self, validator_index: usize) -> Result<&mut u64, Error> { - self.balances_mut() - .get_mut(validator_index) - .ok_or(Error::BalancesOutOfBounds(validator_index)) - } - /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. /// /// Spec v0.12.1 @@ -1575,7 +1669,7 @@ impl BeaconState { epoch: Epoch, previous_epoch: Epoch, current_epoch: Epoch, - ) -> Result<&mut VariableList, Error> { + ) -> Result<&mut List, Error> { if epoch == current_epoch { match self { BeaconState::Base(_) => Err(BeaconStateError::IncorrectStateVariant), @@ -1659,7 +1753,6 @@ impl BeaconState { self.drop_committee_cache(RelativeEpoch::Current)?; self.drop_committee_cache(RelativeEpoch::Next)?; self.drop_pubkey_cache(); - self.drop_tree_hash_cache(); self.drop_progressive_balances_cache(); *self.exit_cache_mut() = ExitCache::default(); *self.slashings_cache_mut() = SlashingsCache::default(); @@ -1718,7 +1811,7 @@ impl BeaconState { &self, epoch: Epoch, spec: &ChainSpec, - ) -> Result { + ) -> Result, Error> { CommitteeCache::initialized(self, epoch, spec) } @@ -1732,7 +1825,7 @@ impl BeaconState { self.committee_caches_mut().rotate_left(1); let next = Self::committee_cache_index(RelativeEpoch::Next); - *self.committee_cache_at_index_mut(next)? = CommitteeCache::default(); + *self.committee_cache_at_index_mut(next)? = Arc::new(CommitteeCache::default()); Ok(()) } @@ -1747,21 +1840,24 @@ impl BeaconState { /// Get the committee cache for some `slot`. /// /// Return an error if the cache for the slot's epoch is not initialized. - fn committee_cache_at_slot(&self, slot: Slot) -> Result<&CommitteeCache, Error> { + fn committee_cache_at_slot(&self, slot: Slot) -> Result<&Arc, Error> { let epoch = slot.epoch(E::slots_per_epoch()); let relative_epoch = RelativeEpoch::from_epoch(self.current_epoch(), epoch)?; self.committee_cache(relative_epoch) } /// Get the committee cache at a given index. - fn committee_cache_at_index(&self, index: usize) -> Result<&CommitteeCache, Error> { + fn committee_cache_at_index(&self, index: usize) -> Result<&Arc, Error> { self.committee_caches() .get(index) .ok_or(Error::CommitteeCachesOutOfBounds(index)) } /// Get a mutable reference to the committee cache at a given index. - fn committee_cache_at_index_mut(&mut self, index: usize) -> Result<&mut CommitteeCache, Error> { + fn committee_cache_at_index_mut( + &mut self, + index: usize, + ) -> Result<&mut Arc, Error> { self.committee_caches_mut() .get_mut(index) .ok_or(Error::CommitteeCachesOutOfBounds(index)) @@ -1769,7 +1865,10 @@ impl BeaconState { /// Returns the cache for some `RelativeEpoch`. Returns an error if the cache has not been /// initialized. - pub fn committee_cache(&self, relative_epoch: RelativeEpoch) -> Result<&CommitteeCache, Error> { + pub fn committee_cache( + &self, + relative_epoch: RelativeEpoch, + ) -> Result<&Arc, Error> { let i = Self::committee_cache_index(relative_epoch); let cache = self.committee_cache_at_index(i)?; @@ -1780,30 +1879,10 @@ impl BeaconState { } } - /// Returns the cache for some `RelativeEpoch`, replacing the existing cache with an - /// un-initialized cache. Returns an error if the existing cache has not been initialized. - pub fn take_committee_cache( - &mut self, - relative_epoch: RelativeEpoch, - ) -> Result { - let i = Self::committee_cache_index(relative_epoch); - let current_epoch = self.current_epoch(); - let cache = self - .committee_caches_mut() - .get_mut(i) - .ok_or(Error::CommitteeCachesOutOfBounds(i))?; - - if cache.is_initialized_at(relative_epoch.into_epoch(current_epoch)) { - Ok(mem::take(cache)) - } else { - Err(Error::CommitteeCacheUninitialized(Some(relative_epoch))) - } - } - /// Drops the cache, leaving it in an uninitialized state. pub fn drop_committee_cache(&mut self, relative_epoch: RelativeEpoch) -> Result<(), Error> { *self.committee_cache_at_index_mut(Self::committee_cache_index(relative_epoch))? = - CommitteeCache::default(); + Arc::new(CommitteeCache::default()); Ok(()) } @@ -1813,13 +1892,11 @@ impl BeaconState { /// never re-add a pubkey. pub fn update_pubkey_cache(&mut self) -> Result<(), Error> { let mut pubkey_cache = mem::take(self.pubkey_cache_mut()); - for (i, validator) in self - .validators() - .iter() - .enumerate() - .skip(pubkey_cache.len()) - { - let success = pubkey_cache.insert(validator.pubkey, i); + let start_index = pubkey_cache.len(); + + for (i, validator) in self.validators().iter_from(start_index)?.enumerate() { + let index = start_index.safe_add(i)?; + let success = pubkey_cache.insert(validator.pubkey, index); if !success { return Err(Error::PubkeyCacheInconsistent); } @@ -1834,96 +1911,71 @@ impl BeaconState { *self.pubkey_cache_mut() = PubkeyCache::default() } + pub fn has_pending_mutations(&self) -> bool { + let mut any_pending_mutations = false; + match &self { + Self::Base(self_inner) => { + map_beacon_state_base_tree_list_fields_immutable!(self_inner, |_, self_field| { + any_pending_mutations |= self_field.has_pending_updates(); + }); + } + Self::Altair(self_inner) => { + map_beacon_state_altair_tree_list_fields_immutable!(self_inner, |_, self_field| { + any_pending_mutations |= self_field.has_pending_updates(); + }); + } + Self::Merge(self_inner) => { + map_beacon_state_bellatrix_tree_list_fields_immutable!( + self_inner, + |_, self_field| { + any_pending_mutations |= self_field.has_pending_updates(); + } + ); + } + Self::Capella(self_inner) => { + map_beacon_state_capella_tree_list_fields_immutable!( + self_inner, + |_, self_field| { + any_pending_mutations |= self_field.has_pending_updates(); + } + ); + } + Self::Deneb(self_inner) => { + map_beacon_state_deneb_tree_list_fields_immutable!(self_inner, |_, self_field| { + any_pending_mutations |= self_field.has_pending_updates(); + }); + } + Self::Electra(self_inner) => { + map_beacon_state_electra_tree_list_fields_immutable!( + self_inner, + |_, self_field| { + any_pending_mutations |= self_field.has_pending_updates(); + } + ); + } + }; + any_pending_mutations + } + /// Completely drops the `progressive_balances_cache` cache, replacing it with a new, empty cache. fn drop_progressive_balances_cache(&mut self) { *self.progressive_balances_cache_mut() = ProgressiveBalancesCache::default(); } - /// Initialize but don't fill the tree hash cache, if it isn't already initialized. - pub fn initialize_tree_hash_cache(&mut self) { - if !self.tree_hash_cache().is_initialized() { - *self.tree_hash_cache_mut() = BeaconTreeHashCache::new(self) - } - } - /// Compute the tree hash root of the state using the tree hash cache. /// /// Initialize the tree hash cache if it isn't already initialized. pub fn update_tree_hash_cache(&mut self) -> Result { - self.initialize_tree_hash_cache(); - - let cache = self.tree_hash_cache_mut().take(); - - if let Some(mut cache) = cache { - // Note: we return early if the tree hash fails, leaving `self.tree_hash_cache` as - // None. There's no need to keep a cache that fails. - let root = cache.recalculate_tree_hash_root(self)?; - self.tree_hash_cache_mut().restore(cache); - Ok(root) - } else { - Err(Error::TreeHashCacheNotInitialized) - } + self.apply_pending_mutations()?; + Ok(self.tree_hash_root()) } /// Compute the tree hash root of the validators using the tree hash cache. /// /// Initialize the tree hash cache if it isn't already initialized. pub fn update_validators_tree_hash_cache(&mut self) -> Result { - self.initialize_tree_hash_cache(); - - let cache = self.tree_hash_cache_mut().take(); - - if let Some(mut cache) = cache { - // Note: we return early if the tree hash fails, leaving `self.tree_hash_cache` as - // None. There's no need to keep a cache that fails. - let root = cache.recalculate_validators_tree_hash_root(self.validators())?; - self.tree_hash_cache_mut().restore(cache); - Ok(root) - } else { - Err(Error::TreeHashCacheNotInitialized) - } - } - - /// Completely drops the tree hash cache, replacing it with a new, empty cache. - pub fn drop_tree_hash_cache(&mut self) { - self.tree_hash_cache_mut().uninitialize(); - } - - /// Clone the state whilst preserving only the selected caches. - pub fn clone_with(&self, config: CloneConfig) -> Self { - let mut res = match self { - BeaconState::Base(inner) => BeaconState::Base(inner.clone()), - BeaconState::Altair(inner) => BeaconState::Altair(inner.clone()), - BeaconState::Merge(inner) => BeaconState::Merge(inner.clone()), - BeaconState::Capella(inner) => BeaconState::Capella(inner.clone()), - BeaconState::Deneb(inner) => BeaconState::Deneb(inner.clone()), - BeaconState::Electra(inner) => BeaconState::Electra(inner.clone()), - }; - if config.committee_caches { - res.committee_caches_mut() - .clone_from(self.committee_caches()); - *res.total_active_balance_mut() = *self.total_active_balance(); - } - if config.pubkey_cache { - *res.pubkey_cache_mut() = self.pubkey_cache().clone(); - } - if config.exit_cache { - *res.exit_cache_mut() = self.exit_cache().clone(); - } - if config.slashings_cache { - *res.slashings_cache_mut() = self.slashings_cache().clone(); - } - if config.tree_hash_cache { - *res.tree_hash_cache_mut() = self.tree_hash_cache().clone(); - } - if config.progressive_balances_cache { - *res.progressive_balances_cache_mut() = self.progressive_balances_cache().clone(); - } - res - } - - pub fn clone_with_only_committee_caches(&self) -> Self { - self.clone_with(CloneConfig::committee_caches_only()) + self.validators_mut().apply_updates()?; + Ok(self.validators().tree_hash_root()) } /// Passing `previous_epoch` to this function rather than computing it internally provides @@ -1979,10 +2031,177 @@ impl BeaconState { self.epoch_cache().get_base_reward(validator_index) } - pub fn compute_merkle_proof( - &mut self, - generalized_index: usize, - ) -> Result, Error> { + #[allow(clippy::arithmetic_side_effects)] + pub fn rebase_on(&mut self, base: &Self, spec: &ChainSpec) -> Result<(), Error> { + // Required for macros (which use type-hints internally). + + match (&mut *self, base) { + (Self::Base(self_inner), Self::Base(base_inner)) => { + bimap_beacon_state_base_tree_list_fields!( + self_inner, + base_inner, + |_, self_field, base_field| { self_field.rebase_on(base_field) } + ); + } + (Self::Base(_), _) => (), + (Self::Altair(self_inner), Self::Altair(base_inner)) => { + bimap_beacon_state_altair_tree_list_fields!( + self_inner, + base_inner, + |_, self_field, base_field| { self_field.rebase_on(base_field) } + ); + } + (Self::Altair(_), _) => (), + (Self::Merge(self_inner), Self::Merge(base_inner)) => { + bimap_beacon_state_bellatrix_tree_list_fields!( + self_inner, + base_inner, + |_, self_field, base_field| { self_field.rebase_on(base_field) } + ); + } + (Self::Merge(_), _) => (), + (Self::Capella(self_inner), Self::Capella(base_inner)) => { + bimap_beacon_state_capella_tree_list_fields!( + self_inner, + base_inner, + |_, self_field, base_field| { self_field.rebase_on(base_field) } + ); + } + (Self::Capella(_), _) => (), + (Self::Deneb(self_inner), Self::Deneb(base_inner)) => { + bimap_beacon_state_deneb_tree_list_fields!( + self_inner, + base_inner, + |_, self_field, base_field| { self_field.rebase_on(base_field) } + ); + } + (Self::Deneb(_), _) => (), + (Self::Electra(self_inner), Self::Electra(base_inner)) => { + bimap_beacon_state_electra_tree_list_fields!( + self_inner, + base_inner, + |_, self_field, base_field| { self_field.rebase_on(base_field) } + ); + } + (Self::Electra(_), _) => (), + } + + // Use sync committees from `base` if they are equal. + if let Ok(current_sync_committee) = self.current_sync_committee_mut() { + if let Ok(base_sync_committee) = base.current_sync_committee() { + if current_sync_committee == base_sync_committee { + *current_sync_committee = base_sync_committee.clone(); + } + } + } + if let Ok(next_sync_committee) = self.next_sync_committee_mut() { + if let Ok(base_sync_committee) = base.next_sync_committee() { + if next_sync_committee == base_sync_committee { + *next_sync_committee = base_sync_committee.clone(); + } + } + } + + // Rebase caches like the committee caches and the pubkey cache, which are expensive to + // rebuild and likely to be re-usable from the base state. + self.rebase_caches_on(base, spec)?; + + Ok(()) + } + + pub fn rebase_caches_on(&mut self, base: &Self, spec: &ChainSpec) -> Result<(), Error> { + // Use pubkey cache from `base` if it contains superior information (likely if our cache is + // uninitialized). + let num_validators = self.validators().len(); + let pubkey_cache = self.pubkey_cache_mut(); + let base_pubkey_cache = base.pubkey_cache(); + if pubkey_cache.len() < base_pubkey_cache.len() && pubkey_cache.len() < num_validators { + *pubkey_cache = base_pubkey_cache.clone(); + } + + // Use committee caches from `base` if they are relevant. + let epochs = [ + self.previous_epoch(), + self.current_epoch(), + self.next_epoch()?, + ]; + for (index, epoch) in epochs.into_iter().enumerate() { + if let Ok(base_relative_epoch) = RelativeEpoch::from_epoch(base.current_epoch(), epoch) + { + *self.committee_cache_at_index_mut(index)? = + base.committee_cache(base_relative_epoch)?.clone(); + + // Ensure total active balance cache remains built whenever current committee + // cache is built. + if epoch == self.current_epoch() { + self.build_total_active_balance_cache(spec)?; + } + } + } + + Ok(()) + } +} + +impl BeaconState { + /// The number of fields of the `BeaconState` rounded up to the nearest power of two. + /// + /// This is relevant to tree-hashing of the `BeaconState`. + /// + /// We assume this value is stable across forks. This assumption is checked in the + /// `check_num_fields_pow2` test. + pub const NUM_FIELDS_POW2: usize = BeaconStateMerge::::NUM_FIELDS.next_power_of_two(); + + /// Specialised deserialisation method that uses the `ChainSpec` as context. + #[allow(clippy::arithmetic_side_effects)] + pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result { + // Slot is after genesis_time (u64) and genesis_validators_root (Hash256). + let slot_start = ::ssz_fixed_len() + ::ssz_fixed_len(); + let slot_end = slot_start + ::ssz_fixed_len(); + + let slot_bytes = bytes + .get(slot_start..slot_end) + .ok_or(DecodeError::InvalidByteLength { + len: bytes.len(), + expected: slot_end, + })?; + + let slot = Slot::from_ssz_bytes(slot_bytes)?; + let fork_at_slot = spec.fork_name_at_slot::(slot); + + Ok(map_fork_name!( + fork_at_slot, + Self, + <_>::from_ssz_bytes(bytes)? + )) + } + + #[allow(clippy::arithmetic_side_effects)] + pub fn apply_pending_mutations(&mut self) -> Result<(), Error> { + match self { + Self::Base(inner) => { + map_beacon_state_base_tree_list_fields!(inner, |_, x| { x.apply_updates() }) + } + Self::Altair(inner) => { + map_beacon_state_altair_tree_list_fields!(inner, |_, x| { x.apply_updates() }) + } + Self::Merge(inner) => { + map_beacon_state_bellatrix_tree_list_fields!(inner, |_, x| { x.apply_updates() }) + } + Self::Capella(inner) => { + map_beacon_state_capella_tree_list_fields!(inner, |_, x| { x.apply_updates() }) + } + Self::Deneb(inner) => { + map_beacon_state_deneb_tree_list_fields!(inner, |_, x| { x.apply_updates() }) + } + Self::Electra(inner) => { + map_beacon_state_electra_tree_list_fields!(inner, |_, x| { x.apply_updates() }) + } + } + Ok(()) + } + + pub fn compute_merkle_proof(&self, generalized_index: usize) -> Result, Error> { // 1. Convert generalized index to field index. let field_index = match generalized_index { light_client_update::CURRENT_SYNC_COMMITTEE_INDEX @@ -1992,7 +2211,7 @@ impl BeaconState { // in the `BeaconState`: // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate generalized_index - .checked_sub(tree_hash_cache::NUM_BEACON_STATE_HASH_TREE_ROOT_LEAVES) + .checked_sub(Self::NUM_FIELDS_POW2) .ok_or(Error::IndexNotSupported(generalized_index))? } light_client_update::FINALIZED_ROOT_INDEX => { @@ -2002,20 +2221,47 @@ impl BeaconState { // Subtract off the internal nodes. Result should be 105/2 - 32 = 20 which matches // position of `finalized_checkpoint` in `BeaconState`. finalized_checkpoint_generalized_index - .checked_sub(tree_hash_cache::NUM_BEACON_STATE_HASH_TREE_ROOT_LEAVES) + .checked_sub(Self::NUM_FIELDS_POW2) .ok_or(Error::IndexNotSupported(generalized_index))? } _ => return Err(Error::IndexNotSupported(generalized_index)), }; // 2. Get all `BeaconState` leaves. - self.initialize_tree_hash_cache(); - let mut cache = self - .tree_hash_cache_mut() - .take() - .ok_or(Error::TreeHashCacheNotInitialized)?; - let leaves = cache.recalculate_tree_hash_leaves(self)?; - self.tree_hash_cache_mut().restore(cache); + let mut leaves = vec![]; + #[allow(clippy::arithmetic_side_effects)] + match self { + BeaconState::Base(state) => { + map_beacon_state_base_fields!(state, |_, field| { + leaves.push(field.tree_hash_root()); + }); + } + BeaconState::Altair(state) => { + map_beacon_state_altair_fields!(state, |_, field| { + leaves.push(field.tree_hash_root()); + }); + } + BeaconState::Merge(state) => { + map_beacon_state_bellatrix_fields!(state, |_, field| { + leaves.push(field.tree_hash_root()); + }); + } + BeaconState::Capella(state) => { + map_beacon_state_capella_fields!(state, |_, field| { + leaves.push(field.tree_hash_root()); + }); + } + BeaconState::Deneb(state) => { + map_beacon_state_deneb_fields!(state, |_, field| { + leaves.push(field.tree_hash_root()); + }); + } + BeaconState::Electra(state) => { + map_beacon_state_electra_fields!(state, |_, field| { + leaves.push(field.tree_hash_root()); + }); + } + }; // 3. Make deposit tree. // Use the depth of the `BeaconState` fields (i.e. `log2(32) = 5`). @@ -2074,9 +2320,10 @@ impl From for Error { } } -/// Helper function for "cloning" a field by using its default value. -fn clone_default(_value: &T) -> T { - T::default() +impl From for Error { + fn from(e: milhouse::Error) -> Self { + Self::MilhouseError(e) + } } impl CompareFields for BeaconState { diff --git a/consensus/types/src/beacon_state/clone_config.rs b/consensus/types/src/beacon_state/clone_config.rs deleted file mode 100644 index 27e066d5db6..00000000000 --- a/consensus/types/src/beacon_state/clone_config.rs +++ /dev/null @@ -1,47 +0,0 @@ -/// Configuration struct for controlling which caches of a `BeaconState` should be cloned. -#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] -pub struct CloneConfig { - pub committee_caches: bool, - pub pubkey_cache: bool, - pub exit_cache: bool, - pub slashings_cache: bool, - pub tree_hash_cache: bool, - pub progressive_balances_cache: bool, -} - -impl CloneConfig { - pub fn all() -> Self { - Self { - committee_caches: true, - pubkey_cache: true, - exit_cache: true, - slashings_cache: true, - tree_hash_cache: true, - progressive_balances_cache: true, - } - } - - pub fn none() -> Self { - Self::default() - } - - pub fn committee_caches_only() -> Self { - Self { - committee_caches: true, - ..Self::none() - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn sanity() { - assert!(CloneConfig::all().pubkey_cache); - assert!(!CloneConfig::none().tree_hash_cache); - assert!(CloneConfig::committee_caches_only().committee_caches); - assert!(!CloneConfig::committee_caches_only().exit_cache); - } -} diff --git a/consensus/types/src/beacon_state/committee_cache.rs b/consensus/types/src/beacon_state/committee_cache.rs index a6b12cf5af3..7913df8e00e 100644 --- a/consensus/types/src/beacon_state/committee_cache.rs +++ b/consensus/types/src/beacon_state/committee_cache.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use ssz::{four_byte_option_impl, Decode, DecodeError, Encode}; use ssz_derive::{Decode, Encode}; use std::ops::Range; +use std::sync::Arc; use swap_or_not_shuffle::shuffle_list; mod tests; @@ -65,7 +66,7 @@ impl CommitteeCache { state: &BeaconState, epoch: Epoch, spec: &ChainSpec, - ) -> Result { + ) -> Result, Error> { // Check that the cache is being built for an in-range epoch. // // We allow caches to be constructed for historic epochs, per: @@ -115,13 +116,13 @@ impl CommitteeCache { .ok_or(Error::ShuffleIndexOutOfBounds(v))? = NonZeroUsize::new(i + 1).into(); } - Ok(CommitteeCache { + Ok(Arc::new(CommitteeCache { initialized_epoch: Some(epoch), shuffling, shuffling_positions, committees_per_slot, slots_per_epoch: E::slots_per_epoch(), - }) + })) } /// Returns `true` if the cache has been initialized at the supplied `epoch`. diff --git a/consensus/types/src/beacon_state/committee_cache/tests.rs b/consensus/types/src/beacon_state/committee_cache/tests.rs index a5effb9363b..a2274765691 100644 --- a/consensus/types/src/beacon_state/committee_cache/tests.rs +++ b/consensus/types/src/beacon_state/committee_cache/tests.rs @@ -92,7 +92,7 @@ async fn shuffles_for_the_right_epoch() { .map(|i| Hash256::from_low_u64_be(i as u64)) .collect(); - *state.randao_mixes_mut() = FixedVector::from(distinct_hashes); + *state.randao_mixes_mut() = Vector::try_from_iter(distinct_hashes).unwrap(); let previous_seed = state .get_seed(state.previous_epoch(), Domain::BeaconAttester, spec) diff --git a/consensus/types/src/beacon_state/exit_cache.rs b/consensus/types/src/beacon_state/exit_cache.rs index bda788e63b9..0bb984b6676 100644 --- a/consensus/types/src/beacon_state/exit_cache.rs +++ b/consensus/types/src/beacon_state/exit_cache.rs @@ -1,10 +1,9 @@ use super::{BeaconStateError, ChainSpec, Epoch, Validator}; use safe_arith::SafeArith; -use serde::{Deserialize, Serialize}; use std::cmp::Ordering; /// Map from exit epoch to the number of validators with that exit epoch. -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq)] pub struct ExitCache { /// True if the cache has been initialized. initialized: bool, @@ -16,7 +15,11 @@ pub struct ExitCache { impl ExitCache { /// Initialize a new cache for the given list of validators. - pub fn new(validators: &[Validator], spec: &ChainSpec) -> Result { + pub fn new<'a, V, I>(validators: V, spec: &ChainSpec) -> Result + where + V: IntoIterator, + I: ExactSizeIterator + Iterator, + { let mut exit_cache = ExitCache { initialized: true, max_exit_epoch: Epoch::new(0), @@ -24,7 +27,7 @@ impl ExitCache { }; // Add all validators with a non-default exit epoch to the cache. validators - .iter() + .into_iter() .filter(|validator| validator.exit_epoch != spec.far_future_epoch) .try_for_each(|validator| exit_cache.record_validator_exit(validator.exit_epoch))?; Ok(exit_cache) diff --git a/consensus/types/src/beacon_state/iter.rs b/consensus/types/src/beacon_state/iter.rs index 2d3ad02c836..2caa0365e01 100644 --- a/consensus/types/src/beacon_state/iter.rs +++ b/consensus/types/src/beacon_state/iter.rs @@ -74,7 +74,7 @@ mod test { let mut state: BeaconState = BeaconState::new(0, <_>::default(), &spec); for i in 0..state.block_roots().len() { - state.block_roots_mut()[i] = root_slot(i).1; + *state.block_roots_mut().get_mut(i).unwrap() = root_slot(i).1; } assert_eq!( @@ -122,7 +122,7 @@ mod test { let mut state: BeaconState = BeaconState::new(0, <_>::default(), &spec); for i in 0..state.block_roots().len() { - state.block_roots_mut()[i] = root_slot(i).1; + *state.block_roots_mut().get_mut(i).unwrap() = root_slot(i).1; } assert_eq!( diff --git a/consensus/types/src/beacon_state/pubkey_cache.rs b/consensus/types/src/beacon_state/pubkey_cache.rs index 0b61ea3c5f8..d58dd7bc1dd 100644 --- a/consensus/types/src/beacon_state/pubkey_cache.rs +++ b/consensus/types/src/beacon_state/pubkey_cache.rs @@ -1,21 +1,21 @@ use crate::*; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use rpds::HashTrieMapSync as HashTrieMap; type ValidatorIndex = usize; #[allow(clippy::len_without_is_empty)] -#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Clone, Default)] pub struct PubkeyCache { - /// Maintain the number of keys added to the map. It is not sufficient to just use the HashMap - /// len, as it does not increase when duplicate keys are added. Duplicate keys are used during - /// testing. + /// Maintain the number of keys added to the map. It is not sufficient to just use the + /// HashTrieMap len, as it does not increase when duplicate keys are added. Duplicate keys are + /// used during testing. len: usize, - map: HashMap, + map: HashTrieMap, } impl PubkeyCache { /// Returns the number of validator indices added to the map so far. + #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> ValidatorIndex { self.len } @@ -26,7 +26,7 @@ impl PubkeyCache { /// that an index is never skipped. pub fn insert(&mut self, pubkey: PublicKeyBytes, index: ValidatorIndex) -> bool { if index == self.len { - self.map.insert(pubkey, index); + self.map.insert_mut(pubkey, index); self.len = self .len .checked_add(1) diff --git a/consensus/types/src/beacon_state/slashings_cache.rs b/consensus/types/src/beacon_state/slashings_cache.rs index cfdc349f86c..45d8f7e2129 100644 --- a/consensus/types/src/beacon_state/slashings_cache.rs +++ b/consensus/types/src/beacon_state/slashings_cache.rs @@ -1,13 +1,13 @@ use crate::{BeaconStateError, Slot, Validator}; use arbitrary::Arbitrary; -use std::collections::HashSet; +use rpds::HashTrieSetSync as HashTrieSet; /// Persistent (cheap to clone) cache of all slashed validator indices. #[derive(Debug, Default, Clone, PartialEq, Arbitrary)] pub struct SlashingsCache { latest_block_slot: Option, #[arbitrary(default)] - slashed_validators: HashSet, + slashed_validators: HashTrieSet, } impl SlashingsCache { @@ -49,7 +49,7 @@ impl SlashingsCache { validator_index: usize, ) -> Result<(), BeaconStateError> { self.check_initialized(block_slot)?; - self.slashed_validators.insert(validator_index); + self.slashed_validators.insert_mut(validator_index); Ok(()) } diff --git a/consensus/types/src/beacon_state/tests.rs b/consensus/types/src/beacon_state/tests.rs index 00625a1788e..012c063afef 100644 --- a/consensus/types/src/beacon_state/tests.rs +++ b/consensus/types/src/beacon_state/tests.rs @@ -1,20 +1,14 @@ #![cfg(test)] -use crate::test_utils::*; -use beacon_chain::test_utils::{ - interop_genesis_state_with_eth1, test_spec, BeaconChainHarness, EphemeralHarnessType, - DEFAULT_ETH1_BLOCK_HASH, -}; +use crate::{test_utils::*, ForkName}; +use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType}; use beacon_chain::types::{ - test_utils::TestRandom, BeaconState, BeaconStateAltair, BeaconStateBase, BeaconStateError, - ChainSpec, CloneConfig, Domain, Epoch, EthSpec, FixedVector, Hash256, Keypair, MainnetEthSpec, - MinimalEthSpec, RelativeEpoch, Slot, + test_utils::TestRandom, BeaconState, BeaconStateAltair, BeaconStateBase, BeaconStateCapella, + BeaconStateDeneb, BeaconStateElectra, BeaconStateError, BeaconStateMerge, ChainSpec, Domain, + Epoch, EthSpec, Hash256, Keypair, MainnetEthSpec, MinimalEthSpec, RelativeEpoch, Slot, Vector, }; -use safe_arith::SafeArith; use ssz::Encode; -use state_processing::per_slot_processing; use std::ops::Mul; use swap_or_not_shuffle::compute_shuffled_index; -use tree_hash::TreeHash; pub const MAX_VALIDATOR_COUNT: usize = 129; pub const SLOT_OFFSET: Slot = Slot::new(1); @@ -101,7 +95,11 @@ async fn test_beacon_proposer_index() { // Test with two validators per slot, first validator has zero balance. let mut state = build_state::((E::slots_per_epoch() as usize).mul(2)).await; let slot0_candidate0 = ith_candidate(&state, Slot::new(0), 0, &spec); - state.validators_mut()[slot0_candidate0].effective_balance = 0; + state + .validators_mut() + .get_mut(slot0_candidate0) + .unwrap() + .effective_balance = 0; test(&state, Slot::new(0), 1); for i in 1..E::slots_per_epoch() { test(&state, Slot::from(i), 0); @@ -159,85 +157,6 @@ async fn cache_initialization() { test_cache_initialization(&mut state, RelativeEpoch::Next, &spec); } -fn test_clone_config(base_state: &BeaconState, clone_config: CloneConfig) { - let state = base_state.clone_with(clone_config); - if clone_config.committee_caches { - state - .committee_cache(RelativeEpoch::Previous) - .expect("committee cache exists"); - state - .committee_cache(RelativeEpoch::Current) - .expect("committee cache exists"); - state - .committee_cache(RelativeEpoch::Next) - .expect("committee cache exists"); - state - .total_active_balance() - .expect("total active balance exists"); - } else { - state - .committee_cache(RelativeEpoch::Previous) - .expect_err("shouldn't exist"); - state - .committee_cache(RelativeEpoch::Current) - .expect_err("shouldn't exist"); - state - .committee_cache(RelativeEpoch::Next) - .expect_err("shouldn't exist"); - } - if clone_config.pubkey_cache { - assert_ne!(state.pubkey_cache().len(), 0); - } else { - assert_eq!(state.pubkey_cache().len(), 0); - } - if clone_config.exit_cache { - state - .exit_cache() - .check_initialized() - .expect("exit cache exists"); - } else { - state - .exit_cache() - .check_initialized() - .expect_err("exit cache doesn't exist"); - } - if clone_config.tree_hash_cache { - assert!(state.tree_hash_cache().is_initialized()); - } else { - assert!( - !state.tree_hash_cache().is_initialized(), - "{:?}", - clone_config - ); - } -} - -#[tokio::test] -async fn clone_config() { - let spec = MinimalEthSpec::default_spec(); - - let mut state = build_state::(16).await; - - state.build_caches(&spec).unwrap(); - state - .update_tree_hash_cache() - .expect("should update tree hash cache"); - - let num_caches = 6; - let all_configs = (0..2u8.pow(num_caches)).map(|i| CloneConfig { - committee_caches: (i & 1) != 0, - pubkey_cache: ((i >> 1) & 1) != 0, - exit_cache: ((i >> 2) & 1) != 0, - slashings_cache: ((i >> 3) & 1) != 0, - tree_hash_cache: ((i >> 4) & 1) != 0, - progressive_balances_cache: ((i >> 5) & 1) != 0, - }); - - for config in all_configs { - test_clone_config(&state, config); - } -} - /// Tests committee-specific components #[cfg(test)] mod committees { @@ -328,10 +247,9 @@ mod committees { let harness = get_harness::(validator_count, slot).await; let mut new_head_state = harness.get_current_state(); - let distinct_hashes: Vec = (0..E::epochs_per_historical_vector()) - .map(|i| Hash256::from_low_u64_be(i as u64)) - .collect(); - *new_head_state.randao_mixes_mut() = FixedVector::from(distinct_hashes); + let distinct_hashes = + (0..E::epochs_per_historical_vector()).map(|i| Hash256::from_low_u64_be(i as u64)); + *new_head_state.randao_mixes_mut() = Vector::try_from_iter(distinct_hashes).unwrap(); new_head_state .force_build_committee_cache(RelativeEpoch::Previous, spec) @@ -487,120 +405,22 @@ fn decode_base_and_altair() { } #[test] -fn tree_hash_cache_linear_history() { - let mut rng = XorShiftRng::from_seed([42; 16]); - - let mut state: BeaconState = - BeaconState::Base(BeaconStateBase::random_for_test(&mut rng)); - - let root = state.update_tree_hash_cache().unwrap(); - - assert_eq!(root.as_bytes(), &state.tree_hash_root()[..]); - - /* - * A cache should hash twice without updating the slot. - */ - - assert_eq!( - state.update_tree_hash_cache().unwrap(), - root, - "tree hash result should be identical on the same slot" - ); - - /* - * A cache should not hash after updating the slot but not updating the state roots. - */ - - // The tree hash cache needs to be rebuilt since it was dropped when it failed. - state - .update_tree_hash_cache() - .expect("should rebuild cache"); - - *state.slot_mut() += 1; - - assert_eq!( - state.update_tree_hash_cache(), - Err(BeaconStateError::NonLinearTreeHashCacheHistory), - "should not build hash without updating the state root" - ); - - /* - * The cache should update if the slot and state root are updated. - */ - - // The tree hash cache needs to be rebuilt since it was dropped when it failed. - let root = state - .update_tree_hash_cache() - .expect("should rebuild cache"); - - *state.slot_mut() += 1; - state - .set_state_root(state.slot() - 1, root) - .expect("should set state root"); - - let root = state.update_tree_hash_cache().unwrap(); - assert_eq!(root.as_bytes(), &state.tree_hash_root()[..]); -} - -// Check how the cache behaves when there's a distance larger than `SLOTS_PER_HISTORICAL_ROOT` -// since its last update. -#[test] -fn tree_hash_cache_linear_history_long_skip() { - let validator_count = 128; - let keypairs = generate_deterministic_keypairs(validator_count); - - let spec = &test_spec::(); - - // This state has a cache that advances normally each slot. - let mut state: BeaconState = interop_genesis_state_with_eth1( - &keypairs, - 0, - Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), - None, - spec, - ) - .unwrap(); - - state.update_tree_hash_cache().unwrap(); - - // This state retains its original cache until it is updated after a long skip. - let mut original_cache_state = state.clone(); - assert!(original_cache_state.tree_hash_cache().is_initialized()); - - // Advance the states to a slot beyond the historical state root limit, using the state root - // from the first state to avoid touching the original state's cache. - let start_slot = state.slot(); - let target_slot = start_slot - .safe_add(MinimalEthSpec::slots_per_historical_root() as u64 + 1) - .unwrap(); - - let mut prev_state_root; - while state.slot() < target_slot { - prev_state_root = state.update_tree_hash_cache().unwrap(); - per_slot_processing(&mut state, None, spec).unwrap(); - per_slot_processing(&mut original_cache_state, Some(prev_state_root), spec).unwrap(); +fn check_num_fields_pow2() { + use metastruct::NumFields; + pub type E = MainnetEthSpec; + + for fork_name in ForkName::list_all() { + let num_fields = match fork_name { + ForkName::Base => BeaconStateBase::::NUM_FIELDS, + ForkName::Altair => BeaconStateAltair::::NUM_FIELDS, + ForkName::Merge => BeaconStateMerge::::NUM_FIELDS, + ForkName::Capella => BeaconStateCapella::::NUM_FIELDS, + ForkName::Deneb => BeaconStateDeneb::::NUM_FIELDS, + ForkName::Electra => BeaconStateElectra::::NUM_FIELDS, + }; + assert_eq!( + num_fields.next_power_of_two(), + BeaconState::::NUM_FIELDS_POW2 + ); } - - // The state with the original cache should still be initialized at the starting slot. - assert_eq!( - original_cache_state - .tree_hash_cache() - .initialized_slot() - .unwrap(), - start_slot - ); - - // Updating the tree hash cache should be successful despite the long skip. - assert_eq!( - original_cache_state.update_tree_hash_cache().unwrap(), - state.update_tree_hash_cache().unwrap() - ); - - assert_eq!( - original_cache_state - .tree_hash_cache() - .initialized_slot() - .unwrap(), - target_slot - ); } diff --git a/consensus/types/src/beacon_state/tree_hash_cache.rs b/consensus/types/src/beacon_state/tree_hash_cache.rs deleted file mode 100644 index 290020b1b35..00000000000 --- a/consensus/types/src/beacon_state/tree_hash_cache.rs +++ /dev/null @@ -1,645 +0,0 @@ -#![allow(clippy::arithmetic_side_effects)] -#![allow(clippy::disallowed_methods)] -#![allow(clippy::indexing_slicing)] - -use super::Error; -use crate::historical_summary::HistoricalSummaryCache; -use crate::{BeaconState, EthSpec, Hash256, ParticipationList, Slot, Unsigned, Validator}; -use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, TreeHashCache}; -use rayon::prelude::*; -use ssz_derive::{Decode, Encode}; -use ssz_types::VariableList; -use std::cmp::Ordering; -use tree_hash::{mix_in_length, MerkleHasher, TreeHash}; - -/// The number of leaves (including padding) on the `BeaconState` Merkle tree. -/// -/// ## Note -/// -/// This constant is set with the assumption that there are `> 16` and `<= 32` fields on the -/// `BeaconState`. **Tree hashing will fail if this value is set incorrectly.** -pub const NUM_BEACON_STATE_HASH_TREE_ROOT_LEAVES: usize = 32; - -/// The number of nodes in the Merkle tree of a validator record. -const NODES_PER_VALIDATOR: usize = 15; - -/// The number of validator record tree hash caches stored in each arena. -/// -/// This is primarily used for concurrency; if we have 16 validators and set `VALIDATORS_PER_ARENA -/// == 8` then it is possible to do a 2-core concurrent hash. -/// -/// Do not set to 0. -const VALIDATORS_PER_ARENA: usize = 4_096; - -#[derive(Debug, PartialEq, Clone, Encode, Decode)] -pub struct Eth1DataVotesTreeHashCache { - arena: CacheArena, - tree_hash_cache: TreeHashCache, - voting_period: u64, - roots: VariableList, -} - -impl Eth1DataVotesTreeHashCache { - /// Instantiates a new cache. - /// - /// Allocates the necessary memory to store all of the cached Merkle trees. Only the leaves are - /// hashed, leaving the internal nodes as all-zeros. - pub fn new(state: &BeaconState) -> Self { - let mut arena = CacheArena::default(); - let roots: VariableList<_, _> = state - .eth1_data_votes() - .iter() - .map(|eth1_data| eth1_data.tree_hash_root()) - .collect::>() - .into(); - let tree_hash_cache = roots.new_tree_hash_cache(&mut arena); - - Self { - arena, - tree_hash_cache, - voting_period: Self::voting_period(state.slot()), - roots, - } - } - - fn voting_period(slot: Slot) -> u64 { - slot.as_u64() / E::SlotsPerEth1VotingPeriod::to_u64() - } - - pub fn recalculate_tree_hash_root(&mut self, state: &BeaconState) -> Result { - if state.eth1_data_votes().len() < self.roots.len() - || Self::voting_period(state.slot()) != self.voting_period - { - *self = Self::new(state); - } - - state - .eth1_data_votes() - .iter() - .skip(self.roots.len()) - .try_for_each(|eth1_data| self.roots.push(eth1_data.tree_hash_root()))?; - - self.roots - .recalculate_tree_hash_root(&mut self.arena, &mut self.tree_hash_cache) - .map_err(Into::into) - } -} - -/// A cache that performs a caching tree hash of the entire `BeaconState` struct. -/// -/// This type is a wrapper around the inner cache, which does all the work. -#[derive(Debug, Default, PartialEq, Clone)] -pub struct BeaconTreeHashCache { - inner: Option>, -} - -impl BeaconTreeHashCache { - pub fn new(state: &BeaconState) -> Self { - Self { - inner: Some(BeaconTreeHashCacheInner::new(state)), - } - } - - pub fn is_initialized(&self) -> bool { - self.inner.is_some() - } - - /// Move the inner cache out so that the containing `BeaconState` can be borrowed. - pub fn take(&mut self) -> Option> { - self.inner.take() - } - - /// Restore the inner cache after using `take`. - pub fn restore(&mut self, inner: BeaconTreeHashCacheInner) { - self.inner = Some(inner); - } - - /// Make the cache empty. - pub fn uninitialize(&mut self) { - self.inner = None; - } - - /// Return the slot at which the cache was last updated. - /// - /// This should probably only be used during testing. - pub fn initialized_slot(&self) -> Option { - Some(self.inner.as_ref()?.previous_state?.1) - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct BeaconTreeHashCacheInner { - /// Tracks the previously generated state root to ensure the next state root provided descends - /// directly from this state. - previous_state: Option<(Hash256, Slot)>, - // Validators cache - validators: ValidatorsListTreeHashCache, - // Arenas - fixed_arena: CacheArena, - balances_arena: CacheArena, - slashings_arena: CacheArena, - // Caches - block_roots: TreeHashCache, - state_roots: TreeHashCache, - historical_roots: TreeHashCache, - historical_summaries: OptionalTreeHashCache, - balances: TreeHashCache, - randao_mixes: TreeHashCache, - slashings: TreeHashCache, - eth1_data_votes: Eth1DataVotesTreeHashCache, - inactivity_scores: OptionalTreeHashCache, - // Participation caches - previous_epoch_participation: OptionalTreeHashCache, - current_epoch_participation: OptionalTreeHashCache, -} - -impl BeaconTreeHashCacheInner { - /// Instantiates a new cache. - /// - /// Allocates the necessary memory to store all of the cached Merkle trees. Only the leaves are - /// hashed, leaving the internal nodes as all-zeros. - pub fn new(state: &BeaconState) -> Self { - let mut fixed_arena = CacheArena::default(); - let block_roots = state.block_roots().new_tree_hash_cache(&mut fixed_arena); - let state_roots = state.state_roots().new_tree_hash_cache(&mut fixed_arena); - let historical_roots = state - .historical_roots() - .new_tree_hash_cache(&mut fixed_arena); - let historical_summaries = OptionalTreeHashCache::new( - state - .historical_summaries() - .ok() - .map(HistoricalSummaryCache::new) - .as_ref(), - ); - - let randao_mixes = state.randao_mixes().new_tree_hash_cache(&mut fixed_arena); - - let validators = ValidatorsListTreeHashCache::new::(state.validators()); - - let mut balances_arena = CacheArena::default(); - let balances = state.balances().new_tree_hash_cache(&mut balances_arena); - - let mut slashings_arena = CacheArena::default(); - let slashings = state.slashings().new_tree_hash_cache(&mut slashings_arena); - - let inactivity_scores = OptionalTreeHashCache::new(state.inactivity_scores().ok()); - - let previous_epoch_participation = OptionalTreeHashCache::new( - state - .previous_epoch_participation() - .ok() - .map(ParticipationList::new) - .as_ref(), - ); - let current_epoch_participation = OptionalTreeHashCache::new( - state - .current_epoch_participation() - .ok() - .map(ParticipationList::new) - .as_ref(), - ); - - Self { - previous_state: None, - validators, - fixed_arena, - balances_arena, - slashings_arena, - block_roots, - state_roots, - historical_roots, - historical_summaries, - balances, - randao_mixes, - slashings, - inactivity_scores, - eth1_data_votes: Eth1DataVotesTreeHashCache::new(state), - previous_epoch_participation, - current_epoch_participation, - } - } - - pub fn recalculate_tree_hash_leaves( - &mut self, - state: &BeaconState, - ) -> Result, Error> { - let mut leaves = vec![ - // Genesis data leaves. - state.genesis_time().tree_hash_root(), - state.genesis_validators_root().tree_hash_root(), - // Current fork data leaves. - state.slot().tree_hash_root(), - state.fork().tree_hash_root(), - state.latest_block_header().tree_hash_root(), - // Roots leaves. - state - .block_roots() - .recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.block_roots)?, - state - .state_roots() - .recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.state_roots)?, - state - .historical_roots() - .recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.historical_roots)?, - // Eth1 Data leaves. - state.eth1_data().tree_hash_root(), - self.eth1_data_votes.recalculate_tree_hash_root(state)?, - state.eth1_deposit_index().tree_hash_root(), - // Validator leaves. - self.validators - .recalculate_tree_hash_root(state.validators())?, - state - .balances() - .recalculate_tree_hash_root(&mut self.balances_arena, &mut self.balances)?, - state - .randao_mixes() - .recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.randao_mixes)?, - state - .slashings() - .recalculate_tree_hash_root(&mut self.slashings_arena, &mut self.slashings)?, - ]; - - // Participation - if let BeaconState::Base(state) = state { - leaves.push(state.previous_epoch_attestations.tree_hash_root()); - leaves.push(state.current_epoch_attestations.tree_hash_root()); - } else { - leaves.push( - self.previous_epoch_participation - .recalculate_tree_hash_root(&ParticipationList::new( - state.previous_epoch_participation()?, - ))?, - ); - leaves.push( - self.current_epoch_participation - .recalculate_tree_hash_root(&ParticipationList::new( - state.current_epoch_participation()?, - ))?, - ); - } - // Checkpoint leaves - leaves.push(state.justification_bits().tree_hash_root()); - leaves.push(state.previous_justified_checkpoint().tree_hash_root()); - leaves.push(state.current_justified_checkpoint().tree_hash_root()); - leaves.push(state.finalized_checkpoint().tree_hash_root()); - // Inactivity & light-client sync committees (Altair and later). - if let Ok(inactivity_scores) = state.inactivity_scores() { - leaves.push( - self.inactivity_scores - .recalculate_tree_hash_root(inactivity_scores)?, - ); - } - if let Ok(current_sync_committee) = state.current_sync_committee() { - leaves.push(current_sync_committee.tree_hash_root()); - } - - if let Ok(next_sync_committee) = state.next_sync_committee() { - leaves.push(next_sync_committee.tree_hash_root()); - } - - // Execution payload (merge and later). - if let Ok(payload_header) = state.latest_execution_payload_header() { - leaves.push(payload_header.tree_hash_root()); - } - - // Withdrawal indices (Capella and later). - if let Ok(next_withdrawal_index) = state.next_withdrawal_index() { - leaves.push(next_withdrawal_index.tree_hash_root()); - } - if let Ok(next_withdrawal_validator_index) = state.next_withdrawal_validator_index() { - leaves.push(next_withdrawal_validator_index.tree_hash_root()); - } - - // Historical roots/summaries (Capella and later). - if let Ok(historical_summaries) = state.historical_summaries() { - leaves.push( - self.historical_summaries.recalculate_tree_hash_root( - &HistoricalSummaryCache::new(historical_summaries), - )?, - ); - } - - Ok(leaves) - } - - /// Updates the cache and returns the tree hash root for the given `state`. - /// - /// The provided `state` should be a descendant of the last `state` given to this function, or - /// the `Self::new` function. If the state is more than `SLOTS_PER_HISTORICAL_ROOT` slots - /// after `self.previous_state` then the whole cache will be re-initialized. - pub fn recalculate_tree_hash_root(&mut self, state: &BeaconState) -> Result { - // If this cache has previously produced a root, ensure that it is in the state root - // history of this state. - // - // This ensures that the states applied have a linear history, this - // allows us to make assumptions about how the state changes over times and produce a more - // efficient algorithm. - if let Some((previous_root, previous_slot)) = self.previous_state { - // The previously-hashed state must not be newer than `state`. - if previous_slot > state.slot() { - return Err(Error::TreeHashCacheSkippedSlot { - cache: previous_slot, - state: state.slot(), - }); - } - - // If the state is newer, the previous root must be in the history of the given state. - // If the previous slot is out of range of the `state_roots` array (indicating a long - // gap between the cache's last use and the current state) then we re-initialize. - match state.get_state_root(previous_slot) { - Ok(state_previous_root) if *state_previous_root == previous_root => {} - Ok(_) => return Err(Error::NonLinearTreeHashCacheHistory), - Err(Error::SlotOutOfBounds) => { - *self = Self::new(state); - } - Err(e) => return Err(e), - } - } - - let mut hasher = MerkleHasher::with_leaves(NUM_BEACON_STATE_HASH_TREE_ROOT_LEAVES); - - let leaves = self.recalculate_tree_hash_leaves(state)?; - for leaf in leaves { - hasher.write(leaf.as_bytes())?; - } - - let root = hasher.finish()?; - - self.previous_state = Some((root, state.slot())); - - Ok(root) - } - - /// Updates the cache and provides the root of the given `validators`. - pub fn recalculate_validators_tree_hash_root( - &mut self, - validators: &[Validator], - ) -> Result { - self.validators.recalculate_tree_hash_root(validators) - } -} - -/// A specialized cache for computing the tree hash root of `state.validators`. -#[derive(Debug, PartialEq, Clone, Default, Encode, Decode)] -struct ValidatorsListTreeHashCache { - list_arena: CacheArena, - list_cache: TreeHashCache, - values: ParallelValidatorTreeHash, -} - -impl ValidatorsListTreeHashCache { - /// Instantiates a new cache. - /// - /// Allocates the necessary memory to store all of the cached Merkle trees but does perform any - /// hashing. - fn new(validators: &[Validator]) -> Self { - let mut list_arena = CacheArena::default(); - Self { - list_cache: TreeHashCache::new( - &mut list_arena, - int_log(E::ValidatorRegistryLimit::to_usize()), - validators.len(), - ), - list_arena, - values: ParallelValidatorTreeHash::new(validators), - } - } - - /// Updates the cache and returns the tree hash root for the given `state`. - /// - /// This function makes assumptions that the `validators` list will only change in accordance - /// with valid per-block/per-slot state transitions. - fn recalculate_tree_hash_root(&mut self, validators: &[Validator]) -> Result { - let mut list_arena = std::mem::take(&mut self.list_arena); - - let leaves = self.values.leaves(validators)?; - let num_leaves = leaves.iter().map(|arena| arena.len()).sum(); - - let leaves_iter = ForcedExactSizeIterator { - iter: leaves.into_iter().flatten().map(|h| h.to_fixed_bytes()), - len: num_leaves, - }; - - let list_root = self - .list_cache - .recalculate_merkle_root(&mut list_arena, leaves_iter)?; - - self.list_arena = list_arena; - - Ok(mix_in_length(&list_root, validators.len())) - } -} - -/// Provides a wrapper around some `iter` if the number of items in the iterator is known to the -/// programmer but not the compiler. This allows use of `ExactSizeIterator` in some occasions. -/// -/// Care should be taken to ensure `len` is accurate. -struct ForcedExactSizeIterator { - iter: I, - len: usize, -} - -impl> Iterator for ForcedExactSizeIterator { - type Item = V; - - fn next(&mut self) -> Option { - self.iter.next() - } -} - -impl> ExactSizeIterator for ForcedExactSizeIterator { - fn len(&self) -> usize { - self.len - } -} - -/// Provides a cache for each of the `Validator` objects in `state.validators` and computes the -/// roots of these using Rayon parallelization. -#[derive(Debug, PartialEq, Clone, Default, Encode, Decode)] -pub struct ParallelValidatorTreeHash { - /// Each arena and its associated sub-trees. - arenas: Vec<(CacheArena, Vec)>, -} - -impl ParallelValidatorTreeHash { - /// Instantiates a new cache. - /// - /// Allocates the necessary memory to store all of the cached Merkle trees but does perform any - /// hashing. - fn new(validators: &[Validator]) -> Self { - let num_arenas = std::cmp::max( - 1, - (validators.len() + VALIDATORS_PER_ARENA - 1) / VALIDATORS_PER_ARENA, - ); - - let mut arenas = (1..=num_arenas) - .map(|i| { - let num_validators = if i == num_arenas { - validators.len() % VALIDATORS_PER_ARENA - } else { - VALIDATORS_PER_ARENA - }; - NODES_PER_VALIDATOR * num_validators - }) - .map(|capacity| (CacheArena::with_capacity(capacity), vec![])) - .collect::>(); - - validators.iter().enumerate().for_each(|(i, v)| { - let (arena, caches) = &mut arenas[i / VALIDATORS_PER_ARENA]; - caches.push(v.new_tree_hash_cache(arena)) - }); - - Self { arenas } - } - - /// Returns the number of validators stored in self. - fn len(&self) -> usize { - self.arenas.last().map_or(0, |last| { - // Subtraction cannot underflow because `.last()` ensures the `.len() > 0`. - (self.arenas.len() - 1) * VALIDATORS_PER_ARENA + last.1.len() - }) - } - - /// Updates the caches for each `Validator` in `validators` and returns a list that maps 1:1 - /// with `validators` to the hash of each validator. - /// - /// This function makes assumptions that the `validators` list will only change in accordance - /// with valid per-block/per-slot state transitions. - fn leaves(&mut self, validators: &[Validator]) -> Result>, Error> { - match self.len().cmp(&validators.len()) { - Ordering::Less => validators.iter().skip(self.len()).for_each(|v| { - if self - .arenas - .last() - .map_or(true, |last| last.1.len() >= VALIDATORS_PER_ARENA) - { - let mut arena = CacheArena::default(); - let cache = v.new_tree_hash_cache(&mut arena); - self.arenas.push((arena, vec![cache])) - } else { - let (arena, caches) = &mut self - .arenas - .last_mut() - .expect("Cannot reach this block if arenas is empty."); - caches.push(v.new_tree_hash_cache(arena)) - } - }), - Ordering::Greater => { - return Err(Error::ValidatorRegistryShrunk); - } - Ordering::Equal => (), - } - - self.arenas - .par_iter_mut() - .enumerate() - .map(|(arena_index, (arena, caches))| { - caches - .iter_mut() - .enumerate() - .map(move |(cache_index, cache)| { - let val_index = (arena_index * VALIDATORS_PER_ARENA) + cache_index; - - let validator = validators - .get(val_index) - .ok_or(Error::TreeHashCacheInconsistent)?; - - validator - .recalculate_tree_hash_root(arena, cache) - .map_err(Error::CachedTreeHashError) - }) - .collect() - }) - .collect() - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct OptionalTreeHashCache { - inner: Option, -} - -#[derive(Debug, PartialEq, Clone)] -pub struct OptionalTreeHashCacheInner { - arena: CacheArena, - tree_hash_cache: TreeHashCache, -} - -impl OptionalTreeHashCache { - /// Initialize a new cache if `item.is_some()`. - fn new>(item: Option<&C>) -> Self { - let inner = item.map(OptionalTreeHashCacheInner::new); - Self { inner } - } - - /// Compute the tree hash root for the given `item`. - /// - /// This function will initialize the inner cache if necessary (e.g. when crossing the fork). - fn recalculate_tree_hash_root>( - &mut self, - item: &C, - ) -> Result { - let cache = self - .inner - .get_or_insert_with(|| OptionalTreeHashCacheInner::new(item)); - item.recalculate_tree_hash_root(&mut cache.arena, &mut cache.tree_hash_cache) - .map_err(Into::into) - } -} - -impl OptionalTreeHashCacheInner { - fn new>(item: &C) -> Self { - let mut arena = CacheArena::default(); - let tree_hash_cache = item.new_tree_hash_cache(&mut arena); - OptionalTreeHashCacheInner { - arena, - tree_hash_cache, - } - } -} - -impl arbitrary::Arbitrary<'_> for BeaconTreeHashCache { - fn arbitrary(_u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - Ok(Self::default()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{MainnetEthSpec, ParticipationFlags}; - - #[test] - fn validator_node_count() { - let mut arena = CacheArena::default(); - let v = Validator::default(); - let _cache = v.new_tree_hash_cache(&mut arena); - assert_eq!(arena.backing_len(), NODES_PER_VALIDATOR); - } - - #[test] - fn participation_flags() { - type N = ::ValidatorRegistryLimit; - let len = 65; - let mut test_flag = ParticipationFlags::default(); - test_flag.add_flag(0).unwrap(); - let epoch_participation = VariableList::<_, N>::new(vec![test_flag; len]).unwrap(); - - let mut cache = OptionalTreeHashCache { inner: None }; - - let cache_root = cache - .recalculate_tree_hash_root(&ParticipationList::new(&epoch_participation)) - .unwrap(); - let recalc_root = cache - .recalculate_tree_hash_root(&ParticipationList::new(&epoch_participation)) - .unwrap(); - - assert_eq!(cache_root, recalc_root, "recalculated root should match"); - assert_eq!( - cache_root, - epoch_participation.tree_hash_root(), - "cached root should match uncached" - ); - } -} diff --git a/consensus/types/src/blob_sidecar.rs b/consensus/types/src/blob_sidecar.rs index 31b1307aa7f..e54bc2f4f97 100644 --- a/consensus/types/src/blob_sidecar.rs +++ b/consensus/types/src/blob_sidecar.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::{ beacon_block_body::BLOB_KZG_COMMITMENTS_INDEX, BeaconBlockHeader, BeaconStateError, Blob, - EthSpec, Hash256, SignedBeaconBlockHeader, Slot, + EthSpec, FixedVector, Hash256, SignedBeaconBlockHeader, Slot, VariableList, }; use crate::{KzgProofs, SignedBeaconBlock}; use bls::Signature; @@ -16,7 +16,6 @@ use safe_arith::{ArithError, SafeArith}; use serde::{Deserialize, Serialize}; use ssz::Encode; use ssz_derive::{Decode, Encode}; -use ssz_types::{FixedVector, VariableList}; use std::fmt::Debug; use std::hash::Hash; use std::sync::Arc; diff --git a/consensus/types/src/execution_payload_header.rs b/consensus/types/src/execution_payload_header.rs index 3d0b0aca41d..02850304f1d 100644 --- a/consensus/types/src/execution_payload_header.rs +++ b/consensus/types/src/execution_payload_header.rs @@ -32,7 +32,8 @@ use tree_hash_derive::TreeHash; tree_hash(enum_behaviour = "transparent") ), cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), - partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + map_ref_into(ExecutionPayloadHeader) )] #[derive( Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative, arbitrary::Arbitrary, @@ -364,6 +365,27 @@ impl TryFrom> for ExecutionPayloadHeaderDe } } +impl<'a, E: EthSpec> ExecutionPayloadHeaderRefMut<'a, E> { + /// Mutate through + pub fn replace(self, header: ExecutionPayloadHeader) -> Result<(), BeaconStateError> { + match self { + ExecutionPayloadHeaderRefMut::Merge(mut_ref) => { + *mut_ref = header.try_into()?; + } + ExecutionPayloadHeaderRefMut::Capella(mut_ref) => { + *mut_ref = header.try_into()?; + } + ExecutionPayloadHeaderRefMut::Deneb(mut_ref) => { + *mut_ref = header.try_into()?; + } + ExecutionPayloadHeaderRefMut::Electra(mut_ref) => { + *mut_ref = header.try_into()?; + } + } + Ok(()) + } +} + impl TryFrom> for ExecutionPayloadHeaderElectra { type Error = BeaconStateError; fn try_from(header: ExecutionPayloadHeader) -> Result { diff --git a/consensus/types/src/historical_batch.rs b/consensus/types/src/historical_batch.rs index 1c565c0092d..7bac9699eb6 100644 --- a/consensus/types/src/historical_batch.rs +++ b/consensus/types/src/historical_batch.rs @@ -23,8 +23,10 @@ use tree_hash_derive::TreeHash; )] #[arbitrary(bound = "E: EthSpec")] pub struct HistoricalBatch { - pub block_roots: FixedVector, - pub state_roots: FixedVector, + #[test_random(default)] + pub block_roots: Vector, + #[test_random(default)] + pub state_roots: Vector, } #[cfg(test)] diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index dee55789398..feefbc48946 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -98,7 +98,6 @@ pub mod sync_committee_contribution; pub mod sync_committee_message; pub mod sync_selection_proof; pub mod sync_subnet_id; -mod tree_hash_impls; pub mod validator_registration_data; pub mod withdrawal; @@ -132,7 +131,7 @@ pub use crate::beacon_block_body::{ }; pub use crate::beacon_block_header::BeaconBlockHeader; pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee}; -pub use crate::beacon_state::{BeaconTreeHashCache, Error as BeaconStateError, *}; +pub use crate::beacon_state::{Error as BeaconStateError, *}; pub use crate::blob_sidecar::{BlobSidecar, BlobSidecarList, BlobsList}; pub use crate::bls_to_execution_change::BlsToExecutionChange; pub use crate::chain_spec::{ChainSpec, Config, Domain}; @@ -257,8 +256,7 @@ pub use bls::{ AggregatePublicKey, AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey, Signature, SignatureBytes, }; - pub use kzg::{KzgCommitment, KzgProof, VERSIONED_HASH_VERSION_KZG}; - +pub use milhouse::{self, List, Vector}; pub use ssz_types::{typenum, typenum::Unsigned, BitList, BitVector, FixedVector, VariableList}; pub use superstruct::superstruct; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 33fbf214c80..4d42d357c19 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,8 +1,7 @@ -use super::{BeaconState, EthSpec, FixedVector, Hash256, LightClientHeader, SyncCommittee}; use crate::{ - light_client_update::*, test_utils::TestRandom, ChainSpec, ForkName, ForkVersionDeserialize, - LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock, - Slot, + light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, EthSpec, FixedVector, + ForkName, ForkVersionDeserialize, Hash256, LightClientHeader, LightClientHeaderAltair, + LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock, Slot, SyncCommittee, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index c5c730e8b83..5f83b0db523 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -58,6 +58,7 @@ pub struct LightClientFinalityUpdate { #[superstruct(only(Deneb), partial_getter(rename = "finalized_header_deneb"))] pub finalized_header: LightClientHeaderDeneb, /// Merkle proof attesting finalized header. + #[test_random(default)] pub finality_branch: FixedVector, /// current sync aggregate pub sync_aggregate: SyncAggregate, diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index af9cbc16610..d5e8cd592df 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -37,6 +37,7 @@ pub const EXECUTION_PAYLOAD_PROOF_LEN: usize = 4; #[derive(Debug, PartialEq, Clone)] pub enum Error { SszTypesError(ssz_types::Error), + MilhouseError(milhouse::Error), BeaconStateError(beacon_state::Error), ArithError(ArithError), AltairForkNotActive, @@ -65,6 +66,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: milhouse::Error) -> Error { + Error::MilhouseError(e) + } +} + /// A LightClientUpdate is the update we request solely to either complete the bootstrapping process, /// or to sync up to the last committee period, we need to have one ready for each ALTAIR period /// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD]. diff --git a/consensus/types/src/test_utils/test_random.rs b/consensus/types/src/test_utils/test_random.rs index 0adaf81bd7d..72a7a036ccc 100644 --- a/consensus/types/src/test_utils/test_random.rs +++ b/consensus/types/src/test_utils/test_random.rs @@ -87,7 +87,7 @@ where } } -impl TestRandom for FixedVector +impl TestRandom for ssz_types::FixedVector where T: TestRandom, { diff --git a/consensus/types/src/tree_hash_impls.rs b/consensus/types/src/tree_hash_impls.rs deleted file mode 100644 index eb3660d4666..00000000000 --- a/consensus/types/src/tree_hash_impls.rs +++ /dev/null @@ -1,165 +0,0 @@ -//! This module contains custom implementations of `CachedTreeHash` for ETH2-specific types. -//! -//! It makes some assumptions about the layouts and update patterns of other structs in this -//! crate, and should be updated carefully whenever those structs are changed. -use crate::{Epoch, Hash256, PublicKeyBytes, Validator}; -use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, Error, TreeHashCache}; -use int_to_bytes::int_to_fixed_bytes32; -use tree_hash::merkle_root; - -/// Number of struct fields on `Validator`. -const NUM_VALIDATOR_FIELDS: usize = 8; - -impl CachedTreeHash for Validator { - fn new_tree_hash_cache(&self, arena: &mut CacheArena) -> TreeHashCache { - TreeHashCache::new(arena, int_log(NUM_VALIDATOR_FIELDS), NUM_VALIDATOR_FIELDS) - } - - /// Efficiently tree hash a `Validator`, assuming it was updated by a valid state transition. - /// - /// Specifically, we assume that the `pubkey` field is constant. - fn recalculate_tree_hash_root( - &self, - arena: &mut CacheArena, - cache: &mut TreeHashCache, - ) -> Result { - // Otherwise just check the fields which might have changed. - let dirty_indices = cache - .leaves() - .iter_mut(arena)? - .enumerate() - .flat_map(|(i, leaf)| { - // Pubkey field (index 0) is constant. - if i == 0 && cache.initialized { - None - } else if process_field_by_index(self, i, leaf, !cache.initialized) { - Some(i) - } else { - None - } - }) - .collect(); - - cache.update_merkle_root(arena, dirty_indices) - } -} - -fn process_field_by_index( - v: &Validator, - field_idx: usize, - leaf: &mut Hash256, - force_update: bool, -) -> bool { - match field_idx { - 0 => process_pubkey_bytes_field(&v.pubkey, leaf, force_update), - 1 => process_slice_field(v.withdrawal_credentials.as_bytes(), leaf, force_update), - 2 => process_u64_field(v.effective_balance, leaf, force_update), - 3 => process_bool_field(v.slashed, leaf, force_update), - 4 => process_epoch_field(v.activation_eligibility_epoch, leaf, force_update), - 5 => process_epoch_field(v.activation_epoch, leaf, force_update), - 6 => process_epoch_field(v.exit_epoch, leaf, force_update), - 7 => process_epoch_field(v.withdrawable_epoch, leaf, force_update), - _ => panic!( - "Validator type only has {} fields, {} out of bounds", - NUM_VALIDATOR_FIELDS, field_idx - ), - } -} - -fn process_pubkey_bytes_field( - val: &PublicKeyBytes, - leaf: &mut Hash256, - force_update: bool, -) -> bool { - let new_tree_hash = merkle_root(val.as_serialized(), 0); - process_slice_field(new_tree_hash.as_bytes(), leaf, force_update) -} - -fn process_slice_field(new_tree_hash: &[u8], leaf: &mut Hash256, force_update: bool) -> bool { - if force_update || leaf.as_bytes() != new_tree_hash { - leaf.assign_from_slice(new_tree_hash); - true - } else { - false - } -} - -fn process_u64_field(val: u64, leaf: &mut Hash256, force_update: bool) -> bool { - let new_tree_hash = int_to_fixed_bytes32(val); - process_slice_field(&new_tree_hash[..], leaf, force_update) -} - -fn process_epoch_field(val: Epoch, leaf: &mut Hash256, force_update: bool) -> bool { - process_u64_field(val.as_u64(), leaf, force_update) -} - -fn process_bool_field(val: bool, leaf: &mut Hash256, force_update: bool) -> bool { - process_u64_field(val as u64, leaf, force_update) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::test_utils::TestRandom; - use rand::SeedableRng; - use rand_xorshift::XorShiftRng; - use tree_hash::TreeHash; - - fn test_validator_tree_hash(v: &Validator) { - let arena = &mut CacheArena::default(); - - let mut cache = v.new_tree_hash_cache(arena); - // With a fresh cache - assert_eq!( - &v.tree_hash_root()[..], - v.recalculate_tree_hash_root(arena, &mut cache) - .unwrap() - .as_bytes(), - "{:?}", - v - ); - // With a completely up-to-date cache - assert_eq!( - &v.tree_hash_root()[..], - v.recalculate_tree_hash_root(arena, &mut cache) - .unwrap() - .as_bytes(), - "{:?}", - v - ); - } - - #[test] - fn default_validator() { - test_validator_tree_hash(&Validator::default()); - } - - #[test] - fn zeroed_validator() { - let v = Validator { - activation_eligibility_epoch: Epoch::from(0u64), - activation_epoch: Epoch::from(0u64), - ..Default::default() - }; - test_validator_tree_hash(&v); - } - - #[test] - fn random_validators() { - let mut rng = XorShiftRng::from_seed([0xf1; 16]); - let num_validators = 1000; - (0..num_validators) - .map(|_| Validator::random_for_test(&mut rng)) - .for_each(|v| test_validator_tree_hash(&v)); - } - - #[test] - #[allow(clippy::assertions_on_constants)] - pub fn smallvec_size_check() { - // If this test fails we need to go and reassess the length of the `SmallVec` in - // `cached_tree_hash::TreeHashCache`. If the size of the `SmallVec` is too slow we're going - // to start doing heap allocations for each validator, this will fragment memory and slow - // us down. - assert!(NUM_VALIDATOR_FIELDS <= 8,); - } -} diff --git a/lcli/src/new_testnet.rs b/lcli/src/new_testnet.rs index f9da3d2b3e9..edba4249966 100644 --- a/lcli/src/new_testnet.rs +++ b/lcli/src/new_testnet.rs @@ -264,7 +264,7 @@ fn initialize_state_with_validators( let mut state = BeaconState::new(genesis_time, eth1_data, spec); // Seed RANDAO with Eth1 entropy - state.fill_randao_mixes_with(eth1_block_hash); + state.fill_randao_mixes_with(eth1_block_hash).unwrap(); for keypair in keypairs.iter() { let withdrawal_credentials = |pubkey: &PublicKey| { diff --git a/lcli/src/replace_state_pubkeys.rs b/lcli/src/replace_state_pubkeys.rs index 0f9fac3aff9..e8d012b16ec 100644 --- a/lcli/src/replace_state_pubkeys.rs +++ b/lcli/src/replace_state_pubkeys.rs @@ -42,7 +42,8 @@ pub fn run(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(), let mut deposit_tree = DepositDataTree::create(&[], 0, DEPOSIT_TREE_DEPTH); let mut deposit_root = Hash256::zero(); - for (index, validator) in state.validators_mut().iter_mut().enumerate() { + let validators = state.validators_mut(); + for index in 0..validators.len() { let (secret, _) = recover_validator_secret_from_mnemonic(seed.as_bytes(), index as u32, KeyType::Voting) .map_err(|e| format!("Unable to generate validator key: {:?}", e))?; @@ -52,11 +53,11 @@ pub fn run(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(), eprintln!("{}: {}", index, keypair.pk); - validator.pubkey = keypair.pk.into(); + validators.get_mut(index).unwrap().pubkey = keypair.pk.into(); // Update the deposit tree. let mut deposit_data = DepositData { - pubkey: validator.pubkey, + pubkey: validators.get(index).unwrap().pubkey, // Set this to a junk value since it's very time consuming to generate the withdrawal // keys and it's not useful for the time being. withdrawal_credentials: Hash256::zero(), diff --git a/lcli/src/skip_slots.rs b/lcli/src/skip_slots.rs index 9e5da7709f1..d421c077d83 100644 --- a/lcli/src/skip_slots.rs +++ b/lcli/src/skip_slots.rs @@ -57,7 +57,7 @@ use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; use std::time::{Duration, Instant}; -use types::{BeaconState, CloneConfig, EthSpec, Hash256}; +use types::{BeaconState, EthSpec, Hash256}; const HTTP_TIMEOUT: Duration = Duration::from_secs(10); @@ -128,7 +128,7 @@ pub fn run( }; for i in 0..runs { - let mut state = state.clone_with(CloneConfig::all()); + let mut state = state.clone(); let start = Instant::now(); diff --git a/lcli/src/transition_blocks.rs b/lcli/src/transition_blocks.rs index c72b41b1d44..77fd352829f 100644 --- a/lcli/src/transition_blocks.rs +++ b/lcli/src/transition_blocks.rs @@ -76,7 +76,7 @@ use ssz::Encode; use state_processing::state_advance::complete_state_advance; use state_processing::{ block_signature_verifier::BlockSignatureVerifier, per_block_processing, AllCaches, - BlockSignatureStrategy, ConsensusContext, StateProcessingStrategy, VerifyBlockRoot, + BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, }; use std::borrow::Cow; use std::fs::File; @@ -85,7 +85,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::{Duration, Instant}; use store::HotColdDB; -use types::{BeaconState, ChainSpec, CloneConfig, EthSpec, Hash256, SignedBeaconBlock}; +use types::{BeaconState, ChainSpec, EthSpec, Hash256, SignedBeaconBlock}; const HTTP_TIMEOUT: Duration = Duration::from_secs(10); @@ -234,7 +234,7 @@ pub fn run( let mut output_post_state = None; let mut saved_ctxt = None; for i in 0..runs { - let pre_state = pre_state.clone_with(CloneConfig::all()); + let pre_state = pre_state.clone(); let block = block.clone(); let start = Instant::now(); @@ -399,7 +399,6 @@ fn do_transition( &mut pre_state, &block, BlockSignatureStrategy::NoVerification, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, spec, diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index ec10ff4429d..9983717cb1e 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -172,26 +172,6 @@ fn shuffling_cache_set() { .with_config(|config| assert_eq!(config.chain.shuffling_cache_size, 500)); } -#[test] -fn snapshot_cache_default() { - CommandLineTest::new() - .run_with_zero_port() - .with_config(|config| { - assert_eq!( - config.chain.snapshot_cache_size, - beacon_node::beacon_chain::snapshot_cache::DEFAULT_SNAPSHOT_CACHE_SIZE - ) - }); -} - -#[test] -fn snapshot_cache_set() { - CommandLineTest::new() - .flag("state-cache-size", Some("500")) - .run_with_zero_port() - .with_config(|config| assert_eq!(config.chain.snapshot_cache_size, 500)); -} - #[test] fn fork_choice_before_proposal_timeout_default() { CommandLineTest::new() diff --git a/testing/ef_tests/src/case_result.rs b/testing/ef_tests/src/case_result.rs index 67ab9c51bbf..c511d9a1ca0 100644 --- a/testing/ef_tests/src/case_result.rs +++ b/testing/ef_tests/src/case_result.rs @@ -39,6 +39,9 @@ pub fn compare_beacon_state_results_without_caches( if let (Ok(ref mut result), Some(ref mut expected)) = (result.as_mut(), expected.as_mut()) { result.drop_all_caches().unwrap(); expected.drop_all_caches().unwrap(); + + result.apply_pending_mutations().unwrap(); + expected.apply_pending_mutations().unwrap(); } compare_result_detailed(result, expected) diff --git a/testing/ef_tests/src/cases/merkle_proof_validity.rs b/testing/ef_tests/src/cases/merkle_proof_validity.rs index a2e831ade59..cf0b9f77c8f 100644 --- a/testing/ef_tests/src/cases/merkle_proof_validity.rs +++ b/testing/ef_tests/src/cases/merkle_proof_validity.rs @@ -51,7 +51,7 @@ impl LoadCase for MerkleProofValidity { impl Case for MerkleProofValidity { fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> { let mut state = self.state.clone(); - state.initialize_tree_hash_cache(); + state.update_tree_hash_cache().unwrap(); let Ok(proof) = state.compute_merkle_proof(self.merkle_proof.leaf_index) else { return Err(Error::FailedToParseTest( "Could not retrieve merkle proof".to_string(), @@ -77,9 +77,6 @@ impl Case for MerkleProofValidity { } } - // Tree hash cache should still be initialized (not dropped). - assert!(state.tree_hash_cache().is_initialized()); - Ok(()) } } diff --git a/testing/ef_tests/src/cases/sanity_blocks.rs b/testing/ef_tests/src/cases/sanity_blocks.rs index b0902cb5b74..91bb995cc43 100644 --- a/testing/ef_tests/src/cases/sanity_blocks.rs +++ b/testing/ef_tests/src/cases/sanity_blocks.rs @@ -5,7 +5,7 @@ use crate::decode::{ssz_decode_file_with, ssz_decode_state, yaml_decode_file}; use serde::Deserialize; use state_processing::{ per_block_processing, per_slot_processing, BlockProcessingError, BlockSignatureStrategy, - ConsensusContext, StateProcessingStrategy, VerifyBlockRoot, + ConsensusContext, VerifyBlockRoot, }; use types::{BeaconState, RelativeEpoch, SignedBeaconBlock}; @@ -96,7 +96,6 @@ impl Case for SanityBlocks { &mut indiv_state, signed_block, BlockSignatureStrategy::VerifyIndividual, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, spec, @@ -107,7 +106,6 @@ impl Case for SanityBlocks { &mut bulk_state, signed_block, BlockSignatureStrategy::VerifyBulk, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, spec, diff --git a/testing/ef_tests/src/cases/ssz_generic.rs b/testing/ef_tests/src/cases/ssz_generic.rs index bb2465aae10..8de3e217f00 100644 --- a/testing/ef_tests/src/cases/ssz_generic.rs +++ b/testing/ef_tests/src/cases/ssz_generic.rs @@ -1,14 +1,14 @@ #![allow(non_snake_case)] use super::*; -use crate::cases::common::{TestU128, TestU256}; -use crate::decode::{snappy_decode_file, yaml_decode_file}; -use serde::Deserialize; -use serde::{de::Error as SerdeError, Deserializer}; +use crate::cases::common::{SszStaticType, TestU128, TestU256}; +use crate::cases::ssz_static::{check_serialization, check_tree_hash}; +use crate::decode::{log_file_access, snappy_decode_file, yaml_decode_file}; +use serde::{de::Error as SerdeError, Deserialize, Deserializer}; use ssz_derive::{Decode, Encode}; use tree_hash_derive::TreeHash; use types::typenum::*; -use types::{BitList, BitVector, FixedVector, VariableList}; +use types::{BitList, BitVector, FixedVector, ForkName, VariableList, Vector}; #[derive(Debug, Clone, Deserialize)] struct Metadata { @@ -125,6 +125,20 @@ impl Case for SszGeneric { let elem_ty = parts[1]; let length = parts[2]; + // Skip length 0 tests. Milhouse doesn't have any checks against 0-capacity lists. + if length == "0" { + log_file_access(self.path.join("serialized.ssz_snappy")); + return Ok(()); + } + + type_dispatch!( + ssz_generic_test, + (&self.path), + Vector, + <>, + [elem_ty => primitive_type] + [length => typenum] + )?; type_dispatch!( ssz_generic_test, (&self.path), @@ -263,8 +277,8 @@ struct ComplexTestStruct { #[serde(deserialize_with = "byte_list_from_hex_str")] D: VariableList, E: VarTestStruct, - F: FixedVector, - G: FixedVector, + F: Vector, + G: Vector, } #[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)] diff --git a/testing/ef_tests/src/cases/ssz_static.rs b/testing/ef_tests/src/cases/ssz_static.rs index e41c90c6e03..5f0ac3525c4 100644 --- a/testing/ef_tests/src/cases/ssz_static.rs +++ b/testing/ef_tests/src/cases/ssz_static.rs @@ -118,7 +118,6 @@ impl Case for SszStaticTHC> { check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?; let mut state = self.value.clone(); - state.initialize_tree_hash_cache(); let cached_tree_hash_root = state.update_tree_hash_cache().unwrap(); check_tree_hash(&self.roots.root, cached_tree_hash_root.as_bytes())?; diff --git a/testing/ef_tests/src/cases/transition.rs b/testing/ef_tests/src/cases/transition.rs index 927589948a2..b2c49a96feb 100644 --- a/testing/ef_tests/src/cases/transition.rs +++ b/testing/ef_tests/src/cases/transition.rs @@ -4,7 +4,7 @@ use crate::decode::{ssz_decode_file_with, ssz_decode_state, yaml_decode_file}; use serde::Deserialize; use state_processing::{ per_block_processing, state_advance::complete_state_advance, BlockSignatureStrategy, - ConsensusContext, StateProcessingStrategy, VerifyBlockRoot, + ConsensusContext, VerifyBlockRoot, }; use std::str::FromStr; use types::{BeaconState, Epoch, SignedBeaconBlock}; @@ -114,7 +114,6 @@ impl Case for TransitionTest { &mut state, block, BlockSignatureStrategy::VerifyBulk, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, spec, diff --git a/testing/state_transition_vectors/src/exit.rs b/testing/state_transition_vectors/src/exit.rs index e3cd346da13..61cae6dbe1b 100644 --- a/testing/state_transition_vectors/src/exit.rs +++ b/testing/state_transition_vectors/src/exit.rs @@ -1,7 +1,7 @@ use super::*; use state_processing::{ per_block_processing, per_block_processing::errors::ExitInvalid, BlockProcessingError, - BlockSignatureStrategy, ConsensusContext, StateProcessingStrategy, VerifyBlockRoot, + BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, }; use types::{BeaconBlock, Epoch}; @@ -68,7 +68,6 @@ impl ExitTest { state, block, BlockSignatureStrategy::VerifyIndividual, - StateProcessingStrategy::Accurate, VerifyBlockRoot::True, &mut ctxt, &E::default_spec(), @@ -332,7 +331,7 @@ mod custom_tests { fn assert_exited(state: &BeaconState, validator_index: usize) { let spec = E::default_spec(); - let validator = &state.validators()[validator_index]; + let validator = &state.validators().get(validator_index).unwrap(); assert_eq!( validator.exit_epoch, // This is correct until we exceed the churn limit. If that happens, we