OSDN Git Service

229cbc5876a6a950cbc00f7d881aa78a31ca7726
[bytom/vapor.git] / wallet / unconfirmed.go
1 package wallet
2
3 import (
4         "encoding/hex"
5         "fmt"
6         "sort"
7         "time"
8
9         "github.com/vapor/protocol/bc"
10
11         log "github.com/sirupsen/logrus"
12
13         acc "github.com/vapor/account"
14         "github.com/vapor/blockchain/query"
15         "github.com/vapor/crypto/sha3pool"
16         "github.com/vapor/protocol"
17         "github.com/vapor/protocol/bc/types"
18 )
19
20 const (
21         UnconfirmedTxCheckPeriod = 30 * time.Minute
22         MaxUnconfirmedTxDuration = 24 * time.Hour
23 )
24
25 // SortByTimestamp implements sort.Interface for AnnotatedTx slices
26 type SortByTimestamp []*query.AnnotatedTx
27
28 func (a SortByTimestamp) Len() int           { return len(a) }
29 func (a SortByTimestamp) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
30 func (a SortByTimestamp) Less(i, j int) bool { return a[i].Timestamp > a[j].Timestamp }
31
32 // SortByHeight implements sort.Interface for AnnotatedTx slices
33 type SortByHeight []*query.AnnotatedTx
34
35 func (a SortByHeight) Len() int           { return len(a) }
36 func (a SortByHeight) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
37 func (a SortByHeight) Less(i, j int) bool { return a[i].BlockHeight > a[j].BlockHeight }
38
39 // AddUnconfirmedTx handle wallet status update when tx add into txpool
40 func (w *Wallet) AddUnconfirmedTx(txD *protocol.TxDesc) {
41         if err := w.saveUnconfirmedTx(txD.Tx); err != nil {
42                 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("wallet fail on saveUnconfirmedTx")
43         }
44
45         utxos := txOutToUtxos(txD.Tx, txD.StatusFail, 0)
46         utxos = w.filterAccountUtxo(utxos)
47         w.AccountMgr.AddUnconfirmedUtxo(utxos)
48 }
49
50 // GetUnconfirmedTxs get account unconfirmed transactions, filter transactions by accountID when accountID is not empty
51 func (w *Wallet) GetUnconfirmedTxs(accountID string) ([]*query.AnnotatedTx, error) {
52         annotatedTxs := []*query.AnnotatedTx{}
53         annotatedTxs, err := w.Store.ListUnconfirmedTransactions()
54         if err != nil {
55                 return nil, err
56         }
57
58         newAnnotatedTxs := []*query.AnnotatedTx{}
59         for _, annotatedTx := range annotatedTxs {
60                 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
61                         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
62                         newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
63                 }
64         }
65
66         sort.Sort(SortByTimestamp(newAnnotatedTxs))
67         return newAnnotatedTxs, nil
68 }
69
70 // GetUnconfirmedTxByTxID get unconfirmed transaction by txID
71 func (w *Wallet) GetUnconfirmedTxByTxID(txID string) (*query.AnnotatedTx, error) {
72         annotatedTx, err := w.Store.GetUnconfirmedTransaction(txID)
73         if err != nil {
74                 return nil, err
75         }
76
77         if annotatedTx == nil {
78                 return nil, fmt.Errorf("No transaction(tx_id=%s) from txpool", txID)
79         }
80         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
81         return annotatedTx, nil
82 }
83
84 // RemoveUnconfirmedTx handle wallet status update when tx removed from txpool
85 func (w *Wallet) RemoveUnconfirmedTx(txD *protocol.TxDesc) {
86         if !w.checkRelatedTransaction(txD.Tx) {
87                 return
88         }
89
90         w.Store.DeleteUnconfirmedTransaction(txD.Tx.ID.String())
91         w.AccountMgr.RemoveUnconfirmedUtxo(txD.Tx.ResultIds)
92 }
93
94 func (w *Wallet) buildAnnotatedUnconfirmedTx(tx *types.Tx) *query.AnnotatedTx {
95         annotatedTx := &query.AnnotatedTx{
96                 ID:        tx.ID,
97                 Timestamp: uint64(time.Now().UnixNano() / int64(time.Millisecond)),
98                 Inputs:    make([]*query.AnnotatedInput, 0, len(tx.Inputs)),
99                 Outputs:   make([]*query.AnnotatedOutput, 0, len(tx.Outputs)),
100                 Size:      tx.SerializedSize,
101         }
102
103         for i := range tx.Inputs {
104                 annotatedTx.Inputs = append(annotatedTx.Inputs, w.BuildAnnotatedInput(tx, uint32(i)))
105         }
106         for i := range tx.Outputs {
107                 annotatedTx.Outputs = append(annotatedTx.Outputs, w.BuildAnnotatedOutput(tx, i))
108         }
109         return annotatedTx
110 }
111
112 // checkRelatedTransaction check related unconfirmed transaction.
113 func (w *Wallet) checkRelatedTransaction(tx *types.Tx) bool {
114         for _, v := range tx.Outputs {
115                 var hash [32]byte
116                 sha3pool.Sum256(hash[:], v.ControlProgram())
117                 cp, err := w.AccountMgr.GetControlProgram(bc.NewHash(hash))
118                 if err != nil && err != acc.ErrFindCtrlProgram {
119                         log.WithFields(log.Fields{"module": logModule, "err": err, "hash": hex.EncodeToString(hash[:])}).Error("checkRelatedTransaction fail.")
120                         continue
121                 }
122                 if cp != nil {
123                         return true
124                 }
125         }
126
127         for _, v := range tx.Inputs {
128                 outid, err := v.SpentOutputID()
129                 if err != nil {
130                         continue
131                 }
132                 utxo, err := w.Store.GetStandardUTXO(outid)
133                 if err != nil && err != ErrGetStandardUTXO {
134                         log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("checkRelatedTransaction fail.")
135                         continue
136                 }
137                 if utxo != nil {
138                         return true
139                 }
140         }
141         return false
142 }
143
144 // SaveUnconfirmedTx save unconfirmed annotated transaction to the database
145 func (w *Wallet) saveUnconfirmedTx(tx *types.Tx) error {
146         if !w.checkRelatedTransaction(tx) {
147                 return nil
148         }
149
150         // annotate account and asset
151         annotatedTx := w.buildAnnotatedUnconfirmedTx(tx)
152         annotatedTxs := []*query.AnnotatedTx{}
153         annotatedTxs = append(annotatedTxs, annotatedTx)
154         w.annotateTxsAccount(annotatedTxs)
155
156         if err := w.Store.SetUnconfirmedTransaction(tx.ID.String(), annotatedTxs[0]); err != nil {
157                 return err
158         }
159         return nil
160 }
161
162 func (w *Wallet) delExpiredTxs() error {
163         AnnotatedTx, err := w.GetUnconfirmedTxs("")
164         if err != nil {
165                 return err
166         }
167
168         for _, tx := range AnnotatedTx {
169                 if time.Now().After(time.Unix(int64(tx.Timestamp), 0).Add(MaxUnconfirmedTxDuration)) {
170                         w.Store.DeleteUnconfirmedTransaction(tx.ID.String())
171                 }
172         }
173         return nil
174 }
175
176 //delUnconfirmedTx periodically delete locally stored timeout did not confirm txs
177 func (w *Wallet) delUnconfirmedTx() {
178         if err := w.delExpiredTxs(); err != nil {
179                 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("wallet fail on delUnconfirmedTx")
180                 return
181         }
182
183         ticker := time.NewTicker(UnconfirmedTxCheckPeriod)
184         defer ticker.Stop()
185
186         for {
187                 <-ticker.C
188                 if err := w.delExpiredTxs(); err != nil {
189                         log.WithFields(log.Fields{"module": logModule, "err": err}).Error("wallet fail on delUnconfirmedTx")
190                 }
191         }
192 }