Move proof generation to the type system level (#8185)

* Start

* Finish!!!!

* Update client/basic-authorship/src/basic_authorship.rs

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>

* Review comments

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
Bastian Köcher
2021-02-24 21:43:50 +01:00
committed by GitHub
parent 3309c4366b
commit 8a0e8ea9a6
17 changed files with 230 additions and 121 deletions
-1
View File
@@ -6691,7 +6691,6 @@ dependencies = [
"sp-api", "sp-api",
"sp-block-builder", "sp-block-builder",
"sp-blockchain", "sp-blockchain",
"sp-consensus",
"sp-core", "sp-core",
"sp-inherents", "sp-inherents",
"sp-runtime", "sp-runtime",
+1 -2
View File
@@ -48,7 +48,7 @@ use sp_transaction_pool::{
TransactionStatusStreamFor, TransactionStatusStreamFor,
TxHash, TxHash,
}; };
use sp_consensus::{Environment, Proposer, RecordProof}; use sp_consensus::{Environment, Proposer};
use crate::{ use crate::{
common::SizeType, common::SizeType,
@@ -170,7 +170,6 @@ impl core::Benchmark for ConstructionBenchmark {
inherent_data_providers.create_inherent_data().expect("Create inherent data failed"), inherent_data_providers.create_inherent_data().expect("Create inherent data failed"),
Default::default(), Default::default(),
std::time::Duration::from_secs(20), std::time::Duration::from_secs(20),
RecordProof::Yes,
), ),
).map(|r| r.block).expect("Proposing failed"); ).map(|r| r.block).expect("Proposing failed");
-2
View File
@@ -478,7 +478,6 @@ mod tests {
use sc_consensus_epochs::descendent_query; use sc_consensus_epochs::descendent_query;
use sp_consensus::{ use sp_consensus::{
Environment, Proposer, BlockImportParams, BlockOrigin, ForkChoiceStrategy, BlockImport, Environment, Proposer, BlockImportParams, BlockOrigin, ForkChoiceStrategy, BlockImport,
RecordProof,
}; };
use node_primitives::{Block, DigestItem, Signature}; use node_primitives::{Block, DigestItem, Signature};
use node_runtime::{BalancesCall, Call, UncheckedExtrinsic, Address}; use node_runtime::{BalancesCall, Call, UncheckedExtrinsic, Address};
@@ -611,7 +610,6 @@ mod tests {
inherent_data, inherent_data,
digest, digest,
std::time::Duration::from_secs(1), std::time::Duration::from_secs(1),
RecordProof::Yes,
).await ).await
}).expect("Error making test block").block; }).expect("Error making test block").block;
@@ -20,7 +20,6 @@ let future = proposer.propose(
Default::default(), Default::default(),
Default::default(), Default::default(),
Duration::from_secs(2), Duration::from_secs(2),
RecordProof::Yes,
); );
// We wait until the proposition is performed. // We wait until the proposition is performed.
@@ -23,7 +23,7 @@
use std::{pin::Pin, time, sync::Arc}; use std::{pin::Pin, time, sync::Arc};
use sc_client_api::backend; use sc_client_api::backend;
use codec::Decode; use codec::Decode;
use sp_consensus::{evaluation, Proposal, RecordProof}; use sp_consensus::{evaluation, Proposal, ProofRecording, DisableProofRecording, EnableProofRecording};
use sp_core::traits::SpawnNamed; use sp_core::traits::SpawnNamed;
use sp_inherents::InherentData; use sp_inherents::InherentData;
use log::{error, info, debug, trace, warn}; use log::{error, info, debug, trace, warn};
@@ -52,7 +52,7 @@ use sc_proposer_metrics::MetricsLink as PrometheusMetrics;
pub const DEFAULT_MAX_BLOCK_SIZE: usize = 4 * 1024 * 1024 + 512; pub const DEFAULT_MAX_BLOCK_SIZE: usize = 4 * 1024 * 1024 + 512;
/// Proposer factory. /// Proposer factory.
pub struct ProposerFactory<A, B, C> { pub struct ProposerFactory<A, B, C, PR> {
spawn_handle: Box<dyn SpawnNamed>, spawn_handle: Box<dyn SpawnNamed>,
/// The client instance. /// The client instance.
client: Arc<C>, client: Arc<C>,
@@ -60,12 +60,15 @@ pub struct ProposerFactory<A, B, C> {
transaction_pool: Arc<A>, transaction_pool: Arc<A>,
/// Prometheus Link, /// Prometheus Link,
metrics: PrometheusMetrics, metrics: PrometheusMetrics,
/// phantom member to pin the `Backend` type. /// phantom member to pin the `Backend`/`ProofRecording` type.
_phantom: PhantomData<B>, _phantom: PhantomData<(B, PR)>,
max_block_size: usize, max_block_size: usize,
} }
impl<A, B, C> ProposerFactory<A, B, C> { impl<A, B, C> ProposerFactory<A, B, C, DisableProofRecording> {
/// Create a new proposer factory.
///
/// Proof recording will be disabled when using proposers built by this instance to build blocks.
pub fn new( pub fn new(
spawn_handle: impl SpawnNamed + 'static, spawn_handle: impl SpawnNamed + 'static,
client: Arc<C>, client: Arc<C>,
@@ -81,7 +84,30 @@ impl<A, B, C> ProposerFactory<A, B, C> {
max_block_size: DEFAULT_MAX_BLOCK_SIZE, max_block_size: DEFAULT_MAX_BLOCK_SIZE,
} }
} }
}
impl<A, B, C> ProposerFactory<A, B, C, EnableProofRecording> {
/// Create a new proposer factory with proof recording enabled.
///
/// Each proposer created by this instance will record a proof while building a block.
pub fn with_proof_recording(
spawn_handle: impl SpawnNamed + 'static,
client: Arc<C>,
transaction_pool: Arc<A>,
prometheus: Option<&PrometheusRegistry>,
) -> Self {
ProposerFactory {
spawn_handle: Box::new(spawn_handle),
client,
transaction_pool,
metrics: PrometheusMetrics::new(prometheus),
_phantom: PhantomData,
max_block_size: DEFAULT_MAX_BLOCK_SIZE,
}
}
}
impl<A, B, C, PR> ProposerFactory<A, B, C, PR> {
/// Set the maximum block size in bytes. /// Set the maximum block size in bytes.
/// ///
/// The default value for the maximum block size is: /// The default value for the maximum block size is:
@@ -91,7 +117,7 @@ impl<A, B, C> ProposerFactory<A, B, C> {
} }
} }
impl<B, Block, C, A> ProposerFactory<A, B, C> impl<B, Block, C, A, PR> ProposerFactory<A, B, C, PR>
where where
A: TransactionPool<Block = Block> + 'static, A: TransactionPool<Block = Block> + 'static,
B: backend::Backend<Block> + Send + Sync + 'static, B: backend::Backend<Block> + Send + Sync + 'static,
@@ -101,18 +127,18 @@ impl<B, Block, C, A> ProposerFactory<A, B, C>
C::Api: ApiExt<Block, StateBackend = backend::StateBackendFor<B, Block>> C::Api: ApiExt<Block, StateBackend = backend::StateBackendFor<B, Block>>
+ BlockBuilderApi<Block>, + BlockBuilderApi<Block>,
{ {
pub fn init_with_now( fn init_with_now(
&mut self, &mut self,
parent_header: &<Block as BlockT>::Header, parent_header: &<Block as BlockT>::Header,
now: Box<dyn Fn() -> time::Instant + Send + Sync>, now: Box<dyn Fn() -> time::Instant + Send + Sync>,
) -> Proposer<B, Block, C, A> { ) -> Proposer<B, Block, C, A, PR> {
let parent_hash = parent_header.hash(); let parent_hash = parent_header.hash();
let id = BlockId::hash(parent_hash); let id = BlockId::hash(parent_hash);
info!("🙌 Starting consensus session on top of parent {:?}", parent_hash); info!("🙌 Starting consensus session on top of parent {:?}", parent_hash);
let proposer = Proposer { let proposer = Proposer::<_, _, _, _, PR> {
spawn_handle: self.spawn_handle.clone(), spawn_handle: self.spawn_handle.clone(),
client: self.client.clone(), client: self.client.clone(),
parent_hash, parent_hash,
@@ -129,8 +155,8 @@ impl<B, Block, C, A> ProposerFactory<A, B, C>
} }
} }
impl<A, B, Block, C> sp_consensus::Environment<Block> for impl<A, B, Block, C, PR> sp_consensus::Environment<Block> for
ProposerFactory<A, B, C> ProposerFactory<A, B, C, PR>
where where
A: TransactionPool<Block = Block> + 'static, A: TransactionPool<Block = Block> + 'static,
B: backend::Backend<Block> + Send + Sync + 'static, B: backend::Backend<Block> + Send + Sync + 'static,
@@ -139,9 +165,10 @@ impl<A, B, Block, C> sp_consensus::Environment<Block> for
+ Send + Sync + 'static, + Send + Sync + 'static,
C::Api: ApiExt<Block, StateBackend = backend::StateBackendFor<B, Block>> C::Api: ApiExt<Block, StateBackend = backend::StateBackendFor<B, Block>>
+ BlockBuilderApi<Block>, + BlockBuilderApi<Block>,
PR: ProofRecording,
{ {
type CreateProposer = future::Ready<Result<Self::Proposer, Self::Error>>; type CreateProposer = future::Ready<Result<Self::Proposer, Self::Error>>;
type Proposer = Proposer<B, Block, C, A>; type Proposer = Proposer<B, Block, C, A, PR>;
type Error = sp_blockchain::Error; type Error = sp_blockchain::Error;
fn init( fn init(
@@ -153,7 +180,7 @@ impl<A, B, Block, C> sp_consensus::Environment<Block> for
} }
/// The proposer logic. /// The proposer logic.
pub struct Proposer<B, Block: BlockT, C, A: TransactionPool> { pub struct Proposer<B, Block: BlockT, C, A: TransactionPool, PR> {
spawn_handle: Box<dyn SpawnNamed>, spawn_handle: Box<dyn SpawnNamed>,
client: Arc<C>, client: Arc<C>,
parent_hash: <Block as BlockT>::Hash, parent_hash: <Block as BlockT>::Hash,
@@ -162,12 +189,12 @@ pub struct Proposer<B, Block: BlockT, C, A: TransactionPool> {
transaction_pool: Arc<A>, transaction_pool: Arc<A>,
now: Box<dyn Fn() -> time::Instant + Send + Sync>, now: Box<dyn Fn() -> time::Instant + Send + Sync>,
metrics: PrometheusMetrics, metrics: PrometheusMetrics,
_phantom: PhantomData<B>, _phantom: PhantomData<(B, PR)>,
max_block_size: usize, max_block_size: usize,
} }
impl<A, B, Block, C> sp_consensus::Proposer<Block> for impl<A, B, Block, C, PR> sp_consensus::Proposer<Block> for
Proposer<B, Block, C, A> Proposer<B, Block, C, A, PR>
where where
A: TransactionPool<Block = Block> + 'static, A: TransactionPool<Block = Block> + 'static,
B: backend::Backend<Block> + Send + Sync + 'static, B: backend::Backend<Block> + Send + Sync + 'static,
@@ -176,19 +203,21 @@ impl<A, B, Block, C> sp_consensus::Proposer<Block> for
+ Send + Sync + 'static, + Send + Sync + 'static,
C::Api: ApiExt<Block, StateBackend = backend::StateBackendFor<B, Block>> C::Api: ApiExt<Block, StateBackend = backend::StateBackendFor<B, Block>>
+ BlockBuilderApi<Block>, + BlockBuilderApi<Block>,
PR: ProofRecording,
{ {
type Transaction = backend::TransactionFor<B, Block>; type Transaction = backend::TransactionFor<B, Block>;
type Proposal = Pin<Box<dyn Future< type Proposal = Pin<Box<dyn Future<
Output = Result<Proposal<Block, Self::Transaction>, Self::Error> Output = Result<Proposal<Block, Self::Transaction, PR::Proof>, Self::Error>
> + Send>>; > + Send>>;
type Error = sp_blockchain::Error; type Error = sp_blockchain::Error;
type ProofRecording = PR;
type Proof = PR::Proof;
fn propose( fn propose(
self, self,
inherent_data: InherentData, inherent_data: InherentData,
inherent_digests: DigestFor<Block>, inherent_digests: DigestFor<Block>,
max_duration: time::Duration, max_duration: time::Duration,
record_proof: RecordProof,
) -> Self::Proposal { ) -> Self::Proposal {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let spawn_handle = self.spawn_handle.clone(); let spawn_handle = self.spawn_handle.clone();
@@ -200,7 +229,6 @@ impl<A, B, Block, C> sp_consensus::Proposer<Block> for
inherent_data, inherent_data,
inherent_digests, inherent_digests,
deadline, deadline,
record_proof,
).await; ).await;
if tx.send(res).is_err() { if tx.send(res).is_err() {
trace!("Could not send block production result to proposer!"); trace!("Could not send block production result to proposer!");
@@ -213,7 +241,7 @@ impl<A, B, Block, C> sp_consensus::Proposer<Block> for
} }
} }
impl<A, B, Block, C> Proposer<B, Block, C, A> impl<A, B, Block, C, PR> Proposer<B, Block, C, A, PR>
where where
A: TransactionPool<Block = Block>, A: TransactionPool<Block = Block>,
B: backend::Backend<Block> + Send + Sync + 'static, B: backend::Backend<Block> + Send + Sync + 'static,
@@ -222,14 +250,14 @@ impl<A, B, Block, C> Proposer<B, Block, C, A>
+ Send + Sync + 'static, + Send + Sync + 'static,
C::Api: ApiExt<Block, StateBackend = backend::StateBackendFor<B, Block>> C::Api: ApiExt<Block, StateBackend = backend::StateBackendFor<B, Block>>
+ BlockBuilderApi<Block>, + BlockBuilderApi<Block>,
PR: ProofRecording,
{ {
async fn propose_with( async fn propose_with(
self, self,
inherent_data: InherentData, inherent_data: InherentData,
inherent_digests: DigestFor<Block>, inherent_digests: DigestFor<Block>,
deadline: time::Instant, deadline: time::Instant,
record_proof: RecordProof, ) -> Result<Proposal<Block, backend::TransactionFor<B, Block>, PR::Proof>, sp_blockchain::Error> {
) -> Result<Proposal<Block, backend::TransactionFor<B, Block>>, sp_blockchain::Error> {
/// If the block is full we will attempt to push at most /// If the block is full we will attempt to push at most
/// this number of transactions before quitting for real. /// this number of transactions before quitting for real.
/// It allows us to increase block utilization. /// It allows us to increase block utilization.
@@ -238,7 +266,7 @@ impl<A, B, Block, C> Proposer<B, Block, C, A>
let mut block_builder = self.client.new_block_at( let mut block_builder = self.client.new_block_at(
&self.parent_id, &self.parent_id,
inherent_digests, inherent_digests,
record_proof, PR::ENABLED,
)?; )?;
for inherent in block_builder.create_inherents(inherent_data)? { for inherent in block_builder.create_inherents(inherent_data)? {
@@ -361,6 +389,8 @@ impl<A, B, Block, C> Proposer<B, Block, C, A>
error!("Failed to evaluate authored block: {:?}", err); error!("Failed to evaluate authored block: {:?}", err);
} }
let proof = PR::into_proof(proof)
.map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?;
Ok(Proposal { block, proof, storage_changes }) Ok(Proposal { block, proof, storage_changes })
} }
} }
@@ -452,7 +482,7 @@ mod tests {
// when // when
let deadline = time::Duration::from_secs(3); let deadline = time::Duration::from_secs(3);
let block = futures::executor::block_on( let block = futures::executor::block_on(
proposer.propose(Default::default(), Default::default(), deadline, RecordProof::No) proposer.propose(Default::default(), Default::default(), deadline)
).map(|r| r.block).unwrap(); ).map(|r| r.block).unwrap();
// then // then
@@ -497,7 +527,7 @@ mod tests {
let deadline = time::Duration::from_secs(1); let deadline = time::Duration::from_secs(1);
futures::executor::block_on( futures::executor::block_on(
proposer.propose(Default::default(), Default::default(), deadline, RecordProof::No) proposer.propose(Default::default(), Default::default(), deadline)
).map(|r| r.block).unwrap(); ).map(|r| r.block).unwrap();
} }
@@ -543,7 +573,7 @@ mod tests {
let deadline = time::Duration::from_secs(9); let deadline = time::Duration::from_secs(9);
let proposal = futures::executor::block_on( let proposal = futures::executor::block_on(
proposer.propose(Default::default(), Default::default(), deadline, RecordProof::No), proposer.propose(Default::default(), Default::default(), deadline),
).unwrap(); ).unwrap();
assert_eq!(proposal.block.extrinsics().len(), 1); assert_eq!(proposal.block.extrinsics().len(), 1);
@@ -624,7 +654,7 @@ mod tests {
// when // when
let deadline = time::Duration::from_secs(9); let deadline = time::Duration::from_secs(9);
let block = futures::executor::block_on( let block = futures::executor::block_on(
proposer.propose(Default::default(), Default::default(), deadline, RecordProof::No) proposer.propose(Default::default(), Default::default(), deadline)
).map(|r| r.block).unwrap(); ).map(|r| r.block).unwrap();
// then // then
+1 -2
View File
@@ -22,7 +22,7 @@
//! //!
//! ``` //! ```
//! # use sc_basic_authorship::ProposerFactory; //! # use sc_basic_authorship::ProposerFactory;
//! # use sp_consensus::{Environment, Proposer, RecordProof}; //! # use sp_consensus::{Environment, Proposer};
//! # use sp_runtime::generic::BlockId; //! # use sp_runtime::generic::BlockId;
//! # use std::{sync::Arc, time::Duration}; //! # use std::{sync::Arc, time::Duration};
//! # use substrate_test_runtime_client::{ //! # use substrate_test_runtime_client::{
@@ -61,7 +61,6 @@
//! Default::default(), //! Default::default(),
//! Default::default(), //! Default::default(),
//! Duration::from_secs(2), //! Duration::from_secs(2),
//! RecordProof::Yes,
//! ); //! );
//! //!
//! // We wait until the proposition is performed. //! // We wait until the proposition is performed.
@@ -17,7 +17,6 @@ targets = ["x86_64-unknown-linux-gnu"]
sp-state-machine = { version = "0.9.0", path = "../../primitives/state-machine" } sp-state-machine = { version = "0.9.0", path = "../../primitives/state-machine" }
sp-runtime = { version = "3.0.0", path = "../../primitives/runtime" } sp-runtime = { version = "3.0.0", path = "../../primitives/runtime" }
sp-api = { version = "3.0.0", path = "../../primitives/api" } sp-api = { version = "3.0.0", path = "../../primitives/api" }
sp-consensus = { version = "0.9.0", path = "../../primitives/consensus/common" }
sp-blockchain = { version = "3.0.0", path = "../../primitives/blockchain" } sp-blockchain = { version = "3.0.0", path = "../../primitives/blockchain" }
sp-core = { version = "3.0.0", path = "../../primitives/core" } sp-core = { version = "3.0.0", path = "../../primitives/core" }
sp-block-builder = { version = "3.0.0", path = "../../primitives/block-builder" } sp-block-builder = { version = "3.0.0", path = "../../primitives/block-builder" }
+37 -1
View File
@@ -37,12 +37,48 @@ use sp_core::ExecutionContext;
use sp_api::{ use sp_api::{
Core, ApiExt, ApiRef, ProvideRuntimeApi, StorageChanges, StorageProof, TransactionOutcome, Core, ApiExt, ApiRef, ProvideRuntimeApi, StorageChanges, StorageProof, TransactionOutcome,
}; };
use sp_consensus::RecordProof;
pub use sp_block_builder::BlockBuilder as BlockBuilderApi; pub use sp_block_builder::BlockBuilder as BlockBuilderApi;
use sc_client_api::backend; use sc_client_api::backend;
/// Used as parameter to [`BlockBuilderProvider`] to express if proof recording should be enabled.
///
/// When `RecordProof::Yes` is given, all accessed trie nodes should be saved. These recorded
/// trie nodes can be used by a third party to proof this proposal without having access to the
/// full storage.
#[derive(Copy, Clone, PartialEq)]
pub enum RecordProof {
/// `Yes`, record a proof.
Yes,
/// `No`, don't record any proof.
No,
}
impl RecordProof {
/// Returns if `Self` == `Yes`.
pub fn yes(&self) -> bool {
matches!(self, Self::Yes)
}
}
/// Will return [`RecordProof::No`] as default value.
impl Default for RecordProof {
fn default() -> Self {
Self::No
}
}
impl From<bool> for RecordProof {
fn from(val: bool) -> Self {
if val {
Self::Yes
} else {
Self::No
}
}
}
/// A block that was build by [`BlockBuilder`] plus some additional data. /// A block that was build by [`BlockBuilder`] plus some additional data.
/// ///
/// This additional data includes the `storage_changes`, these changes can be applied to the /// This additional data includes the `storage_changes`, these changes can be applied to the
+8 -5
View File
@@ -179,7 +179,7 @@ pub fn start_aura<B, C, SC, E, I, P, SO, CAW, BS, Error>(
&inherent_data_providers, &inherent_data_providers,
slot_duration.slot_duration() slot_duration.slot_duration()
)?; )?;
Ok(sc_consensus_slots::start_slot_worker::<_, _, _, _, _, AuraSlotCompatible, _>( Ok(sc_consensus_slots::start_slot_worker::<_, _, _, _, _, AuraSlotCompatible, _, _>(
slot_duration, slot_duration,
select_chain, select_chain,
worker, worker,
@@ -877,7 +877,9 @@ pub fn import_queue<B, I, C, P, S, CAW>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use sp_consensus::{NoNetwork as DummyOracle, Proposal, RecordProof, AlwaysCanAuthor}; use sp_consensus::{
NoNetwork as DummyOracle, Proposal, AlwaysCanAuthor, DisableProofRecording,
};
use sc_network_test::{Block as TestBlock, *}; use sc_network_test::{Block as TestBlock, *};
use sp_runtime::traits::{Block as BlockT, DigestFor}; use sp_runtime::traits::{Block as BlockT, DigestFor};
use sc_network::config::ProtocolConfig; use sc_network::config::ProtocolConfig;
@@ -916,20 +918,21 @@ mod tests {
substrate_test_runtime_client::Backend, substrate_test_runtime_client::Backend,
TestBlock TestBlock
>; >;
type Proposal = future::Ready<Result<Proposal<TestBlock, Self::Transaction>, Error>>; type Proposal = future::Ready<Result<Proposal<TestBlock, Self::Transaction, ()>, Error>>;
type ProofRecording = DisableProofRecording;
type Proof = ();
fn propose( fn propose(
self, self,
_: InherentData, _: InherentData,
digests: DigestFor<TestBlock>, digests: DigestFor<TestBlock>,
_: Duration, _: Duration,
_: RecordProof,
) -> Self::Proposal { ) -> Self::Proposal {
let r = self.1.new_block(digests).unwrap().build().map_err(|e| e.into()); let r = self.1.new_block(digests).unwrap().build().map_err(|e| e.into());
future::ready(r.map(|b| Proposal { future::ready(r.map(|b| Proposal {
block: b.block, block: b.block,
proof: b.proof, proof: (),
storage_changes: b.storage_changes, storage_changes: b.storage_changes,
})) }))
} }
+9 -9
View File
@@ -32,11 +32,10 @@ use sp_consensus_babe::{AuthorityPair, Slot, AllowedSlots, make_transcript, make
use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging; use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging;
use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; use sc_block_builder::{BlockBuilder, BlockBuilderProvider};
use sp_consensus::{ use sp_consensus::{
NoNetwork as DummyOracle, Proposal, RecordProof, AlwaysCanAuthor, NoNetwork as DummyOracle, Proposal, DisableProofRecording, AlwaysCanAuthor,
import_queue::{BoxBlockImport, BoxJustificationImport}, import_queue::{BoxBlockImport, BoxJustificationImport},
}; };
use sc_network_test::*; use sc_network_test::{Block as TestBlock, *};
use sc_network_test::{Block as TestBlock, PeersClient};
use sc_network::config::ProtocolConfig; use sc_network::config::ProtocolConfig;
use sp_runtime::{generic::DigestItem, traits::{Block as BlockT, DigestFor}}; use sp_runtime::{generic::DigestItem, traits::{Block as BlockT, DigestFor}};
use sc_client_api::{BlockchainEvents, backend::TransactionFor}; use sc_client_api::{BlockchainEvents, backend::TransactionFor};
@@ -44,8 +43,7 @@ use log::debug;
use std::{time::Duration, cell::RefCell, task::Poll}; use std::{time::Duration, cell::RefCell, task::Poll};
use rand::RngCore; use rand::RngCore;
use rand_chacha::{ use rand_chacha::{
rand_core::SeedableRng, rand_core::SeedableRng, ChaChaRng,
ChaChaRng,
}; };
use sc_keystore::LocalKeystore; use sc_keystore::LocalKeystore;
use sp_application_crypto::key_types::BABE; use sp_application_crypto::key_types::BABE;
@@ -112,7 +110,8 @@ impl DummyProposer {
Result< Result<
Proposal< Proposal<
TestBlock, TestBlock,
sc_client_api::TransactionFor<substrate_test_runtime_client::Backend, TestBlock> sc_client_api::TransactionFor<substrate_test_runtime_client::Backend, TestBlock>,
()
>, >,
Error Error
> >
@@ -163,21 +162,22 @@ impl DummyProposer {
// mutate the block header according to the mutator. // mutate the block header according to the mutator.
(self.factory.mutator)(&mut block.header, Stage::PreSeal); (self.factory.mutator)(&mut block.header, Stage::PreSeal);
future::ready(Ok(Proposal { block, proof: None, storage_changes: Default::default() })) future::ready(Ok(Proposal { block, proof: (), storage_changes: Default::default() }))
} }
} }
impl Proposer<TestBlock> for DummyProposer { impl Proposer<TestBlock> for DummyProposer {
type Error = Error; type Error = Error;
type Transaction = sc_client_api::TransactionFor<substrate_test_runtime_client::Backend, TestBlock>; type Transaction = sc_client_api::TransactionFor<substrate_test_runtime_client::Backend, TestBlock>;
type Proposal = future::Ready<Result<Proposal<TestBlock, Self::Transaction>, Error>>; type Proposal = future::Ready<Result<Proposal<TestBlock, Self::Transaction, ()>, Error>>;
type ProofRecording = DisableProofRecording;
type Proof = ();
fn propose( fn propose(
mut self, mut self,
_: InherentData, _: InherentData,
pre_digests: DigestFor<TestBlock>, pre_digests: DigestFor<TestBlock>,
_: Duration, _: Duration,
_: RecordProof,
) -> Self::Proposal { ) -> Self::Proposal {
self.propose_with(pre_digests) self.propose_with(pre_digests)
} }
@@ -123,8 +123,11 @@ pub async fn seal_block<B, BI, SC, C, E, P>(
Default::default() Default::default()
}; };
let proposal = proposer.propose(id.clone(), digest, Duration::from_secs(MAX_PROPOSAL_DURATION), false.into()) let proposal = proposer.propose(
.map_err(|err| Error::StringError(format!("{:?}", err))).await?; id.clone(),
digest,
Duration::from_secs(MAX_PROPOSAL_DURATION),
).map_err(|err| Error::StringError(format!("{:?}", err))).await?;
if proposal.block.extrinsics().len() == inherents_len && !create_empty { if proposal.block.extrinsics().len() == inherents_len && !create_empty {
return Err(Error::EmptyTransactionPool) return Err(Error::EmptyTransactionPool)
+7 -6
View File
@@ -52,8 +52,7 @@ use sp_consensus_pow::{Seal, TotalDifficulty, POW_ENGINE_ID};
use sp_inherents::{InherentDataProviders, InherentData}; use sp_inherents::{InherentDataProviders, InherentData};
use sp_consensus::{ use sp_consensus::{
BlockImportParams, BlockOrigin, ForkChoiceStrategy, SyncOracle, Environment, Proposer, BlockImportParams, BlockOrigin, ForkChoiceStrategy, SyncOracle, Environment, Proposer,
SelectChain, Error as ConsensusError, CanAuthorWith, RecordProof, BlockImport, SelectChain, Error as ConsensusError, CanAuthorWith, BlockImport, BlockCheckParams, ImportResult,
BlockCheckParams, ImportResult,
}; };
use sp_consensus::import_queue::{ use sp_consensus::import_queue::{
BoxBlockImport, BasicQueue, Verifier, BoxJustificationImport, BoxBlockImport, BasicQueue, Verifier, BoxJustificationImport,
@@ -549,7 +548,10 @@ pub fn start_mining_worker<Block, C, S, Algorithm, E, SO, CAW>(
timeout: Duration, timeout: Duration,
build_time: Duration, build_time: Duration,
can_author_with: CAW, can_author_with: CAW,
) -> (Arc<Mutex<MiningWorker<Block, Algorithm, C>>>, impl Future<Output = ()>) where ) -> (
Arc<Mutex<MiningWorker<Block, Algorithm, C, <E::Proposer as Proposer<Block>>::Proof>>>,
impl Future<Output = ()>,
) where
Block: BlockT, Block: BlockT,
C: ProvideRuntimeApi<Block> + BlockchainEvents<Block> + 'static, C: ProvideRuntimeApi<Block> + BlockchainEvents<Block> + 'static,
S: SelectChain<Block> + 'static, S: SelectChain<Block> + 'static,
@@ -566,7 +568,7 @@ pub fn start_mining_worker<Block, C, S, Algorithm, E, SO, CAW>(
} }
let timer = UntilImportedOrTimeout::new(client.import_notification_stream(), timeout); let timer = UntilImportedOrTimeout::new(client.import_notification_stream(), timeout);
let worker = Arc::new(Mutex::new(MiningWorker::<Block, Algorithm, C> { let worker = Arc::new(Mutex::new(MiningWorker::<Block, Algorithm, C, _> {
build: None, build: None,
algorithm: algorithm.clone(), algorithm: algorithm.clone(),
block_import, block_import,
@@ -664,7 +666,6 @@ pub fn start_mining_worker<Block, C, S, Algorithm, E, SO, CAW>(
inherent_data, inherent_data,
inherent_digest, inherent_digest,
build_time.clone(), build_time.clone(),
RecordProof::No,
).await { ).await {
Ok(x) => x, Ok(x) => x,
Err(err) => { Err(err) => {
@@ -678,7 +679,7 @@ pub fn start_mining_worker<Block, C, S, Algorithm, E, SO, CAW>(
}, },
}; };
let build = MiningBuild::<Block, Algorithm, C> { let build = MiningBuild::<Block, Algorithm, C, _> {
metadata: MiningMetadata { metadata: MiningMetadata {
best_hash, best_hash,
pre_hash: proposal.block.header().hash(), pre_hash: proposal.block.header().hash(),
+16 -6
View File
@@ -40,21 +40,31 @@ pub struct MiningMetadata<H, D> {
} }
/// A build of mining, containing the metadata and the block proposal. /// A build of mining, containing the metadata and the block proposal.
pub struct MiningBuild<Block: BlockT, Algorithm: PowAlgorithm<Block>, C: sp_api::ProvideRuntimeApi<Block>> { pub struct MiningBuild<
Block: BlockT,
Algorithm: PowAlgorithm<Block>,
C: sp_api::ProvideRuntimeApi<Block>,
Proof
> {
/// Mining metadata. /// Mining metadata.
pub metadata: MiningMetadata<Block::Hash, Algorithm::Difficulty>, pub metadata: MiningMetadata<Block::Hash, Algorithm::Difficulty>,
/// Mining proposal. /// Mining proposal.
pub proposal: Proposal<Block, sp_api::TransactionFor<C, Block>>, pub proposal: Proposal<Block, sp_api::TransactionFor<C, Block>, Proof>,
} }
/// Mining worker that exposes structs to query the current mining build and submit mined blocks. /// Mining worker that exposes structs to query the current mining build and submit mined blocks.
pub struct MiningWorker<Block: BlockT, Algorithm: PowAlgorithm<Block>, C: sp_api::ProvideRuntimeApi<Block>> { pub struct MiningWorker<
pub(crate) build: Option<MiningBuild<Block, Algorithm, C>>, Block: BlockT,
Algorithm: PowAlgorithm<Block>,
C: sp_api::ProvideRuntimeApi<Block>,
Proof
> {
pub(crate) build: Option<MiningBuild<Block, Algorithm, C, Proof>>,
pub(crate) algorithm: Algorithm, pub(crate) algorithm: Algorithm,
pub(crate) block_import: BoxBlockImport<Block, sp_api::TransactionFor<C, Block>>, pub(crate) block_import: BoxBlockImport<Block, sp_api::TransactionFor<C, Block>>,
} }
impl<Block, Algorithm, C> MiningWorker<Block, Algorithm, C> where impl<Block, Algorithm, C, Proof> MiningWorker<Block, Algorithm, C, Proof> where
Block: BlockT, Block: BlockT,
C: sp_api::ProvideRuntimeApi<Block>, C: sp_api::ProvideRuntimeApi<Block>,
Algorithm: PowAlgorithm<Block>, Algorithm: PowAlgorithm<Block>,
@@ -72,7 +82,7 @@ impl<Block, Algorithm, C> MiningWorker<Block, Algorithm, C> where
pub(crate) fn on_build( pub(crate) fn on_build(
&mut self, &mut self,
build: MiningBuild<Block, Algorithm, C>, build: MiningBuild<Block, Algorithm, C, Proof>,
) { ) {
self.build = Some(build); self.build = Some(build);
} }
+12 -14
View File
@@ -40,7 +40,7 @@ use log::{debug, error, info, warn};
use parking_lot::Mutex; use parking_lot::Mutex;
use sp_api::{ProvideRuntimeApi, ApiRef}; use sp_api::{ProvideRuntimeApi, ApiRef};
use sp_arithmetic::traits::BaseArithmetic; use sp_arithmetic::traits::BaseArithmetic;
use sp_consensus::{BlockImport, Proposer, SyncOracle, SelectChain, CanAuthorWith, SlotData, RecordProof}; use sp_consensus::{BlockImport, Proposer, SyncOracle, SelectChain, CanAuthorWith, SlotData};
use sp_consensus_slots::Slot; use sp_consensus_slots::Slot;
use sp_inherents::{InherentData, InherentDataProviders}; use sp_inherents::{InherentData, InherentDataProviders};
use sp_runtime::{ use sp_runtime::{
@@ -57,20 +57,18 @@ pub type StorageChanges<Transaction, Block> =
/// The result of [`SlotWorker::on_slot`]. /// The result of [`SlotWorker::on_slot`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SlotResult<Block: BlockT> { pub struct SlotResult<Block: BlockT, Proof> {
/// The block that was built. /// The block that was built.
pub block: Block, pub block: Block,
/// The optional storage proof that was calculated while building the block. /// The storage proof that was recorded while building the block.
/// pub storage_proof: Proof,
/// This needs to be enabled for the proposer to get this storage proof.
pub storage_proof: Option<sp_trie::StorageProof>,
} }
/// A worker that should be invoked at every new slot. /// A worker that should be invoked at every new slot.
/// ///
/// The implementation should not make any assumptions of the slot being bound to the time or /// The implementation should not make any assumptions of the slot being bound to the time or
/// similar. The only valid assumption is that the slot number is always increasing. /// similar. The only valid assumption is that the slot number is always increasing.
pub trait SlotWorker<B: BlockT> { pub trait SlotWorker<B: BlockT, Proof> {
/// Called when a new slot is triggered. /// Called when a new slot is triggered.
/// ///
/// Returns a future that resolves to a [`SlotResult`] iff a block was successfully built in /// Returns a future that resolves to a [`SlotResult`] iff a block was successfully built in
@@ -79,7 +77,7 @@ pub trait SlotWorker<B: BlockT> {
&mut self, &mut self,
chain_head: B::Header, chain_head: B::Header,
slot_info: SlotInfo, slot_info: SlotInfo,
) -> Pin<Box<dyn Future<Output = Option<SlotResult<B>>> + Send>>; ) -> Pin<Box<dyn Future<Output = Option<SlotResult<B, Proof>>> + Send>>;
} }
/// A skeleton implementation for `SlotWorker` which tries to claim a slot at /// A skeleton implementation for `SlotWorker` which tries to claim a slot at
@@ -206,7 +204,7 @@ pub trait SimpleSlotWorker<B: BlockT> {
&mut self, &mut self,
chain_head: B::Header, chain_head: B::Header,
slot_info: SlotInfo, slot_info: SlotInfo,
) -> Pin<Box<dyn Future<Output = Option<SlotResult<B>>> + Send>> ) -> Pin<Box<dyn Future<Output = Option<SlotResult<B, <Self::Proposer as Proposer<B>>::Proof>>> + Send>>
where where
<Self::Proposer as Proposer<B>>::Proposal: Unpin + Send + 'static, <Self::Proposer as Proposer<B>>::Proposal: Unpin + Send + 'static,
{ {
@@ -307,7 +305,6 @@ pub trait SimpleSlotWorker<B: BlockT> {
logs, logs,
}, },
slot_remaining_duration, slot_remaining_duration,
RecordProof::No,
).map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e)))); ).map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e))));
let proposal_work = let proposal_work =
@@ -384,12 +381,13 @@ pub trait SimpleSlotWorker<B: BlockT> {
} }
} }
impl<B: BlockT, T: SimpleSlotWorker<B>> SlotWorker<B> for T { impl<B: BlockT, T: SimpleSlotWorker<B>> SlotWorker<B, <T::Proposer as Proposer<B>>::Proof> for T
{
fn on_slot( fn on_slot(
&mut self, &mut self,
chain_head: B::Header, chain_head: B::Header,
slot_info: SlotInfo, slot_info: SlotInfo,
) -> Pin<Box<dyn Future<Output = Option<SlotResult<B>>> + Send>> { ) -> Pin<Box<dyn Future<Output = Option<SlotResult<B, <T::Proposer as Proposer<B>>::Proof>>> + Send>> {
SimpleSlotWorker::on_slot(self, chain_head, slot_info) SimpleSlotWorker::on_slot(self, chain_head, slot_info)
} }
} }
@@ -407,7 +405,7 @@ pub trait SlotCompatible {
/// ///
/// Every time a new slot is triggered, `worker.on_slot` is called and the future it returns is /// Every time a new slot is triggered, `worker.on_slot` is called and the future it returns is
/// polled until completion, unless we are major syncing. /// polled until completion, unless we are major syncing.
pub fn start_slot_worker<B, C, W, T, SO, SC, CAW>( pub fn start_slot_worker<B, C, W, T, SO, SC, CAW, Proof>(
slot_duration: SlotDuration<T>, slot_duration: SlotDuration<T>,
client: C, client: C,
mut worker: W, mut worker: W,
@@ -419,7 +417,7 @@ pub fn start_slot_worker<B, C, W, T, SO, SC, CAW>(
where where
B: BlockT, B: BlockT,
C: SelectChain<B>, C: SelectChain<B>,
W: SlotWorker<B>, W: SlotWorker<B, Proof>,
SO: SyncOracle + Send, SO: SyncOracle + Send,
SC: SlotCompatible + Unpin, SC: SlotCompatible + Unpin,
T: SlotData + Clone, T: SlotData + Clone,
@@ -193,13 +193,12 @@ mod tests {
use jsonrpc_core::{Notification, Output, types::Params}; use jsonrpc_core::{Notification, Output, types::Params};
use parity_scale_codec::{Encode, Decode}; use parity_scale_codec::{Encode, Decode};
use sc_block_builder::BlockBuilder; use sc_block_builder::{BlockBuilder, RecordProof};
use sc_finality_grandpa::{ use sc_finality_grandpa::{
report, AuthorityId, GrandpaJustificationSender, GrandpaJustification, report, AuthorityId, GrandpaJustificationSender, GrandpaJustification,
FinalityProof, FinalityProof,
}; };
use sp_blockchain::HeaderBackend; use sp_blockchain::HeaderBackend;
use sp_consensus::RecordProof;
use sp_core::crypto::Public; use sp_core::crypto::Public;
use sp_keyring::Ed25519Keyring; use sp_keyring::Ed25519Keyring;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
@@ -438,7 +437,7 @@ mod tests {
&*client, &*client,
client.info().best_hash, client.info().best_hash,
client.info().best_number, client.info().best_number,
RecordProof::Yes, RecordProof::No,
Default::default(), Default::default(),
&*backend, &*backend,
).unwrap().build().unwrap(); ).unwrap().build().unwrap();
@@ -52,7 +52,7 @@ use sp_state_machine::{
use sc_executor::RuntimeVersion; use sc_executor::RuntimeVersion;
use sp_consensus::{ use sp_consensus::{
Error as ConsensusError, BlockStatus, BlockImportParams, BlockCheckParams, Error as ConsensusError, BlockStatus, BlockImportParams, BlockCheckParams,
ImportResult, BlockOrigin, ForkChoiceStrategy, RecordProof, ImportResult, BlockOrigin, ForkChoiceStrategy,
}; };
use sp_blockchain::{ use sp_blockchain::{
self as blockchain, self as blockchain,
@@ -66,7 +66,7 @@ use sp_api::{
CallApiAt, ConstructRuntimeApi, Core as CoreApi, ApiExt, ApiRef, ProvideRuntimeApi, CallApiAt, ConstructRuntimeApi, Core as CoreApi, ApiExt, ApiRef, ProvideRuntimeApi,
CallApiAtParams, CallApiAtParams,
}; };
use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider, RecordProof};
use sc_client_api::{ use sc_client_api::{
backend::{ backend::{
self, BlockImportOperation, PrunableStateChangesTrieStorage, self, BlockImportOperation, PrunableStateChangesTrieStorage,
@@ -36,7 +36,7 @@ use sp_runtime::{
generic::BlockId, traits::{Block as BlockT, DigestFor, NumberFor, HashFor}, generic::BlockId, traits::{Block as BlockT, DigestFor, NumberFor, HashFor},
}; };
use futures::prelude::*; use futures::prelude::*;
pub use sp_inherents::InherentData; use sp_state_machine::StorageProof;
pub mod block_validation; pub mod block_validation;
pub mod offline_tracker; pub mod offline_tracker;
@@ -55,6 +55,7 @@ pub use block_import::{
pub use select_chain::SelectChain; pub use select_chain::SelectChain;
pub use sp_state_machine::Backend as StateBackend; pub use sp_state_machine::Backend as StateBackend;
pub use import_queue::DefaultImportQueue; pub use import_queue::DefaultImportQueue;
pub use sp_inherents::InherentData;
/// Block status. /// Block status.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@@ -89,53 +90,81 @@ pub trait Environment<B: BlockT> {
} }
/// A proposal that is created by a [`Proposer`]. /// A proposal that is created by a [`Proposer`].
pub struct Proposal<Block: BlockT, Transaction> { pub struct Proposal<Block: BlockT, Transaction, Proof> {
/// The block that was build. /// The block that was build.
pub block: Block, pub block: Block,
/// Optional proof that was recorded while building the block. /// Proof that was recorded while building the block.
pub proof: Option<sp_state_machine::StorageProof>, pub proof: Proof,
/// The storage changes while building this block. /// The storage changes while building this block.
pub storage_changes: sp_state_machine::StorageChanges<Transaction, HashFor<Block>, NumberFor<Block>>, pub storage_changes: sp_state_machine::StorageChanges<Transaction, HashFor<Block>, NumberFor<Block>>,
} }
/// Used as parameter to [`Proposer`] to tell the requirement on recording a proof. /// Error that is returned when [`ProofRecording`] requested to record a proof,
/// but no proof was recorded.
#[derive(Debug, thiserror::Error)]
#[error("Proof should be recorded, but no proof was provided.")]
pub struct NoProofRecorded;
/// A trait to express the state of proof recording on type system level.
/// ///
/// When `RecordProof::Yes` is given, all accessed trie nodes should be saved. These recorded /// This is used by [`Proposer`] to signal if proof recording is enabled. This can be used by
/// trie nodes can be used by a third party to proof this proposal without having access to the /// downstream users of the [`Proposer`] trait to enforce that proof recording is activated when
/// full storage. /// required. The only two implementations of this trait are [`DisableProofRecording`] and
#[derive(Copy, Clone, PartialEq)] /// [`EnableProofRecording`].
pub enum RecordProof { ///
/// `Yes`, record a proof. /// This trait is sealed and can not be implemented outside of this crate!
Yes, pub trait ProofRecording: Send + Sync + private::Sealed + 'static {
/// `No`, don't record any proof. /// The proof type that will be used internally.
No, type Proof: Send + Sync + 'static;
/// Is proof recording enabled?
const ENABLED: bool;
/// Convert the given `storage_proof` into [`Self::Proof`].
///
/// Internally Substrate uses `Option<StorageProof>` to express the both states of proof
/// recording (for now) and as [`Self::Proof`] is some different type, we need to provide a
/// function to convert this value.
///
/// If the proof recording was requested, but `None` is given, this will return
/// `Err(NoProofRecorded)`.
fn into_proof(storage_proof: Option<StorageProof>) -> Result<Self::Proof, NoProofRecorded>;
} }
impl RecordProof { /// Express that proof recording is disabled.
/// Returns if `Self` == `Yes`. ///
pub fn yes(&self) -> bool { /// For more information see [`ProofRecording`].
match self { pub struct DisableProofRecording;
Self::Yes => true,
Self::No => false, impl ProofRecording for DisableProofRecording {
} type Proof = ();
const ENABLED: bool = false;
fn into_proof(_: Option<StorageProof>) -> Result<Self::Proof, NoProofRecorded> {
Ok(())
} }
} }
/// Will return [`RecordProof::No`] as default value. /// Express that proof recording is enabled.
impl Default for RecordProof { ///
fn default() -> Self { /// For more information see [`ProofRecording`].
Self::No pub struct EnableProofRecording;
impl ProofRecording for EnableProofRecording {
type Proof = sp_state_machine::StorageProof;
const ENABLED: bool = true;
fn into_proof(proof: Option<StorageProof>) -> Result<Self::Proof, NoProofRecorded> {
proof.ok_or_else(|| NoProofRecorded)
} }
} }
impl From<bool> for RecordProof { /// Provides `Sealed` trait to prevent implementing trait [`ProofRecording`] outside of this crate.
fn from(val: bool) -> Self { mod private {
if val { /// Special trait that prevents the implementation of [`super::ProofRecording`] outside of this
Self::Yes /// crate.
} else { pub trait Sealed {}
Self::No
} impl Sealed for super::DisableProofRecording {}
} impl Sealed for super::EnableProofRecording {}
} }
/// Logic for a proposer. /// Logic for a proposer.
@@ -150,8 +179,16 @@ pub trait Proposer<B: BlockT> {
/// The transaction type used by the backend. /// The transaction type used by the backend.
type Transaction: Default + Send + 'static; type Transaction: Default + Send + 'static;
/// Future that resolves to a committed proposal with an optional proof. /// Future that resolves to a committed proposal with an optional proof.
type Proposal: Future<Output = Result<Proposal<B, Self::Transaction>, Self::Error>> + type Proposal:
Send + Unpin + 'static; Future<Output = Result<Proposal<B, Self::Transaction, Self::Proof>, Self::Error>>
+ Send
+ Unpin
+ 'static;
/// The supported proof recording by the implementator of this trait. See [`ProofRecording`]
/// for more information.
type ProofRecording: self::ProofRecording<Proof = Self::Proof> + Send + Sync + 'static;
/// The proof type used by [`Self::ProofRecording`].
type Proof: Send + Sync + 'static;
/// Create a proposal. /// Create a proposal.
/// ///
@@ -167,7 +204,6 @@ pub trait Proposer<B: BlockT> {
inherent_data: InherentData, inherent_data: InherentData,
inherent_digests: DigestFor<B>, inherent_digests: DigestFor<B>,
max_duration: Duration, max_duration: Duration,
record_proof: RecordProof,
) -> Self::Proposal; ) -> Self::Proposal;
} }