Update Cumulus for Parachains V1 (#224)

* Start with something

* Whatever

* Update

* MOARE

* Make cumulus-network compile and tests work

* Update more and fixes

* More stuff

* More fixes

* Make collator build

* Make test almost work

* Remove contracts runtime

* More test work

* Make service compile

* Fix test-service

* Fix test client

* More fixes

* Fix collator test

* Fix network tests (again)

* Make everything compile, finally

* Fix tests

* Update to latest masters

* Remove ignore

* Switch to different branch in polkadot for now

* Update reference

* Make it compile with latest changes

* Update collator/src/lib.rs

Co-authored-by: Robert Habermeier <rphmeier@gmail.com>

* Update to latest upstream

* Update to latest master

* Fix test

Co-authored-by: Robert Habermeier <rphmeier@gmail.com>
This commit is contained in:
Bastian Köcher
2020-11-08 22:18:09 +01:00
committed by GitHub
parent 55364ac254
commit 28338431fe
87 changed files with 3815 additions and 17343 deletions
+2122 -1590
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -1,14 +1,11 @@
[workspace] [workspace]
members = [ members = [
"consensus", "consensus",
"message-broker",
"network", "network",
"parachain-upgrade", "parachain-upgrade",
"primitives", "primitives",
"rococo-parachains/", "rococo-parachains/",
"rococo-parachains/contracts-runtime",
"rococo-parachains/pallets/parachain-info", "rococo-parachains/pallets/parachain-info",
"rococo-parachains/pallets/token-dealer",
"rococo-parachains/primitives", "rococo-parachains/primitives",
"rococo-parachains/runtime", "rococo-parachains/runtime",
"runtime", "runtime",
@@ -16,7 +13,6 @@ members = [
"test/runtime", "test/runtime",
"test/client", "test/client",
"test/service", "test/service",
"upward-message",
] ]
[profile.release] [profile.release]
+25 -20
View File
@@ -6,21 +6,26 @@ edition = "2018"
[dependencies] [dependencies]
# Substrate dependencies # Substrate dependencies
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-cli = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
# Polkadot dependencies # Polkadot dependencies
polkadot-collator = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test", features = [ "real-overseer" ] }
polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-validation = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-validation = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-node-primitives = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-overseer = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-node-subsystem = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
# Cumulus dependencies # Cumulus dependencies
cumulus-consensus = { path = "../consensus" } cumulus-consensus = { path = "../consensus" }
@@ -36,17 +41,17 @@ parking_lot = "0.9"
[dev-dependencies] [dev-dependencies]
# Cumulus dependencies # Cumulus dependencies
test-runtime = { package = "cumulus-test-runtime", path = "../test/runtime" } cumulus-test-runtime = { path = "../test/runtime" }
test-client = { package = "cumulus-test-client", path = "../test/client" } cumulus-test-client = { path = "../test/client" }
# Substrate dependencies # Substrate dependencies
substrate-test-client = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } substrate-test-client = { git = "https://github.com/paritytech/substrate", branch = "master" }
# Polkadot dependencies # Polkadot dependencies
polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-node-subsystem-test-helpers = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
# Other dependencies # Other dependencies
env_logger = "0.7.1" env_logger = "0.7.1"
+373 -401
View File
@@ -16,24 +16,18 @@
//! Cumulus Collator implementation for Substrate. //! Cumulus Collator implementation for Substrate.
use cumulus_network::{ use cumulus_network::WaitToAnnounce;
DelayedBlockAnnounceValidator, JustifiedBlockAnnounceValidator, WaitToAnnounce,
};
use cumulus_primitives::{ use cumulus_primitives::{
inherents::{ inherents::{DownwardMessagesType, DOWNWARD_MESSAGES_IDENTIFIER, VALIDATION_DATA_IDENTIFIER},
DownwardMessagesType, DOWNWARD_MESSAGES_IDENTIFIER, well_known_keys, ValidationData,
VALIDATION_FUNCTION_PARAMS_IDENTIFIER as VFP_IDENT,
},
validation_function_params::ValidationFunctionParams,
HeadData,
}; };
use cumulus_runtime::ParachainBlockData; use cumulus_runtime::ParachainBlockData;
use sc_client_api::{Backend as BackendT, BlockBackend, Finalizer, StateBackend, UsageProvider}; use sc_client_api::{BlockBackend, Finalizer, StateBackend, UsageProvider};
use sp_blockchain::HeaderBackend; use sp_blockchain::HeaderBackend;
use sp_consensus::{ use sp_consensus::{
BlockImport, BlockImportParams, BlockOrigin, BlockStatus, Environment, Error as ConsensusError, BlockImport, BlockImportParams, BlockOrigin, BlockStatus, Environment, Error as ConsensusError,
ForkChoiceStrategy, Proposal, Proposer, RecordProof, SyncOracle, ForkChoiceStrategy, Proposal, Proposer, RecordProof,
}; };
use sp_core::traits::SpawnNamed; use sp_core::traits::SpawnNamed;
use sp_inherents::{InherentData, InherentDataProviders}; use sp_inherents::{InherentData, InherentDataProviders};
@@ -41,73 +35,106 @@ use sp_runtime::{
generic::BlockId, generic::BlockId,
traits::{BlakeTwo256, Block as BlockT, Header as HeaderT}, traits::{BlakeTwo256, Block as BlockT, Header as HeaderT},
}; };
use sp_state_machine::InspectState;
use polkadot_collator::{ use polkadot_node_primitives::{Collation, CollationGenerationConfig};
BuildParachainContext, Network as CollatorNetwork, ParachainContext, RuntimeApiCollection, use polkadot_node_subsystem::messages::{CollationGenerationMessage, CollatorProtocolMessage};
}; use polkadot_overseer::OverseerHandler;
use polkadot_primitives::v0::{ use polkadot_primitives::v1::{
self as parachain, Block as PBlock, BlockData, DownwardMessage, GlobalValidationData, Block as PBlock, BlockData, CollatorPair, Hash as PHash, HeadData, Id as ParaId, PoV,
Hash as PHash, Id as ParaId, LocalValidationData, UpwardMessage,
}; };
use polkadot_service::RuntimeApiCollection;
use codec::{Decode, Encode}; use codec::{Decode, Encode};
use log::{debug, error, trace}; use log::{debug, error, info, trace};
use futures::prelude::*; use futures::prelude::*;
use std::{marker::PhantomData, pin::Pin, sync::Arc, time::Duration}; use std::{marker::PhantomData, sync::Arc, time::Duration};
use parking_lot::Mutex; use parking_lot::Mutex;
type TransactionFor<E, Block> =
<<E as Environment<Block>>::Proposer as Proposer<Block>>::Transaction;
/// The implementation of the Cumulus `Collator`. /// The implementation of the Cumulus `Collator`.
pub struct Collator<Block: BlockT, PF, BI, BS> { pub struct Collator<Block: BlockT, PF, BI, BS, Backend> {
proposer_factory: Arc<Mutex<PF>>, proposer_factory: Arc<Mutex<PF>>,
_phantom: PhantomData<Block>, _phantom: PhantomData<Block>,
inherent_data_providers: InherentDataProviders, inherent_data_providers: InherentDataProviders,
collator_network: Arc<dyn CollatorNetwork>,
block_import: Arc<Mutex<BI>>, block_import: Arc<Mutex<BI>>,
block_status: Arc<BS>, block_status: Arc<BS>,
wait_to_announce: Arc<Mutex<WaitToAnnounce<Block>>>, wait_to_announce: Arc<Mutex<WaitToAnnounce<Block>>>,
backend: Arc<Backend>,
} }
impl<Block: BlockT, PF, BI, BS> Collator<Block, PF, BI, BS> { impl<Block: BlockT, PF, BI, BS, Backend> Clone for Collator<Block, PF, BI, BS, Backend> {
fn clone(&self) -> Self {
Self {
proposer_factory: self.proposer_factory.clone(),
inherent_data_providers: self.inherent_data_providers.clone(),
_phantom: PhantomData,
block_import: self.block_import.clone(),
block_status: self.block_status.clone(),
wait_to_announce: self.wait_to_announce.clone(),
backend: self.backend.clone(),
}
}
}
impl<Block, PF, BI, BS, Backend> Collator<Block, PF, BI, BS, Backend>
where
Block: BlockT,
PF: Environment<Block> + 'static + Send,
PF::Proposer: Send,
BI: BlockImport<
Block,
Error = ConsensusError,
Transaction = <PF::Proposer as Proposer<Block>>::Transaction,
> + Send
+ Sync
+ 'static,
BS: BlockBackend<Block>,
Backend: sc_client_api::Backend<Block> + 'static,
{
/// Create a new instance. /// Create a new instance.
fn new( fn new(
proposer_factory: PF, proposer_factory: PF,
inherent_data_providers: InherentDataProviders, inherent_data_providers: InherentDataProviders,
collator_network: impl CollatorNetwork + Clone + 'static, overseer_handler: OverseerHandler,
block_import: BI, block_import: BI,
block_status: Arc<BS>, block_status: Arc<BS>,
spawner: Arc<dyn SpawnNamed + Send + Sync>, spawner: Arc<dyn SpawnNamed + Send + Sync>,
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>, announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
backend: Arc<Backend>,
) -> Self { ) -> Self {
let collator_network = Arc::new(collator_network);
let wait_to_announce = Arc::new(Mutex::new(WaitToAnnounce::new( let wait_to_announce = Arc::new(Mutex::new(WaitToAnnounce::new(
spawner, spawner,
announce_block, announce_block,
collator_network.clone(), overseer_handler,
))); )));
Self { Self {
proposer_factory: Arc::new(Mutex::new(proposer_factory)), proposer_factory: Arc::new(Mutex::new(proposer_factory)),
inherent_data_providers, inherent_data_providers,
_phantom: PhantomData, _phantom: PhantomData,
collator_network,
block_import: Arc::new(Mutex::new(block_import)), block_import: Arc::new(Mutex::new(block_import)),
block_status, block_status,
wait_to_announce, wait_to_announce,
backend,
} }
} }
/// Get the inherent data with validation function parameters injected /// Get the inherent data with validation function parameters injected
fn inherent_data( fn inherent_data(
inherent_providers: InherentDataProviders, &mut self,
global_validation: GlobalValidationData, validation_data: &ValidationData,
local_validation: LocalValidationData,
downward_messages: DownwardMessagesType, downward_messages: DownwardMessagesType,
) -> Option<InherentData> { ) -> Option<InherentData> {
let mut inherent_data = inherent_providers let mut inherent_data = self
.inherent_data_providers
.create_inherent_data() .create_inherent_data()
.map_err(|e| { .map_err(|e| {
error!( error!(
@@ -119,10 +146,7 @@ impl<Block: BlockT, PF, BI, BS> Collator<Block, PF, BI, BS> {
.ok()?; .ok()?;
inherent_data inherent_data
.put_data( .put_data(VALIDATION_DATA_IDENTIFIER, validation_data)
VFP_IDENT,
&ValidationFunctionParams::from((global_validation, local_validation)),
)
.map_err(|e| { .map_err(|e| {
error!( error!(
target: "cumulus-collator", target: "cumulus-collator",
@@ -145,36 +169,7 @@ impl<Block: BlockT, PF, BI, BS> Collator<Block, PF, BI, BS> {
Some(inherent_data) Some(inherent_data)
} }
}
impl<Block: BlockT, PF, BI, BS> Clone for Collator<Block, PF, BI, BS> {
fn clone(&self) -> Self {
Self {
proposer_factory: self.proposer_factory.clone(),
inherent_data_providers: self.inherent_data_providers.clone(),
_phantom: PhantomData,
collator_network: self.collator_network.clone(),
block_import: self.block_import.clone(),
block_status: self.block_status.clone(),
wait_to_announce: self.wait_to_announce.clone(),
}
}
}
impl<Block, PF, BI, BS> Collator<Block, PF, BI, BS>
where
Block: BlockT,
PF: Environment<Block> + 'static + Send,
PF::Proposer: Send,
BI: BlockImport<
Block,
Error = ConsensusError,
Transaction = <PF::Proposer as Proposer<Block>>::Transaction,
> + Send
+ Sync
+ 'static,
BS: BlockBackend<Block>,
{
/// Checks the status of the given block hash in the Parachain. /// Checks the status of the given block hash in the Parachain.
/// ///
/// Returns `true` if the block could be found and is good to be build on. /// Returns `true` if the block could be found and is good to be build on.
@@ -215,197 +210,212 @@ where
} }
} }
} }
}
impl<Block, PF, BI, BS> ParachainContext for Collator<Block, PF, BI, BS> fn build_collation(
where
Block: BlockT,
PF: Environment<Block> + 'static + Send,
PF::Proposer: Send,
BI: BlockImport<
Block,
Error = ConsensusError,
Transaction = <PF::Proposer as Proposer<Block>>::Transaction,
> + Send
+ Sync
+ 'static,
BS: BlockBackend<Block>,
{
type ProduceCandidate =
Pin<Box<dyn Future<Output = Option<(BlockData, parachain::HeadData)>> + Send>>;
fn produce_candidate(
&mut self, &mut self,
relay_chain_parent: PHash, block: ParachainBlockData<Block>,
global_validation: GlobalValidationData, block_hash: Block::Hash,
local_validation: LocalValidationData, ) -> Option<Collation> {
downward_messages: Vec<DownwardMessage>, let block_data = BlockData(block.encode());
) -> Self::ProduceCandidate { let header = block.into_header();
let factory = self.proposer_factory.clone(); let head_data = HeadData(header.encode());
let inherent_providers = self.inherent_data_providers.clone();
let block_import = self.block_import.clone();
trace!(target: "cumulus-collator", "Producing candidate"); let state = match self.backend.state_at(BlockId::Hash(block_hash)) {
Ok(state) => state,
let last_head = match HeadData::<Block>::decode(&mut &local_validation.parent_head.0[..]) {
Ok(x) => x,
Err(e) => { Err(e) => {
error!(target: "cumulus-collator", "Could not decode the head data: {:?}", e); error!(target: "cumulus-collator", "Failed to get state of the freshly built block: {:?}", e);
return Box::pin(future::ready(None)); return None;
} }
}; };
if !self.check_block_status(last_head.header.hash()) { state.inspect_state(|| {
return future::ready(None).boxed(); let upward_messages = sp_io::storage::get(well_known_keys::UPWARD_MESSAGES);
} let upward_messages = match upward_messages.map(|v| Vec::<UpwardMessage>::decode(&mut &v[..])) {
Some(Ok(msgs)) => msgs,
Some(Err(e)) => {
error!(target: "cumulus-collator", "Failed to decode upward messages from the build block: {:?}", e);
return None
},
None => Vec::new(),
};
let proposer_future = factory.lock().init(&last_head.header); let new_validation_code = sp_io::storage::get(well_known_keys::NEW_VALIDATION_CODE);
let wait_to_announce = self.wait_to_announce.clone(); Some(Collation {
upward_messages,
new_validation_code: new_validation_code.map(Into::into),
head_data,
proof_of_validity: PoV { block_data },
// TODO!
processed_downward_messages: 0,
})
})
}
Box::pin(async move { async fn produce_candidate(
let proposer = proposer_future mut self,
.await relay_parent: PHash,
.map_err(|e| { validation_data: ValidationData,
error!( ) -> Option<Collation> {
target: "cumulus-collator", trace!(target: "cumulus-collator", "Producing candidate");
"Could not create proposer: {:?}",
e,
)
})
.ok()?;
let inherent_data = Self::inherent_data(
inherent_providers,
global_validation,
local_validation,
downward_messages,
)?;
let Proposal {
block,
storage_changes,
proof,
} = proposer
.propose(
inherent_data,
Default::default(),
//TODO: Fix this.
Duration::from_millis(500),
RecordProof::Yes,
)
.await
.map_err(|e| {
error!(
target: "cumulus-collator",
"Proposing failed: {:?}",
e,
)
})
.ok()?;
let proof = match proof {
Some(proof) => proof,
None => {
error!(
target: "cumulus-collator",
"Proposer did not return the requested proof.",
);
let last_head =
match Block::Header::decode(&mut &validation_data.persisted.parent_head.0[..]) {
Ok(x) => x,
Err(e) => {
error!(target: "cumulus-collator", "Could not decode the head data: {:?}", e);
return None; return None;
} }
}; };
let (header, extrinsics) = block.deconstruct(); let last_head_hash = last_head.hash();
if !self.check_block_status(last_head_hash) {
return None;
}
// Create the parachain block data for the validators. info!(
let b = ParachainBlockData::<Block>::new(header.clone(), extrinsics, proof); target: "cumulus-collator",
"Starting collation for relay parent `{}` on parent `{}`.",
relay_parent,
last_head_hash,
);
let mut block_import_params = BlockImportParams::new(BlockOrigin::Own, header); let proposer_future = self.proposer_factory.lock().init(&last_head);
block_import_params.body = Some(b.extrinsics().to_vec());
// Best block is determined by the relay chain.
block_import_params.fork_choice = Some(ForkChoiceStrategy::Custom(false));
block_import_params.storage_changes = Some(storage_changes);
if let Err(err) = block_import let proposer = proposer_future
.lock() .await
.import_block(block_import_params, Default::default()) .map_err(|e| {
{
error!( error!(
target: "cumulus-collator", target: "cumulus-collator",
"Error importing build block (at {:?}): {:?}", "Could not create proposer: {:?}",
b.header().parent_hash(), e,
err, )
})
.ok()?;
let inherent_data = self.inherent_data(
&validation_data,
// TODO get the downward messages
Vec::new(),
)?;
let Proposal {
block,
storage_changes,
proof,
} = proposer
.propose(
inherent_data,
Default::default(),
//TODO: Fix this.
Duration::from_millis(500),
RecordProof::Yes,
)
.await
.map_err(|e| {
error!(
target: "cumulus-collator",
"Proposing failed: {:?}",
e,
)
})
.ok()?;
let proof = match proof {
Some(proof) => proof,
None => {
error!(
target: "cumulus-collator",
"Proposer did not return the requested proof.",
); );
return None; return None;
} }
};
let block_data = BlockData(b.encode()); let (header, extrinsics) = block.deconstruct();
let header = b.into_header(); let block_hash = header.hash();
let encoded_header = header.encode();
let hash = header.hash();
let head_data = HeadData::<Block> { header };
let candidate = (block_data, parachain::HeadData(head_data.encode())); // Create the parachain block data for the validators.
let b = ParachainBlockData::<Block>::new(header.clone(), extrinsics, proof);
wait_to_announce let mut block_import_params = BlockImportParams::new(BlockOrigin::Own, header);
.lock() block_import_params.body = Some(b.extrinsics().to_vec());
.wait_to_announce(hash, relay_chain_parent, encoded_header); // Best block is determined by the relay chain.
block_import_params.fork_choice = Some(ForkChoiceStrategy::Custom(false));
block_import_params.storage_changes = Some(storage_changes);
trace!(target: "cumulus-collator", "Produced candidate: {:?}", candidate); if let Err(err) = self
.block_import
.lock()
.import_block(block_import_params, Default::default())
{
error!(
target: "cumulus-collator",
"Error importing build block (at {:?}): {:?}",
b.header().parent_hash(),
err,
);
Some(candidate) return None;
})
}
}
/// Implements `BuildParachainContext` to build a collator instance.
pub struct CollatorBuilder<Block: BlockT, PF, BI, Backend, Client, BS> {
proposer_factory: PF,
inherent_data_providers: InherentDataProviders,
block_import: BI,
block_status: Arc<BS>,
para_id: ParaId,
client: Arc<Client>,
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
delayed_block_announce_validator: DelayedBlockAnnounceValidator<Block>,
_marker: PhantomData<(Block, Backend)>,
}
impl<Block: BlockT, PF, BI, Backend, Client, BS>
CollatorBuilder<Block, PF, BI, Backend, Client, BS>
{
/// Create a new instance of self.
pub fn new(
proposer_factory: PF,
inherent_data_providers: InherentDataProviders,
block_import: BI,
block_status: Arc<BS>,
para_id: ParaId,
client: Arc<Client>,
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
delayed_block_announce_validator: DelayedBlockAnnounceValidator<Block>,
) -> Self {
Self {
proposer_factory,
inherent_data_providers,
block_import,
block_status,
para_id,
client,
announce_block,
delayed_block_announce_validator,
_marker: PhantomData,
} }
let collation = self.build_collation(b, block_hash)?;
let pov_hash = collation.proof_of_validity.hash();
self.wait_to_announce
.lock()
.wait_to_announce(block_hash, pov_hash);
info!(target: "cumulus-collator", "Produced proof-of-validity candidate `{:?}` from block `{:?}`.", pov_hash, block_hash);
Some(collation)
} }
} }
type TransactionFor<E, Block> = /// Parameters for [`start_collator`].
<<E as Environment<Block>>::Proposer as Proposer<Block>>::Transaction; pub struct StartCollatorParams<Block: BlockT, PF, BI, Backend, Client, BS, Spawner, PClient> {
pub proposer_factory: PF,
pub inherent_data_providers: InherentDataProviders,
pub backend: Arc<Backend>,
pub block_import: BI,
pub block_status: Arc<BS>,
pub client: Arc<Client>,
pub announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
pub overseer_handler: OverseerHandler,
pub spawner: Spawner,
pub para_id: ParaId,
pub key: CollatorPair,
pub polkadot_client: Arc<PClient>,
}
impl<Block: BlockT, PF, BI, Backend, Client, BS> BuildParachainContext pub async fn start_collator<
for CollatorBuilder<Block, PF, BI, Backend, Client, BS> Block: BlockT,
PF,
BI,
Backend,
Client,
BS,
Spawner,
PClient,
PBackend,
PApi,
>(
StartCollatorParams {
proposer_factory,
inherent_data_providers,
backend,
block_import,
block_status,
client,
announce_block,
mut overseer_handler,
spawner,
para_id,
key,
polkadot_client,
}: StartCollatorParams<Block, PF, BI, Backend, Client, BS, Spawner, PClient>,
) -> Result<(), String>
where where
PF: Environment<Block> + Send + 'static, PF: Environment<Block> + Send + 'static,
BI: BlockImport<Block, Error = sp_consensus::Error, Transaction = TransactionFor<PF, Block>> BI: BlockImport<Block, Error = sp_consensus::Error, Transaction = TransactionFor<PF, Block>>
@@ -421,87 +431,79 @@ where
+ BlockBackend<Block> + BlockBackend<Block>
+ 'static, + 'static,
for<'a> &'a Client: BlockImport<Block>, for<'a> &'a Client: BlockImport<Block>,
BS: BlockBackend<Block>, BS: BlockBackend<Block> + Send + Sync + 'static,
Spawner: SpawnNamed + Clone + Send + Sync + 'static,
PBackend: sc_client_api::Backend<PBlock>,
PBackend::State: StateBackend<BlakeTwo256>,
PApi: RuntimeApiCollection<StateBackend = PBackend::State>,
PClient: polkadot_service::AbstractClient<PBlock, PBackend, Api = PApi> + 'static,
{ {
type ParachainContext = Collator<Block, PF, BI, BS>; let follow = match cumulus_consensus::follow_polkadot(
para_id,
client,
polkadot_client,
announce_block.clone(),
) {
Ok(follow) => follow,
Err(e) => return Err(format!("Could not start following polkadot: {:?}", e)),
};
fn build<Spawner, PClient, PBackend, PNetwork>( spawner.spawn("cumulus-follow-polkadot", follow.map(|_| ()).boxed());
self,
polkadot_client: Arc<PClient>,
spawner: Spawner,
polkadot_network: PNetwork,
) -> Result<Self::ParachainContext, ()>
where
Spawner: SpawnNamed + Clone + Send + Sync + 'static,
PBackend: BackendT<PBlock>,
PBackend::State: StateBackend<BlakeTwo256>,
PClient: polkadot_service::AbstractClient<PBlock, PBackend> + 'static,
PClient::Api: RuntimeApiCollection<StateBackend = PBackend::State>,
PNetwork: CollatorNetwork + SyncOracle + Clone + 'static,
{
let CollatorBuilder {
proposer_factory,
inherent_data_providers,
block_import,
block_status,
para_id,
client,
announce_block,
delayed_block_announce_validator,
_marker,
} = self;
delayed_block_announce_validator.set(Box::new(JustifiedBlockAnnounceValidator::new(
polkadot_client.clone(),
para_id,
Box::new(polkadot_network.clone()),
)));
let follow = match cumulus_consensus::follow_polkadot( let collator = Collator::new(
para_id, proposer_factory,
client, inherent_data_providers,
polkadot_client, overseer_handler.clone(),
announce_block.clone(), block_import,
) { block_status,
Ok(follow) => follow, Arc::new(spawner),
Err(e) => { announce_block,
return Err(error!("Could not start following polkadot: {:?}", e)); backend,
} );
};
spawner.spawn("cumulus-follow-polkadot", follow.map(|_| ()).boxed()); let config = CollationGenerationConfig {
key,
para_id,
collator: Box::new(move |relay_parent, validation_data| {
let collator = collator.clone();
collator
.produce_candidate(relay_parent, validation_data.clone())
.boxed()
}),
};
Ok(Collator::new( overseer_handler
proposer_factory, .send_msg(CollationGenerationMessage::Initialize(config))
inherent_data_providers, .await
polkadot_network, .map_err(|e| format!("Failed to send `Initialize` message: {:?}", e))?;
block_import,
block_status, overseer_handler
Arc::new(spawner), .send_msg(CollatorProtocolMessage::CollateOn(para_id))
announce_block, .await
)) .map_err(|e| format!("Failed to send `CollateOn` message: {:?}", e))
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::time::Duration; use std::{pin::Pin, time::Duration};
use polkadot_collator::{collate, SignedStatement}; use sc_block_builder::BlockBuilderProvider;
use polkadot_primitives::v0::Id as ParaId; use sp_core::{testing::TaskExecutor, Pair};
use sp_blockchain::Result as ClientResult;
use sp_core::testing::TaskExecutor;
use sp_inherents::InherentData; use sp_inherents::InherentData;
use sp_keyring::Sr25519Keyring; use sp_runtime::traits::DigestFor;
use sp_runtime::traits::{DigestFor, Header as HeaderT};
use sp_state_machine::StorageProof;
use substrate_test_client::{NativeExecutor, WasmExecutionMethod::Interpreted};
use test_client::{DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt}; use cumulus_test_client::{
use test_runtime::{Block, Header}; generate_block_inherents, Client, DefaultTestClientBuilderExt, NativeExecutor,
TestClientBuilder, TestClientBuilderExt, WasmExecutionMethod::Interpreted,
};
use cumulus_test_runtime::{Block, Header};
use futures::{future, Stream}; use polkadot_node_subsystem::messages::CollationGenerationMessage;
use polkadot_node_subsystem_test_helpers::ForwardSubsystem;
use polkadot_overseer::{AllSubsystems, Overseer};
use futures::{channel::mpsc, executor::block_on, future};
#[derive(Debug)] #[derive(Debug)]
struct Error; struct Error;
@@ -512,7 +514,7 @@ mod tests {
} }
} }
struct DummyFactory; struct DummyFactory(Arc<Client>);
impl Environment<Block> for DummyFactory { impl Environment<Block> for DummyFactory {
type Proposer = DummyProposer; type Proposer = DummyProposer;
@@ -521,150 +523,120 @@ mod tests {
Box<dyn Future<Output = Result<Self::Proposer, Self::Error>> + Send + Unpin + 'static>, Box<dyn Future<Output = Result<Self::Proposer, Self::Error>> + Send + Unpin + 'static>,
>; >;
fn init(&mut self, _: &Header) -> Self::CreateProposer { fn init(&mut self, header: &Header) -> Self::CreateProposer {
Box::pin(future::ready(Ok(DummyProposer))) Box::pin(future::ready(Ok(DummyProposer {
client: self.0.clone(),
header: header.clone(),
})))
} }
} }
struct DummyProposer; struct DummyProposer {
client: Arc<Client>,
header: Header,
}
impl Proposer<Block> for DummyProposer { impl Proposer<Block> for DummyProposer {
type Error = Error; type Error = Error;
type Proposal = future::Ready<Result<Proposal<Block, Self::Transaction>, Error>>; type Proposal = future::Ready<Result<Proposal<Block, Self::Transaction>, Error>>;
type Transaction = sc_client_api::TransactionFor<test_client::Backend, Block>; type Transaction = sc_client_api::TransactionFor<cumulus_test_client::Backend, Block>;
fn propose( fn propose(
self, self,
_: InherentData, _: InherentData,
digest: DigestFor<Block>, digest: DigestFor<Block>,
_: Duration, _: Duration,
_: RecordProof, record_proof: RecordProof,
) -> Self::Proposal { ) -> Self::Proposal {
let header = Header::new( let block_id = BlockId::Hash(self.header.hash());
1337, let mut builder = self
Default::default(), .client
Default::default(), .new_block_at(&block_id, digest, record_proof.yes())
Default::default(), .expect("Initializes new block");
digest,
); generate_block_inherents(&*self.client, None)
.into_iter()
.for_each(|e| builder.push(e).expect("Pushes an inherent"));
let (block, storage_changes, proof) =
builder.build().expect("Creates block").into_inner();
future::ready(Ok(Proposal { future::ready(Ok(Proposal {
block: Block::new(header, Vec::new()), block,
storage_changes: Default::default(), storage_changes,
proof: Some(StorageProof::empty()), proof,
})) }))
} }
} }
#[derive(Clone)]
struct DummyCollatorNetwork;
impl CollatorNetwork for DummyCollatorNetwork {
fn checked_statements(
&self,
_: PHash,
) -> Pin<Box<dyn Stream<Item = SignedStatement> + Send>> {
unimplemented!("Not required in tests")
}
}
impl SyncOracle for DummyCollatorNetwork {
fn is_major_syncing(&mut self) -> bool {
unimplemented!("Not required in tests")
}
fn is_offline(&mut self) -> bool {
unimplemented!("Not required in tests")
}
}
#[derive(Clone)]
struct DummyPolkadotClient;
impl cumulus_consensus::PolkadotClient for DummyPolkadotClient {
type Error = Error;
type HeadStream = Box<dyn futures::Stream<Item = Vec<u8>> + Send + Unpin>;
fn new_best_heads(&self, _: ParaId) -> ClientResult<Self::HeadStream> {
unimplemented!("Not required in tests")
}
fn finalized_heads(&self, _: ParaId) -> ClientResult<Self::HeadStream> {
unimplemented!("Not required in tests")
}
fn parachain_head_at(
&self,
_: &BlockId<PBlock>,
_: ParaId,
) -> ClientResult<Option<Vec<u8>>> {
unimplemented!("Not required in tests")
}
}
#[test] #[test]
fn collates_produces_a_block() { fn collates_produces_a_block() {
let id = ParaId::from(100);
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let spawner = TaskExecutor::new(); let spawner = TaskExecutor::new();
let para_id = ParaId::from(100);
let announce_block = |_, _| (); let announce_block = |_, _| ();
let block_announce_validator = DelayedBlockAnnounceValidator::new(); let client_builder = TestClientBuilder::new();
let client = Arc::new(TestClientBuilder::new().build()); let backend = client_builder.backend();
let client = Arc::new(client_builder.build());
let builder = CollatorBuilder::new(
DummyFactory,
InherentDataProviders::default(),
client.clone(),
client.clone(),
id,
client.clone(),
Arc::new(announce_block),
block_announce_validator,
);
let context = builder
.build::<_, _, polkadot_service::FullBackend, _>(
Arc::new(
substrate_test_client::TestClientBuilder::<_, _, _, ()>::default()
.build_with_native_executor::<polkadot_service::polkadot_runtime::RuntimeApi, _>(
Some(NativeExecutor::<polkadot_service::PolkadotExecutor>::new(
Interpreted,
None,
1,
)),
)
.0,
),
spawner,
DummyCollatorNetwork,
)
.expect("Creates parachain context");
let header = client.header(&BlockId::Number(0)).unwrap().unwrap(); let header = client.header(&BlockId::Number(0)).unwrap().unwrap();
let collation = collate( let (sub_tx, sub_rx) = mpsc::channel(64);
Default::default(),
id,
GlobalValidationData {
block_number: 0,
max_code_size: 0,
max_head_data_size: 0,
},
LocalValidationData {
parent_head: parachain::HeadData(HeadData::<Block> { header }.encode()),
balance: 10,
code_upgrade_allowed: None,
},
Vec::new(),
context,
Arc::new(Sr25519Keyring::Alice.pair().into()),
);
let collation = futures::executor::block_on(collation).unwrap(); let all_subsystems =
AllSubsystems::<()>::dummy().replace_collation_generation(ForwardSubsystem(sub_tx));
let (overseer, handler) = Overseer::new(Vec::new(), all_subsystems, None, spawner.clone())
.expect("Creates overseer");
let block_data = collation.pov.block_data; spawner.spawn("overseer", overseer.run().then(|_| async { () }).boxed());
let collator_start =
start_collator::<_, _, _, _, _, _, _, _, polkadot_service::FullBackend, _>(
StartCollatorParams {
proposer_factory: DummyFactory(client.clone()),
inherent_data_providers: Default::default(),
backend,
block_import: client.clone(),
block_status: client.clone(),
client: client.clone(),
announce_block: Arc::new(announce_block),
overseer_handler: handler,
spawner,
para_id,
key: CollatorPair::generate().0,
polkadot_client: Arc::new(
substrate_test_client::TestClientBuilder::<_, _, _, ()>::default()
.build_with_native_executor::<polkadot_service::polkadot_runtime::RuntimeApi, _>(
Some(NativeExecutor::<polkadot_service::PolkadotExecutor>::new(
Interpreted,
None,
1,
)),
)
.0,
),
},
);
block_on(collator_start).expect("Should start collator");
let msg = block_on(sub_rx.into_future())
.0
.expect("message should be send by `start_collator` above.");
let config = match msg {
CollationGenerationMessage::Initialize(config) => config,
};
let mut validation_data = ValidationData::default();
validation_data.persisted.parent_head = header.encode().into();
let collation = block_on((config.collator)(Default::default(), &validation_data))
.expect("Collation is build");
let block_data = collation.proof_of_validity.block_data;
let block = Block::decode(&mut &block_data.0[..]).expect("Is a valid block"); let block = Block::decode(&mut &block_data.0[..]).expect("Is a valid block");
assert_eq!(1337, *block.header().number()); assert_eq!(1, *block.header().number());
} }
} }
+11 -11
View File
@@ -7,19 +7,19 @@ edition = "2018"
[dependencies] [dependencies]
# substrate deps # substrate deps
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "master" }
# polkadot deps # polkadot deps
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-runtime = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-runtime = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
# other deps # other deps
futures = { version = "0.3.1", features = ["compat"] } futures = { version = "0.3.1", features = ["compat"] }
+4 -2
View File
@@ -26,7 +26,9 @@ use sp_runtime::{
traits::{Block as BlockT, Header as HeaderT}, traits::{Block as BlockT, Header as HeaderT},
}; };
use polkadot_primitives::v0::{Block as PBlock, Hash as PHash, Id as ParaId, ParachainHost}; use polkadot_primitives::v1::{
Block as PBlock, Hash as PHash, Id as ParaId, OccupiedCoreAssumption, ParachainHost,
};
use codec::Decode; use codec::Decode;
use futures::{future, Future, FutureExt, Stream, StreamExt}; use futures::{future, Future, FutureExt, Stream, StreamExt};
@@ -281,7 +283,7 @@ where
para_id: ParaId, para_id: ParaId,
) -> ClientResult<Option<Vec<u8>>> { ) -> ClientResult<Option<Vec<u8>>> {
self.runtime_api() self.runtime_api()
.local_validation_data(at, para_id) .persisted_validation_data(at, para_id, OccupiedCoreAssumption::TimedOut)
.map(|s| s.map(|s| s.parent_head.0)) .map(|s| s.map(|s| s.parent_head.0))
} }
} }
-35
View File
@@ -1,35 +0,0 @@
[package]
name = "cumulus-message-broker"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
# substrate deps
frame-support = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false }
# Other dependencies
codec = { package = "parity-scale-codec", version = "1.3.0", features = [ "derive" ], default-features = false }
# Cumulus dependencies
cumulus-primitives = { path = "../primitives", default-features = false }
cumulus-upward-message = { path = "../upward-message", default-features = false }
[features]
default = [ "std" ]
std = [
"frame-support/std",
"frame-system/std",
"sp-inherents/std",
"sp-io/std",
"sp-std/std",
"sp-runtime/std",
"codec/std",
"cumulus-primitives/std",
"cumulus-upward-message/std",
]
-151
View File
@@ -1,151 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Cumulus message broker pallet.
//!
//! This pallet provides support for handling downward, upward messages and
//! XMCP messages.
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use cumulus_primitives::{
inherents::{DownwardMessagesType, DOWNWARD_MESSAGES_IDENTIFIER},
well_known_keys,
xcmp::{RawXCMPMessage, XCMPMessageHandler, XCMPMessageSender},
DownwardMessage, DownwardMessageHandler, GenericUpwardMessage, ParaId, UpwardMessageOrigin,
UpwardMessageSender,
};
use cumulus_upward_message::XCMPMessage;
use frame_support::{
decl_event, decl_module, storage,
traits::Get,
weights::{DispatchClass, Weight},
};
use frame_system::ensure_none;
use sp_inherents::{InherentData, InherentIdentifier, MakeFatalError, ProvideInherent};
use sp_runtime::traits::Hash;
use sp_std::vec::Vec;
/// Configuration trait of this pallet.
pub trait Trait: frame_system::Trait {
/// Event type used by the runtime.
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
/// The downward message handlers that will be informed when a message is received.
type DownwardMessageHandlers: DownwardMessageHandler;
/// The upward message type used by the Parachain runtime.
type UpwardMessage: codec::Codec + XCMPMessage;
/// The XCMP message handlers that will be informed when a XCMP message is received.
type XCMPMessageHandlers: XCMPMessageHandler<Self::XCMPMessage>;
/// The XCMP message type used by the Parachain runtime.
type XCMPMessage: codec::Codec;
/// The Id of the parachain.
type ParachainId: Get<ParaId>;
}
decl_event! {
pub enum Event<T> where Hash = <T as frame_system::Trait>::Hash {
/// An upward message was sent to the relay chain.
///
/// The hash corresponds to the hash of the encoded upward message.
UpwardMessageSent(Hash),
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
/// Executes the given downward messages by calling the message handlers.
///
/// The origin of this call needs to be `None` as this is an inherent.
#[weight = (10, DispatchClass::Mandatory)]
fn execute_downward_messages(origin, messages: Vec<DownwardMessage>) {
ensure_none(origin)?;
//TODO: max messages should not be hardcoded. It should be determined based on the
// weight used by the handlers.
let max_messages = 10;
messages.iter().take(max_messages).for_each(|msg| {
match msg {
DownwardMessage::XCMPMessage(msg) => {
if let Ok(msg) = RawXCMPMessage::decode(&mut &msg[..]) {
if let Ok(xcmp_msg) = T::XCMPMessage::decode(&mut &msg.data[..]) {
T::XCMPMessageHandlers::handle_xcmp_message(msg.from, &xcmp_msg);
}
}
},
msg => T::DownwardMessageHandlers::handle_downward_message(msg),
}
});
let processed = sp_std::cmp::min(messages.len(), max_messages) as u32;
storage::unhashed::put(well_known_keys::PROCESSED_DOWNWARD_MESSAGES, &processed);
}
fn on_initialize() -> Weight {
storage::unhashed::kill(well_known_keys::UPWARD_MESSAGES);
T::DbWeight::get().writes(1)
}
fn deposit_event() = default;
}
}
impl<T: Trait> UpwardMessageSender<T::UpwardMessage> for Module<T> {
fn send_upward_message(msg: &T::UpwardMessage, origin: UpwardMessageOrigin) -> Result<(), ()> {
//TODO: check fee schedule
let data = msg.encode();
let data_hash = T::Hashing::hash(&data);
let msg = GenericUpwardMessage { origin, data };
sp_io::storage::append(well_known_keys::UPWARD_MESSAGES, msg.encode());
Self::deposit_event(RawEvent::UpwardMessageSent(data_hash));
Ok(())
}
}
impl<T: Trait> XCMPMessageSender<T::XCMPMessage> for Module<T> {
fn send_xcmp_message(dest: ParaId, msg: &T::XCMPMessage) -> Result<(), ()> {
let message = RawXCMPMessage {
from: T::ParachainId::get(),
data: msg.encode(),
};
Self::send_upward_message(
&T::UpwardMessage::send_message(dest, message.encode()),
UpwardMessageOrigin::Parachain,
)
}
}
impl<T: Trait> ProvideInherent for Module<T> {
type Call = Call<T>;
type Error = MakeFatalError<()>;
const INHERENT_IDENTIFIER: InherentIdentifier = DOWNWARD_MESSAGES_IDENTIFIER;
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
data.get_data::<DownwardMessagesType>(&DOWNWARD_MESSAGES_IDENTIFIER)
.expect("Downward messages inherent data failed to decode")
.map(|msgs| Call::execute_downward_messages(msgs))
}
}
+17 -16
View File
@@ -7,18 +7,21 @@ edition = "2018"
[dependencies] [dependencies]
# substrate deps # substrate deps
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
# polkadot deps # polkadot deps
polkadot-collator = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-statement-table = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-statement-table = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-validation = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-validation = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-overseer = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-network = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-node-primitives = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-node-subsystem = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
# cumulus deps # cumulus deps
cumulus-primitives = { path = "../primitives" } cumulus-primitives = { path = "../primitives" }
@@ -33,9 +36,7 @@ parking_lot = "0.10.2"
cumulus-test-runtime = { path = "../test/runtime" } cumulus-test-runtime = { path = "../test/runtime" }
# substrate deps # substrate deps
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
# polkadot deps
polkadot-test-runtime-client = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" }
+261 -187
View File
@@ -14,9 +14,11 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. // along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Cumulus-specific network implementation. //! Parachain specific networking
//! //!
//! Contains message send between collators and logic to process them. //! Provides a custom block announcement implementation for parachains
//! that use the relay chain provided consensus. See [`BlockAnnounceValidator`]
//! and [`WaitToAnnounce`] for more information about this implementation.
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@@ -24,7 +26,7 @@ mod tests;
use sp_api::ProvideRuntimeApi; use sp_api::ProvideRuntimeApi;
use sp_blockchain::{Error as ClientError, HeaderBackend}; use sp_blockchain::{Error as ClientError, HeaderBackend};
use sp_consensus::{ use sp_consensus::{
block_validation::{BlockAnnounceValidator, Validation}, block_validation::{BlockAnnounceValidator as BlockAnnounceValidatorT, Validation},
SyncOracle, SyncOracle,
}; };
use sp_core::traits::SpawnNamed; use sp_core::traits::SpawnNamed;
@@ -33,35 +35,60 @@ use sp_runtime::{
traits::{Block as BlockT, Header as HeaderT}, traits::{Block as BlockT, Header as HeaderT},
}; };
use polkadot_collator::Network as CollatorNetwork; use polkadot_node_primitives::{SignedFullStatement, Statement};
use polkadot_network::legacy::gossip::{GossipMessage, GossipStatement}; use polkadot_node_subsystem::messages::StatementDistributionMessage;
use polkadot_primitives::v0::{Block as PBlock, Hash as PHash, Id as ParaId, ParachainHost}; use polkadot_overseer::OverseerHandler;
use polkadot_statement_table::v0::{SignedStatement, Statement}; use polkadot_primitives::v1::{
use polkadot_validation::check_statement; Block as PBlock, Hash as PHash, Id as ParaId, OccupiedCoreAssumption, ParachainHost,
SigningContext,
use cumulus_primitives::HeadData; };
use polkadot_service::ClientHandle;
use codec::{Decode, Encode}; use codec::{Decode, Encode};
use futures::{channel::oneshot, future::FutureExt, pin_mut, select, StreamExt}; use futures::{
use log::trace; channel::{mpsc, oneshot},
future::{ready, FutureExt},
pin_mut, select, Future, StreamExt,
};
use log::{trace, warn};
use parking_lot::Mutex; use std::{marker::PhantomData, pin::Pin, sync::Arc};
use std::{marker::PhantomData, sync::Arc};
/// Validate that data is a valid justification from a relay-chain validator that the block is a /// Parachain specific block announce validator.
/// valid parachain-block candidate.
/// Data encoding is just `GossipMessage`, the relay-chain validator candidate statement message is
/// the justification.
/// ///
/// Note: if no justification is provided the annouce is considered valid. /// This block announce validator is required if the parachain is running
pub struct JustifiedBlockAnnounceValidator<B, P> { /// with the relay chain provided consensus to make sure each node only
/// imports a reasonable number of blocks per round. The relay chain provided
/// consensus doesn't have any authorities and so it could happen that without
/// this special block announce validator a node would need to import *millions*
/// of blocks per round, which is clearly not doable.
///
/// To solve this problem, each block announcement is delayed until a collator
/// has received a [`Statement::Seconded`] for its `PoV`. This message tells the
/// collator that its `PoV` was validated successfully by a parachain validator and
/// that it is very likely that this `PoV` will be included in the relay chain. Every
/// collator that doesn't receive the message for its `PoV` will not announce its block.
/// For more information on the block announcement, see [`WaitToAnnounce`].
///
/// For each block announcement that is received, the generic block announcement validation
/// will call this validator and provides the extra data that was attached to the announcement.
/// We call this extra data `justification`.
/// It is expected that the attached data is a SCALE encoded [`SignedFullStatement`]. The
/// statement is checked to be a [`Statement::Seconded`] and that it is signed by an active
/// parachain validator.
///
/// If no justification was provided we check if the block announcement is at the tip of the known
/// chain. If it is at the tip, it is required to provide a justification or otherwise we reject
/// it. However, if the announcement is for a block below the tip the announcement is accepted
/// as it probably comes from a node that is currently syncing the chain.
pub struct BlockAnnounceValidator<B, P> {
phantom: PhantomData<B>, phantom: PhantomData<B>,
polkadot_client: Arc<P>, polkadot_client: Arc<P>,
para_id: ParaId, para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>, polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
} }
impl<B, P> JustifiedBlockAnnounceValidator<B, P> { impl<B, P> BlockAnnounceValidator<B, P> {
pub fn new( pub fn new(
polkadot_client: Arc<P>, polkadot_client: Arc<P>,
para_id: ParaId, para_id: ParaId,
@@ -76,192 +103,239 @@ impl<B, P> JustifiedBlockAnnounceValidator<B, P> {
} }
} }
impl<B: BlockT, P> BlockAnnounceValidator<B> for JustifiedBlockAnnounceValidator<B, P> impl<B: BlockT, P> BlockAnnounceValidatorT<B> for BlockAnnounceValidator<B, P>
where where
P: ProvideRuntimeApi<PBlock> + HeaderBackend<PBlock>, P: ProvideRuntimeApi<PBlock> + HeaderBackend<PBlock> + 'static,
P::Api: ParachainHost<PBlock>, P::Api: ParachainHost<PBlock>,
{ {
fn validate( fn validate(
&mut self, &mut self,
header: &B::Header, header: &B::Header,
mut data: &[u8], mut data: &[u8],
) -> Result<Validation, Box<dyn std::error::Error + Send>> { ) -> Pin<Box<dyn Future<Output = Result<Validation, Box<dyn std::error::Error + Send>>> + Send>>
{
if self.polkadot_sync_oracle.is_major_syncing() { if self.polkadot_sync_oracle.is_major_syncing() {
return Ok(Validation::Success { is_new_best: false }); return ready(Ok(Validation::Success { is_new_best: false })).boxed();
} }
let runtime_api = self.polkadot_client.runtime_api(); let runtime_api = self.polkadot_client.runtime_api();
let polkadot_info = self.polkadot_client.info(); let polkadot_info = self.polkadot_client.info();
if data.is_empty() { if data.is_empty() {
// Check if block is equal or higher than best (this requires a justification) let polkadot_client = self.polkadot_client.clone();
let runtime_api_block_id = BlockId::Hash(polkadot_info.best_hash); let header = header.clone();
let block_number = header.number(); let para_id = self.para_id;
let local_validation_data = runtime_api return async move {
.local_validation_data(&runtime_api_block_id, self.para_id) // Check if block is equal or higher than best (this requires a justification)
.map_err(|e| Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)? let runtime_api_block_id = BlockId::Hash(polkadot_info.best_hash);
.ok_or_else(|| { let block_number = header.number();
Box::new(ClientError::Msg(
"Could not find parachain head in relay chain".into(),
)) as Box<_>
})?;
let parent_head = HeadData::<B>::decode(&mut &local_validation_data.parent_head.0[..])
.map_err(|e| {
Box::new(ClientError::Msg(format!(
"Failed to decode parachain head: {:?}",
e
))) as Box<_>
})?;
let known_best_number = parent_head.header.number();
return Ok(if block_number >= known_best_number { let local_validation_data = polkadot_client
trace!( .runtime_api()
target: "cumulus-network", .persisted_validation_data(
"validation failed because a justification is needed if the block at the top of the chain." &runtime_api_block_id,
); para_id,
OccupiedCoreAssumption::TimedOut,
)
.map_err(|e| Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)?
.ok_or_else(|| {
Box::new(ClientError::Msg(
"Could not find parachain head in relay chain".into(),
)) as Box<_>
})?;
let parent_head = B::Header::decode(&mut &local_validation_data.parent_head.0[..])
.map_err(|e| {
Box::new(ClientError::Msg(format!(
"Failed to decode parachain head: {:?}",
e
))) as Box<_>
})?;
let known_best_number = parent_head.number();
Validation::Failure if block_number >= known_best_number {
} else { trace!(
Validation::Success { is_new_best: false } target: "cumulus-network",
}); "validation failed because a justification is needed if the block at the top of the chain."
);
Ok(Validation::Failure)
} else {
Ok(Validation::Success { is_new_best: false })
}
}
.boxed();
} }
// Check data is a gossip message. let signed_stmt = match SignedFullStatement::decode(&mut data) {
let gossip_message = GossipMessage::decode(&mut data).map_err(|_| { Ok(r) => r,
Box::new(ClientError::BadJustification( Err(_) => return ready(Err(Box::new(ClientError::BadJustification(
"cannot decode block announced justification, must be a gossip message".to_string(), "cannot decode block announcement justification, must be a `SignedFullStatement`"
)) as Box<_> .to_string(),
})?; )) as Box<_>))
.boxed(),
};
// Check message is a gossip statement. // Check statement is a candidate statement.
let gossip_statement = match gossip_message { let candidate_receipt = match signed_stmt.payload() {
GossipMessage::Statement(gossip_statement) => gossip_statement, Statement::Seconded(ref candidate_receipt) => candidate_receipt,
_ => { _ => {
return Err(Box::new(ClientError::BadJustification( return ready(Err(Box::new(ClientError::BadJustification(
"block announced justification statement must be a gossip statement" "block announcement justification must be a `Statement::Seconded`".to_string(),
.to_string(), )) as Box<_>))
)) as Box<_>) .boxed()
} }
}; };
let GossipStatement {
relay_chain_leaf,
signed_statement: SignedStatement {
statement,
signature,
sender,
},
} = gossip_statement;
// Check that the relay chain parent of the block is the relay chain head // Check that the relay chain parent of the block is the relay chain head
let best_number = polkadot_info.best_number; let best_number = polkadot_info.best_number;
let validator_index = signed_stmt.validator_index();
let relay_parent = &candidate_receipt.descriptor.relay_parent;
match self.polkadot_client.number(relay_chain_leaf) { match self.polkadot_client.number(*relay_parent) {
Err(err) => { Err(err) => {
return Err(Box::new(ClientError::Backend(format!( return ready(Err(Box::new(ClientError::Backend(format!(
"could not find block number for {}: {}", "could not find block number for {}: {}",
relay_chain_leaf, err, relay_parent, err,
)))); ))) as Box<_>))
.boxed();
} }
Ok(Some(x)) if x == best_number => {} Ok(Some(x)) if x == best_number => {}
Ok(None) => { Ok(None) => {
return Err(Box::new(ClientError::UnknownBlock( return ready(Err(
relay_chain_leaf.to_string(), Box::new(ClientError::UnknownBlock(relay_parent.to_string())) as Box<_>,
))); ))
.boxed();
} }
Ok(Some(_)) => { Ok(Some(_)) => {
trace!( trace!(
target: "cumulus-network", target: "cumulus-network",
"validation failed because the relay chain parent ({}) is not the relay chain \ "validation failed because the relay chain parent ({}) is not the relay chain \
head ({})", head ({})",
relay_chain_leaf, best_number, relay_parent,
best_number,
); );
return Ok(Validation::Failure); return ready(Ok(Validation::Failure)).boxed();
} }
} }
let runtime_api_block_id = BlockId::Hash(relay_chain_leaf); let runtime_api_block_id = BlockId::Hash(*relay_parent);
let signing_context = runtime_api let session_index = match runtime_api.session_index_for_child(&runtime_api_block_id) {
.signing_context(&runtime_api_block_id) Ok(r) => r,
.map_err(|e| Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)?; Err(e) => {
return ready(Err(Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)).boxed()
// Check that the signer is a legit validator.
let authorities = runtime_api
.validators(&runtime_api_block_id)
.map_err(|e| Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)?;
let signer = authorities.get(sender as usize).ok_or_else(|| {
Box::new(ClientError::BadJustification(
"block accounced justification signer is a validator index out of bound"
.to_string(),
)) as Box<_>
})?;
// Check statement is correctly signed.
if !check_statement(&statement, &signature, signer.clone(), &signing_context) {
return Err(Box::new(ClientError::BadJustification(
"block announced justification signature is invalid".to_string(),
)) as Box<_>);
}
// Check statement is a candidate statement.
let candidate_receipt = match statement {
Statement::Candidate(candidate_receipt) => candidate_receipt,
_ => {
return Err(Box::new(ClientError::BadJustification(
"block announced justification statement must be a candidate statement"
.to_string(),
)) as Box<_>)
} }
}; };
// Check the header in the candidate_receipt match header given header. let signing_context = SigningContext {
if header.encode() != candidate_receipt.head_data.0 { parent_hash: *relay_parent,
return Err(Box::new(ClientError::BadJustification( session_index,
"block announced header does not match the one justified".to_string(), };
)) as Box<_>);
}
Ok(Validation::Success { is_new_best: true }) // Check that the signer is a legit validator.
} let authorities = match runtime_api.validators(&runtime_api_block_id) {
} Ok(r) => r,
Err(e) => {
/// A `BlockAnnounceValidator` that will be able to validate data when its internal return ready(Err(Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)).boxed()
/// `BlockAnnounceValidator` is set.
pub struct DelayedBlockAnnounceValidator<B: BlockT>(
Arc<Mutex<Option<Box<dyn BlockAnnounceValidator<B> + Send>>>>,
);
impl<B: BlockT> DelayedBlockAnnounceValidator<B> {
pub fn new() -> DelayedBlockAnnounceValidator<B> {
DelayedBlockAnnounceValidator(Arc::new(Mutex::new(None)))
}
pub fn set(&self, validator: Box<dyn BlockAnnounceValidator<B> + Send>) {
*self.0.lock() = Some(validator);
}
}
impl<B: BlockT> Clone for DelayedBlockAnnounceValidator<B> {
fn clone(&self) -> DelayedBlockAnnounceValidator<B> {
DelayedBlockAnnounceValidator(self.0.clone())
}
}
impl<B: BlockT> BlockAnnounceValidator<B> for DelayedBlockAnnounceValidator<B> {
fn validate(
&mut self,
header: &B::Header,
data: &[u8],
) -> Result<Validation, Box<dyn std::error::Error + Send>> {
match self.0.lock().as_mut() {
Some(validator) => validator.validate(header, data),
None => {
log::warn!("BlockAnnounce validator not yet set, rejecting block announcement");
Ok(Validation::Failure)
} }
};
let signer = match authorities.get(validator_index as usize) {
Some(r) => r,
None => {
return ready(Err(Box::new(ClientError::BadJustification(
"block accouncement justification signer is a validator index out of bound"
.to_string(),
)) as Box<_>))
.boxed()
}
};
// Check statement is correctly signed.
if signed_stmt
.check_signature(&signing_context, &signer)
.is_err()
{
return ready(Err(Box::new(ClientError::BadJustification(
"block announced justification signature is invalid".to_string(),
)) as Box<_>))
.boxed();
} }
// Check the header in the candidate_receipt match header given header.
if header.encode() != candidate_receipt.commitments.head_data.0 {
return ready(Err(Box::new(ClientError::BadJustification(
"block announced header does not match the one justified".to_string(),
)) as Box<_>))
.boxed();
}
ready(Ok(Validation::Success { is_new_best: true })).boxed()
}
}
/// Build a block announce validator instance.
///
/// Returns a boxed [`BlockAnnounceValidator`].
pub fn build_block_announce_validator<B: BlockT>(
polkadot_client: polkadot_service::Client,
para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
) -> Box<dyn BlockAnnounceValidatorT<B> + Send> {
BlockAnnounceValidatorBuilder::new(polkadot_client, para_id, polkadot_sync_oracle).build()
}
/// Block announce validator builder.
///
/// Builds a [`BlockAnnounceValidator`] for a parachain. As this requires
/// a concrete Polkadot client instance, the builder takes a [`polkadot_service::Client`]
/// that wraps this concrete instanace. By using [`polkadot_service::ExecuteWithClient`]
/// the builder gets access to this concrete instance.
struct BlockAnnounceValidatorBuilder<B> {
phantom: PhantomData<B>,
polkadot_client: polkadot_service::Client,
para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
}
impl<B: BlockT> BlockAnnounceValidatorBuilder<B> {
/// Create a new instance of the builder.
fn new(
polkadot_client: polkadot_service::Client,
para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
) -> Self {
Self {
polkadot_client,
para_id,
polkadot_sync_oracle,
phantom: PhantomData,
}
}
/// Build the block announce validator.
fn build(self) -> Box<dyn BlockAnnounceValidatorT<B> + Send> {
self.polkadot_client.clone().execute_with(self)
}
}
impl<B: BlockT> polkadot_service::ExecuteWithClient for BlockAnnounceValidatorBuilder<B> {
type Output = Box<dyn BlockAnnounceValidatorT<B> + Send>;
fn execute_with_client<PClient, Api, PBackend>(self, client: Arc<PClient>) -> Self::Output
where
<Api as sp_api::ApiExt<PBlock>>::StateBackend:
sp_api::StateBackend<sp_runtime::traits::BlakeTwo256>,
PBackend: sc_client_api::Backend<PBlock>,
PBackend::State: sp_api::StateBackend<sp_runtime::traits::BlakeTwo256>,
Api: polkadot_service::RuntimeApiCollection<StateBackend = PBackend::State>,
PClient: polkadot_service::AbstractClient<PBlock, PBackend, Api = Api> + 'static,
{
Box::new(BlockAnnounceValidator::new(
client,
self.para_id,
self.polkadot_sync_oracle,
))
} }
} }
@@ -273,7 +347,7 @@ impl<B: BlockT> BlockAnnounceValidator<B> for DelayedBlockAnnounceValidator<B> {
pub struct WaitToAnnounce<Block: BlockT> { pub struct WaitToAnnounce<Block: BlockT> {
spawner: Arc<dyn SpawnNamed + Send + Sync>, spawner: Arc<dyn SpawnNamed + Send + Sync>,
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>, announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
collator_network: Arc<dyn CollatorNetwork>, overseer_handler: OverseerHandler,
current_trigger: oneshot::Sender<()>, current_trigger: oneshot::Sender<()>,
} }
@@ -282,29 +356,24 @@ impl<Block: BlockT> WaitToAnnounce<Block> {
pub fn new( pub fn new(
spawner: Arc<dyn SpawnNamed + Send + Sync>, spawner: Arc<dyn SpawnNamed + Send + Sync>,
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>, announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
collator_network: Arc<dyn CollatorNetwork>, overseer_handler: OverseerHandler,
) -> WaitToAnnounce<Block> { ) -> WaitToAnnounce<Block> {
let (tx, _rx) = oneshot::channel(); let (tx, _rx) = oneshot::channel();
WaitToAnnounce { WaitToAnnounce {
spawner, spawner,
announce_block, announce_block,
collator_network, overseer_handler,
current_trigger: tx, current_trigger: tx,
} }
} }
/// Wait for a candidate message for the block, then announce the block. The candidate /// Wait for a candidate message for the block, then announce the block. The candidate
/// message will be added as justification to the block announcement. /// message will be added as justification to the block announcement.
pub fn wait_to_announce( pub fn wait_to_announce(&mut self, block_hash: <Block as BlockT>::Hash, pov_hash: PHash) {
&mut self,
hash: <Block as BlockT>::Hash,
relay_chain_leaf: PHash,
head_data: Vec<u8>,
) {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let announce_block = self.announce_block.clone(); let announce_block = self.announce_block.clone();
let collator_network = self.collator_network.clone(); let overseer_handler = self.overseer_handler.clone();
self.current_trigger = tx; self.current_trigger = tx;
@@ -312,11 +381,10 @@ impl<Block: BlockT> WaitToAnnounce<Block> {
"cumulus-wait-to-announce", "cumulus-wait-to-announce",
async move { async move {
let t1 = wait_to_announce::<Block>( let t1 = wait_to_announce::<Block>(
hash, block_hash,
relay_chain_leaf, pov_hash,
announce_block, announce_block,
collator_network, overseer_handler,
&head_data,
) )
.fuse(); .fuse();
let t2 = rx.fuse(); let t2 = rx.fuse();
@@ -349,24 +417,30 @@ impl<Block: BlockT> WaitToAnnounce<Block> {
} }
async fn wait_to_announce<Block: BlockT>( async fn wait_to_announce<Block: BlockT>(
hash: <Block as BlockT>::Hash, block_hash: <Block as BlockT>::Hash,
relay_chain_leaf: PHash, pov_hash: PHash,
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>, announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
collator_network: Arc<dyn CollatorNetwork>, mut overseer_handler: OverseerHandler,
head_data: &Vec<u8>,
) { ) {
let mut checked_statements = collator_network.checked_statements(relay_chain_leaf); let (sender, mut receiver) = mpsc::channel(5);
if overseer_handler
.send_msg(StatementDistributionMessage::RegisterStatementListener(
sender,
))
.await
.is_err()
{
warn!(
target: "cumulus-network",
"Failed to register the statement listener!",
);
return;
}
while let Some(statement) = checked_statements.next().await { while let Some(statement) = receiver.next().await {
match &statement.statement { match &statement.payload() {
Statement::Candidate(c) if &c.head_data.0 == head_data => { Statement::Seconded(c) if &c.descriptor.pov_hash == &pov_hash => {
let gossip_message: GossipMessage = GossipStatement { announce_block(block_hash, statement.encode());
relay_chain_leaf,
signed_statement: statement,
}
.into();
announce_block(hash, gossip_message.encode());
break; break;
} }
+187 -237
View File
@@ -16,21 +16,25 @@
use super::*; use super::*;
use cumulus_test_runtime::{Block, Header}; use cumulus_test_runtime::{Block, Header};
use polkadot_primitives::v0::{ use futures::executor::block_on;
AbridgedCandidateReceipt, Block as PBlock, Chain, CollatorId, DutyRoster, GlobalValidationData, use polkadot_node_primitives::{SignedFullStatement, Statement};
Hash as PHash, Header as PHeader, Id as ParaId, LocalValidationData, ParachainHost, Retriable, use polkadot_primitives::v1::{
SigningContext, ValidationCode, ValidatorId, AuthorityDiscoveryId, Block as PBlock, BlockNumber, CandidateCommitments, CandidateDescriptor,
CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash as PHash,
HeadData, Header as PHeader, Id as ParaId, OccupiedCoreAssumption, ParachainHost,
PersistedValidationData, SessionIndex, SigningContext, ValidationCode, ValidationData,
ValidationOutputs, ValidatorId, ValidatorIndex, InboundDownwardMessage,
}; };
use polkadot_test_runtime_client::{
DefaultTestClientBuilderExt, TestClient, TestClientBuilder, TestClientBuilderExt,
};
use polkadot_validation::{sign_table_statement, Statement};
use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_api::{ApiRef, ProvideRuntimeApi};
use sp_blockchain::{Error as ClientError, HeaderBackend}; use sp_blockchain::{Error as ClientError, HeaderBackend};
use sp_consensus::block_validation::BlockAnnounceValidator; use sp_consensus::block_validation::BlockAnnounceValidator as _;
use sp_core::H256; use sp_core::H256;
use sp_keyring::Sr25519Keyring; use sp_keyring::Sr25519Keyring;
use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::{
traits::{NumberFor, Zero},
RuntimeAppPublic,
};
#[derive(Clone)] #[derive(Clone)]
struct DummyCollatorNetwork; struct DummyCollatorNetwork;
@@ -45,26 +49,16 @@ impl SyncOracle for DummyCollatorNetwork {
} }
} }
fn make_validator() -> JustifiedBlockAnnounceValidator<Block, TestApi> { fn make_validator_and_api() -> (BlockAnnounceValidator<Block, TestApi>, Arc<TestApi>) {
let (validator, _client) = make_validator_and_client(); let api = Arc::new(TestApi::new());
validator
}
fn make_validator_and_client() -> (
JustifiedBlockAnnounceValidator<Block, TestApi>,
Arc<TestApi>,
) {
let builder = TestClientBuilder::new();
let client = Arc::new(TestApi::new(Arc::new(builder.build())));
( (
JustifiedBlockAnnounceValidator::new( BlockAnnounceValidator::new(
client.clone(), api.clone(),
ParaId::from(56), ParaId::from(56),
Box::new(DummyCollatorNetwork), Box::new(DummyCollatorNetwork),
), ),
client, api,
) )
} }
@@ -79,42 +73,58 @@ fn default_header() -> Header {
} }
fn make_gossip_message_and_header( fn make_gossip_message_and_header(
client: Arc<TestApi>, api: Arc<TestApi>,
relay_chain_leaf: H256, relay_parent: H256,
) -> (GossipMessage, Header) { validator_index: u32,
let key = Sr25519Keyring::Alice.pair().into(); ) -> (SignedFullStatement, Header) {
let signing_context = client let keystore: SyncCryptoStorePtr = Arc::new(KeyStore::new());
let alice_public = SyncCryptoStore::sr25519_generate_new(
&*keystore,
ValidatorId::ID,
Some(&Sr25519Keyring::Alice.to_seed()),
)
.unwrap();
let session_index = api
.runtime_api() .runtime_api()
.signing_context(&BlockId::Hash(relay_chain_leaf)) .session_index_for_child(&BlockId::Hash(relay_parent))
.unwrap(); .unwrap();
let header = default_header(); let signing_context = SigningContext {
let candidate_receipt = AbridgedCandidateReceipt { parent_hash: relay_parent,
head_data: header.encode().into(), session_index,
..AbridgedCandidateReceipt::default()
};
let statement = Statement::Candidate(candidate_receipt);
let signature = sign_table_statement(&statement, &key, &signing_context);
let sender = 0;
let gossip_statement = GossipStatement {
relay_chain_leaf,
signed_statement: SignedStatement {
statement,
signature,
sender,
},
}; };
(GossipMessage::Statement(gossip_statement), header) let header = default_header();
let candidate_receipt = CommittedCandidateReceipt {
commitments: CandidateCommitments {
head_data: header.encode().into(),
..Default::default()
},
descriptor: CandidateDescriptor {
relay_parent,
..Default::default()
},
};
let statement = Statement::Seconded(candidate_receipt);
let signed = block_on(SignedFullStatement::sign(
&keystore,
statement,
&signing_context,
validator_index,
&alice_public.into(),
))
.expect("Signing statement");
(signed, header)
} }
#[test] #[test]
fn valid_if_no_data_and_less_than_best_known_number() { fn valid_if_no_data_and_less_than_best_known_number() {
let mut validator = make_validator(); let mut validator = make_validator_and_api().0;
let header = Header { let header = Header {
number: 0, number: 0,
..default_header() ..default_header()
}; };
let res = validator.validate(&header, &[]); let res = block_on(validator.validate(&header, &[]));
assert_eq!( assert_eq!(
res.unwrap(), res.unwrap(),
@@ -125,12 +135,12 @@ fn valid_if_no_data_and_less_than_best_known_number() {
#[test] #[test]
fn invalid_if_no_data_exceeds_best_known_number() { fn invalid_if_no_data_exceeds_best_known_number() {
let mut validator = make_validator(); let mut validator = make_validator_and_api().0;
let header = Header { let header = Header {
number: 1, number: 1,
..default_header() ..default_header()
}; };
let res = validator.validate(&header, &[]); let res = block_on(validator.validate(&header, &[]));
assert_eq!( assert_eq!(
res.unwrap(), res.unwrap(),
@@ -140,28 +150,26 @@ fn invalid_if_no_data_exceeds_best_known_number() {
} }
#[test] #[test]
fn check_gossip_message_is_valid() { fn check_statement_is_encoded_correctly() {
let mut validator = make_validator(); let mut validator = make_validator_and_api().0;
let header = default_header(); let header = default_header();
let res = validator.validate(&header, &[0x42]).err(); let res = block_on(validator.validate(&header, &[0x42]))
.err()
.expect("Should fail on invalid encoded statement");
assert!(
res.is_some(),
"only data that are gossip message are allowed"
);
assert!(matches!( assert!(matches!(
*res.unwrap().downcast::<ClientError>().unwrap(), *res.downcast::<ClientError>().unwrap(),
ClientError::BadJustification(x) if x.contains("must be a gossip message") ClientError::BadJustification(x) if x.contains("must be a `SignedFullStatement`")
)); ));
} }
#[test] #[test]
fn check_relay_parent_is_head() { fn check_relay_parent_is_head() {
let (mut validator, client) = make_validator_and_client(); let (mut validator, api) = make_validator_and_api();
let relay_chain_leaf = H256::zero(); let relay_chain_leaf = H256::zero();
let (gossip_message, header) = make_gossip_message_and_header(client, relay_chain_leaf); let (gossip_message, header) = make_gossip_message_and_header(api, relay_chain_leaf, 0);
let data = gossip_message.encode(); let data = gossip_message.encode();
let res = validator.validate(&header, data.as_slice()); let res = block_on(validator.validate(&header, data.as_slice()));
assert_eq!( assert_eq!(
res.unwrap(), res.unwrap(),
@@ -172,199 +180,136 @@ fn check_relay_parent_is_head() {
#[test] #[test]
fn check_relay_parent_actually_exists() { fn check_relay_parent_actually_exists() {
let (mut validator, client) = make_validator_and_client(); let (mut validator, api) = make_validator_and_api();
let relay_chain_leaf = H256::from_low_u64_be(42); let relay_parent = H256::from_low_u64_be(42);
let (gossip_message, header) = make_gossip_message_and_header(client, relay_chain_leaf); let (signed_statement, header) = make_gossip_message_and_header(api, relay_parent, 0);
let data = gossip_message.encode(); let data = signed_statement.encode();
let res = validator.validate(&header, data.as_slice()).err(); let res = block_on(validator.validate(&header, &data))
.err()
.expect("Should fail on unknown relay parent");
assert!(
res.is_some(),
"validation should fail if the relay chain leaf does not exist"
);
assert!(matches!( assert!(matches!(
*res.unwrap().downcast::<ClientError>().unwrap(), *res.downcast::<ClientError>().unwrap(),
ClientError::UnknownBlock(_) ClientError::UnknownBlock(_)
)); ));
} }
#[test] #[test]
fn check_relay_parent_fails_if_cannot_retrieve_number() { fn check_relay_parent_fails_if_cannot_retrieve_number() {
let (mut validator, client) = make_validator_and_client(); let (mut validator, api) = make_validator_and_api();
let relay_chain_leaf = H256::from_low_u64_be(0xdead); let relay_parent = H256::from_low_u64_be(0xdead);
let (gossip_message, header) = make_gossip_message_and_header(client, relay_chain_leaf); let (signed_statement, header) = make_gossip_message_and_header(api, relay_parent, 0);
let data = gossip_message.encode(); let data = signed_statement.encode();
let res = validator.validate(&header, data.as_slice()).err(); let res = block_on(validator.validate(&header, &data))
.err()
.expect("Should fail when the relay chain number could not be retrieved");
assert!(
res.is_some(),
"validation should fail if the relay chain leaf does not exist"
);
assert!(matches!( assert!(matches!(
*res.unwrap().downcast::<ClientError>().unwrap(), *res.downcast::<ClientError>().unwrap(),
ClientError::Backend(_) ClientError::Backend(_)
)); ));
} }
#[test] #[test]
fn check_signer_is_legit_validator() { fn check_signer_is_legit_validator() {
let (mut validator, client) = make_validator_and_client(); let (mut validator, api) = make_validator_and_api();
let relay_chain_leaf = H256::from_low_u64_be(1); let relay_parent = H256::from_low_u64_be(1);
let key = Sr25519Keyring::Alice.pair().into(); let (signed_statement, header) = make_gossip_message_and_header(api, relay_parent, 1);
let signing_context = client let data = signed_statement.encode();
.runtime_api()
.signing_context(&BlockId::Hash(relay_chain_leaf))
.unwrap();
let header = default_header();
let candidate_receipt = AbridgedCandidateReceipt {
head_data: header.encode().into(),
..AbridgedCandidateReceipt::default()
};
let statement = Statement::Candidate(candidate_receipt);
let signature = sign_table_statement(&statement, &key, &signing_context);
let sender = 1;
let gossip_statement = GossipStatement {
relay_chain_leaf,
signed_statement: SignedStatement {
statement,
signature,
sender,
},
};
let gossip_message = GossipMessage::Statement(gossip_statement);
let data = gossip_message.encode(); let res = block_on(validator.validate(&header, &data))
let res = validator.validate(&header, data.as_slice()).err(); .err()
.expect("Should fail on invalid validator");
assert!(
res.is_some(),
"validation should fail if the signer is not a legit validator"
);
assert!(matches!( assert!(matches!(
*res.unwrap().downcast::<ClientError>().unwrap(), *res.downcast::<ClientError>().unwrap(),
ClientError::BadJustification(x) if x.contains("signer is a validator") ClientError::BadJustification(x) if x.contains("signer is a validator")
)); ));
} }
#[test] #[test]
fn check_statement_is_correctly_signed() { fn check_statement_is_correctly_signed() {
let (mut validator, client) = make_validator_and_client(); let (mut validator, api) = make_validator_and_api();
let header = default_header(); let relay_parent = H256::from_low_u64_be(1);
let relay_chain_leaf = H256::from_low_u64_be(1);
let key = Sr25519Keyring::Alice.pair().into(); let (signed_statement, header) = make_gossip_message_and_header(api, relay_parent, 0);
let signing_context = client
.runtime_api()
.signing_context(&BlockId::Hash(relay_chain_leaf))
.unwrap();
let mut candidate_receipt = AbridgedCandidateReceipt::default();
let statement = Statement::Candidate(candidate_receipt.clone());
let signature = sign_table_statement(&statement, &key, &signing_context);
// alterate statement so the signature doesn't match anymore let mut data = signed_statement.encode();
candidate_receipt = AbridgedCandidateReceipt {
parachain_index: candidate_receipt.parachain_index + 1,
..candidate_receipt
};
let statement = Statement::Candidate(candidate_receipt);
let sender = 0; // The signature comes at the end of the type, so change a bit to make the signature invalid.
let gossip_statement = GossipStatement { let last = data.len() - 1;
relay_chain_leaf, data[last] = data[last].wrapping_add(1);
signed_statement: SignedStatement {
statement,
signature,
sender,
},
};
let gossip_message = GossipMessage::Statement(gossip_statement);
let data = gossip_message.encode(); let res = block_on(validator.validate(&header, &data))
let res = validator.validate(&header, data.as_slice()).err(); .err()
.expect("Validation should fail if the statement is not signed correctly");
assert!(
res.is_some(),
"validation should fail if the statement is not correctly signed"
);
assert!(matches!( assert!(matches!(
*res.unwrap().downcast::<ClientError>().unwrap(), *res.downcast::<ClientError>().unwrap(),
ClientError::BadJustification(x) if x.contains("signature is invalid") ClientError::BadJustification(x) if x.contains("signature is invalid")
)); ));
} }
#[test] #[test]
fn check_statement_is_a_candidate_message() { fn check_statement_seconded() {
let (mut validator, client) = make_validator_and_client(); let (mut validator, api) = make_validator_and_api();
let header = default_header(); let header = default_header();
let relay_chain_leaf = H256::from_low_u64_be(1); let relay_parent = H256::from_low_u64_be(1);
let key = Sr25519Keyring::Alice.pair().into(); let keystore: SyncCryptoStorePtr = Arc::new(KeyStore::new());
let signing_context = client let alice_public = SyncCryptoStore::sr25519_generate_new(
&*keystore,
ValidatorId::ID,
Some(&Sr25519Keyring::Alice.to_seed()),
)
.unwrap();
let session_index = api
.runtime_api() .runtime_api()
.signing_context(&BlockId::Hash(relay_chain_leaf)) .session_index_for_child(&BlockId::Hash(relay_parent))
.unwrap(); .unwrap();
let statement = Statement::Valid(H256::zero()); let signing_context = SigningContext {
let signature = sign_table_statement(&statement, &key, &signing_context); parent_hash: relay_parent,
let sender = 0; session_index,
let gossip_statement = GossipStatement {
relay_chain_leaf,
signed_statement: SignedStatement {
statement,
signature,
sender,
},
}; };
let gossip_message = GossipMessage::Statement(gossip_statement);
let data = gossip_message.encode(); let statement = Statement::Valid(Default::default());
let res = validator.validate(&header, data.as_slice()).err();
let signed_statement = block_on(SignedFullStatement::sign(
&keystore,
statement,
&signing_context,
0,
&alice_public.into(),
))
.expect("Signs statement");
let data = signed_statement.encode();
let res = block_on(validator.validate(&header, &data))
.err()
.expect("validation should fail if not seconded statement");
assert!(
res.is_some(),
"validation should fail if the statement is not a candidate message"
);
assert!(matches!( assert!(matches!(
*res.unwrap().downcast::<ClientError>().unwrap(), *res.downcast::<ClientError>().unwrap(),
ClientError::BadJustification(x) if x.contains("must be a candidate statement") ClientError::BadJustification(x) if x.contains("must be a `Statement::Seconded`")
)); ));
} }
#[test] #[test]
fn check_header_match_candidate_receipt_header() { fn check_header_match_candidate_receipt_header() {
let (mut validator, client) = make_validator_and_client(); let (mut validator, api) = make_validator_and_api();
let relay_chain_leaf = H256::from_low_u64_be(1); let relay_parent = H256::from_low_u64_be(1);
let key = Sr25519Keyring::Alice.pair().into(); let (signed_statement, mut header) = make_gossip_message_and_header(api, relay_parent, 0);
let signing_context = client let data = signed_statement.encode();
.runtime_api() header.number = 300;
.signing_context(&BlockId::Hash(relay_chain_leaf))
.unwrap();
let header = default_header();
let candidate_receipt = AbridgedCandidateReceipt::default();
let statement = Statement::Candidate(candidate_receipt);
let signature = sign_table_statement(&statement, &key, &signing_context);
let sender = 0;
let gossip_statement = GossipStatement {
relay_chain_leaf,
signed_statement: SignedStatement {
statement,
signature,
sender,
},
};
let gossip_message = GossipMessage::Statement(gossip_statement);
let data = gossip_message.encode(); let res = block_on(validator.validate(&header, &data))
let res = validator.validate(&header, data.as_slice()).err(); .err()
.expect("validation should fail if the header in doesn't match");
assert!(
res.is_some(),
"validation should fail if the header in the candidate_receipt doesn't \
match the header provided"
);
assert!(matches!( assert!(matches!(
*res.unwrap().downcast::<ClientError>().unwrap(), *res.downcast::<ClientError>().unwrap(),
ClientError::BadJustification(x) if x.contains("header does not match") ClientError::BadJustification(x) if x.contains("header does not match")
)); ));
} }
@@ -372,31 +317,25 @@ fn check_header_match_candidate_receipt_header() {
#[derive(Default)] #[derive(Default)]
struct ApiData { struct ApiData {
validators: Vec<ValidatorId>, validators: Vec<ValidatorId>,
duties: Vec<Chain>,
active_parachains: Vec<(ParaId, Option<(CollatorId, Retriable)>)>,
} }
#[derive(Clone)]
struct TestApi { struct TestApi {
data: Arc<Mutex<ApiData>>, data: Arc<ApiData>,
client: Arc<TestClient>,
} }
impl TestApi { impl TestApi {
fn new(client: Arc<TestClient>) -> Self { fn new() -> Self {
Self { Self {
client, data: Arc::new(ApiData {
data: Arc::new(Mutex::new(ApiData {
validators: vec![Sr25519Keyring::Alice.public().into()], validators: vec![Sr25519Keyring::Alice.public().into()],
..Default::default() }),
})),
} }
} }
} }
#[derive(Default)] #[derive(Default)]
struct RuntimeApi { struct RuntimeApi {
data: Arc<Mutex<ApiData>>, data: Arc<ApiData>,
} }
impl ProvideRuntimeApi<PBlock> for TestApi { impl ProvideRuntimeApi<PBlock> for TestApi {
@@ -415,48 +354,59 @@ sp_api::mock_impl_runtime_apis! {
type Error = sp_blockchain::Error; type Error = sp_blockchain::Error;
fn validators(&self) -> Vec<ValidatorId> { fn validators(&self) -> Vec<ValidatorId> {
self.data.lock().validators.clone() self.data.validators.clone()
} }
fn duty_roster(&self) -> DutyRoster { fn validator_groups(&self) -> (Vec<Vec<ValidatorIndex>>, GroupRotationInfo<BlockNumber>) {
DutyRoster { (Vec::new(), GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 0, now: 0 })
validator_duty: self.data.lock().duties.clone(),
}
} }
fn active_parachains(&self) -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { fn availability_cores(&self) -> Vec<CoreState<BlockNumber>> {
self.data.lock().active_parachains.clone() Vec::new()
} }
fn parachain_code(_: ParaId) -> Option<ValidationCode> { fn full_validation_data(&self, _: ParaId, _: OccupiedCoreAssumption) -> Option<ValidationData<BlockNumber>> {
Some(ValidationCode(Vec::new())) None
} }
fn global_validation_data() -> GlobalValidationData { fn persisted_validation_data(&self, _: ParaId, _: OccupiedCoreAssumption) -> Option<PersistedValidationData<BlockNumber>> {
Default::default() Some(PersistedValidationData {
} parent_head: HeadData(default_header().encode()),
fn local_validation_data(_: ParaId) -> Option<LocalValidationData> {
Some(LocalValidationData {
parent_head: HeadData::<Block> { header: default_header() }.encode().into(),
..Default::default() ..Default::default()
}) })
} }
fn get_heads(_: Vec<<PBlock as BlockT>::Extrinsic>) -> Option<Vec<AbridgedCandidateReceipt>> { fn session_index_for_child(&self) -> SessionIndex {
Some(Vec::new()) 0
} }
fn signing_context() -> SigningContext { fn validation_code(&self, _: ParaId, _: OccupiedCoreAssumption) -> Option<ValidationCode> {
SigningContext { None
session_index: Default::default(),
parent_hash: Default::default(),
}
} }
fn downward_messages(_: ParaId) -> Vec<polkadot_primitives::v0::DownwardMessage> { fn candidate_pending_availability(&self, _: ParaId) -> Option<CommittedCandidateReceipt<PHash>> {
None
}
fn candidate_events(&self) -> Vec<CandidateEvent<PHash>> {
Vec::new() Vec::new()
} }
fn validator_discovery(_: Vec<ValidatorId>) -> Vec<Option<AuthorityDiscoveryId>> {
Vec::new()
}
fn check_validation_outputs(_: ParaId, _: ValidationOutputs) -> bool {
false
}
fn dmq_contents(_: ParaId) -> Vec<InboundDownwardMessage<BlockNumber>> {
Vec::new()
}
fn historical_validation_code(_: ParaId, _: BlockNumber) -> Option<ValidationCode> {
None
}
} }
} }
+13 -13
View File
@@ -11,27 +11,27 @@ cumulus-primitives = { path = "../primitives", default-features = false }
cumulus-runtime = { path = "../runtime", default-features = false } cumulus-runtime = { path = "../runtime", default-features = false }
# Polkadot dependencies # Polkadot dependencies
parachain = { package = "polkadot-parachain", git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false } parachain = { package = "polkadot-parachain", git = "https://github.com/paritytech/polkadot", default-features = false , branch = "bkchr-adder-collator-integration-test" }
# Substrate dependencies # Substrate dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", version = "2.0.0-rc5", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", version = "2.0.0-rc5", default-features = false , branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sp-io = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
sp-version = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sp-version = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
# Other Dependencies # Other Dependencies
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"]} codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"]}
serde = { version = "1.0.101", optional = true, features = ["derive"] } serde = { version = "1.0.101", optional = true, features = ["derive"] }
[dev-dependencies] [dev-dependencies]
sp-externalities = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sp-externalities = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
substrate-test-runtime-client = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } substrate-test-runtime-client = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
sp-version = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sp-version = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" }
[features] [features]
default = ['std'] default = ['std']
+96 -64
View File
@@ -30,12 +30,13 @@
//! Users must ensure that they register this pallet as an inherent provider. //! Users must ensure that they register this pallet as an inherent provider.
use cumulus_primitives::{ use cumulus_primitives::{
inherents::VALIDATION_FUNCTION_PARAMS_IDENTIFIER as INHERENT_IDENTIFIER, inherents::VALIDATION_DATA_IDENTIFIER as INHERENT_IDENTIFIER,
validation_function_params::{OnValidationFunctionParams, ValidationFunctionParams}, well_known_keys::{NEW_VALIDATION_CODE, VALIDATION_DATA},
well_known_keys::{NEW_VALIDATION_CODE, VALIDATION_FUNCTION_PARAMS}, OnValidationData, ValidationData,
}; };
use frame_support::{ use frame_support::{
decl_error, decl_event, decl_module, decl_storage, ensure, storage, weights::DispatchClass, decl_error, decl_event, decl_module, decl_storage, ensure, storage,
weights::{DispatchClass, Weight},
}; };
use frame_system::{ensure_none, ensure_root}; use frame_system::{ensure_none, ensure_root};
use parachain::primitives::RelayChainBlockNumber; use parachain::primitives::RelayChainBlockNumber;
@@ -50,10 +51,8 @@ pub trait Trait: frame_system::Trait {
/// The overarching event type. /// The overarching event type.
type Event: From<Event> + Into<<Self as frame_system::Trait>::Event>; type Event: From<Event> + Into<<Self as frame_system::Trait>::Event>;
/// Something which can be notified when the validation function params are set. /// Something which can be notified when the validation data is set.
/// type OnValidationData: OnValidationData;
/// Set this to `()` if not needed.
type OnValidationFunctionParams: OnValidationFunctionParams;
} }
// This pallet's storage items. // This pallet's storage items.
@@ -64,8 +63,11 @@ decl_storage! {
PendingValidationFunction get(fn new_validation_function): PendingValidationFunction get(fn new_validation_function):
Option<(RelayChainBlockNumber, Vec<u8>)>; Option<(RelayChainBlockNumber, Vec<u8>)>;
/// Were the VFPs updated this block? /// Were the [`ValidationData`] updated in this block?
DidUpdateVFPs: bool; DidUpdateValidationData: bool;
/// Were the validation data set to notify the relay chain?
DidSetValidationCode: bool;
} }
} }
@@ -79,7 +81,8 @@ decl_module! {
// TODO: figure out a better weight than this // TODO: figure out a better weight than this
#[weight = (0, DispatchClass::Operational)] #[weight = (0, DispatchClass::Operational)]
pub fn schedule_upgrade(origin, validation_function: Vec<u8>) { pub fn schedule_upgrade(origin, validation_function: Vec<u8>) {
System::<T>::can_set_code(origin, &validation_function)?; ensure_root(origin)?;
System::<T>::can_set_code(&validation_function)?;
Self::schedule_upgrade_impl(validation_function)?; Self::schedule_upgrade_impl(validation_function)?;
} }
@@ -93,7 +96,7 @@ decl_module! {
Self::schedule_upgrade_impl(validation_function)?; Self::schedule_upgrade_impl(validation_function)?;
} }
/// Set the current validation function parameters /// Set the current validation data.
/// ///
/// This should be invoked exactly once per block. It will panic at the finalization /// This should be invoked exactly once per block. It will panic at the finalization
/// phase if the call was not invoked. /// phase if the call was not invoked.
@@ -103,45 +106,52 @@ decl_module! {
/// As a side effect, this function upgrades the current validation function /// As a side effect, this function upgrades the current validation function
/// if the appropriate time has come. /// if the appropriate time has come.
#[weight = (0, DispatchClass::Mandatory)] #[weight = (0, DispatchClass::Mandatory)]
fn set_validation_function_parameters(origin, vfp: ValidationFunctionParams) { fn set_validation_data(origin, vfp: ValidationData) {
ensure_none(origin)?; ensure_none(origin)?;
assert!(!DidUpdateVFPs::exists(), "VFPs must be updated only once in the block"); assert!(!DidUpdateValidationData::exists(), "ValidationData must be updated only once in a block");
// initialization logic: we know that this runs exactly once every block, // initialization logic: we know that this runs exactly once every block,
// which means we can put the initialization logic here to remove the // which means we can put the initialization logic here to remove the
// sequencing problem. // sequencing problem.
if let Some((apply_block, validation_function)) = PendingValidationFunction::get() { if let Some((apply_block, validation_function)) = PendingValidationFunction::get() {
if vfp.relay_chain_height >= apply_block { if vfp.persisted.block_number >= apply_block {
PendingValidationFunction::kill(); PendingValidationFunction::kill();
Self::put_parachain_code(&validation_function); Self::put_parachain_code(&validation_function);
Self::deposit_event(Event::ValidationFunctionApplied(vfp.relay_chain_height)); Self::deposit_event(Event::ValidationFunctionApplied(vfp.persisted.block_number));
} }
} }
storage::unhashed::put(VALIDATION_FUNCTION_PARAMS, &vfp); storage::unhashed::put(VALIDATION_DATA, &vfp);
DidUpdateVFPs::put(true); DidUpdateValidationData::put(true);
<T::OnValidationFunctionParams as OnValidationFunctionParams>::on_validation_function_params(vfp); <T::OnValidationData as OnValidationData>::on_validation_data(vfp);
} }
fn on_finalize() { fn on_finalize() {
assert!(DidUpdateVFPs::take(), "VFPs must be updated once per block"); assert!(DidUpdateValidationData::take(), "VFPs must be updated once per block");
DidSetValidationCode::take();
}
fn on_initialize(n: T::BlockNumber) -> Weight {
// To prevent removing `NEW_VALIDATION_CODE` that was set by another `on_initialize` like
// for example from scheduler, we only kill the storage entry if it was not yet updated
// in the current block.
if !DidSetValidationCode::get() {
storage::unhashed::kill(NEW_VALIDATION_CODE);
}
storage::unhashed::kill(VALIDATION_DATA);
0
} }
} }
} }
impl<T: Trait> Module<T> { impl<T: Trait> Module<T> {
/// Get validation function parameters. /// Get validation data.
/// ///
/// This will return `None` if this module's inherent has not yet run. /// Returns `Some(_)` after the inherent set the data for the current block.
/// If it returns `Some(_)`, the validation function params are current for this block. pub fn validation_data() -> Option<ValidationData> {
pub fn validation_function_params() -> Option<ValidationFunctionParams> { storage::unhashed::get(VALIDATION_DATA)
if DidUpdateVFPs::get() {
// this storage value is set by cumulus during block validation,
// and also by the inherent from this module.
storage::unhashed::get(VALIDATION_FUNCTION_PARAMS)
} else {
None
}
} }
/// Put a new validation function into a particular location where polkadot /// Put a new validation function into a particular location where polkadot
@@ -149,6 +159,7 @@ impl<T: Trait> Module<T> {
/// upgrade has been scheduled. /// upgrade has been scheduled.
fn notify_polkadot_of_pending_upgrade(code: &[u8]) { fn notify_polkadot_of_pending_upgrade(code: &[u8]) {
storage::unhashed::put_raw(NEW_VALIDATION_CODE, code); storage::unhashed::put_raw(NEW_VALIDATION_CODE, code);
DidSetValidationCode::put(true);
} }
/// Put a new validation function into a particular location where this /// Put a new validation function into a particular location where this
@@ -159,22 +170,33 @@ impl<T: Trait> Module<T> {
/// `true` when a code upgrade is currently legal /// `true` when a code upgrade is currently legal
pub fn can_set_code() -> bool { pub fn can_set_code() -> bool {
Self::validation_function_params() Self::validation_data()
.map(|vfp| vfp.code_upgrade_allowed.is_some()) .map(|vfp| vfp.transient.code_upgrade_allowed.is_some())
.unwrap_or_default() .unwrap_or_default()
} }
/// The maximum code size permitted, in bytes. /// The maximum code size permitted, in bytes.
pub fn max_code_size() -> Option<u32> { pub fn max_code_size() -> Option<u32> {
Self::validation_function_params().map(|vfp| vfp.max_code_size) Self::validation_data().map(|vfp| vfp.transient.max_code_size)
} }
/// The implementation of the runtime upgrade scheduling. /// The implementation of the runtime upgrade scheduling.
fn schedule_upgrade_impl(validation_function: Vec<u8>) -> frame_support::dispatch::DispatchResult { fn schedule_upgrade_impl(
ensure!(!PendingValidationFunction::exists(), Error::<T>::OverlappingUpgrades); validation_function: Vec<u8>,
let vfp = Self::validation_function_params().ok_or(Error::<T>::ValidationFunctionParamsNotAvailable)?; ) -> frame_support::dispatch::DispatchResult {
ensure!(validation_function.len() <= vfp.max_code_size as usize, Error::<T>::TooBig); ensure!(
let apply_block = vfp.code_upgrade_allowed.ok_or(Error::<T>::ProhibitedByPolkadot)?; !PendingValidationFunction::exists(),
Error::<T>::OverlappingUpgrades
);
let vfp = Self::validation_data().ok_or(Error::<T>::ValidationDataNotAvailable)?;
ensure!(
validation_function.len() <= vfp.transient.max_code_size as usize,
Error::<T>::TooBig
);
let apply_block = vfp
.transient
.code_upgrade_allowed
.ok_or(Error::<T>::ProhibitedByPolkadot)?;
// When a code upgrade is scheduled, it has to be applied in two // When a code upgrade is scheduled, it has to be applied in two
// places, synchronized: both polkadot and the individual parachain // places, synchronized: both polkadot and the individual parachain
@@ -197,15 +219,13 @@ impl<T: Trait> ProvideInherent for Module<T> {
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
fn create_inherent(data: &InherentData) -> Option<Self::Call> { fn create_inherent(data: &InherentData) -> Option<Self::Call> {
// If the inherent is not present, this returns None early. This in turn will let data: ValidationData = data
// cause the on_finalize assertion to fail.
let vfp: ValidationFunctionParams = data
.get_data(&INHERENT_IDENTIFIER) .get_data(&INHERENT_IDENTIFIER)
.ok() .ok()
.flatten() .flatten()
.expect("validation function params are always injected into inherent data; qed"); .expect("validation function params are always injected into inherent data; qed");
Some(Call::set_validation_function_parameters(vfp)) Some(Call::set_validation_data(data))
} }
} }
@@ -226,8 +246,8 @@ decl_error! {
ProhibitedByPolkadot, ProhibitedByPolkadot,
/// The supplied validation function has compiled into a blob larger than Polkadot is willing to run /// The supplied validation function has compiled into a blob larger than Polkadot is willing to run
TooBig, TooBig,
/// The inherent which supplies the validation function params did not run this block /// The inherent which supplies the validation data did not run this block
ValidationFunctionParamsNotAvailable, ValidationDataNotAvailable,
} }
} }
@@ -237,6 +257,7 @@ mod tests {
use super::*; use super::*;
use codec::Encode; use codec::Encode;
use cumulus_primitives::{PersistedValidationData, TransientValidationData};
use frame_support::{ use frame_support::{
assert_ok, assert_ok,
dispatch::UnfilteredDispatchable, dispatch::UnfilteredDispatchable,
@@ -305,7 +326,7 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength; type MaximumBlockLength = MaximumBlockLength;
type AvailableBlockRatio = AvailableBlockRatio; type AvailableBlockRatio = AvailableBlockRatio;
type Version = Version; type Version = Version;
type ModuleToIndex = (); type PalletInfo = ();
type AccountData = (); type AccountData = ();
type OnNewAccount = (); type OnNewAccount = ();
type OnKilledAccount = (); type OnKilledAccount = ();
@@ -317,7 +338,7 @@ mod tests {
} }
impl Trait for Test { impl Trait for Test {
type Event = TestEvent; type Event = TestEvent;
type OnValidationFunctionParams = (); type OnValidationData = ();
} }
type ParachainUpgrade = Module<Test>; type ParachainUpgrade = Module<Test>;
@@ -376,8 +397,7 @@ mod tests {
tests: Vec<BlockTest>, tests: Vec<BlockTest>,
pending_upgrade: Option<RelayChainBlockNumber>, pending_upgrade: Option<RelayChainBlockNumber>,
ran: bool, ran: bool,
vfp_maker: vfp_maker: Option<Box<dyn Fn(&BlockTests, RelayChainBlockNumber) -> ValidationData>>,
Option<Box<dyn Fn(&BlockTests, RelayChainBlockNumber) -> ValidationFunctionParams>>,
} }
impl BlockTests { impl BlockTests {
@@ -418,9 +438,9 @@ mod tests {
}) })
} }
fn with_validation_function_params<F>(mut self, f: F) -> Self fn with_validation_data<F>(mut self, f: F) -> Self
where where
F: 'static + Fn(&BlockTests, RelayChainBlockNumber) -> ValidationFunctionParams, F: 'static + Fn(&BlockTests, RelayChainBlockNumber) -> ValidationData,
{ {
self.vfp_maker = Some(Box::new(f)); self.vfp_maker = Some(Box::new(f));
self self
@@ -453,18 +473,24 @@ mod tests {
// now mess with the storage the way validate_block does // now mess with the storage the way validate_block does
let vfp = match self.vfp_maker { let vfp = match self.vfp_maker {
None => ValidationFunctionParams { None => ValidationData {
max_code_size: 10 * 1024 * 1024, // 10 mb persisted: PersistedValidationData {
relay_chain_height: *n as RelayChainBlockNumber, block_number: *n as RelayChainBlockNumber,
code_upgrade_allowed: if self.pending_upgrade.is_some() { ..Default::default()
None },
} else { transient: TransientValidationData {
Some(*n as RelayChainBlockNumber + 1000) max_code_size: 10 * 1024 * 1024, // 10 mb
code_upgrade_allowed: if self.pending_upgrade.is_some() {
None
} else {
Some(*n as RelayChainBlockNumber + 1000)
},
..Default::default()
}, },
}, },
Some(ref maker) => maker(self, *n as RelayChainBlockNumber), Some(ref maker) => maker(self, *n as RelayChainBlockNumber),
}; };
storage::unhashed::put(VALIDATION_FUNCTION_PARAMS, &vfp); storage::unhashed::put(VALIDATION_DATA, &vfp);
storage::unhashed::kill(NEW_VALIDATION_CODE); storage::unhashed::kill(NEW_VALIDATION_CODE);
// It is insufficient to push the validation function params // It is insufficient to push the validation function params
@@ -491,7 +517,7 @@ mod tests {
if self.pending_upgrade.is_some() { if self.pending_upgrade.is_some() {
panic!("attempted to set validation code while upgrade was pending"); panic!("attempted to set validation code while upgrade was pending");
} }
self.pending_upgrade = vfp.code_upgrade_allowed; self.pending_upgrade = vfp.transient.code_upgrade_allowed;
} }
// clean up // clean up
@@ -621,10 +647,16 @@ mod tests {
#[test] #[test]
fn checks_size() { fn checks_size() {
BlockTests::new() BlockTests::new()
.with_validation_function_params(|_, n| ValidationFunctionParams { .with_validation_data(|_, n| ValidationData {
max_code_size: 32, persisted: PersistedValidationData {
relay_chain_height: n, block_number: n,
code_upgrade_allowed: Some(n + 1000), ..Default::default()
},
transient: TransientValidationData {
max_code_size: 32,
code_upgrade_allowed: Some(n + 1000),
..Default::default()
},
}) })
.add(123, || { .add(123, || {
assert_eq!( assert_eq!(
+8 -8
View File
@@ -6,26 +6,26 @@ edition = "2018"
[dependencies] [dependencies]
# Substrate dependencies # Substrate dependencies
sc-service = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", optional = true } sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false } sc-chain-spec = { git = "https://github.com/paritytech/substrate", optional = true, branch = "master" }
# Polkadot dependencies # Polkadot dependencies
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "bkchr-adder-collator-integration-test" }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "bkchr-adder-collator-integration-test" }
# Other dependencies # Other dependencies
codec = { package = "parity-scale-codec", version = "1.0.5", default-features = false, features = [ "derive" ] } codec = { package = "parity-scale-codec", version = "1.0.5", default-features = false, features = [ "derive" ] }
impl-trait-for-tuples = "0.1.3" impl-trait-for-tuples = "0.1.3"
# Polkadot dependencies # Polkadot dependencies
polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false } polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "bkchr-adder-collator-integration-test" }
[features] [features]
default = [ "std" ] default = [ "std" ]
std = [ std = [
"sc-service", "sc-chain-spec",
"sp-std/std", "sp-std/std",
"codec/std", "codec/std",
"polkadot-primitives/std", "polkadot-primitives/std",
+1 -1
View File
@@ -15,7 +15,7 @@
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>. // along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
use codec::Encode; use codec::Encode;
use sc_service::ChainSpec; use sc_chain_spec::ChainSpec;
use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}; use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero};
/// Generate the genesis state for a given ChainSpec. /// Generate the genesis state for a given ChainSpec.
+13 -25
View File
@@ -25,18 +25,15 @@ pub use polkadot_core_primitives::DownwardMessage;
/// It is "generic" in such a way, that the actual message is encoded in the `data` field. /// It is "generic" in such a way, that the actual message is encoded in the `data` field.
/// Besides the `data` it also holds the `origin` of the message. /// Besides the `data` it also holds the `origin` of the message.
pub use polkadot_parachain::primitives::UpwardMessage as GenericUpwardMessage; pub use polkadot_parachain::primitives::UpwardMessage as GenericUpwardMessage;
pub use polkadot_parachain::primitives::{ pub use polkadot_parachain::primitives::{Id as ParaId, ValidationParams};
Id as ParaId, ParachainDispatchOrigin as UpwardMessageOrigin, pub use polkadot_primitives::v1::{
PersistedValidationData, TransientValidationData, ValidationData,
}; };
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod genesis; pub mod genesis;
pub mod validation_function_params;
pub mod xcmp; pub mod xcmp;
use codec::{Decode, Encode};
use sp_runtime::traits::Block as BlockT;
/// Identifiers and types related to Cumulus Inherents /// Identifiers and types related to Cumulus Inherents
pub mod inherents { pub mod inherents {
use sp_inherents::InherentIdentifier; use sp_inherents::InherentIdentifier;
@@ -47,11 +44,10 @@ pub mod inherents {
/// The type of the inherent downward messages. /// The type of the inherent downward messages.
pub type DownwardMessagesType = sp_std::vec::Vec<crate::DownwardMessage>; pub type DownwardMessagesType = sp_std::vec::Vec<crate::DownwardMessage>;
/// The identifier for the `validation_function_params` inherent. /// The identifier for the `set_validation_data` inherent.
pub const VALIDATION_FUNCTION_PARAMS_IDENTIFIER: InherentIdentifier = *b"valfunp0"; pub const VALIDATION_DATA_IDENTIFIER: InherentIdentifier = *b"valfunp0";
/// The type of the inherent. /// The type of the inherent.
pub type ValidationFunctionParamsType = pub type ValidationDataType = crate::ValidationData;
crate::validation_function_params::ValidationFunctionParams;
} }
/// Well known keys for values in the storage. /// Well known keys for values in the storage.
@@ -61,11 +57,11 @@ pub mod well_known_keys {
/// The upward messages are stored as SCALE encoded `Vec<GenericUpwardMessage>`. /// The upward messages are stored as SCALE encoded `Vec<GenericUpwardMessage>`.
pub const UPWARD_MESSAGES: &'static [u8] = b":cumulus_upward_messages:"; pub const UPWARD_MESSAGES: &'static [u8] = b":cumulus_upward_messages:";
/// Current validation function parameters. /// Current validation data.
pub const VALIDATION_FUNCTION_PARAMS: &'static [u8] = b":cumulus_validation_function_params"; pub const VALIDATION_DATA: &'static [u8] = b":cumulus_validation_data:";
/// Code upgarde (set as appropriate by a pallet). /// Code upgarde (set as appropriate by a pallet).
pub const NEW_VALIDATION_CODE: &'static [u8] = b":cumulus_new_validation_code"; pub const NEW_VALIDATION_CODE: &'static [u8] = b":cumulus_new_validation_code:";
/// The storage key for the processed downward messages. /// The storage key for the processed downward messages.
/// ///
@@ -80,16 +76,8 @@ pub trait DownwardMessageHandler {
fn handle_downward_message(msg: &DownwardMessage); fn handle_downward_message(msg: &DownwardMessage);
} }
/// Something that can send upward messages. /// A trait which is called when the validation data is set.
pub trait UpwardMessageSender<UpwardMessage> { #[impl_trait_for_tuples::impl_for_tuples(30)]
/// Send an upward message to the relay chain. pub trait OnValidationData {
/// fn on_validation_data(data: ValidationData);
/// Returns an error if sending failed.
fn send_upward_message(msg: &UpwardMessage, origin: UpwardMessageOrigin) -> Result<(), ()>;
}
/// The head data of the parachain, stored in the relay chain.
#[derive(Decode, Encode, Debug)]
pub struct HeadData<Block: BlockT> {
pub header: Block::Header,
} }
@@ -1,66 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Validation Function Parameters govern the ability of parachains to upgrade their validation functions.
use codec::{Decode, Encode};
use polkadot_parachain::primitives::{RelayChainBlockNumber, ValidationParams};
use polkadot_primitives::v0::{GlobalValidationData, LocalValidationData};
/// Validation Function Parameters
///
/// This struct is the subset of [`ValidationParams`](polkadot_parachain::ValidationParams)
/// which is of interest when upgrading parachain validation functions.
#[derive(PartialEq, Eq, Encode, Decode, Clone, Copy, Default, sp_runtime::RuntimeDebug)]
pub struct ValidationFunctionParams {
/// The maximum code size permitted, in bytes.
pub max_code_size: u32,
/// The current relay-chain block number.
pub relay_chain_height: RelayChainBlockNumber,
/// Whether a code upgrade is allowed or not, and at which height the upgrade
/// would be applied after, if so. The parachain logic should apply any upgrade
/// issued in this block after the first block
/// with `relay_chain_height` at least this value, if `Some`. if `None`, issue
/// no upgrade.
pub code_upgrade_allowed: Option<RelayChainBlockNumber>,
}
impl From<&ValidationParams> for ValidationFunctionParams {
fn from(vp: &ValidationParams) -> Self {
ValidationFunctionParams {
max_code_size: vp.max_code_size,
relay_chain_height: vp.relay_chain_height,
code_upgrade_allowed: vp.code_upgrade_allowed,
}
}
}
impl From<(GlobalValidationData, LocalValidationData)> for ValidationFunctionParams {
fn from(t: (GlobalValidationData, LocalValidationData)) -> Self {
let (global_validation, local_validation) = t;
ValidationFunctionParams {
max_code_size: global_validation.max_code_size,
relay_chain_height: global_validation.block_number,
code_upgrade_allowed: local_validation.code_upgrade_allowed,
}
}
}
/// A trait which is called when the validation function parameters are set
#[impl_trait_for_tuples::impl_for_tuples(30)]
pub trait OnValidationFunctionParams {
fn on_validation_function_params(vfp: ValidationFunctionParams);
}
+44 -34
View File
@@ -18,44 +18,41 @@ parking_lot = '0.9.0'
trie-root = '0.15.2' trie-root = '0.15.2'
codec = { package = 'parity-scale-codec', version = '1.0.0' } codec = { package = 'parity-scale-codec', version = '1.0.0' }
structopt = "0.3.3" structopt = "0.3.3"
ansi_term = "0.12.1"
serde = { version = "1.0.101", features = ["derive"] } serde = { version = "1.0.101", features = ["derive"] }
hex-literal = "0.2.1" hex-literal = "0.2.1"
# Parachain dependencies # Parachain dependencies
parachain-runtime = { package = "cumulus-test-parachain-runtime", path = "runtime" } parachain-runtime = { package = "cumulus-test-parachain-runtime", path = "runtime" }
parachain-contracts-runtime = { package = "cumulus-contracts-parachain-runtime", path = "contracts-runtime" }
rococo-parachain-primitives = { path = "primitives" } rococo-parachain-primitives = { path = "primitives" }
# Substrate dependencies # Substrate dependencies
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-session = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-session = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-cli = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-executor = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-executor = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-service = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", version = "0.8.0-rc5" } sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-basic-authorship = { git = "https://github.com/paritytech/substrate", version = "0.8.0-rc5" , branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-informant = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-tracing = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
# RPC related dependencies # RPC related dependencies
cumulus-pallet-contracts-rpc = { path = "./pallets/contracts/rpc" } jsonrpc-core = "15.1.0"
jsonrpc-core = "14.2.0"
# Cumulus dependencies # Cumulus dependencies
cumulus-consensus = { path = "../consensus" } cumulus-consensus = { path = "../consensus" }
@@ -65,15 +62,28 @@ cumulus-primitives = { path = "../primitives" }
cumulus-service = { path = "../service" } cumulus-service = { path = "../service" }
# Polkadot dependencies # Polkadot dependencies
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-collator = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-cli = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-cli = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-test-service = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
[build-dependencies] [build-dependencies]
substrate-build-script-utils = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } substrate-build-script-utils = { git = "https://github.com/paritytech/substrate", branch = "master" }
[dev-dependencies] [dev-dependencies]
assert_cmd = "0.12" assert_cmd = "0.12"
nix = "0.17" nix = "0.17"
rand = "0.7.3"
tokio = { version = "0.2.13", features = ["macros"] }
# Polkadot dependencies
polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-test-runtime = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-test-client = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-test-service = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
# Substrate dependencies
pallet-sudo = { git = "https://github.com/paritytech/substrate", branch = "master" }
substrate-test-client = { git = "https://github.com/paritytech/substrate", branch = "master" }
substrate-test-runtime-client = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -1,95 +0,0 @@
[package]
name = 'cumulus-contracts-parachain-runtime'
version = '0.1.0'
authors = ["Parity Technologies <admin@parity.io>"]
edition = '2018'
[dependencies]
serde = { version = "1.0.101", optional = true, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
cumulus-token-dealer = { path = "../pallets/token-dealer", default-features = false}
parachain-info = { path = "../pallets/parachain-info", default-features = false}
rococo-parachain-primitives = { path = "../primitives", default-features = false}
# Substrate dependencies
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-version = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-offchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
pallet-sudo = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
# In-Tree Fork of seal that does not use child trie nor storage transactions
cumulus-pallet-contracts = { path = "../pallets/contracts", default-features = false }
cumulus-pallet-contracts-primitives = { path = "../pallets/contracts/common", default-features = false }
cumulus-pallet-contracts-rpc-runtime-api = { path = "../pallets/contracts/rpc/runtime-api", default-features = false }
# Cumulus dependencies
cumulus-runtime = { path = "../../runtime", default-features = false }
cumulus-parachain-upgrade = { path = "../../parachain-upgrade", default-features = false }
cumulus-message-broker = { path = "../../message-broker", default-features = false }
cumulus-upward-message = { path = "../../upward-message", default-features = false }
cumulus-primitives = { path = "../../primitives", default-features = false }
# Polkadot dependencies
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false }
[build-dependencies]
wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1.0.6" }
[features]
default = [ "std" ]
std = [
"codec/std",
"serde",
"sp-api/std",
"sp-std/std",
"sp-io/std",
"sp-core/std",
"sp-runtime/std",
"sp-version/std",
"sp-offchain/std",
"sp-session/std",
"sp-block-builder/std",
"sp-transaction-pool/std",
"sp-inherents/std",
"frame-support/std",
"frame-executive/std",
"frame-system/std",
"pallet-balances/std",
"cumulus-pallet-contracts/std",
"cumulus-pallet-contracts-primitives/std",
"cumulus-pallet-contracts-rpc-runtime-api/std",
"pallet-randomness-collective-flip/std",
"pallet-timestamp/std",
"pallet-sudo/std",
"pallet-transaction-payment/std",
"parachain-info/std",
"rococo-parachain-primitives/std",
"cumulus-runtime/std",
"cumulus-parachain-upgrade/std",
"cumulus-message-broker/std",
"cumulus-upward-message/std",
"cumulus-primitives/std",
"polkadot-parachain/std",
"cumulus-token-dealer/std",
]
# Will be enabled by the `wasm-builder` when building the runtime for WASM.
runtime-wasm = [
"cumulus-upward-message/runtime-wasm",
]
@@ -1,26 +0,0 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
use wasm_builder_runner::WasmBuilder;
fn main() {
WasmBuilder::new()
.with_current_project()
.with_wasm_builder_from_crates("2.0.0")
.export_heap_base()
.import_memory()
.build()
}
@@ -1,411 +0,0 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Cumulus 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.
// Cumulus 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "std"), no_std)]
// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
#![recursion_limit = "256"]
// Make the WASM binary available.
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
use cumulus_pallet_contracts_rpc_runtime_api::ContractExecResult;
use rococo_parachain_primitives::*;
use sp_api::impl_runtime_apis;
use sp_core::OpaqueMetadata;
use sp_runtime::{
create_runtime_str, generic, impl_opaque_keys,
traits::{BlakeTwo256, Block as BlockT, IdentityLookup, Saturating},
transaction_validity::{TransactionSource, TransactionValidity},
ApplyExtrinsicResult,
};
use sp_std::prelude::*;
#[cfg(feature = "std")]
use sp_version::NativeVersion;
use sp_version::RuntimeVersion;
// A few exports that help ease life for downstream crates.
pub use frame_support::{
construct_runtime, parameter_types,
traits::Randomness,
weights::{constants::WEIGHT_PER_SECOND, IdentityFee, Weight},
StorageValue,
};
pub use pallet_balances::Call as BalancesCall;
pub use pallet_timestamp::Call as TimestampCall;
#[cfg(any(feature = "std", test))]
pub use sp_runtime::BuildStorage;
pub use sp_runtime::{Perbill, Permill};
pub type SessionHandlers = ();
impl_opaque_keys! {
pub struct SessionKeys {}
}
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("cumulus-contracts-parachain"),
impl_name: create_runtime_str!("cumulus-contracts-parachain"),
authoring_version: 1,
spec_version: 4,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
};
pub const MILLISECS_PER_BLOCK: u64 = 6000;
pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
pub const EPOCH_DURATION_IN_BLOCKS: u32 = 10 * MINUTES;
// These time units are defined in number of blocks.
pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
pub const HOURS: BlockNumber = MINUTES * 60;
pub const DAYS: BlockNumber = HOURS * 24;
// 1 in 4 blocks (on average, not counting collisions) will be primary babe blocks.
pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4);
#[derive(codec::Encode, codec::Decode)]
pub enum XCMPMessage<XAccountId, XBalance> {
/// Transfer tokens to the given account from the Parachain account.
TransferToken(XAccountId, XBalance),
}
/// The version information used to identify this runtime when compiled natively.
#[cfg(feature = "std")]
pub fn native_version() -> NativeVersion {
NativeVersion {
runtime_version: VERSION,
can_author_with: Default::default(),
}
}
parameter_types! {
pub const BlockHashCount: BlockNumber = 250;
pub const MaximumBlockWeight: Weight = 2 * WEIGHT_PER_SECOND;
/// Assume 10% of weight for average on_initialize calls.
pub MaximumExtrinsicWeight: Weight = AvailableBlockRatio::get()
.saturating_sub(Perbill::from_percent(10)) * MaximumBlockWeight::get();
pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
pub const MaximumBlockLength: u32 = 5 * 1024 * 1024;
pub const Version: RuntimeVersion = VERSION;
pub const ExtrinsicBaseWeight: Weight = 10_000_000;
}
impl frame_system::Trait for Runtime {
/// The identifier used to distinguish between accounts.
type AccountId = AccountId;
/// The aggregated dispatch type that is available for extrinsics.
type Call = Call;
/// The lookup mechanism to get account ID from whatever is passed in dispatchers.
type Lookup = IdentityLookup<AccountId>;
/// The index type for storing how many extrinsics an account has signed.
type Index = Index;
/// The index type for blocks.
type BlockNumber = BlockNumber;
/// The type for hashing blocks and tries.
type Hash = Hash;
/// The hashing algorithm used.
type Hashing = BlakeTwo256;
/// The header type.
type Header = generic::Header<BlockNumber, BlakeTwo256>;
/// The ubiquitous event type.
type Event = Event;
/// The ubiquitous origin type.
type Origin = Origin;
/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
type BlockHashCount = BlockHashCount;
/// Maximum weight of each block. With a default weight system of 1byte == 1weight, 4mb is ok.
type MaximumBlockWeight = MaximumBlockWeight;
/// Maximum size of all encoded transactions (in bytes) that are allowed in one block.
type MaximumBlockLength = MaximumBlockLength;
/// Portion of the block weight that is available to all normal transactions.
type AvailableBlockRatio = AvailableBlockRatio;
/// Runtime version.
type Version = Version;
/// Converts a module to an index of this module in the runtime.
type ModuleToIndex = ModuleToIndex;
type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = ();
type OnKilledAccount = ();
type DbWeight = ();
type ExtrinsicBaseWeight = ExtrinsicBaseWeight;
type BlockExecutionWeight = ();
type MaximumExtrinsicWeight = MaximumExtrinsicWeight;
type BaseCallFilter = ();
type SystemWeightInfo = ();
}
parameter_types! {
pub const MinimumPeriod: u64 = SLOT_DURATION / 2;
}
impl pallet_timestamp::Trait for Runtime {
/// A timestamp: milliseconds since the unix epoch.
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = MinimumPeriod;
type WeightInfo = ();
}
parameter_types! {
pub const ExistentialDeposit: u128 = 500;
pub const TransferFee: u128 = 0;
pub const CreationFee: u128 = 0;
pub const TransactionByteFee: u128 = 1;
}
impl pallet_balances::Trait for Runtime {
/// The type for recording an account's balance.
type Balance = Balance;
/// The ubiquitous event type.
type Event = Event;
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
}
impl pallet_transaction_payment::Trait for Runtime {
type Currency = Balances;
type OnTransactionPayment = ();
type TransactionByteFee = TransactionByteFee;
type WeightToFee = IdentityFee<Balance>;
type FeeMultiplierUpdate = ();
}
impl pallet_sudo::Trait for Runtime {
type Call = Call;
type Event = Event;
}
impl cumulus_parachain_upgrade::Trait for Runtime {
type Event = Event;
type OnValidationFunctionParams = ();
}
impl cumulus_message_broker::Trait for Runtime {
type Event = Event;
type DownwardMessageHandlers = TokenDealer;
type UpwardMessage = cumulus_upward_message::RococoUpwardMessage;
type ParachainId = ParachainInfo;
type XCMPMessage = cumulus_token_dealer::XCMPMessage<AccountId, Balance>;
type XCMPMessageHandlers = TokenDealer;
}
impl cumulus_token_dealer::Trait for Runtime {
type Event = Event;
type UpwardMessageSender = MessageBroker;
type UpwardMessage = cumulus_upward_message::RococoUpwardMessage;
type Currency = Balances;
type XCMPMessageSender = MessageBroker;
}
impl parachain_info::Trait for Runtime {}
// We disable the rent system for easier testing.
parameter_types! {
pub const TombstoneDeposit: Balance = 0;
pub const RentByteFee: Balance = 0;
pub const RentDepositOffset: Balance = 0;
pub const SurchargeReward: Balance = 0;
}
impl cumulus_pallet_contracts::Trait for Runtime {
type Time = Timestamp;
type Randomness = RandomnessCollectiveFlip;
type Currency = Balances;
type Call = Call;
type Event = Event;
type DetermineContractAddress = cumulus_pallet_contracts::SimpleAddressDeterminer<Runtime>;
type TrieIdGenerator = cumulus_pallet_contracts::TrieIdFromParentCounter<Runtime>;
type RentPayment = ();
type SignedClaimHandicap = cumulus_pallet_contracts::DefaultSignedClaimHandicap;
type TombstoneDeposit = TombstoneDeposit;
type StorageSizeOffset = cumulus_pallet_contracts::DefaultStorageSizeOffset;
type RentByteFee = RentByteFee;
type RentDepositOffset = RentDepositOffset;
type SurchargeReward = SurchargeReward;
type MaxDepth = cumulus_pallet_contracts::DefaultMaxDepth;
type MaxValueSize = cumulus_pallet_contracts::DefaultMaxValueSize;
type WeightPrice = pallet_transaction_payment::Module<Self>;
}
construct_runtime! {
pub enum Runtime where
Block = Block,
NodeBlock = rococo_parachain_primitives::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: frame_system::{Module, Call, Storage, Config, Event<T>},
Timestamp: pallet_timestamp::{Module, Call, Storage, Inherent},
Balances: pallet_balances::{Module, Call, Storage, Config<T>, Event<T>},
Contracts: cumulus_pallet_contracts::{Module, Call, Config, Storage, Event<T>},
Sudo: pallet_sudo::{Module, Call, Storage, Config<T>, Event<T>},
RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage},
ParachainUpgrade: cumulus_parachain_upgrade::{Module, Call, Storage, Inherent, Event},
MessageBroker: cumulus_message_broker::{Module, Call, Inherent, Event<T>},
TokenDealer: cumulus_token_dealer::{Module, Call, Event<T>},
TransactionPayment: pallet_transaction_payment::{Module, Storage},
ParachainInfo: parachain_info::{Module, Storage, Config},
}
}
/// The address format for describing accounts.
pub type Address = AccountId;
/// Block header type as expected by this runtime.
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
/// Block type as expected by this runtime.
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
/// A Block signed with a Justification
pub type SignedBlock = generic::SignedBlock<Block>;
/// BlockId type as expected by this runtime.
pub type BlockId = generic::BlockId<Block>;
/// The SignedExtension to the basic transaction logic.
pub type SignedExtra = (
frame_system::CheckSpecVersion<Runtime>,
frame_system::CheckGenesis<Runtime>,
frame_system::CheckEra<Runtime>,
frame_system::CheckNonce<Runtime>,
frame_system::CheckWeight<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
/// Extrinsic type that has already been checked.
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Call, SignedExtra>;
/// Executive: handles dispatch to the various modules.
pub type Executive = frame_executive::Executive<
Runtime,
Block,
frame_system::ChainContext<Runtime>,
Runtime,
AllModules,
>;
impl_runtime_apis! {
impl sp_api::Core<Block> for Runtime {
fn version() -> RuntimeVersion {
VERSION
}
fn execute_block(block: Block) {
Executive::execute_block(block)
}
fn initialize_block(header: &<Block as BlockT>::Header) {
Executive::initialize_block(header)
}
}
impl sp_api::Metadata<Block> for Runtime {
fn metadata() -> OpaqueMetadata {
Runtime::metadata().into()
}
}
impl sp_block_builder::BlockBuilder<Block> for Runtime {
fn apply_extrinsic(
extrinsic: <Block as BlockT>::Extrinsic,
) -> ApplyExtrinsicResult {
Executive::apply_extrinsic(extrinsic)
}
fn finalize_block() -> <Block as BlockT>::Header {
Executive::finalize_block()
}
fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<<Block as BlockT>::Extrinsic> {
data.create_extrinsics()
}
fn check_inherents(block: Block, data: sp_inherents::InherentData) -> sp_inherents::CheckInherentsResult {
data.check_extrinsics(&block)
}
fn random_seed() -> <Block as BlockT>::Hash {
RandomnessCollectiveFlip::random_seed()
}
}
impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
fn validate_transaction(
source: TransactionSource,
tx: <Block as BlockT>::Extrinsic,
) -> TransactionValidity {
Executive::validate_transaction(source, tx)
}
}
impl sp_offchain::OffchainWorkerApi<Block> for Runtime {
fn offchain_worker(header: &<Block as BlockT>::Header) {
Executive::offchain_worker(header)
}
}
impl sp_session::SessionKeys<Block> for Runtime {
fn decode_session_keys(
encoded: Vec<u8>,
) -> Option<Vec<(Vec<u8>, sp_core::crypto::KeyTypeId)>> {
SessionKeys::decode_into_raw_public_keys(&encoded)
}
fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
SessionKeys::generate(seed)
}
}
impl cumulus_pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber>
for Runtime
{
fn call(
origin: AccountId,
dest: AccountId,
value: Balance,
gas_limit: u64,
input_data: Vec<u8>,
) -> ContractExecResult {
let (exec_result, gas_consumed) =
Contracts::bare_call(origin, dest.into(), value, gas_limit, input_data);
match exec_result {
Ok(v) => ContractExecResult::Success {
flags: v.status.into(),
data: v.data,
gas_consumed: gas_consumed,
},
Err(_) => ContractExecResult::Error,
}
}
fn get_storage(
address: AccountId,
key: [u8; 32],
) -> cumulus_pallet_contracts_primitives::GetStorageResult {
Contracts::get_storage(address, key)
}
fn rent_projection(
address: AccountId,
) -> cumulus_pallet_contracts_primitives::RentProjectionResult<BlockNumber> {
Contracts::rent_projection(address)
}
}
}
cumulus_runtime::register_validate_block!(Block, Executive);
@@ -1,498 +0,0 @@
# Complexity
This analysis is on the computing and memory complexity of specific procedures. It provides a rough estimate of operations performed in general and especially focusing on DB reads and writes. It is also an attempt to estimate the memory consumption at its peak.
The primary goal is to come up with decent pricing for functions that can be invoked by a user (via extrinsics) or by untrusted code that prevents DoS attacks.
# Sandboxing
It makes sense to describe the sandboxing module first because the smart-contract module is built upon it.
## Memory
### set
Copies data from the supervisor's memory to the guest's memory.
**complexity**: It doesn't allocate, and the computational complexity is proportional to the number of bytes to copy.
### get
Copies data from the guest's memory to the supervisor's memory.
**complexity**: It doesn't allocate, and the computational complexity is proportional to the number of bytes to copy.
## Instance
### Instantiation
Instantiation of a sandbox module consists of the following steps:
1. Loading the wasm module in the in-memory representation,
2. Performing validation of the wasm code,
3. Setting up the environment which will be used to instantiate the module,
4. Performing the standard wasm instantiation process, which includes (but is not limited to):
1. Allocating of memory requested by the instance,
2. Copying static data from the module to newly allocated memory,
3. Executing the `start` function.
**Note** that the `start` function can be viewed as a normal function and can do anything that a normal function can do, including allocation of more memory or calling the host environment. The complexity of running the `start` function should be considered separately.
In order to start the process of instantiation, the supervisor should provide the wasm module code being instantiated and the environment definition (a set of functions, memories (and maybe globals and tables in the future) available for import by the guest module) for that module. While the environment definition typically is of the constant size (unless mechanisms like dynamic linking are used), the size of wasm is not.
Validation and instantiation in WebAssembly are designed to be able to be performed in linear time. The allocation and computational complexity of loading a wasm module depend on the underlying wasm VM being used. For example, for JIT compilers it can and probably will be non-linear because of compilation. However, for wasmi, it should be linear. We can try to use other VMs that are able to compile code with memory and time consumption proportional to the size of the code.
Since the module itself requests memory, the amount of allocation depends on the module code itself. If untrusted code is being instantiated, it's up to the supervisor to limit the amount of memory available to allocate.
**complexity**: The computational complexity is proportional to the size of wasm code. Memory complexity is proportional to the size of wasm code and the amount of memory requested by the module.
### Preparation to invoke
Invocation of an exported function in the sandboxed module consists of the following steps:
1. Marshalling, copying and unmarshalling the arguments when passing them between the supervisor and executor,
2. Calling into the underlying VM,
3. Marshalling, copying and unmarshalling the result when passing it between the executor and supervisor,
**Note** that the complexity of running the function code itself should be considered separately.
The actual complexity of invocation depends on the underlying VM. Wasmi will reserve a relatively large chunk of memory for the stack before execution of the code, although it's of constant size.
The size of the arguments and the return value depends on the exact function in question, but can be considered as constant.
**complexity**: Memory and computational complexity can be considered as a constant.
### Call from the guest to the supervisor
The executor handles each call from the guest. The execution of it consists of the following steps:
1. Marshalling, copying and unmarshalling the arguments when passing them between the guest and executor,
2. Calling into the supervisor,
3. Marshaling, copying and unmarshalling the result when passing it between the executor and guest.
**Note** that the complexity of running the supervisor handler should be considered separately.
Because calling into the supervisor requires invoking a wasm VM, the actual complexity of invocation depends on the actual VM used for the runtime/supervisor. Wasmi will reserve a relatively large chunk of memory for the stack before execution of the code, although it's of constant size.
The size of the arguments and the return value depends on the exact function in question, but can be considered as a constant.
**complexity**: Memory and computational complexity can be considered as a constant.
# `AccountDb`
`AccountDb` is an abstraction that supports collecting changes to accounts with the ability to efficiently reverting them. Contract
execution contexts operate on the AccountDb. All changes are flushed into underlying storage only after origin transaction succeeds.
## Relation to the underlying storage
At present, `AccountDb` is implemented as a cascade of overlays with the direct storage at the bottom. The direct
storage `AccountDb` leverages child tries. Each overlay is represented by a `Map`. On a commit from an overlay to an
overlay, maps are merged. On commit from an overlay to the bottommost `AccountDb` all changes are flushed to the storage
and on revert, the overlay is just discarded.
> ️ The underlying storage has a overlay layer implemented as a `Map`. If the runtime reads a storage location and the
> respective key doesn't exist in the overlay, then the underlying storage performs a DB access, but the value won't be
> placed into the overlay. The overlay is only filled with writes.
>
> This means that the overlay can be abused in the following ways:
>
> - The overlay can be inflated by issuing a lot of writes to unique locations,
> - Deliberate cache misses can be induced by reading non-modified storage locations,
It also worth noting that the performance degrades with more state stored in the trie. Due to this
there is not negligible chance that gas schedule will be updated for all operations that involve
storage access.
## get_storage, get_code_hash, get_rent_allowance, get_balance, contract_exists
These functions check the local cache for a requested value and, if it is there, the value is returned. Otherwise, these functions will ask an underlying `AccountDb` for the value. This means that the number of lookups is proportional to the depth of the overlay cascade. If the value can't be found before reaching the bottommost `AccountDb`, then a DB read will be performed (in case `get_balance` the function `free_balance` will be invoked).
A lookup in the local cache consists of at least one `Map` lookup, for locating the specific account. For `get_storage` there is a second lookup: because account's storage is implemented as a nested map, another lookup is required for fetching a storage value by a key.
These functions return an owned value as its result, so memory usage depends on the value being returned.
**complexity**: The memory complexity is proportional to the size of the value. The computational complexity is proportional to the depth of the overlay cascade and the size of the value; the cost is dominated by the DB read though.
## set_storage, set_balance, set_rent_allowance
These functions only modify the local `Map`.
A lookup in the local cache consists of at least one `Map` lookup, for locating the specific account. For `get_storage` there is a second lookup: because account's storage is implemented as a nested map, another lookup is required for fetching a storage value by a key.
While these functions only modify the local `Map`, if changes made by them are committed to the bottommost `AccountDb`, each changed entry in the `Map` will require a DB write. Moreover, if the balance of the account is changed to be below `existential_deposit` then that account along with all its storage will be removed, which requires time proportional to the number of storage entries that account has. It should be ensured that pricing accounts for these facts.
**complexity**: Each lookup has a logarithmical computing time to the number of already inserted entries. No additional memory is required.
## instantiate_contract
Calls `contract_exists` and if it doesn't exist, do not modify the local `Map` similarly to `set_rent_allowance`.
**complexity**: The computational complexity is proportional to the depth of the overlay cascade and the size of the value; the cost is dominated by the DB read though. No additional memory is required.
## commit
In this function, all cached values will be inserted into the underlying `AccountDb` or into the storage.
We are doing `N` inserts into `Map` (`O(log M)` complexity) or into the storage, where `N` is the size of the committed `Map` and `M` is the size of the map of the underlying overlay. Consider adjusting the price of modifying the `AccountDb` to account for this (since pricing for the count of entries in `commit` will make the price of commit way less predictable). No additional memory is required.
Note that in case of storage modification we need to construct a key in the underlying storage. In order to do that we need:
- perform `twox_128` hashing over a concatenation of some prefix literal and the `AccountId` of the storage owner.
- then perform `blake2_256` hashing of the storage key.
- concatenation of these hashes will constitute the key in the underlying storage.
There is also a special case to think of: if the balance of some account goes below `existential_deposit`, then all storage entries of that account will be erased, which requires time proportional to the number of storage entries that account has.
**complexity**: `N` inserts into a `Map` or eventually into the storage (if committed). Every deleted account will induce removal of all its storage which is proportional to the number of storage entries that account has.
## revert
Consists of dropping (in the Rust sense) of the `AccountDb`.
**complexity**: Computing complexity is proportional to a number of changed entries in a overlay. No additional memory is required.
# Executive
## Transfer
This function performs the following steps:
1. Querying source and destination balances from an overlay (see `get_balance`),
2. Querying `existential_deposit`.
3. Executing `ensure_account_liquid` hook.
4. Updating source and destination balance in the overlay (see `set_balance`).
**Note** that the complexity of executing `ensure_account_liquid` hook should be considered separately.
In the course of the execution this function can perform up to 2 DB reads to `get_balance` of source and destination accounts. It can also induce up to 2 DB writes via `set_balance` if flushed to the storage.
Moreover, if the source balance goes below `existential_deposit` then the transfer is denied and
returns with an error.
Assuming marshaled size of a balance value is of the constant size we can neglect its effect on the performance.
**complexity**: up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has. For the current `AccountDb` implementation computing complexity also depends on the depth of the `AccountDb` cascade. Memorywise it can be assumed to be constant.
## Initialization
Before a call or instantiate can be performed the execution context must be initialized.
For the first call or instantiation in the handling of an extrinsic, this involves two calls:
1. `<timestamp::Module<T>>::now()`
2. `<system::Module<T>>::block_number()`
The complexity of initialization depends on the complexity of these functions. In the current
implementation they just involve a DB read.
For subsequent calls and instantiations during contract execution, the initialization requires no
expensive operations.
## Terminate
This function performs the following steps:
1. Check the calling contract is not already on the callstack by calling `is_live`.
2. `transfer` funds from caller to the beneficiary.
3. Flag the caller contract as deleted in the overlay.
`is_live` does not do any database access nor does it allocate memory. It walks up the call
stack and therefore executes in linear time depending on size of the call stack. Because
the call stack is of a fixed maximum size we consider this operation as constant time.
**complexity**: Database accesses as described in Transfer + Removal of the contract. Currently,
we are using child trie removal which is linear in the amount of stored keys. Upcoming changes
will make the account removal constant time.
## Call
This function receives input data for the contract execution. The execution consists of the following steps:
1. Initialization of the execution context.
2. Checking rent payment.
3. Loading code from the DB.
4. `transfer`-ing funds between the caller and the destination account.
5. Executing the code of the destination account.
6. Committing overlayed changed to the underlying `AccountDb`.
**Note** that the complexity of executing the contract code should be considered separately.
Checking for rent involves 2 unconditional DB reads: `ContractInfoOf` and `block_number`
and on top of that at most once per block:
- DB read to `free_balance` and
- `rent_deposit_offset` and
- `rent_byte_price` and
- `Currency::minimum_balance` and
- `tombstone_deposit`.
- Calls to `ensure_can_withdraw`, `withdraw`, `make_free_balance_be` can perform arbitrary logic and should be considered separately,
- `child_storage_root`
- `kill_child_storage`
- mutation of `ContractInfoOf`
Loading code most likely will trigger a DB read, since the code is immutable and therefore will not get into the cache (unless a suicide removes it, or it has been instantiated in the same call chain).
Also, `transfer` can make up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has.
Finally, all changes are `commit`-ted into the underlying overlay. The complexity of this depends on the number of changes performed by the code. Thus, the pricing of storage modification should account for that.
**complexity**:
- Only for the first invocation of the contract: up to 5 DB reads and one DB write as well as logic executed by `ensure_can_withdraw`, `withdraw`, `make_free_balance_be`.
- On top of that for every invocation: Up to 5 DB reads. DB read of the code is of dynamic size. There can also be up to 2 DB writes (if flushed to the storage). Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has.
## Instantiate
This function takes the code of the constructor and input data. Instantiation of a contract consists of the following steps:
1. Initialization of the execution context.
2. Calling `DetermineContractAddress` hook to determine an address for the contract,
3. `transfer`-ing funds between self and the newly instantiated contract.
4. Executing the constructor code. This will yield the final code of the code.
5. Storing the code for the newly instantiated contract in the overlay.
6. Committing overlayed changed to the underlying `AccountDb`.
**Note** that the complexity of executing the constructor code should be considered separately.
**Note** that the complexity of `DetermineContractAddress` hook should be considered separately as well. Most likely it will use some kind of hashing over the code of the constructor and input data. The default `SimpleAddressDeterminer` does precisely that.
**Note** that the constructor returns code in the owned form and it's obtained via return facilities, which should have take fee for the return value.
Also, `transfer` can make up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has.
Storing the code in the overlay may induce another DB write (if flushed to the storage) with the size proportional to the size of the constructor code.
Finally, all changes are `commit`-ted into the underlying overlay. The complexity of this depends on the number of changes performed by the constructor code. Thus, the pricing of storage modification should account for that.
**complexity**: Up to 2 DB reads and induces up to 3 DB writes (if flushed to the storage), one of which is dependent on the size of the code. Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has.
# Externalities
Each external function invoked from a contract can involve some overhead.
## ext_gas
**complexity**: This is of constant complexity.
## ext_set_storage
This function receives a `key` and `value` as arguments. It consists of the following steps:
1. Reading the sandbox memory for `key` and `value` (see sandboxing memory get).
2. Setting the storage at the given `key` to the given `value` (see `set_storage`).
**complexity**: Complexity is proportional to the size of the `value`. This function induces a DB write of size proportional to the `value` size (if flushed to the storage), so should be priced accordingly.
## ext_clear_storage
This function receives a `key` as argument. It consists of the following steps:
1. Reading the sandbox memory for `key` (see sandboxing memory get).
2. Clearing the storage at the given `key` (see `set_storage`).
**complexity**: Complexity is constant. This function induces a DB write to clear the storage entry
(upon being flushed to the storage) and should be priced accordingly.
## ext_get_storage
This function receives a `key` as an argument. It consists of the following steps:
1. Reading the sandbox memory for `key` (see sandboxing memory get).
2. Reading the storage with the given key (see `get_storage`). It receives back the owned result buffer.
3. Replacing the scratch buffer.
Key is of a constant size. Therefore, the sandbox memory load can be considered to be of constant complexity.
Unless the value is cached, a DB read will be performed. The size of the value is not known until the read is
performed. Moreover, the DB read has to be synchronous and no progress can be made until the value is fetched.
**complexity**: The memory and computing complexity is proportional to the size of the fetched value. This function performs a
DB read.
## ext_transfer
This function receives the following arguments:
- `account` buffer of a marshaled `AccountId`,
- `value` buffer of a marshaled `Balance`,
It consists of the following steps:
1. Loading `account` buffer from the sandbox memory (see sandboxing memory get) and then decoding it.
2. Loading `value` buffer from the sandbox memory and then decoding it.
4. Invoking the executive function `transfer`.
Loading of `account` and `value` buffers should be charged. This is because the sizes of buffers are specified by the calling code, even though marshaled representations are, essentially, of constant size. This can be fixed by assigning an upper bound for sizes of `AccountId` and `Balance`.
## ext_call
This function receives the following arguments:
- `callee` buffer of a marshaled `AccountId`,
- `gas` limit which is plain u64,
- `value` buffer of a marshaled `Balance`,
- `input_data`. An arbitrarily sized byte vector.
It consists of the following steps:
1. Loading `callee` buffer from the sandbox memory (see sandboxing memory get) and then decoding it.
2. Loading `value` buffer from the sandbox memory and then decoding it.
3. Loading `input_data` buffer from the sandbox memory.
4. Invoking the executive function `call`.
Loading of `callee` and `value` buffers should be charged. This is because the sizes of buffers are specified by the calling code, even though marshaled representations are, essentially, of constant size. This can be fixed by assigning an upper bound for sizes of `AccountId` and `Balance`.
Loading `input_data` should be charged in any case.
**complexity**: All complexity comes from loading buffers and executing `call` executive function. The former component is proportional to the sizes of `callee`, `value` and `input_data` buffers. The latter component completely depends on the complexity of `call` executive function, and also dominated by it.
## ext_instantiate
This function receives the following arguments:
- `init_code`, a buffer which contains the code of the constructor.
- `gas` limit which is plain u64
- `value` buffer of a marshaled `Balance`
- `input_data`. an arbitrarily sized byte vector.
It consists of the following steps:
1. Loading `init_code` buffer from the sandbox memory (see sandboxing memory get) and then decoding it.
2. Loading `value` buffer from the sandbox memory and then decoding it.
3. Loading `input_data` buffer from the sandbox memory.
4. Invoking `instantiate` executive function.
Loading of `value` buffer should be charged. This is because the size of the buffer is specified by the calling code, even though marshaled representation is, essentially, of constant size. This can be fixed by assigning an upper bound for size for `Balance`.
Loading `init_code` and `input_data` should be charged in any case.
**complexity**: All complexity comes from loading buffers and executing `instantiate` executive function. The former component is proportional to the sizes of `init_code`, `value` and `input_data` buffers. The latter component completely depends on the complexity of `instantiate` executive function and also dominated by it.
## ext_terminate
This function receives the following arguments:
- `beneficiary`, buffer of a marshaled `AccountId`
It consists of the following steps:
1. Loading `beneficiary` buffer from the sandbox memory (see sandboxing memory get) and then decoding it.
Loading of the `beneficiary` buffer should be charged. This is because the sizes of buffers are specified by the calling code, even though marshaled representations are, essentially, of constant size. This can be fixed by assigning an upper bound for sizes of `AccountId`.
**complexity**: All complexity comes from loading buffers and executing `terminate` executive function. The former component is proportional to the size of the `beneficiary` buffer. The latter component completely depends on the complexity of `terminate` executive function and also dominated by it.
## ext_return
This function receives a `data` buffer as an argument. Execution of the function consists of the following steps:
1. Loading `data` buffer from the sandbox memory (see sandboxing memory get),
2. Trapping
**complexity**: The complexity of this function is proportional to the size of the `data` buffer.
## ext_deposit_event
This function receives a `data` buffer as an argument. Execution of the function consists of the following steps:
1. Loading `data` buffer from the sandbox memory (see sandboxing memory get),
2. Insert to nested context execution
3. Copies from nested to underlying contexts
4. Call system deposit event
**complexity**: The complexity of this function is proportional to the size of the `data` buffer.
## ext_caller
This function serializes the address of the caller into the scratch buffer.
**complexity**: Assuming that the address is of constant size, this function has constant complexity.
## ext_random
This function serializes a random number generated by the given subject into the scratch buffer.
The complexity of this function highly depends on the complexity of `System::random`. `max_subject_len`
limits the size of the subject buffer.
**complexity**: The complexity of this function depends on the implementation of `System::random`.
## ext_now
This function serializes the current block's timestamp into the scratch buffer.
**complexity**: Assuming that the timestamp is of constant size, this function has constant complexity.
## ext_scratch_size
This function returns the size of the scratch buffer.
**complexity**: This function is of constant complexity.
## ext_scratch_read
This function copies slice of data from the scratch buffer to the sandbox memory. The calling code specifies the slice length. Execution of the function consists of the following steps:
1. Storing a specified slice of the scratch buffer into the sandbox memory (see sandboxing memory set)
**complexity**: The computing complexity of this function is proportional to the length of the slice. No additional memory is required.
## ext_scratch_write
This function copies slice of data from the sandbox memory to the scratch buffer. The calling code specifies the slice length. Execution of the function consists of the following steps:
1. Loading a slice from the sandbox memory into the (see sandboxing memory get)
**complexity**: Complexity is proportional to the length of the slice.
## ext_set_rent_allowance
This function receives the following argument:
- `value` buffer of a marshaled `Balance`,
It consists of the following steps:
1. Loading `value` buffer from the sandbox memory and then decoding it.
2. Invoking `set_rent_allowance` AccountDB function.
**complexity**: Complexity is proportional to the size of the `value`. This function induces a DB write of size proportional to the `value` size (if flushed to the storage), so should be priced accordingly.
## ext_rent_allowance
It consists of the following steps:
1. Invoking `get_rent_allowance` AccountDB function.
2. Serializing the rent allowance of the current contract into the scratch buffer.
**complexity**: Assuming that the rent allowance is of constant size, this function has constant complexity. This
function performs a DB read.
## ext_block_number
This function serializes the current block's number into the scratch buffer.
**complexity**: Assuming that the block number is of constant size, this function has constant complexity.
## Built-in hashing functions
This paragraph concerns the following supported built-in hash functions:
- `SHA2` with 256-bit width
- `KECCAK` with 256-bit width
- `BLAKE2` with 128-bit and 256-bit widths
These functions compute a cryptographic hash on the given inputs and copy the
resulting hash directly back into the sandboxed Wasm contract output buffer.
Execution of the function consists of the following steps:
1. Load data stored in the input buffer into an intermediate buffer.
2. Compute the cryptographic hash `H` on the intermediate buffer.
3. Copy back the bytes of `H` into the contract side output buffer.
**complexity**: Complexity is proportional to the size of the input buffer in bytes
as well as to the size of the output buffer in bytes. Also different cryptographic
algorithms have different inherent complexity so users must expect the above
mentioned crypto hashes to have varying gas costs.
The complexity of each cryptographic hash function highly depends on the underlying
implementation.
@@ -1,53 +0,0 @@
[package]
name = "cumulus-pallet-contracts"
version = "2.0.0-rc3"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "FRAME pallet for WASM contracts"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
serde = { version = "1.0.101", optional = true, features = ["derive"] }
pwasm-utils = { version = "0.12.0", default-features = false }
codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false, features = ["derive"] }
parity-wasm = { version = "0.41.0", default-features = false }
wasmi-validation = { version = "0.3.0", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-sandbox = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
cumulus-pallet-contracts-primitives = { path = "./common", default-features = false }
[dev-dependencies]
wabt = "0.10"
assert_matches = "1.3.0"
hex-literal = "0.2.1"
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" }
pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" }
pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" }
[features]
default = ["std"]
std = [
"serde",
"codec/std",
"sp-core/std",
"sp-runtime/std",
"sp-io/std",
"sp-std/std",
"sp-sandbox/std",
"frame-support/std",
"frame-system/std",
"parity-wasm/std",
"pwasm-utils/std",
"wasmi-validation/std",
"cumulus-pallet-contracts-primitives/std",
]
@@ -1,26 +0,0 @@
[package]
name = "cumulus-pallet-contracts-primitives"
version = "2.0.0-rc3"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "A crate that hosts a common definitions that are relevant for the pallet-contracts."
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
# This crate should not rely on any of the frame primitives.
codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false, features = ["derive"] }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
[features]
default = ["std"]
std = [
"codec/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -1,47 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! A crate that hosts a common definitions that are relevant for the pallet-contracts.
#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::prelude::*;
/// A result type of a get storage call.
pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
/// The possible errors that can happen querying the storage of a contract.
#[derive(Eq, PartialEq, codec::Encode, codec::Decode, sp_runtime::RuntimeDebug)]
pub enum ContractAccessError {
/// The given address doesn't point to a contract.
DoesntExist,
/// The specified contract is a tombstone and thus cannot have any storage.
IsTombstone,
}
/// A result type of a `rent_projection` call.
pub type RentProjectionResult<BlockNumber> =
Result<RentProjection<BlockNumber>, ContractAccessError>;
#[derive(Eq, PartialEq, codec::Encode, codec::Decode, sp_runtime::RuntimeDebug)]
pub enum RentProjection<BlockNumber> {
/// Eviction is projected to happen at the specified block number.
EvictionAt(BlockNumber),
/// No eviction is scheduled.
///
/// E.g. because the contract accumulated enough funds to offset the rent storage costs.
NoEviction,
}
@@ -1,275 +0,0 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_balance" (func $ext_balance))
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "ext_println" (func $ext_println (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func $current_balance (param $sp i32) (result i64)
(call $ext_balance)
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 8))
)
(call $ext_scratch_read
(i32.sub (get_local $sp) (i32.const 8))
(i32.const 0)
(i32.const 8)
)
(i64.load (i32.sub (get_local $sp) (i32.const 8)))
)
(func (export "deploy"))
(func (export "call")
(local $sp i32)
(local $exit_code i32)
(local $balance i64)
;; Input data is the code hash of the contract to be deployed.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 32)
)
)
;; Copy code hash from scratch buffer into this contract's memory.
(call $ext_scratch_read
(i32.const 24) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 32) ;; Count of bytes to copy.
)
;; Read current balance into local variable.
(set_local $sp (i32.const 1024))
(set_local $balance
(call $current_balance (get_local $sp))
)
;; Fail to deploy the contract since it returns a non-zero exit status.
(set_local $exit_code
(call $ext_instantiate
(i32.const 24) ;; Pointer to the code hash.
(i32.const 32) ;; Length of the code hash.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 9) ;; Pointer to input data buffer address
(i32.const 7) ;; Length of input data buffer
)
)
;; Check non-zero exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x11))
)
;; Check that scratch buffer is empty since contract instantiation failed.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 0))
)
;; Check that balance has not changed.
(call $assert
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
)
;; Fail to deploy the contract due to insufficient gas.
(set_local $exit_code
(call $ext_instantiate
(i32.const 24) ;; Pointer to the code hash.
(i32.const 32) ;; Length of the code hash.
(i64.const 200) ;; How much gas to devote for the execution.
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 8) ;; Pointer to input data buffer address
(i32.const 8) ;; Length of input data buffer
)
)
;; Check for special trap exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x0100))
)
;; Check that scratch buffer is empty since contract instantiation failed.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 0))
)
;; Check that balance has not changed.
(call $assert
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
)
;; Deploy the contract successfully.
(set_local $exit_code
(call $ext_instantiate
(i32.const 24) ;; Pointer to the code hash.
(i32.const 32) ;; Length of the code hash.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 8) ;; Pointer to input data buffer address
(i32.const 8) ;; Length of input data buffer
)
)
;; Check for success exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x00))
)
;; Check that scratch buffer contains the address of the new contract.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 8))
)
;; Copy contract address from scratch buffer into this contract's memory.
(call $ext_scratch_read
(i32.const 16) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 8) ;; Count of bytes to copy.
)
;; Check that balance has been deducted.
(set_local $balance
(i64.sub (get_local $balance) (i64.load (i32.const 0)))
)
(call $assert
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
)
;; Call the new contract and expect it to return failing exit code.
(set_local $exit_code
(call $ext_call
(i32.const 16) ;; Pointer to "callee" address.
(i32.const 8) ;; Length of "callee" address.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 9) ;; Pointer to input data buffer address
(i32.const 7) ;; Length of input data buffer
)
)
;; Check non-zero exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x11))
)
;; Check that scratch buffer contains the expected return data.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 3))
)
(i32.store
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
)
(call $ext_scratch_read
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
(i32.const 3)
)
(call $assert
(i32.eq
(i32.load (i32.sub (get_local $sp) (i32.const 4)))
(i32.const 0x00776655)
)
)
;; Check that balance has not changed.
(call $assert
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
)
;; Fail to call the contract due to insufficient gas.
(set_local $exit_code
(call $ext_call
(i32.const 16) ;; Pointer to "callee" address.
(i32.const 8) ;; Length of "callee" address.
(i64.const 100) ;; How much gas to devote for the execution.
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 8) ;; Pointer to input data buffer address
(i32.const 8) ;; Length of input data buffer
)
)
;; Check for special trap exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x0100))
)
;; Check that scratch buffer is empty since call trapped.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 0))
)
;; Check that balance has not changed.
(call $assert
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
)
;; Call the contract successfully.
(set_local $exit_code
(call $ext_call
(i32.const 16) ;; Pointer to "callee" address.
(i32.const 8) ;; Length of "callee" address.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 8) ;; Pointer to input data buffer address
(i32.const 8) ;; Length of input data buffer
)
)
;; Check for success exit status.
(call $assert
(i32.eq (get_local $exit_code) (i32.const 0x00))
)
;; Check that scratch buffer contains the expected return data.
(call $assert
(i32.eq (call $ext_scratch_size) (i32.const 4))
)
(i32.store
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
)
(call $ext_scratch_read
(i32.sub (get_local $sp) (i32.const 4))
(i32.const 0)
(i32.const 4)
)
(call $assert
(i32.eq
(i32.load (i32.sub (get_local $sp) (i32.const 4)))
(i32.const 0x77665544)
)
)
;; Check that balance has been deducted.
(set_local $balance
(i64.sub (get_local $balance) (i64.load (i32.const 0)))
)
(call $assert
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
)
)
(data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls.
;; Chosen to be greater than existential deposit.
(data (i32.const 8) "\00\11\22\33\44\55\66\77") ;; The input data to instantiations and calls.
)
@@ -1,47 +0,0 @@
(module
(import "env" "ext_rent_allowance" (func $ext_rent_allowance))
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func (export "call"))
(func (export "deploy")
;; fill the scratch buffer with the rent allowance.
(call $ext_rent_allowance)
;; assert $ext_scratch_size == 8
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 8)
)
)
;; copy contents of the scratch buffer into the contract's memory.
(call $ext_scratch_read
(i32.const 8) ;; Pointer in memory to the place where to copy.
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 8) ;; Count of bytes to copy.
)
;; assert that contents of the buffer is equal to <BalanceOf<T>>::max_value().
(call $assert
(i64.eq
(i64.load
(i32.const 8)
)
(i64.const 0xFFFFFFFFFFFFFFFF)
)
)
)
)
@@ -1,80 +0,0 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32)))
(import "env" "ext_hash_sha2_256" (func $ext_hash_sha2_256 (param i32 i32 i32)))
(import "env" "ext_hash_keccak_256" (func $ext_hash_keccak_256 (param i32 i32 i32)))
(import "env" "ext_hash_blake2_256" (func $ext_hash_blake2_256 (param i32 i32 i32)))
(import "env" "ext_hash_blake2_128" (func $ext_hash_blake2_128 (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
(type $hash_fn_sig (func (param i32 i32 i32)))
(table 8 funcref)
(elem (i32.const 1)
$ext_hash_sha2_256
$ext_hash_keccak_256
$ext_hash_blake2_256
$ext_hash_blake2_128
)
(data (i32.const 1) "20202010201008") ;; Output sizes of the hashes in order in hex.
;; Not in use by the tests besides instantiating the contract.
(func (export "deploy"))
;; Called by the tests.
;;
;; The `call` function expects data in a certain format in the scratch
;; buffer.
;;
;; 1. The first byte encodes an identifier for the crypto hash function
;; under test. (*)
;; 2. The rest encodes the input data that is directly fed into the
;; crypto hash function chosen in 1.
;;
;; The `deploy` function then computes the chosen crypto hash function
;; given the input and puts the result back into the scratch buffer.
;; After contract execution the test driver then asserts that the returned
;; values are equal to the expected bytes for the input and chosen hash
;; function.
;;
;; (*) The possible value for the crypto hash identifiers can be found below:
;;
;; | value | Algorithm | Bit Width |
;; |-------|-----------|-----------|
;; | 0 | SHA2 | 256 |
;; | 1 | KECCAK | 256 |
;; | 2 | BLAKE2 | 256 |
;; | 3 | BLAKE2 | 128 |
;; ---------------------------------
(func (export "call") (result i32)
(local $chosen_hash_fn i32)
(local $input_ptr i32)
(local $input_len i32)
(local $output_ptr i32)
(local $output_len i32)
(local.set $input_ptr (i32.const 10))
(call $ext_scratch_read (local.get $input_ptr) (i32.const 0) (call $ext_scratch_size))
(local.set $chosen_hash_fn (i32.load8_u (local.get $input_ptr)))
(if (i32.gt_u (local.get $chosen_hash_fn) (i32.const 7))
;; We check that the chosen hash fn identifier is within bounds: [0,7]
(unreachable)
)
(local.set $input_ptr (i32.add (local.get $input_ptr) (i32.const 1)))
(local.set $input_len (i32.sub (call $ext_scratch_size) (i32.const 1)))
(local.set $output_ptr (i32.const 100))
(local.set $output_len (i32.load8_u (local.get $chosen_hash_fn)))
(call_indirect (type $hash_fn_sig)
(local.get $input_ptr)
(local.get $input_len)
(local.get $output_ptr)
(local.get $chosen_hash_fn) ;; Which crypto hash function to execute.
)
(call $ext_scratch_write
(local.get $output_ptr) ;; Linear memory location of the output buffer.
(local.get $output_len) ;; Number of output buffer bytes.
)
(i32.const 0)
)
)
@@ -1,148 +0,0 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_get_storage" (func $ext_get_storage (param i32) (result i32)))
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func (export "deploy")
;; Input data is the code hash of the contract to be deployed.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 32)
)
)
;; Copy code hash from scratch buffer into this contract's memory.
(call $ext_scratch_read
(i32.const 48) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 32) ;; Count of bytes to copy.
)
;; Deploy the contract with the provided code hash.
(call $assert
(i32.eq
(call $ext_instantiate
(i32.const 48) ;; Pointer to the code hash.
(i32.const 32) ;; Length of the code hash.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 0) ;; Pointer to input data buffer address
(i32.const 0) ;; Length of input data buffer
)
(i32.const 0)
)
)
;; Read the address of the instantiated contract into memory.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 8)
)
)
(call $ext_scratch_read
(i32.const 80) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 8) ;; Count of bytes to copy.
)
;; Store the return address.
(call $ext_set_storage
(i32.const 16) ;; Pointer to the key
(i32.const 80) ;; Pointer to the value
(i32.const 8) ;; Length of the value
)
)
(func (export "call")
;; Read address of destination contract from storage.
(call $assert
(i32.eq
(call $ext_get_storage
(i32.const 16) ;; Pointer to the key
)
(i32.const 0)
)
)
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 8)
)
)
(call $ext_scratch_read
(i32.const 80) ;; The pointer where to store the contract address.
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 8) ;; Count of bytes to copy.
)
;; Calling the destination contract with non-empty input data should fail.
(call $assert
(i32.eq
(call $ext_call
(i32.const 80) ;; Pointer to destination address
(i32.const 8) ;; Length of destination address
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer
(i32.const 0) ;; Pointer to input data buffer address
(i32.const 1) ;; Length of input data buffer
)
(i32.const 0x0100)
)
)
;; Call the destination contract regularly, forcing it to self-destruct.
(call $assert
(i32.eq
(call $ext_call
(i32.const 80) ;; Pointer to destination address
(i32.const 8) ;; Length of destination address
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 8) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer
(i32.const 0) ;; Pointer to input data buffer address
(i32.const 0) ;; Length of input data buffer
)
(i32.const 0)
)
)
;; Calling the destination address with non-empty input data should now work since the
;; contract has been removed. Also transfer a balance to the address so we can ensure this
;; does not keep the contract alive.
(call $assert
(i32.eq
(call $ext_call
(i32.const 80) ;; Pointer to destination address
(i32.const 8) ;; Length of destination address
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer
(i32.const 0) ;; Pointer to input data buffer address
(i32.const 1) ;; Length of input data buffer
)
(i32.const 0)
)
)
)
(data (i32.const 0) "\00\00\01") ;; Endowment to send when creating contract.
(data (i32.const 8) "") ;; Value to send when calling contract.
(data (i32.const 16) "") ;; The key to store the contract address under.
)
@@ -1,14 +0,0 @@
(module
(import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func (export "call")
(call $ext_dispatch_call
(i32.const 8) ;; Pointer to the start of encoded call buffer
(i32.const 11) ;; Length of the buffer
)
)
(func (export "deploy"))
(data (i32.const 8) "\00\00\03\00\00\00\00\00\00\00\C8")
)
@@ -1,15 +0,0 @@
(module
(import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func (export "call")
(call $ext_dispatch_call
(i32.const 8) ;; Pointer to the start of encoded call buffer
(i32.const 11) ;; Length of the buffer
)
(unreachable) ;; trap so that the top level transaction fails
)
(func (export "deploy"))
(data (i32.const 8) "\00\00\03\00\00\00\00\00\00\00\C8")
)
@@ -1,54 +0,0 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_balance" (func $ext_balance))
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func (export "deploy"))
(func (export "call")
;; Send entire remaining balance to the 0 address.
(call $ext_balance)
;; Balance should be encoded as a u64.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 8)
)
)
;; Read balance into memory.
(call $ext_scratch_read
(i32.const 8) ;; Pointer to write balance to
(i32.const 0) ;; Offset into scratch buffer
(i32.const 8) ;; Length of encoded balance
)
;; Self-destruct by sending full balance to the 0 address.
(call $assert
(i32.eq
(call $ext_call
(i32.const 0) ;; Pointer to destination address
(i32.const 8) ;; Length of destination address
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 8) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer
(i32.const 0) ;; Pointer to input data buffer address
(i32.const 0) ;; Length of input data buffer
)
(i32.const 0)
)
)
)
)
@@ -1,74 +0,0 @@
(module
(import "env" "ext_get_runtime_storage"
(func $ext_get_runtime_storage (param i32 i32) (result i32))
)
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func (export "deploy"))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func $call (export "call")
;; Load runtime storage for the first key and assert that it exists.
(call $assert
(i32.eq
(call $ext_get_runtime_storage
(i32.const 16)
(i32.const 4)
)
(i32.const 0)
)
)
;; assert $ext_scratch_size == 4
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 4)
)
)
;; copy contents of the scratch buffer into the contract's memory.
(call $ext_scratch_read
(i32.const 4) ;; Pointer in memory to the place where to copy.
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 4) ;; Count of bytes to copy.
)
;; assert that contents of the buffer is equal to the i32 value of 0x14144020.
(call $assert
(i32.eq
(i32.load
(i32.const 4)
)
(i32.const 0x14144020)
)
)
;; Load the second key and assert that it doesn't exist.
(call $assert
(i32.eq
(call $ext_get_runtime_storage
(i32.const 20)
(i32.const 4)
)
(i32.const 1)
)
)
)
;; The first key, 4 bytes long.
(data (i32.const 16) "\01\02\03\04")
;; The second key, 4 bytes long.
(data (i32.const 20) "\02\03\04\05")
)
@@ -1,56 +0,0 @@
(module
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
(import "env" "ext_restore_to" (func $ext_restore_to (param i32 i32 i32 i32 i32 i32 i32 i32)))
(import "env" "memory" (memory 1 1))
(func (export "call")
(call $ext_restore_to
;; Pointer and length of the encoded dest buffer.
(i32.const 256)
(i32.const 8)
;; Pointer and length of the encoded code hash buffer
(i32.const 264)
(i32.const 32)
;; Pointer and length of the encoded rent_allowance buffer
(i32.const 296)
(i32.const 8)
;; Pointer and number of items in the delta buffer.
;; This buffer specifies multiple keys for removal before restoration.
(i32.const 100)
(i32.const 1)
)
)
(func (export "deploy")
;; Data to restore
(call $ext_set_storage
(i32.const 0)
(i32.const 0)
(i32.const 4)
)
;; ACL
(call $ext_set_storage
(i32.const 100)
(i32.const 0)
(i32.const 4)
)
)
;; Data to restore
(data (i32.const 0) "\28")
;; Buffer that has ACL storage keys.
(data (i32.const 100) "\01")
;; Address of bob
(data (i32.const 256) "\02\00\00\00\00\00\00\00")
;; Code hash of SET_RENT
(data (i32.const 264)
"\c2\1c\41\10\a5\22\d8\59\1c\4c\77\35\dd\2d\bf\a1"
"\13\0b\50\93\76\9b\92\31\97\b7\c5\74\26\aa\38\2a"
)
;; Rent allowance
(data (i32.const 296) "\32\00\00\00\00\00\00\00")
)
@@ -1,27 +0,0 @@
(module
(import "env" "ext_return" (func $ext_return (param i32 i32)))
(import "env" "ext_deposit_event" (func $ext_deposit_event (param i32 i32 i32 i32)))
(import "env" "memory" (memory 1 1))
(start $start)
(func $start
(call $ext_deposit_event
(i32.const 0) ;; The topics buffer
(i32.const 0) ;; The topics buffer's length
(i32.const 8) ;; The data buffer
(i32.const 4) ;; The data buffer's length
)
(call $ext_return
(i32.const 8)
(i32.const 4)
)
(unreachable)
)
(func (export "call")
(unreachable)
)
(func (export "deploy"))
(data (i32.const 8) "\01\02\03\04")
)
@@ -1,39 +0,0 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32)))
(import "env" "memory" (memory 1 1))
;; Deploy routine is the same as call.
(func (export "deploy") (result i32)
(call $call)
)
;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data.
(func $call (export "call") (result i32)
(local $buf_size i32)
(local $exit_status i32)
;; Find out the size of the scratch buffer
(set_local $buf_size (call $ext_scratch_size))
;; Copy scratch buffer into this contract memory.
(call $ext_scratch_read
(i32.const 0) ;; The pointer where to store the scratch buffer contents,
(i32.const 0) ;; Offset from the start of the scratch buffer.
(get_local $buf_size) ;; Count of bytes to copy.
)
;; Copy all but the first 4 bytes of the input data as the output data.
(call $ext_scratch_write
(i32.const 4) ;; Pointer to the data to return.
(i32.sub ;; Count of bytes to copy.
(get_local $buf_size)
(i32.const 4)
)
)
;; Return the first 4 bytes of the input data as the exit status.
(i32.load (i32.const 0))
)
)
@@ -1,7 +0,0 @@
(module
(func (export "call")
(loop $inf (br $inf)) ;; just run out of gas
(unreachable)
)
(func (export "deploy"))
)
@@ -1,72 +0,0 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_address" (func $ext_address))
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "ext_terminate" (func $ext_terminate (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func (export "deploy"))
(func (export "call")
;; If the input data is not empty, then recursively call self with empty input data.
;; This should trap instead of self-destructing since a contract cannot be removed live in
;; the execution stack cannot be removed. If the recursive call traps, then trap here as
;; well.
(if (call $ext_scratch_size)
(then
(call $ext_address)
;; Expect address to be 8 bytes.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 8)
)
)
;; Read own address into memory.
(call $ext_scratch_read
(i32.const 16) ;; Pointer to write address to
(i32.const 0) ;; Offset into scratch buffer
(i32.const 8) ;; Length of encoded address
)
;; Recursively call self with empty input data.
(call $assert
(i32.eq
(call $ext_call
(i32.const 16) ;; Pointer to own address
(i32.const 8) ;; Length of own address
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 8) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer
(i32.const 0) ;; Pointer to input data buffer address
(i32.const 0) ;; Length of input data buffer
)
(i32.const 0)
)
)
)
(else
;; Try to terminate and give balance to django.
(call $ext_terminate
(i32.const 32) ;; Pointer to beneficiary address
(i32.const 8) ;; Length of beneficiary address
)
(unreachable) ;; ext_terminate never returns
)
)
)
;; Address of django
(data (i32.const 32) "\04\00\00\00\00\00\00\00")
)
@@ -1,54 +0,0 @@
(module
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "ext_balance" (func $ext_balance))
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func (export "deploy")
;; Send entire remaining balance to the 0 address.
(call $ext_balance)
;; Balance should be encoded as a u64.
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 8)
)
)
;; Read balance into memory.
(call $ext_scratch_read
(i32.const 8) ;; Pointer to write balance to
(i32.const 0) ;; Offset into scratch buffer
(i32.const 8) ;; Length of encoded balance
)
;; Self-destruct by sending full balance to the 0 address.
(call $assert
(i32.eq
(call $ext_call
(i32.const 0) ;; Pointer to destination address
(i32.const 8) ;; Length of destination address
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 8) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer
(i32.const 0) ;; Pointer to input data buffer address
(i32.const 0) ;; Length of input data buffer
)
(i32.const 0)
)
)
)
(func (export "call"))
)
@@ -1,15 +0,0 @@
;; This module stores a KV pair into the storage
(module
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
(import "env" "memory" (memory 16 16))
(func (export "call")
)
(func (export "deploy")
(call $ext_set_storage
(i32.const 0) ;; Pointer to storage key
(i32.const 0) ;; Pointer to value
(i32.load (i32.const 0)) ;; Size of value
)
)
)
@@ -1,101 +0,0 @@
(module
(import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32)))
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
(import "env" "ext_clear_storage" (func $ext_clear_storage (param i32)))
(import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32)))
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
;; insert a value of 4 bytes into storage
(func $call_0
(call $ext_set_storage
(i32.const 1)
(i32.const 0)
(i32.const 4)
)
)
;; remove the value inserted by call_1
(func $call_1
(call $ext_clear_storage
(i32.const 1)
)
)
;; transfer 50 to ALICE
(func $call_2
(call $ext_dispatch_call
(i32.const 68)
(i32.const 11)
)
)
;; do nothing
(func $call_else)
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
;; Dispatch the call according to input size
(func (export "call")
(local $input_size i32)
(set_local $input_size
(call $ext_scratch_size)
)
(block $IF_ELSE
(block $IF_2
(block $IF_1
(block $IF_0
(br_table $IF_0 $IF_1 $IF_2 $IF_ELSE
(get_local $input_size)
)
(unreachable)
)
(call $call_0)
return
)
(call $call_1)
return
)
(call $call_2)
return
)
(call $call_else)
)
;; Set into storage a 4 bytes value
;; Set call set_rent_allowance with input
(func (export "deploy")
(local $input_size i32)
(set_local $input_size
(call $ext_scratch_size)
)
(call $ext_set_storage
(i32.const 0)
(i32.const 0)
(i32.const 4)
)
(call $ext_scratch_read
(i32.const 0)
(i32.const 0)
(get_local $input_size)
)
(call $ext_set_rent_allowance
(i32.const 0)
(get_local $input_size)
)
)
;; Encoding of 10 in balance
(data (i32.const 0) "\28")
;; Encoding of call transfer 50 to CHARLIE
(data (i32.const 68) "\00\00\03\00\00\00\00\00\00\00\C8")
)
@@ -1,60 +0,0 @@
(module
(import "env" "ext_get_storage" (func $ext_get_storage (param i32) (result i32)))
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
(import "env" "memory" (memory 16 16))
(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)
(func (export "call")
;; assert $ext_scratch_size == 8
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.const 4)
)
)
;; copy contents of the scratch buffer into the contract's memory.
(call $ext_scratch_read
(i32.const 32) ;; Pointer in memory to the place where to copy.
(i32.const 0) ;; Offset from the start of the scratch buffer.
(i32.const 4) ;; Count of bytes to copy.
)
;; place a garbage value in storage, the size of which is specified by the call input.
(call $ext_set_storage
(i32.const 0) ;; Pointer to storage key
(i32.const 0) ;; Pointer to value
(i32.load (i32.const 32)) ;; Size of value
)
(call $assert
(i32.eq
(call $ext_get_storage
(i32.const 0) ;; Pointer to storage key
)
(i32.const 0)
)
)
(call $assert
(i32.eq
(call $ext_scratch_size)
(i32.load (i32.const 32))
)
)
)
(func (export "deploy"))
(data (i32.const 0) "\01") ;; Storage key (32 B)
)
@@ -1,29 +0,0 @@
[package]
name = "cumulus-pallet-contracts-rpc"
version = "0.8.0-rc3"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "Node-specific RPC methods for interaction with contracts."
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "1.3.1" }
jsonrpc-core = "14.2.0"
jsonrpc-core-client = "14.2.0"
jsonrpc-derive = "14.2.1"
sp-blockchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-rpc = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
serde = { version = "1.0.101", features = ["derive"] }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
cumulus-pallet-contracts-primitives = { path = "../common" }
cumulus-pallet-contracts-rpc-runtime-api = { path = "./runtime-api" }
[dev-dependencies]
serde_json = "1.0.41"
@@ -1,29 +0,0 @@
[package]
name = "cumulus-pallet-contracts-rpc-runtime-api"
version = "0.8.0-rc3"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "Runtime API definition required by Contracts RPC extensions."
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false, features = ["derive"] }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
cumulus-pallet-contracts-primitives = { path = "../../common", default-features = false }
[features]
default = ["std"]
std = [
"sp-api/std",
"codec/std",
"sp-std/std",
"sp-runtime/std",
"cumulus-pallet-contracts-primitives/std",
]
@@ -1,89 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Runtime API definition required by Contracts RPC extensions.
//!
//! This API should be imported and implemented by the runtime,
//! of a node that wants to use the custom RPC extension
//! adding Contracts access methods.
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Codec, Decode, Encode};
use cumulus_pallet_contracts_primitives::{GetStorageResult, RentProjectionResult};
use sp_runtime::RuntimeDebug;
use sp_std::vec::Vec;
/// A result of execution of a contract.
#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
pub enum ContractExecResult {
/// The contract returned successfully.
///
/// There is a status code and, optionally, some data returned by the contract.
Success {
/// Flags that the contract passed along on returning to alter its exit behaviour.
/// Described in `pallet_contracts::exec::ReturnFlags`.
flags: u32,
/// Output data returned by the contract.
///
/// Can be empty.
data: Vec<u8>,
/// How much gas was consumed by the call.
gas_consumed: u64,
},
/// The contract execution either trapped or returned an error.
Error,
}
sp_api::decl_runtime_apis! {
/// The API to interact with contracts without using executive.
pub trait ContractsApi<AccountId, Balance, BlockNumber> where
AccountId: Codec,
Balance: Codec,
BlockNumber: Codec,
{
/// Perform a call from a specified account to a given contract.
///
/// See the contracts' `call` dispatchable function for more details.
fn call(
origin: AccountId,
dest: AccountId,
value: Balance,
gas_limit: u64,
input_data: Vec<u8>,
) -> ContractExecResult;
/// Query a given storage key in a given contract.
///
/// Returns `Ok(Some(Vec<u8>))` if the storage value exists under the given key in the
/// specified account and `Ok(None)` if it doesn't. If the account specified by the address
/// doesn't exist, or doesn't have a contract or if the contract is a tombstone, then `Err`
/// is returned.
fn get_storage(
address: AccountId,
key: [u8; 32],
) -> GetStorageResult;
/// Returns the projected time a given contract will be able to sustain paying its rent.
///
/// The returned projection is relevant for the current block, i.e. it is as if the contract
/// was accessed at the current block.
///
/// Returns `Err` if the contract is in a tombstone state or doesn't exist.
fn rent_projection(address: AccountId) -> RentProjectionResult<BlockNumber>;
}
}
@@ -1,325 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Node-specific RPC methods for interaction with contracts.
use std::sync::Arc;
use codec::Codec;
use cumulus_pallet_contracts_primitives::RentProjection;
use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
use serde::{Deserialize, Serialize};
use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend;
use sp_core::{Bytes, H256};
use sp_rpc::number;
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT},
};
use std::convert::TryInto;
pub use self::gen_client::Client as ContractsClient;
pub use cumulus_pallet_contracts_rpc_runtime_api::{
self as runtime_api, ContractExecResult, ContractsApi as ContractsRuntimeApi,
};
const RUNTIME_ERROR: i64 = 1;
const CONTRACT_DOESNT_EXIST: i64 = 2;
const CONTRACT_IS_A_TOMBSTONE: i64 = 3;
/// A rough estimate of how much gas a decent hardware consumes per second,
/// using native execution.
/// This value is used to set the upper bound for maximal contract calls to
/// prevent blocking the RPC for too long.
///
/// As 1 gas is equal to 1 weight we base this on the conducted benchmarks which
/// determined runtime weights:
/// https://github.com/paritytech/substrate/pull/5446
const GAS_PER_SECOND: u64 = 1_000_000_000_000;
/// A private newtype for converting `ContractAccessError` into an RPC error.
struct ContractAccessError(cumulus_pallet_contracts_primitives::ContractAccessError);
impl From<ContractAccessError> for Error {
fn from(e: ContractAccessError) -> Error {
use cumulus_pallet_contracts_primitives::ContractAccessError::*;
match e.0 {
DoesntExist => Error {
code: ErrorCode::ServerError(CONTRACT_DOESNT_EXIST),
message: "The specified contract doesn't exist.".into(),
data: None,
},
IsTombstone => Error {
code: ErrorCode::ServerError(CONTRACT_IS_A_TOMBSTONE),
message: "The contract is a tombstone and doesn't have any storage.".into(),
data: None,
},
}
}
}
/// A struct that encodes RPC parameters required for a call to a smart-contract.
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CallRequest<AccountId, Balance> {
origin: AccountId,
dest: AccountId,
value: Balance,
gas_limit: number::NumberOrHex,
input_data: Bytes,
}
/// An RPC serializable result of contract execution
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub enum RpcContractExecResult {
/// Successful execution
Success {
/// The return flags
flags: u32,
/// Output data
data: Bytes,
/// How much gas was consumed by the call.
gas_consumed: u64,
},
/// Error execution
Error(()),
}
impl From<ContractExecResult> for RpcContractExecResult {
fn from(r: ContractExecResult) -> Self {
match r {
ContractExecResult::Success {
flags,
data,
gas_consumed,
} => RpcContractExecResult::Success {
flags,
data: data.into(),
gas_consumed,
},
ContractExecResult::Error => RpcContractExecResult::Error(()),
}
}
}
/// Contracts RPC methods.
#[rpc]
pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance> {
/// Executes a call to a contract.
///
/// This call is performed locally without submitting any transactions. Thus executing this
/// won't change any state. Nonetheless, the calling state-changing contracts is still possible.
///
/// This method is useful for calling getter-like methods on contracts.
#[rpc(name = "contracts_call")]
fn call(
&self,
call_request: CallRequest<AccountId, Balance>,
at: Option<BlockHash>,
) -> Result<RpcContractExecResult>;
/// Returns the value under a specified storage `key` in a contract given by `address` param,
/// or `None` if it is not set.
#[rpc(name = "contracts_getStorage")]
fn get_storage(
&self,
address: AccountId,
key: H256,
at: Option<BlockHash>,
) -> Result<Option<Bytes>>;
/// Returns the projected time a given contract will be able to sustain paying its rent.
///
/// The returned projection is relevant for the given block, i.e. it is as if the contract was
/// accessed at the beginning of that block.
///
/// Returns `None` if the contract is exempted from rent.
#[rpc(name = "contracts_rentProjection")]
fn rent_projection(
&self,
address: AccountId,
at: Option<BlockHash>,
) -> Result<Option<BlockNumber>>;
}
/// An implementation of contract specific RPC methods.
pub struct Contracts<C, B> {
client: Arc<C>,
_marker: std::marker::PhantomData<B>,
}
impl<C, B> Contracts<C, B> {
/// Create new `Contracts` with the given reference to the client.
pub fn new(client: Arc<C>) -> Self {
Contracts {
client,
_marker: Default::default(),
}
}
}
impl<C, Block, AccountId, Balance>
ContractsApi<
<Block as BlockT>::Hash,
<<Block as BlockT>::Header as HeaderT>::Number,
AccountId,
Balance,
> for Contracts<C, Block>
where
Block: BlockT,
C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
C::Api: ContractsRuntimeApi<
Block,
AccountId,
Balance,
<<Block as BlockT>::Header as HeaderT>::Number,
>,
AccountId: Codec,
Balance: Codec,
{
fn call(
&self,
call_request: CallRequest<AccountId, Balance>,
at: Option<<Block as BlockT>::Hash>,
) -> Result<RpcContractExecResult> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let CallRequest {
origin,
dest,
value,
gas_limit,
input_data,
} = call_request;
// Make sure that gas_limit fits into 64 bits.
let gas_limit: u64 = gas_limit.try_into().map_err(|_| Error {
code: ErrorCode::InvalidParams,
message: format!("{:?} doesn't fit in 64 bit unsigned value", gas_limit),
data: None,
})?;
let max_gas_limit = 5 * GAS_PER_SECOND;
if gas_limit > max_gas_limit {
return Err(Error {
code: ErrorCode::InvalidParams,
message: format!(
"Requested gas limit is greater than maximum allowed: {} > {}",
gas_limit, max_gas_limit
),
data: None,
});
}
let exec_result = api
.call(&at, origin, dest, value, gas_limit, input_data.to_vec())
.map_err(|e| runtime_error_into_rpc_err(e))?;
Ok(exec_result.into())
}
fn get_storage(
&self,
address: AccountId,
key: H256,
at: Option<<Block as BlockT>::Hash>,
) -> Result<Option<Bytes>> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let result = api
.get_storage(&at, address, key.into())
.map_err(|e| runtime_error_into_rpc_err(e))?
.map_err(ContractAccessError)?
.map(Bytes);
Ok(result)
}
fn rent_projection(
&self,
address: AccountId,
at: Option<<Block as BlockT>::Hash>,
) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash));
let result = api
.rent_projection(&at, address)
.map_err(|e| runtime_error_into_rpc_err(e))?
.map_err(ContractAccessError)?;
Ok(match result {
RentProjection::NoEviction => None,
RentProjection::EvictionAt(block_num) => Some(block_num),
})
}
}
/// Converts a runtime trap into an RPC error.
fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> Error {
Error {
code: ErrorCode::ServerError(RUNTIME_ERROR),
message: "Runtime trapped".into(),
data: Some(format!("{:?}", err).into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_core::U256;
#[test]
fn call_request_should_serialize_deserialize_properly() {
type Req = CallRequest<String, u128>;
let req: Req = serde_json::from_str(
r#"
{
"origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
"dest": "5DRakbLVnjVrW6niwLfHGW24EeCEvDAFGEXrtaYS5M4ynoom",
"value": 0,
"gasLimit": 1000000000000,
"inputData": "0x8c97db39"
}
"#,
)
.unwrap();
assert_eq!(req.gas_limit.into_u256(), U256::from(0xe8d4a51000u64));
}
#[test]
fn result_should_serialize_deserialize_properly() {
fn test(expected: &str) {
let res: RpcContractExecResult = serde_json::from_str(expected).unwrap();
let actual = serde_json::to_string(&res).unwrap();
assert_eq!(actual, expected);
}
test(r#"{"success":{"flags":5,"data":"0x1234","gas_consumed":99}}"#);
test(r#"{"error":null}"#);
}
}
@@ -1,450 +0,0 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Auxiliaries to help with managing partial changes to accounts state.
use super::{
AliveContractInfo, BalanceOf, CodeHash, ContractInfo, ContractInfoOf, Trait, TrieId,
TrieIdGenerator,
};
use crate::exec::StorageKey;
use frame_support::{
storage::unhashed as storage,
traits::{Currency, Imbalance, SignedImbalance},
StorageMap,
};
use frame_system;
use sp_io::hashing::blake2_256;
use sp_runtime::traits::{Bounded, Zero};
use sp_std::{
cell::RefCell,
collections::btree_map::{BTreeMap, Entry},
prelude::*,
};
// Note: we don't provide Option<Contract> because we can't create
// the trie_id in the overlay, thus we provide an overlay on the fields
// specifically.
pub struct ChangeEntry<T: Trait> {
/// If Some(_), then the account balance is modified to the value. If None and `reset` is false,
/// the balance unmodified. If None and `reset` is true, the balance is reset to 0.
balance: Option<BalanceOf<T>>,
/// If Some(_), then a contract is instantiated with the code hash. If None and `reset` is false,
/// then the contract code is unmodified. If None and `reset` is true, the contract is deleted.
code_hash: Option<CodeHash<T>>,
/// If Some(_), then the rent allowance is set to the value. If None and `reset` is false, then
/// the rent allowance is unmodified. If None and `reset` is true, the contract is deleted.
rent_allowance: Option<BalanceOf<T>>,
storage: BTreeMap<StorageKey, Option<Vec<u8>>>,
/// If true, indicates that the existing contract and all its storage entries should be removed
/// and replaced with the fields on this change entry. Otherwise, the fields on this change
/// entry are updates merged into the existing contract info and storage.
reset: bool,
}
impl<T: Trait> ChangeEntry<T> {
fn balance(&self) -> Option<BalanceOf<T>> {
self.balance.or_else(|| {
if self.reset {
Some(<BalanceOf<T>>::zero())
} else {
None
}
})
}
fn code_hash(&self) -> Option<Option<CodeHash<T>>> {
if self.reset {
Some(self.code_hash)
} else {
self.code_hash.map(Some)
}
}
fn rent_allowance(&self) -> Option<Option<BalanceOf<T>>> {
if self.reset {
Some(self.rent_allowance)
} else {
self.rent_allowance.map(Some)
}
}
fn storage(&self, location: &StorageKey) -> Option<Option<Vec<u8>>> {
let value = self.storage.get(location).cloned();
if self.reset {
Some(value.unwrap_or(None))
} else {
value
}
}
}
// Cannot derive(Default) since it erroneously bounds T by Default.
impl<T: Trait> Default for ChangeEntry<T> {
fn default() -> Self {
ChangeEntry {
rent_allowance: Default::default(),
balance: Default::default(),
code_hash: Default::default(),
storage: Default::default(),
reset: false,
}
}
}
pub type ChangeSet<T> = BTreeMap<<T as frame_system::Trait>::AccountId, ChangeEntry<T>>;
pub trait AccountDb<T: Trait> {
/// Account is used when overlayed otherwise trie_id must be provided.
/// This is for performance reason.
///
/// Trie id is None iff account doesn't have an associated trie id in <ContractInfoOf<T>>.
/// Because DirectAccountDb bypass the lookup for this association.
fn get_storage(
&self,
account: &T::AccountId,
trie_id: Option<&TrieId>,
location: &StorageKey,
) -> Option<Vec<u8>>;
/// If account has an alive contract then return the code hash associated.
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>>;
/// If account has an alive contract then return the rent allowance associated.
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>>;
/// Returns false iff account has no alive contract nor tombstone.
fn contract_exists(&self, account: &T::AccountId) -> bool;
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T>;
fn commit(&mut self, change_set: ChangeSet<T>);
}
pub struct DirectAccountDb;
impl<T: Trait> AccountDb<T> for DirectAccountDb {
fn get_storage(
&self,
_account: &T::AccountId,
trie_id: Option<&TrieId>,
location: &StorageKey,
) -> Option<Vec<u8>> {
trie_id.and_then(|id| storage::get_raw(&crate::prefixed_key(id, &blake2_256(location))))
}
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash))
}
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>> {
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.rent_allowance))
}
fn contract_exists(&self, account: &T::AccountId) -> bool {
<ContractInfoOf<T>>::contains_key(account)
}
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> {
T::Currency::free_balance(account)
}
fn commit(&mut self, s: ChangeSet<T>) {
let mut total_imbalance = SignedImbalance::zero();
for (address, changed) in s.into_iter() {
if let Some(balance) = changed.balance() {
let imbalance = T::Currency::make_free_balance_be(&address, balance);
total_imbalance = total_imbalance.merge(imbalance);
}
if changed.code_hash().is_some()
|| changed.rent_allowance().is_some()
|| !changed.storage.is_empty()
|| changed.reset
{
let old_info = match <ContractInfoOf<T>>::get(&address) {
Some(ContractInfo::Alive(alive)) => Some(alive),
None => None,
// Cannot commit changes to tombstone contract
Some(ContractInfo::Tombstone(_)) => continue,
};
let mut new_info = match (changed.reset, old_info.clone(), changed.code_hash) {
// Existing contract is being modified.
(false, Some(info), _) => info,
// Existing contract is being removed.
(true, Some(info), None) => {
storage::kill_prefix(&info.trie_id);
<ContractInfoOf<T>>::remove(&address);
continue;
}
// Existing contract is being replaced by a new one.
(true, Some(info), Some(code_hash)) => {
storage::kill_prefix(&info.trie_id);
AliveContractInfo::<T> {
code_hash,
storage_size: 0,
empty_pair_count: 0,
total_pair_count: 0,
trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address),
deduct_block: <frame_system::Module<T>>::block_number(),
rent_allowance: <BalanceOf<T>>::max_value(),
last_write: None,
}
}
// New contract is being instantiated.
(_, None, Some(code_hash)) => AliveContractInfo::<T> {
code_hash,
storage_size: 0,
empty_pair_count: 0,
total_pair_count: 0,
trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address),
deduct_block: <frame_system::Module<T>>::block_number(),
rent_allowance: <BalanceOf<T>>::max_value(),
last_write: None,
},
// There is no existing at the address nor a new one to be instantiated.
(_, None, None) => continue,
};
if let Some(rent_allowance) = changed.rent_allowance {
new_info.rent_allowance = rent_allowance;
}
if let Some(code_hash) = changed.code_hash {
new_info.code_hash = code_hash;
}
if !changed.storage.is_empty() {
new_info.last_write = Some(<frame_system::Module<T>>::block_number());
}
// Here we iterate over all storage key-value pairs that were changed throughout the
// execution of a contract and apply them to the substrate storage.
for (key, opt_new_value) in changed.storage.into_iter() {
let hashed_key = new_info.prefixed_key(&blake2_256(&key));
// In order to correctly update the book keeping we need to fetch the previous
// value of the key-value pair.
//
// It might be a bit more clean if we had an API that supported getting the size
// of the value without going through the loading of it. But at the moment of
// writing, there is no such API.
//
// That's not a show stopper in any case, since the performance cost is
// dominated by the trie traversal anyway.
let opt_prev_value = storage::get_raw(&hashed_key);
// Update the total number of KV pairs and the number of empty pairs.
match (&opt_prev_value, &opt_new_value) {
(Some(prev_value), None) => {
new_info.total_pair_count -= 1;
if prev_value.is_empty() {
new_info.empty_pair_count -= 1;
}
}
(None, Some(new_value)) => {
new_info.total_pair_count += 1;
if new_value.is_empty() {
new_info.empty_pair_count += 1;
}
}
(Some(prev_value), Some(new_value)) => {
if prev_value.is_empty() {
new_info.empty_pair_count -= 1;
}
if new_value.is_empty() {
new_info.empty_pair_count += 1;
}
}
(None, None) => {}
}
// Update the total storage size.
let prev_value_len = opt_prev_value
.as_ref()
.map(|old_value| old_value.len() as u32)
.unwrap_or(0);
let new_value_len = opt_new_value
.as_ref()
.map(|new_value| new_value.len() as u32)
.unwrap_or(0);
new_info.storage_size = new_info
.storage_size
.saturating_add(new_value_len)
.saturating_sub(prev_value_len);
// Finally, perform the change on the storage.
match opt_new_value {
Some(new_value) => storage::put_raw(&hashed_key, &new_value[..]),
None => storage::kill(&hashed_key),
}
}
if old_info
.map(|old_info| old_info != new_info)
.unwrap_or(true)
{
<ContractInfoOf<T>>::insert(&address, ContractInfo::Alive(new_info));
}
}
}
match total_imbalance {
// If we've detected a positive imbalance as a result of our contract-level machinations
// then it's indicative of a buggy contracts system.
// Panicking is far from ideal as it opens up a DoS attack on block validators, however
// it's a less bad option than allowing arbitrary value to be created.
SignedImbalance::Positive(ref p) if !p.peek().is_zero() => {
panic!("contract subsystem resulting in positive imbalance!")
}
_ => {}
}
}
}
pub struct OverlayAccountDb<'a, T: Trait + 'a> {
local: RefCell<ChangeSet<T>>,
underlying: &'a dyn AccountDb<T>,
}
impl<'a, T: Trait> OverlayAccountDb<'a, T> {
pub fn new(underlying: &'a dyn AccountDb<T>) -> OverlayAccountDb<'a, T> {
OverlayAccountDb {
local: RefCell::new(ChangeSet::new()),
underlying,
}
}
pub fn into_change_set(self) -> ChangeSet<T> {
self.local.into_inner()
}
pub fn set_storage(
&mut self,
account: &T::AccountId,
location: StorageKey,
value: Option<Vec<u8>>,
) {
self.local
.borrow_mut()
.entry(account.clone())
.or_insert(Default::default())
.storage
.insert(location, value);
}
/// Return an error if contract already exists (either if it is alive or tombstone)
pub fn instantiate_contract(
&mut self,
account: &T::AccountId,
code_hash: CodeHash<T>,
) -> Result<(), &'static str> {
if self.contract_exists(account) {
return Err("Alive contract or tombstone already exists");
}
let mut local = self.local.borrow_mut();
let contract = local.entry(account.clone()).or_default();
contract.code_hash = Some(code_hash);
contract.rent_allowance = Some(<BalanceOf<T>>::max_value());
Ok(())
}
/// Mark a contract as deleted.
pub fn destroy_contract(&mut self, account: &T::AccountId) {
let mut local = self.local.borrow_mut();
local.insert(
account.clone(),
ChangeEntry {
reset: true,
..Default::default()
},
);
}
/// Assume contract exists
pub fn set_rent_allowance(&mut self, account: &T::AccountId, rent_allowance: BalanceOf<T>) {
self.local
.borrow_mut()
.entry(account.clone())
.or_insert(Default::default())
.rent_allowance = Some(rent_allowance);
}
pub fn set_balance(&mut self, account: &T::AccountId, balance: BalanceOf<T>) {
self.local
.borrow_mut()
.entry(account.clone())
.or_insert(Default::default())
.balance = Some(balance);
}
}
impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
fn get_storage(
&self,
account: &T::AccountId,
trie_id: Option<&TrieId>,
location: &StorageKey,
) -> Option<Vec<u8>> {
self.local
.borrow()
.get(account)
.and_then(|changes| changes.storage(location))
.unwrap_or_else(|| self.underlying.get_storage(account, trie_id, location))
}
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
self.local
.borrow()
.get(account)
.and_then(|changes| changes.code_hash())
.unwrap_or_else(|| self.underlying.get_code_hash(account))
}
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>> {
self.local
.borrow()
.get(account)
.and_then(|changes| changes.rent_allowance())
.unwrap_or_else(|| self.underlying.get_rent_allowance(account))
}
fn contract_exists(&self, account: &T::AccountId) -> bool {
self.local
.borrow()
.get(account)
.and_then(|changes| changes.code_hash().map(|code_hash| code_hash.is_some()))
.unwrap_or_else(|| self.underlying.contract_exists(account))
}
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> {
self.local
.borrow()
.get(account)
.and_then(|changes| changes.balance())
.unwrap_or_else(|| self.underlying.get_balance(account))
}
fn commit(&mut self, s: ChangeSet<T>) {
let mut local = self.local.borrow_mut();
for (address, changed) in s.into_iter() {
match local.entry(address) {
Entry::Occupied(e) => {
let mut value = e.into_mut();
if changed.reset {
*value = changed;
} else {
value.balance = changed.balance.or(value.balance);
value.code_hash = changed.code_hash.or(value.code_hash);
value.rent_allowance = changed.rent_allowance.or(value.rent_allowance);
value.storage.extend(changed.storage.into_iter());
}
}
Entry::Vacant(e) => {
e.insert(changed);
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,348 +0,0 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::Trait;
use frame_support::dispatch::{
DispatchError, DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo,
};
use sp_runtime::traits::Zero;
use sp_std::marker::PhantomData;
#[cfg(test)]
use std::{any::Any, fmt::Debug};
// Gas is essentially the same as weight. It is a 1 to 1 correspondence.
pub type Gas = frame_support::weights::Weight;
#[must_use]
#[derive(Debug, PartialEq, Eq)]
pub enum GasMeterResult {
Proceed,
OutOfGas,
}
impl GasMeterResult {
pub fn is_out_of_gas(&self) -> bool {
match *self {
GasMeterResult::OutOfGas => true,
GasMeterResult::Proceed => false,
}
}
}
#[cfg(not(test))]
pub trait TestAuxiliaries {}
#[cfg(not(test))]
impl<T> TestAuxiliaries for T {}
#[cfg(test)]
pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
#[cfg(test)]
impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
/// This trait represents a token that can be used for charging `GasMeter`.
/// There is no other way of charging it.
///
/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added
/// for consistency). If inlined there should be no observable difference compared
/// to a hand-written code.
pub trait Token<T: Trait>: Copy + Clone + TestAuxiliaries {
/// Metadata type, which the token can require for calculating the amount
/// of gas to charge. Can be a some configuration type or
/// just the `()`.
type Metadata;
/// Calculate amount of gas that should be taken by this token.
///
/// This function should be really lightweight and must not fail. It is not
/// expected that implementors will query the storage or do any kinds of heavy operations.
///
/// That said, implementors of this function still can run into overflows
/// while calculating the amount. In this case it is ok to use saturating operations
/// since on overflow they will return `max_value` which should consume all gas.
fn calculate_amount(&self, metadata: &Self::Metadata) -> Gas;
}
/// A wrapper around a type-erased trait object of what used to be a `Token`.
#[cfg(test)]
pub struct ErasedToken {
pub description: String,
pub token: Box<dyn Any>,
}
pub struct GasMeter<T: Trait> {
gas_limit: Gas,
/// Amount of gas left from initial gas limit. Can reach zero.
gas_left: Gas,
_phantom: PhantomData<T>,
#[cfg(test)]
tokens: Vec<ErasedToken>,
}
impl<T: Trait> GasMeter<T> {
pub fn new(gas_limit: Gas) -> Self {
GasMeter {
gas_limit,
gas_left: gas_limit,
_phantom: PhantomData,
#[cfg(test)]
tokens: Vec::new(),
}
}
/// Account for used gas.
///
/// Amount is calculated by the given `token`.
///
/// Returns `OutOfGas` if there is not enough gas or addition of the specified
/// amount of gas has lead to overflow. On success returns `Proceed`.
///
/// NOTE that amount is always consumed, i.e. if there is not enough gas
/// then the counter will be set to zero.
#[inline]
pub fn charge<Tok: Token<T>>(
&mut self,
metadata: &Tok::Metadata,
token: Tok,
) -> GasMeterResult {
#[cfg(test)]
{
// Unconditionally add the token to the storage.
let erased_tok = ErasedToken {
description: format!("{:?}", token),
token: Box::new(token),
};
self.tokens.push(erased_tok);
}
let amount = token.calculate_amount(metadata);
let new_value = match self.gas_left.checked_sub(amount) {
None => None,
Some(val) => Some(val),
};
// We always consume the gas even if there is not enough gas.
self.gas_left = new_value.unwrap_or_else(Zero::zero);
match new_value {
Some(_) => GasMeterResult::Proceed,
None => GasMeterResult::OutOfGas,
}
}
// Account for not fully used gas.
//
// This can be used after dispatching a runtime call to refund gas that was not
// used by the dispatchable.
pub fn refund(&mut self, gas: Gas) {
self.gas_left = self.gas_left.saturating_add(gas).max(self.gas_limit);
}
/// Allocate some amount of gas and perform some work with
/// a newly created nested gas meter.
///
/// Invokes `f` with either the gas meter that has `amount` gas left or
/// with `None`, if this gas meter has not enough gas to allocate given `amount`.
///
/// All unused gas in the nested gas meter is returned to this gas meter.
pub fn with_nested<R, F: FnOnce(Option<&mut GasMeter<T>>) -> R>(
&mut self,
amount: Gas,
f: F,
) -> R {
// NOTE that it is ok to allocate all available gas since it still ensured
// by `charge` that it doesn't reach zero.
if self.gas_left < amount {
f(None)
} else {
self.gas_left = self.gas_left - amount;
let mut nested = GasMeter::new(amount);
let r = f(Some(&mut nested));
self.gas_left = self.gas_left + nested.gas_left;
r
}
}
/// Returns how much gas left from the initial budget.
pub fn gas_spent(&self) -> Gas {
self.gas_limit - self.gas_left
}
/// Returns how much gas left from the initial budget.
pub fn gas_left(&self) -> Gas {
self.gas_left
}
/// Turn this GasMeter into a DispatchResult that contains the actually used gas.
pub fn into_dispatch_result<R, E>(self, result: Result<R, E>) -> DispatchResultWithPostInfo
where
E: Into<DispatchError>,
{
let post_info = PostDispatchInfo {
actual_weight: Some(self.gas_spent()),
pays_fee: Default::default(),
};
result
.map(|_| post_info)
.map_err(|e| DispatchErrorWithPostInfo {
post_info,
error: e.into(),
})
}
#[cfg(test)]
pub fn tokens(&self) -> &[ErasedToken] {
&self.tokens
}
}
/// A simple utility macro that helps to match against a
/// list of tokens.
#[macro_export]
macro_rules! match_tokens {
($tokens_iter:ident,) => {
};
($tokens_iter:ident, $x:expr, $($rest:tt)*) => {
{
let next = ($tokens_iter).next().unwrap();
let pattern = $x;
// Note that we don't specify the type name directly in this macro,
// we only have some expression $x of some type. At the same time, we
// have an iterator of Box<dyn Any> and to downcast we need to specify
// the type which we want downcast to.
//
// So what we do is we assign `_pattern_typed_next_ref` to a variable which has
// the required type.
//
// Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes
// rustc infer the type `T` (in `downcast_ref<T: Any>`) to be the same as in $x.
let mut _pattern_typed_next_ref = &pattern;
_pattern_typed_next_ref = match next.token.downcast_ref() {
Some(p) => {
assert_eq!(p, &pattern);
p
}
None => {
panic!("expected type {} got {}", stringify!($x), next.description);
}
};
}
match_tokens!($tokens_iter, $($rest)*);
};
}
#[cfg(test)]
mod tests {
use super::{GasMeter, Token};
use crate::tests::Test;
/// A trivial token that charges the specified number of gas units.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct SimpleToken(u64);
impl Token<Test> for SimpleToken {
type Metadata = ();
fn calculate_amount(&self, _metadata: &()) -> u64 {
self.0
}
}
struct MultiplierTokenMetadata {
multiplier: u64,
}
/// A simple token that charges for the given amount multiplied to
/// a multiplier taken from a given metadata.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct MultiplierToken(u64);
impl Token<Test> for MultiplierToken {
type Metadata = MultiplierTokenMetadata;
fn calculate_amount(&self, metadata: &MultiplierTokenMetadata) -> u64 {
// Probably you want to use saturating mul in production code.
self.0 * metadata.multiplier
}
}
#[test]
fn it_works() {
let gas_meter = GasMeter::<Test>::new(50000);
assert_eq!(gas_meter.gas_left(), 50000);
}
#[test]
fn simple() {
let mut gas_meter = GasMeter::<Test>::new(50000);
let result = gas_meter.charge(
&MultiplierTokenMetadata { multiplier: 3 },
MultiplierToken(10),
);
assert!(!result.is_out_of_gas());
assert_eq!(gas_meter.gas_left(), 49_970);
}
#[test]
fn tracing() {
let mut gas_meter = GasMeter::<Test>::new(50000);
assert!(!gas_meter.charge(&(), SimpleToken(1)).is_out_of_gas());
assert!(!gas_meter
.charge(
&MultiplierTokenMetadata { multiplier: 3 },
MultiplierToken(10)
)
.is_out_of_gas());
let mut tokens = gas_meter.tokens()[0..2].iter();
match_tokens!(tokens, SimpleToken(1), MultiplierToken(10),);
}
// This test makes sure that nothing can be executed if there is no gas.
#[test]
fn refuse_to_execute_anything_if_zero() {
let mut gas_meter = GasMeter::<Test>::new(0);
assert!(gas_meter.charge(&(), SimpleToken(1)).is_out_of_gas());
}
// Make sure that if the gas meter is charged by exceeding amount then not only an error
// returned for that charge, but also for all consequent charges.
//
// This is not strictly necessary, because the execution should be interrupted immediately
// if the gas meter runs out of gas. However, this is just a nice property to have.
#[test]
fn overcharge_is_unrecoverable() {
let mut gas_meter = GasMeter::<Test>::new(200);
// The first charge is should lead to OOG.
assert!(gas_meter.charge(&(), SimpleToken(300)).is_out_of_gas());
// The gas meter is emptied at this moment, so this should also fail.
assert!(gas_meter.charge(&(), SimpleToken(1)).is_out_of_gas());
}
// Charging the exact amount that the user paid for should be
// possible.
#[test]
fn charge_exact_amount() {
let mut gas_meter = GasMeter::<Test>::new(25);
assert!(!gas_meter.charge(&(), SimpleToken(25)).is_out_of_gas());
}
}
@@ -1,927 +0,0 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! # Contract Module
//!
//! The Contract module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts.
//!
//! - [`contract::Trait`](./trait.Trait.html)
//! - [`Call`](./enum.Call.html)
//!
//! ## Overview
//!
//! This module extends accounts based on the `Currency` trait to have smart-contract functionality. It can
//! be used with other modules that implement accounts based on `Currency`. These "smart-contract accounts"
//! have the ability to instantiate smart-contracts and make calls to other contract and non-contract accounts.
//!
//! The smart-contract code is stored once in a `code_cache`, and later retrievable via its `code_hash`.
//! This means that multiple smart-contracts can be instantiated from the same `code_cache`, without replicating
//! the code each time.
//!
//! When a smart-contract is called, its associated code is retrieved via the code hash and gets executed.
//! This call can alter the storage entries of the smart-contract account, instantiate new smart-contracts,
//! or call other smart-contracts.
//!
//! Finally, when an account is reaped, its associated code and storage of the smart-contract account
//! will also be deleted.
//!
//! ### Gas
//!
//! Senders must specify a gas limit with every call, as all instructions invoked by the smart-contract require gas.
//! Unused gas is refunded after the call, regardless of the execution outcome.
//!
//! If the gas limit is reached, then all calls and state changes (including balance transfers) are only
//! reverted at the current call's contract level. For example, if contract A calls B and B runs out of gas mid-call,
//! then all of B's calls are reverted. Assuming correct error handling by contract A, A's other calls and state
//! changes still persist.
//!
//! ### Notable Scenarios
//!
//! Contract call failures are not always cascading. When failures occur in a sub-call, they do not "bubble up",
//! and the call will only revert at the specific contract level. For example, if contract A calls contract B, and B
//! fails, A can decide how to handle that failure, either proceeding or reverting A's changes.
//!
//! ## Interface
//!
//! ### Dispatchable functions
//!
//! * `put_code` - Stores the given binary Wasm code into the chain's storage and returns its `code_hash`.
//! * `instantiate` - Deploys a new contract from the given `code_hash`, optionally transferring some balance.
//! This instantiates a new smart contract account and calls its contract deploy handler to
//! initialize the contract.
//! * `call` - Makes a call to an account, optionally transferring some balance.
//!
//! ## Usage
//!
//! The Contract module is a work in progress. The following examples show how this Contract module
//! can be used to instantiate and call contracts.
//!
//! * [`ink`](https://github.com/paritytech/ink) is
//! an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables writing
//! WebAssembly based smart contracts in the Rust programming language. This is a work in progress.
//!
//! ## Related Modules
//!
//! * [Balances](../pallet_balances/index.html)
#![cfg_attr(not(feature = "std"), no_std)]
#[macro_use]
mod gas;
mod account_db;
mod exec;
mod rent;
mod wasm;
#[cfg(test)]
mod tests;
use crate::{
account_db::{AccountDb, DirectAccountDb},
exec::ExecutionContext,
wasm::{WasmLoader, WasmVm},
};
pub use crate::{
exec::{ExecError, ExecResult, ExecReturnValue, StatusCode},
gas::{Gas, GasMeter},
};
use codec::{Codec, Decode, Encode};
use cumulus_pallet_contracts_primitives::{ContractAccessError, RentProjection};
use frame_support::{
decl_error, decl_event, decl_module, decl_storage,
dispatch::{DispatchResult, DispatchResultWithPostInfo, Dispatchable, PostDispatchInfo},
parameter_types,
traits::{Currency, Get, OnUnbalanced, Randomness, Time},
weights::{GetDispatchInfo, Weight},
Parameter,
};
use frame_system::{ensure_root, ensure_signed, RawOrigin};
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use sp_core::crypto::UncheckedFrom;
use sp_runtime::{
traits::{Convert, Hash, MaybeSerializeDeserialize, Member, StaticLookup, Zero},
RuntimeDebug,
};
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
pub type CodeHash<T> = <T as frame_system::Trait>::Hash;
pub type TrieId = Vec<u8>;
/// A function that generates an `AccountId` for a contract upon instantiation.
pub trait ContractAddressFor<CodeHash, AccountId> {
fn contract_address_for(code_hash: &CodeHash, data: &[u8], origin: &AccountId) -> AccountId;
}
/// A function that returns the fee for dispatching a `Call`.
pub trait ComputeDispatchFee<Call, Balance> {
fn compute_dispatch_fee(call: &Call) -> Balance;
}
/// Information for managing an account and its sub trie abstraction.
/// This is the required info to cache for an account
#[derive(Encode, Decode, RuntimeDebug)]
pub enum ContractInfo<T: Trait> {
Alive(AliveContractInfo<T>),
Tombstone(TombstoneContractInfo<T>),
}
impl<T: Trait> ContractInfo<T> {
/// If contract is alive then return some alive info
pub fn get_alive(self) -> Option<AliveContractInfo<T>> {
if let ContractInfo::Alive(alive) = self {
Some(alive)
} else {
None
}
}
/// If contract is alive then return some reference to alive info
pub fn as_alive(&self) -> Option<&AliveContractInfo<T>> {
if let ContractInfo::Alive(ref alive) = self {
Some(alive)
} else {
None
}
}
/// If contract is alive then return some mutable reference to alive info
pub fn as_alive_mut(&mut self) -> Option<&mut AliveContractInfo<T>> {
if let ContractInfo::Alive(ref mut alive) = self {
Some(alive)
} else {
None
}
}
/// If contract is tombstone then return some tombstone info
pub fn get_tombstone(self) -> Option<TombstoneContractInfo<T>> {
if let ContractInfo::Tombstone(tombstone) = self {
Some(tombstone)
} else {
None
}
}
/// If contract is tombstone then return some reference to tombstone info
pub fn as_tombstone(&self) -> Option<&TombstoneContractInfo<T>> {
if let ContractInfo::Tombstone(ref tombstone) = self {
Some(tombstone)
} else {
None
}
}
/// If contract is tombstone then return some mutable reference to tombstone info
pub fn as_tombstone_mut(&mut self) -> Option<&mut TombstoneContractInfo<T>> {
if let ContractInfo::Tombstone(ref mut tombstone) = self {
Some(tombstone)
} else {
None
}
}
}
pub type AliveContractInfo<T> =
RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as frame_system::Trait>::BlockNumber>;
/// Information for managing an account and its sub trie abstraction.
/// This is the required info to cache for an account.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
/// Unique ID for the subtree encoded as a bytes vector.
pub trie_id: TrieId,
/// The total number of bytes used by this contract.
///
/// It is a sum of each key-value pair stored by this contract.
pub storage_size: u32,
/// The number of key-value pairs that have values of zero length.
/// The condition `empty_pair_count ≤ total_pair_count` always holds.
pub empty_pair_count: u32,
/// The total number of key-value pairs in storage of this contract.
pub total_pair_count: u32,
/// The code associated with a given account.
pub code_hash: CodeHash,
/// Pay rent at most up to this value.
pub rent_allowance: Balance,
/// Last block rent has been payed.
pub deduct_block: BlockNumber,
/// Last block child storage has been written.
pub last_write: Option<BlockNumber>,
}
impl<CodeHash, Balance, BlockNumber> RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
pub fn prefixed_key(&self, key: &[u8]) -> Vec<u8> {
prefixed_key(&self.trie_id, key)
}
}
pub(crate) fn prefixed_key(prefix: &[u8], key: &[u8]) -> Vec<u8> {
prefix.iter().chain(key.iter()).cloned().collect()
}
pub type TombstoneContractInfo<T> =
RawTombstoneContractInfo<<T as frame_system::Trait>::Hash, <T as frame_system::Trait>::Hashing>;
#[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);
impl<H, Hasher> RawTombstoneContractInfo<H, Hasher>
where
H: Member
+ MaybeSerializeDeserialize
+ Debug
+ AsRef<[u8]>
+ AsMut<[u8]>
+ Copy
+ Default
+ sp_std::hash::Hash
+ Codec,
Hasher: Hash<Output = H>,
{
fn new(storage_root: &[u8], code_hash: H) -> Self {
let mut buf = Vec::new();
storage_root.using_encoded(|encoded| buf.extend_from_slice(encoded));
buf.extend_from_slice(code_hash.as_ref());
RawTombstoneContractInfo(<Hasher as Hash>::hash(&buf[..]), PhantomData)
}
}
/// Get a trie id (trie id must be unique and collision resistant depending upon its context).
/// Note that it is different than encode because trie id should be collision resistant
/// (being a proper unique identifier).
pub trait TrieIdGenerator<AccountId> {
/// Get a trie id for an account, using reference to parent account trie id to ensure
/// uniqueness of trie id.
///
/// The implementation must ensure every new trie id is unique: two consecutive calls with the
/// same parameter needs to return different trie id values.
fn trie_id(account_id: &AccountId) -> TrieId;
}
/// Get trie id from `account_id`.
pub struct TrieIdFromParentCounter<T: Trait>(PhantomData<T>);
/// This generator uses inner counter for account id and applies the hash over `AccountId +
/// accountid_counter`.
impl<T: Trait> TrieIdGenerator<T::AccountId> for TrieIdFromParentCounter<T>
where
T::AccountId: AsRef<[u8]>,
{
fn trie_id(account_id: &T::AccountId) -> TrieId {
// Note that skipping a value due to error is not an issue here.
// We only need uniqueness, not sequence.
let new_seed = AccountCounter::mutate(|v| {
*v = v.wrapping_add(1);
*v
});
let mut buf = Vec::new();
buf.extend_from_slice(account_id.as_ref());
buf.extend_from_slice(&new_seed.to_le_bytes()[..]);
T::Hashing::hash(&buf[..]).as_ref().into()
}
}
pub type BalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
pub type NegativeImbalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::NegativeImbalance;
parameter_types! {
/// A reasonable default value for [`Trait::SignedClaimedHandicap`].
pub const DefaultSignedClaimHandicap: u32 = 2;
/// A reasonable default value for [`Trait::TombstoneDeposit`].
pub const DefaultTombstoneDeposit: u32 = 16;
/// A reasonable default value for [`Trait::StorageSizeOffset`].
pub const DefaultStorageSizeOffset: u32 = 8;
/// A reasonable default value for [`Trait::RentByteFee`].
pub const DefaultRentByteFee: u32 = 4;
/// A reasonable default value for [`Trait::RentDepositOffset`].
pub const DefaultRentDepositOffset: u32 = 1000;
/// A reasonable default value for [`Trait::SurchargeReward`].
pub const DefaultSurchargeReward: u32 = 150;
/// A reasonable default value for [`Trait::MaxDepth`].
pub const DefaultMaxDepth: u32 = 32;
/// A reasonable default value for [`Trait::MaxValueSize`].
pub const DefaultMaxValueSize: u32 = 16_384;
}
pub trait Trait: frame_system::Trait {
type Time: Time;
type Randomness: Randomness<Self::Hash>;
/// The currency in which fees are paid and contract balances are held.
type Currency: Currency<Self::AccountId>;
/// The outer call dispatch type.
type Call: Parameter
+ Dispatchable<PostInfo = PostDispatchInfo, Origin = <Self as frame_system::Trait>::Origin>
+ GetDispatchInfo;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
/// A function type to get the contract address given the instantiator.
type DetermineContractAddress: ContractAddressFor<CodeHash<Self>, Self::AccountId>;
/// trie id generator
type TrieIdGenerator: TrieIdGenerator<Self::AccountId>;
/// Handler for rent payments.
type RentPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// Number of block delay an extrinsic claim surcharge has.
///
/// When claim surcharge is called by an extrinsic the rent is checked
/// for current_block - delay
type SignedClaimHandicap: Get<Self::BlockNumber>;
/// The minimum amount required to generate a tombstone.
type TombstoneDeposit: Get<BalanceOf<Self>>;
/// A size offset for an contract. A just created account with untouched storage will have that
/// much of storage from the perspective of the state rent.
///
/// This is a simple way to ensure that contracts with empty storage eventually get deleted by
/// making them pay rent. This creates an incentive to remove them early in order to save rent.
type StorageSizeOffset: Get<u32>;
/// Price of a byte of storage per one block interval. Should be greater than 0.
type RentByteFee: Get<BalanceOf<Self>>;
/// The amount of funds a contract should deposit in order to offset
/// the cost of one byte.
///
/// Let's suppose the deposit is 1,000 BU (balance units)/byte and the rent is 1 BU/byte/day,
/// then a contract with 1,000,000 BU that uses 1,000 bytes of storage would pay no rent.
/// But if the balance reduced to 500,000 BU and the storage stayed the same at 1,000,
/// then it would pay 500 BU/day.
type RentDepositOffset: Get<BalanceOf<Self>>;
/// Reward that is received by the party whose touch has led
/// to removal of a contract.
type SurchargeReward: Get<BalanceOf<Self>>;
/// The maximum nesting level of a call/instantiate stack.
type MaxDepth: Get<u32>;
/// The maximum size of a storage value in bytes.
type MaxValueSize: Get<u32>;
/// Used to answer contracts's queries regarding the current weight price. This is **not**
/// used to calculate the actual fee and is only for informational purposes.
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
}
/// Simple contract address determiner.
///
/// Address calculated from the code (of the constructor), input data to the constructor,
/// and the account id that requested the account creation.
///
/// Formula: `blake2_256(blake2_256(code) + blake2_256(data) + origin)`
pub struct SimpleAddressDeterminer<T: Trait>(PhantomData<T>);
impl<T: Trait> ContractAddressFor<CodeHash<T>, T::AccountId> for SimpleAddressDeterminer<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
fn contract_address_for(
code_hash: &CodeHash<T>,
data: &[u8],
origin: &T::AccountId,
) -> T::AccountId {
let data_hash = T::Hashing::hash(data);
let mut buf = Vec::new();
buf.extend_from_slice(code_hash.as_ref());
buf.extend_from_slice(data_hash.as_ref());
buf.extend_from_slice(origin.as_ref());
UncheckedFrom::unchecked_from(T::Hashing::hash(&buf[..]))
}
}
decl_error! {
/// Error for the contracts module.
pub enum Error for Module<T: Trait> {
/// A new schedule must have a greater version than the current one.
InvalidScheduleVersion,
/// An origin must be signed or inherent and auxiliary sender only provided on inherent.
InvalidSurchargeClaim,
/// Cannot restore from nonexisting or tombstone contract.
InvalidSourceContract,
/// Cannot restore to nonexisting or alive contract.
InvalidDestinationContract,
/// Tombstones don't match.
InvalidTombstone,
/// An origin TrieId written in the current block.
InvalidContractOrigin
}
}
decl_module! {
/// Contracts module.
pub struct Module<T: Trait> for enum Call where origin: <T as frame_system::Trait>::Origin {
type Error = Error<T>;
/// Number of block delay an extrinsic claim surcharge has.
///
/// When claim surcharge is called by an extrinsic the rent is checked
/// for current_block - delay
const SignedClaimHandicap: T::BlockNumber = T::SignedClaimHandicap::get();
/// The minimum amount required to generate a tombstone.
const TombstoneDeposit: BalanceOf<T> = T::TombstoneDeposit::get();
/// A size offset for an contract. A just created account with untouched storage will have that
/// much of storage from the perspective of the state rent.
///
/// This is a simple way to ensure that contracts with empty storage eventually get deleted
/// by making them pay rent. This creates an incentive to remove them early in order to save
/// rent.
const StorageSizeOffset: u32 = T::StorageSizeOffset::get();
/// Price of a byte of storage per one block interval. Should be greater than 0.
const RentByteFee: BalanceOf<T> = T::RentByteFee::get();
/// The amount of funds a contract should deposit in order to offset
/// the cost of one byte.
///
/// Let's suppose the deposit is 1,000 BU (balance units)/byte and the rent is 1 BU/byte/day,
/// then a contract with 1,000,000 BU that uses 1,000 bytes of storage would pay no rent.
/// But if the balance reduced to 500,000 BU and the storage stayed the same at 1,000,
/// then it would pay 500 BU/day.
const RentDepositOffset: BalanceOf<T> = T::RentDepositOffset::get();
/// Reward that is received by the party whose touch has led
/// to removal of a contract.
const SurchargeReward: BalanceOf<T> = T::SurchargeReward::get();
/// The maximum nesting level of a call/instantiate stack. A reasonable default
/// value is 100.
const MaxDepth: u32 = T::MaxDepth::get();
/// The maximum size of a storage value in bytes. A reasonable default is 16 KiB.
const MaxValueSize: u32 = T::MaxValueSize::get();
fn deposit_event() = default;
/// Updates the schedule for metering contracts.
///
/// The schedule must have a greater version than the stored schedule.
#[weight = 0]
pub fn update_schedule(origin, schedule: Schedule) -> DispatchResult {
ensure_root(origin)?;
if <Module<T>>::current_schedule().version >= schedule.version {
Err(Error::<T>::InvalidScheduleVersion)?
}
Self::deposit_event(RawEvent::ScheduleUpdated(schedule.version));
CurrentSchedule::put(schedule);
Ok(())
}
/// Stores the given binary Wasm code into the chain's storage and returns its `codehash`.
/// You can instantiate contracts only with stored code.
#[weight = Module::<T>::calc_code_put_costs(&code)]
pub fn put_code(
origin,
code: Vec<u8>
) -> DispatchResult {
ensure_signed(origin)?;
let schedule = <Module<T>>::current_schedule();
let result = wasm::save_code::<T>(code, &schedule);
if let Ok(code_hash) = result {
Self::deposit_event(RawEvent::CodeStored(code_hash));
}
result.map(|_| ()).map_err(Into::into)
}
/// Makes a call to an account, optionally transferring some balance.
///
/// * If the account is a smart-contract account, the associated code will be
/// executed and any value will be transferred.
/// * If the account is a regular account, any value will be transferred.
/// * If no account exists and the call value is not less than `existential_deposit`,
/// a regular account will be created and any value will be transferred.
#[weight = *gas_limit]
pub fn call(
origin,
dest: <T::Lookup as StaticLookup>::Source,
#[compact] value: BalanceOf<T>,
#[compact] gas_limit: Gas,
data: Vec<u8>
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, data)
});
gas_meter.into_dispatch_result(result.map_err(|e| e.reason))
}
/// Instantiates a new contract from the `codehash` generated by `put_code`, optionally transferring some balance.
///
/// Instantiation is executed as follows:
///
/// - The destination address is computed based on the sender and hash of the code.
/// - The smart-contract account is created at the computed address.
/// - The `ctor_code` is executed in the context of the newly-created account. Buffer returned
/// after the execution is saved as the `code` of the account. That code will be invoked
/// upon any call received by this account.
/// - The contract is initialized.
#[weight = *gas_limit]
pub fn instantiate(
origin,
#[compact] endowment: BalanceOf<T>,
#[compact] gas_limit: Gas,
code_hash: CodeHash<T>,
data: Vec<u8>
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.instantiate(endowment, gas_meter, &code_hash, data)
.map(|(_address, output)| output)
});
gas_meter.into_dispatch_result(result.map_err(|e| e.reason))
}
/// Allows block producers to claim a small reward for evicting a contract. If a block producer
/// fails to do so, a regular users will be allowed to claim the reward.
///
/// If contract is not evicted as a result of this call, no actions are taken and
/// the sender is not eligible for the reward.
#[weight = 0]
fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option<T::AccountId>) {
let origin = origin.into();
let (signed, rewarded) = match (origin, aux_sender) {
(Ok(frame_system::RawOrigin::Signed(account)), None) => {
(true, account)
},
(Ok(frame_system::RawOrigin::None), Some(aux_sender)) => {
(false, aux_sender)
},
_ => Err(Error::<T>::InvalidSurchargeClaim)?,
};
// Add some advantage for block producers (who send unsigned extrinsics) by
// adding a handicap: for signed extrinsics we use a slightly older block number
// for the eviction check. This can be viewed as if we pushed regular users back in past.
let handicap = if signed {
T::SignedClaimHandicap::get()
} else {
Zero::zero()
};
// If poking the contract has lead to eviction of the contract, give out the rewards.
if rent::snitch_contract_should_be_evicted::<T>(&dest, handicap) {
T::Currency::deposit_into_existing(&rewarded, T::SurchargeReward::get())?;
}
}
}
}
/// Public APIs provided by the contracts module.
impl<T: Trait> Module<T> {
/// Perform a call to a specified contract.
///
/// This function is similar to `Self::call`, but doesn't perform any address lookups and better
/// suitable for calling directly from Rust.
///
/// It returns the exection result and the amount of used weight.
pub fn bare_call(
origin: T::AccountId,
dest: T::AccountId,
value: BalanceOf<T>,
gas_limit: Gas,
input_data: Vec<u8>,
) -> (ExecResult, Gas) {
let mut gas_meter = GasMeter::new(gas_limit);
(
Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, input_data)
}),
gas_meter.gas_spent(),
)
}
/// Query storage of a specified contract under a specified key.
pub fn get_storage(
address: T::AccountId,
key: [u8; 32],
) -> sp_std::result::Result<Option<Vec<u8>>, ContractAccessError> {
let contract_info = <ContractInfoOf<T>>::get(&address)
.ok_or(ContractAccessError::DoesntExist)?
.get_alive()
.ok_or(ContractAccessError::IsTombstone)?;
let maybe_value = AccountDb::<T>::get_storage(
&DirectAccountDb,
&address,
Some(&contract_info.trie_id),
&key,
);
Ok(maybe_value)
}
pub fn rent_projection(
address: T::AccountId,
) -> sp_std::result::Result<RentProjection<T::BlockNumber>, ContractAccessError> {
rent::compute_rent_projection::<T>(&address)
}
}
impl<T: Trait> Module<T> {
fn calc_code_put_costs(code: &Vec<u8>) -> Gas {
<Module<T>>::current_schedule()
.put_code_per_byte_cost
.saturating_mul(code.len() as Gas)
}
fn execute_wasm(
origin: T::AccountId,
gas_meter: &mut GasMeter<T>,
func: impl FnOnce(&mut ExecutionContext<T, WasmVm, WasmLoader>, &mut GasMeter<T>) -> ExecResult,
) -> ExecResult {
let cfg = Config::preload();
let vm = WasmVm::new(&cfg.schedule);
let loader = WasmLoader::new(&cfg.schedule);
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
let result = func(&mut ctx, gas_meter);
if result
.as_ref()
.map(|output| output.is_success())
.unwrap_or(false)
{
// Commit all changes that made it thus far into the persistent storage.
DirectAccountDb.commit(ctx.overlay.into_change_set());
}
// Execute deferred actions.
ctx.deferred.into_iter().for_each(|deferred| {
use self::exec::DeferredAction::*;
match deferred {
DepositEvent { topics, event } => <frame_system::Module<T>>::deposit_event_indexed(
&*topics,
<T as Trait>::Event::from(event).into(),
),
DispatchRuntimeCall { origin: who, call } => {
let info = call.get_dispatch_info();
let result = call.dispatch(RawOrigin::Signed(who.clone()).into());
let post_info = match result {
Ok(post_info) => post_info,
Err(err) => err.post_info,
};
gas_meter.refund(post_info.calc_unspent(&info));
Self::deposit_event(RawEvent::Dispatched(who, result.is_ok()));
}
RestoreTo {
donor,
dest,
code_hash,
rent_allowance,
delta,
} => {
let result = Self::restore_to(
donor.clone(),
dest.clone(),
code_hash.clone(),
rent_allowance.clone(),
delta,
);
Self::deposit_event(RawEvent::Restored(
donor,
dest,
code_hash,
rent_allowance,
result.is_ok(),
));
}
}
});
result
}
fn restore_to(
_origin: T::AccountId,
_dest: T::AccountId,
_code_hash: CodeHash<T>,
_rent_allowance: BalanceOf<T>,
_delta: Vec<exec::StorageKey>,
) -> DispatchResult {
Err("Restoration currently unsupported for parachains")?
}
}
decl_event! {
pub enum Event<T>
where
Balance = BalanceOf<T>,
<T as frame_system::Trait>::AccountId,
<T as frame_system::Trait>::Hash
{
/// Transfer happened `from` to `to` with given `value` as part of a `call` or `instantiate`.
Transfer(AccountId, AccountId, Balance),
/// Contract deployed by address at the specified address.
Instantiated(AccountId, AccountId),
/// Contract has been evicted and is now in tombstone state.
///
/// # Params
///
/// - `contract`: `AccountId`: The account ID of the evicted contract.
/// - `tombstone`: `bool`: True if the evicted contract left behind a tombstone.
Evicted(AccountId, bool),
/// Restoration for a contract has been initiated.
///
/// # Params
///
/// - `donor`: `AccountId`: Account ID of the restoring contract
/// - `dest`: `AccountId`: Account ID of the restored contract
/// - `code_hash`: `Hash`: Code hash of the restored contract
/// - `rent_allowance: `Balance`: Rent allowance of the restored contract
/// - `success`: `bool`: True if the restoration was successful
Restored(AccountId, AccountId, Hash, Balance, bool),
/// Code with the specified hash has been stored.
CodeStored(Hash),
/// Triggered when the current schedule is updated.
ScheduleUpdated(u32),
/// A call was dispatched from the given account. The bool signals whether it was
/// successful execution or not.
Dispatched(AccountId, bool),
/// An event deposited upon execution of a contract from the account.
ContractExecution(AccountId, Vec<u8>),
}
}
decl_storage! {
trait Store for Module<T: Trait> as Contracts {
/// Current cost schedule for contracts.
CurrentSchedule get(fn current_schedule) config(): Schedule = Schedule::default();
/// A mapping from an original code hash to the original code, untouched by instrumentation.
pub PristineCode: map hasher(identity) CodeHash<T> => Option<Vec<u8>>;
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
pub CodeStorage: map hasher(identity) CodeHash<T> => Option<wasm::PrefabWasmModule>;
/// The subtrie counter.
pub AccountCounter: u64 = 0;
/// The code associated with a given account.
///
/// TWOX-NOTE: SAFE since `AccountId` is a secure hash.
pub ContractInfoOf: map hasher(twox_64_concat) T::AccountId => Option<ContractInfo<T>>;
}
}
/// In-memory cache of configuration values.
///
/// We assume that these values can't be changed in the
/// course of transaction execution.
pub struct Config<T: Trait> {
pub schedule: Schedule,
pub existential_deposit: BalanceOf<T>,
pub tombstone_deposit: BalanceOf<T>,
pub max_depth: u32,
pub max_value_size: u32,
}
impl<T: Trait> Config<T> {
fn preload() -> Config<T> {
Config {
schedule: <Module<T>>::current_schedule(),
existential_deposit: T::Currency::minimum_balance(),
tombstone_deposit: T::TombstoneDeposit::get(),
max_depth: T::MaxDepth::get(),
max_value_size: T::MaxValueSize::get(),
}
}
}
/// Definition of the cost schedule and other parameterizations for wasm vm.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct Schedule {
/// Version of the schedule.
pub version: u32,
/// Cost of putting a byte of code into storage.
pub put_code_per_byte_cost: Gas,
/// Gas cost of a growing memory by single page.
pub grow_mem_cost: Gas,
/// Gas cost of a regular operation.
pub regular_op_cost: Gas,
/// Gas cost per one byte returned.
pub return_data_per_byte_cost: Gas,
/// Gas cost to deposit an event; the per-byte portion.
pub event_data_per_byte_cost: Gas,
/// Gas cost to deposit an event; the cost per topic.
pub event_per_topic_cost: Gas,
/// Gas cost to deposit an event; the base.
pub event_base_cost: Gas,
/// Base gas cost to call into a contract.
pub call_base_cost: Gas,
/// Base gas cost to instantiate a contract.
pub instantiate_base_cost: Gas,
/// Base gas cost to dispatch a runtime call.
pub dispatch_base_cost: Gas,
/// Gas cost per one byte read from the sandbox memory.
pub sandbox_data_read_cost: Gas,
/// Gas cost per one byte written to the sandbox memory.
pub sandbox_data_write_cost: Gas,
/// Cost for a simple balance transfer.
pub transfer_cost: Gas,
/// Cost for instantiating a new contract.
pub instantiate_cost: Gas,
/// The maximum number of topics supported by an event.
pub max_event_topics: u32,
/// Maximum allowed stack height.
///
/// See https://wiki.parity.io/WebAssembly-StackHeight to find out
/// how the stack frame cost is calculated.
pub max_stack_height: u32,
/// Maximum number of memory pages allowed for a contract.
pub max_memory_pages: u32,
/// Maximum allowed size of a declared table.
pub max_table_size: u32,
/// Whether the `ext_println` function is allowed to be used contracts.
/// MUST only be enabled for `dev` chains, NOT for production chains
pub enable_println: bool,
/// The maximum length of a subject used for PRNG generation.
pub max_subject_len: u32,
}
// 500 (2 instructions per nano second on 2GHZ) * 1000x slowdown through wasmi
// This is a wild guess and should be viewed as a rough estimation.
// Proper benchmarks are needed before this value and its derivatives can be used in production.
const WASM_INSTRUCTION_COST: Gas = 500_000;
impl Default for Schedule {
fn default() -> Schedule {
Schedule {
version: 0,
put_code_per_byte_cost: WASM_INSTRUCTION_COST,
grow_mem_cost: WASM_INSTRUCTION_COST,
regular_op_cost: WASM_INSTRUCTION_COST,
return_data_per_byte_cost: WASM_INSTRUCTION_COST,
event_data_per_byte_cost: WASM_INSTRUCTION_COST,
event_per_topic_cost: WASM_INSTRUCTION_COST,
event_base_cost: WASM_INSTRUCTION_COST,
call_base_cost: 135 * WASM_INSTRUCTION_COST,
dispatch_base_cost: 135 * WASM_INSTRUCTION_COST,
instantiate_base_cost: 175 * WASM_INSTRUCTION_COST,
sandbox_data_read_cost: WASM_INSTRUCTION_COST,
sandbox_data_write_cost: WASM_INSTRUCTION_COST,
transfer_cost: 100 * WASM_INSTRUCTION_COST,
instantiate_cost: 200 * WASM_INSTRUCTION_COST,
max_event_topics: 4,
max_stack_height: 64 * 1024,
max_memory_pages: 16,
max_table_size: 16 * 1024,
enable_println: false,
max_subject_len: 32,
}
}
}
@@ -1,393 +0,0 @@
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! A module responsible for computing the right amount of weight and charging it.
use crate::{
AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Module, RawEvent,
TombstoneContractInfo, Trait,
};
use cumulus_pallet_contracts_primitives::{
ContractAccessError, RentProjection, RentProjectionResult,
};
use frame_support::{
storage::unhashed as storage,
traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReason},
StorageMap,
};
use sp_runtime::traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero};
/// The amount to charge.
///
/// This amount respects the contract's rent allowance and the subsistence deposit.
/// Because of that, charging the amount cannot remove the contract.
struct OutstandingAmount<T: Trait> {
amount: BalanceOf<T>,
}
impl<T: Trait> OutstandingAmount<T> {
/// Create the new outstanding amount.
///
/// The amount should be always withdrawable and it should not kill the account.
fn new(amount: BalanceOf<T>) -> Self {
Self { amount }
}
/// Returns the amount this instance wraps.
fn peek(&self) -> BalanceOf<T> {
self.amount
}
/// Withdraws the outstanding amount from the given account.
fn withdraw(self, account: &T::AccountId) {
if let Ok(imbalance) = T::Currency::withdraw(
account,
self.amount,
WithdrawReason::Fee.into(),
ExistenceRequirement::KeepAlive,
) {
// This should never fail. However, let's err on the safe side.
T::RentPayment::on_unbalanced(imbalance);
}
}
}
enum Verdict<T: Trait> {
/// The contract is exempted from paying rent.
///
/// For example, it already paid its rent in the current block, or it has enough deposit for not
/// paying rent at all.
Exempt,
/// Funds dropped below the subsistence deposit.
///
/// Remove the contract along with it's storage.
Kill,
/// The contract cannot afford payment within its rent budget so it gets evicted. However,
/// because its balance is greater than the subsistence threshold it leaves a tombstone.
Evict {
amount: Option<OutstandingAmount<T>>,
},
/// Everything is OK, we just only take some charge.
Charge { amount: OutstandingAmount<T> },
}
/// Returns a fee charged per block from the contract.
///
/// This function accounts for the storage rent deposit. I.e. if the contract possesses enough funds
/// then the fee can drop to zero.
fn compute_fee_per_block<T: Trait>(
balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> BalanceOf<T> {
let free_storage = balance
.checked_div(&T::RentDepositOffset::get())
.unwrap_or_else(Zero::zero);
// For now, we treat every empty KV pair as if it was one byte long.
let empty_pairs_equivalent = contract.empty_pair_count;
let effective_storage_size = <BalanceOf<T>>::from(
contract.storage_size + T::StorageSizeOffset::get() + empty_pairs_equivalent,
)
.saturating_sub(free_storage);
effective_storage_size
.checked_mul(&T::RentByteFee::get())
.unwrap_or(<BalanceOf<T>>::max_value())
}
/// Subsistence threshold is the extension of the minimum balance (aka existential deposit) by the
/// tombstone deposit, required for leaving a tombstone.
///
/// Rent mechanism cannot make the balance lower than subsistence threshold.
fn subsistence_threshold<T: Trait>() -> BalanceOf<T> {
T::Currency::minimum_balance() + T::TombstoneDeposit::get()
}
/// Returns amount of funds available to consume by rent mechanism.
///
/// Rent mechanism cannot consume more than `rent_allowance` set by the contract and it cannot make
/// the balance lower than [`subsistence_threshold`].
///
/// In case the balance is below the subsistence threshold, this function returns `None`.
fn rent_budget<T: Trait>(
balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> Option<BalanceOf<T>> {
let subsistence_threshold = subsistence_threshold::<T>();
if *balance < subsistence_threshold {
return None;
}
let rent_allowed_to_charge = *balance - subsistence_threshold;
Some(<BalanceOf<T>>::min(
contract.rent_allowance,
rent_allowed_to_charge,
))
}
/// Consider the case for rent payment of the given account and returns a `Verdict`.
///
/// Use `handicap` in case you want to change the reference block number. (To get more details see
/// `snitch_contract_should_be_evicted` ).
fn consider_case<T: Trait>(
account: &T::AccountId,
current_block_number: T::BlockNumber,
handicap: T::BlockNumber,
contract: &AliveContractInfo<T>,
) -> Verdict<T> {
// How much block has passed since the last deduction for the contract.
let blocks_passed = {
// Calculate an effective block number, i.e. after adjusting for handicap.
let effective_block_number = current_block_number.saturating_sub(handicap);
effective_block_number.saturating_sub(contract.deduct_block)
};
if blocks_passed.is_zero() {
// Rent has already been paid
return Verdict::Exempt;
}
let balance = T::Currency::free_balance(account);
// An amount of funds to charge per block for storage taken up by the contract.
let fee_per_block = compute_fee_per_block::<T>(&balance, contract);
if fee_per_block.is_zero() {
// The rent deposit offset reduced the fee to 0. This means that the contract
// gets the rent for free.
return Verdict::Exempt;
}
let rent_budget = match rent_budget::<T>(&balance, contract) {
Some(rent_budget) => rent_budget,
None => {
// The contract's balance is already below subsistence threshold. That indicates that
// the contract cannot afford to leave a tombstone.
//
// So cleanly wipe the contract.
return Verdict::Kill;
}
};
let dues = fee_per_block
.checked_mul(&blocks_passed.saturated_into::<u32>().into())
.unwrap_or(<BalanceOf<T>>::max_value());
let insufficient_rent = rent_budget < dues;
// If the rent payment cannot be withdrawn due to locks on the account balance, then evict the
// account.
//
// NOTE: This seems problematic because it provides a way to tombstone an account while
// avoiding the last rent payment. In effect, someone could retroactively set rent_allowance
// for their contract to 0.
let dues_limited = dues.min(rent_budget);
let can_withdraw_rent = T::Currency::ensure_can_withdraw(
account,
dues_limited,
WithdrawReason::Fee.into(),
balance.saturating_sub(dues_limited),
)
.is_ok();
if insufficient_rent || !can_withdraw_rent {
// The contract cannot afford the rent payment and has a balance above the subsistence
// threshold, so it leaves a tombstone.
let amount = if can_withdraw_rent {
Some(OutstandingAmount::new(dues_limited))
} else {
None
};
return Verdict::Evict { amount };
}
return Verdict::Charge {
// We choose to use `dues_limited` here instead of `dues` just to err on the safer side.
amount: OutstandingAmount::new(dues_limited),
};
}
/// Enacts the given verdict and returns the updated `ContractInfo`.
///
/// `alive_contract_info` should be from the same address as `account`.
fn enact_verdict<T: Trait>(
account: &T::AccountId,
alive_contract_info: AliveContractInfo<T>,
current_block_number: T::BlockNumber,
verdict: Verdict<T>,
) -> Option<ContractInfo<T>> {
match verdict {
Verdict::Exempt => return Some(ContractInfo::Alive(alive_contract_info)),
Verdict::Kill => {
<ContractInfoOf<T>>::remove(account);
storage::kill_prefix(&alive_contract_info.trie_id);
<Module<T>>::deposit_event(RawEvent::Evicted(account.clone(), false));
None
}
Verdict::Evict { amount } => {
if let Some(amount) = amount {
amount.withdraw(account);
}
// Use a dummy storage root because restoration is currentlyy unsupported
// for parachains anyways.
let tombstone =
<TombstoneContractInfo<T>>::new(&[0u8; 32], alive_contract_info.code_hash);
let tombstone_info = ContractInfo::Tombstone(tombstone);
<ContractInfoOf<T>>::insert(account, &tombstone_info);
storage::kill_prefix(&alive_contract_info.trie_id);
<Module<T>>::deposit_event(RawEvent::Evicted(account.clone(), true));
Some(tombstone_info)
}
Verdict::Charge { amount } => {
let contract_info = ContractInfo::Alive(AliveContractInfo::<T> {
rent_allowance: alive_contract_info.rent_allowance - amount.peek(),
deduct_block: current_block_number,
..alive_contract_info
});
<ContractInfoOf<T>>::insert(account, &contract_info);
amount.withdraw(account);
Some(contract_info)
}
}
}
/// Make account paying the rent for the current block number
///
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
/// storage.
pub fn collect_rent<T: Trait>(account: &T::AccountId) -> Option<ContractInfo<T>> {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return contract_info,
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = consider_case::<T>(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
);
enact_verdict(account, alive_contract_info, current_block_number, verdict)
}
/// Process a report that a contract under the given address should be evicted.
///
/// Enact the eviction right away if the contract should be evicted and return true.
/// Otherwise, **do nothing** and return false.
///
/// The `handicap` parameter gives a way to check the rent to a moment in the past instead
/// of current block. E.g. if the contract is going to be evicted at the current block,
/// `handicap = 1` can defer the eviction for 1 block. This is useful to handicap certain snitchers
/// relative to others.
///
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
/// storage.
pub fn snitch_contract_should_be_evicted<T: Trait>(
account: &T::AccountId,
handicap: T::BlockNumber,
) -> bool {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return false,
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = consider_case::<T>(
account,
current_block_number,
handicap,
&alive_contract_info,
);
// Enact the verdict only if the contract gets removed.
match verdict {
Verdict::Kill | Verdict::Evict { .. } => {
enact_verdict(account, alive_contract_info, current_block_number, verdict);
true
}
_ => false,
}
}
/// Returns the projected time a given contract will be able to sustain paying its rent. The
/// returned projection is relevant for the current block, i.e. it is as if the contract was
/// accessed at the beginning of the current block. Returns `None` in case if the contract was
/// evicted before or as a result of the rent collection.
///
/// The returned value is only an estimation. It doesn't take into account any top ups, changing the
/// rent allowance, or any problems coming from withdrawing the dues.
///
/// NOTE that this is not a side-effect free function! It will actually collect rent and then
/// compute the projection. This function is only used for implementation of an RPC method through
/// `RuntimeApi` meaning that the changes will be discarded anyway.
pub fn compute_rent_projection<T: Trait>(
account: &T::AccountId,
) -> RentProjectionResult<T::BlockNumber> {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAccessError::IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = consider_case::<T>(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
);
let new_contract_info =
enact_verdict(account, alive_contract_info, current_block_number, verdict);
// Check what happened after enaction of the verdict.
let alive_contract_info = match new_contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAccessError::IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
// Compute how much would the fee per block be with the *updated* balance.
let balance = T::Currency::free_balance(account);
let fee_per_block = compute_fee_per_block::<T>(&balance, &alive_contract_info);
if fee_per_block.is_zero() {
return Ok(RentProjection::NoEviction);
}
// Then compute how much the contract will sustain under these circumstances.
let rent_budget = rent_budget::<T>(&balance, &alive_contract_info).expect(
"the contract exists and in the alive state;
the updated balance must be greater than subsistence deposit;
this function doesn't return `None`;
qed
",
);
let blocks_left = match rent_budget.checked_div(&fee_per_block) {
Some(blocks_left) => blocks_left,
None => {
// `fee_per_block` is not zero here, so `checked_div` can return `None` if
// there is an overflow. This cannot happen with integers though. Return
// `NoEviction` here just in case.
return Ok(RentProjection::NoEviction);
}
};
let blocks_left = blocks_left.saturated_into::<u32>().into();
Ok(RentProjection::EvictionAt(
current_block_number + blocks_left,
))
}
File diff suppressed because it is too large Load Diff
@@ -1,76 +0,0 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! A module that implements instrumented code cache.
//!
//! - In order to run contract code we need to instrument it with gas metering.
//! To do that we need to provide the schedule which will supply exact gas costs values.
//! We cache this code in the storage saving the schedule version.
//! - Before running contract code we check if the cached code has the schedule version that
//! is equal to the current saved schedule.
//! If it is equal then run the code, if it isn't reinstrument with the current schedule.
//! - When we update the schedule we want it to have strictly greater version than the current saved one:
//! this guarantees that every instrumented contract code in cache cannot have the version equal to the current one.
//! Thus, before executing a contract it should be reinstrument with new schedule.
use crate::{
wasm::{prepare, runtime::Env, PrefabWasmModule},
CodeHash, CodeStorage, PristineCode, Schedule, Trait,
};
use frame_support::StorageMap;
use sp_runtime::traits::Hash;
use sp_std::prelude::*;
/// Put code in the storage. The hash of code is used as a key and is returned
/// as a result of this function.
///
/// This function instruments the given code and caches it in the storage.
pub fn save<T: Trait>(
original_code: Vec<u8>,
schedule: &Schedule,
) -> Result<CodeHash<T>, &'static str> {
let prefab_module = prepare::prepare_contract::<Env>(&original_code, schedule)?;
let code_hash = T::Hashing::hash(&original_code);
<CodeStorage<T>>::insert(code_hash, prefab_module);
<PristineCode<T>>::insert(code_hash, original_code);
Ok(code_hash)
}
/// Load code with the given code hash.
///
/// If the module was instrumented with a lower version of schedule than
/// the current one given as an argument, then this function will perform
/// re-instrumentation and update the cache in the storage.
pub fn load<T: Trait>(
code_hash: &CodeHash<T>,
schedule: &Schedule,
) -> Result<PrefabWasmModule, &'static str> {
let mut prefab_module = <CodeStorage<T>>::get(code_hash).ok_or_else(|| "code is not found")?;
if prefab_module.schedule_version < schedule.version {
// The current schedule version is greater than the version of the one cached
// in the storage.
//
// We need to re-instrument the code with the latest schedule here.
let original_code =
<PristineCode<T>>::get(code_hash).ok_or_else(|| "pristine code is not found")?;
prefab_module = prepare::prepare_contract::<Env>(&original_code, schedule)?;
<CodeStorage<T>>::insert(&code_hash, &prefab_module);
}
Ok(prefab_module)
}
@@ -1,331 +0,0 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Definition of macros that hides boilerplate of defining external environment
//! for a wasm module.
//!
//! Most likely you should use `define_env` macro.
#[macro_export]
macro_rules! convert_args {
() => (vec![]);
( $( $t:ty ),* ) => ( vec![ $( { use $crate::wasm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] );
}
#[macro_export]
macro_rules! gen_signature {
( ( $( $params: ty ),* ) ) => (
{
parity_wasm::elements::FunctionType::new(convert_args!($($params),*), None)
}
);
( ( $( $params: ty ),* ) -> $returns: ty ) => (
{
parity_wasm::elements::FunctionType::new(convert_args!($($params),*), Some({
use $crate::wasm::env_def::ConvertibleToWasm; <$returns>::VALUE_TYPE
}))
}
);
}
#[macro_export]
macro_rules! gen_signature_dispatch {
(
$needle_name:ident,
$needle_sig:ident ;
$name:ident
( $ctx:ident $( , $names:ident : $params:ty )* ) $( -> $returns:ty )* , $($rest:tt)* ) => {
if stringify!($name).as_bytes() == $needle_name {
let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* );
if $needle_sig == &signature {
return true;
}
} else {
gen_signature_dispatch!($needle_name, $needle_sig ; $($rest)*);
}
};
( $needle_name:ident, $needle_sig:ident ; ) => {
};
}
/// Unmarshall arguments and then execute `body` expression and return its result.
macro_rules! unmarshall_then_body {
( $body:tt, $ctx:ident, $args_iter:ident, $( $names:ident : $params:ty ),* ) => ({
$(
let $names : <$params as $crate::wasm::env_def::ConvertibleToWasm>::NativeType =
$args_iter.next()
.and_then(|v| <$params as $crate::wasm::env_def::ConvertibleToWasm>
::from_typed_value(v.clone()))
.expect(
"precondition: all imports should be checked against the signatures of corresponding
functions defined by `define_env!` macro by the user of the macro;
signatures of these functions defined by `$params`;
calls always made with arguments types of which are defined by the corresponding imports;
thus types of arguments should be equal to type list in `$params` and
length of argument list and $params should be equal;
thus this can never be `None`;
qed;
"
);
)*
$body
})
}
/// Since we can't specify the type of closure directly at binding site:
///
/// ```nocompile
/// let f: FnOnce() -> Result<<u32 as ConvertibleToWasm>::NativeType, _> = || { /* ... */ };
/// ```
///
/// we use this function to constrain the type of the closure.
#[inline(always)]
pub fn constrain_closure<R, F>(f: F) -> F
where
F: FnOnce() -> Result<R, sp_sandbox::HostError>,
{
f
}
#[macro_export]
macro_rules! unmarshall_then_body_then_marshall {
( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({
let body = $crate::wasm::env_def::macros::constrain_closure::<
<$returns as $crate::wasm::env_def::ConvertibleToWasm>::NativeType, _
>(|| {
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
});
let r = body()?;
return Ok(sp_sandbox::ReturnValue::Value({ use $crate::wasm::env_def::ConvertibleToWasm; r.to_typed_value() }))
});
( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
let body = $crate::wasm::env_def::macros::constrain_closure::<(), _>(|| {
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
});
body()?;
return Ok(sp_sandbox::ReturnValue::Unit)
})
}
#[macro_export]
macro_rules! define_func {
( < E: $ext_ty:tt > $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => {
fn $name< E: $ext_ty >(
$ctx: &mut $crate::wasm::Runtime<E>,
args: &[sp_sandbox::Value],
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> {
#[allow(unused)]
let mut args = args.iter();
unmarshall_then_body_then_marshall!(
args,
$ctx,
( $( $names : $params ),* ) $( -> $returns )* => $body
)
}
};
}
#[macro_export]
macro_rules! register_func {
( $reg_cb:ident, < E: $ext_ty:tt > ; ) => {};
( $reg_cb:ident, < E: $ext_ty:tt > ;
$name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
$( -> $returns:ty )* => $body:tt $($rest:tt)*
) => {
$reg_cb(
stringify!($name).as_bytes(),
{
define_func!(
< E: $ext_ty > $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body
);
$name::<E>
}
);
register_func!( $reg_cb, < E: $ext_ty > ; $($rest)* );
};
}
/// Define a function set that can be imported by executing wasm code.
///
/// **NB**: Be advised that all functions defined by this macro
/// will panic if called with unexpected arguments.
///
/// It's up to the user of this macro to check signatures of wasm code to be executed
/// and reject the code if any imported function has a mismatched signature.
macro_rules! define_env {
( $init_name:ident , < E: $ext_ty:tt > ,
$( $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
$( -> $returns:ty )* => $body:tt , )*
) => {
pub struct $init_name;
impl $crate::wasm::env_def::ImportSatisfyCheck for $init_name {
fn can_satisfy(name: &[u8], func_type: &parity_wasm::elements::FunctionType) -> bool {
gen_signature_dispatch!( name, func_type ; $( $name ( $ctx $(, $names : $params )* ) $( -> $returns )* , )* );
return false;
}
}
impl<E: Ext> $crate::wasm::env_def::FunctionImplProvider<E> for $init_name {
fn impls<F: FnMut(&[u8], $crate::wasm::env_def::HostFunc<E>)>(f: &mut F) {
register_func!(f, < E: $ext_ty > ; $( $name ( $ctx $( , $names : $params )* ) $( -> $returns)* => $body )* );
}
}
};
}
#[cfg(test)]
mod tests {
use crate::{
exec::Ext,
gas::Gas,
wasm::{tests::MockExt, Runtime},
};
use parity_wasm::elements::{FunctionType, ValueType};
use sp_runtime::traits::Zero;
use sp_sandbox::{ReturnValue, Value};
#[test]
fn macro_unmarshall_then_body_then_marshall_value_or_trap() {
fn test_value(
_ctx: &mut u32,
args: &[sp_sandbox::Value],
) -> Result<ReturnValue, sp_sandbox::HostError> {
let mut args = args.iter();
unmarshall_then_body_then_marshall!(
args,
_ctx,
(a: u32, b: u32) -> u32 => {
if b == 0 {
Err(sp_sandbox::HostError)
} else {
Ok(a / b)
}
}
)
}
let ctx = &mut 0;
assert_eq!(
test_value(ctx, &[Value::I32(15), Value::I32(3)]).unwrap(),
ReturnValue::Value(Value::I32(5)),
);
assert!(test_value(ctx, &[Value::I32(15), Value::I32(0)]).is_err());
}
#[test]
fn macro_unmarshall_then_body_then_marshall_unit() {
fn test_unit(
ctx: &mut u32,
args: &[sp_sandbox::Value],
) -> Result<ReturnValue, sp_sandbox::HostError> {
let mut args = args.iter();
unmarshall_then_body_then_marshall!(
args,
ctx,
(a: u32, b: u32) => {
*ctx = a + b;
Ok(())
}
)
}
let ctx = &mut 0;
let result = test_unit(ctx, &[Value::I32(2), Value::I32(3)]).unwrap();
assert_eq!(result, ReturnValue::Unit);
assert_eq!(*ctx, 5);
}
#[test]
fn macro_define_func() {
define_func!( <E: Ext> ext_gas (_ctx, amount: u32) => {
let amount = Gas::from(amount);
if !amount.is_zero() {
Ok(())
} else {
Err(sp_sandbox::HostError)
}
});
let _f: fn(
&mut Runtime<MockExt>,
&[sp_sandbox::Value],
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> = ext_gas::<MockExt>;
}
#[test]
fn macro_gen_signature() {
assert_eq!(
gen_signature!((i32)),
FunctionType::new(vec![ValueType::I32], None),
);
assert_eq!(
gen_signature!( (i32, u32) -> u32 ),
FunctionType::new(vec![ValueType::I32, ValueType::I32], Some(ValueType::I32)),
);
}
#[test]
fn macro_unmarshall_then_body() {
let args = vec![Value::I32(5), Value::I32(3)];
let mut args = args.iter();
let ctx: &mut u32 = &mut 0;
let r = unmarshall_then_body!(
{
*ctx = a + b;
a * b
},
ctx,
args,
a: u32,
b: u32
);
assert_eq!(*ctx, 8);
assert_eq!(r, 15);
}
#[test]
fn macro_define_env() {
use crate::wasm::env_def::ImportSatisfyCheck;
define_env!(Env, <E: Ext>,
ext_gas( _ctx, amount: u32 ) => {
let amount = Gas::from(amount);
if !amount.is_zero() {
Ok(())
} else {
Err(sp_sandbox::HostError)
}
},
);
assert!(Env::can_satisfy(
b"ext_gas",
&FunctionType::new(vec![ValueType::I32], None)
));
assert!(!Env::can_satisfy(
b"not_exists",
&FunctionType::new(vec![], None)
));
}
}
@@ -1,85 +0,0 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
use super::Runtime;
use crate::exec::Ext;
use parity_wasm::elements::{FunctionType, ValueType};
use sp_sandbox::Value;
#[macro_use]
pub(crate) mod macros;
pub trait ConvertibleToWasm: Sized {
const VALUE_TYPE: ValueType;
type NativeType;
fn to_typed_value(self) -> Value;
fn from_typed_value(_: Value) -> Option<Self>;
}
impl ConvertibleToWasm for i32 {
type NativeType = i32;
const VALUE_TYPE: ValueType = ValueType::I32;
fn to_typed_value(self) -> Value {
Value::I32(self)
}
fn from_typed_value(v: Value) -> Option<Self> {
v.as_i32()
}
}
impl ConvertibleToWasm for u32 {
type NativeType = u32;
const VALUE_TYPE: ValueType = ValueType::I32;
fn to_typed_value(self) -> Value {
Value::I32(self as i32)
}
fn from_typed_value(v: Value) -> Option<Self> {
match v {
Value::I32(v) => Some(v as u32),
_ => None,
}
}
}
impl ConvertibleToWasm for u64 {
type NativeType = u64;
const VALUE_TYPE: ValueType = ValueType::I64;
fn to_typed_value(self) -> Value {
Value::I64(self as i64)
}
fn from_typed_value(v: Value) -> Option<Self> {
match v {
Value::I64(v) => Some(v as u64),
_ => None,
}
}
}
pub(crate) type HostFunc<E> = fn(
&mut Runtime<E>,
&[sp_sandbox::Value],
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>;
pub(crate) trait FunctionImplProvider<E: Ext> {
fn impls<F: FnMut(&[u8], HostFunc<E>)>(f: &mut F);
}
/// This trait can be used to check whether the host environment can satisfy
/// a requested function import.
pub trait ImportSatisfyCheck {
/// Returns `true` if the host environment contains a function with
/// the specified name and its type matches to the given type, or `false`
/// otherwise.
fn can_satisfy(name: &[u8], func_type: &FunctionType) -> bool;
}
File diff suppressed because it is too large Load Diff
@@ -1,871 +0,0 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! This module takes care of loading, checking and preprocessing of a
//! wasm module before execution. It also extracts some essential information
//! from a module.
use crate::{
wasm::{env_def::ImportSatisfyCheck, PrefabWasmModule},
Schedule,
};
use parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType};
use pwasm_utils::{self, rules};
use sp_runtime::traits::SaturatedConversion;
use sp_std::prelude::*;
struct ContractModule<'a> {
/// A deserialized module. The module is valid (this is Guaranteed by `new` method).
module: elements::Module,
schedule: &'a Schedule,
}
impl<'a> ContractModule<'a> {
/// Creates a new instance of `ContractModule`.
///
/// Returns `Err` if the `original_code` couldn't be decoded or
/// if it contains an invalid module.
fn new(original_code: &[u8], schedule: &'a Schedule) -> Result<Self, &'static str> {
use wasmi_validation::{validate_module, PlainValidator};
let module =
elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?;
// Make sure that the module is valid.
validate_module::<PlainValidator>(&module).map_err(|_| "Module is not valid")?;
// Return a `ContractModule` instance with
// __valid__ module.
Ok(ContractModule { module, schedule })
}
/// Ensures that module doesn't declare internal memories.
///
/// In this runtime we only allow wasm module to import memory from the environment.
/// Memory section contains declarations of internal linear memories, so if we find one
/// we reject such a module.
fn ensure_no_internal_memory(&self) -> Result<(), &'static str> {
if self
.module
.memory_section()
.map_or(false, |ms| ms.entries().len() > 0)
{
return Err("module declares internal memory");
}
Ok(())
}
/// Ensures that tables declared in the module are not too big.
fn ensure_table_size_limit(&self, limit: u32) -> Result<(), &'static str> {
if let Some(table_section) = self.module.table_section() {
// In Wasm MVP spec, there may be at most one table declared. Double check this
// explicitly just in case the Wasm version changes.
if table_section.entries().len() > 1 {
return Err("multiple tables declared");
}
if let Some(table_type) = table_section.entries().first() {
// Check the table's initial size as there is no instruction or environment function
// capable of growing the table.
if table_type.limits().initial() > limit {
return Err("table exceeds maximum size allowed");
}
}
}
Ok(())
}
/// Ensures that no floating point types are in use.
fn ensure_no_floating_types(&self) -> Result<(), &'static str> {
if let Some(global_section) = self.module.global_section() {
for global in global_section.entries() {
match global.global_type().content_type() {
ValueType::F32 | ValueType::F64 => {
return Err("use of floating point type in globals is forbidden")
}
_ => {}
}
}
}
if let Some(code_section) = self.module.code_section() {
for func_body in code_section.bodies() {
for local in func_body.locals() {
match local.value_type() {
ValueType::F32 | ValueType::F64 => {
return Err("use of floating point type in locals is forbidden")
}
_ => {}
}
}
}
}
if let Some(type_section) = self.module.type_section() {
for wasm_type in type_section.types() {
match wasm_type {
Type::Function(func_type) => {
let return_type = func_type.return_type();
for value_type in func_type.params().iter().chain(return_type.iter()) {
match value_type {
ValueType::F32 | ValueType::F64 => {
return Err(
"use of floating point type in function types is forbidden",
)
}
_ => {}
}
}
}
}
}
}
Ok(())
}
fn inject_gas_metering(self) -> Result<Self, &'static str> {
let gas_rules = rules::Set::new(
self.schedule.regular_op_cost.clone().saturated_into(),
Default::default(),
)
.with_grow_cost(self.schedule.grow_mem_cost.clone().saturated_into())
.with_forbidden_floats();
let contract_module = pwasm_utils::inject_gas_counter(self.module, &gas_rules)
.map_err(|_| "gas instrumentation failed")?;
Ok(ContractModule {
module: contract_module,
schedule: self.schedule,
})
}
fn inject_stack_height_metering(self) -> Result<Self, &'static str> {
let contract_module =
pwasm_utils::stack_height::inject_limiter(self.module, self.schedule.max_stack_height)
.map_err(|_| "stack height instrumentation failed")?;
Ok(ContractModule {
module: contract_module,
schedule: self.schedule,
})
}
/// Check that the module has required exported functions. For now
/// these are just entrypoints:
///
/// - 'call'
/// - 'deploy'
///
/// Any other exports are not allowed.
fn scan_exports(&self) -> Result<(), &'static str> {
let mut deploy_found = false;
let mut call_found = false;
let module = &self.module;
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
let export_entries = module
.export_section()
.map(|is| is.entries())
.unwrap_or(&[]);
let func_entries = module
.function_section()
.map(|fs| fs.entries())
.unwrap_or(&[]);
// Function index space consists of imported function following by
// declared functions. Calculate the total number of imported functions so
// we can use it to convert indexes from function space to declared function space.
let fn_space_offset = module
.import_section()
.map(|is| is.entries())
.unwrap_or(&[])
.iter()
.filter(|entry| match *entry.external() {
External::Function(_) => true,
_ => false,
})
.count();
for export in export_entries {
match export.field() {
"call" => call_found = true,
"deploy" => deploy_found = true,
_ => return Err("unknown export: expecting only deploy and call functions"),
}
// Then check the export kind. "call" and "deploy" are
// functions.
let fn_idx = match export.internal() {
Internal::Function(ref fn_idx) => *fn_idx,
_ => return Err("expected a function"),
};
// convert index from function index space to declared index space.
let fn_idx = match fn_idx.checked_sub(fn_space_offset as u32) {
Some(fn_idx) => fn_idx,
None => {
// Underflow here means fn_idx points to imported function which we don't allow!
return Err("entry point points to an imported function");
}
};
// Then check the signature.
// Both "call" and "deploy" has a [] -> [] or [] -> [i32] function type.
//
// The [] -> [] signature predates the [] -> [i32] signature and is supported for
// backwards compatibility. This will likely be removed once ink! is updated to
// generate modules with the new function signatures.
let func_ty_idx = func_entries
.get(fn_idx as usize)
.ok_or_else(|| "export refers to non-existent function")?
.type_ref();
let Type::Function(ref func_ty) = types
.get(func_ty_idx as usize)
.ok_or_else(|| "function has a non-existent type")?;
if !func_ty.params().is_empty()
|| !(func_ty.return_type().is_none()
|| func_ty.return_type() == Some(ValueType::I32))
{
return Err("entry point has wrong signature");
}
}
if !deploy_found {
return Err("deploy function isn't exported");
}
if !call_found {
return Err("call function isn't exported");
}
Ok(())
}
/// Scan an import section if any.
///
/// This accomplishes two tasks:
///
/// - checks any imported function against defined host functions set, incl.
/// their signatures.
/// - if there is a memory import, returns it's descriptor
fn scan_imports<C: ImportSatisfyCheck>(&self) -> Result<Option<&MemoryType>, &'static str> {
let module = &self.module;
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
let import_entries = module
.import_section()
.map(|is| is.entries())
.unwrap_or(&[]);
let mut imported_mem_type = None;
for import in import_entries {
if import.module() != "env" {
// This import tries to import something from non-"env" module,
// but all imports are located in "env" at the moment.
return Err("module has imports from a non-'env' namespace");
}
let type_idx = match import.external() {
&External::Table(_) => return Err("Cannot import tables"),
&External::Global(_) => return Err("Cannot import globals"),
&External::Function(ref type_idx) => type_idx,
&External::Memory(ref memory_type) => {
if import.field() != "memory" {
return Err("Memory import must have the field name 'memory'");
}
if imported_mem_type.is_some() {
return Err("Multiple memory imports defined");
}
imported_mem_type = Some(memory_type);
continue;
}
};
let Type::Function(ref func_ty) = types
.get(*type_idx as usize)
.ok_or_else(|| "validation: import entry points to a non-existent type")?;
// We disallow importing `ext_println` unless debug features are enabled,
// which should only be allowed on a dev chain
if !self.schedule.enable_println && import.field().as_bytes() == b"ext_println" {
return Err("module imports `ext_println` but debug features disabled");
}
// We disallow importing `gas` function here since it is treated as implementation detail.
if import.field().as_bytes() == b"gas"
|| !C::can_satisfy(import.field().as_bytes(), func_ty)
{
return Err("module imports a non-existent function");
}
}
Ok(imported_mem_type)
}
fn into_wasm_code(self) -> Result<Vec<u8>, &'static str> {
elements::serialize(self.module).map_err(|_| "error serializing instrumented module")
}
}
/// Loads the given module given in `original_code`, performs some checks on it and
/// does some preprocessing.
///
/// The checks are:
///
/// - provided code is a valid wasm module.
/// - the module doesn't define an internal memory instance,
/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`,
/// - all imported functions from the external environment matches defined by `env` module,
///
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
pub fn prepare_contract<C: ImportSatisfyCheck>(
original_code: &[u8],
schedule: &Schedule,
) -> Result<PrefabWasmModule, &'static str> {
let mut contract_module = ContractModule::new(original_code, schedule)?;
contract_module.scan_exports()?;
contract_module.ensure_no_internal_memory()?;
contract_module.ensure_table_size_limit(schedule.max_table_size)?;
contract_module.ensure_no_floating_types()?;
struct MemoryDefinition {
initial: u32,
maximum: u32,
}
let memory_def = if let Some(memory_type) = contract_module.scan_imports::<C>()? {
// Inspect the module to extract the initial and maximum page count.
let limits = memory_type.limits();
match (limits.initial(), limits.maximum()) {
(initial, Some(maximum)) if initial > maximum => {
return Err(
"Requested initial number of pages should not exceed the requested maximum",
);
}
(_, Some(maximum)) if maximum > schedule.max_memory_pages => {
return Err("Maximum number of pages should not exceed the configured maximum.");
}
(initial, Some(maximum)) => MemoryDefinition { initial, maximum },
(_, None) => {
// Maximum number of pages should be always declared.
// This isn't a hard requirement and can be treated as a maximum set
// to configured maximum.
return Err("Maximum number of pages should be always declared.");
}
}
} else {
// If none memory imported then just crate an empty placeholder.
// Any access to it will lead to out of bounds trap.
MemoryDefinition {
initial: 0,
maximum: 0,
}
};
contract_module = contract_module
.inject_gas_metering()?
.inject_stack_height_metering()?;
Ok(PrefabWasmModule {
schedule_version: schedule.version,
initial: memory_def.initial,
maximum: memory_def.maximum,
_reserved: None,
code: contract_module.into_wasm_code()?,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::exec::Ext;
use assert_matches::assert_matches;
use std::fmt;
use wabt;
impl fmt::Debug for PrefabWasmModule {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PreparedContract {{ .. }}")
}
}
// Define test environment for tests. We need ImportSatisfyCheck
// implementation from it. So actual implementations doesn't matter.
define_env!(TestEnv, <E: Ext>,
panic(_ctx) => { unreachable!(); },
// gas is an implementation defined function and a contract can't import it.
gas(_ctx, _amount: u32) => { unreachable!(); },
nop(_ctx, _unused: u64) => { unreachable!(); },
ext_println(_ctx, _ptr: u32, _len: u32) => { unreachable!(); },
);
macro_rules! prepare_test {
($name:ident, $wat:expr, $($expected:tt)*) => {
#[test]
fn $name() {
let wasm = wabt::Wat2Wasm::new().validate(false).convert($wat).unwrap();
let schedule = Schedule::default();
let r = prepare_contract::<TestEnv>(wasm.as_ref(), &schedule);
assert_matches!(r, $($expected)*);
}
};
}
prepare_test!(
no_floats,
r#"
(module
(func (export "call")
(drop
(f32.add
(f32.const 0)
(f32.const 1)
)
)
)
(func (export "deploy"))
)"#,
Err("gas instrumentation failed")
);
mod memories {
use super::*;
// Tests below assumes that maximum page number is configured to a certain number.
#[test]
fn assume_memory_size() {
assert_eq!(Schedule::default().max_memory_pages, 16);
}
prepare_test!(
memory_with_one_page,
r#"
(module
(import "env" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
prepare_test!(
internal_memory_declaration,
r#"
(module
(memory 1 1)
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("module declares internal memory")
);
prepare_test!(
no_memory_import,
r#"
(module
;; no memory imported
(func (export "call"))
(func (export "deploy"))
)"#,
Ok(_)
);
prepare_test!(
initial_exceeds_maximum,
r#"
(module
(import "env" "memory" (memory 16 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Module is not valid")
);
prepare_test!(
no_maximum,
r#"
(module
(import "env" "memory" (memory 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Maximum number of pages should be always declared.")
);
prepare_test!(
requested_maximum_exceeds_configured_maximum,
r#"
(module
(import "env" "memory" (memory 1 17))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Maximum number of pages should not exceed the configured maximum.")
);
prepare_test!(
field_name_not_memory,
r#"
(module
(import "env" "forgetit" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Memory import must have the field name 'memory'")
);
prepare_test!(
multiple_memory_imports,
r#"
(module
(import "env" "memory" (memory 1 1))
(import "env" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Module is not valid")
);
prepare_test!(
table_import,
r#"
(module
(import "env" "table" (table 1 anyfunc))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Cannot import tables")
);
prepare_test!(
global_import,
r#"
(module
(global $g (import "env" "global") i32)
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Cannot import globals")
);
}
mod tables {
use super::*;
// Tests below assumes that maximum table size is configured to a certain number.
#[test]
fn assume_table_size() {
assert_eq!(Schedule::default().max_table_size, 16384);
}
prepare_test!(
no_tables,
r#"
(module
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
prepare_test!(
table_valid_size,
r#"
(module
(table 10000 funcref)
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
prepare_test!(
table_too_big,
r#"
(module
(table 20000 funcref)
(func (export "call"))
(func (export "deploy"))
)"#,
Err("table exceeds maximum size allowed")
);
}
mod imports {
use super::*;
prepare_test!(
can_import_legit_function,
r#"
(module
(import "env" "nop" (func (param i64)))
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
// even though gas is defined the contract can't import it since
// it is an implementation defined.
prepare_test!(
can_not_import_gas_function,
r#"
(module
(import "env" "gas" (func (param i32)))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("module imports a non-existent function")
);
// nothing can be imported from non-"env" module for now.
prepare_test!(
non_env_import,
r#"
(module
(import "another_module" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("module has imports from a non-'env' namespace")
);
// wrong signature
prepare_test!(
wrong_signature,
r#"
(module
(import "env" "gas" (func (param i64)))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("module imports a non-existent function")
);
prepare_test!(
unknown_func_name,
r#"
(module
(import "env" "unknown_func" (func))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("module imports a non-existent function")
);
prepare_test!(
ext_println_debug_disabled,
r#"
(module
(import "env" "ext_println" (func $ext_println (param i32 i32)))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("module imports `ext_println` but debug features disabled")
);
#[test]
fn ext_println_debug_enabled() {
let wasm = wabt::Wat2Wasm::new()
.validate(false)
.convert(
r#"
(module
(import "env" "ext_println" (func $ext_println (param i32 i32)))
(func (export "call"))
(func (export "deploy"))
)
"#,
)
.unwrap();
let mut schedule = Schedule::default();
schedule.enable_println = true;
let r = prepare_contract::<TestEnv>(wasm.as_ref(), &schedule);
assert_matches!(r, Ok(_));
}
}
mod entrypoints {
use super::*;
prepare_test!(
it_works,
r#"
(module
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
prepare_test!(
omit_deploy,
r#"
(module
(func (export "call"))
)
"#,
Err("deploy function isn't exported")
);
prepare_test!(
omit_call,
r#"
(module
(func (export "deploy"))
)
"#,
Err("call function isn't exported")
);
// Try to use imported function as an entry point.
prepare_test!(
try_sneak_export_as_entrypoint,
r#"
(module
(import "env" "panic" (func))
(func (export "deploy"))
(export "call" (func 0))
)
"#,
Err("entry point points to an imported function")
);
// Try to use imported function as an entry point.
prepare_test!(
try_sneak_export_as_global,
r#"
(module
(func (export "deploy"))
(global (export "call") i32 (i32.const 0))
)
"#,
Err("expected a function")
);
prepare_test!(
wrong_signature,
r#"
(module
(func (export "deploy"))
(func (export "call") (param i32))
)
"#,
Err("entry point has wrong signature")
);
prepare_test!(
unknown_exports,
r#"
(module
(func (export "call"))
(func (export "deploy"))
(func (export "whatevs"))
)
"#,
Err("unknown export: expecting only deploy and call functions")
);
prepare_test!(
global_float,
r#"
(module
(global $x f32 (f32.const 0))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("use of floating point type in globals is forbidden")
);
prepare_test!(
local_float,
r#"
(module
(func $foo (local f32))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("use of floating point type in locals is forbidden")
);
prepare_test!(
param_float,
r#"
(module
(func $foo (param f32))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("use of floating point type in function types is forbidden")
);
prepare_test!(
result_float,
r#"
(module
(func $foo (result f32) (f32.const 0))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("use of floating point type in function types is forbidden")
);
}
}
File diff suppressed because it is too large Load Diff
@@ -8,8 +8,8 @@ version = "0.1.0"
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
serde = { version = "1.0.101", optional = true, features = ["derive"] } serde = { version = "1.0.101", optional = true, features = ["derive"] }
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
cumulus-primitives = { path = "../../../primitives", default-features = false } cumulus-primitives = { path = "../../../primitives", default-features = false }
@@ -1,29 +0,0 @@
[package]
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
name = "cumulus-token-dealer"
version = "0.1.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
# Cumulus dependencies
cumulus-upward-message = { path = "../../../upward-message", default-features = false }
cumulus-primitives = { path = "../../../primitives", default-features = false }
# Polkadot dependencies
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false }
[features]
default = ["std"]
std = [
"codec/std",
"cumulus-upward-message/std",
"cumulus-primitives/std",
"frame-support/std",
"frame-system/std",
"polkadot-parachain/std",
]
@@ -1,170 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Cumulus 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.
// Cumulus 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
decl_event, decl_module,
dispatch::DispatchResult,
traits::{Currency, ExistenceRequirement, WithdrawReason},
};
use frame_system::ensure_signed;
use codec::{Decode, Encode};
use cumulus_primitives::{
relay_chain::DownwardMessage,
xcmp::{XCMPMessageHandler, XCMPMessageSender},
DownwardMessageHandler, ParaId, UpwardMessageOrigin, UpwardMessageSender,
};
use cumulus_upward_message::BalancesMessage;
use polkadot_parachain::primitives::AccountIdConversion;
#[derive(Encode, Decode)]
pub enum XCMPMessage<XAccountId, XBalance> {
/// Transfer tokens to the given account from the Parachain account.
TransferToken(XAccountId, XBalance),
}
type BalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
/// Configuration trait of this pallet.
pub trait Trait: frame_system::Trait {
/// Event type used by the runtime.
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
/// The sender of upward messages.
type UpwardMessageSender: UpwardMessageSender<Self::UpwardMessage>;
/// The upward message type used by the Parachain runtime.
type UpwardMessage: codec::Codec + BalancesMessage<Self::AccountId, BalanceOf<Self>>;
/// Currency of the runtime.
type Currency: Currency<Self::AccountId>;
/// The sender of XCMP messages.
type XCMPMessageSender: XCMPMessageSender<XCMPMessage<Self::AccountId, BalanceOf<Self>>>;
}
decl_event! {
pub enum Event<T> where
AccountId = <T as frame_system::Trait>::AccountId,
Balance = BalanceOf<T>
{
/// Transferred tokens to the account on the relay chain.
TransferredTokensToRelayChain(AccountId, Balance),
/// Transferred tokens to the account on request from the relay chain.
TransferredTokensFromRelayChain(AccountId, Balance),
/// Transferred tokens to the account from the given parachain account.
TransferredTokensViaXCMP(ParaId, AccountId, Balance, DispatchResult),
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
/// Transfer `amount` of tokens on the relay chain from the Parachain account to
/// the given `dest` account.
#[weight = 10]
fn transfer_tokens_to_relay_chain(origin, dest: T::AccountId, amount: BalanceOf<T>) {
let who = ensure_signed(origin)?;
let _ = T::Currency::withdraw(
&who,
amount,
WithdrawReason::Transfer.into(),
ExistenceRequirement::AllowDeath,
)?;
let msg = <T as Trait>::UpwardMessage::transfer(dest.clone(), amount.clone());
<T as Trait>::UpwardMessageSender::send_upward_message(&msg, UpwardMessageOrigin::Signed)
.expect("Should not fail; qed");
Self::deposit_event(Event::<T>::TransferredTokensToRelayChain(dest, amount));
}
/// Transfer `amount` of tokens to another parachain.
#[weight = 10]
fn transfer_tokens_to_parachain_chain(
origin,
para_id: u32,
dest: T::AccountId,
amount: BalanceOf<T>,
) {
//TODO we don't make sure that the parachain has some tokens on the other parachain.
let who = ensure_signed(origin)?;
let _ = T::Currency::withdraw(
&who,
amount,
WithdrawReason::Transfer.into(),
ExistenceRequirement::AllowDeath,
)?;
T::XCMPMessageSender::send_xcmp_message(
para_id.into(),
&XCMPMessage::TransferToken(dest, amount),
).expect("Should not fail; qed");
}
fn deposit_event() = default;
}
}
/// This is a hack to convert from one generic type to another where we are sure that both are the
/// same type/use the same encoding.
fn convert_hack<O: Decode>(input: &impl Encode) -> O {
input.using_encoded(|e| Decode::decode(&mut &e[..]).expect("Must be compatible; qed"))
}
impl<T: Trait> DownwardMessageHandler for Module<T> {
fn handle_downward_message(msg: &DownwardMessage) {
match msg {
DownwardMessage::TransferInto(dest, amount, _) => {
let dest = convert_hack(&dest);
let amount: BalanceOf<T> = convert_hack(amount);
let _ = T::Currency::deposit_creating(&dest, amount.clone());
Self::deposit_event(Event::<T>::TransferredTokensFromRelayChain(dest, amount));
}
_ => {}
}
}
}
impl<T: Trait> XCMPMessageHandler<XCMPMessage<T::AccountId, BalanceOf<T>>> for Module<T> {
fn handle_xcmp_message(src: ParaId, msg: &XCMPMessage<T::AccountId, BalanceOf<T>>) {
match msg {
XCMPMessage::TransferToken(dest, amount) => {
let para_account = src.clone().into_account();
let res = T::Currency::transfer(
&para_account,
dest,
amount.clone(),
ExistenceRequirement::AllowDeath,
);
Self::deposit_event(Event::<T>::TransferredTokensViaXCMP(
src,
dest.clone(),
amount.clone(),
res,
));
}
}
}
}
@@ -6,8 +6,8 @@ edition = "2018"
[dependencies] [dependencies]
# Substrate dependencies # Substrate dependencies
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
[features] [features]
default = [ "std" ] default = [ "std" ]
+21 -31
View File
@@ -8,44 +8,41 @@ edition = '2018'
serde = { version = "1.0.101", optional = true, features = ["derive"] } serde = { version = "1.0.101", optional = true, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
cumulus-token-dealer = { path = "../pallets/token-dealer", default-features = false }
parachain-info = { path = "../pallets/parachain-info", default-features = false } parachain-info = { path = "../pallets/parachain-info", default-features = false }
rococo-parachain-primitives = { path = "../primitives", default-features = false } rococo-parachain-primitives = { path = "../primitives", default-features = false }
# Substrate dependencies # Substrate dependencies
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-version = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-version = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-offchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-offchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-transaction-pool = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-sudo = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-sudo = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
# Cumulus dependencies # Cumulus dependencies
cumulus-runtime = { path = "../../runtime", default-features = false } cumulus-runtime = { path = "../../runtime", default-features = false }
cumulus-parachain-upgrade = { path = "../../parachain-upgrade", default-features = false } cumulus-parachain-upgrade = { path = "../../parachain-upgrade", default-features = false }
cumulus-message-broker = { path = "../../message-broker", default-features = false }
cumulus-upward-message = { path = "../../upward-message", default-features = false }
cumulus-primitives = { path = "../../primitives", default-features = false } cumulus-primitives = { path = "../../primitives", default-features = false }
# Polkadot dependencies # Polkadot dependencies
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "bkchr-adder-collator-integration-test" }
[build-dependencies] [build-dependencies]
wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1.0.6" } wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "2.0.0" }
[features] [features]
default = [ "std" ] default = [ "std" ]
@@ -75,12 +72,5 @@ std = [
"rococo-parachain-primitives/std", "rococo-parachain-primitives/std",
"cumulus-runtime/std", "cumulus-runtime/std",
"cumulus-parachain-upgrade/std", "cumulus-parachain-upgrade/std",
"cumulus-message-broker/std",
"cumulus-upward-message/std",
"cumulus-primitives/std", "cumulus-primitives/std",
"cumulus-token-dealer/std",
]
# Will be enabled by the `wasm-builder` when building the runtime for WASM.
runtime-wasm = [
"cumulus-upward-message/runtime-wasm",
] ]
+5 -26
View File
@@ -36,9 +36,6 @@ use sp_std::prelude::*;
use sp_version::NativeVersion; use sp_version::NativeVersion;
use sp_version::RuntimeVersion; use sp_version::RuntimeVersion;
/// Import the token dealer pallet.
pub use cumulus_token_dealer;
// A few exports that help ease life for downstream crates. // A few exports that help ease life for downstream crates.
pub use frame_support::{ pub use frame_support::{
construct_runtime, parameter_types, construct_runtime, parameter_types,
@@ -142,7 +139,7 @@ impl frame_system::Trait for Runtime {
/// Runtime version. /// Runtime version.
type Version = Version; type Version = Version;
/// Converts a module to an index of this module in the runtime. /// Converts a module to an index of this module in the runtime.
type ModuleToIndex = ModuleToIndex; type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<Balance>; type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = (); type OnNewAccount = ();
type OnKilledAccount = (); type OnKilledAccount = ();
@@ -171,6 +168,7 @@ parameter_types! {
pub const TransferFee: u128 = 0; pub const TransferFee: u128 = 0;
pub const CreationFee: u128 = 0; pub const CreationFee: u128 = 0;
pub const TransactionByteFee: u128 = 1; pub const TransactionByteFee: u128 = 1;
pub const MaxLocks: u32 = 50;
} }
impl pallet_balances::Trait for Runtime { impl pallet_balances::Trait for Runtime {
@@ -182,11 +180,11 @@ impl pallet_balances::Trait for Runtime {
type ExistentialDeposit = ExistentialDeposit; type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System; type AccountStore = System;
type WeightInfo = (); type WeightInfo = ();
type MaxLocks = MaxLocks;
} }
impl pallet_transaction_payment::Trait for Runtime { impl pallet_transaction_payment::Trait for Runtime {
type Currency = Balances; type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, ()>;
type OnTransactionPayment = ();
type TransactionByteFee = TransactionByteFee; type TransactionByteFee = TransactionByteFee;
type WeightToFee = IdentityFee<Balance>; type WeightToFee = IdentityFee<Balance>;
type FeeMultiplierUpdate = (); type FeeMultiplierUpdate = ();
@@ -199,24 +197,7 @@ impl pallet_sudo::Trait for Runtime {
impl cumulus_parachain_upgrade::Trait for Runtime { impl cumulus_parachain_upgrade::Trait for Runtime {
type Event = Event; type Event = Event;
type OnValidationFunctionParams = (); type OnValidationData = ();
}
impl cumulus_message_broker::Trait for Runtime {
type Event = Event;
type DownwardMessageHandlers = TokenDealer;
type UpwardMessage = cumulus_upward_message::RococoUpwardMessage;
type ParachainId = ParachainInfo;
type XCMPMessage = cumulus_token_dealer::XCMPMessage<AccountId, Balance>;
type XCMPMessageHandlers = TokenDealer;
}
impl cumulus_token_dealer::Trait for Runtime {
type Event = Event;
type UpwardMessageSender = MessageBroker;
type UpwardMessage = cumulus_upward_message::RococoUpwardMessage;
type Currency = Balances;
type XCMPMessageSender = MessageBroker;
} }
impl parachain_info::Trait for Runtime {} impl parachain_info::Trait for Runtime {}
@@ -233,8 +214,6 @@ construct_runtime! {
Sudo: pallet_sudo::{Module, Call, Storage, Config<T>, Event<T>}, Sudo: pallet_sudo::{Module, Call, Storage, Config<T>, Event<T>},
RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage},
ParachainUpgrade: cumulus_parachain_upgrade::{Module, Call, Storage, Inherent, Event}, ParachainUpgrade: cumulus_parachain_upgrade::{Module, Call, Storage, Inherent, Event},
MessageBroker: cumulus_message_broker::{Module, Call, Inherent, Event<T>},
TokenDealer: cumulus_token_dealer::{Module, Call, Event<T>},
TransactionPayment: pallet_transaction_payment::{Module, Storage}, TransactionPayment: pallet_transaction_payment::{Module, Storage},
ParachainInfo: parachain_info::{Module, Storage, Config}, ParachainInfo: parachain_info::{Module, Storage, Config},
} }
@@ -26,10 +26,6 @@ use sp_runtime::traits::{IdentifyAccount, Verify};
/// Specialized `ChainSpec` for the normal parachain runtime. /// Specialized `ChainSpec` for the normal parachain runtime.
pub type ChainSpec = sc_service::GenericChainSpec<parachain_runtime::GenesisConfig, Extensions>; pub type ChainSpec = sc_service::GenericChainSpec<parachain_runtime::GenesisConfig, Extensions>;
/// Specialized `ChainSpec` for the contracts parachain runtime.
pub type ContractsChainSpec =
sc_service::GenericChainSpec<parachain_contracts_runtime::GenesisConfig, Extensions>;
/// Helper function to generate a crypto pair from seed /// Helper function to generate a crypto pair from seed
pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public { pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
TPublic::Pair::from_string(&format!("//{}", seed), None) TPublic::Pair::from_string(&format!("//{}", seed), None)
@@ -100,42 +96,6 @@ pub fn get_chain_spec(id: ParaId) -> ChainSpec {
) )
} }
pub fn get_contracts_chain_spec(id: ParaId) -> ContractsChainSpec {
ContractsChainSpec::from_genesis(
"Contracts Local Testnet",
"contracts_local_testnet",
ChainType::Local,
move || {
contracts_testnet_genesis(
get_account_id_from_seed::<sr25519::Public>("Alice"),
vec![
get_account_id_from_seed::<sr25519::Public>("Alice"),
get_account_id_from_seed::<sr25519::Public>("Bob"),
get_account_id_from_seed::<sr25519::Public>("Charlie"),
get_account_id_from_seed::<sr25519::Public>("Dave"),
get_account_id_from_seed::<sr25519::Public>("Eve"),
get_account_id_from_seed::<sr25519::Public>("Ferdie"),
get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),
get_account_id_from_seed::<sr25519::Public>("Dave//stash"),
get_account_id_from_seed::<sr25519::Public>("Eve//stash"),
get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),
],
id,
)
},
vec![],
None,
None,
None,
Extensions {
relay_chain: "westend-dev".into(),
para_id: id.into(),
},
)
}
pub fn staging_test_net(id: ParaId) -> ChainSpec { pub fn staging_test_net(id: ParaId) -> ChainSpec {
ChainSpec::from_genesis( ChainSpec::from_genesis(
"Staging Testnet", "Staging Testnet",
@@ -184,28 +144,3 @@ fn testnet_genesis(
parachain_info: Some(parachain_runtime::ParachainInfoConfig { parachain_id: id }), parachain_info: Some(parachain_runtime::ParachainInfoConfig { parachain_id: id }),
} }
} }
fn contracts_testnet_genesis(
root_key: AccountId,
endowed_accounts: Vec<AccountId>,
id: ParaId,
) -> parachain_contracts_runtime::GenesisConfig {
parachain_contracts_runtime::GenesisConfig {
frame_system: Some(parachain_contracts_runtime::SystemConfig {
code: parachain_contracts_runtime::WASM_BINARY
.expect("WASM binary was not build, please build it!")
.to_vec(),
changes_trie_config: Default::default(),
}),
pallet_balances: Some(parachain_contracts_runtime::BalancesConfig {
balances: endowed_accounts
.iter()
.cloned()
.map(|k| (k, 1 << 60))
.collect(),
}),
pallet_sudo: Some(parachain_contracts_runtime::SudoConfig { key: root_key }),
parachain_info: Some(parachain_contracts_runtime::ParachainInfoConfig { parachain_id: id }),
cumulus_pallet_contracts: None,
}
}
+21 -3
View File
@@ -22,9 +22,6 @@ use structopt::StructOpt;
/// Sub-commands supported by the collator. /// Sub-commands supported by the collator.
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
pub enum Subcommand { pub enum Subcommand {
#[structopt(flatten)]
Base(sc_cli::Subcommand),
/// Export the genesis state of the parachain. /// Export the genesis state of the parachain.
#[structopt(name = "export-genesis-state")] #[structopt(name = "export-genesis-state")]
ExportGenesisState(ExportGenesisStateCommand), ExportGenesisState(ExportGenesisStateCommand),
@@ -32,6 +29,27 @@ pub enum Subcommand {
/// Export the genesis wasm of the parachain. /// Export the genesis wasm of the parachain.
#[structopt(name = "export-genesis-wasm")] #[structopt(name = "export-genesis-wasm")]
ExportGenesisWasm(ExportGenesisWasmCommand), ExportGenesisWasm(ExportGenesisWasmCommand),
/// Build a chain specification.
BuildSpec(sc_cli::BuildSpecCmd),
/// Validate blocks.
CheckBlock(sc_cli::CheckBlockCmd),
/// Export blocks.
ExportBlocks(sc_cli::ExportBlocksCmd),
/// Export the state of a given block into a chain spec.
ExportState(sc_cli::ExportStateCmd),
/// Import blocks.
ImportBlocks(sc_cli::ImportBlocksCmd),
/// Remove the whole chain.
PurgeChain(sc_cli::PurgeChainCmd),
/// Revert the chain to a previous state.
Revert(sc_cli::RevertCmd),
} }
/// Command for exporting the genesis state of the parachain /// Command for exporting the genesis state of the parachain
+77 -64
View File
@@ -27,10 +27,13 @@ use sc_cli::{
ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams,
NetworkParams, Result, RuntimeVersion, SharedParams, SubstrateCli, NetworkParams, Result, RuntimeVersion, SharedParams, SubstrateCli,
}; };
use sc_service::config::{BasePath, PrometheusConfig}; use sc_service::{
config::{BasePath, PrometheusConfig},
PartialComponents,
};
use sp_core::hexdisplay::HexDisplay; use sp_core::hexdisplay::HexDisplay;
use sp_runtime::traits::Block as BlockT; use sp_runtime::traits::Block as BlockT;
use std::{io::Write, net::SocketAddr, sync::Arc}; use std::{io::Write, net::SocketAddr};
fn load_spec( fn load_spec(
id: &str, id: &str,
@@ -47,7 +50,6 @@ fn load_spec(
"track" => Ok(Box::new(chain_spec::ChainSpec::from_json_bytes( "track" => Ok(Box::new(chain_spec::ChainSpec::from_json_bytes(
&include_bytes!("../res/track.json")[..], &include_bytes!("../res/track.json")[..],
)?)), )?)),
"contracts" => Ok(Box::new(chain_spec::get_contracts_chain_spec(para_id))),
"" => Ok(Box::new(chain_spec::get_chain_spec(para_id))), "" => Ok(Box::new(chain_spec::get_chain_spec(para_id))),
path => Ok(Box::new(chain_spec::ChainSpec::from_json_file( path => Ok(Box::new(chain_spec::ChainSpec::from_json_file(
path.into(), path.into(),
@@ -143,50 +145,79 @@ fn extract_genesis_wasm(chain_spec: &Box<dyn sc_service::ChainSpec>) -> Result<V
.ok_or_else(|| "Could not find wasm file in genesis state!".into()) .ok_or_else(|| "Could not find wasm file in genesis state!".into())
} }
fn use_contracts_runtime(chain_spec: &Box<dyn ChainSpec>) -> bool {
chain_spec.id().starts_with("trick") || chain_spec.id().starts_with("contracts")
}
/// Parse command line arguments into service configuration. /// Parse command line arguments into service configuration.
pub fn run() -> Result<()> { pub fn run() -> Result<()> {
let cli = Cli::from_args(); let cli = Cli::from_args();
match &cli.subcommand { match &cli.subcommand {
Some(Subcommand::Base(subcommand)) => { Some(Subcommand::BuildSpec(cmd)) => {
let runner = cli.create_runner(subcommand)?; let runner = cli.create_runner(cmd)?;
runner.sync_run(|config| cmd.run(config.chain_spec, config.network))
if use_contracts_runtime(&runner.config().chain_spec) { }
runner.run_subcommand(subcommand, |mut config| { Some(Subcommand::CheckBlock(cmd)) => {
let params = crate::service::new_partial::< let runner = cli.create_runner(cmd)?;
parachain_contracts_runtime::RuntimeApi, runner.async_run(|config| {
crate::service::ContractsRuntimeExecutor, let PartialComponents {
>(&mut config)?; client,
task_manager,
Ok(( import_queue,
params.client, ..
params.backend, } = crate::service::new_partial(&config)?;
params.import_queue, Ok((cmd.run(client, import_queue), task_manager))
params.task_manager, })
)) }
}) Some(Subcommand::ExportBlocks(cmd)) => {
} else { let runner = cli.create_runner(cmd)?;
runner.run_subcommand(subcommand, |mut config| { runner.async_run(|config| {
let params = crate::service::new_partial::< let PartialComponents {
parachain_runtime::RuntimeApi, client,
crate::service::RuntimeExecutor, task_manager,
>(&mut config)?; ..
} = crate::service::new_partial(&config)?;
Ok(( Ok((cmd.run(client, config.database), task_manager))
params.client, })
params.backend, }
params.import_queue, Some(Subcommand::ExportState(cmd)) => {
params.task_manager, let runner = cli.create_runner(cmd)?;
)) runner.async_run(|config| {
}) let PartialComponents {
} client,
task_manager,
..
} = crate::service::new_partial(&config)?;
Ok((cmd.run(client, config.chain_spec), task_manager))
})
}
Some(Subcommand::ImportBlocks(cmd)) => {
let runner = cli.create_runner(cmd)?;
runner.async_run(|config| {
let PartialComponents {
client,
task_manager,
import_queue,
..
} = crate::service::new_partial(&config)?;
Ok((cmd.run(client, import_queue), task_manager))
})
}
Some(Subcommand::PurgeChain(cmd)) => {
let runner = cli.create_runner(cmd)?;
runner.sync_run(|config| cmd.run(config.database))
}
Some(Subcommand::Revert(cmd)) => {
let runner = cli.create_runner(cmd)?;
runner.async_run(|config| {
let PartialComponents {
client,
task_manager,
backend,
..
} = crate::service::new_partial(&config)?;
Ok((cmd.run(client, backend), task_manager))
})
} }
Some(Subcommand::ExportGenesisState(params)) => { Some(Subcommand::ExportGenesisState(params)) => {
sc_cli::init_logger(""); sc_cli::init_logger("", sc_tracing::TracingReceiver::Log, None)?;
let block: Block = generate_genesis_block(&load_spec( let block: Block = generate_genesis_block(&load_spec(
&params.chain.clone().unwrap_or_default(), &params.chain.clone().unwrap_or_default(),
@@ -203,7 +234,7 @@ pub fn run() -> Result<()> {
Ok(()) Ok(())
} }
Some(Subcommand::ExportGenesisWasm(params)) => { Some(Subcommand::ExportGenesisWasm(params)) => {
sc_cli::init_logger(""); sc_cli::init_logger("", sc_tracing::TracingReceiver::Log, None)?;
let wasm_file = let wasm_file =
extract_genesis_wasm(&cli.load_spec(&params.chain.clone().unwrap_or_default())?)?; extract_genesis_wasm(&cli.load_spec(&params.chain.clone().unwrap_or_default())?)?;
@@ -219,9 +250,9 @@ pub fn run() -> Result<()> {
None => { None => {
let runner = cli.create_runner(&*cli.run)?; let runner = cli.create_runner(&*cli.run)?;
runner.run_node_until_exit(|config| { runner.run_node_until_exit(|config| async move {
// TODO // TODO
let key = Arc::new(sp_core::Pair::generate().0); let key = sp_core::Pair::generate().0;
let extension = chain_spec::Extensions::try_get(&config.chain_spec); let extension = chain_spec::Extensions::try_get(&config.chain_spec);
let relay_chain_id = extension.map(|e| e.relay_chain.clone()); let relay_chain_id = extension.map(|e| e.relay_chain.clone());
@@ -253,29 +284,11 @@ pub fn run() -> Result<()> {
info!("Parachain id: {:?}", id); info!("Parachain id: {:?}", id);
info!("Parachain Account: {}", parachain_account); info!("Parachain Account: {}", parachain_account);
info!("Parachain genesis state: {}", genesis_state); info!("Parachain genesis state: {}", genesis_state);
info!( info!("Is collating: {}", if collator { "yes" } else { "no" });
"Is collating: {}",
if collator { "yes" } else { "no" }
);
if use_contracts_runtime(&config.chain_spec) { crate::service::start_node(config, key, polkadot_config, id, collator)
crate::service::start_contracts_node( .await
config,
key,
polkadot_config,
id,
collator,
)
} else {
crate::service::start_node(
config,
key,
polkadot_config,
id,
collator,
)
.map(|r| r.0) .map(|r| r.0)
}
}) })
} }
} }
+39 -114
View File
@@ -14,42 +14,34 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>. // along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
use ansi_term::Color; use cumulus_network::build_block_announce_validator;
use cumulus_network::DelayedBlockAnnounceValidator;
use cumulus_service::{ use cumulus_service::{
prepare_node_config, start_collator, start_full_node, StartCollatorParams, StartFullNodeParams, prepare_node_config, start_collator, start_full_node, StartCollatorParams, StartFullNodeParams,
}; };
use parachain_runtime::RuntimeApi;
use polkadot_primitives::v0::CollatorPair; use polkadot_primitives::v0::CollatorPair;
use rococo_parachain_primitives::Block; use rococo_parachain_primitives::Block;
use sc_executor::native_executor_instance; use sc_executor::native_executor_instance;
pub use sc_executor::NativeExecutor; pub use sc_executor::NativeExecutor;
use sc_informant::OutputFormat;
use sc_service::{Configuration, PartialComponents, Role, TFullBackend, TFullClient, TaskManager}; use sc_service::{Configuration, PartialComponents, Role, TFullBackend, TFullClient, TaskManager};
use sp_api::ConstructRuntimeApi; use sp_core::Pair;
use sp_runtime::traits::BlakeTwo256; use sp_runtime::traits::BlakeTwo256;
use sp_trie::PrefixedMemoryDB; use sp_trie::PrefixedMemoryDB;
use std::sync::Arc; use std::sync::Arc;
// Native executor instance. // Native executor instance.
native_executor_instance!( native_executor_instance!(
pub RuntimeExecutor, pub Executor,
parachain_runtime::api::dispatch, parachain_runtime::api::dispatch,
parachain_runtime::native_version, parachain_runtime::native_version,
); );
// Native executor instance for the contracts runtime.
native_executor_instance!(
pub ContractsRuntimeExecutor,
parachain_contracts_runtime::api::dispatch,
parachain_contracts_runtime::native_version,
);
/// Starts a `ServiceBuilder` for a full service. /// 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 /// 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. /// be able to perform chain operations.
pub fn new_partial<RuntimeApi, Executor>( pub fn new_partial(
config: &mut Configuration, config: &Configuration,
) -> Result< ) -> Result<
PartialComponents< PartialComponents<
TFullClient<Block, RuntimeApi, Executor>, TFullClient<Block, RuntimeApi, Executor>,
@@ -60,27 +52,10 @@ pub fn new_partial<RuntimeApi, Executor>(
(), (),
>, >,
sc_service::Error, sc_service::Error,
> > {
where
RuntimeApi: ConstructRuntimeApi<Block, TFullClient<Block, RuntimeApi, Executor>>
+ Send
+ Sync
+ 'static,
RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block>
+ sp_api::Metadata<Block>
+ sp_session::SessionKeys<Block>
+ sp_api::ApiExt<
Block,
Error = sp_blockchain::Error,
StateBackend = sc_client_api::StateBackendFor<TFullBackend<Block>, Block>,
> + sp_offchain::OffchainWorkerApi<Block>
+ sp_block_builder::BlockBuilder<Block>,
sc_client_api::StateBackendFor<TFullBackend<Block>, Block>: sp_api::StateBackend<BlakeTwo256>,
Executor: sc_executor::NativeExecutionDispatch + 'static,
{
let inherent_data_providers = sp_inherents::InherentDataProviders::new(); let inherent_data_providers = sp_inherents::InherentDataProviders::new();
let (client, backend, keystore, task_manager) = let (client, backend, keystore_container, task_manager) =
sc_service::new_full_parts::<Block, RuntimeApi, Executor>(&config)?; sc_service::new_full_parts::<Block, RuntimeApi, Executor>(&config)?;
let client = Arc::new(client); let client = Arc::new(client);
@@ -105,7 +80,7 @@ where
backend, backend,
client, client,
import_queue, import_queue,
keystore, keystore_container,
task_manager, task_manager,
transaction_pool, transaction_pool,
inherent_data_providers, inherent_data_providers,
@@ -119,30 +94,15 @@ where
/// Start a node with the given parachain `Configuration` and relay chain `Configuration`. /// Start a node with the given parachain `Configuration` and relay chain `Configuration`.
/// ///
/// This is the actual implementation that is abstract over the executor and the runtime api. /// This is the actual implementation that is abstract over the executor and the runtime api.
fn start_node_impl<RuntimeApi, Executor, RB>( async fn start_node_impl<RB>(
parachain_config: Configuration, parachain_config: Configuration,
collator_key: Arc<CollatorPair>, collator_key: CollatorPair,
mut polkadot_config: polkadot_collator::Configuration, polkadot_config: Configuration,
id: polkadot_primitives::v0::Id, id: polkadot_primitives::v0::Id,
validator: bool, validator: bool,
rpc_ext_builder: RB, rpc_ext_builder: RB,
) -> sc_service::error::Result<(TaskManager, Arc<TFullClient<Block, RuntimeApi, Executor>>)> ) -> sc_service::error::Result<(TaskManager, Arc<TFullClient<Block, RuntimeApi, Executor>>)>
where where
RuntimeApi: ConstructRuntimeApi<Block, TFullClient<Block, RuntimeApi, Executor>>
+ Send
+ Sync
+ 'static,
RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block>
+ sp_api::Metadata<Block>
+ sp_session::SessionKeys<Block>
+ sp_api::ApiExt<
Block,
Error = sp_blockchain::Error,
StateBackend = sc_client_api::StateBackendFor<TFullBackend<Block>, Block>,
> + sp_offchain::OffchainWorkerApi<Block>
+ sp_block_builder::BlockBuilder<Block>,
sc_client_api::StateBackendFor<TFullBackend<Block>, Block>: sp_api::StateBackend<BlakeTwo256>,
Executor: sc_executor::NativeExecutionDispatch + 'static,
RB: Fn( RB: Fn(
Arc<TFullClient<Block, RuntimeApi, Executor>>, Arc<TFullClient<Block, RuntimeApi, Executor>>,
) -> jsonrpc_core::IoHandler<sc_rpc::Metadata> ) -> jsonrpc_core::IoHandler<sc_rpc::Metadata>
@@ -153,18 +113,12 @@ where
return Err("Light client not supported!".into()); return Err("Light client not supported!".into());
} }
let mut parachain_config = prepare_node_config(parachain_config); let parachain_config = prepare_node_config(parachain_config);
parachain_config.informant_output_format = OutputFormat { let polkadot_full_node =
enable_color: true, cumulus_service::build_polkadot_full_node(polkadot_config, collator_key.public())?;
prefix: format!("[{}] ", Color::Yellow.bold().paint("Parachain")),
};
polkadot_config.informant_output_format = OutputFormat {
enable_color: true,
prefix: format!("[{}] ", Color::Blue.bold().paint("Relaychain")),
};
let params = new_partial::<RuntimeApi, Executor>(&mut parachain_config)?; let params = new_partial(&parachain_config)?;
params params
.inherent_data_providers .inherent_data_providers
.register_provider(sp_timestamp::InherentDataProvider) .register_provider(sp_timestamp::InherentDataProvider)
@@ -172,11 +126,11 @@ where
let client = params.client.clone(); let client = params.client.clone();
let backend = params.backend.clone(); let backend = params.backend.clone();
let block_announce_validator = DelayedBlockAnnounceValidator::new(); let block_announce_validator = build_block_announce_validator(
let block_announce_validator_builder = { polkadot_full_node.client.clone(),
let block_announce_validator = block_announce_validator.clone(); id,
move |_| Box::new(block_announce_validator) as Box<_> Box::new(polkadot_full_node.network.clone()),
}; );
let prometheus_registry = parachain_config.prometheus_registry().cloned(); let prometheus_registry = parachain_config.prometheus_registry().cloned();
let transaction_pool = params.transaction_pool.clone(); let transaction_pool = params.transaction_pool.clone();
@@ -190,16 +144,13 @@ where
spawn_handle: task_manager.spawn_handle(), spawn_handle: task_manager.spawn_handle(),
import_queue, import_queue,
on_demand: None, on_demand: None,
block_announce_validator_builder: Some(Box::new(block_announce_validator_builder)), block_announce_validator_builder: Some(Box::new(|_| block_announce_validator)),
finality_proof_request_builder: None, finality_proof_request_builder: None,
finality_proof_provider: None, finality_proof_provider: None,
})?; })?;
let rpc_extensions_builder = { let rpc_client = client.clone();
let client = client.clone(); let rpc_extensions_builder = Box::new(move |_, _| rpc_ext_builder(rpc_client.clone()));
Box::new(move |_deny_unsafe| rpc_ext_builder(client.clone()))
};
sc_service::spawn_tasks(sc_service::SpawnTasksParams { sc_service::spawn_tasks(sc_service::SpawnTasksParams {
on_demand: None, on_demand: None,
@@ -210,8 +161,8 @@ where
task_manager: &mut task_manager, task_manager: &mut task_manager,
telemetry_connection_sinks: Default::default(), telemetry_connection_sinks: Default::default(),
config: parachain_config, config: parachain_config,
keystore: params.keystore, keystore: params.keystore_container.sync_keystore(),
backend, backend: backend.clone(),
network: network.clone(), network: network.clone(),
network_status_sinks, network_status_sinks,
system_rpc_tx, system_rpc_tx,
@@ -224,10 +175,12 @@ where
if validator { if validator {
let proposer_factory = sc_basic_authorship::ProposerFactory::new( let proposer_factory = sc_basic_authorship::ProposerFactory::new(
task_manager.spawn_handle(),
client.clone(), client.clone(),
transaction_pool, transaction_pool,
prometheus_registry.as_ref(), prometheus_registry.as_ref(),
); );
let spawner = task_manager.spawn_handle();
let params = StartCollatorParams { let params = StartCollatorParams {
para_id: id, para_id: id,
@@ -237,22 +190,21 @@ where
block_status: client.clone(), block_status: client.clone(),
announce_block, announce_block,
client: client.clone(), client: client.clone(),
block_announce_validator,
task_manager: &mut task_manager, task_manager: &mut task_manager,
polkadot_config,
collator_key, collator_key,
polkadot_full_node,
spawner,
backend,
}; };
start_collator(params)?; start_collator(params).await?;
} else { } else {
let params = StartFullNodeParams { let params = StartFullNodeParams {
client: client.clone(), client: client.clone(),
announce_block, announce_block,
polkadot_config,
collator_key,
block_announce_validator,
task_manager: &mut task_manager, task_manager: &mut task_manager,
para_id: id, para_id: id,
polkadot_full_node,
}; };
start_full_node(params)?; start_full_node(params)?;
@@ -264,17 +216,14 @@ where
} }
/// Start a normal parachain node. /// Start a normal parachain node.
pub fn start_node( pub async fn start_node(
parachain_config: Configuration, parachain_config: Configuration,
collator_key: Arc<CollatorPair>, collator_key: CollatorPair,
polkadot_config: polkadot_collator::Configuration, polkadot_config: Configuration,
id: polkadot_primitives::v0::Id, id: polkadot_primitives::v0::Id,
validator: bool, validator: bool,
) -> sc_service::error::Result<( ) -> sc_service::error::Result<(TaskManager, Arc<TFullClient<Block, RuntimeApi, Executor>>)> {
TaskManager, start_node_impl(
Arc<TFullClient<Block, parachain_runtime::RuntimeApi, RuntimeExecutor>>,
)> {
start_node_impl::<parachain_runtime::RuntimeApi, RuntimeExecutor, _>(
parachain_config, parachain_config,
collator_key, collator_key,
polkadot_config, polkadot_config,
@@ -282,29 +231,5 @@ pub fn start_node(
validator, validator,
|_| Default::default(), |_| Default::default(),
) )
} .await
/// Start a contracts parachain node.
pub fn start_contracts_node(
parachain_config: Configuration,
collator_key: Arc<CollatorPair>,
polkadot_config: polkadot_collator::Configuration,
id: polkadot_primitives::v0::Id,
validator: bool,
) -> sc_service::error::Result<TaskManager> {
start_node_impl::<parachain_contracts_runtime::RuntimeApi, ContractsRuntimeExecutor, _>(
parachain_config,
collator_key,
polkadot_config,
id,
validator,
|client| {
let mut io = jsonrpc_core::IoHandler::default();
use cumulus_pallet_contracts_rpc::{Contracts, ContractsApi};
io.extend_with(ContractsApi::to_delegate(Contracts::new(client)));
io
},
)
.map(|r| r.0)
} }
+15 -18
View File
@@ -15,28 +15,25 @@ trie-db = { version = "0.22.0", default-features = false }
cumulus-primitives = { path = "../primitives", default-features = false } cumulus-primitives = { path = "../primitives", default-features = false }
# Substrate dependencies # Substrate dependencies
frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-trie = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-trie = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
# Polkadot dependencies # Polkadot dependencies
parachain = { package = "polkadot-parachain", git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false, features = [ "wasm-api" ] } parachain = { package = "polkadot-parachain", git = "https://github.com/paritytech/polkadot", default-features = false, features = [ "wasm-api" ] , branch = "bkchr-adder-collator-integration-test" }
[dev-dependencies] [dev-dependencies]
# Substrate dependencies sc-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sc-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sc-client-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-executor = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-executor = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } cumulus-test-client = { path = "../test/client" }
sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } env_logger = "0.7.1"
# Cumulus dependencies
test-client = { package = "cumulus-test-client", path = "../test/client" }
[features] [features]
default = ["std"] default = ["std"]
@@ -19,24 +19,22 @@
use frame_executive::ExecuteBlock; use frame_executive::ExecuteBlock;
use sp_runtime::traits::{Block as BlockT, HashFor, Header as HeaderT}; use sp_runtime::traits::{Block as BlockT, HashFor, Header as HeaderT};
use sp_std::{boxed::Box, vec::Vec, collections::btree_map::BTreeMap, ops::Bound}; use sp_std::{boxed::Box, collections::btree_map::BTreeMap, ops::Bound, vec::Vec};
use sp_trie::{delta_trie_root, read_trie_value, Layout, MemoryDB, StorageProof}; use sp_trie::{delta_trie_root, read_trie_value, Layout, MemoryDB, StorageProof};
use hash_db::{HashDB, EMPTY_PREFIX}; use hash_db::{HashDB, EMPTY_PREFIX};
use trie_db::{TrieDB, TrieDBIterator, Trie, TrieError}; use trie_db::{Trie, TrieDB, TrieDBIterator};
use parachain::primitives::{HeadData, ValidationCode, ValidationParams, ValidationResult}; use parachain::primitives::{HeadData, ValidationCode, ValidationParams, ValidationResult};
use codec::{Decode, Encode, EncodeAppend}; use codec::{Decode, Encode, EncodeAppend};
use cumulus_primitives::{ use cumulus_primitives::{
validation_function_params::ValidationFunctionParams,
well_known_keys::{ well_known_keys::{
NEW_VALIDATION_CODE, PROCESSED_DOWNWARD_MESSAGES, UPWARD_MESSAGES, NEW_VALIDATION_CODE, PROCESSED_DOWNWARD_MESSAGES, UPWARD_MESSAGES, VALIDATION_DATA,
VALIDATION_FUNCTION_PARAMS,
}, },
GenericUpwardMessage, GenericUpwardMessage, ValidationData,
}; };
/// Stores the global [`Storage`] instance. /// Stores the global [`Storage`] instance.
@@ -117,16 +115,13 @@ pub fn validate_block<B: BlockT, E: ExecuteBlock<B>>(params: ValidationParams) -
let block = B::new(block_data.header, block_data.extrinsics); let block = B::new(block_data.header, block_data.extrinsics);
assert!( assert!(
parent_head.hash() == *block.header().parent_hash(), parent_head.hash() == *block.header().parent_hash(),
"Invalid parent hash" "Invalid parent hash",
); );
// make a copy for later use
let validation_function_params = (&params).into();
let storage_inner = WitnessStorage::<B>::new( let storage_inner = WitnessStorage::<B>::new(
block_data.storage_proof, block_data.storage_proof,
parent_head.state_root().clone(), parent_head.state_root().clone(),
validation_function_params, params,
) )
.expect("Witness data and storage root always match; qed"); .expect("Witness data and storage root always match; qed");
@@ -153,9 +148,6 @@ pub fn validate_block<B: BlockT, E: ExecuteBlock<B>>(params: ValidationParams) -
// its scheduled upgrade so we can validate that block number later. // its scheduled upgrade so we can validate that block number later.
let new_validation_code = let new_validation_code =
with_storage(|storage| storage.modified(NEW_VALIDATION_CODE)).map(ValidationCode); with_storage(|storage| storage.modified(NEW_VALIDATION_CODE)).map(ValidationCode);
if new_validation_code.is_some() && validation_function_params.code_upgrade_allowed.is_none() {
panic!("Attempt to upgrade validation function when not permitted!");
}
// Extract potential upward messages from the storage. // Extract potential upward messages from the storage.
let upward_messages = match with_storage(|storage| storage.modified(UPWARD_MESSAGES)) { let upward_messages = match with_storage(|storage| storage.modified(UPWARD_MESSAGES)) {
@@ -183,7 +175,7 @@ struct WitnessStorage<B: BlockT> {
witness_data: MemoryDB<HashFor<B>>, witness_data: MemoryDB<HashFor<B>>,
overlay: BTreeMap<Vec<u8>, Option<Vec<u8>>>, overlay: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
storage_root: B::Hash, storage_root: B::Hash,
params: ValidationFunctionParams, validation_params: ValidationParams,
} }
impl<B: BlockT> WitnessStorage<B> { impl<B: BlockT> WitnessStorage<B> {
@@ -193,9 +185,9 @@ impl<B: BlockT> WitnessStorage<B> {
fn new( fn new(
storage_proof: StorageProof, storage_proof: StorageProof,
storage_root: B::Hash, storage_root: B::Hash,
params: ValidationFunctionParams, validation_params: ValidationParams,
) -> Result<Self, &'static str> { ) -> Result<Self, &'static str> {
let mut db = storage_proof.into_memory_db(); let db = storage_proof.into_memory_db();
if !HashDB::contains(&db, &storage_root, EMPTY_PREFIX) { if !HashDB::contains(&db, &storage_root, EMPTY_PREFIX) {
return Err("Witness data does not contain given storage root."); return Err("Witness data does not contain given storage root.");
@@ -205,7 +197,7 @@ impl<B: BlockT> WitnessStorage<B> {
witness_data: db, witness_data: db,
overlay: Default::default(), overlay: Default::default(),
storage_root, storage_root,
params, validation_params,
}) })
} }
@@ -238,41 +230,50 @@ impl<B: BlockT> WitnessStorage<B> {
/// Find the next storage key after the given `key` in the overlay. /// Find the next storage key after the given `key` in the overlay.
fn overlay_next_key(&self, key: &[u8]) -> Option<(&[u8], Option<&[u8]>)> { fn overlay_next_key(&self, key: &[u8]) -> Option<(&[u8], Option<&[u8]>)> {
let range = (Bound::Excluded(key), Bound::Unbounded); let range = (Bound::Excluded(key), Bound::Unbounded);
self.overlay.range::<[u8], _>(range).next().map(|(k, v)| (&k[..], v.as_deref())) self.overlay
.range::<[u8], _>(range)
.next()
.map(|(k, v)| (&k[..], v.as_deref()))
}
/// Checks that the encoded `ValidationData` in `data` is correct.
///
/// Should be removed with: https://github.com/paritytech/cumulus/issues/217
fn check_validation_data(&self, mut data: &[u8]) {
let validation_data = ValidationData::decode(&mut data).expect("Invalid `ValidationData`");
assert_eq!(
self.validation_params.parent_head,
validation_data.persisted.parent_head
);
assert_eq!(
self.validation_params.relay_chain_height,
validation_data.persisted.block_number
);
assert_eq!(
self.validation_params.hrmp_mqc_heads,
validation_data.persisted.hrmp_mqc_heads
);
} }
} }
impl<B: BlockT> Storage for WitnessStorage<B> { impl<B: BlockT> Storage for WitnessStorage<B> {
fn modified(&self, key: &[u8]) -> Option<Vec<u8>> { fn modified(&self, key: &[u8]) -> Option<Vec<u8>> {
match key { self.overlay.get(key).cloned().unwrap_or(None)
VALIDATION_FUNCTION_PARAMS => Some(self.params.encode()),
key => self
.overlay
.get(key)
.cloned()
.unwrap_or(None),
}
} }
fn get(&self, key: &[u8]) -> Option<Vec<u8>> { fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
match key { self.overlay.get(key).cloned().unwrap_or_else(|| {
VALIDATION_FUNCTION_PARAMS => Some(self.params.encode()), read_trie_value::<Layout<HashFor<B>>, _>(&self.witness_data, &self.storage_root, key)
key => self .expect("Reading key from trie.")
.overlay })
.get(key)
.cloned()
.unwrap_or_else(|| {
read_trie_value::<Layout<HashFor<B>>, _>(
&self.witness_data,
&self.storage_root,
key,
)
.expect("Reading key from trie.")
})
}
} }
fn insert(&mut self, key: &[u8], value: &[u8]) { fn insert(&mut self, key: &[u8], value: &[u8]) {
if key == VALIDATION_DATA {
self.check_validation_data(value);
}
self.overlay.insert(key.to_vec(), Some(value.to_vec())); self.overlay.insert(key.to_vec(), Some(value.to_vec()));
} }
@@ -339,13 +340,17 @@ impl<B: BlockT> Storage for WitnessStorage<B> {
let next_overlay_key = self.overlay_next_key(key); let next_overlay_key = self.overlay_next_key(key);
match (next_trie_key, next_overlay_key) { match (next_trie_key, next_overlay_key) {
(Some(backend_key), Some(overlay_key)) if &backend_key[..] < overlay_key.0 => Some(backend_key), (Some(backend_key), Some(overlay_key)) if &backend_key[..] < overlay_key.0 => {
Some(backend_key)
}
(backend_key, None) => backend_key, (backend_key, None) => backend_key,
(_, Some(overlay_key)) => if overlay_key.1.is_some() { (_, Some(overlay_key)) => {
Some(overlay_key.0.to_vec()) if overlay_key.1.is_some() {
} else { Some(overlay_key.0.to_vec())
self.next_key(&overlay_key.0) } else {
}, self.next_key(&overlay_key.0)
}
}
} }
} }
} }
+33 -15
View File
@@ -16,6 +16,13 @@
use crate::ParachainBlockData; use crate::ParachainBlockData;
use cumulus_primitives::{PersistedValidationData, ValidationData};
use cumulus_test_client::{
generate_block_inherents,
runtime::{Block, Hash, Header, UncheckedExtrinsic, WASM_BINARY},
transfer, Client, DefaultTestClientBuilderExt, LongestChain, TestClientBuilder,
TestClientBuilderExt,
};
use parachain::primitives::{BlockData, HeadData, ValidationParams, ValidationResult}; use parachain::primitives::{BlockData, HeadData, ValidationParams, ValidationResult};
use sc_block_builder::BlockBuilderProvider; use sc_block_builder::BlockBuilderProvider;
use sc_executor::{ use sc_executor::{
@@ -30,12 +37,6 @@ use sp_runtime::{
generic::BlockId, generic::BlockId,
traits::{Block as BlockT, Header as HeaderT}, traits::{Block as BlockT, Header as HeaderT},
}; };
use test_client::{
generate_block_inherents,
runtime::{Block, Hash, Header, UncheckedExtrinsic, WASM_BINARY},
transfer, Client, DefaultTestClientBuilderExt, LongestChain, TestClientBuilder,
TestClientBuilderExt,
};
use codec::{Decode, Encode}; use codec::{Decode, Encode};
@@ -48,10 +49,9 @@ fn call_validate_block(
let params = ValidationParams { let params = ValidationParams {
block_data: BlockData(block_data.encode()), block_data: BlockData(block_data.encode()),
parent_head: HeadData(parent_head.encode()), parent_head: HeadData(parent_head.encode()),
code_upgrade_allowed: None,
max_code_size: 1024,
max_head_data_size: 1024,
relay_chain_height: 1, relay_chain_height: 1,
hrmp_mqc_heads: Vec::new(),
dmq_mqc_head: Default::default(),
} }
.encode(); .encode();
@@ -86,15 +86,26 @@ fn create_test_client() -> (Client, LongestChain) {
fn build_block_with_proof( fn build_block_with_proof(
client: &Client, client: &Client,
extra_extrinsics: Vec<UncheckedExtrinsic>, extra_extrinsics: Vec<UncheckedExtrinsic>,
parent_head: Header,
) -> (Block, sp_trie::StorageProof) { ) -> (Block, sp_trie::StorageProof) {
let block_id = BlockId::Hash(client.info().best_hash); let block_id = BlockId::Hash(client.info().best_hash);
let mut builder = client let mut builder = client
.new_block_at(&block_id, Default::default(), true) .new_block_at(&block_id, Default::default(), true)
.expect("Initializes new block"); .expect("Initializes new block");
generate_block_inherents(client) generate_block_inherents(
.into_iter() client,
.for_each(|e| builder.push(e).expect("Pushes an inherent")); Some(ValidationData {
persisted: PersistedValidationData {
block_number: 1,
parent_head: parent_head.encode().into(),
..Default::default()
},
..Default::default()
}),
)
.into_iter()
.for_each(|e| builder.push(e).expect("Pushes an inherent"));
extra_extrinsics extra_extrinsics
.into_iter() .into_iter()
@@ -112,9 +123,11 @@ fn build_block_with_proof(
#[test] #[test]
fn validate_block_no_extra_extrinsics() { fn validate_block_no_extra_extrinsics() {
let _ = env_logger::try_init();
let (client, longest_chain) = create_test_client(); let (client, longest_chain) = create_test_client();
let parent_head = longest_chain.best_chain().expect("Best block exists"); let parent_head = longest_chain.best_chain().expect("Best block exists");
let (block, witness_data) = build_block_with_proof(&client, vec![]); let (block, witness_data) = build_block_with_proof(&client, vec![], parent_head.clone());
let (header, extrinsics) = block.deconstruct(); let (header, extrinsics) = block.deconstruct();
let block_data = ParachainBlockData::new(header.clone(), extrinsics, witness_data); let block_data = ParachainBlockData::new(header.clone(), extrinsics, witness_data);
@@ -125,6 +138,8 @@ fn validate_block_no_extra_extrinsics() {
#[test] #[test]
fn validate_block_with_extra_extrinsics() { fn validate_block_with_extra_extrinsics() {
let _ = env_logger::try_init();
let (client, longest_chain) = create_test_client(); let (client, longest_chain) = create_test_client();
let parent_head = longest_chain.best_chain().expect("Best block exists"); let parent_head = longest_chain.best_chain().expect("Best block exists");
let extra_extrinsics = vec![ let extra_extrinsics = vec![
@@ -133,7 +148,8 @@ fn validate_block_with_extra_extrinsics() {
transfer(&client, Charlie, Alice, 500), transfer(&client, Charlie, Alice, 500),
]; ];
let (block, witness_data) = build_block_with_proof(&client, extra_extrinsics); let (block, witness_data) =
build_block_with_proof(&client, extra_extrinsics, parent_head.clone());
let (header, extrinsics) = block.deconstruct(); let (header, extrinsics) = block.deconstruct();
let block_data = ParachainBlockData::new(header.clone(), extrinsics, witness_data); let block_data = ParachainBlockData::new(header.clone(), extrinsics, witness_data);
@@ -145,9 +161,11 @@ fn validate_block_with_extra_extrinsics() {
#[test] #[test]
#[should_panic(expected = "Calls `validate_block`: Other(\"Trap: Trap { kind: Unreachable }\")")] #[should_panic(expected = "Calls `validate_block`: Other(\"Trap: Trap { kind: Unreachable }\")")]
fn validate_block_invalid_parent_hash() { fn validate_block_invalid_parent_hash() {
let _ = env_logger::try_init();
let (client, longest_chain) = create_test_client(); let (client, longest_chain) = create_test_client();
let parent_head = longest_chain.best_chain().expect("Best block exists"); let parent_head = longest_chain.best_chain().expect("Best block exists");
let (block, witness_data) = build_block_with_proof(&client, vec![]); let (block, witness_data) = build_block_with_proof(&client, vec![], parent_head.clone());
let (mut header, extrinsics) = block.deconstruct(); let (mut header, extrinsics) = block.deconstruct();
header.set_parent_hash(Hash::from_low_u64_be(1)); header.set_parent_hash(Hash::from_low_u64_be(1));
+14 -12
View File
@@ -9,19 +9,21 @@ edition = "2018"
cumulus-consensus = { path = "../consensus" } cumulus-consensus = { path = "../consensus" }
cumulus-collator = { path = "../collator" } cumulus-collator = { path = "../collator" }
cumulus-primitives = { path = "../primitives" } cumulus-primitives = { path = "../primitives" }
cumulus-network = { path = "../network" }
# Substrate dependencies # Substrate dependencies
sc-service = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
# Polkadot dependencies # Polkadot dependencies
polkadot-collator = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-overseer = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
# Other deps
futures = "0.3.6"
+159 -95
View File
@@ -18,33 +18,37 @@
//! //!
//! Provides functions for starting a collator node or a normal full node. //! Provides functions for starting a collator node or a normal full node.
use cumulus_collator::CollatorBuilder;
use cumulus_network::{DelayedBlockAnnounceValidator, JustifiedBlockAnnounceValidator};
use cumulus_primitives::ParaId; use cumulus_primitives::ParaId;
use polkadot_primitives::v0::{Block as PBlock, CollatorPair}; use futures::{Future, FutureExt};
use polkadot_service::{AbstractClient, ClientHandle, RuntimeApiCollection}; use polkadot_overseer::OverseerHandler;
use polkadot_primitives::v1::{Block as PBlock, CollatorId, CollatorPair};
use polkadot_service::{AbstractClient, Client as PClient, ClientHandle, RuntimeApiCollection};
use sc_client_api::{Backend as BackendT, BlockBackend, Finalizer, UsageProvider}; use sc_client_api::{Backend as BackendT, BlockBackend, Finalizer, UsageProvider};
use sc_service::{Configuration, Role, TaskManager}; use sc_service::{error::Result as ServiceResult, Configuration, Role, TaskManager};
use sp_blockchain::{HeaderBackend, Result as ClientResult}; use sp_blockchain::HeaderBackend;
use sp_consensus::{BlockImport, Environment, Error as ConsensusError, Proposer, SyncOracle}; use sp_consensus::{BlockImport, Environment, Error as ConsensusError, Proposer};
use sp_core::crypto::Pair; use sp_core::traits::SpawnNamed;
use sp_inherents::InherentDataProviders; use sp_inherents::InherentDataProviders;
use sp_runtime::traits::{BlakeTwo256, Block as BlockT}; use sp_runtime::traits::{BlakeTwo256, Block as BlockT};
use std::{marker::PhantomData, sync::Arc}; use std::{marker::PhantomData, sync::Arc};
/// Polkadot full node handles.
type PFullNode<C> = polkadot_service::NewFull<C>;
/// Parameters given to [`start_collator`]. /// Parameters given to [`start_collator`].
pub struct StartCollatorParams<'a, Block: BlockT, PF, BI, BS, Client> { pub struct StartCollatorParams<'a, Block: BlockT, PF, BI, BS, Client, Backend, Spawner, PClient> {
pub para_id: ParaId,
pub proposer_factory: PF, pub proposer_factory: PF,
pub inherent_data_providers: InherentDataProviders, pub inherent_data_providers: InherentDataProviders,
pub backend: Arc<Backend>,
pub block_import: BI, pub block_import: BI,
pub block_status: Arc<BS>, pub block_status: Arc<BS>,
pub announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
pub client: Arc<Client>, pub client: Arc<Client>,
pub block_announce_validator: DelayedBlockAnnounceValidator<Block>, pub announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
pub spawner: Spawner,
pub para_id: ParaId,
pub collator_key: CollatorPair,
pub polkadot_full_node: PFullNode<PClient>,
pub task_manager: &'a mut TaskManager, pub task_manager: &'a mut TaskManager,
pub polkadot_config: Configuration,
pub collator_key: Arc<CollatorPair>,
} }
/// Start a collator node for a parachain. /// Start a collator node for a parachain.
@@ -52,20 +56,21 @@ pub struct StartCollatorParams<'a, Block: BlockT, PF, BI, BS, Client> {
/// A collator is similar to a validator in a normal blockchain. /// A collator is similar to a validator in a normal blockchain.
/// It is responsible for producing blocks and sending the blocks to a /// It is responsible for producing blocks and sending the blocks to a
/// parachain validator for validation and inclusion into the relay chain. /// parachain validator for validation and inclusion into the relay chain.
pub fn start_collator<'a, Block, PF, BI, BS, Client, Backend>( pub async fn start_collator<'a, Block, PF, BI, BS, Client, Backend, Spawner, PClient>(
StartCollatorParams { StartCollatorParams {
para_id,
proposer_factory, proposer_factory,
inherent_data_providers, inherent_data_providers,
backend,
block_import, block_import,
block_status, block_status,
announce_block,
client, client,
block_announce_validator, announce_block,
task_manager, spawner,
polkadot_config, para_id,
collator_key, collator_key,
}: StartCollatorParams<'a, Block, PF, BI, BS, Client>, polkadot_full_node,
task_manager,
}: StartCollatorParams<'a, Block, PF, BI, BS, Client, Backend, Spawner, PClient>,
) -> sc_service::error::Result<()> ) -> sc_service::error::Result<()>
where where
Block: BlockT, Block: BlockT,
@@ -87,55 +92,124 @@ where
+ 'static, + 'static,
for<'b> &'b Client: BlockImport<Block>, for<'b> &'b Client: BlockImport<Block>,
Backend: BackendT<Block> + 'static, Backend: BackendT<Block> + 'static,
Spawner: SpawnNamed + Clone + Send + Sync + 'static,
PClient: ClientHandle,
{ {
let builder = CollatorBuilder::new( polkadot_full_node
proposer_factory, .client
inherent_data_providers, .execute_with(StartCollator {
block_import, proposer_factory,
block_status, inherent_data_providers,
para_id, backend,
client, client,
announce_block, announce_block,
block_announce_validator, overseer_handler: polkadot_full_node
); .overseer_handler
.ok_or_else(|| "Polkadot full node did not provided an `OverseerHandler`!")?,
spawner,
para_id,
collator_key,
block_import,
block_status,
})
.await?;
let (polkadot_future, polkadot_task_manager) = task_manager.add_child(polkadot_full_node.task_manager);
polkadot_collator::start_collator(builder, para_id, collator_key, polkadot_config)?;
task_manager
.spawn_essential_handle()
.spawn("polkadot", polkadot_future);
task_manager.add_child(polkadot_task_manager);
Ok(()) Ok(())
} }
struct StartCollator<Block: BlockT, Client, Backend, PF, BI, BS, Spawner> {
proposer_factory: PF,
inherent_data_providers: InherentDataProviders,
backend: Arc<Backend>,
block_import: BI,
block_status: Arc<BS>,
client: Arc<Client>,
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
overseer_handler: OverseerHandler,
spawner: Spawner,
para_id: ParaId,
collator_key: CollatorPair,
}
impl<Block, Client, Backend, PF, BI, BS, Spawner> polkadot_service::ExecuteWithClient
for StartCollator<Block, Client, Backend, PF, BI, BS, Spawner>
where
Block: BlockT,
PF: Environment<Block> + Send + 'static,
BI: BlockImport<
Block,
Error = ConsensusError,
Transaction = <PF::Proposer as Proposer<Block>>::Transaction,
> + Send
+ Sync
+ 'static,
BS: BlockBackend<Block> + Send + Sync + 'static,
Client: Finalizer<Block, Backend>
+ UsageProvider<Block>
+ HeaderBackend<Block>
+ Send
+ Sync
+ BlockBackend<Block>
+ 'static,
for<'b> &'b Client: BlockImport<Block>,
Backend: BackendT<Block> + 'static,
Spawner: SpawnNamed + Clone + Send + Sync + 'static,
{
type Output = std::pin::Pin<Box<dyn Future<Output = ServiceResult<()>>>>;
fn execute_with_client<PClient, Api, PBackend>(self, client: Arc<PClient>) -> Self::Output
where
<Api as sp_api::ApiExt<PBlock>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
PBackend: sc_client_api::Backend<PBlock>,
PBackend::State: sp_api::StateBackend<BlakeTwo256>,
Api: RuntimeApiCollection<StateBackend = PBackend::State>,
PClient: AbstractClient<PBlock, PBackend, Api = Api> + 'static,
{
async move {
cumulus_collator::start_collator(cumulus_collator::StartCollatorParams {
proposer_factory: self.proposer_factory,
inherent_data_providers: self.inherent_data_providers,
backend: self.backend,
block_import: self.block_import,
block_status: self.block_status,
client: self.client,
announce_block: self.announce_block,
overseer_handler: self.overseer_handler,
spawner: self.spawner,
para_id: self.para_id,
key: self.collator_key,
polkadot_client: client,
})
.await
.map_err(Into::into)
}
.boxed()
}
}
/// Parameters given to [`start_full_node`]. /// Parameters given to [`start_full_node`].
pub struct StartFullNodeParams<'a, Block: BlockT, Client> { pub struct StartFullNodeParams<'a, Block: BlockT, Client, PClient> {
pub polkadot_config: Configuration,
pub collator_key: Arc<CollatorPair>,
pub para_id: ParaId, pub para_id: ParaId,
pub block_announce_validator: DelayedBlockAnnounceValidator<Block>,
pub client: Arc<Client>, pub client: Arc<Client>,
pub announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>, pub polkadot_full_node: PFullNode<PClient>,
pub task_manager: &'a mut TaskManager, pub task_manager: &'a mut TaskManager,
pub announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
} }
/// Start a full node for a parachain. /// Start a full node for a parachain.
/// ///
/// A full node will only sync the given parachain and will follow the /// A full node will only sync the given parachain and will follow the
/// tip of the chain. /// tip of the chain.
pub fn start_full_node<Block, Client, Backend>( pub fn start_full_node<Block, Client, Backend, PClient>(
StartFullNodeParams { StartFullNodeParams {
polkadot_config,
collator_key,
para_id,
block_announce_validator,
client, client,
announce_block, announce_block,
task_manager, task_manager,
}: StartFullNodeParams<Block, Client>, polkadot_full_node,
para_id,
}: StartFullNodeParams<Block, Client, PClient>,
) -> sc_service::error::Result<()> ) -> sc_service::error::Result<()>
where where
Block: BlockT, Block: BlockT,
@@ -147,54 +221,23 @@ where
+ 'static, + 'static,
for<'a> &'a Client: BlockImport<Block>, for<'a> &'a Client: BlockImport<Block>,
Backend: BackendT<Block> + 'static, Backend: BackendT<Block> + 'static,
PClient: ClientHandle,
{ {
let is_light = matches!(polkadot_config.role, Role::Light); polkadot_full_node.client.execute_with(StartFullNode {
let (polkadot_task_manager, pclient, handles) = if is_light {
Err("Light client not supported.".into())
} else {
polkadot_service::build_full(
polkadot_config,
Some((collator_key.public(), para_id)),
None,
false,
6000,
None,
)
}?;
let polkadot_network = handles
.polkadot_network
.expect("Polkadot service is started; qed");
pclient.execute_with(InitParachainFullNode {
block_announce_validator,
para_id,
polkadot_sync_oracle: Box::new(polkadot_network),
announce_block, announce_block,
para_id,
client, client,
task_manager, task_manager,
_phantom: PhantomData, _phantom: PhantomData,
})?; })?;
task_manager.add_child(polkadot_task_manager); task_manager.add_child(polkadot_full_node.task_manager);
Ok(()) Ok(())
} }
/// Prepare the parachain's node condifugration struct StartFullNode<'a, Block: BlockT, Client, Backend> {
///
/// This function will disable the default announcement of Substrate for the parachain in favor
/// of the one of Cumulus.
pub fn prepare_node_config(mut parachain_config: Configuration) -> Configuration {
parachain_config.announce_block = false;
parachain_config
}
struct InitParachainFullNode<'a, Block: BlockT, Client, Backend> {
block_announce_validator: DelayedBlockAnnounceValidator<Block>,
para_id: ParaId, para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>, announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
client: Arc<Client>, client: Arc<Client>,
task_manager: &'a mut TaskManager, task_manager: &'a mut TaskManager,
@@ -202,7 +245,7 @@ struct InitParachainFullNode<'a, Block: BlockT, Client, Backend> {
} }
impl<'a, Block, Client, Backend> polkadot_service::ExecuteWithClient impl<'a, Block, Client, Backend> polkadot_service::ExecuteWithClient
for InitParachainFullNode<'a, Block, Client, Backend> for StartFullNode<'a, Block, Client, Backend>
where where
Block: BlockT, Block: BlockT,
Client: Finalizer<Block, Backend> Client: Finalizer<Block, Backend>
@@ -214,7 +257,7 @@ where
for<'b> &'b Client: BlockImport<Block>, for<'b> &'b Client: BlockImport<Block>,
Backend: BackendT<Block> + 'static, Backend: BackendT<Block> + 'static,
{ {
type Output = ClientResult<()>; type Output = ServiceResult<()>;
fn execute_with_client<PClient, Api, PBackend>(self, client: Arc<PClient>) -> Self::Output fn execute_with_client<PClient, Api, PBackend>(self, client: Arc<PClient>) -> Self::Output
where where
@@ -224,13 +267,6 @@ where
Api: RuntimeApiCollection<StateBackend = PBackend::State>, Api: RuntimeApiCollection<StateBackend = PBackend::State>,
PClient: AbstractClient<PBlock, PBackend, Api = Api> + 'static, PClient: AbstractClient<PBlock, PBackend, Api = Api> + 'static,
{ {
self.block_announce_validator
.set(Box::new(JustifiedBlockAnnounceValidator::new(
client.clone(),
self.para_id,
self.polkadot_sync_oracle,
)));
let future = cumulus_consensus::follow_polkadot( let future = cumulus_consensus::follow_polkadot(
self.para_id, self.para_id,
self.client, self.client,
@@ -244,3 +280,31 @@ where
Ok(()) Ok(())
} }
} }
/// Prepare the parachain's node condifugration
///
/// This function will disable the default announcement of Substrate for the parachain in favor
/// of the one of Cumulus.
pub fn prepare_node_config(mut parachain_config: Configuration) -> Configuration {
parachain_config.announce_block = false;
parachain_config
}
/// Build the Polkadot full node using the given `config`.
pub fn build_polkadot_full_node(
config: Configuration,
collator_id: CollatorId,
) -> sc_service::error::Result<PFullNode<PClient>> {
let is_light = matches!(config.role, Role::Light);
if is_light {
Err("Light client not supported.".into())
} else {
polkadot_service::build_full(
config,
polkadot_service::IsCollator::Yes(collator_id),
None,
None,
)
}
}
+23 -19
View File
@@ -5,25 +5,29 @@ authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
sc-service = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
test-client = { package = "substrate-test-client", git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } substrate-test-client = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
codec = { package = "parity-scale-codec", version = "1.0.5", default-features = false, features = [ "derive" ] } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-test-primitives = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-test-primitives = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" }
# Cumulus dependencies # Cumulus deps
cumulus-test-runtime = { path = "../runtime" }
cumulus-test-service = { path = "../service" } cumulus-test-service = { path = "../service" }
cumulus-primitives = { path = "../../primitives" } cumulus-primitives = { path = "../../primitives" }
runtime = { package = "cumulus-test-runtime", path = "../runtime" }
# Polkadot deps
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
# Other deps
codec = { package = "parity-scale-codec", version = "1.0.5", default-features = false, features = [ "derive" ] }
+16 -11
View File
@@ -15,34 +15,39 @@
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>. // along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
use crate::Client; use crate::Client;
use cumulus_primitives::{ use cumulus_primitives::{inherents::VALIDATION_DATA_IDENTIFIER, ValidationData};
inherents::VALIDATION_FUNCTION_PARAMS_IDENTIFIER, use cumulus_test_runtime::GetLastTimestamp;
validation_function_params::ValidationFunctionParams, use polkadot_primitives::v1::BlockNumber as PBlockNumber;
};
use runtime::GetLastTimestamp;
use sc_block_builder::BlockBuilderApi; use sc_block_builder::BlockBuilderApi;
use sp_api::ProvideRuntimeApi; use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend; use sp_blockchain::HeaderBackend;
use sp_core::ExecutionContext; use sp_core::ExecutionContext;
use sp_runtime::generic::BlockId; use sp_runtime::generic::BlockId;
/// Generate the inherents to a block so you don't have to. /// Generate the inherents required by the test runtime.
pub fn generate_block_inherents(client: &Client) -> Vec<runtime::UncheckedExtrinsic> { ///
let mut inherent_data = sp_consensus::InherentData::new(); /// - `validation_data`: The [`ValidationData`] that will be passed as inherent
/// data into the runtime when building the inherents. If
/// `None` is passed, the default value will be used.
pub fn generate_block_inherents(
client: &Client,
validation_data: Option<ValidationData<PBlockNumber>>,
) -> Vec<cumulus_test_runtime::UncheckedExtrinsic> {
let mut inherent_data = sp_inherents::InherentData::new();
let block_id = BlockId::Hash(client.info().best_hash); let block_id = BlockId::Hash(client.info().best_hash);
let last_timestamp = client let last_timestamp = client
.runtime_api() .runtime_api()
.get_last_timestamp(&block_id) .get_last_timestamp(&block_id)
.expect("Get last timestamp"); .expect("Get last timestamp");
let timestamp = last_timestamp + runtime::MinimumPeriod::get(); let timestamp = last_timestamp + cumulus_test_runtime::MinimumPeriod::get();
inherent_data inherent_data
.put_data(sp_timestamp::INHERENT_IDENTIFIER, &timestamp) .put_data(sp_timestamp::INHERENT_IDENTIFIER, &timestamp)
.expect("Put timestamp failed"); .expect("Put timestamp failed");
inherent_data inherent_data
.put_data( .put_data(
VALIDATION_FUNCTION_PARAMS_IDENTIFIER, VALIDATION_DATA_IDENTIFIER,
&ValidationFunctionParams::default(), &validation_data.unwrap_or_default(),
) )
.expect("Put validation function params failed"); .expect("Put validation function params failed");
+8 -8
View File
@@ -21,7 +21,7 @@ mod block_builder;
pub use block_builder::*; pub use block_builder::*;
use codec::Encode; use codec::Encode;
pub use runtime; pub use cumulus_test_runtime as runtime;
use runtime::{ use runtime::{
Balance, Block, BlockHashCount, Call, GenesisConfig, Runtime, Signature, SignedExtra, Balance, Block, BlockHashCount, Call, GenesisConfig, Runtime, Signature, SignedExtra,
SignedPayload, UncheckedExtrinsic, VERSION, SignedPayload, UncheckedExtrinsic, VERSION,
@@ -35,14 +35,14 @@ use sp_runtime::{
BuildStorage, SaturatedConversion, BuildStorage, SaturatedConversion,
}; };
use std::collections::BTreeMap; use std::collections::BTreeMap;
pub use test_client::*; pub use substrate_test_client::*;
mod local_executor { mod local_executor {
use test_client::sc_executor::native_executor_instance; use substrate_test_client::sc_executor::native_executor_instance;
native_executor_instance!( native_executor_instance!(
pub LocalExecutor, pub LocalExecutor,
runtime::api::dispatch, cumulus_test_runtime::api::dispatch,
runtime::native_version, cumulus_test_runtime::native_version,
); );
} }
@@ -50,14 +50,14 @@ mod local_executor {
pub use local_executor::LocalExecutor; pub use local_executor::LocalExecutor;
/// Test client database backend. /// Test client database backend.
pub type Backend = test_client::Backend<Block>; pub type Backend = substrate_test_client::Backend<Block>;
/// Test client executor. /// Test client executor.
pub type Executor = client::LocalCallExecutor<Backend, sc_executor::NativeExecutor<LocalExecutor>>; pub type Executor = client::LocalCallExecutor<Backend, sc_executor::NativeExecutor<LocalExecutor>>;
/// Test client builder for Cumulus /// Test client builder for Cumulus
pub type TestClientBuilder = pub type TestClientBuilder =
test_client::TestClientBuilder<Block, Executor, Backend, GenesisParameters>; substrate_test_client::TestClientBuilder<Block, Executor, Backend, GenesisParameters>;
/// LongestChain type for the test runtime/client. /// LongestChain type for the test runtime/client.
pub type LongestChain = sc_consensus::LongestChain<Backend, Block>; pub type LongestChain = sc_consensus::LongestChain<Backend, Block>;
@@ -71,7 +71,7 @@ pub struct GenesisParameters {
support_changes_trie: bool, support_changes_trie: bool,
} }
impl test_client::GenesisInit for GenesisParameters { impl substrate_test_client::GenesisInit for GenesisParameters {
fn genesis_storage(&self) -> Storage { fn genesis_storage(&self) -> Storage {
let changes_trie_config: Option<ChangesTrieConfiguration> = if self.support_changes_trie { let changes_trie_config: Option<ChangesTrieConfiguration> = if self.support_changes_trie {
Some(sp_test_primitives::changes_trie_config()) Some(sp_test_primitives::changes_trie_config())
+21 -27
View File
@@ -9,37 +9,36 @@ codec = { package = "parity-scale-codec", version = "1.3.0", default-features =
serde = { version = "1.0.101", optional = true, features = ["derive"] } serde = { version = "1.0.101", optional = true, features = ["derive"] }
# Substrate dependencies # Substrate dependencies
frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-sudo = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-sudo = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-offchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-offchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-transaction-pool = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-version = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-version = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
# Cumulus dependencies # Cumulus dependencies
cumulus-parachain-upgrade = { path = "../../parachain-upgrade", default-features = false } cumulus-parachain-upgrade = { path = "../../parachain-upgrade", default-features = false }
cumulus-primitives = { path = "../../primitives", default-features = false } cumulus-primitives = { path = "../../primitives", default-features = false }
cumulus-runtime = { path = "../../runtime", default-features = false } cumulus-runtime = { path = "../../runtime", default-features = false }
cumulus-upward-message = { path = "../../upward-message", default-features = false }
# Polkadot dependencies # Polkadot dependencies
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "bkchr-adder-collator-integration-test" }
[build-dependencies] [build-dependencies]
wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1.0.6" } wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "2.0.0" }
[features] [features]
default = [ "std" ] default = [ "std" ]
@@ -48,7 +47,6 @@ std = [
"cumulus-parachain-upgrade/std", "cumulus-parachain-upgrade/std",
"cumulus-primitives/std", "cumulus-primitives/std",
"cumulus-runtime/std", "cumulus-runtime/std",
"cumulus-upward-message/std",
"frame-executive/std", "frame-executive/std",
"frame-support/std", "frame-support/std",
"frame-system/std", "frame-system/std",
@@ -70,7 +68,3 @@ std = [
"sp-transaction-pool/std", "sp-transaction-pool/std",
"sp-version/std", "sp-version/std",
] ]
# Will be enabled by the `wasm-builder` when building the runtime for WASM.
runtime-wasm = [
"cumulus-upward-message/runtime-wasm",
]
+4 -5
View File
@@ -131,8 +131,7 @@ impl frame_system::Trait for Runtime {
type AvailableBlockRatio = AvailableBlockRatio; type AvailableBlockRatio = AvailableBlockRatio;
/// Runtime version. /// Runtime version.
type Version = Version; type Version = Version;
/// Converts a module to an index of this module in the runtime. type PalletInfo = PalletInfo;
type ModuleToIndex = ModuleToIndex;
type AccountData = pallet_balances::AccountData<Balance>; type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = (); type OnNewAccount = ();
type OnKilledAccount = (); type OnKilledAccount = ();
@@ -172,11 +171,11 @@ impl pallet_balances::Trait for Runtime {
type ExistentialDeposit = ExistentialDeposit; type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System; type AccountStore = System;
type WeightInfo = (); type WeightInfo = ();
type MaxLocks = ();
} }
impl pallet_transaction_payment::Trait for Runtime { impl pallet_transaction_payment::Trait for Runtime {
type Currency = Balances; type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, ()>;
type OnTransactionPayment = ();
type TransactionByteFee = TransactionByteFee; type TransactionByteFee = TransactionByteFee;
type WeightToFee = IdentityFee<Balance>; type WeightToFee = IdentityFee<Balance>;
type FeeMultiplierUpdate = (); type FeeMultiplierUpdate = ();
@@ -189,7 +188,7 @@ impl pallet_sudo::Trait for Runtime {
impl cumulus_parachain_upgrade::Trait for Runtime { impl cumulus_parachain_upgrade::Trait for Runtime {
type Event = Event; type Event = Event;
type OnValidationFunctionParams = (); type OnValidationData = ();
} }
parameter_types! { parameter_types! {
+36 -36
View File
@@ -1,48 +1,47 @@
[package] [package]
name = 'cumulus-test-service' name = "cumulus-test-service"
version = '0.1.0' version = '0.1.0'
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
edition = '2018' edition = '2018'
[dependencies] [dependencies]
ansi_term = "0.12.1"
codec = { package = 'parity-scale-codec', version = '1.0.0' } codec = { package = 'parity-scale-codec', version = '1.0.0' }
rand = "0.7.3" rand = "0.7.3"
serde = { version = "1.0.101", features = ["derive"] } serde = { version = "1.0.101", features = ["derive"] }
# Substrate # Substrate
sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", version = "0.8.0-rc5" } sc-basic-authorship = { git = "https://github.com/paritytech/substrate", version = "0.8.0-rc5" , branch = "master" }
sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-executor = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-informant = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-executor = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-network = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-service = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-branch" } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-session = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-session = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" }
substrate-test-client = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } substrate-test-client = { git = "https://github.com/paritytech/substrate", branch = "master" }
# Polkadot # Polkadot
polkadot-collator = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-test-service = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-test-service = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-overseer = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
# Cumulus # Cumulus
cumulus-collator = { path = "../../collator" }
cumulus-consensus = { path = "../../consensus" } cumulus-consensus = { path = "../../consensus" }
cumulus-network = { path = "../../network" } cumulus-network = { path = "../../network" }
cumulus-primitives = { path = "../../primitives" } cumulus-primitives = { path = "../../primitives" }
@@ -50,17 +49,18 @@ cumulus-service = { path = "../../service" }
cumulus-test-runtime = { path = "../runtime" } cumulus-test-runtime = { path = "../runtime" }
# RPC related dependencies # RPC related dependencies
jsonrpc-core = "14.2.0" jsonrpc-core = "15.1.0"
[dev-dependencies] [dev-dependencies]
futures = { version = "0.3.5" } futures = { version = "0.3.5" }
tokio = { version = "0.2.21", features = ["macros"] } tokio = { version = "0.2.21", features = ["macros"] }
# Polkadot dependencies # Polkadot dependencies
polkadot-test-runtime = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-test-runtime = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
polkadot-test-service = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } polkadot-test-service = { git = "https://github.com/paritytech/polkadot", branch = "bkchr-adder-collator-integration-test" }
# Substrate dependencies # Substrate dependencies
pallet-sudo = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } pallet-sudo = { git = "https://github.com/paritytech/substrate", branch = "master" }
substrate-test-runtime-client = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } substrate-test-runtime-client = { git = "https://github.com/paritytech/substrate", branch = "master" }
substrate-test-utils = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch" } substrate-test-utils = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master" }
+65 -139
View File
@@ -24,21 +24,17 @@ mod genesis;
pub use chain_spec::*; pub use chain_spec::*;
pub use genesis::*; pub use genesis::*;
use ansi_term::Color;
use core::future::Future; use core::future::Future;
use cumulus_collator::CollatorBuilder; use cumulus_network::BlockAnnounceValidator;
use cumulus_network::DelayedBlockAnnounceValidator;
use cumulus_primitives::ParaId; use cumulus_primitives::ParaId;
use cumulus_service::{ use cumulus_service::{
prepare_node_config, start_full_node, StartCollatorParams, StartFullNodeParams, prepare_node_config, start_collator, start_full_node, StartCollatorParams, StartFullNodeParams,
}; };
use cumulus_test_runtime::{NodeBlock as Block, RuntimeApi}; use cumulus_test_runtime::{NodeBlock as Block, RuntimeApi};
use polkadot_primitives::v0::CollatorPair; use polkadot_primitives::v1::CollatorPair;
use sc_client_api::execution_extensions::ExecutionStrategies; use sc_client_api::execution_extensions::ExecutionStrategies;
use sc_client_api::BlockBackend;
use sc_executor::native_executor_instance; use sc_executor::native_executor_instance;
pub use sc_executor::NativeExecutor; pub use sc_executor::NativeExecutor;
use sc_informant::OutputFormat;
use sc_network::{config::TransportConfig, multiaddr, NetworkService}; use sc_network::{config::TransportConfig, multiaddr, NetworkService};
use sc_service::{ use sc_service::{
config::{ config::{
@@ -48,8 +44,7 @@ use sc_service::{
BasePath, ChainSpec, Configuration, Error as ServiceError, PartialComponents, Role, BasePath, ChainSpec, Configuration, Error as ServiceError, PartialComponents, Role,
RpcHandlers, TFullBackend, TFullClient, TaskExecutor, TaskManager, RpcHandlers, TFullBackend, TFullClient, TaskExecutor, TaskManager,
}; };
use sp_consensus::{BlockImport, Environment, Error as ConsensusError, Proposer}; use sp_core::{Pair, H256};
use sp_core::{crypto::Pair, H256};
use sp_keyring::Sr25519Keyring; use sp_keyring::Sr25519Keyring;
use sp_runtime::traits::BlakeTwo256; use sp_runtime::traits::BlakeTwo256;
use sp_state_machine::BasicExternalities; use sp_state_machine::BasicExternalities;
@@ -83,7 +78,7 @@ pub fn new_partial(
> { > {
let inherent_data_providers = sp_inherents::InherentDataProviders::new(); let inherent_data_providers = sp_inherents::InherentDataProviders::new();
let (client, backend, keystore, task_manager) = let (client, backend, keystore_container, task_manager) =
sc_service::new_full_parts::<Block, RuntimeApi, RuntimeExecutor>(&config)?; sc_service::new_full_parts::<Block, RuntimeApi, RuntimeExecutor>(&config)?;
let client = Arc::new(client); let client = Arc::new(client);
@@ -108,7 +103,7 @@ pub fn new_partial(
backend, backend,
client, client,
import_queue, import_queue,
keystore, keystore_container,
task_manager, task_manager,
transaction_pool, transaction_pool,
inherent_data_providers, inherent_data_providers,
@@ -119,96 +114,22 @@ pub fn new_partial(
Ok(params) Ok(params)
} }
/// Start a test collator node for a parachain.
///
/// A collator is similar to a validator in a normal blockchain.
/// It is responsible for producing blocks and sending the blocks to a
/// parachain validator for validation and inclusion into the relay chain.
pub fn start_test_collator<'a, PF, BI, BS>(
StartCollatorParams {
para_id,
proposer_factory,
inherent_data_providers,
block_import,
block_status,
announce_block,
client,
block_announce_validator,
task_manager,
polkadot_config,
collator_key,
}: StartCollatorParams<'a, Block, PF, BI, BS, TFullClient<Block, RuntimeApi, RuntimeExecutor>>,
) -> sc_service::error::Result<()>
where
PF: Environment<Block> + Send + 'static,
BI: BlockImport<
Block,
Error = ConsensusError,
Transaction = <PF::Proposer as Proposer<Block>>::Transaction,
> + Send
+ Sync
+ 'static,
BS: BlockBackend<Block> + Send + Sync + 'static,
{
let builder = CollatorBuilder::new(
proposer_factory,
inherent_data_providers,
block_import,
block_status,
para_id,
client,
announce_block,
block_announce_validator,
);
let (polkadot_future, polkadot_task_manager) = {
let (task_manager, client, handles, _network, _rpc_handlers) =
polkadot_test_service::polkadot_test_new_full(
polkadot_config,
Some((collator_key.public(), para_id)),
None,
false,
6000,
)?;
let test_client = polkadot_test_service::TestClient(client);
let future = polkadot_collator::build_collator_service(
task_manager.spawn_handle(),
handles,
test_client,
para_id,
collator_key,
builder,
)?;
(future, task_manager)
};
task_manager
.spawn_essential_handle()
.spawn("polkadot", polkadot_future);
task_manager.add_child(polkadot_task_manager);
Ok(())
}
/// Start a node with the given parachain `Configuration` and relay chain `Configuration`. /// Start a node with the given parachain `Configuration` and relay chain `Configuration`.
/// ///
/// This is the actual implementation that is abstract over the executor and the runtime api. /// This is the actual implementation that is abstract over the executor and the runtime api.
fn start_node_impl<RB>( #[sc_cli::prefix_logs_with(parachain_config.network.node_name.as_str())]
async fn start_node_impl<RB>(
parachain_config: Configuration, parachain_config: Configuration,
collator_key: Arc<CollatorPair>, collator_key: CollatorPair,
mut polkadot_config: polkadot_collator::Configuration, polkadot_config: Configuration,
para_id: ParaId, para_id: ParaId,
validator: bool, is_collator: bool,
rpc_ext_builder: RB, rpc_ext_builder: RB,
) -> sc_service::error::Result<( ) -> sc_service::error::Result<(
TaskManager, TaskManager,
Arc<TFullClient<Block, RuntimeApi, RuntimeExecutor>>, Arc<TFullClient<Block, RuntimeApi, RuntimeExecutor>>,
Arc<NetworkService<Block, H256>>, Arc<NetworkService<Block, H256>>,
Arc<RpcHandlers>, RpcHandlers,
)> )>
where where
RB: Fn( RB: Fn(
@@ -223,32 +144,30 @@ where
let mut parachain_config = prepare_node_config(parachain_config); let mut parachain_config = prepare_node_config(parachain_config);
parachain_config.informant_output_format = OutputFormat {
enable_color: true,
prefix: format!("[{}] ", Color::Yellow.bold().paint("Parachain")),
};
polkadot_config.informant_output_format = OutputFormat {
enable_color: true,
prefix: format!("[{}] ", Color::Blue.bold().paint("Relaychain")),
};
let params = new_partial(&mut parachain_config)?; let params = new_partial(&mut parachain_config)?;
params params
.inherent_data_providers .inherent_data_providers
.register_provider(sp_timestamp::InherentDataProvider) .register_provider(sp_timestamp::InherentDataProvider)
.unwrap(); .expect("Registers timestamp inherent data provider.");
let transaction_pool = params.transaction_pool.clone();
let mut task_manager = params.task_manager;
let polkadot_full_node = polkadot_test_service::new_full(
polkadot_config,
polkadot_service::IsCollator::Yes(collator_key.public()),
)?;
let client = params.client.clone(); let client = params.client.clone();
let backend = params.backend.clone(); let backend = params.backend.clone();
let block_announce_validator = DelayedBlockAnnounceValidator::new(); let block_announce_validator = BlockAnnounceValidator::new(
let block_announce_validator_builder = { polkadot_full_node.client.clone(),
let block_announce_validator = block_announce_validator.clone(); para_id,
move |_| Box::new(block_announce_validator) as Box<_> Box::new(polkadot_full_node.network.clone()),
}; );
let block_announce_validator_builder = move |_| Box::new(block_announce_validator) as Box<_>;
let prometheus_registry = parachain_config.prometheus_registry().cloned(); let prometheus_registry = parachain_config.prometheus_registry().cloned();
let transaction_pool = params.transaction_pool.clone();
let mut task_manager = params.task_manager;
let import_queue = params.import_queue; let import_queue = params.import_queue;
let (network, network_status_sinks, system_rpc_tx, start_network) = let (network, network_status_sinks, system_rpc_tx, start_network) =
sc_service::build_network(sc_service::BuildNetworkParams { sc_service::build_network(sc_service::BuildNetworkParams {
@@ -266,7 +185,7 @@ where
let rpc_extensions_builder = { let rpc_extensions_builder = {
let client = client.clone(); let client = client.clone();
Box::new(move |_deny_unsafe| rpc_ext_builder(client.clone())) Box::new(move |_, _| rpc_ext_builder(client.clone()))
}; };
let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams {
@@ -278,7 +197,7 @@ where
task_manager: &mut task_manager, task_manager: &mut task_manager,
telemetry_connection_sinks: Default::default(), telemetry_connection_sinks: Default::default(),
config: parachain_config, config: parachain_config,
keystore: params.keystore, keystore: params.keystore_container.sync_keystore(),
backend, backend,
network: network.clone(), network: network.clone(),
network_status_sinks, network_status_sinks,
@@ -290,37 +209,38 @@ where
Arc::new(move |hash, data| network.announce_block(hash, data)) Arc::new(move |hash, data| network.announce_block(hash, data))
}; };
if validator { let polkadot_full_node = polkadot_full_node.with_client(polkadot_test_service::TestClient);
if is_collator {
let proposer_factory = sc_basic_authorship::ProposerFactory::new( let proposer_factory = sc_basic_authorship::ProposerFactory::new(
task_manager.spawn_handle(),
client.clone(), client.clone(),
transaction_pool, transaction_pool,
prometheus_registry.as_ref(), prometheus_registry.as_ref(),
); );
let params = StartCollatorParams { let params = StartCollatorParams {
para_id,
block_import: client.clone(),
proposer_factory, proposer_factory,
inherent_data_providers: params.inherent_data_providers, inherent_data_providers: params.inherent_data_providers,
backend: params.backend,
block_import: client.clone(),
block_status: client.clone(), block_status: client.clone(),
announce_block, announce_block,
client: client.clone(), client: client.clone(),
block_announce_validator, spawner: task_manager.spawn_handle(),
task_manager: &mut task_manager, task_manager: &mut task_manager,
polkadot_config, para_id,
collator_key, collator_key,
polkadot_full_node,
}; };
start_test_collator(params)?; start_collator(params).await?;
} else { } else {
let params = StartFullNodeParams { let params = StartFullNodeParams {
client: client.clone(), client: client.clone(),
announce_block, announce_block,
polkadot_config,
collator_key,
block_announce_validator,
task_manager: &mut task_manager, task_manager: &mut task_manager,
para_id, para_id,
polkadot_full_node,
}; };
start_full_node(params)?; start_full_node(params)?;
@@ -343,14 +263,14 @@ pub struct CumulusTestNode {
/// to other nodes. /// to other nodes.
pub addr: MultiaddrWithPeerId, pub addr: MultiaddrWithPeerId,
/// RPCHandlers to make RPC queries. /// RPCHandlers to make RPC queries.
pub rpc_handlers: Arc<RpcHandlers>, pub rpc_handlers: RpcHandlers,
} }
/// Run a Cumulus test node using the Cumulus test runtime. The node will be using an in-memory /// Run a Cumulus test node using the Cumulus test runtime. The node will be using an in-memory
/// socket, therefore you need to provide boot nodes if you want it to be connected to other nodes. /// socket, therefore you need to provide boot nodes if you want it to be connected to other nodes.
/// The `storage_update_func` can be used to make adjustements to the runtime before the node /// The `storage_update_func` can be used to make adjustements to the runtime before the node
/// starts. /// starts.
pub fn run_test_node( pub async fn run_test_node(
task_executor: TaskExecutor, task_executor: TaskExecutor,
key: Sr25519Keyring, key: Sr25519Keyring,
parachain_storage_update_func: impl Fn(), parachain_storage_update_func: impl Fn(),
@@ -358,32 +278,39 @@ pub fn run_test_node(
parachain_boot_nodes: Vec<MultiaddrWithPeerId>, parachain_boot_nodes: Vec<MultiaddrWithPeerId>,
polkadot_boot_nodes: Vec<MultiaddrWithPeerId>, polkadot_boot_nodes: Vec<MultiaddrWithPeerId>,
para_id: ParaId, para_id: ParaId,
validator: bool, is_collator: bool,
) -> CumulusTestNode { ) -> CumulusTestNode {
let collator_key = Arc::new(sp_core::Pair::generate().0); let collator_key = CollatorPair::generate().0;
let parachain_config = node_config( let parachain_config = node_config(
parachain_storage_update_func, parachain_storage_update_func,
task_executor.clone(), task_executor.clone(),
key, key,
parachain_boot_nodes, parachain_boot_nodes,
para_id, para_id,
is_collator,
) )
.expect("could not generate Configuration"); .expect("could not generate Configuration");
let polkadot_config = polkadot_test_service::node_config( let mut polkadot_config = polkadot_test_service::node_config(
polkadot_storage_update_func, polkadot_storage_update_func,
task_executor.clone(), task_executor.clone(),
key, key,
polkadot_boot_nodes, polkadot_boot_nodes,
false,
); );
polkadot_config.network.node_name =
format!("{} (relay chain)", polkadot_config.network.node_name);
let multiaddr = parachain_config.network.listen_addresses[0].clone(); let multiaddr = parachain_config.network.listen_addresses[0].clone();
let (task_manager, client, network, rpc_handlers) = start_node_impl::<_>( let (task_manager, client, network, rpc_handlers) = start_node_impl(
parachain_config, parachain_config,
collator_key, collator_key,
polkadot_config, polkadot_config,
para_id, para_id,
validator, is_collator,
|_| Default::default(), |_| Default::default(),
) )
.await
.expect("could not create Cumulus test service"); .expect("could not create Cumulus test service");
let peer_id = network.local_peer_id().clone(); let peer_id = network.local_peer_id().clone();
@@ -407,11 +334,16 @@ pub fn node_config(
key: Sr25519Keyring, key: Sr25519Keyring,
boot_nodes: Vec<MultiaddrWithPeerId>, boot_nodes: Vec<MultiaddrWithPeerId>,
para_id: ParaId, para_id: ParaId,
is_collator: bool,
) -> Result<Configuration, ServiceError> { ) -> Result<Configuration, ServiceError> {
let base_path = BasePath::new_temp_dir()?; let base_path = BasePath::new_temp_dir()?;
let root = base_path.path().to_path_buf(); let root = base_path.path().to_path_buf();
let role = Role::Authority { let role = if is_collator {
sentry_nodes: Vec::new(), Role::Authority {
sentry_nodes: Vec::new(),
}
} else {
Role::Full
}; };
let key_seed = key.to_seed(); let key_seed = key.to_seed();
let mut spec = Box::new(chain_spec::get_chain_spec(para_id)); let mut spec = Box::new(chain_spec::get_chain_spec(para_id));
@@ -425,19 +357,15 @@ pub fn node_config(
spec.set_storage(storage); spec.set_storage(storage);
let mut network_config = NetworkConfiguration::new( let mut network_config = NetworkConfiguration::new(
format!("Cumulus Test Node for: {}", key_seed), format!("{} (parachain)", key_seed.to_string()),
"network/test/0.1", "network/test/0.1",
Default::default(), Default::default(),
None, None,
); );
let informant_output_format = OutputFormat {
enable_color: false,
prefix: format!("[{}] ", key_seed),
};
network_config.boot_nodes = boot_nodes; network_config.boot_nodes = boot_nodes;
network_config.allow_non_globals_in_dht = false; network_config.allow_non_globals_in_dht = true;
network_config network_config
.listen_addresses .listen_addresses
@@ -452,10 +380,7 @@ pub fn node_config(
task_executor, task_executor,
transaction_pool: Default::default(), transaction_pool: Default::default(),
network: network_config, network: network_config,
keystore: KeystoreConfig::Path { keystore: KeystoreConfig::InMemory,
path: root.join("key"),
password: None,
},
database: DatabaseConfig::RocksDb { database: DatabaseConfig::RocksDb {
path: root.join("db"), path: root.join("db"),
cache_size: 128, cache_size: 128,
@@ -495,7 +420,8 @@ pub fn node_config(
max_runtime_instances: 8, max_runtime_instances: 8,
announce_block: true, announce_block: true,
base_path: Some(base_path), base_path: Some(base_path),
informant_output_format, informant_output_format: Default::default(),
wasm_runtime_overrides: None,
}) })
} }
+26 -27
View File
@@ -21,38 +21,35 @@ use sc_service::TaskExecutor;
use substrate_test_runtime_client::AccountKeyring::*; use substrate_test_runtime_client::AccountKeyring::*;
#[substrate_test_utils::test] #[substrate_test_utils::test]
#[ignore]
async fn test_collating_and_non_collator_mode_catching_up(task_executor: TaskExecutor) { async fn test_collating_and_non_collator_mode_catching_up(task_executor: TaskExecutor) {
sc_cli::init_logger("", Default::default(), None).expect("Sets up logger");
let para_id = ParaId::from(100); let para_id = ParaId::from(100);
// start alice // start alice
let alice = polkadot_test_service::run_test_node(task_executor.clone(), Alice, || {}, vec![]); let alice = polkadot_test_service::run_validator_node(task_executor.clone(), Alice, || {}, vec![]);
// start bob // start bob
let bob = polkadot_test_service::run_test_node( let bob = polkadot_test_service::run_validator_node(
task_executor.clone(), task_executor.clone(),
Bob, Bob,
|| {}, || {},
vec![alice.addr.clone()], vec![alice.addr.clone()],
); );
// ensure alice and bob can produce blocks
join!(alice.wait_for_blocks(2), bob.wait_for_blocks(2));
// register parachain // register parachain
alice alice
.register_para( .register_parachain(
para_id, para_id,
cumulus_test_runtime::WASM_BINARY cumulus_test_runtime::WASM_BINARY
.expect("You need to build the WASM binary to run this test!") .expect("You need to build the WASM binary to run this test!")
.to_vec() .to_vec(),
.into(),
initial_head_data(para_id), initial_head_data(para_id),
) )
.await .await
.unwrap(); .unwrap();
// run cumulus charlie (a validator) // run cumulus charlie (a parachain collator)
let charlie = cumulus_test_service::run_test_node( let charlie = cumulus_test_service::run_test_node(
task_executor.clone(), task_executor.clone(),
Charlie, Charlie,
@@ -62,29 +59,31 @@ async fn test_collating_and_non_collator_mode_catching_up(task_executor: TaskExe
vec![alice.addr.clone(), bob.addr.clone()], vec![alice.addr.clone(), bob.addr.clone()],
para_id, para_id,
true, true,
); )
charlie.wait_for_blocks(2).await; .await;
charlie.wait_for_blocks(5).await;
// run cumulus dave (not a validator) //TODO: Fix bug with syncing and bring back!
// run cumulus dave (a parachain full node)
// //
// a collator running in non-validator mode should be able to sync blocks from the tip of the // Should sync to the tip
// parachain // let dave = cumulus_test_service::run_test_node(
let dave = cumulus_test_service::run_test_node( // task_executor.clone(),
task_executor.clone(), // Dave,
Dave, // || {},
|| {}, // || {},
|| {}, // vec![charlie.addr.clone()],
vec![charlie.addr.clone()], // vec![alice.addr.clone(), bob.addr.clone()],
vec![alice.addr.clone(), bob.addr.clone()], // para_id,
para_id, // false,
false, // )
); // .await;
dave.wait_for_blocks(4).await; // dave.wait_for_blocks(4).await;
join!( join!(
alice.task_manager.clean_shutdown(), alice.task_manager.clean_shutdown(),
bob.task_manager.clean_shutdown(), bob.task_manager.clean_shutdown(),
charlie.task_manager.clean_shutdown(), charlie.task_manager.clean_shutdown(),
dave.task_manager.clean_shutdown(), // dave.task_manager.clean_shutdown(),
); );
} }
-42
View File
@@ -1,42 +0,0 @@
[package]
name = "cumulus-upward-message"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
# Substrate dependencies
sp-std = { git = "https://github.com/paritytech/substrate", branch = "rococo-branch", default-features = false }
# Polkadot deps
polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false }
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false }
# All these should be optional dependenices, but given the perfect Cargo, it is not possible.
polkadot-runtime = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false }
kusama-runtime = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false }
westend-runtime = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false }
rococo-runtime = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false }
[features]
default = [ "std" ]
std = [
"sp-std/std",
"polkadot-runtime/std",
"kusama-runtime/std",
"westend-runtime/std",
"rococo-runtime/std",
"polkadot-core-primitives/std",
"polkadot-parachain/std",
]
# Should be enabled by when compiling the runtime for WASM.
#
# This disables the runtime api of the Polkadot/Kusama/Westend
# runtimes to not clash with the runtime api of the Parachain when building
# for WASM.
runtime-wasm = [
"polkadot-runtime/disable-runtime-api",
"westend-runtime/disable-runtime-api",
"rococo-runtime/disable-runtime-api",
"kusama-runtime/disable-runtime-api",
]
-37
View File
@@ -1,37 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Kusama upward message
use crate::*;
use kusama_runtime::{BalancesCall, ParachainsCall};
use polkadot_core_primitives::{AccountId, Balance};
use sp_std::vec::Vec;
/// The Kusama upward message.
pub type UpwardMessage = kusama_runtime::Call;
impl BalancesMessage<AccountId, Balance> for UpwardMessage {
fn transfer(dest: AccountId, amount: Balance) -> Self {
BalancesCall::transfer(dest, amount).into()
}
}
impl XCMPMessage for UpwardMessage {
fn send_message(dest: ParaId, msg: Vec<u8>) -> Self {
ParachainsCall::send_xcmp_message(dest, msg).into()
}
}
-49
View File
@@ -1,49 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Upward messages types and traits for Polkadot, Kusama, Rococo and Westend.
//!
//! As Cumulus needs to suits multiple Polkadot-like runtimes the upward message
//! type is different for each of them. To support all of them, Cumulus provides
//! traits to write upward message generic code.
#![cfg_attr(not(feature = "std"), no_std)]
use polkadot_parachain::primitives::Id as ParaId;
use sp_std::vec::Vec;
mod kusama;
mod polkadot;
mod rococo;
mod westend;
pub use kusama::UpwardMessage as KusamaUpwardMessage;
pub use polkadot::UpwardMessage as PolkadotUpwardMessage;
pub use rococo::UpwardMessage as RococoUpwardMessage;
pub use westend::UpwardMessage as WestendUpwardMessage;
/// A `Balances` related upward message.
pub trait BalancesMessage<AccountId, Balance>: Sized {
/// Transfer the given `amount` from the parachain account to the given
/// `dest` account.
fn transfer(dest: AccountId, amount: Balance) -> Self;
}
/// A `XCMP` related upward message.
pub trait XCMPMessage: Sized {
/// Send the given XCMP message to given parachain.
fn send_message(dest: ParaId, msg: Vec<u8>) -> Self;
}
-37
View File
@@ -1,37 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Polkadot upward message
use crate::*;
use polkadot_core_primitives::{AccountId, Balance};
use polkadot_runtime::{BalancesCall, ParachainsCall};
use sp_std::vec::Vec;
/// The Polkadot upward message.
pub type UpwardMessage = polkadot_runtime::Call;
impl BalancesMessage<AccountId, Balance> for UpwardMessage {
fn transfer(dest: AccountId, amount: Balance) -> Self {
BalancesCall::transfer(dest, amount).into()
}
}
impl XCMPMessage for UpwardMessage {
fn send_message(dest: ParaId, msg: Vec<u8>) -> Self {
ParachainsCall::send_xcmp_message(dest, msg).into()
}
}
-37
View File
@@ -1,37 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Rococo upward message
use crate::*;
use polkadot_core_primitives::{AccountId, Balance};
use rococo_runtime::{BalancesCall, ParachainsCall};
use sp_std::vec::Vec;
/// The Rococo upward message.
pub type UpwardMessage = rococo_runtime::Call;
impl BalancesMessage<AccountId, Balance> for UpwardMessage {
fn transfer(dest: AccountId, amount: Balance) -> Self {
BalancesCall::transfer(dest, amount).into()
}
}
impl XCMPMessage for UpwardMessage {
fn send_message(dest: ParaId, msg: Vec<u8>) -> Self {
ParachainsCall::send_xcmp_message(dest, msg).into()
}
}
-37
View File
@@ -1,37 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Substrate 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.
// Substrate 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Westend upward message
use crate::*;
use polkadot_core_primitives::{AccountId, Balance};
use sp_std::vec::Vec;
use westend_runtime::{BalancesCall, ParachainsCall};
/// The Westend upward message.
pub type UpwardMessage = westend_runtime::Call;
impl BalancesMessage<AccountId, Balance> for UpwardMessage {
fn transfer(dest: AccountId, amount: Balance) -> Self {
BalancesCall::transfer(dest, amount).into()
}
}
impl XCMPMessage for UpwardMessage {
fn send_message(dest: ParaId, msg: Vec<u8>) -> Self {
ParachainsCall::send_xcmp_message(dest, msg).into()
}
}