ruby-ethereum 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/lib/ethereum.rb +53 -0
- data/lib/ethereum/abi.rb +333 -0
- data/lib/ethereum/abi/contract_translator.rb +174 -0
- data/lib/ethereum/abi/type.rb +117 -0
- data/lib/ethereum/account.rb +72 -0
- data/lib/ethereum/address.rb +60 -0
- data/lib/ethereum/base_convert.rb +53 -0
- data/lib/ethereum/block.rb +1311 -0
- data/lib/ethereum/block_header.rb +211 -0
- data/lib/ethereum/bloom.rb +83 -0
- data/lib/ethereum/cached_block.rb +36 -0
- data/lib/ethereum/chain.rb +400 -0
- data/lib/ethereum/constant.rb +26 -0
- data/lib/ethereum/core_ext/array/safe_slice.rb +18 -0
- data/lib/ethereum/core_ext/module/lru_cache.rb +20 -0
- data/lib/ethereum/core_ext/numeric/denominations.rb +45 -0
- data/lib/ethereum/core_ext/object/truth.rb +47 -0
- data/lib/ethereum/db.rb +6 -0
- data/lib/ethereum/db/base_db.rb +9 -0
- data/lib/ethereum/db/ephem_db.rb +63 -0
- data/lib/ethereum/db/overlay_db.rb +72 -0
- data/lib/ethereum/db/refcount_db.rb +188 -0
- data/lib/ethereum/env.rb +64 -0
- data/lib/ethereum/ethash.rb +78 -0
- data/lib/ethereum/ethash_ruby.rb +38 -0
- data/lib/ethereum/ethash_ruby/cache.rb +47 -0
- data/lib/ethereum/ethash_ruby/hashimoto.rb +75 -0
- data/lib/ethereum/ethash_ruby/utils.rb +53 -0
- data/lib/ethereum/exceptions.rb +28 -0
- data/lib/ethereum/external_call.rb +173 -0
- data/lib/ethereum/fast_rlp.rb +81 -0
- data/lib/ethereum/fast_vm.rb +625 -0
- data/lib/ethereum/fast_vm/call_data.rb +44 -0
- data/lib/ethereum/fast_vm/message.rb +29 -0
- data/lib/ethereum/fast_vm/state.rb +25 -0
- data/lib/ethereum/ffi/openssl.rb +390 -0
- data/lib/ethereum/index.rb +97 -0
- data/lib/ethereum/log.rb +43 -0
- data/lib/ethereum/miner.rb +84 -0
- data/lib/ethereum/opcodes.rb +131 -0
- data/lib/ethereum/private_key.rb +92 -0
- data/lib/ethereum/pruning_trie.rb +28 -0
- data/lib/ethereum/public_key.rb +88 -0
- data/lib/ethereum/receipt.rb +53 -0
- data/lib/ethereum/secp256k1.rb +58 -0
- data/lib/ethereum/secure_trie.rb +49 -0
- data/lib/ethereum/sedes.rb +42 -0
- data/lib/ethereum/special_contract.rb +95 -0
- data/lib/ethereum/spv.rb +79 -0
- data/lib/ethereum/spv/proof.rb +31 -0
- data/lib/ethereum/spv/proof_constructor.rb +19 -0
- data/lib/ethereum/spv/proof_verifier.rb +24 -0
- data/lib/ethereum/tester.rb +14 -0
- data/lib/ethereum/tester/abi_contract.rb +65 -0
- data/lib/ethereum/tester/fixture.rb +31 -0
- data/lib/ethereum/tester/language.rb +30 -0
- data/lib/ethereum/tester/log_recorder.rb +13 -0
- data/lib/ethereum/tester/solidity_wrapper.rb +144 -0
- data/lib/ethereum/tester/state.rb +194 -0
- data/lib/ethereum/transaction.rb +196 -0
- data/lib/ethereum/transient_trie.rb +28 -0
- data/lib/ethereum/trie.rb +549 -0
- data/lib/ethereum/trie/nibble_key.rb +184 -0
- data/lib/ethereum/utils.rb +191 -0
- data/lib/ethereum/version.rb +5 -0
- data/lib/ethereum/vm.rb +606 -0
- data/lib/ethereum/vm/call_data.rb +40 -0
- data/lib/ethereum/vm/message.rb +30 -0
- data/lib/ethereum/vm/state.rb +25 -0
- metadata +284 -0
@@ -0,0 +1,1311 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Ethereum
|
6
|
+
|
7
|
+
##
|
8
|
+
# A block.
|
9
|
+
#
|
10
|
+
# All attributes from the block header are accessible via properties (i.e.
|
11
|
+
# `block.prevhash` is equivalent to `block.header.prevhash`). It is ensured
|
12
|
+
# that no discrepancies between header and the block occur.
|
13
|
+
#
|
14
|
+
class Block
|
15
|
+
include RLP::Sedes::Serializable
|
16
|
+
|
17
|
+
HeaderGetters = (BlockHeader.serializable_fields.keys - %i(state_root receipts_root tx_list_root)).freeze
|
18
|
+
HeaderSetters = HeaderGetters.map {|field| :"#{field}=" }.freeze
|
19
|
+
|
20
|
+
set_serializable_fields(
|
21
|
+
header: BlockHeader,
|
22
|
+
transaction_list: RLP::Sedes::CountableList.new(Transaction),
|
23
|
+
uncles: RLP::Sedes::CountableList.new(BlockHeader)
|
24
|
+
)
|
25
|
+
|
26
|
+
extend Forwardable
|
27
|
+
def_delegators :header, *HeaderGetters, *HeaderSetters
|
28
|
+
|
29
|
+
attr :env, :db, :config
|
30
|
+
attr_accessor :state, :transactions, :receipts, :refunds, :suicides, :ether_delta, :ancestor_hashes, :logs, :log_listeners
|
31
|
+
|
32
|
+
class <<self
|
33
|
+
##
|
34
|
+
# Assumption: blocks loaded from the db are not manipulated -> can be
|
35
|
+
# cached including hash.
|
36
|
+
def find(env, hash)
|
37
|
+
raise ArgumentError, "env must be instance of Env" unless env.instance_of?(Env)
|
38
|
+
blk = RLP.decode env.db.get(hash), sedes: Block, env: env
|
39
|
+
CachedBlock.create_cached blk
|
40
|
+
end
|
41
|
+
|
42
|
+
def verify(block, parent)
|
43
|
+
block2 = RLP.decode RLP.encode(block), sedes: Block, env: parent.env, parent: parent
|
44
|
+
raise "block not match" unless block == block2
|
45
|
+
true
|
46
|
+
rescue InvalidBlock
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Create a block without specifying transactions or uncles.
|
52
|
+
#
|
53
|
+
# @param header_rlp [String] the RLP encoded block header
|
54
|
+
# @param env [Env] provide database for the block
|
55
|
+
#
|
56
|
+
# @return [Block]
|
57
|
+
#
|
58
|
+
def build_from_header(header_rlp, env)
|
59
|
+
header = RLP.decode header_rlp, sedes: BlockHeader
|
60
|
+
new header, transaction_list: nil, uncles: [], env: env
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Create a new block based on a parent block.
|
65
|
+
#
|
66
|
+
# The block will not include any transactions and will not be finalized.
|
67
|
+
#
|
68
|
+
def build_from_parent(parent, coinbase, nonce: Constant::BYTE_EMPTY, extra_data: Constant::BYTE_EMPTY, timestamp: Time.now.to_i, uncles: [], env: nil)
|
69
|
+
env ||= parent.env
|
70
|
+
|
71
|
+
header = BlockHeader.new(
|
72
|
+
prevhash: parent.full_hash,
|
73
|
+
uncles_hash: Utils.keccak256_rlp(uncles),
|
74
|
+
coinbase: coinbase,
|
75
|
+
state_root: parent.state_root,
|
76
|
+
tx_list_root: Trie::BLANK_ROOT,
|
77
|
+
receipts_root: Trie::BLANK_ROOT,
|
78
|
+
bloom: 0,
|
79
|
+
difficulty: calc_difficulty(parent, timestamp),
|
80
|
+
mixhash: Constant::BYTE_EMPTY,
|
81
|
+
number: parent.number+1,
|
82
|
+
gas_limit: calc_gaslimit(parent),
|
83
|
+
gas_used: 0,
|
84
|
+
timestamp: timestamp,
|
85
|
+
extra_data: extra_data,
|
86
|
+
nonce: nonce
|
87
|
+
)
|
88
|
+
|
89
|
+
Block.new(
|
90
|
+
header,
|
91
|
+
transaction_list: [],
|
92
|
+
uncles: uncles,
|
93
|
+
env: env,
|
94
|
+
parent: parent,
|
95
|
+
making: true
|
96
|
+
).tap do |blk|
|
97
|
+
blk.ancestor_hashes = [parent.full_hash] + parent.ancestor_hashes
|
98
|
+
blk.log_listeners = parent.log_listeners
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def calc_difficulty(parent, ts)
|
103
|
+
config = parent.config
|
104
|
+
offset = parent.difficulty / config[:block_diff_factor]
|
105
|
+
|
106
|
+
if parent.number >= (config[:homestead_fork_blknum] - 1)
|
107
|
+
sign = [1 - ((ts - parent.timestamp) / config[:homestead_diff_adjustment_cutoff]), -99].max
|
108
|
+
else
|
109
|
+
sign = (ts - parent.timestamp) < config[:diff_adjustment_cutoff] ? 1 : -1
|
110
|
+
end
|
111
|
+
|
112
|
+
# If we enter a special mode where the genesis difficulty starts off
|
113
|
+
# below the minimal difficulty, we allow low-difficulty blocks (this will
|
114
|
+
# never happen in the official protocol)
|
115
|
+
o = [parent.difficulty + offset*sign, [parent.difficulty, config[:min_diff]].min].max
|
116
|
+
period_count = (parent.number + 1) / config[:expdiff_period]
|
117
|
+
if period_count >= config[:expdiff_free_periods]
|
118
|
+
o = [o + 2**(period_count - config[:expdiff_free_periods]), config[:min_diff]].max
|
119
|
+
end
|
120
|
+
|
121
|
+
o
|
122
|
+
end
|
123
|
+
|
124
|
+
def calc_gaslimit(parent)
|
125
|
+
config = parent.config
|
126
|
+
decay = parent.gas_limit / config[:gaslimit_ema_factor]
|
127
|
+
new_contribution = ((parent.gas_used * config[:blklim_factor_nom]) / config[:blklim_factor_den] / config[:gaslimit_ema_factor])
|
128
|
+
|
129
|
+
gl = [parent.gas_limit - decay + new_contribution, config[:min_gas_limit]].max
|
130
|
+
if gl < config[:genesis_gas_limit]
|
131
|
+
gl2 = parent.gas_limit + decay
|
132
|
+
gl = [config[:genesis_gas_limit], gl2].min
|
133
|
+
end
|
134
|
+
raise ValueError, "invalid gas limit" unless check_gaslimit(parent, gl)
|
135
|
+
|
136
|
+
gl
|
137
|
+
end
|
138
|
+
|
139
|
+
def check_gaslimit(parent, gas_limit)
|
140
|
+
config = parent.config
|
141
|
+
adjmax = parent.gas_limit / config[:gaslimit_adjmax_factor]
|
142
|
+
(gas_limit - parent.gas_limit).abs <= adjmax && gas_limit >= parent.config[:min_gas_limit]
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Build the genesis block.
|
147
|
+
#
|
148
|
+
def genesis(env, options={})
|
149
|
+
allowed_args = %i(start_alloc bloom prevhash coinbase difficulty gas_limit gas_used timestamp extra_data mixhash nonce)
|
150
|
+
invalid_options = options.keys - allowed_args
|
151
|
+
raise ArgumentError, "invalid options: #{invalid_options}" unless invalid_options.empty?
|
152
|
+
|
153
|
+
start_alloc = options[:start_alloc] || env.config[:genesis_initial_alloc]
|
154
|
+
|
155
|
+
header = BlockHeader.new(
|
156
|
+
prevhash: options[:prevhash] || env.config[:genesis_prevhash],
|
157
|
+
uncles_hash: Utils.keccak256_rlp([]),
|
158
|
+
coinbase: options[:coinbase] || env.config[:genesis_coinbase],
|
159
|
+
state_root: Trie::BLANK_ROOT,
|
160
|
+
tx_list_root: Trie::BLANK_ROOT,
|
161
|
+
receipts_root: Trie::BLANK_ROOT,
|
162
|
+
bloom: options[:bloom] || 0,
|
163
|
+
difficulty: options[:difficulty] || env.config[:genesis_difficulty],
|
164
|
+
number: 0,
|
165
|
+
gas_limit: options[:gas_limit] || env.config[:genesis_gas_limit],
|
166
|
+
gas_used: options[:gas_used] || 0,
|
167
|
+
timestamp: options[:timestamp] || 0,
|
168
|
+
extra_data: options[:extra_data] || env.config[:genesis_extra_data],
|
169
|
+
mixhash: options[:mixhash] || env.config[:genesis_mixhash],
|
170
|
+
nonce: options[:nonce] || env.config[:genesis_nonce]
|
171
|
+
)
|
172
|
+
|
173
|
+
block = Block.new header, transaction_list: [], uncles: [], env: env
|
174
|
+
|
175
|
+
start_alloc.each do |addr, data|
|
176
|
+
addr = Utils.normalize_address addr
|
177
|
+
|
178
|
+
block.set_balance addr, data[:wei] if data[:wei]
|
179
|
+
block.set_balance addr, data[:balance] if data[:balance]
|
180
|
+
block.set_code addr, data[:code] if data[:code]
|
181
|
+
block.set_nonce addr, data[:nonce] if data[:nonce]
|
182
|
+
|
183
|
+
if data[:storage]
|
184
|
+
data[:storage].each {|k, v| block.set_storage_data addr, k, v }
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
block.commit_state
|
190
|
+
block.commit_state_db
|
191
|
+
|
192
|
+
# genesis block has predefined state root (so no additional
|
193
|
+
# finalization necessary)
|
194
|
+
block
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# Arguments in format of:
|
200
|
+
# `header, transaction_list=[], uncles=[], env=nil, parent=nil,
|
201
|
+
# making=false`
|
202
|
+
#
|
203
|
+
# @param args [Array] mix of arguments:
|
204
|
+
#
|
205
|
+
# * header {BlockHeader} optional. if given, will be used as block
|
206
|
+
# header. if not given, you must specify header by `options[:header]`
|
207
|
+
# * options (Hash) optional.
|
208
|
+
# - transaction_list {Array[Transaction]} a list of transactions
|
209
|
+
# which are replayed if the state given by the header is not known.
|
210
|
+
# If the state is known, `nil` can be used instead of the empty
|
211
|
+
# list.
|
212
|
+
# - uncles {Array[BlockHeader]} a list of the headers of the uncles
|
213
|
+
# of this block
|
214
|
+
# - env {Env} env including db in which the block's state,
|
215
|
+
# transactions and receipts are stored (required)
|
216
|
+
# - parent {Block} optional parent which if not given may have to be
|
217
|
+
# loaded from the database for replay
|
218
|
+
#
|
219
|
+
def initialize(*args)
|
220
|
+
header = args.first.instance_of?(BlockHeader) ? args.first : nil
|
221
|
+
options = args.last.instance_of?(Hash) ? args.last : {}
|
222
|
+
|
223
|
+
header = options.delete(:header) if options.has_key?(:header)
|
224
|
+
transaction_list = options.has_key?(:transaction_list) ? options[:transaction_list] : []
|
225
|
+
uncles = options.has_key?(:uncles) ? options[:uncles] : []
|
226
|
+
env = options.delete(:env)
|
227
|
+
parent = options.delete(:parent)
|
228
|
+
making = options.has_key?(:making) ? options.delete(:making) : false
|
229
|
+
|
230
|
+
raise ArgumentError, "No Env object given" unless env.instance_of?(Env)
|
231
|
+
raise ArgumentError, "No database object given" unless env.db.is_a?(DB::BaseDB)
|
232
|
+
|
233
|
+
@env = env
|
234
|
+
@db = env.db
|
235
|
+
@config = env.config
|
236
|
+
|
237
|
+
_set_field :header, header
|
238
|
+
_set_field :uncles, uncles
|
239
|
+
|
240
|
+
reset_cache
|
241
|
+
@get_transactions_cache = []
|
242
|
+
|
243
|
+
self.suicides = []
|
244
|
+
self.logs = []
|
245
|
+
self.log_listeners = []
|
246
|
+
|
247
|
+
self.refunds = 0
|
248
|
+
self.ether_delta = 0
|
249
|
+
|
250
|
+
self.ancestor_hashes = number > 0 ? [prevhash] : [nil]*256
|
251
|
+
|
252
|
+
validate_parent!(parent) if parent
|
253
|
+
|
254
|
+
original_values = {
|
255
|
+
bloom: bloom,
|
256
|
+
gas_used: gas_used,
|
257
|
+
timestamp: timestamp,
|
258
|
+
difficulty: difficulty,
|
259
|
+
uncles_hash: uncles_hash,
|
260
|
+
header_mutable: header.mutable?
|
261
|
+
}
|
262
|
+
|
263
|
+
make_mutable!
|
264
|
+
header.make_mutable!
|
265
|
+
|
266
|
+
@transactions = PruningTrie.new db
|
267
|
+
@receipts = PruningTrie.new db
|
268
|
+
|
269
|
+
initialize_state(transaction_list, parent, making)
|
270
|
+
|
271
|
+
validate_block!(original_values)
|
272
|
+
unless db.has_key?("validated:#{full_hash}")
|
273
|
+
if number == 0
|
274
|
+
db.put "validated:#{full_hash}", '1'
|
275
|
+
else
|
276
|
+
db.put_temporarily "validated:#{full_hash}", '1'
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
header.block = self
|
281
|
+
header.instance_variable_set :@_mutable, original_values[:header_mutable]
|
282
|
+
end
|
283
|
+
|
284
|
+
def add_listener(l)
|
285
|
+
log_listeners.push l
|
286
|
+
end
|
287
|
+
|
288
|
+
##
|
289
|
+
# The binary block hash. This is equivalent to `header.full_hash`.
|
290
|
+
#
|
291
|
+
def full_hash
|
292
|
+
Utils.keccak256_rlp header
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# The hex encoded block hash. This is equivalent to `header.full_hash_hex`.
|
297
|
+
#
|
298
|
+
def full_hash_hex
|
299
|
+
Utils.encode_hex full_hash
|
300
|
+
end
|
301
|
+
|
302
|
+
def tx_list_root
|
303
|
+
@transactions.root_hash
|
304
|
+
end
|
305
|
+
|
306
|
+
def tx_list_root=(v)
|
307
|
+
@transactions = PruningTrie.new db, v
|
308
|
+
end
|
309
|
+
|
310
|
+
def receipts_root
|
311
|
+
@receipts.root_hash
|
312
|
+
end
|
313
|
+
|
314
|
+
def receipts_root=(v)
|
315
|
+
@receipts = PruningTrie.new db, v
|
316
|
+
end
|
317
|
+
|
318
|
+
def state_root
|
319
|
+
commit_state
|
320
|
+
@state.root_hash
|
321
|
+
end
|
322
|
+
|
323
|
+
def state_root=(v)
|
324
|
+
@state = SecureTrie.new PruningTrie.new(db, v)
|
325
|
+
reset_cache
|
326
|
+
end
|
327
|
+
|
328
|
+
def transaction_list
|
329
|
+
@transaction_count.times.map {|i| get_transaction(i) }
|
330
|
+
end
|
331
|
+
|
332
|
+
##
|
333
|
+
# Validate the uncles of this block.
|
334
|
+
#
|
335
|
+
def validate_uncles
|
336
|
+
return false if Utils.keccak256_rlp(uncles) != uncles_hash
|
337
|
+
return false if uncles.size > config[:max_uncles]
|
338
|
+
|
339
|
+
uncles.each do |uncle|
|
340
|
+
raise InvalidUncles, "Cannot find uncle prevhash in db" unless db.include?(uncle.prevhash)
|
341
|
+
if uncle.number == number
|
342
|
+
logger.error "uncle at same block height", block: self
|
343
|
+
return false
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
max_uncle_depth = config[:max_uncle_depth]
|
348
|
+
ancestor_chain = [self] + get_ancestor_list(max_uncle_depth+1)
|
349
|
+
raise ValueError, "invalid ancestor chain" unless ancestor_chain.size == [number+1, max_uncle_depth+2].min
|
350
|
+
|
351
|
+
# Uncles of this block cannot be direct ancestors and cannot also be
|
352
|
+
# uncles included 1-6 blocks ago.
|
353
|
+
ineligible = []
|
354
|
+
ancestor_chain.safe_slice(1..-1).each {|a| ineligible.concat a.uncles }
|
355
|
+
ineligible.concat(ancestor_chain.map {|a| a.header })
|
356
|
+
|
357
|
+
eligible_ancestor_hashes = ancestor_chain.safe_slice(2..-1).map(&:full_hash)
|
358
|
+
|
359
|
+
uncles.each do |uncle|
|
360
|
+
parent = Block.find env, uncle.prevhash
|
361
|
+
return false if uncle.difficulty != Block.calc_difficulty(parent, uncle.timestamp)
|
362
|
+
return false unless uncle.check_pow
|
363
|
+
|
364
|
+
unless eligible_ancestor_hashes.include?(uncle.prevhash)
|
365
|
+
eligible = eligible_ancestor_hashes.map {|h| Utils.encode_hex(h) }
|
366
|
+
logger.error "Uncle does not have a valid ancestor", block: self, eligible: eligible, uncle_prevhash: Utils.encode_hex(uncle.prevhash)
|
367
|
+
return false
|
368
|
+
end
|
369
|
+
|
370
|
+
if ineligible.include?(uncle)
|
371
|
+
logger.error "Duplicate uncle", block: self, uncle: Utils.encode_hex(Utils.keccak256_rlp(uncle))
|
372
|
+
return false
|
373
|
+
end
|
374
|
+
|
375
|
+
# FIXME: what if uncles include previously rewarded uncle?
|
376
|
+
ineligible.push uncle
|
377
|
+
end
|
378
|
+
|
379
|
+
true
|
380
|
+
end
|
381
|
+
|
382
|
+
def add_refund(x)
|
383
|
+
self.refunds += x
|
384
|
+
end
|
385
|
+
|
386
|
+
##
|
387
|
+
# Add a transaction to the transaction trie.
|
388
|
+
#
|
389
|
+
# Note that this does not execute anything, i.e. the state is not updated.
|
390
|
+
#
|
391
|
+
def add_transaction_to_list(tx)
|
392
|
+
k = RLP.encode @transaction_count
|
393
|
+
@transactions[k] = RLP.encode(tx)
|
394
|
+
|
395
|
+
r = mk_transaction_receipt tx
|
396
|
+
@receipts[k] = RLP.encode(r)
|
397
|
+
|
398
|
+
self.bloom |= r.bloom
|
399
|
+
@transaction_count += 1
|
400
|
+
end
|
401
|
+
|
402
|
+
def build_external_call(tx)
|
403
|
+
ExternalCall.new self, tx
|
404
|
+
end
|
405
|
+
|
406
|
+
def apply_transaction(tx)
|
407
|
+
validate_transaction tx
|
408
|
+
|
409
|
+
intrinsic_gas = get_intrinsic_gas tx
|
410
|
+
|
411
|
+
logger.debug "apply transaction", tx: tx.log_dict
|
412
|
+
increment_nonce tx.sender
|
413
|
+
|
414
|
+
# buy startgas
|
415
|
+
delta_balance tx.sender, -tx.startgas*tx.gasprice
|
416
|
+
|
417
|
+
message_gas = tx.startgas - intrinsic_gas
|
418
|
+
message_data = VM::CallData.new tx.data.bytes, 0, tx.data.size
|
419
|
+
message = VM::Message.new tx.sender, tx.to, tx.value, message_gas, message_data, code_address: tx.to
|
420
|
+
|
421
|
+
ec = build_external_call tx
|
422
|
+
|
423
|
+
if tx.to.true? && tx.to != Address::CREATE_CONTRACT
|
424
|
+
result, gas_remained, data = ec.apply_msg message
|
425
|
+
logger.debug "_res_", result: result, gas_remained: gas_remained, data: data
|
426
|
+
else # CREATE
|
427
|
+
result, gas_remained, data = ec.create message
|
428
|
+
raise ValueError, "gas remained is not numeric" unless gas_remained.is_a?(Numeric)
|
429
|
+
logger.debug "_create_", result: result, gas_remained: gas_remained, data: data
|
430
|
+
end
|
431
|
+
raise ValueError, "gas remained cannot be negative" unless gas_remained >= 0
|
432
|
+
logger.debug "TX APPLIED", result: result, gas_remained: gas_remained, data: data
|
433
|
+
|
434
|
+
if result.true?
|
435
|
+
logger.debug "TX SUCCESS", data: data
|
436
|
+
|
437
|
+
gas_used = tx.startgas - gas_remained
|
438
|
+
|
439
|
+
self.refunds += self.suicides.uniq.size * Opcodes::GSUICIDEREFUND
|
440
|
+
if refunds > 0
|
441
|
+
gas_refund = [refunds, gas_used/2].min
|
442
|
+
|
443
|
+
logger.debug "Refunding", gas_refunded: gas_refund
|
444
|
+
gas_remained += gas_refund
|
445
|
+
gas_used -= gas_refund
|
446
|
+
self.refunds = 0
|
447
|
+
end
|
448
|
+
|
449
|
+
delta_balance tx.sender, tx.gasprice * gas_remained
|
450
|
+
delta_balance coinbase, tx.gasprice * gas_used
|
451
|
+
self.gas_used += gas_used
|
452
|
+
|
453
|
+
output = tx.to.true? ? Utils.int_array_to_bytes(data) : data
|
454
|
+
success = 1
|
455
|
+
else # 0 = OOG failure in both cases
|
456
|
+
logger.debug "TX FAILED", reason: 'out of gas', startgas: tx.startgas, gas_remained: gas_remained
|
457
|
+
|
458
|
+
self.gas_used += tx.startgas
|
459
|
+
delta_balance coinbase, tx.gasprice*tx.startgas
|
460
|
+
|
461
|
+
output = Constant::BYTE_EMPTY
|
462
|
+
success = 0
|
463
|
+
end
|
464
|
+
|
465
|
+
commit_state
|
466
|
+
|
467
|
+
suicides.each do |s|
|
468
|
+
self.ether_delta -= get_balance(s)
|
469
|
+
set_balance s, 0 # TODO: redundant with code in SUICIDE op?
|
470
|
+
del_account s
|
471
|
+
end
|
472
|
+
self.suicides = []
|
473
|
+
|
474
|
+
add_transaction_to_list tx
|
475
|
+
self.logs = []
|
476
|
+
|
477
|
+
# TODO: change success to Bool type
|
478
|
+
return success, output
|
479
|
+
end
|
480
|
+
|
481
|
+
def get_intrinsic_gas(tx)
|
482
|
+
intrinsic_gas = tx.intrinsic_gas_used
|
483
|
+
|
484
|
+
if number >= config[:homestead_fork_blknum]
|
485
|
+
intrinsic_gas += Opcodes::CREATE[3] if tx.to.false? || tx.to == Address::CREATE_CONTRACT
|
486
|
+
end
|
487
|
+
|
488
|
+
intrinsic_gas
|
489
|
+
end
|
490
|
+
|
491
|
+
##
|
492
|
+
# Get the `num`th transaction in this block.
|
493
|
+
#
|
494
|
+
# @raise [IndexError] if the transaction does not exist
|
495
|
+
#
|
496
|
+
def get_transaction(num)
|
497
|
+
index = RLP.encode num
|
498
|
+
tx = @transactions.get index
|
499
|
+
|
500
|
+
raise IndexError, "Transaction does not exist" if tx == Trie::BLANK_NODE
|
501
|
+
RLP.decode tx, sedes: Transaction
|
502
|
+
end
|
503
|
+
|
504
|
+
##
|
505
|
+
# Build a list of all transactions in this block.
|
506
|
+
#
|
507
|
+
def get_transactions
|
508
|
+
# FIXME: such memoization is potentially buggy - what if pop b from and
|
509
|
+
# push a to the cache? size will not change while content changed.
|
510
|
+
if @get_transactions_cache.size != @transaction_count
|
511
|
+
@get_transactions_cache = transaction_list
|
512
|
+
end
|
513
|
+
|
514
|
+
@get_transactions_cache
|
515
|
+
end
|
516
|
+
|
517
|
+
##
|
518
|
+
# helper to check if block contains a tx.
|
519
|
+
#
|
520
|
+
def get_transaction_hashes
|
521
|
+
@transaction_count.times.map do |i|
|
522
|
+
Utils.keccak256 @transactions[RLP.encode(i)]
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
def include_transaction?(tx_hash)
|
527
|
+
raise ArgumentError, "argument must be transaction hash in bytes" unless tx_hash.size == 32
|
528
|
+
get_transaction_hashes.include?(tx_hash)
|
529
|
+
end
|
530
|
+
|
531
|
+
def transaction_count
|
532
|
+
@transaction_count
|
533
|
+
end
|
534
|
+
|
535
|
+
##
|
536
|
+
# Apply rewards and commit.
|
537
|
+
#
|
538
|
+
def finalize
|
539
|
+
delta = @config[:block_reward] + @config[:nephew_reward] * uncles.size
|
540
|
+
|
541
|
+
delta_balance coinbase, delta
|
542
|
+
self.ether_delta += delta
|
543
|
+
|
544
|
+
uncles.each do |uncle|
|
545
|
+
r = @config[:block_reward] * (@config[:uncle_depth_penalty_factor] + uncle.number - number) / @config[:uncle_depth_penalty_factor]
|
546
|
+
|
547
|
+
delta_balance uncle.coinbase, r
|
548
|
+
self.ether_delta += r
|
549
|
+
end
|
550
|
+
|
551
|
+
commit_state
|
552
|
+
end
|
553
|
+
|
554
|
+
##
|
555
|
+
# Serialize the block to a readable hash.
|
556
|
+
#
|
557
|
+
# @param with_state [Bool] include state for all accounts
|
558
|
+
# @param full_transactions [Bool] include serialized transactions (hashes
|
559
|
+
# otherwise)
|
560
|
+
# @param with_storage_roots [Bool] if account states are included also
|
561
|
+
# include their storage roots
|
562
|
+
# @param with_uncles [Bool] include uncle hashes
|
563
|
+
#
|
564
|
+
# @return [Hash] a hash represents the block
|
565
|
+
#
|
566
|
+
def to_h(with_state: false, full_transactions: false, with_storage_roots: false, with_uncles: false)
|
567
|
+
b = { header: header.to_h }
|
568
|
+
|
569
|
+
txlist = []
|
570
|
+
get_transactions.each_with_index do |tx, i|
|
571
|
+
receipt_rlp = @receipts[RLP.encode(i)]
|
572
|
+
receipt = RLP.decode receipt_rlp, sedes: Receipt
|
573
|
+
txjson = full_transactions ? tx.to_h : tx.full_hash
|
574
|
+
|
575
|
+
logs = receipt.logs.map {|l| Log.serialize(l) }
|
576
|
+
|
577
|
+
txlist.push(
|
578
|
+
tx: txjson,
|
579
|
+
medstate: Utils.encode_hex(receipt.state_root),
|
580
|
+
gas: receipt.gas_used.to_s,
|
581
|
+
logs: logs,
|
582
|
+
bloom: Sedes.int256.serialize(receipt.bloom)
|
583
|
+
)
|
584
|
+
end
|
585
|
+
b[:transactions] = txlist
|
586
|
+
|
587
|
+
if with_state
|
588
|
+
state_dump = {}
|
589
|
+
@state.each do |address, v|
|
590
|
+
state_dump[Utils.encode_hex(address)] = account_to_dict(address, with_storage_root: with_storage_roots)
|
591
|
+
end
|
592
|
+
b[:state] = state_dump
|
593
|
+
end
|
594
|
+
|
595
|
+
if with_uncles
|
596
|
+
b[:uncles] = uncles.map {|u| RLP.decode(u, sedes: BlockHeader) }
|
597
|
+
end
|
598
|
+
|
599
|
+
b
|
600
|
+
end
|
601
|
+
|
602
|
+
def mining_hash
|
603
|
+
header.mining_hash
|
604
|
+
end
|
605
|
+
|
606
|
+
##
|
607
|
+
# `true` if this block has a known parent, otherwise `false`.
|
608
|
+
#
|
609
|
+
def has_parent?
|
610
|
+
get_parent
|
611
|
+
true
|
612
|
+
rescue UnknownParentError
|
613
|
+
false
|
614
|
+
end
|
615
|
+
|
616
|
+
def get_parent_header
|
617
|
+
raise UnknownParentError, "Genesis block has no parent" if number == 0
|
618
|
+
BlockHeader.find db, prevhash
|
619
|
+
rescue KeyError
|
620
|
+
raise UnknownParentError, Utils.encode_hex(prevhash)
|
621
|
+
end
|
622
|
+
|
623
|
+
##
|
624
|
+
# Get the parent of this block.
|
625
|
+
#
|
626
|
+
def get_parent
|
627
|
+
raise UnknownParentError, "Genesis block has no parent" if number == 0
|
628
|
+
Block.find env, prevhash
|
629
|
+
rescue KeyError
|
630
|
+
raise UnknownParentError, Utils.encode_hex(prevhash)
|
631
|
+
end
|
632
|
+
|
633
|
+
##
|
634
|
+
# Get the summarized difficulty.
|
635
|
+
#
|
636
|
+
# If the summarized difficulty is not stored in the database, it will be
|
637
|
+
# calculated recursively and put int the database.
|
638
|
+
#
|
639
|
+
def chain_difficulty
|
640
|
+
return difficulty if genesis?
|
641
|
+
|
642
|
+
k = "difficulty:#{Utils.encode_hex(full_hash)}"
|
643
|
+
return Utils.decode_int(db.get(k)) if db.has_key?(k)
|
644
|
+
|
645
|
+
o = difficulty + get_parent.chain_difficulty
|
646
|
+
@state.db.put_temporarily k, Utils.encode_int(o)
|
647
|
+
o
|
648
|
+
end
|
649
|
+
|
650
|
+
##
|
651
|
+
# Commit account caches. Write the account caches on the corresponding
|
652
|
+
# tries.
|
653
|
+
#
|
654
|
+
def commit_state
|
655
|
+
return if @journal.empty?
|
656
|
+
|
657
|
+
changes = []
|
658
|
+
addresses = @caches[:all].keys.sort
|
659
|
+
|
660
|
+
addresses.each do |addr|
|
661
|
+
acct = get_account addr
|
662
|
+
|
663
|
+
%i(balance nonce code storage).each do |field|
|
664
|
+
if v = @caches[field][addr]
|
665
|
+
changes.push [field, addr, v]
|
666
|
+
acct.send :"#{field}=", v
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
t = SecureTrie.new PruningTrie.new(db, acct.storage)
|
671
|
+
@caches.fetch("storage:#{addr}", {}).each do |k, v|
|
672
|
+
enckey = Utils.zpad Utils.coerce_to_bytes(k), 32
|
673
|
+
val = RLP.encode v
|
674
|
+
changes.push ['storage', addr, k, v]
|
675
|
+
|
676
|
+
v.true? ? t.set(enckey, val) : t.delete(enckey)
|
677
|
+
end
|
678
|
+
|
679
|
+
acct.storage = t.root_hash
|
680
|
+
@state[addr] = RLP.encode(acct)
|
681
|
+
end
|
682
|
+
logger.debug "delta changes=#{changes}"
|
683
|
+
|
684
|
+
reset_cache
|
685
|
+
db.put_temporarily "validated:#{full_hash}", '1'
|
686
|
+
end
|
687
|
+
|
688
|
+
def commit_state_db
|
689
|
+
@state.db.commit
|
690
|
+
end
|
691
|
+
|
692
|
+
def account_exists(address)
|
693
|
+
address = Utils.normalize_address address
|
694
|
+
@state[address].size > 0 || @caches[:all].has_key?(address)
|
695
|
+
end
|
696
|
+
|
697
|
+
def add_log(log)
|
698
|
+
logs.push log
|
699
|
+
log_listeners.each {|l| l.call log }
|
700
|
+
end
|
701
|
+
|
702
|
+
##
|
703
|
+
# Increase the balance of an account.
|
704
|
+
#
|
705
|
+
# @param address [String] the address of the account (binary or hex string)
|
706
|
+
# @param value [Integer] can be positive or negative
|
707
|
+
#
|
708
|
+
# @return [Bool] return `true` if successful, otherwise `false`
|
709
|
+
#
|
710
|
+
def delta_balance(address, value)
|
711
|
+
delta_account_item(address, :balance, value)
|
712
|
+
end
|
713
|
+
|
714
|
+
##
|
715
|
+
# Reset cache and journal without commiting any changes.
|
716
|
+
#
|
717
|
+
def reset_cache
|
718
|
+
@caches = {
|
719
|
+
all: {},
|
720
|
+
balance: {},
|
721
|
+
nonce: {},
|
722
|
+
code: {},
|
723
|
+
storage: {}
|
724
|
+
}
|
725
|
+
@journal = []
|
726
|
+
end
|
727
|
+
|
728
|
+
##
|
729
|
+
# Make a snapshot of the current state to enable later reverting.
|
730
|
+
#
|
731
|
+
def snapshot
|
732
|
+
{ state: @state.root_hash,
|
733
|
+
gas: gas_used,
|
734
|
+
txs: @transactions,
|
735
|
+
txcount: @transaction_count,
|
736
|
+
refunds: refunds,
|
737
|
+
suicides: suicides,
|
738
|
+
suicides_size: suicides.size,
|
739
|
+
logs: logs,
|
740
|
+
logs_size: logs.size,
|
741
|
+
journal: @journal, # pointer to reference, so is not static
|
742
|
+
journal_size: @journal.size,
|
743
|
+
ether_delta: ether_delta
|
744
|
+
}
|
745
|
+
end
|
746
|
+
|
747
|
+
##
|
748
|
+
# Revert to a previously made snapshot.
|
749
|
+
#
|
750
|
+
# Reverting is for example neccessary when a contract runs out of gas
|
751
|
+
# during execution.
|
752
|
+
#
|
753
|
+
def revert(mysnapshot)
|
754
|
+
logger.debug "REVERTING"
|
755
|
+
|
756
|
+
@journal = mysnapshot[:journal]
|
757
|
+
# if @journal changed after snapshot
|
758
|
+
while @journal.size > mysnapshot[:journal_size]
|
759
|
+
cache, index, prev, post = @journal.pop
|
760
|
+
logger.debug "revert journal", cache: cache, index: index, prev: prev, post: post
|
761
|
+
if prev
|
762
|
+
@caches[cache][index] = prev
|
763
|
+
else
|
764
|
+
@caches[cache].delete index
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
self.suicides = mysnapshot[:suicides]
|
769
|
+
suicides.pop while suicides.size > mysnapshot[:suicides_size]
|
770
|
+
|
771
|
+
self.logs = mysnapshot[:logs]
|
772
|
+
logs.pop while logs.size > mysnapshot[:logs_size]
|
773
|
+
|
774
|
+
self.refunds = mysnapshot[:refunds]
|
775
|
+
self.gas_used = mysnapshot[:gas]
|
776
|
+
self.ether_delta = mysnapshot[:ether_delta]
|
777
|
+
|
778
|
+
@transactions = mysnapshot[:txs]
|
779
|
+
@transaction_count = mysnapshot[:txcount]
|
780
|
+
|
781
|
+
@state.set_root_hash mysnapshot[:state]
|
782
|
+
|
783
|
+
@get_transactions_cache = []
|
784
|
+
end
|
785
|
+
|
786
|
+
##
|
787
|
+
# Get the receipt of the `num`th transaction.
|
788
|
+
#
|
789
|
+
# @raise [IndexError] if receipt at index is not found
|
790
|
+
#
|
791
|
+
# @return [Receipt]
|
792
|
+
#
|
793
|
+
def get_receipt(num)
|
794
|
+
index = RLP.encode num
|
795
|
+
receipt = @receipts[index]
|
796
|
+
|
797
|
+
if receipt == Trie::BLANK_NODE
|
798
|
+
raise IndexError, "Receipt does not exist"
|
799
|
+
else
|
800
|
+
RLP.decode receipt, sedes: Receipt
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
804
|
+
##
|
805
|
+
# Build a list of all receipts in this block.
|
806
|
+
#
|
807
|
+
def get_receipts
|
808
|
+
receipts = []
|
809
|
+
i = 0
|
810
|
+
loop do
|
811
|
+
begin
|
812
|
+
receipts.push get_receipt(i)
|
813
|
+
rescue IndexError
|
814
|
+
return receipts
|
815
|
+
end
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
##
|
820
|
+
# Get the nonce of an account.
|
821
|
+
#
|
822
|
+
# @param address [String] the address of the account (binary or hex string)
|
823
|
+
#
|
824
|
+
# @return [Integer] the nonce value
|
825
|
+
#
|
826
|
+
def get_nonce(address)
|
827
|
+
get_account_item address, :nonce
|
828
|
+
end
|
829
|
+
|
830
|
+
##
|
831
|
+
# Set the nonce of an account.
|
832
|
+
#
|
833
|
+
# @param address [String] the address of the account (binary or hex string)
|
834
|
+
# @param value [Integer] the new nonce
|
835
|
+
#
|
836
|
+
# @return [Bool] `true` if successful, otherwise `false`
|
837
|
+
#
|
838
|
+
def set_nonce(address, value)
|
839
|
+
set_account_item address, :nonce, value
|
840
|
+
end
|
841
|
+
|
842
|
+
##
|
843
|
+
# Increment the nonce of an account.
|
844
|
+
#
|
845
|
+
# @param address [String] the address of the account (binary or hex string)
|
846
|
+
#
|
847
|
+
# @return [Bool] `true` if successful, otherwise `false`
|
848
|
+
#
|
849
|
+
def increment_nonce(address)
|
850
|
+
if get_nonce(address) == 0
|
851
|
+
delta_account_item address, :nonce, config[:account_initial_nonce]+1
|
852
|
+
else
|
853
|
+
delta_account_item address, :nonce, 1
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
##
|
858
|
+
# Get the balance of an account.
|
859
|
+
#
|
860
|
+
# @param address [String] the address of the account (binary or hex string)
|
861
|
+
#
|
862
|
+
# @return [Integer] balance value
|
863
|
+
#
|
864
|
+
def get_balance(address)
|
865
|
+
get_account_item address, :balance
|
866
|
+
end
|
867
|
+
|
868
|
+
##
|
869
|
+
# Set the balance of an account.
|
870
|
+
#
|
871
|
+
# @param address [String] the address of the account (binary or hex string)
|
872
|
+
# @param value [Integer] the new balance value
|
873
|
+
#
|
874
|
+
# @return [Bool] `true` if successful, otherwise `false`
|
875
|
+
#
|
876
|
+
def set_balance(address, value)
|
877
|
+
set_account_item address, :balance, value
|
878
|
+
end
|
879
|
+
|
880
|
+
##
|
881
|
+
# Increase the balance of an account.
|
882
|
+
#
|
883
|
+
# @param address [String] the address of the account (binary or hex string)
|
884
|
+
# @param value [Integer] can be positive or negative
|
885
|
+
#
|
886
|
+
# @return [Bool] `true` if successful, otherwise `false`
|
887
|
+
#
|
888
|
+
def delta_balance(address, value)
|
889
|
+
delta_account_item address, :balance, value
|
890
|
+
end
|
891
|
+
|
892
|
+
##
|
893
|
+
# Transfer a value between two account balance.
|
894
|
+
#
|
895
|
+
# @param from [String] the address of the sending account (binary or hex
|
896
|
+
# string)
|
897
|
+
# @param to [String] the address of the receiving account (binary or hex
|
898
|
+
# string)
|
899
|
+
# @param value [Integer] the (positive) value to send
|
900
|
+
#
|
901
|
+
# @return [Bool] `true` if successful, otherwise `false`
|
902
|
+
#
|
903
|
+
def transfer_value(from, to, value)
|
904
|
+
raise ArgumentError, "value must be greater or equal than zero" unless value >= 0
|
905
|
+
delta_balance(from, -value) && delta_balance(to, value)
|
906
|
+
end
|
907
|
+
|
908
|
+
##
|
909
|
+
# Get the code of an account.
|
910
|
+
#
|
911
|
+
# @param address [String] the address of the account (binary or hex string)
|
912
|
+
#
|
913
|
+
# @return [String] account code
|
914
|
+
#
|
915
|
+
def get_code(address)
|
916
|
+
get_account_item address, :code
|
917
|
+
end
|
918
|
+
|
919
|
+
##
|
920
|
+
# Set the code of an account.
|
921
|
+
#
|
922
|
+
# @param address [String] the address of the account (binary or hex string)
|
923
|
+
# @param value [String] the new code bytes
|
924
|
+
#
|
925
|
+
# @return [Bool] `true` if successful, otherwise `false`
|
926
|
+
#
|
927
|
+
def set_code(address, value)
|
928
|
+
set_account_item address, :code, value
|
929
|
+
end
|
930
|
+
|
931
|
+
##
|
932
|
+
# Get the trie holding an account's storage.
|
933
|
+
#
|
934
|
+
# @param address [String] the address of the account (binary or hex string)
|
935
|
+
#
|
936
|
+
# @return [Trie] the storage trie of account
|
937
|
+
#
|
938
|
+
def get_storage(address)
|
939
|
+
storage_root = get_account_item address, :storage
|
940
|
+
SecureTrie.new PruningTrie.new(db, storage_root)
|
941
|
+
end
|
942
|
+
|
943
|
+
def reset_storage(address)
|
944
|
+
set_account_item address, :storage, Constant::BYTE_EMPTY
|
945
|
+
|
946
|
+
cache_key = "storage:#{address}"
|
947
|
+
if @caches.has_key?(cache_key)
|
948
|
+
@caches[cache_key].each {|k, v| set_and_journal cache_key, k, 0 }
|
949
|
+
end
|
950
|
+
end
|
951
|
+
|
952
|
+
##
|
953
|
+
# Get a specific item in the storage of an account.
|
954
|
+
#
|
955
|
+
# @param address [String] the address of the account (binary or hex string)
|
956
|
+
# @param index [Integer] the index of the requested item in the storage
|
957
|
+
#
|
958
|
+
# @return [Integer] the value at storage index
|
959
|
+
#
|
960
|
+
def get_storage_data(address, index)
|
961
|
+
address = Utils.normalize_address address
|
962
|
+
|
963
|
+
cache = @caches["storage:#{address}"]
|
964
|
+
return cache[index] if cache && cache.has_key?(index)
|
965
|
+
|
966
|
+
key = Utils.zpad Utils.coerce_to_bytes(index), 32
|
967
|
+
value = get_storage(address)[key]
|
968
|
+
|
969
|
+
value.true? ? RLP.decode(value, sedes: Sedes.big_endian_int) : 0
|
970
|
+
end
|
971
|
+
|
972
|
+
##
|
973
|
+
# Set a specific item in the storage of an account.
|
974
|
+
#
|
975
|
+
# @param address [String] the address of the account (binary or hex string)
|
976
|
+
# @param index [Integer] the index of the requested item in the storage
|
977
|
+
# @param value [Integer] the new value of the item
|
978
|
+
#
|
979
|
+
def set_storage_data(address, index, value)
|
980
|
+
address = Utils.normalize_address address
|
981
|
+
|
982
|
+
cache_key = "storage:#{address}"
|
983
|
+
unless @caches.has_key?(cache_key)
|
984
|
+
@caches[cache_key] = {}
|
985
|
+
set_and_journal :all, address, true
|
986
|
+
end
|
987
|
+
|
988
|
+
set_and_journal cache_key, index, value
|
989
|
+
end
|
990
|
+
|
991
|
+
##
|
992
|
+
# Delete an account.
|
993
|
+
#
|
994
|
+
# @param address [String] the address of the account (binary or hex string)
|
995
|
+
#
|
996
|
+
def del_account(address)
|
997
|
+
address = Utils.normalize_address address
|
998
|
+
commit_state
|
999
|
+
@state.delete address
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
##
|
1003
|
+
# Serialize an account to a hash with human readable entries.
|
1004
|
+
#
|
1005
|
+
# @param address [String] the account address
|
1006
|
+
# @param with_storage_root [Bool] include the account's storage root
|
1007
|
+
# @param with_storage [Bool] include the whole account's storage
|
1008
|
+
#
|
1009
|
+
# @return [Hash] hash represent the account
|
1010
|
+
#
|
1011
|
+
def account_to_dict(address, with_storage_root: false, with_storage: true)
|
1012
|
+
address = Utils.normalize_address address
|
1013
|
+
|
1014
|
+
# if there are uncommited account changes the current storage root is
|
1015
|
+
# meaningless
|
1016
|
+
raise ArgumentError, "cannot include storage root with uncommited account changes" if with_storage_root && !@journal.empty?
|
1017
|
+
|
1018
|
+
h = {}
|
1019
|
+
account = get_account address
|
1020
|
+
|
1021
|
+
h[:nonce] = (@caches[:nonce][address] || account.nonce).to_s
|
1022
|
+
h[:balance] = (@caches[:balance][address] || account.balance).to_s
|
1023
|
+
|
1024
|
+
code = @caches[:code][address] || account.code
|
1025
|
+
h[:code] = "0x#{Utils.encode_hex code}"
|
1026
|
+
|
1027
|
+
storage_trie = SecureTrie.new PruningTrie.new(db, account.storage)
|
1028
|
+
h[:storage_root] = Utils.encode_hex storage_trie.root_hash if with_storage_root
|
1029
|
+
if with_storage
|
1030
|
+
h[:storage] = {}
|
1031
|
+
sh = storage_trie.to_h
|
1032
|
+
|
1033
|
+
cache = @caches["storage:#{address}"] || {}
|
1034
|
+
keys = cache.keys.map {|k| Utils.zpad Utils.coerce_to_bytes(k), 32 }
|
1035
|
+
|
1036
|
+
(sh.keys + keys).each do |k|
|
1037
|
+
hexkey = "0x#{Utils.encode_hex Utils.zunpad(k)}"
|
1038
|
+
|
1039
|
+
v = cache[Utils.big_endian_to_int(k)]
|
1040
|
+
if v.true?
|
1041
|
+
h[:storage][hexkey] = "0x#{Utils.encode_hex Utils.int_to_big_endian(v)}"
|
1042
|
+
else
|
1043
|
+
v = sh[k]
|
1044
|
+
h[:storage][hexkey] = "0x#{Utils.encode_hex RLP.decode(v)}" if v
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
h
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
##
|
1053
|
+
# Return `n` ancestors of this block.
|
1054
|
+
#
|
1055
|
+
# @return [Array] array of ancestors in format of `[parent, parent.parent, ...]
|
1056
|
+
#
|
1057
|
+
def get_ancestor_list(n)
|
1058
|
+
raise ArgumentError, "n must be greater or equal than zero" unless n >= 0
|
1059
|
+
|
1060
|
+
return [] if n == 0 || number == 0
|
1061
|
+
parent = get_parent
|
1062
|
+
[parent] + parent.get_ancestor_list(n-1)
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def get_ancestor_hash(n)
|
1066
|
+
raise ArgumentError, "n must be greater than 0 and less or equal than 256" unless n > 0 && n <= 256
|
1067
|
+
|
1068
|
+
while ancestor_hashes.size < n
|
1069
|
+
if number == ancestor_hashes.size - 1
|
1070
|
+
ancestor_hashes.push nil
|
1071
|
+
else
|
1072
|
+
ancestor_hashes.push self.class.find(env, ancestor_hashes[-1]).get_parent().full_hash
|
1073
|
+
end
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
ancestor_hashes[n-1]
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
def genesis?
|
1080
|
+
number == 0
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
##
|
1084
|
+
# Two blocks are equal iff they have the same hash.
|
1085
|
+
#
|
1086
|
+
def ==(other)
|
1087
|
+
(other.instance_of?(Block) || other.instance_of?(CachedBlock)) &&
|
1088
|
+
full_hash == other.full_hash
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
def hash
|
1092
|
+
Utils.big_endian_to_int full_hash
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
def >(other)
|
1096
|
+
number > other.number
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
def <(other)
|
1100
|
+
number < other.number
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
def to_s
|
1104
|
+
"#<#{self.class.name}:#{object_id} ##{number} #{Utils.encode_hex full_hash[0,8]}>"
|
1105
|
+
end
|
1106
|
+
alias :inspect :to_s
|
1107
|
+
|
1108
|
+
private
|
1109
|
+
|
1110
|
+
def logger
|
1111
|
+
@logger ||= Logger.new 'eth.block'
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
def initialize_state(transaction_list, parent, making)
|
1115
|
+
state_unknown =
|
1116
|
+
prevhash != @config[:genesis_prevhash] &&
|
1117
|
+
number != 0 &&
|
1118
|
+
header.state_root != PruningTrie::BLANK_ROOT &&
|
1119
|
+
(header.state_root.size != 32 || !db.has_key?("validated:#{full_hash}")) &&
|
1120
|
+
!making
|
1121
|
+
|
1122
|
+
if state_unknown
|
1123
|
+
raise ArgumentError, "transaction list cannot be nil" unless transaction_list
|
1124
|
+
|
1125
|
+
parent ||= get_parent_header
|
1126
|
+
@state = SecureTrie.new PruningTrie.new(db, parent.state_root)
|
1127
|
+
@transaction_count = 0 # TODO - should always equal @transactions.size
|
1128
|
+
self.gas_used = 0
|
1129
|
+
|
1130
|
+
transaction_list.each {|tx| apply_transaction tx }
|
1131
|
+
|
1132
|
+
finalize
|
1133
|
+
else # trust the state root in the header
|
1134
|
+
@state = SecureTrie.new PruningTrie.new(db, header._state_root)
|
1135
|
+
@transaction_count = 0
|
1136
|
+
|
1137
|
+
transaction_list.each {|tx| add_transaction_to_list(tx) } if transaction_list
|
1138
|
+
raise ValidationError, "Transaction list root hash does not match" if @transactions.root_hash != header.tx_list_root
|
1139
|
+
|
1140
|
+
# receipts trie populated by add_transaction_to_list is incorrect (it
|
1141
|
+
# doesn't know intermediate states), so reset it
|
1142
|
+
@receipts = PruningTrie.new db, header.receipts_root
|
1143
|
+
end
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
##
|
1147
|
+
# Validate block (header) against previous block.
|
1148
|
+
#
|
1149
|
+
def validate_parent!(parent)
|
1150
|
+
raise ValidationError, "Parent lives in different database" if parent && db != parent.db && db.db != parent.db # TODO: refactor the db.db mess
|
1151
|
+
raise ValidationError, "Block's prevhash and parent's hash do not match" if prevhash != parent.full_hash
|
1152
|
+
raise ValidationError, "Block's number is not the successor of its parent number" if number != parent.number+1
|
1153
|
+
raise ValidationError, "Block's gaslimit is inconsistent with its parent's gaslimit" unless Block.check_gaslimit(parent, gas_limit)
|
1154
|
+
raise ValidationError, "Block's difficulty is inconsistent with its parent's difficulty" if difficulty != Block.calc_difficulty(parent, timestamp)
|
1155
|
+
raise ValidationError, "Gas used exceeds gas limit" if gas_used > gas_limit
|
1156
|
+
raise ValidationError, "Timestamp equal to or before parent" if timestamp <= parent.timestamp
|
1157
|
+
raise ValidationError, "Timestamp way too large" if timestamp > Constant::UINT_MAX
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
##
|
1161
|
+
# Validate (transaction applied) block against its header, plus fields and
|
1162
|
+
# value check.
|
1163
|
+
#
|
1164
|
+
def validate_block!(original_values)
|
1165
|
+
raise InvalidBlock, "gas_used mistmatch actual: #{gas_used} target: #{original_values[:gas_used]}" if gas_used != original_values[:gas_used]
|
1166
|
+
raise InvalidBlock, "timestamp mistmatch actual: #{timestamp} target: #{original_values[:timestamp]}" if timestamp != original_values[:timestamp]
|
1167
|
+
raise InvalidBlock, "difficulty mistmatch actual: #{difficulty} target: #{original_values[:difficulty]}" if difficulty != original_values[:difficulty]
|
1168
|
+
raise InvalidBlock, "bloom mistmatch actual: #{bloom} target: #{original_values[:bloom]}" if bloom != original_values[:bloom]
|
1169
|
+
|
1170
|
+
uh = Utils.keccak256_rlp uncles
|
1171
|
+
raise InvalidBlock, "uncles_hash mistmatch actual: #{uh} target: #{original_values[:uncles_hash]}" if uh != original_values[:uncles_hash]
|
1172
|
+
|
1173
|
+
raise InvalidBlock, "header must reference no block" unless header.block.nil?
|
1174
|
+
|
1175
|
+
raise InvalidBlock, "state_root mistmatch actual: #{Utils.encode_hex @state.root_hash} target: #{Utils.encode_hex header.state_root}" if @state.root_hash != header.state_root
|
1176
|
+
raise InvalidBlock, "tx_list_root mistmatch actual: #{@transactions.root_hash} target: #{header.tx_list_root}" if @transactions.root_hash != header.tx_list_root
|
1177
|
+
raise InvalidBlock, "receipts_root mistmatch actual: #{@receipts.root_hash} target: #{header.receipts_root}" if @receipts.root_hash != header.receipts_root
|
1178
|
+
|
1179
|
+
raise ValueError, "Block is invalid" unless validate_fields
|
1180
|
+
|
1181
|
+
raise ValueError, "Extra data cannot exceed #{config[:max_extradata_length]} bytes" if header.extra_data.size > config[:max_extradata_length]
|
1182
|
+
raise ValueError, "Coinbase cannot be empty address" if header.coinbase.false?
|
1183
|
+
raise ValueError, "State merkle root of block #{self} not found in database" unless @state.root_hash_valid?
|
1184
|
+
raise ValueError, "PoW check failed" if !genesis? && nonce.true? && !header.check_pow
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
def validate_transaction(tx)
|
1188
|
+
raise UnsignedTransactionError.new(tx) unless tx.sender
|
1189
|
+
|
1190
|
+
acct_nonce = get_nonce tx.sender
|
1191
|
+
raise InvalidNonce, "#{tx}: nonce actual: #{tx.nonce} target: #{acct_nonce}" if acct_nonce != tx.nonce
|
1192
|
+
|
1193
|
+
min_gas = get_intrinsic_gas tx
|
1194
|
+
raise InsufficientStartGas, "#{tx}: startgas actual: #{tx.startgas} target: #{min_gas}" if tx.startgas < min_gas
|
1195
|
+
|
1196
|
+
total_cost = tx.value + tx.gasprice * tx.startgas
|
1197
|
+
balance = get_balance tx.sender
|
1198
|
+
raise InsufficientBalance, "#{tx}: balance actual: #{balance} target: #{total_cost}" if balance < total_cost
|
1199
|
+
|
1200
|
+
accum_gas = gas_used + tx.startgas
|
1201
|
+
raise BlockGasLimitReached, "#{tx}: gaslimit actual: #{accum_gas} target: #{gas_limit}" if accum_gas > gas_limit
|
1202
|
+
|
1203
|
+
tx.check_low_s if number >= config[:homestead_fork_blknum]
|
1204
|
+
|
1205
|
+
true
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
##
|
1209
|
+
# Check that the values of all fields are well formed.
|
1210
|
+
#
|
1211
|
+
# Serialize and deserialize and check that the values didn't change.
|
1212
|
+
#
|
1213
|
+
def validate_fields
|
1214
|
+
l = Block.serialize self
|
1215
|
+
RLP.decode(RLP.encode(l)) == l
|
1216
|
+
end
|
1217
|
+
|
1218
|
+
##
|
1219
|
+
# Add a value to an account item.
|
1220
|
+
#
|
1221
|
+
# If the resulting value would be negative, it is left unchanged and
|
1222
|
+
# `false` is returned.
|
1223
|
+
#
|
1224
|
+
# @param address [String] the address of the account (binary or hex string)
|
1225
|
+
# @param param [Symbol] the parameter to increase or decrease (`:nonce`,
|
1226
|
+
# `:balance`, `:storage`, or `:code`)
|
1227
|
+
# @param value [Integer] can be positive or negative
|
1228
|
+
#
|
1229
|
+
# @return [Bool] `true` if the operation was successful, `false` if not
|
1230
|
+
#
|
1231
|
+
def delta_account_item(address, param, value)
|
1232
|
+
new_value = get_account_item(address, param) + value
|
1233
|
+
return false if new_value < 0
|
1234
|
+
|
1235
|
+
set_account_item(address, param, new_value % 2**256)
|
1236
|
+
true
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
##
|
1240
|
+
# Get a specific parameter of a specific account.
|
1241
|
+
#
|
1242
|
+
# @param address [String] the address of the account (binary or hex string)
|
1243
|
+
# @param param [Symbol] the requested parameter (`:nonce`, `:balance`,
|
1244
|
+
# `:storage` or `:code`)
|
1245
|
+
#
|
1246
|
+
# @return [Object] the value
|
1247
|
+
#
|
1248
|
+
def get_account_item(address, param)
|
1249
|
+
address = Utils.normalize_address address, allow_blank: true
|
1250
|
+
return @caches[param][address] if @caches[param].has_key?(address)
|
1251
|
+
|
1252
|
+
account = get_account address
|
1253
|
+
v = account.send param
|
1254
|
+
@caches[param][address] = v
|
1255
|
+
v
|
1256
|
+
end
|
1257
|
+
|
1258
|
+
##
|
1259
|
+
# Set a specific parameter of a specific account.
|
1260
|
+
#
|
1261
|
+
# @param address [String] the address of the account (binary or hex string)
|
1262
|
+
# @param param [Symbol] the requested parameter (`:nonce`, `:balance`,
|
1263
|
+
# `:storage` or `:code`)
|
1264
|
+
# @param value [Object] the new value
|
1265
|
+
#
|
1266
|
+
def set_account_item(address, param, value)
|
1267
|
+
raise ArgumentError, "invalid address: #{address}" unless address.size == 20 || address.size == 40
|
1268
|
+
address = Utils.decode_hex(address) if address.size == 40
|
1269
|
+
|
1270
|
+
set_and_journal(param, address, value)
|
1271
|
+
set_and_journal(:all, address, true)
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
##
|
1275
|
+
# Get the account with the given address.
|
1276
|
+
#
|
1277
|
+
# Note that this method ignores cached account items.
|
1278
|
+
#
|
1279
|
+
def get_account(address)
|
1280
|
+
address = Utils.normalize_address address, allow_blank: true
|
1281
|
+
rlpdata = @state[address]
|
1282
|
+
|
1283
|
+
if rlpdata == Trie::BLANK_NODE
|
1284
|
+
Account.build_blank db, config[:account_initial_nonce]
|
1285
|
+
else
|
1286
|
+
RLP.decode(rlpdata, sedes: Account, db: db).tap do |acct|
|
1287
|
+
acct.make_mutable!
|
1288
|
+
acct._cached_rlp = nil
|
1289
|
+
end
|
1290
|
+
end
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
##
|
1294
|
+
# @param ns [Symbol] cache namespace
|
1295
|
+
# @param k [String] cache key
|
1296
|
+
# @param v [Object] cache value
|
1297
|
+
#
|
1298
|
+
def set_and_journal(ns, k, v)
|
1299
|
+
prev = @caches[ns][k]
|
1300
|
+
if prev != v
|
1301
|
+
@journal.push [ns, k, prev, v]
|
1302
|
+
@caches[ns][k] = v
|
1303
|
+
end
|
1304
|
+
end
|
1305
|
+
|
1306
|
+
def mk_transaction_receipt(tx)
|
1307
|
+
Receipt.new state_root, gas_used, logs
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
end
|
1311
|
+
end
|