github.com/igggame/nebulas-go@v2.1.0+incompatible/core/transaction_pool.go (about) 1 // Copyright (C) 2017 go-nebulas authors 2 // 3 // This file is part of the go-nebulas library. 4 // 5 // the go-nebulas library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // the go-nebulas library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with the go-nebulas library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 19 package core 20 21 import ( 22 "sync" 23 "time" 24 25 "github.com/nebulasio/go-nebulas/core/state" 26 27 "github.com/gogo/protobuf/proto" 28 "github.com/nebulasio/go-nebulas/common/sorted" 29 "github.com/nebulasio/go-nebulas/core/pb" 30 "github.com/nebulasio/go-nebulas/net" 31 "github.com/nebulasio/go-nebulas/util" 32 "github.com/nebulasio/go-nebulas/util/byteutils" 33 "github.com/nebulasio/go-nebulas/util/logging" 34 "github.com/sirupsen/logrus" 35 ) 36 37 var ( 38 metricUpdateInterval = time.Second 39 txEvictInterval = time.Minute 40 txLifetime = time.Minute * 90 41 ) 42 43 // TransactionPool cache txs, is thread safe 44 type TransactionPool struct { 45 receivedMessageCh chan net.Message 46 quitCh chan int 47 48 size int 49 candidates *sorted.Slice 50 buckets map[byteutils.HexHash]*sorted.Slice 51 all map[byteutils.HexHash]*Transaction 52 bucketsLastUpdate map[byteutils.HexHash]time.Time 53 54 ns net.Service 55 mu sync.RWMutex 56 57 minGasPrice *util.Uint128 // the lowest gasPrice. 58 maxGasLimit *util.Uint128 // the maximum gasLimit. 59 60 eventEmitter *EventEmitter 61 bc *BlockChain 62 63 access *Access 64 } 65 66 func nonceCmp(a interface{}, b interface{}) int { 67 txa := a.(*Transaction) 68 txb := b.(*Transaction) 69 if txa.Nonce() < txb.Nonce() { 70 return -1 71 } else if txa.Nonce() > txb.Nonce() { 72 return 1 73 } else { 74 return txb.GasPrice().Cmp(txa.GasPrice()) 75 } 76 } 77 78 func gasCmp(a interface{}, b interface{}) int { 79 txa := a.(*Transaction) 80 txb := b.(*Transaction) 81 return txb.GasPrice().Cmp(txa.GasPrice()) 82 } 83 84 // NewTransactionPool create a new TransactionPool 85 func NewTransactionPool(size int) (*TransactionPool, error) { 86 return &TransactionPool{ 87 receivedMessageCh: make(chan net.Message, size), 88 quitCh: make(chan int, 1), 89 size: size, 90 candidates: sorted.NewSlice(gasCmp), 91 buckets: make(map[byteutils.HexHash]*sorted.Slice), 92 all: make(map[byteutils.HexHash]*Transaction), 93 bucketsLastUpdate: make(map[byteutils.HexHash]time.Time), 94 minGasPrice: TransactionGasPrice, 95 maxGasLimit: TransactionMaxGas, 96 }, nil 97 } 98 99 // SetGasConfig config the lowest gasPrice and the maximum gasLimit. 100 func (pool *TransactionPool) SetGasConfig(gasPrice, gasLimit *util.Uint128) error { 101 if gasPrice == nil || gasPrice.Cmp(util.NewUint128()) <= 0 { 102 pool.minGasPrice = TransactionGasPrice 103 } else if gasPrice.Cmp(TransactionMaxGasPrice) <= 0 { 104 pool.minGasPrice = gasPrice 105 } else { 106 return ErrInvalidGasPrice 107 } 108 if gasLimit == nil || gasLimit.Cmp(util.NewUint128()) <= 0 { 109 pool.maxGasLimit = TransactionMaxGas 110 } else if gasPrice.Cmp(TransactionMaxGas) <= 0 { 111 pool.maxGasLimit = gasLimit 112 } else { 113 return ErrInvalidGasLimit 114 } 115 return nil 116 } 117 118 // RegisterInNetwork register message subscriber in network. 119 func (pool *TransactionPool) RegisterInNetwork(ns net.Service) { 120 ns.Register(net.NewSubscriber(pool, pool.receivedMessageCh, true, MessageTypeNewTx, net.MessageWeightNewTx)) 121 pool.ns = ns 122 } 123 124 func (pool *TransactionPool) setBlockChain(bc *BlockChain) { 125 pool.bc = bc 126 } 127 128 func (pool *TransactionPool) setEventEmitter(emitter *EventEmitter) { 129 pool.eventEmitter = emitter 130 } 131 132 func (pool *TransactionPool) setAccess(access *Access) { 133 pool.access = access 134 } 135 136 // Start start loop. 137 func (pool *TransactionPool) Start() { 138 logging.CLog().WithFields(logrus.Fields{ 139 "size": pool.size, 140 }).Info("Starting TransactionPool...") 141 142 go pool.loop() 143 } 144 145 // Stop stop loop. 146 func (pool *TransactionPool) Stop() { 147 logging.CLog().WithFields(logrus.Fields{ 148 "size": pool.size, 149 }).Info("Stop TransactionPool.") 150 151 pool.quitCh <- 0 152 } 153 154 func (pool *TransactionPool) loop() { 155 logging.CLog().WithFields(logrus.Fields{ 156 "size": pool.size, 157 }).Info("Started TransactionPool.") 158 159 metricsUpdateChan := time.NewTicker(metricUpdateInterval).C 160 evictChan := time.NewTicker(txEvictInterval).C 161 162 for { 163 select { 164 case <-metricsUpdateChan: 165 metricsReceivedTx.Update(int64(len(pool.receivedMessageCh))) 166 metricsCachedTx.Update(int64(len(pool.all))) 167 metricsBucketTx.Update(int64(len(pool.buckets))) 168 metricsCandidates.Update(int64(pool.candidates.Len())) 169 170 case <-evictChan: 171 pool.evictExpiredTransactions() 172 173 case <-pool.quitCh: 174 logging.CLog().WithFields(logrus.Fields{ 175 "size": pool.size, 176 }).Info("Stopped TransactionPool.") 177 return 178 case msg := <-pool.receivedMessageCh: 179 if msg.MessageType() != MessageTypeNewTx { 180 logging.VLog().WithFields(logrus.Fields{ 181 "messageType": msg.MessageType(), 182 "message": msg, 183 "err": "not new tx msg", 184 }).Debug("Received unregistered message.") 185 continue 186 } 187 188 tx := new(Transaction) 189 pbTx := new(corepb.Transaction) 190 if err := proto.Unmarshal(msg.Data(), pbTx); err != nil { 191 logging.VLog().WithFields(logrus.Fields{ 192 "msgType": msg.MessageType(), 193 "msg": msg, 194 "err": err, 195 }).Debug("Failed to unmarshal data.") 196 continue 197 } 198 if err := tx.FromProto(pbTx); err != nil { 199 logging.VLog().WithFields(logrus.Fields{ 200 "msgType": msg.MessageType(), 201 "msg": msg, 202 "err": err, 203 }).Debug("Failed to recover a tx from proto data.") 204 continue 205 } 206 207 if err := pool.PushAndRelay(tx); err != nil { 208 logging.VLog().WithFields(logrus.Fields{ 209 "func": "TxPool.loop", 210 "messageType": msg.MessageType(), 211 "transaction": tx, 212 "err": err, 213 }).Debug("Failed to push a tx into tx pool.") 214 continue 215 } 216 } 217 } 218 } 219 220 // GetMinGasPrice return the minGasPrice 221 func (pool *TransactionPool) GetMinGasPrice() *util.Uint128 { 222 return pool.minGasPrice 223 } 224 225 // GetMaxGasLimit return the maxGasLimit 226 func (pool *TransactionPool) GetMaxGasLimit() *util.Uint128 { 227 return pool.maxGasLimit 228 } 229 230 // GetTransaction return transaction of given hash from transaction pool. 231 func (pool *TransactionPool) GetTransaction(hash byteutils.Hash) *Transaction { 232 pool.mu.Lock() 233 defer pool.mu.Unlock() 234 235 return pool.all[hash.Hex()] 236 } 237 238 // PushAndRelay push tx into pool and relay it 239 func (pool *TransactionPool) PushAndRelay(tx *Transaction) error { 240 if err := pool.Push(tx); err != nil { 241 logging.VLog().WithFields(logrus.Fields{ 242 "tx": tx.StringWithoutData(), 243 "err": err, 244 }).Debug("Failed to push tx") 245 return err 246 } 247 248 // TODO: if tx relay , don't relay again @fengzi @roy 249 pool.ns.Relay(MessageTypeNewTx, tx, net.MessagePriorityNormal) 250 return nil 251 } 252 253 // PushAndBroadcast push tx into pool and broadcast it 254 func (pool *TransactionPool) PushAndBroadcast(tx *Transaction) error { 255 if err := pool.Push(tx); err != nil { 256 logging.VLog().WithFields(logrus.Fields{ 257 "tx": tx.StringWithoutData(), 258 "err": err, 259 }).Debug("Failed to push tx") 260 return err 261 } 262 263 pool.ns.Broadcast(MessageTypeNewTx, tx, net.MessagePriorityNormal) 264 return nil 265 } 266 267 // Push tx into pool 268 func (pool *TransactionPool) Push(tx *Transaction) error { 269 pool.mu.Lock() 270 defer pool.mu.Unlock() 271 // add tx log in super node 272 if pool.bc.superNode == true { 273 logging.VLog().WithFields(logrus.Fields{ 274 "tx": tx, 275 }).Debug("Push tx to transaction pool") 276 } 277 278 // only super node need the access control 279 //if pool.bc.superNode == true { 280 if err := pool.access.CheckTransaction(tx); err != nil { 281 logging.VLog().WithFields(logrus.Fields{ 282 "tx.hash": tx.hash, 283 "error": err, 284 }).Debug("Failed to check transaction in access.") 285 return err 286 } 287 288 // check dip reward 289 if err := pool.bc.dip.CheckReward(tx); err != nil { 290 logging.VLog().WithFields(logrus.Fields{ 291 "tx.hash": tx.hash, 292 "error": err, 293 }).Debug("Failed to check transaction for dip reward.") 294 return err 295 } 296 297 // verify non-dup tx 298 if _, ok := pool.all[tx.hash.Hex()]; ok { 299 metricsDuplicateTx.Inc(1) 300 return ErrDuplicatedTransaction 301 } // ToRefine: refine the lock scope 302 303 // if tx's gasPrice below the pool config lowest gasPrice, return ErrBelowGasPrice 304 if tx.gasPrice.Cmp(pool.minGasPrice) < 0 { 305 metricsTxPoolBelowGasPrice.Inc(1) 306 return ErrBelowGasPrice 307 } 308 309 if tx.gasLimit.Cmp(util.NewUint128()) <= 0 { 310 metricsTxPoolGasLimitLessOrEqualToZero.Inc(1) 311 return ErrGasLimitLessOrEqualToZero 312 } 313 314 if tx.gasLimit.Cmp(pool.maxGasLimit) > 0 { 315 metricsTxPoolOutOfGasLimit.Inc(1) 316 return ErrOutOfGasLimit 317 } 318 319 // verify hash & sign of tx 320 if err := tx.VerifyIntegrity(pool.bc.chainID); err != nil { 321 metricsInvalidTx.Inc(1) 322 return err 323 } 324 325 // cache the verified tx 326 pool.pushTx(tx) 327 // drop max tx in longest bucket if full 328 if len(pool.all) > pool.size { 329 poollen := len(pool.all) 330 pool.dropTx() 331 332 logging.VLog().WithFields(logrus.Fields{ 333 "tx": tx.StringWithoutData(), 334 "size": pool.size, 335 "bpoolsize": poollen, 336 "apoolsize": len(pool.all), 337 "bucketsize": len(pool.buckets), 338 }).Debug("drop tx") 339 } 340 341 // trigger pending transaction 342 event := &state.Event{ 343 Topic: TopicPendingTransaction, 344 Data: tx.JSONString(), 345 } 346 pool.eventEmitter.Trigger(event) 347 348 return nil 349 } 350 351 func (pool *TransactionPool) pushTx(tx *Transaction) { 352 slot := tx.from.address.Hex() 353 bucket, ok := pool.buckets[slot] 354 if !ok { 355 bucket = sorted.NewSlice(nonceCmp) 356 pool.buckets[slot] = bucket 357 } 358 oldCandidate := bucket.Left() 359 bucket.Push(tx) 360 pool.all[tx.hash.Hex()] = tx 361 newCandidate := bucket.Left() 362 // replace candidate 363 if oldCandidate == nil { 364 pool.candidates.Push(newCandidate) 365 } else if oldCandidate != newCandidate { 366 pool.candidates.Del(oldCandidate) 367 pool.candidates.Push(newCandidate) 368 } 369 370 // Initialize bucket time. Do not update in pushTx() after init. 371 // Because tx could be taken out and then push back if verification fail 372 if _, ok := pool.bucketsLastUpdate[slot]; !ok { 373 pool.bucketsLastUpdate[slot] = time.Now() 374 } 375 } 376 377 func (pool *TransactionPool) popTx(tx *Transaction) { 378 bucket := pool.buckets[tx.from.address.Hex()] 379 delete(pool.all, tx.hash.Hex()) 380 bucket.PopLeft() 381 if bucket.Len() != 0 { 382 candidate := bucket.Left() 383 pool.candidates.Push(candidate) 384 } else { 385 delete(pool.buckets, tx.from.address.Hex()) 386 delete(pool.bucketsLastUpdate, tx.from.address.Hex()) 387 } 388 } 389 390 func (pool *TransactionPool) dropTx() { 391 var longestSlice *sorted.Slice 392 longestLen := 0 393 for _, v := range pool.buckets { 394 if v.Len() > longestLen { 395 longestLen = v.Len() 396 longestSlice = v 397 } 398 } 399 400 logging.VLog().WithFields(logrus.Fields{ 401 "longestsize": longestLen, 402 }).Debug("Drop tx from longest bucket.") 403 404 if longestLen > 0 { 405 drop := longestSlice.PopRight().(*Transaction) 406 if drop != nil { 407 delete(pool.all, drop.Hash().Hex()) 408 if longestLen == 1 { 409 pool.candidates.Del(drop) 410 delete(pool.buckets, drop.from.address.Hex()) 411 delete(pool.bucketsLastUpdate, drop.from.address.Hex()) 412 } 413 } 414 } 415 } 416 417 // PopWithBlacklist return a tx with highest gasprice and not in the blocklist 418 func (pool *TransactionPool) PopWithBlacklist(fromBlacklist *sync.Map, toBlacklist *sync.Map) *Transaction { 419 pool.mu.Lock() 420 defer pool.mu.Unlock() 421 422 if fromBlacklist == nil { 423 fromBlacklist = new(sync.Map) 424 } 425 if toBlacklist == nil { 426 toBlacklist = new(sync.Map) 427 } 428 429 size := pool.candidates.Len() 430 for i := 0; i < size; i++ { 431 tx := pool.candidates.Index(i).(*Transaction) 432 if _, ok := fromBlacklist.Load(tx.from.address.Hex()); !ok { 433 if _, ok := toBlacklist.Load(tx.to.address.Hex()); !ok { 434 pool.candidates.Del(tx) 435 pool.popTx(tx) 436 return tx 437 } 438 } 439 } 440 return nil 441 } 442 443 // Pop a transaction from pool 444 func (pool *TransactionPool) Pop() *Transaction { 445 pool.mu.Lock() 446 defer pool.mu.Unlock() 447 448 candidates := pool.candidates 449 val := candidates.PopLeft() 450 if val == nil { 451 return nil 452 } 453 tx := val.(*Transaction) 454 pool.popTx(tx) 455 return tx 456 } 457 458 // Del a transaction from pool 459 func (pool *TransactionPool) Del(tx *Transaction) { 460 pool.mu.Lock() 461 defer pool.mu.Unlock() 462 463 bucket := pool.buckets[tx.from.address.Hex()] 464 if bucket != nil && bucket.Len() > 0 { 465 oldCandidate := bucket.Left() 466 left := oldCandidate.(*Transaction) 467 for left.Nonce() <= tx.Nonce() { 468 bucket.PopLeft() 469 delete(pool.all, left.Hash().Hex()) 470 471 // trigger pending transaction 472 event := &state.Event{ 473 Topic: TopicDropTransaction, 474 Data: left.String(), 475 } 476 pool.eventEmitter.Trigger(event) 477 478 logging.VLog().WithFields(logrus.Fields{ 479 "tx": left.Hash().Hex(), 480 "size": pool.size, 481 "poolsize": len(pool.all), 482 "bucketsize": len(pool.buckets), 483 }).Debug("Delete transaction") 484 485 if bucket.Len() > 0 { 486 left = bucket.Left().(*Transaction) 487 } else { 488 delete(pool.buckets, left.from.address.Hex()) 489 delete(pool.bucketsLastUpdate, left.from.address.Hex()) 490 break 491 } 492 } 493 494 newCandidate := bucket.Left() 495 // replace candidate 496 if oldCandidate != newCandidate { 497 pool.candidates.Del(oldCandidate) 498 delete(pool.bucketsLastUpdate, tx.from.address.Hex()) 499 if newCandidate != nil { 500 pool.candidates.Push(newCandidate) 501 502 //update bucket update time when txs are put on chain 503 pool.bucketsLastUpdate[tx.from.address.Hex()] = time.Now() 504 } 505 } 506 507 } else { 508 //remove key of bucketsLastUpdate when bucket is empty 509 delete(pool.bucketsLastUpdate, tx.from.address.Hex()) 510 } 511 } 512 513 // Empty return if the pool is empty 514 func (pool *TransactionPool) Empty() bool { 515 pool.mu.Lock() 516 defer pool.mu.Unlock() 517 return len(pool.all) == 0 518 } 519 520 func (pool *TransactionPool) evictExpiredTransactions() { 521 pool.mu.Lock() 522 defer pool.mu.Unlock() 523 524 for slot := range pool.buckets { 525 if timeLastDate, ok := pool.bucketsLastUpdate[slot]; ok { 526 if time.Since(timeLastDate) > txLifetime { 527 bucket := pool.buckets[slot] 528 529 val := bucket.PopLeft() 530 if tx := val.(*Transaction); tx != nil && tx.hash != nil { 531 pool.candidates.Del(tx) // only remove the first from candidates 532 } 533 for val != nil { 534 if tx := val.(*Transaction); tx != nil && tx.hash != nil { 535 delete(pool.all, tx.hash.Hex()) 536 logging.VLog().WithFields(logrus.Fields{ 537 "tx.hash": tx.hash.Hex(), 538 "size": pool.size, 539 "poolsize": len(pool.all), 540 "bucketsize": len(pool.buckets), 541 "tx": tx.StringWithoutData(), 542 }).Debug("Remove expired transactions.") 543 // trigger pending transaction 544 event := &state.Event{ 545 Topic: TopicDropTransaction, 546 Data: tx.JSONString(), 547 } 548 pool.eventEmitter.Trigger(event) 549 } 550 551 val = bucket.PopLeft() 552 } 553 delete(pool.buckets, slot) 554 delete(pool.bucketsLastUpdate, slot) 555 } 556 } 557 } 558 } 559 560 // get pending tx count 561 func (pool *TransactionPool) GetPending(addr *Address) uint64 { 562 pool.mu.Lock() 563 defer pool.mu.Unlock() 564 565 slot := addr.address.Hex() 566 bucket, ok := pool.buckets[slot] 567 if !ok { 568 return 0 569 } 570 return uint64(bucket.Len()) 571 }