chain/recover.go
package chain
import (
"bytes"
"errors"
"fmt"
"os"
"runtime"
"runtime/debug"
"github.com/aergoio/aergo/v2/internal/enc/base58"
"github.com/aergoio/aergo/v2/internal/enc/gob"
"github.com/aergoio/aergo/v2/types"
"github.com/aergoio/aergo/v2/types/dbkey"
)
var (
ErrInvalidPrevHash = errors.New("no of previous hash block is invalid")
ErrRecoInvalidBest = errors.New("best block is not equal to old chain")
)
func RecoverExit() {
if r := recover(); r != nil {
logger.Error().Str("callstack", string(debug.Stack())).Msg("panic occurred in chain manager")
os.Exit(10)
}
}
// Recover has 2 situation
// 1. normal recovery
// normal recovery recovers error that has occurs while adding single block
// 2. reorg recovery
// reorg recovery recovers error that has occurs while executing reorg
func (cs *ChainService) Recover() error {
defer RecoverExit()
logger.Debug().Msg("recover start")
// check if reorg marker exists
marker, err := cs.cdb.getReorgMarker()
if err != nil {
return err
}
if marker == nil {
// normal recover
// TODO check state root maker of bestblock
if err := cs.recoverNormal(); err != nil {
return err
}
return nil
}
logger.Info().Str("reorg marker", marker.toString()).Msg("chain recovery started")
runtime.LockOSThread()
defer runtime.UnlockOSThread()
best, err := cs.GetBestBlock()
if err != nil {
return err
}
// check status of chain
if !bytes.Equal(best.BlockHash(), marker.BrBestHash) {
logger.Error().Str("best", best.ID()).Str("markerbest", base58.Encode(marker.BrBestHash)).Msg("best block is not equal to old chain")
return ErrRecoInvalidBest
}
if err = cs.recoverReorg(marker); err != nil {
return err
}
return nil
}
// recover from normal
// set stateRoot for bestBlock
// when panic occurred, memory state of server may not be consistent.
// so restart server when panic in chainservice
func (cs *ChainService) recoverNormal() error {
best, err := cs.GetBestBlock()
if err != nil {
return err
}
logger.Info().Msg("start normal recovery")
stateDB := cs.sdb.GetStateDB()
if !stateDB.HasMarker(best.GetHeader().GetBlocksRootHash()) {
logger.Warn().Str("besthash", best.ID()).Uint64("no", best.GetHeader().GetBlockNo()).Msg("marker of state root does not exist")
}
if !bytes.Equal(cs.sdb.GetStateDB().GetRoot(), best.GetHeader().GetBlocksRootHash()) {
return ErrRecoInvalidSdbRoot
}
logger.Info().Msg("recover normal end")
return nil
}
// recoverReorg redo task that need to be performed after swapping chain meta
// 1. delete receipts of rollbacked blocks
// 2. swap tx mapping
func (cs *ChainService) recoverReorg(marker *ReorgMarker) error {
// build reorganizer from reorg marker
topBlock, err := cs.GetBlock(marker.BrTopHash)
if err != nil {
return err
}
if err = cs.reorg(topBlock, marker); err != nil {
logger.Error().Err(err).Msg("failed to retry reorg")
return err
}
logger.Info().Msg("recover reorg end")
return nil
}
type ReorgMarker struct {
cdb *ChainDB
BrStartHash []byte
BrStartNo types.BlockNo
BrBestHash []byte
BrBestNo types.BlockNo
BrTopHash []byte
BrTopNo types.BlockNo
}
func NewReorgMarker(reorg *reorganizer) *ReorgMarker {
return &ReorgMarker{
cdb: reorg.cs.cdb,
BrStartHash: reorg.brStartBlock.BlockHash(),
BrStartNo: reorg.brStartBlock.GetHeader().GetBlockNo(),
BrBestHash: reorg.bestBlock.BlockHash(),
BrBestNo: reorg.bestBlock.GetHeader().GetBlockNo(),
BrTopHash: reorg.brTopBlock.BlockHash(),
BrTopNo: reorg.brTopBlock.GetHeader().GetBlockNo(),
}
}
// RecoverChainMapping rollback chain (no/hash) mapping to old chain of reorg.
// it is required for LIB loading
func (rm *ReorgMarker) RecoverChainMapping(cdb *ChainDB) error {
best, err := cdb.GetBestBlock()
if err != nil {
return err
}
if bytes.Equal(best.BlockHash(), rm.BrBestHash) {
return nil
}
logger.Info().Str("marker", rm.toString()).Str("curbest", best.ID()).Uint64("curbestno", best.GetHeader().GetBlockNo()).Msg("start to recover chain mapping")
bestBlock, err := cdb.getBlock(rm.BrBestHash)
if err != nil {
return err
}
bulk := cdb.store.NewBulk()
defer bulk.DiscardLast()
var tmpBlkNo types.BlockNo
var tmpBlk *types.Block
// remove unnecessary chain mapping of new chain
for tmpBlkNo = rm.BrTopNo; tmpBlkNo > rm.BrBestNo; tmpBlkNo-- {
logger.Debug().Uint64("no", tmpBlkNo).Msg("delete chain mapping of new chain")
bulk.Delete(types.BlockNoToBytes(tmpBlkNo))
}
tmpBlk = bestBlock
tmpBlkNo = tmpBlk.GetHeader().GetBlockNo()
for tmpBlkNo > rm.BrStartNo {
logger.Debug().Str("hash", tmpBlk.ID()).Uint64("no", tmpBlkNo).Msg("update chain mapping to old chain")
bulk.Set(types.BlockNoToBytes(tmpBlkNo), tmpBlk.BlockHash())
if tmpBlk, err = cdb.getBlock(tmpBlk.GetHeader().GetPrevBlockHash()); err != nil {
return err
}
if tmpBlkNo != tmpBlk.GetHeader().GetBlockNo()+1 {
return ErrInvalidPrevHash
}
tmpBlkNo = tmpBlk.GetHeader().GetBlockNo()
}
logger.Info().Uint64("bestno", rm.BrBestNo).Msg("update best block")
bulk.Set(dbkey.LatestBlock(), types.BlockNoToBytes(rm.BrBestNo))
bulk.Flush()
cdb.setLatest(bestBlock)
logger.Info().Msg("succeed to recover chain mapping")
return nil
}
func (rm *ReorgMarker) setCDB(cdb *ChainDB) {
rm.cdb = cdb
}
func (rm *ReorgMarker) write() error {
if err := rm.cdb.writeReorgMarker(rm); err != nil {
return err
}
return nil
}
func (rm *ReorgMarker) delete() {
rm.cdb.deleteReorgMarker()
}
func (rm *ReorgMarker) toBytes() ([]byte, error) {
return gob.Encode(rm)
}
func (rm *ReorgMarker) toString() string {
buf := ""
if len(rm.BrStartHash) != 0 {
buf = buf + fmt.Sprintf("branch root=(%d, %s).", rm.BrStartNo, base58.Encode(rm.BrStartHash))
}
if len(rm.BrTopHash) != 0 {
buf = buf + fmt.Sprintf("branch top=(%d, %s).", rm.BrTopNo, base58.Encode(rm.BrTopHash))
}
if len(rm.BrBestHash) != 0 {
buf = buf + fmt.Sprintf("org best=(%d, %s).", rm.BrBestNo, base58.Encode(rm.BrBestHash))
}
return buf
}