OSDN Git Service

Merge pull request #1137 from Bytom/v1.0.3_fix
[bytom/bytom.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/bytom/account"
12         "github.com/bytom/blockchain/query"
13         "github.com/bytom/crypto/sha3pool"
14         "github.com/bytom/protocol"
15         "github.com/bytom/protocol/bc/types"
16 )
17
18 const (
19         //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix
20         UnconfirmedTxPrefix = "UTXS:"
21 )
22
23 func calcUnconfirmedTxKey(formatKey string) []byte {
24         return []byte(UnconfirmedTxPrefix + formatKey)
25 }
26
27 // SortByTimestamp implements sort.Interface for AnnotatedTx slices
28 type SortByTimestamp []*query.AnnotatedTx
29
30 func (a SortByTimestamp) Len() int           { return len(a) }
31 func (a SortByTimestamp) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
32 func (a SortByTimestamp) Less(i, j int) bool { return a[i].Timestamp > a[j].Timestamp }
33
34 // AddUnconfirmedTx handle wallet status update when tx add into txpool
35 func (w *Wallet) AddUnconfirmedTx(txD *protocol.TxDesc) {
36         if err := w.saveUnconfirmedTx(txD.Tx); err != nil {
37                 log.WithField("err", err).Error("wallet fail on saveUnconfirmedTx")
38         }
39
40         utxos := txOutToUtxos(txD.Tx, txD.StatusFail, 0)
41         utxos = w.filterAccountUtxo(utxos)
42         w.AccountMgr.AddUnconfirmedUtxo(utxos)
43 }
44
45 // GetUnconfirmedTxs get account unconfirmed transactions, filter transactions by accountID when accountID is not empty
46 func (w *Wallet) GetUnconfirmedTxs(accountID string) ([]*query.AnnotatedTx, error) {
47         annotatedTxs := []*query.AnnotatedTx{}
48         txIter := w.DB.IteratorPrefix([]byte(UnconfirmedTxPrefix))
49         defer txIter.Release()
50
51         for txIter.Next() {
52                 annotatedTx := &query.AnnotatedTx{}
53                 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
54                         return nil, err
55                 }
56
57                 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
58                         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
59                         annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
60                 }
61         }
62
63         sort.Sort(SortByTimestamp(annotatedTxs))
64         return annotatedTxs, nil
65 }
66
67 // GetUnconfirmedTxByTxID get unconfirmed transaction by txID
68 func (w *Wallet) GetUnconfirmedTxByTxID(txID string) (*query.AnnotatedTx, error) {
69         annotatedTx := &query.AnnotatedTx{}
70         txInfo := w.DB.Get(calcUnconfirmedTxKey(txID))
71         if txInfo == nil {
72                 return nil, fmt.Errorf("No transaction(tx_id=%s) from txpool", txID)
73         }
74
75         if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
76                 return nil, err
77         }
78
79         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
80         return annotatedTx, nil
81 }
82
83 // RemoveUnconfirmedTx handle wallet status update when tx removed from txpool
84 func (w *Wallet) RemoveUnconfirmedTx(txD *protocol.TxDesc) {
85         w.AccountMgr.RemoveUnconfirmedUtxo(txD.Tx.ResultIds)
86 }
87
88 func (w *Wallet) buildAnnotatedUnconfirmedTx(tx *types.Tx) *query.AnnotatedTx {
89         annotatedTx := &query.AnnotatedTx{
90                 ID:        tx.ID,
91                 Timestamp: uint64(time.Now().Unix()),
92                 Inputs:    make([]*query.AnnotatedInput, 0, len(tx.Inputs)),
93                 Outputs:   make([]*query.AnnotatedOutput, 0, len(tx.Outputs)),
94                 Size:      tx.SerializedSize,
95         }
96
97         for i := range tx.Inputs {
98                 annotatedTx.Inputs = append(annotatedTx.Inputs, w.BuildAnnotatedInput(tx, uint32(i)))
99         }
100         for i := range tx.Outputs {
101                 annotatedTx.Outputs = append(annotatedTx.Outputs, w.BuildAnnotatedOutput(tx, i))
102         }
103         return annotatedTx
104 }
105
106 // checkRelatedTransaction check related unconfirmed transaction.
107 func (w *Wallet) checkRelatedTransaction(tx *types.Tx) bool {
108         for _, v := range tx.Outputs {
109                 var hash [32]byte
110                 sha3pool.Sum256(hash[:], v.ControlProgram)
111                 if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
112                         return true
113                 }
114         }
115
116         for _, v := range tx.Inputs {
117                 outid, err := v.SpentOutputID()
118                 if err != nil {
119                         continue
120                 }
121                 if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
122                         return true
123                 }
124         }
125         return false
126 }
127
128 // SaveUnconfirmedTx save unconfirmed annotated transaction to the database
129 func (w *Wallet) saveUnconfirmedTx(tx *types.Tx) error {
130         if !w.checkRelatedTransaction(tx) {
131                 return nil
132         }
133
134         // annotate account and asset
135         annotatedTx := w.buildAnnotatedUnconfirmedTx(tx)
136         annotatedTxs := []*query.AnnotatedTx{}
137         annotatedTxs = append(annotatedTxs, annotatedTx)
138         annotateTxsAccount(annotatedTxs, w.DB)
139
140         rawTx, err := json.Marshal(annotatedTxs[0])
141         if err != nil {
142                 return err
143         }
144
145         w.DB.Set(calcUnconfirmedTxKey(tx.ID.String()), rawTx)
146         return nil
147 }