diff --git a/polkadot/collator/src/lib.rs b/polkadot/collator/src/lib.rs index c6323776816f9a5149b106c631985b7e322c39d6..20c254b3488993579973f583a65de327f420edc3 100644 --- a/polkadot/collator/src/lib.rs +++ b/polkadot/collator/src/lib.rs @@ -56,7 +56,7 @@ use primitives::{ed25519, Pair}; use polkadot_primitives::{BlockId, SessionKey, Hash, Block}; use polkadot_primitives::parachain::{ self, BlockData, DutyRoster, HeadData, ConsolidatedIngress, Message, Id as ParaId, Extrinsic, - PoVBlock, + PoVBlock, Status as ParachainStatus, }; use polkadot_cli::{PolkadotService, CustomConfiguration, ParachainHost}; use polkadot_cli::{Worker, IntoExit, ProvideRuntimeApi, TaskExecutor}; @@ -105,7 +105,7 @@ pub trait ParachainContext: Clone { fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>( &self, relay_parent: Hash, - last_head: HeadData, + status: ParachainStatus, ingress: I, ) -> Self::ProduceCandidate; } @@ -128,7 +128,7 @@ pub trait RelayChainContext { pub fn collate<'a, R, P>( relay_parent: Hash, local_id: ParaId, - last_head: HeadData, + parachain_status: ParachainStatus, relay_context: R, para_context: P, key: Arc<ed25519::Pair>, @@ -146,7 +146,7 @@ pub fn collate<'a, R, P>( .and_then(move |ingress| { para_context.produce_candidate( relay_parent, - last_head, + parachain_status, ingress.0.iter().flat_map(|&(id, ref msgs)| msgs.iter().cloned().map(move |msg| (id, msg))) ) .into_future() @@ -311,8 +311,8 @@ impl<P, E> Worker for CollationNode<P, E> where let work = future::lazy(move || { let api = client.runtime_api(); - let last_head = match try_fr!(api.parachain_head(&id, para_id)) { - Some(last_head) => last_head, + let status = match try_fr!(api.parachain_status(&id, para_id)) { + Some(status) => status, None => return future::Either::A(future::ok(())), }; @@ -333,7 +333,7 @@ impl<P, E> Worker for CollationNode<P, E> where let collation_work = collate( relay_parent, para_id, - HeadData(last_head), + status, context, parachain_context, key, @@ -403,7 +403,7 @@ pub fn run_collator<P, E, I, ArgT>( #[cfg(test)] mod tests { use std::collections::HashMap; - use polkadot_primitives::parachain::OutgoingMessage; + use polkadot_primitives::parachain::{OutgoingMessage, FeeSchedule}; use keyring::AuthorityKeyring; use super::*; @@ -433,7 +433,7 @@ mod tests { fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>( &self, _relay_parent: Hash, - _last_head: HeadData, + _status: ParachainStatus, ingress: I, ) -> Result<(BlockData, HeadData, Extrinsic), InvalidHead> { // send messages right back. @@ -484,7 +484,14 @@ mod tests { let collation = collate( Default::default(), id, - HeadData(vec![5]), + ParachainStatus { + head_data: HeadData(vec![5]), + balance: 10, + fee_schedule: FeeSchedule { + base: 0, + per_byte: 1, + }, + }, context.clone(), DummyParachainContext, AuthorityKeyring::Alice.pair().into(), diff --git a/polkadot/network/src/tests/validation.rs b/polkadot/network/src/tests/validation.rs index 3ec328f805b64eb31ee11f1cee044eacd3d96b88..cc5e60ea0cb05c0ef1887d48a4a71c65a1e9dcb0 100644 --- a/polkadot/network/src/tests/validation.rs +++ b/polkadot/network/src/tests/validation.rs @@ -30,7 +30,8 @@ use polkadot_validation::{SharedTable, MessagesFrom, Network}; use polkadot_primitives::{SessionKey, Block, Hash, Header, BlockId}; use polkadot_primitives::parachain::{ Id as ParaId, Chain, DutyRoster, ParachainHost, OutgoingMessage, - ValidatorId, StructuredUnroutedIngress, BlockIngressRoots, + ValidatorId, StructuredUnroutedIngress, BlockIngressRoots, Status, + FeeSchedule, HeadData, }; use parking_lot::Mutex; use substrate_client::error::Result as ClientResult; @@ -282,14 +283,21 @@ impl ParachainHost<Block> for RuntimeApi { Ok(NativeOrEncoded::Native(self.data.lock().active_parachains.clone())) } - fn ParachainHost_parachain_head_runtime_api_impl( + fn ParachainHost_parachain_status_runtime_api_impl( &self, _at: &BlockId, _: ExecutionContext, _: Option<ParaId>, _: Vec<u8>, - ) -> ClientResult<NativeOrEncoded<Option<Vec<u8>>>> { - Ok(NativeOrEncoded::Native(Some(Vec::new()))) + ) -> ClientResult<NativeOrEncoded<Option<Status>>> { + Ok(NativeOrEncoded::Native(Some(Status { + head_data: HeadData(Vec::new()), + balance: 0, + fee_schedule: FeeSchedule { + base: 0, + per_byte: 0, + } + }))) } fn ParachainHost_parachain_code_runtime_api_impl( diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs index eb0bba3092327edcae303baf68cf184bdec18865..c8ff2c3d0d7587e1f17451a138166f0b82446855 100644 --- a/polkadot/primitives/src/parachain.rs +++ b/polkadot/primitives/src/parachain.rs @@ -28,7 +28,9 @@ use serde::{Serialize, Deserialize}; use primitives::bytes; use primitives::ed25519; -pub use polkadot_parachain::{Id, AccountIdConversion, ParachainDispatchOrigin}; +pub use polkadot_parachain::{ + Id, AccountIdConversion, ParachainDispatchOrigin, +}; /// Identity that collators use. pub type CollatorId = ed25519::Public; @@ -328,6 +330,39 @@ impl AttestedCandidate { } } +/// A fee schedule for messages. This is a linear function in the number of bytes of a message. +#[derive(PartialEq, Eq, PartialOrd, Hash, Default, Clone, Copy, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct FeeSchedule { + /// The base fee charged for all messages. + pub base: Balance, + /// The per-byte fee charged on top of that. + pub per_byte: Balance, +} + +impl FeeSchedule { + /// Compute the fee for a message of given size. + pub fn compute_fee(&self, n_bytes: usize) -> Balance { + use rstd::mem; + debug_assert!(mem::size_of::<Balance>() >= mem::size_of::<usize>()); + + let n_bytes = n_bytes as Balance; + self.base.saturating_add(n_bytes.saturating_mul(self.per_byte)) + } +} + +/// Current Status of a parachain. +#[derive(PartialEq, Eq, Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct Status { + /// The head of the parachain. + pub head_data: HeadData, + /// The current balance of the parachain. + pub balance: Balance, + /// The fee schedule for messages coming from this parachain. + pub fee_schedule: FeeSchedule, +} + substrate_client::decl_runtime_apis! { /// The API for querying the state of parachains on-chain. pub trait ParachainHost { @@ -337,8 +372,8 @@ substrate_client::decl_runtime_apis! { fn duty_roster() -> DutyRoster; /// Get the currently active parachains. fn active_parachains() -> Vec<Id>; - /// Get the given parachain's head data blob. - fn parachain_head(id: Id) -> Option<Vec<u8>>; + /// Get the given parachain's status. + fn parachain_status(id: Id) -> Option<Status>; /// Get the given parachain's head code blob. fn parachain_code(id: Id) -> Option<Vec<u8>>; /// Get all the unrouted ingress roots at the given block that @@ -354,3 +389,16 @@ pub mod id { /// Parachain host runtime API id. pub const PARACHAIN_HOST: ApiId = *b"parahost"; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn balance_bigger_than_usize() { + let zero_b: Balance = 0; + let zero_u: usize = 0; + + assert!(zero_b.leading_zeros() >= zero_u.leading_zeros()); + } +} diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs index d817a39151b894673584d42fe110bb8aaa66b339..1eda4a6773203f5bccff930b558951f1e212e09f 100644 --- a/polkadot/runtime/src/lib.rs +++ b/polkadot/runtime/src/lib.rs @@ -234,6 +234,7 @@ impl grandpa::Trait for Runtime { impl parachains::Trait for Runtime { type Origin = Origin; type Call = Call; + type ParachainCurrency = Balances; } parameter_types!{ @@ -364,8 +365,8 @@ impl_runtime_apis! { fn active_parachains() -> Vec<parachain::Id> { Parachains::active_parachains() } - fn parachain_head(id: parachain::Id) -> Option<Vec<u8>> { - Parachains::parachain_head(&id) + fn parachain_status(id: parachain::Id) -> Option<parachain::Status> { + Parachains::parachain_status(&id) } fn parachain_code(id: parachain::Id) -> Option<Vec<u8>> { Parachains::parachain_code(&id) diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs index 04370f87a357e55fac7fd5a533fed9ee55a0cd5b..4b077c0475fa89575b73e37dd44c64d18331feaf 100644 --- a/polkadot/runtime/src/parachains.rs +++ b/polkadot/runtime/src/parachains.rs @@ -25,13 +25,14 @@ use bitvec::{bitvec, BigEndian}; use sr_primitives::traits::{ Hash as HashT, BlakeTwo256, Member, CheckedConversion, Saturating, One, Zero, }; -use primitives::{Hash, parachain::{ - Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, AccountIdConversion, +use primitives::{Hash, Balance, parachain::{ + self, Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, AccountIdConversion, ParachainDispatchOrigin, UpwardMessage, BlockIngressRoots, }}; use {system, session}; use srml_support::{ - StorageValue, StorageMap, storage::AppendableStorageMap, Parameter, Dispatchable, dispatch::Result + StorageValue, StorageMap, storage::AppendableStorageMap, Parameter, Dispatchable, dispatch::Result, + traits::{Currency, WithdrawReason, ExistenceRequirement} }; #[cfg(feature = "std")] @@ -142,12 +143,46 @@ impl<T: Trait> ParachainRegistrar<T::AccountId> for Module<T> { } } +// wrapper trait because an associated type of `Currency<Self::AccountId,Balance=Balance>` +// doesn't work.` +pub trait ParachainCurrency<AccountId> { + fn free_balance(para_id: ParaId) -> Balance; + fn deduct(para_id: ParaId, amount: Balance) -> Result; +} + +impl<AccountId, T: Currency<AccountId>> ParachainCurrency<AccountId> for T where + T::Balance: From<Balance> + Into<Balance>, + ParaId: AccountIdConversion<AccountId>, +{ + fn free_balance(para_id: ParaId) -> Balance { + let para_account = para_id.into_account(); + T::free_balance(¶_account).into() + } + + fn deduct(para_id: ParaId, amount: Balance) -> Result { + let para_account = para_id.into_account(); + + // burn the fee. + let _ = T::withdraw( + ¶_account, + amount.into(), + WithdrawReason::Fee, + ExistenceRequirement::KeepAlive, + )?; + + Ok(()) + } +} + pub trait Trait: session::Trait { /// The outer origin type. type Origin: From<Origin> + From<system::RawOrigin<Self::AccountId>>; /// The outer call dispatch type. type Call: Parameter + Dispatchable<Origin=<Self as Trait>::Origin>; + + /// Some way of interacting with balances for fees. + type ParachainCurrency: ParachainCurrency<Self::AccountId>; } /// Origin for the parachains module. @@ -269,7 +304,7 @@ decl_module! { } } - Self::check_attestations(&heads)?; + Self::check_candidates(&heads)?; let current_number = <system::Module<T>>::block_number(); @@ -549,6 +584,21 @@ impl<T: Trait> Module<T> { .collect()) } + /// Get the parachain status necessary for validation. + pub fn parachain_status(id: ¶chain::Id) -> Option<parachain::Status> { + let balance = T::ParachainCurrency::free_balance(*id); + Self::parachain_head(id).map(|head_data| parachain::Status { + head_data: parachain::HeadData(head_data), + balance, + // TODO: https://github.com/paritytech/polkadot/issues/92 + // plug in some real values here. most likely governable. + fee_schedule: parachain::FeeSchedule { + base: 0, + per_byte: 0, + } + }) + } + fn check_egress_queue_roots(head: &AttestedCandidate, active_parachains: &[ParaId]) -> Result { let mut last_egress_id = None; let mut iter = active_parachains.iter(); @@ -584,7 +634,7 @@ impl<T: Trait> Module<T> { // check the attestations on these candidates. The candidates should have been checked // that each candidates' chain ID is valid. - fn check_attestations(attested_candidates: &[AttestedCandidate]) -> Result { + fn check_candidates(attested_candidates: &[AttestedCandidate]) -> Result{ use primitives::parachain::ValidityAttestation; use sr_primitives::traits::Verify; @@ -661,7 +711,8 @@ impl<T: Trait> Module<T> { let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]); for candidate in attested_candidates { - let validator_group = validator_groups.group_for(candidate.parachain_index()) + let para_id = candidate.parachain_index(); + let validator_group = validator_groups.group_for(para_id) .ok_or("no validator group for parachain")?; ensure!( @@ -669,6 +720,9 @@ impl<T: Trait> Module<T> { "Not enough validity attestations" ); + let fees = candidate.candidate().fees; + T::ParachainCurrency::deduct(para_id, fees)?; + let mut candidate_hash = None; let mut encoded_implicit = None; let mut encoded_explicit = None; @@ -824,7 +878,7 @@ mod tests { } impl balances::Trait for Test { - type Balance = u64; + type Balance = Balance; type OnFreeBalanceZero = (); type OnNewAccount = (); type Event = (); @@ -852,6 +906,7 @@ mod tests { impl Trait for Test { type Origin = Origin; type Call = Call; + type ParachainCurrency = balances::Module<Test>; } type Parachains = Module<Test>; diff --git a/polkadot/test-parachains/adder/collator/src/main.rs b/polkadot/test-parachains/adder/collator/src/main.rs index ca0ab87064a393f40b09cb75f7caef7342a6ae7d..2692ee260136f12b8e679622911046d26e0fef78 100644 --- a/polkadot/test-parachains/adder/collator/src/main.rs +++ b/polkadot/test-parachains/adder/collator/src/main.rs @@ -23,7 +23,10 @@ use std::sync::Arc; use adder::{HeadData as AdderHead, BlockData as AdderBody}; use substrate_primitives::Pair; use parachain::codec::{Encode, Decode}; -use primitives::{Hash, parachain::{HeadData, BlockData, Id as ParaId, Message, Extrinsic}}; +use primitives::Hash; +use primitives::parachain::{ + HeadData, BlockData, Id as ParaId, Message, Extrinsic, Status as ParachainStatus, +}; use collator::{InvalidHead, ParachainContext, VersionInfo}; use parking_lot::Mutex; @@ -50,11 +53,11 @@ impl ParachainContext for AdderContext { fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>( &self, _relay_parent: Hash, - last_head: HeadData, + status: ParachainStatus, ingress: I, ) -> Result<(BlockData, HeadData, Extrinsic), InvalidHead> { - let adder_head = AdderHead::decode(&mut &last_head.0[..]) + let adder_head = AdderHead::decode(&mut &status.head_data.0[..]) .ok_or(InvalidHead)?; let mut db = self.db.lock(); diff --git a/polkadot/validation/src/collation.rs b/polkadot/validation/src/collation.rs index 77c6a905293be72905974c29e9968b900fd07592..8d1785ba82c8fa4dd2e165c901fa1183bcafca86 100644 --- a/polkadot/validation/src/collation.rs +++ b/polkadot/validation/src/collation.rs @@ -21,9 +21,9 @@ use std::sync::Arc; -use polkadot_primitives::{Block, Hash, BlockId, parachain::CollatorId, parachain::{ - ConsolidatedIngress, StructuredUnroutedIngress, CandidateReceipt, ParachainHost, - Id as ParaId, Collation, Extrinsic, OutgoingMessage, UpwardMessage +use polkadot_primitives::{Block, Hash, BlockId, Balance, parachain::{ + CollatorId, ConsolidatedIngress, StructuredUnroutedIngress, CandidateReceipt, ParachainHost, + Id as ParaId, Collation, Extrinsic, OutgoingMessage, UpwardMessage, FeeSchedule, }}; use runtime_primitives::traits::ProvideRuntimeApi; use parachain::{wasm_executor::{self, ExternalitiesError}, MessageRef, UpwardMessageRef}; @@ -167,6 +167,9 @@ pub enum Error { /// Parachain validation produced wrong relay-chain messages #[display(fmt = "Parachain validation produced wrong relay-chain messages (expected: {:?}, got {:?})", expected, got)] UpwardMessagesInvalid { expected: Vec<UpwardMessage>, got: Vec<UpwardMessage> }, + /// Parachain validation produced wrong fees to charge to parachain. + #[display(fmt = "Parachain validation produced wrong relay-chain fees (expected: {:?}, got {:?})", expected, got)] + FeesChargedInvalid { expected: Balance, got: Balance }, } impl std::error::Error for Error { @@ -268,17 +271,19 @@ struct Externalities { parachain_index: ParaId, outgoing: Vec<OutgoingMessage>, upward: Vec<UpwardMessage>, + fees_charged: Balance, + free_balance: Balance, + fee_schedule: FeeSchedule, } impl wasm_executor::Externalities for Externalities { fn post_message(&mut self, message: MessageRef) -> Result<(), ExternalitiesError> { - // TODO: https://github.com/paritytech/polkadot/issues/92 - // check per-message and per-byte fees for the parachain. let target: ParaId = message.target.into(); if target == self.parachain_index { return Err(ExternalitiesError::CannotPostMessage("posted message to self")); } + self.apply_message_fee(message.data.len())?; self.outgoing.push(OutgoingMessage { target, data: message.data.to_vec(), @@ -290,8 +295,8 @@ impl wasm_executor::Externalities for Externalities { fn post_upward_message(&mut self, message: UpwardMessageRef) -> Result<(), ExternalitiesError> { - // TODO: https://github.com/paritytech/polkadot/issues/92 - // check per-message and per-byte fees for the parachain. + self.apply_message_fee(message.data.len())?; + self.upward.push(UpwardMessage { origin: message.origin, data: message.data.to_vec(), @@ -300,9 +305,18 @@ impl wasm_executor::Externalities for Externalities { } } - - impl Externalities { + fn apply_message_fee(&mut self, message_len: usize) -> Result<(), ExternalitiesError> { + let fee = self.fee_schedule.compute_fee(message_len); + let new_fees_charged = self.fees_charged.saturating_add(fee); + if new_fees_charged > self.free_balance { + Err(ExternalitiesError::CannotPostMessage("could not cover fee.")) + } else { + self.fees_charged = new_fees_charged; + Ok(()) + } + } + // Performs final checks of validity, producing the extrinsic data. fn final_checks( self, @@ -315,6 +329,13 @@ impl Externalities { }); } + if self.fees_charged != candidate.fees { + return Err(Error::FeesChargedInvalid { + expected: candidate.fees.clone(), + got: self.fees_charged.clone(), + }); + } + check_extrinsic( self.outgoing, &candidate.egress_queue_roots[..], @@ -383,15 +404,16 @@ pub fn validate_collation<P>( let validation_code = api.parachain_code(relay_parent, para_id)? .ok_or_else(|| Error::InactiveParachain(para_id))?; - let chain_head = api.parachain_head(relay_parent, para_id)? + let chain_status = api.parachain_status(relay_parent, para_id)? .ok_or_else(|| Error::InactiveParachain(para_id))?; let roots = api.ingress(relay_parent, para_id)? .ok_or_else(|| Error::InactiveParachain(para_id))?; + validate_incoming(&roots, &collation.pov.ingress)?; let params = ValidationParams { - parent_head: chain_head, + parent_head: chain_status.head_data.0, block_data: collation.pov.block_data.0.clone(), ingress: collation.pov.ingress.0.iter() .flat_map(|&(source, ref messages)| { @@ -407,6 +429,9 @@ pub fn validate_collation<P>( parachain_index: collation.receipt.parachain_index.clone(), outgoing: Vec::new(), upward: Vec::new(), + free_balance: chain_status.balance, + fee_schedule: chain_status.fee_schedule, + fees_charged: 0, }; match wasm_executor::validate_candidate(&validation_code, params, &mut ext) { @@ -481,6 +506,12 @@ mod tests { parachain_index: 5.into(), outgoing: Vec::new(), upward: Vec::new(), + fees_charged: 0, + free_balance: 1_000_000, + fee_schedule: FeeSchedule { + base: 1000, + per_byte: 10, + }, }; assert!(ext.post_message(MessageRef { target: 1.into(), data: &[] }).is_ok()); @@ -495,6 +526,12 @@ mod tests { upward: vec![ UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Parachain }, ], + fees_charged: 0, + free_balance: 1_000_000, + fee_schedule: FeeSchedule { + base: 1000, + per_byte: 10, + }, }; let receipt = CandidateReceipt { parachain_index: 5.into(), @@ -550,4 +587,43 @@ mod tests { }; assert!(ext().final_checks(&receipt).is_ok()); } + + #[test] + fn ext_checks_fees_and_updates_correctly() { + let mut ext = Externalities { + parachain_index: 5.into(), + outgoing: Vec::new(), + upward: vec![ + UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Parachain }, + ], + fees_charged: 0, + free_balance: 1_000_000, + fee_schedule: FeeSchedule { + base: 1000, + per_byte: 10, + }, + }; + + ext.apply_message_fee(100).unwrap(); + assert_eq!(ext.fees_charged, 2000); + + ext.post_message(MessageRef { + target: 1.into(), + data: &[0u8; 100], + }).unwrap(); + assert_eq!(ext.fees_charged, 4000); + + ext.post_upward_message(UpwardMessageRef { + origin: ParachainDispatchOrigin::Signed, + data: &[0u8; 100], + }).unwrap(); + assert_eq!(ext.fees_charged, 6000); + + + ext.apply_message_fee((1_000_000 - 6000 - 1000) / 10).unwrap(); + assert_eq!(ext.fees_charged, 1_000_000); + + // cannot pay fee. + assert!(ext.apply_message_fee(1).is_err()); + } }