github.com/FusionFoundation/efsn/v4@v4.2.0/accounts/manager.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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-ethereum 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-ethereum 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/FusionFoundation/efsn/v4/event" 25 ) 26 27 // managerSubBufferSize determines how many incoming wallet events 28 // the manager will buffer in its channel. 29 const managerSubBufferSize = 50 30 31 // newBackendEvent lets the manager know it should 32 // track the given backend for wallet updates. 33 type newBackendEvent struct { 34 backend Backend 35 processed chan struct{} // Informs event emitter that backend has been integrated 36 } 37 38 // Manager is an overarching account manager that can communicate with various 39 // backends for signing transactions. 40 type Manager struct { 41 backends map[reflect.Type][]Backend // Index of backends currently registered 42 updaters []event.Subscription // Wallet update subscriptions for all backends 43 updates chan WalletEvent // Subscription sink for backend wallet changes 44 newBackends chan newBackendEvent // Incoming backends to be tracked by the manager 45 wallets []Wallet // Cache of all wallets from all registered backends 46 47 feed event.Feed // Wallet feed notifying of arrivals/departures 48 49 quit chan chan error 50 term chan struct{} // Channel is closed upon termination of the update loop 51 lock sync.RWMutex 52 } 53 54 // NewManager creates a generic account manager to sign transaction via various 55 // supported backends. 56 func NewManager(backends ...Backend) *Manager { 57 // Retrieve the initial list of wallets from the backends and sort by URL 58 var wallets []Wallet 59 for _, backend := range backends { 60 wallets = merge(wallets, backend.Wallets()...) 61 } 62 // Subscribe to wallet notifications from all backends 63 updates := make(chan WalletEvent, managerSubBufferSize) 64 65 subs := make([]event.Subscription, len(backends)) 66 for i, backend := range backends { 67 subs[i] = backend.Subscribe(updates) 68 } 69 // Assemble the account manager and return 70 am := &Manager{ 71 backends: make(map[reflect.Type][]Backend), 72 updaters: subs, 73 updates: updates, 74 newBackends: make(chan newBackendEvent), 75 wallets: wallets, 76 quit: make(chan chan error), 77 term: make(chan struct{}), 78 } 79 for _, backend := range backends { 80 kind := reflect.TypeOf(backend) 81 am.backends[kind] = append(am.backends[kind], backend) 82 } 83 go am.update() 84 85 return am 86 } 87 88 // Close terminates the account manager's internal notification processes. 89 func (am *Manager) Close() error { 90 errc := make(chan error) 91 am.quit <- errc 92 return <-errc 93 } 94 95 // AddBackend starts the tracking of an additional backend for wallet updates. 96 // cmd/geth assumes once this func returns the backends have been already integrated. 97 func (am *Manager) AddBackend(backend Backend) { 98 done := make(chan struct{}) 99 am.newBackends <- newBackendEvent{backend, done} 100 <-done 101 } 102 103 // update is the wallet event loop listening for notifications from the backends 104 // and updating the cache of wallets. 105 func (am *Manager) update() { 106 // Close all subscriptions when the manager terminates 107 defer func() { 108 am.lock.Lock() 109 for _, sub := range am.updaters { 110 sub.Unsubscribe() 111 } 112 am.updaters = nil 113 am.lock.Unlock() 114 }() 115 116 // Loop until termination 117 for { 118 select { 119 case event := <-am.updates: 120 // Wallet event arrived, update local cache 121 am.lock.Lock() 122 switch event.Kind { 123 case WalletArrived: 124 am.wallets = merge(am.wallets, event.Wallet) 125 case WalletDropped: 126 am.wallets = drop(am.wallets, event.Wallet) 127 } 128 am.lock.Unlock() 129 130 // Notify any listeners of the event 131 am.feed.Send(event) 132 case event := <-am.newBackends: 133 am.lock.Lock() 134 // Update caches 135 backend := event.backend 136 am.wallets = merge(am.wallets, backend.Wallets()...) 137 am.updaters = append(am.updaters, backend.Subscribe(am.updates)) 138 kind := reflect.TypeOf(backend) 139 am.backends[kind] = append(am.backends[kind], backend) 140 am.lock.Unlock() 141 close(event.processed) 142 case errc := <-am.quit: 143 // Manager terminating, return 144 errc <- nil 145 // Signals event emitters the loop is not receiving values 146 // to prevent them from getting stuck. 147 close(am.term) 148 return 149 } 150 } 151 } 152 153 // Backends retrieves the backend(s) with the given type from the account manager. 154 func (am *Manager) Backends(kind reflect.Type) []Backend { 155 am.lock.RLock() 156 defer am.lock.RUnlock() 157 158 return am.backends[kind] 159 } 160 161 // Wallets returns all signer accounts registered under this account manager. 162 func (am *Manager) Wallets() []Wallet { 163 am.lock.RLock() 164 defer am.lock.RUnlock() 165 166 cpy := make([]Wallet, len(am.wallets)) 167 copy(cpy, am.wallets) 168 return cpy 169 } 170 171 // Wallet retrieves the wallet associated with a particular URL. 172 func (am *Manager) Wallet(url string) (Wallet, error) { 173 am.lock.RLock() 174 defer am.lock.RUnlock() 175 176 parsed, err := parseURL(url) 177 if err != nil { 178 return nil, err 179 } 180 for _, wallet := range am.Wallets() { 181 if wallet.URL() == parsed { 182 return wallet, nil 183 } 184 } 185 return nil, ErrUnknownWallet 186 } 187 188 // Find attempts to locate the wallet corresponding to a specific account. Since 189 // accounts can be dynamically added to and removed from wallets, this method has 190 // a linear runtime in the number of wallets. 191 func (am *Manager) Find(account Account) (Wallet, error) { 192 am.lock.RLock() 193 defer am.lock.RUnlock() 194 195 for _, wallet := range am.wallets { 196 if wallet.Contains(account) { 197 return wallet, nil 198 } 199 } 200 return nil, ErrUnknownAccount 201 } 202 203 // Subscribe creates an async subscription to receive notifications when the 204 // manager detects the arrival or departure of a wallet from any of its backends. 205 func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription { 206 return am.feed.Subscribe(sink) 207 } 208 209 // merge is a sorted analogue of append for wallets, where the ordering of the 210 // origin list is preserved by inserting new wallets at the correct position. 211 // 212 // The original slice is assumed to be already sorted by URL. 213 func merge(slice []Wallet, wallets ...Wallet) []Wallet { 214 for _, wallet := range wallets { 215 n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) 216 if n == len(slice) { 217 slice = append(slice, wallet) 218 continue 219 } 220 slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...) 221 } 222 return slice 223 } 224 225 // drop is the couterpart of merge, which looks up wallets from within the sorted 226 // cache and removes the ones specified. 227 func drop(slice []Wallet, wallets ...Wallet) []Wallet { 228 for _, wallet := range wallets { 229 n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) 230 if n == len(slice) { 231 // Wallet not found, may happen during startup 232 continue 233 } 234 slice = append(slice[:n], slice[n+1:]...) 235 } 236 return slice 237 }