Skip to content
Merged
179 changes: 112 additions & 67 deletions apps/aecore/lib/aecore/chain/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,114 +7,137 @@ defmodule Aecore.Chain.Worker do

alias Aecore.Structures.Block
alias Aecore.Chain.ChainState
alias Aecore.Utils.Blockchain.BlockValidation
alias Aecore.Utils.Blockchain.Difficulty
alias Aecore.Txs.Pool.Worker, as: Pool
alias Aecore.Utils.Blockchain.BlockValidation
alias Aecore.Peers.Worker, as: Peers

use GenServer

def start_link do
GenServer.start_link(
__MODULE__,
{[Block.genesis_block()], ChainState.calculate_block_state(Block.genesis_block().txs)},
name: __MODULE__
)
GenServer.start_link(__MODULE__, {}, name: __MODULE__)
end

def init(initial_state) do
def init(_) do
genesis_block_hash = BlockValidation.block_header_hash(Block.genesis_block().header)

genesis_block_map = %{genesis_block_hash => Block.genesis_block()}
genesis_chain_state = ChainState.calculate_block_state(Block.genesis_block().txs)
latest_block_chain_state = %{genesis_block_hash => genesis_chain_state}

initial_state = {genesis_block_map, latest_block_chain_state}

{:ok, initial_state}
end

@spec latest_block() :: %Block{}
def latest_block() do
GenServer.call(__MODULE__, :latest_block)
end
latest_block_hashes = get_latest_block_chain_state() |> Map.keys()
latest_block_hash = case(length(latest_block_hashes)) do
1 -> List.first(latest_block_hashes)
_ -> throw({:error, "multiple or none latest block hashes"})
end

@spec get_prior_blocks_for_validity_check() :: tuple()
def get_prior_blocks_for_validity_check() do
GenServer.call(__MODULE__, :get_prior_blocks_for_validity_check)
get_block(latest_block_hash)
end

@spec get_block_by_hash(term()) :: %Block{}
def get_block_by_hash(hash) do
GenServer.call(__MODULE__, {:get_block_by_hash, hash})
@spec get_latest_block_chain_state() :: tuple()
def get_latest_block_chain_state() do
GenServer.call(__MODULE__, :get_latest_block_chain_state)
end

@spec all_blocks() :: list()
def all_blocks() do
GenServer.call(__MODULE__, :all_blocks)
@spec get_block_by_hex_hash(term()) :: %Block{}
def get_block_by_hex_hash(hash) do
GenServer.call(__MODULE__, {:get_block_by_hex_hash, hash})
end

@spec add_block(%Block{}) :: :ok
def add_block(%Block{} = b) do
GenServer.call(__MODULE__, {:add_block, b})
@spec get_block(term()) :: %Block{}
def get_block(hash) do
GenServer.call(__MODULE__, {:get_block, hash})
end

@spec chain_state() :: map()
def chain_state() do
GenServer.call(__MODULE__, :chain_state)
@spec get_blocks(binary(), integer()) :: :ok
def get_blocks(start_block_hash, size) do
Enum.reverse(get_blocks([], start_block_hash, size))
end

@spec get_blocks_for_difficulty_calculation() :: list()
def get_blocks_for_difficulty_calculation() do
GenServer.call(__MODULE__, :get_blocks_for_difficulty_calculation)
@spec add_block(%Block{}) :: :ok
def add_block(%Block{} = block) do
GenServer.call(__MODULE__, {:add_block, block})
end

def handle_call(:latest_block, _from, state) do
[lb | _] = elem(state, 0)
{:reply, lb, state}
@spec chain_state(binary()) :: map()
def chain_state(latest_block_hash) do
GenServer.call(__MODULE__, {:chain_state, latest_block_hash})
end

def handle_call(:get_prior_blocks_for_validity_check, _from, state) do
chain = elem(state, 0)

if length(chain) == 1 do
[lb | _] = chain
{:reply, {lb, nil}, state}
else
[lb, prev | _] = chain
{:reply, {lb, prev}, state}
end
def handle_call(:get_latest_block_chain_state, _from, state) do
{_, latest_block_chain_state} = state
{:reply, latest_block_chain_state, state}
end

def handle_call({:get_block_by_hash, hash}, _from, state) do
block = Enum.find(elem(state, 0), fn(block) ->
block.header
|> BlockValidation.block_header_hash()
|> Base.encode16() == hash end)
def handle_call({:get_block, block_hash}, _from, state) do
{block_map, _} = state
block = block_map[block_hash]

if(block != nil) do
{:reply, block, state}
else
{:reply, {:error, "Block not found"}, state}
end
end

def handle_call(:all_blocks, _from, state) do
chain = elem(state, 0)
{:reply, chain, state}
def handle_call({:get_block_by_hex_hash, hash}, _from, state) do
{chain, _} = state
case(Enum.find(chain, fn{block_hash, _block} ->
block_hash |> Base.encode16() == hash end)) do
{_, block} ->
{:reply, block, state}
nil ->
{:reply, {:error, "Block not found"}, state}
end
end

