mirror of https://github.com/ethereum/go-ethereum
parent
b3c0e9d3cc
commit
fad5eb0a87
@ -1,88 +0,0 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// Backend is an "account provider" that can specify a batch of accounts it can
|
||||
// sign transactions with and upon request, do so.
|
||||
type Backend interface { |
||||
// Accounts retrieves the list of signing accounts the backend is currently aware of.
|
||||
Accounts() []Account |
||||
|
||||
// HasAddress reports whether an account with the given address is present.
|
||||
HasAddress(addr common.Address) bool |
||||
|
||||
// SignHash requests the backend to sign the given hash.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
//
|
||||
// If the backend requires additional authentication to sign the request (e.g.
|
||||
// a password to decrypt the account, or a PIN code o verify the transaction),
|
||||
// an AuthNeededError instance will be returned, containing infos for the user
|
||||
// about which fields or actions are needed. The user may retry by providing
|
||||
// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
|
||||
// the account in a keystore).
|
||||
SignHash(acc Account, hash []byte) ([]byte, error) |
||||
|
||||
// SignTx requests the backend to sign the given transaction.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
//
|
||||
// If the backend requires additional authentication to sign the request (e.g.
|
||||
// a password to decrypt the account, or a PIN code o verify the transaction),
|
||||
// an AuthNeededError instance will be returned, containing infos for the user
|
||||
// about which fields or actions are needed. The user may retry by providing
|
||||
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
|
||||
// the account in a keystore).
|
||||
SignTx(acc Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) |
||||
|
||||
// SignHashWithPassphrase requests the backend to sign the given transaction with
|
||||
// the given passphrase as extra authentication information.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
SignHashWithPassphrase(acc Account, passphrase string, hash []byte) ([]byte, error) |
||||
|
||||
// SignTxWithPassphrase requests the backend to sign the given transaction, with the
|
||||
// given passphrase as extra authentication information.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
SignTxWithPassphrase(acc Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) |
||||
|
||||
// TODO(karalabe,fjl): watching and caching needs the Go subscription system
|
||||
// Watch requests the backend to send a notification to the specified channel whenever
|
||||
// an new account appears or an existing one disappears.
|
||||
//Watch(chan AccountEvent) error
|
||||
|
||||
// Unwatch requests the backend stop sending notifications to the given channel.
|
||||
//Unwatch(chan AccountEvent) error
|
||||
} |
||||
|
||||
// TODO(karalabe,fjl): watching and caching needs the Go subscription system
|
||||
// type AccountEvent struct {
|
||||
// Account Account
|
||||
// Added bool
|
||||
// }
|
@ -0,0 +1,133 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// keystoreWallet implements the accounts.Wallet interface for the original
|
||||
// keystore.
|
||||
type keystoreWallet struct { |
||||
account accounts.Account // Single account contained in this wallet
|
||||
keystore *KeyStore // Keystore where the account originates from
|
||||
} |
||||
|
||||
// Type implements accounts.Wallet, returning the textual type of the wallet.
|
||||
func (w *keystoreWallet) Type() string { |
||||
return "secret-storage" |
||||
} |
||||
|
||||
// URL implements accounts.Wallet, returning the URL of the account within.
|
||||
func (w *keystoreWallet) URL() string { |
||||
return w.account.URL |
||||
} |
||||
|
||||
// Status implements accounts.Wallet, always returning "open", since there is no
|
||||
// concept of open/close for plain keystore accounts.
|
||||
func (w *keystoreWallet) Status() string { |
||||
return "Open" |
||||
} |
||||
|
||||
// Open implements accounts.Wallet, but is a noop for plain wallets since there
|
||||
// is no connection or decryption step necessary to access the list of accounts.
|
||||
func (w *keystoreWallet) Open(passphrase string) error { return nil } |
||||
|
||||
// Close implements accounts.Wallet, but is a noop for plain wallets since is no
|
||||
// meaningful open operation.
|
||||
func (w *keystoreWallet) Close() error { return nil } |
||||
|
||||
// Accounts implements accounts.Wallet, returning an account list consisting of
|
||||
// a single account that the plain kestore wallet contains.
|
||||
func (w *keystoreWallet) Accounts() []accounts.Account { |
||||
return []accounts.Account{w.account} |
||||
} |
||||
|
||||
// Contains implements accounts.Wallet, returning whether a particular account is
|
||||
// or is not wrapped by this wallet instance.
|
||||
func (w *keystoreWallet) Contains(account accounts.Account) bool { |
||||
return account.Address == w.account.Address && (account.URL == "" || account.URL == w.account.URL) |
||||
} |
||||
|
||||
// Derive implements accounts.Wallet, but is a noop for plain wallets since there
|
||||
// is no notion of hierarchical account derivation for plain keystore accounts.
|
||||
func (w *keystoreWallet) Derive(path string, pin bool) (accounts.Account, error) { |
||||
return accounts.Account{}, accounts.ErrNotSupported |
||||
} |
||||
|
||||
// SignHash implements accounts.Wallet, attempting to sign the given hash with
|
||||
// the given account. If the wallet does not wrap this particular account, an
|
||||
// error is returned to avoid account leakage (even though in theory we may be
|
||||
// able to sign via our shared keystore backend).
|
||||
func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) { |
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
if account.URL != "" && account.URL != w.account.URL { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignHash(account, hash) |
||||
} |
||||
|
||||
// SignTx implements accounts.Wallet, attempting to sign the given transaction
|
||||
// with the given account. If the wallet does not wrap this particular account,
|
||||
// an error is returned to avoid account leakage (even though in theory we may
|
||||
// be able to sign via our shared keystore backend).
|
||||
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
if account.URL != "" && account.URL != w.account.URL { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignTx(account, tx, chainID) |
||||
} |
||||
|
||||
// SignHashWithPassphrase implements accounts.Wallet, attempting to sign the
|
||||
// given hash with the given account using passphrase as extra authentication.
|
||||
func (w *keystoreWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { |
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
if account.URL != "" && account.URL != w.account.URL { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignHashWithPassphrase(account, passphrase, hash) |
||||
} |
||||
|
||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||
// transaction with the given account using passphrase as extra authentication.
|
||||
func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
if account.URL != "" && account.URL != w.account.URL { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID) |
||||
} |
@ -0,0 +1,194 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts |
||||
|
||||
import ( |
||||
"reflect" |
||||
"sort" |
||||
"sync" |
||||
|
||||
"github.com/ethereum/go-ethereum/event" |
||||
) |
||||
|
||||
// Manager is an overarching account manager that can communicate with various
|
||||
// backends for signing transactions.
|
||||
type Manager struct { |
||||
backends map[reflect.Type][]Backend // Index of backends currently registered
|
||||
updaters []event.Subscription // Wallet update subscriptions for all backends
|
||||
updates chan WalletEvent // Subscription sink for backend wallet changes
|
||||
wallets []Wallet // Cache of all wallets from all registered backends
|
||||
|
||||
feed event.Feed // Wallet feed notifying of arrivals/departures
|
||||
|
||||
quit chan chan error |
||||
lock sync.RWMutex |
||||
} |
||||
|
||||
// NewManager creates a generic account manager to sign transaction via various
|
||||
// supported backends.
|
||||
func NewManager(backends ...Backend) *Manager { |
||||
// Subscribe to wallet notifications from all backends
|
||||
updates := make(chan WalletEvent, 4*len(backends)) |
||||
|
||||
subs := make([]event.Subscription, len(backends)) |
||||
for i, backend := range backends { |
||||
subs[i] = backend.Subscribe(updates) |
||||
} |
||||
// Retrieve the initial list of wallets from the backends and sort by URL
|
||||
var wallets []Wallet |
||||
for _, backend := range backends { |
||||
wallets = merge(wallets, backend.Wallets()...) |
||||
} |
||||
// Assemble the account manager and return
|
||||
am := &Manager{ |
||||
backends: make(map[reflect.Type][]Backend), |
||||
updaters: subs, |
||||
updates: updates, |
||||
wallets: wallets, |
||||
quit: make(chan chan error), |
||||
} |
||||
for _, backend := range backends { |
||||
kind := reflect.TypeOf(backend) |
||||
am.backends[kind] = append(am.backends[kind], backend) |
||||
} |
||||
go am.update() |
||||
|
||||
return am |
||||
} |
||||
|
||||
// Close terminates the account manager's internal notification processes.
|
||||
func (am *Manager) Close() error { |
||||
errc := make(chan error) |
||||
am.quit <- errc |
||||
return <-errc |
||||
} |
||||
|
||||
// update is the wallet event loop listening for notifications from the backends
|
||||
// and updating the cache of wallets.
|
||||
func (am *Manager) update() { |
||||
// Close all subscriptions when the manager terminates
|
||||
defer func() { |
||||
am.lock.Lock() |
||||
for _, sub := range am.updaters { |
||||
sub.Unsubscribe() |
||||
} |
||||
am.updaters = nil |
||||
am.lock.Unlock() |
||||
}() |
||||
|
||||
// Loop until termination
|
||||
for { |
||||
select { |
||||
case event := <-am.updates: |
||||
// Wallet event arrived, update local cache
|
||||
am.lock.Lock() |
||||
if event.Arrive { |
||||
am.wallets = merge(am.wallets, event.Wallet) |
||||
} else { |
||||
am.wallets = drop(am.wallets, event.Wallet) |
||||
} |
||||
am.lock.Unlock() |
||||
|
||||
// Notify any listeners of the event
|
||||
am.feed.Send(event) |
||||
|
||||
case errc := <-am.quit: |
||||
// Manager terminating, return
|
||||
errc <- nil |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Backends retrieves the backend(s) with the given type from the account manager.
|
||||
func (am *Manager) Backends(kind reflect.Type) []Backend { |
||||
return am.backends[kind] |
||||
} |
||||
|
||||
// Wallets returns all signer accounts registered under this account manager.
|
||||
func (am *Manager) Wallets() []Wallet { |
||||
am.lock.RLock() |
||||
defer am.lock.RUnlock() |
||||
|
||||
cpy := make([]Wallet, len(am.wallets)) |
||||
copy(cpy, am.wallets) |
||||
return cpy |
||||
} |
||||
|
||||
// Wallet retrieves the wallet associated with a particular URL.
|
||||
func (am *Manager) Wallet(url string) (Wallet, error) { |
||||
am.lock.RLock() |
||||
defer am.lock.RUnlock() |
||||
|
||||
for _, wallet := range am.Wallets() { |
||||
if wallet.URL() == url { |
||||
return wallet, nil |
||||
} |
||||
} |
||||
return nil, ErrUnknownWallet |
||||
} |
||||
|
||||
// Find attempts to locate the wallet corresponding to a specific account. Since
|
||||
// accounts can be dynamically added to and removed from wallets, this method has
|
||||
// a linear runtime in the number of wallets.
|
||||
func (am *Manager) Find(account Account) (Wallet, error) { |
||||
am.lock.RLock() |
||||
defer am.lock.RUnlock() |
||||
|
||||
for _, wallet := range am.wallets { |
||||
if wallet.Contains(account) { |
||||
return wallet, nil |
||||
} |
||||
} |
||||
return nil, ErrUnknownAccount |
||||
} |
||||
|
||||
// Subscribe creates an async subscription to receive notifications when the
|
||||
// manager detects the arrival or departure of a wallet from any of its backends.
|
||||
func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription { |
||||
return am.feed.Subscribe(sink) |
||||
} |
||||
|
||||
// merge is a sorted analogue of append for wallets, where the ordering of the
|
||||
// origin list is preserved by inserting new wallets at the correct position.
|
||||
//
|
||||
// The original slice is assumed to be already sorted by URL.
|
||||
func merge(slice []Wallet, wallets ...Wallet) []Wallet { |
||||
for _, wallet := range wallets { |
||||
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL() >= wallet.URL() }) |
||||
if n == len(slice) { |
||||
slice = append(slice, wallet) |
||||
continue |
||||
} |
||||
slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...) |
||||
} |
||||
return slice |
||||
} |
||||
|
||||
// drop is the couterpart of merge, which looks up wallets from within the sorted
|
||||
// cache and removes the ones specified.
|
||||
func drop(slice []Wallet, wallets ...Wallet) []Wallet { |
||||
for _, wallet := range wallets { |
||||
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL() >= wallet.URL() }) |
||||
if n == len(slice) { |
||||
// Wallet not found, may happen during startup
|
||||
continue |
||||
} |
||||
slice = append(slice[:n], slice[n+1:]...) |
||||
} |
||||
return slice |
||||
} |
@ -0,0 +1,205 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// This file contains the implementation for interacting with the Ledger hardware
|
||||
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
||||
// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc
|
||||
|
||||
// +build !ios
|
||||
|
||||
package usbwallet |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
"github.com/karalabe/gousb/usb" |
||||
) |
||||
|
||||
// ledgerDeviceIDs are the known device IDs that Ledger wallets use.
|
||||
var ledgerDeviceIDs = []deviceID{ |
||||
{Vendor: 0x2c97, Product: 0x0000}, // Ledger Blue
|
||||
{Vendor: 0x2c97, Product: 0x0001}, // Ledger Nano S
|
||||
} |
||||
|
||||
// Maximum time between wallet refreshes (if USB hotplug notifications don't work).
|
||||
const ledgerRefreshCycle = time.Second |
||||
|
||||
// Minimum time between wallet refreshes to avoid USB trashing.
|
||||
const ledgerRefreshThrottling = 500 * time.Millisecond |
||||
|
||||
// LedgerHub is a accounts.Backend that can find and handle Ledger hardware wallets.
|
||||
type LedgerHub struct { |
||||
ctx *usb.Context // Context interfacing with a libusb instance
|
||||
|
||||
refreshed time.Time // Time instance when the list of wallets was last refreshed
|
||||
wallets []accounts.Wallet // List of Ledger devices currently tracking
|
||||
updateFeed event.Feed // Event feed to notify wallet additions/removals
|
||||
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
||||
updating bool // Whether the event notification loop is running
|
||||
|
||||
quit chan chan error |
||||
lock sync.RWMutex |
||||
} |
||||
|
||||
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
||||
func NewLedgerHub() (*LedgerHub, error) { |
||||
// Initialize the USB library to access Ledgers through
|
||||
ctx, err := usb.NewContext() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
// Create the USB hub, start and return it
|
||||
hub := &LedgerHub{ |
||||
ctx: ctx, |
||||
quit: make(chan chan error), |
||||
} |
||||
hub.refreshWallets() |
||||
|
||||
return hub, nil |
||||
} |
||||
|
||||
// Wallets implements accounts.Backend, returning all the currently tracked USB
|
||||
// devices that appear to be Ledger hardware wallets.
|
||||
func (hub *LedgerHub) Wallets() []accounts.Wallet { |
||||
// Make sure the list of wallets is up to date
|
||||
hub.refreshWallets() |
||||
|
||||
hub.lock.RLock() |
||||
defer hub.lock.RUnlock() |
||||
|
||||
cpy := make([]accounts.Wallet, len(hub.wallets)) |
||||
copy(cpy, hub.wallets) |
||||
return cpy |
||||
} |
||||
|
||||
// refreshWallets scans the USB devices attached to the machine and updates the
|
||||
// list of wallets based on the found devices.
|
||||
func (hub *LedgerHub) refreshWallets() { |
||||
// Don't scan the USB like crazy it the user fetches wallets in a loop
|
||||
hub.lock.RLock() |
||||
elapsed := time.Since(hub.refreshed) |
||||
hub.lock.RUnlock() |
||||
|
||||
if elapsed < ledgerRefreshThrottling { |
||||
return |
||||
} |
||||
// Retrieve the current list of Ledger devices
|
||||
var devIDs []deviceID |
||||
var busIDs []uint16 |
||||
|
||||
hub.ctx.ListDevices(func(desc *usb.Descriptor) bool { |
||||
// Gather Ledger devices, don't connect any just yet
|
||||
for _, id := range ledgerDeviceIDs { |
||||
if desc.Vendor == id.Vendor && desc.Product == id.Product { |
||||
devIDs = append(devIDs, deviceID{Vendor: desc.Vendor, Product: desc.Product}) |
||||
busIDs = append(busIDs, uint16(desc.Bus)<<8+uint16(desc.Address)) |
||||
return false |
||||
} |
||||
} |
||||
// Not ledger, ignore and don't connect either
|
||||
return false |
||||
}) |
||||
// Transform the current list of wallets into the new one
|
||||
hub.lock.Lock() |
||||
|
||||
wallets := make([]accounts.Wallet, 0, len(devIDs)) |
||||
events := []accounts.WalletEvent{} |
||||
|
||||
for i := 0; i < len(devIDs); i++ { |
||||
devID, busID := devIDs[i], busIDs[i] |
||||
url := fmt.Sprintf("ledger://%03d:%03d", busID>>8, busID&0xff) |
||||
|
||||
// Drop wallets while they were in front of the next account
|
||||
for len(hub.wallets) > 0 && hub.wallets[0].URL() < url { |
||||
events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Arrive: false}) |
||||
hub.wallets = hub.wallets[1:] |
||||
} |
||||
// If there are no more wallets or the account is before the next, wrap new wallet
|
||||
if len(hub.wallets) == 0 || hub.wallets[0].URL() > url { |
||||
wallet := &ledgerWallet{context: hub.ctx, hardwareID: devID, locationID: busID, url: url} |
||||
|
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: true}) |
||||
wallets = append(wallets, wallet) |
||||
continue |
||||
} |
||||
// If the account is the same as the first wallet, keep it
|
||||
if hub.wallets[0].URL() == url { |
||||
wallets = append(wallets, hub.wallets[0]) |
||||
hub.wallets = hub.wallets[1:] |
||||
continue |
||||
} |
||||
} |
||||
// Drop any leftover wallets and set the new batch
|
||||
for _, wallet := range hub.wallets { |
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: false}) |
||||
} |
||||
hub.refreshed = time.Now() |
||||
hub.wallets = wallets |
||||
hub.lock.Unlock() |
||||
|
||||
// Fire all wallet events and return
|
||||
for _, event := range events { |
||||
hub.updateFeed.Send(event) |
||||
} |
||||
} |
||||
|
||||
// Subscribe implements accounts.Backend, creating an async subscription to
|
||||
// receive notifications on the addition or removal of Ledger wallets.
|
||||
func (hub *LedgerHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { |
||||
// We need the mutex to reliably start/stop the update loop
|
||||
hub.lock.Lock() |
||||
defer hub.lock.Unlock() |
||||
|
||||
// Subscribe the caller and track the subscriber count
|
||||
sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink)) |
||||
|
||||
// Subscribers require an active notification loop, start it
|
||||
if !hub.updating { |
||||
hub.updating = true |
||||
go hub.updater() |
||||
} |
||||
return sub |
||||
} |
||||
|
||||
// updater is responsible for maintaining an up-to-date list of wallets stored in
|
||||
// the keystore, and for firing wallet addition/removal events. It listens for
|
||||
// account change events from the underlying account cache, and also periodically
|
||||
// forces a manual refresh (only triggers for systems where the filesystem notifier
|
||||
// is not running).
|
||||
func (hub *LedgerHub) updater() { |
||||
for { |
||||
// Wait for a USB hotplug event (not supported yet) or a refresh timeout
|
||||
select { |
||||
//case <-hub.changes: // reenable on hutplug implementation
|
||||
case <-time.After(ledgerRefreshCycle): |
||||
} |
||||
// Run the wallet refresher
|
||||
hub.refreshWallets() |
||||
|
||||
// If all our subscribers left, stop the updater
|
||||
hub.lock.Lock() |
||||
if hub.updateScope.Count() == 0 { |
||||
hub.updating = false |
||||
hub.lock.Unlock() |
||||
return |
||||
} |
||||
hub.lock.Unlock() |
||||
} |
||||
} |
Loading…
Reference in new issue