9 "github.com/vapor/protocol/bc"
11 log "github.com/sirupsen/logrus"
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"
21 UnconfirmedTxCheckPeriod = 30 * time.Minute
22 MaxUnconfirmedTxDuration = 24 * time.Hour
25 // SortByTimestamp implements sort.Interface for AnnotatedTx slices
26 type SortByTimestamp []*query.AnnotatedTx
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 }
32 // SortByHeight implements sort.Interface for AnnotatedTx slices
33 type SortByHeight []*query.AnnotatedTx
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 }
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")
45 utxos := txOutToUtxos(txD.Tx, txD.StatusFail, 0)
46 utxos = w.filterAccountUtxo(utxos)
47 w.AccountMgr.AddUnconfirmedUtxo(utxos)
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()
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...)
66 sort.Sort(SortByTimestamp(newAnnotatedTxs))
67 return newAnnotatedTxs, nil
70 // GetUnconfirmedTxByTxID get unconfirmed transaction by txID
71 func (w *Wallet) GetUnconfirmedTxByTxID(txID string) (*query.AnnotatedTx, error) {
72 annotatedTx, err := w.Store.GetUnconfirmedTransaction(txID)
77 if annotatedTx == nil {
78 return nil, fmt.Errorf("No transaction(tx_id=%s) from txpool", txID)
80 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
81 return annotatedTx, nil
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) {
90 w.Store.DeleteUnconfirmedTransaction(txD.Tx.ID.String())
91 w.AccountMgr.RemoveUnconfirmedUtxo(txD.Tx.ResultIds)
94 func (w *Wallet) buildAnnotatedUnconfirmedTx(tx *types.Tx) *query.AnnotatedTx {
95 annotatedTx := &query.AnnotatedTx{
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,
103 for i := range tx.Inputs {
104 annotatedTx.Inputs = append(annotatedTx.Inputs, w.BuildAnnotatedInput(tx, uint32(i)))
106 for i := range tx.Outputs {
107 annotatedTx.Outputs = append(annotatedTx.Outputs, w.BuildAnnotatedOutput(tx, i))
112 // checkRelatedTransaction check related unconfirmed transaction.
113 func (w *Wallet) checkRelatedTransaction(tx *types.Tx) bool {
114 for _, v := range tx.Outputs {
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.")
127 for _, v := range tx.Inputs {
128 outid, err := v.SpentOutputID()
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.")
144 // SaveUnconfirmedTx save unconfirmed annotated transaction to the database
145 func (w *Wallet) saveUnconfirmedTx(tx *types.Tx) error {
146 if !w.checkRelatedTransaction(tx) {
150 // annotate account and asset
151 annotatedTx := w.buildAnnotatedUnconfirmedTx(tx)
152 annotatedTxs := []*query.AnnotatedTx{}
153 annotatedTxs = append(annotatedTxs, annotatedTx)
154 w.annotateTxsAccount(annotatedTxs)
156 if err := w.Store.SetUnconfirmedTransaction(tx.ID.String(), annotatedTxs[0]); err != nil {
162 func (w *Wallet) delExpiredTxs() error {
163 AnnotatedTx, err := w.GetUnconfirmedTxs("")
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())
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")
183 ticker := time.NewTicker(UnconfirmedTxCheckPeriod)
188 if err := w.delExpiredTxs(); err != nil {
189 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("wallet fail on delUnconfirmedTx")