Block reward contract (#8419)

* engine: add block reward contract abi and helper client

* aura: add support for block reward contract

* engine: test block reward contract client

* aura: test block reward contract

* engine + aura: add missing docs

* engine: share SystemCall type alias

* aura: add transition for block reward contract

* engine: fix example block reward contract source link and bytecode
This commit is contained in:
André Silva
2018-04-20 11:32:00 +01:00
committed by Afri Schoedon
parent 9c5e35548d
commit 24f6d8296b
9 changed files with 412 additions and 22 deletions

View File

@@ -27,6 +27,8 @@ use account_provider::AccountProvider;
use block::*;
use client::EngineClient;
use engines::{Engine, Seal, EngineError, ConstructedVerifier};
use engines::block_reward;
use engines::block_reward::{BlockRewardContract, RewardKind};
use error::{Error, BlockError};
use ethjson;
use machine::{AuxiliaryData, Call, EthereumMachine};
@@ -68,6 +70,10 @@ pub struct AuthorityRoundParams {
pub immediate_transitions: bool,
/// Block reward in base units.
pub block_reward: U256,
/// Block reward contract transition block.
pub block_reward_contract_transition: u64,
/// Block reward contract.
pub block_reward_contract: Option<BlockRewardContract>,
/// Number of accepted uncles transition block.
pub maximum_uncle_count_transition: u64,
/// Number of accepted uncles.
@@ -95,6 +101,8 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
validate_step_transition: p.validate_step_transition.map_or(0, Into::into),
immediate_transitions: p.immediate_transitions.unwrap_or(false),
block_reward: p.block_reward.map_or_else(Default::default, Into::into),
block_reward_contract_transition: p.block_reward_contract_transition.map_or(0, Into::into),
block_reward_contract: p.block_reward_contract_address.map(BlockRewardContract::new),
maximum_uncle_count_transition: p.maximum_uncle_count_transition.map_or(0, Into::into),
maximum_uncle_count: p.maximum_uncle_count.map_or(0, Into::into),
empty_steps_transition: p.empty_steps_transition.map_or(u64::max_value(), |n| ::std::cmp::max(n.into(), 1)),
@@ -388,6 +396,8 @@ pub struct AuthorityRound {
epoch_manager: Mutex<EpochManager>,
immediate_transitions: bool,
block_reward: U256,
block_reward_contract_transition: u64,
block_reward_contract: Option<BlockRewardContract>,
maximum_uncle_count_transition: u64,
maximum_uncle_count: usize,
empty_steps_transition: u64,
@@ -620,6 +630,8 @@ impl AuthorityRound {
epoch_manager: Mutex::new(EpochManager::blank()),
immediate_transitions: our_params.immediate_transitions,
block_reward: our_params.block_reward,
block_reward_contract_transition: our_params.block_reward_contract_transition,
block_reward_contract: our_params.block_reward_contract,
maximum_uncle_count_transition: our_params.maximum_uncle_count_transition,
maximum_uncle_count: our_params.maximum_uncle_count,
empty_steps_transition: our_params.empty_steps_transition,
@@ -970,9 +982,7 @@ impl Engine<EthereumMachine> for AuthorityRound {
/// Apply the block reward on finalisation of the block.
fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> {
use parity_machine::WithBalances;
let mut rewards = Vec::new();
let mut benefactors = Vec::new();
if block.header().number() >= self.empty_steps_transition {
let empty_steps = if block.header().seal().is_empty() {
// this is a new block, calculate rewards based on the empty steps messages we have accumulated
@@ -998,17 +1008,33 @@ impl Engine<EthereumMachine> for AuthorityRound {
for empty_step in empty_steps {
let author = empty_step.author()?;
rewards.push((author, self.block_reward));
benefactors.push((author, RewardKind::EmptyStep));
}
}
let author = *block.header().author();
rewards.push((author, self.block_reward));
benefactors.push((author, RewardKind::Author));
for &(ref author, ref block_reward) in rewards.iter() {
self.machine.add_balance(block, author, block_reward)?;
}
self.machine.note_rewards(block, &rewards, &[])
let rewards = match self.block_reward_contract {
Some(ref c) if block.header().number() >= self.block_reward_contract_transition => {
let mut call = |to, data| {
let result = self.machine.execute_as_system(
block,
to,
U256::max_value(), // unbounded gas? maybe make configurable.
Some(data),
);
result.map_err(|e| format!("{}", e))
};
c.reward(&benefactors, &mut call)?
},
_ => {
benefactors.into_iter().map(|(author, _)| (author, self.block_reward)).collect()
},
};
block_reward::apply_block_rewards(&rewards, block, &self.machine)
}
/// Check the number of seal fields.
@@ -1521,6 +1547,8 @@ mod tests {
empty_steps_transition: u64::max_value(),
maximum_empty_steps: 0,
block_reward: Default::default(),
block_reward_contract_transition: 0,
block_reward_contract: Default::default(),
};
let aura = {
@@ -1563,6 +1591,8 @@ mod tests {
empty_steps_transition: u64::max_value(),
maximum_empty_steps: 0,
block_reward: Default::default(),
block_reward_contract_transition: 0,
block_reward_contract: Default::default(),
};
let aura = {
@@ -1617,6 +1647,8 @@ mod tests {
empty_steps_transition: u64::max_value(),
maximum_empty_steps: 0,
block_reward: Default::default(),
block_reward_contract_transition: 0,
block_reward_contract: Default::default(),
};
let mut c_params = ::spec::CommonParams::default();
@@ -1894,4 +1926,71 @@ mod tests {
_ => false,
});
}
#[test]
fn block_reward_contract() {
let spec = Spec::new_test_round_block_reward_contract();
let tap = Arc::new(AccountProvider::transient_provider());
let addr1 = tap.insert_account(keccak("1").into(), "1").unwrap();
let engine = &*spec.engine;
let genesis_header = spec.genesis_header();
let db1 = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let db2 = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let client = generate_dummy_client_with_spec_and_accounts(
Spec::new_test_round_block_reward_contract,
None,
);
engine.register_client(Arc::downgrade(&client) as _);
// step 2
let b1 = OpenBlock::new(
engine,
Default::default(),
false,
db1,
&genesis_header,
last_hashes.clone(),
addr1,
(3141562.into(), 31415620.into()),
vec![],
false,
).unwrap();
let b1 = b1.close_and_lock();
// since the block is empty it isn't sealed and we generate empty steps
engine.set_signer(tap.clone(), addr1, "1".into());
assert_eq!(engine.generate_seal(b1.block(), &genesis_header), Seal::None);
engine.step();
// step 3
// the signer of the accumulated empty step message should be rewarded
let b2 = OpenBlock::new(
engine,
Default::default(),
false,
db2,
&genesis_header,
last_hashes.clone(),
addr1,
(3141562.into(), 31415620.into()),
vec![],
false,
).unwrap();
let addr1_balance = b2.block().state().balance(&addr1).unwrap();
// after closing the block `addr1` should be reward twice, one for the included empty step
// message and another for block creation
let b2 = b2.close_and_lock();
// the contract rewards (1000 + kind) for each benefactor/reward kind
assert_eq!(
b2.block().state().balance(&addr1).unwrap(),
addr1_balance + (1000 + 0).into() + (1000 + 2).into(),
)
}
}

View File

@@ -0,0 +1,184 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity 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.
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
use ethabi;
use ethabi::ParamType;
use ethereum_types::{H160, Address, U256};
use block::ExecutedBlock;
use error::Error;
use machine::EthereumMachine;
use super::SystemCall;
use_contract!(block_reward_contract, "BlockReward", "res/contracts/block_reward.json");
/// The kind of block reward.
/// Depending on the consensus engine the allocated block reward might have
/// different semantics which could lead e.g. to different reward values.
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum RewardKind {
/// Reward attributed to the block author.
Author = 0,
/// Reward attributed to the block uncle(s).
Uncle = 1,
/// Reward attributed to the author(s) of empty step(s) included in the block (AuthorityRound engine).
EmptyStep = 2,
}
impl From<RewardKind> for u16 {
fn from(reward_kind: RewardKind) -> Self {
reward_kind as u16
}
}
/// A client for the block reward contract.
pub struct BlockRewardContract {
/// Address of the contract.
address: Address,
block_reward_contract: block_reward_contract::BlockReward,
}
impl BlockRewardContract {
/// Create a new block reward contract client targeting the given address.
pub fn new(address: Address) -> BlockRewardContract {
BlockRewardContract {
address,
block_reward_contract: block_reward_contract::BlockReward::default(),
}
}
/// Calls the block reward contract with the given benefactors list (and associated reward kind)
/// and returns the reward allocation (address - value). The block reward contract *must* be
/// called by the system address so the `caller` must ensure that (e.g. using
/// `machine.execute_as_system`).
pub fn reward(
&self,
benefactors: &[(Address, RewardKind)],
caller: &mut SystemCall,
) -> Result<Vec<(Address, U256)>, Error> {
let reward = self.block_reward_contract.functions().reward();
let input = reward.input(
benefactors.iter().map(|&(address, _)| H160::from(address)),
benefactors.iter().map(|&(_, ref reward_kind)| u16::from(*reward_kind)),
);
let output = caller(self.address, input)
.map_err(Into::into)
.map_err(::engines::EngineError::FailedSystemCall)?;
// since this is a non-constant call we can't use ethabi's function output
// deserialization, sadness ensues.
let types = &[
ParamType::Array(Box::new(ParamType::Address)),
ParamType::Array(Box::new(ParamType::Uint(256))),
];
let tokens = ethabi::decode(types, &output)
.map_err(|err| err.to_string())
.map_err(::engines::EngineError::FailedSystemCall)?;
assert!(tokens.len() == 2);
let addresses = tokens[0].clone().to_array().expect("type checked by ethabi::decode; qed");
let rewards = tokens[1].clone().to_array().expect("type checked by ethabi::decode; qed");
if addresses.len() != rewards.len() {
return Err(::engines::EngineError::FailedSystemCall(
"invalid data returned by reward contract: both arrays must have the same size".into()
).into());
}
let addresses = addresses.into_iter().map(|t| t.to_address().expect("type checked by ethabi::decode; qed"));
let rewards = rewards.into_iter().map(|t| t.to_uint().expect("type checked by ethabi::decode; qed"));
Ok(addresses.zip(rewards).collect())
}
}
/// Applies the given block rewards, i.e. adds the given balance to each benefactors' address.
/// If tracing is enabled the operations are recorded.
pub fn apply_block_rewards(rewards: &[(Address, U256)], block: &mut ExecutedBlock, machine: &EthereumMachine) -> Result<(), Error> {
use parity_machine::WithBalances;
for &(ref author, ref block_reward) in rewards {
machine.add_balance(block, author, block_reward)?;
}
machine.note_rewards(block, &rewards, &[])
}
#[cfg(test)]
mod test {
use client::PrepareOpenBlock;
use ethereum_types::U256;
use spec::Spec;
use test_helpers::generate_dummy_client_with_spec_and_accounts;
use super::{BlockRewardContract, RewardKind};
#[test]
fn block_reward_contract() {
let client = generate_dummy_client_with_spec_and_accounts(
Spec::new_test_round_block_reward_contract,
None,
);
let machine = Spec::new_test_machine();
// the spec has a block reward contract defined at the given address
let block_reward_contract = BlockRewardContract::new(
"0000000000000000000000000000000000000042".into(),
);
let mut call = |to, data| {
let mut block = client.prepare_open_block(
"0000000000000000000000000000000000000001".into(),
(3141562.into(), 31415620.into()),
vec![],
);
let result = machine.execute_as_system(
block.block_mut(),
to,
U256::max_value(),
Some(data),
);
result.map_err(|e| format!("{}", e))
};
// if no benefactors are given no rewards are attributed
assert!(block_reward_contract.reward(&vec![], &mut call).unwrap().is_empty());
// the contract rewards (1000 + kind) for each benefactor
let benefactors = vec![
("0000000000000000000000000000000000000033".into(), RewardKind::Author),
("0000000000000000000000000000000000000034".into(), RewardKind::Uncle),
("0000000000000000000000000000000000000035".into(), RewardKind::EmptyStep),
];
let rewards = block_reward_contract.reward(&benefactors, &mut call).unwrap();
let expected = vec![
("0000000000000000000000000000000000000033".into(), U256::from(1000)),
("0000000000000000000000000000000000000034".into(), U256::from(1000 + 1)),
("0000000000000000000000000000000000000035".into(), U256::from(1000 + 2)),
];
assert_eq!(expected, rewards);
}
}

View File

@@ -18,6 +18,7 @@
mod authority_round;
mod basic_authority;
mod block_reward;
mod instant_seal;
mod null_engine;
mod signer;
@@ -56,7 +57,7 @@ use ethereum_types::{H256, U256, Address};
use unexpected::{Mismatch, OutOfBounds};
use bytes::Bytes;
/// Default EIP-210 contrat code.
/// Default EIP-210 contract code.
/// As defined in https://github.com/ethereum/EIPs/pull/210
pub const DEFAULT_BLOCKHASH_CONTRACT: &'static str = "73fffffffffffffffffffffffffffffffffffffffe33141561006a5760014303600035610100820755610100810715156100455760003561010061010083050761010001555b6201000081071515610064576000356101006201000083050761020001555b5061013e565b4360003512151561008457600060405260206040f361013d565b61010060003543031315156100a857610100600035075460605260206060f361013c565b6101006000350715156100c55762010000600035430313156100c8565b60005b156100ea576101006101006000350507610100015460805260206080f361013b565b620100006000350715156101095763010000006000354303131561010c565b60005b1561012f57610100620100006000350507610200015460a052602060a0f361013a565b600060c052602060c0f35b5b5b5b5b";
@@ -119,6 +120,9 @@ pub enum Seal {
None,
}
/// A system-calling closure. Enacts calls on a block's state from the system address.
pub type SystemCall<'a> = FnMut(Address, Vec<u8>) -> Result<Vec<u8>, String> + 'a;
/// Type alias for a function we can get headers by hash through.
pub type Headers<'a, H> = Fn(H256) -> Option<H> + 'a;

View File

@@ -38,9 +38,7 @@ pub use self::simple_list::SimpleList;
use self::contract::ValidatorContract;
use self::safe_contract::ValidatorSafeContract;
use self::multi::Multi;
/// A system-calling closure. Enacts calls on a block's state from the system address.
pub type SystemCall<'a> = FnMut(Address, Bytes) -> Result<Bytes, String> + 'a;
use super::SystemCall;
/// Creates a validator set from spec.
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {

View File

@@ -848,6 +848,13 @@ impl Spec {
load_bundled!("authority_round_empty_steps")
}
/// Create a new Spec with AuthorityRound consensus (with empty steps) using a block reward
/// contract. The contract source code can be found at:
/// https://github.com/parity-contracts/block-reward/blob/daf7d44383b6cdb11cb6b953b018648e2b027cfb/contracts/ExampleBlockReward.sol
pub fn new_test_round_block_reward_contract() -> Self {
load_bundled!("authority_round_block_reward_contract")
}
/// Create a new Spec with Tendermint consensus which does internal sealing (not requiring
/// work).
/// Account keccak("0") and keccak("1") are a authorities.