accounts: refactor API for generalized USB wallets

parent db568a61
...@@ -42,8 +42,9 @@ type Wallet interface { ...@@ -42,8 +42,9 @@ type Wallet interface {
URL() URL URL() URL
// Status returns a textual status to aid the user in the current state of the // Status returns a textual status to aid the user in the current state of the
// wallet. // wallet. It also returns an error indicating any failure the wallet might have
Status() string // encountered.
Status() (string, error)
// Open initializes access to a wallet instance. It is not meant to unlock or // Open initializes access to a wallet instance. It is not meant to unlock or
// decrypt account keys, rather simply to establish a connection to hardware // decrypt account keys, rather simply to establish a connection to hardware
......
...@@ -37,11 +37,11 @@ func TestHDPathParsing(t *testing.T) { ...@@ -37,11 +37,11 @@ func TestHDPathParsing(t *testing.T) {
{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, {"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
// Plain relative derivation paths // Plain relative derivation paths
{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, {"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, {"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, {"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, {"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, {"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
// Hexadecimal absolute derivation paths // Hexadecimal absolute derivation paths
{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, {"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
...@@ -52,11 +52,11 @@ func TestHDPathParsing(t *testing.T) { ...@@ -52,11 +52,11 @@ func TestHDPathParsing(t *testing.T) {
{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, {"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
// Hexadecimal relative derivation paths // Hexadecimal relative derivation paths
{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, {"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, {"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, {"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, {"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, {"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
// Weird inputs just to ensure they work // Weird inputs just to ensure they work
{" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, {" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
......
...@@ -36,16 +36,16 @@ func (w *keystoreWallet) URL() accounts.URL { ...@@ -36,16 +36,16 @@ func (w *keystoreWallet) URL() accounts.URL {
return w.account.URL return w.account.URL
} }
// Status implements accounts.Wallet, always returning "open", since there is no // Status implements accounts.Wallet, returning whether the account held by the
// concept of open/close for plain keystore accounts. // keystore wallet is unlocked or not.
func (w *keystoreWallet) Status() string { func (w *keystoreWallet) Status() (string, error) {
w.keystore.mu.RLock() w.keystore.mu.RLock()
defer w.keystore.mu.RUnlock() defer w.keystore.mu.RUnlock()
if _, ok := w.keystore.unlocked[w.account.Address]; ok { if _, ok := w.keystore.unlocked[w.account.Address]; ok {
return "Unlocked" return "Unlocked", nil
} }
return "Locked" return "Locked", nil
} }
// Open implements accounts.Wallet, but is a noop for plain wallets since there // Open implements accounts.Wallet, but is a noop for plain wallets since there
......
...@@ -29,24 +29,28 @@ import ( ...@@ -29,24 +29,28 @@ import (
) )
// LedgerScheme is the protocol scheme prefixing account and wallet URLs. // LedgerScheme is the protocol scheme prefixing account and wallet URLs.
var LedgerScheme = "ledger" const LedgerScheme = "ledger"
// ledgerDeviceIDs are the known device IDs that Ledger wallets use. // TrezorScheme is the protocol scheme prefixing account and wallet URLs.
var ledgerDeviceIDs = []deviceID{ const TrezorScheme = "trezor"
{Vendor: 0x2c97, Product: 0x0000}, // Ledger Blue
{Vendor: 0x2c97, Product: 0x0001}, // Ledger Nano S // refreshCycle is the maximum time between wallet refreshes (if USB hotplug
} // notifications don't work).
const refreshCycle = time.Second
// Maximum time between wallet refreshes (if USB hotplug notifications don't work). // refreshThrottling is the minimum time between wallet refreshes to avoid USB
const ledgerRefreshCycle = time.Second // trashing.
const refreshThrottling = 500 * time.Millisecond
// Minimum time between wallet refreshes to avoid USB trashing. // Hub is a accounts.Backend that can find and handle generic USB hardware wallets.
const ledgerRefreshThrottling = 500 * time.Millisecond type Hub struct {
scheme string // Protocol scheme prefixing account and wallet URLs.
vendorID uint16 // USB vendor identifier used for device discovery
productIDs []uint16 // USB product identifiers used for device discovery
makeDriver func(log.Logger) driver // Factory method to construct a vendor specific driver
// LedgerHub is a accounts.Backend that can find and handle Ledger hardware wallets.
type LedgerHub struct {
refreshed time.Time // Time instance when the list of wallets was last refreshed refreshed time.Time // Time instance when the list of wallets was last refreshed
wallets []accounts.Wallet // List of Ledger devices currently tracking wallets []accounts.Wallet // List of USB wallet devices currently tracking
updateFeed event.Feed // Event feed to notify wallet additions/removals updateFeed event.Feed // Event feed to notify wallet additions/removals
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
updating bool // Whether the event notification loop is running updating bool // Whether the event notification loop is running
...@@ -61,20 +65,34 @@ type LedgerHub struct { ...@@ -61,20 +65,34 @@ type LedgerHub struct {
} }
// NewLedgerHub creates a new hardware wallet manager for Ledger devices. // NewLedgerHub creates a new hardware wallet manager for Ledger devices.
func NewLedgerHub() (*LedgerHub, error) { func NewLedgerHub() (*Hub, error) {
return newHub(LedgerScheme, 0x2c97, []uint16{0x0000 /* Ledger Blue */, 0x0001 /* Ledger Nano S */}, newLedgerDriver)
}
// NewTrezorHub creates a new hardware wallet manager for Trezor devices.
func NewTrezorHub() (*Hub, error) {
return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, newTrezorDriver)
}
// newHub creates a new hardware wallet manager for generic USB devices.
func newHub(scheme string, vendorID uint16, productIDs []uint16, makeDriver func(log.Logger) driver) (*Hub, error) {
if !hid.Supported() { if !hid.Supported() {
return nil, errors.New("unsupported platform") return nil, errors.New("unsupported platform")
} }
hub := &LedgerHub{ hub := &Hub{
quit: make(chan chan error), scheme: scheme,
vendorID: vendorID,
productIDs: productIDs,
makeDriver: makeDriver,
quit: make(chan chan error),
} }
hub.refreshWallets() hub.refreshWallets()
return hub, nil return hub, nil
} }
// Wallets implements accounts.Backend, returning all the currently tracked USB // Wallets implements accounts.Backend, returning all the currently tracked USB
// devices that appear to be Ledger hardware wallets. // devices that appear to be hardware wallets.
func (hub *LedgerHub) Wallets() []accounts.Wallet { func (hub *Hub) Wallets() []accounts.Wallet {
// Make sure the list of wallets is up to date // Make sure the list of wallets is up to date
hub.refreshWallets() hub.refreshWallets()
...@@ -88,17 +106,17 @@ func (hub *LedgerHub) Wallets() []accounts.Wallet { ...@@ -88,17 +106,17 @@ func (hub *LedgerHub) Wallets() []accounts.Wallet {
// refreshWallets scans the USB devices attached to the machine and updates the // refreshWallets scans the USB devices attached to the machine and updates the
// list of wallets based on the found devices. // list of wallets based on the found devices.
func (hub *LedgerHub) refreshWallets() { func (hub *Hub) refreshWallets() {
// Don't scan the USB like crazy it the user fetches wallets in a loop // Don't scan the USB like crazy it the user fetches wallets in a loop
hub.stateLock.RLock() hub.stateLock.RLock()
elapsed := time.Since(hub.refreshed) elapsed := time.Since(hub.refreshed)
hub.stateLock.RUnlock() hub.stateLock.RUnlock()
if elapsed < ledgerRefreshThrottling { if elapsed < refreshThrottling {
return return
} }
// Retrieve the current list of Ledger devices // Retrieve the current list of USB wallet devices
var ledgers []hid.DeviceInfo var devices []hid.DeviceInfo
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
// hidapi on Linux opens the device during enumeration to retrieve some infos, // hidapi on Linux opens the device during enumeration to retrieve some infos,
...@@ -113,10 +131,10 @@ func (hub *LedgerHub) refreshWallets() { ...@@ -113,10 +131,10 @@ func (hub *LedgerHub) refreshWallets() {
return return
} }
} }
for _, info := range hid.Enumerate(0, 0) { // Can't enumerate directly, one valid ID is the 0 wildcard for _, info := range hid.Enumerate(hub.vendorID, 0) {
for _, id := range ledgerDeviceIDs { for _, id := range hub.productIDs {
if info.VendorID == id.Vendor && info.ProductID == id.Product { if info.ProductID == id && info.Interface == 0 {
ledgers = append(ledgers, info) devices = append(devices, info)
break break
} }
} }
...@@ -128,20 +146,27 @@ func (hub *LedgerHub) refreshWallets() { ...@@ -128,20 +146,27 @@ func (hub *LedgerHub) refreshWallets() {
// Transform the current list of wallets into the new one // Transform the current list of wallets into the new one
hub.stateLock.Lock() hub.stateLock.Lock()
wallets := make([]accounts.Wallet, 0, len(ledgers)) wallets := make([]accounts.Wallet, 0, len(devices))
events := []accounts.WalletEvent{} events := []accounts.WalletEvent{}
for _, ledger := range ledgers { for _, device := range devices {
url := accounts.URL{Scheme: LedgerScheme, Path: ledger.Path} url := accounts.URL{Scheme: hub.scheme, Path: device.Path}
// Drop wallets in front of the next device or those that failed for some reason // Drop wallets in front of the next device or those that failed for some reason
for len(hub.wallets) > 0 && (hub.wallets[0].URL().Cmp(url) < 0 || hub.wallets[0].(*ledgerWallet).failed()) { for len(hub.wallets) > 0 {
// Abort if we're past the current device and found an operational one
_, failure := hub.wallets[0].Status()
if hub.wallets[0].URL().Cmp(url) >= 0 || failure == nil {
break
}
// Drop the stale and failed devices
events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped}) events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped})
hub.wallets = hub.wallets[1:] hub.wallets = hub.wallets[1:]
} }
// If there are no more wallets or the device is before the next, wrap new wallet // If there are no more wallets or the device is before the next, wrap new wallet
if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 { if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 {
wallet := &ledgerWallet{hub: hub, url: &url, info: ledger, log: log.New("url", url)} logger := log.New("url", url)
wallet := &wallet{hub: hub, driver: hub.makeDriver(logger), url: &url, info: device, log: logger}
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
wallets = append(wallets, wallet) wallets = append(wallets, wallet)
...@@ -169,8 +194,8 @@ func (hub *LedgerHub) refreshWallets() { ...@@ -169,8 +194,8 @@ func (hub *LedgerHub) refreshWallets() {
} }
// Subscribe implements accounts.Backend, creating an async subscription to // Subscribe implements accounts.Backend, creating an async subscription to
// receive notifications on the addition or removal of Ledger wallets. // receive notifications on the addition or removal of USB wallets.
func (hub *LedgerHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
// We need the mutex to reliably start/stop the update loop // We need the mutex to reliably start/stop the update loop
hub.stateLock.Lock() hub.stateLock.Lock()
defer hub.stateLock.Unlock() defer hub.stateLock.Unlock()
...@@ -186,16 +211,13 @@ func (hub *LedgerHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscrip ...@@ -186,16 +211,13 @@ func (hub *LedgerHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscrip
return sub return sub
} }
// updater is responsible for maintaining an up-to-date list of wallets stored in // updater is responsible for maintaining an up-to-date list of wallets managed
// the keystore, and for firing wallet addition/removal events. It listens for // by the USB hub, and for firing wallet addition/removal events.
// account change events from the underlying account cache, and also periodically func (hub *Hub) updater() {
// forces a manual refresh (only triggers for systems where the filesystem notifier
// is not running).
func (hub *LedgerHub) updater() {
for { for {
// TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout // TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout
// <-hub.changes // <-hub.changes
time.Sleep(ledgerRefreshCycle) time.Sleep(refreshCycle)
// Run the wallet refresher // Run the wallet refresher
hub.refreshWallets() hub.refreshWallets()
......
This diff is collapsed.
// 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 usbwallet
import (
"errors"
"runtime"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/karalabe/hid"
)
// TrezorScheme is the protocol scheme prefixing account and wallet URLs.
var TrezorScheme = "trezor"
// trezorVendorID is the USB vendor ID for SatoshiLabs.
var trezorVendorID = uint16(0x534c)
// trezorDeviceID is the USB device ID for the Trezor 1.
var trezorDeviceID = uint16(0x0001)
// Maximum time between wallet refreshes (if USB hotplug notifications don't work).
const trezorRefreshCycle = time.Second
// Minimum time between wallet refreshes to avoid USB trashing.
const trezorRefreshThrottling = 500 * time.Millisecond
// TrezorHub is a accounts.Backend that can find and handle Trezor hardware wallets.
type TrezorHub struct {
refreshed time.Time // Time instance when the list of wallets was last refreshed
wallets []accounts.Wallet // List of Trezor 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
stateLock sync.RWMutex // Protects the internals of the hub from racey access
// TODO(karalabe): remove if hotplug lands on Windows
commsPend int // Number of operations blocking enumeration
commsLock sync.Mutex // Lock protecting the pending counter and enumeration
}
// NewTrezorHub creates a new hardware wallet manager for Trezor devices.
func NewTrezorHub() (*TrezorHub, error) {
if !hid.Supported() {
return nil, errors.New("unsupported platform")
}
hub := &TrezorHub{
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 Trezor hardware wallets.
func (hub *TrezorHub) Wallets() []accounts.Wallet {
// Make sure the list of wallets is up to date
hub.refreshWallets()
hub.stateLock.RLock()
defer hub.stateLock.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 *TrezorHub) refreshWallets() {
// Don't scan the USB like crazy it the user fetches wallets in a loop
hub.stateLock.RLock()
elapsed := time.Since(hub.refreshed)
hub.stateLock.RUnlock()
if elapsed < trezorRefreshThrottling {
return
}
// Retrieve the current list of Trezor devices
var trezors []hid.DeviceInfo
if runtime.GOOS == "linux" {
// hidapi on Linux opens the device during enumeration to retrieve some infos,
// breaking the Trezor protocol if that is waiting for user confirmation. This
// is a bug acknowledged at Trezor, but it won't be fixed on old devices so we
// need to prevent concurrent comms ourselves. The more elegant solution would
// be to ditch enumeration in favor of hutplug events, but that don't work yet
// on Windows so if we need to hack it anyway, this is more elegant for now.
hub.commsLock.Lock()
if hub.commsPend > 0 { // A confirmation is pending, don't refresh
hub.commsLock.Unlock()
return
}
}
for _, info := range hid.Enumerate(trezorVendorID, trezorDeviceID) {
if info.Interface == 0 { // interface #1 is the debug link, skip it
trezors = append(trezors, info)
}
}
if runtime.GOOS == "linux" {
// See rationale before the enumeration why this is needed and only on Linux.
hub.commsLock.Unlock()
}
// Transform the current list of wallets into the new one
hub.stateLock.Lock()
wallets := make([]accounts.Wallet, 0, len(trezors))
events := []accounts.WalletEvent{}
for _, trezor := range trezors {
url := accounts.URL{Scheme: TrezorScheme, Path: trezor.Path}
// Drop wallets in front of the next device or those that failed for some reason
for len(hub.wallets) > 0 && (hub.wallets[0].URL().Cmp(url) < 0 || hub.wallets[0].(*trezorWallet).failed()) {
events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped})
hub.wallets = hub.wallets[1:]
}
// If there are no more wallets or the device is before the next, wrap new wallet
if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 {
wallet := &trezorWallet{hub: hub, url: &url, info: trezor, log: log.New("url", url)}
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
wallets = append(wallets, wallet)
continue
}
// If the device is the same as the first wallet, keep it
if hub.wallets[0].URL().Cmp(url) == 0 {
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, Kind: accounts.WalletDropped})
}
hub.refreshed = time.Now()
hub.wallets = wallets
hub.stateLock.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 Trezor wallets.
func (hub *TrezorHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
// We need the mutex to reliably start/stop the update loop
hub.stateLock.Lock()
defer hub.stateLock.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 *TrezorHub) 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(trezorRefreshCycle):
}
// Run the wallet refresher
hub.refreshWallets()
// If all our subscribers left, stop the updater
hub.stateLock.Lock()
if hub.updateScope.Count() == 0 {
hub.updating = false
hub.stateLock.Unlock()
return
}
hub.stateLock.Unlock()
}
}
// 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 usbwallet implements support for USB hardware wallets.
package usbwallet
import "time"
// deviceID is a combined vendor/product identifier to uniquely identify a USB
// hardware device.
type deviceID struct {
Vendor uint16 // The Vendor identifer
Product uint16 // The Product identifier
}
// Maximum time between wallet health checks to detect USB unplugs.
const heartbeatCycle = time.Second
// Minimum time to wait between self derivation attempts, even it the user is
// requesting accounts like crazy.
const selfDeriveThrottling = time.Second
...@@ -251,7 +251,9 @@ func startNode(ctx *cli.Context, stack *node.Node) { ...@@ -251,7 +251,9 @@ func startNode(ctx *cli.Context, stack *node.Node) {
log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
} }
case accounts.WalletOpened: case accounts.WalletOpened:
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", event.Wallet.Status()) status, _ := event.Wallet.Status()
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
if event.Wallet.URL().Scheme == "ledger" { if event.Wallet.URL().Scheme == "ledger" {
event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader) event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader)
} else { } else {
......
...@@ -230,18 +230,25 @@ func (s *PrivateAccountAPI) ListAccounts() []common.Address { ...@@ -230,18 +230,25 @@ func (s *PrivateAccountAPI) ListAccounts() []common.Address {
type rawWallet struct { type rawWallet struct {
URL string `json:"url"` URL string `json:"url"`
Status string `json:"status"` Status string `json:"status"`
Accounts []accounts.Account `json:"accounts"` Failure string `json:"failure,omitempty"`
Accounts []accounts.Account `json:"accounts,omitempty"`
} }
// ListWallets will return a list of wallets this node manages. // ListWallets will return a list of wallets this node manages.
func (s *PrivateAccountAPI) ListWallets() []rawWallet { func (s *PrivateAccountAPI) ListWallets() []rawWallet {
wallets := make([]rawWallet, 0) // return [] instead of nil if empty wallets := make([]rawWallet, 0) // return [] instead of nil if empty
for _, wallet := range s.am.Wallets() { for _, wallet := range s.am.Wallets() {
wallets = append(wallets, rawWallet{ status, failure := wallet.Status()
raw := rawWallet{
URL: wallet.URL().String(), URL: wallet.URL().String(),
Status: wallet.Status(), Status: status,
Accounts: wallet.Accounts(), Accounts: wallet.Accounts(),
}) }
if failure != nil {
raw.Failure = failure.Error()
}
wallets = append(wallets, raw)
} }
return wallets return wallets
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment