Add block reward contract config to ethash and allow off-chain contracts (#9312)

This adds block reward contract config to ethash. A new config `blockRewardContractCode` is also added to both Aura and ethash. When specified, it will execute the code directly and overrides any `blockRewardContractAddress` config. Having this `blockRewardContractCode` config allows chains to deploy hard fork by simply replacing the current config value, without the need from us to support any `multi` block reward scheme.
This commit is contained in:
Wei Tang 2018-08-29 23:17:18 +08:00 committed by André Silva
parent 1073d56245
commit 74ce0f738e
8 changed files with 261 additions and 116 deletions

View File

@ -100,7 +100,11 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
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),
block_reward_contract: match (p.block_reward_contract_code, p.block_reward_contract_address) {
(Some(code), _) => Some(BlockRewardContract::new_from_code(Arc::new(code.into()))),
(_, Some(address)) => Some(BlockRewardContract::new_from_address(address.into())),
(None, None) => None,
},
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)),
@ -1043,7 +1047,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> {
let mut benefactors = Vec::new();
let mut beneficiaries = 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
@ -1069,32 +1073,22 @@ impl Engine<EthereumMachine> for AuthorityRound {
for empty_step in empty_steps {
let author = empty_step.author()?;
benefactors.push((author, RewardKind::EmptyStep));
beneficiaries.push((author, RewardKind::EmptyStep));
}
}
let author = *block.header().author();
benefactors.push((author, RewardKind::Author));
beneficiaries.push((author, RewardKind::Author));
let rewards: Vec<_> = match self.block_reward_contract {
Some(ref c) if block.header().number() >= self.block_reward_contract_transition => {
// NOTE: this logic should be moved to a function when another
// engine needs support for block reward contract.
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))
};
let mut call = super::default_system_or_code_call(&self.machine, block);
let rewards = c.reward(&benefactors, &mut call)?;
let rewards = c.reward(&beneficiaries, &mut call)?;
rewards.into_iter().map(|(author, amount)| (author, RewardKind::External, amount)).collect()
},
_ => {
benefactors.into_iter().map(|(author, reward_kind)| (author, reward_kind, self.block_reward)).collect()
beneficiaries.into_iter().map(|(author, reward_kind)| (author, reward_kind, self.block_reward)).collect()
},
};

View File

