Use same fmt and clippy configs as in Substrate (#7611)

* Use same rustfmt.toml as Substrate

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* format format file

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Format with new config

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add Substrate Clippy config

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Print Clippy version in CI

Otherwise its difficult to reproduce locally.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Make fmt happy

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update node/core/pvf/src/error.rs

Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

* Update node/core/pvf/src/error.rs

Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>
This commit is contained in:
Oliver Tale-Yazdi
2023-08-14 16:29:29 +02:00
committed by GitHub
parent ac435c96cf
commit 342d720573
203 changed files with 1880 additions and 1504 deletions
+1
View File
@@ -29,4 +29,5 @@ rustflags = [
"-Aclippy::needless_option_as_deref", # false positives
"-Aclippy::derivable_impls", # false positives
"-Aclippy::stable_sort_primitive", # prefer stable sort
"-Aclippy::extra-unused-type-parameters", # stylistic
]
+2 -2
View File
@@ -130,8 +130,8 @@ pub struct RunCmd {
pub overseer_channel_capacity_override: Option<usize>,
/// Path to the directory where auxiliary worker binaries reside. If not specified, the main
/// binary's directory is searched first, then `/usr/lib/polkadot` is searched. TESTING ONLY: if
/// the path points to an executable rather then directory, that executable is used both as
/// binary's directory is searched first, then `/usr/lib/polkadot` is searched. TESTING ONLY:
/// if the path points to an executable rather then directory, that executable is used both as
/// preparation and execution worker.
#[arg(long, value_name = "PATH")]
pub workers_path: Option<PathBuf>,
+2 -2
View File
@@ -148,8 +148,8 @@ impl SubstrateCli for Cli {
let chain_spec = Box::new(service::PolkadotChainSpec::from_json_file(path.clone())?)
as Box<dyn service::ChainSpec>;
// When `force_*` is given or the file name starts with the name of one of the known chains,
// we use the chain spec for the specific chain.
// When `force_*` is given or the file name starts with the name of one of the known
// chains, we use the chain spec for the specific chain.
if self.run.force_rococo ||
chain_spec.is_rococo() ||
chain_spec.is_wococo() ||
+6 -6
View File
@@ -91,10 +91,10 @@ impl sp_std::fmt::Debug for CandidateHash {
pub type Nonce = u32;
/// The balance of an account.
/// 128-bits (or 38 significant decimal figures) will allow for 10 m currency (`10^7`) at a resolution
/// to all for one second's worth of an annualised 50% reward be paid to a unit holder (`10^11` unit
/// denomination), or `10^18` total atomic units, to grow at 50%/year for 51 years (`10^9` multiplier)
/// for an eventual total of `10^27` units (27 significant decimal figures).
/// 128-bits (or 38 significant decimal figures) will allow for 10 m currency (`10^7`) at a
/// resolution to all for one second's worth of an annualised 50% reward be paid to a unit holder
/// (`10^11` unit denomination), or `10^18` total atomic units, to grow at 50%/year for 51 years
/// (`10^9` multiplier) for an eventual total of `10^27` units (27 significant decimal figures).
/// We round denomination to `10^12` (12 SDF), and leave the other redundancy at the upper end so
/// that 32 bits may be multiplied with a balance in 128 bits without worrying about overflow.
pub type Balance = u128;
@@ -121,8 +121,8 @@ pub type Remark = [u8; 32];
/// The size of the message is limited by the `config.max_downward_message_size` parameter.
pub type DownwardMessage = sp_std::vec::Vec<u8>;
/// A wrapped version of `DownwardMessage`. The difference is that it has attached the block number when
/// the message was sent.
/// A wrapped version of `DownwardMessage`. The difference is that it has attached the block number
/// when the message was sent.
#[derive(Encode, Decode, Clone, sp_runtime::RuntimeDebug, PartialEq, TypeInfo)]
pub struct InboundDownwardMessage<BlockNumber = crate::BlockNumber> {
/// The block number at which these messages were put into the downward message queue.
+13 -9
View File
@@ -22,9 +22,11 @@
//!
//! * If there is no collation generation config, ignore.
//! * Otherwise, for each `activated` head in the update:
//! * Determine if the para is scheduled on any core by fetching the `availability_cores` Runtime API.
//! * Determine if the para is scheduled on any core by fetching the `availability_cores` Runtime
//! API.
//! * Use the Runtime API subsystem to fetch the full validation data.
//! * Invoke the `collator`, and use its outputs to produce a [`CandidateReceipt`], signed with the configuration's `key`.
//! * Invoke the `collator`, and use its outputs to produce a [`CandidateReceipt`], signed with
//! the configuration's `key`.
//! * Dispatch a [`CollatorProtocolMessage::DistributeCollation`]`(receipt, pov)`.
#![deny(missing_docs)]
@@ -77,8 +79,8 @@ impl CollationGenerationSubsystem {
/// Conceptually, this is very simple: it just loops forever.
///
/// - On incoming overseer messages, it starts or stops jobs as appropriate.
/// - On other incoming messages, if they can be converted into `Job::ToJob` and
/// include a hash, then they're forwarded to the appropriate individual job.
/// - On other incoming messages, if they can be converted into `Job::ToJob` and include a hash,
/// then they're forwarded to the appropriate individual job.
/// - On outgoing messages from the jobs, it forwards them to the overseer.
///
/// If `err_tx` is not `None`, errors are forwarded onto that channel as they occur.
@@ -109,9 +111,10 @@ impl CollationGenerationSubsystem {
}
// handle an incoming message. return true if we should break afterwards.
// note: this doesn't strictly need to be a separate function; it's more an administrative function
// so that we don't clutter the run loop. It could in principle be inlined directly into there.
// it should hopefully therefore be ok that it's an async function mutably borrowing self.
// note: this doesn't strictly need to be a separate function; it's more an administrative
// function so that we don't clutter the run loop. It could in principle be inlined directly
// into there. it should hopefully therefore be ok that it's an async function mutably borrowing
// self.
async fn handle_incoming<Context>(
&mut self,
incoming: SubsystemResult<FromOrchestra<<Context as SubsystemContext>::Message>>,
@@ -319,8 +322,9 @@ async fn handle_new_activations<Context>(
// As long as `POV_BOMB_LIMIT` is at least `max_pov_size`, this ensures
// that honest collators never produce a PoV which is uncompressed.
//
// As such, honest collators never produce an uncompressed PoV which starts with
// a compression magic number, which would lead validators to reject the collation.
// As such, honest collators never produce an uncompressed PoV which starts
// with a compression magic number, which would lead validators to reject
// the collation.
if encoded_size > validation_data.max_pov_size as usize {
gum::debug!(
target: LOG_TARGET,
@@ -203,9 +203,9 @@ mod handle_new_activations {
.into_inner();
// the only activated hash should be from the 4 hash:
// each activated hash generates two scheduled cores: one with its value * 4, one with its value * 5
// given that the test configuration has a `para_id` of 16, there's only one way to get that value: with the 4
// hash.
// each activated hash generates two scheduled cores: one with its value * 4, one with its
// value * 5 given that the test configuration has a `para_id` of 16, there's only one way
// to get that value: with the 4 hash.
assert_eq!(requested_validation_data, vec![[4; 32].into()]);
}
@@ -301,8 +301,8 @@ mod handle_new_activations {
.into_inner();
// we expect a single message to be sent, containing a candidate receipt.
// we don't care too much about the `commitments_hash` right now, but let's ensure that we've calculated the
// correct descriptor
// we don't care too much about the `commitments_hash` right now, but let's ensure that
// we've calculated the correct descriptor
let expect_pov_hash =
test_collation_compressed().proof_of_validity.into_compressed().hash();
let expect_validation_data_hash = test_validation_data().hash();
@@ -42,8 +42,8 @@ pub enum RequiredTranches {
/// assignments that are before the local time.
maximum_broadcast: DelayTranche,
/// The clock drift, in ticks, to apply to the local clock when determining whether
/// to broadcast an assignment or when to schedule a wakeup. The local clock should be treated
/// as though it is `clock_drift` ticks earlier.
/// to broadcast an assignment or when to schedule a wakeup. The local clock should be
/// treated as though it is `clock_drift` ticks earlier.
clock_drift: Tick,
},
/// An exact number of required tranches and a number of no-shows. This indicates that
@@ -55,8 +55,8 @@ pub enum RequiredTranches {
/// The amount of missing votes that should be tolerated.
tolerated_missing: usize,
/// When the next no-show would be, if any. This is used to schedule the next wakeup in the
/// event that there are some assignments that don't have corresponding approval votes. If this
/// is `None`, all assignments have approvals.
/// event that there are some assignments that don't have corresponding approval votes. If
/// this is `None`, all assignments have approvals.
next_no_show: Option<Tick>,
/// The last tick at which a needed assignment was received.
last_assignment_tick: Option<Tick>,
@@ -218,13 +218,14 @@ impl AssignmentCriteria for RealAssignmentCriteria {
}
/// Compute the assignments for a given block. Returns a map containing all assignments to cores in
/// the block. If more than one assignment targets the given core, only the earliest assignment is kept.
/// the block. If more than one assignment targets the given core, only the earliest assignment is
/// kept.
///
/// The `leaving_cores` parameter indicates all cores within the block where a candidate was included,
/// as well as the group index backing those.
/// The `leaving_cores` parameter indicates all cores within the block where a candidate was
/// included, as well as the group index backing those.
///
/// The current description of the protocol assigns every validator to check every core. But at different times.
/// The idea is that most assignments are never triggered and fall by the wayside.
/// The current description of the protocol assigns every validator to check every core. But at
/// different times. The idea is that most assignments are never triggered and fall by the wayside.
///
/// This will not assign to anything the local validator was part of the backing group for.
pub(crate) fn compute_assignments(
@@ -463,8 +464,8 @@ pub(crate) enum InvalidAssignmentReason {
/// * Sample is out of bounds
/// * Validator is present in backing group.
///
/// This function does not check whether the core is actually a valid assignment or not. That should be done
/// outside the scope of this function.
/// This function does not check whether the core is actually a valid assignment or not. That should
/// be done outside the scope of this function.
pub(crate) fn check_assignment_cert(
claimed_core_index: CoreIndex,
validator_index: ValidatorIndex,
@@ -104,7 +104,8 @@ enum ImportedBlockInfoError {
VrfInfoUnavailable,
}
/// Computes information about the imported block. Returns an error if the info couldn't be extracted.
/// Computes information about the imported block. Returns an error if the info couldn't be
/// extracted.
#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
async fn imported_block_info<Context>(
ctx: &mut Context,
@@ -181,20 +182,21 @@ async fn imported_block_info<Context>(
// It's not obvious whether to use the hash or the parent hash for this, intuitively. We
// want to use the block hash itself, and here's why:
//
// First off, 'epoch' in BABE means 'session' in other places. 'epoch' is the terminology from
// the paper, which we fulfill using 'session's, which are a Substrate consensus concept.
// First off, 'epoch' in BABE means 'session' in other places. 'epoch' is the terminology
// from the paper, which we fulfill using 'session's, which are a Substrate consensus
// concept.
//
// In BABE, the on-chain and off-chain view of the current epoch can differ at epoch boundaries
// because epochs change precisely at a slot. When a block triggers a new epoch, the state of
// its parent will still have the old epoch. Conversely, we have the invariant that every
// block in BABE has the epoch _it was authored in_ within its post-state. So we use the
// block, and not its parent.
// In BABE, the on-chain and off-chain view of the current epoch can differ at epoch
// boundaries because epochs change precisely at a slot. When a block triggers a new epoch,
// the state of its parent will still have the old epoch. Conversely, we have the invariant
// that every block in BABE has the epoch _it was authored in_ within its post-state. So we
// use the block, and not its parent.
//
// It's worth nothing that Polkadot session changes, at least for the purposes of parachains,
// would function the same way, except for the fact that they're always delayed by one block.
// This gives us the opposite invariant for sessions - the parent block's post-state gives
// us the canonical information about the session index for any of its children, regardless
// of which slot number they might be produced at.
// It's worth nothing that Polkadot session changes, at least for the purposes of
// parachains, would function the same way, except for the fact that they're always delayed
// by one block. This gives us the opposite invariant for sessions - the parent block's
// post-state gives us the canonical information about the session index for any of its
// children, regardless of which slot number they might be produced at.
ctx.send_message(RuntimeApiMessage::Request(
block_hash,
RuntimeApiRequest::CurrentBabeEpoch(s_tx),
+20 -15
View File
@@ -1232,8 +1232,8 @@ async fn handle_from_overseer<Context>(
);
// Our first wakeup will just be the tranche of our assignment,
// if any. This will likely be superseded by incoming assignments
// and approvals which trigger rescheduling.
// if any. This will likely be superseded by incoming
// assignments and approvals which trigger rescheduling.
actions.push(Action::ScheduleWakeup {
block_hash: block_batch.block_hash,
block_number: block_batch.block_number,
@@ -1256,12 +1256,14 @@ async fn handle_from_overseer<Context>(
crate::ops::canonicalize(db, block_number, block_hash)
.map_err(|e| SubsystemError::with_origin("db", e))?;
// `prune_finalized_wakeups` prunes all finalized block hashes. We prune spans accordingly.
// `prune_finalized_wakeups` prunes all finalized block hashes. We prune spans
// accordingly.
wakeups.prune_finalized_wakeups(block_number, &mut state.spans);
// // `prune_finalized_wakeups` prunes all finalized block hashes. We prune spans accordingly.
// let hash_set = wakeups.block_numbers.values().flatten().collect::<HashSet<_>>();
// state.spans.retain(|hash, _| hash_set.contains(hash));
// // `prune_finalized_wakeups` prunes all finalized block hashes. We prune spans
// accordingly. let hash_set =
// wakeups.block_numbers.values().flatten().collect::<HashSet<_>>(); state.spans.
// retain(|hash, _| hash_set.contains(hash));
Vec::new()
},
@@ -1403,8 +1405,8 @@ async fn get_approval_signatures_for_candidate<Context>(
tx_distribution,
));
// Because of the unbounded sending and the nature of the call (just fetching data from state),
// this should not block long:
// Because of the unbounded sending and the nature of the call (just fetching data from
// state), this should not block long:
match rx_distribution.timeout(WAIT_FOR_SIGS_TIMEOUT).await {
None => {
gum::warn!(
@@ -2117,9 +2119,10 @@ impl ApprovalStateTransition {
}
}
// Advance the approval state, either by importing an approval vote which is already checked to be valid and corresponding to an assigned
// validator on the candidate and block, or by noting that there are no further wakeups or tranches needed. This updates the block entry and candidate entry as
// necessary and schedules any further wakeups.
// Advance the approval state, either by importing an approval vote which is already checked to be
// valid and corresponding to an assigned validator on the candidate and block, or by noting that
// there are no further wakeups or tranches needed. This updates the block entry and candidate entry
// as necessary and schedules any further wakeups.
async fn advance_approval_state<Sender>(
sender: &mut Sender,
state: &State,
@@ -2251,7 +2254,8 @@ where
// 1. This is not a local approval, as we don't store anything new in the approval entry.
// 2. The candidate is not newly approved, as we haven't altered the approval entry's
// approved flag with `mark_approved` above.
// 3. The approver, if any, had already approved the candidate, as we haven't altered the bitfield.
// 3. The approver, if any, had already approved the candidate, as we haven't altered the
// bitfield.
if transition.is_local_approval() || newly_approved || !already_approved_by.unwrap_or(true)
{
// In all other cases, we need to write the candidate entry.
@@ -2279,7 +2283,8 @@ fn should_trigger_assignment(
&approval_entry,
RequiredTranches::All,
)
.is_approved(Tick::max_value()), // when all are required, we are just waiting for the first 1/3+
// when all are required, we are just waiting for the first 1/3+
.is_approved(Tick::max_value()),
RequiredTranches::Pending { maximum_broadcast, clock_drift, .. } => {
let drifted_tranche_now =
tranche_now.saturating_sub(clock_drift as DelayTranche);
@@ -2615,8 +2620,8 @@ async fn launch_approval<Context>(
match val_rx.await {
Err(_) => return ApprovalState::failed(validator_index, candidate_hash),
Ok(Ok(ValidationResult::Valid(_, _))) => {
// Validation checked out. Issue an approval command. If the underlying service is unreachable,
// then there isn't anything we can do.
// Validation checked out. Issue an approval command. If the underlying service is
// unreachable, then there isn't anything we can do.
gum::trace!(target: LOG_TARGET, ?candidate_hash, ?para_id, "Candidate Valid");
@@ -161,7 +161,8 @@ pub fn canonicalize(
}
}
// Update all blocks-at-height keys, deleting all those which now have empty `block_assignments`.
// Update all blocks-at-height keys, deleting all those which now have empty
// `block_assignments`.
for (h, at) in visited_heights.into_iter() {
if at.is_empty() {
overlay_db.delete_blocks_at_height(h);
@@ -170,8 +171,8 @@ pub fn canonicalize(
}
}
// due to the fork pruning, this range actually might go too far above where our actual highest block is,
// if a relatively short fork is canonicalized.
// due to the fork pruning, this range actually might go too far above where our actual highest
// block is, if a relatively short fork is canonicalized.
// TODO https://github.com/paritytech/polkadot/issues/3389
let new_range = StoredBlockRange(canon_number + 1, std::cmp::max(range.1, canon_number + 2));
+19 -16
View File
@@ -67,8 +67,8 @@ const META_PREFIX: &[u8; 4] = b"meta";
const UNFINALIZED_PREFIX: &[u8; 11] = b"unfinalized";
const PRUNE_BY_TIME_PREFIX: &[u8; 13] = b"prune_by_time";
// We have some keys we want to map to empty values because existence of the key is enough. We use this because
// rocksdb doesn't support empty values.
// We have some keys we want to map to empty values because existence of the key is enough. We use
// this because rocksdb doesn't support empty values.
const TOMBSTONE_VALUE: &[u8] = b" ";
/// Unavailable blocks are kept for 1 hour.
@@ -139,10 +139,11 @@ enum State {
/// Candidate data was first observed at the given time but is not available in any block.
#[codec(index = 0)]
Unavailable(BETimestamp),
/// The candidate was first observed at the given time and was included in the given list of unfinalized blocks, which may be
/// empty. The timestamp here is not used for pruning. Either one of these blocks will be finalized or the state will regress to
/// `State::Unavailable`, in which case the same timestamp will be reused. Blocks are sorted ascending first by block number and
/// then hash.
/// The candidate was first observed at the given time and was included in the given list of
/// unfinalized blocks, which may be empty. The timestamp here is not used for pruning. Either
/// one of these blocks will be finalized or the state will regress to `State::Unavailable`, in
/// which case the same timestamp will be reused. Blocks are sorted ascending first by block
/// number and then hash.
#[codec(index = 1)]
Unfinalized(BETimestamp, Vec<(BEBlockNumber, Hash)>),
/// Candidate data has appeared in a finalized block and did so at the given time.
@@ -820,8 +821,8 @@ fn note_block_included(
match load_meta(db, config, &candidate_hash)? {
None => {
// This is alarming. We've observed a block being included without ever seeing it backed.
// Warn and ignore.
// This is alarming. We've observed a block being included without ever seeing it
// backed. Warn and ignore.
gum::warn!(
target: LOG_TARGET,
?candidate_hash,
@@ -894,9 +895,9 @@ async fn process_block_finalized<Context>(
let mut db_transaction = DBTransaction::new();
let (start_prefix, end_prefix) = finalized_block_range(finalized_number);
// We have to do some juggling here of the `iter` to make sure it doesn't cross the `.await` boundary
// as it is not `Send`. That is why we create the iterator once within this loop, drop it,
// do an asynchronous request, and then instantiate the exact same iterator again.
// We have to do some juggling here of the `iter` to make sure it doesn't cross the `.await`
// boundary as it is not `Send`. That is why we create the iterator once within this loop,
// drop it, do an asynchronous request, and then instantiate the exact same iterator again.
let batch_num = {
let mut iter = subsystem
.db
@@ -961,8 +962,9 @@ async fn process_block_finalized<Context>(
update_blocks_at_finalized_height(&subsystem, &mut db_transaction, batch, batch_num, now)?;
// We need to write at the end of the loop so the prefix iterator doesn't pick up the same values again
// in the next iteration. Another unfortunate effect of having to re-initialize the iterator.
// We need to write at the end of the loop so the prefix iterator doesn't pick up the same
// values again in the next iteration. Another unfortunate effect of having to re-initialize
// the iterator.
subsystem.db.write(db_transaction)?;
}
@@ -1215,7 +1217,8 @@ fn process_message(
// We do not bubble up internal errors to caller subsystems, instead the
// tx channel is dropped and that error is caught by the caller subsystem.
//
// We bubble up the specific error here so `av-store` logs still tell what happend.
// We bubble up the specific error here so `av-store` logs still tell what
// happend.
return Err(e.into())
},
}
@@ -1298,8 +1301,8 @@ fn store_available_data(
.with_candidate(candidate_hash)
.with_pov(&available_data.pov);
// Important note: This check below is critical for consensus and the `backing` subsystem relies on it to
// ensure candidate validity.
// Important note: This check below is critical for consensus and the `backing` subsystem relies
// on it to ensure candidate validity.
let chunks = erasure::obtain_chunks_v1(n_validators, &available_data)?;
let branches = erasure::branches(chunks.as_ref());
+8 -6
View File
@@ -422,7 +422,8 @@ struct CandidateBackingJob<Context> {
awaiting_validation: HashSet<CandidateHash>,
/// Data needed for retrying in case of `ValidatedCandidateCommand::AttestNoPoV`.
fallbacks: HashMap<CandidateHash, (AttestingData, Option<jaeger::Span>)>,
/// `Some(h)` if this job has already issued `Seconded` statement for some candidate with `h` hash.
/// `Some(h)` if this job has already issued `Seconded` statement for some candidate with `h`
/// hash.
seconded: Option<CandidateHash>,
/// The candidates that are includable, by hash. Each entry here indicates
/// that we've sent the provisioner the backed candidate.
@@ -562,9 +563,10 @@ async fn store_available_data(
expected_erasure_root: Hash,
) -> Result<(), Error> {
let (tx, rx) = oneshot::channel();
// Important: the `av-store` subsystem will check if the erasure root of the `available_data` matches `expected_erasure_root`
// which was provided by the collator in the `CandidateReceipt`. This check is consensus critical and the `backing` subsystem
// relies on it for ensuring candidate validity.
// Important: the `av-store` subsystem will check if the erasure root of the `available_data`
// matches `expected_erasure_root` which was provided by the collator in the `CandidateReceipt`.
// This check is consensus critical and the `backing` subsystem relies on it for ensuring
// candidate validity.
sender
.send_message(AvailabilityStoreMessage::StoreAvailableData {
candidate_hash,
@@ -582,8 +584,8 @@ async fn store_available_data(
// Make a `PoV` available.
//
// This calls the AV store to write the available data to storage. The AV store also checks the erasure root matches
// the `expected_erasure_root`.
// This calls the AV store to write the available data to storage. The AV store also checks the
// erasure root matches the `expected_erasure_root`.
// This returns `Err()` on erasure root mismatch or due to any AV store subsystem error.
//
// Otherwise, it returns either `Ok(())`
+2 -1
View File
@@ -54,7 +54,8 @@ impl Metrics {
self.0.as_ref().map(|metrics| metrics.process_statement.start_timer())
}
/// Provide a timer for handling `CandidateBackingMessage::GetBackedCandidates` which observes on drop.
/// Provide a timer for handling `CandidateBackingMessage::GetBackedCandidates` which observes
/// on drop.
pub fn time_get_backed_candidates(
&self,
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
+2 -1
View File
@@ -84,7 +84,8 @@ impl Default for TestState {
];
let keystore = Arc::new(sc_keystore::LocalKeystore::in_memory());
// Make sure `Alice` key is in the keystore, so this mocked node will be a parachain validator.
// Make sure `Alice` key is in the keystore, so this mocked node will be a parachain
// validator.
Keystore::sr25519_generate_new(&*keystore, ValidatorId::ID, Some(&validators[0].to_seed()))
.expect("Insert key into keystore");
@@ -137,8 +137,8 @@ async fn get_availability_cores(
/// - get the list of core states from the runtime
/// - for each core, concurrently determine chunk availability (see `get_core_availability`)
/// - return the bitfield if there were no errors at any point in this process
/// (otherwise, it's prone to false negatives)
/// - return the bitfield if there were no errors at any point in this process (otherwise, it's
/// prone to false negatives)
async fn construct_availability_bitfield(
relay_parent: Hash,
span: &jaeger::Span,
@@ -67,15 +67,15 @@ mod tests;
const LOG_TARGET: &'static str = "parachain::candidate-validation";
/// The amount of time to wait before retrying after a retry-able backing validation error. We use a lower value for the
/// backing case, to fit within the lower backing timeout.
/// The amount of time to wait before retrying after a retry-able backing validation error. We use a
/// lower value for the backing case, to fit within the lower backing timeout.
#[cfg(not(test))]
const PVF_BACKING_EXECUTION_RETRY_DELAY: Duration = Duration::from_millis(500);
#[cfg(test)]
const PVF_BACKING_EXECUTION_RETRY_DELAY: Duration = Duration::from_millis(200);
/// The amount of time to wait before retrying after a retry-able approval validation error. We use a higher value for
/// the approval case since we have more time, and if we wait longer it is more likely that transient conditions will
/// resolve.
/// The amount of time to wait before retrying after a retry-able approval validation error. We use
/// a higher value for the approval case since we have more time, and if we wait longer it is more
/// likely that transient conditions will resolve.
#[cfg(not(test))]
const PVF_APPROVAL_EXECUTION_RETRY_DELAY: Duration = Duration::from_secs(3);
#[cfg(test)]
@@ -451,9 +451,9 @@ where
const ASSUMPTIONS: &[OccupiedCoreAssumption] = &[
OccupiedCoreAssumption::Included,
OccupiedCoreAssumption::TimedOut,
// `TimedOut` and `Free` both don't perform any speculation and therefore should be the same
// for our purposes here. In other words, if `TimedOut` matched then the `Free` must be
// matched as well.
// `TimedOut` and `Free` both don't perform any speculation and therefore should be the
// same for our purposes here. In other words, if `TimedOut` matched then the `Free` must
// be matched as well.
];
// Consider running these checks in parallel to reduce validation latency.
@@ -482,9 +482,10 @@ where
AssumptionCheckOutcome::Matches(validation_data, validation_code) =>
Ok(Some((validation_data, validation_code))),
AssumptionCheckOutcome::DoesNotMatch => {
// If neither the assumption of the occupied core having the para included or the assumption
// of the occupied core timing out are valid, then the persisted_validation_data_hash in the descriptor
// is not based on the relay parent and is thus invalid.
// If neither the assumption of the occupied core having the para included or the
// assumption of the occupied core timing out are valid, then the
// persisted_validation_data_hash in the descriptor is not based on the relay parent and
// is thus invalid.
Ok(None)
},
AssumptionCheckOutcome::BadRequest =>
@@ -704,7 +705,8 @@ where
"Invalid candidate (commitments hash)"
);
// If validation produced a new set of commitments, we treat the candidate as invalid.
// If validation produced a new set of commitments, we treat the candidate as
// invalid.
Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))
} else {
Ok(ValidationResult::Valid(outputs, persisted_validation_data))
@@ -744,7 +746,8 @@ trait ValidationBackend {
prep_timeout,
PrepareJobKind::Compilation,
);
// We keep track of the total time that has passed and stop retrying if we are taking too long.
// We keep track of the total time that has passed and stop retrying if we are taking too
// long.
let total_time_start = Instant::now();
let mut validation_result =
@@ -780,8 +783,8 @@ trait ValidationBackend {
_ => break,
}
// If we got a possibly transient error, retry once after a brief delay, on the assumption
// that the conditions that caused this error may have resolved on their own.
// If we got a possibly transient error, retry once after a brief delay, on the
// assumption that the conditions that caused this error may have resolved on their own.
{
// Wait a brief delay before retrying.
futures_timer::Delay::new(retry_delay).await;
@@ -44,13 +44,15 @@ mod tree;
mod tests;
const LOG_TARGET: &str = "parachain::chain-selection";
/// Timestamp based on the 1 Jan 1970 UNIX base, which is persistent across node restarts and OS reboots.
/// Timestamp based on the 1 Jan 1970 UNIX base, which is persistent across node restarts and OS
/// reboots.
type Timestamp = u64;
// If a block isn't approved in 120 seconds, nodes will abandon it
// and begin building on another chain.
const STAGNANT_TIMEOUT: Timestamp = 120;
// Delay prunning of the stagnant keys in prune only mode by 25 hours to avoid interception with the finality
// Delay prunning of the stagnant keys in prune only mode by 25 hours to avoid interception with the
// finality
const STAGNANT_PRUNE_DELAY: Timestamp = 25 * 60 * 60;
// Maximum number of stagnant entries cleaned during one `STAGNANT_TIMEOUT` iteration
const MAX_STAGNANT_ENTRIES: usize = 1000;
@@ -52,8 +52,8 @@ const CLEANED_VOTES_WATERMARK_KEY: &[u8; 23] = b"cleaned-votes-watermark";
/// this should not be done at once, but rather in smaller batches so nodes won't get stalled by
/// this.
///
/// 300 is with session duration of 1 hour and 30 parachains around <3_000_000 key purges in the worst
/// case. Which is already quite a lot, at the same time we have around 21_000 sessions on
/// 300 is with session duration of 1 hour and 30 parachains around <3_000_000 key purges in the
/// worst case. Which is already quite a lot, at the same time we have around 21_000 sessions on
/// Kusama. This means at 300 purged sessions per session, cleaning everything up will take
/// around 3 days. Depending on how severe disk usage becomes, we might want to bump the batch
/// size, at the cost of risking issues at session boundaries (performance).
@@ -346,7 +346,8 @@ pub(crate) fn note_earliest_session(
if pruned_disputes.len() != 0 {
overlay_db.write_recent_disputes(new_recent_disputes);
// Note: Deleting old candidate votes is handled in `write` based on the earliest session.
// Note: Deleting old candidate votes is handled in `write` based on the
// earliest session.
}
}
},
@@ -19,12 +19,12 @@
//! This module encapsulates the actual logic for importing new votes and provides easy access of
//! the current state for votes for a particular candidate.
//!
//! In particular there is `CandidateVoteState` which tells what can be concluded for a particular set of
//! votes. E.g. whether a dispute is ongoing, whether it is confirmed, concluded, ..
//! In particular there is `CandidateVoteState` which tells what can be concluded for a particular
//! set of votes. E.g. whether a dispute is ongoing, whether it is confirmed, concluded, ..
//!
//! Then there is `ImportResult` which reveals information about what changed once additional votes
//! got imported on top of an existing `CandidateVoteState` and reveals "dynamic" information, like whether
//! due to the import a dispute was raised/got confirmed, ...
//! got imported on top of an existing `CandidateVoteState` and reveals "dynamic" information, like
//! whether due to the import a dispute was raised/got confirmed, ...
use std::collections::{BTreeMap, HashMap, HashSet};
@@ -92,8 +92,8 @@ pub struct InitialData {
pub(crate) struct Initialized {
keystore: Arc<LocalKeystore>,
runtime_info: RuntimeInfo,
/// This is the highest `SessionIndex` seen via `ActiveLeavesUpdate`. It doesn't matter if it was
/// cached successfully or not. It is used to detect ancient disputes.
/// This is the highest `SessionIndex` seen via `ActiveLeavesUpdate`. It doesn't matter if it
/// was cached successfully or not. It is used to detect ancient disputes.
highest_session_seen: SessionIndex,
/// Will be set to `true` if an error occured during the last caching attempt
gaps_in_cache: bool,
@@ -308,8 +308,8 @@ impl Initialized {
Ok(session_idx)
if self.gaps_in_cache || session_idx > self.highest_session_seen =>
{
// Fetch the last `DISPUTE_WINDOW` number of sessions unless there are no gaps in
// cache and we are not missing too many `SessionInfo`s
// Fetch the last `DISPUTE_WINDOW` number of sessions unless there are no gaps
// in cache and we are not missing too many `SessionInfo`s
let mut lower_bound = session_idx.saturating_sub(DISPUTE_WINDOW.get() - 1);
if !self.gaps_in_cache && self.highest_session_seen > lower_bound {
lower_bound = self.highest_session_seen + 1
@@ -1133,8 +1133,8 @@ impl Initialized {
}
// Participate in dispute if we did not cast a vote before and actually have keys to cast a
// local vote. Disputes should fall in one of the categories below, otherwise we will refrain
// from participation:
// local vote. Disputes should fall in one of the categories below, otherwise we will
// refrain from participation:
// - `is_included` lands in prioritised queue
// - `is_confirmed` | `is_backed` lands in best effort queue
// We don't participate in disputes on finalized candidates.
@@ -17,12 +17,13 @@
//! Implements the dispute coordinator subsystem.
//!
//! This is the central subsystem of the node-side components which participate in disputes.
//! This subsystem wraps a database which tracks all statements observed by all validators over some window of sessions.
//! Votes older than this session window are pruned.
//! This subsystem wraps a database which tracks all statements observed by all validators over some
//! window of sessions. Votes older than this session window are pruned.
//!
//! This subsystem will be the point which produce dispute votes, either positive or negative, based on locally-observed
//! validation results as well as a sink for votes received by other subsystems. When importing a dispute vote from
//! another node, this will trigger dispute participation to recover and validate the block.
//! This subsystem will be the point which produce dispute votes, either positive or negative, based
//! on locally-observed validation results as well as a sink for votes received by other subsystems.
//! When importing a dispute vote from another node, this will trigger dispute participation to
//! recover and validate the block.
use std::{num::NonZeroUsize, sync::Arc};
@@ -92,10 +93,10 @@ mod spam_slots;
/// Handling of participation requests via `Participation`.
///
/// `Participation` provides an API (`Participation::queue_participation`) for queuing of dispute participations and will process those
/// participation requests, such that most important/urgent disputes will be resolved and processed
/// first and more importantly it will order requests in a way so disputes will get resolved, even
/// if there are lots of them.
/// `Participation` provides an API (`Participation::queue_participation`) for queuing of dispute
/// participations and will process those participation requests, such that most important/urgent
/// disputes will be resolved and processed first and more importantly it will order requests in a
/// way so disputes will get resolved, even if there are lots of them.
pub(crate) mod participation;
/// Pure processing of vote imports.
@@ -294,8 +294,8 @@ impl Queues {
return Self::pop_impl(&mut self.priority)
}
// `pop_best_effort` and `pop_priority` do the same but on different `BTreeMap`s. This function has
// the extracted implementation
// `pop_best_effort` and `pop_priority` do the same but on different `BTreeMap`s. This function
// has the extracted implementation
fn pop_impl(
target: &mut BTreeMap<CandidateComparator, ParticipationRequest>,
) -> Option<(CandidateComparator, ParticipationRequest)> {
@@ -331,9 +331,10 @@ impl Queues {
#[derive(Copy, Clone)]
#[cfg_attr(test, derive(Debug))]
struct CandidateComparator {
/// Block number of the relay parent. It's wrapped in an `Option<>` because there are cases when
/// it can't be obtained. For example when the node is lagging behind and new leaves are received
/// with a slight delay. Candidates with unknown relay parent are treated with the lowest priority.
/// Block number of the relay parent. It's wrapped in an `Option<>` because there are cases
/// when it can't be obtained. For example when the node is lagging behind and new leaves are
/// received with a slight delay. Candidates with unknown relay parent are treated with the
/// lowest priority.
///
/// The order enforced by `CandidateComparator` is important because we want to participate in
/// the oldest disputes first.
@@ -346,9 +347,10 @@ struct CandidateComparator {
/// that is not stable. If a new fork appears after the fact, we would start ordering the same
/// candidate differently, which would result in the same candidate getting queued twice.
relay_parent_block_number: Option<BlockNumber>,
/// By adding the `CandidateHash`, we can guarantee a unique ordering across candidates with the
/// same relay parent block number. Candidates without `relay_parent_block_number` are ordered by
/// the `candidate_hash` (and treated with the lowest priority, as already mentioned).
/// By adding the `CandidateHash`, we can guarantee a unique ordering across candidates with
/// the same relay parent block number. Candidates without `relay_parent_block_number` are
/// ordered by the `candidate_hash` (and treated with the lowest priority, as already
/// mentioned).
candidate_hash: CandidateHash,
}
@@ -364,11 +366,11 @@ impl CandidateComparator {
/// Create a candidate comparator for a given candidate.
///
/// Returns:
/// - `Ok(CandidateComparator{Some(relay_parent_block_number), candidate_hash})` when the
/// - `Ok(CandidateComparator{Some(relay_parent_block_number), candidate_hash})` when the
/// relay parent can be obtained. This is the happy case.
/// - `Ok(CandidateComparator{None, candidate_hash})` in case the candidate's relay parent
/// can't be obtained.
/// - `FatalError` in case the chain API call fails with an unexpected error.
/// - `FatalError` in case the chain API call fails with an unexpected error.
pub async fn new(
sender: &mut impl overseer::DisputeCoordinatorSenderTrait,
candidate: &CandidateReceipt,
@@ -53,8 +53,8 @@ fn clone_request(request: &ParticipationRequest) -> ParticipationRequest {
/// Check that dequeuing acknowledges order.
///
/// Any priority item will be dequeued before any best effort items, priority and best effort with
/// known parent block number items will be processed in order. Best effort items without known parent
/// block number should be treated with lowest priority.
/// known parent block number items will be processed in order. Best effort items without known
/// parent block number should be treated with lowest priority.
#[test]
fn ordering_works_as_expected() {
let metrics = Metrics::default();
@@ -305,7 +305,8 @@ fn reqs_get_queued_on_no_recent_block() {
// Responds to messages from the test and verifies its behaviour
let request_handler = async {
// If we receive `BlockNumber` request this implicitly proves that the participation is queued
// If we receive `BlockNumber` request this implicitly proves that the participation is
// queued
assert_matches!(
ctx_handle.recv().await,
AllMessages::ChainApi(ChainApiMessage::BlockNumber(_, tx)) => {
@@ -98,7 +98,8 @@ mod ref_counted_candidates_tests {
/// Keeps track of scraped candidates. Supports `insert`, `remove_up_to_height` and `contains`
/// operations.
pub struct ScrapedCandidates {
/// Main data structure which keeps the candidates we know about. `contains` does lookups only here.
/// Main data structure which keeps the candidates we know about. `contains` does lookups only
/// here.
candidates: RefCountedCandidates,
/// Keeps track at which block number a candidate was inserted. Used in `remove_up_to_height`.
/// Without this tracking we won't be able to remove all candidates before block X.
@@ -117,7 +118,8 @@ impl ScrapedCandidates {
self.candidates.contains(candidate_hash)
}
// Removes all candidates up to a given height. The candidates at the block height are NOT removed.
// Removes all candidates up to a given height. The candidates at the block height are NOT
// removed.
pub fn remove_up_to_height(&mut self, height: &BlockNumber) -> HashSet<CandidateHash> {
let mut candidates_modified: HashSet<CandidateHash> = HashSet::new();
let not_stale = self.candidates_by_block_number.split_off(&height);
@@ -120,7 +120,8 @@ impl Inclusions {
) {
for candidate in candidates_modified {
if let Some(blocks_including) = self.inclusions_inner.get_mut(&candidate) {
// Returns everything after the given key, including the key. This works because the blocks are sorted in ascending order.
// Returns everything after the given key, including the key. This works because the
// blocks are sorted in ascending order.
*blocks_including = blocks_including.split_off(height);
}
}
@@ -150,8 +151,8 @@ impl Inclusions {
///
/// Concretely:
///
/// - Monitors for `CandidateIncluded` events to keep track of candidates that have been
/// included on chains.
/// - Monitors for `CandidateIncluded` events to keep track of candidates that have been included on
/// chains.
/// - Monitors for `CandidateBacked` events to keep track of all backed candidates.
/// - Calls `FetchOnChainVotes` for each block to gather potentially missed votes from chain.
///
@@ -294,11 +295,11 @@ impl ChainScraper {
/// Prune finalized candidates.
///
/// We keep each candidate for `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks after finalization.
/// After that we treat it as low priority.
/// We keep each candidate for `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks after
/// finalization. After that we treat it as low priority.
pub fn process_finalized_block(&mut self, finalized_block_number: &BlockNumber) {
// `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION - 1` because `finalized_block_number`counts to the
// candidate lifetime.
// `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION - 1` because
// `finalized_block_number`counts to the candidate lifetime.
match finalized_block_number.checked_sub(DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION - 1)
{
Some(key_to_prune) => {
@@ -183,7 +183,8 @@ fn get_backed_candidate_event(block_number: BlockNumber) -> Vec<CandidateEvent>
GroupIndex::from(0),
)]
}
/// Hash for a 'magic' candidate. This is meant to be a special candidate used to verify special cases.
/// Hash for a 'magic' candidate. This is meant to be a special candidate used to verify special
/// cases.
fn get_magic_candidate_hash() -> Hash {
BlakeTwo256::hash(&"abc".encode())
}
@@ -425,7 +426,7 @@ fn scraper_requests_candidates_of_non_finalized_ancestors() {
&chain,
finalized_block_number,
BLOCKS_TO_SKIP -
(finalized_block_number - DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION) as usize, // Expect the provider not to go past finalized block.
(finalized_block_number - DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION) as usize, /* Expect the provider not to go past finalized block. */
get_backed_and_included_candidate_events,
);
join(process_active_leaves_update(ctx.sender(), &mut ordering, next_update), overseer_fut)
@@ -468,7 +469,8 @@ fn scraper_prunes_finalized_candidates() {
let candidate = make_candidate_receipt(get_block_number_hash(TEST_TARGET_BLOCK_NUMBER));
// After `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks the candidate should be removed
// After `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks the candidate should be
// removed
finalized_block_number =
TEST_TARGET_BLOCK_NUMBER + DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION;
process_finalized_block(&mut scraper, &finalized_block_number);
@@ -518,8 +520,9 @@ fn scraper_handles_backed_but_not_included_candidate() {
finalized_block_number += 1;
process_finalized_block(&mut scraper, &finalized_block_number);
// `FIRST_TEST_BLOCK` is finalized, which is within `BACKED_CANDIDATE_LIFETIME_AFTER_FINALIZATION` window.
// The candidate should still be backed.
// `FIRST_TEST_BLOCK` is finalized, which is within
// `BACKED_CANDIDATE_LIFETIME_AFTER_FINALIZATION` window. The candidate should still be
// backed.
let candidate = make_candidate_receipt(get_block_number_hash(TEST_TARGET_BLOCK_NUMBER));
assert!(!scraper.is_candidate_included(&candidate.hash()));
assert!(scraper.is_candidate_backed(&candidate.hash()));
@@ -576,7 +579,8 @@ fn scraper_handles_the_same_candidate_incuded_in_two_different_block_heights() {
.await;
// Finalize blocks to enforce pruning of scraped events.
// The magic candidate was added twice, so it shouldn't be removed if we finalize two more blocks.
// The magic candidate was added twice, so it shouldn't be removed if we finalize two more
// blocks.
finalized_block_number = test_targets.first().expect("there are two block nums") +
DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION;
process_finalized_block(&mut scraper, &finalized_block_number);
@@ -641,7 +645,8 @@ fn inclusions_per_candidate_properly_adds_and_prunes() {
])
);
// After `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks the earlier inclusion should be removed
// After `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks the earlier inclusion should
// be removed
finalized_block_number =
TEST_TARGET_BLOCK_NUMBER + DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION;
process_finalized_block(&mut scraper, &finalized_block_number);
@@ -734,8 +734,9 @@ fn too_many_unconfirmed_statements_are_considered_spam() {
.await;
// Participation has to fail here, otherwise the dispute will be confirmed. However
// participation won't happen at all because the dispute is neither backed, not confirmed
// nor the candidate is included. Or in other words - we'll refrain from participation.
// participation won't happen at all because the dispute is neither backed, not
// confirmed nor the candidate is included. Or in other words - we'll refrain from
// participation.
{
let (tx, rx) = oneshot::channel();
@@ -2050,7 +2051,8 @@ fn concluded_supermajority_against_non_active_after_time() {
ImportStatementsResult::ValidImport => {}
);
// Use a different expected commitments hash to ensure the candidate validation returns invalid.
// Use a different expected commitments hash to ensure the candidate validation returns
// invalid.
participation_with_distribution(
&mut virtual_overseer,
&candidate_hash,
@@ -2351,7 +2353,8 @@ fn resume_dispute_with_local_statement() {
assert_eq!(messages.len(), 1, "A message should have gone out.");
// Assert that subsystem is not sending Participation messages because we issued a local statement
// Assert that subsystem is not sending Participation messages because we issued a local
// statement
assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none());
virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
@@ -2445,7 +2448,8 @@ fn resume_dispute_without_local_statement_or_local_key() {
Box::pin(async move {
test_state.handle_resume_sync(&mut virtual_overseer, session).await;
// Assert that subsystem is not sending Participation messages because we issued a local statement
// Assert that subsystem is not sending Participation messages because we issued a
// local statement
assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none());
virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
@@ -2751,7 +2755,8 @@ fn redundant_votes_ignored() {
}
#[test]
/// Make sure no disputes are recorded when there are no opposing votes, even if we reached supermajority.
/// Make sure no disputes are recorded when there are no opposing votes, even if we reached
/// supermajority.
fn no_onesided_disputes() {
test_harness(|mut test_state, mut virtual_overseer| {
Box::pin(async move {
@@ -3124,16 +3129,17 @@ fn participation_requests_reprioritized_for_newly_included() {
candidate_receipt.descriptor.pov_hash = Hash::from(
[repetition; 32], // Altering this receipt so its hash will be changed
);
// Set consecutive parents (starting from zero). They will order the candidates for participation.
// Set consecutive parents (starting from zero). They will order the candidates for
// participation.
let parent_block_num: BlockNumber = repetition as BlockNumber - 1;
candidate_receipt.descriptor.relay_parent =
test_state.block_num_to_header.get(&parent_block_num).unwrap().clone();
receipts.push(candidate_receipt.clone());
}
// Mark all candidates as backed, so their participation requests make it to best effort.
// These calls must all occur before including the candidates due to test overseer
// oddities.
// Mark all candidates as backed, so their participation requests make it to best
// effort. These calls must all occur before including the candidates due to test
// overseer oddities.
let mut candidate_events = Vec::new();
for r in receipts.iter() {
candidate_events.push(make_candidate_backed_event(r.clone()))
@@ -3172,7 +3178,8 @@ fn participation_requests_reprioritized_for_newly_included() {
.await;
// Handle corresponding messages to unblock import
// we need to handle `ApprovalVotingMessage::GetApprovalSignaturesForCandidate` for import
// we need to handle `ApprovalVotingMessage::GetApprovalSignaturesForCandidate` for
// import
handle_approval_vote_request(
&mut virtual_overseer,
&candidate_hash,
@@ -3180,8 +3187,9 @@ fn participation_requests_reprioritized_for_newly_included() {
)
.await;
// We'll trigger participation for the first `MAX_PARALLEL_PARTICIPATIONS` candidates.
// The rest will be queued => we need to handle `ChainApiMessage::BlockNumber` for them.
// We'll trigger participation for the first `MAX_PARALLEL_PARTICIPATIONS`
// candidates. The rest will be queued => we need to handle
// `ChainApiMessage::BlockNumber` for them.
if idx >= crate::participation::MAX_PARALLEL_PARTICIPATIONS {
// We send the `idx` as parent block number, because it is used for ordering.
// This way we get predictable ordering and participation.
@@ -3201,11 +3209,13 @@ fn participation_requests_reprioritized_for_newly_included() {
)
.await;
// NB: The checks below are a bit racy. In theory candidate 2 can be processed even before candidate 0 and this is okay. If any
// of the asserts in the two functions after this comment fail -> rework `participation_with_distribution` to expect a set of
// NB: The checks below are a bit racy. In theory candidate 2 can be processed even
// before candidate 0 and this is okay. If any of the asserts in the two functions after
// this comment fail -> rework `participation_with_distribution` to expect a set of
// commitment hashes instead of just one.
// This is the candidate for which participation was started initially (`MAX_PARALLEL_PARTICIPATIONS` threshold was not yet hit)
// This is the candidate for which participation was started initially
// (`MAX_PARALLEL_PARTICIPATIONS` threshold was not yet hit)
participation_with_distribution(
&mut virtual_overseer,
&receipts.get(0).expect("There is more than one candidate").hash(),
@@ -3326,7 +3336,8 @@ fn informs_chain_selection_when_dispute_concluded_against() {
ImportStatementsResult::ValidImport => {}
);
// Use a different expected commitments hash to ensure the candidate validation returns invalid.
// Use a different expected commitments hash to ensure the candidate validation returns
// invalid.
participation_with_distribution(
&mut virtual_overseer,
&candidate_hash,
@@ -3440,7 +3451,8 @@ fn session_info_is_requested_only_once() {
test_state.handle_resume_sync(&mut virtual_overseer, session).await;
// This leaf activation shouldn't fetch `SessionInfo` because the session is already cached
// This leaf activation shouldn't fetch `SessionInfo` because the session is already
// cached
test_state
.activate_leaf_at_session(
&mut virtual_overseer,
@@ -3475,8 +3487,8 @@ fn session_info_is_requested_only_once() {
});
}
// Big jump means the new session we see with a leaf update is at least a `DISPUTE_WINDOW` bigger than
// the already known one. In this case The whole `DISPUTE_WINDOW` should be fetched.
// Big jump means the new session we see with a leaf update is at least a `DISPUTE_WINDOW` bigger
// than the already known one. In this case The whole `DISPUTE_WINDOW` should be fetched.
#[test]
fn session_info_big_jump_works() {
test_harness(|mut test_state, mut virtual_overseer| {
@@ -3485,7 +3497,8 @@ fn session_info_big_jump_works() {
test_state.handle_resume_sync(&mut virtual_overseer, session_on_startup).await;
// This leaf activation shouldn't fetch `SessionInfo` because the session is already cached
// This leaf activation shouldn't fetch `SessionInfo` because the session is already
// cached
test_state
.activate_leaf_at_session(
&mut virtual_overseer,
@@ -3525,8 +3538,8 @@ fn session_info_big_jump_works() {
});
}
// Small jump means the new session we see with a leaf update is at less than last known one + `DISPUTE_WINDOW`. In this
// case fetching should start from last known one + 1.
// Small jump means the new session we see with a leaf update is at less than last known one +
// `DISPUTE_WINDOW`. In this case fetching should start from last known one + 1.
#[test]
fn session_info_small_jump_works() {
test_harness(|mut test_state, mut virtual_overseer| {
@@ -3535,7 +3548,8 @@ fn session_info_small_jump_works() {
test_state.handle_resume_sync(&mut virtual_overseer, session_on_startup).await;
// This leaf activation shouldn't fetch `SessionInfo` because the session is already cached
// This leaf activation shouldn't fetch `SessionInfo` because the session is already
// cached
test_state
.activate_leaf_at_session(
&mut virtual_overseer,
@@ -16,11 +16,12 @@
//! The parachain inherent data provider
//!
//! Parachain backing and approval is an off-chain process, but the parachain needs to progress on chain as well. To
//! make it progress on chain a block producer needs to forward information about the state of a parachain to the
//! runtime. This information is forwarded through an inherent to the runtime. Here we provide the
//! [`ParachainInherentDataProvider`] that requests the relevant data from the provisioner subsystem and creates the
//! the inherent data that the runtime will use to create an inherent.
//! Parachain backing and approval is an off-chain process, but the parachain needs to progress on
//! chain as well. To make it progress on chain a block producer needs to forward information about
//! the state of a parachain to the runtime. This information is forwarded through an inherent to
//! the runtime. Here we provide the [`ParachainInherentDataProvider`] that requests the relevant
//! data from the provisioner subsystem and creates the the inherent data that the runtime will use
//! to create an inherent.
#![deny(unused_crate_dependencies, unused_results)]
@@ -14,7 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! The disputes module is responsible for selecting dispute votes to be sent with the inherent data.
//! The disputes module is responsible for selecting dispute votes to be sent with the inherent
//! data.
use crate::LOG_TARGET;
use futures::channel::oneshot;
@@ -22,7 +23,8 @@ use polkadot_node_primitives::CandidateVotes;
use polkadot_node_subsystem::{messages::DisputeCoordinatorMessage, overseer};
use polkadot_primitives::{CandidateHash, SessionIndex};
/// Request the relevant dispute statements for a set of disputes identified by `CandidateHash` and the `SessionIndex`.
/// Request the relevant dispute statements for a set of disputes identified by `CandidateHash` and
/// the `SessionIndex`.
async fn request_votes(
sender: &mut impl overseer::ProvisionerSenderTrait,
disputes_to_query: Vec<(SessionIndex, CandidateHash)>,
@@ -48,7 +48,8 @@ pub const MAX_DISPUTE_VOTES_FORWARDED_TO_RUNTIME: usize = 200;
/// Controls how much dispute votes to be fetched from the `dispute-coordinator` per iteration in
/// `fn vote_selection`. The purpose is to fetch the votes in batches until
/// `MAX_DISPUTE_VOTES_FORWARDED_TO_RUNTIME` is reached. If all votes are fetched in single call
/// we might fetch votes which we never use. This will create unnecessary load on `dispute-coordinator`.
/// we might fetch votes which we never use. This will create unnecessary load on
/// `dispute-coordinator`.
///
/// This value should be less than `MAX_DISPUTE_VOTES_FORWARDED_TO_RUNTIME`. Increase it in case
/// `provisioner` sends too many `QueryCandidateVotes` messages to `dispite-coordinator`.
@@ -68,22 +69,23 @@ const VOTES_SELECTION_BATCH_SIZE: usize = 11;
/// * Offchain vs Onchain
/// * Concluded onchain vs Unconcluded onchain
///
/// Provisioner fetches all disputes from `dispute-coordinator` and separates them in multiple partitions.
/// Please refer to `struct PartitionedDisputes` for details about the actual partitions.
/// Each partition has got a priority implicitly assigned to it and the disputes are selected based on this
/// priority (e.g. disputes in partition 1, then if there is space - disputes from partition 2 and so on).
/// Provisioner fetches all disputes from `dispute-coordinator` and separates them in multiple
/// partitions. Please refer to `struct PartitionedDisputes` for details about the actual
/// partitions. Each partition has got a priority implicitly assigned to it and the disputes are
/// selected based on this priority (e.g. disputes in partition 1, then if there is space - disputes
/// from partition 2 and so on).
///
/// # Votes selection
///
/// Besides the prioritization described above the votes in each partition are filtered too. Provisioner
/// fetches all onchain votes and filters them out from all partitions. As a result the Runtime receives
/// only fresh votes (votes it didn't know about).
/// Besides the prioritization described above the votes in each partition are filtered too.
/// Provisioner fetches all onchain votes and filters them out from all partitions. As a result the
/// Runtime receives only fresh votes (votes it didn't know about).
///
/// # How the onchain votes are fetched
///
/// The logic outlined above relies on `RuntimeApiRequest::Disputes` message from the Runtime. The user
/// check the Runtime version before calling `select_disputes`. If the function is used with old runtime
/// an error is logged and the logic will continue with empty onchain votes `HashMap`.
/// The logic outlined above relies on `RuntimeApiRequest::Disputes` message from the Runtime. The
/// user check the Runtime version before calling `select_disputes`. If the function is used with
/// old runtime an error is logged and the logic will continue with empty onchain votes `HashMap`.
pub async fn select_disputes<Sender>(
sender: &mut Sender,
metrics: &metrics::Metrics,
@@ -110,7 +112,8 @@ where
r
},
Err(GetOnchainDisputesError::NotSupported(runtime_api_err, relay_parent)) => {
// Runtime version is checked before calling this method, so the error below should never happen!
// Runtime version is checked before calling this method, so the error below should
// never happen!
gum::error!(
target: LOG_TARGET,
?runtime_api_err,
@@ -152,7 +155,8 @@ where
gum::trace!(target: LOG_TARGET, ?leaf, "Filtering recent disputes");
// Filter out unconfirmed disputes. However if the dispute is already onchain - don't skip it.
// In this case we'd better push as much fresh votes as possible to bring it to conclusion faster.
// In this case we'd better push as much fresh votes as possible to bring it to conclusion
// faster.
let recent_disputes = recent_disputes
.into_iter()
.filter(|d| d.2.is_confirmed_concluded() || onchain.contains_key(&(d.0, d.1)))
@@ -178,9 +182,9 @@ where
make_multi_dispute_statement_set(metrics, result)
}
/// Selects dispute votes from `PartitionedDisputes` which should be sent to the runtime. Votes which
/// are already onchain are filtered out. Result should be sorted by `(SessionIndex, CandidateHash)`
/// which is enforced by the `BTreeMap`. This is a requirement from the runtime.
/// Selects dispute votes from `PartitionedDisputes` which should be sent to the runtime. Votes
/// which are already onchain are filtered out. Result should be sorted by `(SessionIndex,
/// CandidateHash)` which is enforced by the `BTreeMap`. This is a requirement from the runtime.
async fn vote_selection<Sender>(
sender: &mut Sender,
partitioned: PartitionedDisputes,
@@ -237,9 +241,9 @@ where
for (session_index, candidate_hash, selected_votes) in votes {
let votes_len = selected_votes.valid.raw().len() + selected_votes.invalid.len();
if votes_len + total_votes_len > MAX_DISPUTE_VOTES_FORWARDED_TO_RUNTIME {
// we are done - no more votes can be added. Importantly, we don't add any votes for a dispute here
// if we can't fit them all. This gives us an important invariant, that backing votes for
// disputes make it into the provisioned vote set.
// we are done - no more votes can be added. Importantly, we don't add any votes for
// a dispute here if we can't fit them all. This gives us an important invariant,
// that backing votes for disputes make it into the provisioned vote set.
gum::trace!(
target: LOG_TARGET,
?request_votes_counter,
@@ -483,7 +487,8 @@ fn make_multi_dispute_statement_set(
.collect()
}
/// Gets the on-chain disputes at a given block number and returns them as a `HashMap` so that searching in them is cheap.
/// Gets the on-chain disputes at a given block number and returns them as a `HashMap` so that
/// searching in them is cheap.
pub async fn get_onchain_disputes<Sender>(
sender: &mut Sender,
relay_parent: Hash,
@@ -237,21 +237,22 @@ fn partitioning_happy_case() {
);
}
// This test verifies the double voting behavior. Currently we don't care if a supermajority is achieved with or
// without the 'help' of a double vote (a validator voting for and against at the same time). This makes the test
// a bit pointless but anyway I'm leaving it here to make this decision explicit and have the test code ready in
// case this behavior needs to be further tested in the future.
// Link to the PR with the discussions: https://github.com/paritytech/polkadot/pull/5567
// This test verifies the double voting behavior. Currently we don't care if a supermajority is
// achieved with or without the 'help' of a double vote (a validator voting for and against at the
// same time). This makes the test a bit pointless but anyway I'm leaving it here to make this
// decision explicit and have the test code ready in case this behavior needs to be further tested
// in the future. Link to the PR with the discussions: https://github.com/paritytech/polkadot/pull/5567
#[test]
fn partitioning_doubled_onchain_vote() {
let mut input = Vec::<(SessionIndex, CandidateHash, DisputeStatus)>::new();
let mut onchain = HashMap::<(u32, CandidateHash), DisputeState>::new();
// Dispute A relies on a 'double onchain vote' to conclude. Validator with index 0 has voted both `for` and `against`.
// Despite that this dispute should be considered 'can conclude onchain'.
// Dispute A relies on a 'double onchain vote' to conclude. Validator with index 0 has voted
// both `for` and `against`. Despite that this dispute should be considered 'can conclude
// onchain'.
let dispute_a = (3, CandidateHash(Hash::random()), DisputeStatus::Active);
// Dispute B has supermajority + 1 votes, so the doubled onchain vote doesn't affect it. It should be considered
// as 'can conclude onchain'.
// Dispute B has supermajority + 1 votes, so the doubled onchain vote doesn't affect it. It
// should be considered as 'can conclude onchain'.
let dispute_b = (4, CandidateHash(Hash::random()), DisputeStatus::Active);
input.push(dispute_a.clone());
input.push(dispute_b.clone());
+2 -1
View File
@@ -81,7 +81,8 @@ pub enum Error {
OverseerExited(SubsystemError),
}
/// Used by `get_onchain_disputes` to represent errors related to fetching on-chain disputes from the Runtime
/// Used by `get_onchain_disputes` to represent errors related to fetching on-chain disputes from
/// the Runtime
#[allow(dead_code)] // Remove when promoting to stable
#[fatality::fatality]
pub enum GetOnchainDisputesError {
+18 -15
View File
@@ -466,11 +466,11 @@ async fn send_inherent_data(
/// - not more than one per validator
/// - each 1 bit must correspond to an occupied core
///
/// If we have too many, an arbitrary selection policy is fine. For purposes of maximizing availability,
/// we pick the one with the greatest number of 1 bits.
/// If we have too many, an arbitrary selection policy is fine. For purposes of maximizing
/// availability, we pick the one with the greatest number of 1 bits.
///
/// Note: This does not enforce any sorting precondition on the output; the ordering there will be unrelated
/// to the sorting of the input.
/// Note: This does not enforce any sorting precondition on the output; the ordering there will be
/// unrelated to the sorting of the input.
fn select_availability_bitfields(
cores: &[CoreState],
bitfields: &[SignedAvailabilityBitfield],
@@ -532,7 +532,8 @@ fn select_availability_bitfields(
selected.into_values().collect()
}
/// Determine which cores are free, and then to the degree possible, pick a candidate appropriate to each free core.
/// Determine which cores are free, and then to the degree possible, pick a candidate appropriate to
/// each free core.
async fn select_candidates(
availability_cores: &[CoreState],
bitfields: &[SignedAvailabilityBitfield],
@@ -593,7 +594,8 @@ async fn select_candidates(
let computed_validation_data_hash = validation_data.hash();
// we arbitrarily pick the first of the backed candidates which match the appropriate selection criteria
// we arbitrarily pick the first of the backed candidates which match the appropriate
// selection criteria
if let Some(candidate) = candidates.iter().find(|backed_candidate| {
let descriptor = &backed_candidate.descriptor;
descriptor.para_id == scheduled_core.para_id &&
@@ -628,12 +630,12 @@ async fn select_candidates(
gum::trace!(target: LOG_TARGET, leaf_hash=?relay_parent,
"Got {} backed candidates", candidates.len());
// `selected_candidates` is generated in ascending order by core index, and `GetBackedCandidates`
// _should_ preserve that property, but let's just make sure.
// `selected_candidates` is generated in ascending order by core index, and
// `GetBackedCandidates` _should_ preserve that property, but let's just make sure.
//
// We can't easily map from `BackedCandidate` to `core_idx`, but we know that every selected candidate
// maps to either 0 or 1 backed candidate, and the hashes correspond. Therefore, by checking them
// in order, we can ensure that the backed candidates are also in order.
// We can't easily map from `BackedCandidate` to `core_idx`, but we know that every selected
// candidate maps to either 0 or 1 backed candidate, and the hashes correspond. Therefore, by
// checking them in order, we can ensure that the backed candidates are also in order.
let mut backed_idx = 0;
for selected in selected_candidates {
if selected ==
@@ -705,8 +707,9 @@ fn bitfields_indicate_availability(
let validator_idx = bitfield.validator_index().0 as usize;
match availability.get_mut(validator_idx) {
None => {
// in principle, this function might return a `Result<bool, Error>` so that we can more clearly express this error condition
// however, in practice, that would just push off an error-handling routine which would look a whole lot like this one.
// in principle, this function might return a `Result<bool, Error>` so that we can
// more clearly express this error condition however, in practice, that would just
// push off an error-handling routine which would look a whole lot like this one.
// simpler to just handle the error internally here.
gum::warn!(
target: LOG_TARGET,
@@ -726,8 +729,8 @@ fn bitfields_indicate_availability(
3 * availability.count_ones() >= 2 * availability.len()
}
// If we have to be absolutely precise here, this method gets the version of the `ParachainHost` api.
// For brevity we'll just call it 'runtime version'.
// If we have to be absolutely precise here, this method gets the version of the `ParachainHost`
// api. For brevity we'll just call it 'runtime version'.
async fn has_required_runtime(
sender: &mut impl overseer::ProvisionerSenderTrait,
relay_parent: Hash,
@@ -28,9 +28,10 @@ struct MetricsInner {
/// Bitfields array length in `ProvisionerInherentData` (the result for `RequestInherentData`)
inherent_data_response_bitfields: prometheus::Histogram,
/// The following metrics track how many disputes/votes the runtime will have to process. These will count
/// all recent statements meaning every dispute from last sessions: 10 min on Rococo, 60 min on Kusama and
/// 4 hours on Polkadot. The metrics are updated only when the node authors a block, so values vary across nodes.
/// The following metrics track how many disputes/votes the runtime will have to process. These
/// will count all recent statements meaning every dispute from last sessions: 10 min on
/// Rococo, 60 min on Kusama and 4 hours on Polkadot. The metrics are updated only when the
/// node authors a block, so values vary across nodes.
inherent_data_dispute_statement_sets: prometheus::Counter<prometheus::U64>,
inherent_data_dispute_statements: prometheus::CounterVec<prometheus::U64>,
+2 -1
View File
@@ -90,7 +90,8 @@ mod select_availability_bitfields {
let cores = vec![occupied_core(0), occupied_core(1)];
// we pass in three bitfields with two validators
// this helps us check the postcondition that we get two bitfields back, for which the validators differ
// this helps us check the postcondition that we get two bitfields back, for which the
// validators differ
let bitfields = vec![
signed_bitfield(&keystore, bitvec.clone(), ValidatorIndex(0)),
signed_bitfield(&keystore, bitvec.clone(), ValidatorIndex(1)),
+2 -2
View File
@@ -110,8 +110,8 @@ struct State {
///
/// Here are some fun facts about these futures:
///
/// - Pre-checking can take quite some time, in the matter of tens of seconds, so the futures here
/// can soak for quite some time.
/// - Pre-checking can take quite some time, in the matter of tens of seconds, so the futures
/// here can soak for quite some time.
/// - Pre-checking of one PVF can take drastically more time than pre-checking of another PVF.
/// This leads to results coming out of order.
///
+2 -2
View File
@@ -110,8 +110,8 @@ impl TestState {
Self { leaves, sessions, last_session_index }
}
/// A convenience function to receive a message from the overseer and returning `None` if nothing
/// was received within a reasonable (for local tests anyway) timeout.
/// A convenience function to receive a message from the overseer and returning `None` if
/// nothing was received within a reasonable (for local tests anyway) timeout.
async fn recv_timeout(&mut self, handle: &mut VirtualOverseer) -> Option<AllMessages> {
futures::select! {
msg = handle.recv().fuse() => {
+13 -10
View File
@@ -18,8 +18,8 @@ use crate::prepare::PrepareStats;
use parity_scale_codec::{Decode, Encode};
use std::fmt;
/// Result of PVF preparation performed by the validation host. Contains stats about the preparation if
/// successful
/// Result of PVF preparation performed by the validation host. Contains stats about the preparation
/// if successful
pub type PrepareResult = Result<PrepareStats, PrepareError>;
/// An error that occurred during the prepare part of the PVF pipeline.
@@ -35,13 +35,15 @@ pub enum PrepareError {
Panic(String),
/// Failed to prepare the PVF due to the time limit.
TimedOut,
/// An IO error occurred. This state is reported by either the validation host or by the worker.
/// An IO error occurred. This state is reported by either the validation host or by the
/// worker.
IoErr(String),
/// The temporary file for the artifact could not be created at the given cache path. This state is reported by the
/// validation host (not by the worker).
/// The temporary file for the artifact could not be created at the given cache path. This
/// state is reported by the validation host (not by the worker).
CreateTmpFileErr(String),
/// The response from the worker is received, but the file cannot be renamed (moved) to the final destination
/// location. This state is reported by the validation host (not by the worker).
/// The response from the worker is received, but the file cannot be renamed (moved) to the
/// final destination location. This state is reported by the validation host (not by the
/// worker).
RenameTmpFileErr(String),
}
@@ -81,15 +83,16 @@ impl fmt::Display for PrepareError {
/// Some internal error occurred.
///
/// Should only ever be used for validation errors independent of the candidate and PVF, or for errors we ruled out
/// during pre-checking (so preparation errors are fine).
/// Should only ever be used for validation errors independent of the candidate and PVF, or for
/// errors we ruled out during pre-checking (so preparation errors are fine).
#[derive(Debug, Clone, Encode, Decode)]
pub enum InternalValidationError {
/// Some communication error occurred with the host.
HostCommunication(String),
/// Could not find or open compiled artifact file.
CouldNotOpenFile(String),
/// An error occurred in the CPU time monitor thread. Should be totally unrelated to validation.
/// An error occurred in the CPU time monitor thread. Should be totally unrelated to
/// validation.
CpuTimeMonitorThread(String),
/// Some non-deterministic preparation error occurred.
NonDeterministicPrepareError(PrepareError),
@@ -35,10 +35,10 @@ use std::any::{Any, TypeId};
// left for the stack; this is, of course, overridable at link time when compiling the runtime)
// plus the number of pages specified in the `extra_heap_pages` passed to the executor.
//
// By default, rustc (or `lld` specifically) should allocate 1 MiB for the shadow stack, or 16 pages.
// The data section for runtimes are typically rather small and can fit in a single digit number of
// WASM pages, so let's say an extra 16 pages. Thus let's assume that 32 pages or 2 MiB are used for
// these needs by default.
// By default, rustc (or `lld` specifically) should allocate 1 MiB for the shadow stack, or 16
// pages. The data section for runtimes are typically rather small and can fit in a single digit
// number of WASM pages, so let's say an extra 16 pages. Thus let's assume that 32 pages or 2 MiB
// are used for these needs by default.
const DEFAULT_HEAP_PAGES_ESTIMATE: u32 = 32;
const EXTRA_HEAP_PAGES: u32 = 2048;
@@ -65,9 +65,9 @@ pub const DEFAULT_CONFIG: Config = Config {
//
// Here is how the values below were chosen.
//
// At the moment of writing, the default native stack size limit is 1 MiB. Assuming a logical item
// (see the docs about the field and the instrumentation algorithm) is 8 bytes, 1 MiB can
// fit 2x 65536 logical items.
// At the moment of writing, the default native stack size limit is 1 MiB. Assuming a
// logical item (see the docs about the field and the instrumentation algorithm) is 8 bytes,
// 1 MiB can fit 2x 65536 logical items.
//
// Since reaching the native stack limit is undesirable, we halve the logical item limit and
// also increase the native 256x. This hopefully should preclude wasm code from reaching
@@ -113,7 +113,7 @@ pub fn params_to_wasmtime_semantics(par: &ExecutorParams) -> Result<Semantics, S
ExecutorParam::WasmExtBulkMemory => sem.wasm_bulk_memory = true,
// TODO: Not implemented yet; <https://github.com/paritytech/polkadot/issues/6472>.
ExecutorParam::PrecheckingMaxMemory(_) => (),
ExecutorParam::PvfPrepTimeout(_, _) | ExecutorParam::PvfExecTimeout(_, _) => (), // Not used here
ExecutorParam::PvfPrepTimeout(_, _) | ExecutorParam::PvfExecTimeout(_, _) => (), /* Not used here */
}
}
sem.deterministic_stack_limit = Some(stack_limit);
@@ -135,8 +135,8 @@ impl Executor {
Ok(Self { config })
}
/// Executes the given PVF in the form of a compiled artifact and returns the result of execution
/// upon success.
/// Executes the given PVF in the form of a compiled artifact and returns the result of
/// execution upon success.
///
/// # Safety
///
@@ -251,9 +251,9 @@ pub mod thread {
Arc::new((Mutex::new(WaitOutcome::Pending), Condvar::new()))
}
/// Runs a worker thread. Will first enable security features, and afterwards notify the threads waiting on the
/// condvar. Catches panics during execution and resumes the panics after triggering the condvar, so that the
/// waiting thread is notified on panics.
/// Runs a worker thread. Will first enable security features, and afterwards notify the threads
/// waiting on the condvar. Catches panics during execution and resumes the panics after
/// triggering the condvar, so that the waiting thread is notified on panics.
///
/// # Returns
///
@@ -239,7 +239,8 @@ pub fn worker_entrypoint(
WaitOutcome::TimedOut => {
match cpu_time_monitor_thread.join() {
Ok(Some(cpu_time_elapsed)) => {
// Log if we exceed the timeout and the other thread hasn't finished.
// Log if we exceed the timeout and the other thread hasn't
// finished.
gum::warn!(
target: LOG_TARGET,
%worker_pid,
@@ -190,8 +190,9 @@ pub fn worker_entrypoint(
// If we are pre-checking, check for runtime construction errors.
//
// As pre-checking is more strict than just preparation in terms of memory and
// time, it is okay to do extra checks here. This takes negligible time anyway.
// As pre-checking is more strict than just preparation in terms of memory
// and time, it is okay to do extra checks here. This takes negligible time
// anyway.
if let PrepareJobKind::Prechecking = prepare_job_kind {
result = result.and_then(|output| {
runtime_construction_check(output.0.as_ref(), executor_params)?;
@@ -253,10 +254,11 @@ pub fn worker_entrypoint(
// Write the serialized artifact into a temp file.
//
// PVF host only keeps artifacts statuses in its memory, successfully
// compiled code gets stored on the disk (and consequently deserialized
// by execute-workers). The prepare worker is only required to send `Ok`
// to the pool to indicate the success.
// PVF host only keeps artifacts statuses in its memory,
// successfully compiled code gets stored on the disk (and
// consequently deserialized by execute-workers). The prepare worker
// is only required to send `Ok` to the pool to indicate the
// success.
gum::debug!(
target: LOG_TARGET,
@@ -275,7 +277,8 @@ pub fn worker_entrypoint(
WaitOutcome::TimedOut => {
match cpu_time_monitor_thread.join() {
Ok(Some(cpu_time_elapsed)) => {
// Log if we exceed the timeout and the other thread hasn't finished.
// Log if we exceed the timeout and the other thread hasn't
// finished.
gum::warn!(
target: LOG_TARGET,
%worker_pid,
@@ -83,8 +83,8 @@ pub mod memory_tracker {
///
/// # Errors
///
/// For simplicity, any errors are returned as a string. As this is not a critical component, errors
/// are used for informational purposes (logging) only.
/// For simplicity, any errors are returned as a string. As this is not a critical component,
/// errors are used for informational purposes (logging) only.
pub fn memory_tracker_loop(condvar: thread::Cond) -> Result<MemoryAllocationStats, String> {
// NOTE: This doesn't need to be too fine-grained since preparation currently takes 3-10s or
// more. Apart from that, there is not really a science to this number.
+2 -1
View File
@@ -224,7 +224,8 @@ impl Artifacts {
.is_none());
}
/// Remove and retrieve the artifacts from the table that are older than the supplied Time-To-Live.
/// Remove and retrieve the artifacts from the table that are older than the supplied
/// Time-To-Live.
pub fn prune(&mut self, artifact_ttl: Duration) -> Vec<ArtifactId> {
let now = SystemTime::now();
+15 -14
View File
@@ -38,29 +38,30 @@ pub enum InvalidCandidate {
/// The worker has died during validation of a candidate. That may fall in one of the following
/// categories, which we cannot distinguish programmatically:
///
/// (a) Some sort of transient glitch caused the worker process to abort. An example would be that
/// the host machine ran out of free memory and the OOM killer started killing the processes,
/// and in order to save the parent it will "sacrifice child" first.
/// (a) Some sort of transient glitch caused the worker process to abort. An example would be
/// that the host machine ran out of free memory and the OOM killer started killing the
/// processes, and in order to save the parent it will "sacrifice child" first.
///
/// (b) The candidate triggered a code path that has lead to the process death. For example,
/// the PVF found a way to consume unbounded amount of resources and then it either exceeded
/// an `rlimit` (if set) or, again, invited OOM killer. Another possibility is a bug in
/// wasmtime allowed the PVF to gain control over the execution worker.
/// the PVF found a way to consume unbounded amount of resources and then it either
/// exceeded an `rlimit` (if set) or, again, invited OOM killer. Another possibility is a
/// bug in wasmtime allowed the PVF to gain control over the execution worker.
///
/// We attribute such an event to an *invalid candidate* in either case.
///
/// The rationale for this is that a glitch may lead to unfair rejecting candidate by a single
/// validator. If the glitch is somewhat more persistent the validator will reject all candidate
/// thrown at it and hopefully the operator notices it by decreased reward performance of the
/// validator. On the other hand, if the worker died because of (b) we would have better chances
/// to stop the attack.
/// validator. If the glitch is somewhat more persistent the validator will reject all
/// candidate thrown at it and hopefully the operator notices it by decreased reward
/// performance of the validator. On the other hand, if the worker died because of (b) we would
/// have better chances to stop the attack.
AmbiguousWorkerDeath,
/// PVF execution (compilation is not included) took more time than was allotted.
HardTimeout,
/// A panic occurred and we can't be sure whether the candidate is really invalid or some internal glitch occurred.
/// Whenever we are unsure, we can never treat an error as internal as we would abstain from voting. This is bad
/// because if the issue was due to the candidate, then all validators would abstain, stalling finality on the
/// chain. So we will first retry the candidate, and if the issue persists we are forced to vote invalid.
/// A panic occurred and we can't be sure whether the candidate is really invalid or some
/// internal glitch occurred. Whenever we are unsure, we can never treat an error as internal
/// as we would abstain from voting. This is bad because if the issue was due to the candidate,
/// then all validators would abstain, stalling finality on the chain. So we will first retry
/// the candidate, and if the issue persists we are forced to vote invalid.
Panic(String),
}
+2 -1
View File
@@ -419,7 +419,8 @@ fn spawn_extra_worker(queue: &mut Queue, job: ExecuteJob) {
/// beforehand. In such a way, a race condition is avoided: during the worker being spawned,
/// another job in the queue, with an incompatible execution environment, may become stale, and
/// the queue would have to kill a newly started worker and spawn another one.
/// Nevertheless, if the worker finishes executing the job, it becomes idle and may be used to execute other jobs with a compatible execution environment.
/// Nevertheless, if the worker finishes executing the job, it becomes idle and may be used to
/// execute other jobs with a compatible execution environment.
async fn spawn_worker_task(
program_path: PathBuf,
job: ExecuteJob,
@@ -74,8 +74,9 @@ pub enum Outcome {
/// PVF execution completed successfully and the result is returned. The worker is ready for
/// another job.
Ok { result_descriptor: ValidationResult, duration: Duration, idle_worker: IdleWorker },
/// The candidate validation failed. It may be for example because the wasm execution triggered a trap.
/// Errors related to the preparation process are not expected to be encountered by the execution workers.
/// The candidate validation failed. It may be for example because the wasm execution triggered
/// a trap. Errors related to the preparation process are not expected to be encountered by the
/// execution workers.
InvalidCandidate { err: String, idle_worker: IdleWorker },
/// An internal error happened during the validation. Such an error is most likely related to
/// some transient glitch.
@@ -95,7 +96,8 @@ pub enum Outcome {
/// Given the idle token of a worker and parameters of work, communicates with the worker and
/// returns the outcome.
///
/// NOTE: Not returning the idle worker token in `Outcome` will trigger the child process being killed.
/// NOTE: Not returning the idle worker token in `Outcome` will trigger the child process being
/// killed.
pub async fn start_work(
worker: IdleWorker,
artifact: ArtifactPathId,
+10 -9
View File
@@ -455,8 +455,8 @@ async fn handle_precheck_pvf(
ArtifactState::Preparing { waiting_for_response, num_failures: _ } =>
waiting_for_response.push(result_sender),
ArtifactState::FailedToProcess { error, .. } => {
// Do not retry failed preparation if another pre-check request comes in. We do not retry pre-checking,
// anyway.
// Do not retry failed preparation if another pre-check request comes in. We do not
// retry pre-checking, anyway.
let _ = result_sender.send(PrepareResult::Err(error.clone()));
},
}
@@ -470,8 +470,8 @@ async fn handle_precheck_pvf(
/// Handles PVF execution.
///
/// This will try to prepare the PVF, if a prepared artifact does not already exist. If there is already a
/// preparation job, we coalesce the two preparation jobs.
/// This will try to prepare the PVF, if a prepared artifact does not already exist. If there is
/// already a preparation job, we coalesce the two preparation jobs.
///
/// If the prepare job succeeded previously, we will enqueue an execute job right away.
///
@@ -521,7 +521,8 @@ async fn handle_execute_pvf(
"handle_execute_pvf: Re-queuing PVF preparation for prepared artifact with missing file."
);
// The artifact has been prepared previously but the file is missing, prepare it again.
// The artifact has been prepared previously but the file is missing, prepare it
// again.
*state = ArtifactState::Preparing {
waiting_for_response: Vec::new(),
num_failures: 0,
@@ -721,8 +722,8 @@ async fn handle_prepare_done(
pending_requests
{
if result_tx.is_canceled() {
// Preparation could've taken quite a bit of time and the requester may be not interested
// in execution anymore, in which case we just skip the request.
// Preparation could've taken quite a bit of time and the requester may be not
// interested in execution anymore, in which case we just skip the request.
continue
}
@@ -855,8 +856,8 @@ fn can_retry_prepare_after_failure(
return false
}
// Retry if the retry cooldown has elapsed and if we have already retried less than `NUM_PREPARE_RETRIES` times. IO
// errors may resolve themselves.
// Retry if the retry cooldown has elapsed and if we have already retried less than
// `NUM_PREPARE_RETRIES` times. IO errors may resolve themselves.
SystemTime::now() >= last_time_failed + PREPARE_FAILURE_COOLDOWN &&
num_failures <= NUM_PREPARE_RETRIES
}
+15 -15
View File
@@ -32,26 +32,26 @@
//! (a) PVF pre-checking. This takes the `Pvf` code and tries to prepare it (verify and
//! compile) in order to pre-check its validity.
//!
//! (b) PVF execution. This accepts the PVF [`params`][`polkadot_parachain::primitives::ValidationParams`]
//! and the `Pvf` code, prepares (verifies and compiles) the code, and then executes PVF
//! with the `params`.
//! (b) PVF execution. This accepts the PVF
//! [`params`][`polkadot_parachain::primitives::ValidationParams`] and the `Pvf` code, prepares
//! (verifies and compiles) the code, and then executes PVF with the `params`.
//!
//! (c) Heads up. This request allows to signal that the given PVF may be needed soon and that it
//! should be prepared for execution.
//!
//! The preparation results are cached for some time after they either used or was signaled in heads up.
//! All requests that depends on preparation of the same PVF are bundled together and will be executed
//! as soon as the artifact is prepared.
//! The preparation results are cached for some time after they either used or was signaled in heads
//! up. All requests that depends on preparation of the same PVF are bundled together and will be
//! executed as soon as the artifact is prepared.
//!
//! # Priority
//!
//! PVF execution requests can specify the [priority][`Priority`] with which the given request should
//! be handled. Different priority levels have different effects. This is discussed below.
//! PVF execution requests can specify the [priority][`Priority`] with which the given request
//! should be handled. Different priority levels have different effects. This is discussed below.
//!
//! Preparation started by a heads up signal always starts with the background priority. If there
//! is already a request for that PVF preparation under way the priority is inherited. If after heads
//! up, a new PVF execution request comes in with a higher priority, then the original task's priority
//! will be adjusted to match the new one if it's larger.
//! is already a request for that PVF preparation under way the priority is inherited. If after
//! heads up, a new PVF execution request comes in with a higher priority, then the original task's
//! priority will be adjusted to match the new one if it's larger.
//!
//! Priority can never go down, only up.
//!
@@ -63,11 +63,11 @@
//! dissimilar to actors. Each of such "processes" is a future task that contains an event loop that
//! processes incoming messages, potentially delegating sub-tasks to other "processes".
//!
//! Two of these processes are queues. The first one is for preparation jobs and the second one is for
//! execution. Both of the queues are backed by separate pools of workers of different kind.
//! Two of these processes are queues. The first one is for preparation jobs and the second one is
//! for execution. Both of the queues are backed by separate pools of workers of different kind.
//!
//! Preparation workers handle preparation requests by prevalidating and instrumenting PVF wasm code,
//! and then passing it into the compiler, to prepare the artifact.
//! Preparation workers handle preparation requests by prevalidating and instrumenting PVF wasm
//! code, and then passing it into the compiler, to prepare the artifact.
//!
//! ## Artifacts
//!
+2 -1
View File
@@ -85,7 +85,8 @@ impl Metrics {
#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
if let Some(tracker_stats) = memory_stats.memory_tracker_stats {
// We convert these stats from B to KB to match the unit of `ru_maxrss` from `getrusage`.
// We convert these stats from B to KB to match the unit of `ru_maxrss` from
// `getrusage`.
let max_resident_kb = (tracker_stats.resident / 1024) as f64;
let max_allocated_kb = (tracker_stats.allocated / 1024) as f64;
+9 -7
View File
@@ -61,9 +61,9 @@ pub enum ToPool {
/// Request the given worker to start working on the given code.
///
/// Once the job either succeeded or failed, a [`FromPool::Concluded`] message will be sent back.
/// It's also possible that the worker dies before handling the message in which case [`FromPool::Rip`]
/// will be sent back.
/// Once the job either succeeded or failed, a [`FromPool::Concluded`] message will be sent
/// back. It's also possible that the worker dies before handling the message in which case
/// [`FromPool::Rip`] will be sent back.
///
/// In either case, the worker is considered busy and no further `StartWork` messages should be
/// sent until either `Concluded` or `Rip` message is received.
@@ -237,8 +237,8 @@ fn handle_to_pool(
);
} else {
// idle token is present after spawn and after a job is concluded;
// the precondition for `StartWork` is it should be sent only if all previous work
// items concluded;
// the precondition for `StartWork` is it should be sent only if all previous
// work items concluded;
// thus idle token is Some;
// qed.
never!("unexpected absence of the idle token in prepare pool");
@@ -311,7 +311,8 @@ fn handle_mux(
match outcome {
Outcome::Concluded { worker: idle, result } =>
handle_concluded_no_rip(from_pool, spawned, worker, idle, result),
// Return `Concluded`, but do not kill the worker since the error was on the host side.
// Return `Concluded`, but do not kill the worker since the error was on the host
// side.
Outcome::CreateTmpFileErr { worker: idle, err } => handle_concluded_no_rip(
from_pool,
spawned,
@@ -319,7 +320,8 @@ fn handle_mux(
idle,
Err(PrepareError::CreateTmpFileErr(err)),
),
// Return `Concluded`, but do not kill the worker since the error was on the host side.
// Return `Concluded`, but do not kill the worker since the error was on the host
// side.
Outcome::RenameTmpFileErr { worker: idle, result: _, err } =>
handle_concluded_no_rip(
from_pool,
+3 -2
View File
@@ -96,8 +96,9 @@ impl WorkerData {
}
}
/// A queue structured like this is prone to starving, however, we don't care that much since we expect
/// there is going to be a limited number of critical jobs and we don't really care if background starve.
/// A queue structured like this is prone to starving, however, we don't care that much since we
/// expect there is going to be a limited number of critical jobs and we don't really care if
/// background starve.
#[derive(Default)]
struct Unscheduled {
normal: VecDeque<Job>,
@@ -247,8 +247,8 @@ where
let outcome = f(tmp_file.clone(), stream).await;
// The function called above is expected to move `tmp_file` to a new location upon success. However,
// the function may as well fail and in that case we should remove the tmp file here.
// The function called above is expected to move `tmp_file` to a new location upon success.
// However, the function may as well fail and in that case we should remove the tmp file here.
//
// In any case, we try to remove the file here so that there are no leftovers. We only report
// errors that are different from the `NotFound`.
+14 -12
View File
@@ -196,13 +196,15 @@ pub enum SpawnErr {
Handshake,
}
/// This is a representation of a potentially running worker. Drop it and the process will be killed.
/// This is a representation of a potentially running worker. Drop it and the process will be
/// killed.
///
/// A worker's handle is also a future that resolves when it's detected that the worker's process
/// has been terminated. Since the worker is running in another process it is obviously not
/// necessary to poll this future to make the worker run, it's only for termination detection.
///
/// This future relies on the fact that a child process's stdout `fd` is closed upon it's termination.
/// This future relies on the fact that a child process's stdout `fd` is closed upon it's
/// termination.
#[pin_project]
pub struct WorkerHandle {
child: process::Child,
@@ -240,15 +242,15 @@ impl WorkerHandle {
child_id,
stdout,
program: program.as_ref().to_path_buf(),
// We don't expect the bytes to be ever read. But in case we do, we should not use a buffer
// of a small size, because otherwise if the child process does return any data we will end up
// issuing a syscall for each byte. We also prefer not to do allocate that on the stack, since
// each poll the buffer will be allocated and initialized (and that's due `poll_read` takes &mut [u8]
// and there are no guarantees that a `poll_read` won't ever read from there even though that's
// unlikely).
// We don't expect the bytes to be ever read. But in case we do, we should not use a
// buffer of a small size, because otherwise if the child process does return any data
// we will end up issuing a syscall for each byte. We also prefer not to do allocate
// that on the stack, since each poll the buffer will be allocated and initialized (and
// that's due `poll_read` takes &mut [u8] and there are no guarantees that a `poll_read`
// won't ever read from there even though that's unlikely).
//
// OTOH, we also don't want to be super smart here and we could just afford to allocate a buffer
// for that here.
// OTOH, we also don't want to be super smart here and we could just afford to allocate
// a buffer for that here.
drop_box: vec![0; 8192].into_boxed_slice(),
})
}
@@ -280,8 +282,8 @@ impl futures::Future for WorkerHandle {
}
},
Err(err) => {
// The implementation is guaranteed to not to return `WouldBlock` and Interrupted. This
// leaves us with legit errors which we suppose were due to termination.
// The implementation is guaranteed to not to return `WouldBlock` and Interrupted.
// This leaves us with legit errors which we suppose were due to termination.
// Log the status code.
gum::debug!(
+6 -5
View File
@@ -321,7 +321,8 @@ where
return futures::pending!()
}
// If there are active requests, this will always resolve to `Some(_)` when a request is finished.
// If there are active requests, this will always resolve to `Some(_)` when a request is
// finished.
if let Some(Ok(Some(result))) = self.active_requests.next().await {
self.store_cache(result);
}
@@ -343,10 +344,10 @@ where
{
loop {
// Let's add some back pressure when the subsystem is running at `MAX_PARALLEL_REQUESTS`.
// This can never block forever, because `active_requests` is owned by this task and any mutations
// happen either in `poll_requests` or `spawn_request` - so if `is_busy` returns true, then
// even if all of the requests finish before us calling `poll_requests` the `active_requests` length
// remains invariant.
// This can never block forever, because `active_requests` is owned by this task and any
// mutations happen either in `poll_requests` or `spawn_request` - so if `is_busy` returns
// true, then even if all of the requests finish before us calling `poll_requests` the
// `active_requests` length remains invariant.
if subsystem.is_busy() {
// Since we are not using any internal waiting queues, we need to wait for exactly
// one request to complete before we can read the next one from the overseer channel.
+2 -1
View File
@@ -895,7 +895,8 @@ fn multiple_requests_in_parallel_are_working() {
receivers.push(rx);
}
// The backpressure from reaching `MAX_PARALLEL_REQUESTS` will make the test block, we need to drop the lock.
// The backpressure from reaching `MAX_PARALLEL_REQUESTS` will make the test block, we need
// to drop the lock.
drop(lock);
for _ in 0..MAX_PARALLEL_REQUESTS * 100 {
+7 -8
View File
@@ -67,14 +67,13 @@
//!
//! Here's the rundown on how fields work:
//!
//! - Fields on spans and events are specified using the `syntax field_name =
//! field_value`.
//! - Local variables may be used as field values without an assignment, similar to
//! struct initializers.
//! - The `?` sigil is shorthand that specifies a field should be recorded using its
//! `fmt::Debug` implementation.
//! - The `%` sigil operates similarly, but indicates that the value should be
//! recorded using its `fmt::Display` implementation.
//! - Fields on spans and events are specified using the `syntax field_name = field_value`.
//! - Local variables may be used as field values without an assignment, similar to struct
//! initializers.
//! - The `?` sigil is shorthand that specifies a field should be recorded using its `fmt::Debug`
//! implementation.
//! - The `%` sigil operates similarly, but indicates that the value should be recorded using its
//! `fmt::Display` implementation.
//!
//! For full details, again see [the tracing
//! docs](https://docs.rs/tracing/latest/tracing/index.html#recording-fields).
+2 -1
View File
@@ -132,7 +132,8 @@ impl Jaeger {
match tokio::net::UdpSocket::bind("0.0.0.0:0").await {
Ok(udp_socket) => loop {
let buf = traces_out.next().await;
// UDP sending errors happen only either if the API is misused or in case of missing privilege.
// UDP sending errors happen only either if the API is misused or in case of
// missing privilege.
if let Err(e) = udp_socket.send_to(&buf, jaeger_agent).await {
log::debug!(target: "jaeger", "UDP send error: {}", e);
}
+2 -2
View File
@@ -110,8 +110,8 @@ impl PerLeafSpan {
/// Creates a new instance.
///
/// Takes the `leaf_span` that is created by the overseer per leaf and a name for a child span.
/// Both will be stored in this object, while the child span is implicitly accessible by using the
/// [`Deref`](std::ops::Deref) implementation.
/// Both will be stored in this object, while the child span is implicitly accessible by using
/// the [`Deref`](std::ops::Deref) implementation.
pub fn new(leaf_span: Arc<Span>, name: &'static str) -> Self {
let span = leaf_span.child(name);
+14 -8
View File
@@ -125,8 +125,8 @@ where
Self { fake_validation, fake_validation_error, distribution, spawner }
}
/// Creates and sends the validation response for a given candidate. Queries the runtime to obtain the validation data for the
/// given candidate.
/// Creates and sends the validation response for a given candidate. Queries the runtime to
/// obtain the validation data for the given candidate.
pub fn send_validation_response<Sender>(
&self,
candidate_descriptor: CandidateDescriptor,
@@ -203,7 +203,8 @@ where
{
type Message = CandidateValidationMessage;
// Capture all (approval and backing) candidate validation requests and depending on configuration fail them.
// Capture all (approval and backing) candidate validation requests and depending on
// configuration fail them.
fn intercept_incoming(
&self,
subsystem_sender: &mut Sender,
@@ -279,7 +280,8 @@ where
},
FakeCandidateValidation::ApprovalInvalid |
FakeCandidateValidation::BackingAndApprovalInvalid => {
// Set the validation result to invalid with probability `p` and trigger a dispute
// Set the validation result to invalid with probability `p` and trigger a
// dispute
let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
match behave_maliciously {
true => {
@@ -294,7 +296,8 @@ where
&validation_result,
);
// We're not even checking the candidate, this makes us appear faster than honest validators.
// We're not even checking the candidate, this makes us appear
// faster than honest validators.
sender.send(Ok(validation_result)).unwrap();
None
},
@@ -370,7 +373,8 @@ where
);
None
},
// If the `PoV` is malicious, we behave normally with some probability `(1-p)`
// If the `PoV` is malicious, we behave normally with some probability
// `(1-p)`
false => Some(FromOrchestra::Communication {
msg: CandidateValidationMessage::ValidateFromChainState(
candidate_receipt,
@@ -383,7 +387,8 @@ where
},
FakeCandidateValidation::BackingInvalid |
FakeCandidateValidation::BackingAndApprovalInvalid => {
// Maliciously set the validation result to invalid for a valid candidate with probability `p`
// Maliciously set the validation result to invalid for a valid candidate
// with probability `p`
let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
match behave_maliciously {
true => {
@@ -396,7 +401,8 @@ where
"😈 Maliciously sending invalid validation result: {:?}.",
&validation_result,
);
// We're not even checking the candidate, this makes us appear faster than honest validators.
// We're not even checking the candidate, this makes us appear
// faster than honest validators.
response_sender.send(Ok(validation_result)).unwrap();
None
},
@@ -45,14 +45,15 @@ use std::sync::Arc;
#[command(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub struct DisputeAncestorOptions {
/// Malicious candidate validation subsystem configuration. When enabled, node PVF execution is skipped
/// during backing and/or approval and it's result can by specified by this option and `--fake-validation-error`
/// for invalid candidate outcomes.
/// Malicious candidate validation subsystem configuration. When enabled, node PVF execution is
/// skipped during backing and/or approval and it's result can by specified by this option and
/// `--fake-validation-error` for invalid candidate outcomes.
#[arg(long, value_enum, ignore_case = true, default_value_t = FakeCandidateValidation::BackingAndApprovalInvalid)]
pub fake_validation: FakeCandidateValidation,
/// Applies only when `--fake-validation` is configured to reject candidates as invalid. It allows
/// to specify the exact error to return from the malicious candidate validation subsystem.
/// Applies only when `--fake-validation` is configured to reject candidates as invalid. It
/// allows to specify the exact error to return from the malicious candidate validation
/// subsystem.
#[arg(long, value_enum, ignore_case = true, default_value_t = FakeCandidateValidationError::InvalidOutputs)]
pub fake_validation_error: FakeCandidateValidationError,
@@ -88,14 +88,15 @@ where
"Received request to second candidate",
);
// Need to draw value from Bernoulli distribution with given probability of success defined by the clap parameter.
// Note that clap parameter must be f64 since this is expected by the Bernoulli::new() function.
// It must be converted from u8, due to the lack of support for the .range() call on u64 in the clap crate.
// Need to draw value from Bernoulli distribution with given probability of success
// defined by the clap parameter. Note that clap parameter must be f64 since this is
// expected by the Bernoulli::new() function. It must be converted from u8, due to
// the lack of support for the .range() call on u64 in the clap crate.
let distribution = Bernoulli::new(self.percentage / 100.0)
.expect("Invalid probability! Percentage must be in range [0..=100].");
// Draw a random boolean from the Bernoulli distribution with probability of true equal to `p`.
// We use `rand::thread_rng` as the source of randomness.
// Draw a random boolean from the Bernoulli distribution with probability of true
// equal to `p`. We use `rand::thread_rng` as the source of randomness.
let generate_malicious_candidate = distribution.sample(&mut rand::thread_rng());
if generate_malicious_candidate == true {
+2 -1
View File
@@ -19,7 +19,8 @@
//! Collects a bunch of metrics providers and related features such as
//! `Metronome` for usage with metrics collections.
//!
//! This crate also reexports Prometheus metric types which are expected to be implemented by subsystems.
//! This crate also reexports Prometheus metric types which are expected to be implemented by
//! subsystems.
#![deny(missing_docs)]
#![deny(unused_imports)]
@@ -102,11 +102,13 @@ impl RecentlyOutdated {
// Aggression has 3 levels:
//
// * Aggression Level 0: The basic behaviors described above.
// * Aggression Level 1: The originator of a message sends to all peers. Other peers follow the rules above.
// * Aggression Level 2: All peers send all messages to all their row and column neighbors.
// This means that each validator will, on average, receive each message approximately `2*sqrt(n)` times.
// The aggression level of messages pertaining to a block increases when that block is unfinalized and
// is a child of the finalized block.
// * Aggression Level 1: The originator of a message sends to all peers. Other peers follow the
// rules above.
// * Aggression Level 2: All peers send all messages to all their row and column neighbors. This
// means that each validator will, on average, receive each message approximately `2*sqrt(n)`
// times.
// The aggression level of messages pertaining to a block increases when that block is unfinalized
// and is a child of the finalized block.
// This means that only one block at a time has its messages propagated with aggression > 0.
//
// A note on aggression thresholds: changes in propagation apply only to blocks which are the
@@ -120,7 +122,8 @@ impl RecentlyOutdated {
struct AggressionConfig {
/// Aggression level 1: all validators send all their own messages to all peers.
l1_threshold: Option<BlockNumber>,
/// Aggression level 2: level 1 + all validators send all messages to all peers in the X and Y dimensions.
/// Aggression level 2: level 1 + all validators send all messages to all peers in the X and Y
/// dimensions.
l2_threshold: Option<BlockNumber>,
/// How often to re-send messages to all targeted recipients.
/// This applies to all unfinalized blocks.
@@ -167,11 +170,12 @@ struct State {
blocks: HashMap<Hash, BlockEntry>,
/// Our view updates to our peers can race with `NewBlocks` updates. We store messages received
/// against the directly mentioned blocks in our view in this map until `NewBlocks` is received.
/// against the directly mentioned blocks in our view in this map until `NewBlocks` is
/// received.
///
/// As long as the parent is already in the `blocks` map and `NewBlocks` messages aren't delayed
/// by more than a block length, this strategy will work well for mitigating the race. This is
/// also a race that occurs typically on local networks.
/// As long as the parent is already in the `blocks` map and `NewBlocks` messages aren't
/// delayed by more than a block length, this strategy will work well for mitigating the race.
/// This is also a race that occurs typically on local networks.
pending_known: HashMap<Hash, Vec<(PeerId, PendingMessage)>>,
/// Peer data is partially stored here, and partially inline within the [`BlockEntry`]s
@@ -947,7 +951,8 @@ impl State {
}
}
// Invariant: to our knowledge, none of the peers except for the `source` know about the assignment.
// Invariant: to our knowledge, none of the peers except for the `source` know about the
// assignment.
metrics.on_assignment_imported();
let topology = self.topologies.get_topology(entry.session);
@@ -1239,7 +1244,8 @@ impl State {
}
}
// Invariant: to our knowledge, none of the peers except for the `source` know about the approval.
// Invariant: to our knowledge, none of the peers except for the `source` know about the
// approval.
metrics.on_approval_imported();
let required_routing = match entry.candidates.get_mut(candidate_index as usize) {
@@ -1925,9 +1931,9 @@ const fn ensure_size_not_zero(size: usize) -> usize {
}
/// The maximum amount of assignments per batch is 33% of maximum allowed by protocol.
/// This is an arbitrary value. Bumping this up increases the maximum amount of approvals or assignments
/// we send in a single message to peers. Exceeding `MAX_NOTIFICATION_SIZE` will violate the protocol
/// configuration.
/// This is an arbitrary value. Bumping this up increases the maximum amount of approvals or
/// assignments we send in a single message to peers. Exceeding `MAX_NOTIFICATION_SIZE` will violate
/// the protocol configuration.
pub const MAX_ASSIGNMENT_BATCH_SIZE: usize = ensure_size_not_zero(
MAX_NOTIFICATION_SIZE as usize /
std::mem::size_of::<(IndirectAssignmentCert, CandidateIndex)>() /
@@ -315,7 +315,8 @@ impl RunningTask {
continue
},
};
// We drop the span so that the span is not active whilst we validate and store the chunk.
// We drop the span so that the span is not active whilst we validate and store the
// chunk.
drop(_chunk_recombine_span);
let _chunk_validate_and_store_span = span
.child("validate-and-store-chunk")
@@ -114,8 +114,8 @@ impl Requester {
.with_string_tag("leaf", format!("{:?}", leaf.hash))
.with_stage(jaeger::Stage::AvailabilityDistribution);
// Order important! We need to handle activated, prior to deactivated, otherwise we might
// cancel still needed jobs.
// Order important! We need to handle activated, prior to deactivated, otherwise we
// might cancel still needed jobs.
self.start_requesting_chunks(ctx, runtime, leaf, &span).await?;
}
@@ -168,8 +168,8 @@ impl Requester {
// any tasks separately.
//
// The next time the subsystem receives leaf update, some of spawned task will be bumped
// to be live in fresh relay parent, while some might get dropped due to the current leaf
// being deactivated.
// to be live in fresh relay parent, while some might get dropped due to the current
// leaf being deactivated.
self.add_cores(ctx, runtime, leaf, leaf_session_index, cores, span).await?;
}
@@ -177,7 +177,6 @@ impl Requester {
}
/// Stop requesting chunks for obsolete heads.
///
fn stop_requesting_chunks(&mut self, obsolete_leaves: impl Iterator<Item = Hash>) {
let obsolete_leaves: HashSet<_> = obsolete_leaves.collect();
self.fetches.retain(|_, task| {
@@ -226,10 +225,10 @@ impl Requester {
.with_session_info(
context,
runtime,
// We use leaf here, the relay_parent must be in the same session as the
// leaf. This is guaranteed by runtime which ensures that cores are cleared
// at session boundaries. At the same time, only leaves are guaranteed to
// be fetchable by the state trie.
// We use leaf here, the relay_parent must be in the same session as
// the leaf. This is guaranteed by runtime which ensures that cores are
// cleared at session boundaries. At the same time, only leaves are
// guaranteed to be fetchable by the state trie.
leaf,
leaf_session_index,
|info| FetchTaskConfig::new(leaf, &core, tx, metrics, info, span),
@@ -23,7 +23,6 @@
//! was almost done, thus we would have wasted time with our impatience. By simply making them
//! not count towards length, we can make sure to have enough "live" requests ongoing, while at the
//! same time taking advantage of some maybe "late" response from the undead.
//!
use std::{
pin::Pin,
@@ -111,7 +111,8 @@ const SMALL_POV_LIMIT: usize = 128 * 1024;
pub enum RecoveryStrategy {
/// We always try the backing group first, then fallback to validator chunks.
BackersFirstAlways,
/// We try the backing group first if PoV size is lower than specified, then fallback to validator chunks.
/// We try the backing group first if PoV size is lower than specified, then fallback to
/// validator chunks.
BackersFirstIfSizeLower(usize),
/// We always recover using validator chunks.
ChunksAlways,
@@ -132,7 +133,8 @@ impl RecoveryStrategy {
}
}
/// Returns the PoV size limit in bytes for `BackersFirstIfSizeLower` strategy, otherwise `None`.
/// Returns the PoV size limit in bytes for `BackersFirstIfSizeLower` strategy, otherwise
/// `None`.
pub fn pov_size_limit(&self) -> Option<usize> {
match *self {
RecoveryStrategy::BackersFirstIfSizeLower(limit) => Some(limit),
@@ -165,8 +167,8 @@ struct RequestChunksFromValidators {
///
/// including failed ones.
total_received_responses: usize,
/// a random shuffling of the validators which indicates the order in which we connect to the validators and
/// request the chunk from them.
/// a random shuffling of the validators which indicates the order in which we connect to the
/// validators and request the chunk from them.
shuffling: VecDeque<ValidatorIndex>,
/// Chunks received so far.
received_chunks: HashMap<ValidatorIndex, ErasureChunk>,
@@ -215,7 +217,8 @@ enum ErasureTask {
HashMap<ValidatorIndex, ErasureChunk>,
oneshot::Sender<Result<AvailableData, ErasureEncodingError>>,
),
/// Re-encode `AvailableData` into erasure chunks in order to verify the provided root hash of the Merkle tree.
/// Re-encode `AvailableData` into erasure chunks in order to verify the provided root hash of
/// the Merkle tree.
Reencode(usize, Hash, AvailableData, oneshot::Sender<Option<AvailableData>>),
}
@@ -808,8 +811,8 @@ where
self.params.metrics.on_recovery_started();
loop {
// These only fail if we cannot reach the underlying subsystem, which case there is nothing
// meaningful we can do.
// These only fail if we cannot reach the underlying subsystem, which case there is
// nothing meaningful we can do.
match self.source {
Source::RequestFromBackers(ref mut from_backers) => {
match from_backers.run(&self.params, &mut self.sender).await {
@@ -1008,7 +1011,8 @@ async fn launch_recovery_task<Context>(
);
backing_group = backing_group.filter(|_| {
// We keep the backing group only if `1/3` of chunks sum up to less than `small_pov_limit`.
// We keep the backing group only if `1/3` of chunks sum up to less than
// `small_pov_limit`.
prefer_backing_group
});
}
@@ -1194,18 +1198,21 @@ impl AvailabilityRecoverySubsystem {
let (erasure_task_tx, erasure_task_rx) = futures::channel::mpsc::channel(16);
let mut erasure_task_rx = erasure_task_rx.fuse();
// `ThreadPoolBuilder` spawns the tasks using `spawn_blocking`. For each worker there will be a `mpsc` channel created.
// Each of these workers take the `Receiver` and poll it in an infinite loop.
// All of the sender ends of the channel are sent as a vec which we then use to create a `Cycle` iterator.
// We use this iterator to assign work in a round-robin fashion to the workers in the pool.
// `ThreadPoolBuilder` spawns the tasks using `spawn_blocking`. For each worker there will
// be a `mpsc` channel created. Each of these workers take the `Receiver` and poll it in an
// infinite loop. All of the sender ends of the channel are sent as a vec which we then use
// to create a `Cycle` iterator. We use this iterator to assign work in a round-robin
// fashion to the workers in the pool.
//
// How work is dispatched to the pool from the recovery tasks:
// - Once a recovery task finishes retrieving the availability data, it needs to reconstruct from chunks and/or
// - Once a recovery task finishes retrieving the availability data, it needs to reconstruct
// from chunks and/or
// re-encode the data which are heavy CPU computations.
// To do so it sends an `ErasureTask` to the main loop via the `erasure_task` channel, and waits for the results
// over a `oneshot` channel.
// To do so it sends an `ErasureTask` to the main loop via the `erasure_task` channel, and
// waits for the results over a `oneshot` channel.
// - In the subsystem main loop we poll the `erasure_task_rx` receiver.
// - We forward the received `ErasureTask` to the `next()` sender yielded by the `Cycle` iterator.
// - We forward the received `ErasureTask` to the `next()` sender yielded by the `Cycle`
// iterator.
// - Some worker thread handles it and sends the response over the `oneshot` channel.
// Create a thread pool with 2 workers.
@@ -1348,11 +1355,13 @@ impl ThreadPoolBuilder {
// Creates a pool of `size` workers, where 1 <= `size` <= `MAX_THREADS`.
//
// Each worker is created by `spawn_blocking` and takes the receiver side of a channel
// while all of the senders are returned to the caller. Each worker runs `erasure_task_thread` that
// polls the `Receiver` for an `ErasureTask` which is expected to be CPU intensive. The larger
// the input (more or larger chunks/availability data), the more CPU cycles will be spent.
// while all of the senders are returned to the caller. Each worker runs `erasure_task_thread`
// that polls the `Receiver` for an `ErasureTask` which is expected to be CPU intensive. The
// larger the input (more or larger chunks/availability data), the more CPU cycles will be
// spent.
//
// For example, for 32KB PoVs, we'd expect re-encode to eat as much as 90ms and 500ms for 2.5MiB.
// For example, for 32KB PoVs, we'd expect re-encode to eat as much as 90ms and 500ms for
// 2.5MiB.
//
// After executing such a task, the worker sends the response via a provided `oneshot` sender.
//
@@ -817,7 +817,8 @@ fn wrong_chunk_index_leads_to_recovery_error() {
let candidate_hash = test_state.candidate.hash();
// These chunks should fail the index check as they don't have the correct index for validator.
// These chunks should fail the index check as they don't have the correct index for
// validator.
test_state.chunks[1] = test_state.chunks[0].clone();
test_state.chunks[2] = test_state.chunks[0].clone();
test_state.chunks[3] = test_state.chunks[0].clone();
+4 -2
View File
@@ -14,7 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! The Network Bridge Subsystem - handles _incoming_ messages from the network, forwarded to the relevant subsystems.
//! The Network Bridge Subsystem - handles _incoming_ messages from the network, forwarded to the
//! relevant subsystems.
use super::*;
use always_assert::never;
@@ -86,7 +87,8 @@ pub struct NetworkBridgeRx<N, AD> {
}
impl<N, AD> NetworkBridgeRx<N, AD> {
/// Create a new network bridge subsystem with underlying network service and authority discovery service.
/// Create a new network bridge subsystem with underlying network service and authority
/// discovery service.
///
/// This assumes that the network service has had the notifications protocol for the network
/// bridge already registered. See [`peers_sets_info`](peers_sets_info).
+3 -2
View File
@@ -795,8 +795,9 @@ fn peer_messages_sent_via_overseer() {
network_handle.disconnect_peer(peer.clone(), PeerSet::Validation).await;
// Approval distribution message comes first, and the message is only sent to that subsystem.
// then a disconnection event arises that is sent to all validation networking subsystems.
// Approval distribution message comes first, and the message is only sent to that
// subsystem. then a disconnection event arises that is sent to all validation networking
// subsystems.
assert_matches!(
virtual_overseer.recv().await,
+2 -1
View File
@@ -61,7 +61,8 @@ pub struct NetworkBridgeTx<N, AD> {
}
impl<N, AD> NetworkBridgeTx<N, AD> {
/// Create a new network bridge subsystem with underlying network service and authority discovery service.
/// Create a new network bridge subsystem with underlying network service and authority
/// discovery service.
///
/// This assumes that the network service has had the notifications protocol for the network
/// bridge already registered. See [`peers_sets_info`](peers_sets_info).
@@ -106,9 +106,10 @@ impl<N: Network, AD: AuthorityDiscovery> Service<N, AD> {
/// It will ask the network to connect to the validators and not disconnect
/// from them at least until the next request is issued for the same peer set.
///
/// This method will also disconnect from previously connected validators not in the `validator_ids` set.
/// it takes `network_service` and `authority_discovery_service` by value
/// and returns them as a workaround for the Future: Send requirement imposed by async function implementation.
/// This method will also disconnect from previously connected validators not in the
/// `validator_ids` set. it takes `network_service` and `authority_discovery_service` by value
/// and returns them as a workaround for the Future: Send requirement imposed by async function
/// implementation.
pub async fn on_request(
&mut self,
validator_ids: Vec<AuthorityDiscoveryId>,
@@ -225,8 +225,8 @@ struct State {
/// Our validator groups per active leaf.
our_validators_groups: HashMap<Hash, ValidatorGroup>,
/// The mapping from [`PeerId`] to [`HashSet<AuthorityDiscoveryId>`]. This is filled over time as we learn the [`PeerId`]'s
/// by `PeerConnected` events.
/// The mapping from [`PeerId`] to [`HashSet<AuthorityDiscoveryId>`]. This is filled over time
/// as we learn the [`PeerId`]'s by `PeerConnected` events.
peer_ids: HashMap<PeerId, HashSet<AuthorityDiscoveryId>>,
/// Tracks which validators we want to stay connected to.
@@ -241,8 +241,8 @@ struct State {
/// All collation fetching requests that are still waiting to be answered.
///
/// They are stored per relay parent, when our view changes and the relay parent moves out, we will cancel the fetch
/// request.
/// They are stored per relay parent, when our view changes and the relay parent moves out, we
/// will cancel the fetch request.
waiting_collation_fetches: HashMap<Hash, WaitingCollationFetches>,
/// Active collation fetches.
@@ -526,8 +526,8 @@ async fn connect_to_validators<Context>(
/// Advertise collation to the given `peer`.
///
/// This will only advertise a collation if there exists one for the given `relay_parent` and the given `peer` is
/// set as validator for our para at the given `relay_parent`.
/// This will only advertise a collation if there exists one for the given `relay_parent` and the
/// given `peer` is set as validator for our para at the given `relay_parent`.
#[overseer::contextbounds(CollatorProtocol, prefix = self::overseer)]
async fn advertise_collation<Context>(
ctx: &mut Context,
@@ -638,7 +638,8 @@ async fn process_msg<Context>(
);
},
NetworkBridgeUpdate(event) => {
// We should count only this shoulder in the histogram, as other shoulders are just introducing noise
// We should count only this shoulder in the histogram, as other shoulders are just
// introducing noise
let _ = state.metrics.time_process_msg();
if let Err(e) = handle_network_msg(ctx, runtime, state, event).await {
@@ -160,8 +160,8 @@ impl TestState {
/// Generate a new relay parent and inform the subsystem about the new view.
///
/// If `merge_views == true` it means the subsystem will be informed that we are working on the old `relay_parent`
/// and the new one.
/// If `merge_views == true` it means the subsystem will be informed that we are working on the
/// old `relay_parent` and the new one.
async fn advance_to_new_round(
&mut self,
virtual_overseer: &mut VirtualOverseer,
@@ -901,7 +901,8 @@ fn collate_on_two_different_relay_chain_blocks() {
let old_relay_parent = test_state.relay_parent;
// Advance to a new round, while informing the subsystem that the old and the new relay parent are active.
// Advance to a new round, while informing the subsystem that the old and the new relay
// parent are active.
test_state.advance_to_new_round(virtual_overseer, true).await;
distribute_collation(virtual_overseer, &test_state, true).await;
@@ -1085,7 +1086,8 @@ where
.await
.unwrap();
// Keep the feedback channel alive because we need to use it to inform about the finished transfer.
// Keep the feedback channel alive because we need to use it to inform about the
// finished transfer.
let feedback_tx = assert_matches!(
rx.await,
Ok(full_response) => {
@@ -23,9 +23,9 @@
//! We keep a simple FIFO buffer of N validator groups and a bitvec for each advertisement,
//! 1 indicating we want to be connected to i-th validator in a buffer, 0 otherwise.
//!
//! The bit is set to 1 for the whole **group** whenever it's inserted into the buffer. Given a relay
//! parent, one can reset a bit back to 0 for particular **validator**. For example, if a collation
//! was fetched or some timeout has been hit.
//! The bit is set to 1 for the whole **group** whenever it's inserted into the buffer. Given a
//! relay parent, one can reset a bit back to 0 for particular **validator**. For example, if a
//! collation was fetched or some timeout has been hit.
//!
//! The bitwise OR over known advertisements gives us validators indices for connection request.
@@ -730,7 +730,8 @@ fn reject_connection_to_next_group() {
})
}
// Ensure that we fetch a second collation, after the first checked collation was found to be invalid.
// Ensure that we fetch a second collation, after the first checked collation was found to be
// invalid.
#[test]
fn fetch_next_collation_on_invalid_collation() {
let test_state = TestState::default();
@@ -60,8 +60,8 @@ use self::sender::{DisputeSender, DisputeSenderMessage};
/// ## The receiver [`DisputesReceiver`]
///
/// The receiving side is implemented as `DisputesReceiver` and is run as a separate long running task within
/// this subsystem ([`DisputesReceiver::run`]).
/// The receiving side is implemented as `DisputesReceiver` and is run as a separate long running
/// task within this subsystem ([`DisputesReceiver::run`]).
///
/// Conceptually all the receiver has to do, is waiting for incoming requests which are passed in
/// via a dedicated channel and forwarding them to the dispute coordinator via
@@ -101,8 +101,8 @@ const LOG_TARGET: &'static str = "parachain::dispute-distribution";
/// Rate limit on the `receiver` side.
///
/// If messages from one peer come in at a higher rate than every `RECEIVE_RATE_LIMIT` on average, we
/// start dropping messages from that peer to enforce that limit.
/// If messages from one peer come in at a higher rate than every `RECEIVE_RATE_LIMIT` on average,
/// we start dropping messages from that peer to enforce that limit.
pub const RECEIVE_RATE_LIMIT: Duration = Duration::from_millis(100);
/// Rate limit on the `sender` side.
@@ -192,8 +192,8 @@ impl Batch {
/// Calculate when the next tick should happen.
///
/// This will usually return `now + BATCH_COLLECTING_INTERVAL`, except if the lifetime of this batch
/// would exceed `MAX_BATCH_LIFETIME`.
/// This will usually return `now + BATCH_COLLECTING_INTERVAL`, except if the lifetime of this
/// batch would exceed `MAX_BATCH_LIFETIME`.
///
/// # Arguments
///
@@ -50,8 +50,8 @@ impl<Payload: Eq + Ord> WaitingQueue<Payload> {
/// Push a `PendingWake`.
///
/// The next call to `wait_ready` will make sure to wake soon enough to process that new event in a
/// timely manner.
/// The next call to `wait_ready` will make sure to wake soon enough to process that new event
/// in a timely manner.
pub fn push(&mut self, wake: PendingWake<Payload>) {
self.pending_wakes.push(wake);
// Reset timer as it is potentially obsolete now:
@@ -382,11 +382,11 @@ where
if let Err(pending_response) = batch_result {
// We don't expect honest peers to send redundant votes within a single batch,
// as the timeout for retry is much higher. Still we don't want to punish the
// node as it might not be the node's fault. Some other (malicious) node could have been
// faster sending the same votes in order to harm the reputation of that honest
// node. Given that we already have a rate limit, if a validator chooses to
// waste available rate with redundant votes - so be it. The actual dispute
// resolution is unaffected.
// node as it might not be the node's fault. Some other (malicious) node could
// have been faster sending the same votes in order to harm the reputation of
// that honest node. Given that we already have a rate limit, if a validator
// chooses to waste available rate with redundant votes - so be it. The actual
// dispute resolution is unaffected.
gum::debug!(
target: LOG_TARGET,
?peer,
@@ -45,8 +45,8 @@ use crate::{
///
/// The unit of work for a `SendTask` is an authority/validator.
pub struct SendTask<M> {
/// The request we are supposed to get out to all `parachain` validators of the dispute's session
/// and to all current authorities.
/// The request we are supposed to get out to all `parachain` validators of the dispute's
/// session and to all current authorities.
request: DisputeRequest,
/// The set of authorities we need to send our messages to. This set will change at session
@@ -185,7 +185,8 @@ impl<M: 'static + Send + Sync> SendTask<M> {
/// Handle a finished response waiting task.
///
/// Called by `DisputeSender` upon reception of the corresponding message from our spawned `wait_response_task`.
/// Called by `DisputeSender` upon reception of the corresponding message from our spawned
/// `wait_response_task`.
pub fn on_finished_send(&mut self, authority: &AuthorityDiscoveryId, result: TaskResult) {
match result {
TaskResult::Failed(err) => {
@@ -204,8 +205,8 @@ impl<M: 'static + Send + Sync> SendTask<M> {
TaskResult::Succeeded => {
let status = match self.deliveries.get_mut(&authority) {
None => {
// Can happen when a sending became irrelevant while the response was already
// queued.
// Can happen when a sending became irrelevant while the response was
// already queued.
gum::debug!(
target: LOG_TARGET,
candidate = ?self.request.0.candidate_receipt.hash(),
@@ -246,7 +246,8 @@ where
{
let mut connections = authorities_past_present_future(sender, leaf).await?;
// Remove all of our locally controlled validator indices so we don't connect to ourself.
// Remove all of our locally controlled validator indices so we don't connect to
// ourself.
let connections =
if remove_all_controlled(&self.keystore, &mut connections) != 0 {
connections
@@ -17,17 +17,20 @@
//! Grid topology support implementation
//! The basic operation of the 2D grid topology is that:
//! * A validator producing a message sends it to its row-neighbors and its column-neighbors
//! * A validator receiving a message originating from one of its row-neighbors sends it to its column-neighbors
//! * A validator receiving a message originating from one of its column-neighbors sends it to its row-neighbors
//! * A validator receiving a message originating from one of its row-neighbors sends it to its
//! column-neighbors
//! * A validator receiving a message originating from one of its column-neighbors sends it to its
//! row-neighbors
//!
//! This grid approach defines 2 unique paths for every validator to reach every other validator in at most 2 hops.
//! This grid approach defines 2 unique paths for every validator to reach every other validator in
//! at most 2 hops.
//!
//! However, we also supplement this with some degree of random propagation:
//! every validator, upon seeing a message for the first time, propagates it to 8 random peers.
//! This inserts some redundancy in case the grid topology isn't working or is being attacked -
//! an adversary doesn't know which peers a validator will send to.
//! This is combined with the property that the adversary doesn't know which validators will elect to check a block.
//!
//! This is combined with the property that the adversary doesn't know which validators will elect
//! to check a block.
use crate::PeerId;
use polkadot_primitives::{AuthorityDiscoveryId, SessionIndex, ValidatorIndex};
@@ -188,7 +191,8 @@ impl GridNeighbors {
(false, false) => RequiredRouting::None,
(true, false) => RequiredRouting::GridY, // messages from X go to Y
(false, true) => RequiredRouting::GridX, // messages from Y go to X
(true, true) => RequiredRouting::GridXY, // if the grid works as expected, this shouldn't happen.
(true, true) => RequiredRouting::GridXY, /* if the grid works as expected, this
* shouldn't happen. */
}
}
@@ -213,7 +217,8 @@ impl GridNeighbors {
"Grid topology is unexpected, play it safe and send to X AND Y"
);
RequiredRouting::GridXY
}, // if the grid works as expected, this shouldn't happen.
}, /* if the grid works as expected, this
* shouldn't happen. */
}
}
+4 -2
View File
@@ -91,7 +91,8 @@ impl Into<sc_network::ObservedRole> for ObservedRole {
/// Specialized wrapper around [`View`].
///
/// Besides the access to the view itself, it also gives access to the [`jaeger::Span`] per leave/head.
/// Besides the access to the view itself, it also gives access to the [`jaeger::Span`] per
/// leave/head.
#[derive(Debug, Clone, Default)]
pub struct OurView {
view: View,
@@ -131,7 +132,8 @@ impl std::ops::Deref for OurView {
}
}
/// Construct a new [`OurView`] with the given chain heads, finalized number 0 and disabled [`jaeger::Span`]'s.
/// Construct a new [`OurView`] with the given chain heads, finalized number 0 and disabled
/// [`jaeger::Span`]'s.
///
/// NOTE: Use for tests only.
///
@@ -98,7 +98,8 @@ impl PeerSet {
max_notification_size,
handshake: None,
set_config: SetConfig {
// Non-authority nodes don't need to accept incoming connections on this peer set:
// Non-authority nodes don't need to accept incoming connections on this peer
// set:
in_peers: if is_authority == IsAuthority::Yes { 100 } else { 0 },
out_peers: 0,
reserved_nodes: Vec::new(),
@@ -78,8 +78,8 @@ where
/// reputation changes in that case.
///
/// Params:
/// - The raw request to decode
/// - Reputation changes to apply for the peer in case decoding fails.
/// - The raw request to decode
/// - Reputation changes to apply for the peer in case decoding fails.
fn try_from_raw(
raw: sc_network::config::IncomingRequest,
reputation_changes: Vec<UnifiedReputationChange>,
@@ -110,9 +110,9 @@ pub const MAX_PARALLEL_STATEMENT_REQUESTS: u32 = 3;
/// Response size limit for responses of POV like data.
///
/// This is larger than `MAX_POV_SIZE` to account for protocol overhead and for additional data in
/// `CollationFetchingV1` or `AvailableDataFetchingV1` for example. We try to err on larger limits here
/// as a too large limit only allows an attacker to waste our bandwidth some more, a too low limit
/// might have more severe effects.
/// `CollationFetchingV1` or `AvailableDataFetchingV1` for example. We try to err on larger limits
/// here as a too large limit only allows an attacker to waste our bandwidth some more, a too low
/// limit might have more severe effects.
const POV_RESPONSE_SIZE: u64 = MAX_POV_SIZE as u64 + 10_000;
/// Maximum response sizes for `StatementFetchingV1`.
@@ -185,8 +185,8 @@ struct VcPerPeerTracker {
}
impl VcPerPeerTracker {
/// Note that the remote should now be aware that a validator has seconded a given candidate (by hash)
/// based on a message that we have sent it from our local pool.
/// Note that the remote should now be aware that a validator has seconded a given candidate (by
/// hash) based on a message that we have sent it from our local pool.
fn note_local(&mut self, h: CandidateHash) {
if !note_hash(&mut self.local_observed, h) {
gum::warn!(
@@ -198,8 +198,8 @@ impl VcPerPeerTracker {
}
}
/// Note that the remote should now be aware that a validator has seconded a given candidate (by hash)
/// based on a message that it has sent us.
/// Note that the remote should now be aware that a validator has seconded a given candidate (by
/// hash) based on a message that it has sent us.
///
/// Returns `true` if the peer was allowed to send us such a message, `false` otherwise.
fn note_remote(&mut self, h: CandidateHash) -> bool {
@@ -226,8 +226,8 @@ fn note_hash(
/// knowledge that a peer has about goings-on in a relay parent.
#[derive(Default)]
struct PeerRelayParentKnowledge {
/// candidates that the peer is aware of because we sent statements to it. This indicates that we can
/// send other statements pertaining to that candidate.
/// candidates that the peer is aware of because we sent statements to it. This indicates that
/// we can send other statements pertaining to that candidate.
sent_candidates: HashSet<CandidateHash>,
/// candidates that peer is aware of, because we received statements from it.
received_candidates: HashSet<CandidateHash>,
@@ -321,13 +321,13 @@ impl PeerRelayParentKnowledge {
}
}
/// Attempt to update our view of the peer's knowledge with this statement's fingerprint based on
/// a message we are receiving from the peer.
/// Attempt to update our view of the peer's knowledge with this statement's fingerprint based
/// on a message we are receiving from the peer.
///
/// Provide the maximum message count that we can receive per candidate. In practice we should
/// not receive more statements for any one candidate than there are members in the group assigned
/// to that para, but this maximum needs to be lenient to account for equivocations that may be
/// cross-group. As such, a maximum of 2 * `n_validators` is recommended.
/// not receive more statements for any one candidate than there are members in the group
/// assigned to that para, but this maximum needs to be lenient to account for equivocations
/// that may be cross-group. As such, a maximum of 2 * `n_validators` is recommended.
///
/// This returns an error if the peer should not have sent us this message according to protocol
/// rules for flood protection.
@@ -490,13 +490,13 @@ impl PeerData {
self.view_knowledge.get(relay_parent).map_or(false, |k| k.can_send(fingerprint))
}
/// Attempt to update our view of the peer's knowledge with this statement's fingerprint based on
/// a message we are receiving from the peer.
/// Attempt to update our view of the peer's knowledge with this statement's fingerprint based
/// on a message we are receiving from the peer.
///
/// Provide the maximum message count that we can receive per candidate. In practice we should
/// not receive more statements for any one candidate than there are members in the group assigned
/// to that para, but this maximum needs to be lenient to account for equivocations that may be
/// cross-group. As such, a maximum of 2 * `n_validators` is recommended.
/// not receive more statements for any one candidate than there are members in the group
/// assigned to that para, but this maximum needs to be lenient to account for equivocations
/// that may be cross-group. As such, a maximum of 2 * `n_validators` is recommended.
///
/// This returns an error if the peer should not have sent us this message according to protocol
/// rules for flood protection.
@@ -600,8 +600,8 @@ enum NotedStatement<'a> {
/// Large statement fetching status.
enum LargeStatementStatus {
/// We are currently fetching the statement data from a remote peer. We keep a list of other nodes
/// claiming to have that data and will fallback on them.
/// We are currently fetching the statement data from a remote peer. We keep a list of other
/// nodes claiming to have that data and will fallback on them.
Fetching(FetchingInfo),
/// Statement data is fetched or we got it locally via `StatementDistributionMessage::Share`.
FetchedOrShared(CommittedCandidateReceipt),
@@ -712,8 +712,8 @@ impl ActiveHeadData {
/// to have been checked, including that the validator index is not out-of-bounds and
/// the signature is valid.
///
/// Any other statements or those that reference a candidate we are not aware of cannot be accepted
/// and will return `NotedStatement::NotUseful`.
/// Any other statements or those that reference a candidate we are not aware of cannot be
/// accepted and will return `NotedStatement::NotUseful`.
fn note_statement(&mut self, statement: SignedFullStatement) -> NotedStatement {
let validator_index = statement.validator_index();
let comparator = StoredStatementComparator {
@@ -1272,9 +1272,9 @@ async fn retrieve_statement_from_message<'a, Context>(
}
},
protocol_v1::StatementDistributionMessage::Statement(_, s) => {
// No fetch in progress, safe to return any statement immediately (we don't bother
// about normal network jitter which might cause `Valid` statements to arrive early
// for now.).
// No fetch in progress, safe to return any statement immediately (we don't
// bother about normal network jitter which might cause `Valid` statements to
// arrive early for now.).
return Some(s)
},
}
@@ -1470,7 +1470,8 @@ async fn handle_incoming_message<'a, Context>(
);
match rep {
// This happens when a Valid statement has been received but there is no corresponding Seconded
// This happens when a Valid statement has been received but there is no corresponding
// Seconded
COST_UNEXPECTED_STATEMENT_UNKNOWN_CANDIDATE => {
metrics.on_unexpected_statement_valid();
// Report peer merely if this is not a duplicate out-of-view statement that
@@ -824,8 +824,8 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
})
.await;
// receive a seconded statement from peer A. it should be propagated onwards to peer B and to
// candidate backing.
// receive a seconded statement from peer A. it should be propagated onwards to peer B and
// to candidate backing.
let statement = {
let signing_context = SigningContext { parent_hash: hash_a, session_index };
@@ -2536,8 +2536,8 @@ fn handle_multiple_seconded_statements() {
})
.await;
// receive a seconded statement from peer A. it should be propagated onwards to peer B and to
// candidate backing.
// receive a seconded statement from peer A. it should be propagated onwards to peer B and
// to candidate backing.
let statement = {
let signing_context = SigningContext { parent_hash: relay_parent_hash, session_index };
+17 -17
View File
@@ -211,10 +211,10 @@ impl Handle {
/// Wait for a block with the given hash to be in the active-leaves set.
///
/// The response channel responds if the hash was activated and is closed if the hash was deactivated.
/// Note that due the fact the overseer doesn't store the whole active-leaves set, only deltas,
/// the response channel may never return if the hash was deactivated before this call.
/// In this case, it's the caller's responsibility to ensure a timeout is set.
/// The response channel responds if the hash was activated and is closed if the hash was
/// deactivated. Note that due the fact the overseer doesn't store the whole active-leaves set,
/// only deltas, the response channel may never return if the hash was deactivated before this
/// call. In this case, it's the caller's responsibility to ensure a timeout is set.
pub async fn wait_for_activation(
&mut self,
hash: Hash,
@@ -355,7 +355,6 @@ pub async fn forward_events<P: BlockchainEvents<Block>>(client: Arc<P>, mut hand
/// +-----------+
/// | |
/// +-----------+
///
/// ```
///
/// [`Subsystem`]: trait.Subsystem.html
@@ -363,8 +362,8 @@ pub async fn forward_events<P: BlockchainEvents<Block>>(client: Arc<P>, mut hand
/// # Example
///
/// The [`Subsystems`] may be any type as long as they implement an expected interface.
/// Here, we create a mock validation subsystem and a few dummy ones and start the `Overseer` with them.
/// For the sake of simplicity the termination of the example is done with a timeout.
/// Here, we create a mock validation subsystem and a few dummy ones and start the `Overseer` with
/// them. For the sake of simplicity the termination of the example is done with a timeout.
/// ```
/// # use std::time::Duration;
/// # use futures::{executor, pin_mut, select, FutureExt};
@@ -394,11 +393,11 @@ pub async fn forward_events<P: BlockchainEvents<Block>>(client: Arc<P>, mut hand
/// impl<Ctx> overseer::Subsystem<Ctx, SubsystemError> for ValidationSubsystem
/// where
/// Ctx: overseer::SubsystemContext<
/// Message=CandidateValidationMessage,
/// AllMessages=AllMessages,
/// Signal=OverseerSignal,
/// Error=SubsystemError,
/// >,
/// Message=CandidateValidationMessage,
/// AllMessages=AllMessages,
/// Signal=OverseerSignal,
/// Error=SubsystemError,
/// >,
/// {
/// fn start(
/// self,
@@ -426,10 +425,10 @@ pub async fn forward_events<P: BlockchainEvents<Block>>(client: Arc<P>, mut hand
///
/// let spawner = sp_core::testing::TaskExecutor::new();
/// let (overseer, _handle) = dummy_overseer_builder(spawner, AlwaysSupportsParachains, None)
/// .unwrap()
/// .replace_candidate_validation(|_| ValidationSubsystem)
/// .build()
/// .unwrap();
/// .unwrap()
/// .replace_candidate_validation(|_| ValidationSubsystem)
/// .build()
/// .unwrap();
///
/// let timer = Delay::new(Duration::from_millis(50)).fuse();
///
@@ -825,7 +824,8 @@ where
// If there are no leaves being deactivated, we don't need to send an update.
//
// Our peers will be informed about our finalized block the next time we activating/deactivating some leaf.
// Our peers will be informed about our finalized block the next time we
// activating/deactivating some leaf.
if !update.is_empty() {
self.broadcast_signal(OverseerSignal::ActiveLeaves(update)).await?;
}
@@ -105,8 +105,8 @@ impl DisputeMessage {
/// - the invalid statement is indeed an invalid one
/// - the valid statement is indeed a valid one
/// - The passed `CandidateReceipt` has the correct hash (as signed in the statements).
/// - the given validator indices match with the given `ValidatorId`s in the statements,
/// given a `SessionInfo`.
/// - the given validator indices match with the given `ValidatorId`s in the statements, given a
/// `SessionInfo`.
///
/// We don't check whether the given `SessionInfo` matches the `SessionIndex` in the
/// statements, because we can't without doing a runtime query. Nevertheless this smart
@@ -16,7 +16,8 @@
use parity_scale_codec::{Decode, Encode};
/// Timestamp based on the 1 Jan 1970 UNIX base, which is persistent across node restarts and OS reboots.
/// Timestamp based on the 1 Jan 1970 UNIX base, which is persistent across node restarts and OS
/// reboots.
pub type Timestamp = u64;
/// The status of dispute.
@@ -88,8 +89,8 @@ impl DisputeStatus {
}
}
/// Transition the status to a new status after observing the dispute has concluded for the candidate.
/// This may be a no-op if the status was already concluded.
/// Transition the status to a new status after observing the dispute has concluded for the
/// candidate. This may be a no-op if the status was already concluded.
pub fn conclude_for(self, now: Timestamp) -> DisputeStatus {
match self {
DisputeStatus::Active | DisputeStatus::Confirmed => DisputeStatus::ConcludedFor(now),
@@ -98,8 +99,8 @@ impl DisputeStatus {
}
}
/// Transition the status to a new status after observing the dispute has concluded against the candidate.
/// This may be a no-op if the status was already concluded.
/// Transition the status to a new status after observing the dispute has concluded against the
/// candidate. This may be a no-op if the status was already concluded.
pub fn conclude_against(self, now: Timestamp) -> DisputeStatus {
match self {
DisputeStatus::Active | DisputeStatus::Confirmed =>
+14 -12
View File
@@ -180,8 +180,8 @@ impl std::fmt::Debug for Statement {
impl Statement {
/// Get the candidate hash referenced by this statement.
///
/// If this is a `Statement::Seconded`, this does hash the candidate receipt, which may be expensive
/// for large candidates.
/// If this is a `Statement::Seconded`, this does hash the candidate receipt, which may be
/// expensive for large candidates.
pub fn candidate_hash(&self) -> CandidateHash {
match *self {
Statement::Valid(ref h) => *h,
@@ -215,8 +215,8 @@ impl EncodeAs<CompactStatement> for Statement {
///
/// Signing context and validator set should be apparent from context.
///
/// This statement is "full" in the sense that the `Seconded` variant includes the candidate receipt.
/// Only the compact `SignedStatement` is suitable for submission to the chain.
/// This statement is "full" in the sense that the `Seconded` variant includes the candidate
/// receipt. Only the compact `SignedStatement` is suitable for submission to the chain.
pub type SignedFullStatement = Signed<Statement, CompactStatement>;
/// Variant of `SignedFullStatement` where the signature has not yet been verified.
@@ -256,8 +256,8 @@ pub enum InvalidCandidate {
/// Result of the validation of the candidate.
#[derive(Debug)]
pub enum ValidationResult {
/// Candidate is valid. The validation process yields these outputs and the persisted validation
/// data used to form inputs.
/// Candidate is valid. The validation process yields these outputs and the persisted
/// validation data used to form inputs.
Valid(CandidateCommitments, PersistedValidationData),
/// Candidate is invalid.
Invalid(InvalidCandidate),
@@ -321,7 +321,8 @@ pub struct Collation<BlockNumber = polkadot_primitives::BlockNumber> {
pub proof_of_validity: MaybeCompressedPoV,
/// The number of messages processed from the DMQ.
pub processed_downward_messages: u32,
/// The mark which specifies the block number up to which all inbound HRMP messages are processed.
/// The mark which specifies the block number up to which all inbound HRMP messages are
/// processed.
pub hrmp_watermark: BlockNumber,
}
@@ -344,9 +345,9 @@ pub struct CollationResult {
pub collation: Collation,
/// An optional result sender that should be informed about a successfully seconded collation.
///
/// There is no guarantee that this sender is informed ever about any result, it is completely okay to just drop it.
/// However, if it is called, it should be called with the signed statement of a parachain validator seconding the
/// collation.
/// There is no guarantee that this sender is informed ever about any result, it is completely
/// okay to just drop it. However, if it is called, it should be called with the signed
/// statement of a parachain validator seconding the collation.
pub result_sender: Option<futures::channel::oneshot::Sender<CollationSecondedSignal>>,
}
@@ -362,8 +363,9 @@ impl CollationResult {
/// Collation function.
///
/// Will be called with the hash of the relay chain block the parachain block should be build on and the
/// [`ValidationData`] that provides information about the state of the parachain on the relay chain.
/// Will be called with the hash of the relay chain block the parachain block should be build on and
/// the [`ValidationData`] that provides information about the state of the parachain on the relay
/// chain.
///
/// Returns an optional [`CollationResult`].
#[cfg(not(target_os = "unknown"))]
+6 -5
View File
@@ -529,11 +529,12 @@ fn kusama_staging_testnet_config_genesis(wasm_binary: &[u8]) -> kusama::RuntimeG
hex!["12b782529c22032ed4694e0f6e7d486be7daa6d12088f6bc74d593b3900b8438"].into(),
];
// for i in 1 2 3 4; do for j in stash controller; do subkey inspect "$SECRET//$i//$j"; done; done
// for i in 1 2 3 4; do for j in babe; do subkey --sr25519 inspect "$SECRET//$i//$j"; done; done
// for i in 1 2 3 4; do for j in grandpa; do subkey --ed25519 inspect "$SECRET//$i//$j"; done; done
// for i in 1 2 3 4; do for j in im_online; do subkey --sr25519 inspect "$SECRET//$i//$j"; done; done
// for i in 1 2 3 4; do for j in para_validator para_assignment; do subkey --sr25519 inspect "$SECRET//$i//$j"; done; done
// for i in 1 2 3 4; do for j in stash controller; do subkey inspect "$SECRET//$i//$j"; done;
// done for i in 1 2 3 4; do for j in babe; do subkey --sr25519 inspect "$SECRET//$i//$j"; done;
// done for i in 1 2 3 4; do for j in grandpa; do subkey --ed25519 inspect "$SECRET//$i//$j";
// done; done for i in 1 2 3 4; do for j in im_online; do subkey --sr25519 inspect
// "$SECRET//$i//$j"; done; done for i in 1 2 3 4; do for j in para_validator para_assignment;
// do subkey --sr25519 inspect "$SECRET//$i//$j"; done; done
let initial_authorities: Vec<(
AccountId,
AccountId,
@@ -16,7 +16,8 @@
//! Provides "fake" runtime API implementations
//!
//! These are used to provide a type that implements these runtime APIs without requiring to import the native runtimes.
//! These are used to provide a type that implements these runtime APIs without requiring to import
//! the native runtimes.
use beefy_primitives::ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature};
use grandpa_primitives::AuthorityId as GrandpaId;
+8 -6
View File
@@ -696,9 +696,10 @@ pub const AVAILABILITY_CONFIG: AvailabilityConfig = AvailabilityConfig {
/// This is an advanced feature and not recommended for general use. Generally, `build_full` is
/// a better choice.
///
/// `overseer_enable_anyways` always enables the overseer, based on the provided `OverseerGenerator`,
/// regardless of the role the node has. The relay chain selection (longest or disputes-aware) is
/// still determined based on the role of the node. Likewise for authority discovery.
/// `overseer_enable_anyways` always enables the overseer, based on the provided
/// `OverseerGenerator`, regardless of the role the node has. The relay chain selection (longest or
/// disputes-aware) is still determined based on the role of the node. Likewise for authority
/// discovery.
///
/// `workers_path` is used to get the path to the directory where auxiliary worker binaries reside.
/// If not specified, the main binary's directory is searched first, then `/usr/lib/polkadot` is
@@ -1331,9 +1332,10 @@ pub fn new_chain_ops(
/// The actual "flavor", aka if it will use `Polkadot`, `Rococo` or `Kusama` is determined based on
/// [`IdentifyVariant`] using the chain spec.
///
/// `overseer_enable_anyways` always enables the overseer, based on the provided `OverseerGenerator`,
/// regardless of the role the node has. The relay chain selection (longest or disputes-aware) is
/// still determined based on the role of the node. Likewise for authority discovery.
/// `overseer_enable_anyways` always enables the overseer, based on the provided
/// `OverseerGenerator`, regardless of the role the node has. The relay chain selection (longest or
/// disputes-aware) is still determined based on the role of the node. Likewise for authority
/// discovery.
#[cfg(feature = "full-node")]
pub fn build_full<OverseerGenerator: OverseerGen>(
config: Configuration,
@@ -472,8 +472,8 @@ where
let lag = initial_leaf_number.saturating_sub(subchain_number);
self.metrics.note_approval_checking_finality_lag(lag);
// Messages sent to `approval-distrbution` are known to have high `ToF`, we need to spawn a task for sending
// the message to not block here and delay finality.
// Messages sent to `approval-distrbution` are known to have high `ToF`, we need to spawn a
// task for sending the message to not block here and delay finality.
if let Some(spawn_handle) = &self.spawn_handle {
let mut overseer_handle = self.overseer.clone();
let lag_update_task = async move {
@@ -537,9 +537,10 @@ where
error = ?e,
"Call to `DetermineUndisputedChain` failed",
);
// We need to return a sane finality target. But, we are unable to ensure we are not
// finalizing something that is being disputed or has been concluded as invalid. We will be
// conservative here and not vote for finality above the ancestor passed in.
// We need to return a sane finality target. But, we are unable to ensure we
// are not finalizing something that is being disputed or has been concluded
// as invalid. We will be conservative here and not vote for finality above
// the ancestor passed in.
return Ok(target_hash)
},
};

Some files were not shown because too many files have changed in this diff Show More