Commit 5d4cce00 authored by asynchronous rob's avatar asynchronous rob Committed by Bastian Köcher
Browse files

Charge fees for parachain execution (#293)

* burn parachain funds depending on candidate fees

* charge fees when executing parachain

* fix test compilation

* branch grumble addressed

* test that Balance >= usize
parent fe6b7c3f
Pipeline #41369 passed with stages
in 12 minutes and 24 seconds
......@@ -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(),
......
......@@ -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(
......
......@@ -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());
}
}
......@@ -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)
......
......@@ -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(&para_account).into()
}
fn deduct(para_id: ParaId, amount: Balance) -> Result {
let para_account = para_id.into_account();
// burn the fee.
let _ = T::withdraw(
&para_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: &parachain::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>;
......
......@@ -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();
......
......@@ -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());
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment