manager.go 8.4 KB

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