/
blockchain.go
760 lines (629 loc) · 20.2 KB
/
blockchain.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
// Package blockchain provides functionalities for
// creating, managing and accessing the blockchain state.
package blockchain
import (
"encoding/json"
"fmt"
"sync"
"time"
"github.com/ellcrys/elld/crypto"
"github.com/syndtr/goleveldb/leveldb"
"github.com/gobuffalo/packr"
"github.com/olebedev/emitter"
"github.com/ellcrys/elld/blockchain/common"
"github.com/ellcrys/elld/config"
"github.com/ellcrys/elld/elldb"
"github.com/ellcrys/elld/types"
"github.com/ellcrys/elld/types/core"
"github.com/ellcrys/elld/util"
"github.com/ellcrys/elld/util/cache"
"github.com/ellcrys/elld/util/logger"
)
const (
// MaxOrphanBlocksCacheSize is the number of blocks we can keep in the orphan block cache
MaxOrphanBlocksCacheSize = 500
// MaxRejectedBlocksCacheSize is the number of blocks we can keep in the rejected block cache
MaxRejectedBlocksCacheSize = 100
)
var (
// GenesisBlockFileName is the name of the file that contains the genesis block
GenesisBlockFileName = "genesis.json"
)
// Blockchain represents the Ellcrys blockchain. It provides
// functionalities for interacting with the underlying database
// and primitives.
type Blockchain struct {
// lock is a general purpose lock for store etc
lock *sync.RWMutex
// coinbase is the key that identifies this blockchain
// instance.
coinbase *crypto.Key
// genesisBlock is the initial, hardcoded block
// shared by all clients. It is the root of all chains.
genesisBlock types.Block
// processLock is used to lock the main block processing method
processLock *sync.Mutex
// cfg is the client configuration
cfg *config.EngineConfig
// log is used for logging output
log logger.Logger
// db is the the database
db elldb.DB
// orphanBlocks stores blocks whose parents are unknown
orphanBlocks *cache.Cache
// rejectedBlocks stores collection of blocks that have been deemed invalid.
// This allows us to quickly learn and discard blocks that are found here.
rejectedBlocks *cache.Cache
// txPool contains all transactions awaiting inclusion in a block
txPool types.TxPool
// eventEmitter allows the manager to listen to specific
// events or broadcast events about its state
eventEmitter *emitter.Emitter
// skipDecideBestChain skips the operation to decide which chain is the best
// when a block is processed.
// Note: Used to prevent re-organisation when creating
// and assembling blocks into desired chains in integration test.
skipDecideBestChain bool
// chl is a lock for chain events
chl *sync.RWMutex
// bestChain is the chain considered to be the true chain.
// It is protected by lock
bestChain *Chain
// chains holds all known chains
chains map[util.String]*Chain
// rol is a lock used for re-org events
rol *sync.RWMutex
// reOrgActive indicates an ongoing reorganization
reOrgActive bool
}
// New creates a Blockchain instance.
func New(txPool types.TxPool, cfg *config.EngineConfig, log logger.Logger) *Blockchain {
bc := new(Blockchain)
bc.txPool = txPool
bc.log = log
bc.cfg = cfg
bc.lock = &sync.RWMutex{}
bc.chl = &sync.RWMutex{}
bc.rol = &sync.RWMutex{}
bc.processLock = &sync.Mutex{}
bc.chains = make(map[util.String]*Chain)
bc.orphanBlocks = cache.NewCache(MaxOrphanBlocksCacheSize)
bc.rejectedBlocks = cache.NewCache(MaxRejectedBlocksCacheSize)
bc.eventEmitter = &emitter.Emitter{}
return bc
}
// SetCoinbase sets the coinbase key that is used to
// identify the current blockchain instance
func (b *Blockchain) SetCoinbase(coinbase *crypto.Key) {
b.coinbase = coinbase
}
// SetGenesisBlock sets the genesis block
func (b *Blockchain) SetGenesisBlock(block types.Block) {
b.genesisBlock = block
}
// LoadBlockFromFile loads a block from a file
func LoadBlockFromFile(name string) (types.Block, error) {
box := packr.NewBox("./data")
data := box.Bytes(name)
if len(data) == 0 {
return nil, fmt.Errorf("block file not found")
}
var gBlock core.Block
if err := json.Unmarshal(data, &gBlock); err != nil {
return nil, err
}
return &gBlock, nil
}
// makeChainID creates a chain id
// Combination: blake2b(current nano time + initial block hash
// + pointer address of initial block)
func makeChainID(initialBlock types.Block) util.String {
now := time.Now().Unix()
blockHash := initialBlock.GetHashAsHex()
id := fmt.Sprintf("%s %d %d", blockHash, now, util.GetPtrAddr(initialBlock))
hash := util.BytesToHash(util.Blake2b256([]byte(id)))
return util.String(hash.HexStr())
}
// Up opens the database, initializes the store and
// creates the genesis block (if required)
func (b *Blockchain) Up() error {
var err error
// We cannot boot up the blockchain manager if a common.DB
// implementation has not been set.
if b.db == nil {
return fmt.Errorf("db has not been initialized")
}
// Get known chains
chains, err := b.getChains()
if err != nil {
return err
}
// If at this point the genesis block has not
// been set, we attempt to load it from the
// genesis.json file.
if b.genesisBlock == nil {
b.genesisBlock, err = LoadBlockFromFile(GenesisBlockFileName)
if err != nil {
return err
}
}
// If there are no known chains described in the metadata and none
// in the cache, then we create a new chain and save it
if len(chains) == 0 {
b.log.Info("No branch found. Creating genesis state")
// Create the genesis chain and the genesis block.
gBlock := b.genesisBlock
if gBlock.GetNumber() != 1 {
return fmt.Errorf("genesis block error: expected block number 1")
}
// Create and save the genesis chain
gChainID := makeChainID(gBlock)
gChain := NewChain(gChainID, b.db, b.cfg, b.log)
if err := b.saveChain(gChain, "", 0); err != nil {
return fmt.Errorf("failed to save genesis chain: %s", err)
}
// Process the genesis block.
if _, err := b.maybeAcceptBlock(gBlock, gChain); err != nil {
return fmt.Errorf("genesis block error: %s", err)
}
b.log.Info("Genesis state created",
"Hash", gBlock.GetHash().SS(),
"Difficulty", gBlock.GetHeader().GetDifficulty())
return nil
}
// Load all known chains
for _, chainInfo := range chains {
if err := b.loadChain(chainInfo); err != nil {
return err
}
}
if numChains := len(chains); numChains > 0 {
b.log.Info("Known branches have been loaded", "NumBranches", numChains)
}
// Set the root chain and make it the initial main chain
b.bestChain = b.getRootChain()
// Using the best chain rule, we mush select the best chain
// and set it as the current bestChain.
err = b.decideBestChain()
if err != nil {
return fmt.Errorf("failed to determine best chain: %s", err)
}
return nil
}
// getRootChain finds the root chain of the tree.
// This is usually the main chain and it has no branch.
func (b *Blockchain) getRootChain() *Chain {
b.chl.RLock()
defer b.chl.RUnlock()
for _, c := range b.chains {
if !c.HasParent() {
return c
}
}
return nil
}
// getBlockByHash finds and returns a block by hash only
// SetEventEmitter sets the event emitter
func (b *Blockchain) SetEventEmitter(ee *emitter.Emitter) {
b.eventEmitter = ee
}
func (b *Blockchain) getBlockValidator(block types.Block) *BlockValidator {
v := NewBlockValidator(block, b.txPool, b, b.cfg, b.log)
v.setContext(types.ContextBlock)
return v
}
// GetTxPool gets the transaction pool
func (b *Blockchain) GetTxPool() types.TxPool {
return b.txPool
}
func (b *Blockchain) reOrgIsActive() bool {
b.rol.RLock()
defer b.rol.RUnlock()
return b.reOrgActive
}
func (b *Blockchain) setReOrgStatus(active bool) {
b.rol.Lock()
defer b.rol.Unlock()
b.reOrgActive = active
}
// loadChain finds and load a chain into the chain cache. It
// can be used to find both standalone chain and child chains.
func (b *Blockchain) loadChain(ci *core.ChainInfo) error {
if ci == nil {
return fmt.Errorf("chain info is required")
}
// Check whether the chain information is a genesis chain.
// A genesis chain info does not include a parent chain id
// and a parent block number since it has no parent.
if ci.GetParentChainID() == "" && ci.GetParentBlockNumber() == 0 {
b.addChain(NewChainFromChainInfo(ci, b.db, b.cfg, b.log))
return nil
}
// Both parent chain ID and block number must be
// set. We cannot allow one to only be set.
if (ci.GetParentChainID() != "" && ci.GetParentBlockNumber() == 0) ||
(ci.GetParentChainID() == "" && ci.GetParentBlockNumber() != 0) {
return fmt.Errorf("chain load failed: chain parent chain ID and block are required")
}
// construct a new chain
chain := NewChainFromChainInfo(ci, b.db, b.cfg, b.log)
// Load the chain's parent chain and block
_, err := chain.loadParent()
if err != nil {
return fmt.Errorf("chain load failed: %s", err)
}
// add chain to cache
b.addChain(chain)
return nil
}
// GetBestChain gets the chain that is currently considered the main chain
func (b *Blockchain) GetBestChain() types.Chainer {
b.chl.RLock()
defer b.chl.RUnlock()
return b.bestChain
}
// SetBestChain sets the current main chain
func (b *Blockchain) SetBestChain(c *Chain) {
b.chl.Lock()
defer b.chl.Unlock()
b.bestChain = c
}
// SetDB sets the database to use
func (b *Blockchain) SetDB(db elldb.DB) {
b.lock.Lock()
defer b.lock.Unlock()
b.db = db
}
// GetDB gets the database
func (b *Blockchain) GetDB() elldb.DB {
b.lock.RLock()
defer b.lock.RUnlock()
return b.db
}
func (b *Blockchain) removeChain(chain *Chain) {
b.chl.Lock()
defer b.chl.Unlock()
if _, ok := b.chains[chain.GetID()]; ok {
delete(b.chains, chain.GetID())
}
return
}
// findChainByBlockHash finds the chain where the given block
// hash exists. It returns the block, the chain, the header of
// highest block in the chain.
func (b *Blockchain) findChainByBlockHash(hash util.Hash,
opts ...types.CallOp) (block types.Block, chain *Chain,
chainTipHeader types.Header, err error) {
b.chl.RLock()
defer b.chl.RUnlock()
chains := b.chains
for _, chain := range chains {
// Find the block by its hash. If we don't
// find the block in this chain, we continue to the
// next chain.
block, err := chain.getBlockByHash(hash, opts...)
if err != nil {
if err != core.ErrBlockNotFound {
return nil, nil, nil, err
}
continue
}
// At the point, we have found chain the block belongs to.
// Next we get the header of the block at the tip of the chain.
chainTipHeader, err := chain.Current(opts...)
if err != nil {
if err != core.ErrBlockNotFound {
return nil, nil, nil, err
}
}
return block, chain, chainTipHeader, nil
}
return nil, nil, nil, nil
}
// addRejectedBlock adds the block to the rejection cache.
func (b *Blockchain) addRejectedBlock(block types.Block) {
b.rejectedBlocks.Add(block.GetHash().HexStr(), struct{}{})
}
// isRejected checks whether a block exists in the
// rejection cache
func (b *Blockchain) isRejected(block types.Block) bool {
return b.rejectedBlocks.Has(block.GetHash().HexStr())
}
// addOrphanBlock adds a block to the collection of
// orphaned blocks.
func (b *Blockchain) addOrphanBlock(block types.Block) {
// Insert the block to the cache with a 1 hour expiration
b.orphanBlocks.AddWithExp(block.GetHash().HexStr(), block,
time.Now().Add(time.Hour))
b.log.Debug("Added block to orphan cache",
"BlockNo", block.GetNumber(),
"CacheSize", b.orphanBlocks.Len())
}
// isOrphanBlock checks whether a block is present in the
// collection of orphaned blocks.
func (b *Blockchain) isOrphanBlock(blockHash util.Hash) bool {
return b.orphanBlocks.Get(blockHash.HexStr()) != nil
}
// NewChainFromChainInfo creates an instance of a Chain given a NewChainFromChainInfo
func (b *Blockchain) NewChainFromChainInfo(ci types.ChainInfo) *Chain {
ch := NewChain(ci.GetID(), b.db, b.cfg, b.log)
ch.info.ParentChainID = ci.GetParentChainID()
ch.info.ParentBlockNumber = ci.GetParentBlockNumber()
return ch
}
// findChainInfo finds information about chain
func (b *Blockchain) findChainInfo(chainID util.String) (*core.ChainInfo, error) {
b.chl.RLock()
chains := b.chains
b.chl.RUnlock()
// check whether the chain exists in the cache
if chain, ok := chains[chainID]; ok {
return chain.info, nil
}
var chainInfo core.ChainInfo
// At this point, we did not find the chain in
// the cache. We search the database instead.
var chainKey = common.MakeKeyChain(chainID.Bytes())
result := b.db.GetByPrefix(chainKey)
if len(result) == 0 {
return nil, core.ErrChainNotFound
}
if err := result[0].Scan(&chainInfo); err != nil {
return nil, err
}
return &chainInfo, nil
}
// IsMainChain checks whether cr is the main chain
func (b *Blockchain) IsMainChain(cr types.ChainReaderFactory) bool {
b.chl.RLock()
isMain := b.bestChain.GetID() == cr.GetID()
b.chl.RUnlock()
return isMain
}
// saveChain persist a given chain to the database.
// It will also cache the chain to support faster querying.
func (b *Blockchain) saveChain(chain *Chain, parentChainID util.String,
parentBlockNumber uint64, opts ...types.CallOp) error {
var txOp = common.GetTxOp(b.db, opts...)
if txOp.Closed() {
return leveldb.ErrClosed
}
chain.info = &core.ChainInfo{
ID: chain.GetID(),
ParentBlockNumber: parentBlockNumber,
ParentChainID: parentChainID,
Timestamp: chain.info.Timestamp,
}
if err := chain.save(txOp); err != nil {
return txOp.Rollback()
}
b.addChain(chain)
return nil
}
// getChains gets all known chains from storage
func (b *Blockchain) getChains() (chainsInfo []*core.ChainInfo, err error) {
chainsKey := common.MakeQueryKeyChains()
result := b.db.GetByPrefix(chainsKey)
for _, r := range result {
var ci core.ChainInfo
if err = r.Scan(&ci); err != nil {
return nil, err
}
chainsInfo = append(chainsInfo, &ci)
}
return
}
// hasChain checks whether a chain exists.
func (b *Blockchain) hasChain(chain *Chain) bool {
b.chl.RLock()
defer b.chl.RUnlock()
_, ok := b.chains[chain.GetID()]
return ok
}
// addChain adds a new chain to the list of chains.
func (b *Blockchain) addChain(chain *Chain) {
b.chl.Lock()
defer b.chl.Unlock()
_, ok := b.chains[chain.GetID()]
if !ok {
b.chains[chain.GetID()] = chain
}
return
}
// newChain creates a new chain which may represent a fork.
//
// 'initialBlock' i block that will be added to this chain.
// It can be the genesis block or branch block.
//
// 'parentBlock' is the block that is the parent of the
// initialBlock. The parentBlock will be a block in another
// chain if this chain is being created in response
// to a new branch.
//
// While parentChain is the chain on which the parent block
// belongs to.
func (b *Blockchain) newChain(tx elldb.Tx, initialBlock types.Block,
parentBlock types.Block, parentChain *Chain) (*Chain, error) {
// The block and its parent must be provided.
// They must also be related through the
// initialBlock referencing the parent block's hash.
if initialBlock == nil {
return nil, fmt.Errorf("initial block cannot be nil")
}
if parentBlock == nil {
return nil, fmt.Errorf("initial block parent cannot be nil")
}
if !initialBlock.GetHeader().GetParentHash().Equal(parentBlock.GetHash()) {
return nil, fmt.Errorf("initial block and parent are not related")
}
if parentChain == nil {
return nil, fmt.Errorf("parent chain cannot be nil")
}
// Create a new chain.
chainID := makeChainID(initialBlock)
chain := NewChain(util.String(chainID), b.db, b.cfg, b.log)
// Set the parent block and parent chain on the new chain.
chain.parentBlock = parentBlock
chain.parentChain = parentChain
// keep a record of this chain in the store
b.saveChain(chain, parentChain.GetID(), parentBlock.GetNumber(),
&common.OpTx{Tx: tx, CanFinish: false})
return chain, nil
}
// GetTransaction finds a transaction in the main chain and returns it
func (b *Blockchain) GetTransaction(hash util.Hash,
opts ...types.CallOp) (types.Transaction, error) {
b.chl.RLock()
defer b.chl.RUnlock()
var mainChain = b.bestChain
if mainChain == nil {
return nil, core.ErrBestChainUnknown
}
tx, err := mainChain.GetTransaction(hash, opts...)
if err != nil {
return nil, err
}
return tx, nil
}
// ChainReader creates a chain reader to read the main chain
func (b *Blockchain) ChainReader() types.ChainReaderFactory {
b.chl.RLock()
defer b.chl.RUnlock()
return NewChainReader(b.bestChain)
}
// GetChainReaderByHash returns a chain reader to a chain
// where a block with the given hash exists
func (b *Blockchain) GetChainReaderByHash(hash util.Hash) types.ChainReaderFactory {
_, chain, _, _ := b.findChainByBlockHash(hash)
if chain == nil {
return nil
}
return chain.ChainReader()
}
// GetChainsReader gets chain reader for all known chains
func (b *Blockchain) GetChainsReader() (readers []types.ChainReaderFactory) {
b.chl.RLock()
defer b.chl.RUnlock()
chains := b.chains
for _, c := range chains {
readers = append(readers, NewChainReader(c))
}
return
}
// GetLocators fetches a list of block hashes used to
// compare and sync the local chain with a remote chain.
// We collect the most recent 10 block hashes and
// then exponentially fetch more hashes until there are
// no more blocks.
// The genesis block must be added as the last hash
// if not already included.
func (b *Blockchain) GetLocators() ([]util.Hash, error) {
b.chl.RLock()
mainChain := b.bestChain
b.chl.RUnlock()
if mainChain == nil {
return nil, core.ErrBestChainUnknown
}
curBlockHeader, err := mainChain.Current()
if err != nil {
return nil, fmt.Errorf("failed to get current block header: %s", err)
}
locators := []util.Hash{}
topHeight := curBlockHeader.GetNumber()
// first, we get the hashes of the last 10 blocks
// using step of 1 backwards. After the last 10 blocks
// are fetched, the step
step := uint64(1)
for i := topHeight; i > 0; i -= step {
block, err := mainChain.GetBlock(i)
if err != nil {
if err != core.ErrBlockNotFound {
return nil, err
}
break
}
locators = append(locators, block.GetHash())
if len(locators) >= 10 {
step *= 2
}
}
// Add the genesis block hash as the final hash
// if it isn't already the last hash.
// We need to add the genesis block in case it got
// missed during the above exponential selection.
if !locators[len(locators)-1].Equal(b.genesisBlock.GetHash()) {
locators = append(locators, b.genesisBlock.GetHash())
}
return locators, nil
}
// OrphanBlocks returns a cache reader for orphan blocks
func (b *Blockchain) OrphanBlocks() types.CacheReader {
return b.orphanBlocks
}
// GetEventEmitter gets the event emitter
func (b *Blockchain) GetEventEmitter() *emitter.Emitter {
return b.eventEmitter
}
// selectTransactions collects transactions from the head
// of the pool up to the specified maxSize.
func (b *Blockchain) selectTransactions(maxSize int64) (selectedTxs []types.Transaction,
err error) {
totalSelectedTxsSize := int64(0)
cache := []types.Transaction{}
nonces := make(map[util.String]uint64)
for b.txPool.Size() > 0 {
// Get a transaction from the top of
// the pool
tx := b.txPool.Container().First()
// Check whether the addition of this
// transaction will push us over the
// size limit
if totalSelectedTxsSize+tx.GetSizeNoFee() > maxSize {
cache = append(cache, tx)
// And also, if the amount of space left for new
// transactions is less that the minimum
// transaction size, then we exit immediately
if maxSize-totalSelectedTxsSize < 230 {
break
}
continue
}
// Check the current nonce value from
// the cache and ensure the transaction's
// nonce matches the expected/next nonce value.
if nonce, ok := nonces[tx.GetFrom()]; ok {
if (nonce + 1) != tx.GetNonce() {
cache = append(cache, tx)
continue
}
nonces[tx.GetFrom()] = tx.GetNonce()
} else {
// At this point, the nonce was not cached,
// so we need to fetch it from the database,
// add to the cache and ensure the
// transaction's nonce matches the expected/next value
nonces[tx.GetFrom()], err = b.GetAccountNonce(tx.GetFrom())
if err != nil {
return nil, err
}
if (nonces[tx.GetFrom()] + 1) != tx.GetNonce() {
cache = append(cache, tx)
continue
}
nonces[tx.GetFrom()] = tx.GetNonce()
}
// Add the transaction to the
// selected tx slice and update the
// total selected transactions size
selectedTxs = append(selectedTxs, tx)
totalSelectedTxsSize += tx.GetSizeNoFee()
// Add the transaction back the cache
// so it can be put back in the pool.
cache = append(cache, tx)
}
// put the cached transactions back to the pool
for _, tx := range cache {
_ = b.txPool.Put(tx)
}
return
}