reth 0.1.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 +6 -0
- data/bin/reth +208 -0
- data/lib/reth.rb +44 -0
- data/lib/reth/account.rb +195 -0
- data/lib/reth/account_service.rb +361 -0
- data/lib/reth/app.rb +15 -0
- data/lib/reth/chain_service.rb +500 -0
- data/lib/reth/config.rb +88 -0
- data/lib/reth/db_service.rb +90 -0
- data/lib/reth/duplicates_filter.rb +29 -0
- data/lib/reth/eth_protocol.rb +209 -0
- data/lib/reth/genesisdata/genesis_frontier.json +26691 -0
- data/lib/reth/genesisdata/genesis_morden.json +27 -0
- data/lib/reth/genesisdata/genesis_olympic.json +48 -0
- data/lib/reth/jsonrpc.rb +13 -0
- data/lib/reth/jsonrpc/app.rb +79 -0
- data/lib/reth/jsonrpc/filter.rb +288 -0
- data/lib/reth/jsonrpc/handler.rb +424 -0
- data/lib/reth/jsonrpc/helper.rb +156 -0
- data/lib/reth/jsonrpc/server.rb +24 -0
- data/lib/reth/jsonrpc/service.rb +37 -0
- data/lib/reth/keystore.rb +150 -0
- data/lib/reth/leveldb_service.rb +79 -0
- data/lib/reth/profile.rb +66 -0
- data/lib/reth/sync_task.rb +273 -0
- data/lib/reth/synchronizer.rb +192 -0
- data/lib/reth/transient_block.rb +40 -0
- data/lib/reth/utils.rb +22 -0
- data/lib/reth/version.rb +3 -0
- metadata +201 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
{
|
2
|
+
"nonce": "0x00006d6f7264656e",
|
3
|
+
"difficulty": "0x20000",
|
4
|
+
"mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
|
5
|
+
"coinbase": "0x0000000000000000000000000000000000000000",
|
6
|
+
"timestamp": "0x00",
|
7
|
+
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
8
|
+
"extraData": "0x",
|
9
|
+
"gasLimit": "0x2FEFD8",
|
10
|
+
"alloc": {
|
11
|
+
"0000000000000000000000000000000000000001": {
|
12
|
+
"balance": "1"
|
13
|
+
},
|
14
|
+
"0000000000000000000000000000000000000002": {
|
15
|
+
"balance": "1"
|
16
|
+
},
|
17
|
+
"0000000000000000000000000000000000000003": {
|
18
|
+
"balance": "1"
|
19
|
+
},
|
20
|
+
"0000000000000000000000000000000000000004": {
|
21
|
+
"balance": "1"
|
22
|
+
},
|
23
|
+
"102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": {
|
24
|
+
"balance": "1606938044258990275541962092341162602522202993782792835301376"
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
{
|
2
|
+
"nonce": "0x0000000000000042",
|
3
|
+
"difficulty": "0x20000",
|
4
|
+
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
5
|
+
"coinbase": "0x0000000000000000000000000000000000000000",
|
6
|
+
"timestamp": "0x00",
|
7
|
+
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
8
|
+
"extraData": "",
|
9
|
+
"gasLimit": "0x2fefd8",
|
10
|
+
"alloc": {
|
11
|
+
"dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {
|
12
|
+
"balance": "1606938044258990275541962092341162602522202993782792835301376"
|
13
|
+
},
|
14
|
+
"e6716f9544a56c530d868e4bfbacb172315bdead": {
|
15
|
+
"balance": "1606938044258990275541962092341162602522202993782792835301376"
|
16
|
+
},
|
17
|
+
"b9c015918bdaba24b4ff057a92a3873d6eb201be": {
|
18
|
+
"balance": "1606938044258990275541962092341162602522202993782792835301376"
|
19
|
+
},
|
20
|
+
"1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {
|
21
|
+
"balance": "1606938044258990275541962092341162602522202993782792835301376"
|
22
|
+
},
|
23
|
+
"2ef47100e0787b915105fd5e3f4ff6752079d5cb": {
|
24
|
+
"balance": "1606938044258990275541962092341162602522202993782792835301376"
|
25
|
+
},
|
26
|
+
"cd2a3d9f938e13cd947ec05abc7fe734df8dd826": {
|
27
|
+
"balance": "1606938044258990275541962092341162602522202993782792835301376"
|
28
|
+
},
|
29
|
+
"6c386a4b26f73c802f34673f7248bb118f97424a": {
|
30
|
+
"balance": "1606938044258990275541962092341162602522202993782792835301376"
|
31
|
+
},
|
32
|
+
"e4157b34ea9615cfbde6b4fda419828124b70c78": {
|
33
|
+
"balance": "1606938044258990275541962092341162602522202993782792835301376"
|
34
|
+
},
|
35
|
+
"0000000000000000000000000000000000000001": {
|
36
|
+
"balance": "1"
|
37
|
+
},
|
38
|
+
"0000000000000000000000000000000000000002": {
|
39
|
+
"balance": "1"
|
40
|
+
},
|
41
|
+
"0000000000000000000000000000000000000003": {
|
42
|
+
"balance": "1"
|
43
|
+
},
|
44
|
+
"0000000000000000000000000000000000000004": {
|
45
|
+
"balance": "1"
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
data/lib/reth/jsonrpc.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rack'
|
3
|
+
require 'sinatra/base'
|
4
|
+
require 'sinatra/json'
|
5
|
+
|
6
|
+
require 'ethereum'
|
7
|
+
|
8
|
+
require 'reth/jsonrpc/helper'
|
9
|
+
require 'reth/jsonrpc/filter'
|
10
|
+
require 'reth/jsonrpc/handler'
|
11
|
+
require 'reth/jsonrpc/server'
|
12
|
+
require 'reth/jsonrpc/service'
|
13
|
+
require 'reth/jsonrpc/app'
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Reth
|
2
|
+
module JSONRPC
|
3
|
+
|
4
|
+
class App < Sinatra::Base
|
5
|
+
|
6
|
+
configure do
|
7
|
+
enable :logging
|
8
|
+
end
|
9
|
+
|
10
|
+
CORS_ORIGIN = "http://localhost:8080"
|
11
|
+
|
12
|
+
options "*" do
|
13
|
+
response.headers["Allow"] = "POST,OPTIONS"
|
14
|
+
response.headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept"
|
15
|
+
response.headers["Access-Control-Allow-Origin"] = "http://localhost:8080"
|
16
|
+
response.headers["Access-Control-Allow-Methods"] = "POST"
|
17
|
+
|
18
|
+
200
|
19
|
+
end
|
20
|
+
|
21
|
+
post '/' do
|
22
|
+
response.headers["Access-Control-Allow-Origin"] = CORS_ORIGIN
|
23
|
+
|
24
|
+
begin
|
25
|
+
data = JSON.parse request.body.read
|
26
|
+
puts "Params: #{data.inspect}"
|
27
|
+
|
28
|
+
result = if data.instance_of?(Array)
|
29
|
+
data.map {|d| dispatch d }
|
30
|
+
else
|
31
|
+
dispatch data
|
32
|
+
end
|
33
|
+
|
34
|
+
json result
|
35
|
+
rescue JSON::ParserError
|
36
|
+
puts $!
|
37
|
+
puts $!.backtrace[0,10].join("\n")
|
38
|
+
json jsonrpc: '2.0', error: $!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(node)
|
43
|
+
@node = node
|
44
|
+
|
45
|
+
@default_block = 'latest'
|
46
|
+
@default_address = Account.test_accounts.keys.first
|
47
|
+
@default_startgas = 500 * 1000
|
48
|
+
@default_gas_price = 60.shannon
|
49
|
+
|
50
|
+
super()
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
include Handler
|
56
|
+
|
57
|
+
def dispatch(data)
|
58
|
+
# TODO: filter injection, validate method names
|
59
|
+
result = send :"handle_#{data['method']}", *data['params']
|
60
|
+
|
61
|
+
{jsonrpc: data['jsonrpc'], id: data['id'], result: result}
|
62
|
+
rescue NoMethodError
|
63
|
+
if $!.message =~ /handle_([a-zA-Z_]+)/
|
64
|
+
{jsonrpc: '2.0', id: data['id'], error: "jsonrpc method not defined: #{$1}"}
|
65
|
+
else
|
66
|
+
puts $!
|
67
|
+
puts $!.backtrace[0,10].join("\n")
|
68
|
+
{jsonrpc: data['jsonrpc'], id: data['id'], error: $!}
|
69
|
+
end
|
70
|
+
rescue
|
71
|
+
puts $!
|
72
|
+
puts $!.backtrace[0,10].join("\n")
|
73
|
+
{jsonrpc: data['jsonrpc'], id: data['id'], error: $!}
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
module Reth
|
2
|
+
module JSONRPC
|
3
|
+
|
4
|
+
class Filter
|
5
|
+
class <<self
|
6
|
+
def next_id
|
7
|
+
@next_id ||= 0
|
8
|
+
@next_id += 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def map
|
12
|
+
@map ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(id)
|
16
|
+
map[id]
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(id)
|
20
|
+
map.delete id
|
21
|
+
end
|
22
|
+
|
23
|
+
def include?(id)
|
24
|
+
map.include?(id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class LogFilter
|
30
|
+
|
31
|
+
class <<self
|
32
|
+
def create(obj, chain)
|
33
|
+
f = new obj, chain
|
34
|
+
id = Filter.next_id
|
35
|
+
Filter.map[id] = f
|
36
|
+
id
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
include Helper
|
41
|
+
|
42
|
+
def initialize(obj, chain)
|
43
|
+
@chain = chain
|
44
|
+
@first_block, @last_block, @addresses, @topics = parse_obj obj
|
45
|
+
|
46
|
+
@last_head = @chain.head
|
47
|
+
@last_block_checked = nil
|
48
|
+
|
49
|
+
@log_dict = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
def check
|
53
|
+
first, last = get_from_to @first_block, @last_block
|
54
|
+
first = [@last_head.number + 1, first].min if @first_block.is_a?(String)
|
55
|
+
raise "first must not be greater than last" unless first <= last
|
56
|
+
|
57
|
+
if @last_block_checked
|
58
|
+
first = [@last_block_checked.number + 1, first].max
|
59
|
+
return {} if first > last
|
60
|
+
end
|
61
|
+
|
62
|
+
blocks_to_check = []
|
63
|
+
(first...last).each do |n|
|
64
|
+
blocks_to_check.push @chain.index.get_block_by_number(n)
|
65
|
+
end
|
66
|
+
|
67
|
+
head = @chain.head
|
68
|
+
head_candidate = @chain.head_candidate
|
69
|
+
|
70
|
+
if last == head_candidate.number
|
71
|
+
blocks_to_check.push head_candidate
|
72
|
+
else
|
73
|
+
blocks_to_check.push @chain.get(@chain.index.get_block_by_number(last))
|
74
|
+
end
|
75
|
+
|
76
|
+
int32 = RLP::Sedes::BigEndianInt.new 32
|
77
|
+
|
78
|
+
new_logs = {}
|
79
|
+
blocks_to_check.each_with_index do |block, i|
|
80
|
+
unless [Ethereum::Block, Ethereum::CachedBlock].include?(block.class)
|
81
|
+
# must be blockhash
|
82
|
+
bloom = @chain.get_bloom block
|
83
|
+
|
84
|
+
if @addresses
|
85
|
+
pass_address_check = @addresses.any? {|addr| Ethereum::Bloom.query(bloom, addr) }
|
86
|
+
next unless pass_address_check
|
87
|
+
end
|
88
|
+
|
89
|
+
topics = (@topics || []).map {|t| int32.serialize(t) }
|
90
|
+
topic_bloom = Ethereum::Bloom.from_array topics
|
91
|
+
next if Ethereum::Bloom.combine(bloom, topic_bloom) != bloom
|
92
|
+
|
93
|
+
block = @chain.get block
|
94
|
+
end
|
95
|
+
|
96
|
+
r_idx = nil
|
97
|
+
l_idx = nil
|
98
|
+
log = nil
|
99
|
+
block.get_receipts.each_with_index do |receipt, ri|
|
100
|
+
r_idx = ri
|
101
|
+
|
102
|
+
receipt.logs.each_with_index do |_log, li|
|
103
|
+
log = _log
|
104
|
+
l_idx = li
|
105
|
+
|
106
|
+
next if @addresses && !@addresses.include?(log.address)
|
107
|
+
|
108
|
+
if @topics
|
109
|
+
topic_match = log.topics.size >= @topics.size
|
110
|
+
next unless topic_match
|
111
|
+
|
112
|
+
@topics.zip(log.topics).each do |filter_topic, log_topic|
|
113
|
+
if filter_topic && filter_topic != log_topic
|
114
|
+
topic_match = false
|
115
|
+
break
|
116
|
+
end
|
117
|
+
end
|
118
|
+
next unless topic_match
|
119
|
+
end
|
120
|
+
|
121
|
+
tx = block.get_transaction r_idx
|
122
|
+
id = Ethereum::Utils.keccak256 "#{tx.full_hash}#{l_idx}"
|
123
|
+
pending = block == head_candidate
|
124
|
+
new_logs[id] = {
|
125
|
+
log: log,
|
126
|
+
log_idx: l_idx,
|
127
|
+
block: block,
|
128
|
+
txhash: tx.full_hash,
|
129
|
+
tx_idx: r_idx
|
130
|
+
}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
@last_block_checked = if blocks_to_check.last != head_candidate
|
136
|
+
blocks_to_check.last
|
137
|
+
else
|
138
|
+
blocks_to_check.size >= 2 ? blocks_to_check[-2] : nil
|
139
|
+
end
|
140
|
+
|
141
|
+
if @last_block_checked && ![Ethereum::Block, Ethereum::CachedBlock].include?(@last_block_checked.class)
|
142
|
+
@last_block_checked = @chain.get @last_block_checked
|
143
|
+
end
|
144
|
+
|
145
|
+
actually_new_ids = new_logs.keys - @log_dict.keys
|
146
|
+
@log_dict.merge! new_logs
|
147
|
+
|
148
|
+
actually_new_ids.map {|id| [id, new_logs[id]] }.to_h
|
149
|
+
end
|
150
|
+
|
151
|
+
def logs
|
152
|
+
check
|
153
|
+
@log_dict.values
|
154
|
+
end
|
155
|
+
|
156
|
+
def new_logs
|
157
|
+
check.values
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_s
|
161
|
+
"<Filter(addressed=#{@addresses}, topics=#{@topics}, first=#{@first_block}, last=#{@last_block})>"
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def parse_obj(obj)
|
167
|
+
raise ArgumentError, 'obj must be a Hash' unless obj.instance_of?(Hash)
|
168
|
+
|
169
|
+
addresses = case obj['address']
|
170
|
+
when String
|
171
|
+
[hex_to_bytes(obj['address'])]
|
172
|
+
when Array
|
173
|
+
obj['address'].map {|addr| hex_to_bytes(addr) }
|
174
|
+
when NilClass
|
175
|
+
nil
|
176
|
+
else
|
177
|
+
raise ArgumentError, "address must be String or Array of Strings"
|
178
|
+
end
|
179
|
+
|
180
|
+
topics = nil
|
181
|
+
if obj.has_key?('topics')
|
182
|
+
topics = []
|
183
|
+
obj['topics'].each do |t|
|
184
|
+
if t
|
185
|
+
topics.push(Ethereum::Utils.big_endian_to_int hex_to_bytes(t))
|
186
|
+
else
|
187
|
+
topics.push(nil)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
from_block = decode_block_tag(obj['fromBlock'] || 'latest')
|
193
|
+
to_block = decode_block_tag(obj['toBlock'] || 'latest')
|
194
|
+
|
195
|
+
from, to = get_from_to from_block, to_block
|
196
|
+
raise ArgumentError, 'fromBlock must not be newer than toBlock' if from > to
|
197
|
+
|
198
|
+
return from_block, to_block, addresses, topics
|
199
|
+
end
|
200
|
+
|
201
|
+
def get_from_to(from_block, to_block)
|
202
|
+
block_tags = {
|
203
|
+
'earliest' => 0,
|
204
|
+
'latest' => @chain.head.number,
|
205
|
+
'pending' => @chain.head_candidate.number
|
206
|
+
}
|
207
|
+
from = from_block.is_a?(Integer) ? from_block : block_tags[from_block]
|
208
|
+
to = to_block.is_a?(Integer) ? to_block : block_tags[to_block]
|
209
|
+
|
210
|
+
return from, to
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
class BlockFilter
|
216
|
+
|
217
|
+
class <<self
|
218
|
+
def create(chain)
|
219
|
+
f = new chain
|
220
|
+
id = Filter.next_id
|
221
|
+
Filter.map[id] = f
|
222
|
+
id
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def initialize(chain)
|
227
|
+
@chain = chain
|
228
|
+
@latest_block = chain.head
|
229
|
+
end
|
230
|
+
|
231
|
+
def check
|
232
|
+
new_blocks = []
|
233
|
+
block = @chain.head
|
234
|
+
|
235
|
+
while block.number > @latest_block.number
|
236
|
+
new_blocks.push block
|
237
|
+
block = block.get_parent
|
238
|
+
end
|
239
|
+
|
240
|
+
puts "previous latest block not in current chain!" if block != @latest_block
|
241
|
+
@latest_block = new_blocks.first if new_blocks.size > 0
|
242
|
+
|
243
|
+
new_blocks.reverse
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
class PendingTransactionFilter
|
249
|
+
|
250
|
+
class <<self
|
251
|
+
def create(chain)
|
252
|
+
f = new chain
|
253
|
+
id = Filter.next_id
|
254
|
+
Filter.map[id] = f
|
255
|
+
id
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def initialize(chain)
|
260
|
+
@chain = chain
|
261
|
+
@latest_block = @chain.head_candidate
|
262
|
+
@reported_txs = []
|
263
|
+
end
|
264
|
+
|
265
|
+
def check
|
266
|
+
head_candidate = @chain.head_candidate
|
267
|
+
pending_txs = head_candidate.get_transactions
|
268
|
+
new_txs = pending_txs.select {|tx| !@reported_txs.include?(tx.full_hash) }
|
269
|
+
|
270
|
+
block = head_candidate.get_parent
|
271
|
+
while block.number >= @latest_block.number
|
272
|
+
block.get_transactions.reverse.each do |tx|
|
273
|
+
new_txs.push(tx) unless @reported_txs.include?(tx.full_hash)
|
274
|
+
end
|
275
|
+
|
276
|
+
block = block.get_parent
|
277
|
+
end
|
278
|
+
|
279
|
+
@latest_block = head_candidate
|
280
|
+
@reported_txs = pending_txs.map(&:full_hash)
|
281
|
+
|
282
|
+
new_txs.reverse
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
end
|