OSDN Git Service

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