OSDN Git Service

7ab39fbae79de0213b9ef8a7ab72bb5ad7556ec3
[bytom/vapor.git] / blockchain / pseudohsm / keycache.go
1 package pseudohsm
2
3 import (
4         "bufio"
5         "encoding/hex"
6         "encoding/json"
7         "fmt"
8         "io/ioutil"
9         "os"
10         "path/filepath"
11         "sort"
12         "strings"
13         "sync"
14         "time"
15
16         log "github.com/sirupsen/logrus"
17
18         "github.com/vapor/crypto/ed25519/chainkd"
19 )
20
21 // Minimum amount of time between cache reloads. This limit applies if the platform does
22 // not support change notifications. It also applies if the keystore directory does not
23 // exist yet, the code will attempt to create a watcher at most this often.
24 const minReloadInterval = 2 * time.Second
25
26 type keysByFile []XPub
27
28 func (s keysByFile) Len() int           { return len(s) }
29 func (s keysByFile) Less(i, j int) bool { return s[i].File < s[j].File }
30 func (s keysByFile) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
31
32 // AmbiguousKeyError is returned when attempting to unlock
33 // an XPub for which more than one file exists.
34 type AmbiguousKeyError struct {
35         Pubkey  string
36         Matches []XPub
37 }
38
39 func (err *AmbiguousKeyError) Error() string {
40         files := ""
41         for i, a := range err.Matches {
42                 files += a.File
43                 if i < len(err.Matches)-1 {
44                         files += ", "
45                 }
46         }
47         return fmt.Sprintf("multiple keys match keys (%s)", files)
48 }
49
50 // keyCache is a live index of all keys in the keystore.
51 type keyCache struct {
52         keydir   string
53         watcher  *watcher
54         mu       sync.Mutex
55         all      keysByFile
56         byPubs   map[chainkd.XPub][]XPub
57         throttle *time.Timer
58 }
59
60 func newKeyCache(keydir string) *keyCache {
61         kc := &keyCache{
62                 keydir: keydir,
63                 byPubs: make(map[chainkd.XPub][]XPub),
64         }
65         kc.watcher = newWatcher(kc)
66         return kc
67 }
68
69 func (kc *keyCache) hasKey(xpub chainkd.XPub) bool {
70         kc.maybeReload()
71         kc.mu.Lock()
72         defer kc.mu.Unlock()
73         return len(kc.byPubs[xpub]) > 0
74 }
75
76 func (kc *keyCache) hasAlias(alias string) bool {
77         xpubs := kc.keys()
78         for _, xpub := range xpubs {
79                 if xpub.Alias == alias {
80                         return true
81                 }
82         }
83         return false
84 }
85
86 func (kc *keyCache) add(newKey XPub) {
87         kc.mu.Lock()
88         defer kc.mu.Unlock()
89
90         i := sort.Search(len(kc.all), func(i int) bool { return kc.all[i].File >= newKey.File })
91         if i < len(kc.all) && kc.all[i] == newKey {
92                 return
93         }
94         // newKey is not in the cache.
95         kc.all = append(kc.all, XPub{})
96         copy(kc.all[i+1:], kc.all[i:])
97         kc.all[i] = newKey
98         kc.byPubs[newKey.XPub] = append(kc.byPubs[newKey.XPub], newKey)
99 }
100
101 func (kc *keyCache) keys() []XPub {
102         kc.maybeReload()
103         kc.mu.Lock()
104         defer kc.mu.Unlock()
105         cpy := make([]XPub, len(kc.all))
106         copy(cpy, kc.all)
107         return cpy
108 }
109
110 func (kc *keyCache) maybeReload() {
111         kc.mu.Lock()
112         defer kc.mu.Unlock()
113
114         if kc.watcher.running {
115                 return // A watcher is running and will keep the cache up-to-date.
116         }
117
118         if kc.throttle == nil {
119                 kc.throttle = time.NewTimer(0)
120         } else {
121                 select {
122                 case <-kc.throttle.C:
123                 default:
124                         return // The cache was reloaded recently.
125                 }
126         }
127         kc.watcher.start()
128         kc.reload()
129         kc.throttle.Reset(minReloadInterval)
130 }
131
132 // find returns the cached keys for alias if there is a unique match.
133 // The exact matching rules are explained by the documentation of Account.
134 // Callers must hold ac.mu.
135 func (kc *keyCache) find(xpub XPub) (XPub, error) {
136         // Limit search to xpub candidates if possible.
137         matches := kc.all
138         if (xpub.XPub != chainkd.XPub{}) {
139                 matches = kc.byPubs[xpub.XPub]
140         }
141         if xpub.File != "" {
142                 // If only the basename is specified, complete the path.
143                 if !strings.ContainsRune(xpub.File, filepath.Separator) {
144                         xpub.File = filepath.Join(kc.keydir, xpub.File)
145                 }
146                 for i := range matches {
147                         if matches[i].File == xpub.File {
148                                 return matches[i], nil
149                         }
150                 }
151                 if (xpub.XPub == chainkd.XPub{}) {
152                         return XPub{}, ErrLoadKey
153                 }
154         }
155         switch len(matches) {
156         case 1:
157                 return matches[0], nil
158         case 0:
159                 return XPub{}, ErrLoadKey
160         default:
161                 err := &AmbiguousKeyError{Pubkey: hex.EncodeToString(xpub.XPub[:]), Matches: make([]XPub, len(matches))}
162                 copy(err.Matches, matches)
163                 return XPub{}, err
164         }
165 }
166
167 // reload caches addresses of existing key.
168 // Callers must hold ac.mu.
169 func (kc *keyCache) reload() {
170         keys, err := kc.scan()
171         if err != nil {
172                 log.WithFields(log.Fields{"module": logModule, "load keys error": err}).Error("can't load keys")
173         }
174         kc.all = keys
175         sort.Sort(kc.all)
176         for k := range kc.byPubs {
177                 delete(kc.byPubs, k)
178         }
179         for _, k := range keys {
180                 kc.byPubs[k.XPub] = append(kc.byPubs[k.XPub], k)
181         }
182         log.WithFields(log.Fields{"module": logModule, "cache has keys:": len(kc.all)}).Debug("reloaded keys")
183 }
184
185 func (kc *keyCache) scan() ([]XPub, error) {
186         files, err := ioutil.ReadDir(kc.keydir)
187         if err != nil {
188                 return nil, err
189         }
190         var (
191                 buf     = new(bufio.Reader)
192                 keys    []XPub
193                 keyJSON struct {
194                         Alias string       `json:"alias"`
195                         XPub  chainkd.XPub `json:"xpub"`
196                 }
197         )
198         for _, fi := range files {
199                 path := filepath.Join(kc.keydir, fi.Name())
200                 if skipKeyFile(fi) {
201                         //log.Printf("ignoring file %v", path)
202                         //fmt.Printf("ignoring file %v", path)
203                         continue
204                 }
205                 fd, err := os.Open(path)
206                 if err != nil {
207                         //log.Printf(err)
208                         fmt.Printf("err")
209                         continue
210                 }
211                 buf.Reset(fd)
212                 // Parse the address.
213                 keyJSON.Alias = ""
214                 err = json.NewDecoder(buf).Decode(&keyJSON)
215                 switch {
216                 case err != nil:
217                         log.WithFields(log.Fields{"module": logModule, "decode json err": err}).Errorf("can't decode key %s: %v", path, err)
218
219                 case (keyJSON.Alias == ""):
220                         log.WithFields(log.Fields{"module": logModule, "can't decode key, key path:": path}).Warn("missing or void alias")
221                 default:
222                         keys = append(keys, XPub{XPub: keyJSON.XPub, Alias: keyJSON.Alias, File: path})
223                 }
224                 fd.Close()
225         }
226         return keys, err
227 }
228
229 func (kc *keyCache) delete(removed XPub) {
230         kc.mu.Lock()
231         defer kc.mu.Unlock()
232         kc.all = removeKey(kc.all, removed)
233         if ba := removeKey(kc.byPubs[removed.XPub], removed); len(ba) == 0 {
234                 delete(kc.byPubs, removed.XPub)
235         } else {
236                 kc.byPubs[removed.XPub] = ba
237         }
238 }
239
240 func removeKey(slice []XPub, elem XPub) []XPub {
241         for i := range slice {
242                 if slice[i] == elem {
243                         return append(slice[:i], slice[i+1:]...)
244                 }
245         }
246         return slice
247 }
248
249 func skipKeyFile(fi os.FileInfo) bool {
250         // Skip editor backups and UNIX-style hidden files.
251         if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
252                 return true
253         }
254         // Skip misc special files, directories (yes, symlinks too).
255         if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
256                 return true
257         }
258         return false
259 }
260
261 func (kc *keyCache) close() {
262         kc.mu.Lock()
263         kc.watcher.close()
264         if kc.throttle != nil {
265                 kc.throttle.Stop()
266         }
267         kc.mu.Unlock()
268 }