mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 18:27:56 +00:00
Use BTreeMap rather than HashMap in demo runtime.
This commit is contained in:
@@ -11,3 +11,6 @@ polkadot-primitives = { path = "../primitives" }
|
||||
substrate-client = { path = "../../substrate/client" }
|
||||
substrate-executor = { path = "../../substrate/executor" }
|
||||
substrate-state-machine = { path = "../../substrate/state-machine" }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-keyring = { path = "../../substrate/keyring" }
|
||||
|
||||
+231
-11
@@ -18,7 +18,7 @@
|
||||
//! runtime.
|
||||
|
||||
extern crate polkadot_executor as polkadot_executor;
|
||||
extern crate polkadot_runtime ;
|
||||
extern crate polkadot_runtime;
|
||||
extern crate polkadot_primitives as primitives;
|
||||
extern crate substrate_client as client;
|
||||
extern crate substrate_executor as substrate_executor;
|
||||
@@ -27,13 +27,18 @@ extern crate substrate_state_machine as state_machine;
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate substrate_keyring as keyring;
|
||||
|
||||
use client::backend::Backend;
|
||||
use client::Client;
|
||||
use polkadot_runtime::runtime;
|
||||
use polkadot_executor::Executor as LocalDispatch;
|
||||
use substrate_executor::{NativeExecutionDispatch, NativeExecutor};
|
||||
use primitives::{AccountId, SessionKey};
|
||||
use primitives::block::Id as BlockId;
|
||||
use state_machine::OverlayedChanges;
|
||||
use primitives::{AccountId, SessionKey, Timestamp};
|
||||
use primitives::block::{Id as BlockId, Block, Header, Body};
|
||||
use primitives::transaction::UncheckedTransaction;
|
||||
use primitives::parachain::DutyRoster;
|
||||
|
||||
error_chain! {
|
||||
@@ -43,10 +48,21 @@ error_chain! {
|
||||
description("Unknown runtime code")
|
||||
display("Unknown runtime code")
|
||||
}
|
||||
/// Unknown block ID.
|
||||
UnknownBlock(b: BlockId) {
|
||||
description("Unknown block")
|
||||
display("Unknown block")
|
||||
}
|
||||
/// Attempted to push an inherent transaction manually.
|
||||
PushedInherentTransaction(tx: UncheckedTransaction) {
|
||||
description("Attempted to push an inherent transaction to a block."),
|
||||
display("Pushed inherent transaction to a block: {:?}", tx),
|
||||
}
|
||||
/// Badly-formed transaction.
|
||||
BadlyFormedTransaction(tx: UncheckedTransaction) {
|
||||
description("Attempted to push a badly-formed transaction to a block."),
|
||||
display("Pushed badly-formed transaction to a block: {:?}", tx),
|
||||
}
|
||||
/// Some other error.
|
||||
// TODO: allow to be specified as associated type of PolkadotApi
|
||||
Other(e: Box<::std::error::Error + Send>) {
|
||||
@@ -60,10 +76,29 @@ error_chain! {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<client::error::Error> for Error {
|
||||
fn from(e: client::error::Error) -> Error {
|
||||
match e {
|
||||
client::error::Error(client::error::ErrorKind::UnknownBlock(b), _) => Error::from_kind(ErrorKind::UnknownBlock(b)),
|
||||
other => Error::from_kind(ErrorKind::Other(Box::new(other) as Box<_>)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BlockBuilder: Sized {
|
||||
/// Push a non-inherent transaction.
|
||||
fn push_transaction(&mut self, transaction: UncheckedTransaction) -> Result<()>;
|
||||
|
||||
/// Finalise the block.
|
||||
fn bake(self) -> Block;
|
||||
}
|
||||
|
||||
/// Trait encapsulating the Polkadot API.
|
||||
///
|
||||
/// All calls should fail when the exact runtime is unknown.
|
||||
pub trait PolkadotApi {
|
||||
type BlockBuilder: BlockBuilder;
|
||||
|
||||
/// Get session keys at a given block.
|
||||
fn session_keys(&self, at: &BlockId) -> Result<Vec<SessionKey>>;
|
||||
|
||||
@@ -72,24 +107,26 @@ pub trait PolkadotApi {
|
||||
|
||||
/// Get the authority duty roster at a block.
|
||||
fn duty_roster(&self, at: &BlockId) -> Result<DutyRoster>;
|
||||
}
|
||||
|
||||
fn convert_client_error(e: client::error::Error) -> Error {
|
||||
match e {
|
||||
client::error::Error(client::error::ErrorKind::UnknownBlock(b), _) => Error::from_kind(ErrorKind::UnknownBlock(b)),
|
||||
other => Error::from_kind(ErrorKind::Other(Box::new(other) as Box<_>)),
|
||||
}
|
||||
/// Get the timestamp registered at a block.
|
||||
fn timestamp(&self, at: &BlockId) -> Result<Timestamp>;
|
||||
|
||||
/// Evaluate a block and see if it gives an error.
|
||||
fn evaluate_block(&self, at: &BlockId, block: Block) -> Result<()>;
|
||||
|
||||
/// Create a block builder on top of the parent block.
|
||||
fn build_block(&self, parent: &BlockId, timestamp: u64) -> Result<Self::BlockBuilder>;
|
||||
}
|
||||
|
||||
// set up the necessary scaffolding to execute the runtime.
|
||||
macro_rules! with_runtime {
|
||||
($client: ident, $at: expr, $exec: expr) => {{
|
||||
// bail if the code is not the same as the natively linked.
|
||||
if $client.code_at($at).map_err(convert_client_error)? != LocalDispatch::native_equivalent() {
|
||||
if $client.code_at($at)? != LocalDispatch::native_equivalent() {
|
||||
bail!(ErrorKind::UnknownRuntime);
|
||||
}
|
||||
|
||||
$client.state_at($at).map_err(convert_client_error).and_then(|state| {
|
||||
$client.state_at($at).map_err(Error::from).and_then(|state| {
|
||||
let mut changes = Default::default();
|
||||
let mut ext = state_machine::Ext {
|
||||
overlay: &mut changes,
|
||||
@@ -104,6 +141,8 @@ macro_rules! with_runtime {
|
||||
impl<B: Backend> PolkadotApi for Client<B, NativeExecutor<LocalDispatch>>
|
||||
where ::client::error::Error: From<<<B as Backend>::State as state_machine::backend::Backend>::Error>
|
||||
{
|
||||
type BlockBuilder = ClientBlockBuilder<B::State>;
|
||||
|
||||
fn session_keys(&self, at: &BlockId) -> Result<Vec<SessionKey>> {
|
||||
with_runtime!(self, at, ::runtime::consensus::authorities)
|
||||
}
|
||||
@@ -115,4 +154,185 @@ impl<B: Backend> PolkadotApi for Client<B, NativeExecutor<LocalDispatch>>
|
||||
fn duty_roster(&self, at: &BlockId) -> Result<DutyRoster> {
|
||||
with_runtime!(self, at, ::runtime::parachains::calculate_duty_roster)
|
||||
}
|
||||
|
||||
fn timestamp(&self, at: &BlockId) -> Result<Timestamp> {
|
||||
with_runtime!(self, at, ::runtime::timestamp::get)
|
||||
}
|
||||
|
||||
fn evaluate_block(&self, at: &BlockId, block: Block) -> Result<()> {
|
||||
with_runtime!(self, at, || ::runtime::system::internal::execute_block(block))
|
||||
}
|
||||
|
||||
fn build_block(&self, parent: &BlockId, timestamp: Timestamp) -> Result<Self::BlockBuilder> {
|
||||
if self.code_at(parent)? != LocalDispatch::native_equivalent() {
|
||||
bail!(ErrorKind::UnknownRuntime);
|
||||
}
|
||||
|
||||
let header = Header {
|
||||
parent_hash: self.block_hash_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))?,
|
||||
number: self.block_number_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))? + 1,
|
||||
state_root: Default::default(),
|
||||
transaction_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
};
|
||||
|
||||
let body = Body {
|
||||
timestamp: timestamp,
|
||||
transactions: Vec::new(),
|
||||
};
|
||||
|
||||
let mut builder = ClientBlockBuilder {
|
||||
parent: *parent,
|
||||
changes: OverlayedChanges::default(),
|
||||
state: self.state_at(parent)?,
|
||||
header,
|
||||
timestamp,
|
||||
transactions: Vec::new(),
|
||||
};
|
||||
|
||||
for inherent in body.inherent_transactions() {
|
||||
builder.execute_transaction(inherent)?;
|
||||
}
|
||||
|
||||
Ok(builder)
|
||||
}
|
||||
}
|
||||
|
||||
/// A polkadot block builder.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientBlockBuilder<S> {
|
||||
parent: BlockId,
|
||||
changes: OverlayedChanges,
|
||||
state: S,
|
||||
header: Header,
|
||||
timestamp: Timestamp,
|
||||
transactions: Vec<UncheckedTransaction>,
|
||||
}
|
||||
|
||||
impl<S: state_machine::Backend> ClientBlockBuilder<S>
|
||||
where S::Error: Into<client::error::Error>
|
||||
{
|
||||
// executes a transaction, inherent or otherwise, without appending to the list
|
||||
fn execute_transaction(&mut self, transaction: UncheckedTransaction) -> Result<()> {
|
||||
if !transaction.is_well_formed() {
|
||||
bail!(ErrorKind::BadlyFormedTransaction(transaction));
|
||||
}
|
||||
|
||||
let mut ext = state_machine::Ext {
|
||||
overlay: &mut self.changes,
|
||||
backend: &self.state,
|
||||
};
|
||||
|
||||
// TODO: avoid clone
|
||||
let header = self.header.clone();
|
||||
let result = ::substrate_executor::with_native_environment(
|
||||
&mut ext,
|
||||
move || runtime::system::internal::execute_transaction(transaction, header),
|
||||
).map_err(Into::into);
|
||||
|
||||
match result {
|
||||
Ok(header) => {
|
||||
ext.overlay.commit_prospective();
|
||||
self.header = header;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
ext.overlay.discard_prospective();
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: state_machine::Backend> BlockBuilder for ClientBlockBuilder<S>
|
||||
where S::Error: Into<client::error::Error>
|
||||
{
|
||||
fn push_transaction(&mut self, transaction: UncheckedTransaction) -> Result<()> {
|
||||
if transaction.transaction.function.is_inherent() {
|
||||
bail!(ErrorKind::PushedInherentTransaction(transaction));
|
||||
} else {
|
||||
self.execute_transaction(transaction.clone())?;
|
||||
self.transactions.push(transaction);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn bake(mut self) -> Block {
|
||||
let mut ext = state_machine::Ext {
|
||||
overlay: &mut self.changes,
|
||||
backend: &self.state,
|
||||
};
|
||||
|
||||
let old_header = self.header;
|
||||
let final_header = ::substrate_executor::with_native_environment(
|
||||
&mut ext,
|
||||
move || runtime::system::internal::finalise_block(old_header)
|
||||
).expect("all inherent transactions pushed; all other transactions executed correctly; qed");
|
||||
|
||||
Block {
|
||||
header: final_header,
|
||||
body: Body {
|
||||
timestamp: self.timestamp,
|
||||
transactions: self.transactions,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use client::in_mem::Backend as InMemory;
|
||||
use polkadot_runtime::genesismap::{additional_storage_with_genesis, GenesisConfig};
|
||||
use substrate_executor::NativeExecutionDispatch;
|
||||
use keyring::Keyring;
|
||||
|
||||
fn validators() -> Vec<AccountId> {
|
||||
vec![
|
||||
Keyring::One.to_raw_public(),
|
||||
Keyring::Two.to_raw_public(),
|
||||
]
|
||||
}
|
||||
|
||||
fn client() -> Client<InMemory, NativeExecutor<LocalDispatch>> {
|
||||
::client::new_in_mem(
|
||||
LocalDispatch::new(),
|
||||
|| {
|
||||
let config = GenesisConfig::new_simple(validators(), 100);
|
||||
|
||||
// override code entry.
|
||||
let mut storage = config.genesis_map();
|
||||
storage.insert(b":code".to_vec(), LocalDispatch::native_equivalent().to_vec());
|
||||
|
||||
let block = ::client::genesis::construct_genesis_block(
|
||||
&config.genesis_map()
|
||||
);
|
||||
storage.extend(additional_storage_with_genesis(&block));
|
||||
(block.header, storage.into_iter().collect())
|
||||
}
|
||||
).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gets_session_and_validator_keys() {
|
||||
let client = client();
|
||||
assert_eq!(client.session_keys(&BlockId::Number(0)).unwrap(), validators());
|
||||
assert_eq!(client.validators(&BlockId::Number(0)).unwrap(), validators());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_block() {
|
||||
let client = client();
|
||||
|
||||
let block_builder = client.build_block(&BlockId::Number(0), 1_000_000).unwrap();
|
||||
let block = block_builder.bake();
|
||||
|
||||
assert_eq!(block.header.number, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_build_block_on_unknown_parent() {
|
||||
let client = client();
|
||||
assert!(client.build_block(&BlockId::Number(100), 1_000_000).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ pub trait ParachainContext {
|
||||
fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>(
|
||||
&self,
|
||||
ingress: I,
|
||||
) -> (parachain::BlockData, polkadot_primitives::Signature);
|
||||
) -> (parachain::BlockData, polkadot_primitives::AccountId, polkadot_primitives::Signature);
|
||||
}
|
||||
|
||||
/// Relay chain context needed to collate.
|
||||
@@ -131,7 +131,7 @@ pub fn collate<'a, R, P>(local_id: ParaId, relay_context: R, para_context: P)
|
||||
P: ParachainContext + 'a,
|
||||
{
|
||||
Box::new(collate_ingress(relay_context).map(move |ingress| {
|
||||
let (block_data, signature) = para_context.produce_candidate(
|
||||
let (block_data, _, signature) = para_context.produce_candidate(
|
||||
ingress.0.iter().flat_map(|&(id, ref msgs)| msgs.iter().cloned().map(move |msg| (id, msg)))
|
||||
);
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ futures = "0.1.17"
|
||||
parking_lot = "0.4"
|
||||
tokio-timer = "0.1.2"
|
||||
ed25519 = { path = "../../substrate/ed25519" }
|
||||
error-chain = "0.11"
|
||||
polkadot-api = { path = "../api" }
|
||||
polkadot-collator = { path = "../collator" }
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
polkadot-statement-table = { path = "../statement-table" }
|
||||
substrate-bft = { path = "../../substrate/bft" }
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Errors that can occur during the consensus process.
|
||||
|
||||
use primitives::block::HeaderHash;
|
||||
|
||||
error_chain! {
|
||||
links {
|
||||
PolkadotApi(::polkadot_api::Error, ::polkadot_api::ErrorKind);
|
||||
Bft(::bft::Error, ::bft::ErrorKind);
|
||||
}
|
||||
|
||||
errors {
|
||||
InvalidDutyRosterLength(expected: usize, got: usize) {
|
||||
description("Duty Roster had invalid length"),
|
||||
display("Invalid duty roster length: expected {}, got {}", expected, got),
|
||||
}
|
||||
ProposalNotForPolkadot {
|
||||
description("Proposal provided not a Polkadot block."),
|
||||
display("Proposal provided not a Polkadot block."),
|
||||
}
|
||||
TimestampInFuture {
|
||||
description("Proposal had timestamp too far in the future."),
|
||||
display("Proposal had timestamp too far in the future."),
|
||||
}
|
||||
WrongParentHash(expected: HeaderHash, got: HeaderHash) {
|
||||
description("Proposal had wrong parent hash."),
|
||||
display("Proposal had wrong parent hash. Expected {:?}, got {:?}", expected, got),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::bft::InputStreamConcluded> for Error {
|
||||
fn from(err: ::bft::InputStreamConcluded) -> Self {
|
||||
::bft::Error::from(err).into()
|
||||
}
|
||||
}
|
||||
+343
-30
@@ -29,31 +29,73 @@
|
||||
//!
|
||||
//! Groups themselves may be compromised by malicious authorities.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use codec::Slicable;
|
||||
use table::Table;
|
||||
use table::generic::Statement as GenericStatement;
|
||||
use polkadot_primitives::Hash;
|
||||
use polkadot_primitives::parachain::{Id as ParaId, CandidateReceipt};
|
||||
use primitives::block::Block as SubstrateBlock;
|
||||
use primitives::AuthorityId;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
extern crate futures;
|
||||
extern crate ed25519;
|
||||
extern crate parking_lot;
|
||||
extern crate tokio_timer;
|
||||
extern crate polkadot_api;
|
||||
extern crate polkadot_collator as collator;
|
||||
extern crate polkadot_statement_table as table;
|
||||
extern crate polkadot_primitives;
|
||||
extern crate substrate_bft as bft;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_primitives as primitives;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use codec::Slicable;
|
||||
use table::{Table, Context as TableContextTrait};
|
||||
use table::generic::Statement as GenericStatement;
|
||||
use polkadot_api::{PolkadotApi, BlockBuilder};
|
||||
use polkadot_primitives::{Hash, Timestamp};
|
||||
use polkadot_primitives::block::Block as PolkadotBlock;
|
||||
use polkadot_primitives::parachain::{Id as ParaId, DutyRoster, BlockData, Extrinsic, CandidateReceipt};
|
||||
use primitives::block::{Block as SubstrateBlock, Header as SubstrateHeader, HeaderHash, Id as BlockId};
|
||||
use primitives::AuthorityId;
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::future;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub use self::error::{ErrorKind, Error};
|
||||
|
||||
mod error;
|
||||
|
||||
/// A handle to a statement table router.
|
||||
pub trait TableRouter {
|
||||
/// Errors when fetching data from the network.
|
||||
type Error;
|
||||
/// Future that resolves when candidate data is fetched.
|
||||
type FetchCandidate: IntoFuture<Item=BlockData,Error=Self::Error>;
|
||||
/// Future that resolves when extrinsic candidate data is fetched.
|
||||
type FetchExtrinsic: IntoFuture<Item=Extrinsic,Error=Self::Error>;
|
||||
|
||||
/// Note local candidate data.
|
||||
fn local_candidate_data(&self, block_data: BlockData, extrinsic: Extrinsic);
|
||||
|
||||
/// Fetch block data for a specific candidate.
|
||||
fn fetch_block_data(&self, candidate: &CandidateReceipt) -> Self::FetchCandidate;
|
||||
|
||||
/// Fetch extrinsic data for a specific candidate.
|
||||
fn fetch_extrinsic_data(&self, candidate: &CandidateReceipt) -> Self::FetchExtrinsic;
|
||||
}
|
||||
|
||||
/// A long-lived network which can create statement table routing instances.
|
||||
pub trait Network {
|
||||
/// The table router type. This should handle importing of any statements,
|
||||
/// routing statements to peers, and driving completion of any `StatementProducers`.
|
||||
type TableRouter: TableRouter;
|
||||
|
||||
/// Instantiate a table router using the given shared table.
|
||||
fn table_router(&self, table: Arc<SharedTable>) -> Self::TableRouter;
|
||||
}
|
||||
|
||||
/// Information about a specific group.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct GroupInfo {
|
||||
/// Authorities meant to check validity of candidates.
|
||||
pub validity_guarantors: HashSet<AuthorityId>,
|
||||
@@ -89,6 +131,10 @@ impl table::Context for TableContext {
|
||||
}
|
||||
|
||||
impl TableContext {
|
||||
fn local_id(&self) -> AuthorityId {
|
||||
self.key.public().0
|
||||
}
|
||||
|
||||
fn sign_statement(&self, statement: table::Statement) -> table::SignedStatement {
|
||||
let signature = sign_table_statement(&statement, &self.key, &self.parent_hash);
|
||||
let local_id = self.key.public().0;
|
||||
@@ -124,16 +170,120 @@ pub fn sign_table_statement(statement: &table::Statement, key: &ed25519::Pair, p
|
||||
struct SharedTableInner {
|
||||
table: Table<TableContext>,
|
||||
proposed_digest: Option<Hash>,
|
||||
checked_validity: HashSet<Hash>,
|
||||
checked_availability: HashSet<Hash>,
|
||||
}
|
||||
|
||||
impl SharedTableInner {
|
||||
fn import_statement(
|
||||
// Import a single statement. Provide a handle to a table router.
|
||||
fn import_statement<R: TableRouter>(
|
||||
&mut self,
|
||||
context: &TableContext,
|
||||
statement: ::table::SignedStatement,
|
||||
router: &R,
|
||||
statement: table::SignedStatement,
|
||||
received_from: Option<AuthorityId>,
|
||||
) -> Option<table::Summary> {
|
||||
self.table.import_statement(context, statement, received_from)
|
||||
) -> StatementProducer<<R::FetchCandidate as IntoFuture>::Future, <R::FetchExtrinsic as IntoFuture>::Future> {
|
||||
let mut producer = StatementProducer {
|
||||
fetch_block_data: None,
|
||||
fetch_extrinsic: None,
|
||||
produced_statements: Default::default(),
|
||||
_key: context.key.clone(),
|
||||
};
|
||||
|
||||
let summary = match self.table.import_statement(context, statement, received_from) {
|
||||
Some(summary) => summary,
|
||||
None => return producer,
|
||||
};
|
||||
|
||||
let local_id = context.local_id();
|
||||
let is_validity_member = context.is_member_of(&local_id, &summary.group_id);
|
||||
let is_availability_member =
|
||||
context.is_availability_guarantor_of(&local_id, &summary.group_id);
|
||||
|
||||
let digest = &summary.candidate;
|
||||
|
||||
// TODO: consider a strategy based on the number of candidate votes as well.
|
||||
// only check validity if this wasn't locally proposed.
|
||||
let checking_validity = is_validity_member
|
||||
&& self.proposed_digest.as_ref().map_or(true, |d| d != digest)
|
||||
&& self.checked_validity.insert(digest.clone());
|
||||
|
||||
let checking_availability = is_availability_member && self.checked_availability.insert(digest.clone());
|
||||
|
||||
if checking_validity || checking_availability {
|
||||
match self.table.get_candidate(&digest) {
|
||||
None => {} // TODO: handle table inconsistency somehow?
|
||||
Some(candidate) => {
|
||||
if checking_validity {
|
||||
producer.fetch_block_data = Some(router.fetch_block_data(candidate).into_future().fuse());
|
||||
}
|
||||
|
||||
if checking_availability {
|
||||
producer.fetch_extrinsic = Some(router.fetch_extrinsic_data(candidate).into_future().fuse());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
producer
|
||||
}
|
||||
}
|
||||
|
||||
/// Produced statements about a specific candidate.
|
||||
/// Both may be `None`.
|
||||
#[derive(Default)]
|
||||
pub struct ProducedStatements {
|
||||
/// A statement about the validity of the candidate.
|
||||
pub validity: Option<table::Statement>,
|
||||
/// A statement about the availability of the candidate.
|
||||
pub availability: Option<table::Statement>,
|
||||
}
|
||||
|
||||
/// Future that produces statements about a specific candidate.
|
||||
pub struct StatementProducer<D: Future, E: Future> {
|
||||
fetch_block_data: Option<future::Fuse<D>>,
|
||||
fetch_extrinsic: Option<future::Fuse<E>>,
|
||||
produced_statements: ProducedStatements,
|
||||
_key: Arc<ed25519::Pair>,
|
||||
}
|
||||
|
||||
impl<D, E, Err> Future for StatementProducer<D, E>
|
||||
where
|
||||
D: Future<Item=BlockData,Error=Err>,
|
||||
E: Future<Item=Extrinsic,Error=Err>,
|
||||
{
|
||||
type Item = ProducedStatements;
|
||||
type Error = Err;
|
||||
|
||||
fn poll(&mut self) -> Poll<ProducedStatements, Err> {
|
||||
let mut done = true;
|
||||
if let Some(ref mut fetch_block_data) = self.fetch_block_data {
|
||||
match fetch_block_data.poll()? {
|
||||
Async::Ready(_block_data) => {
|
||||
// TODO [PoC-2] : validate block data here and make statement.
|
||||
},
|
||||
Async::NotReady => {
|
||||
done = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut fetch_extrinsic) = self.fetch_extrinsic {
|
||||
match fetch_extrinsic.poll()? {
|
||||
Async::Ready(_extrinsic) => {
|
||||
// TODO [PoC-2]: guarantee availability of data and make statment.
|
||||
}
|
||||
Async::NotReady => {
|
||||
done = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if done {
|
||||
Ok(Async::Ready(::std::mem::replace(&mut self.produced_statements, Default::default())))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +297,7 @@ impl Clone for SharedTable {
|
||||
fn clone(&self) -> Self {
|
||||
SharedTable {
|
||||
context: self.context.clone(),
|
||||
inner: self.inner.clone()
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,24 +313,34 @@ impl SharedTable {
|
||||
inner: Arc::new(Mutex::new(SharedTableInner {
|
||||
table: Table::default(),
|
||||
proposed_digest: None,
|
||||
checked_validity: HashSet::new(),
|
||||
checked_availability: HashSet::new(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Import a single statement.
|
||||
pub fn import_statement(
|
||||
/// Get group info.
|
||||
pub fn group_info(&self) -> &HashMap<ParaId, GroupInfo> {
|
||||
&self.context.groups
|
||||
}
|
||||
|
||||
/// Import a single statement. Provide a handle to a table router
|
||||
/// for dispatching any other requests which come up.
|
||||
pub fn import_statement<R: TableRouter>(
|
||||
&self,
|
||||
router: &R,
|
||||
statement: table::SignedStatement,
|
||||
received_from: Option<AuthorityId>,
|
||||
) -> Option<table::Summary> {
|
||||
self.inner.lock().import_statement(&*self.context, statement, received_from)
|
||||
) -> StatementProducer<<R::FetchCandidate as IntoFuture>::Future, <R::FetchExtrinsic as IntoFuture>::Future> {
|
||||
self.inner.lock().import_statement(&*self.context, router, statement, received_from)
|
||||
}
|
||||
|
||||
/// Sign and import a local statement.
|
||||
pub fn sign_and_import(
|
||||
pub fn sign_and_import<R: TableRouter>(
|
||||
&self,
|
||||
router: &R,
|
||||
statement: table::Statement,
|
||||
) -> Option<table::Summary> {
|
||||
) -> StatementProducer<<R::FetchCandidate as IntoFuture>::Future, <R::FetchExtrinsic as IntoFuture>::Future> {
|
||||
let proposed_digest = match statement {
|
||||
GenericStatement::Candidate(ref c) => Some(c.hash()),
|
||||
_ => None,
|
||||
@@ -193,21 +353,25 @@ impl SharedTable {
|
||||
inner.proposed_digest = proposed_digest;
|
||||
}
|
||||
|
||||
inner.import_statement(&*self.context, signed_statement, None)
|
||||
inner.import_statement(&*self.context, router, signed_statement, None)
|
||||
}
|
||||
|
||||
/// Import many statements at once.
|
||||
///
|
||||
/// Provide an iterator yielding pairs of (statement, received_from).
|
||||
pub fn import_statements<I, U>(&self, iterable: I) -> U
|
||||
pub fn import_statements<R, I, U>(&self, router: &R, iterable: I) -> U
|
||||
where
|
||||
R: TableRouter,
|
||||
I: IntoIterator<Item=(table::SignedStatement, Option<AuthorityId>)>,
|
||||
U: ::std::iter::FromIterator<table::Summary>,
|
||||
U: ::std::iter::FromIterator<StatementProducer<
|
||||
<R::FetchCandidate as IntoFuture>::Future,
|
||||
<R::FetchExtrinsic as IntoFuture>::Future>
|
||||
>,
|
||||
{
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
iterable.into_iter().filter_map(move |(statement, received_from)| {
|
||||
inner.import_statement(&*self.context, statement, received_from)
|
||||
iterable.into_iter().map(move |(statement, received_from)| {
|
||||
inner.import_statement(&*self.context, router, statement, received_from)
|
||||
}).collect()
|
||||
}
|
||||
|
||||
@@ -241,3 +405,152 @@ impl SharedTable {
|
||||
self.inner.lock().proposed_digest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn make_group_info(roster: DutyRoster, authorities: &[AuthorityId]) -> Result<HashMap<ParaId, GroupInfo>, Error> {
|
||||
if roster.validator_duty.len() != authorities.len() {
|
||||
bail!(ErrorKind::InvalidDutyRosterLength(authorities.len(), roster.validator_duty.len()))
|
||||
}
|
||||
|
||||
if roster.guarantor_duty.len() != authorities.len() {
|
||||
bail!(ErrorKind::InvalidDutyRosterLength(authorities.len(), roster.guarantor_duty.len()))
|
||||
}
|
||||
|
||||
let mut map = HashMap::new();
|
||||
|
||||
let duty_iter = authorities.iter().zip(&roster.validator_duty).zip(&roster.guarantor_duty);
|
||||
for ((authority, v_duty), a_duty) in duty_iter {
|
||||
use polkadot_primitives::parachain::Chain;
|
||||
|
||||
match *v_duty {
|
||||
Chain::Relay => {}, // does nothing for now.
|
||||
Chain::Parachain(ref id) => {
|
||||
map.entry(id.clone()).or_insert_with(GroupInfo::default)
|
||||
.validity_guarantors
|
||||
.insert(authority.clone());
|
||||
}
|
||||
}
|
||||
|
||||
match *a_duty {
|
||||
Chain::Relay => {}, // does nothing for now.
|
||||
Chain::Parachain(ref id) => {
|
||||
map.entry(id.clone()).or_insert_with(GroupInfo::default)
|
||||
.availability_guarantors
|
||||
.insert(authority.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for live_group in map.values_mut() {
|
||||
let validity_len = live_group.validity_guarantors.len();
|
||||
let availability_len = live_group.availability_guarantors.len();
|
||||
|
||||
live_group.needed_validity = validity_len / 2 + validity_len % 2;
|
||||
live_group.needed_availability = availability_len / 2 + availability_len % 2;
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
/// Polkadot proposer factory.
|
||||
pub struct ProposerFactory<C, N> {
|
||||
/// The client instance.
|
||||
pub client: Arc<C>,
|
||||
/// The backing network handle.
|
||||
pub network: N,
|
||||
}
|
||||
|
||||
impl<C: PolkadotApi, N: Network> bft::ProposerFactory for ProposerFactory<C, N> {
|
||||
type Proposer = Proposer<C, N::TableRouter>;
|
||||
type Error = Error;
|
||||
|
||||
fn init(&self, parent_header: &SubstrateHeader, authorities: &[AuthorityId], sign_with: Arc<ed25519::Pair>) -> Result<Self::Proposer, Error> {
|
||||
let parent_hash = parent_header.hash();
|
||||
let duty_roster = self.client.duty_roster(&BlockId::Hash(parent_hash))?;
|
||||
|
||||
let group_info = make_group_info(duty_roster, authorities)?;
|
||||
let table = Arc::new(SharedTable::new(group_info, sign_with, parent_hash));
|
||||
let router = self.network.table_router(table.clone());
|
||||
|
||||
// TODO [PoC-2]: kick off collation process.
|
||||
Ok(Proposer {
|
||||
parent_hash,
|
||||
_table: table,
|
||||
_router: router,
|
||||
client: self.client.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn current_timestamp() -> Timestamp {
|
||||
use std::time;
|
||||
|
||||
time::SystemTime::now().duration_since(time::UNIX_EPOCH)
|
||||
.expect("now always later than unix epoch; qed")
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
/// The Polkadot proposer logic.
|
||||
pub struct Proposer<C, R> {
|
||||
parent_hash: HeaderHash,
|
||||
client: Arc<C>,
|
||||
_table: Arc<SharedTable>,
|
||||
_router: R,
|
||||
}
|
||||
|
||||
impl<C: PolkadotApi, R: TableRouter> bft::Proposer for Proposer<C, R> {
|
||||
type Error = Error;
|
||||
type Create = Result<SubstrateBlock, Error>;
|
||||
type Evaluate = Result<bool, Error>;
|
||||
|
||||
fn propose(&self) -> Result<SubstrateBlock, Error> {
|
||||
// TODO: handle case when current timestamp behind that in state.
|
||||
let polkadot_block = self.client.build_block(
|
||||
&BlockId::Hash(self.parent_hash),
|
||||
current_timestamp()
|
||||
)?.bake();
|
||||
|
||||
// TODO: integrate transaction queue and `push_transaction`s.
|
||||
|
||||
let substrate_block = Slicable::decode(&mut polkadot_block.encode().as_slice())
|
||||
.expect("polkadot blocks defined to serialize to substrate blocks correctly; qed");
|
||||
|
||||
Ok(substrate_block)
|
||||
}
|
||||
|
||||
// TODO: certain kinds of errors here should lead to a misbehavior report.
|
||||
fn evaluate(&self, proposal: &SubstrateBlock) -> Result<bool, Error> {
|
||||
evaluate_proposal(proposal, &*self.client, current_timestamp(), &self.parent_hash)
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_proposal<C: PolkadotApi>(
|
||||
proposal: &SubstrateBlock,
|
||||
client: &C,
|
||||
now: Timestamp,
|
||||
parent_hash: &HeaderHash,
|
||||
) -> Result<bool, Error> {
|
||||
const MAX_TIMESTAMP_DRIFT: Timestamp = 4;
|
||||
|
||||
let encoded = Slicable::encode(proposal);
|
||||
let proposal = PolkadotBlock::decode(&mut &encoded[..])
|
||||
.ok_or_else(|| ErrorKind::ProposalNotForPolkadot)?;
|
||||
|
||||
if proposal.header.parent_hash != *parent_hash {
|
||||
bail!(ErrorKind::WrongParentHash(*parent_hash, proposal.header.parent_hash));
|
||||
}
|
||||
|
||||
// no need to check number because
|
||||
// a) we assume the parent is valid.
|
||||
// b) the runtime checks that `proposal.parent_hash` == `block_hash(proposal.number - 1)`
|
||||
|
||||
let block_timestamp = proposal.body.timestamp;
|
||||
|
||||
// TODO: just defer using `tokio_timer` to delay prepare vote.
|
||||
if block_timestamp > now + MAX_TIMESTAMP_DRIFT {
|
||||
bail!(ErrorKind::TimestampInFuture)
|
||||
}
|
||||
|
||||
// execute the block.
|
||||
client.evaluate_block(&BlockId::Hash(*parent_hash), proposal)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
+116
-88
@@ -44,8 +44,10 @@ mod tests {
|
||||
use polkadot_runtime::runtime::staking::balance;
|
||||
use state_machine::{CodeExecutor, TestExternalities};
|
||||
use primitives::twox_128;
|
||||
use polkadot_primitives::{Hash, Header, BlockNumber, Block, Digest, Transaction,
|
||||
UncheckedTransaction, Function};
|
||||
use polkadot_primitives::{
|
||||
Hash, Header, Body, BlockNumber, Block, Digest, Transaction,
|
||||
UncheckedTransaction, Function, InherentFunction,
|
||||
};
|
||||
use ed25519::{Public, Pair};
|
||||
|
||||
const BLOATY_CODE: &[u8] = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm");
|
||||
@@ -58,76 +60,6 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
fn tx() -> UncheckedTransaction {
|
||||
let transaction = Transaction {
|
||||
signed: Keyring::One.to_raw_public(),
|
||||
nonce: 0,
|
||||
function: Function::StakingTransfer(Keyring::Two.to_raw_public(), 69),
|
||||
};
|
||||
let signature = Keyring::from_raw_public(transaction.signed).unwrap()
|
||||
.sign(&transaction.encode());
|
||||
|
||||
UncheckedTransaction { transaction, signature }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panic_execution_with_foreign_code_gives_error() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let mut t: TestExternalities = map![
|
||||
twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0]
|
||||
];
|
||||
|
||||
let r = Executor::new().call(&mut t, BLOATY_CODE, "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx()));
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panic_execution_with_native_equivalent_code_gives_error() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let mut t: TestExternalities = map![
|
||||
twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0]
|
||||
];
|
||||
|
||||
let r = Executor::new().call(&mut t, COMPACT_CODE, "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx()));
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_execution_with_native_equivalent_code_gives_ok() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let two = Keyring::Two.to_raw_public();
|
||||
|
||||
let mut t: TestExternalities = map![
|
||||
twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0]
|
||||
];
|
||||
|
||||
let r = Executor::new().call(&mut t, COMPACT_CODE, "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx()));
|
||||
assert!(r.is_ok());
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
assert_eq!(balance(&one), 42);
|
||||
assert_eq!(balance(&two), 69);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_execution_with_foreign_code_gives_ok() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let two = Keyring::Two.to_raw_public();
|
||||
|
||||
let mut t: TestExternalities = map![
|
||||
twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0]
|
||||
];
|
||||
|
||||
let r = Executor::new().call(&mut t, BLOATY_CODE, "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx()));
|
||||
assert!(r.is_ok());
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
assert_eq!(balance(&one), 42);
|
||||
assert_eq!(balance(&two), 69);
|
||||
});
|
||||
}
|
||||
|
||||
fn new_test_ext() -> TestExternalities {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let two = Keyring::Two.to_raw_public();
|
||||
@@ -152,40 +84,77 @@ mod tests {
|
||||
]
|
||||
}
|
||||
|
||||
fn construct_block(number: BlockNumber, parent_hash: Hash, state_root: Hash, txs: Vec<Transaction>) -> (Vec<u8>, Hash) {
|
||||
fn set_timestamp(timestamp: u64) -> UncheckedTransaction {
|
||||
UncheckedTransaction::inherent(InherentFunction::TimestampSet(timestamp))
|
||||
}
|
||||
|
||||
fn tx() -> UncheckedTransaction {
|
||||
let transaction = Transaction {
|
||||
signed: Keyring::One.to_raw_public(),
|
||||
nonce: 0,
|
||||
function: Function::StakingTransfer(Keyring::Two.to_raw_public(), 69),
|
||||
};
|
||||
let signature = Keyring::from_raw_public(transaction.signed).unwrap()
|
||||
.sign(&transaction.encode());
|
||||
|
||||
UncheckedTransaction { transaction, signature }
|
||||
}
|
||||
|
||||
fn execute_tx_on<C>(executor: C, ext: &mut TestExternalities, code: &[u8], tx: UncheckedTransaction, header: Header)
|
||||
-> Result<Vec<u8>, C::Error>
|
||||
where C: CodeExecutor
|
||||
{
|
||||
let next_header = executor.call(ext, code, "execute_transaction", &vec![].and(&header).and(&set_timestamp(100_000))).unwrap();
|
||||
let next_input = next_header.and(&tx);
|
||||
|
||||
executor.call(ext, code, "execute_transaction", &next_input[..])
|
||||
}
|
||||
|
||||
fn construct_block(number: BlockNumber, parent_hash: Hash, state_root: Hash, timestamp: u64, txs: Vec<Transaction>) -> (Vec<u8>, Hash) {
|
||||
use triehash::ordered_trie_root;
|
||||
|
||||
|
||||
let transactions = txs.into_iter().map(|transaction| {
|
||||
let signature = Pair::from(Keyring::from_public(Public::from_raw(transaction.signed)).unwrap())
|
||||
.sign(&transaction.encode());
|
||||
|
||||
UncheckedTransaction { transaction, signature }
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
let transaction_root = ordered_trie_root(transactions.iter().map(Slicable::encode)).0.into();
|
||||
}).collect();
|
||||
|
||||
let header = Header {
|
||||
parent_hash,
|
||||
number,
|
||||
state_root,
|
||||
transaction_root,
|
||||
transaction_root: Default::default(),
|
||||
digest: Digest { logs: vec![], },
|
||||
};
|
||||
let hash = header.blake2_256();
|
||||
|
||||
(Block { header, transactions }.encode(), hash.into())
|
||||
let mut block = Block {
|
||||
header,
|
||||
body: Body { timestamp, transactions },
|
||||
};
|
||||
|
||||
let transaction_root = ordered_trie_root(block.all_transactions().map(|tx| Slicable::encode(&tx))).0.into();
|
||||
block.header.transaction_root = transaction_root;
|
||||
|
||||
let hash = block.header.blake2_256();
|
||||
|
||||
(block.encode(), hash.into())
|
||||
}
|
||||
|
||||
fn block1() -> (Vec<u8>, Hash) {
|
||||
construct_block(
|
||||
1,
|
||||
[69u8; 32].into(),
|
||||
hex!("2481853da20b9f4322f34650fea5f240dcbfb266d02db94bfa0153c31f4a29db").into(),
|
||||
vec![Transaction {
|
||||
signed: Keyring::One.to_raw_public(),
|
||||
nonce: 0,
|
||||
function: Function::StakingTransfer(Keyring::Two.to_raw_public(), 69),
|
||||
}]
|
||||
hex!("3df569d47a0d7f4a448486f04fba4eea3e9dfca001319c609f88b3a67b0dd1ea").into(),
|
||||
100_000,
|
||||
vec![
|
||||
Transaction {
|
||||
signed: Keyring::One.to_raw_public(),
|
||||
nonce: 0,
|
||||
function: Function::StakingTransfer(Keyring::Two.to_raw_public(), 69),
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -193,7 +162,8 @@ mod tests {
|
||||
construct_block(
|
||||
2,
|
||||
block1().1,
|
||||
hex!("1feb4d3a2e587079e6ce1685fa79994efd995e33cb289d39cded67aac1bb46a9").into(),
|
||||
hex!("c8776c92e8012bf6b3f206448eda3f00bca26d77f220f4714c81cbc92a30e1e2").into(),
|
||||
200_000,
|
||||
vec![
|
||||
Transaction {
|
||||
signed: Keyring::Two.to_raw_public(),
|
||||
@@ -209,6 +179,64 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panic_execution_with_foreign_code_gives_error() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let mut t: TestExternalities = map![
|
||||
twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0]
|
||||
];
|
||||
|
||||
let r = execute_tx_on(Executor::new(), &mut t, BLOATY_CODE, tx(), Header::from_block_number(1));
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panic_execution_with_native_equivalent_code_gives_error() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let mut t: TestExternalities = map![
|
||||
twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0]
|
||||
];
|
||||
|
||||
let r = execute_tx_on(Executor::new(), &mut t, COMPACT_CODE, tx(), Header::from_block_number(1));
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_execution_with_native_equivalent_code_gives_ok() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let two = Keyring::Two.to_raw_public();
|
||||
|
||||
let mut t: TestExternalities = map![
|
||||
twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0]
|
||||
];
|
||||
|
||||
let r = execute_tx_on(Executor::new(), &mut t, COMPACT_CODE, tx(), Header::from_block_number(1));
|
||||
assert!(r.is_ok());
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
assert_eq!(balance(&one), 42);
|
||||
assert_eq!(balance(&two), 69);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_execution_with_foreign_code_gives_ok() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let two = Keyring::Two.to_raw_public();
|
||||
|
||||
let mut t: TestExternalities = map![
|
||||
twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0]
|
||||
];
|
||||
|
||||
let r = execute_tx_on(Executor::new(), &mut t, BLOATY_CODE, tx(), Header::from_block_number(1));
|
||||
assert!(r.is_ok());
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
assert_eq!(balance(&one), 42);
|
||||
assert_eq!(balance(&two), 69);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_native_block_import_works() {
|
||||
let mut t = new_test_ext();
|
||||
@@ -255,7 +283,7 @@ mod tests {
|
||||
];
|
||||
|
||||
let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm");
|
||||
let r = WasmExecutor.call(&mut t, &foreign_code[..], "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx()));
|
||||
let r = execute_tx_on(WasmExecutor, &mut t, &foreign_code[..], tx(), Header::from_block_number(1));
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
@@ -269,7 +297,7 @@ mod tests {
|
||||
];
|
||||
|
||||
let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm");
|
||||
let r = WasmExecutor.call(&mut t, &foreign_code[..], "execute_transaction", &vec![].and(&Header::from_block_number(1u64)).and(&tx()));
|
||||
let r = execute_tx_on(WasmExecutor, &mut t, &foreign_code[..], tx(), Header::from_block_number(1));
|
||||
assert!(r.is_ok());
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
|
||||
@@ -21,7 +21,7 @@ use primitives::bytes;
|
||||
use primitives::H256;
|
||||
use rstd::vec::Vec;
|
||||
use codec::{Input, Slicable};
|
||||
use transaction::UncheckedTransaction;
|
||||
use transaction::{UncheckedTransaction, Function, InherentFunction};
|
||||
|
||||
pub use primitives::block::Id;
|
||||
|
||||
@@ -69,8 +69,65 @@ impl Slicable for Digest {
|
||||
}
|
||||
}
|
||||
|
||||
/// The block "body": A bunch of transactions.
|
||||
pub type Body = Vec<UncheckedTransaction>;
|
||||
/// Iterator over all inherent transactions.
|
||||
pub struct InherentTransactions<'a> {
|
||||
number: u64,
|
||||
body: &'a Body,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for InherentTransactions<'a> {
|
||||
type Item = UncheckedTransaction;
|
||||
|
||||
fn next(&mut self) -> Option<UncheckedTransaction> {
|
||||
if self.number == InherentFunction::count() {
|
||||
return None
|
||||
}
|
||||
|
||||
self.number += 1;
|
||||
|
||||
let function = match self.number {
|
||||
1 => Some(InherentFunction::TimestampSet(self.body.timestamp)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
function.map(UncheckedTransaction::inherent)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for an iterator over all transactions in a block.
|
||||
pub type AllTransactions<'a> = ::rstd::iter::Chain<
|
||||
InherentTransactions<'a>,
|
||||
::rstd::iter::Cloned<::rstd::slice::Iter<'a, UncheckedTransaction>>,
|
||||
>;
|
||||
|
||||
/// The block body. Contains timestamp and transactions.
|
||||
// TODO: add candidates update as well.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
|
||||
#[cfg_attr(feature = "std", serde(deny_unknown_fields))]
|
||||
pub struct Body {
|
||||
/// The timestamp of the block.
|
||||
pub timestamp: u64,
|
||||
/// The transactions in the block.
|
||||
pub transactions: Vec<UncheckedTransaction>,
|
||||
}
|
||||
|
||||
impl Body {
|
||||
/// Get an iterator over all inherent transactions of the body.
|
||||
pub fn inherent_transactions(&self) -> InherentTransactions {
|
||||
InherentTransactions {
|
||||
number: 0,
|
||||
body: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an iterator over all transactions in a block.
|
||||
pub fn all_transactions(&self) -> AllTransactions {
|
||||
self.inherent_transactions().chain(self.transactions.iter().cloned())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A Polkadot relay chain block.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
@@ -78,21 +135,65 @@ pub type Body = Vec<UncheckedTransaction>;
|
||||
pub struct Block {
|
||||
/// The block header.
|
||||
pub header: Header,
|
||||
/// All relay-chain transactions.
|
||||
pub transactions: Body,
|
||||
/// The block body.
|
||||
pub body: Body,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
/// Get an iterator over all inherent transactions of the body.
|
||||
pub fn inherent_transactions(&self) -> InherentTransactions {
|
||||
self.body.inherent_transactions()
|
||||
}
|
||||
|
||||
/// Get an iterator over all transactions in a block.
|
||||
pub fn all_transactions(&self) -> AllTransactions {
|
||||
self.body.all_transactions()
|
||||
}
|
||||
}
|
||||
|
||||
impl Slicable for Block {
|
||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||
let (header, transactions) = try_opt!(Slicable::decode(input));
|
||||
Some(Block { header, transactions })
|
||||
let header = try_opt!(Slicable::decode(input));
|
||||
|
||||
let transactions_len: u32 = try_opt!(Slicable::decode(input));
|
||||
let regular_transactions_len = try_opt!(transactions_len.checked_sub(InherentFunction::count() as u32));
|
||||
|
||||
let timestamp_tx = try_opt!(UncheckedTransaction::decode(input));
|
||||
let timestamp = match timestamp_tx.transaction.function {
|
||||
Function::Inherent(InherentFunction::TimestampSet(ref t)) if timestamp_tx.is_well_formed() => { t.clone() }
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let transactions: Option<Vec<_>> = (0..regular_transactions_len)
|
||||
.map(|_| UncheckedTransaction::decode(input))
|
||||
.filter(|tx| tx.as_ref().map_or(true, |tx| tx.is_well_formed()))
|
||||
.collect();
|
||||
|
||||
let body = Body {
|
||||
timestamp,
|
||||
transactions: try_opt!(transactions),
|
||||
};
|
||||
|
||||
Some(Block { header, body })
|
||||
}
|
||||
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
let mut v = Vec::new();
|
||||
|
||||
v.extend(self.header.encode());
|
||||
v.extend(self.transactions.encode());
|
||||
|
||||
// encode inherent transactions before non-inherent.
|
||||
let transactions_len = self.body.transactions.len() as u64 + InherentFunction::count();
|
||||
(transactions_len as u32).using_encoded(|s| v.extend(s));
|
||||
|
||||
let timestamp_set_tx = UncheckedTransaction::inherent(
|
||||
InherentFunction::TimestampSet(self.body.timestamp)
|
||||
);
|
||||
|
||||
v.extend(timestamp_set_tx.encode());
|
||||
for non_inherent_transaction in &self.body.transactions {
|
||||
v.extend(non_inherent_transaction.encode());
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
@@ -186,4 +287,89 @@ mod tests {
|
||||
let v = header.encode();
|
||||
assert_eq!(Header::decode(&mut &v[..]).unwrap(), header);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_encoding_round_trip() {
|
||||
let mut block = Block {
|
||||
header: Header::from_block_number(1),
|
||||
body: Body {
|
||||
timestamp: 100_000_000,
|
||||
transactions: Vec::new(),
|
||||
}
|
||||
};
|
||||
|
||||
let raw = block.encode();
|
||||
let decoded = Block::decode(&mut &raw[..]).unwrap();
|
||||
|
||||
assert_eq!(block, decoded);
|
||||
|
||||
block.body.transactions.push(UncheckedTransaction {
|
||||
transaction: ::transaction::Transaction {
|
||||
function: Function::StakingStake,
|
||||
signed: Default::default(),
|
||||
nonce: 10101,
|
||||
},
|
||||
signature: Default::default(),
|
||||
});
|
||||
|
||||
let raw = block.encode();
|
||||
let decoded = Block::decode(&mut &raw[..]).unwrap();
|
||||
|
||||
assert_eq!(block, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_encoding_substrate_round_trip() {
|
||||
let mut block = Block {
|
||||
header: Header::from_block_number(1),
|
||||
body: Body {
|
||||
timestamp: 100_000_000,
|
||||
transactions: Vec::new(),
|
||||
}
|
||||
};
|
||||
|
||||
block.body.transactions.push(UncheckedTransaction {
|
||||
transaction: ::transaction::Transaction {
|
||||
function: Function::StakingStake,
|
||||
signed: Default::default(),
|
||||
nonce: 10101,
|
||||
},
|
||||
signature: Default::default(),
|
||||
});
|
||||
|
||||
let raw = block.encode();
|
||||
let decoded_substrate = ::primitives::block::Block::decode(&mut &raw[..]).unwrap();
|
||||
let encoded_substrate = decoded_substrate.encode();
|
||||
let decoded = Block::decode(&mut &encoded_substrate[..]).unwrap();
|
||||
|
||||
assert_eq!(block, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_body_without_inherents_fails() {
|
||||
let substrate_blank = ::primitives::block::Block {
|
||||
header: ::primitives::block::Header::from_block_number(1),
|
||||
transactions: Vec::new(),
|
||||
};
|
||||
|
||||
let encoded_substrate = substrate_blank.encode();
|
||||
assert!(Block::decode(&mut &encoded_substrate[..]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inherent_transactions_iter_contains_all_inherent() {
|
||||
let block = Block {
|
||||
header: Header::from_block_number(1),
|
||||
body: Body {
|
||||
timestamp: 10101,
|
||||
transactions: Vec::new(),
|
||||
}
|
||||
};
|
||||
|
||||
let mut iter = block.inherent_transactions();
|
||||
|
||||
assert_eq!(InherentFunction::count(), 1); // following depends on this assertion.
|
||||
assert_eq!(iter.next().unwrap(), UncheckedTransaction::inherent(InherentFunction::TimestampSet(10101)));
|
||||
assert!(iter.next().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ pub mod validator;
|
||||
pub mod block;
|
||||
pub mod transaction;
|
||||
|
||||
pub use self::block::{Header, Block, Log, Digest};
|
||||
pub use self::block::{Header, Body, Block, Log, Digest};
|
||||
pub use self::block::Number as BlockNumber;
|
||||
pub use self::transaction::{Transaction, UncheckedTransaction, Function, Proposal};
|
||||
pub use self::transaction::{Transaction, UncheckedTransaction, Function, InherentFunction, Proposal};
|
||||
|
||||
/// Virtual account ID that represents the idea of a dispatch/statement being signed by everybody
|
||||
/// (who matters). Essentially this means that a majority of validators have decided it is
|
||||
@@ -75,5 +75,8 @@ pub type TxOrder = u64;
|
||||
/// A hash of some data used by the relay chain.
|
||||
pub type Hash = primitives::H256;
|
||||
|
||||
/// Alias to 520-bit hash when used in the context of a signature on the relay chain.
|
||||
/// Alias to 512-bit hash when used in the context of a signature on the relay chain.
|
||||
pub type Signature = primitives::hash::H512;
|
||||
|
||||
/// A timestamp: seconds since the unix epoch.
|
||||
pub type Timestamp = u64;
|
||||
|
||||
@@ -121,6 +121,13 @@ impl Slicable for DutyRoster {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extrinsic data for a parachain.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
|
||||
#[cfg_attr(feature = "std", serde(deny_unknown_fields))]
|
||||
pub struct Extrinsic;
|
||||
|
||||
/// Candidate parachain block.
|
||||
///
|
||||
/// https://github.com/w3f/polkadot-spec/blob/master/spec.md#candidate-para-chain-block
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
use rstd::vec::Vec;
|
||||
use codec::{Input, Slicable};
|
||||
use ::Signature;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::fmt;
|
||||
@@ -184,12 +185,35 @@ impl FunctionId {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inherent functions on the runtime.
|
||||
/// These must be called each block by the `EVERYBODY` account.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub enum InherentFunction {
|
||||
/// Set the timestamp.
|
||||
TimestampSet(u64),
|
||||
}
|
||||
|
||||
impl InherentFunction {
|
||||
/// Get the number of inherent functions.
|
||||
pub fn count() -> u64 {
|
||||
1
|
||||
}
|
||||
|
||||
/// Get the index.
|
||||
pub fn index(&self) -> u64 {
|
||||
match *self {
|
||||
InherentFunction::TimestampSet(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions on the runtime.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub enum Function {
|
||||
/// Set the timestamp.
|
||||
TimestampSet(u64),
|
||||
/// An inherent function.
|
||||
Inherent(InherentFunction),
|
||||
/// Set temporary session key as a validator.
|
||||
SessionSetKey(::SessionKey),
|
||||
/// Staking subsystem: begin staking.
|
||||
@@ -204,12 +228,34 @@ pub enum Function {
|
||||
GovernanceApprove(BlockNumber),
|
||||
}
|
||||
|
||||
impl Function {
|
||||
/// The number of inherent functions.
|
||||
pub fn inherent_functions() -> u64 { InherentFunction::count() }
|
||||
|
||||
/// Whether this function is "inherent": that it must be part of every
|
||||
/// block at the given index and no other.
|
||||
///
|
||||
/// Transactions containing inherent functions should not be signed.
|
||||
pub fn is_inherent(&self) -> bool {
|
||||
self.inherent_index().is_some()
|
||||
}
|
||||
|
||||
/// If this function is inherent, returns the index it should occupy
|
||||
/// in the block. Otherwise returns `None`.
|
||||
pub fn inherent_index(&self) -> Option<u64> {
|
||||
match *self {
|
||||
Function::Inherent(ref inner) => Some(inner.index()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Slicable for Function {
|
||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||
let id = try_opt!(u8::decode(input).and_then(FunctionId::from_u8));
|
||||
Some(match id {
|
||||
FunctionId::TimestampSet =>
|
||||
Function::TimestampSet(try_opt!(Slicable::decode(input))),
|
||||
Function::Inherent(InherentFunction::TimestampSet(try_opt!(Slicable::decode(input)))),
|
||||
FunctionId::SessionSetKey =>
|
||||
Function::SessionSetKey(try_opt!(Slicable::decode(input))),
|
||||
FunctionId::StakingStake => Function::StakingStake,
|
||||
@@ -230,7 +276,7 @@ impl Slicable for Function {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
let mut v = Vec::new();
|
||||
match *self {
|
||||
Function::TimestampSet(ref data) => {
|
||||
Function::Inherent(InherentFunction::TimestampSet(ref data)) => {
|
||||
(FunctionId::TimestampSet as u8).using_encoded(|s| v.extend(s));
|
||||
data.using_encoded(|s| v.extend(s));
|
||||
}
|
||||
@@ -308,7 +354,33 @@ pub struct UncheckedTransaction {
|
||||
/// The actual transaction information.
|
||||
pub transaction: Transaction,
|
||||
/// The signature; should be an Ed25519 signature applied to the serialised `transaction` field.
|
||||
pub signature: super::Signature,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl UncheckedTransaction {
|
||||
/// Whether the transaction is well-formed. In particular checks that
|
||||
/// inherent transactions have the correct signed and signature fields.
|
||||
///
|
||||
/// Does not check signatures on other transactions.
|
||||
pub fn is_well_formed(&self) -> bool {
|
||||
if self.transaction.function.is_inherent() {
|
||||
self.transaction.signed == ::EVERYBODY && self.signature == Signature::zero()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new inherent-style transaction from the given function.
|
||||
pub fn inherent(function: InherentFunction) -> Self {
|
||||
UncheckedTransaction {
|
||||
transaction: Transaction {
|
||||
function: Function::Inherent(function),
|
||||
nonce: 0,
|
||||
signed: ::EVERYBODY
|
||||
},
|
||||
signature: Signature::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Slicable for UncheckedTransaction {
|
||||
@@ -372,7 +444,7 @@ mod tests {
|
||||
transaction: Transaction {
|
||||
signed: [1; 32],
|
||||
nonce: 999u64,
|
||||
function: Function::TimestampSet(135135),
|
||||
function: Function::Inherent(InherentFunction::TimestampSet(135135)),
|
||||
},
|
||||
signature: primitives::hash::H512([0; 64]),
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use runtime::{system, parachains, consensus, session};
|
||||
use runtime::{system, parachains, consensus, session, timestamp};
|
||||
|
||||
impl_stubs!(
|
||||
execute_block => |block| system::internal::execute_block(block),
|
||||
@@ -23,5 +23,6 @@ impl_stubs!(
|
||||
validator_count => |()| session::validator_count(),
|
||||
validators => |()| session::validators(),
|
||||
authorities => |()| consensus::authorities(),
|
||||
duty_roster => |()| parachains::calculate_duty_roster()
|
||||
duty_roster => |()| parachains::calculate_duty_roster(),
|
||||
get_timestamp => |()| timestamp::get()
|
||||
);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
//! Environment API: Allows certain information to be accessed throughout the runtime.
|
||||
|
||||
use rstd::boxed::Box;
|
||||
use rstd::mem;
|
||||
use rstd::cell::RefCell;
|
||||
use rstd::rc::Rc;
|
||||
|
||||
@@ -32,6 +31,8 @@ pub struct Environment {
|
||||
pub parent_hash: Hash,
|
||||
/// The current block digest.
|
||||
pub digest: Digest,
|
||||
/// The current transaction index
|
||||
pub transaction_index: u64,
|
||||
}
|
||||
|
||||
/// Do something with the environment and return its value. Keep the function short.
|
||||
@@ -52,7 +53,7 @@ fn env() -> Rc<RefCell<Environment>> {
|
||||
let singleton: Rc<RefCell<Environment>> = Rc::new(RefCell::new(Default::default()));
|
||||
|
||||
// Put it in the heap so it can outlive this call
|
||||
SINGLETON = mem::transmute(Box::new(singleton));
|
||||
SINGLETON = Box::into_raw(Box::new(singleton)) as *const _;
|
||||
}
|
||||
|
||||
// Now we give out a copy of the data that is safe to use concurrently.
|
||||
@@ -73,7 +74,7 @@ fn env() -> Rc<RefCell<Environment>> {
|
||||
let singleton: Rc<RefCell<Environment>> = Rc::new(RefCell::new(Default::default()));
|
||||
|
||||
// Put it in the heap so it can outlive this call
|
||||
*s.borrow_mut() = mem::transmute(Box::new(singleton));
|
||||
*s.borrow_mut() = Box::into_raw(Box::new(singleton)) as *const _;
|
||||
}
|
||||
|
||||
// Now we give out a copy of the data that is safe to use concurrently.
|
||||
|
||||
@@ -41,7 +41,7 @@ pub mod api;
|
||||
pub mod transaction {
|
||||
use rstd::ops;
|
||||
use polkadot_primitives::Signature;
|
||||
pub use polkadot_primitives::{Transaction, UncheckedTransaction};
|
||||
pub use polkadot_primitives::{Transaction, Function, UncheckedTransaction};
|
||||
|
||||
/// A type-safe indicator that a transaction has been checked.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
@@ -63,10 +63,20 @@ pub mod transaction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the signature on a transaction.
|
||||
///
|
||||
/// On failure, return the transaction back.
|
||||
pub fn check(tx: UncheckedTransaction) -> Result<CheckedTransaction, UncheckedTransaction> {
|
||||
/// Check the validity of a transaction: whether it can appear at the given index
|
||||
/// and whether it is correctly authenticated.
|
||||
pub fn check(tx: UncheckedTransaction, index: u64) -> Result<CheckedTransaction, UncheckedTransaction> {
|
||||
match tx.transaction.function.inherent_index() {
|
||||
Some(correct_index) => {
|
||||
if index != correct_index || !tx.is_well_formed() { return Err(tx) }
|
||||
return Ok(CheckedTransaction(tx));
|
||||
}
|
||||
None => {
|
||||
// non-inherent functions must appear after inherent.
|
||||
if index < Function::inherent_functions() { return Err(tx) }
|
||||
}
|
||||
}
|
||||
|
||||
let msg = ::codec::Slicable::encode(&tx.transaction);
|
||||
if ::runtime_io::ed25519_verify(&tx.signature.0, &msg, &tx.transaction.signed) {
|
||||
Ok(CheckedTransaction(tx))
|
||||
|
||||
@@ -24,12 +24,12 @@ use codec::{KeyedVec, Slicable};
|
||||
use runtime_support::{Hashable, storage};
|
||||
use environment::with_env;
|
||||
use polkadot_primitives::{AccountId, Hash, TxOrder, BlockNumber, Block, Header,
|
||||
UncheckedTransaction, Function, Log};
|
||||
UncheckedTransaction, Function, InherentFunction, Log};
|
||||
use runtime::{staking, session};
|
||||
|
||||
const NONCE_OF: &[u8] = b"sys:non:";
|
||||
const BLOCK_HASH_AT: &[u8] = b"sys:old:";
|
||||
const CODE: &[u8] = b"sys:cod";
|
||||
const TEMP_TRANSACTION_NUMBER: &[u8] = b"temp:txcount:";
|
||||
|
||||
/// The current block number being processed. Set by `execute_block`.
|
||||
pub fn block_number() -> BlockNumber {
|
||||
@@ -71,8 +71,10 @@ pub mod internal {
|
||||
// any initial checks
|
||||
initial_checks(&block);
|
||||
|
||||
// execute transactions
|
||||
block.transactions.iter().cloned().for_each(super::execute_transaction);
|
||||
// execute all transactions, inherent or otherwise.
|
||||
for (tx_num, tx) in block.all_transactions().enumerate() {
|
||||
super::execute_transaction(tx, tx_num as u64);
|
||||
}
|
||||
|
||||
// post-transactional book-keeping.
|
||||
staking::internal::check_new_era();
|
||||
@@ -87,6 +89,8 @@ pub mod internal {
|
||||
|
||||
/// Execute a transaction outside of the block execution function.
|
||||
/// This doesn't attempt to validate anything regarding the block.
|
||||
/// Note that when building a block transaction by transaction, the
|
||||
/// inherent methods must be called manually.
|
||||
pub fn execute_transaction(utx: UncheckedTransaction, mut header: Header) -> Header {
|
||||
// populate environment from header.
|
||||
with_env(|e| {
|
||||
@@ -95,11 +99,16 @@ pub mod internal {
|
||||
mem::swap(&mut header.digest, &mut e.digest);
|
||||
});
|
||||
|
||||
super::execute_transaction(utx);
|
||||
let tx_num: u64 = storage::get_or_default(TEMP_TRANSACTION_NUMBER);
|
||||
|
||||
super::execute_transaction(utx, tx_num);
|
||||
|
||||
with_env(|e| {
|
||||
mem::swap(&mut header.digest, &mut e.digest);
|
||||
});
|
||||
|
||||
storage::put(TEMP_TRANSACTION_NUMBER, &(tx_num + 1));
|
||||
|
||||
header
|
||||
}
|
||||
|
||||
@@ -113,6 +122,11 @@ pub mod internal {
|
||||
mem::swap(&mut header.digest, &mut e.digest);
|
||||
});
|
||||
|
||||
let tx_count: u64 = storage::take_or_default(TEMP_TRANSACTION_NUMBER);
|
||||
if tx_count < Function::inherent_functions() {
|
||||
panic!("Not enough transactions provided to fulfill all inherent functions.");
|
||||
}
|
||||
|
||||
staking::internal::check_new_era();
|
||||
session::internal::check_rotate_session();
|
||||
|
||||
@@ -125,54 +139,61 @@ pub mod internal {
|
||||
|
||||
header
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch a function.
|
||||
pub fn dispatch_function(function: &Function, transactor: &AccountId) {
|
||||
match *function {
|
||||
Function::StakingStake => {
|
||||
::runtime::staking::public::stake(transactor);
|
||||
}
|
||||
Function::StakingUnstake => {
|
||||
::runtime::staking::public::unstake(transactor);
|
||||
}
|
||||
Function::StakingTransfer(dest, value) => {
|
||||
::runtime::staking::public::transfer(transactor, &dest, value);
|
||||
}
|
||||
Function::SessionSetKey(session) => {
|
||||
::runtime::session::public::set_key(transactor, &session);
|
||||
}
|
||||
Function::TimestampSet(t) => {
|
||||
::runtime::timestamp::public::set(t);
|
||||
}
|
||||
Function::GovernancePropose(ref proposal) => {
|
||||
::runtime::governance::public::propose(transactor, proposal);
|
||||
}
|
||||
Function::GovernanceApprove(era_index) => {
|
||||
::runtime::governance::public::approve(transactor, era_index);
|
||||
}
|
||||
/// Dispatch a function.
|
||||
fn dispatch_function(function: &Function, transactor: &AccountId) {
|
||||
match *function {
|
||||
Function::Inherent(InherentFunction::TimestampSet(t)) => {
|
||||
::runtime::timestamp::public::set(t);
|
||||
}
|
||||
Function::StakingStake => {
|
||||
::runtime::staking::public::stake(transactor);
|
||||
}
|
||||
Function::StakingUnstake => {
|
||||
::runtime::staking::public::unstake(transactor);
|
||||
}
|
||||
Function::StakingTransfer(dest, value) => {
|
||||
::runtime::staking::public::transfer(transactor, &dest, value);
|
||||
}
|
||||
Function::SessionSetKey(session) => {
|
||||
::runtime::session::public::set_key(transactor, &session);
|
||||
}
|
||||
Function::GovernancePropose(ref proposal) => {
|
||||
::runtime::governance::public::propose(transactor, proposal);
|
||||
}
|
||||
Function::GovernanceApprove(era_index) => {
|
||||
::runtime::governance::public::approve(transactor, era_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_transaction(utx: UncheckedTransaction) {
|
||||
fn execute_transaction(utx: UncheckedTransaction, tx_num: u64) {
|
||||
use ::transaction;
|
||||
|
||||
// Verify the signature is good.
|
||||
let tx = match transaction::check(utx) {
|
||||
// Verify the transaction is authenticated at its position.
|
||||
let tx = match transaction::check(utx, tx_num) {
|
||||
Ok(tx) => tx,
|
||||
Err(_) => panic!("All transactions should be properly signed"),
|
||||
Err(_) => panic!("Transaction at index {} not properly authenticated", tx_num),
|
||||
};
|
||||
|
||||
// check nonce
|
||||
let nonce_key = tx.signed.to_keyed_vec(NONCE_OF);
|
||||
let expected_nonce: TxOrder = storage::get_or(&nonce_key, 0);
|
||||
let (expected_nonce, increment_nonce) = if !tx.function.is_inherent() {
|
||||
(storage::get_or(&nonce_key, 0), true)
|
||||
} else {
|
||||
(0, false)
|
||||
};
|
||||
|
||||
assert!(tx.nonce == expected_nonce, "All transactions should have the correct nonce");
|
||||
|
||||
// increment nonce in storage
|
||||
storage::put(&nonce_key, &(expected_nonce + 1));
|
||||
// increment nonce in storage, unless it's the EVERYBODY account.
|
||||
if increment_nonce {
|
||||
storage::put(&nonce_key, &(expected_nonce + 1));
|
||||
}
|
||||
|
||||
// decode parameters and dispatch
|
||||
internal::dispatch_function(&tx.function, &tx.signed);
|
||||
dispatch_function(&tx.function, &tx.signed);
|
||||
}
|
||||
|
||||
fn initial_checks(block: &Block) {
|
||||
@@ -185,7 +206,7 @@ fn initial_checks(block: &Block) {
|
||||
);
|
||||
|
||||
// check transaction trie root represents the transactions.
|
||||
let txs = block.transactions.iter().map(Slicable::encode).collect::<Vec<_>>();
|
||||
let txs = block.all_transactions().map(|tx| Slicable::encode(&tx)).collect::<Vec<_>>();
|
||||
let txs = txs.iter().map(Vec::as_slice).collect::<Vec<_>>();
|
||||
let txs_root = enumerated_trie_root(&txs).into();
|
||||
info_expect_equal_hash(&header.transaction_root, &txs_root);
|
||||
@@ -239,9 +260,37 @@ mod tests {
|
||||
use keyring::Keyring;
|
||||
use environment::with_env;
|
||||
use primitives::hexdisplay::HexDisplay;
|
||||
use polkadot_primitives::{Header, Digest, UncheckedTransaction, Transaction, Function};
|
||||
use polkadot_primitives::{Header, Body, Digest, UncheckedTransaction, Transaction, Function, InherentFunction};
|
||||
use runtime::staking;
|
||||
|
||||
fn set_timestamp() -> UncheckedTransaction {
|
||||
UncheckedTransaction::inherent(InherentFunction::TimestampSet(100_000))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn fails_if_first_not_timestamp_set() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let two = Keyring::Two.to_raw_public();
|
||||
|
||||
let mut t: TestExternalities = map![
|
||||
twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0]
|
||||
];
|
||||
|
||||
let tx = UncheckedTransaction {
|
||||
transaction: Transaction {
|
||||
signed: one.clone(),
|
||||
nonce: 0,
|
||||
function: Function::StakingTransfer(two, 69),
|
||||
},
|
||||
signature: hex!("5f9832c5a4a39e2dd4a3a0c5b400e9836beb362cb8f7d845a8291a2ae6fe366612e080e4acd0b5a75c3d0b6ee69614a68fb63698c1e76bf1f2dcd8fa617ddf05").into(),
|
||||
};
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
internal::execute_transaction(tx, Header::from_block_number(1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staking_balance_transfer_dispatch_works() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
@@ -261,7 +310,9 @@ mod tests {
|
||||
};
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
internal::execute_transaction(tx, Header::from_block_number(1));
|
||||
let header = Header::from_block_number(1);
|
||||
let header = internal::execute_transaction(set_timestamp(), header);
|
||||
internal::execute_transaction(tx, header);
|
||||
assert_eq!(staking::balance(&one), 42);
|
||||
assert_eq!(staking::balance(&two), 69);
|
||||
});
|
||||
@@ -293,22 +344,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn block_import_works() {
|
||||
let one = Keyring::One.to_raw_public();
|
||||
let two = Keyring::Two.to_raw_public();
|
||||
|
||||
let mut t = new_test_ext();
|
||||
|
||||
let h = Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: hex!("1ab2dbb7d4868a670b181327b0b6a58dc64b10cfb9876f737a5aa014b8da31e0").into(),
|
||||
transaction_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(),
|
||||
state_root: hex!("aa4fbcdc09b21e4366aebccd9b9ec0831a8a2765c712d3397f121ff8e60e21e2").into(),
|
||||
transaction_root: hex!("328ae80be3adf358d2a2e188cbe1bfd3f8cd5b15a2e7666e2b4eccf7450efc32").into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
};
|
||||
|
||||
let b = Block {
|
||||
header: h,
|
||||
transactions: vec![],
|
||||
body: Body {
|
||||
timestamp: 100_000,
|
||||
transactions: vec![]
|
||||
}
|
||||
};
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
@@ -334,7 +385,10 @@ mod tests {
|
||||
|
||||
let b = Block {
|
||||
header: h,
|
||||
transactions: vec![],
|
||||
body: Body {
|
||||
timestamp: 100_000,
|
||||
transactions: vec![],
|
||||
}
|
||||
};
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
@@ -360,7 +414,10 @@ mod tests {
|
||||
|
||||
let b = Block {
|
||||
header: h,
|
||||
transactions: vec![],
|
||||
body: Body {
|
||||
timestamp: 100_000,
|
||||
transactions: vec![],
|
||||
}
|
||||
};
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
//! Timestamp manager: just handles the current timestamp.
|
||||
|
||||
use runtime_support::storage;
|
||||
|
||||
pub type Timestamp = u64;
|
||||
use polkadot_primitives::Timestamp;
|
||||
|
||||
const CURRENT_TIMESTAMP: &[u8] = b"tim:val";
|
||||
|
||||
@@ -32,6 +31,10 @@ pub mod public {
|
||||
|
||||
/// Set the current time.
|
||||
pub fn set(now: Timestamp) {
|
||||
if super::get() > now {
|
||||
panic!("last timestamp less than now");
|
||||
}
|
||||
|
||||
storage::put(CURRENT_TIMESTAMP, &now);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user