github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/wallet/unconfirmed.go (about) 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/bytom/account" 12 "github.com/bytom/bytom/blockchain/query" 13 "github.com/bytom/bytom/crypto/sha3pool" 14 "github.com/bytom/bytom/protocol" 15 "github.com/bytom/bytom/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.WithFields(log.Fields{"module": logModule, "err": err}).Error("wallet fail on saveUnconfirmedTx") 40 } 41 42 utxos := txOutToUtxos(txD.Tx, 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 if !w.checkRelatedTransaction(txD.Tx) { 88 return 89 } 90 w.DB.Delete(calcUnconfirmedTxKey(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().Unix()), 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 if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil { 118 return true 119 } 120 } 121 122 for _, v := range tx.Inputs { 123 outid, err := v.SpentOutputID() 124 if err != nil { 125 continue 126 } 127 if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil { 128 return true 129 } 130 } 131 return false 132 } 133 134 // SaveUnconfirmedTx save unconfirmed annotated transaction to the database 135 func (w *Wallet) saveUnconfirmedTx(tx *types.Tx) error { 136 if !w.checkRelatedTransaction(tx) { 137 return nil 138 } 139 140 // annotate account and asset 141 annotatedTx := w.buildAnnotatedUnconfirmedTx(tx) 142 annotatedTxs := []*query.AnnotatedTx{} 143 annotatedTxs = append(annotatedTxs, annotatedTx) 144 annotateTxsAccount(annotatedTxs, w.DB) 145 146 rawTx, err := json.Marshal(annotatedTxs[0]) 147 if err != nil { 148 return err 149 } 150 151 w.DB.Set(calcUnconfirmedTxKey(tx.ID.String()), rawTx) 152 return nil 153 } 154 155 func (w *Wallet) delExpiredTxs() error { 156 AnnotatedTx, err := w.GetUnconfirmedTxs("") 157 if err != nil { 158 return err 159 } 160 for _, tx := range AnnotatedTx { 161 if time.Now().After(time.Unix(int64(tx.Timestamp), 0).Add(MaxUnconfirmedTxDuration)) { 162 w.DB.Delete(calcUnconfirmedTxKey(tx.ID.String())) 163 } 164 } 165 return nil 166 } 167 168 //delUnconfirmedTx periodically delete locally stored timeout did not confirm txs 169 func (w *Wallet) delUnconfirmedTx() { 170 if err := w.delExpiredTxs(); err != nil { 171 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("wallet fail on delUnconfirmedTx") 172 return 173 } 174 ticker := time.NewTicker(UnconfirmedTxCheckPeriod) 175 defer ticker.Stop() 176 for { 177 <-ticker.C 178 if err := w.delExpiredTxs(); err != nil { 179 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("wallet fail on delUnconfirmedTx") 180 } 181 } 182 }