diff --git a/go.mod b/go.mod index 5258f34..27d3d52 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( cosmossdk.io/math v1.5.3 cosmossdk.io/store v1.1.2 github.com/cometbft/cometbft v0.38.17 + github.com/cometbft/cometbft-db v0.14.1 github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/cosmos-sdk v0.50.13 github.com/cosmos/gogoproto v1.7.0 @@ -87,7 +88,6 @@ require ( github.com/cockroachdb/pebble v1.1.2 // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/cometbft/cometbft-db v0.14.1 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect diff --git a/server/commands.go b/server/init_cmd.go similarity index 100% rename from server/commands.go rename to server/init_cmd.go diff --git a/server/migration_cmd.go b/server/migration_cmd.go new file mode 100644 index 0000000..8f3c545 --- /dev/null +++ b/server/migration_cmd.go @@ -0,0 +1,247 @@ +package server + +import ( + "bytes" + "context" + "errors" + "fmt" + "path/filepath" + + dbm "github.com/cometbft/cometbft-db" + abci "github.com/cometbft/cometbft/abci/types" + cometbftcmd "github.com/cometbft/cometbft/cmd/cometbft/commands" + cfg "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/libs/os" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cometbft/cometbft/state" + "github.com/cometbft/cometbft/store" + cometbfttypes "github.com/cometbft/cometbft/types" + "github.com/spf13/cobra" + + rollkitstore "github.com/rollkit/rollkit/pkg/store" + rollkittypes "github.com/rollkit/rollkit/types" +) + +var flagDaHeight = "da-height" + +// MigrateToRollkitCmd returns a command that migrates the data from the CometBFT chain to Rollkit. +func MigrateToRollkitCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "rollkit-migration", + Short: "Migrate the data from the CometBFT chain to Rollkit", + Long: "Migrate the data from the CometBFT chain to Rollkit. This command should be used to migrate nodes or the sequencer.", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + config, err := cometbftcmd.ParseConfig(cmd) + if err != nil { + return err + } + + cometBlockStore, cometStateStore, err := loadStateAndBlockStore(config) + if err != nil { + return err + } + + cometBFTstate, err := cometStateStore.Load() + if err != nil { + return err + } + + lastBlockHeight := cometBFTstate.LastBlockHeight + + cmd.Printf("Last block height: %d\n", lastBlockHeight) + + rollkitStore, err := loadRollkitStateStore(config.RootDir, config.DBPath) + if err != nil { + return err + } + + daHeight, err := cmd.Flags().GetUint64(flagDaHeight) + if err != nil { + return fmt.Errorf("error reading %s flag: %w", flagDaHeight, err) + } + rollkitState, err := rollkitStateFromCometBFTState(cometBFTstate, daHeight) + if err != nil { + return err + } + + if err = rollkitStore.UpdateState( + context.Background(), rollkitState, + ); err != nil { + return err + } + + // migrate all the blocks from the CometBFT block store to the rollkit store + // the migration is done in reverse order, starting from the last block + for height := lastBlockHeight; height > 0; height-- { + cmd.Printf("Migrating block %d...\n", height) + + block := cometBlockStore.LoadBlock(height) + if block == nil { + cmd.Printf("Block %d not found in CometBFT block store, skipping...\n", height) + continue + } + + header, data, signature := cometBlockToRollkit(block) + + if err = rollkitStore.SaveBlockData(context.Background(), header, data, &signature); err != nil { + return err + } + + // Only save extended commit info if vote extensions are enabled + if enabled := cometBFTstate.ConsensusParams.ABCI.VoteExtensionsEnabled(block.Height); enabled { + extendedCommit := cometBlockStore.LoadBlockExtendedCommit(lastBlockHeight) + + extendedCommitInfo := abci.ExtendedCommitInfo{ + Round: extendedCommit.Round, + } + + for _, vote := range extendedCommit.ToExtendedVoteSet("", cometBFTstate.LastValidators).List() { + power := int64(0) + for _, v := range cometBFTstate.LastValidators.Validators { + if bytes.Equal(v.Address.Bytes(), vote.ValidatorAddress) { + power = v.VotingPower + break + } + } + + extendedCommitInfo.Votes = append(extendedCommitInfo.Votes, abci.ExtendedVoteInfo{ + Validator: abci.Validator{ + Address: vote.ValidatorAddress, + Power: power, + }, + VoteExtension: vote.Extension, + ExtensionSignature: vote.ExtensionSignature, + BlockIdFlag: cmtproto.BlockIDFlag(vote.CommitSig().BlockIDFlag), + }) + } + + _ = extendedCommitInfo + // rollkitStore.SaveExtendedCommit(context.Background(), header.Height(), &extendedCommitInfo) + panic("Saving extended commit info is not implemented yet") + } + + cmd.Println("Block", height, "migrated") + } + + cmd.Println("Migration completed successfully") + return errors.Join(rollkitStore.Close(), cometBlockStore.Close(), cometStateStore.Close()) + }, + } + + cmd.Flags().Uint64(flagDaHeight, 1, "The DA height to set in the Rollkit state. Defaults to 1.") + + return cmd +} + +// cometBlockToRollkit converts a cometBFT block to a rollkit block +func cometBlockToRollkit(block *cometbfttypes.Block) (*rollkittypes.SignedHeader, *rollkittypes.Data, rollkittypes.Signature) { + var ( + header *rollkittypes.SignedHeader + data *rollkittypes.Data + signature rollkittypes.Signature + ) + + // find proposer signature + for _, sig := range block.LastCommit.Signatures { + if bytes.Equal(sig.ValidatorAddress.Bytes(), block.ProposerAddress.Bytes()) { + signature = sig.Signature + break + } + } + + header = &rollkittypes.SignedHeader{ + Header: rollkittypes.Header{ + BaseHeader: rollkittypes.BaseHeader{ + Height: uint64(block.Height), + Time: uint64(block.Time.UnixNano()), + ChainID: block.ChainID, + }, + Version: rollkittypes.Version{ + Block: block.Version.Block, + App: block.Version.App, + }, + LastHeaderHash: block.LastBlockID.Hash.Bytes(), + LastCommitHash: block.LastCommitHash.Bytes(), + DataHash: block.DataHash.Bytes(), + ConsensusHash: block.ConsensusHash.Bytes(), + AppHash: block.AppHash.Bytes(), + LastResultsHash: block.LastResultsHash.Bytes(), + ValidatorHash: block.ValidatorsHash.Bytes(), + ProposerAddress: block.ProposerAddress.Bytes(), + }, + Signature: signature, // TODO: figure out this. + } + + data = &rollkittypes.Data{ + Metadata: &rollkittypes.Metadata{ + ChainID: block.ChainID, + Height: uint64(block.Height), + Time: uint64(block.Time.UnixNano()), + LastDataHash: block.DataHash.Bytes(), + }, + } + + for _, tx := range block.Txs { + data.Txs = append(data.Txs, rollkittypes.Tx(tx)) + } + + return header, data, signature +} + +func loadStateAndBlockStore(config *cfg.Config) (*store.BlockStore, state.Store, error) { + dbType := dbm.BackendType(config.DBBackend) + + if !os.FileExists(filepath.Join(config.DBDir(), "blockstore.db")) { + return nil, nil, fmt.Errorf("no blockstore found in %v", config.DBDir()) + } + + // Get BlockStore + blockStoreDB, err := dbm.NewDB("blockstore", dbType, config.DBDir()) + if err != nil { + return nil, nil, err + } + blockStore := store.NewBlockStore(blockStoreDB) + + if !os.FileExists(filepath.Join(config.DBDir(), "state.db")) { + return nil, nil, fmt.Errorf("no statestore found in %v", config.DBDir()) + } + + // Get StateStore + stateDB, err := dbm.NewDB("state", dbType, config.DBDir()) + if err != nil { + return nil, nil, err + } + stateStore := state.NewStore(stateDB, state.StoreOptions{ + DiscardABCIResponses: config.Storage.DiscardABCIResponses, + }) + + return blockStore, stateStore, nil +} + +func loadRollkitStateStore(rootDir, dbPath string) (rollkitstore.Store, error) { + baseKV, err := rollkitstore.NewDefaultKVStore(rootDir, dbPath, "rollkit") + if err != nil { + return nil, err + } + + return rollkitstore.New(baseKV), nil +} + +func rollkitStateFromCometBFTState(cometBFTState state.State, daHeight uint64) (rollkittypes.State, error) { + return rollkittypes.State{ + Version: rollkittypes.Version{ + Block: cometBFTState.Version.Consensus.Block, + App: cometBFTState.Version.Consensus.App, + }, + ChainID: cometBFTState.ChainID, + InitialHeight: uint64(cometBFTState.LastBlockHeight), // The initial height is the migration height + LastBlockHeight: uint64(cometBFTState.LastBlockHeight), + LastBlockTime: cometBFTState.LastBlockTime, + + DAHeight: daHeight, + + LastResultsHash: cometBFTState.LastResultsHash, + AppHash: cometBFTState.AppHash, + }, nil +}