OSDN Git Service

add dpos consensus
[bytom/vapor.git] / wallet / recovery.go
1 package wallet
2
3 import (
4         "encoding/json"
5         "fmt"
6         "reflect"
7         "sync"
8         "sync/atomic"
9         "time"
10
11         "github.com/tendermint/tmlibs/db"
12
13         "github.com/vapor/account"
14         "github.com/vapor/blockchain/signers"
15         "github.com/vapor/crypto/ed25519/chainkd"
16         "github.com/vapor/crypto/sha3pool"
17         "github.com/vapor/errors"
18         "github.com/vapor/protocol/bc"
19         "github.com/vapor/protocol/bc/types"
20 )
21
22 const (
23         // acctRecoveryWindow defines the account derivation lookahead used when
24         // attempting to recover the set of used accounts.
25         acctRecoveryWindow = uint64(6)
26
27         // addrRecoveryWindow defines the address derivation lookahead used when
28         // attempting to recover the set of used addresses.
29         addrRecoveryWindow = uint64(128)
30 )
31
32 //recoveryKey key for db store recovery info.
33 var (
34         recoveryKey = []byte("RecoveryInfo")
35
36         // ErrRecoveryBusy another recovery in progress, can not get recovery manager lock
37         ErrRecoveryBusy = errors.New("another recovery in progress")
38
39         // ErrInvalidAcctID can not find account by account id
40         ErrInvalidAcctID = errors.New("invalid account id")
41 )
42
43 // branchRecoveryState maintains the required state in-order to properly
44 // recover addresses derived from a particular account's internal or external
45 // derivation branch.
46 //
47 // A branch recovery state supports operations for:
48 //  - Expanding the look-ahead horizon based on which indexes have been found.
49 //  - Registering derived addresses with indexes within the horizon.
50 //  - Reporting an invalid child index that falls into the horizon.
51 //  - Reporting that an address has been found.
52 //  - Retrieving all currently derived addresses for the branch.
53 //  - Looking up a particular address by its child index.
54 type branchRecoveryState struct {
55         // recoveryWindow defines the key-derivation lookahead used when
56         // attempting to recover the set of addresses on this branch.
57         RecoveryWindow uint64
58
59         // horizion records the highest child index watched by this branch.
60         Horizon uint64
61
62         // nextUnfound maintains the child index of the successor to the highest
63         // index that has been found during recovery of this branch.
64         NextUnfound uint64
65 }
66
67 // newBranchRecoveryState creates a new branchRecoveryState that can be used to
68 // track either the external or internal branch of an account's derivation path.
69 func newBranchRecoveryState(recoveryWindow uint64) *branchRecoveryState {
70         return &branchRecoveryState{
71                 RecoveryWindow: recoveryWindow,
72                 Horizon:        1,
73                 NextUnfound:    1,
74         }
75 }
76
77 // extendHorizon returns the current horizon and the number of addresses that
78 // must be derived in order to maintain the desired recovery window.
79 func (brs *branchRecoveryState) extendHorizon() (uint64, uint64) {
80         // Compute the new horizon, which should surpass our last found address
81         // by the recovery window.
82         curHorizon := brs.Horizon
83
84         minValidHorizon := brs.NextUnfound + brs.RecoveryWindow
85
86         // If the current horizon is sufficient, we will not have to derive any
87         // new keys.
88         if curHorizon >= minValidHorizon {
89                 return curHorizon, 0
90         }
91
92         // Otherwise, the number of addresses we should derive corresponds to
93         // the delta of the two horizons, and we update our new horizon.
94         delta := minValidHorizon - curHorizon
95         brs.Horizon = minValidHorizon
96
97         return curHorizon, delta
98 }
99
100 // reportFound updates the last found index if the reported index exceeds the
101 // current value.
102 func (brs *branchRecoveryState) reportFound(index uint64) {
103         if index >= brs.NextUnfound {
104                 brs.NextUnfound = index + 1
105         }
106 }
107
108 // addressRecoveryState is used to manage the recovery of addresses generated
109 // under a particular BIP32/BIP44 account. Each account tracks both an external and
110 // internal branch recovery state, both of which use the same recovery window.
111 type addressRecoveryState struct {
112         // ExternalBranch is the recovery state of addresses generated for
113         // external use, i.e. receiving addresses.
114         ExternalBranch *branchRecoveryState
115
116         // InternalBranch is the recovery state of addresses generated for
117         // internal use, i.e. change addresses.
118         InternalBranch *branchRecoveryState
119
120         Account *account.Account
121 }
122
123 func newAddressRecoveryState(recoveryWindow uint64, account *account.Account) *addressRecoveryState {
124         return &addressRecoveryState{
125                 ExternalBranch: newBranchRecoveryState(recoveryWindow),
126                 InternalBranch: newBranchRecoveryState(recoveryWindow),
127                 Account:        account,
128         }
129 }
130
131 // recoveryState used to record the status of a recovery process.
132 type recoveryState struct {
133         // XPubs recovery account xPubs
134         XPubs []chainkd.XPub
135
136         // The time to start the recovery task, used to detemine whether
137         // recovery task is completed.
138         StartTime time.Time
139
140         // XPubsStatus maintains a map of each requested XPub to its active
141         // account recovery state.
142         XPubsStatus *branchRecoveryState
143
144         // AcctStatus maintains a map of each requested key scope to its active
145         // recovery state.
146         AccountsStatus map[string]*addressRecoveryState
147 }
148
149 func newRecoveryState() *recoveryState {
150         return &recoveryState{
151                 AccountsStatus: make(map[string]*addressRecoveryState),
152                 StartTime:      time.Now(),
153         }
154 }
155
156 // stateForScope returns a ScopeRecoveryState for the provided key scope. If one
157 // does not already exist, a new one will be generated with the RecoveryState's
158 // recoveryWindow.
159 func (rs *recoveryState) stateForScope(account *account.Account) {
160         // If the account recovery state already exists, return it.
161         if _, ok := rs.AccountsStatus[account.ID]; ok {
162                 return
163         }
164
165         // Otherwise, initialize the recovery state for this scope with the
166         // chosen recovery window.
167         rs.AccountsStatus[account.ID] = newAddressRecoveryState(addrRecoveryWindow, account)
168 }
169
170 // recoveryManager manage recovery wallet from key.
171 type recoveryManager struct {
172         mu sync.Mutex
173
174         db         db.DB
175         accountMgr *account.Manager
176
177         locked int32
178
179         started bool
180
181         // state encapsulates and allocates the necessary recovery state for all
182         // key scopes and subsidiary derivation paths.
183         state *recoveryState
184
185         //addresses all addresses derivation lookahead used when
186         // attempting to recover the set of used addresses.
187         addresses map[bc.Hash]*account.CtrlProgram
188 }
189
190 // newRecoveryManager create recovery manger.
191 func newRecoveryManager(db db.DB, accountMgr *account.Manager) *recoveryManager {
192         return &recoveryManager{
193                 db:         db,
194                 accountMgr: accountMgr,
195                 addresses:  make(map[bc.Hash]*account.CtrlProgram),
196                 state:      newRecoveryState(),
197         }
198 }
199
200 func (m *recoveryManager) AddrResurrect(accts []*account.Account) error {
201         m.mu.Lock()
202         defer m.mu.Unlock()
203
204         for _, acct := range accts {
205                 m.state.stateForScope(acct)
206                 if err := m.extendScanAddresses(acct.ID, true); err != nil {
207                         return err
208                 }
209
210                 if err := m.extendScanAddresses(acct.ID, false); err != nil {
211                         return err
212                 }
213         }
214
215         m.state.StartTime = time.Now()
216         m.started = true
217         return nil
218 }
219
220 func (m *recoveryManager) AcctResurrect(xPubs []chainkd.XPub) error {
221         m.mu.Lock()
222         defer m.mu.Unlock()
223
224         if !m.tryStartXPubsRec() {
225                 return ErrRecoveryBusy
226         }
227
228         m.state.XPubs = xPubs
229         m.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
230
231         if err := m.extendScanAccounts(); err != nil {
232                 m.stopXPubsRec()
233                 return err
234         }
235         m.state.StartTime = time.Now()
236         m.started = true
237         return nil
238 }
239
240 func (m *recoveryManager) commitStatusInfo() error {
241         rawStatus, err := json.Marshal(m.state)
242         if err != nil {
243                 return err
244         }
245
246         m.db.Set(recoveryKey, rawStatus)
247         return nil
248 }
249
250 func genAcctAlias(xPubs []chainkd.XPub, index uint64) string {
251         var tmp []byte
252         for _, xPub := range xPubs {
253                 tmp = append(tmp, xPub[:6]...)
254         }
255         return fmt.Sprintf("%x:%x", tmp, index)
256 }
257
258 func (m *recoveryManager) extendScanAccounts() error {
259         if m.state.XPubsStatus == nil {
260                 return nil
261         }
262
263         curHorizon, delta := m.state.XPubsStatus.extendHorizon()
264         for index := curHorizon; index < curHorizon+delta; index++ {
265                 alias := genAcctAlias(m.state.XPubs, index)
266                 account, err := account.CreateAccount(m.state.XPubs, len(m.state.XPubs), alias, index, signers.BIP0044)
267                 if err != nil {
268                         return err
269                 }
270
271                 m.state.stateForScope(account)
272                 //generate resurrect address for new account.
273                 if err := m.extendScanAddresses(account.ID, true); err != nil {
274                         return err
275                 }
276
277                 if err := m.extendScanAddresses(account.ID, false); err != nil {
278                         return err
279                 }
280         }
281
282         return nil
283 }
284
285 func getCPHash(cp []byte) bc.Hash {
286         var h [32]byte
287         sha3pool.Sum256(h[:], cp)
288         return bc.NewHash(h)
289 }
290
291 func (m *recoveryManager) extendAddress(acct *account.Account, index uint64, change bool) error {
292         cp, err := account.CreateCtrlProgram(acct, index, change)
293         if err != nil {
294                 return err
295         }
296
297         m.addresses[getCPHash(cp.ControlProgram)] = cp
298         return nil
299 }
300
301 func (m *recoveryManager) extendScanAddresses(accountID string, change bool) error {
302         state, ok := m.state.AccountsStatus[accountID]
303         if !ok {
304                 return ErrInvalidAcctID
305         }
306
307         var curHorizon, delta uint64
308         if change {
309                 curHorizon, delta = state.InternalBranch.extendHorizon()
310         } else {
311                 curHorizon, delta = state.ExternalBranch.extendHorizon()
312         }
313         for index := curHorizon; index < curHorizon+delta; index++ {
314                 if err := m.extendAddress(state.Account, index, change); err != nil {
315                         return err
316                 }
317         }
318         return nil
319 }
320
321 func (m *recoveryManager) processBlock(b *types.Block) error {
322         for _, tx := range b.Transactions {
323                 for _, output := range tx.Outputs {
324                         if cp, ok := m.addresses[getCPHash(output.ControlProgram)]; ok {
325                                 status, ok := m.state.AccountsStatus[cp.AccountID]
326                                 if !ok {
327                                         return ErrInvalidAcctID
328                                 }
329
330                                 if err := m.reportFound(status.Account, cp); err != nil {
331                                         return err
332                                 }
333                         }
334                 }
335         }
336
337         return nil
338 }
339
340 // FilterRecoveryTxs Filter transactions that meet the recovery address
341 func (m *recoveryManager) FilterRecoveryTxs(b *types.Block) error {
342         m.mu.Lock()
343         defer m.mu.Unlock()
344
345         if !m.started {
346                 return nil
347         }
348         if b.Time().After(m.state.StartTime) {
349                 m.finished()
350                 return nil
351         }
352         return m.processBlock(b)
353 }
354
355 func (m *recoveryManager) finished() {
356         m.db.Delete(recoveryKey)
357         m.started = false
358         m.addresses = make(map[bc.Hash]*account.CtrlProgram)
359         m.state = newRecoveryState()
360         m.stopXPubsRec()
361 }
362
363 func (m *recoveryManager) LoadStatusInfo() error {
364         m.mu.Lock()
365         defer m.mu.Unlock()
366
367         rawStatus := m.db.Get(recoveryKey)
368         if rawStatus == nil {
369                 return nil
370         }
371
372         if err := json.Unmarshal(rawStatus, m.state); err != nil {
373                 return err
374         }
375
376         if m.state.XPubs != nil && !m.tryStartXPubsRec() {
377                 return ErrRecoveryBusy
378         }
379
380         if err := m.restoreAddresses(); err != nil {
381                 m.stopXPubsRec()
382                 return err
383         }
384
385         m.started = true
386         return nil
387 }
388
389 // restoreAddresses resume addresses for unfinished tasks
390 func (m *recoveryManager) restoreAddresses() error {
391         for _, state := range m.state.AccountsStatus {
392                 for index := uint64(1); index <= state.InternalBranch.Horizon; index++ {
393                         if err := m.extendAddress(state.Account, index, true); err != nil {
394                                 return err
395                         }
396                 }
397
398                 for index := uint64(1); index <= state.ExternalBranch.Horizon; index++ {
399                         if err := m.extendAddress(state.Account, index, false); err != nil {
400                                 return err
401                         }
402                 }
403         }
404         return nil
405 }
406
407 // reportFound found your own address operation.
408 func (m *recoveryManager) reportFound(account *account.Account, cp *account.CtrlProgram) error {
409         if m.state.XPubsStatus != nil && reflect.DeepEqual(m.state.XPubs, account.XPubs) {
410                 //recovery from XPubs need save account to db.
411                 if err := m.saveAccount(account); err != nil {
412                         return err
413                 }
414
415                 m.state.XPubsStatus.reportFound(account.KeyIndex)
416                 if err := m.extendScanAccounts(); err != nil {
417                         return err
418                 }
419         }
420
421         if cp.Change {
422                 m.state.AccountsStatus[account.ID].InternalBranch.reportFound(cp.KeyIndex)
423         } else {
424                 m.state.AccountsStatus[account.ID].ExternalBranch.reportFound(cp.KeyIndex)
425         }
426
427         if err := m.extendScanAddresses(account.ID, cp.Change); err != nil {
428                 return err
429         }
430
431         if err := m.accountMgr.CreateBatchAddresses(account.ID, cp.Change, cp.KeyIndex); err != nil {
432                 return err
433         }
434
435         return m.commitStatusInfo()
436 }
437
438 func (m *recoveryManager) saveAccount(acct *account.Account) error {
439         tmp, err := m.accountMgr.FindByID(acct.ID)
440         if err != nil && errors.Root(err) != account.ErrFindAccount {
441                 return err
442         }
443
444         if tmp != nil {
445                 return nil
446         }
447         return m.accountMgr.SaveAccount(acct)
448 }
449
450 //tryStartXPubsRec guarantee that only one xPubs recovery is in progress.
451 func (m *recoveryManager) tryStartXPubsRec() bool {
452         return atomic.CompareAndSwapInt32(&m.locked, 0, 1)
453 }
454
455 //stopXPubsRec release xPubs recovery lock.
456 func (m *recoveryManager) stopXPubsRec() {
457         m.state.XPubs = nil
458         atomic.StoreInt32(&m.locked, 0)
459 }