github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/integration/assembly.go (about)

     1  package integration
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"errors"
     6  	"fmt"
     7  	"path"
     8  
     9  	"github.com/status-im/keycard-go/hexutils"
    10  	"github.com/syndtr/goleveldb/leveldb/opt"
    11  	"github.com/unicornultrafoundation/go-helios/consensus"
    12  	"github.com/unicornultrafoundation/go-helios/hash"
    13  	"github.com/unicornultrafoundation/go-helios/native/idx"
    14  	"github.com/unicornultrafoundation/go-helios/u2udb"
    15  	"github.com/unicornultrafoundation/go-helios/u2udb/multidb"
    16  	"github.com/unicornultrafoundation/go-u2u/accounts"
    17  	"github.com/unicornultrafoundation/go-u2u/accounts/keystore"
    18  	"github.com/unicornultrafoundation/go-u2u/cmd/utils"
    19  	"github.com/unicornultrafoundation/go-u2u/common"
    20  	"github.com/unicornultrafoundation/go-u2u/crypto"
    21  	"github.com/unicornultrafoundation/go-u2u/log"
    22  
    23  	"github.com/unicornultrafoundation/go-u2u/gossip"
    24  	"github.com/unicornultrafoundation/go-u2u/u2u/genesis"
    25  	"github.com/unicornultrafoundation/go-u2u/utils/adapters/vecmt2dagidx"
    26  	"github.com/unicornultrafoundation/go-u2u/utils/dbutil/compactdb"
    27  	"github.com/unicornultrafoundation/go-u2u/vecmt"
    28  )
    29  
    30  var (
    31  	MetadataPrefix = hexutils.HexToBytes("0068c2927bf842c3e9e2f1364494a33a752db334b9a819534bc9f17d2c3b4e5970008ff519d35a86f29fcaa5aae706b75dee871f65f174fcea1747f2915fc92158f6bfbf5eb79f65d16225738594bffb")
    32  	FlushIDKey     = append(common.CopyBytes(MetadataPrefix), 0x0c)
    33  	TablesKey      = append(common.CopyBytes(MetadataPrefix), 0x0d)
    34  )
    35  
    36  // GenesisMismatchError is raised when trying to overwrite an existing
    37  // genesis block with an incompatible one.
    38  type GenesisMismatchError struct {
    39  	Stored, New hash.Hash
    40  }
    41  
    42  // Error implements error interface.
    43  func (e *GenesisMismatchError) Error() string {
    44  	return fmt.Sprintf("database contains incompatible genesis (have %s, new %s)", e.Stored.String(), e.New.String())
    45  }
    46  
    47  type Configs struct {
    48  	U2U            gossip.Config
    49  	U2UStore       gossip.StoreConfig
    50  	Hashgraph      consensus.Config
    51  	HashgraphStore consensus.StoreConfig
    52  	VectorClock    vecmt.IndexConfig
    53  	DBs            DBsConfig
    54  }
    55  
    56  func panics(name string) func(error) {
    57  	return func(err error) {
    58  		log.Crit(fmt.Sprintf("%s error", name), "err", err)
    59  	}
    60  }
    61  
    62  func mustOpenDB(producer u2udb.DBProducer, name string) u2udb.Store {
    63  	db, err := producer.OpenDB(name)
    64  	if err != nil {
    65  		utils.Fatalf("Failed to open '%s' database: %v", name, err)
    66  	}
    67  	return db
    68  }
    69  
    70  func getStores(producer u2udb.FlushableDBProducer, cfg Configs) (*gossip.Store, *consensus.Store) {
    71  	gdb := gossip.NewStore(producer, cfg.U2UStore)
    72  
    73  	cMainDb := mustOpenDB(producer, "hashgraph")
    74  	cGetEpochDB := func(epoch idx.Epoch) u2udb.Store {
    75  		return mustOpenDB(producer, fmt.Sprintf("hashgraph-%d", epoch))
    76  	}
    77  	cdb := consensus.NewStore(cMainDb, cGetEpochDB, panics("Hashgraph store"), cfg.HashgraphStore)
    78  	return gdb, cdb
    79  }
    80  
    81  func getEpoch(producer u2udb.FlushableDBProducer, cfg Configs) idx.Epoch {
    82  	gdb := gossip.NewStore(producer, cfg.U2UStore)
    83  	defer gdb.Close()
    84  	return gdb.GetEpoch()
    85  }
    86  
    87  func rawApplyGenesis(gdb *gossip.Store, cdb *consensus.Store, g genesis.Genesis, cfg Configs) error {
    88  	_, _, _, err := rawMakeEngine(gdb, cdb, &g, cfg)
    89  	return err
    90  }
    91  
    92  func rawMakeEngine(gdb *gossip.Store, cdb *consensus.Store, g *genesis.Genesis, cfg Configs) (*consensus.Consensus, *vecmt.Index, gossip.BlockProc, error) {
    93  	blockProc := gossip.DefaultBlockProc()
    94  
    95  	if g != nil {
    96  		_, err := gdb.ApplyGenesis(*g)
    97  		if err != nil {
    98  			return nil, nil, blockProc, fmt.Errorf("failed to write Gossip genesis state: %v", err)
    99  		}
   100  
   101  		err = cdb.ApplyGenesis(&consensus.Genesis{
   102  			Epoch:      gdb.GetEpoch(),
   103  			Validators: gdb.GetValidators(),
   104  		})
   105  		if err != nil {
   106  			return nil, nil, blockProc, fmt.Errorf("failed to write Hashgraph genesis state: %v", err)
   107  		}
   108  	}
   109  
   110  	// create consensus
   111  	vecClock := vecmt.NewIndex(panics("Vector clock"), cfg.VectorClock)
   112  	engine := consensus.NewConsensus(cdb, &GossipStoreAdapter{gdb}, vecmt2dagidx.Wrap(vecClock), panics("Hashgraph"), cfg.Hashgraph)
   113  	return engine, vecClock, blockProc, nil
   114  }
   115  
   116  func applyGenesis(dbs u2udb.FlushableDBProducer, g genesis.Genesis, cfg Configs) error {
   117  	gdb, cdb := getStores(dbs, cfg)
   118  	defer gdb.Close()
   119  	defer cdb.Close()
   120  	log.Info("Applying genesis state")
   121  	err := rawApplyGenesis(gdb, cdb, g, cfg)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	err = gdb.Commit()
   126  	if err != nil {
   127  		return err
   128  	}
   129  	return nil
   130  }
   131  
   132  func migrate(dbs u2udb.FlushableDBProducer, cfg Configs) error {
   133  	gdb, cdb := getStores(dbs, cfg)
   134  	defer gdb.Close()
   135  	defer cdb.Close()
   136  	err := gdb.Commit()
   137  	if err != nil {
   138  		return err
   139  	}
   140  	return nil
   141  }
   142  
   143  func CheckStateInitialized(chaindataDir string, cfg DBsConfig) error {
   144  	if isInterrupted(chaindataDir) {
   145  		return errors.New("genesis processing isn't finished")
   146  	}
   147  	runtimeProducers, runtimeScopedProducers := SupportedDBs(chaindataDir, cfg.RuntimeCache)
   148  	dbs, err := MakeMultiProducer(runtimeProducers, runtimeScopedProducers, cfg.Routing)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	return dbs.Close()
   153  }
   154  
   155  func compactDB(typ multidb.TypeName, name string, producer u2udb.DBProducer) error {
   156  	humanName := path.Join(string(typ), name)
   157  	db, err := producer.OpenDB(name)
   158  	defer db.Close()
   159  	if err != nil {
   160  		return err
   161  	}
   162  	return compactdb.Compact(db, humanName, 16*opt.GiB)
   163  }
   164  
   165  func makeEngine(chaindataDir string, g *genesis.Genesis, genesisProc bool, cfg Configs) (*consensus.Consensus, *vecmt.Index, *gossip.Store, *consensus.Store, gossip.BlockProc, func() error, error) {
   166  	// Genesis processing
   167  	if genesisProc {
   168  		setGenesisProcessing(chaindataDir)
   169  		// use increased DB cache for genesis processing
   170  		genesisProducers, _ := SupportedDBs(chaindataDir, cfg.DBs.GenesisCache)
   171  		if g == nil {
   172  			return nil, nil, nil, nil, gossip.BlockProc{}, nil, fmt.Errorf("missing --genesis flag for an empty datadir")
   173  		}
   174  		dbs, err := MakeDirectMultiProducer(genesisProducers, cfg.DBs.Routing)
   175  		if err != nil {
   176  			return nil, nil, nil, nil, gossip.BlockProc{}, nil, fmt.Errorf("failed to make DB multi-producer: %v", err)
   177  		}
   178  		err = applyGenesis(dbs, *g, cfg)
   179  		if err != nil {
   180  			_ = dbs.Close()
   181  			return nil, nil, nil, nil, gossip.BlockProc{}, nil, fmt.Errorf("failed to apply genesis state: %v", err)
   182  		}
   183  		_ = dbs.Close()
   184  		setGenesisComplete(chaindataDir)
   185  	}
   186  	// Compact DBs after first launch
   187  	if genesisProc {
   188  		genesisProducers, _ := SupportedDBs(chaindataDir, cfg.DBs.GenesisCache)
   189  		for typ, p := range genesisProducers {
   190  			for _, name := range p.Names() {
   191  				if err := compactDB(typ, name, p); err != nil {
   192  					return nil, nil, nil, nil, gossip.BlockProc{}, nil, err
   193  				}
   194  			}
   195  		}
   196  	}
   197  	// Check DBs are synced
   198  	{
   199  		err := CheckStateInitialized(chaindataDir, cfg.DBs)
   200  		if err != nil {
   201  			return nil, nil, nil, nil, gossip.BlockProc{}, nil, err
   202  		}
   203  	}
   204  	// Migration
   205  	{
   206  		runtimeProducers, _ := SupportedDBs(chaindataDir, cfg.DBs.RuntimeCache)
   207  		dbs, err := MakeDirectMultiProducer(runtimeProducers, cfg.DBs.Routing)
   208  		if err != nil {
   209  			return nil, nil, nil, nil, gossip.BlockProc{}, nil, err
   210  		}
   211  
   212  		// drop previous epoch DBs, which do not survive restart
   213  		epoch := getEpoch(dbs, cfg)
   214  		leDB, err := dbs.OpenDB(fmt.Sprintf("hashgraph-%d", epoch))
   215  		if err != nil {
   216  			_ = dbs.Close()
   217  			return nil, nil, nil, nil, gossip.BlockProc{}, nil, err
   218  		}
   219  		_ = leDB.Close()
   220  		leDB.Drop()
   221  		goDB, err := dbs.OpenDB(fmt.Sprintf("gossip-%d", epoch))
   222  		if err != nil {
   223  			_ = dbs.Close()
   224  			return nil, nil, nil, nil, gossip.BlockProc{}, nil, err
   225  		}
   226  		_ = goDB.Close()
   227  		goDB.Drop()
   228  
   229  		err = migrate(dbs, cfg)
   230  		_ = dbs.Close()
   231  		if err != nil {
   232  			return nil, nil, nil, nil, gossip.BlockProc{}, nil, fmt.Errorf("failed to migrate state: %v", err)
   233  		}
   234  	}
   235  	// Live setup
   236  
   237  	runtimeProducers, runtimeScopedProducers := SupportedDBs(chaindataDir, cfg.DBs.RuntimeCache)
   238  	// open flushable DBs
   239  	dbs, err := MakeMultiProducer(runtimeProducers, runtimeScopedProducers, cfg.DBs.Routing)
   240  	if err != nil {
   241  		return nil, nil, nil, nil, gossip.BlockProc{}, nil, err
   242  	}
   243  
   244  	gdb, cdb := getStores(dbs, cfg)
   245  	defer func() {
   246  		if err != nil {
   247  			gdb.Close()
   248  			cdb.Close()
   249  			dbs.Close()
   250  		}
   251  	}()
   252  
   253  	// compare genesis with the input
   254  	genesisID := gdb.GetGenesisID()
   255  	if genesisID == nil {
   256  		err = errors.New("malformed chainstore: genesis ID is not written")
   257  		return nil, nil, nil, nil, gossip.BlockProc{}, dbs.Close, err
   258  	}
   259  	if g != nil {
   260  		if *genesisID != g.GenesisID {
   261  			err = &GenesisMismatchError{*genesisID, g.GenesisID}
   262  			return nil, nil, nil, nil, gossip.BlockProc{}, dbs.Close, err
   263  		}
   264  	}
   265  
   266  	engine, vecClock, blockProc, err := rawMakeEngine(gdb, cdb, nil, cfg)
   267  	if err != nil {
   268  		err = fmt.Errorf("failed to make engine: %v", err)
   269  		return nil, nil, nil, nil, gossip.BlockProc{}, dbs.Close, err
   270  	}
   271  
   272  	if genesisProc {
   273  		err = gdb.Commit()
   274  		if err != nil {
   275  			err = fmt.Errorf("failed to commit DBs: %v", err)
   276  			return nil, nil, nil, nil, gossip.BlockProc{}, dbs.Close, err
   277  		}
   278  	}
   279  
   280  	return engine, vecClock, gdb, cdb, blockProc, dbs.Close, nil
   281  }
   282  
   283  // MakeEngine makes consensus engine from config.
   284  func MakeEngine(chaindataDir string, g *genesis.Genesis, cfg Configs) (*consensus.Consensus, *vecmt.Index, *gossip.Store, *consensus.Store, gossip.BlockProc, func() error) {
   285  	// Legacy DBs migrate
   286  	if cfg.DBs.MigrationMode != "reformat" && cfg.DBs.MigrationMode != "rebuild" && cfg.DBs.MigrationMode != "" {
   287  		utils.Fatalf("MigrationMode must be 'reformat' or 'rebuild'")
   288  	}
   289  	if !isEmpty(path.Join(chaindataDir, "gossip")) {
   290  		MakeDBDirs(chaindataDir)
   291  		genesisProducers, _ := SupportedDBs(chaindataDir, cfg.DBs.GenesisCache)
   292  		dbs, err := MakeDirectMultiProducer(genesisProducers, cfg.DBs.Routing)
   293  		if err != nil {
   294  			utils.Fatalf("Failed to make engine: %v", err)
   295  		}
   296  		err = migrateLegacyDBs(chaindataDir, dbs, cfg.DBs.MigrationMode, cfg.DBs.Routing)
   297  		_ = dbs.Close()
   298  		if err != nil {
   299  			utils.Fatalf("Failed to migrate state: %v", err)
   300  		}
   301  	}
   302  
   303  	dropAllDBsIfInterrupted(chaindataDir)
   304  	firstLaunch := isEmpty(chaindataDir)
   305  	MakeDBDirs(chaindataDir)
   306  
   307  	engine, vecClock, gdb, cdb, blockProc, closeDBs, err := makeEngine(chaindataDir, g, firstLaunch, cfg)
   308  	if err != nil {
   309  		if firstLaunch {
   310  			dropAllDBs(chaindataDir)
   311  		}
   312  		utils.Fatalf("Failed to make engine: %v", err)
   313  	}
   314  
   315  	rules := gdb.GetRules()
   316  	genesisID := gdb.GetGenesisID()
   317  	if firstLaunch {
   318  		log.Info("Applied genesis state", "name", rules.Name, "id", rules.NetworkID, "genesis", genesisID.String())
   319  	} else {
   320  		log.Info("Genesis is already written", "name", rules.Name, "id", rules.NetworkID, "genesis", genesisID.String())
   321  	}
   322  
   323  	return engine, vecClock, gdb, cdb, blockProc, closeDBs
   324  }
   325  
   326  // SetAccountKey sets key into accounts manager and unlocks it with pswd.
   327  func SetAccountKey(
   328  	am *accounts.Manager, key *ecdsa.PrivateKey, pswd string,
   329  ) (
   330  	acc accounts.Account,
   331  ) {
   332  	kss := am.Backends(keystore.KeyStoreType)
   333  	if len(kss) < 1 {
   334  		log.Crit("Keystore is not found")
   335  		return
   336  	}
   337  	ks := kss[0].(*keystore.KeyStore)
   338  
   339  	acc = accounts.Account{
   340  		Address: crypto.PubkeyToAddress(key.PublicKey),
   341  	}
   342  
   343  	imported, err := ks.ImportECDSA(key, pswd)
   344  	if err == nil {
   345  		acc = imported
   346  	} else if err.Error() != "account already exists" {
   347  		log.Crit("Failed to import key", "err", err)
   348  	}
   349  
   350  	err = ks.Unlock(acc, pswd)
   351  	if err != nil {
   352  		log.Crit("failed to unlock key", "err", err)
   353  	}
   354  
   355  	return
   356  }