Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
Geth-Modification
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张蕾
Geth-Modification
Commits
5d9ac49c
Unverified
Commit
5d9ac49c
authored
Aug 09, 2017
by
Péter Szilágyi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
accounts: refactor API for generalized USB wallets
parent
db568a61
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
1450 additions
and
306 deletions
+1450
-306
accounts.go
accounts/accounts.go
+3
-2
hd_test.go
accounts/hd_test.go
+10
-10
keystore_wallet.go
accounts/keystore/keystore_wallet.go
+5
-5
hub.go
accounts/usbwallet/hub.go
+62
-40
ledger.go
accounts/usbwallet/ledger.go
+464
-0
trezor.go
accounts/usbwallet/trezor.go
+330
-0
trezor_hub.go
accounts/usbwallet/trezor_hub.go
+0
-210
usbwallet.go
accounts/usbwallet/usbwallet.go
+0
-34
wallet.go
accounts/usbwallet/wallet.go
+562
-0
main.go
cmd/geth/main.go
+3
-1
api.go
internal/ethapi/api.go
+11
-4
No files found.
accounts/accounts.go
View file @
5d9ac49c
...
@@ -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
...
...
accounts/hd_test.go
View file @
5d9ac49c
...
@@ -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
,
0
x80000000
+
0
}},
{
"128'"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0x80000000
+
128
}},
{
"128'"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0
,
0
x80000000
+
128
}},
{
"2147483648"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0x80000000
+
0
}},
{
"2147483648"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0
,
0
x80000000
+
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
,
0
x80000000
+
0
}},
{
"0x80'"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0x80000000
+
128
}},
{
"0x80'"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0
,
0
x80000000
+
128
}},
{
"0x80000000"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0x80000000
+
0
}},
{
"0x80000000"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0
,
0
x80000000
+
0
}},
// Weird inputs just to ensure they work
// Weird inputs just to ensure they work
{
" m / 44 '
\n
/
\n
60
\n\n\t
' /
\n
0 ' /
\t\t
0"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0
}},
{
" m / 44 '
\n
/
\n
60
\n\n\t
' /
\n
0 ' /
\t\t
0"
,
DerivationPath
{
0x80000000
+
44
,
0x80000000
+
60
,
0x80000000
+
0
,
0
}},
...
...
accounts/keystore/keystore_wallet.go
View file @
5d9ac49c
...
@@ -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
...
...
accounts/usbwallet/
ledger_
hub.go
→
accounts/usbwallet/hub.go
View file @
5d9ac49c
...
@@ -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
*
Ledger
Hub
)
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
*
Ledger
Hub
)
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
<
ledgerR
efreshThrottling
{
if
elapsed
<
r
efreshThrottling
{
return
return
}
}
// Retrieve the current list of
Ledger
devices
// Retrieve the current list of
USB wallet
devices
var
ledger
s
[]
hid
.
DeviceInfo
var
device
s
[]
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
ledgerDevice
IDs
{
for
_
,
id
:=
range
hub
.
product
IDs
{
if
info
.
VendorID
==
id
.
Vendor
&&
info
.
ProductID
==
id
.
Product
{
if
info
.
ProductID
==
id
&&
info
.
Interface
==
0
{
ledgers
=
append
(
ledger
s
,
info
)
devices
=
append
(
device
s
,
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
(
ledger
s
))
wallets
:=
make
([]
accounts
.
Wallet
,
0
,
len
(
device
s
))
events
:=
[]
accounts
.
WalletEvent
{}
events
:=
[]
accounts
.
WalletEvent
{}
for
_
,
ledger
:=
range
ledger
s
{
for
_
,
device
:=
range
device
s
{
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
*
Ledger
Hub
)
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
(
ledgerR
efreshCycle
)
time
.
Sleep
(
r
efreshCycle
)
// Run the wallet refresher
// Run the wallet refresher
hub
.
refreshWallets
()
hub
.
refreshWallets
()
...
...
accounts/usbwallet/ledger
_wallet
.go
→
accounts/usbwallet/ledger.go
View file @
5d9ac49c
This diff is collapsed.
Click to expand it.
accounts/usbwallet/trezor.go
0 → 100644
View file @
5d9ac49c
This diff is collapsed.
Click to expand it.
accounts/usbwallet/trezor_hub.go
deleted
100644 → 0
View file @
db568a61
// 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
()
}
}
accounts/usbwallet/usbwallet.go
deleted
100644 → 0
View file @
db568a61
// 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
accounts/usbwallet/
trezor_
wallet.go
→
accounts/usbwallet/wallet.go
View file @
5d9ac49c
This diff is collapsed.
Click to expand it.
cmd/geth/main.go
View file @
5d9ac49c
...
@@ -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
{
...
...
internal/ethapi/api.go
View file @
5d9ac49c
...
@@ -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
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment