github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/cmd/u2u/launcher/genesiscmd.go (about) 1 package launcher 2 3 import ( 4 "compress/gzip" 5 "errors" 6 "fmt" 7 "io" 8 "math" 9 "os" 10 "path" 11 "strconv" 12 "strings" 13 14 "github.com/syndtr/goleveldb/leveldb/opt" 15 "github.com/unicornultrafoundation/go-helios/common/bigendian" 16 "github.com/unicornultrafoundation/go-helios/hash" 17 "github.com/unicornultrafoundation/go-helios/native/idx" 18 "github.com/unicornultrafoundation/go-helios/u2udb" 19 "github.com/unicornultrafoundation/go-helios/u2udb/pebble" 20 "github.com/unicornultrafoundation/go-u2u/cmd/utils" 21 "github.com/unicornultrafoundation/go-u2u/log" 22 "github.com/unicornultrafoundation/go-u2u/rlp" 23 "gopkg.in/urfave/cli.v1" 24 25 "github.com/unicornultrafoundation/go-u2u/gossip" 26 "github.com/unicornultrafoundation/go-u2u/gossip/evmstore" 27 "github.com/unicornultrafoundation/go-u2u/native/ibr" 28 "github.com/unicornultrafoundation/go-u2u/native/ier" 29 "github.com/unicornultrafoundation/go-u2u/u2u/genesis" 30 "github.com/unicornultrafoundation/go-u2u/u2u/genesisstore" 31 "github.com/unicornultrafoundation/go-u2u/u2u/genesisstore/fileshash" 32 "github.com/unicornultrafoundation/go-u2u/utils/devnullfile" 33 "github.com/unicornultrafoundation/go-u2u/utils/iodb" 34 ) 35 36 type dropableFile struct { 37 io.ReadWriteSeeker 38 io.Closer 39 path string 40 } 41 42 func (f dropableFile) Drop() error { 43 return os.Remove(f.path) 44 } 45 46 type mptIterator struct { 47 u2udb.Iterator 48 } 49 50 func (it mptIterator) Next() bool { 51 for it.Iterator.Next() { 52 if evmstore.IsMptKey(it.Key()) { 53 return true 54 } 55 } 56 return false 57 } 58 59 type mptAndPreimageIterator struct { 60 u2udb.Iterator 61 } 62 63 func (it mptAndPreimageIterator) Next() bool { 64 for it.Iterator.Next() { 65 if evmstore.IsMptKey(it.Key()) || evmstore.IsPreimageKey(it.Key()) { 66 return true 67 } 68 } 69 return false 70 } 71 72 type excludingIterator struct { 73 u2udb.Iterator 74 exclude u2udb.Reader 75 } 76 77 func (it excludingIterator) Next() bool { 78 for it.Iterator.Next() { 79 if ok, _ := it.exclude.Has(it.Key()); !ok { 80 return true 81 } 82 } 83 return false 84 } 85 86 type unitWriter struct { 87 plain io.WriteSeeker 88 gziper *gzip.Writer 89 fileshasher *fileshash.Writer 90 dataStartPos int64 91 uncompressedSize uint64 92 } 93 94 func newUnitWriter(plain io.WriteSeeker) *unitWriter { 95 return &unitWriter{ 96 plain: plain, 97 } 98 } 99 100 func (w *unitWriter) Start(header genesis.Header, name, tmpDirPath string) error { 101 if w.plain == nil { 102 // dry run 103 w.fileshasher = fileshash.WrapWriter(nil, genesisstore.FilesHashPieceSize, func(int) fileshash.TmpWriter { 104 return devnullfile.DevNull{} 105 }) 106 return nil 107 } 108 // Write unit marker and version 109 _, err := w.plain.Write(append(genesisstore.FileHeader, genesisstore.FileVersion...)) 110 if err != nil { 111 return err 112 } 113 114 // write genesis header 115 err = rlp.Encode(w.plain, genesisstore.Unit{ 116 UnitName: name, 117 Header: header, 118 }) 119 if err != nil { 120 return err 121 } 122 123 w.dataStartPos, err = w.plain.Seek(8+8+32, io.SeekCurrent) 124 if err != nil { 125 return err 126 } 127 128 w.gziper, _ = gzip.NewWriterLevel(w.plain, gzip.BestCompression) 129 130 w.fileshasher = fileshash.WrapWriter(w.gziper, genesisstore.FilesHashPieceSize, func(tmpI int) fileshash.TmpWriter { 131 tmpI++ 132 tmpPath := path.Join(tmpDirPath, fmt.Sprintf("genesis-%s-tmp-%d", name, tmpI)) 133 _ = os.MkdirAll(tmpDirPath, os.ModePerm) 134 tmpFh, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_RDWR, os.ModePerm) 135 if err != nil { 136 log.Crit("File opening error", "path", tmpPath, "err", err) 137 } 138 return dropableFile{ 139 ReadWriteSeeker: tmpFh, 140 Closer: tmpFh, 141 path: tmpPath, 142 } 143 }) 144 return nil 145 } 146 147 func (w *unitWriter) Flush() (hash.Hash, error) { 148 if w.plain == nil { 149 return w.fileshasher.Root(), nil 150 } 151 h, err := w.fileshasher.Flush() 152 if err != nil { 153 return hash.Hash{}, err 154 } 155 156 err = w.gziper.Close() 157 if err != nil { 158 return hash.Hash{}, err 159 } 160 161 endPos, err := w.plain.Seek(0, io.SeekCurrent) 162 if err != nil { 163 return hash.Hash{}, err 164 } 165 166 _, err = w.plain.Seek(w.dataStartPos-(8+8+32), io.SeekStart) 167 if err != nil { 168 return hash.Hash{}, err 169 } 170 171 _, err = w.plain.Write(h.Bytes()) 172 if err != nil { 173 return hash.Hash{}, err 174 } 175 _, err = w.plain.Write(bigendian.Uint64ToBytes(uint64(endPos - w.dataStartPos))) 176 if err != nil { 177 return hash.Hash{}, err 178 } 179 _, err = w.plain.Write(bigendian.Uint64ToBytes(w.uncompressedSize)) 180 if err != nil { 181 return hash.Hash{}, err 182 } 183 184 _, err = w.plain.Seek(0, io.SeekEnd) 185 if err != nil { 186 return hash.Hash{}, err 187 } 188 return h, nil 189 } 190 191 func (w *unitWriter) Write(b []byte) (n int, err error) { 192 n, err = w.fileshasher.Write(b) 193 w.uncompressedSize += uint64(n) 194 return 195 } 196 197 func getEpochBlock(epoch idx.Epoch, store *gossip.Store) idx.Block { 198 bs, _ := store.GetHistoryBlockEpochState(epoch) 199 if bs == nil { 200 return 0 201 } 202 return bs.LastBlock.Idx 203 } 204 205 func exportGenesis(ctx *cli.Context) error { 206 if len(ctx.Args()) < 1 { 207 utils.Fatalf("This command requires an argument.") 208 } 209 210 from := idx.Epoch(1) 211 if len(ctx.Args()) > 1 { 212 n, err := strconv.ParseUint(ctx.Args().Get(1), 10, 32) 213 if err != nil { 214 return err 215 } 216 from = idx.Epoch(n) 217 } 218 to := idx.Epoch(math.MaxUint32) 219 if len(ctx.Args()) > 2 { 220 n, err := strconv.ParseUint(ctx.Args().Get(2), 10, 32) 221 if err != nil { 222 return err 223 } 224 to = idx.Epoch(n) 225 } 226 mode := ctx.String(EvmExportMode.Name) 227 if mode != "full" && mode != "ext-mpt" && mode != "mpt" { 228 return errors.New("--export.evm.mode must be one of {full, ext-mpt, mpt}") 229 } 230 231 var excludeEvmDB u2udb.Store 232 if excludeEvmDBPath := ctx.String(EvmExportExclude.Name); len(excludeEvmDBPath) > 0 { 233 db, err := pebble.New(excludeEvmDBPath, 1024*opt.MiB, utils.MakeDatabaseHandles()/2, nil, nil) 234 if err != nil { 235 return err 236 } 237 excludeEvmDB = db 238 } 239 240 sectionsStr := ctx.String(GenesisExportSections.Name) 241 sections := map[string]string{} 242 for _, str := range strings.Split(sectionsStr, ",") { 243 before := len(sections) 244 if strings.HasPrefix(str, "brs") { 245 sections["brs"] = str 246 } else if strings.HasPrefix(str, "ers") { 247 sections["ers"] = str 248 } else if strings.HasPrefix(str, "evm") { 249 sections["evm"] = str 250 } else { 251 return fmt.Errorf("unknown section '%s': has to start with either 'brs' or 'ers' or 'evm'", str) 252 } 253 if len(sections) == before { 254 return fmt.Errorf("duplicate section: '%s'", str) 255 } 256 } 257 258 cfg := makeAllConfigs(ctx) 259 tmpPath := path.Join(cfg.Node.DataDir, "tmp") 260 _ = os.RemoveAll(tmpPath) 261 defer os.RemoveAll(tmpPath) 262 263 rawDbs := makeDirectDBsProducer(cfg) 264 gdb := makeGossipStore(rawDbs, cfg) 265 if gdb.GetHighestLamport() != 0 { 266 log.Warn("Attempting genesis export not in a beginning of an epoch. Genesis file output may contain excessive data.") 267 } 268 defer gdb.Close() 269 270 fn := ctx.Args().First() 271 272 // Open the file handle 273 var plain io.WriteSeeker 274 if fn != "dry-run" { 275 fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) 276 if err != nil { 277 return err 278 } 279 defer fh.Close() 280 plain = fh 281 } 282 283 header := genesis.Header{ 284 GenesisID: *gdb.GetGenesisID(), 285 NetworkID: gdb.GetEpochState().Rules.NetworkID, 286 NetworkName: gdb.GetEpochState().Rules.Name, 287 } 288 var epochsHash hash.Hash 289 var blocksHash hash.Hash 290 var evmHash hash.Hash 291 292 if from < 1 { 293 // avoid underflow 294 from = 1 295 } 296 if to > gdb.GetEpoch() { 297 to = gdb.GetEpoch() 298 } 299 if len(sections["ers"]) > 0 { 300 log.Info("Exporting epochs", "from", from, "to", to) 301 writer := newUnitWriter(plain) 302 err := writer.Start(header, sections["ers"], tmpPath) 303 if err != nil { 304 return err 305 } 306 for i := to; i >= from; i-- { 307 er := gdb.GetFullEpochRecord(i) 308 if er == nil { 309 log.Warn("No epoch record", "epoch", i) 310 break 311 } 312 b, _ := rlp.EncodeToBytes(ier.LlrIdxFullEpochRecord{ 313 LlrFullEpochRecord: *er, 314 Idx: i, 315 }) 316 _, err := writer.Write(b) 317 if err != nil { 318 return err 319 } 320 } 321 epochsHash, err = writer.Flush() 322 if err != nil { 323 return err 324 } 325 log.Info("Exported epochs") 326 fmt.Printf("- Epochs hash: %v \n", epochsHash.String()) 327 } 328 329 if len(sections["brs"]) > 0 { 330 toBlock := getEpochBlock(to, gdb) 331 fromBlock := getEpochBlock(from, gdb) 332 if sections["brs"] != "brs" { 333 // to continue prev section, include blocks of prev epochs too, excluding first blocks of prev epoch (which is last block if prev section) 334 fromBlock = getEpochBlock(from-1, gdb) + 1 335 } 336 if fromBlock < 1 { 337 // avoid underflow 338 fromBlock = 1 339 } 340 log.Info("Exporting blocks", "from", fromBlock, "to", toBlock) 341 writer := newUnitWriter(plain) 342 err := writer.Start(header, sections["brs"], tmpPath) 343 if err != nil { 344 return err 345 } 346 for i := toBlock; i >= fromBlock; i-- { 347 br := gdb.GetFullBlockRecord(i) 348 if br == nil { 349 log.Warn("No block record", "block", i) 350 break 351 } 352 if i%200000 == 0 { 353 log.Info("Exporting blocks", "last", i) 354 } 355 b, _ := rlp.EncodeToBytes(ibr.LlrIdxFullBlockRecord{ 356 LlrFullBlockRecord: *br, 357 Idx: i, 358 }) 359 _, err := writer.Write(b) 360 if err != nil { 361 return err 362 } 363 } 364 blocksHash, err = writer.Flush() 365 if err != nil { 366 return err 367 } 368 log.Info("Exported blocks") 369 fmt.Printf("- Blocks hash: %v \n", blocksHash.String()) 370 } 371 372 if len(sections["evm"]) > 0 { 373 log.Info("Exporting EVM data") 374 writer := newUnitWriter(plain) 375 err := writer.Start(header, sections["evm"], tmpPath) 376 if err != nil { 377 return err 378 } 379 it := gdb.EvmStore().EvmDb.NewIterator(nil, nil) 380 if mode == "mpt" { 381 // iterate only over MPT data 382 it = mptIterator{it} 383 } else if mode == "ext-mpt" { 384 // iterate only over MPT data and preimages 385 it = mptAndPreimageIterator{it} 386 } 387 if excludeEvmDB != nil { 388 it = excludingIterator{it, excludeEvmDB} 389 } 390 defer it.Release() 391 err = iodb.Write(writer, it) 392 if err != nil { 393 return err 394 } 395 evmHash, err = writer.Flush() 396 if err != nil { 397 return err 398 } 399 log.Info("Exported EVM data") 400 fmt.Printf("- EVM hash: %v \n", evmHash.String()) 401 } 402 403 return nil 404 }