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
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
|