OSDN Git Service

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