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  }