github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/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/event" 14 "github.com/bytom/bytom/protocol/bc" 15 "github.com/bytom/bytom/protocol/bc/types" 16 "github.com/bytom/bytom/protocol/state" 17 ) 18 19 // msg type 20 const ( 21 MsgNewTx = iota 22 MsgRemoveTx 23 logModule = "protocol" 24 ) 25 26 var ( 27 maxCachedErrTxs = 1000 28 maxMsgChSize = 1000 29 maxNewTxNum = 10000 30 maxOrphanNum = 2000 31 32 orphanTTL = 10 * time.Minute 33 orphanExpireScanInterval = 3 * time.Minute 34 35 // ErrTransactionNotExist is the pre-defined error message 36 ErrTransactionNotExist = errors.New("transaction are not existed in the mempool") 37 // ErrPoolIsFull indicates the pool is full 38 ErrPoolIsFull = errors.New("transaction pool reach the max number") 39 // ErrDustTx indicates transaction is dust tx 40 ErrDustTx = errors.New("transaction is dust tx") 41 ) 42 43 type TxMsgEvent struct{ TxMsg *TxPoolMsg } 44 45 // TxDesc store tx and related info for mining strategy 46 type TxDesc struct { 47 Tx *types.Tx `json:"transaction"` 48 Added time.Time `json:"-"` 49 StatusFail bool `json:"status_fail"` 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 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 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 (tp *TxPool) IsDust(tx *types.Tx) bool { 214 return isTransactionNoBtmInput(tx) || isTransactionZeroOutput(tx) 215 } 216 217 func (tp *TxPool) processTransaction(tx *types.Tx, statusFail bool, height, fee uint64) (bool, error) { 218 tp.mtx.Lock() 219 defer tp.mtx.Unlock() 220 221 txD := &TxDesc{ 222 Tx: tx, 223 StatusFail: statusFail, 224 Weight: tx.SerializedSize, 225 Height: height, 226 Fee: fee, 227 } 228 requireParents, err := tp.checkOrphanUtxos(tx) 229 if err != nil { 230 return false, err 231 } 232 233 if len(requireParents) > 0 { 234 return true, tp.addOrphan(txD, requireParents) 235 } 236 237 if err := tp.addTransaction(txD); err != nil { 238 return false, err 239 } 240 241 tp.processOrphans(txD) 242 return false, nil 243 } 244 245 // ProcessTransaction is the main entry for txpool handle new tx, ignore dust tx. 246 func (tp *TxPool) ProcessTransaction(tx *types.Tx, statusFail bool, height, fee uint64) (bool, error) { 247 if tp.IsDust(tx) { 248 log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Warn("dust tx") 249 return false, nil 250 } 251 return tp.processTransaction(tx, statusFail, height, fee) 252 } 253 254 func (tp *TxPool) addOrphan(txD *TxDesc, requireParents []*bc.Hash) error { 255 if len(tp.orphans) >= maxOrphanNum { 256 return ErrPoolIsFull 257 } 258 259 orphan := &orphanTx{txD, time.Now().Add(orphanTTL)} 260 tp.orphans[txD.Tx.ID] = orphan 261 for _, hash := range requireParents { 262 if _, ok := tp.orphansByPrev[*hash]; !ok { 263 tp.orphansByPrev[*hash] = make(map[bc.Hash]*orphanTx) 264 } 265 tp.orphansByPrev[*hash][txD.Tx.ID] = orphan 266 } 267 return nil 268 } 269 270 func (tp *TxPool) addTransaction(txD *TxDesc) error { 271 if len(tp.pool) >= maxNewTxNum { 272 return ErrPoolIsFull 273 } 274 275 tx := txD.Tx 276 txD.Added = time.Now() 277 tp.pool[tx.ID] = txD 278 for _, id := range tx.ResultIds { 279 output, err := tx.Output(*id) 280 if err != nil { 281 // error due to it's a retirement, utxo doesn't care this output type so skip it 282 continue 283 } 284 if !txD.StatusFail || *output.Source.Value.AssetId == *consensus.BTMAssetID { 285 tp.utxo[*id] = tx 286 } 287 } 288 289 atomic.StoreInt64(&tp.lastUpdated, time.Now().Unix()) 290 tp.eventDispatcher.Post(TxMsgEvent{TxMsg: &TxPoolMsg{TxDesc: txD, MsgType: MsgNewTx}}) 291 log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Debug("Add tx to mempool") 292 return nil 293 } 294 295 func (tp *TxPool) checkOrphanUtxos(tx *types.Tx) ([]*bc.Hash, error) { 296 view := state.NewUtxoViewpoint() 297 if err := tp.store.GetTransactionsUtxo(view, []*bc.Tx{tx.Tx}); err != nil { 298 return nil, err 299 } 300 301 hashes := []*bc.Hash{} 302 for _, hash := range tx.SpentOutputIDs { 303 if !view.CanSpend(&hash) && tp.utxo[hash] == nil { 304 hashes = append(hashes, &hash) 305 } 306 } 307 return hashes, nil 308 } 309 310 func (tp *TxPool) orphanExpireWorker() { 311 ticker := time.NewTicker(orphanExpireScanInterval) 312 defer ticker.Stop() 313 314 for now := range ticker.C { 315 tp.ExpireOrphan(now) 316 } 317 } 318 319 func (tp *TxPool) processOrphans(txD *TxDesc) { 320 processOrphans := []*orphanTx{} 321 addRely := func(tx *types.Tx) { 322 for _, outHash := range tx.ResultIds { 323 orphans, ok := tp.orphansByPrev[*outHash] 324 if !ok { 325 continue 326 } 327 328 for _, orphan := range orphans { 329 processOrphans = append(processOrphans, orphan) 330 } 331 delete(tp.orphansByPrev, *outHash) 332 } 333 } 334 335 addRely(txD.Tx) 336 for ; len(processOrphans) > 0; processOrphans = processOrphans[1:] { 337 processOrphan := processOrphans[0] 338 requireParents, err := tp.checkOrphanUtxos(processOrphan.Tx) 339 if err != nil { 340 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("processOrphans got unexpect error") 341 continue 342 } 343 344 if len(requireParents) == 0 { 345 addRely(processOrphan.Tx) 346 tp.removeOrphan(&processOrphan.Tx.ID) 347 tp.addTransaction(processOrphan.TxDesc) 348 } 349 } 350 } 351 352 func (tp *TxPool) removeOrphan(hash *bc.Hash) { 353 orphan, ok := tp.orphans[*hash] 354 if !ok { 355 return 356 } 357 358 for _, spend := range orphan.Tx.SpentOutputIDs { 359 orphans, ok := tp.orphansByPrev[spend] 360 if !ok { 361 continue 362 } 363 364 if delete(orphans, *hash); len(orphans) == 0 { 365 delete(tp.orphansByPrev, spend) 366 } 367 } 368 delete(tp.orphans, *hash) 369 }