-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add block reward contract config to ethash and allow off-chain contracts #9312
Changes from 7 commits
cf6135b
88d1eae
d09d031
6c4d2c4
6c03980
5c379d4
0ea2236
ac0d3e1
56a409c
0dd53bf
c494b79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ use ethjson; | |
| use machine::{AuxiliaryData, Call, EthereumMachine}; | ||
| use hash::keccak; | ||
| use header::{Header, BlockNumber, ExtendedHeader}; | ||
| use super::SystemOrCodeCallKind; | ||
| use super::signer::EngineSigner; | ||
| use super::validator_set::{ValidatorSet, SimpleList, new_validator_set}; | ||
| use self::finality::RollingFinality; | ||
|
|
@@ -100,7 +101,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 +1048,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 +1074,47 @@ 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), | ||
| ); | ||
| let result = match to { | ||
| SystemOrCodeCallKind::Address(address) => { | ||
| self.machine.execute_as_system( | ||
| block, | ||
| address, | ||
| U256::max_value(), | ||
| Some(data), | ||
| ) | ||
| }, | ||
| SystemOrCodeCallKind::Code(code, code_hash) => { | ||
| self.machine.execute_code_as_system( | ||
| block, | ||
| None, | ||
| Some(code), | ||
| Some(code_hash), | ||
| U256::max_value(), | ||
| Some(data), | ||
| ) | ||
| }, | ||
| }; | ||
|
|
||
| result.map_err(|e| format!("{}", e)) | ||
| }; | ||
|
|
||
| let rewards = c.reward(&benefactors, &mut call)?; | ||
| let rewards = c.reward(&beneficiaries, &mut call)?; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 at this. I think the wiki also uses "benefactors" instead of "beneficiaries" which was very confusing to me when I read it. |
||
| 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() | ||
| }, | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,80 +21,104 @@ 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 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) without any reference of block difference. This is used by all except Ethash engine's reward contract. | ||
| Uncle, | ||
|
||
| /// Reward attributed to the block uncle(s) with given difference. This is used by Ethash engine's reward contract. | ||
| UncleWithDepth(u8), | ||
| } | ||
|
|
||
| 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 => 1, | ||
| RewardKind::UncleWithDepth(depth) => 100 + depth as u16, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| 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 | RewardKind::UncleWithDepth(_) => | ||
| trace::RewardType::Uncle, | ||
| RewardKind::EmptyStep => trace::RewardType::EmptyStep, | ||
| RewardKind::External => trace::RewardType::External, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// 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 +151,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)], | ||
|
|
@@ -139,7 +163,7 @@ pub fn apply_block_rewards<M: Machine + WithBalances + WithRewards>( | |
| } | ||
|
|
||
| let rewards: Vec<_> = rewards.into_iter().map(|&(a, k, r)| (a, k.into(), r)).collect(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is a little awkward. It would be nice to do this work only if tracing is enabled. I'm not sure what the reason is for having both
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's probably possible to unify them, although now they're a bit different since the tracing type doesn't distinguish between uncle depth. Although maybe we can change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| machine.note_rewards(block, &rewards) | ||
| machine.note_rewards(block, &rewards) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
|
|
@@ -149,6 +173,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 +186,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,27 +197,32 @@ mod test { | |
| vec![], | ||
| ).unwrap(); | ||
|
|
||
| let result = machine.execute_as_system( | ||
| block.block_mut(), | ||
| to, | ||
| U256::max_value(), | ||
| Some(data), | ||
| ); | ||
| 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), | ||
| ("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)), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -133,6 +133,18 @@ 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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think it makes sense to remove this type? It seems like everywhere
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we are still using |
||
|
|
||
| /// 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), | ||
| } | ||
|
|
||
| /// Type alias for a function we can get headers by hash through. | ||
| pub type Headers<'a, H> = Fn(H256) -> Option<H> + 'a; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this comment not relevant anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the original comment didn't make sense in this case (I copied it from some other usage of
execute_code_as_system😛). It probably makes sense to execute the block reward contract with unbounded gas. Otherwise, if we cap it and it goes out of gas what do we default to? No rewards for that block? The block reward contract is a critical part of consensus so I think the framework should assume it is correct, in this case we should assume that it will terminate, if it doesn't your chain is broken.