ruby-ethereum 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|