Skip to content

Commit cc7d42b

Browse files
authored
Merge pull request #106 from aeternity/GH-88
[GH-#88] Refactor chain to be map of blocks, traverse top to bottom
2 parents bc4dbc7 + ae953e4 commit cc7d42b

File tree

13 files changed

+213
-152
lines changed

13 files changed

+213
-152
lines changed

apps/aecore/lib/aecore/chain/worker.ex

Lines changed: 112 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,114 +7,137 @@ defmodule Aecore.Chain.Worker do
77

88
alias Aecore.Structures.Block
99
alias Aecore.Chain.ChainState
10-
alias Aecore.Utils.Blockchain.BlockValidation
11-
alias Aecore.Utils.Blockchain.Difficulty
1210
alias Aecore.Txs.Pool.Worker, as: Pool
11+
alias Aecore.Utils.Blockchain.BlockValidation
1312
alias Aecore.Peers.Worker, as: Peers
1413

1514
use GenServer
1615

1716
def start_link do
18-
GenServer.start_link(
19-
__MODULE__,
20-
{[Block.genesis_block()], ChainState.calculate_block_state(Block.genesis_block().txs)},
21-
name: __MODULE__
22-
)
17+
GenServer.start_link(__MODULE__, {}, name: __MODULE__)
2318
end
2419

25-
def init(initial_state) do
20+
def init(_) do
21+
genesis_block_hash = BlockValidation.block_header_hash(Block.genesis_block().header)
22+
23+
genesis_block_map = %{genesis_block_hash => Block.genesis_block()}
24+
genesis_chain_state = ChainState.calculate_block_state(Block.genesis_block().txs)
25+
latest_block_chain_state = %{genesis_block_hash => genesis_chain_state}
26+
27+
initial_state = {genesis_block_map, latest_block_chain_state}
28+
2629
{:ok, initial_state}
2730
end
2831

2932
@spec latest_block() :: %Block{}
3033
def latest_block() do
31-
GenServer.call(__MODULE__, :latest_block)
32-
end
34+
latest_block_hashes = get_latest_block_chain_state() |> Map.keys()
35+
latest_block_hash = case(length(latest_block_hashes)) do
36+
1 -> List.first(latest_block_hashes)
37+
_ -> throw({:error, "multiple or none latest block hashes"})
38+
end
3339

34-
@spec get_prior_blocks_for_validity_check() :: tuple()
35-
def get_prior_blocks_for_validity_check() do
36-
GenServer.call(__MODULE__, :get_prior_blocks_for_validity_check)
40+
get_block(latest_block_hash)
3741
end
3842

39-
@spec get_block_by_hash(term()) :: %Block{}
40-
def get_block_by_hash(hash) do
41-
GenServer.call(__MODULE__, {:get_block_by_hash, hash})
43+
@spec get_latest_block_chain_state() :: tuple()
44+
def get_latest_block_chain_state() do
45+
GenServer.call(__MODULE__, :get_latest_block_chain_state)
4246
end
4347

44-
@spec all_blocks() :: list()
45-
def all_blocks() do
46-
GenServer.call(__MODULE__, :all_blocks)
48+
@spec get_block_by_hex_hash(term()) :: %Block{}
49+
def get_block_by_hex_hash(hash) do
50+
GenServer.call(__MODULE__, {:get_block_by_hex_hash, hash})
4751
end
4852

49-
@spec add_block(%Block{}) :: :ok
50-
def add_block(%Block{} = b) do
51-
GenServer.call(__MODULE__, {:add_block, b})
53+
@spec get_block(term()) :: %Block{}
54+
def get_block(hash) do
55+
GenServer.call(__MODULE__, {:get_block, hash})
5256
end
5357

54-
@spec chain_state() :: map()
55-
def chain_state() do
56-
GenServer.call(__MODULE__, :chain_state)
58+
@spec get_blocks(binary(), integer()) :: :ok
59+
def get_blocks(start_block_hash, size) do
60+
Enum.reverse(get_blocks([], start_block_hash, size))
5761
end
5862

59-
@spec get_blocks_for_difficulty_calculation() :: list()
60-
def get_blocks_for_difficulty_calculation() do
61-
GenServer.call(__MODULE__, :get_blocks_for_difficulty_calculation)
63+
@spec add_block(%Block{}) :: :ok
64+
def add_block(%Block{} = block) do
65+
GenServer.call(__MODULE__, {:add_block, block})
6266
end
6367

64-
def handle_call(:latest_block, _from, state) do
65-
[lb | _] = elem(state, 0)
66-
{:reply, lb, state}
68+
@spec chain_state(binary()) :: map()
69+
def chain_state(latest_block_hash) do
70+
GenServer.call(__MODULE__, {:chain_state, latest_block_hash})
6771
end
6872

69-
def handle_call(:get_prior_blocks_for_validity_check, _from, state) do
70-
chain = elem(state, 0)
71-
72-
if length(chain) == 1 do
73-
[lb | _] = chain
74-
{:reply, {lb, nil}, state}
75-
else
76-
[lb, prev | _] = chain
77-
{:reply, {lb, prev}, state}
78-
end
73+
def handle_call(:get_latest_block_chain_state, _from, state) do
74+
{_, latest_block_chain_state} = state
75+
{:reply, latest_block_chain_state, state}
7976
end
8077

81-
def handle_call({:get_block_by_hash, hash}, _from, state) do
82-
block = Enum.find(elem(state, 0), fn(block) ->
83-
block.header
84-
|> BlockValidation.block_header_hash()
85-
|> Base.encode16() == hash end)
78+
def handle_call({:get_block, block_hash}, _from, state) do
79+
{block_map, _} = state
80+
block = block_map[block_hash]
81+
8682
if(block != nil) do
8783
{:reply, block, state}
8884
else
8985
{:reply, {:error, "Block not found"}, state}
9086
end
9187
end
9288

93-
def handle_call(:all_blocks, _from, state) do
94-
chain = elem(state, 0)
95-
{:reply, chain, state}
89+
def handle_call({:get_block_by_hex_hash, hash}, _from, state) do
90+
{chain, _} = state
91+
case(Enum.find(chain, fn{block_hash, _block} ->
92+
block_hash |> Base.encode16() == hash end)) do
93+
{_, block} ->
94+
{:reply, block, state}
95+
nil ->
96+
{:reply, {:error, "Block not found"}, state}
97+
end
9698
end
9799

98-
def handle_call({:add_block, %Block{} = b}, _from, state) do
99-
{chain, prev_chain_state} = state
100-
[prior_block | _] = chain
101-
new_block_state = ChainState.calculate_block_state(b.txs)
102-
new_chain_state = ChainState.calculate_chain_state(new_block_state, prev_chain_state)
100+
def handle_call({:add_block, %Block{} = block}, _from, state) do
101+
{chain, chain_state} = state
102+
prev_block_chain_state = chain_state[block.header.prev_hash]
103+
new_block_state = ChainState.calculate_block_state(block.txs)
104+
new_chain_state = ChainState.calculate_chain_state(new_block_state, prev_block_chain_state)
103105

104106
try do
105-
BlockValidation.validate_block!(b, prior_block, new_chain_state)
106-
Enum.each(b.txs, fn(tx) -> Pool.remove_transaction(tx) end)
107+
BlockValidation.validate_block!(block, chain[block.header.prev_hash], new_chain_state)
108+
109+
Enum.each(block.txs, fn(tx) -> Pool.remove_transaction(tx) end)
110+
111+
{block_map, latest_block_chain_state} = state
112+
block_hash = BlockValidation.block_header_hash(block.header)
113+
updated_block_map = Map.put(block_map, block_hash, block)
114+
has_prev_block = Map.has_key?(latest_block_chain_state, block.header.prev_hash)
115+
116+
{deleted_latest_chain_state, prev_chain_state} = case has_prev_block do
117+
true ->
118+
prev_chain_state = Map.get(latest_block_chain_state, block.header.prev_hash)
119+
{Map.delete(latest_block_chain_state, block.header.prev_hash), prev_chain_state}
120+
false ->
121+
{latest_block_chain_state, %{}}
122+
end
123+
124+
new_block_state = ChainState.calculate_block_state(block.txs)
125+
new_chain_state = ChainState.calculate_chain_state(new_block_state, prev_chain_state)
126+
127+
updated_latest_block_chainstate = Map.put(deleted_latest_chain_state, block_hash, new_chain_state)
128+
107129
total_tokens = ChainState.calculate_total_tokens(new_chain_state)
130+
108131
Logger.info(fn ->
109-
"Added block ##{b.header.height} with hash #{b.header
132+
"Added block ##{block.header.height} with hash #{block.header
110133
|> BlockValidation.block_header_hash()
111134
|> Base.encode16()}, total tokens: #{total_tokens}"
112135
end)
113136

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

