github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/protocol/txpool.go (about) 1 package protocol 2 3 import ( 4 "errors" 5 "sync" 6 "sync/atomic" 7 "time" 8 9 "github.com/golang/groupcache/lru" 10 log "github.com/sirupsen/logrus" 11 12 "github.com/bytom/bytom/consensus" 13 "github.com/bytom/bytom/consensus/bcrp" 14 "github.com/bytom/bytom/event" 15 "github.com/bytom/bytom/protocol/bc" 16 "github.com/bytom/bytom/protocol/bc/types" 17 "github.com/bytom/bytom/protocol/state" 18 ) 19 20 // msg type 21 const ( 22 MsgNewTx = iota 23 MsgRemoveTx 24 logModule = "protocol" 25 ) 26 27 var ( 28 maxCachedErrTxs = 1000 29 maxMsgChSize = 1000 30 maxNewTxNum = 10000 31 maxOrphanNum = 2000 32 33 orphanTTL = 10 * time.Minute 34 orphanExpireScanInterval = 3 * time.Minute 35 36 // ErrTransactionNotExist is the pre-defined error message 37 ErrTransactionNotExist = errors.New("transaction are not existed in the mempool") 38 // ErrPoolIsFull indicates the pool is full 39 ErrPoolIsFull = errors.New("transaction pool reach the max number") 40 // ErrDustTx indicates transaction is dust tx 41 ErrDustTx = errors.New("transaction is dust tx") 42 ) 43 44 type TxMsgEvent struct{ TxMsg *TxPoolMsg } 45 46 // TxDesc store tx and related info for mining strategy 47 type TxDesc struct { 48 Tx *types.Tx `json:"transaction"` 49 Added time.Time `json:"-"` 50 Height uint64 `json:"-"` 51 Weight uint64 `json:"-"` 52 Fee uint64 `json:"-"` 53 } 54 55 // TxPoolMsg is use for notify pool changes 56 type TxPoolMsg struct { 57 *TxDesc 58 MsgType int 59 } 60 61 type orphanTx struct { 62 *TxDesc 63 expiration time.Time 64 } 65 66 // TxPool is use for store the unconfirmed transaction 67 type TxPool struct { 68 lastUpdated int64 69 mtx sync.RWMutex 70 store state.Store 71 pool map[bc.Hash]*TxDesc 72 utxo map[bc.Hash]*types.Tx 73 orphans map[bc.Hash]*orphanTx 74 orphansByPrev map[bc.Hash]map[bc.Hash]*orphanTx 75 errCache *lru.Cache 76 eventDispatcher *event.Dispatcher 77 } 78 79 // NewTxPool init a new TxPool 80 func NewTxPool(store state.Store, dispatcher *event.Dispatcher) *TxPool { 81 tp := &TxPool{ 82 lastUpdated: time.Now().Unix(), 83 store: store, 84 pool: make(map[bc.Hash]*TxDesc), 85 utxo: make(map[bc.Hash]*types.Tx), 86 orphans: make(map[bc.Hash]*orphanTx), 87 orphansByPrev: make(map[bc.Hash]map[bc.Hash]*orphanTx), 88 errCache: lru.New(maxCachedErrTxs), 89 eventDispatcher: dispatcher, 90 } 91 go tp.orphanExpireWorker() 92 return tp 93 } 94 95 // AddErrCache add a failed transaction record to lru cache 96 func (tp *TxPool) AddErrCache(txHash *bc.Hash, err error) { 97 tp.mtx.Lock() 98 defer tp.mtx.Unlock() 99 100 tp.errCache.Add(txHash, err) 101 } 102 103 // ExpireOrphan expire all the orphans that before the input time range 104 func (tp *TxPool) ExpireOrphan(now time.Time) { 105 tp.mtx.Lock() 106 defer tp.mtx.Unlock() 107 108 for hash, orphan := range tp.orphans { 109 if orphan.expiration.Before(now) { 110 tp.removeOrphan(&hash) 111 } 112 } 113 } 114 115 // GetErrCache return the error of the transaction 116 func (tp *TxPool) GetErrCache(txHash *bc.Hash) error { 117 tp.mtx.Lock() 118 defer tp.mtx.Unlock() 119 120 v, ok := tp.errCache.Get(txHash) 121 if !ok { 122 return nil 123 } 124 return v.(error) 125 } 126 127 // RemoveTransaction remove a transaction from the pool 128 func (tp *TxPool) RemoveTransaction(txHash *bc.Hash) { 129 tp.mtx.Lock() 130 defer tp.mtx.Unlock() 131 132 txD, ok := tp.pool[*txHash] 133 if !ok { 134 return 135 } 136 137 for _, output := range txD.Tx.ResultIds { 138 delete(tp.utxo, *output) 139 } 140 delete(tp.pool, *txHash) 141 142 atomic.StoreInt64(&tp.lastUpdated, time.Now().Unix()) 143 tp.eventDispatcher.Post(TxMsgEvent{TxMsg: &TxPoolMsg{TxDesc: txD, MsgType: MsgRemoveTx}}) 144 log.WithFields(log.Fields{"module": logModule, "tx_id": txHash}).Debug("remove tx from mempool") 145 } 146 147 // GetTransaction return the TxDesc by hash 148 func (tp *TxPool) GetTransaction(txHash *bc.Hash) (*TxDesc, error) { 149 tp.mtx.RLock() 150 defer tp.mtx.RUnlock() 151 152 if txD, ok := tp.pool[*txHash]; ok { 153 return txD, nil 154 } 155 return nil, ErrTransactionNotExist 156 } 157 158 // GetTransactions return all the transactions in the pool 159 func (tp *TxPool) GetTransactions() []*TxDesc { 160 tp.mtx.RLock() 161 defer tp.mtx.RUnlock() 162 163 txDs := make([]*TxDesc, len(tp.pool)) 164 i := 0 165 for _, desc := range tp.pool { 166 txDs[i] = desc 167 i++ 168 } 169 return txDs 170 } 171 172 // IsTransactionInPool check wheather a transaction in pool or not 173 func (tp *TxPool) IsTransactionInPool(txHash *bc.Hash) bool { 174 tp.mtx.RLock() 175 defer tp.mtx.RUnlock() 176 177 _, ok := tp.pool[*txHash] 178 return ok 179 } 180 181 // IsTransactionInErrCache check wheather a transaction in errCache or not 182 func (tp *TxPool) IsTransactionInErrCache(txHash *bc.Hash) bool { 183 tp.mtx.RLock() 184 defer tp.mtx.RUnlock() 185 186 _, ok := tp.errCache.Get(txHash) 187 return ok 188 } 189 190 // HaveTransaction IsTransactionInErrCache check is transaction in errCache or pool 191 func (tp *TxPool) HaveTransaction(txHash *bc.Hash) bool { 192 return tp.IsTransactionInPool(txHash) || tp.IsTransactionInErrCache(txHash) 193 } 194 195 func isTransactionNoBtmInput(tx *types.Tx) bool { 196 for _, input := range tx.TxData.Inputs { 197 if input.AssetID() == *consensus.BTMAssetID { 198 return false 199 } 200 } 201 return true 202 } 203 204 func isTransactionZeroOutput(tx *types.Tx) bool { 205 for _, output := range tx.TxData.Outputs { 206 if output.Amount == uint64(0) { 207 return true 208 } 209 } 210 return false 211 } 212 213 func isInvalidBCRPTx(tx *types.Tx) bool { 214 for _, output := range tx.TxData.Outputs { 215 if bcrp.IsBCRPScript(output.ControlProgram) { 216 return true 217 } 218 } 219 return false 220 } 221 222 func (tp *TxPool) IsDust(tx *types.Tx) bool { 223 return isTransactionNoBtmInput(tx) || isTransactionZeroOutput(tx) || isInvalidBCRPTx(tx) 224 } 225 226 func (tp *TxPool) processTransaction(tx *types.Tx, height, fee uint64) (bool, error) { 227 tp.mtx.Lock() 228 defer tp.mtx.Unlock() 229 230 txD := &TxDesc{ 231 Tx: tx, 232 Weight: tx.SerializedSize, 233 Height: height, 234 Fee: fee, 235 } 236 requireParents, err := tp.checkOrphanUtxos(tx) 237 if err != nil { 238 return false, err 239 } 240 241 if len(requireParents) > 0 { 242 return true, tp.addOrphan(txD, requireParents) 243 } 244 245 if err := tp.addTransaction(txD); err != nil { 246 return false, err 247 } 248 249 tp.processOrphans(txD) 250 return false, nil 251 } 252 253 // ProcessTransaction is the main entry for txpool handle new tx, ignore dust tx. 254 func (tp *TxPool) ProcessTransaction(tx *types.Tx, height, fee uint64) (bool, error) { 255 if tp.IsDust(tx) { 256 log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Warn("dust tx") 257 return false, nil 258 } 259 return tp.processTransaction(tx, height, fee) 260 } 261 262 func (tp *TxPool) addOrphan(txD *TxDesc, requireParents []*bc.Hash) error { 263 if len(tp.orphans) >= maxOrphanNum { 264 return ErrPoolIsFull 265 } 266 267 orphan := &orphanTx{txD, time.Now().Add(orphanTTL)} 268 tp.orphans[txD.Tx.ID] = orphan 269 for _, hash := range requireParents { 270 if _, ok := tp.orphansByPrev[*hash]; !ok { 271 tp.orphansByPrev[*hash] = make(map[bc.Hash]*orphanTx) 272 } 273 tp.orphansByPrev[*hash][txD.Tx.ID] = orphan 274 } 275 return nil 276 } 277 278 func (tp *TxPool) addTransaction(txD *TxDesc) error { 279 if len(tp.pool) >= maxNewTxNum { 280 return ErrPoolIsFull 281 } 282 283 tx := txD.Tx 284 txD.Added = time.Now() 285 tp.pool[tx.ID] = txD 286 for _, id := range tx.ResultIds { 287 _, err := tx.OriginalOutput(*id) 288 if err != nil { 289 // error due to it's a retirement, utxo doesn't care this output type so skip it 290 continue 291 } 292 293 tp.utxo[*id] = tx 294 } 295 296 atomic.StoreInt64(&tp.lastUpdated, time.Now().Unix()) 297 tp.eventDispatcher.Post(TxMsgEvent{TxMsg: &TxPoolMsg{TxDesc: txD, MsgType: MsgNewTx}}) 298 log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Debug("Add tx to mempool") 299 return nil 300 } 301 302 func (tp *TxPool) checkOrphanUtxos(tx *types.Tx) ([]*bc.Hash, error) { 303 view := state.NewUtxoViewpoint() 304 if err := tp.store.GetTransactionsUtxo(view, []*bc.Tx{tx.Tx}); err != nil { 305 return nil, err 306 } 307 308 hashes := []*bc.Hash{} 309 for _, hash := range tx.SpentOutputIDs { 310 if !view.CanSpend(&hash) && tp.utxo[hash] == nil { 311 hashes = append(hashes, &hash) 312 } 313 } 314 return hashes, nil 315 } 316 317 func (tp *TxPool) orphanExpireWorker() { 318 ticker := time.NewTicker(orphanExpireScanInterval) 319 defer ticker.Stop() 320 321 for now := range ticker.C { 322 tp.ExpireOrphan(now) 323 } 324 } 325 326 func (tp *TxPool) processOrphans(txD *TxDesc) { 327 processOrphans := []*orphanTx{} 328 addRely := func(tx *types.Tx) { 329 for _, outHash := range tx.ResultIds { 330 orphans, ok := tp.orphansByPrev[*outHash] 331 if !ok { 332 continue 333 } 334 335 for _, orphan := range orphans { 336 processOrphans = append(processOrphans, orphan) 337 } 338 delete(tp.orphansByPrev, *outHash) 339 } 340 } 341 342 addRely(txD.Tx) 343 for ; len(processOrphans) > 0; processOrphans = processOrphans[1:] { 344 processOrphan := processOrphans[0] 345 requireParents, err := tp.checkOrphanUtxos(processOrphan.Tx) 346 if err != nil { 347 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("processOrphans got unexpect error") 348 continue 349 } 350 351 if len(requireParents) == 0 { 352 addRely(processOrphan.Tx) 353 tp.removeOrphan(&processOrphan.Tx.ID) 354 tp.addTransaction(processOrphan.TxDesc) 355 } 356 } 357 } 358 359 func (tp *TxPool) removeOrphan(hash *bc.Hash) { 360 orphan, ok := tp.orphans[*hash] 361 if !ok { 362 return 363 } 364 365 for _, spend := range orphan.Tx.SpentOutputIDs { 366 orphans, ok := tp.orphansByPrev[spend] 367 if !ok { 368 continue 369 } 370 371 if delete(orphans, *hash); len(orphans) == 0 { 372 delete(tp.orphansByPrev, spend) 373 } 374 } 375 delete(tp.orphans, *hash) 376 }