Unverified Commit 9d25f342 authored by gary rong's avatar gary rong Committed by GitHub

core: track and improve tx indexing/unindexing (#21331)

* core: add background indexer to waitgroup

* core: make indexer stopable

* core/rawdb: add unit tests

* core/rawdb: fix lint

* core/rawdb: fix tests

* core/rawdb: fix linter
parent 6e713710
...@@ -345,6 +345,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par ...@@ -345,6 +345,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
go bc.update() go bc.update()
if txLookupLimit != nil { if txLookupLimit != nil {
bc.txLookupLimit = *txLookupLimit bc.txLookupLimit = *txLookupLimit
bc.wg.Add(1)
go bc.maintainTxIndex(txIndexBlock) go bc.maintainTxIndex(txIndexBlock)
} }
// If periodic cache journal is required, spin it up. // If periodic cache journal is required, spin it up.
...@@ -2230,6 +2232,8 @@ func (bc *BlockChain) update() { ...@@ -2230,6 +2232,8 @@ func (bc *BlockChain) update() {
// sync, Geth will automatically construct the missing indices and delete // sync, Geth will automatically construct the missing indices and delete
// the extra indices. // the extra indices.
func (bc *BlockChain) maintainTxIndex(ancients uint64) { func (bc *BlockChain) maintainTxIndex(ancients uint64) {
defer bc.wg.Done()
// Before starting the actual maintenance, we need to handle a special case, // Before starting the actual maintenance, we need to handle a special case,
// where user might init Geth with an external ancient database. If so, we // where user might init Geth with an external ancient database. If so, we
// need to reindex all necessary transactions before starting to process any // need to reindex all necessary transactions before starting to process any
...@@ -2239,7 +2243,7 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { ...@@ -2239,7 +2243,7 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
if bc.txLookupLimit != 0 && ancients > bc.txLookupLimit { if bc.txLookupLimit != 0 && ancients > bc.txLookupLimit {
from = ancients - bc.txLookupLimit from = ancients - bc.txLookupLimit
} }
rawdb.IndexTransactions(bc.db, from, ancients) rawdb.IndexTransactions(bc.db, from, ancients, bc.quit)
} }
// indexBlocks reindexes or unindexes transactions depending on user configuration // indexBlocks reindexes or unindexes transactions depending on user configuration
indexBlocks := func(tail *uint64, head uint64, done chan struct{}) { indexBlocks := func(tail *uint64, head uint64, done chan struct{}) {
...@@ -2253,24 +2257,24 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { ...@@ -2253,24 +2257,24 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
rawdb.WriteTxIndexTail(bc.db, 0) rawdb.WriteTxIndexTail(bc.db, 0)
} else { } else {
// Prune all stale tx indices and record the tx index tail // Prune all stale tx indices and record the tx index tail
rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1) rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1, bc.quit)
} }
return return
} }
// If a previous indexing existed, make sure that we fill in any missing entries // If a previous indexing existed, make sure that we fill in any missing entries
if bc.txLookupLimit == 0 || head < bc.txLookupLimit { if bc.txLookupLimit == 0 || head < bc.txLookupLimit {
if *tail > 0 { if *tail > 0 {
rawdb.IndexTransactions(bc.db, 0, *tail) rawdb.IndexTransactions(bc.db, 0, *tail, bc.quit)
} }
return return
} }
// Update the transaction index to the new chain state // Update the transaction index to the new chain state
if head-bc.txLookupLimit+1 < *tail { if head-bc.txLookupLimit+1 < *tail {
// Reindex a part of missing indices and rewind index tail to HEAD-limit // Reindex a part of missing indices and rewind index tail to HEAD-limit
rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail) rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail, bc.quit)
} else { } else {
// Unindex a part of stale indices and forward index tail to HEAD-limit // Unindex a part of stale indices and forward index tail to HEAD-limit
rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1) rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1, bc.quit)
} }
} }
// Any reindexing done, start listening to chain events and moving the index window // Any reindexing done, start listening to chain events and moving the index window
...@@ -2294,6 +2298,10 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { ...@@ -2294,6 +2298,10 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
case <-done: case <-done:
done = nil done = nil
case <-bc.quit: case <-bc.quit:
if done != nil {
log.Info("Waiting background transaction indexer to exit")
<-done
}
return return
} }
} }
......
This diff is collapsed.
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"math/big" "math/big"
"reflect" "reflect"
"sort" "sort"
"sync"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -59,7 +60,7 @@ func TestChainIterator(t *testing.T) { ...@@ -59,7 +60,7 @@ func TestChainIterator(t *testing.T) {
} }
for i, c := range cases { for i, c := range cases {
var numbers []int var numbers []int
hashCh, _ := iterateTransactions(chainDb, c.from, c.to, c.reverse) hashCh := iterateTransactions(chainDb, c.from, c.to, c.reverse, nil)
if hashCh != nil { if hashCh != nil {
for h := range hashCh { for h := range hashCh {
numbers = append(numbers, int(h.number)) numbers = append(numbers, int(h.number))
...@@ -80,3 +81,85 @@ func TestChainIterator(t *testing.T) { ...@@ -80,3 +81,85 @@ func TestChainIterator(t *testing.T) {
} }
} }
} }
func TestIndexTransactions(t *testing.T) {
// Construct test chain db
chainDb := NewMemoryDatabase()
var block *types.Block
var txs []*types.Transaction
for i := uint64(0); i <= 10; i++ {
if i == 0 {
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, nil, nil, nil, newHasher()) // Empty genesis block
} else {
tx := types.NewTransaction(i, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11})
txs = append(txs, tx)
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher())
}
WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
}
// verify checks whether the tx indices in the range [from, to)
// is expected.
verify := func(from, to int, exist bool, tail uint64) {
for i := from; i < to; i++ {
if i == 0 {
continue
}
number := ReadTxLookupEntry(chainDb, txs[i-1].Hash())
if exist && number == nil {
t.Fatalf("Transaction indice missing")
}
if !exist && number != nil {
t.Fatalf("Transaction indice is not deleted")
}
}
number := ReadTxIndexTail(chainDb)
if number == nil || *number != tail {
t.Fatalf("Transaction tail mismatch")
}
}
IndexTransactions(chainDb, 5, 11, nil)
verify(5, 11, true, 5)
verify(0, 5, false, 5)
IndexTransactions(chainDb, 0, 5, nil)
verify(0, 11, true, 0)
UnindexTransactions(chainDb, 0, 5, nil)
verify(5, 11, true, 5)
verify(0, 5, false, 5)
UnindexTransactions(chainDb, 5, 11, nil)
verify(0, 11, false, 11)
// Testing corner cases
signal := make(chan struct{})
var once sync.Once
indexTransactionsForTesting(chainDb, 5, 11, signal, func(n uint64) bool {
if n <= 8 {
once.Do(func() {
close(signal)
})
return false
}
return true
})
verify(9, 11, true, 9)
verify(0, 9, false, 9)
IndexTransactions(chainDb, 0, 9, nil)
signal = make(chan struct{})
var once2 sync.Once
unindexTransactionsForTesting(chainDb, 0, 11, signal, func(n uint64) bool {
if n >= 8 {
once2.Do(func() {
close(signal)
})
return false
}
return true
})
verify(8, 11, true, 8)
verify(0, 8, false, 8)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment