mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +00:00
The drop helper uses sort.Search on the sorted wallet list, but the returned index can point at the next greater wallet when the target URL is missing. Check that the located wallet URL matches before removing it so a missing drop event does not delete an unrelated wallet. Add a regression test for dropping a URL that sorts between two existing wallets.
266 lines
8.1 KiB
Go
266 lines
8.1 KiB
Go
// 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/common"
|
|
"github.com/ethereum/go-ethereum/event"
|
|
)
|
|
|
|
// managerSubBufferSize determines how many incoming wallet events
|
|
// the manager will buffer in its channel.
|
|
const managerSubBufferSize = 50
|
|
|
|
// Config is a legacy struct which is not used
|
|
type Config struct {
|
|
InsecureUnlockAllowed bool // Unused legacy-parameter
|
|
}
|
|
|
|
// newBackendEvent lets the manager know it should
|
|
// track the given backend for wallet updates.
|
|
type newBackendEvent struct {
|
|
backend Backend
|
|
processed chan struct{} // Informs event emitter that backend has been integrated
|
|
}
|
|
|
|
// 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
|
|
newBackends chan newBackendEvent // Incoming backends to be tracked by the manager
|
|
wallets []Wallet // Cache of all wallets from all registered backends
|
|
|
|
feed event.Feed // Wallet feed notifying of arrivals/departures
|
|
|
|
quit chan chan error
|
|
term chan struct{} // Channel is closed upon termination of the update loop
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
// NewManager creates a generic account manager to sign transaction via various
|
|
// supported backends.
|
|
func NewManager(config *Config, backends ...Backend) *Manager {
|
|
// 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()...)
|
|
}
|
|
// Subscribe to wallet notifications from all backends
|
|
updates := make(chan WalletEvent, managerSubBufferSize)
|
|
|
|
subs := make([]event.Subscription, len(backends))
|
|
for i, backend := range backends {
|
|
subs[i] = backend.Subscribe(updates)
|
|
}
|
|
// Assemble the account manager and return
|
|
am := &Manager{
|
|
backends: make(map[reflect.Type][]Backend),
|
|
updaters: subs,
|
|
updates: updates,
|
|
newBackends: make(chan newBackendEvent),
|
|
wallets: wallets,
|
|
quit: make(chan chan error),
|
|
term: make(chan struct{}),
|
|
}
|
|
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
|
|
}
|
|
|
|
// AddBackend starts the tracking of an additional backend for wallet updates.
|
|
// cmd/geth assumes once this func returns the backends have been already integrated.
|
|
func (am *Manager) AddBackend(backend Backend) {
|
|
done := make(chan struct{})
|
|
am.newBackends <- newBackendEvent{backend, done}
|
|
<-done
|
|
}
|
|
|
|
// 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()
|
|
switch event.Kind {
|
|
case WalletArrived:
|
|
am.wallets = merge(am.wallets, event.Wallet)
|
|
case WalletDropped:
|
|
am.wallets = drop(am.wallets, event.Wallet)
|
|
}
|
|
am.lock.Unlock()
|
|
|
|
// Notify any listeners of the event
|
|
am.feed.Send(event)
|
|
case event := <-am.newBackends:
|
|
am.lock.Lock()
|
|
// Update caches
|
|
backend := event.backend
|
|
am.wallets = merge(am.wallets, backend.Wallets()...)
|
|
am.updaters = append(am.updaters, backend.Subscribe(am.updates))
|
|
kind := reflect.TypeOf(backend)
|
|
am.backends[kind] = append(am.backends[kind], backend)
|
|
am.lock.Unlock()
|
|
close(event.processed)
|
|
case errc := <-am.quit:
|
|
// Close all owned wallets
|
|
for _, w := range am.wallets {
|
|
w.Close()
|
|
}
|
|
// Manager terminating, return
|
|
errc <- nil
|
|
// Signals event emitters the loop is not receiving values
|
|
// to prevent them from getting stuck.
|
|
close(am.term)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Backends retrieves the backend(s) with the given type from the account manager.
|
|
func (am *Manager) Backends(kind reflect.Type) []Backend {
|
|
am.lock.RLock()
|
|
defer am.lock.RUnlock()
|
|
|
|
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()
|
|
|
|
return am.walletsNoLock()
|
|
}
|
|
|
|
// walletsNoLock returns all registered wallets. Callers must hold am.lock.
|
|
func (am *Manager) walletsNoLock() []Wallet {
|
|
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()
|
|
|
|
parsed, err := parseURL(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, wallet := range am.walletsNoLock() {
|
|
if wallet.URL() == parsed {
|
|
return wallet, nil
|
|
}
|
|
}
|
|
return nil, ErrUnknownWallet
|
|
}
|
|
|
|
// Accounts returns all account addresses of all wallets within the account manager
|
|
func (am *Manager) Accounts() []common.Address {
|
|
am.lock.RLock()
|
|
defer am.lock.RUnlock()
|
|
|
|
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
|
for _, wallet := range am.wallets {
|
|
for _, account := range wallet.Accounts() {
|
|
addresses = append(addresses, account.Address)
|
|
}
|
|
}
|
|
return addresses
|
|
}
|
|
|
|
// 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().Cmp(wallet.URL()) >= 0 })
|
|
if n == len(slice) {
|
|
slice = append(slice, wallet)
|
|
continue
|
|
}
|
|
slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...)
|
|
}
|
|
return slice
|
|
}
|
|
|
|
// drop is the counterpart 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().Cmp(wallet.URL()) >= 0 })
|
|
if n == len(slice) || slice[n].URL().Cmp(wallet.URL()) != 0 {
|
|
// Wallet not found, may happen during startup
|
|
continue
|
|
}
|
|
slice = append(slice[:n], slice[n+1:]...)
|
|
}
|
|
return slice
|
|
}
|