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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 81f3646dda45ea703ebdd86931afa3ca03560838
|
4
|
+
data.tar.gz: f5d710770445a550aca6fade8a7f268845a085fd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5d8366c448058a052bccc36b0226361e5d9bb56c23797df2217a4926e06ca44e6bc5f2b856e45c5345f4751b1aff76c0ca607906c551ceabbb7847db9bdb9f3b
|
7
|
+
data.tar.gz: d2ce932b075f7895a6db74628050cc10aace81c689a4edbc21b3f861af5917eda2ca882d1617a28ee69102f52f6d5b07aeb82a99ad8d7e3ba60ae03c7de71c8d
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Jan Xie
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/bin/reth
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'slop'
|
6
|
+
require 'reth'
|
7
|
+
|
8
|
+
class RethControl
|
9
|
+
|
10
|
+
Services = [
|
11
|
+
Reth::DBService,
|
12
|
+
Reth::AccountService,
|
13
|
+
DEVp2p::Discovery::Service,
|
14
|
+
DEVp2p::PeerManager,
|
15
|
+
Reth::ChainService,
|
16
|
+
Reth::JSONRPC::Service
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
attr :app, :config
|
20
|
+
|
21
|
+
def initialize(options={})
|
22
|
+
load_config(options)
|
23
|
+
@app = Reth::App.new(@config)
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_config(options)
|
27
|
+
datadir = options[:data_dir] or raise ArgumentError.new('missing datadir')
|
28
|
+
profile = if options[:network_id]
|
29
|
+
Reth::Profile.private(options[:network_id])
|
30
|
+
else
|
31
|
+
Reth::Profile.public(options[:profile].to_sym)
|
32
|
+
end
|
33
|
+
|
34
|
+
Reth::Config.setup(datadir)
|
35
|
+
@config = profile.deep_merge Reth::Config.load(datadir)
|
36
|
+
|
37
|
+
# TODO: allow bootstrap_node to be Array, merge with defaults
|
38
|
+
if options[:bootstrap_node]
|
39
|
+
@config.discovery.bootstrap_nodes = [ options[:bootstrap_node] ]
|
40
|
+
end
|
41
|
+
|
42
|
+
update_config_with_defaults @config, Reth::Config.get_default_config([Reth::App] + Services)
|
43
|
+
update_config_with_defaults @config, {eth: {block: Reth::Env::DEFAULT_CONFIG}}
|
44
|
+
|
45
|
+
genesis_from_config_file = @config.fetch(:eth, {})[:genesis]
|
46
|
+
if genesis_from_config_file
|
47
|
+
# Fixed genesis_hash take from profile must be deleted as custom genesis loaded
|
48
|
+
@config[:eth].delete :genesis_hash
|
49
|
+
@config[:eth][:genesis] = genesis_from_config_file
|
50
|
+
end
|
51
|
+
|
52
|
+
update_config_from_genesis_json @config[:eth][:genesis]
|
53
|
+
|
54
|
+
# TODO: bootstrap_nodes_from_config_file
|
55
|
+
|
56
|
+
@config = @config.deep_merge options
|
57
|
+
|
58
|
+
dump_config
|
59
|
+
end
|
60
|
+
|
61
|
+
def dump_config
|
62
|
+
puts_header 'CONFIGURATION'
|
63
|
+
|
64
|
+
cfg = @config.to_hash
|
65
|
+
alloc = cfg.fetch('eth', {}).fetch('block', {}).fetch('genesis_initial_alloc', {})
|
66
|
+
if alloc.size > 100
|
67
|
+
puts "omitting reporting of #{alloc.size} accounts in genesis"
|
68
|
+
cfg['eth']['block'].delete('genesis_initial_alloc')
|
69
|
+
end
|
70
|
+
|
71
|
+
puts cfg
|
72
|
+
end
|
73
|
+
|
74
|
+
def register_services
|
75
|
+
exclude_services = @app.config[:deactivated_services]
|
76
|
+
Services.each do |service|
|
77
|
+
raise ArgumentError, 'service must be DEVp2p::Service' unless service.instance_of?(Class) && service < DEVp2p::Service
|
78
|
+
|
79
|
+
next if exclude_services.include?(service.name)
|
80
|
+
service.register_with_app @app
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def start
|
85
|
+
register_services
|
86
|
+
|
87
|
+
puts_header "starting"
|
88
|
+
@app.start
|
89
|
+
|
90
|
+
#trap("INT") { @app.stop }
|
91
|
+
#trap("TERM") { @app.stop }
|
92
|
+
#trap("QUIT") { @app.stop }
|
93
|
+
|
94
|
+
#10000.times do |i|
|
95
|
+
# sleep 2
|
96
|
+
# @app.services.db.put i.to_s, Time.now.to_s
|
97
|
+
# @app.services.db.commit
|
98
|
+
#end
|
99
|
+
|
100
|
+
evt_exit = Concurrent::Event.new
|
101
|
+
do_exit = proc do
|
102
|
+
@app.stop
|
103
|
+
evt_exit.set
|
104
|
+
puts "\nexit."
|
105
|
+
end
|
106
|
+
|
107
|
+
Signal.trap("INT", &do_exit)
|
108
|
+
Signal.trap("TERM", &do_exit)
|
109
|
+
Signal.trap("QUIT", &do_exit)
|
110
|
+
|
111
|
+
Thread.new { evt_exit.wait }.join
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def puts_header(text)
|
117
|
+
puts "\n>>>>> #{text}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def update_config_with_defaults(config, default_config)
|
121
|
+
DEVp2p::Utils.update_config_with_defaults config, default_config
|
122
|
+
end
|
123
|
+
|
124
|
+
def update_config_from_genesis_json(genesis_json_filename_or_hash)
|
125
|
+
genesis = genesis_json_filename_or_hash.instance_of?(String) ?
|
126
|
+
JSON.parse(File.read(genesis_json_filename_or_hash)) :
|
127
|
+
genesis_json_filename_or_hash
|
128
|
+
|
129
|
+
@config[:eth] ||= {}
|
130
|
+
@config[:eth][:block] ||= {}
|
131
|
+
|
132
|
+
id = ->(x) { x }
|
133
|
+
parse_int_or_hex = ->(x) { Reth::Utils.parse_int_or_hex(x) }
|
134
|
+
dec = ->(x) { Reth::Utils.decode_hex Reth::Utils.remove_0x_head(x) }
|
135
|
+
|
136
|
+
m = {
|
137
|
+
'alloc' => [:genesis_initial_alloc, id],
|
138
|
+
'difficulty' => [:genesis_difficulty, parse_int_or_hex],
|
139
|
+
'timestamp' => [:genesis_timestamp, parse_int_or_hex],
|
140
|
+
'extraData' => [:genesis_extra_data, dec],
|
141
|
+
'gasLimit' => [:genesis_gas_limit, parse_int_or_hex],
|
142
|
+
'mixhash' => [:genesis_mixhash, dec],
|
143
|
+
'parentHash' => [:genesis_prevhash, dec],
|
144
|
+
'coinbase' => [:genesis_coinbase, dec],
|
145
|
+
'nonce' => [:genesis_nonce, dec]
|
146
|
+
}
|
147
|
+
|
148
|
+
genesis.each do |k, v|
|
149
|
+
target_key, trans = m[k]
|
150
|
+
@config[:eth][:block][target_key] = trans.call v
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
format = " %s %s"
|
156
|
+
result = Slop.parse do |o|
|
157
|
+
o.separator ''
|
158
|
+
o.separator 'Commands:'
|
159
|
+
o.separator(format % ['run', 'Start the client (--dev to stop on error).'])
|
160
|
+
|
161
|
+
o.separator ''
|
162
|
+
o.separator 'Options:'
|
163
|
+
|
164
|
+
o.string '--profile', 'Configuration profile, livenet or testnet. [default: livenet]', default: 'livenet'
|
165
|
+
o.string '-c', '--config', 'Alternative configuration file.'
|
166
|
+
o.string '-C', 'Single configuration parameters (<param>=<value>).'
|
167
|
+
o.string '-d', '--data-dir', "Data directory. [default: #{Reth::Config::DEFAULT_DATA_DIR}]", default: Reth::Config::DEFAULT_DATA_DIR
|
168
|
+
|
169
|
+
o.string '-l', '--log-config', 'Logger configuration. [default: info]', default: 'info'
|
170
|
+
o.string '--log-file', 'Log to file instead of standard outputs.'
|
171
|
+
|
172
|
+
o.int '-n', '--network-id', 'Network id. Any number greater than 2 will create a private network. If specified predefined profile (by --profile) will not be used.'
|
173
|
+
o.string '-b', '--bootstrap-node', 'Single bootstrap node as enode://pubkey@host:port.'
|
174
|
+
|
175
|
+
o.bool '-m', '--mine', 'Enable miner. [default: disabled]', defalut: false
|
176
|
+
o.int '--mining-pct', 'CPU percentage used for mining.'
|
177
|
+
|
178
|
+
o.string '--unlock', 'Unlock an account (prompts for password).'
|
179
|
+
o.string '--password', 'Path to a password file.'
|
180
|
+
|
181
|
+
o.separator ''
|
182
|
+
o.separator 'Misc:'
|
183
|
+
|
184
|
+
o.on '-v', '--version' do
|
185
|
+
puts "version: #{Reth::CLIENT_VERSION_STRING}"
|
186
|
+
exit
|
187
|
+
end
|
188
|
+
|
189
|
+
o.on '-h', '--help' do
|
190
|
+
puts o
|
191
|
+
exit
|
192
|
+
end
|
193
|
+
end
|
194
|
+
opts = result.to_hash
|
195
|
+
command = ARGV[0]
|
196
|
+
|
197
|
+
Reth::Logger.level = opts[:log_config]
|
198
|
+
|
199
|
+
# frontier genesis:
|
200
|
+
# d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3
|
201
|
+
|
202
|
+
case command
|
203
|
+
when 'run'
|
204
|
+
RethControl.new(opts).start
|
205
|
+
else
|
206
|
+
puts result
|
207
|
+
end
|
208
|
+
|
data/lib/reth.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
require 'devp2p'
|
4
|
+
require 'ethereum'
|
5
|
+
|
6
|
+
module Reth
|
7
|
+
|
8
|
+
CLIENT_NAME = 'reth'
|
9
|
+
CLIENT_VERSION = "#{VERSION}/#{RUBY_PLATFORM}/#{RUBY_ENGINE}-#{RUBY_VERSION}"
|
10
|
+
CLIENT_VERSION_STRING = "#{CLIENT_NAME}-v#{CLIENT_VERSION}"
|
11
|
+
|
12
|
+
Env = Ethereum::Env
|
13
|
+
DB = Ethereum::DB
|
14
|
+
BlockHeader = Ethereum::BlockHeader
|
15
|
+
Block = Ethereum::Block
|
16
|
+
Transaction = Ethereum::Transaction
|
17
|
+
Chain = Ethereum::Chain
|
18
|
+
|
19
|
+
Logger = BlockLogger
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'reth/utils'
|
24
|
+
require 'reth/config'
|
25
|
+
require 'reth/profile'
|
26
|
+
|
27
|
+
require 'reth/keystore'
|
28
|
+
require 'reth/account'
|
29
|
+
|
30
|
+
require 'reth/duplicates_filter'
|
31
|
+
require 'reth/sync_task'
|
32
|
+
require 'reth/synchronizer'
|
33
|
+
|
34
|
+
require 'reth/transient_block'
|
35
|
+
require 'reth/eth_protocol'
|
36
|
+
|
37
|
+
require 'reth/account_service'
|
38
|
+
require 'reth/db_service'
|
39
|
+
require 'reth/leveldb_service'
|
40
|
+
require 'reth/chain_service'
|
41
|
+
|
42
|
+
require 'reth/jsonrpc'
|
43
|
+
require 'reth/app'
|
44
|
+
|
data/lib/reth/account.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module Reth
|
4
|
+
|
5
|
+
class Account
|
6
|
+
|
7
|
+
class <<self
|
8
|
+
def test_accounts
|
9
|
+
return @test_accounts if @test_accounts
|
10
|
+
|
11
|
+
@test_accounts = 9.times.map do |i|
|
12
|
+
privkey = (i+1).chr * 32
|
13
|
+
address = Ethereum::PrivateKey.new(privkey).to_address
|
14
|
+
["0x#{Ethereum::Utils.encode_hex(address)}", privkey]
|
15
|
+
end.to_h
|
16
|
+
|
17
|
+
@test_accounts
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_addresses
|
21
|
+
@test_addresses ||= test_accounts.keys.map {|k| Ethereum::Utils.normalize_address(k) }
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Create a new account.
|
26
|
+
#
|
27
|
+
# Note that this creates the account in memory and does not store it on
|
28
|
+
# disk.
|
29
|
+
#
|
30
|
+
# @param password [String] used to encrypt the private key
|
31
|
+
# @param key [String] the private key, or `nil` to generate a random one
|
32
|
+
# @param uuid [String] an optional id
|
33
|
+
#
|
34
|
+
def create(password, key=nil, uuid=nil, path=nil)
|
35
|
+
key ||= Utils.mk_random_privkey
|
36
|
+
|
37
|
+
json = Keystore.make_json key, password
|
38
|
+
json[:id] = uuid
|
39
|
+
|
40
|
+
new json, password, path
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Load an account from a keystore file.
|
45
|
+
#
|
46
|
+
# @param path [String] full path to the keyfile
|
47
|
+
# @param password [String] the password to decrypt the key file or
|
48
|
+
# `nil` to leave it encrypted
|
49
|
+
#
|
50
|
+
def load(path, password=nil)
|
51
|
+
json = JSON.load File.read(path)
|
52
|
+
raise ValidationError, 'Invalid keystore file' unless Keystore.validate(json)
|
53
|
+
new json, password, path
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_accessor :path, :address, :keystore
|
58
|
+
|
59
|
+
def initialize(keystore, password=nil, path=nil)
|
60
|
+
@keystore = Hashie.symbolize_keys keystore
|
61
|
+
@address = keystore[:address] ? Utils.decode_hex(keystore[:address]) : nil
|
62
|
+
|
63
|
+
@path = path
|
64
|
+
@locked = true
|
65
|
+
|
66
|
+
unlock(password) if password
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Dump the keystore for later disk storage.
|
71
|
+
#
|
72
|
+
# The result inherits the entries `crypto` and `version` from `Keystore`,
|
73
|
+
# and adds `address` and `id` in accordance with the parameters
|
74
|
+
# `include_address` and `include_id`.
|
75
|
+
#
|
76
|
+
# If address or id are not known, they are not added, even if requested.
|
77
|
+
#
|
78
|
+
# @param include_address [Bool] flag denoting if the address should be
|
79
|
+
# included or not
|
80
|
+
# @param include_id [Bool] flag denoting if the id should be included or
|
81
|
+
# not
|
82
|
+
#
|
83
|
+
def dump(include_address=true, include_id=true)
|
84
|
+
h = {}
|
85
|
+
h[:crypto] = @keystore[:crypto]
|
86
|
+
h[:version] = @keystore[:version]
|
87
|
+
|
88
|
+
h[:address] = Utils.encode_hex address if include_address && address
|
89
|
+
h[:id] = uuid if include_id && uuid
|
90
|
+
|
91
|
+
JSON.dump(h)
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Unlock the account with a password.
|
96
|
+
#
|
97
|
+
# If the account is already unlocked, nothing happens, even if the
|
98
|
+
# password is wrong.
|
99
|
+
#
|
100
|
+
# @raise [ValueError] (originating from `Keystore.decode_json`) if the
|
101
|
+
# password is wrong and the account is locked
|
102
|
+
#
|
103
|
+
def unlock(password)
|
104
|
+
if @locked
|
105
|
+
@privkey = Keystore.decode_json @keystore, password
|
106
|
+
@locked = false
|
107
|
+
address # get address such that it stays accessible after a subsequent lock
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Relock an unlocked account.
|
113
|
+
#
|
114
|
+
# This method sets `privkey` to `nil` (unlike `address` which is
|
115
|
+
# preserved).
|
116
|
+
#
|
117
|
+
def lock
|
118
|
+
@privkey = nil
|
119
|
+
@locked = true
|
120
|
+
end
|
121
|
+
|
122
|
+
def privkey
|
123
|
+
@locked ? nil : @privkey
|
124
|
+
end
|
125
|
+
|
126
|
+
def pubkey
|
127
|
+
@locked ? nil : PrivateKey.new(@privkey).to_pubkey
|
128
|
+
end
|
129
|
+
|
130
|
+
def address
|
131
|
+
unless @address
|
132
|
+
if @keystore[:address]
|
133
|
+
@address = Utils.decode_hex(@keystore[:address])
|
134
|
+
elsif !@locked
|
135
|
+
@address = PrivateKey.new(@privkey).to_address
|
136
|
+
else
|
137
|
+
return nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
@address
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# An optional unique identifier, formatted according to UUID version 4,
|
146
|
+
# or `nil` if the account does not have an id.
|
147
|
+
#
|
148
|
+
def uuid
|
149
|
+
@keystore[:id]
|
150
|
+
end
|
151
|
+
|
152
|
+
def uuid=(id)
|
153
|
+
if id
|
154
|
+
@keystore[:id] = id
|
155
|
+
else
|
156
|
+
@keystore.delete :id
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Sign a Transaction with the private key of this account.
|
162
|
+
#
|
163
|
+
# If the account is unlocked, this is equivalent to
|
164
|
+
# `tx.sign(account.privkey)`.
|
165
|
+
#
|
166
|
+
# @param tx [Transaction] the transaction to sign
|
167
|
+
# @raise [ValueError] if the account is locked
|
168
|
+
#
|
169
|
+
def sign_tx(tx)
|
170
|
+
if privkey
|
171
|
+
logger.info "signing tx", tx: tx, account: self
|
172
|
+
tx.sign privkey
|
173
|
+
else
|
174
|
+
raise ValueError, "Locked account cannot sign tx"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def locked?
|
179
|
+
@locked
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_s
|
183
|
+
addr = address ? Utils.encode_hex(address) : '?'
|
184
|
+
"<Account(address=#{addr}, id=#{uuid})>"
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def logger
|
190
|
+
@logger ||= Logger.new('accounts')
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|