github.com/klaytn/klaytn@v1.12.1/accounts/manager.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2017 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from accounts/manager.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package accounts 22 23 import ( 24 "reflect" 25 "sort" 26 "sync" 27 28 "github.com/klaytn/klaytn/event" 29 ) 30 31 // Manager is an overarching account manager that can communicate with various 32 // backends for signing transactions. 33 type Manager struct { 34 backends map[reflect.Type][]Backend // Index of backends currently registered 35 updaters []event.Subscription // Wallet update subscriptions for all backends 36 updates chan WalletEvent // Subscription sink for backend wallet changes 37 wallets []Wallet // Cache of all wallets from all registered backends 38 39 feed event.Feed // Wallet feed notifying of arrivals/departures 40 41 quit chan chan error 42 lock sync.RWMutex 43 } 44 45 //go:generate mockgen -destination=accounts/mocks/account_manager_mock.go github.com/klaytn/klaytn/accounts AccountManager 46 // AccountManager is an interface of accounts.Manager struct. 47 type AccountManager interface { 48 Wallet(url string) (Wallet, error) 49 Wallets() []Wallet 50 Find(account Account) (Wallet, error) 51 Backends(kind reflect.Type) []Backend 52 Subscribe(sink chan<- WalletEvent) event.Subscription 53 } 54 55 // NewManager creates a generic account manager to sign transaction via various 56 // supported backends. 57 func NewManager(backends ...Backend) *Manager { 58 // Retrieve the initial list of wallets from the backends and sort by URL 59 var wallets []Wallet 60 for _, backend := range backends { 61 wallets = merge(wallets, backend.Wallets()...) 62 } 63 // Subscribe to wallet notifications from all backends 64 updates := make(chan WalletEvent, 4*len(backends)) 65 66 subs := make([]event.Subscription, len(backends)) 67 for i, backend := range backends { 68 subs[i] = backend.Subscribe(updates) 69 } 70 // Assemble the account manager and return 71 am := &Manager{ 72 backends: make(map[reflect.Type][]Backend), 73 updaters: subs, 74 updates: updates, 75 wallets: wallets, 76 quit: make(chan chan error), 77 } 78 for _, backend := range backends { 79 kind := reflect.TypeOf(backend) 80 am.backends[kind] = append(am.backends[kind], backend) 81 } 82 go am.update() 83 84 return am 85 } 86 87 // Close terminates the account manager's internal notification processes. 88 func (am *Manager) Close() error { 89 errc := make(chan error) 90 am.quit <- errc 91 return <-errc 92 } 93 94 // update is the wallet event loop listening for notifications from the backends 95 // and updating the cache of wallets. 96 func (am *Manager) update() { 97 // Close all subscriptions when the manager terminates 98 defer func() { 99 am.lock.Lock() 100 for _, sub := range am.updaters { 101 sub.Unsubscribe() 102 } 103 am.updaters = nil 104 am.lock.Unlock() 105 }() 106 107 // Loop until termination 108 for { 109 select { 110 case event := <-am.updates: 111 // Wallet event arrived, update local cache 112 am.lock.Lock() 113 switch event.Kind { 114 case WalletArrived: 115 am.wallets = merge(am.wallets, event.Wallet) 116 case WalletDropped: 117 am.wallets = drop(am.wallets, event.Wallet) 118 } 119 am.lock.Unlock() 120 121 // Notify any listeners of the event 122 am.feed.Send(event) 123 124 case errc := <-am.quit: 125 // Manager terminating, return 126 errc <- nil 127 return 128 } 129 } 130 } 131 132 // Backends retrieves the backend(s) with the given type from the account manager. 133 func (am *Manager) Backends(kind reflect.Type) []Backend { 134 return am.backends[kind] 135 } 136 137 // Wallets returns all signer accounts registered under this account manager. 138 func (am *Manager) Wallets() []Wallet { 139 am.lock.RLock() 140 defer am.lock.RUnlock() 141 142 cpy := make([]Wallet, len(am.wallets)) 143 copy(cpy, am.wallets) 144 return cpy 145 } 146 147 // Wallet retrieves the wallet associated with a particular URL. 148 func (am *Manager) Wallet(url string) (Wallet, error) { 149 am.lock.RLock() 150 defer am.lock.RUnlock() 151 152 parsed, err := parseURL(url) 153 if err != nil { 154 return nil, err 155 } 156 for _, wallet := range am.Wallets() { 157 if wallet.URL() == parsed { 158 return wallet, nil 159 } 160 } 161 return nil, ErrUnknownWallet 162 } 163 164 // Find attempts to locate the wallet corresponding to a specific account. Since 165 // accounts can be dynamically added to and removed from wallets, this method has 166 // a linear runtime in the number of wallets. 167 func (am *Manager) Find(account Account) (Wallet, error) { 168 am.lock.RLock() 169 defer am.lock.RUnlock() 170 171 for _, wallet := range am.wallets { 172 if wallet.Contains(account) { 173 return wallet, nil 174 } 175 } 176 return nil, ErrUnknownAccount 177 } 178 179 // Subscribe creates an async subscription to receive notifications when the 180 // manager detects the arrival or departure of a wallet from any of its backends. 181 func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription { 182 return am.feed.Subscribe(sink) 183 } 184 185 // merge is a sorted analogue of append for wallets, where the ordering of the 186 // origin list is preserved by inserting new wallets at the correct position. 187 // 188 // The original slice is assumed to be already sorted by URL. 189 func merge(slice []Wallet, wallets ...Wallet) []Wallet { 190 for _, wallet := range wallets { 191 n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) 192 if n == len(slice) { 193 slice = append(slice, wallet) 194 continue 195 } 196 slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...) 197 } 198 return slice 199 } 200 201 // drop is the couterpart of merge, which looks up wallets from within the sorted 202 // cache and removes the ones specified. 203 func drop(slice []Wallet, wallets ...Wallet) []Wallet { 204 for _, wallet := range wallets { 205 n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) 206 if n == len(slice) { 207 // Wallet not found, may happen during startup 208 continue 209 } 210 slice = append(slice[:n], slice[n+1:]...) 211 } 212 return slice 213 }