core, cmd/geth: improved recover functionality

`geth recover` now accepts both hashes and numbers using "#" and no
longer requires the ethereum instance.
This commit is contained in:
Jeffrey Wilcke 2015-07-01 16:15:02 +02:00
parent bb418a43c1
commit 70d5d791cc
3 changed files with 133 additions and 87 deletions

View File

@ -37,7 +37,10 @@ import (
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -72,10 +75,13 @@ func init() {
{ {
Action: blockRecovery, Action: blockRecovery,
Name: "recover", Name: "recover",
Usage: "attempts to recover a corrupted database by setting a new block head by number", Usage: "attempts to recover a corrupted database by setting a new block by number or hash. See help recover.",
Description: ` Description: `
The recover commands will attempt to read out the last The recover commands will attempt to read out the last
block based on that. block based on that.
recover #number recovers by number
recover <hex> recovers by hash
`, `,
}, },
blocktestCommand, blocktestCommand,
@ -450,17 +456,33 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (pass
} }
func blockRecovery(ctx *cli.Context) { func blockRecovery(ctx *cli.Context) {
num := ctx.Args().First() arg := ctx.Args().First()
if len(ctx.Args()) < 1 { if len(ctx.Args()) < 1 && len(arg) > 0 {
glog.Fatal("recover requires block number") glog.Fatal("recover requires block number or hash")
} }
cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx) cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx)
ethereum, err := eth.New(cfg) blockDb, err := ethdb.NewLDBDatabase(filepath.Join(cfg.DataDir, "blockchain"))
if err != nil { if err != nil {
utils.Fatalf("%v", err) glog.Fatalln("could not open db:", err)
} }
ethereum.ChainManager().Recover(common.String2Big(num).Uint64())
var block *types.Block
if arg[0] == '#' {
block = core.GetBlockByNumber(blockDb, common.String2Big(arg[1:]).Uint64())
} else {
block = core.GetBlockByHash(blockDb, common.HexToHash(arg))
}
if block == nil {
glog.Fatalln("block not found. Recovery failed")
}
err = core.WriteHead(blockDb, block)
if err != nil {
glog.Fatalln("block write err", err)
}
glog.Infof("Recovery succesful. New HEAD %x\n", block.Hash())
} }
func startEth(ctx *cli.Context, eth *eth.Ethereum) { func startEth(ctx *cli.Context, eth *eth.Ethereum) {

View File

@ -1,7 +1,6 @@
package core package core
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
@ -17,7 +16,6 @@ import (
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/pow"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/hashicorp/golang-lru" "github.com/hashicorp/golang-lru"
@ -40,53 +38,6 @@ const (
checkpointLimit = 200 checkpointLimit = 200
) )
// CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block b should have when created at time
// given the parent block's time and difficulty.
func CalcDifficulty(time int64, parentTime int64, parentDiff *big.Int) *big.Int {
diff := new(big.Int)
adjust := new(big.Int).Div(parentDiff, params.DifficultyBoundDivisor)
if big.NewInt(time-parentTime).Cmp(params.DurationLimit) < 0 {
diff.Add(parentDiff, adjust)
} else {
diff.Sub(parentDiff, adjust)
}
if diff.Cmp(params.MinimumDifficulty) < 0 {
return params.MinimumDifficulty
}
return diff
}
// CalcTD computes the total difficulty of block.
func CalcTD(block, parent *types.Block) *big.Int {
if parent == nil {
return block.Difficulty()
}
d := block.Difficulty()
d.Add(d, parent.Td)
return d
}
// CalcGasLimit computes the gas limit of the next block after parent.
// The result may be modified by the caller.
func CalcGasLimit(parent *types.Block) *big.Int {
decay := new(big.Int).Div(parent.GasLimit(), params.GasLimitBoundDivisor)
contrib := new(big.Int).Mul(parent.GasUsed(), big.NewInt(3))
contrib = contrib.Div(contrib, big.NewInt(2))
contrib = contrib.Div(contrib, params.GasLimitBoundDivisor)
gl := new(big.Int).Sub(parent.GasLimit(), decay)
gl = gl.Add(gl, contrib)
gl = gl.Add(gl, big.NewInt(1))
gl.Set(common.BigMax(gl, params.MinGasLimit))
if gl.Cmp(params.GenesisGasLimit) < 0 {
gl.Add(parent.GasLimit(), decay)
gl.Set(common.BigMin(gl, params.GenesisGasLimit))
}
return gl
}
type ChainManager struct { type ChainManager struct {
//eth EthManager //eth EthManager
blockDb common.Database blockDb common.Database
@ -238,16 +189,6 @@ func (self *ChainManager) setTransState(statedb *state.StateDB) {
self.transState = statedb self.transState = statedb
} }
func (bc *ChainManager) Recover(num uint64) {
block := bc.GetBlockByNumber(num)
if block != nil {
bc.insert(block)
glog.Infof("Recovery succesful. New HEAD %x\n", block.Hash())
} else {
glog.Fatalln("Recovery failed")
}
}
func (bc *ChainManager) recover() bool { func (bc *ChainManager) recover() bool {
data, _ := bc.blockDb.Get([]byte("checkpoint")) data, _ := bc.blockDb.Get([]byte("checkpoint"))
if len(data) != 0 { if len(data) != 0 {
@ -378,12 +319,7 @@ func (self *ChainManager) ExportN(w io.Writer, first uint64, last uint64) error
// insert injects a block into the current chain block chain. Note, this function // insert injects a block into the current chain block chain. Note, this function
// assumes that the `mu` mutex is held! // assumes that the `mu` mutex is held!
func (bc *ChainManager) insert(block *types.Block) { func (bc *ChainManager) insert(block *types.Block) {
key := append(blockNumPre, block.Number().Bytes()...) err := WriteHead(bc.blockDb, block)
err := bc.blockDb.Put(key, block.Hash().Bytes())
if err != nil {
glog.Fatal("db write fail:", err)
}
err = bc.blockDb.Put([]byte("LastBlock"), block.Hash().Bytes())
if err != nil { if err != nil {
glog.Fatal("db write fail:", err) glog.Fatal("db write fail:", err)
} }
@ -458,20 +394,15 @@ func (self *ChainManager) GetBlock(hash common.Hash) *types.Block {
return block.(*types.Block) return block.(*types.Block)
} }
data, _ := self.blockDb.Get(append(blockHashPre, hash[:]...)) block := GetBlockByHash(self.blockDb, hash)
if len(data) == 0 { if block == nil {
return nil
}
var block types.StorageBlock
if err := rlp.Decode(bytes.NewReader(data), &block); err != nil {
glog.V(logger.Error).Infof("invalid block RLP for hash %x: %v", hash, err)
return nil return nil
} }
// Add the block to the cache // Add the block to the cache
self.cache.Add(hash, (*types.Block)(&block)) self.cache.Add(hash, (*types.Block)(block))
return (*types.Block)(&block) return (*types.Block)(block)
} }
func (self *ChainManager) GetBlockByNumber(num uint64) *types.Block { func (self *ChainManager) GetBlockByNumber(num uint64) *types.Block {
@ -497,12 +428,7 @@ func (self *ChainManager) GetBlocksFromHash(hash common.Hash, n int) (blocks []*
// non blocking version // non blocking version
func (self *ChainManager) getBlockByNumber(num uint64) *types.Block { func (self *ChainManager) getBlockByNumber(num uint64) *types.Block {
key, _ := self.blockDb.Get(append(blockNumPre, big.NewInt(int64(num)).Bytes()...)) return GetBlockByNumber(self.blockDb, num)
if len(key) == 0 {
return nil
}
return self.GetBlock(common.BytesToHash(key))
} }
func (self *ChainManager) GetUnclesInChain(block *types.Block, length int) (uncles []*types.Header) { func (self *ChainManager) GetUnclesInChain(block *types.Block, length int) (uncles []*types.Header) {

98
core/chain_util.go Normal file
View File

@ -0,0 +1,98 @@
package core
import (
"bytes"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)
// CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block b should have when created at time
// given the parent block's time and difficulty.
func CalcDifficulty(time int64, parentTime int64, parentDiff *big.Int) *big.Int {
diff := new(big.Int)
adjust := new(big.Int).Div(parentDiff, params.DifficultyBoundDivisor)
if big.NewInt(time-parentTime).Cmp(params.DurationLimit) < 0 {
diff.Add(parentDiff, adjust)
} else {
diff.Sub(parentDiff, adjust)
}
if diff.Cmp(params.MinimumDifficulty) < 0 {
return params.MinimumDifficulty
}
return diff
}
// CalcTD computes the total difficulty of block.
func CalcTD(block, parent *types.Block) *big.Int {
if parent == nil {
return block.Difficulty()
}
d := block.Difficulty()
d.Add(d, parent.Td)
return d
}
// CalcGasLimit computes the gas limit of the next block after parent.
// The result may be modified by the caller.
func CalcGasLimit(parent *types.Block) *big.Int {
decay := new(big.Int).Div(parent.GasLimit(), params.GasLimitBoundDivisor)
contrib := new(big.Int).Mul(parent.GasUsed(), big.NewInt(3))
contrib = contrib.Div(contrib, big.NewInt(2))
contrib = contrib.Div(contrib, params.GasLimitBoundDivisor)
gl := new(big.Int).Sub(parent.GasLimit(), decay)
gl = gl.Add(gl, contrib)
gl = gl.Add(gl, big.NewInt(1))
gl.Set(common.BigMax(gl, params.MinGasLimit))
if gl.Cmp(params.GenesisGasLimit) < 0 {
gl.Add(parent.GasLimit(), decay)
gl.Set(common.BigMin(gl, params.GenesisGasLimit))
}
return gl
}
// GetBlockByHash returns the block corresponding to the hash or nil if not found
func GetBlockByHash(db common.Database, hash common.Hash) *types.Block {
data, _ := db.Get(append(blockHashPre, hash[:]...))
if len(data) == 0 {
return nil
}
var block types.StorageBlock
if err := rlp.Decode(bytes.NewReader(data), &block); err != nil {
glog.V(logger.Error).Infof("invalid block RLP for hash %x: %v", hash, err)
return nil
}
return (*types.Block)(&block)
}
// GetBlockByHash returns the canonical block by number or nil if not found
func GetBlockByNumber(db common.Database, number uint64) *types.Block {
key, _ := db.Get(append(blockNumPre, big.NewInt(int64(number)).Bytes()...))
if len(key) == 0 {
return nil
}
return GetBlockByHash(db, common.BytesToHash(key))
}
// WriteHead force writes the current head
func WriteHead(db common.Database, block *types.Block) error {
key := append(blockNumPre, block.Number().Bytes()...)
err := db.Put(key, block.Hash().Bytes())
if err != nil {
return err
}
err = db.Put([]byte("LastBlock"), block.Hash().Bytes())
if err != nil {
return err
}
return nil
}