Skip to content

Commit

Permalink
refactor!: Split out consumer genesis state (#1324)
Browse files Browse the repository at this point in the history
Refactor consumer genesis state
* Split ConsumerGenesis datatype
* Move other non-shared types to consumer
* fix some protbuf lint issues
* make proto-format
* Rename PrivateConsumerGenesisState to GenesisState
* Fix linter issues
* Correct some proto buf
* Protobuf use deprecated instead of reserved
* Add genesis transformation command to interchain-security-cd
* Remove unused field from test data
* Added some basic checks
* Fix linter warnings
* Added test
* Minor test fixes + lint warnings
* Fix lint warning
* Updated comment
* Addressed comments
* Update docs
* Addressed comments
  • Loading branch information
bermuell authored and MSalopek committed Dec 1, 2023
1 parent 0cfc206 commit 1d2641a
Show file tree
Hide file tree
Showing 35 changed files with 3,316 additions and 2,029 deletions.
4 changes: 1 addition & 3 deletions app/consumer-democracy/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ import (
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
// add mint
mint "github.com/cosmos/cosmos-sdk/x/mint"
mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
Expand Down Expand Up @@ -112,7 +111,6 @@ import (
ccvdistr "github.com/cosmos/interchain-security/v3/x/ccv/democracy/distribution"
ccvgov "github.com/cosmos/interchain-security/v3/x/ccv/democracy/governance"
ccvstaking "github.com/cosmos/interchain-security/v3/x/ccv/democracy/staking"
ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types"
)

const (
Expand Down Expand Up @@ -713,7 +711,7 @@ func New(
return fromVM, fmt.Errorf("failed to unmarshal genesis state: %w", err)
}

consumerGenesis := ccvtypes.ConsumerGenesisState{}
consumerGenesis := consumertypes.GenesisState{}
appCodec.MustUnmarshalJSON(appState[consumertypes.ModuleName], &consumerGenesis)

consumerGenesis.PreCCV = true
Expand Down
103 changes: 103 additions & 0 deletions app/consumer/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@ package app

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"

consumerTypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types"
"github.com/cosmos/interchain-security/v3/x/ccv/types"
)

// The genesis state of the blockchain is represented here as a map of raw json
Expand All @@ -15,7 +27,98 @@ import (
// object provided to it during init.
type GenesisState map[string]json.RawMessage

// Migration of consumer genesis content as it is exported from a provider version v1,2,3
// to a format readable by current consumer implementation.
func transform(jsonRaw []byte, ctx client.Context) (json.RawMessage, error) {
// v1,2,3 uses deprecated fields of GenesisState type
oldConsumerGenesis := consumerTypes.GenesisState{}
err := ctx.Codec.UnmarshalJSON(jsonRaw, &oldConsumerGenesis)
if err != nil {
return nil, fmt.Errorf("reading consumer genesis data failed: %s", err)
}

// some sanity checks for v2 transformation
if len(oldConsumerGenesis.Provider.InitialValSet) > 0 {
return nil, fmt.Errorf("invalid source version. Unexpected element 'provider.initial_val_set'")
}

if oldConsumerGenesis.Provider.ClientState != nil {
return nil, fmt.Errorf("invalid source version. Unexpected element 'provider.client_state'")
}

if oldConsumerGenesis.Provider.ConsensusState != nil {
return nil, fmt.Errorf("invalid source version. Unexpected element 'provider.consensus_state'")
}

// Version 2 of provider genesis data fills up deprecated fields
// ProviderClientState, ConsensusState and InitialValSet
newGenesis := types.ConsumerGenesisState{
Params: oldConsumerGenesis.Params,
Provider: types.ProviderInfo{
ClientState: oldConsumerGenesis.ProviderClientState,
ConsensusState: oldConsumerGenesis.ProviderConsensusState,
InitialValSet: oldConsumerGenesis.InitialValSet,
},
}

newJson, err := ctx.Codec.MarshalJSON(&newGenesis)
if err != nil {
return nil, fmt.Errorf("failed marshalling data to new type: %s", err)
}
return newJson, nil
}

// Transform a consumer genesis json file exported from a given ccv provider version
// to a consumer genesis json format supported by current ccv consumer version.
// Result will be written to defined output.
func TransformConsumerGenesis(cmd *cobra.Command, args []string) error {
sourceFile := args[0]

jsonRaw, err := os.ReadFile(filepath.Clean(sourceFile))
if err != nil {
return err
}

clientCtx := client.GetClientContextFromCmd(cmd)
newConsumerGenesis, err := transform(jsonRaw, clientCtx)
if err != nil {
return err
}

bz, err := newConsumerGenesis.MarshalJSON()
if err != nil {
return fmt.Errorf("failed exporting new consumer genesis to JSON: %s", err)
}

sortedBz, err := sdk.SortJSON(bz)
if err != nil {
return fmt.Errorf("failed sorting transformed consumer genesis JSON: %s", err)
}

cmd.Println(string(sortedBz))
return nil
}

// NewDefaultGenesisState generates the default state for the application.
func NewDefaultGenesisState(cdc codec.JSONCodec) GenesisState {
return ModuleBasics.DefaultGenesis(cdc)
}

// GetConsumerGenesisTransformCmd transforms Consumer Genesis JSON content exported from a
// provider version v1,v2 or v3 to a JSON format supported by this consumer version.
func GetConsumerGenesisTransformCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "transform [genesis-file]",
Short: "Transform CCV consumer genesis from an older provider version not supporting current format",
Long: strings.TrimSpace(
fmt.Sprintf(`Transform the consumer genesis file from a provider version v1,v2 or v3 to a version supported by this consumer. Result is printed to STDOUT.
Example:
$ %s transform /path/to/ccv_consumer_genesis.json`, version.AppName),
),
Args: cobra.ExactArgs(1),
RunE: TransformConsumerGenesis,
}

return cmd
}
213 changes: 213 additions & 0 deletions app/consumer/genesis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package app_test

import (
"bytes"
"context"
"io/fs"
"os"
"path/filepath"
"testing"
"time"

"github.com/spf13/cobra"
"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/x/auth/types"

app "github.com/cosmos/interchain-security/v3/app/consumer"
consumerTypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types"
)

// Testdata mapping consumer genesis exports to a provider module version as
// used by transformation function for consumer genesis content.
var consumerGenesisStates map[string]string = map[string]string{
"v2": `
{
"params": {
"enabled": true,
"blocks_per_distribution_transmission": "1500",
"distribution_transmission_channel": "",
"provider_fee_pool_addr_str": "",
"ccv_timeout_period": "2419200s",
"transfer_timeout_period": "3600s",
"consumer_redistribution_fraction": "0.75",
"historical_entries": "10000",
"unbonding_period": "1728000s",
"soft_opt_out_threshold": "",
"reward_denoms": [],
"provider_reward_denoms": []
},
"provider_client_id": "",
"provider_channel_id": "",
"new_chain": true,
"provider_client_state": {
"chain_id": "cosmoshub-4",
"trust_level": {
"numerator": "1",
"denominator": "3"
},
"trusting_period": "1197504s",
"unbonding_period": "1814400s",
"max_clock_drift": "10s",
"frozen_height": {
"revision_number": "0",
"revision_height": "0"
},
"latest_height": {
"revision_number": "4",
"revision_height": "15211521"
},
"proof_specs": [
{
"leaf_spec": {
"hash": "SHA256",
"prehash_key": "NO_HASH",
"prehash_value": "SHA256",
"length": "VAR_PROTO",
"prefix": "AA=="
},
"inner_spec": {
"child_order": [
0,
1
],
"child_size": 33,
"min_prefix_length": 4,
"max_prefix_length": 12,
"empty_child": null,
"hash": "SHA256"
},
"max_depth": 0,
"min_depth": 0
},
{
"leaf_spec": {
"hash": "SHA256",
"prehash_key": "NO_HASH",
"prehash_value": "SHA256",
"length": "VAR_PROTO",
"prefix": "AA=="
},
"inner_spec": {
"child_order": [
0,
1
],
"child_size": 32,
"min_prefix_length": 1,
"max_prefix_length": 1,
"empty_child": null,
"hash": "SHA256"
},
"max_depth": 0,
"min_depth": 0
}
],
"upgrade_path": [
"upgrade",
"upgradedIBCState"
],
"allow_update_after_expiry": true,
"allow_update_after_misbehaviour": true
},
"provider_consensus_state": {
"timestamp": "2023-05-08T11:00:01.563901871Z",
"root": {
"hash": "qKVnVSXlsjDHC8ekKcy/0zSjzr3YekCurld9R4W07EI="
},
"next_validators_hash": "E08978F493101A3C5D459FB3087B8CFBA9E82D7A1FE1441E7D77E11AC0586BAC"
},
"maturing_packets": [],
"initial_val_set": [
{
"pub_key": {
"ed25519": "cOQZvh/h9ZioSeUMZB/1Vy1Xo5x2sjrVjlE/qHnYifM="
},
"power": "2345194"
},
{
"pub_key": {
"ed25519": "vGSKfbQyKApvBhinpOOA0XQAdjxceihYNwtGskfZGyQ="
},
"power": "463811"
}
],
"height_to_valset_update_id": [],
"outstanding_downtime_slashing": [],
"pending_consumer_packets": {
"list": []
},
"last_transmission_block_height": {
"height": "0"
},
"preCCV": false
}
`,
}

func getClientCtx() client.Context {
encodingConfig := app.MakeTestEncodingConfig()
return client.Context{}.
WithCodec(encodingConfig.Codec).
WithInterfaceRegistry(encodingConfig.InterfaceRegistry).
WithTxConfig(encodingConfig.TxConfig).
WithLegacyAmino(encodingConfig.Amino).
WithInput(os.Stdin).
WithAccountRetriever(types.AccountRetriever{})
}

// Setup client context
func getGenesisTransformCmd() (*cobra.Command, error) {
cmd := app.GetConsumerGenesisTransformCmd()
clientCtx := getClientCtx()
ctx := context.WithValue(context.Background(), client.ClientContextKey, &clientCtx)
cmd.SetContext(ctx)
err := client.SetCmdClientContext(cmd, clientCtx)
return cmd, err
}

// Check transformation of a version 2 ConsumerGenesis export to
// consumer genesis json format used by current consumer implementation.
func TestConsumerGenesisTransformationV2(t *testing.T) {
version := "v2"
filePath := filepath.Join(t.TempDir(), "oldConsumerGenesis.json")

err := os.WriteFile(
filePath,
[]byte(consumerGenesisStates[version]),
fs.FileMode(0o644))
require.NoError(t, err)
defer os.Remove(filePath)

cmd, err := getGenesisTransformCmd()
require.NoError(t, err, "Error setting up transformation command: %s", err)
cmd.SetArgs([]string{filePath})

output := new(bytes.Buffer)
cmd.SetOutput(output)
require.NoError(t, err)
_, err = cmd.ExecuteC()
require.NoError(t, err)

consumerGenesis := consumerTypes.GenesisState{}

bz := output.Bytes()
ctx := client.GetClientContextFromCmd(cmd)
err = ctx.Codec.UnmarshalJSON(bz, &consumerGenesis)
require.NoError(t, err, "Error unmarshalling transformed genesis state :%s", err)

// Some basic sanity checks on the content.
require.Nil(t, consumerGenesis.ProviderClientState)
require.NotNil(t, consumerGenesis.Provider.ClientState)
require.Equal(t, "cosmoshub-4", consumerGenesis.Provider.ClientState.ChainId)

require.Nil(t, consumerGenesis.ProviderConsensusState)
require.NotNil(t, consumerGenesis.Provider.ConsensusState)
require.Equal(t, time.Date(2023, time.May, 8, 11, 0, 1, 563901871, time.UTC),
consumerGenesis.Provider.ConsensusState.Timestamp)

require.Empty(t, consumerGenesis.InitialValSet)
require.NotEmpty(t, consumerGenesis.Provider.InitialValSet)
}
2 changes: 1 addition & 1 deletion cmd/interchain-security-cd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
// add keybase, auxiliary RPC, query, genesis, and tx child commands
rootCmd.AddCommand(
rpc.StatusCommand(),
genesisCommand(encodingConfig),
genesisCommand(encodingConfig, consumer.GetConsumerGenesisTransformCmd()),
queryCommand(),
txCommand(),
keys.Commands(consumer.DefaultNodeHome),
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/consumer-development/changeover-procedure.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ RevisionNumber: 0, RevisionHeight: 111

* `spawn_time` listed in the proposal MUST be before the `upgrade_height` listed in the the upgrade proposal on the standalone chain.
:::caution
`spawn_time` must occur before the `upgrade_height` on the standalone chain is reached becasue the `provider` chain must generate the `ConsumerGenesis` that contains the **validator set** that will be used after the changeover.
`spawn_time` must occur before the `upgrade_height` on the standalone chain is reached because the `provider` chain must generate the `ConsumerGenesis` that contains the **validator set** that will be used after the changeover.
:::

* `unbonding_period` must correspond to the value used on the standalone chain. Otherwise, the clients used for the ccv protocol may be incorrectly initialized.
Expand Down Expand Up @@ -128,7 +128,7 @@ To help validators and other node runners onboard onto your chain, please prepar

This should include (at minimum):

- [ ] genesis.json with CCV data (after spawn time passes)
- [ ] genesis.json with CCV data (after spawn time passes). Check if CCV data needs to be transformed (see [Transform Consumer Genesis](./consumer-genesis-transformation.md))
- [ ] information about relevant seed/peer nodes you are running
- [ ] relayer information (compatible versions)
- [ ] copy of your governance proposal (as JSON)
Expand Down
Loading

0 comments on commit 1d2641a

Please sign in to comment.