-
Notifications
You must be signed in to change notification settings - Fork 3
Runtime API & Outer Node Service
Below are mixes of core libraries and primitive libraries. To understand the library structure of Substrate. Read this documentation
Crate name | Description | Type |
---|---|---|
sc-consensus-babe | Subtrate core utilities for BABE | Core |
sc-consensus-epochs | Generic utilities for epoch-based consensus engines. | Core |
sc-consensus-babe-rpc | RPC api for babe. | Core |
sc-consensus-grandpa | Integration of the GRANDPA finality gadget into substrate. | Core |
sp-consensus-grandpa | Primitives for GRANDPA integration, suitable for WASM compilation. | Primitive |
sc_rpc_spec_v2 | Substrate JSON-RPC interface v2. | Core |
sp_consensus | Common utilities for building and using consensus engines in substrate. | Primitive |
sc_rpc_api | A collection of RPC methods and subscriptions supported by all substrate clients. | Core |
sc_consensus_slots | Some consensus algorithms have a concept of slots, which are intervals in time during which certain events can and/or must occur. This crate provides generic functionality for slots. | Core |
sp_transaction_storage_proof | Storage proof primitives. Contains types and basic code to extract storage proofs for indexed transactions. | Primitive |
What is Runtime API in Substrate?
Substrate node consist of outer node services and a runtime and this separation of responsibilities is an important concept for designing Susbtrate-based chains and building upgradable logic. However, the outer node services and the runtime must communicate with each other to complete many critical operations, including reading and writing data and performing state transitions. The outer node services communicate with the runtime by calling runtime application programming interfaces to perform specific tasks.
Add BABE to the SessionKeys
Substrate provides the Session pallet, which allows validators to manage their session keys.
Session keys are "hot keys" that are used by validators to sign consensus-related messages.
- They are not meant to be used as account keys that control funds and should only be used for their intended purpose.
- They can be changed regularly; your Controller only needs to create new session keys by signing a session's public key and broadcasting this certificate via an extrinsic.
- Session keys are also defined generically and made concrete in the runtime.
impl_opaque_keys! {
pub struct SessionKeys {
- pub aura: Aura,
+ pub babe: Babe,
+ pub grandpa: Grandpa,
}
}
You can declare any number of Session keys. The above code declares the BABE session key used in pallet_session
because we need the validator to sign BABE consensus-related messages.
We need to add additional sp_consensus_babe
package to our runtime. This package contains the primitives types and utilities for the BABE consensus.
sp-consensus-babe = { workspace = true }
[features]
std=[
"sp-consensus-babe/std",
]
First, we implement the trait BabeApi
for the Runtime
struct with unimplemented function body. We will finish the implementation of these functions later with explanation.
impl sp_consensus_babe::BabeApi<Block> for Runtime {
fn configuration() -> sp_consensus_babe::BabeConfiguration {
unimplemented!();
}
fn current_epoch_start() -> sp_consensus_babe::Slot {
Babe::current_epoch_start()
}
fn current_epoch() -> sp_consensus_babe::Epoch {
Babe::current_epoch()
}
fn next_epoch() -> sp_consensus_babe::Epoch {
Babe::next_epoch()
}
fn generate_key_ownership_proof(
_slot: sp_consensus_babe::Slot,
authority_id: sp_consensus_babe::AuthorityId,
) -> Option<sp_consensus_babe::OpaqueKeyOwnershipProof> {
unimplemented!();
}
fn submit_report_equivocation_unsigned_extrinsic(
equivocation_proof: sp_consensus_babe::EquivocationProof<<Block as BlockT>::Header>,
key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof,
) -> Option<()> {
None
}
}
-
sp_consensus_babe::OpaqueKeyOwnershipProof
: An opaque type used to represent the key ownership proof at the runtime API boundary. The inner value is an encoded representation of the actual key ownership proof which will be parameterized when defining the runtime. At the runtime API boundary this type is unknown and as such we keep this opaque representation, implementors of the runtime API will have to make sure that all usages of OpaqueKeyOwnershipProof refer to the same type.
impl sp_consensus_grandpa::GrandpaApi<Block> for Runtime {
fn grandpa_authorities() -> sp_consensus_grandpa::AuthorityList {
Grandpa::grandpa_authorities()
}
fn current_set_id() -> sp_consensus_grandpa::SetId {
Grandpa::current_set_id()
}
fn submit_report_equivocation_unsigned_extrinsic(
equivocation_proof: sp_consensus_grandpa::EquivocationProof<
<Block as BlockT>::Hash,
NumberFor<Block>,
>,
key_owner_proof: sp_consensus_grandpa::OpaqueKeyOwnershipProof,
) -> Option<()> {
let key_owner_proof = key_owner_proof.decode()?;
Grandpa::submit_unsigned_equivocation_report(
equivocation_proof,
key_owner_proof,
)
}
fn generate_key_ownership_proof(
_set_id: sp_consensus_grandpa::SetId,
authority_id: GrandpaId,
) -> Option<sp_consensus_grandpa::OpaqueKeyOwnershipProof> {
use codec::Encode;
Historical::prove((sp_consensus_grandpa::KEY_TYPE, authority_id))
.map(|p| p.encode())
.map(sp_consensus_grandpa::OpaqueKeyOwnershipProof::new)
}
}
For now, we don't remove code related to AURA yet, we will work on it after BABE is successfully implemented in our Runtime and Node. It would be clearer if you visit the code directly to view the changes but here is some of the changes made to the node/src/chain_spec.rs
pub fn template_session_keys(
- aura: AuraId,
+ babe: BabeId,
+ grandpa: GrandpaId,
) -> parachain_template_runtime::SessionKeys {
- parachain_template_runtime::SessionKeys { aura: keys }
+ parachain_template_runtime::SessionKeys { babe, grandpa }
}
fn testnet_genesis(
- invulnerables: Vec<(AccountId, AuraId)>,
+ invulnerables: Vec<(AccountId, BabeId, GrandpaId)>,
endowed_accounts: Vec<AccountId>,
root: AccountId,
id: ParaId,
) -> serde_json::Value {...}
Learn more about Setting up the Collator.
/// Starts a `ServiceBuilder` for a full service.
///
/// Use this macro if you don't actually need the full service, but just the builder in order to
/// be able to perform chain operations.
pub fn new_partial(
config: &Configuration,
) -> Result<
Service<
impl Fn(
sc_rpc::DenyUnsafe,
sc_rpc::SubscriptionTaskExecutor,
) -> Result<jsonrpsee::RpcModule<()>, sc_service::Error>,
>,
sc_service::Error,
> {
let telemetry = config
.telemetry_endpoints
.clone()
.filter(|x| !x.is_empty())
.map(|endpoints| -> Result<_, sc_telemetry::Error> {
let worker = TelemetryWorker::new(16)?;
let telemetry = worker.handle().new_telemetry(endpoints);
Ok((worker, telemetry))
})
.transpose()?;
let heap_pages = config
.default_heap_pages
.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static {
extra_pages: h as _,
});
let wasm = WasmExecutor::builder()
.with_execution_method(config.wasm_method)
.with_onchain_heap_alloc_strategy(heap_pages)
.with_offchain_heap_alloc_strategy(heap_pages)
.with_max_runtime_instances(config.max_runtime_instances)
.with_runtime_cache_size(config.runtime_cache_size)
.build();
let executor = ParachainExecutor::new_with_wasm_executor(wasm);
let (client, backend, keystore_container, task_manager) =
sc_service::new_full_parts_record_import::<Block, RuntimeApi, _>(
config,
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
executor,
true,
)?;
let client = Arc::new(client);
let select_chain = sc_consensus::LongestChain::new(backend.clone());
let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle());
let telemetry = telemetry.map(|(worker, telemetry)| {
task_manager
.spawn_handle()
.spawn("telemetry", None, worker.run());
telemetry
});
let transaction_pool = sc_transaction_pool::BasicPool::new_full(
config.transaction_pool.clone(),
config.role.is_authority().into(),
config.prometheus_registry(),
task_manager.spawn_essential_handle(),
client.clone(),
);
let block_import = ParachainBlockImport::new(client.clone(), backend.clone());
let (grandpa_block_import, grandpa_link) = sc_consensus_grandpa::block_import(
client.clone(),
GRANDPA_JUSTIFICATION_PERIOD,
&(client.clone() as Arc<_>),
select_chain.clone(),
telemetry.as_ref().map(|x| x.handle()),
)?;
let justification_import = grandpa_block_import.clone();
let (babe_block_import, babe_link) = sc_consensus_babe::block_import(
sc_consensus_babe::configuration(&*client)?,
grandpa_block_import,
client.clone(),
)?;
let slot_duration = babe_link.config().slot_duration();
let babe_import_queue_params = sc_consensus_babe::ImportQueueParams {
link: babe_link.clone(),
block_import: babe_block_import.clone(),
justification_import: Some(Box::new(justification_import)),
client: client.clone(),
select_chain: select_chain.clone(),
create_inherent_data_providers: move |_, ()| async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot =
sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_slot_duration(
*timestamp,
slot_duration,
);
Ok((slot, timestamp))
},
spawner: &task_manager.spawn_essential_handle(),
registry: config.prometheus_registry(),
telemetry: telemetry.as_ref().map(|x| x.handle()),
offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(RejectAllTxPool::default()),
};
let (import_queue, babe_worker_handle) =
sc_consensus_babe::import_queue(babe_import_queue_params)?;
let import_setup = (babe_block_import, grandpa_link, babe_link);
let (rpc_extensions_builder, rpc_setup) = {
let (_, grandpa_link, _babe_link) = &import_setup;
let justification_stream = grandpa_link.justification_stream();
let shared_authority_set = grandpa_link.shared_authority_set().clone();
let shared_voter_state = sc_consensus_grandpa::SharedVoterState::empty();
let shared_voter_state2 = shared_voter_state.clone();
let finality_proof_provider = sc_consensus_grandpa::FinalityProofProvider::new_for_service(
backend.clone(),
Some(shared_authority_set.clone()),
);
let client = client.clone();
let pool = transaction_pool.clone();
let select_chain = select_chain.clone();
let keystore = keystore_container.keystore();
let chain_spec = config.chain_spec.cloned_box();
let rpc_extensions_builder = move |deny_unsafe, subscription_executor| {
let deps = FullDeps {
client: client.clone(),
pool: pool.clone(),
select_chain: select_chain.clone(),
chain_spec: chain_spec.cloned_box(),
deny_unsafe,
babe: BabeDeps {
babe_worker_handle: babe_worker_handle.clone(),
keystore: keystore.clone(),
},
grandpa: GrandpaDeps {
shared_voter_state: shared_voter_state.clone(),
shared_authority_set: shared_authority_set.clone(),
justification_stream: justification_stream.clone(),
subscription_executor,
finality_provider: finality_proof_provider.clone(),
},
};
create_full(deps).map_err(Into::into)
};
(rpc_extensions_builder, shared_voter_state2)
};
Ok(PartialComponents {
backend,
client,
import_queue,
keystore_container,
task_manager,
transaction_pool,
select_chain,
other: (
block_import,
rpc_extensions_builder,
import_setup,
rpc_setup,
telemetry,
telemetry_worker_handle,
),
})
}