Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

time-based slots and non-parachain support #58

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
35bf82d
Draft the initial structure inspired by Aura
JoshOrndorff May 6, 2022
8cea89c
pivot to `SlotWorker` trait
JoshOrndorff May 7, 2022
9cc9960
tiny refactor in parachain code: better ordering of lines
JoshOrndorff May 7, 2022
6597f48
checkpoint. sketching out the on_slot function
JoshOrndorff May 7, 2022
77c7199
worker nearly compiles
JoshOrndorff May 7, 2022
18be745
clean up
JoshOrndorff May 7, 2022
9b0bdbd
cargo fmt
JoshOrndorff May 7, 2022
33635d7
make it compile (including the athorfilterapi stuff grr)
JoshOrndorff May 8, 2022
70a9170
Sketch start_nimbus_standalone. Darn close to compiling. Lifetime error.
JoshOrndorff May 8, 2022
1445807
Explicit phantom data type, doesn't solve lifetime issue
JoshOrndorff May 8, 2022
c796642
Comment about client vs select chain
JoshOrndorff May 8, 2022
9649a2e
cargo fmt
JoshOrndorff May 8, 2022
dd55487
OMFG, it was just sloppy copy pasta
JoshOrndorff May 8, 2022
daeec31
Revert "Comment about client vs select chain"
JoshOrndorff May 8, 2022
13c6bbe
Don't need to handle sync oracle ourselves. That happens in start_slo…
JoshOrndorff May 9, 2022
10b9827
update comments
JoshOrndorff May 9, 2022
021539c
prune more old comments
JoshOrndorff May 9, 2022
fdcc587
checkpoint: start wiring it into the service.
JoshOrndorff May 9, 2022
0b33eea
start understanding `sc_consensus_slots::InherentDataProviderExt` - c…
JoshOrndorff May 9, 2022
542175a
woohoo! It actually compiles again!
JoshOrndorff May 10, 2022
386eef4
cargo fmt
JoshOrndorff May 10, 2022
3345786
Sketch `TimeBasedSlots` beacon
JoshOrndorff May 10, 2022
ac6c4e3
Change runtime to 6 second slots
JoshOrndorff May 10, 2022
bc60eca
Fork Choice strategy led to an actual block being authored!!!!
JoshOrndorff May 10, 2022
a448e66
It's division you idiot!
JoshOrndorff May 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions nimbus-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ sp-application-crypto = { git = "https://github.com/paritytech/substrate", branc
sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
# TODO: Do we need both sp- and sc- ?
sp-consensus-slots = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-consensus-slots = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
Expand Down
16 changes: 13 additions & 3 deletions nimbus-consensus/src/import_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,15 @@ where
}
}

/// Start an import queue for a Cumulus collator that does not uses any special authoring logic.
/// Start an import queue for nimbus consensus
///
/// The block import object provided should be the `NimbusBlockImport` or a wrapper of it.
pub fn import_queue<Client, Block: BlockT, I, CIDP>(
client: Arc<Client>,
block_import: I,
create_inherent_data_providers: CIDP,
spawner: &impl sp_core::traits::SpawnEssentialNamed,
registry: Option<&substrate_prometheus_endpoint::Registry>,
parachain: bool,
) -> ClientResult<BasicQueue<Block, I::Transaction>>
where
I: BlockImport<Block, Error = ConsensusError> + Send + Sync + 'static,
Expand All @@ -209,7 +210,7 @@ where

