Current section
Files
Jump to
Current section
Files
lib/block/header.ex
defmodule Block.Header do
@moduledoc """
This structure codifies the header of a block in the blockchain.
"""
@empty_trie MerklePatriciaTree.Trie.empty_trie_root_hash
@empty_keccak [] |> ExRLP.encode |> :keccakf1600.sha3_256
defstruct [
parent_hash: nil, # Hp P(BH)Hr
ommers_hash: @empty_keccak, # Ho KEC(RLP(L∗H(BU)))
beneficiary: nil, # Hc
state_root: @empty_trie, # Hr TRIE(LS(Π(σ, B)))
transactions_root: @empty_trie, # Ht TRIE({∀i < kBTk, i ∈ P : p(i, LT (BT[i]))})
receipts_root: @empty_trie, # He TRIE({∀i < kBRk, i ∈ P : p(i, LR(BR[i]))})
logs_bloom: <<0::2048>>, # Hb bloom
difficulty: nil, # Hd
number: nil, # Hi
gas_limit: 0, # Hl
gas_used: 0, # Hg
timestamp: nil, # Hs
extra_data: <<>>, # Hx
mix_hash: nil, # Hm
nonce: nil, # Hn
]
# As defined in Eq.(35)
@type t :: %__MODULE__{
parent_hash: EVM.hash,
ommers_hash: EVM.trie_root,
beneficiary: EVM.address,
state_root: EVM.trie_root,
transactions_root: EVM.trie_root,
receipts_root: EVM.trie_root,
logs_bloom: binary(), # TODO
difficulty: integer() | nil,
number: integer() | nil,
gas_limit: EVM.val,
gas_used: EVM.val,
timestamp: EVM.timestamp | nil,
extra_data: binary(),
mix_hash: EVM.hash | nil,
nonce: <<_::64>> | nil, # TODO: 64-bit hash?
}
# The start of the Homestead block, as defined in Eq.(13) of the Yellow Paper (N_H)
@homestead_block 1_150_000
@initial_difficulty 131_072 # d_0 from Eq.(40)
@minimum_difficulty @initial_difficulty # Mimics d_0 in Eq.(39), but variable on different chains
@difficulty_bound_divisor 2048
@max_extra_data_bytes 32 # Eq.(58)
@gas_limit_bound_divisor 1024 # Constant from Eq.(45) and Eq.(46)
@min_gas_limit 125_000 # Eq.(47)
@doc """
Returns the block that defines the start of Homestead.
This should be a constant, but it's configurable on different
chains, and as such, as allow you to pass that configuration
variable (which ends up making this the identity function, if so).
"""
@spec homestead(integer()) :: integer()
def homestead(homestead_block \\ @homestead_block), do: homestead_block
@doc """
This functions encode a header into a value that can
be RLP encoded. This is defined as L_H Eq.(32) in the Yellow Paper.
## Examples
iex> Block.Header.serialize(%Block.Header{parent_hash: <<1::256>>, ommers_hash: <<2::256>>, beneficiary: <<3::160>>, state_root: <<4::256>>, transactions_root: <<5::256>>, receipts_root: <<6::256>>, logs_bloom: <<>>, difficulty: 5, number: 1, gas_limit: 5, gas_used: 3, timestamp: 6, extra_data: "Hi mom", mix_hash: <<7::256>>, nonce: <<8::64>>})
[<<1::256>>, <<2::256>>, <<3::160>>, <<4::256>>, <<5::256>>, <<6::256>>, <<>>, 5, 1, 5, 3, 6, "Hi mom", <<7::256>>, <<8::64>>]
"""
@spec serialize(t) :: ExRLP.t
def serialize(h) do
[
h.parent_hash,
h.ommers_hash,
h.beneficiary,
h.state_root,
h.transactions_root,
h.receipts_root,
h.logs_bloom,
h.difficulty,
(if h.number == 0, do: <<>>, else: h.number),
h.gas_limit,
(if h.number == 0, do: <<>>, else: h.gas_used),
h.timestamp,
h.extra_data,
h.mix_hash,
h.nonce
]
end
@doc """
Deserializes a block header from an RLP encodable structure.
This effectively undoes the encoding defined in L_H Eq.(32) of the
Yellow Paper.
## Examples
iex> Block.Header.deserialize([<<1::256>>, <<2::256>>, <<3::160>>, <<4::256>>, <<5::256>>, <<6::256>>, <<>>, <<5>>, <<1>>, <<5>>, <<3>>, <<6>>, "Hi mom", <<7::256>>, <<8::64>>])
%Block.Header{parent_hash: <<1::256>>, ommers_hash: <<2::256>>, beneficiary: <<3::160>>, state_root: <<4::256>>, transactions_root: <<5::256>>, receipts_root: <<6::256>>, logs_bloom: <<>>, difficulty: 5, number: 1, gas_limit: 5, gas_used: 3, timestamp: 6, extra_data: "Hi mom", mix_hash: <<7::256>>, nonce: <<8::64>>}
"""
@spec deserialize(ExRLP.t) :: t
def deserialize(rlp) do
[
parent_hash,
ommers_hash,
beneficiary,
state_root,
transactions_root,
receipts_root,
logs_bloom,
difficulty,
number,
gas_limit,
gas_used,
timestamp,
extra_data,
mix_hash,
nonce
] = rlp
%__MODULE__{
parent_hash: parent_hash,
ommers_hash: ommers_hash,
beneficiary: beneficiary,
state_root: state_root,
transactions_root: transactions_root,
receipts_root: receipts_root,
logs_bloom: logs_bloom,
difficulty: :binary.decode_unsigned(difficulty),
number: :binary.decode_unsigned(number),
gas_limit: :binary.decode_unsigned(gas_limit),
gas_used: :binary.decode_unsigned(gas_used),
timestamp: :binary.decode_unsigned(timestamp),
extra_data: extra_data,
mix_hash: mix_hash,
nonce: nonce,
}
end
@doc """
Computes hash of a block header, which is simply the hash of the serialized block header.
This is defined in Eq.(37) of the Yellow Paper.
## Examples
iex> %Block.Header{number: 5, parent_hash: <<1, 2, 3>>, beneficiary: <<2, 3, 4>>, difficulty: 100, timestamp: 11, mix_hash: <<1>>, nonce: <<2>>}
...> |> Block.Header.hash()
<<78, 28, 127, 10, 192, 253, 127, 239, 254, 179, 39, 34, 245, 44, 152, 98, 128, 71, 238, 155, 100, 161, 199, 71, 243, 223, 172, 191, 74, 99, 128, 63>>
iex> %Block.Header{number: 0, parent_hash: <<1, 2, 3>>, beneficiary: <<2, 3, 4>>, difficulty: 100, timestamp: 11, mix_hash: <<1>>, nonce: <<2>>}
...> |> Block.Header.hash()
<<218, 225, 46, 241, 196, 160, 136, 96, 109, 216, 73, 167, 92, 174, 91, 228, 85, 112, 234, 129, 99, 200, 158, 61, 223, 166, 165, 132, 187, 24, 142, 193>>
"""
@spec hash(t) :: EVM.hash
def hash(header) do
header |> serialize() |> ExRLP.encode |> :keccakf1600.sha3_256()
end
@doc """
Returns true if a given block is before the
Homestead block.
## Examples
iex> Block.Header.is_before_homestead?(%Block.Header{number: 5})
true
iex> Block.Header.is_before_homestead?(%Block.Header{number: 5_000_000})
false
iex> Block.Header.is_before_homestead?(%Block.Header{number: 1_150_000})
false
iex> Block.Header.is_before_homestead?(%Block.Header{number: 5}, 6)
true
iex> Block.Header.is_before_homestead?(%Block.Header{number: 5}, 4)
false
"""
@spec is_before_homestead?(t, integer()) :: boolean()
def is_before_homestead?(h, homestead_block \\ @homestead_block) do
h.number < homestead_block
end
@doc """
Returns true if a given block is at or after the
Homestead block.
## Examples
iex> Block.Header.is_after_homestead?(%Block.Header{number: 5})
false
iex> Block.Header.is_after_homestead?(%Block.Header{number: 5_000_000})
true
iex> Block.Header.is_after_homestead?(%Block.Header{number: 1_150_000})
true
iex> Block.Header.is_after_homestead?(%Block.Header{number: 5}, 6)
false
"""
@spec is_after_homestead?(t, integer()) :: boolean()
def is_after_homestead?(h, homestead_block \\ @homestead_block), do: not is_before_homestead?(h, homestead_block)
@doc """
Returns true if the block header is valid. This defines
Eq.(50), Eq.(51), Eq.(52), Eq.(53), Eq.(54), Eq.(55),
Eq.(56), Eq.(57) and Eq.(58) of the Yellow Paper, commonly
referred to as V(H).
# TODO: Add proof of work check
## Examples
iex> Block.Header.is_valid?(%Block.Header{number: 0, difficulty: 131_072, gas_limit: 200_000}, nil)
:valid
iex> Block.Header.is_valid?(%Block.Header{number: 0, difficulty: 5, gas_limit: 5}, nil, true)
{:invalid, [:invalid_difficulty, :invalid_gas_limit]}
iex> Block.Header.is_valid?(%Block.Header{number: 1, difficulty: 131_136, gas_limit: 200_000, timestamp: 65}, %Block.Header{number: 0, difficulty: 131_072, gas_limit: 200_000, timestamp: 55})
:valid
iex> Block.Header.is_valid?(%Block.Header{number: 1, difficulty: 131_000, gas_limit: 200_000, timestamp: 65}, %Block.Header{number: 0, difficulty: 131_072, gas_limit: 200_000, timestamp: 55}, true)
{:invalid, [:invalid_difficulty]}
iex> Block.Header.is_valid?(%Block.Header{number: 1, difficulty: 131_136, gas_limit: 200_000, timestamp: 45}, %Block.Header{number: 0, difficulty: 131_072, gas_limit: 200_000, timestamp: 55})
{:invalid, [:child_timestamp_invalid]}
iex> Block.Header.is_valid?(%Block.Header{number: 1, difficulty: 131_136, gas_limit: 300_000, timestamp: 65}, %Block.Header{number: 0, difficulty: 131_072, gas_limit: 200_000, timestamp: 55})
{:invalid, [:invalid_gas_limit]}
iex> Block.Header.is_valid?(%Block.Header{number: 2, difficulty: 131_136, gas_limit: 200_000, timestamp: 65}, %Block.Header{number: 0, difficulty: 131_072, gas_limit: 200_000, timestamp: 55})
{:invalid, [:child_number_invalid]}
iex> Block.Header.is_valid?(%Block.Header{number: 1, difficulty: 131_136, gas_limit: 200_000, timestamp: 65, extra_data: "0123456789012345678901234567890123456789"}, %Block.Header{number: 0, difficulty: 131_072, gas_limit: 200_000, timestamp: 55})
{:invalid, [:extra_data_too_large]}
# TODO: Add tests for setting homestead_block
# TODO: Add tests for setting initial_difficulty
# TODO: Add tests for setting minimum_difficulty
# TODO: Add tests for setting difficulty_bound_divisor
# TODO: Add tests for setting gas_limit_bound_divisor
# TODO: Add tests for setting min_gas_limit
"""
@spec is_valid?(t, t | nil, integer(), integer(), integer(), integer(), integer(), integer()) :: :valid | {:invalid, [atom()]}
def is_valid?(header, parent_header, homestead_block \\ @homestead_block, initial_difficulty \\ @initial_difficulty, minimum_difficulty \\ @minimum_difficulty, difficulty_bound_divisor \\ @difficulty_bound_divisor, gas_limit_bound_divisor \\ @gas_limit_bound_divisor, min_gas_limit \\ @min_gas_limit) do
parent_gas_limit = if parent_header, do: parent_header.gas_limit, else: nil
errors = [] ++
(if header.difficulty == get_difficulty(header, parent_header, initial_difficulty, minimum_difficulty, difficulty_bound_divisor, homestead_block), do: [], else: [:invalid_difficulty]) ++ # Eq.(51)
(if header.gas_used <= header.gas_limit, do: [], else: [:exceeded_gas_limit]) ++ # Eq.(52)
(if is_gas_limit_valid?(header.gas_limit, parent_gas_limit, gas_limit_bound_divisor, min_gas_limit), do: [], else: [:invalid_gas_limit]) ++ # Eq.(53), Eq.(54) and Eq.(55)
(if is_nil(parent_header) or header.timestamp > parent_header.timestamp, do: [], else: [:child_timestamp_invalid]) ++ # Eq.(56)
(if header.number == 0 or header.number == parent_header.number + 1, do: [], else: [:child_number_invalid]) ++ # Eq.(57)
(if byte_size(header.extra_data) <= @max_extra_data_bytes, do: [], else: [:extra_data_too_large])
case errors do
[] -> :valid
_ -> {:invalid, errors}
end
end
@doc """
Returns the total available gas left for all transactions in
this block. This is the total gas limit minus the gas used
in transactions.
## Examples
iex> Block.Header.available_gas(%Block.Header{gas_limit: 50_000, gas_used: 30_000})
20_000
"""
@spec available_gas(t) :: EVM.Gas.t
def available_gas(header) do
header.gas_limit - header.gas_used
end
@doc """
Calculates the difficulty of a new block header. This implements Eq.(39),
Eq.(40), Eq.(41), Eq.(42), Eq.(43) and Eq.(44) of the Yellow Paper.
## Examples
iex> Block.Header.get_difficulty(
...> %Block.Header{number: 0, timestamp: 55},
...> nil
...> )
131_072
iex> Block.Header.get_difficulty(
...> %Block.Header{number: 1, timestamp: 1479642530},
...> %Block.Header{number: 0, timestamp: 0, difficulty: 1_048_576}
...> )
1_048_064
iex> Block.Header.get_difficulty(
...> %Block.Header{number: 33, timestamp: 66},
...> %Block.Header{number: 32, timestamp: 55, difficulty: 300_000}
...> )
300_146
iex> Block.Header.get_difficulty(
...> %Block.Header{number: 33, timestamp: 88},
...> %Block.Header{number: 32, timestamp: 55, difficulty: 300_000}
...> )
299_854
# TODO: Is this right? These numbers are quite a jump
iex> Block.Header.get_difficulty(
...> %Block.Header{number: 3_000_001, timestamp: 66},
...> %Block.Header{number: 3_000_000, timestamp: 55, difficulty: 300_000}
...> )
268_735_456
iex> Block.Header.get_difficulty(
...> %Block.Header{number: 3_000_001, timestamp: 155},
...> %Block.Header{number: 3_000_000, timestamp: 55, difficulty: 300_000}
...> )
268_734_142
Test actual Ropsten genesis block
iex> Block.Header.get_difficulty(
...> %Block.Header{number: 0, timestamp: 0},
...> nil,
...> 0x100000,
...> 0x020000,
...> 0x0800,
...> 0
...> )
1_048_576
# Test actual Ropsten first block
iex> Block.Header.get_difficulty(
...> %Block.Header{number: 1, timestamp: 1_479_642_530},
...> %Block.Header{number: 0, timestamp: 0, difficulty: 1_048_576},
...> 0x100000,
...> 0x020000,
...> 0x0800,
...> 0
...> )
997_888
"""
@spec get_difficulty(t, t | nil, integer()) :: integer()
def get_difficulty(header, parent_header, initial_difficulty \\ @initial_difficulty, minimum_difficulty \\ @minimum_difficulty, difficulty_bound_divisor \\ @difficulty_bound_divisor, homestead_block \\ @homestead_block) do
cond do
header.number == 0 -> initial_difficulty
is_before_homestead?(header, homestead_block) ->
# Find the delta from parent block
difficulty_delta = difficulty_x(parent_header.difficulty, difficulty_bound_divisor) * difficulty_s1(header, parent_header) + difficulty_e(header)
# Add delta to parent block
next_difficulty = parent_header.difficulty + difficulty_delta
# Return next difficulty, capped at minimum
max(minimum_difficulty, next_difficulty)
true ->
# Find the delta from parent block (note: we use difficulty_s2 since we're after Homestead)
difficulty_delta = difficulty_x(parent_header.difficulty, difficulty_bound_divisor) * difficulty_s2(header, parent_header) + difficulty_e(header)
# Add delta to parent's difficulty
next_difficulty = parent_header.difficulty + difficulty_delta
# Return next difficulty, capped at minimum
max(minimum_difficulty, next_difficulty)
end
end
# Eq.(42) ς1 - Effectively decides if blocks are being mined too quicky or too slower
@spec difficulty_s1(t, t) :: integer()
defp difficulty_s1(header, parent_header) do
if header.timestamp < ( parent_header.timestamp + 13 ), do: 1, else: -1
end
# Eq.(43) ς2
@spec difficulty_s2(t, t) :: integer()
defp difficulty_s2(header, parent_header) do
s = MathHelper.floor( ( header.timestamp - parent_header.timestamp ) / 10 )
max(1 - s, -99)
end
# Eq.(41) x - Creates some multiplier for how much we should change difficulty based on previous difficulty
@spec difficulty_x(integer(), integer()) :: integer()
defp difficulty_x(parent_difficulty, difficulty_bound_divisor), do: MathHelper.floor(parent_difficulty / difficulty_bound_divisor)
# Eq.(44) ε - Adds a delta to ensure we're increasing difficulty over time
@spec difficulty_e(t) :: integer()
defp difficulty_e(header) do
MathHelper.floor(
:math.pow(
2,
MathHelper.floor( header.number / 100_000 ) - 2
)
)
end
@doc """
Function to determine if the gas limit set is valid. The miner gets to
specify a gas limit, so long as it's in range. This allows about a 0.1% change
per block.
This function directly implements Eq.(45), Eq.(46) and Eq.(47).
## Examples
iex> Block.Header.is_gas_limit_valid?(1_000_000, nil)
true
iex> Block.Header.is_gas_limit_valid?(1_000, nil)
false
iex> Block.Header.is_gas_limit_valid?(1_000_000, 1_000_000)
true
iex> Block.Header.is_gas_limit_valid?(1_000_000, 2_000_000)
false
iex> Block.Header.is_gas_limit_valid?(1_000_000, 500_000)
false
iex> Block.Header.is_gas_limit_valid?(1_000_000, 999_500)
true
iex> Block.Header.is_gas_limit_valid?(1_000_000, 999_000)
false
iex> Block.Header.is_gas_limit_valid?(1_000_000, 2_000_000, 1)
true
iex> Block.Header.is_gas_limit_valid?(1_000, nil, 1024, 500)
true
"""
@spec is_gas_limit_valid?(EVM.Gas.t, EVM.Gas.t | nil) :: boolean()
def is_gas_limit_valid?(gas_limit, parent_gas_limit, gas_limit_bound_divisor \\ @gas_limit_bound_divisor, min_gas_limit \\ @min_gas_limit) do
if parent_gas_limit == nil do
# It's not entirely clear from the Yellow Paper
# whether a genesis block should have any limits
# on gas limit, other than min gas limit.
gas_limit > min_gas_limit
else
max_delta = MathHelper.floor(parent_gas_limit / gas_limit_bound_divisor)
( gas_limit < parent_gas_limit + max_delta ) and
( gas_limit > parent_gas_limit - max_delta ) and
gas_limit > min_gas_limit
end
end
end