diff --git a/cmd/kwil-admin/cmds/migration/genesis.go b/cmd/kwil-admin/cmds/migration/genesis.go index 131fe635e..79624486f 100644 --- a/cmd/kwil-admin/cmds/migration/genesis.go +++ b/cmd/kwil-admin/cmds/migration/genesis.go @@ -12,6 +12,7 @@ import ( "github.com/kwilteam/kwil-db/cmd/kwil-admin/cmds/common" "github.com/kwilteam/kwil-db/common/chain" "github.com/kwilteam/kwil-db/core/types" + "github.com/kwilteam/kwil-db/internal/migrations" "github.com/kwilteam/kwil-db/internal/statesync" ) @@ -49,11 +50,18 @@ func genesisStateCmd() *cobra.Command { return display.PrintErr(cmd, err) } + // this check should change in every version: + // For backwards compatibility, we should be able to unmarshal structs from previous versions. + // Since v0.9 is our first time supporting migration, we only need to check for v0.9. + if metadata.Version != migrations.MigrationVersion { + return display.PrintErr(cmd, fmt.Errorf("genesis state download is incompatible. Received version: %d, supported versions: [%d]", metadata.Version, migrations.MigrationVersion)) + } + // If there is no active migration or if the migration has not started yet, return the migration state // indicating that there is no genesis state to download. if metadata.MigrationState.Status == types.NoActiveMigration || metadata.MigrationState.Status == types.MigrationNotStarted || - metadata.GenesisConfig == nil || metadata.SnapshotMetadata == nil { + metadata.GenesisInfo == nil || metadata.SnapshotMetadata == nil { return display.PrintCmd(cmd, &MigrationState{ Info: metadata.MigrationState, }) @@ -69,18 +77,33 @@ func genesisStateCmd() *cobra.Command { return display.PrintErr(cmd, err) } - // retrieve the genesis config - var genCfg chain.GenesisConfig - if err = json.Unmarshal(metadata.GenesisConfig, &genCfg); err != nil { - return display.PrintErr(cmd, err) - } - // retrieve the snapshot metadata var snapshotMetadata statesync.Snapshot if err = json.Unmarshal(metadata.SnapshotMetadata, &snapshotMetadata); err != nil { return display.PrintErr(cmd, err) } + genCfg := chain.GenesisConfig{ + DataAppHash: metadata.GenesisInfo.AppHash, + InitialHeight: metadata.MigrationState.StartHeight, + ConsensusParams: &chain.ConsensusParams{ + BaseConsensusParams: chain.BaseConsensusParams{ + Migration: chain.MigrationParams{ + StartHeight: metadata.MigrationState.StartHeight, + EndHeight: metadata.MigrationState.EndHeight, + }, + }, + }, + } + + for _, nv := range metadata.GenesisInfo.Validators { + genCfg.Validators = append(genCfg.Validators, &chain.GenesisValidator{ + Name: nv.Name, + PubKey: nv.PubKey, + Power: nv.Power, + }) + } + // Print the genesis state to the genesis.json file genesisFile := filepath.Join(expandedDir, genesisFileName) err = genCfg.SaveAs(genesisFile) diff --git a/cmd/kwil-admin/cmds/migration/list.go b/cmd/kwil-admin/cmds/migration/list.go index 772c53593..69a5d83e5 100644 --- a/cmd/kwil-admin/cmds/migration/list.go +++ b/cmd/kwil-admin/cmds/migration/list.go @@ -59,7 +59,6 @@ func (m *MigrationsList) MarshalText() ([]byte, error) { msg.WriteString(fmt.Sprintf("%s:\n", migration.ID)) msg.WriteString(fmt.Sprintf("\tactivationPeriod: %d\n", migration.ActivationPeriod)) msg.WriteString(fmt.Sprintf("\tmigrationDuration: %d\n", migration.Duration)) - msg.WriteString(fmt.Sprintf("\tchainID: %s\n", migration.ChainID)) msg.WriteString(fmt.Sprintf("\ttimestamp: %s\n", migration.Timestamp)) } return msg.Bytes(), nil diff --git a/cmd/kwil-admin/cmds/migration/propose.go b/cmd/kwil-admin/cmds/migration/propose.go index b42fe1365..c9d98ea1b 100644 --- a/cmd/kwil-admin/cmds/migration/propose.go +++ b/cmd/kwil-admin/cmds/migration/propose.go @@ -13,17 +13,20 @@ import ( ) var ( - proposeLong = "A Validator operator can submit a migration proposal using the `propose` subcommand. The migration proposal includes the new `chain-id`, `activation-period` and `duration`. This action will generate a migration resolution for the other validators to vote on. If a super-majority of validators approve the migration proposal, the migration will commence after the specified activation-period blocks from approval and will continue for the duration defined by duration blocks." + proposeLong = `A validator operator can submit a migration proposal using the ` + "`" + `propose` + "`" + ` subcommand. + +The migration proposal includes the ` + "`" + `activation-period` + "`" + ` and ` + "`" + `duration` + "`" + `. This will generate a migration resolution +for the other validators to vote on. If a super-majority of validators approve the migration proposal, the migration will +commence after the specified activation-period blocks from approval and will continue for the duration defined by duration blocks.` - proposeExample = `# Submit a migration proposal to migrate to a new chain "kwil-chain-new" with activation period 1000 and migration duration of 14400 blocks. -kwil-admin migrate propose --activation-period 1000 --duration 14400 --chain-id kwil-chain-new + proposeExample = `# Submit a migration proposal to migrate to a new chain with activation period 1000 and migration duration of 14400 blocks. +kwil-admin migrate propose --activation-period 1000 --duration 14400 (or) -kwil-admin migrate propose -a 1000 -d 14400 -c kwil-chain-new` +kwil-admin migrate propose -a 1000 -d 14400` ) func proposeCmd() *cobra.Command { var activationPeriod, migrationDuration uint64 - var chainID string cmd := &cobra.Command{ Use: "propose", @@ -42,14 +45,9 @@ func proposeCmd() *cobra.Command { return display.PrintErr(cmd, errors.New("start-height and migration duration must be greater than 0")) } - if chainID == "" { - return display.PrintErr(cmd, errors.New("chain-id configuration is not set")) - } - proposal := migrations.MigrationDeclaration{ ActivationPeriod: activationPeriod, Duration: migrationDuration, - ChainID: chainID, Timestamp: time.Now().String(), } proposalBts, err := proposal.MarshalBinary() @@ -70,6 +68,5 @@ func proposeCmd() *cobra.Command { cmd.Flags().Uint64VarP(&activationPeriod, "activation-period", "a", 0, "The number of blocks before the migration is activated since the approval of the proposal.") cmd.Flags().Uint64VarP(&migrationDuration, "duration", "d", 0, "The duration of the migration.") - cmd.Flags().StringVarP(&chainID, "chain-id", "c", "", "The chain ID of the migration.") return cmd } diff --git a/cmd/kwil-admin/cmds/setup/genesis.go b/cmd/kwil-admin/cmds/setup/genesis.go index a474d3efa..8f5d69711 100644 --- a/cmd/kwil-admin/cmds/setup/genesis.go +++ b/cmd/kwil-admin/cmds/setup/genesis.go @@ -21,9 +21,6 @@ var ( This command creates a new genesis file with optionally specified modifications. -If the ` + "`" + `--migration` + "`" + ` flag is set, an incomplete genesis file is generated that can be used in a zero -downtime migration. If generating a migration genesis file, validators and initial state cannot be set. - Validators, balance allocations, and forks should have the format "name:key:power", "address:balance", and "name:height" respectively.` @@ -34,16 +31,13 @@ kwil-admin setup genesis kwil-admin setup genesis --out /path/to/directory --chain-id mychainid --validator my_validator:890fe7ae9cb1fa6177555d5651e1b8451b4a9c64021c876236c700bc2690ff1d:1 # Create a new genesis.json with the specified allocation -kwil-admin setup genesis --alloc 0x7f5f4552091a69125d5dfcb7b8c2659029395bdf:100 - -# Create a new genesis.json file to be used in a network migration -kwil-admin setup genesis --migration --out /path/to/directory --chain-id mychainid` +kwil-admin setup genesis --alloc 0x7f5f4552091a69125d5dfcb7b8c2659029395bdf:100` ) func genesisCmd() *cobra.Command { var validators, allocs, forks []string var chainID, output, genesisState string - var migration, withGasCosts bool + var withGasCosts bool var maxBytesPerBlock, joinExpiry, voteExpiry, maxVotesPerBlock int64 cmd := &cobra.Command{ Use: "genesis", @@ -69,10 +63,6 @@ func genesisCmd() *cobra.Command { genesisInfo := chain.DefaultGenesisConfig() if cmd.Flags().Changed(validatorsFlag) { - if migration { - return makeErr(errors.New("cannot set validators when generating a migration genesis file")) - } - for _, v := range validators { parts := strings.Split(v, ":") if len(parts) != 3 { @@ -134,17 +124,9 @@ func genesisCmd() *cobra.Command { } } if cmd.Flags().Changed(chainIDFlag) { - if migration { - return makeErr(errors.New("cannot set chain ID when generating a migration genesis file")) - } - genesisInfo.ChainID = chainID } if cmd.Flags().Changed(genesisStateFlag) { - if migration { - return makeErr(errors.New("cannot set genesis state when generating a migration genesis file")) - } - apphash, err := appHashFromSnapshotFile(genesisState) if err != nil { return makeErr(err) @@ -166,12 +148,6 @@ func genesisCmd() *cobra.Command { genesisInfo.ConsensusParams.Votes.MaxVotesPerTx = maxVotesPerBlock } - if migration { - genesisInfo.Validators = nil - genesisInfo.Alloc = nil - genesisInfo.ForkHeights = nil - } - existingFile, err := os.Stat(out) if err == nil && existingFile.IsDir() { return makeErr(fmt.Errorf("a directory already exists at %s, please remove it first", out)) @@ -198,7 +174,6 @@ func genesisCmd() *cobra.Command { cmd.Flags().Int64Var(&joinExpiry, joinExpiryFlag, 0, "Number of blocks before a join proposal expires") cmd.Flags().Int64Var(&voteExpiry, voteExpiryFlag, 0, "Number of blocks before a vote proposal expires") cmd.Flags().Int64Var(&maxVotesPerBlock, maxVotesPerTxFlag, 0, "Maximum number of votes per validator transaction (each validator has 1 validator tx per block)") - cmd.Flags().BoolVar(&migration, migrationFlag, false, "Generate an incomplete genesis file for zero downtime migration") cmd.Flags().StringVar(&genesisState, genesisStateFlag, "", "Path to a genesis state file") return cmd diff --git a/cmd/kwild/server/migration.go b/cmd/kwild/server/migration.go index ccf603623..7ba06b94f 100644 --- a/cmd/kwild/server/migration.go +++ b/cmd/kwild/server/migration.go @@ -21,6 +21,7 @@ import ( "github.com/kwilteam/kwil-db/core/log" "github.com/kwilteam/kwil-db/core/types" "github.com/kwilteam/kwil-db/internal/abci/cometbft" + "github.com/kwilteam/kwil-db/internal/migrations" "github.com/kwilteam/kwil-db/internal/statesync" ) @@ -60,14 +61,33 @@ func PrepareForMigration(ctx context.Context, kwildCfg *commonCfg.KwildConfig, g logger.Info("Entering migration mode", log.String("migrate_from", kwildCfg.MigrationConfig.MigrateFrom)) snapshotFileName := config.GenesisStateFileName(kwildCfg.RootDir) - // check if genesis hash is set in the genesis config - if genesisCfg.DataAppHash != nil && - genesisCfg.ConsensusParams.Migration.StartHeight != 0 && - genesisCfg.ConsensusParams.Migration.EndHeight != 0 && - validateGenesisState(snapshotFileName, genesisCfg.DataAppHash) { - // genesis state already downloaded. No need to poll for genesis state + + // if the genesis state is already downloaded, then no need to poll for genesis state + _, err := os.Stat(snapshotFileName) + if err == nil { logger.Info("Genesis state already downloaded", log.String("genesis snapshot", snapshotFileName)) + + if err := validateGenesisState(snapshotFileName, genesisCfg.DataAppHash); err != nil { + return nil, nil, err + } + return kwildCfg, genesisCfg, nil + } else if !os.IsNotExist(err) { + return nil, nil, fmt.Errorf("failed to check genesis state file: %w", err) + } + + // if we reach here, then we still need to download the genesis state + // Therefore, the genesis app hash, initial height, and migration info + // should not already be set in the genesis config. + if len(genesisCfg.DataAppHash) != 0 { + return nil, nil, errors.New("migration genesis config should not have app hash set") + } + if genesisCfg.InitialHeight != 0 && genesisCfg.InitialHeight != 1 { + // we are forcing users to adopt the height provided by the old chain + return nil, nil, errors.New("migration genesis config should not have initial height set") + } + if genesisCfg.ConsensusParams.Migration.IsMigration() { + return nil, nil, errors.New("migration genesis config should not have migration info set") } // old chain client @@ -124,21 +144,23 @@ func (m *migrationClient) downloadGenesisState(ctx context.Context) error { return err } + // this check should change in every version: + // For backwards compatibility, we should be able to unmarshal structs from previous versions. + // Since v0.9 is our first time supporting migration, we only need to check for v0.9. + if metadata.Version != migrations.MigrationVersion { + return fmt.Errorf("genesis state download is incompatible. Received version: %d, supported versions: [%d]", metadata.Version, migrations.MigrationVersion) + } + // Check if the genesis state is ready if metadata.MigrationState.Status == types.NoActiveMigration || metadata.MigrationState.Status == types.MigrationNotStarted { return fmt.Errorf("status %s", metadata.MigrationState.Status.String()) } // Genesis state should ready - if metadata.SnapshotMetadata == nil || metadata.GenesisConfig == nil { + if metadata.SnapshotMetadata == nil || metadata.GenesisInfo == nil { return errors.New("genesis state not available") } - var genCfg chain.GenesisConfig - if err := json.Unmarshal(metadata.GenesisConfig, &genCfg); err != nil { - return fmt.Errorf("failed to unmarshal genesis config: %w", err) - } - // Save the genesis state var snapshotMetadata statesync.Snapshot if err := json.Unmarshal(metadata.SnapshotMetadata, &snapshotMetadata); err != nil { @@ -169,11 +191,27 @@ func (m *migrationClient) downloadGenesisState(ctx context.Context) error { } // Update the genesis config - m.genesisCfg.DataAppHash = genCfg.DataAppHash - m.genesisCfg.Validators = genCfg.Validators - m.genesisCfg.ConsensusParams.Migration = genCfg.ConsensusParams.Migration + m.genesisCfg.DataAppHash = metadata.GenesisInfo.AppHash + m.genesisCfg.ConsensusParams.Migration = chain.MigrationParams{ + StartHeight: metadata.MigrationState.StartHeight, + EndHeight: metadata.MigrationState.EndHeight, + } m.genesisCfg.InitialHeight = metadata.MigrationState.StartHeight + // if validators are not set in the genesis config, then set them. + // Otherwise, ignore the validators from the old chain. + if len(m.genesisCfg.Validators) == 0 { + for _, v := range metadata.GenesisInfo.Validators { + m.genesisCfg.Validators = append(m.genesisCfg.Validators, &chain.GenesisValidator{ + Name: v.Name, + PubKey: v.PubKey, + Power: v.Power, + }) + } + } else { + m.logger.Warn("Validators already set in the genesis config. Ignoring the validators from the old chain") + } + // persist the genesis config if err := m.genesisCfg.SaveAs(filepath.Join(m.kwildCfg.RootDir, cometbft.GenesisJSONName)); err != nil { return fmt.Errorf("failed to save genesis config: %w", err) @@ -191,30 +229,34 @@ func (m *migrationClient) downloadGenesisState(ctx context.Context) error { return nil } -func validateGenesisState(filename string, appHash []byte) bool { - // check if the genesis state file exists - if _, err := os.Stat(filename); os.IsNotExist(err) { - return false - } +// validateGenesisState validates the genesis state file against the app hash. +// It is the caller's responsibility to check if the file exists. +func validateGenesisState(filename string, appHash []byte) error { + // we don't need to check if the file exists since the caller should have already checked it genesisStateFile, err := os.Open(filename) if err != nil { - return false + return fmt.Errorf("failed to open genesis state file: %w", err) } // gzip reader and hash reader gzipReader, err := gzip.NewReader(genesisStateFile) if err != nil { - failBuild(err, "failed to create gzip reader") + return fmt.Errorf("failed to create gzip reader: %w", err) } defer gzipReader.Close() hasher := sha256.New() _, err = io.Copy(hasher, gzipReader) if err != nil { - return false + return fmt.Errorf("failed to hash genesis state file: %w", err) } hash := hasher.Sum(nil) - return appHash != nil && len(hash) == len(appHash) && bytes.Equal(hash, appHash) + + if !bytes.Equal(hash, appHash) { + return errors.New("app hash does not match the genesis state") + } + + return nil } diff --git a/common/chain/chaincfg.go b/common/chain/chaincfg.go index 2fed0aa2d..fc4d36374 100644 --- a/common/chain/chaincfg.go +++ b/common/chain/chaincfg.go @@ -135,6 +135,11 @@ type MigrationParams struct { EndHeight int64 `json:"end_height,omitempty"` } +// IsMigration returns true if the migration parameters are set. +func (m *MigrationParams) IsMigration() bool { + return m.StartHeight != 0 && m.EndHeight != 0 +} + func defaultConsensusParams() *ConsensusParams { return &ConsensusParams{ BaseConsensusParams: BaseConsensusParams{ diff --git a/core/types/types.go b/core/types/types.go index 80f05a4e1..c72400806 100644 --- a/core/types/types.go +++ b/core/types/types.go @@ -87,7 +87,6 @@ type Migration struct { ID *UUID `json:"id"` // ID is the UUID of the migration resolution/proposal ActivationPeriod int64 `json:"activation_height"` // ActivationPeriod is the amount of blocks before the migration is activated. Duration int64 `json:"migration_duration"` // MigrationDuration is the duration of the migration process starting from the ActivationHeight - ChainID string `json:"chain_id"` // ChainID of the new network Timestamp string `json:"timestamp"` // Timestamp when the migration was proposed } @@ -126,8 +125,26 @@ type MigrationState struct { // for the migration. type MigrationMetadata struct { MigrationState MigrationState `json:"migration_state"` // MigrationState is the current state of the migration - GenesisConfig []byte `json:"genesis_config"` // GenesisConfig is the genesis file data + GenesisInfo *GenesisInfo `json:"genesis_info"` // GenesisInfo is the genesis information SnapshotMetadata []byte `json:"snapshot_metadata"` // SnapshotMetadata is the snapshot metadata + Version int `json:"version"` // Version of the migration metadata +} + +// GenesisInfo holds the genesis information that the new network should use +// in its genesis file. +type GenesisInfo struct { + // AppHash is the application hash of the old network at the StartHeight + AppHash HexBytes `json:"app_hash"` + // Validators is the list of validators that the new network should start with + Validators []*NamedValidator `json:"validators"` +} + +// NamedValidator is a validator with a name. +// Since CometBFT assigns validators human-readable names, this struct +// is used to represent a validator with its name that will be used in the genesis file. +type NamedValidator struct { + Name string `json:"name"` + Validator `json:"validator"` } // ServiceMode describes the operating mode of the user service. Namely, if the diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go index c409c9933..df94093c5 100644 --- a/internal/migrations/migrations.go +++ b/internal/migrations/migrations.go @@ -42,6 +42,8 @@ func init() { } } +const MigrationVersion int = 0 + // MigrationDeclaration creates a new migration. It is used to agree on terms of a migration, // and is voted on using Kwil's vote store. type MigrationDeclaration struct { @@ -51,10 +53,6 @@ type MigrationDeclaration struct { ActivationPeriod uint64 // Duration is the amount of blocks the migration will take to complete. Duration uint64 - // ChainID is the new chain ID that the network will migrate to. - // A new chain ID should always be used for a new network, to avoid - // cross-network replay attacks. - ChainID string // Timestamp is the time the migration was created. It is set by the migration // creator. The primary purpose of it is to guarantee uniqueness of the serialized // MigrationDeclaration, since that is a requirement for the voting system. @@ -116,7 +114,6 @@ func (m *Migrator) startMigration(ctx context.Context, app *common.App, resoluti active := &activeMigration{ StartHeight: block.Height + activationPeriod, EndHeight: block.Height + activationPeriod + dur, - ChainID: mig.ChainID, } err = createMigration(ctx, app.DB, active) diff --git a/internal/migrations/migrator.go b/internal/migrations/migrator.go index 793b3bf5c..5cbd6780a 100644 --- a/internal/migrations/migrator.go +++ b/internal/migrations/migrator.go @@ -9,7 +9,6 @@ import ( "os" "path/filepath" "sync" - "time" cmtTypes "github.com/cometbft/cometbft/types" "github.com/kwilteam/kwil-db/common" @@ -18,8 +17,6 @@ import ( "github.com/kwilteam/kwil-db/core/log" "github.com/kwilteam/kwil-db/core/types" "github.com/kwilteam/kwil-db/core/types/serialize" - "github.com/kwilteam/kwil-db/internal/abci/cometbft" - "github.com/kwilteam/kwil-db/internal/abci/meta" "github.com/kwilteam/kwil-db/internal/sql/pg" "github.com/kwilteam/kwil-db/internal/sql/versioning" "github.com/kwilteam/kwil-db/internal/txapp" @@ -46,7 +43,6 @@ var ( } metadataFileName = "metadata.json" - genesisFileName = "genesis.json" ) const ( @@ -116,8 +112,6 @@ type activeMigration struct { StartHeight int64 // EndHeight is the height at which the migration ends. EndHeight int64 - // ChainID is the chain ID of the migration. - ChainID string } // SetupMigrator initializes the migrator instance with the necessary dependencies. @@ -246,12 +240,6 @@ func (m *Migrator) NotifyHeight(ctx context.Context, block *common.BlockContext, return err } - // Retrieve the consensus params stored in the DB - cfgParams, err := meta.LoadParams(ctx, tx) - if err != nil { - return err - } - // Retrieve snapshot hash snapshots := m.snapshotter.ListSnapshots() if len(snapshots) == 0 { @@ -270,14 +258,14 @@ func (m *Migrator) NotifyHeight(ctx context.Context, block *common.BlockContext, } } - go m.generateGenesisConfig(ctx, cfgParams, snapshots[0].SnapshotHash, genesisVals, m.Logger) + go m.generateGenesisConfig(ctx, snapshots[0].SnapshotHash, genesisVals, m.Logger) } if block.Height == m.activeMigration.EndHeight { // starting from here, no more transactions of any kind will be accepted or mined. block.ChainContext.NetworkParameters.MigrationStatus = types.MigrationCompleted m.halted = true - m.Logger.Info("migration to chain completed, no new transactions will be accepted", log.String("ChainID", m.activeMigration.ChainID)) + m.Logger.Info("migration to chain completed, no new transactions will be accepted") } // wait for signal on doneChan, indicating that all changesets have been written to disk @@ -299,7 +287,7 @@ func (m *Migrator) NotifyHeight(ctx context.Context, block *common.BlockContext, // This function is called only once at the start height of the migration. // It is run asynchronously as we don't have access to the cometbft's state during replay. // Therefore we need to wait for the consensus params fn to be set before we can generate the genesis file. -func (m *Migrator) generateGenesisConfig(ctx context.Context, cfgParams *common.NetworkParameters, snapshotHash []byte, genesisValidators []*chain.GenesisValidator, logger log.Logger) { +func (m *Migrator) generateGenesisConfig(ctx context.Context, snapshotHash []byte, genesisValidators []*chain.GenesisValidator, logger log.Logger) { // block until the m.consensusParamsFn is closed <-m.consensusParamsFnChan @@ -309,7 +297,7 @@ func (m *Migrator) generateGenesisConfig(ctx context.Context, cfgParams *common. return } - logger.Info("generating genesis config for the new chain", log.String("ChainID", m.activeMigration.ChainID)) + logger.Info("generating genesis config for the new chain") height := m.activeMigration.StartHeight - 1 consensusParmas := m.consensusParamsFn(ctx, &height) @@ -318,28 +306,37 @@ func (m *Migrator) generateGenesisConfig(ctx context.Context, cfgParams *common. return } - finalCfg := cometbft.MergeConsensusParams(consensusParmas, cfgParams) - // Migration Params - finalCfg.Migration.StartHeight = m.activeMigration.StartHeight - finalCfg.Migration.EndHeight = m.activeMigration.EndHeight + var genVals []*types.NamedValidator - genCfg := &chain.GenesisConfig{ - ChainID: m.activeMigration.ChainID, - GenesisTime: time.Now().Round(0).UTC(), - // Initial height set to 0 - DataAppHash: snapshotHash, - // Allocs are not needed, as the transfers are included in the snapshot - // forks can be dropped, as they maynot be relevant to the new chain - Validators: genesisValidators, - ConsensusParams: finalCfg, + for _, v := range genesisValidators { + genVals = append(genVals, &types.NamedValidator{ + Name: v.Name, + Validator: types.Validator{ + PubKey: v.PubKey, + Power: v.Power, + }, + }) } - // Save the genesis file - err := genCfg.SaveAs(formatGenesisFilename(m.dir)) + genInfo := &types.GenesisInfo{ + AppHash: snapshotHash, + Validators: genVals, + } + + bts, err := json.Marshal(genInfo) if err != nil { - logger.Error("failed to save genesis file", log.Error(err)) + logger.Error("failed to marshal genesis info", log.Error(err)) return } + + // Save the genesis info + err = os.WriteFile(formatGenesisInfoFileName(m.dir), bts, 0644) + if err != nil { + logger.Error("failed to save genesis info", log.Error(err)) + return + } + + logger.Info("genesis config generated successfully") } func (m *Migrator) PersistLastChangesetHeight(ctx context.Context, tx sql.Executor) error { @@ -380,6 +377,7 @@ func (m *Migrator) GetMigrationMetadata() (*types.MigrationMetadata, error) { MigrationState: types.MigrationState{ Status: types.NoActiveMigration, }, + Version: MigrationVersion, }, nil } @@ -391,6 +389,7 @@ func (m *Migrator) GetMigrationMetadata() (*types.MigrationMetadata, error) { StartHeight: m.activeMigration.StartHeight, EndHeight: m.activeMigration.EndHeight, }, + Version: MigrationVersion, }, nil } @@ -409,14 +408,15 @@ func (m *Migrator) GetMigrationMetadata() (*types.MigrationMetadata, error) { return nil, err } - genCfg, err := chain.LoadGenesisConfig(formatGenesisFilename(m.dir)) + // read the genesis config + genCfg, err := os.ReadFile(formatGenesisInfoFileName(m.dir)) if err != nil { return nil, err } - // serialize genesis config data - configBts, err := json.Marshal(genCfg) - if err != nil { + // unmarshal + var genesisInfo types.GenesisInfo + if err := json.Unmarshal(genCfg, &genesisInfo); err != nil { return nil, err } @@ -432,7 +432,8 @@ func (m *Migrator) GetMigrationMetadata() (*types.MigrationMetadata, error) { EndHeight: m.activeMigration.EndHeight, }, SnapshotMetadata: snapshotBts, - GenesisConfig: configBts, + GenesisInfo: &genesisInfo, + Version: MigrationVersion, }, nil } @@ -749,8 +750,8 @@ func formatChangesetMetadataFilename(mdir string, height int64) string { return filepath.Join(mdir, changesetsDirName, fmt.Sprintf("block-%d", height), metadataFileName) } -func formatGenesisFilename(mdir string) string { - return filepath.Join(mdir, genesisFileName) +func formatGenesisInfoFileName(mdir string) string { + return filepath.Join(mdir, "genesis_info.json") } // CleanupResolutionsAtStartup is called at startup to clean up the resolutions table. It does the below things: diff --git a/internal/migrations/sql.go b/internal/migrations/sql.go index 8e78c3049..3ff10778e 100644 --- a/internal/migrations/sql.go +++ b/internal/migrations/sql.go @@ -51,16 +51,15 @@ var ( tableMigrationsSQL = `CREATE TABLE IF NOT EXISTS ` + migrationsSchemaName + `.migration ( id INT PRIMARY KEY, start_height INT8 NOT NULL, - end_height INT8 NOT NULL, - chain_id TEXT NOT NULL + end_height INT8 NOT NULL )` // getMigrationSQL is the sql query used to get the current migration. - getMigrationSQL = `SELECT start_height, end_height, chain_id FROM ` + migrationsSchemaName + `.migration;` + getMigrationSQL = `SELECT start_height, end_height FROM ` + migrationsSchemaName + `.migration;` // migrationIsActiveSQL is the sql query used to check if a migration is active. migrationIsActiveSQL = `SELECT EXISTS(SELECT 1 FROM ` + migrationsSchemaName + `.migration);` // createMigrationSQL is the sql query used to create a new migration. - createMigrationSQL = `INSERT INTO ` + migrationsSchemaName + `.migration (id, start_height, end_height, chain_id) VALUES ($1, $2, $3, $4);` + createMigrationSQL = `INSERT INTO ` + migrationsSchemaName + `.migration (id, start_height, end_height) VALUES ($1, $2, $3);` lastStoreChangeset = `last_stored_changeset` @@ -108,11 +107,6 @@ func getMigrationState(ctx context.Context, db sql.Executor) (*activeMigration, return nil, fmt.Errorf("internal bug: duration is not an int64") } - md.ChainID, ok = row[2].(string) - if !ok { - return nil, fmt.Errorf("internal bug: chain ID is not a string") - } - return md, nil } @@ -139,7 +133,7 @@ func migrationActive(ctx context.Context, db sql.Executor) (bool, error) { // createMigration creates a new migration state in the database. func createMigration(ctx context.Context, db sql.Executor, md *activeMigration) error { - _, err := db.Execute(ctx, createMigrationSQL, 1, md.StartHeight, md.EndHeight, md.ChainID) + _, err := db.Execute(ctx, createMigrationSQL, 1, md.StartHeight, md.EndHeight) return err } diff --git a/internal/services/jsonrpc/usersvc/service.go b/internal/services/jsonrpc/usersvc/service.go index 67142563b..5d2d84aab 100644 --- a/internal/services/jsonrpc/usersvc/service.go +++ b/internal/services/jsonrpc/usersvc/service.go @@ -936,7 +936,6 @@ func (svc *Service) ListPendingMigrations(ctx context.Context, req *userjson.Lis ID: res.ID, ActivationPeriod: (int64)(mig.ActivationPeriod), Duration: (int64)(mig.Duration), - ChainID: mig.ChainID, Timestamp: mig.Timestamp, }) } diff --git a/internal/statesync/snapshot.go b/internal/statesync/snapshot.go index ee805ab39..91bd5ed92 100644 --- a/internal/statesync/snapshot.go +++ b/internal/statesync/snapshot.go @@ -5,11 +5,12 @@ import ( "os" ) -// Snapshot is the header of a snapshot file representing the snapshot of the database at a certain height. -// It contains the height, format, chunk count, hash, size, and name of the snapshot. - const HashLen int = 32 +// Snapshot is the header of a snapshot file representing the snapshot of the database at a certain height. +// It contains the height, format, chunk count, hash, size, and name of the snapshot. +// WARNING: This struct CAN NOT be changed without breaking functionality, +// since it is used for communication between nodes. type Snapshot struct { Height uint64 `json:"height"` Format uint32 `json:"format"` diff --git a/test/driver/operator/cli_driver.go b/test/driver/operator/cli_driver.go index a293dace8..148008231 100644 --- a/test/driver/operator/cli_driver.go +++ b/test/driver/operator/cli_driver.go @@ -183,9 +183,9 @@ type respTxQuery struct { } `json:"tx_result"` } -func (o *OperatorCLIDriver) SubmitMigrationProposal(ctx context.Context, activationHeight, migrationDuration *big.Int, chainID string) ([]byte, error) { +func (o *OperatorCLIDriver) SubmitMigrationProposal(ctx context.Context, activationHeight, migrationDuration *big.Int) ([]byte, error) { var res display.TxHashResponse - err := o.runCommand(ctx, &res, "migrate", "propose", activationHeight.String(), migrationDuration.String(), chainID) + err := o.runCommand(ctx, &res, "migrate", "propose", "--activation-period", activationHeight.String(), "--duration", migrationDuration.String()) if err != nil { return nil, err } diff --git a/test/driver/operator/client_driver.go b/test/driver/operator/client_driver.go index 01ca69ea7..da5035d25 100644 --- a/test/driver/operator/client_driver.go +++ b/test/driver/operator/client_driver.go @@ -88,7 +88,7 @@ func (a *AdminClientDriver) ConnectedPeers(ctx context.Context) ([]string, error return peers, nil } -func (a *AdminClientDriver) SubmitMigrationProposal(ctx context.Context, activationPeriod, migrationDuration *big.Int, chainID string) ([]byte, error) { +func (a *AdminClientDriver) SubmitMigrationProposal(ctx context.Context, activationPeriod, migrationDuration *big.Int) ([]byte, error) { // return a.Client.SubmitMigrationProposal(ctx, activationHeight, migrationDuration, chainID) activationHeight := activationPeriod.Uint64() dur := migrationDuration.Uint64() @@ -96,7 +96,6 @@ func (a *AdminClientDriver) SubmitMigrationProposal(ctx context.Context, activat res := migrations.MigrationDeclaration{ ActivationPeriod: activationHeight, Duration: dur, - ChainID: chainID, Timestamp: time.Now().String(), } proposalBts, err := res.MarshalBinary() diff --git a/test/integration/kwild_test.go b/test/integration/kwild_test.go index 07b2febcc..d6449f5f5 100644 --- a/test/integration/kwild_test.go +++ b/test/integration/kwild_test.go @@ -979,7 +979,7 @@ func TestLongRunningNetworkMigrations(t *testing.T) { newDir := helper.MigrationSetup(ctx) // Trigger a network migration request - specifications.SubmitMigrationProposal(ctx, t, node0Driver, integration.MigrationChainID) + specifications.SubmitMigrationProposal(ctx, t, node0Driver) // node1 approves the migration and verifies that the migration is still pending specifications.ApproveMigration(ctx, t, node1Driver, true) diff --git a/test/specifications/dsl.go b/test/specifications/dsl.go index 5945b0460..a9b8bf79a 100644 --- a/test/specifications/dsl.go +++ b/test/specifications/dsl.go @@ -158,7 +158,7 @@ type PeersDsl interface { type MigrationOpsDsl interface { TxQueryDsl - SubmitMigrationProposal(ctx context.Context, activationHeight *big.Int, migrationDuration *big.Int, chainID string) ([]byte, error) + SubmitMigrationProposal(ctx context.Context, activationHeight *big.Int, migrationDuration *big.Int) ([]byte, error) ApproveMigration(ctx context.Context, migrationResolutionID *types.UUID) ([]byte, error) ListMigrations(ctx context.Context) ([]*types.Migration, error) GenesisState(ctx context.Context) (*types.MigrationMetadata, error) diff --git a/test/specifications/migration.go b/test/specifications/migration.go index 13bf96ac1..22cfab918 100644 --- a/test/specifications/migration.go +++ b/test/specifications/migration.go @@ -16,11 +16,11 @@ import ( ) // Trigger migration -func SubmitMigrationProposal(ctx context.Context, t *testing.T, netops MigrationOpsDsl, chainID string) { +func SubmitMigrationProposal(ctx context.Context, t *testing.T, netops MigrationOpsDsl) { t.Log("Executing migration trigger specification") // Trigger migration" - txHash, err := netops.SubmitMigrationProposal(ctx, big.NewInt(1), big.NewInt(100), chainID) + txHash, err := netops.SubmitMigrationProposal(ctx, big.NewInt(1), big.NewInt(100)) require.NoError(t, err) // Ensure that the Tx is mined.