Current section

Files

Jump to
blockchain lib blockchain chain.ex
Raw

lib/blockchain/chain.ex

defmodule Blockchain.Chain do
@moduledoc """
Represents the information about a specific chain. This
will either be a current chain (such as homestead), or
a test chain (such as ropsten). Different chains have
different parameters, such as accounts with an initial
balance and when EIPs are implemented.
For compatibility, we'll use the configuration files from Parity:
https://github.com/paritytech/parity/tree/master/ethcore/res/ethereum
"""
require Integer
defstruct [
name: nil,
engine: %{},
params: %{},
genesis: %{},
nodes: [],
accounts: %{}
]
@type t :: %__MODULE__{
name: String.t,
engine: %{
String.t => %{
minimum_difficulty: integer(),
difficulty_bound_divisor: integer(),
duration_limit: integer(),
homestead_transition: integer(),
eip150_transition: integer(),
eip160_transition: integer(),
eip161abc_transition: integer(),
eip161d_transition: integer(),
max_code_size: integer()
}
},
params: %{
gas_limit_bound_divisor: integer(),
block_reward: integer(),
account_start_nonce: integer(),
maximum_extra_data_size: integer(),
min_gas_limit: integer(),
eip155_transition: integer(),
eip98_transition: integer(),
eip86_transition: integer(),
},
genesis: %{
difficulty: integer(),
author: EVM.address,
timestamp: integer(),
parent_hash: EVM.hash,
extra_data: binary(),
gas_limit: EVM.Gas.t,
mix_hash: binary(),
nonce: binary(),
},
nodes: [String.t],
accounts: %{
EVM.address => %{
balance: EVM.Wei.t,
nonce: integer(),
storage: %{
binary() => binary()
}
# TODO: Handle built-in
}
}
}
@doc """
Loads a given blockchain, such as Homestead or Ropsten. This
chain is used to set the genesis block and tweak parameters
of the Blockchain and EVM.
See the `/chains` directory of this repo for supported
block chains.
## Examples
iex> Blockchain.Chain.load_chain(:ropsten).name
"Ropsten"
iex> Blockchain.Chain.load_chain(:ropsten).genesis.difficulty
0x100000
"""
@spec load_chain(atom()) :: t
def load_chain(chain) do
chain_data = read_chain!(chain)
%__MODULE__{
name: chain_data["name"],
engine: get_engine(chain_data["engine"]),
params: get_params(chain_data["params"]),
genesis: get_genesis(chain_data["genesis"]),
nodes: chain_data["nodes"],
accounts: get_accounts(chain_data["accounts"])
}
end
defp get_engine(engine_map) do
for {engine, %{"params" => params}} <- engine_map do
{engine, %{
minimum_difficulty: params["minimumDifficulty"] |> load_hex,
difficulty_bound_divisor: params["difficultyBoundDivisor"] |> load_hex,
duration_limit: params["durationLimit"] |> load_hex,
homestead_transition: params["homesteadTransition"],
eip150_transition: params["eip150Transition"],
eip160_transition: params["eip160Transition"],
eip161abc_transition: params["eip161abcTransition"],
eip161d_transition: params["eip161dTransition"],
max_code_size: params["maxCodeSize"],
}}
end |> Enum.into(%{})
end
defp get_params(params_map) do
%{
gas_limit_bound_divisor: params_map["gasLimitBoundDivisor"] |> load_hex,
block_reward: params_map["blockReward"] |> load_hex,
account_start_nonce: params_map["accountStartNonce"] |> load_hex,
maximum_extra_data_size: params_map["maximumExtraDataSize"] |> load_hex,
min_gas_limit: params_map["minGasLimit"] |> load_hex,
eip155_transition: params_map["eip155Transition"],
eip98_transition: params_map["eip98Transition"] |> load_hex,
eip86_transition: params_map["eip86Transition"] |> load_hex,
}
end
defp get_genesis(genesis_map) do
%{
difficulty: genesis_map["difficulty"] |> load_hex,
author: genesis_map["author"] |> load_address,
timestamp: genesis_map["timestamp"] |> load_hex,
parent_hash: genesis_map["parentHash"] |> load_raw_hex,
extra_data: genesis_map["extraData"] |> load_raw_hex,
gas_limit: genesis_map["gasLimit"] |> load_hex,
}
end
defp get_accounts(account_map) do
for {address, account_info} <- account_map do
{load_address(address), %{
balance: account_info["balance"] |> load_decimal,
nonce: ( if account_info["nonce"], do: account_info["nonce"] |> load_hex, else: 0 ),
}}
end |> Enum.into(%{})
end
@spec read_chain!(atom()) :: map()
defp read_chain!(chain) do
{:ok, body} = File.read(chain |> chain_filename)
Poison.decode!(body)
end
@spec chain_filename(atom()) :: String.t
defp chain_filename(chain) do
"chains/#{Atom.to_string(chain)}.json"
end
@spec load_address(String.t) :: binary()
defp load_address(hex_data), do: load_raw_hex(hex_data)
@spec load_raw_hex(String.t) :: binary()
defp load_raw_hex("0x" <> hex_data), do: load_raw_hex(hex_data)
defp load_raw_hex(hex_data) when Integer.is_odd(byte_size(hex_data)), do: load_raw_hex("0" <> hex_data)
defp load_raw_hex(hex_data) do
Base.decode16!(hex_data, case: :mixed)
end
@spec load_decimal(String.t) :: integer()
def load_decimal(dec_data) do
{res, ""} = Integer.parse(dec_data)
res
end
@spec load_hex(String.t) :: integer()
defp load_hex(hex_data), do: hex_data |> load_raw_hex |> :binary.decode_unsigned
end