def handle_call({:add_block, %Block{} = b}, _from, state) do
{chain, prev_chain_state} = state
[prior_block | _] = chain
new_block_state = ChainState.calculate_block_state(b.txs)
new_chain_state = ChainState.calculate_chain_state(new_block_state, prev_chain_state)
def handle_call({:add_block, %Block{} = block}, _from, state) do
{chain, chain_state} = state
prev_block_chain_state = chain_state[block.header.prev_hash]
new_block_state = ChainState.calculate_block_state(block.txs)
new_chain_state = ChainState.calculate_chain_state(new_block_state, prev_block_chain_state)

try do
BlockValidation.validate_block!(b, prior_block, new_chain_state)
Enum.each(b.txs, fn(tx) -> Pool.remove_transaction(tx) end)
BlockValidation.validate_block!(block, chain[block.header.prev_hash], new_chain_state)

Enum.each(block.txs, fn(tx) -> Pool.remove_transaction(tx) end)

{block_map, latest_block_chain_state} = state
block_hash = BlockValidation.block_header_hash(block.header)
updated_block_map = Map.put(block_map, block_hash, block)
has_prev_block = Map.has_key?(latest_block_chain_state, block.header.prev_hash)

{deleted_latest_chain_state, prev_chain_state} = case has_prev_block do
true ->
prev_chain_state = Map.get(latest_block_chain_state, block.header.prev_hash)
{Map.delete(latest_block_chain_state, block.header.prev_hash), prev_chain_state}
false ->
{latest_block_chain_state, %{}}
end

new_block_state = ChainState.calculate_block_state(block.txs)
new_chain_state = ChainState.calculate_chain_state(new_block_state, prev_chain_state)

updated_latest_block_chainstate = Map.put(deleted_latest_chain_state, block_hash, new_chain_state)

total_tokens = ChainState.calculate_total_tokens(new_chain_state)

Logger.info(fn ->
"Added block ##{b.header.height} with hash #{b.header
"Added block ##{block.header.height} with hash #{block.header
|> BlockValidation.block_header_hash()
|> Base.encode16()}, total tokens: #{total_tokens}"
end)

## Block was validated, now we can send it to other peers
Peers.broadcast_to_all({:new_block, b})
Peers.broadcast_to_all({:new_block, block})

