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,211 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
|
5
|
+
##
|
6
|
+
# A block header.
|
7
|
+
#
|
8
|
+
# If the block with this header exists as an instance of {Block}, the
|
9
|
+
# connection can be made explicit by setting `BlockHeader.block`. Then,
|
10
|
+
# `BlockHeader.state_root`, `BlockHeader.tx_list_root` and
|
11
|
+
# `BlockHeader.receipts_root` always refer to the up-to-date value in the
|
12
|
+
# block instance.
|
13
|
+
#
|
14
|
+
# * `@block` - an instance of {Block} or `nil`
|
15
|
+
# * `@prevhash` - the 32 byte hash of the previous block
|
16
|
+
# * `@uncles_hash` - the 32 byte hash of the RLP encoded list of uncle headers
|
17
|
+
# * `@coinbase` - the 20 byte coinbase address
|
18
|
+
# * `@state_root` - the root of the block's state trie
|
19
|
+
# * `@tx_list_root` - the root of the block's transaction trie
|
20
|
+
# * `@receipts_root` - the root of the block's receipts trie
|
21
|
+
# * `@bloom` - bloom filter
|
22
|
+
# * `@difficulty` - the block's difficulty
|
23
|
+
# * `@number` - the number of ancestors of this block (0 for the genesis block)
|
24
|
+
# * `@gas_limit` - the block's gas limit
|
25
|
+
# * `@gas_used` - the total amount of gas used by all transactions in this block
|
26
|
+
# * `@timestamp` - a UNIX timestamp
|
27
|
+
# * `@extra_data` - up to 1024 bytes of additional data
|
28
|
+
# * `@nonce` - a 8 byte nonce constituting a proof-of-work, or the empty
|
29
|
+
# string as a placeholder
|
30
|
+
#
|
31
|
+
class BlockHeader
|
32
|
+
include RLP::Sedes::Serializable
|
33
|
+
|
34
|
+
extend Sedes
|
35
|
+
|
36
|
+
set_serializable_fields(
|
37
|
+
prevhash: hash32,
|
38
|
+
uncles_hash: hash32,
|
39
|
+
coinbase: address,
|
40
|
+
state_root: trie_root,
|
41
|
+
tx_list_root: trie_root,
|
42
|
+
receipts_root: trie_root,
|
43
|
+
bloom: int256,
|
44
|
+
difficulty: big_endian_int,
|
45
|
+
number: big_endian_int,
|
46
|
+
gas_limit: big_endian_int,
|
47
|
+
gas_used: big_endian_int,
|
48
|
+
timestamp: big_endian_int,
|
49
|
+
extra_data: binary,
|
50
|
+
mixhash: binary,
|
51
|
+
nonce: RLP::Sedes::Binary.new(min_length: 8, allow_empty: true) # FIXME: should be fixed length 8?
|
52
|
+
)
|
53
|
+
|
54
|
+
class <<self
|
55
|
+
def from_block_rlp(rlp_data)
|
56
|
+
block_data = RLP.decode_lazy rlp_data
|
57
|
+
deserialize block_data[0]
|
58
|
+
end
|
59
|
+
|
60
|
+
def find(db, hash)
|
61
|
+
bh = from_block_rlp db.get(hash)
|
62
|
+
raise ValidationError, "BlockHeader.hash is broken" if bh.full_hash != hash
|
63
|
+
bh
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
attr_accessor :block
|
68
|
+
|
69
|
+
def initialize(options={})
|
70
|
+
fields = {
|
71
|
+
prevhash: Env::DEFAULT_CONFIG[:genesis_prevhash],
|
72
|
+
uncles_hash: Utils.keccak256_rlp([]),
|
73
|
+
coinbase: Env::DEFAULT_CONFIG[:genesis_coinbase],
|
74
|
+
state_root: PruningTrie::BLANK_ROOT,
|
75
|
+
tx_list_root: PruningTrie::BLANK_ROOT,
|
76
|
+
receipts_root: PruningTrie::BLANK_ROOT,
|
77
|
+
bloom: 0,
|
78
|
+
difficulty: Env::DEFAULT_CONFIG[:genesis_difficulty],
|
79
|
+
number: 0,
|
80
|
+
gas_limit: Env::DEFAULT_CONFIG[:genesis_gas_limit],
|
81
|
+
gas_used: 0,
|
82
|
+
timestamp: 0,
|
83
|
+
extra_data: '',
|
84
|
+
mixhash: Env::DEFAULT_CONFIG[:genesis_mixhash],
|
85
|
+
nonce: ''
|
86
|
+
}.merge(options)
|
87
|
+
|
88
|
+
fields[:coinbase] = Utils.decode_hex(fields[:coinbase]) if fields[:coinbase].size == 40
|
89
|
+
raise ArgumentError, "invalid coinbase #{fields[:coinbase]}" unless fields[:coinbase].size == 20
|
90
|
+
raise ArgumentError, "invalid difficulty" unless fields[:difficulty] > 0
|
91
|
+
|
92
|
+
self.block = nil
|
93
|
+
@fimxe_hash = nil
|
94
|
+
|
95
|
+
super(**fields)
|
96
|
+
end
|
97
|
+
|
98
|
+
def _state_root
|
99
|
+
@state_root
|
100
|
+
end
|
101
|
+
|
102
|
+
def state_root
|
103
|
+
get_with_block :state_root
|
104
|
+
end
|
105
|
+
|
106
|
+
def state_root=(v)
|
107
|
+
set_with_block :state_root, v
|
108
|
+
end
|
109
|
+
|
110
|
+
def tx_list_root
|
111
|
+
get_with_block :tx_list_root
|
112
|
+
end
|
113
|
+
|
114
|
+
def tx_list_root=(v)
|
115
|
+
set_with_block :tx_list_root, v
|
116
|
+
end
|
117
|
+
|
118
|
+
def receipts_root
|
119
|
+
get_with_block :receipts_root
|
120
|
+
end
|
121
|
+
|
122
|
+
def receipts_root=(v)
|
123
|
+
set_with_block :receipts_root, v
|
124
|
+
end
|
125
|
+
|
126
|
+
def full_hash
|
127
|
+
Utils.keccak256_rlp self
|
128
|
+
end
|
129
|
+
|
130
|
+
def full_hash_hex
|
131
|
+
Utils.encode_hex full_hash
|
132
|
+
end
|
133
|
+
|
134
|
+
def mining_hash
|
135
|
+
Utils.keccak256 RLP.encode(self, sedes: self.class.exclude(%i(mixhash nonce)))
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Check if the proof-of-work of the block is valid.
|
140
|
+
#
|
141
|
+
# @param nonce [String] if given the proof of work function will be
|
142
|
+
# evaluated with this nonce instead of the one already present in the
|
143
|
+
# header
|
144
|
+
#
|
145
|
+
# @return [Bool]
|
146
|
+
#
|
147
|
+
def check_pow(nonce=nil)
|
148
|
+
logger.debug "checking pow", block: full_hash_hex[0,8]
|
149
|
+
Miner.check_pow(number, mining_hash, mixhash, nonce || self.nonce, difficulty)
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Serialize the header to a readable hash.
|
154
|
+
#
|
155
|
+
def to_h
|
156
|
+
h = {}
|
157
|
+
|
158
|
+
%i(prevhash uncles_hash extra_data nonce mixhash).each do |field|
|
159
|
+
h[field] = "0x#{Utils.encode_hex(send field)}"
|
160
|
+
end
|
161
|
+
|
162
|
+
%i(state_root tx_list_root receipts_root coinbase).each do |field|
|
163
|
+
h[field] = Utils.encode_hex send(field)
|
164
|
+
end
|
165
|
+
|
166
|
+
%i(number difficulty gas_limit gas_used timestamp).each do |field|
|
167
|
+
h[field] = send(field).to_s
|
168
|
+
end
|
169
|
+
|
170
|
+
h[:bloom] = Utils.encode_hex Sedes.int256.serialize(bloom)
|
171
|
+
|
172
|
+
h
|
173
|
+
end
|
174
|
+
|
175
|
+
def to_s
|
176
|
+
"#<#{self.class.name}:#{object_id} ##{number} #{full_hash_hex[0,8]}>"
|
177
|
+
end
|
178
|
+
alias :inspect :to_s
|
179
|
+
|
180
|
+
##
|
181
|
+
# Two blockheader are equal iff they have the same hash.
|
182
|
+
#
|
183
|
+
def ==(other)
|
184
|
+
other.instance_of?(BlockHeader) && full_hash == other.full_hash
|
185
|
+
end
|
186
|
+
alias :eql? :==
|
187
|
+
|
188
|
+
def hash
|
189
|
+
Utils.big_endian_to_int full_hash
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def logger
|
195
|
+
@logger ||= Logger.new 'eth.block.header'
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_with_block(attr)
|
199
|
+
block ? block.send(attr) : instance_variable_get(:"@#{attr}")
|
200
|
+
end
|
201
|
+
|
202
|
+
def set_with_block(attr, v)
|
203
|
+
if block
|
204
|
+
block.send :"#{attr}=", v
|
205
|
+
else
|
206
|
+
_set_field attr, v
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
|
5
|
+
###
|
6
|
+
# Blooms are the 3-point, 2048-bit (11-bits/point) Bloom filter of each
|
7
|
+
# component (except data) of each log entry of each transaction.
|
8
|
+
#
|
9
|
+
# We set the bits of a 2048-bit value whose indices are given by the low
|
10
|
+
# order 9-bits of the first three double-bytes of the SHA3 of each value.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# bloom(0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6)
|
14
|
+
# sha3: bd2b01afcd27800b54d2179edc49e2bffde5078bb6d0b204694169b1643fb108
|
15
|
+
# first 3 double-bytes: bd2b, 01af, cd27
|
16
|
+
# bits in bloom: 1323, 431, 1319
|
17
|
+
#
|
18
|
+
# Blooms are type of `Integer`.
|
19
|
+
#
|
20
|
+
class Bloom
|
21
|
+
|
22
|
+
BITS = 2048
|
23
|
+
MASK = 2047
|
24
|
+
BUCKETS = 3
|
25
|
+
|
26
|
+
class <<self
|
27
|
+
def from(v)
|
28
|
+
insert(0, v)
|
29
|
+
end
|
30
|
+
|
31
|
+
def from_array(args)
|
32
|
+
blooms = args.map {|arg| from(arg) }
|
33
|
+
combine *blooms
|
34
|
+
end
|
35
|
+
|
36
|
+
def insert(bloom, v)
|
37
|
+
h = Utils.keccak256 v
|
38
|
+
BUCKETS.times {|i| bloom |= get_index(h, i) }
|
39
|
+
bloom
|
40
|
+
end
|
41
|
+
|
42
|
+
def query(bloom, v)
|
43
|
+
b = from v
|
44
|
+
(bloom & b) == b
|
45
|
+
end
|
46
|
+
|
47
|
+
def combine(*args)
|
48
|
+
args.reduce(0, &:|)
|
49
|
+
end
|
50
|
+
|
51
|
+
def bits(v)
|
52
|
+
h = Utils.keccak256 v
|
53
|
+
BUCKETS.times.map {|i| bits_in_number get_index(h, i) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def bits_in_number(v)
|
57
|
+
BITS.times.select {|i| (1<<i) & v > 0 }
|
58
|
+
end
|
59
|
+
|
60
|
+
def b256(int_bloom)
|
61
|
+
Utils.zpad Utils.int_to_big_endian(int_bloom), 256
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Get index for hash double-byte in bloom.
|
66
|
+
#
|
67
|
+
# @param hash [String] value hash
|
68
|
+
# @param pos [Integer] double-byte position in hash, can only be 0, 1, 2
|
69
|
+
#
|
70
|
+
# @return [Integer] bloom index
|
71
|
+
#
|
72
|
+
def get_index(hash, pos)
|
73
|
+
raise ArgumentError, "invalid double-byte position" unless [0,1,2].include?(pos)
|
74
|
+
|
75
|
+
i = pos*2
|
76
|
+
hi = hash[i].ord << 8
|
77
|
+
lo = hash[i+1].ord
|
78
|
+
1 << ((hi+lo) & MASK)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Ethereum
|
2
|
+
module CachedBlock
|
3
|
+
|
4
|
+
def self.create_cached(blk)
|
5
|
+
blk.singleton_class.send :include, self
|
6
|
+
blk
|
7
|
+
end
|
8
|
+
|
9
|
+
def state_root=(*args)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
def revert(*args)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def commit_state
|
18
|
+
# do nothing
|
19
|
+
end
|
20
|
+
|
21
|
+
def hash
|
22
|
+
Utils.big_endian_to_int full_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def full_hash
|
26
|
+
@full_hash ||= super
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def set_account_item(*args)
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,400 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Ethereum
|
4
|
+
|
5
|
+
##
|
6
|
+
# Manages the chain and requests to it.
|
7
|
+
#
|
8
|
+
class Chain
|
9
|
+
|
10
|
+
HEAD_KEY = 'HEAD'.freeze
|
11
|
+
|
12
|
+
attr :env, :index, :head_candidate
|
13
|
+
|
14
|
+
##
|
15
|
+
# @param env [Ethereum::Env] configuration of the chain
|
16
|
+
#
|
17
|
+
def initialize(env, genesis: nil, new_head_cb: nil, coinbase: Address::ZERO)
|
18
|
+
raise ArgumentError, "env must be instance of Env" unless env.instance_of?(Env)
|
19
|
+
|
20
|
+
@env = env
|
21
|
+
@db = env.db
|
22
|
+
|
23
|
+
@new_head_cb = new_head_cb
|
24
|
+
@index = Index.new env
|
25
|
+
@coinbase = coinbase
|
26
|
+
|
27
|
+
initialize_blockchain(genesis) unless @db.has_key?(HEAD_KEY)
|
28
|
+
logger.debug "chain @ head_hash=#{head}"
|
29
|
+
|
30
|
+
@genesis = get @index.get_block_by_number(0)
|
31
|
+
logger.debug "got genesis", nonce: Utils.encode_hex(@genesis.nonce), difficulty: @genesis.difficulty
|
32
|
+
|
33
|
+
@head_candidate = nil
|
34
|
+
update_head_candidate
|
35
|
+
end
|
36
|
+
|
37
|
+
def head
|
38
|
+
initialize_blockchain unless @db && @db.has_key?(HEAD_KEY)
|
39
|
+
ptr = @db.get HEAD_KEY
|
40
|
+
Block.find @env, ptr
|
41
|
+
end
|
42
|
+
|
43
|
+
def coinbase
|
44
|
+
raise AssertError, "coinbase changed!" unless @head_candidate.coinbase == @coinbase
|
45
|
+
@coinbase
|
46
|
+
end
|
47
|
+
|
48
|
+
def coinbase=(v)
|
49
|
+
@coinbase = v
|
50
|
+
# block reward goes to different address => redo finalization of head candidate
|
51
|
+
update_head head
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Return the uncles of `block`.
|
56
|
+
#
|
57
|
+
def get_uncles(block)
|
58
|
+
if block.has_parent?
|
59
|
+
get_brothers(block.get_parent)
|
60
|
+
else
|
61
|
+
[]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Return the uncles of the hypothetical child of `block`.
|
67
|
+
#
|
68
|
+
def get_brothers(block)
|
69
|
+
o = []
|
70
|
+
i = 0
|
71
|
+
|
72
|
+
while block.has_parent? && i < @env.config[:max_uncle_depth]
|
73
|
+
parent = block.get_parent
|
74
|
+
children = get_children(parent).select {|c| c != block }
|
75
|
+
o.concat children
|
76
|
+
block = parent
|
77
|
+
i += 1
|
78
|
+
end
|
79
|
+
|
80
|
+
o
|
81
|
+
end
|
82
|
+
|
83
|
+
def get(blockhash)
|
84
|
+
raise ArgumentError, "blockhash must be a String" unless blockhash.instance_of?(String)
|
85
|
+
raise ArgumentError, "blockhash size must be 32" unless blockhash.size == 32
|
86
|
+
Block.find(@env, blockhash)
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_bloom(blockhash)
|
90
|
+
b = RLP.decode RLP.descend(@db.get(blockhash), 0, 6)
|
91
|
+
Utils.big_endian_to_int b
|
92
|
+
end
|
93
|
+
|
94
|
+
def has_block(blockhash)
|
95
|
+
raise ArgumentError, "blockhash must be a String" unless blockhash.instance_of?(String)
|
96
|
+
raise ArgumentError, "blockhash size must be 32" unless blockhash.size == 32
|
97
|
+
@db.include?(blockhash)
|
98
|
+
end
|
99
|
+
alias :include? :has_block
|
100
|
+
alias :has_key? :has_block
|
101
|
+
|
102
|
+
def commit
|
103
|
+
@db.commit
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Returns `true` if block was added successfully.
|
108
|
+
#
|
109
|
+
def add_block(block, forward_pending_transaction=true)
|
110
|
+
unless block.has_parent? || block.genesis?
|
111
|
+
logger.debug "missing parent", block_hash: block
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
|
115
|
+
unless block.validate_uncles
|
116
|
+
logger.debug "invalid uncles", block_hash: block
|
117
|
+
return false
|
118
|
+
end
|
119
|
+
|
120
|
+
unless block.header.check_pow || block.genesis?
|
121
|
+
logger.debug "invalid nonce", block_hash: block
|
122
|
+
return false
|
123
|
+
end
|
124
|
+
|
125
|
+
if block.has_parent?
|
126
|
+
begin
|
127
|
+
Block.verify(block, block.get_parent)
|
128
|
+
rescue InvalidBlock => e
|
129
|
+
log.fatal "VERIFICATION FAILED", block_hash: block, error: e
|
130
|
+
|
131
|
+
f = File.join Utils.data_dir, 'badblock.log'
|
132
|
+
File.write(f, Utils.encode_hex(RLP.encode(block)))
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
if block.number < head.number
|
138
|
+
logger.debug "older than head", block_hash: block, head_hash: head
|
139
|
+
end
|
140
|
+
|
141
|
+
@index.add_block block
|
142
|
+
store_block block
|
143
|
+
|
144
|
+
# set to head if this makes the longest chain w/ most work for that number
|
145
|
+
if block.chain_difficulty > head.chain_difficulty
|
146
|
+
logger.debug "new head", block_hash: block, num_tx: block.transaction_count
|
147
|
+
update_head block, forward_pending_transaction
|
148
|
+
elsif block.number > head.number
|
149
|
+
logger.warn "has higher blk number than head but lower chain_difficulty", block_has: block, head_hash: head, block_difficulty: block.chain_difficulty, head_difficulty: head.chain_difficulty
|
150
|
+
end
|
151
|
+
|
152
|
+
# Refactor the long calling chain
|
153
|
+
block.transactions.clear_all
|
154
|
+
block.receipts.clear_all
|
155
|
+
block.state.db.commit_refcount_changes block.number
|
156
|
+
block.state.db.cleanup block.number
|
157
|
+
|
158
|
+
commit # batch commits all changes that came with the new block
|
159
|
+
true
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_children(block)
|
163
|
+
@index.get_children(block.full_hash).map {|c| get(c) }
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Add a transaction to the `head_candidate` block.
|
168
|
+
#
|
169
|
+
# If the transaction is invalid, the block will not be changed.
|
170
|
+
#
|
171
|
+
# @return [Bool,NilClass] `true` is the transaction was successfully added or
|
172
|
+
# `false` if the transaction was invalid, `nil` if it's already included
|
173
|
+
#
|
174
|
+
def add_transaction(transaction)
|
175
|
+
raise AssertError, "head candiate cannot be nil" unless @head_candidate
|
176
|
+
|
177
|
+
hc = @head_candidate
|
178
|
+
logger.debug "add tx", num_txs: transaction_count, tx: transaction, on: hc
|
179
|
+
|
180
|
+
if @head_candidate.include_transaction?(transaction.full_hash)
|
181
|
+
logger.debug "known tx"
|
182
|
+
return
|
183
|
+
end
|
184
|
+
|
185
|
+
old_state_root = hc.state_root
|
186
|
+
# revert finalization
|
187
|
+
hc.state_root = @pre_finalize_state_root
|
188
|
+
begin
|
189
|
+
success, output = hc.apply_transaction(transaction)
|
190
|
+
rescue InvalidTransaction => e
|
191
|
+
# if unsuccessful the prerequisites were not fullfilled and the tx is
|
192
|
+
# invalid, state must not have changed
|
193
|
+
logger.debug "invalid tx", error: e
|
194
|
+
hc.state_root = old_state_root
|
195
|
+
return false
|
196
|
+
end
|
197
|
+
logger.debug "valid tx"
|
198
|
+
|
199
|
+
# we might have a new head_candidate (due to ctx switches in up layer)
|
200
|
+
if @head_candidate != hc
|
201
|
+
logger.debug "head_candidate changed during validation, trying again"
|
202
|
+
return add_transaction(transaction)
|
203
|
+
end
|
204
|
+
|
205
|
+
@pre_finalize_state_root = hc.state_root
|
206
|
+
hc.finalize
|
207
|
+
logger.debug "tx applied", result: output
|
208
|
+
|
209
|
+
raise AssertError, "state root unchanged!" unless old_state_root != hc.state_root
|
210
|
+
true
|
211
|
+
end
|
212
|
+
|
213
|
+
##
|
214
|
+
# Get a list of new transactions not yet included in a mined block but
|
215
|
+
# known to the chain.
|
216
|
+
#
|
217
|
+
def get_transactions
|
218
|
+
if @head_candidate
|
219
|
+
logger.debug "get_transactions called", on: @head_candidate
|
220
|
+
@head_candidate.get_transactions
|
221
|
+
else
|
222
|
+
[]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def transaction_count
|
227
|
+
@head_candidate ? @head_candidate.transaction_count : 0
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Return `count` of blocks starting from head or `start`.
|
232
|
+
#
|
233
|
+
def get_chain(start: '', count: 10)
|
234
|
+
logger.debug "get_chain", start: Utils.encode_hex(start), count: count
|
235
|
+
|
236
|
+
if start.true?
|
237
|
+
return [] unless @index.db.include?(start)
|
238
|
+
|
239
|
+
block = get start
|
240
|
+
return [] unless in_main_branch?(block)
|
241
|
+
else
|
242
|
+
block = head
|
243
|
+
end
|
244
|
+
|
245
|
+
blocks = []
|
246
|
+
count.times do |i|
|
247
|
+
blocks.push block
|
248
|
+
break if block.genesis?
|
249
|
+
block = block.get_parent
|
250
|
+
end
|
251
|
+
|
252
|
+
blocks
|
253
|
+
end
|
254
|
+
|
255
|
+
def in_main_branch?(block)
|
256
|
+
block.full_hash == @index.get_block_by_number(block.number)
|
257
|
+
rescue KeyError
|
258
|
+
false
|
259
|
+
end
|
260
|
+
|
261
|
+
def get_descendants(block, count: 1)
|
262
|
+
logger.debug "get_descendants", block_hash: block
|
263
|
+
raise AssertError, "cannot find block hash in current chain" unless include?(block.full_hash)
|
264
|
+
|
265
|
+
block_numbers = (block.number+1)...([head.number+1, block.number+count+1].min)
|
266
|
+
block_numbers.map {|n| get @index.get_block_by_number(n) }
|
267
|
+
end
|
268
|
+
|
269
|
+
def update_head(block, forward_pending_transaction=true)
|
270
|
+
logger.debug "updating head"
|
271
|
+
logger.debug "New Head is on a different branch", head_hash: block, old_head_hash: head if !block.genesis? && block.get_parent != head
|
272
|
+
|
273
|
+
# Some temporary auditing to make sure pruning is working well
|
274
|
+
if block.number > 0 && block.number % 500 == 0 && @db.instance_of?(DB::RefcountDB)
|
275
|
+
# TODO
|
276
|
+
end
|
277
|
+
|
278
|
+
# Fork detected, revert death row and change logs
|
279
|
+
if block.number > 0
|
280
|
+
b = block.get_parent
|
281
|
+
h = head
|
282
|
+
b_children = []
|
283
|
+
|
284
|
+
if b.full_hash != h.full_hash
|
285
|
+
logger.warn "reverting"
|
286
|
+
|
287
|
+
while h.number > b.number
|
288
|
+
h.state.db.revert_refcount_changes h.number
|
289
|
+
h = h.get_parent
|
290
|
+
end
|
291
|
+
while b.number > h.number
|
292
|
+
b_children.push b
|
293
|
+
b = b.get_parent
|
294
|
+
end
|
295
|
+
|
296
|
+
while b.full_hash != h.full_hash
|
297
|
+
h.state.db.revert_refcount_changes h.number
|
298
|
+
h = h.get_parent
|
299
|
+
|
300
|
+
b_children.push b
|
301
|
+
b = b.get_parent
|
302
|
+
end
|
303
|
+
|
304
|
+
b_children.each do |bc|
|
305
|
+
Block.verify(bc, bc.get_parent)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
@db.put HEAD_KEY, block.full_hash
|
311
|
+
raise "Chain write error!" unless @db.get(HEAD_KEY) == block.full_hash
|
312
|
+
|
313
|
+
@index.update_blocknumbers(head)
|
314
|
+
raise "Fail to update head!" unless head == block
|
315
|
+
|
316
|
+
logger.debug "set new head", head: head
|
317
|
+
update_head_candidate forward_pending_transaction
|
318
|
+
|
319
|
+
@new_head_cb.call(block) if @new_head_cb && !block.genesis?
|
320
|
+
end
|
321
|
+
|
322
|
+
private
|
323
|
+
|
324
|
+
def logger
|
325
|
+
@logger ||= Logger.new 'eth.chain'
|
326
|
+
end
|
327
|
+
|
328
|
+
def initialize_blockchain(genesis=nil)
|
329
|
+
logger.info "Initializing new chain"
|
330
|
+
|
331
|
+
unless genesis
|
332
|
+
genesis = Block.genesis(@env)
|
333
|
+
logger.info "new genesis", genesis_hash: genesis, difficulty: genesis.difficulty
|
334
|
+
@index.add_block genesis
|
335
|
+
end
|
336
|
+
|
337
|
+
store_block genesis
|
338
|
+
raise "failed to store block" unless genesis == Block.find(@env, genesis.full_hash)
|
339
|
+
|
340
|
+
update_head genesis
|
341
|
+
raise "falied to update head" unless include?(genesis.full_hash)
|
342
|
+
|
343
|
+
commit
|
344
|
+
end
|
345
|
+
|
346
|
+
def store_block(block)
|
347
|
+
if block.number > 0
|
348
|
+
@db.put_temporarily block.full_hash, RLP.encode(block)
|
349
|
+
else
|
350
|
+
@db.put block.full_hash, RLP.encode(block)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# after new head is set
|
355
|
+
def update_head_candidate(forward_pending_transaction=true)
|
356
|
+
logger.debug "updating head candidate", head: head
|
357
|
+
|
358
|
+
# collect uncles
|
359
|
+
blk = head # parent of the block we are collecting uncles for
|
360
|
+
uncles = get_brothers(blk).map(&:header).uniq
|
361
|
+
|
362
|
+
(@env.config[:max_uncle_depth]+2).times do |i|
|
363
|
+
blk.uncles.each {|u| uncles.delete u }
|
364
|
+
blk = blk.get_parent if blk.has_parent?
|
365
|
+
end
|
366
|
+
|
367
|
+
raise "strange uncle found!" unless uncles.empty? || uncles.map(&:number).max <= head.number
|
368
|
+
|
369
|
+
uncles = uncles[0, @env.config[:max_uncles]]
|
370
|
+
|
371
|
+
# create block
|
372
|
+
ts = [Time.now.to_i, head.timestamp+1].max
|
373
|
+
_env = Env.new DB::OverlayDB.new(head.db), config: @env.config, global_config: @env.global_config
|
374
|
+
hc = Block.build_from_parent head, @coinbase, timestamp: ts, uncles: uncles, env: _env
|
375
|
+
raise ValidationError, "invalid uncles" unless hc.validate_uncles
|
376
|
+
|
377
|
+
@pre_finalize_state_root = hc.state_root
|
378
|
+
hc.finalize
|
379
|
+
|
380
|
+
# add transactions from previous head candidate
|
381
|
+
old_hc = @head_candidate
|
382
|
+
@head_candidate = hc
|
383
|
+
|
384
|
+
if old_hc
|
385
|
+
tx_hashes = head.get_transaction_hashes
|
386
|
+
pending = old_hc.get_transactions.select {|tx| !tx_hashes.include?(tx.full_hash) }
|
387
|
+
|
388
|
+
if pending.true?
|
389
|
+
if forward_pending_transaction
|
390
|
+
logger.debug "forwarding pending transaction", num: pending.size
|
391
|
+
pending.each {|tx| add_transaction tx }
|
392
|
+
else
|
393
|
+
logger.debug "discarding pending transaction", num: pending.size
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
end
|
400
|
+
end
|