diff options
Diffstat (limited to 'accounts/manager.go')
-rw-r--r-- | accounts/manager.go | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/accounts/manager.go b/accounts/manager.go new file mode 100644 index 0000000..39211da --- /dev/null +++ b/accounts/manager.go @@ -0,0 +1,229 @@ +// 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/ava-labs/go-ethereum/common" + "github.com/ava-labs/go-ethereum/event" +) + +// Config contains the settings of the global account manager. +// +// TODO(rjl493456442, karalabe, holiman): Get rid of this when account management +// is removed in favor of Clef. +type Config struct { + InsecureUnlockAllowed bool // Whether account unlocking in insecure environment is allowed +} + +// Manager is an overarching account manager that can communicate with various +// backends for signing transactions. +type Manager struct { + config *Config // Global account manager configurations + 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(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, 4*len(backends)) + + subs := make([]event.Subscription, len(backends)) + for i, backend := range backends { + subs[i] = backend.Subscribe(updates) + } + // Assemble the account manager and return + am := &Manager{ + config: config, + 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 +} + +// Config returns the configuration of account manager. +func (am *Manager) Config() *Config { + return am.config +} + +// 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 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() + + parsed, err := parseURL(url) + if err != nil { + return nil, err + } + for _, wallet := range am.Wallets() { + 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 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().Cmp(wallet.URL()) >= 0 }) + if n == len(slice) { + // Wallet not found, may happen during startup + continue + } + slice = append(slice[:n], slice[n+1:]...) + } + return slice +} |