{:reply, :ok, {[b | chain], new_chain_state}}
{:reply, :ok, {updated_block_map, updated_latest_block_chainstate}}
catch
{:error, message} ->
Logger.error(fn ->
Expand All @@ -124,15 +147,37 @@ defmodule Aecore.Chain.Worker do
end
end

def handle_call(:chain_state, _from, state) do
chain_state = elem(state, 1)
{:reply, chain_state, state}
def handle_call({:chain_state, latest_block_hash}, _from, state) do
{_, chain_state} = state
{:reply, chain_state[latest_block_hash], state}
end

def handle_call(:get_blocks_for_difficulty_calculation, _from, state) do
chain = elem(state, 0)
number_of_blocks = Difficulty.get_number_of_blocks()
blocks_for_difficulty_calculation = Enum.take(chain, number_of_blocks)
{:reply, blocks_for_difficulty_calculation, state}
def chain_state() do
latest_block = latest_block()
latest_block_hash = BlockValidation.block_header_hash(latest_block.header)
chain_state(latest_block_hash)
end

def all_blocks() do
latest_block_obj = latest_block()
latest_block_hash = BlockValidation.block_header_hash(latest_block_obj.header)
get_blocks(latest_block_hash, latest_block_obj.header.height)
end

defp get_blocks(blocks_acc, next_block_hash, size) do
cond do
size > 0 ->
case(GenServer.call(__MODULE__, {:get_block, next_block_hash})) do
{:error, _} -> blocks_acc
block ->
updated_block_acc = [block | blocks_acc]
prev_block_hash = block.header.prev_hash
next_size = size - 1

get_blocks(updated_block_acc, prev_block_hash, next_size)
end
true ->
blocks_acc
end
end
end
99 changes: 56 additions & 43 deletions apps/aecore/lib/aecore/miner/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule Aecore.Miner.Worker do

## Idle ##
def idle({:call, from}, :start, _data) do
IO.puts("Mining resuming by user")
IO.puts("Mining resumed by user")
GenStateMachine.cast(__MODULE__, :mine)
{:next_state, :running, 0, [{:reply, from, :ok}]}
end
Expand Down Expand Up @@ -78,7 +78,7 @@ defmodule Aecore.Miner.Worker do
end

def running({:call, from}, :suspend, data) do
IO.puts("Mined stop by user")
IO.puts("Mining stopped by user")
{:next_state, :idle, data, [{:reply, from, :ok}]}
end

Expand Down Expand Up @@ -106,52 +106,65 @@ defmodule Aecore.Miner.Worker do
## Internal
@spec mine_next_block(integer()) :: :ok | :error
defp mine_next_block(start_nonce) do
chain_state = Chain.chain_state()
latest_block = Chain.latest_block()
latest_block_hash = BlockValidation.block_header_hash(latest_block.header)
chain_state = Chain.chain_state(latest_block_hash)

txs_list = Map.values(Pool.get_pool())
ordered_txs_list = Enum.sort(txs_list, fn(tx1, tx2) -> tx1.data.nonce < tx2.data.nonce end)

blocks_for_difficulty_calculation = Chain.get_blocks_for_difficulty_calculation()
{latest_block, previous_block} = Chain.get_prior_blocks_for_validity_check()

BlockValidation.validate_block!(latest_block, previous_block, chain_state)

valid_txs = BlockValidation.filter_invalid_transactions_chainstate(ordered_txs_list, chain_state)
{_, pubkey} = Keys.pubkey()
valid_txs = [get_coinbase_transaction(pubkey) | valid_txs]
root_hash = BlockValidation.calculate_root_hash(valid_txs)

new_block_state = ChainState.calculate_block_state(valid_txs)
new_chain_state = ChainState.calculate_chain_state(new_block_state, chain_state)
chain_state_hash = ChainState.calculate_chain_state_hash(new_chain_state)

latest_block_hash = BlockValidation.block_header_hash(latest_block.header)

difficulty = Difficulty.calculate_next_difficulty(blocks_for_difficulty_calculation)

unmined_header =
Header.create(
latest_block.header.height + 1,
latest_block_hash,
root_hash,
chain_state_hash,
difficulty,
0, #start from nonce 0, will be incremented in mining
Block.current_block_version()
)
Logger.debug("start nonce #{start_nonce}. Final nonce = #{start_nonce + @nonce_per_cycle}")
case Cuckoo.generate(%{unmined_header
| nonce: start_nonce + @nonce_per_cycle}) do
{:ok, mined_header} ->
block = %Block{header: mined_header, txs: valid_txs}
Chain.add_block(block)
Logger.info(fn ->
"Mined block ##{block.header.height}, difficulty target #{block.header.difficulty_target}, nonce #{block.header.nonce}"
end)
{:block_found, 0}

blocks_for_difficulty_calculation = Chain.get_blocks(latest_block_hash, Difficulty.get_number_of_blocks())
previous_block = cond do
latest_block == Block.genesis_block() -> nil
true ->
blocks = Chain.get_blocks(latest_block_hash, 2)
Enum.at(blocks, 1)
end
try do
BlockValidation.validate_block!(latest_block, previous_block, chain_state)

valid_txs = BlockValidation.filter_invalid_transactions_chainstate(ordered_txs_list, chain_state)
{_, pubkey} = Keys.pubkey()
valid_txs = [get_coinbase_transaction(pubkey) | valid_txs]
root_hash = BlockValidation.calculate_root_hash(valid_txs)

new_block_state = ChainState.calculate_block_state(valid_txs)
new_chain_state = ChainState.calculate_chain_state(new_block_state, chain_state)
chain_state_hash = ChainState.calculate_chain_state_hash(new_chain_state)

latest_block_hash = BlockValidation.block_header_hash(latest_block.header)

difficulty = Difficulty.calculate_next_difficulty(blocks_for_difficulty_calculation)

unmined_header =
Header.create(
latest_block.header.height + 1,
latest_block_hash,
root_hash,
chain_state_hash,
difficulty,
0, #start from nonce 0, will be incremented in mining
Block.current_block_version()
)
Logger.debug("start nonce #{start_nonce}. Final nonce = #{start_nonce + @nonce_per_cycle}")
case Cuckoo.generate(%{unmined_header
| nonce: start_nonce + @nonce_per_cycle}) do
{:ok, mined_header} ->
block = %Block{header: mined_header, txs: valid_txs}
Logger.info(fn ->
"Mined block ##{block.header.height}, difficulty target #{block.header.difficulty_target}, nonce #{block.header.nonce}"
end)
Chain.add_block(block)
{:block_found, 0}

{:error, _message} ->
{:no_block_found, start_nonce + @nonce_per_cycle}
end
catch
{:error, _message} ->
{:no_block_found, start_nonce + @nonce_per_cycle}
Logger.error(fn ->
"Failed to mine block"
end)
end
end
end
6 changes: 1 addition & 5 deletions apps/aecore/lib/aecore/peers/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule Aecore.Peers.Worker do

@spec genesis_block_header_hash() :: term()
def genesis_block_header_hash() do
Block.genesis_header()
Block.genesis_block().header
|> BlockValidation.block_header_hash()
|> Base.encode16()
end
Expand Down Expand Up @@ -134,10 +134,6 @@ defmodule Aecore.Peers.Worker do
end

## Internal functions
defp send_to_peers(_uri, _data, []) do
Logger.warn("Empty peers list")
end

defp send_to_peers(uri, data, peers) do
for peer <- peers do
HttpClient.post(peer, data, uri)
Expand Down
3 changes: 1 addition & 2 deletions apps/aecore/lib/aecore/structures/block.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ defmodule Aecore.Structures.Block do
@current_block_version
end

@spec genesis_header() :: Header.header()
def genesis_header() do
defp genesis_header() do
h = Application.get_env(:aecore, :pow)[:genesis_header]
struct(Aecore.Structures.Header, h)
end
Expand Down
Loading