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

     1  package chain
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/aergoio/aergo/consensus"
    10  	"github.com/aergoio/aergo/internal/enc"
    11  	"github.com/aergoio/aergo/message"
    12  	"github.com/aergoio/aergo/state"
    13  	"github.com/aergoio/aergo/types"
    14  )
    15  
    16  const (
    17  	initBlkCount = 20
    18  )
    19  
    20  var (
    21  	reorgKeyStr = "_reorg_marker_"
    22  	reorgKey    = []byte(reorgKeyStr)
    23  )
    24  
    25  var (
    26  	ErrInvalidReorgMarker = errors.New("reorg marker is invalid")
    27  	ErrMarkerNil          = errors.New("reorg marker is nil")
    28  )
    29  
    30  type reorganizer struct {
    31  	//input info
    32  	cs         *ChainService
    33  	bestBlock  *types.Block
    34  	brTopBlock *types.Block //branch top block
    35  
    36  	//collected info from chain
    37  	brStartBlock *types.Block
    38  	newBlocks    []*types.Block //roll forward target blocks
    39  	oldBlocks    []*types.Block //roll back target blocks
    40  
    41  	marker *ReorgMarker
    42  
    43  	recover bool
    44  
    45  	gatherFn       func() error
    46  	gatherPostFn   func()
    47  	executeBlockFn func(bstate *state.BlockState, block *types.Block) error
    48  }
    49  
    50  type ErrReorgBlock struct {
    51  	msg string
    52  
    53  	blockNo   uint64
    54  	blockHash []byte
    55  }
    56  
    57  func (ec *ErrReorgBlock) Error() string {
    58  	if ec.blockHash != nil {
    59  		return fmt.Sprintf("%s, block:%d,%s", ec.msg, ec.blockNo, enc.ToString(ec.blockHash))
    60  	} else if ec.blockNo != 0 {
    61  		return fmt.Sprintf("%s, block:%d", ec.msg, ec.blockNo)
    62  	} else {
    63  		return fmt.Sprintf("%s", ec.msg)
    64  	}
    65  }
    66  
    67  var (
    68  	ErrInvalidBranchRoot  = errors.New("best block can't be branch root block")
    69  	ErrGatherChain        = errors.New("new/old blocks must exist")
    70  	ErrNotExistBranchRoot = errors.New("branch root block doesn't exist")
    71  	ErrInvalidSwapChain   = errors.New("New chain is not longer than old chain")
    72  
    73  	errMsgNoBlock         = "block not found in the chain DB"
    74  	errMsgInvalidOldBlock = "rollback target is not valid"
    75  )
    76  
    77  func (cs *ChainService) needReorg(block *types.Block) bool {
    78  	cdb := cs.cdb
    79  	blockNo := block.BlockNo()
    80  
    81  	latest := cdb.getBestBlockNo()
    82  	isNeed := latest < blockNo
    83  
    84  	if isNeed {
    85  		logger.Debug().
    86  			Uint64("blockNo", blockNo).
    87  			Uint64("latestNo", latest).
    88  			Str("prev", block.ID()).
    89  			Msg("need reorganization")
    90  	}
    91  
    92  	return isNeed
    93  }
    94  
    95  func newReorganizer(cs *ChainService, topBlock *types.Block, marker *ReorgMarker) (*reorganizer, error) {
    96  	isReco := (marker != nil)
    97  
    98  	reorg := &reorganizer{
    99  		cs:         cs,
   100  		brTopBlock: topBlock,
   101  		newBlocks:  make([]*types.Block, 0, initBlkCount),
   102  		oldBlocks:  make([]*types.Block, 0, initBlkCount),
   103  		recover:    isReco,
   104  		marker:     marker,
   105  	}
   106  
   107  	if isReco {
   108  		marker.setCDB(reorg.cs.cdb)
   109  
   110  		if err := reorg.initRecovery(marker); err != nil {
   111  			return nil, err
   112  		}
   113  
   114  		reorg.gatherFn = reorg.gatherReco
   115  		reorg.gatherPostFn = nil
   116  		reorg.executeBlockFn = cs.executeBlockReco
   117  	} else {
   118  		reorg.gatherFn = reorg.gather
   119  		reorg.gatherPostFn = reorg.newMarker
   120  		reorg.executeBlockFn = cs.executeBlock
   121  	}
   122  
   123  	TestDebugger.Check(DEBUG_CHAIN_RANDOM_STOP, 0, nil)
   124  
   125  	return reorg, nil
   126  }
   127  
   128  //TODO: gather delete request of played tx (1 msg)
   129  func (cs *ChainService) reorg(topBlock *types.Block, marker *ReorgMarker) error {
   130  	logger.Info().Uint64("blockNo", topBlock.GetHeader().GetBlockNo()).Str("hash", topBlock.ID()).
   131  		Bool("recovery", (marker != nil)).Msg("reorg started")
   132  
   133  	begT := time.Now()
   134  
   135  	reorg, err := newReorganizer(cs, topBlock, marker)
   136  	if err != nil {
   137  		logger.Error().Err(err).Msg("new reorganazier failed")
   138  		return err
   139  	}
   140  
   141  	err = reorg.gatherFn()
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	if reorg.gatherPostFn != nil {
   147  		reorg.gatherPostFn()
   148  	}
   149  
   150  	if !cs.NeedReorganization(reorg.brStartBlock.BlockNo()) {
   151  		return consensus.ErrorConsensus{Msg: "reorganization rejected by consensus"}
   152  	}
   153  
   154  	err = reorg.rollback()
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	//it's possible to occur error while executing branch block (forgery)
   160  	if err := reorg.rollforward(); err != nil {
   161  		return err
   162  	}
   163  
   164  	if err := reorg.swapChain(); err != nil {
   165  		switch ec := err.(type) {
   166  		case *ErrDebug:
   167  			return ec
   168  		}
   169  		logger.Fatal().Err(err).Msg("reorg failed while swapping chain, it can't recover")
   170  		return err
   171  	}
   172  
   173  	cs.stat.updateEvent(ReorgStat, time.Since(begT), reorg.oldBlocks[0], reorg.newBlocks[0], reorg.brStartBlock)
   174  	logger.Info().Msg("reorg end")
   175  
   176  	return nil
   177  }
   178  
   179  func (reorg *reorganizer) initRecovery(marker *ReorgMarker) error {
   180  	var startBlock, bestBlock, topBlock *types.Block
   181  	var err error
   182  
   183  	if marker == nil {
   184  		return ErrMarkerNil
   185  	}
   186  
   187  	topBlock = reorg.brTopBlock
   188  
   189  	cdb := reorg.cs.cdb
   190  
   191  	logger.Info().Str("marker", marker.toString()).Msg("new reorganizer")
   192  
   193  	if startBlock, err = cdb.getBlock(marker.BrStartHash); err != nil {
   194  		return err
   195  	}
   196  
   197  	if bestBlock, err = cdb.getBlock(marker.BrBestHash); err != nil {
   198  		return err
   199  	}
   200  
   201  	if bestBlock.GetHeader().GetBlockNo() >= topBlock.GetHeader().GetBlockNo() ||
   202  		startBlock.GetHeader().GetBlockNo() >= bestBlock.GetHeader().GetBlockNo() ||
   203  		startBlock.GetHeader().GetBlockNo() >= topBlock.GetHeader().GetBlockNo() {
   204  		return ErrInvalidReorgMarker
   205  	}
   206  
   207  	reorg.brStartBlock = startBlock
   208  	reorg.bestBlock = bestBlock
   209  
   210  	return nil
   211  }
   212  
   213  func (reorg *reorganizer) newMarker() {
   214  	if reorg.marker != nil {
   215  		return
   216  	}
   217  
   218  	reorg.marker = NewReorgMarker(reorg)
   219  }
   220  
   221  // swap oldchain to newchain oneshot (best effort)
   222  //  - chain height mapping
   223  //  - tx mapping
   224  //  - best block
   225  func (reorg *reorganizer) swapChain() error {
   226  	logger.Info().Msg("swap chain to new branch")
   227  
   228  	if err := TestDebugger.Check(DEBUG_CHAIN_STOP, 1, nil); err != nil {
   229  		return err
   230  	}
   231  
   232  	if err := reorg.marker.write(); err != nil {
   233  		return err
   234  	}
   235  
   236  	if err := TestDebugger.Check(DEBUG_CHAIN_STOP, 2, nil); err != nil {
   237  		return err
   238  	}
   239  
   240  	reorg.deleteOldReceipts()
   241  
   242  	//TODO batch notification of rollforward blocks
   243  
   244  	if err := reorg.swapTxMapping(); err != nil {
   245  		return err
   246  	}
   247  
   248  	if err := reorg.swapChainMapping(); err != nil {
   249  		return err
   250  	}
   251  
   252  	if err := TestDebugger.Check(DEBUG_CHAIN_STOP, 3, nil); err != nil {
   253  		return err
   254  	}
   255  
   256  	reorg.marker.delete()
   257  
   258  	return nil
   259  }
   260  
   261  // swapChainMapping swaps chain meta from org chain to side chain and deleting reorg marker.
   262  // it should be executed by 1 tx to be atomic.
   263  func (reorg *reorganizer) swapChainMapping() error {
   264  	cdb := reorg.cs.cdb
   265  
   266  	logger.Info().Msg("swap chain mapping for new branch")
   267  
   268  	best, err := cdb.GetBestBlock()
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	if reorg.recover && bytes.Equal(best.GetHash(), reorg.brTopBlock.GetHash()) {
   274  		logger.Warn().Msg("swap of chain mapping has already finished")
   275  		return nil
   276  	}
   277  
   278  	if err := cdb.swapChainMapping(reorg.newBlocks); err != nil {
   279  		return err
   280  	}
   281  
   282  	return nil
   283  }
   284  
   285  func (reorg *reorganizer) swapTxMapping() error {
   286  	// newblock/oldblock
   287  	// push mempool (old - tx)
   288  	cs := reorg.cs
   289  	cdb := cs.cdb
   290  
   291  	var oldTxs = make(map[types.TxID]*types.Tx)
   292  
   293  	for _, oldBlock := range reorg.oldBlocks {
   294  		for _, tx := range oldBlock.GetBody().GetTxs() {
   295  			oldTxs[types.ToTxID(tx.GetHash())] = tx
   296  		}
   297  	}
   298  
   299  	var overwrap int
   300  
   301  	// insert new tx mapping
   302  	for i := len(reorg.newBlocks) - 1; i >= 0; i-- {
   303  		newBlock := reorg.newBlocks[i]
   304  
   305  		for _, tx := range newBlock.GetBody().GetTxs() {
   306  			if _, ok := oldTxs[types.ToTxID(tx.GetHash())]; ok {
   307  				overwrap++
   308  				delete(oldTxs, types.ToTxID(tx.GetHash()))
   309  			}
   310  		}
   311  
   312  		dbTx := cs.cdb.store.NewTx()
   313  
   314  		if err := cdb.addTxsOfBlock(&dbTx, newBlock.GetBody().GetTxs(), newBlock.BlockHash()); err != nil {
   315  			dbTx.Discard()
   316  			return err
   317  		}
   318  
   319  		dbTx.Commit()
   320  	}
   321  
   322  	// delete old tx mapping
   323  	bulk := cdb.store.NewBulk()
   324  	defer bulk.DiscardLast()
   325  
   326  	for _, oldTx := range oldTxs {
   327  		bulk.Delete(oldTx.Hash)
   328  	}
   329  
   330  	bulk.Flush()
   331  
   332  	//add rollbacked Tx to mempool (except played tx in roll forward)
   333  	count := len(oldTxs)
   334  	logger.Debug().Int("tx count", count).Int("overwrapped count", overwrap).Msg("tx add to mempool")
   335  
   336  	if count > 0 {
   337  		//txs := make([]*types.Tx, 0, count)
   338  
   339  		for _, tx := range oldTxs {
   340  			//			logger.Debug().Str("txID", txID.String()).Msg("tx added")
   341  			//			txs = append(txs, tx)
   342  			cs.RequestTo(message.MemPoolSvc, &message.MemPoolPut{
   343  				Tx: tx,
   344  			})
   345  		}
   346  		//	cs.RequestTo(message.MemPoolSvc, &message.MemPoolPut{
   347  		//		Txs: txs,
   348  		//	})
   349  	}
   350  	return nil
   351  }
   352  
   353  func (reorg *reorganizer) dumpOldBlocks() {
   354  	for _, block := range reorg.oldBlocks {
   355  		logger.Debug().Str("hash", block.ID()).Uint64("blockNo", block.GetHeader().GetBlockNo()).
   356  			Msg("dump rollback block")
   357  	}
   358  }
   359  
   360  // Find branch root and gather rollforard/rollback target blocks
   361  func (reorg *reorganizer) gather() error {
   362  	//find branch root block , gather rollforward Target block
   363  	var err error
   364  	cdb := reorg.cs.cdb
   365  
   366  	bestBlock, err := cdb.GetBestBlock()
   367  	if err != nil {
   368  		return err
   369  	}
   370  	reorg.bestBlock = bestBlock
   371  
   372  	brBlock := reorg.brTopBlock
   373  	brBlockNo := brBlock.BlockNo()
   374  
   375  	curBestNo := cdb.getBestBlockNo()
   376  
   377  	for {
   378  		if brBlockNo <= curBestNo {
   379  			mainBlock, err := cdb.GetBlockByNo(brBlockNo)
   380  			// One must be able to look up any main chain block by its block
   381  			// no from the chain DB.
   382  			if err != nil {
   383  				return &ErrReorgBlock{errMsgNoBlock, brBlockNo, nil}
   384  			}
   385  
   386  			//found branch root
   387  			if bytes.Equal(brBlock.BlockHash(), mainBlock.BlockHash()) {
   388  				if curBestNo == brBlockNo {
   389  					return ErrInvalidBranchRoot
   390  				}
   391  				if len(reorg.newBlocks) == 0 || len(reorg.oldBlocks) == 0 {
   392  					return ErrGatherChain
   393  				}
   394  				reorg.brStartBlock = brBlock
   395  
   396  				logger.Debug().Str("hash", brBlock.ID()).Uint64("blockNo", brBlockNo).
   397  					Msg("found branch root block")
   398  
   399  				return nil
   400  			}
   401  
   402  			//gather rollback target
   403  			logger.Debug().Str("hash", mainBlock.ID()).Uint64("blockNo", brBlockNo).
   404  				Msg("gather rollback target")
   405  			reorg.oldBlocks = append(reorg.oldBlocks, mainBlock)
   406  		}
   407  
   408  		if brBlockNo <= 0 {
   409  			break
   410  		}
   411  
   412  		//gather rollforward target
   413  		logger.Debug().Str("hash", brBlock.ID()).Uint64("blockNo", brBlockNo).
   414  			Msg("gather rollforward target")
   415  		reorg.newBlocks = append(reorg.newBlocks, brBlock)
   416  
   417  		//get prev block from branch
   418  		if brBlock, err = cdb.getBlock(brBlock.GetHeader().GetPrevBlockHash()); err != nil {
   419  			return err
   420  		}
   421  
   422  		prevBrBlockNo := brBlock.GetHeader().GetBlockNo()
   423  		if brBlockNo-1 != prevBrBlockNo {
   424  			return &ErrReorgBlock{errMsgInvalidOldBlock, prevBrBlockNo, brBlock.BlockHash()}
   425  		}
   426  		brBlockNo = brBlock.GetHeader().GetBlockNo()
   427  	}
   428  
   429  	return ErrNotExistBranchRoot
   430  }
   431  
   432  // build reorg chain info from marker
   433  func (reorg *reorganizer) gatherReco() error {
   434  	var err error
   435  
   436  	cdb := reorg.cs.cdb
   437  
   438  	startBlock := reorg.brStartBlock
   439  	bestBlock := reorg.bestBlock
   440  	topBlock := reorg.brTopBlock
   441  
   442  	reorg.brStartBlock = startBlock
   443  	reorg.bestBlock = bestBlock
   444  
   445  	gatherBlocksToStart := func(top *types.Block, stage string) ([]*types.Block, error) {
   446  		blocks := make([]*types.Block, 0)
   447  
   448  		for tmpBlk := top; tmpBlk.GetHeader().GetBlockNo() > startBlock.GetHeader().GetBlockNo(); {
   449  			blocks = append(blocks, tmpBlk)
   450  
   451  			logger.Debug().Str("stage", stage).Str("hash", tmpBlk.ID()).Uint64("blockNo", tmpBlk.GetHeader().GetBlockNo()).
   452  				Msg("gather target for reco")
   453  
   454  			if tmpBlk, err = cdb.getBlock(tmpBlk.GetHeader().GetPrevBlockHash()); err != nil {
   455  				return blocks, err
   456  			}
   457  		}
   458  
   459  		return blocks, nil
   460  	}
   461  
   462  	reorg.oldBlocks, err = gatherBlocksToStart(bestBlock, "rollback")
   463  	if err != nil {
   464  		return err
   465  	}
   466  
   467  	reorg.newBlocks, err = gatherBlocksToStart(topBlock, "rollforward")
   468  	if err != nil {
   469  		return err
   470  	}
   471  
   472  	return nil
   473  }
   474  
   475  func (reorg *reorganizer) rollback() error {
   476  	brStartBlock := reorg.brStartBlock
   477  	brStartBlockNo := brStartBlock.GetHeader().GetBlockNo()
   478  
   479  	logger.Info().Str("hash", brStartBlock.ID()).Uint64("no", brStartBlockNo).Msg("rollback chain to branch start block")
   480  
   481  	if err := reorg.cs.sdb.SetRoot(brStartBlock.GetHeader().GetBlocksRootHash()); err != nil {
   482  		return fmt.Errorf("failed to rollback sdb(branchRoot:no=%d,hash=%v)", brStartBlockNo,
   483  			brStartBlock.ID())
   484  	}
   485  
   486  	reorg.cs.Update(brStartBlock)
   487  
   488  	return nil
   489  }
   490  
   491  func (reorg *reorganizer) deleteOldReceipts() {
   492  	dbTx := reorg.cs.cdb.NewTx()
   493  	for _, blk := range reorg.oldBlocks {
   494  		reorg.cs.cdb.deleteReceipts(&dbTx, blk.GetHash(), blk.BlockNo())
   495  	}
   496  	dbTx.Commit()
   497  }
   498  
   499  /*
   500  	rollforward
   501  		rollforwardBlock
   502  		add oldTxs to mempool
   503  */
   504  func (reorg *reorganizer) rollforward() error {
   505  	//cs := reorg.cs
   506  
   507  	logger.Info().Bool("recover", reorg.recover).Msg("rollforward chain started")
   508  
   509  	for i := len(reorg.newBlocks) - 1; i >= 0; i-- {
   510  		newBlock := reorg.newBlocks[i]
   511  		newBlockNo := newBlock.GetHeader().GetBlockNo()
   512  
   513  		if err := reorg.executeBlockFn(nil, newBlock); err != nil {
   514  			logger.Error().Bool("recover", reorg.recover).Str("hash", newBlock.ID()).Uint64("no", newBlockNo).
   515  				Msg("failed to execute block in reorg")
   516  			return err
   517  		}
   518  	}
   519  
   520  	return nil
   521  }