Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

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

Merged
merged 11 commits into from Aug 29, 2018
28 changes: 11 additions & 17 deletions ethcore/src/engines/authority_round/mod.rs
Expand Up @@ -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)),
Expand Down Expand Up @@ -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
Expand All @@ -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()
},
};

Expand Down
101 changes: 67 additions & 34 deletions ethcore/src/engines/block_reward.rs
Expand Up @@ -21,80 +21,107 @@ 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,
}
}
}

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,
}
}
}

/// 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)?;

Expand Down Expand Up @@ -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)],
Expand All @@ -139,7 +166,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();
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 RewardKind and RewardType – is it possible to unify them? That way this line could go away entirely I think?

Copy link
Contributor

Choose a reason for hiding this comment

The 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 note_rewards to take RewardKind and handle the conversion itself (only performing it if tracing is enabled)?

Copy link
Collaborator Author

@sorpaas sorpaas Aug 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a new issue on this #9399

(I would hope that we get the above done after #9360, otherwise it'll be a lot of duplicate interface changes.)

machine.note_rewards(block, &rewards)
machine.note_rewards(block, &rewards)
}

#[cfg(test)]
Expand All @@ -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]
Expand All @@ -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(),
);

Expand All @@ -172,30 +200,35 @@ 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),
("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)),
];

Expand Down
40 changes: 40 additions & 0 deletions ethcore/src/engines/mod.rs
Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The 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 SystemCall is supported SystemOrCodeCall should also be? (even if it's not exposed by any parameter).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are still using SystemCall in several other cases, but I do agree that we may eventually want to clean up this.


/// 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;

Expand Down
2 changes: 1 addition & 1 deletion ethcore/src/engines/null_engine.rs
Expand Up @@ -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)
Expand Down