Implement Runtime APIs (#1411)

* create a README on Runtime APIs

* add ParaId type

* write up runtime APIs

* more preamble

* rename

* rejig runtime APIs

* add occupied_since to `BlockNumber`

* skeleton crate for runtime API subsystem

* improve group_for_core

* improve docs on availability cores runtime API

* guide: freed -> free

* add primitives for runtime APIs

* create a v1 ParachainHost API trait

* guide: make validation code return `Option`al.

* skeleton runtime API helpers

* make parachain-host runtime-generic

* skeleton for most runtime API implementation functions

* guide: add runtime API helper methods

* implement new helpers of the inclusion module

* guide: remove retries check, as it is unneeded

* implement helpers for scheduler module for Runtime APIs

* clean up `validator_groups` implementation

* implement next_rotation_at and last_rotation_at

* guide: more helpers on GroupRotationInfo

* almost finish implementing runtime APIs

* add explicit block parameter to runtime API fns

* guide: generalize number parameter

* guide: add group_responsible to occupied-core

* update primitives due to guide changes

* finishing touches on runtime API implementation; squash warnings

* break out runtime API impl to separate file

* add tests for next_up logic

* test group rotation info

* point to filed TODO

* remove unused TODO [now]

* indentation

* guide: para -> para_id

* rename para field to para_id for core meta

* remove reference to outdated AvailabilityCores type

* add an event in `inclusion` for candidates being included or timing out

* guide: candidate events

* guide: adjust language

* Candidate events type from guide and adjust inclusion event

* implement `candidate_events` runtime API

* fix runtime test compilation

* max -> min

* fix typos

* guide: add `RuntimeAPIRequest::CandidateEvents`
This commit is contained in:
Robert Habermeier
2020-07-18 16:01:51 -04:00
committed by GitHub
parent 5624bd8bf4
commit dddde219a2
18 changed files with 1151 additions and 33 deletions
@@ -0,0 +1,23 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Runtime API implementations for Parachains.
//!
//! These are exposed as different modules using different sets of primitives.
//! At the moment there is only a v1 module and it is not completely clear how migration
//! to a v2 would be done.
pub mod v1;
@@ -0,0 +1,287 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//! A module exporting runtime API implementation functions for all runtime APIs using v1
//! primitives.
//!
//! Runtimes implementing the v1 runtime API are recommended to forward directly to these
//! functions.
use primitives::v1::{
ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationSchedule,
Id as ParaId, OccupiedCoreAssumption, LocalValidationData, SessionIndex, ValidationCode,
CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex,
GroupIndex, CandidateEvent,
};
use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating, Zero};
use frame_support::debug;
use crate::{initializer, inclusion, scheduler, configuration, paras};
/// Implementation for the `validators` function of the runtime API.
pub fn validators<T: initializer::Trait>() -> Vec<ValidatorId> {
<inclusion::Module<T>>::validators()
}
/// Implementation for the `validator_groups` function of the runtime API.
pub fn validator_groups<T: initializer::Trait>() -> (
Vec<Vec<ValidatorIndex>>,
GroupRotationInfo<T::BlockNumber>,
) {
let groups = <scheduler::Module<T>>::validator_groups();
let rotation_info = <scheduler::Module<T>>::group_rotation_info();
(groups, rotation_info)
}
/// Implementation for the `availability_cores` function of the runtime API.
pub fn availability_cores<T: initializer::Trait>() -> Vec<CoreState<T::BlockNumber>> {
let cores = <scheduler::Module<T>>::availability_cores();
let parachains = <paras::Module<T>>::parachains();
let config = <configuration::Module<T>>::config();
let rotation_info = <scheduler::Module<T>>::group_rotation_info();
let time_out_at = |backed_in_number, availability_period| {
let time_out_at = backed_in_number + availability_period;
if rotation_info.group_rotation_frequency == Zero::zero() {
return time_out_at;
}
let current_window = rotation_info.last_rotation_at() + availability_period;
let next_rotation = rotation_info.next_rotation_at();
// If we are within `period` blocks of rotation, timeouts are being checked
// actively. We could even time out this block.
if time_out_at < current_window {
time_out_at
} else if time_out_at <= next_rotation {
// Otherwise, it will time out at the sooner of the next rotation
next_rotation
} else {
// or the scheduled time-out. This is by definition within `period` blocks
// of `next_rotation` and is thus a valid timeout block.
time_out_at
}
};
let group_responsible_for = |backed_in_number, core_index| {
match <scheduler::Module<T>>::group_assigned_to_core(core_index, backed_in_number) {
Some(g) => g,
None => {
debug::warn!("Could not determine the group responsible for core extracted \
from list of cores for some prior block in same session");
GroupIndex(0)
}
}
};
let mut core_states: Vec<_> = cores.into_iter().enumerate().map(|(i, core)| match core {
Some(occupied) => {
CoreState::Occupied(match occupied {
CoreOccupied::Parachain => {
let para_id = parachains[i];
let pending_availability = <inclusion::Module<T>>
::pending_availability(para_id)
.expect("Occupied core always has pending availability; qed");
let backed_in_number = pending_availability.backed_in_number().clone();
OccupiedCore {
para_id,
next_up_on_available: <scheduler::Module<T>>::next_up_on_available(
CoreIndex(i as u32)
),
occupied_since: backed_in_number,
time_out_at: time_out_at(
backed_in_number,
config.chain_availability_period,
),
next_up_on_time_out: <scheduler::Module<T>>::next_up_on_time_out(
CoreIndex(i as u32)
),
availability: pending_availability.availability_votes().clone(),
group_responsible: group_responsible_for(
backed_in_number,
pending_availability.core_occupied(),
),
}
}
CoreOccupied::Parathread(p) => {
let para_id = p.claim.0;
let pending_availability = <inclusion::Module<T>>
::pending_availability(para_id)
.expect("Occupied core always has pending availability; qed");
let backed_in_number = pending_availability.backed_in_number().clone();
OccupiedCore {
para_id,
next_up_on_available: <scheduler::Module<T>>::next_up_on_available(
CoreIndex(i as u32)
),
occupied_since: backed_in_number,
time_out_at: time_out_at(
backed_in_number,
config.thread_availability_period,
),
next_up_on_time_out: <scheduler::Module<T>>::next_up_on_time_out(
CoreIndex(i as u32)
),
availability: pending_availability.availability_votes().clone(),
group_responsible: group_responsible_for(
backed_in_number,
pending_availability.core_occupied(),
),
}
}
})
}
None => CoreState::Free,
}).collect();
// This will overwrite only `Free` cores if the scheduler module is working as intended.
for scheduled in <scheduler::Module<T>>::scheduled() {
core_states[scheduled.core.0 as usize] = CoreState::Scheduled(ScheduledCore {
para_id: scheduled.para_id,
collator: scheduled.required_collator().map(|c| c.clone()),
});
}
core_states
}
/// Implementation for the `global_validation_schedule` function of the runtime API.
pub fn global_validation_schedule<T: initializer::Trait>()
-> GlobalValidationSchedule<T::BlockNumber>
{
let config = <configuration::Module<T>>::config();
GlobalValidationSchedule {
max_code_size: config.max_code_size,
max_head_data_size: config.max_head_data_size,
block_number: <system::Module<T>>::block_number() - One::one(),
}
}
/// Implementation for the `local_validation_data` function of the runtime API.
pub fn local_validation_data<T: initializer::Trait>(
para_id: ParaId,
assumption: OccupiedCoreAssumption,
) -> Option<LocalValidationData<T::BlockNumber>> {
let construct = || {
let relay_parent_number = <system::Module<T>>::block_number() - One::one();
let config = <configuration::Module<T>>::config();
let freq = config.validation_upgrade_frequency;
let delay = config.validation_upgrade_delay;
let last_code_upgrade = <paras::Module<T>>::last_code_upgrade(para_id, true)?;
let can_upgrade_code = last_code_upgrade <= relay_parent_number
&& relay_parent_number.saturating_sub(last_code_upgrade) >= freq;
let code_upgrade_allowed = if can_upgrade_code {
Some(relay_parent_number + delay)
} else {
None
};
Some(LocalValidationData {
parent_head: <paras::Module<T>>::para_head(&para_id)?,
balance: 0,
validation_code_hash: BlakeTwo256::hash_of(
&<paras::Module<T>>::current_code(&para_id)?
),
code_upgrade_allowed,
})
};
match assumption {
OccupiedCoreAssumption::Included => {
<inclusion::Module<T>>::force_enact(para_id);
construct()
}
OccupiedCoreAssumption::TimedOut => {
construct()
}
OccupiedCoreAssumption::Free => {
if <inclusion::Module<T>>::pending_availability(para_id).is_some() {
None
} else {
construct()
}
}
}
}
/// Implementation for the `session_index_for_child` function of the runtime API.
pub fn session_index_for_child<T: initializer::Trait>() -> SessionIndex {
// Just returns the session index from `inclusion`. Runtime APIs follow
// initialization so the initializer will have applied any pending session change
// which is expected at the child of the block whose context the runtime API was invoked
// in.
//
// Incidentally, this is also the rationale for why it is OK to query validators or
// occupied cores or etc. and expect the correct response "for child".
<inclusion::Module<T>>::session_index()
}
/// Implementation for the `validation_code` function of the runtime API.
pub fn validation_code<T: initializer::Trait>(
para_id: ParaId,
assumption: OccupiedCoreAssumption,
) -> Option<ValidationCode> {
let fetch = || {
<paras::Module<T>>::current_code(&para_id)
};
match assumption {
OccupiedCoreAssumption::Included => {
<inclusion::Module<T>>::force_enact(para_id);
fetch()
}
OccupiedCoreAssumption::TimedOut => {
fetch()
}
OccupiedCoreAssumption::Free => {
if <inclusion::Module<T>>::pending_availability(para_id).is_some() {
None
} else {
fetch()
}
}
}
}
/// Implementation for the `candidate_pending_availability` function of the runtime API.
pub fn candidate_pending_availability<T: initializer::Trait>(para_id: ParaId)
-> Option<CommittedCandidateReceipt<T::Hash>>
{
<inclusion::Module<T>>::candidate_pending_availability(para_id)
}
/// Implementation for the `candidate_events` function of the runtime API.
// NOTE: this runs without block initialization, as it accesses events.
// this means it can run in a different session than other runtime APIs at the same block.
pub fn candidate_events<T: initializer::Trait>(
extract_event: impl Fn(<T as system::Trait>::Event) -> Option<inclusion::Event<T>>,
) -> Vec<CandidateEvent<T::Hash>> {
use inclusion::Event as RawEvent;
<system::Module<T>>::events().into_iter()
.filter_map(|record| extract_event(record.event))
.map(|event| match event {
RawEvent::<T>::CandidateBacked(c, h) => CandidateEvent::CandidateBacked(c, h),
RawEvent::<T>::CandidateIncluded(c, h) => CandidateEvent::CandidateIncluded(c, h),
RawEvent::<T>::CandidateTimedOut(c, h) => CandidateEvent::CandidateTimedOut(c, h),
})
.collect()
}