aergoio/aergo

View on GitHub
chain/recover.go

Summary

Maintainability
A
1 hr
Test Coverage
C
77%
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
}