2020-04-20 10:35:33 +00:00
|
|
|
package core
|
|
|
|
|
|
|
|
import (
|
2020-05-23 09:19:56 +00:00
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
|
|
|
"runtime"
|
2020-05-17 04:46:30 +00:00
|
|
|
"sort"
|
|
|
|
|
2020-04-20 10:35:33 +00:00
|
|
|
"github.com/ledgerwatch/turbo-geth/common"
|
2020-05-30 07:00:35 +00:00
|
|
|
"github.com/ledgerwatch/turbo-geth/common/changeset"
|
2020-04-20 10:35:33 +00:00
|
|
|
"github.com/ledgerwatch/turbo-geth/common/dbutils"
|
|
|
|
"github.com/ledgerwatch/turbo-geth/ethdb"
|
|
|
|
"github.com/ledgerwatch/turbo-geth/log"
|
|
|
|
)
|
|
|
|
|
2020-05-23 09:19:56 +00:00
|
|
|
func NewIndexGenerator(db ethdb.Database) *IndexGenerator {
|
2020-04-20 10:35:33 +00:00
|
|
|
return &IndexGenerator{
|
2020-05-23 09:19:56 +00:00
|
|
|
db: db,
|
|
|
|
cache: nil,
|
2020-04-20 10:35:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type IndexGenerator struct {
|
2020-05-23 09:19:56 +00:00
|
|
|
db ethdb.Database
|
|
|
|
cache map[string][]IndexWithKey
|
2020-04-20 10:35:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type IndexWithKey struct {
|
2020-04-21 17:43:57 +00:00
|
|
|
Val dbutils.HistoryIndexBytes
|
2020-04-20 10:35:33 +00:00
|
|
|
}
|
|
|
|
|
2020-05-23 09:19:56 +00:00
|
|
|
func (ig *IndexGenerator) changeSetWalker(blockNum uint64, indexBucket []byte) func([]byte, []byte) error {
|
2020-04-20 10:35:33 +00:00
|
|
|
return func(k, v []byte) error {
|
2020-05-23 09:19:56 +00:00
|
|
|
cacheKey := k
|
|
|
|
indexes, ok := ig.cache[string(cacheKey)]
|
2020-04-20 10:35:33 +00:00
|
|
|
if !ok || len(indexes) == 0 {
|
|
|
|
|
2020-05-23 09:19:56 +00:00
|
|
|
indexBytes, err := ig.db.GetIndexChunk(indexBucket, k, blockNum)
|
2020-04-20 10:35:33 +00:00
|
|
|
if err != nil && err != ethdb.ErrKeyNotFound {
|
|
|
|
return err
|
|
|
|
}
|
2020-04-21 17:43:57 +00:00
|
|
|
var index dbutils.HistoryIndexBytes
|
2020-04-20 10:35:33 +00:00
|
|
|
|
|
|
|
if len(indexBytes) == 0 {
|
|
|
|
index = dbutils.NewHistoryIndex()
|
2020-04-25 14:50:32 +00:00
|
|
|
} else if dbutils.CheckNewIndexChunk(indexBytes, blockNum) {
|
2020-04-20 10:35:33 +00:00
|
|
|
index = dbutils.NewHistoryIndex()
|
|
|
|
} else {
|
|
|
|
index = dbutils.WrapHistoryIndex(indexBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
indexes = append(indexes, IndexWithKey{
|
2020-04-21 17:43:57 +00:00
|
|
|
Val: index,
|
2020-04-20 10:35:33 +00:00
|
|
|
})
|
2020-05-23 09:19:56 +00:00
|
|
|
ig.cache[string(cacheKey)] = indexes
|
2020-04-20 10:35:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
lastIndex := indexes[len(indexes)-1]
|
2020-04-25 14:50:32 +00:00
|
|
|
if dbutils.CheckNewIndexChunk(lastIndex.Val, blockNum) {
|
2020-04-20 10:35:33 +00:00
|
|
|
lastIndex.Val = dbutils.NewHistoryIndex()
|
|
|
|
indexes = append(indexes, lastIndex)
|
2020-05-23 09:19:56 +00:00
|
|
|
ig.cache[string(cacheKey)] = indexes
|
2020-04-20 10:35:33 +00:00
|
|
|
}
|
2020-04-25 14:50:32 +00:00
|
|
|
lastIndex.Val = lastIndex.Val.Append(blockNum, len(v) == 0)
|
2020-05-23 09:19:56 +00:00
|
|
|
indexes[len(indexes)-1] = lastIndex
|
|
|
|
ig.cache[string(cacheKey)] = indexes
|
|
|
|
|
2020-04-20 10:35:33 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-30 07:00:35 +00:00
|
|
|
func (ig *IndexGenerator) GenerateIndex(from uint64, changeSetBucket []byte, indexBucket []byte, walkerAdapter func([]byte) changeset.Walker, commitHook func(db ethdb.Database, blockNum uint64) error) error {
|
2020-05-23 09:19:56 +00:00
|
|
|
batchSize := 1000000
|
|
|
|
//addrHash - > index or addhash + last block for full chunk contracts
|
2020-04-20 10:35:33 +00:00
|
|
|
ig.cache = make(map[string][]IndexWithKey, batchSize)
|
|
|
|
|
2020-05-23 09:19:56 +00:00
|
|
|
log.Info("Index generation started", "from", from)
|
2020-04-20 10:35:33 +00:00
|
|
|
commit := func() error {
|
2020-05-17 04:46:30 +00:00
|
|
|
tuples := make(ethdb.MultiPutTuples, 0, len(ig.cache)*3)
|
2020-04-20 10:35:33 +00:00
|
|
|
for key, vals := range ig.cache {
|
2020-05-23 09:19:56 +00:00
|
|
|
for i, val := range vals {
|
2020-04-20 10:35:33 +00:00
|
|
|
var (
|
|
|
|
chunkKey []byte
|
|
|
|
err error
|
|
|
|
)
|
2020-05-23 09:19:56 +00:00
|
|
|
if i == len(vals)-1 {
|
|
|
|
chunkKey = dbutils.CurrentChunkKey([]byte(key))
|
|
|
|
} else {
|
|
|
|
chunkKey, err = val.Val.Key([]byte(key))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-04-20 10:35:33 +00:00
|
|
|
}
|
2020-05-23 09:19:56 +00:00
|
|
|
tuples = append(tuples, indexBucket, chunkKey, val.Val)
|
2020-04-20 10:35:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Sort(tuples)
|
2020-05-17 04:46:30 +00:00
|
|
|
_, err := ig.db.MultiPut(tuples...)
|
2020-04-20 10:35:33 +00:00
|
|
|
if err != nil {
|
2020-05-23 09:19:56 +00:00
|
|
|
log.Error("Unable to put index", "err", err)
|
2020-04-20 10:35:33 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
ig.cache = make(map[string][]IndexWithKey, batchSize)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-23 09:19:56 +00:00
|
|
|
var blockNum uint64
|
|
|
|
currentKey := dbutils.EncodeTimestamp(from)
|
2020-04-20 10:35:33 +00:00
|
|
|
for {
|
|
|
|
stop := true
|
2020-05-23 09:19:56 +00:00
|
|
|
err := ig.db.Walk(changeSetBucket, currentKey, 0, func(k, v []byte) (b bool, e error) {
|
|
|
|
blockNum, _ = dbutils.DecodeTimestamp(k)
|
|
|
|
|
|
|
|
err := walkerAdapter(v).Walk(ig.changeSetWalker(blockNum, indexBucket))
|
2020-04-20 10:35:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(ig.cache) > batchSize {
|
2020-05-23 09:19:56 +00:00
|
|
|
currentKey = common.CopyBytes(k)
|
2020-04-20 10:35:33 +00:00
|
|
|
stop = false
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(ig.cache) > 0 {
|
2020-05-23 09:19:56 +00:00
|
|
|
chunkSize := len(ig.cache)
|
2020-04-20 10:35:33 +00:00
|
|
|
err = commit()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-23 09:19:56 +00:00
|
|
|
var m runtime.MemStats
|
|
|
|
runtime.ReadMemStats(&m)
|
|
|
|
log.Info("Committed batch", "blocknum", blockNum, "chunk size", chunkSize,
|
|
|
|
"alloc", int(m.Alloc/1024), "sys", int(m.Sys/1024), "numGC", int(m.NumGC))
|
2020-04-20 10:35:33 +00:00
|
|
|
}
|
2020-05-23 09:19:56 +00:00
|
|
|
if commitHook != nil {
|
|
|
|
err = commitHook(ig.db, blockNum)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-20 10:35:33 +00:00
|
|
|
if stop {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-23 09:19:56 +00:00
|
|
|
log.Info("Generation index finished", "bucket", string(indexBucket))
|
2020-04-20 10:35:33 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-30 07:00:35 +00:00
|
|
|
func (ig *IndexGenerator) Truncate(timestampTo uint64, changeSetBucket []byte, indexBucket []byte, walkerAdapter func([]byte) changeset.Walker) error {
|
2020-05-23 09:19:56 +00:00
|
|
|
currentKey := dbutils.EncodeTimestamp(timestampTo)
|
|
|
|
keys := make(map[string]struct{})
|
|
|
|
err := ig.db.Walk(changeSetBucket, currentKey, 0, func(k, v []byte) (b bool, e error) {
|
|
|
|
currentKey = common.CopyBytes(k)
|
|
|
|
err := walkerAdapter(v).Walk(func(kk []byte, _ []byte) error {
|
|
|
|
keys[string(kk)] = struct{}{}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
accountHistoryEffects := make(map[string][]byte)
|
|
|
|
var startKey = make([]byte, common.HashLength+8)
|
|
|
|
|
|
|
|
for key := range keys {
|
|
|
|
key := common.CopyBytes([]byte(key))
|
|
|
|
copy(startKey, key)
|
|
|
|
|
|
|
|
binary.BigEndian.PutUint64(startKey[common.HashLength:], timestampTo)
|
|
|
|
if err := ig.db.Walk(indexBucket, startKey, 8*common.HashLength, func(k, v []byte) (bool, error) {
|
|
|
|
timestamp := binary.BigEndian.Uint64(k[common.HashLength:]) // the last timestamp in the chunk
|
|
|
|
kStr := string(common.CopyBytes(k))
|
|
|
|
//fmt.Println("Truncate", common.Bytes2Hex(k), timestamp, timestampTo)
|
|
|
|
if timestamp > timestampTo {
|
|
|
|
accountHistoryEffects[kStr] = nil
|
|
|
|
// truncate the chunk
|
|
|
|
index := dbutils.WrapHistoryIndex(v)
|
|
|
|
index = index.TruncateGreater(timestampTo)
|
|
|
|
if len(index) > 8 { // If the chunk is empty after truncation, it gets simply deleted
|
|
|
|
// Truncated chunk becomes "the last chunk" with the timestamp 0xffff....ffff
|
|
|
|
lastK, err := index.Key(key)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
accountHistoryEffects[string(lastK)] = common.CopyBytes(index)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for key, value := range accountHistoryEffects {
|
|
|
|
if value == nil {
|
|
|
|
//fmt.Println("drop", common.Bytes2Hex([]byte(key)), binary.BigEndian.Uint64([]byte(key)[common.HashLength:]))
|
|
|
|
if err := ig.db.Delete(indexBucket, []byte(key)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//fmt.Println("write", common.Bytes2Hex([]byte(key)), binary.BigEndian.Uint64([]byte(key)[common.HashLength:]))
|
|
|
|
if err := ig.db.Put(indexBucket, []byte(key), value); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ig *IndexGenerator) DropIndex(bucket []byte) error {
|
|
|
|
//todo add truncate to all db
|
|
|
|
if bolt, ok := ig.db.(*ethdb.BoltDatabase); ok {
|
|
|
|
log.Warn("Remove bucket", "bucket", string(bucket))
|
|
|
|
err := bolt.DeleteBucket(bucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errors.New("imposible to drop")
|
|
|
|
}
|