117-
{:reply, :ok, {[b | chain], new_chain_state}}
140+
{:reply, :ok, {updated_block_map, updated_latest_block_chainstate}}
118141
catch
119142
{:error, message} ->
120143
Logger.error(fn ->
@@ -124,15 +147,37 @@ defmodule Aecore.Chain.Worker do
124147
end
125148
end
126149

127-
def handle_call(:chain_state, _from, state) do
128-
chain_state = elem(state, 1)
129-
{:reply, chain_state, state}
150+
def handle_call({:chain_state, latest_block_hash}, _from, state) do
151+
{_, chain_state} = state
152+
{:reply, chain_state[latest_block_hash], state}
130153
end
131154

132-
def handle_call(:get_blocks_for_difficulty_calculation, _from, state) do
133-
chain = elem(state, 0)
134-
number_of_blocks = Difficulty.get_number_of_blocks()
135-
blocks_for_difficulty_calculation = Enum.take(chain, number_of_blocks)
136-
{:reply, blocks_for_difficulty_calculation, state}
155+
def chain_state() do
156+
latest_block = latest_block()
157+
latest_block_hash = BlockValidation.block_header_hash(latest_block.header)
158+
chain_state(latest_block_hash)
159+
end
160+
161+
def all_blocks() do
162+
latest_block_obj = latest_block()
163+
latest_block_hash = BlockValidation.block_header_hash(latest_block_obj.header)
164+
get_blocks(latest_block_hash, latest_block_obj.header.height)
165+
end
166+
167+
defp get_blocks(blocks_acc, next_block_hash, size) do
168+
cond do
169+
size > 0 ->
170+
case(GenServer.call(__MODULE__, {:get_block, next_block_hash})) do
171+
{:error, _} -> blocks_acc
172+
block ->
173+
updated_block_acc = [block | blocks_acc]
174+
prev_block_hash = block.header.prev_hash
175+
next_size = size - 1
176+
177+
get_blocks(updated_block_acc, prev_block_hash, next_size)
178+
end
179+
true ->
180+
blocks_acc
181+
end
137182
end
138183
end

apps/aecore/lib/aecore/miner/worker.ex

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ defmodule Aecore.Miner.Worker do
4141

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

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

@@ -106,52 +106,65 @@ defmodule Aecore.Miner.Worker do
106106
## Internal
107107
@spec mine_next_block(integer()) :: :ok | :error
108108
defp mine_next_block(start_nonce) do
109-
chain_state = Chain.chain_state()
109+
latest_block = Chain.latest_block()
110+
latest_block_hash = BlockValidation.block_header_hash(latest_block.header)
111+
chain_state = Chain.chain_state(latest_block_hash)
110112

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

114-
blocks_for_difficulty_calculation = Chain.get_blocks_for_difficulty_calculation()
115-
{latest_block, previous_block} = Chain.get_prior_blocks_for_validity_check()
116-
117-
BlockValidation.validate_block!(latest_block, previous_block, chain_state)
118-
119-
valid_txs = BlockValidation.filter_invalid_transactions_chainstate(ordered_txs_list, chain_state)
120-
{_, pubkey} = Keys.pubkey()
121-
valid_txs = [get_coinbase_transaction(pubkey) | valid_txs]
122-
root_hash = BlockValidation.calculate_root_hash(valid_txs)
123-
124-
new_block_state = ChainState.calculate_block_state(valid_txs)
125-
new_chain_state = ChainState.calculate_chain_state(new_block_state, chain_state)
126-
chain_state_hash = ChainState.calculate_chain_state_hash(new_chain_state)
127-
128-
latest_block_hash = BlockValidation.block_header_hash(latest_block.header)
129-
130-
difficulty = Difficulty.calculate_next_difficulty(blocks_for_difficulty_calculation)
131-
132-
unmined_header =
133-
Header.create(
134-
latest_block.header.height + 1,
135-
latest_block_hash,
136-
root_hash,
137-
chain_state_hash,
138-
difficulty,
139-
0, #start from nonce 0, will be incremented in mining
140-
Block.current_block_version()
141-
)
142-
Logger.debug("start nonce #{start_nonce}. Final nonce = #{start_nonce + @nonce_per_cycle}")
143-
case Cuckoo.generate(%{unmined_header
144-
| nonce: start_nonce + @nonce_per_cycle}) do
145-
{:ok, mined_header} ->
146-
block = %Block{header: mined_header, txs: valid_txs}
147-
Chain.add_block(block)
148-
Logger.info(fn ->
149-
"Mined block ##{block.header.height}, difficulty target #{block.header.difficulty_target}, nonce #{block.header.nonce}"
150-
end)
151-
{:block_found, 0}
152-
116+
blocks_for_difficulty_calculation = Chain.get_blocks(latest_block_hash, Difficulty.get_number_of_blocks())
117+
previous_block = cond do
118+
latest_block == Block.genesis_block() -> nil
119+
true ->
120+
blocks = Chain.get_blocks(latest_block_hash, 2)
121+
Enum.at(blocks, 1)
122+
end
123+
try do
124+
BlockValidation.validate_block!(latest_block, previous_block, chain_state)
125+
126+
valid_txs = BlockValidation.filter_invalid_transactions_chainstate(ordered_txs_list, chain_state)
127+
{_, pubkey} = Keys.pubkey()
128+
valid_txs = [get_coinbase_transaction(pubkey) | valid_txs]
129+
root_hash = BlockValidation.calculate_root_hash(valid_txs)
130+
131+
new_block_state = ChainState.calculate_block_state(valid_txs)
132+
new_chain_state = ChainState.calculate_chain_state(new_block_state, chain_state)
133+
chain_state_hash = ChainState.calculate_chain_state_hash(new_chain_state)
134+
135+
latest_block_hash = BlockValidation.block_header_hash(latest_block.header)
136+
137+
difficulty = Difficulty.calculate_next_difficulty(blocks_for_difficulty_calculation)
138+
139+
unmined_header =
140+
Header.create(
141+
latest_block.header.height + 1,
142+
latest_block_hash,
143+
root_hash,
144+
chain_state_hash,
145+
difficulty,
146+
0, #start from nonce 0, will be incremented in mining
147+
Block.current_block_version()
148+
)
149+
Logger.debug("start nonce #{start_nonce}. Final nonce = #{start_nonce + @nonce_per_cycle}")
150+
case Cuckoo.generate(%{unmined_header
151+
| nonce: start_nonce + @nonce_per_cycle}) do
152+
{:ok, mined_header} ->
153+
block = %Block{header: mined_header, txs: valid_txs}
154+
Logger.info(fn ->
155+
"Mined block ##{block.header.height}, difficulty target #{block.header.difficulty_target}, nonce #{block.header.nonce}"
156+
end)
157+
Chain.add_block(block)
158+
{:block_found, 0}
159+
160+
{:error, _message} ->
161+
{:no_block_found, start_nonce + @nonce_per_cycle}
162+
end
163+
catch
153164
{:error, _message} ->
154-
{:no_block_found, start_nonce + @nonce_per_cycle}
165+
Logger.error(fn ->
166+
"Failed to mine block"
167+
end)
155168
end
156169
end
157170
end

apps/aecore/lib/aecore/peers/worker.ex

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ defmodule Aecore.Peers.Worker do
4242

4343
@spec genesis_block_header_hash() :: term()
4444
def genesis_block_header_hash() do
45-
Block.genesis_header()
45+
Block.genesis_block().header
4646
|> BlockValidation.block_header_hash()
4747
|> Base.encode16()
4848
end
@@ -134,10 +134,6 @@ defmodule Aecore.Peers.Worker do
134134
end
135135

136136
## Internal functions
137-
defp send_to_peers(_uri, _data, []) do
138-
Logger.warn("Empty peers list")
139-
end
140-
141137
defp send_to_peers(uri, data, peers) do
142138
for peer <- peers do
143139
HttpClient.post(peer, data, uri)

apps/aecore/lib/aecore/structures/block.ex

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ defmodule Aecore.Structures.Block do
1818
@current_block_version
1919
end
2020

21-
@spec genesis_header() :: Header.header()
22-
def genesis_header() do
21+
defp genesis_header() do
2322
h = Application.get_env(:aecore, :pow)[:genesis_header]
2423
struct(Aecore.Structures.Header, h)
2524
end

0 commit comments

Comments
 (0)