mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
core/rawdb: new implementation of tx indexing/unindex using generic tx iterator and hashing rlp-data
This commit is contained in:
parent
0d1ae63c6b
commit
72d41b7029
5 changed files with 277 additions and 171 deletions
|
|
@ -327,6 +327,25 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue
|
||||||
return nil // Can't find the data anywhere.
|
return nil // Can't find the data anywhere.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the canonical
|
||||||
|
// bloc at number, in RLP encoding.
|
||||||
|
func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue {
|
||||||
|
// if it's an ancient one, we don't need the canonical hash
|
||||||
|
data, _ := db.Ancient(freezerBodiesTable, number)
|
||||||
|
if len(data) == 0 {
|
||||||
|
// Need to get the hash
|
||||||
|
data, _ = db.Get(blockBodyKey(number, ReadCanonicalHash(db, number)))
|
||||||
|
// In the background freezer is moving data from leveldb to flatten files.
|
||||||
|
// So during the first check for ancient db, the data is not yet in there,
|
||||||
|
// but when we reach into leveldb, the data was already moved. That would
|
||||||
|
// result in a not found error.
|
||||||
|
if len(data) == 0 {
|
||||||
|
data, _ = db.Ancient(freezerBodiesTable, number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
// WriteBodyRLP stores an RLP encoded block body into the database.
|
// WriteBodyRLP stores an RLP encoded block body into the database.
|
||||||
func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp rlp.RawValue) {
|
func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp rlp.RawValue) {
|
||||||
if err := db.Put(blockBodyKey(number, hash), rlp); err != nil {
|
if err := db.Put(blockBodyKey(number, hash), rlp); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,16 @@ func WriteTxLookupEntries(db ethdb.KeyValueWriter, block *types.Block) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteTxLookupEntriesByHash is identical to WriteTxLookupEntries, but does not
|
||||||
|
// require a full types.Block as input
|
||||||
|
func WriteTxLookupEntriesByHash(db ethdb.KeyValueWriter, number []byte, hashes []common.Hash) {
|
||||||
|
for _, hash := range hashes {
|
||||||
|
if err := db.Put(txLookupKey(hash), number); err != nil {
|
||||||
|
log.Crit("Failed to store transaction lookup entry", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteTxLookupEntry removes all transaction data associated with a hash.
|
// DeleteTxLookupEntry removes all transaction data associated with a hash.
|
||||||
func DeleteTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash) {
|
func DeleteTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||||
if err := db.Delete(txLookupKey(hash)); err != nil {
|
if err := db.Delete(txLookupKey(hash)); err != nil {
|
||||||
|
|
@ -71,9 +81,9 @@ func DeleteTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTxLookupEntries removes all transaction lookups for a given block.
|
// DeleteTxLookupEntries removes all transaction lookups for a given block.
|
||||||
func DeleteTxLookupEntries(db ethdb.KeyValueWriter, block *types.Block) {
|
func DeleteTxLookupEntriesByHash(db ethdb.KeyValueWriter, hashes []common.Hash) {
|
||||||
for _, tx := range block.Transactions() {
|
for _, hash := range hashes {
|
||||||
if err := db.Delete(txLookupKey(tx.Hash())); err != nil {
|
if err := db.Delete(txLookupKey(hash)); err != nil {
|
||||||
log.Crit("Failed to delete transaction lookup entry", "err", err)
|
log.Crit("Failed to delete transaction lookup entry", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,146 +17,20 @@
|
||||||
package rawdb
|
package rawdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"math"
|
"math"
|
||||||
|
"math/big"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/prque"
|
"github.com/ethereum/go-ethereum/common/prque"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
|
||||||
prepareCallback func(*types.Block) // Callback for custom concurrent pre-computations on block data
|
|
||||||
actionCallback func(ethdb.Batch, *types.Block) // Callback for custom sequential operations on block data.
|
|
||||||
)
|
|
||||||
|
|
||||||
// iterateCanonicalChain iterates the specified range of canonical blocks and applies
|
|
||||||
// the given action callback. Note, both for forward and backward iteration, the range
|
|
||||||
// is [from, to).
|
|
||||||
func iterateCanonicalChain(db ethdb.Database, from uint64, to uint64, prepare prepareCallback, action actionCallback, reverse bool, progMsg, doneMsg string) error {
|
|
||||||
// Short circuit if the action is nil or if the range is invalid
|
|
||||||
if action == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if from >= to {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Spawn multi-routines, iterate over the specified blocks and invoke prepare
|
|
||||||
// callback concurrently.
|
|
||||||
var (
|
|
||||||
number int64
|
|
||||||
results = make(chan *types.Block, 4*runtime.NumCPU())
|
|
||||||
)
|
|
||||||
if !reverse {
|
|
||||||
number = int64(from - 1)
|
|
||||||
} else {
|
|
||||||
number = int64(to)
|
|
||||||
}
|
|
||||||
abort := make(chan struct{})
|
|
||||||
defer close(abort)
|
|
||||||
|
|
||||||
threads := to - from
|
|
||||||
if cpus := runtime.NumCPU(); threads > uint64(cpus) {
|
|
||||||
threads = uint64(cpus)
|
|
||||||
}
|
|
||||||
for i := 0; i < int(threads); i++ {
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
// Fetch the next task number, terminating if everything's done
|
|
||||||
var n int64
|
|
||||||
if !reverse {
|
|
||||||
n = atomic.AddInt64(&number, 1)
|
|
||||||
if n >= int64(to) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
n = atomic.AddInt64(&number, -1)
|
|
||||||
if n < int64(from) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block := ReadBlock(db, ReadCanonicalHash(db, uint64(n)), uint64(n))
|
|
||||||
if prepare != nil && block != nil {
|
|
||||||
prepare(block)
|
|
||||||
}
|
|
||||||
// Feed the block to the aggregator, or abort on interrupt
|
|
||||||
select {
|
|
||||||
case results <- block:
|
|
||||||
case <-abort:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
// Reassemble the blocks into a contiguous stream and apply the action callback
|
|
||||||
var (
|
|
||||||
next, first, last int64
|
|
||||||
queue = prque.New(nil)
|
|
||||||
|
|
||||||
batch = db.NewBatch()
|
|
||||||
start = time.Now()
|
|
||||||
logged = start.Add(-7 * time.Second) // Unindex during import is fast, don't double log
|
|
||||||
)
|
|
||||||
if !reverse {
|
|
||||||
next, first, last = int64(from), int64(from), int64(to)
|
|
||||||
} else {
|
|
||||||
next, first, last = int64(to-1), int64(to-1), int64(from-1)
|
|
||||||
}
|
|
||||||
for i := from; i < to; i++ {
|
|
||||||
// Retrieve the next result and bail if it's nil
|
|
||||||
block := <-results
|
|
||||||
if block == nil {
|
|
||||||
return errors.New("broken database")
|
|
||||||
}
|
|
||||||
// Push the block into the import queue and process contiguous ranges
|
|
||||||
priority := -int64(block.NumberU64())
|
|
||||||
if reverse {
|
|
||||||
priority = int64(block.NumberU64())
|
|
||||||
}
|
|
||||||
queue.Push(block, priority)
|
|
||||||
for !queue.Empty() {
|
|
||||||
// If the next available item is gapped, return
|
|
||||||
if _, priority := queue.Peek(); !reverse && -priority != next || reverse && priority != next {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Next block available, pop it off and index it
|
|
||||||
block = queue.PopItem().(*types.Block)
|
|
||||||
|
|
||||||
if !reverse {
|
|
||||||
next++
|
|
||||||
} else {
|
|
||||||
next--
|
|
||||||
}
|
|
||||||
// Invoke action to inject specified data into key-value database.
|
|
||||||
action(batch, block)
|
|
||||||
|
|
||||||
// If enough data was accumulated in memory or we're at the last block, dump to disk
|
|
||||||
if batch.ValueSize() > ethdb.IdealBatchSize || next == last {
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
batch.Reset()
|
|
||||||
}
|
|
||||||
// If we've spent too much time already, notify the user of what we're doing
|
|
||||||
if time.Since(logged) > 8*time.Second {
|
|
||||||
log.Info(progMsg, "blocks", int64(math.Abs(float64(next-first))), "total", to-from, "tail", block.Number(), "hash", block.Hash(), "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
logged = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tail := to
|
|
||||||
if reverse {
|
|
||||||
tail = from
|
|
||||||
}
|
|
||||||
log.Info(doneMsg, "blocks", to-from, "tail", tail, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitDatabaseFromFreezer reinitializes an empty database from a previous batch
|
// InitDatabaseFromFreezer reinitializes an empty database from a previous batch
|
||||||
// of frozen ancient blocks. The method iterates over all the frozen blocks and
|
// of frozen ancient blocks. The method iterates over all the frozen blocks and
|
||||||
// injects into the database the block hash->number mappings.
|
// injects into the database the block hash->number mappings.
|
||||||
|
|
@ -173,12 +47,15 @@ func InitDatabaseFromFreezer(db ethdb.Database) {
|
||||||
hash common.Hash
|
hash common.Hash
|
||||||
)
|
)
|
||||||
for i := uint64(0); i < frozen; i++ {
|
for i := uint64(0); i < frozen; i++ {
|
||||||
|
// Since the freezer has all data in sequential order on a file,
|
||||||
|
// it would be 'neat' to read more data in one go, and let the
|
||||||
|
// freezerdb return N items (e.g up to 1000 items per go)
|
||||||
|
// That would require an API change in Ancients though
|
||||||
if h, err := db.Ancient(freezerHashTable, i); err != nil {
|
if h, err := db.Ancient(freezerHashTable, i); err != nil {
|
||||||
log.Crit("Failed to init database from freezer", "err", err)
|
log.Crit("Failed to init database from freezer", "err", err)
|
||||||
} else {
|
} else {
|
||||||
hash = common.BytesToHash(h)
|
hash = common.BytesToHash(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteHeaderNumber(batch, hash, i)
|
WriteHeaderNumber(batch, hash, i)
|
||||||
// If enough data was accumulated in memory or we're at the last block, dump to disk
|
// If enough data was accumulated in memory or we're at the last block, dump to disk
|
||||||
if batch.ValueSize() > ethdb.IdealBatchSize {
|
if batch.ValueSize() > ethdb.IdealBatchSize {
|
||||||
|
|
@ -189,7 +66,7 @@ func InitDatabaseFromFreezer(db ethdb.Database) {
|
||||||
}
|
}
|
||||||
// If we've spent too much time already, notify the user of what we're doing
|
// If we've spent too much time already, notify the user of what we're doing
|
||||||
if time.Since(logged) > 8*time.Second {
|
if time.Since(logged) > 8*time.Second {
|
||||||
log.Info("Initializing database from freezer", "blocks", i, "total", frozen, "tail", i, "hash", hash, "elapsed", common.PrettyDuration(time.Since(start)))
|
log.Info("Initializing database from freezer", "total", frozen, "number", i, "hash", hash, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
logged = time.Now()
|
logged = time.Now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +77,109 @@ func InitDatabaseFromFreezer(db ethdb.Database) {
|
||||||
|
|
||||||
WriteHeadHeaderHash(db, hash)
|
WriteHeadHeaderHash(db, hash)
|
||||||
WriteHeadFastBlockHash(db, hash)
|
WriteHeadFastBlockHash(db, hash)
|
||||||
log.Info("Initialized database from freezer", "blocks", frozen, "tail", frozen, "elapsed", common.PrettyDuration(time.Since(start)))
|
log.Info("Initialized database from freezer", "blocks", frozen, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockTxHashes struct {
|
||||||
|
number uint64
|
||||||
|
hashes []common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterateTransactions iterates over all transactions in the (canon) block
|
||||||
|
// number(s) given, and yields the hashes on a channel
|
||||||
|
func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool) (chan *blockTxHashes, chan struct{}) {
|
||||||
|
// One thread sequentially reads data from db
|
||||||
|
type numberRlp struct {
|
||||||
|
number uint64
|
||||||
|
rlp rlp.RawValue
|
||||||
|
}
|
||||||
|
if to == from {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
threads := to - from
|
||||||
|
if cpus := runtime.NumCPU(); threads > uint64(cpus) {
|
||||||
|
threads = uint64(cpus)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel
|
||||||
|
hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh
|
||||||
|
abortCh = make(chan struct{})
|
||||||
|
)
|
||||||
|
// lookup runs in one instance
|
||||||
|
lookup := func() {
|
||||||
|
n, end := from, to
|
||||||
|
if reverse {
|
||||||
|
n, end = to-1, from-1
|
||||||
|
}
|
||||||
|
defer close(rlpCh)
|
||||||
|
for n != end {
|
||||||
|
data := ReadCanonicalBodyRLP(db, n)
|
||||||
|
// Feed the block to the aggregator, or abort on interrupt
|
||||||
|
select {
|
||||||
|
case rlpCh <- &numberRlp{n, data}:
|
||||||
|
case <-abortCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if reverse {
|
||||||
|
n--
|
||||||
|
} else {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// process runs in parallell
|
||||||
|
nThreadsAlive := int32(threads)
|
||||||
|
process := func() {
|
||||||
|
defer func() {
|
||||||
|
// Last processor closes the result channel
|
||||||
|
if atomic.AddInt32(&nThreadsAlive, -1) == 0 {
|
||||||
|
close(hashesCh)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var hasher = sha3.NewLegacyKeccak256()
|
||||||
|
for data := range rlpCh {
|
||||||
|
it, err := rlp.NewListIterator(data.rlp)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("tx iteration error", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
it.Next()
|
||||||
|
txs := it.Value()
|
||||||
|
txIt, err := rlp.NewListIterator(txs)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("tx iteration error", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var hashes []common.Hash
|
||||||
|
for txIt.Next() {
|
||||||
|
if err := txIt.Err(); err != nil {
|
||||||
|
log.Warn("tx iteration error", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var txHash common.Hash
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(txIt.Value())
|
||||||
|
hasher.Sum(txHash[:0])
|
||||||
|
hashes = append(hashes, txHash)
|
||||||
|
}
|
||||||
|
result := &blockTxHashes{
|
||||||
|
hashes: hashes,
|
||||||
|
number: data.number,
|
||||||
|
}
|
||||||
|
// Feed the block to the aggregator, or abort on interrupt
|
||||||
|
select {
|
||||||
|
case hashesCh <- result:
|
||||||
|
case <-abortCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go lookup() // start the sequential db accessor
|
||||||
|
for i := 0; i < int(threads); i++ {
|
||||||
|
go process()
|
||||||
|
}
|
||||||
|
return hashesCh, abortCh
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexTransactions creates txlookup indices of the specified block range.
|
// IndexTransactions creates txlookup indices of the specified block range.
|
||||||
|
|
@ -209,41 +188,118 @@ func InitDatabaseFromFreezer(db ethdb.Database) {
|
||||||
// We can write tx index tail flag periodically even without the whole indexing
|
// We can write tx index tail flag periodically even without the whole indexing
|
||||||
// procedure is finished. So that we can resume indexing procedure next time quickly.
|
// procedure is finished. So that we can resume indexing procedure next time quickly.
|
||||||
func IndexTransactions(db ethdb.Database, from uint64, to uint64) {
|
func IndexTransactions(db ethdb.Database, from uint64, to uint64) {
|
||||||
// hashTxs calculates transaction hash in advance using the multi-routine's
|
// short circuit for invalid range
|
||||||
// concurrent computing power.
|
if from >= to {
|
||||||
hashTxs := func(block *types.Block) {
|
return
|
||||||
for _, tx := range block.Transactions() {
|
}
|
||||||
tx.Hash()
|
var (
|
||||||
|
hashesCh, abortCh = iterateTransactions(db, from, to, true)
|
||||||
|
batch = db.NewBatch()
|
||||||
|
start = time.Now()
|
||||||
|
logged = start.Add(-7 * time.Second)
|
||||||
|
// Since we iterate in reverse, we expect the first number to come
|
||||||
|
// in to be [to-1]. Therefore, setting lastNum to means that the
|
||||||
|
// prqueue gap-evaluation will work correctly
|
||||||
|
lastNum = to
|
||||||
|
queue = prque.New(nil)
|
||||||
|
// for stats reporting
|
||||||
|
blockCount, txCount = 0, 0
|
||||||
|
)
|
||||||
|
defer close(abortCh)
|
||||||
|
|
||||||
|
for chanDelivery := range hashesCh {
|
||||||
|
// Push the delivery into the queue and process contiguous ranges.
|
||||||
|
// Since we iterate in reverse, so lower numbers have lower prio, and
|
||||||
|
// we can use the number directly as prio marker
|
||||||
|
queue.Push(chanDelivery, int64(chanDelivery.number))
|
||||||
|
for !queue.Empty() {
|
||||||
|
// If the next available item is gapped, return
|
||||||
|
if _, priority := queue.Peek(); priority != int64(lastNum-1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Next block available, pop it off and index it
|
||||||
|
delivery := queue.PopItem().(*blockTxHashes)
|
||||||
|
lastNum = delivery.number
|
||||||
|
number := new(big.Int).SetUint64(lastNum).Bytes()
|
||||||
|
WriteTxLookupEntriesByHash(batch, number, delivery.hashes)
|
||||||
|
blockCount++
|
||||||
|
txCount += len(delivery.hashes)
|
||||||
|
// If enough data was accumulated in memory or we're at the last block, dump to disk
|
||||||
|
if batch.ValueSize() > ethdb.IdealBatchSize {
|
||||||
|
// Also write the tail there
|
||||||
|
WriteTxIndexTail(batch, lastNum)
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Crit("Failed writing batch to db", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
batch.Reset()
|
||||||
|
}
|
||||||
|
// If we've spent too much time already, notify the user of what we're doing
|
||||||
|
if time.Since(logged) > 8*time.Second {
|
||||||
|
log.Info("Indexing transactions", "blocks", blockCount, "txs", txCount, "current", lastNum, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
logged = time.Now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// writeIndices injects txlookup indices into the database.
|
if lastNum < to {
|
||||||
writeIndices := func(batch ethdb.Batch, block *types.Block) {
|
WriteTxIndexTail(batch, lastNum)
|
||||||
WriteTxLookupEntries(batch, block)
|
// No need to write the batch if we never entered the loop above...
|
||||||
if block.NumberU64() == to-1 || block.NumberU64()%10000 == 0 {
|
if err := batch.Write(); err != nil {
|
||||||
WriteTxIndexTail(batch, block.NumberU64())
|
log.Crit("Failed writing batch to db", "error", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := iterateCanonicalChain(db, from, to, hashTxs, writeIndices, true, "Indexing transactions", "Indexed transactions"); err != nil {
|
log.Info("Indexed transactions", "blocks", blockCount, "txs", txCount, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
log.Crit("Failed to index transactions", "err", err)
|
|
||||||
}
|
|
||||||
WriteTxIndexTail(db, from)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnindexTransactions removes txlookup indices of the specified block range.
|
// UnindexTransactions removes txlookup indices of the specified block range.
|
||||||
func UnindexTransactions(db ethdb.Database, from uint64, to uint64) {
|
func UnindexTransactions(db ethdb.Database, from uint64, to uint64) {
|
||||||
|
// short circuit for invalid range
|
||||||
|
if from >= to {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Write flag first and then unindex the transaction indices. Some indices
|
// Write flag first and then unindex the transaction indices. Some indices
|
||||||
// will be left in the database if crash happens but it's fine.
|
// will be left in the database if crash happens but it's fine.
|
||||||
WriteTxIndexTail(db, to)
|
WriteTxIndexTail(db, to)
|
||||||
|
|
||||||
// If only one block is unindexed, do it directly
|
// If only one block is unindexed, do it directly
|
||||||
if from+1 == to {
|
//if from+1 == to {
|
||||||
DeleteTxLookupEntries(db, ReadBlock(db, ReadCanonicalHash(db, from), from))
|
// data := ReadCanonicalBodyRLP(db, uint64(from))
|
||||||
log.Info("Unindexed transactions", "blocks", 1, "tail", to)
|
// DeleteTxLookupEntries(db, ReadBlock(db, ReadCanonicalHash(db, from), from))
|
||||||
|
// log.Info("Unindexed transactions", "blocks", 1, "tail", to)
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
// TODO @holiman, add this back (if we want it)
|
||||||
|
var (
|
||||||
|
hashesCh, abortCh = iterateTransactions(db, from, to, false)
|
||||||
|
batch = db.NewBatch()
|
||||||
|
start = time.Now()
|
||||||
|
logged = start.Add(-7 * time.Second)
|
||||||
|
)
|
||||||
|
defer close(abortCh)
|
||||||
|
// Otherwise spin up the concurrent iterator and unindexer
|
||||||
|
count := 0
|
||||||
|
for delivery := range hashesCh {
|
||||||
|
DeleteTxLookupEntriesByHash(batch, delivery.hashes)
|
||||||
|
// If enough data was accumulated in memory or we're at the last block, dump to disk
|
||||||
|
// A batch counts the size of deletion as '1', so we need to flush more
|
||||||
|
// often than that.
|
||||||
|
count++
|
||||||
|
if count%1000 == 0 {
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Crit("Failed writing batch to db", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
batch.Reset()
|
||||||
|
}
|
||||||
|
// If we've spent too much time already, notify the user of what we're doing
|
||||||
|
if time.Since(logged) > 8*time.Second {
|
||||||
|
log.Info("Unindexing transactions", "blocks", int64(math.Abs(float64(delivery.number-from))), "total", to-from, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
logged = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Crit("Failed writing batch to db", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Otherwise spin up the concurrent iterator and unindexer
|
log.Info("Indexed transactions", "blocks", int64(math.Abs(float64(to-from))), "total", to-from, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
deleteIndices := func(batch ethdb.Batch, block *types.Block) { DeleteTxLookupEntries(batch, block) }
|
|
||||||
if err := iterateCanonicalChain(db, from, to, nil, deleteIndices, false, "Unindexing transactions", "Unindexed transactions"); err != nil {
|
|
||||||
log.Crit("Failed to unindex transactions", "err", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ package rawdb
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChainIterator(t *testing.T) {
|
func TestChainIterator(t *testing.T) {
|
||||||
|
|
@ -31,11 +31,13 @@ func TestChainIterator(t *testing.T) {
|
||||||
chainDb := NewMemoryDatabase()
|
chainDb := NewMemoryDatabase()
|
||||||
|
|
||||||
var block *types.Block
|
var block *types.Block
|
||||||
|
var txs []*types.Transaction
|
||||||
for i := uint64(0); i <= 10; i++ {
|
for i := uint64(0); i <= 10; i++ {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, nil, nil, nil) // Empty genesis block
|
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, nil, nil, nil) // Empty genesis block
|
||||||
} else {
|
} else {
|
||||||
tx := types.NewTransaction(i, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11})
|
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)
|
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil)
|
||||||
}
|
}
|
||||||
WriteBlock(chainDb, block)
|
WriteBlock(chainDb, block)
|
||||||
|
|
@ -45,23 +47,36 @@ func TestChainIterator(t *testing.T) {
|
||||||
var cases = []struct {
|
var cases = []struct {
|
||||||
from, to uint64
|
from, to uint64
|
||||||
reverse bool
|
reverse bool
|
||||||
expect []uint64
|
expect []int
|
||||||
}{
|
}{
|
||||||
{0, 11, true, []uint64{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}},
|
{0, 11, true, []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}},
|
||||||
{0, 0, true, nil},
|
{0, 0, true, nil},
|
||||||
{10, 11, true, []uint64{10}},
|
{0, 5, true, []int{4, 3, 2, 1, 0}},
|
||||||
{0, 11, false, []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
|
{10, 11, true, []int{10}},
|
||||||
|
{0, 11, false, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
|
||||||
{0, 0, false, nil},
|
{0, 0, false, nil},
|
||||||
{10, 11, false, []uint64{10}},
|
{10, 11, false, []int{10}},
|
||||||
}
|
}
|
||||||
for i, c := range cases {
|
for i, c := range cases {
|
||||||
var visit []uint64
|
var numbers []int
|
||||||
err := iterateCanonicalChain(chainDb, c.from, c.to, nil, func(db ethdb.Batch, b *types.Block) { visit = append(visit, b.NumberU64()) }, c.reverse, "", "")
|
hashCh, _ := iterateTransactions(chainDb, c.from, c.to, c.reverse)
|
||||||
if err != nil {
|
if hashCh != nil {
|
||||||
t.Fatalf("Case %d failed, err %v", i, err)
|
for h := range hashCh {
|
||||||
|
numbers = append(numbers, int(h.number))
|
||||||
|
if len(h.hashes) > 0 {
|
||||||
|
if got, exp := h.hashes[0], txs[h.number-1].Hash(); got != exp {
|
||||||
|
t.Fatalf("hash wrong, got %x exp %x", got, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(visit, c.expect) {
|
if !c.reverse {
|
||||||
t.Fatalf("Case %d failed, visit element mismatch, want %v, got %v", i, c.expect, visit)
|
sort.Ints(numbers)
|
||||||
|
} else {
|
||||||
|
sort.Sort(sort.Reverse(sort.IntSlice(numbers)))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(numbers, c.expect) {
|
||||||
|
t.Fatalf("Case %d failed, visit element mismatch, want %v, got %v", i, c.expect, numbers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -28,6 +27,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getBlock(transactions int, uncles int, dataSize int) *types.Block {
|
func getBlock(transactions int, uncles int, dataSize int) *types.Block {
|
||||||
|
|
@ -152,9 +152,13 @@ func BenchmarkHashing(b *testing.B) {
|
||||||
var got common.Hash
|
var got common.Hash
|
||||||
var hasher = sha3.NewLegacyKeccak256()
|
var hasher = sha3.NewLegacyKeccak256()
|
||||||
b.Run("iteratorhashing", func(b *testing.B) {
|
b.Run("iteratorhashing", func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var hash common.Hash
|
var hash common.Hash
|
||||||
it, err := rlp.NewListIterator(bodyRlp)
|
it, err := rlp.NewListIterator(bodyRlp)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
it.Next()
|
it.Next()
|
||||||
txs := it.Value()
|
txs := it.Value()
|
||||||
txIt, err := rlp.NewListIterator(txs)
|
txIt, err := rlp.NewListIterator(txs)
|
||||||
|
|
@ -171,6 +175,7 @@ func BenchmarkHashing(b *testing.B) {
|
||||||
})
|
})
|
||||||
var exp common.Hash
|
var exp common.Hash
|
||||||
b.Run("fullbodyhashing", func(b *testing.B) {
|
b.Run("fullbodyhashing", func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var body types.Body
|
var body types.Body
|
||||||
rlp.DecodeBytes(bodyRlp, &body)
|
rlp.DecodeBytes(bodyRlp, &body)
|
||||||
|
|
@ -180,6 +185,7 @@ func BenchmarkHashing(b *testing.B) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
b.Run("fullblockhashing", func(b *testing.B) {
|
b.Run("fullblockhashing", func(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var block types.Block
|
var block types.Block
|
||||||
rlp.DecodeBytes(blockRlp, &block)
|
rlp.DecodeBytes(blockRlp, &block)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue