github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/cmd/u2u/launcher/fixdirty.go (about)

     1  package launcher
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"gopkg.in/urfave/cli.v1"
     8  
     9  	"github.com/unicornultrafoundation/go-u2u/cmd/utils"
    10  	"github.com/unicornultrafoundation/go-u2u/log"
    11  	"github.com/unicornultrafoundation/go-u2u/rlp"
    12  
    13  	"github.com/unicornultrafoundation/go-helios/common/bigendian"
    14  	"github.com/unicornultrafoundation/go-helios/consensus"
    15  	"github.com/unicornultrafoundation/go-helios/hash"
    16  	"github.com/unicornultrafoundation/go-helios/native/idx"
    17  	"github.com/unicornultrafoundation/go-helios/u2udb"
    18  	"github.com/unicornultrafoundation/go-helios/u2udb/batched"
    19  	"github.com/unicornultrafoundation/go-helios/u2udb/flushable"
    20  
    21  	"github.com/unicornultrafoundation/go-u2u/gossip"
    22  	"github.com/unicornultrafoundation/go-u2u/integration"
    23  	"github.com/unicornultrafoundation/go-u2u/native/iblockproc"
    24  )
    25  
    26  // maxEpochsToTry represents amount of last closed epochs to try (in case that the last one has the state unavailable)
    27  const maxEpochsToTry = 10000
    28  
    29  // healDirty is the 'db heal' command.
    30  func healDirty(ctx *cli.Context) error {
    31  	if !ctx.Bool(experimentalFlag.Name) {
    32  		utils.Fatalf("Add --experimental flag")
    33  	}
    34  	cfg := makeAllConfigs(ctx)
    35  
    36  	log.Info("Opening databases")
    37  	dbTypes := makeUncheckedDBsProducers(cfg)
    38  	multiProducer := makeDirectDBsProducerFrom(dbTypes, cfg)
    39  
    40  	// reverts the gossip database state
    41  	epochState, topEpoch, err := fixDirtyGossipDb(multiProducer, cfg)
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	// drop epoch-related databases and consensus database
    47  	log.Info("Removing epoch DBs - will be recreated on next start")
    48  	for _, name := range []string{
    49  		fmt.Sprintf("gossip-%d", topEpoch),
    50  		fmt.Sprintf("hashgraph-%d", topEpoch),
    51  		"hashgraph",
    52  	} {
    53  		err = eraseTable(name, multiProducer)
    54  		if err != nil {
    55  			return err
    56  		}
    57  	}
    58  
    59  	// prepare consensus database from epochState
    60  	log.Info("Recreating hashgraph DB")
    61  	cMainDb := mustOpenDB(multiProducer, "hashgraph")
    62  	cGetEpochDB := func(epoch idx.Epoch) u2udb.Store {
    63  		return mustOpenDB(multiProducer, fmt.Sprintf("hashgraph-%d", epoch))
    64  	}
    65  	cdb := consensus.NewStore(cMainDb, cGetEpochDB, panics("hashgraph store"), cfg.HashgraphStore)
    66  	err = cdb.ApplyGenesis(&consensus.Genesis{
    67  		Epoch:      epochState.Epoch,
    68  		Validators: epochState.Validators,
    69  	})
    70  	if err != nil {
    71  		return fmt.Errorf("failed to init consensus database: %v", err)
    72  	}
    73  	_ = cdb.Close()
    74  
    75  	log.Info("Clearing DBs dirty flags")
    76  	id := bigendian.Uint64ToBytes(uint64(time.Now().UnixNano()))
    77  	for typ, producer := range dbTypes {
    78  		err := clearDirtyFlags(id, producer)
    79  		if err != nil {
    80  			log.Crit("Failed to write clean FlushID", "type", typ, "err", err)
    81  		}
    82  	}
    83  
    84  	log.Info("Recovery is complete")
    85  	return nil
    86  }
    87  
    88  // fixDirtyGossipDb reverts the gossip database into state, when was one of last epochs sealed
    89  func fixDirtyGossipDb(producer u2udb.FlushableDBProducer, cfg *config) (
    90  	epochState *iblockproc.EpochState, topEpoch idx.Epoch, err error) {
    91  	gdb := makeGossipStore(producer, cfg) // requires FlushIDKey present (not clean) in all dbs
    92  	defer gdb.Close()
    93  	topEpoch = gdb.GetEpoch()
    94  
    95  	// find the last closed epoch with the state available
    96  	epochIdx, blockState, epochState := getLastEpochWithState(gdb, maxEpochsToTry)
    97  	if blockState == nil || epochState == nil {
    98  		return nil, 0, fmt.Errorf("state for last %d closed epochs is pruned, recovery isn't possible", maxEpochsToTry)
    99  	}
   100  
   101  	// set the historic state to be the current
   102  	log.Info("Reverting to epoch state", "epoch", epochIdx)
   103  	gdb.SetBlockEpochState(*blockState, *epochState)
   104  	gdb.FlushBlockEpochState()
   105  
   106  	// Service.switchEpochTo
   107  	gdb.SetHighestLamport(0)
   108  	gdb.FlushHighestLamport()
   109  
   110  	// removing excessive events (event epoch >= closed epoch)
   111  	log.Info("Removing excessive events")
   112  	gdb.ForEachEventRLP(epochIdx.Bytes(), func(id hash.Event, _ rlp.RawValue) bool {
   113  		gdb.DelEvent(id)
   114  		return true
   115  	})
   116  
   117  	return epochState, topEpoch, nil
   118  }
   119  
   120  // getLastEpochWithState finds the last closed epoch with the state available
   121  func getLastEpochWithState(gdb *gossip.Store, epochsToTry idx.Epoch) (epochIdx idx.Epoch, blockState *iblockproc.BlockState, epochState *iblockproc.EpochState) {
   122  	currentEpoch := gdb.GetEpoch()
   123  	endEpoch := idx.Epoch(1)
   124  	if currentEpoch > epochsToTry {
   125  		endEpoch = currentEpoch - epochsToTry
   126  	}
   127  
   128  	for epochIdx = currentEpoch; epochIdx > endEpoch; epochIdx-- {
   129  		blockState, epochState = gdb.GetHistoryBlockEpochState(epochIdx)
   130  		if blockState == nil || epochState == nil {
   131  			log.Debug("Epoch is not available", "epoch", epochIdx)
   132  			continue
   133  		}
   134  		if !gdb.EvmStore().HasStateDB(blockState.FinalizedStateRoot) {
   135  			log.Debug("EVM state for the epoch is not available", "epoch", epochIdx)
   136  			continue
   137  		}
   138  		log.Debug("Latest epoch with available state found", "epoch", epochIdx)
   139  		return epochIdx, blockState, epochState
   140  	}
   141  
   142  	return 0, nil, nil
   143  }
   144  
   145  func eraseTable(name string, producer u2udb.IterableDBProducer) error {
   146  	log.Info("Cleaning table", "name", name)
   147  	db, err := producer.OpenDB(name)
   148  	if err != nil {
   149  		return fmt.Errorf("unable to open DB %s; %s", name, err)
   150  	}
   151  	db = batched.Wrap(db)
   152  	defer db.Close()
   153  	it := db.NewIterator(nil, nil)
   154  	defer it.Release()
   155  	for it.Next() {
   156  		err := db.Delete(it.Key())
   157  		if err != nil {
   158  			return err
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  // clearDirtyFlags - writes the CleanPrefix into all databases
   165  func clearDirtyFlags(id []byte, rawProducer u2udb.IterableDBProducer) error {
   166  	names := rawProducer.Names()
   167  	for _, name := range names {
   168  		db, err := rawProducer.OpenDB(name)
   169  		if err != nil {
   170  			return err
   171  		}
   172  
   173  		err = db.Put(integration.FlushIDKey, append([]byte{flushable.CleanPrefix}, id...))
   174  		if err != nil {
   175  			log.Crit("Failed to write CleanPrefix", "name", name)
   176  			return err
   177  		}
   178  		log.Info("Database set clean", "name", name)
   179  		_ = db.Close()
   180  	}
   181  	return nil
   182  }
   183  
   184  func mustOpenDB(producer u2udb.DBProducer, name string) u2udb.Store {
   185  	db, err := producer.OpenDB(name)
   186  	if err != nil {
   187  		utils.Fatalf("Failed to open '%s' database: %v", name, err)
   188  	}
   189  	return db
   190  }
   191  
   192  func panics(name string) func(error) {
   193  	return func(err error) {
   194  		log.Crit(fmt.Sprintf("%s error", name), "err", err)
   195  	}
   196  }