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.
@@ -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.
@@ -0,0 +1,6 @@
1
+ # reth
2
+
3
+
4
+ ## License
5
+
6
+ [MIT License](LICENSE)
@@ -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
+
@@ -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
+
@@ -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