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