OSDN Git Service

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