reth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ module Reth
2
+ module JSONRPC
3
+
4
+ class Server
5
+ include Concurrent::Async
6
+
7
+ def initialize(app, host, port)
8
+ super()
9
+
10
+ @app = app
11
+ @host = host
12
+ @port = port
13
+ end
14
+
15
+ def start
16
+ Rack::Handler::WEBrick.run App.new(@app), Host: @host, Port: @port
17
+ rescue
18
+ puts $!
19
+ puts $!.backtrace[0,10].join("\n")
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ module Reth
2
+ module JSONRPC
3
+
4
+ class Service < ::DEVp2p::Service
5
+
6
+ class <<self
7
+ def register_with_app(app)
8
+ config = default_config[:jsonrpc]
9
+ app.register_service self, app, config[:host], config[:port]
10
+ end
11
+ end
12
+
13
+ name 'jsonrpc'
14
+ default_config(
15
+ jsonrpc: {
16
+ host: '127.0.0.1',
17
+ port: 8333
18
+ }
19
+ )
20
+
21
+ def initialize(app, host, port)
22
+ super(app)
23
+
24
+ @app = app
25
+ @host = host
26
+ @port = port
27
+ end
28
+
29
+ def start
30
+ @server = Server.new @app, @host, @port
31
+ @server.async.start
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,150 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'openssl'
4
+
5
+ module Reth
6
+
7
+ class Keystore
8
+
9
+ class PBKDF2
10
+ attr :name, :params
11
+
12
+ def initialize(params=nil)
13
+ @name = 'pbkdf2'
14
+ @params = params || mkparams
15
+ end
16
+
17
+ def eval(pw)
18
+ OpenSSL::PKCS5.pbkdf2_hmac(
19
+ pw,
20
+ Utils.decode_hex(params[:salt]),
21
+ params[:c],
22
+ params[:dklen],
23
+ 'SHA256'
24
+ )
25
+ end
26
+
27
+ def mkparams
28
+ { prf: 'hmac-sha256',
29
+ dklen: 32,
30
+ c: 262144,
31
+ salt: Utils.encode_hex(SecureRandom.random_bytes(16)) }
32
+ end
33
+ end
34
+
35
+ class AES128CTR
36
+ attr :name, :params
37
+
38
+ def initialize(params=nil)
39
+ @name = 'aes-128-ctr'
40
+ @params = params || mkparams
41
+ end
42
+
43
+ def encrypt(text, key)
44
+ cipher = OpenSSL::Cipher.new name
45
+ cipher.encrypt
46
+ cipher.key = key
47
+ cipher.iv = Utils.decode_hex(params[:iv])
48
+ cipher.update(text) + cipher.final
49
+ end
50
+
51
+ def decrypt(text, key)
52
+ cipher = OpenSSL::Cipher.new name
53
+ cipher.decrypt
54
+ cipher.key = key
55
+ cipher.iv = Utils.decode_hex(params[:iv])
56
+ cipher.update(text) + cipher.final
57
+ end
58
+
59
+ def mkparams
60
+ {iv: Utils.encode_hex(SecureRandom.random_bytes(16))}
61
+ end
62
+ end
63
+
64
+ KDF = {
65
+ 'pbkdf2' => PBKDF2
66
+ }.freeze
67
+
68
+ CIPHER = {
69
+ 'aes-128-ctr' => AES128CTR
70
+ }.freeze
71
+
72
+ class <<self
73
+
74
+ def make_json(priv, pw, kdf=PBKDF2.new, cipher=AES128CTR.new)
75
+ derivedkey = kdf.eval pw
76
+
77
+ enckey = derivedkey[0,16]
78
+ c = cipher.encrypt priv, enckey
79
+
80
+ mac = Utils.keccak256 "#{derivedkey[16,16]}#{c}"
81
+ uuid = SecureRandom.uuid
82
+
83
+ {
84
+ crypto: {
85
+ cipher: cipher.name,
86
+ ciphertext: Utils.encode_hex(c),
87
+ cipherparams: cipher.params,
88
+ kdf: kdf.name,
89
+ kdfparams: kdf.params,
90
+ mac: Utils.encode_hex(mac),
91
+ version: 1
92
+ },
93
+ id: uuid,
94
+ version: 3
95
+ }
96
+ end
97
+
98
+ def decode_json(jsondata, pw)
99
+ jsondata = Hashie::Mash.new jsondata
100
+
101
+ cryptdata = jsondata.crypto || jsondata.Crypto
102
+ raise ArgumentError, "JSON data must contain 'crypto' object" unless cryptdata
103
+
104
+ kdfparams = cryptdata.kdfparams
105
+ kdf = KDF[cryptdata.kdf].new kdfparams
106
+
107
+ cipherparams = cryptdata.cipherparams
108
+ cipher = CIPHER[cryptdata.cipher].new cipherparams
109
+
110
+ derivedkey = kdf.eval pw
111
+ raise ValueError, "Derived key must be at least 32 bytes long" unless derivedkey.size >= 32
112
+
113
+ enckey = derivedkey[0,16]
114
+ ct = Utils.decode_hex cryptdata.ciphertext
115
+ o = cipher.decrypt ct, enckey
116
+
117
+ mac1 = Utils.keccak256 "#{derivedkey[16,16]}#{ct}"
118
+ mac2 = Utils.decode_hex cryptdata.mac
119
+ raise ValueError, "MAC mismatch. Password incorrect?" unless mac1 == mac2
120
+
121
+ o
122
+ end
123
+
124
+ ##
125
+ # Check if json has the structure of a keystore file version 3.
126
+ #
127
+ # Note that this test is not complete, e.g. it doesn't check key
128
+ # derivation or cipher parameters.
129
+ #
130
+ # @param json [Hash] data load from json file
131
+ # @return [Bool] `true` if the data appears to be valid, otherwise
132
+ # `false`
133
+ #
134
+ def validate(json)
135
+ return false unless json.has_key?('crypto') || json.has_key?('Crypto')
136
+ return false unless json['version'] == 3
137
+
138
+ crypto = json['crypto'] || json['Crypto']
139
+ return false unless crypto.has_key?('cipher')
140
+ return false unless crypto.has_key?('ciphertext')
141
+ return false unless crypto.has_key?('kdf')
142
+ return false unless crypto.has_key?('mac')
143
+
144
+ true
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ end
@@ -0,0 +1,79 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Reth
4
+
5
+ class LevelDBService < ::DEVp2p::Service
6
+ name 'leveldb'
7
+
8
+ attr :db # implement DB::BaseDB interface
9
+
10
+ def initialize(app)
11
+ super(app)
12
+ @db = DB::LevelDB.new File.join(app.config[:data_dir], 'leveldb')
13
+ end
14
+
15
+ def start
16
+ # do nothing
17
+ end
18
+
19
+ def stop
20
+ # do nothing
21
+ end
22
+
23
+ def get(k)
24
+ @db.get(k)
25
+ rescue KeyError
26
+ nil
27
+ end
28
+
29
+ def put(k, v)
30
+ @db.put(k, v)
31
+ end
32
+
33
+ def commit
34
+ @db.commit
35
+ end
36
+
37
+ def delete(k)
38
+ @db.delete(k)
39
+ end
40
+
41
+ def include?(k)
42
+ @db.include?(k)
43
+ end
44
+ alias has_key? include?
45
+
46
+ def inc_refcount(k, v)
47
+ put(k, v)
48
+ end
49
+
50
+ def dec_refcount(k)
51
+ # do nothing
52
+ end
53
+
54
+ def revert_refcount_changes(epoch)
55
+ # do nothing
56
+ end
57
+
58
+ def commit_refcount_changes(epoch)
59
+ # do nothing
60
+ end
61
+
62
+ def cleanup(epoch)
63
+ # do nothing
64
+ end
65
+
66
+ def put_temporarily(k, v)
67
+ inc_refcount(k, v)
68
+ dec_refcount(k)
69
+ end
70
+
71
+ private
72
+
73
+ def logger
74
+ @logger ||= Logger.new 'db'
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,66 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Reth
4
+
5
+ class Profile
6
+
7
+ GENESIS_DIR = File.expand_path('../genesisdata', __FILE__)
8
+
9
+ ALL = {
10
+ livenet: {
11
+ eth: {
12
+ network_id: 1,
13
+ genesis: File.join(GENESIS_DIR, 'genesis_frontier.json'),
14
+ genesis_hash: 'd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3',
15
+ },
16
+ discovery: {
17
+ bootstrap_nodes: [
18
+ 'enode://487611428e6c99a11a9795a6abe7b529e81315ca6aad66e2a2fc76e3adf263faba0d35466c2f8f68d561dbefa8878d4df5f1f2ddb1fbeab7f42ffb8cd328bd4a@5.1.83.226:30303', # C++
19
+ 'enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303', # GO
20
+ 'enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303', # GO2
21
+ 'enode://2676755dd8477ad3beea32b4e5a144fa10444b70dfa3e05effb0fdfa75683ebd4f75709e1f8126cb5317c5a35cae823d503744e790a3a038ae5dd60f51ee9101@144.76.62.101:30303', # Python
22
+ ]
23
+ },
24
+ },
25
+ testnet: {
26
+ eth: {
27
+ network_id: 2,
28
+ genesis: File.join(GENESIS_DIR, 'genesis_morden.json'),
29
+ genesis_hash: '0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303',
30
+ block: {
31
+ account_initial_nonce: 2 ** 20,
32
+ homestead_fork_blknum: 494000,
33
+ },
34
+ },
35
+ discovery: {
36
+ bootstrap_nodes: [
37
+ 'enode://e4533109cc9bd7604e4ff6c095f7a1d807e15b38e9bfeb05d3b7c423ba86af0a9e89abbf40bd9dde4250fef114cd09270fa4e224cbeef8b7bf05a51e8260d6b8@94.242.229.4:40404' # Go
38
+ ]
39
+ },
40
+ }
41
+ }
42
+
43
+ class <<self
44
+ def all
45
+ @all ||= Hashie::Mash.new ALL
46
+ end
47
+
48
+ def public(name)
49
+ all[name]
50
+ end
51
+
52
+ def private(network_id)
53
+ Hashie::Mash.new({
54
+ eth: {
55
+ network_id: network_id
56
+ },
57
+ discovery: {
58
+ bootstrap_nodes: []
59
+ }
60
+ })
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,273 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module Reth
4
+
5
+ ##
6
+ # Synchronizes the chain starting from a given blockhash. Blockchain hash
7
+ # is fetched from a single peer (which led to the unknown blockhash).
8
+ # Blocks are fetched from the best peers.
9
+ #
10
+ class SyncTask
11
+ MAX_BLOCKS_PER_REQUEST = 32
12
+ INITIAL_BLOCKHASHES_PER_REQUEST = 16
13
+ MAX_BLOCKHASHES_PER_REQUEST = 512
14
+
15
+ BLOCKS_REQUEST_TIMEOUT = 32
16
+ BLOCKHASHES_REQUEST_TIMEOUT = 32
17
+
18
+ attr :start_block_number, :end_block_number
19
+
20
+ def initialize(synchronizer, proto, blockhash, chain_difficulty=0, originator_only=false)
21
+ @synchronizer = synchronizer
22
+ @chain = synchronizer.chain
23
+ @chainservice = synchronizer.chainservice
24
+
25
+ @originating_proto = proto
26
+ @originator_only = originator_only
27
+
28
+ @blockhash = blockhash
29
+ @chain_difficulty = chain_difficulty
30
+
31
+ @requests = {} # proto => [cond, result]
32
+ @start_block_number = @chain.head.number
33
+ @end_block_number = @start_block_number + 1 # minimum synctask
34
+
35
+ @run = Thread.new { run }
36
+ end
37
+
38
+ def run
39
+ logger.info 'spawning new synctask'
40
+
41
+ fetch_hashchain
42
+ rescue
43
+ logger.error $!
44
+ logger.error $!.backtrace[0,20].join("\n")
45
+ task_exit false
46
+ end
47
+
48
+ def task_exit(success=false)
49
+ if success
50
+ logger.debug 'successfully synced'
51
+ else
52
+ logger.warn 'syncing failed'
53
+ end
54
+
55
+ @synchronizer.synctask_exited(success)
56
+ end
57
+
58
+ def protocols
59
+ return [@originating_proto] if @originator_only
60
+ @synchronizer.protocols
61
+ end
62
+
63
+ def fetch_hashchain
64
+ logger.debug 'fetching hashchain'
65
+
66
+ blockhashes_chain = [@blockhash] # youngest to oldest
67
+ blockhash = @blockhash = blockhashes_chain.last
68
+ raise AssertError if @chain.include?(blockhash)
69
+
70
+ # get block hashes until we found a known one
71
+ max_blockhashes_per_request = INITIAL_BLOCKHASHES_PER_REQUEST
72
+ chain_head_number = @chain.head.number
73
+ while !@chain.include?(blockhash)
74
+ blockhashes_batch = []
75
+
76
+ # proto with highest difficulty should be the proto we got the
77
+ # newblock from
78
+ protos = self.protocols
79
+ if protos.nil? || protos.empty?
80
+ logger.warn 'no protocols available'
81
+ return task_exit(false)
82
+ end
83
+
84
+ protos.each do |proto|
85
+ logger.debug "syncing with", proto: proto
86
+ next if proto.stopped?
87
+
88
+ raise AssertError if @requests.has_key?(proto)
89
+ deferred = Concurrent::IVar.new
90
+ @requests[proto] = deferred
91
+
92
+ proto.async.send_getblockhashes blockhash, max_blockhashes_per_request
93
+ begin
94
+ blockhashes_batch = deferred.value(BLOCKHASHES_REQUEST_TIMEOUT)
95
+ rescue Defer::TimedOut
96
+ logger.warn 'syncing hashchain timed out'
97
+ next
98
+ ensure
99
+ @requests.delete proto
100
+ end
101
+
102
+ if blockhashes_batch.empty?
103
+ logger.warn 'empty getblockhashes result'
104
+ next
105
+ end
106
+
107
+ unless blockhashes_batch.all? {|bh| bh.instance_of?(String) }
108
+ logger.warn "get wrong data type", expected: 'String', received: blockhashes_batch.map(&:class).uniq
109
+ next
110
+ end
111
+
112
+ break
113
+ end
114
+
115
+ if blockhashes_batch.empty?
116
+ logger.warn 'syncing failed with all peers', num_protos: protos.size
117
+ return task_exit(false)
118
+ end
119
+
120
+ if @chain.include?(blockhashes_batch.last)
121
+ blockhashes_batch.each do |bh| # youngest to oldest
122
+ blockhash = bh
123
+
124
+ if @chain.include?(blockhash)
125
+ logger.debug "found known blockhash", blockhash: Utils.encode_hex(blockhash), is_genesis: (blockhash == @chain.genesis.full_hash)
126
+ break
127
+ else
128
+ blockhashes_chain.push(blockhash)
129
+ end
130
+ end
131
+ else # no overlap
132
+ blockhashes_chain.concat blockhashes_batch
133
+ blockhash = blockhashes_batch.last
134
+ end
135
+
136
+ logger.debug "downloaded #{blockhashes_chain.size} block hashes, ending with #{Utils.encode_hex(blockhashes_chain.last)}"
137
+ @end_block_number = chain_head_number + blockhashes_chain.size
138
+ max_blockhashes_per_request = MAX_BLOCKHASHES_PER_REQUEST
139
+ end
140
+
141
+ @start_block_number = @chain.get(blockhash).number
142
+ @end_block_number = @start_block_number + blockhashes_chain.size
143
+
144
+ logger.debug 'computed missing numbers', start_number: @start_block_number, end_number: @end_block_number
145
+
146
+ fetch_blocks blockhashes_chain
147
+ end
148
+
149
+ def fetch_blocks(blockhashes_chain)
150
+ raise ArgumentError, 'no blockhashes' if blockhashes_chain.empty?
151
+ logger.debug 'fetching blocks', num: blockhashes_chain.size
152
+
153
+ blockhashes_chain.reverse! # oldest to youngest
154
+ num_blocks = blockhashes_chain.size
155
+ num_fetched = 0
156
+
157
+ while !blockhashes_chain.empty?
158
+ blockhashes_batch = blockhashes_chain[0, MAX_BLOCKS_PER_REQUEST]
159
+ t_blocks = []
160
+
161
+ protos = self.protocols
162
+ if protos.empty?
163
+ logger.warn 'no protocols available'
164
+ return task_exit(false)
165
+ end
166
+
167
+ proto = nil
168
+ reply_proto = nil
169
+ protos.each do |_proto|
170
+ proto = _proto
171
+
172
+ next if proto.stopped?
173
+ raise AssertError if @requests.has_key?(proto)
174
+
175
+ logger.debug 'requesting blocks', num: blockhashes_batch.size
176
+ deferred = Concurrent::IVar.new
177
+ @requests[proto] = deferred
178
+
179
+ proto.async.send_getblocks *blockhashes_batch
180
+ begin
181
+ t_blocks = deferred.value(BLOCKS_REQUEST_TIMEOUT)
182
+ rescue Defer::TimedOut
183
+ logger.warn 'getblocks timed out, trying next proto'
184
+ next
185
+ ensure
186
+ @requests.delete proto
187
+ end
188
+
189
+ if t_blocks.empty?
190
+ logger.warn 'empty getblocks reply, trying next proto'
191
+ next
192
+ elsif !t_blocks.all? {|b| b.instance_of?(TransientBlock) }
193
+ logger.warn 'received unexpected data', data: t_blocks
194
+ t_blocks = []
195
+ next
196
+ end
197
+
198
+ unless t_blocks.map {|b| b.header.full_hash } == blockhashes_batch[0, t_blocks.size]
199
+ logger.warn 'received wrong blocks, should ban peer'
200
+ t_blocks = []
201
+ next
202
+ end
203
+
204
+ reply_proto = proto
205
+ break
206
+ end
207
+
208
+ # add received t_blocks
209
+ num_fetched += t_blocks.size
210
+ logger.debug "received blocks", num: t_blocks.size, num_fetched: num_fetched, total: num_blocks, missing: (num_blocks - num_fetched)
211
+
212
+ if t_blocks.empty?
213
+ logger.warn 'failed to fetch blocks', missing: blockhashes_chain.size
214
+ return task_exit(false)
215
+ end
216
+
217
+ t = Time.now
218
+ logger.debug 'adding blocks', qsize: @chainservice.block_queue.size
219
+ t_blocks.each do |blk|
220
+ b = blockhashes_chain.shift
221
+ raise AssertError unless blk.header.full_hash == b
222
+ raise AssertError if blockhashes_chain.include?(blk.header.full_hash)
223
+
224
+ @chainservice.add_block blk, reply_proto # this blocks if the queue is full
225
+ end
226
+ logger.debug 'adding blocks done', took: (Time.now - t)
227
+ end
228
+
229
+ # done
230
+ last_block = t_blocks.last
231
+ raise AssertError, 'still missing blocks' unless blockhashes_chain.empty?
232
+ raise AssertError, 'still missing blocks' unless last_block.header.full_hash == @blockhash
233
+ logger.debug 'syncing finished'
234
+
235
+ # at this time blocks are not in the chain yet, but in the add_block queue
236
+ if @chain_difficulty >= @chain.head.chain_difficulty
237
+ @chainservice.broadcast_newblock last_block, @chain_difficulty, proto
238
+ end
239
+
240
+ task_exit(true)
241
+ rescue
242
+ logger.error $!
243
+ logger.error $!.backtrace[0,10].join("\n")
244
+ task_exit(false)
245
+ end
246
+
247
+ def receive_blocks(proto, t_blocks)
248
+ logger.debug 'blocks received', proto: proto, num: t_blocks.size
249
+ unless @requests.has_key?(proto)
250
+ logger.debug 'unexpected blocks'
251
+ return
252
+ end
253
+ @requests[proto].set t_blocks
254
+ end
255
+
256
+ def receive_blockhashes(proto, blockhashes)
257
+ logger.debug 'blockhashes received', proto: proto, num: blockhashes.size
258
+ unless @requests.has_key?(proto)
259
+ logger.debug 'unexpected blockhashes'
260
+ return
261
+ end
262
+ @requests[proto].set blockhashes
263
+ end
264
+
265
+ private
266
+
267
+ def logger
268
+ @logger ||= Logger.new('eth.sync.task')
269
+ end
270
+
271
+ end
272
+
273
+ end