Add new Runtime API messages and make runtime API request fallible (#1485)

* polkadot-subsystem: update runtime API message types

* update all networking subsystems to use fallible runtime APIs

* fix bitfield-signing and make it use new runtime APIs

* port candidate-backing to handle runtime API errors and new types

* remove old runtime API messages

* remove unused imports

* fix grumbles

* fix backing tests
This commit is contained in:
Robert Habermeier
2020-07-28 14:02:39 -04:00
committed by GitHub
parent d234ba38bb
commit c8cdfbfd17
9 changed files with 269 additions and 140 deletions
+1
View File
@@ -4606,6 +4606,7 @@ dependencies = [
"bitvec", "bitvec",
"derive_more 0.99.9", "derive_more 0.99.9",
"futures 0.3.5", "futures 0.3.5",
"log 0.4.8",
"polkadot-erasure-coding", "polkadot-erasure-coding",
"polkadot-node-primitives", "polkadot-node-primitives",
"polkadot-node-subsystem", "polkadot-node-subsystem",
+1
View File
@@ -17,6 +17,7 @@ erasure-coding = { package = "polkadot-erasure-coding", path = "../../../erasure
statement-table = { package = "polkadot-statement-table", path = "../../../statement-table" } statement-table = { package = "polkadot-statement-table", path = "../../../statement-table" }
derive_more = "0.99.9" derive_more = "0.99.9"
bitvec = { version = "0.17.4", default-features = false, features = ["alloc"] } bitvec = { version = "0.17.4", default-features = false, features = ["alloc"] }
log = "0.4.8"
[dev-dependencies] [dev-dependencies]
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
+93 -56
View File
@@ -32,7 +32,7 @@ use polkadot_primitives::v1::{
CommittedCandidateReceipt, BackedCandidate, Id as ParaId, ValidatorId, CommittedCandidateReceipt, BackedCandidate, Id as ParaId, ValidatorId,
ValidatorIndex, SigningContext, PoV, OmittedValidationData, ValidatorIndex, SigningContext, PoV, OmittedValidationData,
CandidateDescriptor, AvailableData, ValidatorSignature, Hash, CandidateReceipt, CandidateDescriptor, AvailableData, ValidatorSignature, Hash, CandidateReceipt,
CandidateCommitments, CandidateCommitments, CoreState, CoreIndex,
}; };
use polkadot_node_primitives::{ use polkadot_node_primitives::{
FromTableMisbehavior, Statement, SignedFullStatement, MisbehaviorReport, FromTableMisbehavior, Statement, SignedFullStatement, MisbehaviorReport,
@@ -44,12 +44,14 @@ use polkadot_subsystem::{
AllMessages, AvailabilityStoreMessage, CandidateBackingMessage, CandidateSelectionMessage, AllMessages, AvailabilityStoreMessage, CandidateBackingMessage, CandidateSelectionMessage,
CandidateValidationMessage, NewBackedCandidate, PoVDistributionMessage, ProvisionableData, CandidateValidationMessage, NewBackedCandidate, PoVDistributionMessage, ProvisionableData,
ProvisionerMessage, RuntimeApiMessage, StatementDistributionMessage, ValidationFailed, ProvisionerMessage, RuntimeApiMessage, StatementDistributionMessage, ValidationFailed,
RuntimeApiRequest,
}, },
util::{ util::{
self, self,
request_signing_context, request_session_index_for_child,
request_validator_groups, request_validator_groups,
request_validators, request_validators,
request_from_runtime,
Validator, Validator,
}, },
}; };
@@ -680,19 +682,56 @@ impl util::JobTrait for CandidateBackingJob {
mut tx_from: mpsc::Sender<Self::FromJob>, mut tx_from: mpsc::Sender<Self::FromJob>,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send>> { ) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send>> {
async move { async move {
let (validators, roster, signing_context) = futures::try_join!( macro_rules! try_runtime_api {
($x: expr) => {
match $x {
Ok(x) => x,
Err(e) => {
log::warn!(
target: "candidate_backing",
"Failed to fetch runtime API data for job: {:?}",
e,
);
// We can't do candidate validation work if we don't have the
// requisite runtime API data. But these errors should not take
// down the node.
return Ok(());
}
}
}
}
let (validators, groups, session_index, cores) = futures::try_join!(
request_validators(parent, &mut tx_from).await?, request_validators(parent, &mut tx_from).await?,
request_validator_groups(parent, &mut tx_from).await?, request_validator_groups(parent, &mut tx_from).await?,
request_signing_context(parent, &mut tx_from).await?, request_session_index_for_child(parent, &mut tx_from).await?,
request_from_runtime(
parent,
&mut tx_from,
|tx| RuntimeApiRequest::AvailabilityCores(tx),
).await?,
)?; )?;
let validators = try_runtime_api!(validators);
let (validator_groups, group_rotation_info) = try_runtime_api!(groups);
let session_index = try_runtime_api!(session_index);
let cores = try_runtime_api!(cores);
let signing_context = SigningContext { parent_hash: parent, session_index };
let validator = Validator::construct(&validators, signing_context, keystore.clone())?; let validator = Validator::construct(&validators, signing_context, keystore.clone())?;
let mut groups = HashMap::new(); let mut groups = HashMap::new();
for assignment in roster.scheduled { let n_cores = cores.len();
if let Some(g) = roster.validator_groups.get(assignment.group_idx.0 as usize) { for (idx, core) in cores.into_iter().enumerate() {
groups.insert(assignment.para_id, g.clone()); // Ignore prospective assignments on occupied cores for the time being.
if let CoreState::Scheduled(scheduled) = core {
let core_index = CoreIndex(idx as _);
let group_index = group_rotation_info.group_for_core(core_index, n_cores);
if let Some(g) = validator_groups.get(group_index.0 as usize) {
groups.insert(scheduled.para_id, g.clone());
}
} }
} }
@@ -779,12 +818,12 @@ mod tests {
use assert_matches::assert_matches; use assert_matches::assert_matches;
use futures::{executor, future, Future}; use futures::{executor, future, Future};
use polkadot_primitives::v1::{ use polkadot_primitives::v1::{
AssignmentKind, BlockData, CandidateCommitments, CollatorId, CoreAssignment, CoreIndex, ScheduledCore, BlockData, CandidateCommitments, CollatorId,
LocalValidationData, GlobalValidationData, GroupIndex, HeadData, LocalValidationData, GlobalValidationData, HeadData,
ValidatorPair, ValidityAttestation, ValidatorPair, ValidityAttestation, GroupRotationInfo,
}; };
use polkadot_subsystem::{ use polkadot_subsystem::{
messages::{RuntimeApiRequest, SchedulerRoster}, messages::RuntimeApiRequest,
ActiveLeavesUpdate, FromOverseer, OverseerSignal, ActiveLeavesUpdate, FromOverseer, OverseerSignal,
}; };
use sp_keyring::Sr25519Keyring; use sp_keyring::Sr25519Keyring;
@@ -801,7 +840,8 @@ mod tests {
validator_public: Vec<ValidatorId>, validator_public: Vec<ValidatorId>,
global_validation_data: GlobalValidationData, global_validation_data: GlobalValidationData,
local_validation_data: LocalValidationData, local_validation_data: LocalValidationData,
roster: SchedulerRoster, validator_groups: (Vec<Vec<ValidatorIndex>>, GroupRotationInfo),
availability_cores: Vec<CoreState>,
head_data: HashMap<ParaId, HeadData>, head_data: HashMap<ParaId, HeadData>,
signing_context: SigningContext, signing_context: SigningContext,
relay_parent: Hash, relay_parent: Hash,
@@ -830,53 +870,39 @@ mod tests {
let validator_public = validator_pubkeys(&validators); let validator_public = validator_pubkeys(&validators);
let chain_a_assignment = CoreAssignment { let validator_groups = vec![vec![2, 0, 3], vec![1], vec![4]];
core: CoreIndex::from(0), let group_rotation_info = GroupRotationInfo {
para_id: chain_a, session_start_block: 0,
kind: AssignmentKind::Parachain, group_rotation_frequency: 100,
group_idx: GroupIndex::from(0), now: 1,
};
let chain_b_assignment = CoreAssignment {
core: CoreIndex::from(1),
para_id: chain_b,
kind: AssignmentKind::Parachain,
group_idx: GroupIndex::from(1),
}; };
let thread_collator: CollatorId = Sr25519Keyring::Two.public().into(); let thread_collator: CollatorId = Sr25519Keyring::Two.public().into();
let availability_cores = vec![
let thread_a_assignment = CoreAssignment { CoreState::Scheduled(ScheduledCore {
core: CoreIndex::from(2), para_id: chain_a,
para_id: thread_a, collator: None,
kind: AssignmentKind::Parathread(thread_collator.clone(), 0), }),
group_idx: GroupIndex::from(2), CoreState::Scheduled(ScheduledCore {
}; para_id: chain_b,
collator: None,
let validator_groups = vec![vec![2, 0, 3], vec![1], vec![4]]; }),
CoreState::Scheduled(ScheduledCore {
let parent_hash_1 = [1; 32].into(); para_id: thread_a,
collator: Some(thread_collator.clone()),
let roster = SchedulerRoster { }),
validator_groups, ];
scheduled: vec![
chain_a_assignment,
chain_b_assignment,
thread_a_assignment,
],
upcoming: vec![],
availability_cores: vec![],
};
let signing_context = SigningContext {
session_index: 1,
parent_hash: parent_hash_1,
};
let mut head_data = HashMap::new(); let mut head_data = HashMap::new();
head_data.insert(chain_a, HeadData(vec![4, 5, 6])); head_data.insert(chain_a, HeadData(vec![4, 5, 6]));
let relay_parent = Hash::from([5; 32]); let relay_parent = Hash::from([5; 32]);
let signing_context = SigningContext {
session_index: 1,
parent_hash: relay_parent,
};
let local_validation_data = LocalValidationData { let local_validation_data = LocalValidationData {
parent_head: HeadData(vec![7, 8, 9]), parent_head: HeadData(vec![7, 8, 9]),
balance: Default::default(), balance: Default::default(),
@@ -895,7 +921,8 @@ mod tests {
keystore, keystore,
validators, validators,
validator_public, validator_public,
roster, validator_groups: (validator_groups, group_rotation_info),
availability_cores,
head_data, head_data,
local_validation_data, local_validation_data,
global_validation_data, global_validation_data,
@@ -984,7 +1011,7 @@ mod tests {
AllMessages::RuntimeApi( AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx)) RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx))
) if parent == test_state.relay_parent => { ) if parent == test_state.relay_parent => {
tx.send(test_state.validator_public.clone()).unwrap(); tx.send(Ok(test_state.validator_public.clone())).unwrap();
} }
); );
@@ -994,19 +1021,29 @@ mod tests {
AllMessages::RuntimeApi( AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx)) RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx))
) if parent == test_state.relay_parent => { ) if parent == test_state.relay_parent => {
tx.send(test_state.roster.clone()).unwrap(); tx.send(Ok(test_state.validator_groups.clone())).unwrap();
} }
); );
// Check that subsystem job issues a request for the signing context. // Check that subsystem job issues a request for the session index for child.
assert_matches!( assert_matches!(
virtual_overseer.recv().await, virtual_overseer.recv().await,
AllMessages::RuntimeApi( AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SigningContext(tx)) RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))
) if parent == test_state.relay_parent => { ) if parent == test_state.relay_parent => {
tx.send(test_state.signing_context.clone()).unwrap(); tx.send(Ok(test_state.signing_context.session_index)).unwrap();
} }
); );
// Check that subsystem job issues a request for the availability cores.
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx))
) if parent == test_state.relay_parent => {
tx.send(Ok(test_state.availability_cores.clone())).unwrap();
}
);
} }
// Test that a `CandidateBackingMessage::Second` issues validation work // Test that a `CandidateBackingMessage::Second` issues validation work
+36 -20
View File
@@ -30,7 +30,7 @@ use polkadot_node_subsystem::{
}, },
util::{self, JobManager, JobTrait, ToJobTrait, Validator}, util::{self, JobManager, JobTrait, ToJobTrait, Validator},
}; };
use polkadot_primitives::v1::{AvailabilityBitfield, CoreOccupied, Hash}; use polkadot_primitives::v1::{AvailabilityBitfield, CoreState, Hash};
use std::{convert::TryFrom, pin::Pin, time::Duration}; use std::{convert::TryFrom, pin::Pin, time::Duration};
use wasm_timer::{Delay, Instant}; use wasm_timer::{Delay, Instant};
@@ -130,8 +130,7 @@ pub enum Error {
// this function exists mainly to collect a bunch of potential error points into one. // this function exists mainly to collect a bunch of potential error points into one.
async fn get_core_availability( async fn get_core_availability(
relay_parent: Hash, relay_parent: Hash,
idx: usize, core: CoreState,
core: Option<CoreOccupied>,
sender: &mpsc::Sender<FromJob>, sender: &mpsc::Sender<FromJob>,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
use messages::{ use messages::{
@@ -144,18 +143,23 @@ async fn get_core_availability(
// we have to (cheaply) clone this sender so we can mutate it to actually send anything // we have to (cheaply) clone this sender so we can mutate it to actually send anything
let mut sender = sender.clone(); let mut sender = sender.clone();
// REVIEW: is it safe to ignore parathreads here, or do they also figure in the availability mapping? if let CoreState::Occupied(core) = core {
if let Some(CoreOccupied::Parachain) = core {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
sender sender
.send(RuntimeApi(Request( .send(RuntimeApi(Request(
relay_parent, relay_parent,
CandidatePendingAvailability(idx.into(), tx), CandidatePendingAvailability(core.para_id, tx),
))) )))
.await?; .await?;
let committed_candidate_receipt = match rx.await? { let committed_candidate_receipt = match rx.await? {
Some(ccr) => ccr, Ok(Some(ccr)) => ccr,
None => return Ok(false), Ok(None) => return Ok(false),
Err(e) => {
// Don't take down the node on runtime API errors.
log::warn!(target: "bitfield_signing", "Encountered a runtime API error: {:?}", e);
return Ok(false);
}
}; };
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
sender sender
@@ -171,35 +175,41 @@ async fn get_core_availability(
// the way this function works is not intuitive: // the way this function works is not intuitive:
// //
// - get the scheduler roster so we have a list of cores, in order. // - get the availability cores so we have a list of cores, in order.
// - for each occupied core, fetch `candidate_pending_availability` from runtime // - for each occupied core, fetch `candidate_pending_availability` from runtime
// - from there, we can get the `CandidateDescriptor` // - from there, we can get the `CandidateDescriptor`
// - from there, we can send a `AvailabilityStore::QueryPoV` and set the indexed bit to 1 if it returns Some(_) // - from there, we can send a `AvailabilityStore::QueryPoV` and set the indexed bit to 1 if it returns Some(_)
async fn construct_availability_bitfield( async fn construct_availability_bitfield(
relay_parent: Hash, relay_parent: Hash,
sender: &mut mpsc::Sender<FromJob>, sender: &mut mpsc::Sender<FromJob>,
) -> Result<AvailabilityBitfield, Error> { ) -> Result<Option<AvailabilityBitfield>, Error> {
use futures::lock::Mutex; use futures::lock::Mutex;
use messages::RuntimeApiRequest::ValidatorGroups; use messages::RuntimeApiRequest::AvailabilityCores;
use FromJob::RuntimeApi; use FromJob::RuntimeApi;
use RuntimeApiMessage::Request; use RuntimeApiMessage::Request;
// request the validator groups so we can get the scheduler roster // request the availability cores metadata from runtime.
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
sender sender
.send(RuntimeApi(Request(relay_parent, ValidatorGroups(tx)))) .send(RuntimeApi(Request(relay_parent, AvailabilityCores(tx))))
.await?; .await?;
// we now need sender to be immutable so we can copy the reference to multiple concurrent closures // we now need sender to be immutable so we can copy the reference to multiple concurrent closures
let sender = &*sender; let sender = &*sender;
// wait for the scheduler roster // wait for the cores
let scheduler_roster = rx.await?; let availability_cores = match rx.await? {
Ok(a) => a,
Err(e) => {
log::warn!(target: "bitfield_signing", "Encountered a runtime API error: {:?}", e);
return Ok(None);
}
};
// prepare outputs // prepare outputs
let out = let out =
Mutex::new(bitvec!(bitvec::order::Lsb0, u8; 0; scheduler_roster.availability_cores.len())); Mutex::new(bitvec!(bitvec::order::Lsb0, u8; 0; availability_cores.len()));
// in principle, we know that we never want concurrent access to the _same_ bit within the vec; // in principle, we know that we never want concurrent access to the _same_ bit within the vec;
// we could `let out_ref = out.as_mut_ptr();` here instead, and manually assign bits, avoiding // we could `let out_ref = out.as_mut_ptr();` here instead, and manually assign bits, avoiding
// any need to ever wait to lock this mutex. // any need to ever wait to lock this mutex.
@@ -213,9 +223,9 @@ async fn construct_availability_bitfield(
// //
// In principle, this work is all concurrent, not parallel. In practice, we can't guarantee it, which is why // In principle, this work is all concurrent, not parallel. In practice, we can't guarantee it, which is why
// we need the mutexes and explicit references above. // we need the mutexes and explicit references above.
stream::iter(scheduler_roster.availability_cores.into_iter().enumerate()) stream::iter(availability_cores.into_iter().enumerate())
.for_each_concurrent(None, |(idx, core)| async move { .for_each_concurrent(None, |(idx, core)| async move {
let availability = match get_core_availability(relay_parent, idx, core, sender).await { let availability = match get_core_availability(relay_parent, core, sender).await {
Ok(availability) => availability, Ok(availability) => availability,
Err(err) => { Err(err) => {
errs_ref.lock().await.push(err); errs_ref.lock().await.push(err);
@@ -228,7 +238,7 @@ async fn construct_availability_bitfield(
let errs = errs.into_inner(); let errs = errs.into_inner();
if errs.is_empty() { if errs.is_empty() {
Ok(out.into_inner().into()) Ok(Some(out.into_inner().into()))
} else { } else {
Err(errs.into()) Err(errs.into())
} }
@@ -264,7 +274,13 @@ impl JobTrait for BitfieldSigningJob {
// wait a bit before doing anything else // wait a bit before doing anything else
Delay::new_at(wait_until).await?; Delay::new_at(wait_until).await?;
let bitfield = construct_availability_bitfield(relay_parent, &mut sender).await?; let bitfield =
match construct_availability_bitfield(relay_parent, &mut sender).await?
{
None => return Ok(()),
Some(b) => b,
};
let signed_bitfield = validator.sign(bitfield); let signed_bitfield = validator.sign(bitfield);
// make an anonymous scope to contain some use statements to simplify creating the outbound message // make an anonymous scope to contain some use statements to simplify creating the outbound message
@@ -161,17 +161,23 @@ impl BitfieldDistribution {
for relay_parent in activated { for relay_parent in activated {
trace!(target: "bitd", "Start {:?}", relay_parent); trace!(target: "bitd", "Start {:?}", relay_parent);
// query basic system parameters once // query basic system parameters once
let (validator_set, signing_context) = if let Some((validator_set, signing_context)) =
query_basics(&mut ctx, relay_parent).await?; query_basics(&mut ctx, relay_parent).await?
{
let _ = state.per_relay_parent.insert( // If our runtime API fails, we don't take down the node,
relay_parent, // but we might alter peers' reputations erroneously as a result
PerRelayParentData { // of not having the correct bookkeeping. If we have lost a race
signing_context, // with state pruning, it is unlikely that peers will be sending
validator_set, // us anything to do with this relay-parent anyway.
..Default::default() let _ = state.per_relay_parent.insert(
}, relay_parent,
); PerRelayParentData {
signing_context,
validator_set,
..Default::default()
},
);
}
} }
for relay_parent in deactivated { for relay_parent in deactivated {
@@ -562,12 +568,12 @@ where
async fn query_basics<Context>( async fn query_basics<Context>(
ctx: &mut Context, ctx: &mut Context,
relay_parent: Hash, relay_parent: Hash,
) -> SubsystemResult<(Vec<ValidatorId>, SigningContext)> ) -> SubsystemResult<Option<(Vec<ValidatorId>, SigningContext)>>
where where
Context: SubsystemContext<Message = BitfieldDistributionMessage>, Context: SubsystemContext<Message = BitfieldDistributionMessage>,
{ {
let (validators_tx, validators_rx) = oneshot::channel(); let (validators_tx, validators_rx) = oneshot::channel();
let (signing_tx, signing_rx) = oneshot::channel(); let (session_tx, session_rx) = oneshot::channel();
let query_validators = AllMessages::RuntimeApi(RuntimeApiMessage::Request( let query_validators = AllMessages::RuntimeApi(RuntimeApiMessage::Request(
relay_parent.clone(), relay_parent.clone(),
@@ -576,13 +582,22 @@ where
let query_signing = AllMessages::RuntimeApi(RuntimeApiMessage::Request( let query_signing = AllMessages::RuntimeApi(RuntimeApiMessage::Request(
relay_parent.clone(), relay_parent.clone(),
RuntimeApiRequest::SigningContext(signing_tx), RuntimeApiRequest::SessionIndexForChild(session_tx),
)); ));
ctx.send_messages(std::iter::once(query_validators).chain(std::iter::once(query_signing))) ctx.send_messages(std::iter::once(query_validators).chain(std::iter::once(query_signing)))
.await?; .await?;
Ok((validators_rx.await?, signing_rx.await?)) match (validators_rx.await?, session_rx.await?) {
(Ok(v), Ok(s)) => Ok(Some((
v,
SigningContext { parent_hash: relay_parent, session_index: s },
))),
(Err(e), _) | (_, Err(e)) => {
warn!(target: "bitd", "Failed to fetch basics from runtime API: {:?}", e);
Ok(None)
}
}
} }
#[cfg(test)] #[cfg(test)]
@@ -115,10 +115,27 @@ async fn handle_signal(
RuntimeApiRequest::Validators(vals_tx), RuntimeApiRequest::Validators(vals_tx),
))).await?; ))).await?;
let n_validators = match vals_rx.await? {
Ok(v) => v.len(),
Err(e) => {
log::warn!(target: "pov_distribution",
"Error fetching validators from runtime API for active leaf: {:?}",
e
);
// Not adding bookkeeping here might make us behave funny, but we
// shouldn't take down the node on spurious runtime API errors.
//
// and this is "behave funny" as in be bad at our job, but not in any
// slashable or security-related way.
continue;
}
};
state.relay_parent_state.insert(relay_parent, BlockBasedState { state.relay_parent_state.insert(relay_parent, BlockBasedState {
known: HashMap::new(), known: HashMap::new(),
fetching: HashMap::new(), fetching: HashMap::new(),
n_validators: vals_rx.await?.len(), n_validators: n_validators,
}); });
} }
@@ -847,17 +847,37 @@ async fn run(
let (session_tx, session_rx) = oneshot::channel(); let (session_tx, session_rx) = oneshot::channel();
let val_message = AllMessages::RuntimeApi( let val_message = AllMessages::RuntimeApi(
RuntimeApiMessage::Request(relay_parent, RuntimeApiRequest::Validators(val_tx)), RuntimeApiMessage::Request(
relay_parent,
RuntimeApiRequest::Validators(val_tx),
),
); );
let session_message = AllMessages::RuntimeApi( let session_message = AllMessages::RuntimeApi(
RuntimeApiMessage::Request(relay_parent, RuntimeApiRequest::SigningContext(session_tx)), RuntimeApiMessage::Request(
relay_parent,
RuntimeApiRequest::SessionIndexForChild(session_tx),
),
); );
ctx.send_messages( ctx.send_messages(
std::iter::once(val_message).chain(std::iter::once(session_message)) std::iter::once(val_message).chain(std::iter::once(session_message))
).await?; ).await?;
(val_rx.await?, session_rx.await?.session_index) match (val_rx.await?, session_rx.await?) {
(Ok(v), Ok(s)) => (v, s),
(Err(e), _) | (_, Err(e)) => {
log::warn!(
target: "statement_distribution",
"Failed to fetch runtime API data for active leaf: {:?}",
e,
);
// Lacking this bookkeeping might make us behave funny, although
// not in any slashable way. But we shouldn't take down the node
// on what are likely spurious runtime API errors.
continue;
}
}
}; };
active_heads.entry(relay_parent) active_heads.entry(relay_parent)
+34 -16
View File
@@ -25,11 +25,13 @@
use futures::channel::{mpsc, oneshot}; use futures::channel::{mpsc, oneshot};
use polkadot_primitives::v1::{ use polkadot_primitives::v1::{
BlockNumber, Hash, CommittedCandidateReceipt, Hash, CommittedCandidateReceipt,
CandidateReceipt, PoV, ErasureChunk, BackedCandidate, Id as ParaId, CandidateReceipt, PoV, ErasureChunk, BackedCandidate, Id as ParaId,
SignedAvailabilityBitfield, SigningContext, ValidatorId, ValidationCode, ValidatorIndex, SignedAvailabilityBitfield, ValidatorId, ValidationCode, ValidatorIndex,
CoreAssignment, CoreOccupied, HeadData, CandidateDescriptor, CoreAssignment, CoreOccupied, CandidateDescriptor,
ValidatorSignature, OmittedValidationData, AvailableData, ValidatorSignature, OmittedValidationData, AvailableData, GroupRotationInfo,
CoreState, LocalValidationData, GlobalValidationData, OccupiedCoreAssumption,
CandidateEvent, SessionIndex,
}; };
use polkadot_node_primitives::{ use polkadot_node_primitives::{
MisbehaviorReport, SignedFullStatement, View, ProtocolId, ValidationResult, MisbehaviorReport, SignedFullStatement, View, ProtocolId, ValidationResult,
@@ -286,23 +288,39 @@ pub struct SchedulerRoster {
pub availability_cores: Vec<Option<CoreOccupied>>, pub availability_cores: Vec<Option<CoreOccupied>>,
} }
/// A description of an error causing the runtime API request to be unservable.
#[derive(Debug, Clone)]
pub struct RuntimeApiError(String);
/// A sender for the result of a runtime API request.
pub type RuntimeApiSender<T> = oneshot::Sender<Result<T, RuntimeApiError>>;
/// A request to the Runtime API subsystem. /// A request to the Runtime API subsystem.
#[derive(Debug)] #[derive(Debug)]
pub enum RuntimeApiRequest { pub enum RuntimeApiRequest {
/// Get the current validator set. /// Get the current validator set.
Validators(oneshot::Sender<Vec<ValidatorId>>), Validators(RuntimeApiSender<Vec<ValidatorId>>),
/// Get the assignments of validators to cores. /// Get the validator groups and group rotation info.
ValidatorGroups(oneshot::Sender<SchedulerRoster>), ValidatorGroups(RuntimeApiSender<(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>),
/// Get a signing context for bitfields and statements. /// Get information on all availability cores.
SigningContext(oneshot::Sender<SigningContext>), AvailabilityCores(RuntimeApiSender<Vec<CoreState>>),
/// Get the validation code for a specific para, assuming execution under given block number, and /// Get the global validation data.
/// an optional block number representing an intermediate parablock executed in the context of GlobalValidationData(RuntimeApiSender<GlobalValidationData>),
/// that block. /// Get the local validation data for a particular para, taking the given
ValidationCode(ParaId, BlockNumber, Option<BlockNumber>, oneshot::Sender<ValidationCode>), /// `OccupiedCoreAssumption`, which will inform on how the validation data should be computed
/// Get head data for a specific para. /// if the para currently occupies a core.
HeadData(ParaId, oneshot::Sender<HeadData>), LocalValidationData(
ParaId,
OccupiedCoreAssumption,
RuntimeApiSender<Option<LocalValidationData>>,
),
/// Get the session index that a child of the block will have.
SessionIndexForChild(RuntimeApiSender<SessionIndex>),
/// Get a the candidate pending availability for a particular parachain by parachain / core index /// Get a the candidate pending availability for a particular parachain by parachain / core index
CandidatePendingAvailability(ParaId, oneshot::Sender<Option<CommittedCandidateReceipt>>), CandidatePendingAvailability(ParaId, RuntimeApiSender<Option<CommittedCandidateReceipt>>),
/// Get all events concerning candidates (backing, inclusion, time-out) in the parent of
/// the block in whose state this request is executed.
CandidateEvents(RuntimeApiSender<Vec<CandidateEvent>>),
} }
/// A message to the Runtime API subsystem. /// A message to the Runtime API subsystem.
+33 -29
View File
@@ -21,7 +21,9 @@
//! this module. //! this module.
use crate::{ use crate::{
messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest, SchedulerRoster}, messages::{
AllMessages, RuntimeApiError, RuntimeApiMessage, RuntimeApiRequest, RuntimeApiSender,
},
FromOverseer, SpawnedSubsystem, Subsystem, SubsystemContext, SubsystemError, SubsystemResult, FromOverseer, SpawnedSubsystem, Subsystem, SubsystemContext, SubsystemError, SubsystemResult,
}; };
use futures::{ use futures::{
@@ -37,8 +39,8 @@ use keystore::KeyStorePtr;
use parity_scale_codec::Encode; use parity_scale_codec::Encode;
use pin_project::{pin_project, pinned_drop}; use pin_project::{pin_project, pinned_drop};
use polkadot_primitives::v1::{ use polkadot_primitives::v1::{
EncodeAs, Hash, HeadData, Id as ParaId, Signed, SigningContext, EncodeAs, Hash, Signed, SigningContext, SessionIndex,
ValidatorId, ValidatorIndex, ValidatorPair, ValidatorId, ValidatorIndex, ValidatorPair, GroupRotationInfo,
}; };
use sp_core::{ use sp_core::{
Pair, Pair,
@@ -70,6 +72,9 @@ pub enum Error {
/// A subsystem error /// A subsystem error
#[from] #[from]
Subsystem(SubsystemError), Subsystem(SubsystemError),
/// An error in the runtime API.
#[from]
RuntimeApi(RuntimeApiError),
/// The type system wants this even though it doesn't make sense /// The type system wants this even though it doesn't make sense
#[from] #[from]
Infallible(std::convert::Infallible), Infallible(std::convert::Infallible),
@@ -83,14 +88,17 @@ pub enum Error {
AlreadyForwarding, AlreadyForwarding,
} }
/// A type alias for Runtime API receivers.
pub type RuntimeApiReceiver<T> = oneshot::Receiver<Result<T, RuntimeApiError>>;
/// Request some data from the `RuntimeApi`. /// Request some data from the `RuntimeApi`.
pub async fn request_from_runtime<RequestBuilder, Response, FromJob>( pub async fn request_from_runtime<RequestBuilder, Response, FromJob>(
parent: Hash, parent: Hash,
sender: &mut mpsc::Sender<FromJob>, sender: &mut mpsc::Sender<FromJob>,
request_builder: RequestBuilder, request_builder: RequestBuilder,
) -> Result<oneshot::Receiver<Response>, Error> ) -> Result<RuntimeApiReceiver<Response>, Error>
where where
RequestBuilder: FnOnce(oneshot::Sender<Response>) -> RuntimeApiRequest, RequestBuilder: FnOnce(RuntimeApiSender<Response>) -> RuntimeApiRequest,
FromJob: TryFrom<AllMessages>, FromJob: TryFrom<AllMessages>,
<FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug, <FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug,
{ {
@@ -111,7 +119,7 @@ where
pub async fn request_validators<FromJob>( pub async fn request_validators<FromJob>(
parent: Hash, parent: Hash,
s: &mut mpsc::Sender<FromJob>, s: &mut mpsc::Sender<FromJob>,
) -> Result<oneshot::Receiver<Vec<ValidatorId>>, Error> ) -> Result<RuntimeApiReceiver<Vec<ValidatorId>>, Error>
where where
FromJob: TryFrom<AllMessages>, FromJob: TryFrom<AllMessages>,
<FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug, <FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug,
@@ -119,11 +127,11 @@ where
request_from_runtime(parent, s, |tx| RuntimeApiRequest::Validators(tx)).await request_from_runtime(parent, s, |tx| RuntimeApiRequest::Validators(tx)).await
} }
/// Request the scheduler roster from `RuntimeApi`. /// Request the validator groups.
pub async fn request_validator_groups<FromJob>( pub async fn request_validator_groups<FromJob>(
parent: Hash, parent: Hash,
s: &mut mpsc::Sender<FromJob>, s: &mut mpsc::Sender<FromJob>,
) -> Result<oneshot::Receiver<SchedulerRoster>, Error> ) -> Result<RuntimeApiReceiver<(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>, Error>
where where
FromJob: TryFrom<AllMessages>, FromJob: TryFrom<AllMessages>,
<FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug, <FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug,
@@ -131,29 +139,18 @@ where
request_from_runtime(parent, s, |tx| RuntimeApiRequest::ValidatorGroups(tx)).await request_from_runtime(parent, s, |tx| RuntimeApiRequest::ValidatorGroups(tx)).await
} }
/// Request a `SigningContext` from the `RuntimeApi`. /// Request the session index of the child block.
pub async fn request_signing_context<FromJob>( pub async fn request_session_index_for_child<FromJob>(
parent: Hash, parent: Hash,
s: &mut mpsc::Sender<FromJob>, s: &mut mpsc::Sender<FromJob>,
) -> Result<oneshot::Receiver<SigningContext>, Error> ) -> Result<RuntimeApiReceiver<SessionIndex>, Error>
where where
FromJob: TryFrom<AllMessages>, FromJob: TryFrom<AllMessages>,
<FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug, <FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug,
{ {
request_from_runtime(parent, s, |tx| RuntimeApiRequest::SigningContext(tx)).await request_from_runtime(parent, s, |tx| {
} RuntimeApiRequest::SessionIndexForChild(tx)
}).await
/// Request `HeadData` for some `ParaId` from `RuntimeApi`.
pub async fn request_head_data<FromJob>(
parent: Hash,
s: &mut mpsc::Sender<FromJob>,
id: ParaId,
) -> Result<oneshot::Receiver<HeadData>, Error>
where
FromJob: TryFrom<AllMessages>,
<FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug,
{
request_from_runtime(parent, s, |tx| RuntimeApiRequest::HeadData(id, tx)).await
} }
/// From the given set of validators, find the first key we can sign with, if any. /// From the given set of validators, find the first key we can sign with, if any.
@@ -185,14 +182,21 @@ impl Validator {
FromJob: TryFrom<AllMessages>, FromJob: TryFrom<AllMessages>,
<FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug, <FromJob as TryFrom<AllMessages>>::Error: std::fmt::Debug,
{ {
// Note: request_validators and request_signing_context do not and cannot run concurrently: they both // Note: request_validators and request_session_index_for_child do not and cannot
// have a mutable handle to the same sender. // run concurrently: they both have a mutable handle to the same sender.
// However, each of them returns a oneshot::Receiver, and those are resolved concurrently. // However, each of them returns a oneshot::Receiver, and those are resolved concurrently.
let (validators, signing_context) = futures::try_join!( let (validators, session_index) = futures::try_join!(
request_validators(parent, &mut sender).await?, request_validators(parent, &mut sender).await?,
request_signing_context(parent, &mut sender).await?, request_session_index_for_child(parent, &mut sender).await?,
)?; )?;
let signing_context = SigningContext {
session_index: session_index?,
parent_hash: parent,
};
let validators = validators?;
Self::construct(&validators, signing_context, keystore) Self::construct(&validators, signing_context, keystore)
} }