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 }