Class: Reth::AccountService
- Inherits:
-
DEVp2p::Service
- Object
- DEVp2p::Service
- Reth::AccountService
- Includes:
- Enumerable
- Defined in:
- lib/reth/account_service.rb
Constant Summary collapse
- DEFAULT_COINBASE =
Utils.decode_hex('de0b295669a9fd93d5f28d9ec85e40f4cb697bae')
Instance Attribute Summary collapse
-
#accounts ⇒ Object
readonly
Returns the value of attribute accounts.
Instance Method Summary collapse
- #[](address_or_idx) ⇒ Object
- #accounts_with_address ⇒ Object
-
#add_account(account, store = true, include_address = true, include_id = true) ⇒ Object
Add an account.
-
#coinbase ⇒ Object
Return the address that should be used as coinbase for new blocks.
- #each(&block) ⇒ Object
-
#find(identifier) ⇒ Object
Find an account by either its address, its id, or its index as string.
-
#get_by_address(address) ⇒ Object
Get an account by its address.
-
#get_by_id(id) ⇒ Object
Return the account with a given id.
- #include?(address) ⇒ Boolean
-
#initialize(app) ⇒ AccountService
constructor
A new instance of AccountService.
- #propose_path(address) ⇒ Object
- #sign_tx(address, tx) ⇒ Object
- #size ⇒ Object
- #start ⇒ Object
- #stop ⇒ Object
- #unlocked_accounts ⇒ Object
-
#update_account(account, new_password, include_address = true, include_id = true) ⇒ Object
Replace the password of an account.
Constructor Details
#initialize(app) ⇒ AccountService
Returns a new instance of AccountService.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/reth/account_service.rb', line 22 def initialize(app) super(app) if app.config[:accounts][:keystore_dir][0] == '/' @keystore_dir = app.config[:accounts][:keystore_dir] else # relative path @keystore_dir = File.join app.config[:data_dir], app.config[:accounts][:keystore_dir] end @accounts = [] if !File.exist?(@keystore_dir) logger.warn "keystore directory does not exist", directory: @keystore_dir elsif !File.directory?(@keystore_dir) logger.error "configured keystore directory is a file, not a directory", directory: @keystore_dir else logger.info "searching for key files", directory: @keystore_dir ignore = %w(. ..) Dir.foreach(@keystore_dir) do |filename| next if ignore.include?(filename) begin @accounts.push Account.load(File.join(@keystore_dir, filename)) rescue ValueError logger.warn "invalid file skipped in keystore directory", path: filename end end end @accounts.sort_by! {|acct| acct.path.to_s } if @accounts.empty? logger.warn "no accounts found" else logger.info "found account(s)", accounts: @accounts end end |
Instance Attribute Details
#accounts ⇒ Object (readonly)
Returns the value of attribute accounts.
18 19 20 |
# File 'lib/reth/account_service.rb', line 18 def accounts @accounts end |
Instance Method Details
#[](address_or_idx) ⇒ Object
333 334 335 336 337 338 339 340 341 342 |
# File 'lib/reth/account_service.rb', line 333 def [](address_or_idx) if address_or_idx.instance_of?(String) raise ArgumentError, 'address must be 20 bytes' unless address_or_idx.size == 20 acct = @accounts.find {|acct| acct.address == address_or_idx } acct or raise KeyError else raise ArgumentError, 'address_or_idx must be String or Integer' unless address_or_idx.is_a?(Integer) @accounts[address_or_idx] end end |
#accounts_with_address ⇒ Object
230 231 232 |
# File 'lib/reth/account_service.rb', line 230 def accounts_with_address @accounts.select {|acct| acct.address } end |
#add_account(account, store = true, include_address = true, include_id = true) ⇒ Object
Add an account.
If ‘store` is true the account will be stored as a key file at the location given by `account.path`. If this is `nil` a `ValueError` is raised. `include_address` and `include_id` determine if address and id should be removed for storage or not.
This method will raise a ‘ValueError` if the new account has the same UUID as an account already known to the service. Note that address collisions do not result in an exception as those may slip through anyway for locked accounts with hidden addresses.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/reth/account_service.rb', line 117 def add_account(account, store=true, include_address=true, include_id=true) logger.info "adding account", account: account if account.uuid && @accounts.any? {|acct| acct.uuid == account.uuid } logger.error 'could not add account (UUID collision)', uuid: account.uuid raise ValueError, 'Could not add account (UUID collision)' end if store raise ValueError, 'Cannot store account without path' if account.path.nil? if File.exist?(account.path) logger.error 'File does already exist', path: account.path raise IOError, 'File does already exist' end raise AssertError if @accounts.any? {|acct| acct.path == account.path } begin directory = File.dirname account.path FileUtils.mkdir_p(directory) unless File.exist?(directory) File.open(account.path, 'w') do |f| f.write account.dump(include_address, include_id) end rescue IOError => e logger.error "Could not write to file", path: account.path, message: e.to_s raise e end end @accounts.push account @accounts.sort_by! {|acct| acct.path.to_s } end |
#coinbase ⇒ Object
Return the address that should be used as coinbase for new blocks.
The coinbase address is given by the config field pow.coinbase_hex. If this does not exist, the address of the first account is used instead. If there are no accounts, the coinbase is ‘DEFAULT_COINBASE`.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/reth/account_service.rb', line 80 def coinbase cb_hex = (app.config[:pow] || {})[:coinbase_hex] if cb_hex raise ValueError, 'coinbase must be String' unless cb_hex.is_a?(String) begin cb = Utils.decode_hex Utils.remove_0x_head(cb_hex) rescue TypeError raise ValueError, 'invalid coinbase' end else accts = accounts_with_address return DEFAULT_COINBASE if accts.empty? cb = accts[0].address end raise ValueError, 'wrong coinbase length' if cb.size != 20 if config[:accounts][:must_include_coinbase] raise ValueError, 'no account for coinbase' if !@accounts.map(&:address).include?(cb) end cb end |
#each(&block) ⇒ Object
345 346 347 |
# File 'lib/reth/account_service.rb', line 345 def each(&block) @accounts.each(&block) end |
#find(identifier) ⇒ Object
Find an account by either its address, its id, or its index as string.
Example identifiers:
-
‘9c0e0240776cfbe6fa1eb37e57721e1a88a563d1’ (address)
-
‘0x9c0e0240776cfbe6fa1eb37e57721e1a88a563d1’ (address with 0x prefix)
-
‘01dd527b-f4a5-4b3c-9abb-6a8e7cd6722f’ (UUID)
-
‘3’ (index)
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/reth/account_service.rb', line 256 def find(identifier) identifier = identifier.downcase if identifier =~ /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/ # uuid return get_by_id(identifier) end begin address = Address.new(identifier).to_bytes raise AssertError unless address.size == 20 return self[address] rescue # do nothing end index = identifier.to_i raise ValueError, 'Index must be 1 or greater' if index <= 0 raise KeyError if index > @accounts.size @accounts[index-1] end |
#get_by_address(address) ⇒ Object
Get an account by its address.
Note that even if an account with the given address exists, it might not be found if it is locked. Also, multiple accounts with the same address may exist, in which case the first one is returned (and a warning is logged).
306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/reth/account_service.rb', line 306 def get_by_address(address) raise ArgumentError, 'address must be 20 bytes' unless address.size == 20 accts = @accounts.select {|acct| acct.address == address } if accts.size == 0 raise KeyError, "account not found by address #{Utils.encode_hex(address)}" elsif accts.size > 1 logger.warn "multiple accounts with same address found", address: Utils.encode_hex(address) end accts[0] end |
#get_by_id(id) ⇒ Object
Return the account with a given id.
Note that accounts are not required to have an id.
284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/reth/account_service.rb', line 284 def get_by_id(id) accts = @accounts.select {|acct| acct.uuid == id } if accts.size == 0 raise KeyError, "account with id #{id} unknown" elsif accts.size > 1 logger.warn "multiple accounts with same UUID found", uuid: id end accts[0] end |
#include?(address) ⇒ Boolean
328 329 330 331 |
# File 'lib/reth/account_service.rb', line 328 def include?(address) raise ArgumentError, 'address must be 20 bytes' unless address.size == 20 @accounts.any? {|acct| acct.address == address } end |
#propose_path(address) ⇒ Object
324 325 326 |
# File 'lib/reth/account_service.rb', line 324 def propose_path(address) File.join @keystore_dir, Utils.encode_hex(address) end |
#sign_tx(address, tx) ⇒ Object
320 321 322 |
# File 'lib/reth/account_service.rb', line 320 def sign_tx(address, tx) get_by_address(address).sign_tx(tx) end |
#size ⇒ Object
349 350 351 |
# File 'lib/reth/account_service.rb', line 349 def size @accounts.size end |
#start ⇒ Object
60 61 62 |
# File 'lib/reth/account_service.rb', line 60 def start # do nothing end |
#stop ⇒ Object
64 65 66 |
# File 'lib/reth/account_service.rb', line 64 def stop # do nothing end |
#unlocked_accounts ⇒ Object
234 235 236 |
# File 'lib/reth/account_service.rb', line 234 def unlocked_accounts @accounts.select {|acct| !acct.locked? } end |
#update_account(account, new_password, include_address = true, include_id = true) ⇒ Object
Replace the password of an account.
The update is carried out in three steps:
-
the old keystore file is renamed
-
the new keystore file is created at the previous location of the old
keystore file
-
the old keystore file is removed
In this way, at least one of the keystore files exists on disk at any time and can be recovered if the process is interrupted.
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/reth/account_service.rb', line 172 def update_account(account, new_password, include_address=true, include_id=true) raise ValueError, "Account not managed by account service" unless @accounts.include?(account) raise ValueError, "Cannot update locked account" if account.locked? raise ValueError, 'Account not stored on disk' unless account.path logger.debug "creating new account" new_account = Account.create new_password, account.privkey, account.uuid, account.path backup_path = account.path + '~' i = 1 while File.exist?(backup_path) backup_path = backup_path[0, backup_path.rindex('~')+1] + i.to_s i += 1 end raise AssertError if File.exist?(backup_path) logger.info 'moving old keystore file to backup location', from: account.path, to: backup_path begin FileUtils.mv account.path, backup_path rescue logger.error "could not backup keystore, stopping account update", from: account.path, to: backup_path raise $! end raise AssertError unless File.exist?(backup_path) raise AssertError if File.exist?(new_account.path) account.path = backup_path @accounts.delete account begin add_account new_account, include_address, include_id rescue logger.error 'adding new account failed, recovering from backup' FileUtils.mv backup_path, new_account.path account.path = new_account.path @accounts.push account @accounts.sort_by! {|acct| acct.path.to_s } raise $! end raise AssertError unless File.exist?(new_account.path) logger.info "deleting backup of old keystore", path: backup_path begin FileUtils.rm backup_path rescue logger.error 'failed to delete no longer needed backup of old keystore', path: account.path raise $! end account.keystore = new_account.keystore account.path = new_account.path @accounts.push account @accounts.delete new_account @accounts.sort_by! {|acct| acct.path.to_s } logger.debug "account update successful" end |