github.com/aergoio/aergo@v1.3.1/chain/recover.go (about)

     1  package chain
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/gob"
     6  	"errors"
     7  	"fmt"
     8  	"github.com/aergoio/aergo/internal/enc"
     9  	"github.com/aergoio/aergo/types"
    10  	"os"
    11  	"runtime"
    12  	"runtime/debug"
    13  )
    14  
    15  var (
    16  	ErrInvalidPrevHash = errors.New("no of previous hash block is invalid")
    17  	ErrRecoInvalidBest = errors.New("best block is not equal to old chain")
    18  )
    19  
    20  func RecoverExit() {
    21  	if r := recover(); r != nil {
    22  		logger.Error().Str("callstack", string(debug.Stack())).Msg("panic occurred in chain manager")
    23  		os.Exit(10)
    24  	}
    25  }
    26  
    27  // Recover has 2 situation
    28  // 1. normal recovery
    29  //    normal recovery recovers error that has occures while adding single block
    30  // 2. reorg recovery
    31  //    reorg recovery recovers error that has occures while executing reorg
    32  func (cs *ChainService) Recover() error {
    33  	defer RecoverExit()
    34  
    35  	logger.Debug().Msg("recover start")
    36  
    37  	// check if reorg marker exists
    38  	marker, err := cs.cdb.getReorgMarker()
    39  	if err != nil {
    40  		return err
    41  	}
    42  
    43  	if marker == nil {
    44  		// normal recover
    45  		// TODO check state root maker of bestblock
    46  		if err := cs.recoverNormal(); err != nil {
    47  			return err
    48  		}
    49  		return nil
    50  	}
    51  
    52  	logger.Info().Str("reorg marker", marker.toString()).Msg("chain recovery started")
    53  
    54  	runtime.LockOSThread()
    55  	defer runtime.UnlockOSThread()
    56  
    57  	best, err := cs.GetBestBlock()
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	// check status of chain
    63  	if !bytes.Equal(best.BlockHash(), marker.BrBestHash) {
    64  		logger.Error().Str("best", best.ID()).Str("markerbest", enc.ToString(marker.BrBestHash)).Msg("best block is not equal to old chain")
    65  		return ErrRecoInvalidBest
    66  	}
    67  
    68  	if err = cs.recoverReorg(marker); err != nil {
    69  		return err
    70  	}
    71  
    72  	return nil
    73  }
    74  
    75  // recover from normal
    76  // set stateroot for bestblock
    77  // when panic occured, memory state of server may not be consistent.
    78  // so restart server when panic in chainservice
    79  func (cs *ChainService) recoverNormal() error {
    80  	best, err := cs.GetBestBlock()
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	logger.Info().Msg("start normal recovery")
    86  
    87  	stateDB := cs.sdb.GetStateDB()
    88  	if !stateDB.HasMarker(best.GetHeader().GetBlocksRootHash()) {
    89  		logger.Warn().Str("besthash", best.ID()).Uint64("no", best.GetHeader().GetBlockNo()).Msg("marker of state root does not exist")
    90  	}
    91  
    92  	if !bytes.Equal(cs.sdb.GetStateDB().GetRoot(), best.GetHeader().GetBlocksRootHash()) {
    93  		return ErrRecoInvalidSdbRoot
    94  	}
    95  
    96  	logger.Info().Msg("recover normal end")
    97  
    98  	return nil
    99  }
   100  
   101  // recoverReorg redo task that need to be performed after swapping chain meta
   102  // 1. delete receipts of rollbacked blocks
   103  // 2. swap tx mapping
   104  func (cs *ChainService) recoverReorg(marker *ReorgMarker) error {
   105  	// build reorgnizer from reorg marker
   106  	topBlock, err := cs.GetBlock(marker.BrTopHash)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	if err = cs.reorg(topBlock, marker); err != nil {
   112  		logger.Error().Err(err).Msg("failed to retry reorg")
   113  		return err
   114  	}
   115  
   116  	logger.Info().Msg("recover reorg end")
   117  	return nil
   118  }
   119  
   120  type ReorgMarker struct {
   121  	cdb         *ChainDB
   122  	BrStartHash []byte
   123  	BrStartNo   types.BlockNo
   124  	BrBestHash  []byte
   125  	BrBestNo    types.BlockNo
   126  	BrTopHash   []byte
   127  	BrTopNo     types.BlockNo
   128  }
   129  
   130  func NewReorgMarker(reorg *reorganizer) *ReorgMarker {
   131  	return &ReorgMarker{
   132  		cdb:         reorg.cs.cdb,
   133  		BrStartHash: reorg.brStartBlock.BlockHash(),
   134  		BrStartNo:   reorg.brStartBlock.GetHeader().GetBlockNo(),
   135  		BrBestHash:  reorg.bestBlock.BlockHash(),
   136  		BrBestNo:    reorg.bestBlock.GetHeader().GetBlockNo(),
   137  		BrTopHash:   reorg.brTopBlock.BlockHash(),
   138  		BrTopNo:     reorg.brTopBlock.GetHeader().GetBlockNo(),
   139  	}
   140  }
   141  
   142  // RecoverChainMapping rollback chain (no/hash) mapping to old chain of reorg.
   143  // it is required for LIB loading
   144  func (rm *ReorgMarker) RecoverChainMapping(cdb *ChainDB) error {
   145  	best, err := cdb.GetBestBlock()
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	if bytes.Equal(best.BlockHash(), rm.BrBestHash) {
   151  		return nil
   152  	}
   153  
   154  	logger.Info().Str("marker", rm.toString()).Str("curbest", best.ID()).Uint64("curbestno", best.GetHeader().GetBlockNo()).Msg("start to recover chain mapping")
   155  
   156  	bestBlock, err := cdb.getBlock(rm.BrBestHash)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	bulk := cdb.store.NewBulk()
   162  	defer bulk.DiscardLast()
   163  
   164  	var tmpBlkNo types.BlockNo
   165  	var tmpBlk *types.Block
   166  
   167  	// remove unnecessary chain mapping of new chain
   168  	for tmpBlkNo = rm.BrTopNo; tmpBlkNo > rm.BrBestNo; tmpBlkNo-- {
   169  		logger.Debug().Uint64("no", tmpBlkNo).Msg("delete chain mapping of new chain")
   170  		bulk.Delete(types.BlockNoToBytes(tmpBlkNo))
   171  	}
   172  
   173  	tmpBlk = bestBlock
   174  	tmpBlkNo = tmpBlk.GetHeader().GetBlockNo()
   175  
   176  	for tmpBlkNo > rm.BrStartNo {
   177  		logger.Debug().Str("hash", tmpBlk.ID()).Uint64("no", tmpBlkNo).Msg("update chain mapping to old chain")
   178  
   179  		bulk.Set(types.BlockNoToBytes(tmpBlkNo), tmpBlk.BlockHash())
   180  
   181  		if tmpBlk, err = cdb.getBlock(tmpBlk.GetHeader().GetPrevBlockHash()); err != nil {
   182  			return err
   183  		}
   184  
   185  		if tmpBlkNo != tmpBlk.GetHeader().GetBlockNo()+1 {
   186  			return ErrInvalidPrevHash
   187  		}
   188  		tmpBlkNo = tmpBlk.GetHeader().GetBlockNo()
   189  	}
   190  
   191  	logger.Info().Uint64("bestno", rm.BrBestNo).Msg("update best block")
   192  
   193  	bulk.Set(latestKey, types.BlockNoToBytes(rm.BrBestNo))
   194  	bulk.Flush()
   195  
   196  	cdb.setLatest(bestBlock)
   197  
   198  	logger.Info().Msg("succeed to recover chain mapping")
   199  	return nil
   200  }
   201  
   202  func (rm *ReorgMarker) setCDB(cdb *ChainDB) {
   203  	rm.cdb = cdb
   204  }
   205  
   206  func (rm *ReorgMarker) write() error {
   207  	if err := rm.cdb.writeReorgMarker(rm); err != nil {
   208  		return err
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  func (rm *ReorgMarker) delete() {
   215  	rm.cdb.deleteReorgMarker()
   216  }
   217  
   218  func (rm *ReorgMarker) toBytes() ([]byte, error) {
   219  	var val bytes.Buffer
   220  	encoder := gob.NewEncoder(&val)
   221  	if err := encoder.Encode(rm); err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	return val.Bytes(), nil
   226  }
   227  
   228  func (rm *ReorgMarker) toString() string {
   229  	buf := ""
   230  
   231  	if len(rm.BrStartHash) != 0 {
   232  		buf = buf + fmt.Sprintf("branch root=(%d, %s).", rm.BrStartNo, enc.ToString(rm.BrStartHash))
   233  	}
   234  	if len(rm.BrTopHash) != 0 {
   235  		buf = buf + fmt.Sprintf("branch top=(%d, %s).", rm.BrTopNo, enc.ToString(rm.BrTopHash))
   236  	}
   237  	if len(rm.BrBestHash) != 0 {
   238  		buf = buf + fmt.Sprintf("org best=(%d, %s).", rm.BrBestNo, enc.ToString(rm.BrBestHash))
   239  	}
   240  
   241  	return buf
   242  }