From d58cc541a1725d556e7e57a4459ca8f0c70ff623 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:47:49 +0000 Subject: [PATCH 1/8] Initial plan From 48938db3548966fcec7f2f0e9aec8e614a09a635 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:10:21 +0000 Subject: [PATCH 2/8] Implement rollkitmngr state fetching in rollkit-migrate command Co-authored-by: julienrbrt <29894366+julienrbrt@users.noreply.github.com> --- server/migration_cmd.go | 102 ++++++++++++++++++++++++++++++++--- server/migration_cmd_test.go | 89 ++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 server/migration_cmd_test.go diff --git a/server/migration_cmd.go b/server/migration_cmd.go index 0b00329..988c138 100644 --- a/server/migration_cmd.go +++ b/server/migration_cmd.go @@ -20,6 +20,7 @@ import ( "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/store" cometbfttypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/types/module/testutil" ds "github.com/ipfs/go-datastore" ktds "github.com/ipfs/go-datastore/keytransform" "github.com/spf13/cobra" @@ -29,6 +30,8 @@ import ( rollkitstore "github.com/rollkit/rollkit/pkg/store" rollkittypes "github.com/rollkit/rollkit/types" + "github.com/rollkit/go-execution-abci/modules/rollkitmngr" + rollkitmngrTypes "github.com/rollkit/go-execution-abci/modules/rollkitmngr/types" "github.com/rollkit/go-execution-abci/pkg/adapter" ) @@ -370,14 +373,22 @@ func createRollkitMigrationGenesis(rootDir string, cometBFTState state.State) er sequencerPubKey crypto.PubKey ) - // use the first validator as sequencer (assuming single validator setup for migration) - if len(cometBFTState.LastValidators.Validators) == 1 { - sequencerAddr = cometBFTState.LastValidators.Validators[0].Address.Bytes() - sequencerPubKey = cometBFTState.LastValidators.Validators[0].PubKey + // If the cometbft validators are > 0, fetch the rollkitmngr state to get the sequencer + if len(cometBFTState.LastValidators.Validators) > 0 { + if sequencer, err := getSequencerFromRollkitMngrState(rootDir, cometBFTState); err == nil { + sequencerAddr = sequencer.Address + sequencerPubKey = sequencer.PubKey + } else { + // If rollkitmngr state is not available, fall back to single validator logic + if len(cometBFTState.LastValidators.Validators) == 1 { + sequencerAddr = cometBFTState.LastValidators.Validators[0].Address.Bytes() + sequencerPubKey = cometBFTState.LastValidators.Validators[0].PubKey + } else { + return fmt.Errorf("expected exactly one validator in the last validators, found %d. Unable to determine sequencer from rollkitmngr state: %v", len(cometBFTState.LastValidators.Validators), err) + } + } } else { - // TODO(@julienrbrt): Allow to use rollkitmngr state to get the sequencer address - // ref: https://github.com/rollkit/go-execution-abci/issues/164 - return fmt.Errorf("expected exactly one validator in the last validators, found %d", len(cometBFTState.LastValidators.Validators)) + return fmt.Errorf("no validators found in the last validators") } migrationGenesis := rollkitMigrationGenesis{ @@ -394,6 +405,7 @@ func createRollkitMigrationGenesis(rootDir string, cometBFTState state.State) er return fmt.Errorf("failed to marshal rollkit migration genesis: %w", err) } + const rollkitGenesisFilename = "rollkit_genesis.json" genesisPath := filepath.Join(rootDir, rollkitGenesisFilename) if err := os.WriteFile(genesisPath, genesisBytes, 0o644); err != nil { return fmt.Errorf("failed to write rollkit migration genesis to %s: %w", genesisPath, err) @@ -402,6 +414,82 @@ func createRollkitMigrationGenesis(rootDir string, cometBFTState state.State) er return nil } +// sequencerInfo holds the sequencer information extracted from rollkitmngr state +type sequencerInfo struct { + Address []byte + PubKey crypto.PubKey +} + +// getSequencerFromRollkitMngrState attempts to load the sequencer information from the rollkitmngr module state +func getSequencerFromRollkitMngrState(rootDir string, cometBFTState state.State) (*sequencerInfo, error) { + // Try to load the application database + config := cfg.DefaultConfig() + config.SetRoot(rootDir) + + dbType := dbm.BackendType(config.DBBackend) + + // Check if application database exists + appDBPath := filepath.Join(config.DBDir(), "application.db") + if ok, err := fileExists(appDBPath); !ok || err != nil { + return nil, fmt.Errorf("no application database found in %v: %w", config.DBDir(), err) + } + + // Open application database + appDB, err := dbm.NewDB("application", dbType, config.DBDir()) + if err != nil { + return nil, fmt.Errorf("failed to open application database: %w", err) + } + defer appDB.Close() + + // Set up encoding config + encCfg := testutil.MakeTestEncodingConfig(rollkitmngr.AppModuleBasic{}) + + // Try to read the sequencer directly from the database using the known key format + // In cosmos SDK apps, module state is typically stored with a prefix pattern + // We need to construct the full key path: [moduleStoreKey][collections prefix][item key] + + // The sequencer is stored under the rollkitmngr module with the SequencerKey + // Based on cosmos collections implementation, the key format would be: + // [collections prefix for sequencer item] + SequencerKey + sequencerKey := append([]byte{0}, rollkitmngrTypes.SequencerKey...) // 0 is typical collections prefix + + sequencerBytes, err := appDB.Get(sequencerKey) + if err != nil || sequencerBytes == nil { + // Try alternative key format - sometimes the module prefix is included + moduleKey := append([]byte(rollkitmngrTypes.ModuleName), sequencerKey...) + sequencerBytes, err = appDB.Get(moduleKey) + if err != nil || sequencerBytes == nil { + return nil, fmt.Errorf("sequencer not found in rollkitmngr state") + } + } + + // Decode the sequencer data + var sequencer rollkitmngrTypes.Sequencer + if err := encCfg.Codec.Unmarshal(sequencerBytes, &sequencer); err != nil { + return nil, fmt.Errorf("failed to decode sequencer from rollkitmngr state: %w", err) + } + + // Extract the public key from the sequencer + pubKeyAny := sequencer.ConsensusPubkey + if pubKeyAny == nil { + return nil, fmt.Errorf("sequencer consensus public key is nil") + } + + // Get the cached value which should be a crypto.PubKey + pubKey, ok := pubKeyAny.GetCachedValue().(crypto.PubKey) + if !ok { + return nil, fmt.Errorf("failed to extract public key from sequencer") + } + + // Get the address from the public key + addr := pubKey.Address() + + return &sequencerInfo{ + Address: addr, + PubKey: pubKey, + }, nil +} + // fileExists checks if a file/directory exists. func fileExists(filename string) (bool, error) { _, err := os.Stat(filename) diff --git a/server/migration_cmd_test.go b/server/migration_cmd_test.go new file mode 100644 index 0000000..0098fcd --- /dev/null +++ b/server/migration_cmd_test.go @@ -0,0 +1,89 @@ +package server + +import ( + "testing" + + "github.com/cometbft/cometbft/state" + cometbfttypes "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/require" +) + +func TestCreateRollkitMigrationGenesis_SingleValidator(t *testing.T) { + // Test case: single validator should work with existing logic + validator := &cometbfttypes.Validator{ + Address: []byte("test_validator_addr"), + PubKey: cometbfttypes.NewMockPV().PrivKey.PubKey(), + VotingPower: 100, // Set voting power to avoid the validation error + } + + cometBFTState := state.State{ + ChainID: "test-chain", + InitialHeight: 1, + LastValidators: cometbfttypes.NewValidatorSet([]*cometbfttypes.Validator{validator}), + } + + // Use a temporary directory for testing + tmpDir := t.TempDir() + + // This should succeed with the single validator fallback logic + err := createRollkitMigrationGenesis(tmpDir, cometBFTState) + require.NoError(t, err) +} + +func TestCreateRollkitMigrationGenesis_MultipleValidators_NoRollkitState(t *testing.T) { + // Test case: multiple validators without rollkitmngr state should fail gracefully + validator1 := &cometbfttypes.Validator{ + Address: []byte("test_validator_addr1"), + PubKey: cometbfttypes.NewMockPV().PrivKey.PubKey(), + VotingPower: 100, + } + validator2 := &cometbfttypes.Validator{ + Address: []byte("test_validator_addr2"), + PubKey: cometbfttypes.NewMockPV().PrivKey.PubKey(), + VotingPower: 100, + } + + cometBFTState := state.State{ + ChainID: "test-chain", + InitialHeight: 1, + LastValidators: cometbfttypes.NewValidatorSet([]*cometbfttypes.Validator{validator1, validator2}), + } + + // Use a temporary directory for testing (no rollkitmngr state present) + tmpDir := t.TempDir() + + // This should fail with our new logic when rollkitmngr state is not available + err := createRollkitMigrationGenesis(tmpDir, cometBFTState) + require.Error(t, err) + require.Contains(t, err.Error(), "expected exactly one validator") +} + +func TestCreateRollkitMigrationGenesis_NoValidators(t *testing.T) { + // Test case: no validators should return an error + cometBFTState := state.State{ + ChainID: "test-chain", + InitialHeight: 1, + LastValidators: cometbfttypes.NewValidatorSet([]*cometbfttypes.Validator{}), + } + + // Use a temporary directory for testing + tmpDir := t.TempDir() + + // This should fail + err := createRollkitMigrationGenesis(tmpDir, cometBFTState) + require.Error(t, err) + require.Contains(t, err.Error(), "no validators found") +} + +func TestGetSequencerFromRollkitMngrState_NoDatabase(t *testing.T) { + // Test case: should fail gracefully when no application database exists + tmpDir := t.TempDir() + + cometBFTState := state.State{ + ChainID: "test-chain", + } + + _, err := getSequencerFromRollkitMngrState(tmpDir, cometBFTState) + require.Error(t, err) + require.Contains(t, err.Error(), "no application database found") +} \ No newline at end of file From 88415751aaa0714ec547645f09d56c119b15a255 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:13:31 +0000 Subject: [PATCH 3/8] Improve rollkitmngr state access robustness with multiple key formats and validation Co-authored-by: julienrbrt <29894366+julienrbrt@users.noreply.github.com> --- server/migration_cmd.go | 72 ++++++++++++++++++++++++++++-------- server/migration_cmd_test.go | 33 +++++++++++++++++ 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/server/migration_cmd.go b/server/migration_cmd.go index 988c138..eec3768 100644 --- a/server/migration_cmd.go +++ b/server/migration_cmd.go @@ -444,29 +444,56 @@ func getSequencerFromRollkitMngrState(rootDir string, cometBFTState state.State) // Set up encoding config encCfg := testutil.MakeTestEncodingConfig(rollkitmngr.AppModuleBasic{}) - // Try to read the sequencer directly from the database using the known key format - // In cosmos SDK apps, module state is typically stored with a prefix pattern - // We need to construct the full key path: [moduleStoreKey][collections prefix][item key] + // Try multiple key formats to find the sequencer data + // In cosmos SDK applications, module state can be stored with different key patterns - // The sequencer is stored under the rollkitmngr module with the SequencerKey - // Based on cosmos collections implementation, the key format would be: - // [collections prefix for sequencer item] + SequencerKey - sequencerKey := append([]byte{0}, rollkitmngrTypes.SequencerKey...) // 0 is typical collections prefix + var sequencerBytes []byte + var keyFormat string - sequencerBytes, err := appDB.Get(sequencerKey) - if err != nil || sequencerBytes == nil { - // Try alternative key format - sometimes the module prefix is included - moduleKey := append([]byte(rollkitmngrTypes.ModuleName), sequencerKey...) - sequencerBytes, err = appDB.Get(moduleKey) - if err != nil || sequencerBytes == nil { - return nil, fmt.Errorf("sequencer not found in rollkitmngr state") + // Try different key formats commonly used in cosmos SDK applications + keyFormats := []struct { + name string + key []byte + }{ + { + name: "collections prefix only", + key: append([]byte{0}, rollkitmngrTypes.SequencerKey...), + }, + { + name: "module name + collections prefix", + key: append([]byte(rollkitmngrTypes.ModuleName), append([]byte{0}, rollkitmngrTypes.SequencerKey...)...), + }, + { + name: "sequencer key only", + key: rollkitmngrTypes.SequencerKey, + }, + { + name: "module name + sequencer key", + key: append([]byte(rollkitmngrTypes.ModuleName), rollkitmngrTypes.SequencerKey...), + }, + { + name: "module store prefix + collections format", + key: append([]byte("s/k:"+rollkitmngrTypes.ModuleName+"/"), append([]byte{0}, rollkitmngrTypes.SequencerKey...)...), + }, + } + + for _, format := range keyFormats { + data, err := appDB.Get(format.key) + if err == nil && data != nil { + sequencerBytes = data + keyFormat = format.name + break } } + if sequencerBytes == nil { + return nil, fmt.Errorf("sequencer not found in rollkitmngr state (tried %d key formats)", len(keyFormats)) + } + // Decode the sequencer data var sequencer rollkitmngrTypes.Sequencer if err := encCfg.Codec.Unmarshal(sequencerBytes, &sequencer); err != nil { - return nil, fmt.Errorf("failed to decode sequencer from rollkitmngr state: %w", err) + return nil, fmt.Errorf("failed to decode sequencer from rollkitmngr state (key format: %s): %w", keyFormat, err) } // Extract the public key from the sequencer @@ -478,12 +505,25 @@ func getSequencerFromRollkitMngrState(rootDir string, cometBFTState state.State) // Get the cached value which should be a crypto.PubKey pubKey, ok := pubKeyAny.GetCachedValue().(crypto.PubKey) if !ok { - return nil, fmt.Errorf("failed to extract public key from sequencer") + return nil, fmt.Errorf("failed to extract public key from sequencer (key format: %s)", keyFormat) } // Get the address from the public key addr := pubKey.Address() + // Validate that this sequencer is actually one of the validators + validatorFound := false + for _, validator := range cometBFTState.LastValidators.Validators { + if bytes.Equal(validator.Address.Bytes(), addr) { + validatorFound = true + break + } + } + + if !validatorFound { + return nil, fmt.Errorf("sequencer from rollkitmngr state (address: %x) is not found in the validator set", addr) + } + return &sequencerInfo{ Address: addr, PubKey: pubKey, diff --git a/server/migration_cmd_test.go b/server/migration_cmd_test.go index 0098fcd..87595e7 100644 --- a/server/migration_cmd_test.go +++ b/server/migration_cmd_test.go @@ -86,4 +86,37 @@ func TestGetSequencerFromRollkitMngrState_NoDatabase(t *testing.T) { _, err := getSequencerFromRollkitMngrState(tmpDir, cometBFTState) require.Error(t, err) require.Contains(t, err.Error(), "no application database found") +} + +func TestSequencerInfo_Validation(t *testing.T) { + // Test the sequencer validation logic by creating a mock scenario + // where we have multiple validators and verify the error messages + + validator1 := &cometbfttypes.Validator{ + Address: []byte("test_validator_addr1"), + PubKey: cometbfttypes.NewMockPV().PrivKey.PubKey(), + VotingPower: 100, + } + validator2 := &cometbfttypes.Validator{ + Address: []byte("test_validator_addr2"), + PubKey: cometbfttypes.NewMockPV().PrivKey.PubKey(), + VotingPower: 100, + } + + cometBFTState := state.State{ + ChainID: "test-chain", + InitialHeight: 1, + LastValidators: cometbfttypes.NewValidatorSet([]*cometbfttypes.Validator{validator1, validator2}), + } + + tmpDir := t.TempDir() + + // This should fail since there's no rollkitmngr state, and provide a helpful error message + err := createRollkitMigrationGenesis(tmpDir, cometBFTState) + require.Error(t, err) + + // The error should mention both the validator count issue and the rollkitmngr state issue + require.Contains(t, err.Error(), "expected exactly one validator") + require.Contains(t, err.Error(), "found 2") + require.Contains(t, err.Error(), "Unable to determine sequencer from rollkitmngr state") } \ No newline at end of file From 670255ee06c7d349a54c346cec8d12a8cd22beab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:16:25 +0000 Subject: [PATCH 4/8] Add command configuration validation test and finalize implementation Co-authored-by: julienrbrt <29894366+julienrbrt@users.noreply.github.com> --- server/migration_cmd_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/server/migration_cmd_test.go b/server/migration_cmd_test.go index 87595e7..da38309 100644 --- a/server/migration_cmd_test.go +++ b/server/migration_cmd_test.go @@ -119,4 +119,19 @@ func TestSequencerInfo_Validation(t *testing.T) { require.Contains(t, err.Error(), "expected exactly one validator") require.Contains(t, err.Error(), "found 2") require.Contains(t, err.Error(), "Unable to determine sequencer from rollkitmngr state") +} + +func TestMigrateToRollkitCmd_CommandSetup(t *testing.T) { + // Test that the command is properly configured + cmd := MigrateToRollkitCmd() + + require.NotNil(t, cmd) + require.Equal(t, "rollkit-migrate", cmd.Use) + require.Equal(t, "Migrate the data from the CometBFT chain to Rollkit", cmd.Short) + require.NotNil(t, cmd.RunE) + + // Check that the da-height flag is available + flag := cmd.Flags().Lookup("da-height") + require.NotNil(t, flag) + require.Equal(t, "1", flag.DefValue) } \ No newline at end of file From c0a9468139808d3136cc601aa50f1f0631cde102 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:44:07 +0000 Subject: [PATCH 5/8] Replace hacky database access with proper collections framework for rollkitmngr state querying Co-authored-by: julienrbrt <29894366+julienrbrt@users.noreply.github.com> --- server/migration_cmd.go | 93 +++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/server/migration_cmd.go b/server/migration_cmd.go index eec3768..52b3039 100644 --- a/server/migration_cmd.go +++ b/server/migration_cmd.go @@ -20,7 +20,12 @@ import ( "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/store" cometbfttypes "github.com/cometbft/cometbft/types" - "github.com/cosmos/cosmos-sdk/types/module/testutil" + "cosmossdk.io/collections" + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/testutil" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" ds "github.com/ipfs/go-datastore" ktds "github.com/ipfs/go-datastore/keytransform" "github.com/spf13/cobra" @@ -442,58 +447,54 @@ func getSequencerFromRollkitMngrState(rootDir string, cometBFTState state.State) defer appDB.Close() // Set up encoding config - encCfg := testutil.MakeTestEncodingConfig(rollkitmngr.AppModuleBasic{}) + encCfg := moduletestutil.MakeTestEncodingConfig(rollkitmngr.AppModuleBasic{}) - // Try multiple key formats to find the sequencer data - // In cosmos SDK applications, module state can be stored with different key patterns + // Create a store key for the rollkitmngr module + storeKey := storetypes.NewKVStoreKey(rollkitmngrTypes.ModuleName) - var sequencerBytes []byte - var keyFormat string + // Create a context with the store - use the simple approach from the tests + ctx := testutil.DefaultContext(storeKey, storetypes.NewTransientStoreKey("transient")) - // Try different key formats commonly used in cosmos SDK applications - keyFormats := []struct { - name string - key []byte - }{ - { - name: "collections prefix only", - key: append([]byte{0}, rollkitmngrTypes.SequencerKey...), - }, - { - name: "module name + collections prefix", - key: append([]byte(rollkitmngrTypes.ModuleName), append([]byte{0}, rollkitmngrTypes.SequencerKey...)...), - }, - { - name: "sequencer key only", - key: rollkitmngrTypes.SequencerKey, - }, - { - name: "module name + sequencer key", - key: append([]byte(rollkitmngrTypes.ModuleName), rollkitmngrTypes.SequencerKey...), - }, - { - name: "module store prefix + collections format", - key: append([]byte("s/k:"+rollkitmngrTypes.ModuleName+"/"), append([]byte{0}, rollkitmngrTypes.SequencerKey...)...), - }, - } + // Create store service for collections + storeService := runtime.NewKVStoreService(storeKey) - for _, format := range keyFormats { - data, err := appDB.Get(format.key) - if err == nil && data != nil { - sequencerBytes = data - keyFormat = format.name - break - } + // Set up collections exactly as defined in the keeper + sb := collections.NewSchemaBuilder(storeService) + sequencerCollection := collections.NewItem( + sb, + rollkitmngrTypes.SequencerKey, + "sequencer", + codec.CollValue[rollkitmngrTypes.Sequencer](encCfg.Codec), + ) + + // Build the schema + _, err = sb.Build() + if err != nil { + return nil, fmt.Errorf("failed to build collections schema: %w", err) } - if sequencerBytes == nil { - return nil, fmt.Errorf("sequencer not found in rollkitmngr state (tried %d key formats)", len(keyFormats)) + // We need to copy data from our database to the context's store + // Get the store from the context + store := ctx.MultiStore().GetKVStore(storeKey) + + // Copy the sequencer data from our database to the store + // First, determine the correct key that collections would use + sequencerKey := rollkitmngrTypes.SequencerKey + sequencerBytes, err := appDB.Get(sequencerKey) + if err != nil || sequencerBytes == nil { + return nil, fmt.Errorf("sequencer not found in rollkitmngr state") } - // Decode the sequencer data - var sequencer rollkitmngrTypes.Sequencer - if err := encCfg.Codec.Unmarshal(sequencerBytes, &sequencer); err != nil { - return nil, fmt.Errorf("failed to decode sequencer from rollkitmngr state (key format: %s): %w", keyFormat, err) + // Store it in the context's store so collections can find it + store.Set(sequencerKey, sequencerBytes) + + // Now query using collections + sequencer, err := sequencerCollection.Get(ctx) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return nil, fmt.Errorf("sequencer not found in rollkitmngr state") + } + return nil, fmt.Errorf("failed to get sequencer from rollkitmngr state: %w", err) } // Extract the public key from the sequencer @@ -505,7 +506,7 @@ func getSequencerFromRollkitMngrState(rootDir string, cometBFTState state.State) // Get the cached value which should be a crypto.PubKey pubKey, ok := pubKeyAny.GetCachedValue().(crypto.PubKey) if !ok { - return nil, fmt.Errorf("failed to extract public key from sequencer (key format: %s)", keyFormat) + return nil, fmt.Errorf("failed to extract public key from sequencer") } // Get the address from the public key From 4aa5521d5159a470cf79da38e26b790726778a24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:23:12 +0000 Subject: [PATCH 6/8] Remove store modification and use read-only database access for rollkitmngr state Co-authored-by: julienrbrt <29894366+julienrbrt@users.noreply.github.com> --- server/migration_cmd.go | 58 ++++++++--------------------------------- 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/server/migration_cmd.go b/server/migration_cmd.go index 52b3039..46dde9f 100644 --- a/server/migration_cmd.go +++ b/server/migration_cmd.go @@ -20,11 +20,6 @@ import ( "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/store" cometbfttypes "github.com/cometbft/cometbft/types" - "cosmossdk.io/collections" - storetypes "cosmossdk.io/store/types" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/testutil" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" ds "github.com/ipfs/go-datastore" ktds "github.com/ipfs/go-datastore/keytransform" @@ -439,7 +434,7 @@ func getSequencerFromRollkitMngrState(rootDir string, cometBFTState state.State) return nil, fmt.Errorf("no application database found in %v: %w", config.DBDir(), err) } - // Open application database + // Open application database (read-only) appDB, err := dbm.NewDB("application", dbType, config.DBDir()) if err != nil { return nil, fmt.Errorf("failed to open application database: %w", err) @@ -449,52 +444,21 @@ func getSequencerFromRollkitMngrState(rootDir string, cometBFTState state.State) // Set up encoding config encCfg := moduletestutil.MakeTestEncodingConfig(rollkitmngr.AppModuleBasic{}) - // Create a store key for the rollkitmngr module - storeKey := storetypes.NewKVStoreKey(rollkitmngrTypes.ModuleName) - - // Create a context with the store - use the simple approach from the tests - ctx := testutil.DefaultContext(storeKey, storetypes.NewTransientStoreKey("transient")) - - // Create store service for collections - storeService := runtime.NewKVStoreService(storeKey) - - // Set up collections exactly as defined in the keeper - sb := collections.NewSchemaBuilder(storeService) - sequencerCollection := collections.NewItem( - sb, - rollkitmngrTypes.SequencerKey, - "sequencer", - codec.CollValue[rollkitmngrTypes.Sequencer](encCfg.Codec), - ) - - // Build the schema - _, err = sb.Build() + // Read sequencer data from the database using the collections key format + // Collections store data with a prefix byte (0x00) followed by the key + sequencerKey := append([]byte{0}, rollkitmngrTypes.SequencerKey...) + sequencerBytes, err := appDB.Get(sequencerKey) if err != nil { - return nil, fmt.Errorf("failed to build collections schema: %w", err) + return nil, fmt.Errorf("failed to read sequencer from database: %w", err) } - - // We need to copy data from our database to the context's store - // Get the store from the context - store := ctx.MultiStore().GetKVStore(storeKey) - - // Copy the sequencer data from our database to the store - // First, determine the correct key that collections would use - sequencerKey := rollkitmngrTypes.SequencerKey - sequencerBytes, err := appDB.Get(sequencerKey) - if err != nil || sequencerBytes == nil { + if sequencerBytes == nil { return nil, fmt.Errorf("sequencer not found in rollkitmngr state") } - // Store it in the context's store so collections can find it - store.Set(sequencerKey, sequencerBytes) - - // Now query using collections - sequencer, err := sequencerCollection.Get(ctx) - if err != nil { - if errors.Is(err, collections.ErrNotFound) { - return nil, fmt.Errorf("sequencer not found in rollkitmngr state") - } - return nil, fmt.Errorf("failed to get sequencer from rollkitmngr state: %w", err) + // Decode the sequencer data + var sequencer rollkitmngrTypes.Sequencer + if err := encCfg.Codec.Unmarshal(sequencerBytes, &sequencer); err != nil { + return nil, fmt.Errorf("failed to decode sequencer from rollkitmngr state: %w", err) } // Extract the public key from the sequencer From 229d53334b93083756d4228b109f8695bde6ffc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 15:37:54 +0000 Subject: [PATCH 7/8] Use errors.Join for better error composition in migration command Co-authored-by: julienrbrt <29894366+julienrbrt@users.noreply.github.com> --- server/migration_cmd.go | 11 ++++++++--- server/migration_cmd_test.go | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/server/migration_cmd.go b/server/migration_cmd.go index 46dde9f..66b3a1f 100644 --- a/server/migration_cmd.go +++ b/server/migration_cmd.go @@ -384,7 +384,10 @@ func createRollkitMigrationGenesis(rootDir string, cometBFTState state.State) er sequencerAddr = cometBFTState.LastValidators.Validators[0].Address.Bytes() sequencerPubKey = cometBFTState.LastValidators.Validators[0].PubKey } else { - return fmt.Errorf("expected exactly one validator in the last validators, found %d. Unable to determine sequencer from rollkitmngr state: %v", len(cometBFTState.LastValidators.Validators), err) + return errors.Join( + fmt.Errorf("expected exactly one validator in the last validators, found %d", len(cometBFTState.LastValidators.Validators)), + fmt.Errorf("unable to determine sequencer from rollkitmngr state: %w", err), + ) } } } else { @@ -430,8 +433,10 @@ func getSequencerFromRollkitMngrState(rootDir string, cometBFTState state.State) // Check if application database exists appDBPath := filepath.Join(config.DBDir(), "application.db") - if ok, err := fileExists(appDBPath); !ok || err != nil { - return nil, fmt.Errorf("no application database found in %v: %w", config.DBDir(), err) + if ok, err := fileExists(appDBPath); err != nil { + return nil, fmt.Errorf("error checking application database in %v: %w", config.DBDir(), err) + } else if !ok { + return nil, fmt.Errorf("no application database found in %v", config.DBDir()) } // Open application database (read-only) diff --git a/server/migration_cmd_test.go b/server/migration_cmd_test.go index da38309..f8e29eb 100644 --- a/server/migration_cmd_test.go +++ b/server/migration_cmd_test.go @@ -118,7 +118,7 @@ func TestSequencerInfo_Validation(t *testing.T) { // The error should mention both the validator count issue and the rollkitmngr state issue require.Contains(t, err.Error(), "expected exactly one validator") require.Contains(t, err.Error(), "found 2") - require.Contains(t, err.Error(), "Unable to determine sequencer from rollkitmngr state") + require.Contains(t, err.Error(), "unable to determine sequencer from rollkitmngr state") } func TestMigrateToRollkitCmd_CommandSetup(t *testing.T) { From 7607a6538ef8913377d59556c4ca0e9ccfe2462e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 7 Jul 2025 09:07:02 +0000 Subject: [PATCH 8/8] Preserve rollkitmngr state error when subsequent errors occur Co-authored-by: julienrbrt <29894366+julienrbrt@users.noreply.github.com> --- server/migration_cmd.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/server/migration_cmd.go b/server/migration_cmd.go index 66b3a1f..89be76c 100644 --- a/server/migration_cmd.go +++ b/server/migration_cmd.go @@ -374,11 +374,13 @@ func createRollkitMigrationGenesis(rootDir string, cometBFTState state.State) er ) // If the cometbft validators are > 0, fetch the rollkitmngr state to get the sequencer + var rollkitMngrErr error if len(cometBFTState.LastValidators.Validators) > 0 { if sequencer, err := getSequencerFromRollkitMngrState(rootDir, cometBFTState); err == nil { sequencerAddr = sequencer.Address sequencerPubKey = sequencer.PubKey } else { + rollkitMngrErr = err // If rollkitmngr state is not available, fall back to single validator logic if len(cometBFTState.LastValidators.Validators) == 1 { sequencerAddr = cometBFTState.LastValidators.Validators[0].Address.Bytes() @@ -405,12 +407,24 @@ func createRollkitMigrationGenesis(rootDir string, cometBFTState state.State) er // using cmtjson for marshalling to ensure compatibility with cometbft genesis format genesisBytes, err := cmtjson.MarshalIndent(migrationGenesis, "", " ") if err != nil { + if rollkitMngrErr != nil { + return errors.Join( + fmt.Errorf("failed to marshal rollkit migration genesis: %w", err), + fmt.Errorf("rollkitmngr state query failed: %w", rollkitMngrErr), + ) + } return fmt.Errorf("failed to marshal rollkit migration genesis: %w", err) } const rollkitGenesisFilename = "rollkit_genesis.json" genesisPath := filepath.Join(rootDir, rollkitGenesisFilename) if err := os.WriteFile(genesisPath, genesisBytes, 0o644); err != nil { + if rollkitMngrErr != nil { + return errors.Join( + fmt.Errorf("failed to write rollkit migration genesis to %s: %w", genesisPath, err), + fmt.Errorf("rollkitmngr state query failed: %w", rollkitMngrErr), + ) + } return fmt.Errorf("failed to write rollkit migration genesis to %s: %w", genesisPath, err) }