Skip to content

Commit

Permalink
Implement EIP-1234
Browse files Browse the repository at this point in the history
Summary
=======

This implements EIP-1234 which updates the difficulty calculation and
the block reward for Constantinople.

More details
===========

Thus far we have been using Parity's chain config files to generate
configuration for a chain. We update those files to include the values
for Constantinople.

In this change, however, Parity updates not only some values in the json
configuration but also how they are stored. In particular, the block
rewards and the bomb delay factor can now be a single value or json
objects whose key is the block_number of the fork in question and
the value is the thing looked for. So, for example, we could find this
in a block_reward,

```
block_reward: {
 "0" => 300000000,
 "1700000" => 2000000
}
```

We make updates to our `Chain` module to account for this.

Update ethereum_common_tests

Run generate_state_tests

We update the state tests with tests that are still failing and the
passing percentages in the main README.

This change also updates chains from parity:

https://github.com/paritytech/parity-ethereum/tree/master/ethcore/res/ethereum
  • Loading branch information
MattMSumner committed Sep 25, 2018
1 parent a90ae7a commit 4024c64
Show file tree
Hide file tree
Showing 17 changed files with 2,825 additions and 2,630 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ Ethereum common tests are created for all clients to test against. We plan to pr
- [GeneralStateTests](https://github.com/ethereum/tests/tree/develop/GeneralStateTests) 1088/1113 = 97.8% passing
- [x] EIP158
- [BlockchainTests](https://github.com/ethereum/tests/tree/develop/BlockchainTests) (Includes GeneralStateTests) 1232/1233 = 99.9% passing
- [GeneralStateTests](https://github.com/ethereum/tests/tree/develop/GeneralStateTests) 1174/1180 = 99.5% passing
- [GeneralStateTests](https://github.com/ethereum/tests/tree/develop/GeneralStateTests) 1175/1181 = 99.5% passing
- [x] Byzantium
- [BlockchainTests](https://github.com/ethereum/tests/tree/develop/BlockchainTests) (Includes GeneralStateTests) 4915/4959 = 99.1% passing
- [GeneralStateTests](https://github.com/ethereum/tests/tree/develop/GeneralStateTests) 4741/4784 = 99.1% passing
- [GeneralStateTests](https://github.com/ethereum/tests/tree/develop/GeneralStateTests) 4756/4784 = 99.4% passing
- [ ] Constantinople: View the community [Constantinople Project Tracker](https://github.com/ethereum/pm/issues/53).

## Updating the Common test
Expand Down
34 changes: 11 additions & 23 deletions apps/blockchain/lib/blockchain/block.ex
Original file line number Diff line number Diff line change
Expand Up @@ -517,20 +517,20 @@ defmodule Blockchain.Block do
end

defp get_difficulty(block, parent_block, chain) do
homestead_block = chain.engine["Ethash"][:homestead_transition]
byzantium_block = chain.engine["Ethash"][:eip100b_transition]

cond do
Header.is_before_homestead?(block.header, homestead_block) ->
Header.get_frontier_difficulty(
Chain.after_bomb_delays?(chain, block.header.number) ->
delay_factor = Chain.bomb_delay_factor_for_block(chain, block.header.number)

Header.get_byzantium_difficulty(
block.header,
if(parent_block, do: parent_block.header, else: nil),
delay_factor,
chain.genesis[:difficulty],
chain.engine["Ethash"][:minimum_difficulty],
chain.engine["Ethash"][:difficulty_bound_divisor]
)

Header.is_before_byzantium?(block.header, byzantium_block) ->
Chain.after_homestead?(chain, block.header.number) ->
Header.get_homestead_difficulty(
block.header,
if(parent_block, do: parent_block.header, else: nil),
Expand All @@ -540,7 +540,7 @@ defmodule Blockchain.Block do
)

true ->
Header.get_byzantium_difficulty(
Header.get_frontier_difficulty(
block.header,
if(parent_block, do: parent_block.header, else: nil),
chain.genesis[:difficulty],
Expand Down Expand Up @@ -734,13 +734,11 @@ defmodule Blockchain.Block do
end

defp create_receipt(block_header, new_state, total_gas_used, logs, tx_status, chain) do
eip658_transition = chain.params[:eip658_transition]

state =
if Header.is_before_byzantium?(block_header, eip658_transition) do
new_state.root_hash
else
if Chain.after_byzantium?(chain, block_header.number) do
tx_status
else
new_state.root_hash
end

%Receipt{
Expand Down Expand Up @@ -860,7 +858,7 @@ defmodule Blockchain.Block do
do: block

def add_rewards(block, db, chain) do
base_reward = calculate_base_reward(block, chain)
base_reward = Chain.block_reward_for_block(chain, block.header.number)

state =
block
Expand All @@ -871,16 +869,6 @@ defmodule Blockchain.Block do
set_state(block, state)
end

defp calculate_base_reward(block, chain) do
byzantium_transition = chain.engine["Ethash"][:eip649_transition]

if Header.is_before_byzantium?(block.header, byzantium_transition) do
chain.engine["Ethash"][:block_reward]
else
chain.engine["Ethash"][:eip649_reward]
end
end

defp add_miner_reward(state, block, base_reward) do
ommer_reward = round(base_reward * length(block.ommers) / @block_reward_ommer_divisor)
reward = ommer_reward + base_reward
Expand Down
100 changes: 96 additions & 4 deletions apps/blockchain/lib/blockchain/chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ defmodule Blockchain.Chain do
minimum_difficulty: integer(),
difficulty_bound_divisor: integer(),
duration_limit: integer(),
block_reward: integer(),
block_rewards: [{integer(), integer()}],
homestead_transition: integer(),
eip649_reward: integer(),
eip100b_transition: integer(),
eip649_transition: integer()
eip649_transition: integer(),
difficulty_bomb_delays: [{integer(), integer()}]
}

@type params :: %{
Expand Down Expand Up @@ -185,22 +186,113 @@ defmodule Blockchain.Chain do
end
end

@doc """
Convenience function to determine whether a block number is after the
bomb delays introduced in Byzantium and Constantinople
"""
@spec after_bomb_delays?(t(), integer()) :: boolean()
def after_bomb_delays?(chain = %__MODULE__{}, block_number) do
bomb_delays = chain.engine["Ethash"][:difficulty_bomb_delays]

Enum.any?(bomb_delays, fn {hard_fork_number, _delay} ->
block_number >= hard_fork_number
end)
end

@doc """
Function to determine what the bomb delay is for a block number.
Note: This function should not be called on a block number that happens before
bomb delays. Before bomb delays were introduced, the difficulty calculation
was different and thus we do not expect a bomb delay at all.
"""
@spec bomb_delay_factor_for_block(t, integer()) :: integer()
def bomb_delay_factor_for_block(chain = %__MODULE__{}, block_number) do
bomb_delays = chain.engine["Ethash"][:difficulty_bomb_delays]

{_, delay} =
bomb_delays
|> Enum.sort(fn {k1, _}, {k2, _} -> k1 < k2 end)
|> Enum.take_while(fn {k, _} -> k <= block_number end)
|> List.last()

delay
end

@doc """
Determines the base reward for a block number. The reward changed was lowered
in Byzantium and again in Constantinople
"""
@spec block_reward_for_block(t, integer()) :: integer()
def block_reward_for_block(chain = %__MODULE__{}, block_number) do
{_k, reward} =
chain.engine["Ethash"][:block_rewards]
|> Enum.sort(fn {k, _}, {k2, _} -> k < k2 end)
|> Enum.take_while(fn {k, _} -> k <= block_number end)
|> List.last()

reward
end

@doc """
Helper function to determine if block number is after the homestead transition
based on the chain configuration.
"""
@spec after_homestead?(t, integer()) :: boolean()
def after_homestead?(chain, block_number) do
homestead_block = chain.engine["Ethash"][:homestead_transition]

block_number >= homestead_block
end

@doc """
Helper function to determine if block number is after the byzantium transition
based on the chain configuration.
"""
@spec after_byzantium?(t, integer()) :: boolean()
def after_byzantium?(chain, block_number) do
eip658_transition = chain.params[:eip658_transition]

block_number >= eip658_transition
end

@spec get_engine({String.t(), map}) :: {String.t(), engine()}
defp get_engine({engine, %{"params" => params}}) do
config = %{
minimum_difficulty: params["minimumDifficulty"] |> load_hex(),
difficulty_bound_divisor: params["difficultyBoundDivisor"] |> load_hex(),
duration_limit: params["durationLimit"] |> load_hex(),
block_reward: params["blockReward"] |> load_hex(),
block_rewards: params["blockReward"] |> parse_reward(),
homestead_transition: params["homesteadTransition"] |> load_hex(),
eip649_reward: params["eip649Reward"] |> load_hex(),
eip100b_transition: params["eip100bTransition"] |> load_hex(),
eip649_transition: params["eip649Transition"] |> load_hex()
eip649_transition: params["eip649Transition"] |> load_hex(),
difficulty_bomb_delays: params["difficultyBombDelays"] |> parse_bomb_delays()
}

{engine, config}
end

defp parse_reward(block_reward) when is_binary(block_reward) do
[{load_hex("0x00"), load_hex(block_reward)}]
end

defp parse_reward(block_rewards) do
Enum.map(block_rewards, fn {k, v} ->
{block_number, _} = Integer.parse(k)
{block_number, load_hex(v)}
end)
end

defp parse_bomb_delays(nil), do: []

defp parse_bomb_delays(bomb_delays) do
Enum.map(bomb_delays, fn {k, v} ->
{block_number, _} = Integer.parse(k)
{block_number, v}
end)
end

@spec get_params(map) :: params()
defp get_params(map) do
%{
Expand Down
5 changes: 5 additions & 0 deletions apps/blockchain/scripts/generate_blockchain_tests.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ defmodule GenerateBlockchainTests do
@base_path System.cwd() <> "/../../ethereum_common_tests/BlockchainTests/"
@allowed_forks ["Constantinople", "Byzantium", "Frontier", "Homestead", "EIP150", "EIP158"]
@byzantium_failing_tests_path System.cwd() <> "/test/support/byzantium_failing_tests.txt"
@constantinople_failing_tests_path System.cwd() <>
"/test/support/constantinople_failing_tests.txt"
@initial_pass_fail {[], []}
@number_of_test_groups 20
@ten_minutes 1000 * 60 * 10
Expand Down Expand Up @@ -100,6 +102,9 @@ defmodule GenerateBlockchainTests do
"Byzantium" ->
File.open!(@byzantium_failing_tests_path, [:write])

"Constantinople" ->
File.open!(@constantinople_failing_tests_path, [:write])

_ ->
:stdio
end
Expand Down
5 changes: 4 additions & 1 deletion apps/blockchain/test/blockchain/block_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ defmodule Blockchain.BlockTest do
ommer = <<0x06::160>>
state = MerklePatriciaTree.Trie.new(db)
chain = Chain.load_chain(:ropsten)
byzantium_block_number = chain.engine["Ethash"][:eip649_transition]

{byzantium_block_number, _} =
chain.engine["Ethash"][:block_rewards]
|> Enum.at(1)

block = %Blockchain.Block{
header: %Header{
Expand Down
64 changes: 64 additions & 0 deletions apps/blockchain/test/blockchain/chain_test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,68 @@
defmodule Blockchain.ChainTest do
use ExUnit.Case, async: true
doctest Blockchain.Chain

alias Blockchain.Chain

describe "after_bomb_delays?/2" do
test "checks if the block number is after any of the bomb delays were introduced" do
byzantium_transition = 4_370_000

chain = Chain.load_chain(:foundation)

assert Chain.after_bomb_delays?(chain, byzantium_transition)
assert Chain.after_bomb_delays?(chain, byzantium_transition + 1)
refute Chain.after_bomb_delays?(chain, byzantium_transition - 1)
end
end

describe "bomb_delay_factor_for_block/2" do
test "returns the bomb delay for the block number" do
byzantium_transition = 4_370_000

chain = Chain.load_chain(:foundation)

assert 3_000_000 == Chain.bomb_delay_factor_for_block(chain, byzantium_transition)
assert 3_000_000 == Chain.bomb_delay_factor_for_block(chain, byzantium_transition + 1)
end
end

describe "block_reward_for_block/2" do
test "returns the block reward based on the block number" do
byzantium_transition = 4_370_000
three_eth = 3_000_000_000_000_000_000
five_eth = 5_000_000_000_000_000_000

chain = Chain.load_chain(:foundation)

assert three_eth == Chain.block_reward_for_block(chain, byzantium_transition)
assert three_eth == Chain.block_reward_for_block(chain, byzantium_transition + 1)
assert five_eth == Chain.block_reward_for_block(chain, 0)
assert five_eth == Chain.block_reward_for_block(chain, 1)
end
end

describe "after_homestead?/2" do
test "checks whether or not a block number is after the homestead transition" do
homestead_transition = 1_150_000

chain = Chain.load_chain(:foundation)

assert Chain.after_homestead?(chain, homestead_transition)
assert Chain.after_homestead?(chain, homestead_transition + 1)
refute Chain.after_homestead?(chain, homestead_transition - 1)
end
end

describe "after_byzantium?/2" do
test "checks whether or not a block number is after the byzatium transition" do
byzantium_transition = 4_370_000

chain = Chain.load_chain(:foundation)

assert Chain.after_byzantium?(chain, byzantium_transition)
assert Chain.after_byzantium?(chain, byzantium_transition + 1)
refute Chain.after_byzantium?(chain, byzantium_transition - 1)
end
end
end
Loading

0 comments on commit 4024c64

Please sign in to comment.