reth 0.1.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 +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
|