Skip to content

Commit 5f208bb

Browse files
authored
Implement basic validator custody framework (no backfill) (#7578)
Resolves #6767 This PR implements a basic version of validator custody. - It introduces a new `CustodyContext` object which contains info regarding number of validators attached to a node and the custody count they contribute to the cgc. - The `CustodyContext` is added in the da_checker and has methods for returning the current cgc and the number of columns to sample at head. Note that the logic for returning the cgc existed previously in the network globals. - To estimate the number of validators attached, we use the `beacon_committee_subscriptions` endpoint. This might overestimate the number of validators actually publishing attestations from the node in the case of multi BN setups. We could also potentially use the `publish_attestations` endpoint to get a more conservative estimate at a later point. - Anytime there's a change in the `custody_group_count` due to addition/removal of validators, the custody context should send an event on a broadcast channnel. The only subscriber for the channel exists in the network service which simply subscribes to more subnets. There can be additional subscribers in sync that will start a backfill once the cgc changes. TODO - [ ] **NOT REQUIRED:** Currently, the logic only handles an increase in validator count and does not handle a decrease. We should ideally unsubscribe from subnets when the cgc has decreased. - [ ] **NOT REQUIRED:** Add a service in the `CustodyContext` that emits an event once `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS ` passes after updating the current cgc. This event should be picked up by a subscriber which updates the enr and metadata. - [x] Add more tests
1 parent 076a1c3 commit 5f208bb

38 files changed

+928
-350
lines changed

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,14 @@ use crate::observed_data_sidecars::ObservedDataSidecars;
5858
use crate::observed_operations::{ObservationOutcome, ObservedOperations};
5959
use crate::observed_slashable::ObservedSlashable;
6060
use crate::persisted_beacon_chain::PersistedBeaconChain;
61+
use crate::persisted_custody::persist_custody_context;
6162
use crate::persisted_fork_choice::PersistedForkChoice;
6263
use crate::pre_finalization_cache::PreFinalizationBlockCache;
6364
use crate::shuffling_cache::{BlockShufflingIds, ShufflingCache};
6465
use crate::sync_committee_verification::{
6566
Error as SyncCommitteeError, VerifiedSyncCommitteeMessage, VerifiedSyncContribution,
6667
};
68+
use crate::validator_custody::CustodyContextSsz;
6769
use crate::validator_monitor::{
6870
get_slot_delay_ms, timestamp_now, ValidatorMonitor,
6971
HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS,
@@ -670,6 +672,23 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
670672
Ok(())
671673
}
672674

675+
/// Persists the custody information to disk.
676+
pub fn persist_custody_context(&self) -> Result<(), Error> {
677+
let custody_context: CustodyContextSsz = self
678+
.data_availability_checker
679+
.custody_context()
680+
.as_ref()
681+
.into();
682+
debug!(?custody_context, "Persisting custody context to store");
683+
684+
persist_custody_context::<T::EthSpec, T::HotStore, T::ColdStore>(
685+
self.store.clone(),
686+
custody_context,
687+
)?;
688+
689+
Ok(())
690+
}
691+
673692
/// Returns the slot _right now_ according to `self.slot_clock`. Returns `Err` if the slot is
674693
/// unavailable.
675694
///
@@ -2990,7 +3009,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
29903009
pub async fn verify_block_for_gossip(
29913010
self: &Arc<Self>,
29923011
block: Arc<SignedBeaconBlock<T::EthSpec>>,
2993-
custody_columns_count: usize,
29943012
) -> Result<GossipVerifiedBlock<T>, BlockError> {
29953013
let chain = self.clone();
29963014
self.task_executor
@@ -3000,7 +3018,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
30003018
let slot = block.slot();
30013019
let graffiti_string = block.message().body().graffiti().as_utf8_lossy();
30023020

3003-
match GossipVerifiedBlock::new(block, &chain, custody_columns_count) {
3021+
match GossipVerifiedBlock::new(block, &chain) {
30043022
Ok(verified) => {
30053023
let commitments_formatted = verified.block.commitments_formatted();
30063024
debug!(
@@ -7232,7 +7250,8 @@ impl<T: BeaconChainTypes> Drop for BeaconChain<T> {
72327250
let drop = || -> Result<(), Error> {
72337251
self.persist_fork_choice()?;
72347252
self.persist_op_pool()?;
7235-
self.persist_eth1_cache()
7253+
self.persist_eth1_cache()?;
7254+
self.persist_custody_context()
72367255
};
72377256

72387257
if let Err(e) = drop() {

beacon_node/beacon_chain/src/block_verification.rs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,6 @@ pub struct GossipVerifiedBlock<T: BeaconChainTypes> {
685685
pub block_root: Hash256,
686686
parent: Option<PreProcessingSnapshot<T::EthSpec>>,
687687
consensus_context: ConsensusContext<T::EthSpec>,
688-
custody_columns_count: usize,
689688
}
690689

691690
/// A wrapper around a `SignedBeaconBlock` that indicates that all signatures (except the deposit
@@ -721,7 +720,6 @@ pub trait IntoGossipVerifiedBlock<T: BeaconChainTypes>: Sized {
721720
fn into_gossip_verified_block(
722721
self,
723722
chain: &BeaconChain<T>,
724-
custody_columns_count: usize,
725723
) -> Result<GossipVerifiedBlock<T>, BlockError>;
726724
fn inner_block(&self) -> Arc<SignedBeaconBlock<T::EthSpec>>;
727725
}
@@ -730,7 +728,6 @@ impl<T: BeaconChainTypes> IntoGossipVerifiedBlock<T> for GossipVerifiedBlock<T>
730728
fn into_gossip_verified_block(
731729
self,
732730
_chain: &BeaconChain<T>,
733-
_custody_columns_count: usize,
734731
) -> Result<GossipVerifiedBlock<T>, BlockError> {
735732
Ok(self)
736733
}
@@ -743,9 +740,8 @@ impl<T: BeaconChainTypes> IntoGossipVerifiedBlock<T> for Arc<SignedBeaconBlock<T
743740
fn into_gossip_verified_block(
744741
self,
745742
chain: &BeaconChain<T>,
746-
custody_columns_count: usize,
747743
) -> Result<GossipVerifiedBlock<T>, BlockError> {
748-
GossipVerifiedBlock::new(self, chain, custody_columns_count)
744+
GossipVerifiedBlock::new(self, chain)
749745
}
750746

751747
fn inner_block(&self) -> Arc<SignedBeaconBlock<T::EthSpec>> {
@@ -821,7 +817,6 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
821817
pub fn new(
822818
block: Arc<SignedBeaconBlock<T::EthSpec>>,
823819
chain: &BeaconChain<T>,
824-
custody_columns_count: usize,
825820
) -> Result<Self, BlockError> {
826821
// If the block is valid for gossip we don't supply it to the slasher here because
827822
// we assume it will be transformed into a fully verified block. We *do* need to supply
@@ -831,22 +826,19 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
831826
// The `SignedBeaconBlock` and `SignedBeaconBlockHeader` have the same canonical root,
832827
// but it's way quicker to calculate root of the header since the hash of the tree rooted
833828
// at `BeaconBlockBody` is already computed in the header.
834-
Self::new_without_slasher_checks(block, &header, chain, custody_columns_count).map_err(
835-
|e| {
836-
process_block_slash_info::<_, BlockError>(
837-
chain,
838-
BlockSlashInfo::from_early_error_block(header, e),
839-
)
840-
},
841-
)
829+
Self::new_without_slasher_checks(block, &header, chain).map_err(|e| {
830+
process_block_slash_info::<_, BlockError>(
831+
chain,
832+
BlockSlashInfo::from_early_error_block(header, e),
833+
)
834+
})
842835
}
843836

844837
/// As for new, but doesn't pass the block to the slasher.
845838
fn new_without_slasher_checks(
846839
block: Arc<SignedBeaconBlock<T::EthSpec>>,
847840
block_header: &SignedBeaconBlockHeader,
848841
chain: &BeaconChain<T>,
849-
custody_columns_count: usize,
850842
) -> Result<Self, BlockError> {
851843
// Ensure the block is the correct structure for the fork at `block.slot()`.
852844
block
@@ -1054,7 +1046,6 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
10541046
block_root,
10551047
parent,
10561048
consensus_context,
1057-
custody_columns_count,
10581049
})
10591050
}
10601051

@@ -1202,7 +1193,6 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
12021193
block: MaybeAvailableBlock::AvailabilityPending {
12031194
block_root: from.block_root,
12041195
block,
1205-
custody_columns_count: from.custody_columns_count,
12061196
},
12071197
block_root: from.block_root,
12081198
parent: Some(parent),

beacon_node/beacon_chain/src/block_verification_types.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ use types::{
3131
pub struct RpcBlock<E: EthSpec> {
3232
block_root: Hash256,
3333
block: RpcBlockInner<E>,
34-
custody_columns_count: usize,
3534
}
3635

3736
impl<E: EthSpec> Debug for RpcBlock<E> {
@@ -45,10 +44,6 @@ impl<E: EthSpec> RpcBlock<E> {
4544
self.block_root
4645
}
4746

48-
pub fn custody_columns_count(&self) -> usize {
49-
self.custody_columns_count
50-
}
51-
5247
pub fn as_block(&self) -> &SignedBeaconBlock<E> {
5348
match &self.block {
5449
RpcBlockInner::Block(block) => block,
@@ -103,14 +98,12 @@ impl<E: EthSpec> RpcBlock<E> {
10398
pub fn new_without_blobs(
10499
block_root: Option<Hash256>,
105100
block: Arc<SignedBeaconBlock<E>>,
106-
custody_columns_count: usize,
107101
) -> Self {
108102
let block_root = block_root.unwrap_or_else(|| get_block_root(&block));
109103

110104
Self {
111105
block_root,
112106
block: RpcBlockInner::Block(block),
113-
custody_columns_count,
114107
}
115108
}
116109

@@ -152,16 +145,13 @@ impl<E: EthSpec> RpcBlock<E> {
152145
Ok(Self {
153146
block_root,
154147
block: inner,
155-
// Block is before PeerDAS
156-
custody_columns_count: 0,
157148
})
158149
}
159150

160151
pub fn new_with_custody_columns(
161152
block_root: Option<Hash256>,
162153
block: Arc<SignedBeaconBlock<E>>,
163154
custody_columns: Vec<CustodyDataColumn<E>>,
164-
custody_columns_count: usize,
165155
spec: &ChainSpec,
166156
) -> Result<Self, AvailabilityCheckError> {
167157
let block_root = block_root.unwrap_or_else(|| get_block_root(&block));
@@ -182,7 +172,6 @@ impl<E: EthSpec> RpcBlock<E> {
182172
Ok(Self {
183173
block_root,
184174
block: inner,
185-
custody_columns_count,
186175
})
187176
}
188177

@@ -250,12 +239,10 @@ impl<E: EthSpec> ExecutedBlock<E> {
250239
MaybeAvailableBlock::AvailabilityPending {
251240
block_root: _,
252241
block: pending_block,
253-
custody_columns_count,
254242
} => Self::AvailabilityPending(AvailabilityPendingExecutedBlock::new(
255243
pending_block,
256244
import_data,
257245
payload_verification_outcome,
258-
custody_columns_count,
259246
)),
260247
}
261248
}
@@ -321,21 +308,18 @@ pub struct AvailabilityPendingExecutedBlock<E: EthSpec> {
321308
pub block: Arc<SignedBeaconBlock<E>>,
322309
pub import_data: BlockImportData<E>,
323310
pub payload_verification_outcome: PayloadVerificationOutcome,
324-
pub custody_columns_count: usize,
325311
}
326312

327313
impl<E: EthSpec> AvailabilityPendingExecutedBlock<E> {
328314
pub fn new(
329315
block: Arc<SignedBeaconBlock<E>>,
330316
import_data: BlockImportData<E>,
331317
payload_verification_outcome: PayloadVerificationOutcome,
332-
custody_columns_count: usize,
333318
) -> Self {
334319
Self {
335320
block,
336321
import_data,
337322
payload_verification_outcome,
338-
custody_columns_count,
339323
}
340324
}
341325

beacon_node/beacon_chain/src/builder.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ use crate::light_client_server_cache::LightClientServerCache;
1313
use crate::migrate::{BackgroundMigrator, MigratorConfig};
1414
use crate::observed_data_sidecars::ObservedDataSidecars;
1515
use crate::persisted_beacon_chain::PersistedBeaconChain;
16+
use crate::persisted_custody::load_custody_context;
1617
use crate::shuffling_cache::{BlockShufflingIds, ShufflingCache};
1718
use crate::validator_monitor::{ValidatorMonitor, ValidatorMonitorConfig};
1819
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
1920
use crate::ChainConfig;
21+
use crate::CustodyContext;
2022
use crate::{
2123
BeaconChain, BeaconChainTypes, BeaconForkChoiceStore, BeaconSnapshot, Eth1Chain,
2224
Eth1ChainBackend, ServerSentEventHandler,
@@ -926,6 +928,20 @@ where
926928
}
927929
};
928930

931+
// Load the persisted custody context from the db and initialize
932+
// the context for this run
933+
let custody_context = if let Some(custody) =
934+
load_custody_context::<E, THotStore, TColdStore>(store.clone())
935+
{
936+
Arc::new(CustodyContext::new_from_persisted_custody_context(
937+
custody,
938+
self.import_all_data_columns,
939+
))
940+
} else {
941+
Arc::new(CustodyContext::new(self.import_all_data_columns))
942+
};
943+
debug!(?custody_context, "Loading persisted custody context");
944+
929945
let beacon_chain = BeaconChain {
930946
spec: self.spec.clone(),
931947
config: self.chain_config,
@@ -999,8 +1015,14 @@ where
9991015
validator_monitor: RwLock::new(validator_monitor),
10001016
genesis_backfill_slot,
10011017
data_availability_checker: Arc::new(
1002-
DataAvailabilityChecker::new(slot_clock, self.kzg.clone(), store, self.spec)
1003-
.map_err(|e| format!("Error initializing DataAvailabilityChecker: {:?}", e))?,
1018+
DataAvailabilityChecker::new(
1019+
slot_clock,
1020+
self.kzg.clone(),
1021+
store,
1022+
custody_context,
1023+
self.spec,
1024+
)
1025+
.map_err(|e| format!("Error initializing DataAvailabilityChecker: {:?}", e))?,
10041026
),
10051027
kzg: self.kzg.clone(),
10061028
rng: Arc::new(Mutex::new(rng)),

0 commit comments

Comments
 (0)