core: reuse mempool from AddBlock() for bc.isTxStillRelevant()

It's already there most of the time and creating another slice is just a waste
of time. Checking for presence with map is also a little faster.
This commit is contained in:
Roman Khimov 2020-09-10 15:02:03 +03:00
parent 26339c75dc
commit 7310e748e3

View file

@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -200,7 +199,7 @@ func (bc *Blockchain) init() error {
if err != nil { if err != nil {
return err return err
} }
return bc.storeBlock(genesisBlock) return bc.storeBlock(genesisBlock, nil)
} }
if ver != version { if ver != version {
return fmt.Errorf("storage version mismatch betweeen %s and %s", version, ver) return fmt.Errorf("storage version mismatch betweeen %s and %s", version, ver)
@ -413,6 +412,7 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
bc.addLock.Lock() bc.addLock.Lock()
defer bc.addLock.Unlock() defer bc.addLock.Unlock()
var mp *mempool.Pool
expectedHeight := bc.BlockHeight() + 1 expectedHeight := bc.BlockHeight() + 1
if expectedHeight != block.Index { if expectedHeight != block.Index {
return fmt.Errorf("expected %d, got %d: %w", expectedHeight, block.Index, ErrInvalidBlockIndex) return fmt.Errorf("expected %d, got %d: %w", expectedHeight, block.Index, ErrInvalidBlockIndex)
@ -430,28 +430,26 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
if err != nil { if err != nil {
return fmt.Errorf("block %s is invalid: %w", block.Hash().StringLE(), err) return fmt.Errorf("block %s is invalid: %w", block.Hash().StringLE(), err)
} }
if bc.config.VerifyTransactions { mp = mempool.New(len(block.Transactions))
var mp = mempool.New(len(block.Transactions)) for _, tx := range block.Transactions {
for _, tx := range block.Transactions { var err error
var err error // Transactions are verified before adding them
// Transactions are verified before adding them // into the pool, so there is no point in doing
// into the pool, so there is no point in doing // it again even if we're verifying in-block transactions.
// it again even if we're verifying in-block transactions. if bc.memPool.ContainsKey(tx.Hash()) {
if bc.memPool.ContainsKey(tx.Hash()) { err = mp.Add(tx, bc)
err = mp.Add(tx, bc) if err == nil {
if err == nil { continue
continue
}
} else {
err = bc.verifyAndPoolTx(tx, mp)
}
if err != nil {
return fmt.Errorf("transaction %s failed to verify: %w", tx.Hash().StringLE(), err)
} }
} else {
err = bc.verifyAndPoolTx(tx, mp)
}
if err != nil && bc.config.VerifyTransactions {
return fmt.Errorf("transaction %s failed to verify: %w", tx.Hash().StringLE(), err)
} }
} }
} }
return bc.storeBlock(block) return bc.storeBlock(block, mp)
} }
// AddHeaders processes the given headers and add them to the // AddHeaders processes the given headers and add them to the
@ -569,7 +567,7 @@ func (bc *Blockchain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
// storeBlock performs chain update using the block given, it executes all // storeBlock performs chain update using the block given, it executes all
// transactions with all appropriate side-effects and updates Blockchain state. // transactions with all appropriate side-effects and updates Blockchain state.
// This is the only way to change Blockchain state. // This is the only way to change Blockchain state.
func (bc *Blockchain) storeBlock(block *block.Block) error { func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error {
cache := dao.NewCached(bc.dao) cache := dao.NewCached(bc.dao)
writeBuf := io.NewBufBinWriter() writeBuf := io.NewBufBinWriter()
appExecResults := make([]*state.AppExecResult, 0, 1+len(block.Transactions)) appExecResults := make([]*state.AppExecResult, 0, 1+len(block.Transactions))
@ -612,8 +610,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
writeBuf.Reset() writeBuf.Reset()
} }
var txHashes = make([]util.Uint256, len(block.Transactions)) for _, tx := range block.Transactions {
for i, tx := range block.Transactions {
if err := cache.StoreAsTransaction(tx, block.Index, writeBuf); err != nil { if err := cache.StoreAsTransaction(tx, block.Index, writeBuf); err != nil {
return err return err
} }
@ -654,11 +651,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return fmt.Errorf("failed to store tx exec result: %w", err) return fmt.Errorf("failed to store tx exec result: %w", err)
} }
writeBuf.Reset() writeBuf.Reset()
txHashes[i] = tx.Hash()
} }
sort.Slice(txHashes, func(i, j int) bool {
return txHashes[i].CompareTo(txHashes[j]) < 0
})
root := bc.dao.MPT.StateRoot() root := bc.dao.MPT.StateRoot()
var prevHash util.Uint256 var prevHash util.Uint256
@ -700,7 +693,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
} }
bc.topBlock.Store(block) bc.topBlock.Store(block)
atomic.StoreUint32(&bc.blockHeight, block.Index) atomic.StoreUint32(&bc.blockHeight, block.Index)
bc.memPool.RemoveStale(func(tx *transaction.Transaction) bool { return bc.isTxStillRelevant(tx, txHashes) }, bc) bc.memPool.RemoveStale(func(tx *transaction.Transaction) bool { return bc.isTxStillRelevant(tx, txpool) }, bc)
bc.lock.Unlock() bc.lock.Unlock()
updateBlockHeightMetric(block.Index) updateBlockHeightMetric(block.Index)
@ -1305,17 +1298,18 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
// isTxStillRelevant is a callback for mempool transaction filtering after the // isTxStillRelevant is a callback for mempool transaction filtering after the
// new block addition. It returns false for transactions added by the new block // new block addition. It returns false for transactions added by the new block
// (passed via txHashes) and does witness reverification for non-standard // (passed via txpool) and does witness reverification for non-standard
// contracts. It operates under the assumption that full transaction verification // contracts. It operates under the assumption that full transaction verification
// was already done so we don't need to check basic things like size, input/output // was already done so we don't need to check basic things like size, input/output
// correctness, presence in blocks before the new one, etc. // correctness, presence in blocks before the new one, etc.
func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction, txHashes []util.Uint256) bool { func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool) bool {
var recheckWitness bool var recheckWitness bool
index := sort.Search(len(txHashes), func(i int) bool { if txpool == nil {
return txHashes[i].CompareTo(t.Hash()) >= 0 if bc.dao.HasTransaction(t.Hash()) {
}) return false
if index < len(txHashes) && txHashes[index].Equals(t.Hash()) { }
} else if txpool.ContainsKey(t.Hash()) {
return false return false
} }
if err := bc.verifyTxAttributes(t); err != nil { if err := bc.verifyTxAttributes(t); err != nil {