From 6d83dc58009d707d888d8d3f1c0aab4dec3cc5d9 Mon Sep 17 00:00:00 2001 From: Christoph Burgdorf Date: Wed, 19 Sep 2018 12:39:08 +0200 Subject: [PATCH 1/2] Implement EIP1234 (adjust rewards + difficulty bomb) --- eth/vm/forks/byzantium/__init__.py | 18 +++++++---- eth/vm/forks/byzantium/headers.py | 41 ++++++++++++++++++++---- eth/vm/forks/constantinople/__init__.py | 21 +++++++++++- eth/vm/forks/constantinople/constants.py | 5 +++ eth/vm/forks/constantinople/headers.py | 13 ++++++++ 5 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 eth/vm/forks/constantinople/headers.py diff --git a/eth/vm/forks/byzantium/__init__.py b/eth/vm/forks/byzantium/__init__.py index 4c4d6b8cf4..f709b422a4 100644 --- a/eth/vm/forks/byzantium/__init__.py +++ b/eth/vm/forks/byzantium/__init__.py @@ -2,6 +2,10 @@ Type, ) +from cytoolz import ( + curry, +) + from eth_utils import ( encode_hex, ValidationError, @@ -44,6 +48,13 @@ def make_byzantium_receipt(base_header, transaction, computation, state): return frontier_receipt.copy(state_root=status_code) +@curry +def get_uncle_reward(block_reward, block_number, uncle): + block_number_delta = block_number - uncle.block_number + validate_lte(block_number_delta, MAX_UNCLE_DEPTH) + return (8 - block_number_delta) * block_reward // 8 + + EIP658_STATUS_CODES = { EIP658_TRANSACTION_STATUS_CODE_SUCCESS, EIP658_TRANSACTION_STATUS_CODE_FAILURE, @@ -63,6 +74,7 @@ class ByzantiumVM(SpuriousDragonVM): compute_difficulty = staticmethod(compute_byzantium_difficulty) configure_header = configure_byzantium_header make_receipt = staticmethod(make_byzantium_receipt) + get_uncle_reward = staticmethod(get_uncle_reward(EIP649_BLOCK_REWARD)) @classmethod def validate_receipt(cls, receipt: Receipt) -> None: @@ -80,9 +92,3 @@ def validate_receipt(cls, receipt: Receipt) -> None: @staticmethod def get_block_reward(): return EIP649_BLOCK_REWARD - - @staticmethod - def get_uncle_reward(block_number, uncle): - block_number_delta = block_number - uncle.block_number - validate_lte(block_number_delta, MAX_UNCLE_DEPTH) - return (8 - block_number_delta) * EIP649_BLOCK_REWARD // 8 diff --git a/eth/vm/forks/byzantium/headers.py b/eth/vm/forks/byzantium/headers.py index 841cc65abb..080914da7e 100644 --- a/eth/vm/forks/byzantium/headers.py +++ b/eth/vm/forks/byzantium/headers.py @@ -1,3 +1,10 @@ +from typing import ( + Any, + Callable, +) +from cytoolz import ( + curry, +) from eth.constants import ( EMPTY_UNCLE_HASH, DIFFICULTY_ADJUSTMENT_DENOMINATOR, @@ -5,6 +12,9 @@ BOMB_EXPONENTIAL_PERIOD, BOMB_EXPONENTIAL_FREE_PERIODS, ) +from eth.rlp.headers import ( + BlockHeader, +) from eth.utils.db import ( get_parent_header, ) @@ -12,6 +22,9 @@ validate_gt, validate_header_params_for_configuration, ) +from eth.vm.base import ( + BaseVM +) from eth.vm.forks.frontier.headers import ( create_frontier_header_from_parent, ) @@ -21,7 +34,11 @@ ) -def compute_byzantium_difficulty(parent_header, timestamp): +@curry +def compute_difficulty( + bomb_delay: int, + parent_header: BlockHeader, + timestamp: int) -> int: """ https://github.com/ethereum/EIPs/issues/100 """ @@ -46,7 +63,7 @@ def compute_byzantium_difficulty(parent_header, timestamp): num_bomb_periods = ( max( 0, - parent_header.block_number + 1 - 3000000, + parent_header.block_number + 1 - bomb_delay, ) // BOMB_EXPONENTIAL_PERIOD ) - BOMB_EXPONENTIAL_FREE_PERIODS @@ -56,27 +73,39 @@ def compute_byzantium_difficulty(parent_header, timestamp): return difficulty -def create_byzantium_header_from_parent(parent_header, **header_params): +@curry +def create_header_from_parent(difficulty_fn: Callable[[BlockHeader, int], int], + parent_header: BlockHeader, + **header_params: Any) -> BlockHeader: + if 'difficulty' not in header_params: header_params.setdefault('timestamp', parent_header.timestamp + 1) - header_params['difficulty'] = compute_byzantium_difficulty( + header_params['difficulty'] = difficulty_fn( parent_header=parent_header, timestamp=header_params['timestamp'], ) return create_frontier_header_from_parent(parent_header, **header_params) -def configure_byzantium_header(vm, **header_params): +@curry +def configure_header(difficulty_fn: Callable[[BlockHeader, int], int], + vm: BaseVM, + **header_params: Any) -> BlockHeader: validate_header_params_for_configuration(header_params) with vm.block.header.build_changeset(**header_params) as changeset: if 'timestamp' in header_params and changeset.block_number > 0: parent_header = get_parent_header(changeset.build_rlp(), vm.chaindb) - changeset.difficulty = compute_byzantium_difficulty( + changeset.difficulty = difficulty_fn( parent_header, header_params['timestamp'], ) header = changeset.commit() return header + + +compute_byzantium_difficulty = compute_difficulty(3000000) +create_byzantium_header_from_parent = create_header_from_parent(compute_byzantium_difficulty) +configure_byzantium_header = configure_header(compute_byzantium_difficulty) diff --git a/eth/vm/forks/constantinople/__init__.py b/eth/vm/forks/constantinople/__init__.py index 9ac32eccbc..552df68c97 100644 --- a/eth/vm/forks/constantinople/__init__.py +++ b/eth/vm/forks/constantinople/__init__.py @@ -3,10 +3,19 @@ ) from eth.rlp.blocks import BaseBlock # noqa: F401 -from eth.vm.forks.byzantium import ByzantiumVM +from eth.vm.forks.byzantium import ( + ByzantiumVM, + get_uncle_reward, +) from eth.vm.state import BaseState # noqa: F401 from .blocks import ConstantinopleBlock +from .constants import EIP1234_BLOCK_REWARD +from .headers import ( + compute_constantinople_difficulty, + configure_constantinople_header, + create_constantinople_header_from_parent, +) from .state import ConstantinopleState @@ -17,3 +26,13 @@ class ConstantinopleVM(ByzantiumVM): # classes block_class = ConstantinopleBlock # type: Type[BaseBlock] _state_class = ConstantinopleState # type: Type[BaseState] + + # Methods + create_header_from_parent = staticmethod(create_constantinople_header_from_parent) + compute_difficulty = staticmethod(compute_constantinople_difficulty) + configure_header = configure_constantinople_header + get_uncle_reward = staticmethod(get_uncle_reward(EIP1234_BLOCK_REWARD)) + + @staticmethod + def get_block_reward(): + return EIP1234_BLOCK_REWARD diff --git a/eth/vm/forks/constantinople/constants.py b/eth/vm/forks/constantinople/constants.py index 09b76e041a..0ba9138d13 100644 --- a/eth/vm/forks/constantinople/constants.py +++ b/eth/vm/forks/constantinople/constants.py @@ -1 +1,6 @@ +from eth_utils import denoms + + GAS_EXTCODEHASH_EIP1052 = 400 + +EIP1234_BLOCK_REWARD = 2 * denoms.ether diff --git a/eth/vm/forks/constantinople/headers.py b/eth/vm/forks/constantinople/headers.py new file mode 100644 index 0000000000..62d9f9ee1f --- /dev/null +++ b/eth/vm/forks/constantinople/headers.py @@ -0,0 +1,13 @@ +from eth.vm.forks.byzantium.headers import ( + configure_header, + create_header_from_parent, + compute_difficulty, +) + + +compute_constantinople_difficulty = compute_difficulty(5000000) + +create_constantinople_header_from_parent = create_header_from_parent( + compute_constantinople_difficulty +) +configure_constantinople_header = configure_header(compute_constantinople_difficulty) From ba4eca93014330b419ef02fc4c7bae7619a60efa Mon Sep 17 00:00:00 2001 From: Christoph Burgdorf Date: Fri, 21 Sep 2018 10:13:02 +0200 Subject: [PATCH 2/2] Add tests to ensure rewards work as expected --- tests/core/vm/test_rewards.py | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/core/vm/test_rewards.py diff --git a/tests/core/vm/test_rewards.py b/tests/core/vm/test_rewards.py new file mode 100644 index 0000000000..b24323b0fb --- /dev/null +++ b/tests/core/vm/test_rewards.py @@ -0,0 +1,84 @@ +import pytest + +from eth_utils import ( + to_wei +) + +from eth.chains.base import ( + MiningChain +) +from eth.tools.builder.chain import ( + at_block_number, + build, + disable_pow_check, + mine_block, + byzantium_at, + frontier_at, + homestead_at, + spurious_dragon_at, + tangerine_whistle_at, + constantinople_at, + genesis, +) + + +@pytest.mark.parametrize( + 'vm_fn, miner_1_balance, miner_2_balance', + ( + (frontier_at, 15.15625, 4.375), + (homestead_at, 15.15625, 4.375), + (tangerine_whistle_at, 15.15625, 4.375), + (spurious_dragon_at, 15.15625, 4.375), + (byzantium_at, 9.09375, 2.625), + (constantinople_at, 6.0625, 1.75), + ) +) +def test_rewards(vm_fn, miner_1_balance, miner_2_balance): + + OTHER_MINER_ADDRESS = 20 * b'\x01' + TOTAL_BLOCKS_CANONICAL_CHAIN = 3 + + chain = build( + MiningChain, + vm_fn(0), + disable_pow_check(), + genesis(), + mine_block(), # 1 + mine_block(), # 2 + ) + + fork_chain = build( + chain, + at_block_number(1), + mine_block(extra_data=b'fork-it!', coinbase=OTHER_MINER_ADDRESS), # fork 2 + ) + + # we don't use canonical head here because the fork chain is non-canonical. + uncle = fork_chain.get_block_header_by_hash(fork_chain.header.parent_hash) + assert uncle.block_number == 2 + assert uncle != chain.get_canonical_head() + + chain = build( + chain, + mine_block(uncles=[uncle]), # 3 + ) + + header = chain.get_canonical_head() + block = chain.get_block_by_hash(header.hash) + assert len(block.uncles) == 1 + assert block.uncles[0] == uncle + + vm = chain.get_vm() + coinbase_balance = vm.state.account_db.get_balance(block.header.coinbase) + other_miner_balance = vm.state.account_db.get_balance(uncle.coinbase) + + # We first test if the balance matches what we would determine + # if we made all the API calls involved ourselves. + assert coinbase_balance == (vm.get_block_reward() * + TOTAL_BLOCKS_CANONICAL_CHAIN + + vm.get_nephew_reward()) + assert other_miner_balance == vm.get_uncle_reward(block.number, uncle) + + # But we also ensure the balance matches the numbers that we calculated on paper + assert coinbase_balance == to_wei(miner_1_balance, 'ether') + assert other_miner_balance == to_wei(miner_2_balance, 'ether')