OSDN Git Service

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