/
db.ex
282 lines (237 loc) · 9.04 KB
/
db.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
defmodule AeMdw.Node.Db do
@moduledoc false
alias AeMdw.Blocks
alias AeMdw.DryRun.Runner
alias AeMdw.Db.Model
alias AeMdw.Log
alias AeMdw.Node
import AeMdw.Util
require Logger
require Model
@type pubkey() :: <<_::256>>
@type hash_type() :: nil | :key | :micro
@type hash() :: <<_::256>>
@type height_hash() :: {hash_type(), pos_integer(), binary()}
@type balances_map() :: %{{:address, pubkey()} => integer()}
@type account_balance() :: {integer() | nil, height_hash()}
@opaque key_block() :: tuple()
@opaque micro_block() :: tuple()
@spec get_blocks(Blocks.block_hash(), Blocks.block_hash()) :: {key_block(), [micro_block()]}
def get_blocks(kb_hash, next_kb_hash) do
{:aec_db.get_block(kb_hash), get_micro_blocks(next_kb_hash)}
end
@spec get_blocks_per_height(Blocks.height(), Blocks.block_hash() | Blocks.height()) :: [
{Blocks.height(), [micro_block()], Blocks.block_hash()}
]
def get_blocks_per_height(from_height, block_hash) when is_binary(block_hash),
do: get_blocks_per_height(from_height, block_hash, nil)
def get_blocks_per_height(from_height, to_height) when is_integer(to_height) do
{:ok, header} = :aec_chain.get_key_header_by_height(to_height + 1)
last_mb_hash = :aec_headers.prev_hash(header)
{:ok, last_kb_hash} = :aec_headers.hash_header(header)
get_blocks_per_height(from_height, last_mb_hash, last_kb_hash)
end
defp get_blocks_per_height(from_height, last_mb_hash, last_kb_hash) do
{:ok, root_hash} =
:aec_chain.genesis_block()
|> :aec_blocks.to_header()
|> :aec_headers.hash_header()
{last_mb_hash, last_kb_hash}
|> Stream.unfold(fn
{_last_mb_hash, ^root_hash} ->
nil
{last_mb_hash, last_kb_hash} ->
{key_block, micro_blocks} = get_kb_mbs(last_mb_hash)
prev_hash = :aec_blocks.prev_hash(key_block)
key_header = :aec_blocks.to_header(key_block)
{:ok, key_hash} = :aec_headers.hash_header(key_header)
{{key_block, micro_blocks, last_kb_hash}, {prev_hash, key_hash}}
end)
|> Enum.take_while(fn {key_block, _micro_blocks, _last_kb_hash} ->
:aec_blocks.height(key_block) >= from_height
end)
|> Enum.reverse()
end
defp get_kb_mbs(last_mb_hash) do
last_mb_hash
|> Stream.unfold(fn block_hash ->
block = :aec_db.get_block(block_hash)
{block, :aec_blocks.prev_hash(block)}
end)
|> Enum.reduce_while([], fn block, micro_blocks ->
case :aec_blocks.type(block) do
:micro -> {:cont, [block | micro_blocks]}
:key -> {:halt, {block, micro_blocks}}
end
end)
end
@spec get_micro_blocks(Blocks.block_hash()) :: [micro_block()]
def get_micro_blocks(next_block_hash),
do: next_block_hash |> get_reverse_micro_blocks() |> Enum.reverse()
@spec get_reverse_micro_blocks(Blocks.block_hash()) :: Enumerable.t()
def get_reverse_micro_blocks(next_block_hash) do
next_block_hash
|> :aec_db.get_header()
|> :aec_headers.prev_hash()
|> Stream.unfold(µ_block_walker/1)
end
@spec get_key_block_hash(Blocks.height()) :: Blocks.block_hash() | nil
def get_key_block_hash(height) do
with {:ok, next_kb_header} <- :aec_chain.get_key_header_by_height(height),
{:ok, next_kb_hash} <- :aec_headers.hash_header(next_kb_header) do
next_kb_hash
else
{:error, :chain_too_short} -> nil
end
end
@spec get_next_hash(Blocks.block_hash(), Blocks.mbi()) :: Blocks.block_hash()
def get_next_hash(next_kb_hash, mbi) do
next_kb_hash
|> get_micro_blocks()
|> Enum.reduce_while(0, fn mblock, index ->
if index == mbi + 1 do
ok_mb_hash =
mblock
|> :aec_blocks.to_micro_header()
|> :aec_headers.hash_header()
{:halt, ok_mb_hash}
else
{:cont, index + 1}
end
end)
|> case do
{:ok, mb_hash} -> mb_hash
_mb_count -> next_kb_hash
end
end
@spec get_tx_data(binary()) ::
{Blocks.block_hash(), Node.tx_type(), Node.signed_tx(), Node.tx()}
def get_tx_data(<<_::256>> = tx_hash) do
{block_hash, signed_tx} = :aec_db.find_tx_with_location(tx_hash)
{type, tx_rec} = :aetx.specialize_type(:aetx_sign.tx(signed_tx))
{block_hash, type, signed_tx, tx_rec}
end
@spec get_tx(binary()) :: Node.tx()
def get_tx(<<_::256>> = tx_hash) do
{_, signed_tx} = :aec_db.find_tx_with_location(tx_hash)
{_, tx_rec} = :aetx.specialize_type(:aetx_sign.tx(signed_tx))
tx_rec
end
@spec get_signed_tx(binary()) :: tuple()
def get_signed_tx(<<_::256>> = tx_hash) do
{_, signed_tx} = :aec_db.find_tx_with_location(tx_hash)
signed_tx
end
@spec top_height_hash(boolean()) :: height_hash()
def top_height_hash(false = _the_very_top?) do
block = :aec_chain.top_key_block() |> ok!
header = :aec_blocks.to_key_header(block)
{:key, :aec_headers.height(header), ok!(:aec_headers.hash_header(header))}
end
def top_height_hash(true = _the_very_top?) do
{type, header} =
case :aec_chain.top_block() do
{:mic_block, header, _txs, _} -> {:micro, header}
{:key_block, header} -> {:key, header}
end
{type, :aec_headers.height(header), ok!(:aec_headers.hash_header(header))}
end
@spec aex9_balance(pubkey(), pubkey()) ::
{:ok, account_balance()} | {:error, Runner.call_error()}
def aex9_balance(contract_pk, account_pk),
do: aex9_balance(contract_pk, account_pk, false)
@spec aex9_balance(pubkey(), pubkey(), boolean()) ::
{:ok, account_balance()} | {:error, Runner.call_error()}
def aex9_balance(contract_pk, account_pk, the_very_top?) when is_boolean(the_very_top?),
do: aex9_balance(contract_pk, account_pk, top_height_hash(the_very_top?))
@spec aex9_balance(pubkey(), pubkey(), height_hash()) ::
{:ok, account_balance()} | {:error, Runner.call_error()}
def aex9_balance(contract_pk, account_pk, {type, height, hash}) do
case Runner.call_contract(contract_pk, {type, height, hash}, "balance", [
{:address, account_pk}
]) do
{:ok, {:variant, [0, 1], 1, {amt}}} -> {:ok, {amt, {type, height, hash}}}
{:ok, {:variant, [0, 1], 0, {}}} -> {:ok, {nil, {type, height, hash}}}
{:error, reason} -> {:error, reason}
end
end
@spec aex9_balances!(pubkey()) :: {balances_map(), height_hash()}
def aex9_balances!(contract_pk),
do: aex9_balances!(contract_pk, false)
@spec aex9_balances!(pubkey(), boolean()) :: {balances_map(), height_hash()}
def aex9_balances!(contract_pk, the_very_top?) when is_boolean(the_very_top?),
do: aex9_balances!(contract_pk, top_height_hash(the_very_top?))
@spec aex9_balances!(pubkey(), height_hash()) :: {balances_map(), height_hash()}
def aex9_balances!(contract_pk, {type, height, hash}) do
{:ok, addr_map} =
Runner.call_contract(
contract_pk,
{type, height, hash},
"balances",
[]
)
{addr_map, {type, height, hash}}
end
@spec aex9_balances(pubkey(), height_hash()) ::
{:ok, balances_map()} | {:error, Runner.call_error()}
def aex9_balances(contract_pk, {_type, _height, _hash} = height_hash) do
case Runner.call_contract(
contract_pk,
height_hash,
"balances",
[]
) do
{:ok, addr_map} ->
{:ok, addr_map}
{:error, reason} ->
contract_id = :aeser_api_encoder.encode(:contract_pubkey, contract_pk)
Log.warn("balances() failed! ct_id=#{contract_id}, reason=#{inspect(reason)}")
{:error, reason}
end
end
@spec prev_block_type(tuple()) :: :key | :micro
def prev_block_type(header) do
prev_hash = :aec_headers.prev_hash(header)
prev_key_hash = :aec_headers.prev_key_hash(header)
cond do
:aec_headers.height(header) == 0 -> :key
prev_hash == prev_key_hash -> :key
true -> :micro
end
end
@spec proto_vsn(Blocks.height()) :: non_neg_integer()
def proto_vsn(height) do
{vsn, _height} =
AeMdw.Node.height_proto()
|> Enum.find(fn {_vsn, vsn_height} -> height >= vsn_height end)
vsn
end
@spec nonce_at_block(Blocks.block_hash(), pubkey()) :: non_neg_integer()
def nonce_at_block(mb_hash, account_pk) do
case :aec_accounts_trees.lookup(account_pk, block_accounts_tree(mb_hash)) do
{:value, account} -> :aec_accounts.nonce(account) + 1
:none -> 1
end
end
defp block_accounts_tree(mb_hash) do
{:value, micro_block} = :aec_db.find_block(mb_hash)
header = :aec_blocks.to_header(micro_block)
{:ok, hash} = :aec_headers.hash_header(header)
consensus_mod = :aec_headers.consensus_module(header)
node = {:node, header, hash, :micro}
prev_hash = :aec_block_insertion.node_prev_hash(node)
{:value, trees_in, _tree, _difficulty, _fees, _fraud} =
:aec_db.find_block_state_and_data(prev_hash, true)
node
|> consensus_mod.state_pre_transform_micro_node(trees_in)
|> :aec_trees.accounts()
end
defp micro_block_walker(hash) do
with block <- :aec_db.get_block(hash),
:micro <- :aec_blocks.type(block) do
{block, :aec_blocks.prev_hash(block)}
else
:key -> nil
end
end
end