cmd, eth, trie: implement state database upgrade-indexing

This commit is contained in:
Péter Szilágyi 2016-01-14 16:54:50 +02:00
parent 70978907bc
commit 1d1040a4a1
6 changed files with 89 additions and 10 deletions

View file

@ -291,6 +291,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.FastSyncFlag,
utils.CacheFlag,
utils.LightKDFFlag,
utils.StateRetentionFlag,
utils.JSpathFlag,
utils.ListenPortFlag,
utils.MaxPeersFlag,

View file

@ -69,6 +69,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.GenesisFileFlag,
utils.IdentityFlag,
utils.FastSyncFlag,
utils.StateRetentionFlag,
utils.LightKDFFlag,
utils.CacheFlag,
utils.BlockchainVersionFlag,

View file

@ -163,6 +163,11 @@ var (
Name: "lightkdf",
Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength",
}
StateRetentionFlag = cli.IntFlag{
Name: "retain",
Usage: "Number of recent state tries to retain (0 = keep all history)",
Value: 5000,
}
// Miner settings
// TODO: refactor CPU vs GPU mining flags
MiningEnabledFlag = cli.BoolFlag{
@ -613,6 +618,7 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
ethConf := &eth.Config{
Genesis: MakeGenesisBlock(ctx),
FastSync: ctx.GlobalBool(FastSyncFlag.Name),
StateRetention: ctx.GlobalInt(StateRetentionFlag.Name),
BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name),
DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
NetworkId: ctx.GlobalInt(NetworkIdFlag.Name),

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/common/httpclient"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/filters"
@ -45,6 +46,7 @@ import (
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
"github.com/ethereum/go-ethereum/trie"
)
const (
@ -64,6 +66,7 @@ type Config struct {
NetworkId int // Network ID to use for selecting peers to connect to
Genesis string // Genesis JSON to seed the chain database with
FastSync bool // Enables the state download based fast synchronisation algorithm
StateRetention int // Number of recent state tries to retain pruning
BlockChainVersion int
SkipBcVersionCheck bool // e.g. blockchain export
@ -136,6 +139,10 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
const dbCount = 3
ethdb.OpenFileLimit = 128 / (dbCount + 1)
// Sanity check that the user doesn't throw away more data than what would hurt the network
if config.StateRetention != 0 && config.StateRetention < downloader.FsStateRetention {
return nil, fmt.Errorf("configured state retention (%d) too low (< %d) for network security", config.StateRetention, downloader.FsStateRetention)
}
// Open the chain database and perform any upgrades needed
chainDb, err := ctx.OpenDatabase("chaindata", config.DatabaseCache)
if err != nil {
@ -150,7 +157,9 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
if err := addMipmapBloomBins(chainDb); err != nil {
return nil, err
}
if err := indexStateDatabase(chainDb, uint64(config.StateRetention)); err != nil {
return nil, err
}
dappDb, err := ctx.OpenDatabase("dapp", config.DatabaseCache)
if err != nil {
return nil, err
@ -578,3 +587,63 @@ func addMipmapBloomBins(db ethdb.Database) (err error) {
glog.V(logger.Info).Infoln("upgrade completed in", time.Since(tstart))
return nil
}
// indexStateDatabase iterates over the state tries of the topmost blocks in the
// chain and inserts all child-parent references into the trie index to facilitate
// state trie pruning afterwards.
func indexStateDatabase(db ethdb.Database, retain uint64) error {
// Short circuit if the head block is already indexed
head := core.GetBlock(db, core.GetHeadBlockHash(db))
if head == nil {
return nil // Empty database, don't die on it
}
if _, err := db.Get(trie.ParentReferenceIndexKey(head.Hash().Bytes(), head.Root().Bytes())); err == nil {
return nil // Head already indexed, assume up to date database
}
// Iterate over the top blocks that are being retained and index all the state tries
glog.V(logger.Info).Infoln("Indexing database for state pruning (first block takes a few minutes, please be patient)...")
from, till := uint64(0), head.NumberU64()
if from+retain < till {
from = till - retain
}
for num := from; num <= till; num++ {
glog.V(logger.Info).Infof("%.2f%%: Indexing state of block #%d...", 100*float64(num-from)/float64(till-from+1), num)
// Load the block and ensure any database corruption is signaled
header := core.GetHeader(db, core.GetCanonicalHash(db, num))
if header == nil {
return fmt.Errorf("block #%d missing", num)
}
// Short circuit if the block has already been indexed previously
if _, err := db.Get(trie.ParentReferenceIndexKey(header.Hash().Bytes(), header.Root.Bytes())); err == nil {
continue
}
// Otherwise iterate the entire state trie and insert the indices
stateDb, err := state.New(header.Root, db)
if err != nil {
return err
}
it := state.NewNodeIterator(stateDb)
it.PreOrderHook = func(hash, parent common.Hash) bool {
// If we've already indexed this branch, skip
_, err := db.Get(trie.ParentReferenceIndexKey(parent.Bytes(), hash.Bytes()))
if err == nil {
return false
}
return true
}
for it.Next() {
if it.Hash != (common.Hash{}) && it.Parent != (common.Hash{}) {
if err := db.Put(trie.ParentReferenceIndexKey(it.Parent.Bytes(), it.Hash.Bytes()), nil); err != nil {
return err
}
}
}
if err := db.Put(trie.ParentReferenceIndexKey(header.Hash().Bytes(), header.Root.Bytes()), nil); err != nil {
return err
}
}
glog.V(logger.Info).Infof("Database successfully indexed")
return nil
}

View file

@ -67,6 +67,8 @@ var (
fsHeaderForceVerify = 24 // Number of headers to verify before and after the pivot to accept it
fsPivotInterval = 512 // Number of headers out of which to randomize the pivot point
fsMinFullBlocks = 1024 // Number of blocks to retrieve fully even in fast sync
FsStateRetention = fsMinFullBlocks + fsPivotInterval + 1 // Number of state tries needed to reliably fast sync
)
var (

View file

@ -243,18 +243,18 @@ func (it *NodeIterator) step() {
}
parent.child++
it.stack = append(it.stack, &nodeIteratorState{node: node.Val, parent: ancestor, child: -1})
} else if hash, ok := parent.node.(hashNode); ok {
} else if hashNode, ok := parent.node.(hashNode); ok {
// Hash node, resolve the hash child from the database, then the node itself
if parent.child >= 0 {
break
}
parent.child++
node, err := it.trie.resolveHash(hash, nil, nil)
if hash := common.BytesToHash(hashNode); it.PreOrderHook == nil || it.PreOrderHook(hash, ancestor) {
node, err := it.trie.resolveHash(hashNode, nil, nil)
if err != nil {
panic(err)
}
if hash := common.BytesToHash(hash); it.PreOrderHook == nil || it.PreOrderHook(hash, ancestor) {
it.stack = append(it.stack, &nodeIteratorState{hash: hash, node: node, parent: ancestor, child: -1})
}
} else {