OSDN Git Service

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