Ok(BasicQueue::new(
verifier,
Box::new(NimbusBlockImport::new(block_import, parachain)),
Box::new(block_import),
None,
spawner,
registry,
Expand All @@ -225,6 +226,7 @@ where
///
/// There may be additional nimbus-specific logic here in the future, but for now it is
/// only the conditional parachain logic
#[derive(Clone)]
pub struct NimbusBlockImport<I> {
inner: I,
parachain_context: bool,
Expand Down Expand Up @@ -256,6 +258,14 @@ where
self.inner.check_block(block).await
}

//TODO Is this even useful at all?
// Why not just use the parachain block import for parachain context and nothing for the
// standalone context? This is the same as Aura.
// In hindsight, creating a nimbus block import was probably a bad idea.
//
// Also, the fork choice strategy is set correctly in both the individual consensus workers
// So we don't even need parachain consensus to override it. That is only necessary for Aura
// Where they share the same SlotWorker implementation for parachain and standalone.
async fn import_block(
&mut self,
mut block_import_params: sc_consensus::BlockImportParams<Block, Self::Transaction>,
Expand Down
18 changes: 10 additions & 8 deletions nimbus-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use cumulus_client_consensus_common::{
ParachainBlockImport, ParachainCandidate, ParachainConsensus,
};
use cumulus_primitives_core::{relay_chain::v2::Hash as PHash, ParaId, PersistedValidationData};
pub use import_queue::import_queue;
pub use import_queue::{import_queue, NimbusBlockImport};
use log::{debug, info, warn};
use nimbus_primitives::{
AuthorFilterAPI, CompatibleDigestItem, NimbusApi, NimbusId, NIMBUS_KEY_ID,
Expand All @@ -47,7 +47,9 @@ use std::{marker::PhantomData, sync::Arc, time::Duration};
use tracing::error;
mod import_queue;
mod manual_seal;
mod standalone;
pub use manual_seal::NimbusManualSealConsensusDataProvider;
pub use standalone::start_nimbus_standalone;

const LOG_TARGET: &str = "filtering-consensus";

Expand Down Expand Up @@ -354,13 +356,6 @@ where
}
};

let proposer_future = self.proposer_factory.lock().init(&parent);

let proposer = proposer_future
.await
.map_err(|e| error!(target: LOG_TARGET, error = ?e, "Could not create proposer."))
.ok()?;

let nimbus_id = NimbusId::from_slice(&type_public_pair.1)
.map_err(
|e| error!(target: LOG_TARGET, error = ?e, "Invalid Nimbus ID (wrong length)."),
Expand All @@ -380,6 +375,13 @@ where
logs: vec![CompatibleDigestItem::nimbus_pre_digest(nimbus_id)],
};

let proposer_future = self.proposer_factory.lock().init(&parent);

let proposer = proposer_future
.await
.map_err(|e| error!(target: LOG_TARGET, error = ?e, "Could not create proposer."))
.ok()?;

let Proposal {
block,
storage_changes,
Expand Down
205 changes: 205 additions & 0 deletions nimbus-consensus/src/standalone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright 2019-2021 PureStake Inc.
// This file is part of Nimbus.

// Nimbus 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.

// Nimbus 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 Nimbus. If not, see <http://www.gnu.org/licenses/>.

//! This module contains the code necessary to use nimbus in a sovereign
//! (non-parachain) blockchain node. It implements the SlotWorker trait.

use crate::{first_eligible_key, seal_header, CompatibleDigestItem, LOG_TARGET};
use futures::Future;
use nimbus_primitives::{AuthorFilterAPI, NimbusApi, NimbusId};
use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy};
use sc_consensus_slots::InherentDataProviderExt;
use sc_consensus_slots::{self, SlotInfo, SlotResult, SlotWorker};
use sp_api::ProvideRuntimeApi;
use sp_api::{BlockT, HeaderT};
use sp_application_crypto::ByteArray;
use sp_consensus::CanAuthorWith;
use sp_consensus::SelectChain;
use sp_consensus::SyncOracle;
use sp_consensus::{BlockOrigin, Environment, Proposal, Proposer};
use sp_consensus_slots::SlotDuration;
use sp_inherents::CreateInherentDataProviders;
use sp_keystore::SyncCryptoStorePtr;
use std::{marker::PhantomData, sync::Arc, time::Duration};
use tracing::error;

/// Start the nimbus standalone worker. The returned future should be run in a futures executor.
pub fn start_nimbus_standalone<B, C, SC, BI, PF, CIDP, SO, CAW, Error>(
client: Arc<C>,
select_chain: SC,
block_import: BI,
proposer_factory: PF,
keystore: SyncCryptoStorePtr,
sync_oracle: SO,
can_author_with: CAW,
create_inherent_data_providers: CIDP,
) -> Result<impl Future<Output = ()>, sp_consensus::Error>
where
B: BlockT,
C: ProvideRuntimeApi<B> + Send + Sync,
C::Api: NimbusApi<B>,
C::Api: AuthorFilterAPI<B, NimbusId>, // Grrrrr. Remove this after https://github.com/PureStake/nimbus/pull/30 lands
SC: SelectChain<B>,
BI: BlockImport<B, Transaction = sp_api::TransactionFor<C, B>> + Send + Sync + 'static,
PF: Environment<B, Error = Error> + Send + Sync + 'static,
PF::Proposer: Proposer<B, Error = Error, Transaction = sp_api::TransactionFor<C, B>>,
CIDP: CreateInherentDataProviders<B, ()> + Send,
CIDP::InherentDataProviders: InherentDataProviderExt + Send,
SO: SyncOracle + Send + Sync + Clone,
CAW: CanAuthorWith<B> + Send,
Error: std::error::Error + Send + From<sp_consensus::Error> + 'static,
{
// TODO This should match what the runtime expects.
// To enforce that we'll need a runtime api. Or maybe we can leave it up to the node operator...
// In aura they get it from the genesis
let slot_duration = SlotDuration::from_millis(6000);

let worker = NimbusStandaloneWorker {
client: client.clone(),
block_import,
proposer_factory,
keystore,
_phantom: PhantomData::<B>,
};

Ok(sc_consensus_slots::start_slot_worker(
slot_duration,
select_chain,
worker,
sync_oracle,
create_inherent_data_providers,
can_author_with,
))
}

pub struct NimbusStandaloneWorker<B, C, PF, BI> {
client: Arc<C>,
block_import: BI,
proposer_factory: PF,
keystore: SyncCryptoStorePtr,
_phantom: PhantomData<B>,
}

#[async_trait::async_trait]
impl<B, C, PF, BI> SlotWorker<B, <<PF as Environment<B>>::Proposer as Proposer<B>>::Proof>
for NimbusStandaloneWorker<B, C, PF, BI>
where
B: BlockT,
BI: BlockImport<B> + Send + Sync + 'static,
C: ProvideRuntimeApi<B> + Send + Sync,
C::Api: NimbusApi<B>,
C::Api: AuthorFilterAPI<B, NimbusId>, // Grrrrr. Remove this after https://github.com/PureStake/nimbus/pull/30 lands
PF: Environment<B> + Send + Sync + 'static,
PF::Proposer: Proposer<B, Transaction = BI::Transaction>,
{
async fn on_slot(
&mut self,
slot_info: SlotInfo<B>,
) -> Option<SlotResult<B, <<PF as Environment<B>>::Proposer as Proposer<B>>::Proof>> {
// Here's the rough seam between nimnus's simple u32 and Substrate's `struct Slot<u64>`
let slot: u32 = {
let slot_u64: u64 = slot_info.slot.into();
slot_u64 as u32
};
let parent = &slot_info.chain_head;

// Call into the runtime to predict eligibility
//TODO maybe offer a skip prediction feature. Not tackling that yet.
let maybe_key =
first_eligible_key::<B, C>(self.client.clone(), &*self.keystore, parent, slot);

// Here I'll prototype using the public instead of the type public pair
// I've had a hunch that this is the correct way to do it for a little while
let nimbus_id = match maybe_key {
Some(p) => NimbusId::from_slice(&p.1)
.map_err(
|e| error!(target: LOG_TARGET, error = ?e, "Invalid Nimbus ID (wrong length)."),
)
.ok()?,
None => {
return None;
}
};

// Make the predigest (sc-consensus-slots tackles the inherent data)
let inherent_digests = sp_runtime::generic::Digest {
logs: vec![CompatibleDigestItem::nimbus_pre_digest(nimbus_id.clone())],
};

// Author the block
let proposer_future = self.proposer_factory.init(&parent);

let proposer = proposer_future
.await
.map_err(|e| error!(target: LOG_TARGET, error = ?e, "Could not create proposer."))
.ok()?;

let Proposal {
block,
storage_changes,
proof,
} = proposer
.propose(
slot_info.inherent_data,
inherent_digests,
//TODO: Fix this.
Duration::from_millis(500),
slot_info.block_size_limit,
)
.await
.map_err(|e| error!(target: LOG_TARGET, error = ?e, "Proposing failed."))
.ok()?;

// Sign the block
let (header, extrinsics) = block.clone().deconstruct();

let sig_digest = seal_header::<B>(&header, &*self.keystore, &nimbus_id.into());

let mut block_import_params = BlockImportParams::new(BlockOrigin::Own, header.clone());
block_import_params.post_digests.push(sig_digest.clone());
block_import_params.body = Some(extrinsics.clone());
block_import_params.state_action = sc_consensus::StateAction::ApplyChanges(
sc_consensus::StorageChanges::Changes(storage_changes),
);
block_import_params.fork_choice = Some(ForkChoiceStrategy::LongestChain);

// Import our own block
if let Err(err) = self
.block_import
.import_block(block_import_params, Default::default())
.await
{
error!(
target: LOG_TARGET,
at = ?parent.hash(),
error = ?err,
"Error importing built block.",
);

return None;
}

// Return the block WITH the seal for distribution around the network.
let mut post_header = header.clone();
post_header.digest_mut().logs.push(sig_digest.clone());
let post_block = B::new(post_header, extrinsics);

Some(SlotResult {
block: post_block,
storage_proof: proof,
})
}
}
15 changes: 11 additions & 4 deletions nimbus-primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

#![cfg_attr(not(feature = "std"), no_std)]

use core::marker::PhantomData;

use frame_support::traits::{Get, UnixTime};
use sp_application_crypto::KeyTypeId;
use sp_runtime::traits::BlockNumberProvider;
use sp_runtime::ConsensusEngineId;
Expand Down Expand Up @@ -67,14 +70,18 @@ impl<T: BlockNumberProvider<BlockNumber = u32>> SlotBeacon for T {
}
}

/// PLANNED: A SlotBeacon that starts a new slot based on the timestamp. Behaviorally, this is
/// A SlotBeacon that starts a new slot based on the timestamp. Behaviorally, this is
/// similar to what aura, babe and company do. Implementation-wise it is different because it
/// depends on the timestamp pallet for its notion of time.
pub struct IntervalBeacon;
pub struct TimeBasedSlots<C, D>(PhantomData<(C, D)>);

impl SlotBeacon for IntervalBeacon {
impl<C: UnixTime, D: Get<core::time::Duration>> SlotBeacon for TimeBasedSlots<C, D> {
fn slot() -> u32 {
todo!()
let now = C::now().as_millis();
let duration = D::get().as_millis();
let slot = now / duration;

slot as u32
}
}

Expand Down
6 changes: 6 additions & 0 deletions parachain-template/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ log = "0.4.14"
serde = { version = "1.0.119", features = [ "derive" ] }
flume = "0.10.9"

# Only used for inherent adapter hack. Remove them once that has a better home.
async-trait = "0.1.42"
sc-consensus-slots = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus-slots = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }

# RPC related Dependencies
jsonrpc-core = "18.0.0"

Expand Down
Loading