github.com/core-coin/go-core/v2@v2.1.9/accounts/manager.go (about) 1 // Copyright 2017 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package accounts 18 19 import ( 20 "reflect" 21 "sort" 22 "sync" 23 24 "github.com/core-coin/go-core/v2/common" 25 "github.com/core-coin/go-core/v2/event" 26 ) 27 28 // Config contains the settings of the global account manager. 29 // 30 // TODO(raisty): Get rid of this when account management 31 // is removed in favor of Clef. 32 type Config struct { 33 InsecureUnlockAllowed bool // Whether account unlocking in insecure environment is allowed 34 } 35 36 // Manager is an overarching account manager that can communicate with various 37 // backends for signing transactions. 38 type Manager struct { 39 config *Config // Global account manager configurations 40 backends map[reflect.Type][]Backend // Index of backends currently registered 41 updaters []event.Subscription // Wallet update subscriptions for all backends 42 updates chan WalletEvent // Subscription sink for backend wallet changes 43 wallets []Wallet // Cache of all wallets from all registered backends 44 45 feed event.Feed // Wallet feed notifying of arrivals/departures 46 47 quit chan chan error 48 lock sync.RWMutex 49 } 50 51 // NewManager creates a generic account manager to sign transaction via various 52 // supported backends. 53 func NewManager(config *Config, backends ...Backend) *Manager { 54 // Retrieve the initial list of wallets from the backends and sort by URL 55 var wallets []Wallet 56 for _, backend := range backends { 57 wallets = merge(wallets, backend.Wallets()...) 58 } 59 // Subscribe to wallet notifications from all backends 60 updates := make(chan WalletEvent, 4*len(backends)) 61 62 subs := make([]event.Subscription, len(backends)) 63 for i, backend := range backends { 64 subs[i] = backend.Subscribe(updates) 65 } 66 // Assemble the account manager and return 67 am := &Manager{ 68 config: config, 69 backends: make(map[reflect.Type][]Backend), 70 updaters: subs, 71 updates: updates, 72 wallets: wallets, 73 quit: make(chan chan error), 74 } 75 for _, backend := range backends { 76 kind := reflect.TypeOf(backend) 77 am.backends[kind] = append(am.backends[kind], backend) 78 } 79 go am.update() 80 81 return am 82 } 83 84 // Close terminates the account manager's internal notification processes. 85 func (am *Manager) Close() error { 86 errc := make(chan error) 87 am.quit <- errc 88 return <-errc 89 } 90 91 // Config returns the configuration of account manager. 92 func (am *Manager) Config() *Config { 93 return am.config 94 } 95 96 // update is the wallet event loop listening for notifications from the backends 97 // and updating the cache of wallets. 98 func (am *Manager) update() { 99 // Close all subscriptions when the manager terminates 100 defer func() { 101 am.lock.Lock() 102 for _, sub := range am.updaters { 103 sub.Unsubscribe() 104 } 105 am.updaters = nil 106 am.lock.Unlock() 107 }() 108 109 // Loop until termination 110 for { 111 select { 112 case event := <-am.updates: 113 // Wallet event arrived, update local cache 114 am.lock.Lock() 115 switch event.Kind { 116 case WalletArrived: 117 am.wallets = merge(am.wallets, event.Wallet) 118 case WalletDropped: 119 am.wallets = drop(am.wallets, event.Wallet) 120 } 121 am.lock.Unlock() 122 123 // Notify any listeners of the event 124 am.feed.Send(event) 125 126 case errc := <-am.quit: 127 // Manager terminating, return 128 errc <- nil 129 return 130 } 131 } 132 } 133 134 // Backends retrieves the backend(s) with the given type from the account manager. 135 func (am *Manager) Backends(kind reflect.Type) []Backend { 136 return am.backends[kind] 137 } 138 139 // Wallets returns all signer accounts registered under this account manager. 140 func (am *Manager) Wallets() []Wallet { 141 am.lock.RLock() 142 defer am.lock.RUnlock() 143 144 return am.walletsNoLock() 145 } 146 147 // walletsNoLock returns all registered wallets. Callers must hold am.lock. 148 func (am *Manager) walletsNoLock() []Wallet { 149 cpy := make([]Wallet, len(am.wallets)) 150 copy(cpy, am.wallets) 151 return cpy 152 } 153 154 // Wallet retrieves the wallet associated with a particular URL. 155 func (am *Manager) Wallet(url string) (Wallet, error) { 156 am.lock.RLock() 157 defer am.lock.RUnlock() 158 159 parsed, err := parseURL(url) 160 if err != nil { 161 return nil, err 162 } 163 for _, wallet := range am.walletsNoLock() { 164 if wallet.URL() == parsed { 165 return wallet, nil 166 } 167 } 168 return nil, ErrUnknownWallet 169 } 170 171 // Accounts returns all account addresses of all wallets within the account manager 172 func (am *Manager) Accounts() []common.Address { 173 am.lock.RLock() 174 defer am.lock.RUnlock() 175 176 addresses := make([]common.Address, 0) // return [] instead of nil if empty 177 for _, wallet := range am.wallets { 178 for _, account := range wallet.Accounts() { 179 addresses = append(addresses, account.Address) 180 } 181 } 182 return addresses 183 } 184 185 // Find attempts to locate the wallet corresponding to a specific account. Since 186 // accounts can be dynamically added to and removed from wallets, this method has 187 // a linear runtime in the number of wallets. 188 func (am *Manager) Find(account Account) (Wallet, error) { 189 am.lock.RLock() 190 defer am.lock.RUnlock() 191 192 for _, wallet := range am.wallets { 193 if wallet.Contains(account) { 194 return wallet, nil 195 } 196 } 197 return nil, ErrUnknownAccount 198 } 199 200 // Subscribe creates an async subscription to receive notifications when the 201 // manager detects the arrival or departure of a wallet from any of its backends. 202 func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription { 203 return am.feed.Subscribe(sink) 204 } 205 206 // merge is a sorted analogue of append for wallets, where the ordering of the 207 // origin list is preserved by inserting new wallets at the correct position. 208 // 209 // The original slice is assumed to be already sorted by URL. 210 func merge(slice []Wallet, wallets ...Wallet) []Wallet { 211 for _, wallet := range wallets { 212 n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) 213 if n == len(slice) { 214 slice = append(slice, wallet) 215 continue 216 } 217 slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...) 218 } 219 return slice 220 } 221 222 // drop is the couterpart of merge, which looks up wallets from within the sorted 223 // cache and removes the ones specified. 224 func drop(slice []Wallet, wallets ...Wallet) []Wallet { 225 for _, wallet := range wallets { 226 n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) 227 if n == len(slice) { 228 // Wallet not found, may happen during startup 229 continue 230 } 231 slice = append(slice[:n], slice[n+1:]...) 232 } 233 return slice 234 }