@ -21,33 +21,48 @@ use ethabi;
use ethabi::ParamType;
use ethereum_types::{H160, Address, U256};
use std::sync::Arc;
use hash::keccak;
use error::Error;
use machine::WithRewards;
use parity_machine::{Machine, WithBalances};
use trace;
use super::SystemCall;
use types::BlockNumber;
use super::{SystemOrCodeCall, SystemOrCodeCallKind};
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,
Author,
/// Reward attributed to the author(s) of empty step(s) included in the block (AuthorityRound engine).
EmptyStep = 2,
EmptyStep,
/// Reward attributed by an external protocol (e.g. block reward contract).
External = 3,
External,
/// Reward attributed to the block uncle(s) with given difference.
Uncle(u8),
}
impl RewardKind {
/// Create `RewardKind::Uncle` from given current block number and uncle block number.
pub fn uncle(number: BlockNumber, uncle: BlockNumber) -> Self {
RewardKind::Uncle(if number > uncle && number - uncle <= u8::max_value().into() { (number - uncle) as u8 } else { 0 })
}
}
impl From<RewardKind> for u16 {
fn from(reward_kind: RewardKind) -> Self {
reward_kind as u16
match reward_kind {
RewardKind::Author => 0,
RewardKind::EmptyStep => 2,
RewardKind::External => 3,
RewardKind::Uncle(depth) => 100 + depth as u16,
}
}
}
@ -55,7 +70,7 @@ impl Into<trace::RewardType> for RewardKind {
fn into(self) -> trace::RewardType {
match self {
RewardKind::Author => trace::RewardType::Block,
RewardKind::Uncle => trace::RewardType::Uncle,
RewardKind::Uncle(_) => trace::RewardType::Uncle,
RewardKind::EmptyStep => trace::RewardType::EmptyStep,
RewardKind::External => trace::RewardType::External,
}
@ -63,38 +78,50 @@ impl Into<trace::RewardType> for RewardKind {
}
/// A client for the block reward contract.
#[derive(PartialEq, Debug)]
pub struct BlockRewardContract {
/// Address of the contract.
address: Address,
kind: SystemOrCodeCallKind,
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 {
/// Create a new block reward contract client targeting the system call kind.
pub fn new(kind: SystemOrCodeCallKind) -> BlockRewardContract {
BlockRewardContract {
address,
kind,
block_reward_contract: block_reward_contract::BlockReward::default(),
}
}
/// Calls the block reward contract with the given benefactors list (and associated reward kind)
/// Create a new block reward contract client targeting the contract address.
pub fn new_from_address(address: Address) -> BlockRewardContract {
Self::new(SystemOrCodeCallKind::Address(address))
}
/// Create a new block reward contract client targeting the given code.
pub fn new_from_code(code: Arc<Vec<u8>>) -> BlockRewardContract {
let code_hash = keccak(&code[..]);
Self::new(SystemOrCodeCallKind::Code(code, code_hash))
}
/// Calls the block reward contract with the given beneficiaries 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,
beneficiaries: &[(Address, RewardKind)],
caller: &mut SystemOrCodeCall,
) -> 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)),
beneficiaries.iter().map(|&(address, _)| H160::from(address)),
beneficiaries.iter().map(|&(_, ref reward_kind)| u16::from(*reward_kind)),
);
let output = caller(self.address, input)
let output = caller(self.kind.clone(), input)
.map_err(Into::into)
.map_err(::engines::EngineError::FailedSystemCall)?;
@ -127,7 +154,7 @@ impl BlockRewardContract {
}
}
/// Applies the given block rewards, i.e. adds the given balance to each benefactors' address.
/// Applies the given block rewards, i.e. adds the given balance to each beneficiary' address.
/// If tracing is enabled the operations are recorded.
pub fn apply_block_rewards<M: Machine + WithBalances + WithRewards>(
rewards: &[(Address, RewardKind, U256)],
@ -149,6 +176,7 @@ mod test {
use spec::Spec;
use test_helpers::generate_dummy_client_with_spec_and_accounts;
use engines::SystemOrCodeCallKind;
use super::{BlockRewardContract, RewardKind};
#[test]
@ -161,7 +189,7 @@ mod test {
let machine = Spec::new_test_machine();
// the spec has a block reward contract defined at the given address
let block_reward_contract = BlockRewardContract::new(
let block_reward_contract = BlockRewardContract::new_from_address(
"0000000000000000000000000000000000000042".into(),
);
@ -172,30 +200,35 @@ mod test {
vec![],
).unwrap();
let result = machine.execute_as_system(
let result = match to {
SystemOrCodeCallKind::Address(to) => {
machine.execute_as_system(
block.block_mut(),
to,
U256::max_value(),
Some(data),
);
)
},
_ => panic!("Test reward contract is created by an address, we never reach this branch."),
};
result.map_err(|e| format!("{}", e))
};
// if no benefactors are given no rewards are attributed
// if no beneficiaries 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![
let beneficiaries = vec![
("0000000000000000000000000000000000000033".into(), RewardKind::Author),
("0000000000000000000000000000000000000034".into(), RewardKind::Uncle),
("0000000000000000000000000000000000000034".into(), RewardKind::Uncle(1)),
("0000000000000000000000000000000000000035".into(), RewardKind::EmptyStep),
];
let rewards = block_reward_contract.reward(&benefactors, &mut call).unwrap();
let rewards = block_reward_contract.reward(&beneficiaries, &mut call).unwrap();
let expected = vec![
("0000000000000000000000000000000000000033".into(), U256::from(1000)),
("0000000000000000000000000000000000000034".into(), U256::from(1000 + 1)),
("0000000000000000000000000000000000000034".into(), U256::from(1000 + 101)),
("0000000000000000000000000000000000000035".into(), U256::from(1000 + 2)),
];

View File

@ -133,6 +133,46 @@ pub enum Seal {
/// 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;
/// A system-calling closure. Enacts calls on a block's state with code either from an on-chain contract, or hard-coded EVM or WASM (if enabled on-chain) codes.
pub type SystemOrCodeCall<'a> = FnMut(SystemOrCodeCallKind, Vec<u8>) -> Result<Vec<u8>, String> + 'a;
/// Kind of SystemOrCodeCall, this is either an on-chain address, or code.
#[derive(PartialEq, Debug, Clone)]
pub enum SystemOrCodeCallKind {
/// On-chain address.
Address(Address),
/// Hard-coded code.
Code(Arc<Vec<u8>>, H256),
}
/// Default SystemOrCodeCall implementation.
pub fn default_system_or_code_call<'a>(machine: &'a ::machine::EthereumMachine, block: &'a mut ::block::ExecutedBlock) -> impl FnMut(SystemOrCodeCallKind, Vec<u8>) -> Result<Vec<u8>, String> + 'a {
move |to, data| {
let result = match to {
SystemOrCodeCallKind::Address(address) => {
machine.execute_as_system(
block,
address,
U256::max_value(),
Some(data),
)
},
SystemOrCodeCallKind::Code(code, code_hash) => {
machine.execute_code_as_system(
block,
None,
Some(code),
Some(code_hash),
U256::max_value(),
Some(data),
)
},
};
result.map_err(|e| format!("{}", e))
}
}
/// Type alias for a function we can get headers by hash through.
pub type Headers<'a, H> = Fn(H256) -> Option<H> + 'a;

View File

@ -89,7 +89,7 @@ impl<M: WithBalances + WithRewards> Engine<M> for NullEngine<M>
for u in LiveBlock::uncles(&*block) {
let uncle_author = u.author();
let result_uncle_reward = (reward * U256::from(8 + u.number() - number)).shr(3);
rewards.push((*uncle_author, RewardKind::Uncle, result_uncle_reward));
rewards.push((*uncle_author, RewardKind::uncle(number, u.number()), result_uncle_reward));
}
block_reward::apply_block_rewards(&rewards, block, &self.machine)

View File

@ -19,7 +19,7 @@ use std::cmp;
use std::collections::BTreeMap;
use std::sync::Arc;
use hash::{KECCAK_EMPTY_LIST_RLP};
use engines::block_reward::{self, RewardKind};
use engines::block_reward::{self, BlockRewardContract, RewardKind};
use ethash::{self, quick_get_difficulty, slow_hash_block_number, EthashManager, OptimizeFor};
use ethereum_types::{H256, H64, U256, Address};
use unexpected::{OutOfBounds, Mismatch};
@ -124,6 +124,10 @@ pub struct EthashParams {
pub expip2_transition: u64,
/// EXPIP-2 duration limit
pub expip2_duration_limit: u64,
/// Block reward contract transition block.
pub block_reward_contract_transition: u64,
/// Block reward contract.
pub block_reward_contract: Option<BlockRewardContract>,
}
impl From<ethjson::spec::EthashParams> for EthashParams {
@ -154,6 +158,12 @@ impl From<ethjson::spec::EthashParams> for EthashParams {
eip649_reward: p.eip649_reward.map(Into::into),
expip2_transition: p.expip2_transition.map_or(u64::max_value(), Into::into),
expip2_duration_limit: p.expip2_duration_limit.map_or(30, Into::into),
block_reward_contract_transition: p.block_reward_contract_transition.map_or(0, Into::into),
block_reward_contract: match (p.block_reward_contract_code, p.block_reward_contract_address) {
(Some(code), _) => Some(BlockRewardContract::new_from_code(Arc::new(code.into()))),
(_, Some(address)) => Some(BlockRewardContract::new_from_address(address.into())),
(None, None) => None,
},
}
}
}
@ -231,6 +241,22 @@ impl Engine<EthereumMachine> for Arc<Ethash> {
let author = *LiveBlock::header(&*block).author();
let number = LiveBlock::header(&*block).number();
let rewards = match self.ethash_params.block_reward_contract {
Some(ref c) if number >= self.ethash_params.block_reward_contract_transition => {
let mut beneficiaries = Vec::new();
beneficiaries.push((author, RewardKind::Author));
for u in LiveBlock::uncles(&*block) {
let uncle_author = u.author();
beneficiaries.push((*uncle_author, RewardKind::uncle(number, u.number())));
}
let mut call = engines::default_system_or_code_call(&self.machine, block);
let rewards = c.reward(&beneficiaries, &mut call)?;
rewards.into_iter().map(|(author, amount)| (author, RewardKind::External, amount)).collect()
},
_ => {
let mut rewards = Vec::new();
// Applies EIP-649 reward.
@ -274,9 +300,13 @@ impl Engine<EthereumMachine> for Arc<Ethash> {
reward.shr(5)
};
rewards.push((*uncle_author, RewardKind::Uncle, result_uncle_reward));
rewards.push((*uncle_author, RewardKind::uncle(number, u.number()), result_uncle_reward));
}
rewards
},
};
block_reward::apply_block_rewards(&rewards, block, &self.machine)
}
@ -512,6 +542,8 @@ mod tests {
eip649_reward: None,
expip2_transition: u64::max_value(),
expip2_duration_limit: 30,
block_reward_contract: None,
block_reward_contract_transition: 0,
}
}

View File

@ -29,10 +29,10 @@ use header::{BlockNumber, Header, ExtendedHeader};
use spec::CommonParams;
use state::{CleanupMode, Substate};
use trace::{NoopTracer, NoopVMTracer, Tracer, ExecutiveTracer, RewardType, Tracing};
use transaction::{self, SYSTEM_ADDRESS, UnverifiedTransaction, SignedTransaction};
use transaction::{self, SYSTEM_ADDRESS, UNSIGNED_SENDER, UnverifiedTransaction, SignedTransaction};
use tx_filter::TransactionFilter;
use ethereum_types::{U256, Address};
use ethereum_types::{U256, H256, Address};
use rlp::Rlp;
use vm::{CallType, ActionParams, ActionValue, ParamsType};
use vm::{EnvInfo, Schedule, CreateContractAddress};
@ -122,6 +122,35 @@ impl EthereumMachine {
contract_address: Address,
gas: U256,
data: Option<Vec<u8>>,
) -> Result<Vec<u8>, Error> {
let (code, code_hash) = {
let state = block.state();
(state.code(&contract_address)?,
state.code_hash(&contract_address)?)
};
self.execute_code_as_system(
block,
Some(contract_address),
code,
code_hash,
gas,
data
)
}
/// Same as execute_as_system, but execute code directly. If contract address is None, use the null sender
/// address. If code is None, then this function has no effect. The call is executed without finalization, and does
/// not form a transaction.
pub fn execute_code_as_system(
&self,
block: &mut ExecutedBlock,
contract_address: Option<Address>,
code: Option<Arc<Vec<u8>>>,
code_hash: Option<H256>,
gas: U256,
data: Option<Vec<u8>>
) -> Result<Vec<u8>, Error> {
let env_info = {
let mut env_info = block.env_info();
@ -130,31 +159,27 @@ impl EthereumMachine {
};
let mut state = block.state_mut();
let params = ActionParams {
code_address: contract_address.clone(),
address: contract_address.clone(),
sender: SYSTEM_ADDRESS.clone(),
origin: SYSTEM_ADDRESS.clone(),
gas: gas,
code_address: contract_address.unwrap_or(UNSIGNED_SENDER),
address: contract_address.unwrap_or(UNSIGNED_SENDER),
sender: SYSTEM_ADDRESS,
origin: SYSTEM_ADDRESS,
gas,
gas_price: 0.into(),
value: ActionValue::Transfer(0.into()),
code: state.code(&contract_address)?,
code_hash: state.code_hash(&contract_address)?,
data: data,
code,
code_hash,
data,
call_type: CallType::Call,
params_type: ParamsType::Separate,
};
let schedule = self.schedule(env_info.number);
let mut ex = Executive::new(&mut state, &env_info, self, &schedule);
let mut substate = Substate::new();
let res = ex.call(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer);
let output = match res {
Ok(res) => res.return_data.to_vec(),
Err(e) => {
warn!("Encountered error on making system call: {}", e);
Vec::new()
}
};
let res = ex.call(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).map_err(|e| ::engines::EngineError::FailedSystemCall(format!("{}", e)))?;
let output = res.return_data.to_vec();
Ok(output)
}

View File

@ -16,8 +16,9 @@
//! Authority params deserialization.
use ethereum_types::Address;
use hash::Address;
use uint::Uint;
use bytes::Bytes;
use super::ValidatorSet;
/// Authority params deserialization.
@ -51,6 +52,9 @@ pub struct AuthorityRoundParams {
/// overrides the static block reward definition).
#[serde(rename="blockRewardContractAddress")]
pub block_reward_contract_address: Option<Address>,
/// Block reward code. This overrides the block reward contract address.
#[serde(rename="blockRewardContractCode")]
pub block_reward_contract_code: Option<Bytes>,
/// Block at which maximum uncle count should be considered.
#[serde(rename="maximumUncleCountTransition")]
pub maximum_uncle_count_transition: Option<Uint>,

View File

@ -17,6 +17,7 @@
//! Ethash params deserialization.
use uint::{self, Uint};
use bytes::Bytes;
use hash::Address;
/// Deserializable doppelganger of EthashParams.
@ -48,6 +49,16 @@ pub struct EthashParams {
/// Reward per block in wei.
#[serde(rename="blockReward")]
pub block_reward: Option<Uint>,
/// Block at which the block reward contract should start being used.
#[serde(rename="blockRewardContractTransition")]
pub block_reward_contract_transition: Option<Uint>,
/// Block reward contract address (setting the block reward contract
/// overrides all other block reward parameters).
#[serde(rename="blockRewardContractAddress")]
pub block_reward_contract_address: Option<Address>,
/// Block reward code. This overrides the block reward contract address.
#[serde(rename="blockRewardContractCode")]
pub block_reward_contract_code: Option<Bytes>,
/// See main EthashParams docs.
#[serde(rename="daoHardforkTransition")]
@ -183,7 +194,7 @@ mod tests {
let deserialized: Ethash = serde_json::from_str(s).unwrap();
assert_eq!(deserialized, Ethash {
params: EthashParams{
params: EthashParams {
minimum_difficulty: Uint(U256::from(0x020000)),
difficulty_bound_divisor: Uint(U256::from(0x0800)),
difficulty_increment_divisor: None,
@ -191,6 +202,9 @@ mod tests {
duration_limit: Some(Uint(U256::from(0x0d))),
homestead_transition: Some(Uint(U256::from(0x42))),
block_reward: Some(Uint(U256::from(0x100))),
block_reward_contract_address: None,
block_reward_contract_code: None,
block_reward_contract_transition: None,
dao_hardfork_transition: Some(Uint(U256::from(0x08))),
dao_hardfork_beneficiary: Some(Address(H160::from("0xabcabcabcabcabcabcabcabcabcabcabcabcabca"))),
dao_hardfork_accounts: Some(vec![
@ -256,6 +270,9 @@ mod tests {
duration_limit: None,
homestead_transition: None,
block_reward: None,
block_reward_contract_address: None,
block_reward_contract_code: None,
block_reward_contract_transition: None,
dao_hardfork_transition: None,
dao_hardfork_beneficiary: None,
dao_hardfork_accounts: None,