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:
committed by
Afri Schoedon
parent
9c5e35548d
commit
24f6d8296b
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
184
ethcore/src/engines/block_reward.rs
Normal file
184
ethcore/src/engines/block_reward.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user