diff --git a/CHANGELOG.md b/CHANGELOG.md index e74438f99c..1528717b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Upgrading a consumer from `v1.2.0-multiden` to `v2.0.0` will NOT require state m ## Notable PRs included in v2.0.0 +* (feat!) Add DistributionTransmissionChannel to ConsumerAdditionProposal [#965](https://github.com/cosmos/interchain-security/pull/965) * (feat/fix) limit vsc matured packets handled per endblocker [#1004](https://github.com/cosmos/interchain-security/pull/1004) * (fix) cosumer key prefix order to avoid complex migrations [#963](https://github.com/cosmos/interchain-security/pull/963) and [#991](https://github.com/cosmos/interchain-security/pull/991). The latter PR is the proper fix. * (feat) v1->v2 migrations to accommodate a bugfix having to do with store keys, introduce new params, and deal with consumer genesis state schema changes [#975](https://github.com/cosmos/interchain-security/pull/975) and [#997](https://github.com/cosmos/interchain-security/pull/997) diff --git a/Dockerfile b/Dockerfile index ac528fa8fb..4d81392316 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,7 +48,7 @@ COPY --from=gorelayer-builder /bin/rly /usr/local/bin/ COPY --from=is-builder /go/bin/interchain-security-pd /usr/local/bin/interchain-security-pd COPY --from=is-builder /go/bin/interchain-security-cd /usr/local/bin/interchain-security-cd COPY --from=is-builder /go/bin/interchain-security-cdd /usr/local/bin/interchain-security-cdd - +COPY --from=is-builder /go/bin/interchain-security-sd /usr/local/bin/interchain-security-sd # Copy in the shell scripts that run the testnet ADD ./tests/e2e/testnet-scripts /testnet-scripts diff --git a/Dockerfile.gaia b/Dockerfile.gaia index 196f7dfa74..87448a1d63 100644 --- a/Dockerfile.gaia +++ b/Dockerfile.gaia @@ -74,7 +74,7 @@ COPY --from=hermes-builder /usr/bin/hermes /usr/local/bin/ COPY --from=gaia-builder /go/gaia/build/gaiad /usr/local/bin/interchain-security-pd COPY --from=is-builder /go/bin/interchain-security-cd /usr/local/bin/interchain-security-cd COPY --from=is-builder /go/bin/interchain-security-cdd /usr/local/bin/interchain-security-cdd - +COPY --from=is-builder /go/bin/interchain-security-sd /usr/local/bin/interchain-security-sd # Copy in the shell scripts that run the testnet ADD ./tests/e2e/testnet-scripts /testnet-scripts diff --git a/Makefile b/Makefile index b84eca6524..351852cd21 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ install: go.sum go install $(BUILD_FLAGS) ./cmd/interchain-security-pd go install $(BUILD_FLAGS) ./cmd/interchain-security-cd go install $(BUILD_FLAGS) ./cmd/interchain-security-cdd + go install $(BUILD_FLAGS) ./cmd/interchain-security-sd # run all tests: unit, integration, diff, and E2E test: diff --git a/app/consumer-democracy/app.go b/app/consumer-democracy/app.go index ee299d6221..e9406cfecd 100644 --- a/app/consumer-democracy/app.go +++ b/app/consumer-democracy/app.go @@ -117,7 +117,7 @@ import ( const ( AppName = "interchain-security-cd" - upgradeName = "ics-v1-to-v2" // arbitrary name, define your own appropriately named upgrade + upgradeName = "sovereign-changeover" // arbitrary name, define your own appropriately named upgrade AccountAddressPrefix = "cosmos" ) @@ -645,12 +645,6 @@ func New( // upgrade handler code is application specific. However, as an example, standalone to consumer // changeover chains should utilize customized upgrade handler code similar to below. - // Setting the standalone transfer channel ID is only needed for standalone to consumer changeover chains - // who wish to preserve existing IBC transfer denoms. Here's an example. - // - // Note: This setter needs to execute before the ccv channel handshake is initiated. - app.ConsumerKeeper.SetStandaloneTransferChannelID(ctx, "hardcoded-existing-channel-id") - // TODO: should have a way to read from current node home userHomeDir, err := os.UserHomeDir() if err != nil { diff --git a/app/sovereign/ante_handler.go b/app/sovereign/ante_handler.go new file mode 100644 index 0000000000..3b1a3b7d38 --- /dev/null +++ b/app/sovereign/ante_handler.go @@ -0,0 +1,55 @@ +package app + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + ibcante "github.com/cosmos/ibc-go/v4/modules/core/ante" + ibckeeper "github.com/cosmos/ibc-go/v4/modules/core/keeper" +) + +// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC +// channel keeper. +type HandlerOptions struct { + ante.HandlerOptions + + IBCKeeper *ibckeeper.Keeper +} + +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") + } + if options.BankKeeper == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") + } + if options.SignModeHandler == nil { + return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + } + + sigGasConsumer := options.SigGasConsumer + if sigGasConsumer == nil { + sigGasConsumer = ante.DefaultSigVerificationGasConsumer + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), + ante.NewRejectExtensionOptionsDecorator(), + ante.NewMempoolFeeDecorator(), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), + // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewSetPubKeyDecorator(options.AccountKeeper), + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewAnteDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/app/sovereign/app.go b/app/sovereign/app.go new file mode 100644 index 0000000000..43d732884b --- /dev/null +++ b/app/sovereign/app.go @@ -0,0 +1,838 @@ +package app + +import ( + "fmt" + "io" + stdlog "log" + "net/http" + "os" + "path/filepath" + + appparams "github.com/cosmos/interchain-security/v2/app/params" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/server/api" + "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/std" + store "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" + "github.com/cosmos/cosmos-sdk/x/bank" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/capability" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + crisiskeeper "github.com/cosmos/cosmos-sdk/x/crisis/keeper" + crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" + "github.com/cosmos/cosmos-sdk/x/evidence" + evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + "github.com/cosmos/cosmos-sdk/x/feegrant" + feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module" + "github.com/cosmos/cosmos-sdk/x/params" + paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client" + + "github.com/cosmos/cosmos-sdk/x/slashing" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/upgrade" + upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "github.com/cosmos/ibc-go/v4/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v4/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" + ibc "github.com/cosmos/ibc-go/v4/modules/core" + ibcconnectiontypes "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types" + porttypes "github.com/cosmos/ibc-go/v4/modules/core/05-port/types" + ibchost "github.com/cosmos/ibc-go/v4/modules/core/24-host" + ibckeeper "github.com/cosmos/ibc-go/v4/modules/core/keeper" + ibctestingcore "github.com/cosmos/interchain-security/v2/legacy_ibc_testing/core" + ibctesting "github.com/cosmos/interchain-security/v2/legacy_ibc_testing/testing" + "github.com/gorilla/mux" + "github.com/rakyll/statik/fs" + "github.com/spf13/cast" + abci "github.com/tendermint/tendermint/abci/types" + tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" + dbm "github.com/tendermint/tm-db" + + sdkdistr "github.com/cosmos/cosmos-sdk/x/distribution" + distrclient "github.com/cosmos/cosmos-sdk/x/distribution/client" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + testutil "github.com/cosmos/interchain-security/v2/testutil/integration" + + sdkstaking "github.com/cosmos/cosmos-sdk/x/staking" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + + sdkgov "github.com/cosmos/cosmos-sdk/x/gov" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + // 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" + + paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal" +) + +const ( + AppName = "interchain-security-s" + upgradeName = "v07-Theta" // arbitrary name, define your own appropriately named upgrade + AccountAddressPrefix = "cosmos" +) + +var ( + // DefaultNodeHome default home directories for the application daemon + DefaultNodeHome string + + // ModuleBasics defines the module BasicManager is in charge of setting up basic, + // non-dependant module elements, such as codec registration + // and genesis verification. + ModuleBasics = module.NewBasicManager( + auth.AppModuleBasic{}, + genutil.AppModuleBasic{}, + bank.AppModuleBasic{}, + capability.AppModuleBasic{}, + sdkstaking.AppModuleBasic{}, + mint.AppModuleBasic{}, + sdkdistr.AppModuleBasic{}, + sdkgov.NewAppModuleBasic( + // TODO: eventually remove upgrade proposal handler and cancel proposal handler + paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, + ), + params.AppModuleBasic{}, + crisis.AppModuleBasic{}, + slashing.AppModuleBasic{}, + feegrantmodule.AppModuleBasic{}, + authzmodule.AppModuleBasic{}, + ibc.AppModuleBasic{}, + upgrade.AppModuleBasic{}, + evidence.AppModuleBasic{}, + transfer.AppModuleBasic{}, + vesting.AppModuleBasic{}, + ) + + // module account permissions + maccPerms = map[string][]string{ + authtypes.FeeCollectorName: nil, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + distrtypes.ModuleName: nil, + minttypes.ModuleName: {authtypes.Minter}, + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + govtypes.ModuleName: {authtypes.Burner}, + } +) + +var ( + _ simapp.App = (*App)(nil) + _ servertypes.Application = (*App)(nil) + _ ibctesting.TestingApp = (*App)(nil) +) + +// App extends an ABCI application, but with most of its parameters exported. +// They are exported for convenience in creating helper functions, as object +// capabilities aren't needed for testing. +type App struct { // nolint: golint + *baseapp.BaseApp + legacyAmino *codec.LegacyAmino + appCodec codec.Codec + interfaceRegistry types.InterfaceRegistry + + invCheckPeriod uint + + // keys to access the substores + keys map[string]*sdk.KVStoreKey + tkeys map[string]*sdk.TransientStoreKey + memKeys map[string]*sdk.MemoryStoreKey + + // keepers + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + CapabilityKeeper *capabilitykeeper.Keeper + StakingKeeper stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + + DistrKeeper distrkeeper.Keeper + + GovKeeper govkeeper.Keeper + CrisisKeeper crisiskeeper.Keeper + UpgradeKeeper upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + EvidenceKeeper evidencekeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper + AuthzKeeper authzkeeper.Keeper + + // make scoped keepers public for test purposes + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedTransferKeeper capabilitykeeper.ScopedKeeper + + // the module manager + MM *module.Manager + + // simulation manager + sm *module.SimulationManager + configurator module.Configurator +} + +func init() { + userHomeDir, err := os.UserHomeDir() + if err != nil { + stdlog.Println("Failed to get home dir %2", err) + } + + DefaultNodeHome = filepath.Join(userHomeDir, "."+AppName) +} + +// New returns a reference to an initialized App. +func New( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + skipUpgradeHeights map[int64]bool, + homePath string, + invCheckPeriod uint, + encodingConfig appparams.EncodingConfig, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *App { + appCodec := encodingConfig.Marshaler + legacyAmino := encodingConfig.Amino + interfaceRegistry := encodingConfig.InterfaceRegistry + + bApp := baseapp.NewBaseApp(AppName, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetVersion(version.Version) + bApp.SetInterfaceRegistry(interfaceRegistry) + + keys := sdk.NewKVStoreKeys( + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, + minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, + govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, + evidencetypes.StoreKey, ibctransfertypes.StoreKey, + capabilitytypes.StoreKey, authzkeeper.StoreKey, + ) + tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) + memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) + + app := &App{ + BaseApp: bApp, + legacyAmino: legacyAmino, + appCodec: appCodec, + interfaceRegistry: interfaceRegistry, + invCheckPeriod: invCheckPeriod, + keys: keys, + tkeys: tkeys, + memKeys: memKeys, + } + + app.ParamsKeeper = initParamsKeeper( + appCodec, + legacyAmino, + keys[paramstypes.StoreKey], + tkeys[paramstypes.TStoreKey], + ) + + // set the BaseApp's parameter store + bApp.SetParamStore( + app.ParamsKeeper.Subspace(baseapp.Paramspace).WithKeyTable( + paramskeeper.ConsensusParamsKeyTable()), + ) + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper( + appCodec, + keys[capabilitytypes.StoreKey], + memKeys[capabilitytypes.MemStoreKey], + ) + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) + scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + app.CapabilityKeeper.Seal() + + // add keepers + app.AccountKeeper = authkeeper.NewAccountKeeper( + appCodec, + keys[authtypes.StoreKey], + app.GetSubspace(authtypes.ModuleName), + authtypes.ProtoBaseAccount, + maccPerms, + ) + + bankBlockedAddrs := app.ModuleAccountAddrs() + + app.BankKeeper = bankkeeper.NewBaseKeeper( + appCodec, + keys[banktypes.StoreKey], + app.AccountKeeper, + app.GetSubspace(banktypes.ModuleName), + bankBlockedAddrs, + ) + app.AuthzKeeper = authzkeeper.NewKeeper( + keys[authzkeeper.StoreKey], + appCodec, + app.BaseApp.MsgServiceRouter(), + ) + app.FeeGrantKeeper = feegrantkeeper.NewKeeper( + appCodec, + keys[feegrant.StoreKey], + app.AccountKeeper, + ) + + stakingKeeper := stakingkeeper.NewKeeper( + appCodec, + keys[stakingtypes.StoreKey], + app.AccountKeeper, + app.BankKeeper, + app.GetSubspace(stakingtypes.ModuleName), + ) + + app.MintKeeper = mintkeeper.NewKeeper( + appCodec, keys[minttypes.StoreKey], app.GetSubspace(minttypes.ModuleName), &stakingKeeper, + app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, + ) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, + keys[slashingtypes.StoreKey], + &app.StakingKeeper, + app.GetSubspace(slashingtypes.ModuleName), + ) + app.DistrKeeper = distrkeeper.NewKeeper( + appCodec, + keys[distrtypes.StoreKey], + app.GetSubspace(distrtypes.ModuleName), + app.AccountKeeper, + app.BankKeeper, + &stakingKeeper, + authtypes.FeeCollectorName, + app.ModuleAccountAddrs(), + ) + app.CrisisKeeper = crisiskeeper.NewKeeper( + app.GetSubspace(crisistypes.ModuleName), + invCheckPeriod, + app.BankKeeper, + authtypes.FeeCollectorName, + ) + app.UpgradeKeeper = upgradekeeper.NewKeeper( + skipUpgradeHeights, + keys[upgradetypes.StoreKey], + appCodec, + homePath, + app.BaseApp, + ) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.StakingKeeper = *stakingKeeper.SetHooks( + stakingtypes.NewMultiStakingHooks( + app.DistrKeeper.Hooks(), + app.SlashingKeeper.Hooks(), + ), + ) + + // register the proposal types + sdkgovRouter := govtypes.NewRouter() + sdkgovRouter.AddRoute(govtypes.RouterKey, govtypes.ProposalHandler). + AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). + AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)) + govKeeper := govkeeper.NewKeeper( + appCodec, + keys[govtypes.StoreKey], + app.GetSubspace(govtypes.ModuleName), + app.AccountKeeper, + app.BankKeeper, + &stakingKeeper, + sdkgovRouter, + ) + + app.GovKeeper = *govKeeper.SetHooks( + govtypes.NewMultiGovHooks( + // register the governance hooks + ), + ) + + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, + keys[ibchost.StoreKey], + app.GetSubspace(ibchost.ModuleName), + &app.StakingKeeper, + app.UpgradeKeeper, + scopedIBCKeeper, + ) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, + keys[slashingtypes.StoreKey], + &app.StakingKeeper, + app.GetSubspace(slashingtypes.ModuleName), + ) + + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, + keys[ibctransfertypes.StoreKey], + app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + app.AccountKeeper, + app.BankKeeper, + scopedTransferKeeper, + ) + transferModule := transfer.NewAppModule(app.TransferKeeper) + ibcmodule := transfer.NewIBCModule(app.TransferKeeper) + + // create static IBC router, add transfer route, then set and seal it + ibcRouter := porttypes.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, ibcmodule) + app.IBCKeeper.SetRouter(ibcRouter) + + // create evidence keeper with router + evidenceKeeper := evidencekeeper.NewKeeper( + appCodec, + keys[evidencetypes.StoreKey], + &app.StakingKeeper, + app.SlashingKeeper, + ) + + app.EvidenceKeeper = *evidenceKeeper + + skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)) + + // NOTE: Any module instantiated in the module manager that is later modified + // must be passed by reference here. + app.MM = module.NewManager( + genutil.NewAppModule( + app.AccountKeeper, + app.StakingKeeper, + app.BaseApp.DeliverTx, + encodingConfig.TxConfig, + ), + auth.NewAppModule(appCodec, app.AccountKeeper, nil), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants), + feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), + sdkgov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), + mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + sdkdistr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + sdkstaking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper), + upgrade.NewAppModule(app.UpgradeKeeper), + evidence.NewAppModule(app.EvidenceKeeper), + params.NewAppModule(app.ParamsKeeper), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + app.MM.SetOrderBeginBlockers( + upgradetypes.ModuleName, + capabilitytypes.ModuleName, + minttypes.ModuleName, + distrtypes.ModuleName, + slashingtypes.ModuleName, + evidencetypes.ModuleName, + stakingtypes.ModuleName, + authtypes.ModuleName, + banktypes.ModuleName, + govtypes.ModuleName, + crisistypes.ModuleName, + authz.ModuleName, + feegrant.ModuleName, + paramstypes.ModuleName, + genutiltypes.ModuleName, + vestingtypes.ModuleName, + ibctransfertypes.ModuleName, + ibchost.ModuleName, + ) + app.MM.SetOrderEndBlockers( + crisistypes.ModuleName, + govtypes.ModuleName, + stakingtypes.ModuleName, + capabilitytypes.ModuleName, + authtypes.ModuleName, + banktypes.ModuleName, + distrtypes.ModuleName, + slashingtypes.ModuleName, + minttypes.ModuleName, + evidencetypes.ModuleName, + authz.ModuleName, + feegrant.ModuleName, + paramstypes.ModuleName, + genutiltypes.ModuleName, + upgradetypes.ModuleName, + vestingtypes.ModuleName, + ibctransfertypes.ModuleName, + ibchost.ModuleName, + ) + + // NOTE: The genutils module must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + // NOTE: The genutils module must also occur after auth so that it can access the params from auth. + // NOTE: Capability module must occur first so that it can initialize any capabilities + // so that other modules that want to create or claim capabilities afterwards in InitChain + // can do so safely. + app.MM.SetOrderInitGenesis( + capabilitytypes.ModuleName, + authtypes.ModuleName, + banktypes.ModuleName, + distrtypes.ModuleName, + stakingtypes.ModuleName, + slashingtypes.ModuleName, + govtypes.ModuleName, + minttypes.ModuleName, + crisistypes.ModuleName, + evidencetypes.ModuleName, + authz.ModuleName, + feegrant.ModuleName, + paramstypes.ModuleName, + upgradetypes.ModuleName, + vestingtypes.ModuleName, + genutiltypes.ModuleName, + ibchost.ModuleName, + ibctransfertypes.ModuleName, + ) + + app.MM.RegisterInvariants(&app.CrisisKeeper) + app.MM.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino) + + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) + app.MM.RegisterServices(app.configurator) + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + app.MountMemoryStores(memKeys) + + anteHandler, err := NewAnteHandler( + HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + FeegrantKeeper: app.FeeGrantKeeper, + SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + IBCKeeper: app.IBCKeeper, + }, + ) + if err != nil { + panic(fmt.Errorf("failed to create AnteHandler: %s", err)) + } + app.SetAnteHandler(anteHandler) + + app.SetInitChainer(app.InitChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + + app.UpgradeKeeper.SetUpgradeHandler( + upgradeName, + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) + + fromVM := make(map[string]uint64) + + for moduleName, eachModule := range app.MM.Modules { + fromVM[moduleName] = eachModule.ConsensusVersion() + } + + ctx.Logger().Info("start to run module migrations...") + + return app.MM.RunMigrations(ctx, app.configurator, fromVM) + }, + ) + + upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() + if err != nil { + panic(fmt.Sprintf("failed to read upgrade info from disk %s", err)) + } + + if upgradeInfo.Name == upgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := store.StoreUpgrades{} + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } + + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + tmos.Exit(fmt.Sprintf("failed to load latest version: %s", err)) + } + } + + app.ScopedIBCKeeper = scopedIBCKeeper + app.ScopedTransferKeeper = scopedTransferKeeper + + return app +} + +// Name returns the name of the App +func (app *App) Name() string { return app.BaseApp.Name() } + +// BeginBlocker application updates every begin block +func (app *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + return app.MM.BeginBlock(ctx, req) +} + +// EndBlocker application updates every end block +func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + return app.MM.EndBlock(ctx, req) +} + +// InitChainer application update at chain initialization +func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + var genesisState GenesisState + if err := tmjson.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } + + app.UpgradeKeeper.SetModuleVersionMap(ctx, app.MM.GetVersionMap()) + return app.MM.InitGenesis(ctx, app.appCodec, genesisState) +} + +// LoadHeight loads a particular height +func (app *App) LoadHeight(height int64) error { + return app.LoadVersion(height) +} + +// ModuleAccountAddrs returns all the app's module account addresses. +func (app *App) ModuleAccountAddrs() map[string]bool { + modAccAddrs := make(map[string]bool) + for acc := range maccPerms { + modAccAddrs[authtypes.NewModuleAddress(acc).String()] = true + } + + return modAccAddrs +} + +// LegacyAmino returns App's amino codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *App) LegacyAmino() *codec.LegacyAmino { + return app.legacyAmino +} + +// AppCodec returns the app codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *App) AppCodec() codec.Codec { + return app.appCodec +} + +// InterfaceRegistry returns the InterfaceRegistry +func (app *App) InterfaceRegistry() types.InterfaceRegistry { + return app.interfaceRegistry +} + +// GetKey returns the KVStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *App) GetKey(storeKey string) *sdk.KVStoreKey { + return app.keys[storeKey] +} + +// GetTKey returns the TransientStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *App) GetTKey(storeKey string) *sdk.TransientStoreKey { + return app.tkeys[storeKey] +} + +// GetMemKey returns the MemStoreKey for the provided mem key. +// +// NOTE: This is solely used for testing purposes. +func (app *App) GetMemKey(storeKey string) *sdk.MemoryStoreKey { + return app.memKeys[storeKey] +} + +// GetSubspace returns a param subspace for a given module name. +// +// NOTE: This is solely to be used for testing purposes. +func (app *App) GetSubspace(moduleName string) paramstypes.Subspace { + subspace, _ := app.ParamsKeeper.GetSubspace(moduleName) + return subspace +} + +// SimulationManager implements the SimulationApp interface +func (app *App) SimulationManager() *module.SimulationManager { + return app.sm +} + +func (app *App) GetTestBankKeeper() testutil.TestBankKeeper { + return app.BankKeeper +} + +func (app *App) GetTestAccountKeeper() testutil.TestAccountKeeper { + return app.AccountKeeper +} + +func (app *App) GetTestSlashingKeeper() testutil.TestSlashingKeeper { + return app.SlashingKeeper +} + +func (app *App) GetTestEvidenceKeeper() testutil.TestEvidenceKeeper { + return app.EvidenceKeeper +} + +func (app *App) GetTestStakingKeeper() testutil.TestStakingKeeper { + return app.StakingKeeper +} + +func (app *App) GetTestDistributionKeeper() testutil.TestDistributionKeeper { + return app.DistrKeeper +} + +func (app *App) GetTestMintKeeper() testutil.TestMintKeeper { + return app.MintKeeper +} + +func (app *App) GetTestGovKeeper() testutil.TestGovKeeper { + return app.GovKeeper +} + +// TestingApp functions + +// GetBaseApp implements the TestingApp interface. +func (app *App) GetBaseApp() *baseapp.BaseApp { + return app.BaseApp +} + +// GetStakingKeeper implements the TestingApp interface. +func (app *App) GetStakingKeeper() ibctestingcore.StakingKeeper { + return app.StakingKeeper +} + +// GetIBCKeeper implements the TestingApp interface. +func (app *App) GetIBCKeeper() *ibckeeper.Keeper { + return app.IBCKeeper +} + +// GetScopedIBCKeeper implements the TestingApp interface. +func (app *App) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper { + return app.ScopedIBCKeeper +} + +// GetTxConfig implements the TestingApp interface. +func (app *App) GetTxConfig() client.TxConfig { + return MakeTestEncodingConfig().TxConfig +} + +// RegisterAPIRoutes registers all application module routes with the provided +// API server. +func (app *App) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { + clientCtx := apiSvr.ClientCtx + rpc.RegisterRoutes(clientCtx, apiSvr.Router) + // Register legacy tx routes. + authrest.RegisterTxRoutes(clientCtx, apiSvr.Router) + // Register new tx routes from grpc-gateway. + authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + // Register new tendermint queries routes from grpc-gateway. + tmservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register legacy and grpc-gateway routes for all modules. + ModuleBasics.RegisterRESTRoutes(clientCtx, apiSvr.Router) + ModuleBasics.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // register swagger API from root so that other applications can override easily + if apiConfig.Swagger { + RegisterSwaggerAPI(apiSvr.Router) + } +} + +// RegisterTxService implements the Application.RegisterTxService method. +func (app *App) RegisterTxService(clientCtx client.Context) { + authtx.RegisterTxService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.BaseApp.Simulate, app.interfaceRegistry) +} + +// RegisterTendermintService implements the Application.RegisterTendermintService method. +func (app *App) RegisterTendermintService(clientCtx client.Context) { + tmservice.RegisterTendermintService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.interfaceRegistry) +} + +// RegisterSwaggerAPI registers swagger route with API Server +func RegisterSwaggerAPI(rtr *mux.Router) { + statikFS, err := fs.New() + if err != nil { + panic(err) + } + + staticServer := http.FileServer(statikFS) + rtr.PathPrefix("/swagger/").Handler(http.StripPrefix("/swagger/", staticServer)) +} + +// GetMaccPerms returns a copy of the module account permissions +func GetMaccPerms() map[string][]string { + dupMaccPerms := make(map[string][]string) + for k, v := range maccPerms { + dupMaccPerms[k] = v + } + return dupMaccPerms +} + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) + + paramsKeeper.Subspace(authtypes.ModuleName) + paramsKeeper.Subspace(banktypes.ModuleName) + paramsKeeper.Subspace(stakingtypes.ModuleName) + paramsKeeper.Subspace(minttypes.ModuleName) + paramsKeeper.Subspace(distrtypes.ModuleName) + paramsKeeper.Subspace(slashingtypes.ModuleName) + paramsKeeper.Subspace(govtypes.ModuleName).WithKeyTable(govtypes.ParamKeyTable()) + paramsKeeper.Subspace(crisistypes.ModuleName) + paramsKeeper.Subspace(ibctransfertypes.ModuleName) + paramsKeeper.Subspace(ibchost.ModuleName) + + return paramsKeeper +} + +// MakeTestEncodingConfig creates an EncodingConfig for testing. This function +// should be used only in tests or when creating a new app instance (NewApp*()). +// App user shouldn't create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() appparams.EncodingConfig { + encodingConfig := appparams.MakeTestEncodingConfig() + std.RegisterLegacyAminoCodec(encodingConfig.Amino) + std.RegisterInterfaces(encodingConfig.InterfaceRegistry) + ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) + ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) + return encodingConfig +} diff --git a/app/sovereign/export.go b/app/sovereign/export.go new file mode 100644 index 0000000000..f0b3e3ed76 --- /dev/null +++ b/app/sovereign/export.go @@ -0,0 +1,187 @@ +package app + +import ( + "encoding/json" + "log" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + servertypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// ExportAppStateAndValidators exports the state of the application for a genesis +// file. +func (app *App) ExportAppStateAndValidators( + forZeroHeight bool, jailAllowedAddrs []string, +) (servertypes.ExportedApp, error) { + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + + // We export at last height + 1, because that's the height at which + // Tendermint will start InitChain. + height := app.LastBlockHeight() + 1 + if forZeroHeight { + height = 0 + app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) + } + + genState := app.MM.ExportGenesis(ctx, app.appCodec) + appState, err := json.MarshalIndent(genState, "", " ") + if err != nil { + return servertypes.ExportedApp{}, err + } + + validators, err := staking.WriteValidators(ctx, app.StakingKeeper) + if err != nil { + return servertypes.ExportedApp{}, err + } + return servertypes.ExportedApp{ + AppState: appState, + Validators: validators, + Height: height, + ConsensusParams: app.BaseApp.GetConsensusParams(ctx), + }, nil +} + +// prepare for fresh start at zero height +// NOTE zero height genesis is a temporary feature which will be deprecated +// +// in favour of export at a block height +func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { + applyAllowedAddrs := false + + // check if there is a allowed address list + if len(jailAllowedAddrs) > 0 { + applyAllowedAddrs = true + } + + allowedAddrsMap := make(map[string]bool) + + for _, addr := range jailAllowedAddrs { + _, err := sdk.ValAddressFromBech32(addr) + if err != nil { + log.Fatal(err) + } + allowedAddrsMap[addr] = true + } + + /* Just to be safe, assert the invariants on current state. */ + app.CrisisKeeper.AssertInvariants(ctx) + + /* Handle fee distribution state. */ + + // withdraw all validator commission + app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + _, err := app.DistrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) + if err != nil { + panic(err) + } + return false + }) + + // withdraw all delegator rewards + dels := app.StakingKeeper.GetAllDelegations(ctx) + for _, delegation := range dels { + _, err := app.DistrKeeper.WithdrawDelegationRewards(ctx, delegation.GetDelegatorAddr(), delegation.GetValidatorAddr()) + if err != nil { + panic(err) + } + } + + // clear validator slash events + app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx) + + // clear validator historical rewards + app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) + + // set context height to zero + height := ctx.BlockHeight() + ctx = ctx.WithBlockHeight(0) + + // reinitialize all validators + app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + // donate any unwithdrawn outstanding reward fraction tokens to the community pool + scraps := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, val.GetOperator()) + feePool := app.DistrKeeper.GetFeePool(ctx) + feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) + app.DistrKeeper.SetFeePool(ctx, feePool) + + app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) + return false + }) + + // reinitialize all delegations + for _, del := range dels { + app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, del.GetDelegatorAddr(), del.GetValidatorAddr()) + app.DistrKeeper.Hooks().AfterDelegationModified(ctx, del.GetDelegatorAddr(), del.GetValidatorAddr()) + } + + // reset context height + ctx = ctx.WithBlockHeight(height) + + /* Handle staking state. */ + + // iterate through redelegations, reset creation height + app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { + for i := range red.Entries { + red.Entries[i].CreationHeight = 0 + } + app.StakingKeeper.SetRedelegation(ctx, red) + return false + }) + + // iterate through unbonding delegations, reset creation height + app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { + for i := range ubd.Entries { + ubd.Entries[i].CreationHeight = 0 + } + app.StakingKeeper.SetUnbondingDelegation(ctx, ubd) + return false + }) + + // Iterate through validators by power descending, reset bond heights, and + // update bond intra-tx counters. + store := ctx.KVStore(app.keys[stakingtypes.StoreKey]) + iter := sdk.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) + counter := int16(0) + + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(iter.Key()[1:]) + validator, found := app.StakingKeeper.GetValidator(ctx, addr) + if !found { + panic("expected validator, not found") + } + + validator.UnbondingHeight = 0 + if applyAllowedAddrs && !allowedAddrsMap[addr.String()] { + validator.Jailed = true + } + + app.StakingKeeper.SetValidator(ctx, validator) + counter++ + } + + if err := iter.Close(); err != nil { + panic(err) + } + + if _, err := app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx); err != nil { + panic(err) + } + + /* Handle slashing state. */ + + // reset start height on signing infos + app.SlashingKeeper.IterateValidatorSigningInfos( + ctx, + func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + return false + }, + ) +} diff --git a/app/sovereign/genesis.go b/app/sovereign/genesis.go new file mode 100644 index 0000000000..5bf0c1da80 --- /dev/null +++ b/app/sovereign/genesis.go @@ -0,0 +1,21 @@ +package app + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/codec" +) + +// The genesis state of the blockchain is represented here as a map of raw json +// messages key'd by a identifier string. +// The identifier is used to determine which module genesis information belongs +// to so it may be appropriately routed during init chain. +// Within this application default genesis information is retrieved from +// the ModuleBasicManager which populates json from each BasicModule +// object provided to it during init. +type GenesisState map[string]json.RawMessage + +// NewDefaultGenesisState generates the default state for the application. +func NewDefaultGenesisState(cdc codec.JSONCodec) GenesisState { + return ModuleBasics.DefaultGenesis(cdc) +} diff --git a/cmd/interchain-security-sd/cmd/genaccounts.go b/cmd/interchain-security-sd/cmd/genaccounts.go new file mode 100644 index 0000000000..c3340dccad --- /dev/null +++ b/cmd/interchain-security-sd/cmd/genaccounts.go @@ -0,0 +1,180 @@ +package cmd + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" +) + +const ( + flagVestingStart = "vesting-start-time" + flagVestingEnd = "vesting-end-time" + flagVestingAmt = "vesting-amount" +) + +// AddGenesisAccountCmd returns add-genesis-account cobra Command. +func AddGenesisAccountCmd(defaultNodeHome string) *cobra.Command { + cmd := &cobra.Command{ + Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]", + Short: "Add a genesis account to genesis.json", + Long: `Add a genesis account to genesis.json. The provided account must specify +the account address or key name and a list of initial coins. If a key name is given, +the address will be looked up in the local Keybase. The list of initial tokens must +contain valid denominations. Accounts may optionally be supplied with vesting parameters. +`, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + serverCtx := server.GetServerContextFromCmd(cmd) + config := serverCtx.Config + + config.SetRoot(clientCtx.HomeDir) + + var kr keyring.Keyring + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + inBuf := bufio.NewReader(cmd.InOrStdin()) + keyringBackend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend) + if keyringBackend != "" && clientCtx.Keyring == nil { + var err error + kr, err = keyring.New(sdk.KeyringServiceName(), keyringBackend, clientCtx.HomeDir, inBuf) + if err != nil { + return err + } + } else { + kr = clientCtx.Keyring + } + + info, err := kr.Key(args[0]) + if err != nil { + return fmt.Errorf("failed to get address from Keyring: %w", err) + } + addr = info.GetAddress() + } + + coins, err := sdk.ParseCoinsNormalized(args[1]) + if err != nil { + return fmt.Errorf("failed to parse coins: %w", err) + } + + vestingStart, _ := cmd.Flags().GetInt64(flagVestingStart) + vestingEnd, _ := cmd.Flags().GetInt64(flagVestingEnd) + vestingAmtStr, _ := cmd.Flags().GetString(flagVestingAmt) + + vestingAmt, err := sdk.ParseCoinsNormalized(vestingAmtStr) + if err != nil { + return fmt.Errorf("failed to parse vesting amount: %w", err) + } + + // create concrete account type based on input parameters + var genAccount authtypes.GenesisAccount + + balances := banktypes.Balance{Address: addr.String(), Coins: coins.Sort()} + baseAccount := authtypes.NewBaseAccount(addr, nil, 0, 0) + + if !vestingAmt.IsZero() { + baseVestingAccount := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd) + + if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) || + baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) { + return errors.New("vesting amount cannot be greater than total amount") + } + + switch { + case vestingStart != 0 && vestingEnd != 0: + genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart) + + case vestingEnd != 0: + genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount) + + default: + return errors.New("invalid vesting parameters; must supply start and end time or end time") + } + } else { + genAccount = baseAccount + } + + if err := genAccount.Validate(); err != nil { + return fmt.Errorf("failed to validate new genesis account: %w", err) + } + + genFile := config.GenesisFile() + appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile) + if err != nil { + return fmt.Errorf("failed to unmarshal genesis state: %w", err) + } + + authGenState := authtypes.GetGenesisStateFromAppState(clientCtx.Codec, appState) + + accs, err := authtypes.UnpackAccounts(authGenState.Accounts) + if err != nil { + return fmt.Errorf("failed to get accounts from any: %w", err) + } + + if accs.Contains(addr) { + return fmt.Errorf("cannot add account at existing address %s", addr) + } + + // Add the new account to the set of genesis accounts and sanitize the + // accounts afterwards. + accs = append(accs, genAccount) + accs = authtypes.SanitizeGenesisAccounts(accs) + + genAccs, err := authtypes.PackAccounts(accs) + if err != nil { + return fmt.Errorf("failed to convert accounts into any's: %w", err) + } + authGenState.Accounts = genAccs + + authGenStateBz, err := clientCtx.Codec.MarshalJSON(&authGenState) + if err != nil { + return fmt.Errorf("failed to marshal auth genesis state: %w", err) + } + + appState[authtypes.ModuleName] = authGenStateBz + + bankGenState := banktypes.GetGenesisStateFromAppState(clientCtx.Codec, appState) + bankGenState.Balances = append(bankGenState.Balances, balances) + bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances) + bankGenState.Supply = bankGenState.Supply.Add(balances.Coins...) + + bankGenStateBz, err := clientCtx.Codec.MarshalJSON(bankGenState) + if err != nil { + return fmt.Errorf("failed to marshal bank genesis state: %w", err) + } + + appState[banktypes.ModuleName] = bankGenStateBz + + appStateJSON, err := json.Marshal(appState) + if err != nil { + return fmt.Errorf("failed to marshal application genesis state: %w", err) + } + + genDoc.AppState = appStateJSON + return genutil.ExportGenesisFile(genDoc, genFile) + }, + } + + cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") + cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)") + cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts") + cmd.Flags().Int64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts") + cmd.Flags().Int64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/cmd/interchain-security-sd/cmd/root.go b/cmd/interchain-security-sd/cmd/root.go new file mode 100644 index 0000000000..56f9ad667b --- /dev/null +++ b/cmd/interchain-security-sd/cmd/root.go @@ -0,0 +1,305 @@ +package cmd + +import ( + "errors" + "io" + "os" + "path/filepath" + + serverconfig "github.com/cosmos/cosmos-sdk/server/config" + "github.com/spf13/cast" + "github.com/spf13/cobra" + tmcli "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/config" + "github.com/cosmos/cosmos-sdk/client/debug" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/pruning" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/server" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/snapshots" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + "github.com/cosmos/interchain-security/v2/app/params" + simapp "github.com/cosmos/interchain-security/v2/app/sovereign" +) + +// NewRootCmd creates a new root command for simd. It is called once in the +// main function. +func NewRootCmd() (*cobra.Command, params.EncodingConfig) { + encodingConfig := simapp.MakeTestEncodingConfig() + initClientCtx := client.Context{}. + WithCodec(encodingConfig.Marshaler). + WithInterfaceRegistry(encodingConfig.InterfaceRegistry). + WithTxConfig(encodingConfig.TxConfig). + WithLegacyAmino(encodingConfig.Amino). + WithInput(os.Stdin). + WithAccountRetriever(types.AccountRetriever{}). + WithHomeDir(simapp.DefaultNodeHome). + WithViper("") // In simapp, we don't use any prefix for env variables. + + rootCmd := &cobra.Command{ + Use: "interchain-security-sd", + Short: "simulation app", + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + // set the default command outputs + cmd.SetOut(cmd.OutOrStdout()) + cmd.SetErr(cmd.ErrOrStderr()) + + initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) + if err != nil { + return err + } + + initClientCtx, err = config.ReadFromClientConfig(initClientCtx) + if err != nil { + return err + } + + if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil { + return err + } + + customAppTemplate, customAppConfig := initAppConfig() + + return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig) + }, + } + + initRootCmd(rootCmd, encodingConfig) + + return rootCmd, encodingConfig +} + +// initAppConfig helps to override default appConfig template and configs. +// return "", nil if no custom configuration is required for the application. +func initAppConfig() (string, interface{}) { + // The following code snippet is just for reference. + + // WASMConfig defines configuration for the wasm module. + type WASMConfig struct { + // This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries + QueryGasLimit uint64 `mapstructure:"query_gas_limit"` + + // Address defines the gRPC-web server to listen on + LruSize uint64 `mapstructure:"lru_size"` + } + + type CustomAppConfig struct { + serverconfig.Config + + WASM WASMConfig `mapstructure:"wasm"` + } + + // Optionally allow the chain developer to overwrite the SDK's default + // server config. + srvCfg := serverconfig.DefaultConfig() + // The SDK's default minimum gas price is set to "" (empty value) inside + // app.toml. If left empty by validators, the node will halt on startup. + // However, the chain developer can set a default app.toml value for their + // validators here. + // + // In summary: + // - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their + // own app.toml config, + // - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their + // own app.toml to override, or use this default value. + // + // In simapp, we set the min gas prices to 0. + srvCfg.MinGasPrices = "0stake" + // srvCfg.BaseConfig.IAVLDisableFastNode = true // disable fastnode by default + + customAppConfig := CustomAppConfig{ + Config: *srvCfg, + WASM: WASMConfig{ + LruSize: 1, + QueryGasLimit: 300000, + }, + } + + customAppTemplate := serverconfig.DefaultConfigTemplate + ` +[wasm] +# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries +query_gas_limit = 300000 +# This is the number of wasm vm instances we keep cached in memory for speed-up +# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally +lru_size = 0` + + return customAppTemplate, customAppConfig +} + +func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { + cfg := sdk.GetConfig() + cfg.Seal() + + a := appCreator{encodingConfig} + rootCmd.AddCommand( + genutilcli.InitCmd(simapp.ModuleBasics, simapp.DefaultNodeHome), + genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome), + genutilcli.MigrateGenesisCmd(), + genutilcli.GenTxCmd(simapp.ModuleBasics, encodingConfig.TxConfig, banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome), + genutilcli.ValidateGenesisCmd(simapp.ModuleBasics), + AddGenesisAccountCmd(simapp.DefaultNodeHome), + tmcli.NewCompletionCmd(rootCmd, true), + debug.Cmd(), + config.Cmd(), + pruning.PruningCmd(a.newApp), + ) + + server.AddCommands(rootCmd, simapp.DefaultNodeHome, a.newApp, a.appExport, addModuleInitFlags) + + // add keybase, auxiliary RPC, query, and tx child commands + rootCmd.AddCommand( + rpc.StatusCommand(), + queryCommand(), + txCommand(), + keys.Commands(simapp.DefaultNodeHome), + ) + + // add rosetta + rootCmd.AddCommand(server.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler)) +} + +func addModuleInitFlags(startCmd *cobra.Command) { + crisis.AddModuleInitFlags(startCmd) +} + +func queryCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetAccountCmd(), + rpc.ValidatorCommand(), + rpc.BlockCommand(), + authcmd.QueryTxsByEventsCmd(), + authcmd.QueryTxCmd(), + ) + + simapp.ModuleBasics.AddQueryCommands(cmd) + cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") + + return cmd +} + +func txCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetSignCommand(), + authcmd.GetSignBatchCommand(), + authcmd.GetMultiSignCommand(), + authcmd.GetMultiSignBatchCmd(), + authcmd.GetValidateSignaturesCommand(), + authcmd.GetBroadcastCommand(), + authcmd.GetEncodeCommand(), + authcmd.GetDecodeCommand(), + ) + + simapp.ModuleBasics.AddTxCommands(cmd) + cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") + + return cmd +} + +type appCreator struct { + encCfg params.EncodingConfig +} + +// newApp is an appCreator +func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { + var cache sdk.MultiStorePersistentCache + + if cast.ToBool(appOpts.Get(server.FlagInterBlockCache)) { + cache = store.NewCommitKVStoreCacheManager() + } + + skipUpgradeHeights := make(map[int64]bool) + for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { + skipUpgradeHeights[int64(h)] = true + } + + pruningOpts, err := server.GetPruningOptionsFromFlags(appOpts) + if err != nil { + panic(err) + } + + snapshotDir := filepath.Join(cast.ToString(appOpts.Get(flags.FlagHome)), "data", "snapshots") + snapshotDB, err := sdk.NewLevelDB("metadata", snapshotDir) + if err != nil { + panic(err) + } + snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) + if err != nil { + panic(err) + } + + return simapp.New( + logger, db, traceStore, true, skipUpgradeHeights, + cast.ToString(appOpts.Get(flags.FlagHome)), + cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)), + a.encCfg, + appOpts, + baseapp.SetPruning(pruningOpts), + baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))), + baseapp.SetHaltHeight(cast.ToUint64(appOpts.Get(server.FlagHaltHeight))), + baseapp.SetHaltTime(cast.ToUint64(appOpts.Get(server.FlagHaltTime))), + baseapp.SetMinRetainBlocks(cast.ToUint64(appOpts.Get(server.FlagMinRetainBlocks))), + baseapp.SetInterBlockCache(cache), + baseapp.SetTrace(cast.ToBool(appOpts.Get(server.FlagTrace))), + baseapp.SetIndexEvents(cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))), + baseapp.SetSnapshotStore(snapshotStore), + baseapp.SetSnapshotInterval(cast.ToUint64(appOpts.Get(server.FlagStateSyncSnapshotInterval))), + baseapp.SetSnapshotKeepRecent(cast.ToUint32(appOpts.Get(server.FlagStateSyncSnapshotKeepRecent))), + baseapp.SetIAVLCacheSize(cast.ToInt(appOpts.Get(server.FlagIAVLCacheSize))), + baseapp.SetIAVLDisableFastNode(cast.ToBool(appOpts.Get(server.FlagDisableIAVLFastNode))), + ) +} + +// appExport creates a new simapp (optionally at a given height) +// and exports state. +func (a appCreator) appExport( + logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailAllowedAddrs []string, + appOpts servertypes.AppOptions, +) (servertypes.ExportedApp, error) { + var simApp *simapp.App + homePath, ok := appOpts.Get(flags.FlagHome).(string) + if !ok || homePath == "" { + return servertypes.ExportedApp{}, errors.New("application home not set") + } + + if height != -1 { + simApp = simapp.New(logger, db, traceStore, false, map[int64]bool{}, homePath, uint(1), a.encCfg, appOpts) + + if err := simApp.LoadHeight(height); err != nil { + return servertypes.ExportedApp{}, err + } + } else { + simApp = simapp.New(logger, db, traceStore, true, map[int64]bool{}, homePath, uint(1), a.encCfg, appOpts) + } + + return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs) +} diff --git a/cmd/interchain-security-sd/main.go b/cmd/interchain-security-sd/main.go new file mode 100644 index 0000000000..6b43e7b396 --- /dev/null +++ b/cmd/interchain-security-sd/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "os" + + "github.com/cosmos/cosmos-sdk/server" + svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + app "github.com/cosmos/interchain-security/v2/app/sovereign" + "github.com/cosmos/interchain-security/v2/cmd/interchain-security-sd/cmd" +) + +func main() { + rootCmd, _ := cmd.NewRootCmd() + + if err := svrcmd.Execute(rootCmd, app.DefaultNodeHome); err != nil { + switch e := err.(type) { + case server.ErrorCode: + os.Exit(e.Code) + + default: + os.Exit(1) + } + } +} diff --git a/docs/docs/consumer-development/onboarding.md b/docs/docs/consumer-development/onboarding.md index faa2969e63..89c5f32dc5 100644 --- a/docs/docs/consumer-development/onboarding.md +++ b/docs/docs/consumer-development/onboarding.md @@ -91,6 +91,13 @@ Example of a consumer chain addition proposal. // This param is a part of the cosmos sdk staking module. In the case of // a ccv enabled consumer chain, the ccv module acts as the staking module. "historical_entries": 10000, + // The ID of a token transfer channel used for the Reward Distribution + // sub-protocol. If DistributionTransmissionChannel == "", a new transfer + // channel is created on top of the same connection as the CCV channel. + // Note that transfer_channel_id is the ID of the channel end on the consumer chain. + // it is most relevant for chains performing a sovereign to consumer changeover + // in order to maintan the existing ibc transfer channel + "distribution_transmission_channel": "channel-123" } ``` diff --git a/docs/docs/features/proposals.md b/docs/docs/features/proposals.md index 9285baa62b..a34160ecf4 100644 --- a/docs/docs/features/proposals.md +++ b/docs/docs/features/proposals.md @@ -43,6 +43,9 @@ Minimal example: "historical_entries": 10000, "genesis_hash": "d86d756e10118e66e6805e9cc476949da2e750098fcc7634fd0cc77f57a0b2b0", "binary_hash": "376cdbd3a222a3d5c730c9637454cd4dd925e2f9e2e0d0f3702fc922928583f1" + // relevant for chains performing a sovereign to consumer changeover + // in order to maintan the existing ibc transfer channel + "distribution_transmission_channel": "channel-123" } ``` More examples can be found in the replicated security testnet repository [here](https://github.com/cosmos/testnets/blob/master/replicated-security/baryon-1/proposal-baryon-1.json) and [here](https://github.com/cosmos/testnets/blob/master/replicated-security/noble-1/start-proposal-noble-1.json). diff --git a/proto/interchain_security/ccv/consumer/v1/genesis.proto b/proto/interchain_security/ccv/consumer/v1/genesis.proto index bf019879ac..578518086c 100644 --- a/proto/interchain_security/ccv/consumer/v1/genesis.proto +++ b/proto/interchain_security/ccv/consumer/v1/genesis.proto @@ -34,12 +34,12 @@ message GenesisState { // OutstandingDowntimes nil on new chain, filled in on restart. repeated OutstandingDowntime outstanding_downtime_slashing = 10 [ (gogoproto.nullable) = false ]; - // PendingConsumerPackets nil on new chain, filled in on restart. - interchain_security.ccv.v1.ConsumerPacketDataList pending_consumer_packets = 11 - [ (gogoproto.nullable) = false ]; + // PendingConsumerPackets nil on new chain, filled in on restart. + interchain_security.ccv.v1.ConsumerPacketDataList pending_consumer_packets = 11 + [ (gogoproto.nullable) = false ]; // LastTransmissionBlockHeight nil on new chain, filled in on restart. interchain_security.ccv.consumer.v1.LastTransmissionBlockHeight last_transmission_block_height = 12 - [ (gogoproto.nullable) = false ]; + [ (gogoproto.nullable) = false ]; bool preCCV = 13; // flag indicating whether the consumer CCV module starts in pre-CCV state } diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index 743d2c9c33..d190b3a6ca 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -62,6 +62,13 @@ message ConsumerAdditionProposal { // This param is a part of the cosmos sdk staking module. In the case of // a ccv enabled consumer chain, the ccv module acts as the staking module. int64 historical_entries = 13; + // The ID of a token transfer channel used for the Reward Distribution + // sub-protocol. If DistributionTransmissionChannel == "", a new transfer + // channel is created on top of the same connection as the CCV channel. + // Note that transfer_channel_id is the ID of the channel end on the consumer chain. + // it is most relevant for chains performing a sovereign to consumer changeover + // in order to maintan the existing ibc transfer channel + string distribution_transmission_channel = 14; } // ConsumerRemovalProposal is a governance proposal on the provider chain to remove (and stop) a consumer chain. diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index b1e6863b7a..06bcb7f5f7 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -209,12 +209,14 @@ func (tr TestRun) submitTextProposal( } type submitConsumerAdditionProposalAction struct { - chain chainID - from validatorID - deposit uint - consumerChain chainID - spawnTime uint - initialHeight clienttypes.Height + preCCV bool + chain chainID + from validatorID + deposit uint + consumerChain chainID + spawnTime uint + initialHeight clienttypes.Height + distributionChannel string } func (tr TestRun) submitConsumerAdditionProposal( @@ -238,6 +240,7 @@ func (tr TestRun) submitConsumerAdditionProposal( TransferTimeoutPeriod: params.TransferTimeoutPeriod, UnbondingPeriod: params.UnbondingPeriod, Deposit: fmt.Sprint(action.deposit) + `stake`, + DistributionTransmissionChannel: action.distributionChannel, } bz, err := json.Marshal(prop) @@ -565,6 +568,133 @@ func (tr TestRun) startConsumerChain( }, verbose) } +type ChangeoverChainAction struct { + sovereignChain chainID + providerChain chainID + validators []StartChainValidator + genesisChanges string +} + +func (tr TestRun) changeoverChain( + action ChangeoverChainAction, + verbose bool, +) { + // sleep until the consumer chain genesis is ready on consumer + time.Sleep(5 * time.Second) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, tr.chainConfigs[action.providerChain].binaryName, + + "query", "provider", "consumer-genesis", + string(tr.chainConfigs[action.sovereignChain].chainId), + + `--node`, tr.getQueryNode(action.providerChain), + `-o`, `json`, + ) + + if verbose { + log.Println("changeoverChain cmd: ", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + consumerGenesis := ".app_state.ccvconsumer = " + string(bz) + consumerGenesisChanges := tr.chainConfigs[action.sovereignChain].genesisChanges + if consumerGenesisChanges != "" { + consumerGenesis = consumerGenesis + " | " + consumerGenesisChanges + " | " + action.genesisChanges + } + + tr.startChangeover(ChangeoverChainAction{ + validators: action.validators, + genesisChanges: consumerGenesis, + }, verbose) +} + +func (tr TestRun) startChangeover( + action ChangeoverChainAction, + verbose bool, +) { + chainConfig := tr.chainConfigs[chainID("sover")] + type jsonValAttrs struct { + Mnemonic string `json:"mnemonic"` + Allocation string `json:"allocation"` + Stake string `json:"stake"` + ValId string `json:"val_id"` + PrivValidatorKey string `json:"priv_validator_key"` + NodeKey string `json:"node_key"` + IpSuffix string `json:"ip_suffix"` + + ConsumerMnemonic string `json:"consumer_mnemonic"` + ConsumerPrivValidatorKey string `json:"consumer_priv_validator_key"` + StartWithConsumerKey bool `json:"start_with_consumer_key"` + } + + var validators []jsonValAttrs + for _, val := range action.validators { + validators = append(validators, jsonValAttrs{ + Mnemonic: tr.validatorConfigs[val.id].mnemonic, + NodeKey: tr.validatorConfigs[val.id].nodeKey, + ValId: fmt.Sprint(val.id), + PrivValidatorKey: tr.validatorConfigs[val.id].privValidatorKey, + Allocation: fmt.Sprint(val.allocation) + "stake", + Stake: fmt.Sprint(val.stake) + "stake", + IpSuffix: tr.validatorConfigs[val.id].ipSuffix, + + ConsumerMnemonic: tr.validatorConfigs[val.id].consumerMnemonic, + ConsumerPrivValidatorKey: tr.validatorConfigs[val.id].consumerPrivValidatorKey, + // if true node will be started with consumer key for each consumer chain + StartWithConsumerKey: tr.validatorConfigs[val.id].useConsumerKey, + }) + } + + vals, err := json.Marshal(validators) + if err != nil { + log.Fatal(err) + } + + // Concat genesis changes defined in chain config, with any custom genesis changes for this chain instantiation + var genesisChanges string + if action.genesisChanges != "" { + genesisChanges = chainConfig.genesisChanges + " | " + action.genesisChanges + } else { + genesisChanges = chainConfig.genesisChanges + } + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", + "/testnet-scripts/start-changeover.sh", chainConfig.upgradeBinary, string(vals), + "sover", chainConfig.ipPrefix, genesisChanges, + tr.tendermintConfigOverride, + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("startChangeover: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal("startChangeover died", err) + } +} + type addChainToRelayerAction struct { chain chainID validator validatorID @@ -683,6 +813,7 @@ func (tr TestRun) addChainToHermes( keyName, rpcAddr, wsAddr, + // action.consumer, ) bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, "/root/.hermes/config.toml") @@ -805,6 +936,51 @@ func (tr TestRun) addIbcConnectionGorelayer( tr.waitBlocks(action.chainB, 1, 10*time.Second) } +type createIbcClientsAction struct { + chainA chainID + chainB chainID +} + +// if clients are not provided hermes will first +// create new clients and then a new connection +// otherwise, it would use client provided as CLI argument (-a-client) +func (tr TestRun) createIbcClientsHermes( + action createIbcClientsAction, + verbose bool, +) { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", + "create", "connection", + "--a-chain", string(tr.chainConfigs[action.chainA].chainId), + "--b-chain", string(tr.chainConfigs[action.chainB].chainId), + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("createIbcClientsHermes: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } +} + func (tr TestRun) addIbcConnectionHermes( action addIbcConnectionAction, verbose bool, @@ -850,6 +1026,7 @@ type addIbcChannelAction struct { portA string portB string order string + version string } type startRelayerAction struct{} @@ -936,6 +1113,13 @@ func (tr TestRun) addIbcChannelHermes( action addIbcChannelAction, verbose bool, ) { + // if version is not specified, use the default version when creating ccv connections + // otherwise, use the provided version schema (usually it is ICS20-1 for IBC transfer) + chanVersion := action.version + if chanVersion == "" { + chanVersion = tr.containerConfig.ccvVersion + } + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", "create", "channel", @@ -943,7 +1127,7 @@ func (tr TestRun) addIbcChannelHermes( "--a-connection", "connection-"+fmt.Sprint(action.connectionA), "--a-port", action.portA, "--b-port", action.portB, - "--channel-version", tr.containerConfig.ccvVersion, + "--channel-version", chanVersion, "--order", action.order, ) diff --git a/tests/e2e/actions_sovereign_chain.go b/tests/e2e/actions_sovereign_chain.go new file mode 100644 index 0000000000..c61a2a9a38 --- /dev/null +++ b/tests/e2e/actions_sovereign_chain.go @@ -0,0 +1,166 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "log" + "os/exec" + "time" +) + +type StartSovereignChainAction struct { + chain chainID + validators []StartChainValidator + // Genesis changes specific to this action, appended to genesis changes defined in chain config + genesisChanges string +} + +// calls a simplified startup script (start-sovereign.sh) and runs a validator node +// upgrades are simpler with a single validator node since only one node needs to be upgraded +func (tr TestRun) startSovereignChain( + action StartSovereignChainAction, + verbose bool, +) { + chainConfig := tr.chainConfigs["sover"] + type jsonValAttrs struct { + Mnemonic string `json:"mnemonic"` + Allocation string `json:"allocation"` + Stake string `json:"stake"` + ValId string `json:"val_id"` + PrivValidatorKey string `json:"priv_validator_key"` + NodeKey string `json:"node_key"` + IpSuffix string `json:"ip_suffix"` + + ConsumerMnemonic string `json:"consumer_mnemonic"` + ConsumerPrivValidatorKey string `json:"consumer_priv_validator_key"` + StartWithConsumerKey bool `json:"start_with_consumer_key"` + } + + var validators []jsonValAttrs + for _, val := range action.validators { + validators = append(validators, jsonValAttrs{ + Mnemonic: tr.validatorConfigs[val.id].mnemonic, + NodeKey: tr.validatorConfigs[val.id].nodeKey, + ValId: fmt.Sprint(val.id), + PrivValidatorKey: tr.validatorConfigs[val.id].privValidatorKey, + Allocation: fmt.Sprint(val.allocation) + "stake", + Stake: fmt.Sprint(val.stake) + "stake", + IpSuffix: tr.validatorConfigs[val.id].ipSuffix, + + ConsumerMnemonic: tr.validatorConfigs[val.id].consumerMnemonic, + ConsumerPrivValidatorKey: tr.validatorConfigs[val.id].consumerPrivValidatorKey, + // if true node will be started with consumer key for each consumer chain + StartWithConsumerKey: tr.validatorConfigs[val.id].useConsumerKey, + }) + } + + vals, err := json.Marshal(validators) + if err != nil { + log.Fatal(err) + } + + // Concat genesis changes defined in chain config, with any custom genesis changes for this chain instantiation + var genesisChanges string + if action.genesisChanges != "" { + genesisChanges = chainConfig.genesisChanges + " | " + action.genesisChanges + } else { + genesisChanges = chainConfig.genesisChanges + } + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", + "/testnet-scripts/start-sovereign.sh", chainConfig.binaryName, string(vals), + string(chainConfig.chainId), chainConfig.ipPrefix, genesisChanges, + tr.tendermintConfigOverride, + ) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + for scanner.Scan() { + out := scanner.Text() + if verbose { + fmt.Println("startSovereignChain: " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + tr.addChainToRelayer(addChainToRelayerAction{ + chain: action.chain, + validator: action.validators[0].id, + }, verbose) +} + +type UpgradeProposalAction struct { + chainID chainID + upgradeTitle string + proposer validatorID + upgradeHeight uint64 +} + +func (tr *TestRun) submitUpgradeProposal(action UpgradeProposalAction, verbose bool) { + submit := fmt.Sprintf( + `%s tx gov submit-proposal software-upgrade %s \ + --title %s \ + --deposit 10000000stake \ + --upgrade-height %s \ + --upgrade-info "perform changeover" \ + --description "perform changeover" \ + --gas 900000 \ + --from validator%s \ + --keyring-backend test \ + --chain-id %s \ + --home %s \ + --node %s \ + -b block \ + -y`, + tr.chainConfigs[chainID("sover")].binaryName, + action.upgradeTitle, + action.upgradeTitle, + fmt.Sprint(action.upgradeHeight), + action.proposer, + tr.chainConfigs[chainID("sover")].chainId, + tr.getValidatorHome(chainID("sover"), action.proposer), + tr.getValidatorNode(chainID("sover"), action.proposer), + ) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", + tr.containerConfig.instanceName, + "/bin/bash", "-c", + submit, + ) + + if verbose { + fmt.Println("submitUpgradeProposal cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } +} + +type waitUntilBlockAction struct { + block uint + chain chainID +} + +func (tr *TestRun) waitUntilBlockOnChain(action waitUntilBlockAction) { + fmt.Println("waitUntilBlockOnChain is waiting for block:", action.block) + tr.waitUntilBlock(action.chain, action.block, 120*time.Second) + fmt.Println("waitUntilBlockOnChain done waiting for block:", action.block) +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index cc755cb97a..3038f69f4e 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -47,6 +47,9 @@ type ChainConfig struct { // Example: ".app_state.gov.voting_params.voting_period = \"5s\" | .app_state.slashing.params.signed_blocks_window = \"2\" | .app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\"" genesisChanges string binaryName string + + // binary to use after upgrade height + upgradeBinary string } type ContainerConfig struct { @@ -328,6 +331,51 @@ func MultiConsumerTestRun() TestRun { } } +func ChangeoverTestRun() TestRun { + return TestRun{ + name: "changeover", + containerConfig: ContainerConfig{ + containerName: "interchain-security-changeover-container", + instanceName: "interchain-security-changeover-instance", + ccvVersion: "1", + now: time.Now(), + }, + validatorConfigs: getDefaultValidators(), + chainConfigs: map[chainID]ChainConfig{ + chainID("provi"): { + chainId: chainID("provi"), + binaryName: "interchain-security-pd", + ipPrefix: "7.7.7", + votingWaitTime: 20, + genesisChanges: ".app_state.gov.voting_params.voting_period = \"20s\" | " + + // Custom slashing parameters for testing validator downtime functionality + // See https://docs.cosmos.network/main/modules/slashing/04_begin_block.html#uptime-tracking + ".app_state.slashing.params.signed_blocks_window = \"10\" | " + + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + + ".app_state.slashing.params.downtime_jail_duration = \"2s\" | " + + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + + ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling + ".app_state.provider.params.slash_meter_replenish_period = \"3s\"", + }, + chainID("sover"): { + chainId: chainID("sover"), + binaryName: "interchain-security-sd", + upgradeBinary: "interchain-security-cdd", + ipPrefix: "7.7.8", + votingWaitTime: 20, + genesisChanges: ".app_state.gov.voting_params.voting_period = \"20s\" | " + + ".app_state.slashing.params.signed_blocks_window = \"15\" | " + + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + + ".app_state.slashing.params.downtime_jail_duration = \"2s\" | " + + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + + ".app_state.staking.params.unbonding_time = \"1728000s\"", // making the genesis unbonding time equal to unbonding time in the consumer addition proposal + }, + }, + tendermintConfigOverride: `s/timeout_commit = "5s"/timeout_commit = "1s"/;` + + `s/peer_gossip_sleep_duration = "100ms"/peer_gossip_sleep_duration = "50ms"/;`, + } +} + func (s *TestRun) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag string) { if localSdkPath != "" { fmt.Println("USING LOCAL SDK", localSdkPath) diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 141f8a8cd8..737a318de2 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -57,6 +57,7 @@ func main() { } testRuns := []testRunWithSteps{ + {ChangeoverTestRun(), changeoverSteps}, {DefaultTestRun(), happyPathSteps}, {DemocracyTestRun(true), democracySteps}, {DemocracyTestRun(false), rewardDenomConsumerSteps}, @@ -112,6 +113,14 @@ func (tr *TestRun) runStep(step Step, verbose bool) { switch action := step.action.(type) { case StartChainAction: tr.startChain(action, verbose) + case StartSovereignChainAction: + tr.startSovereignChain(action, verbose) + case UpgradeProposalAction: + tr.submitUpgradeProposal(action, verbose) + case waitUntilBlockAction: + tr.waitUntilBlockOnChain(action) + case ChangeoverChainAction: + tr.changeoverChain(action, verbose) case SendTokensAction: tr.sendTokens(action, verbose) case submitTextProposalAction: @@ -130,6 +139,8 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.startConsumerChain(action, verbose) case addChainToRelayerAction: tr.addChainToRelayer(action, verbose) + case createIbcClientsAction: + tr.createIbcClientsHermes(action, verbose) case addIbcConnectionAction: tr.addIbcConnection(action, verbose) case addIbcChannelAction: diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 9127c65df8..15500dd01f 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -50,6 +50,17 @@ type ConsumerAdditionProposal struct { Status string } +type UpgradeProposal struct { + Title string + Description string + UpgradeHeight uint64 + Type string + Deposit uint + Status string +} + +func (p UpgradeProposal) isProposal() {} + func (p ConsumerAdditionProposal) isProposal() {} type ConsumerRemovalProposal struct { @@ -214,6 +225,20 @@ func (tr TestRun) waitBlocks(chain chainID, blocks uint, timeout time.Duration) } } +func (tr TestRun) waitUntilBlock(chain chainID, block uint, timeout time.Duration) { + start := time.Now() + for { + thisBlock := tr.getBlockHeight(chain) + if thisBlock >= block { + return + } + if time.Since(start) > timeout { + panic(fmt.Sprintf("\n\n\nwaitBlocks method has timed out after: %s\n\n", timeout)) + } + time.Sleep(500 * time.Millisecond) + } +} + func (tr TestRun) getBalances(chain chainID, modelState map[validatorID]uint) map[validatorID]uint { actualState := map[validatorID]uint{} for k := range modelState { @@ -390,6 +415,16 @@ func (tr TestRun) getProposal(chain chainID, proposal uint) Proposal { RevisionHeight: gjson.Get(string(bz), `content.initial_height.revision_height`).Uint(), }, } + case "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal": + height := gjson.Get(string(bz), `content.plan.height`).Uint() + title := gjson.Get(string(bz), `content.plan.name`).String() + return UpgradeProposal{ + Deposit: uint(deposit), + Status: status, + UpgradeHeight: height, + Title: title, + Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", + } case "/interchain_security.ccv.provider.v1.ConsumerRemovalProposal": chainId := gjson.Get(string(bz), `content.chain_id`).String() stopTime := gjson.Get(string(bz), `content.stop_time`).Time().Sub(tr.containerConfig.now) @@ -691,7 +726,14 @@ func (tr TestRun) getQueryNode(chain chainID) string { } // getQueryNodeIP returns query node IP for chain, -// ipSuffix is hardcoded to be 253 on all query nodes. +// ipSuffix is hardcoded to be 253 on all query nodes +// except for "sover" chain where there's only one node func (tr TestRun) getQueryNodeIP(chain chainID) string { + if chain == chainID("sover") { + // return address of first and only validator + return fmt.Sprintf("%s.%s", + tr.chainConfigs[chain].ipPrefix, + tr.validatorConfigs[validatorID("alice")].ipSuffix) + } return fmt.Sprintf("%s.253", tr.chainConfigs[chain].ipPrefix) } diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 8c0b4b2661..7613b05558 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -72,3 +72,18 @@ var multipleConsumers = concatSteps( stepsMultiConsumerDowntimeFromProvider("consu", "densu"), stepsMultiConsumerDoubleSign("consu", "densu"), // double sign on one of the chains ) + +var changeoverSteps = concatSteps( + // start sovereign chain and test delegation operation + + stepRunSovereignChain(), + stepStartProviderChain(), + stepsSovereignTransferChan(), + + // the chain will halt once upgrade height is reached + // after upgrade height is reached, the chain will become a consumer + stepsUpgradeChain(), + stepsChangeoverToConsumer("sover"), + + stepsPostChangeoverDelegate("sover"), +) diff --git a/tests/e2e/steps_sovereign_changeover.go b/tests/e2e/steps_sovereign_changeover.go new file mode 100644 index 0000000000..e122c1fb2a --- /dev/null +++ b/tests/e2e/steps_sovereign_changeover.go @@ -0,0 +1,367 @@ +package main + +import clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" + +// this creates new clients on both chains and a connection (connection-0) between them +// connection-0 is used to create a transfer channel between the chains +// the transfer channel is maintained during the changeover process, meaning that +// the consumer chain will be able to send rewards to the provider chain using the old channel +// as opposed to creating a new transfer channel which happens for new consumers +func stepsSovereignTransferChan() []Step { + return []Step{ + { + action: createIbcClientsAction{ + chainA: chainID("sover"), + chainB: chainID("provi"), + }, + state: State{}, + }, + { + // this will create channel-0 connection end on both chain + action: addIbcChannelAction{ + chainA: chainID("sover"), + chainB: chainID("provi"), + connectionA: 0, + portA: "transfer", + portB: "transfer", + order: "unordered", + version: "ics20-1", + }, + state: State{}, + }, + } +} + +// steps to convert sovereign to consumer chain +func stepsChangeoverToConsumer(consumerName string) []Step { + s := []Step{ + { + action: submitConsumerAdditionProposalAction{ + preCCV: true, + chain: chainID("provi"), + from: validatorID("alice"), + deposit: 10000001, + consumerChain: chainID(consumerName), + // chain-0 is the transfer channelID that gets created in stepsSovereignTransferChan + // the consumer chain will use this channel to send rewards to the provider chain + // there is no need to create a new channel for rewards distribution + distributionChannel: "channel-0", + spawnTime: 0, + initialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 111}, // 1 block after upgrade !important + }, + state: State{ + chainID("provi"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9489999999, + validatorID("bob"): 9500000000, + }, + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: chainID(consumerName), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 111}, + Status: "PROPOSAL_STATUS_VOTING_PERIOD", + }, + }, + }, + }, + }, + { + action: voteGovProposalAction{ + chain: chainID("provi"), + from: []validatorID{validatorID("alice"), validatorID("bob"), validatorID("carol")}, + vote: []string{"yes", "yes", "yes"}, + propNumber: 1, + }, + state: State{ + chainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: chainID(consumerName), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 111}, + Status: "PROPOSAL_STATUS_PASSED", + }, + }, + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + validatorID("bob"): 9500000000, + }, + }, + }, + }, + { + action: ChangeoverChainAction{ + sovereignChain: chainID(consumerName), + providerChain: chainID("provi"), + validators: []StartChainValidator{ + {id: validatorID("alice"), stake: 500000000, allocation: 10000000000}, + {id: validatorID("bob"), stake: 500000000, allocation: 10000000000}, + {id: validatorID("carol"), stake: 500000000, allocation: 10000000000}, + }, + genesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + // uses val powers from consumer + validatorID("alice"): 500, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: addIbcConnectionAction{ + chainA: chainID(consumerName), + chainB: chainID("provi"), + clientA: 1, + clientB: 1, + }, + state: State{}, + }, + { + action: addIbcChannelAction{ + chainA: chainID(consumerName), + chainB: chainID("provi"), + connectionA: 1, + portA: "consumer", + portB: "provider", + order: "ordered", + }, + state: State{}, + }, + } + + return s +} + +// start sovereign chain with a single validator so it is easier to manage +// when the chain is converted to a consumer chain the validators from the +// consumer chain will be used +// validatoralice is the only validator on the sovereign chain that is in both +// sovereign validator set and consumer validator set +func stepRunSovereignChain() []Step { + return []Step{ + { + action: StartSovereignChainAction{ + chain: chainID("sover"), + validators: []StartChainValidator{ + {id: validatorID("alice"), stake: 500000000, allocation: 10000000000}, + }, + }, + state: State{ + chainID("sover"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + }, + }, + }, + }, + { + action: delegateTokensAction{ + chain: chainID("sover"), + from: validatorID("alice"), + to: validatorID("alice"), + amount: 11000000, + }, + state: State{ + chainID("sover"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 0, // does not exist on pre-ccv sover + validatorID("carol"): 0, // does not exist on pre-ccv sover + }, + }, + }, + }, + } +} + +// TODO: use args instead of hardcoding +func stepsUpgradeChain() []Step { + return []Step{ + { + action: UpgradeProposalAction{ + chainID: chainID("sover"), + upgradeTitle: "sovereign-changeover", + proposer: validatorID("alice"), + upgradeHeight: 110, + }, + state: State{ + chainID("sover"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: UpgradeProposal{ + Title: "sovereign-changeover", + UpgradeHeight: 110, + Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", + Deposit: 10000000, + Status: "PROPOSAL_STATUS_VOTING_PERIOD", + }, + }, + }, + }, + }, + { + action: voteGovProposalAction{ + chain: chainID("sover"), + from: []validatorID{validatorID("alice")}, + vote: []string{"yes"}, + propNumber: 1, + }, + state: State{ + chainID("sover"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: UpgradeProposal{ + Deposit: 10000000, + UpgradeHeight: 110, + Title: "sovereign-changeover", + Type: "/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal", + Status: "PROPOSAL_STATUS_PASSED", + }, + }, + }, + }, + }, + { + action: waitUntilBlockAction{ + chain: chainID("sover"), + block: 110, + }, + state: State{}, + }, + } +} + +// stepsPostChangeoverDelegate tests basic delegation and resulting validator power changes after changeover +// we cannot use stepsDelegate and stepsUnbond because they make assumptions about which connection to use +// here we need to use connection-1, and in tests with new consumers connection-0 is used because the chain is new (has no IBC states prior to launch) +func stepsPostChangeoverDelegate(consumerName string) []Step { + return []Step{ + { + action: SendTokensAction{ + chain: chainID(consumerName), + from: validatorID("alice"), + to: validatorID("bob"), + amount: 100, + }, + state: State{ + chainID(consumerName): ChainState{ + // Tx should not go through, ICS channel is not setup until first VSC packet has been relayed to consumer + ValBalances: &map[validatorID]uint{ + validatorID("bob"): 0, + }, + }, + }, + }, + { + action: delegateTokensAction{ + chain: chainID("provi"), + from: validatorID("alice"), + to: validatorID("alice"), + amount: 11000000, + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: relayPacketsAction{ + chainA: chainID("provi"), + chainB: chainID(consumerName), + port: "provider", + channel: 1, + }, + state: State{ + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: SendTokensAction{ + chain: chainID(consumerName), + from: validatorID("alice"), + to: validatorID("bob"), + amount: 100, + }, + state: State{ + chainID(consumerName): ChainState{ + // Tx should go through, ICS channel is setup + ValBalances: &map[validatorID]uint{ + validatorID("bob"): 100, + }, + }, + }, + }, + { + action: unbondTokensAction{ + chain: chainID("provi"), + unbondFrom: validatorID("alice"), + sender: validatorID("alice"), + amount: 1000000, + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 510, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + // Voting power on consumer should not be affected yet + validatorID("alice"): 511, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + { + action: relayPacketsAction{ + chainA: chainID("provi"), + chainB: chainID(consumerName), + port: "provider", + channel: 1, + }, + state: State{ + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 510, + validatorID("bob"): 500, + validatorID("carol"): 500, + }, + }, + }, + }, + } +} diff --git a/tests/e2e/testnet-scripts/sovereign-genesis.json b/tests/e2e/testnet-scripts/sovereign-genesis.json new file mode 100644 index 0000000000..94a279840e --- /dev/null +++ b/tests/e2e/testnet-scripts/sovereign-genesis.json @@ -0,0 +1,279 @@ +{ + "genesis_time": "2023-06-13T11:19:05.998449459Z", + "chain_id": "sover", + "initial_height": "1", + "consensus_params": { + "block": { + "max_bytes": "22020096", + "max_gas": "-1", + "time_iota_ms": "1000" + }, + "evidence": { + "max_age_num_blocks": "100000", + "max_age_duration": "172800000000000", + "max_bytes": "1048576" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + }, + "version": {} + }, + "app_hash": "", + "app_state": { + "auth": { + "params": { + "max_memo_characters": "256", + "tx_sig_limit": "7", + "tx_size_cost_per_byte": "10", + "sig_verify_cost_ed25519": "590", + "sig_verify_cost_secp256k1": "1000" + }, + "accounts": [ + { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "pub_key": null, + "account_number": "0", + "sequence": "0" + } + ] + }, + "authz": { + "authorization": [] + }, + "bank": { + "params": { + "send_enabled": [], + "default_send_enabled": true + }, + "balances": [ + { + "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "coins": [ + { + "denom": "stake", + "amount": "10000000000" + } + ] + } + ], + "supply": [ + { + "denom": "stake", + "amount": "10000000000" + } + ], + "denom_metadata": [] + }, + "capability": { + "index": "1", + "owners": [] + }, + "crisis": { + "constant_fee": { + "denom": "stake", + "amount": "1000" + } + }, + "distribution": { + "params": { + "community_tax": "0.020000000000000000", + "base_proposer_reward": "0.010000000000000000", + "bonus_proposer_reward": "0.040000000000000000", + "withdraw_addr_enabled": true + }, + "fee_pool": { + "community_pool": [] + }, + "delegator_withdraw_infos": [], + "previous_proposer": "", + "outstanding_rewards": [], + "validator_accumulated_commissions": [], + "validator_historical_rewards": [], + "validator_current_rewards": [], + "delegator_starting_infos": [], + "validator_slash_events": [] + }, + "evidence": { + "evidence": [] + }, + "feegrant": { + "allowances": [] + }, + "genutil": { + "gen_txs": [ + { + "body": { + "messages": [ + { + "@type": "/cosmos.staking.v1beta1.MsgCreateValidator", + "description": { + "moniker": "validatoralice", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": { + "rate": "0.100000000000000000", + "max_rate": "0.200000000000000000", + "max_change_rate": "0.010000000000000000" + }, + "min_self_delegation": "1", + "delegator_address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "validator_address": "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + "pubkey": { + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": "RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10=" + }, + "value": { + "denom": "stake", + "amount": "500000000" + } + } + ], + "memo": "8339e14baab81c2a2350e261962263397a8d7fb0@7.7.8.254:26656", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [ + { + "public_key": { + "@type": "/cosmos.crypto.secp256k1.PubKey", + "key": "AsFC8tmbGGQSHthsVStbsQ13/+Yza9IT8KCSXXEN7y9f" + }, + "mode_info": { + "single": { + "mode": "SIGN_MODE_DIRECT" + } + }, + "sequence": "0" + } + ], + "fee": { + "amount": [], + "gas_limit": "200000", + "payer": "", + "granter": "" + } + }, + "signatures": [ + "rZuml3RLgrrZkUoNHw90FuHF/Orxzs0uiwflCkUOcvoA4bzohisjdQhkPWCn5aRw30mqZJGj1IxgXS15XleMvQ==" + ] + } + ] + }, + "gov": { + "starting_proposal_id": "1", + "deposits": [], + "votes": [], + "proposals": [], + "deposit_params": { + "min_deposit": [ + { + "denom": "stake", + "amount": "10000000" + } + ], + "max_deposit_period": "172800s" + }, + "voting_params": { + "voting_period": "20s" + }, + "tally_params": { + "quorum": "0.334000000000000000", + "threshold": "0.500000000000000000", + "veto_threshold": "0.334000000000000000" + } + }, + "ibc": { + "client_genesis": { + "clients": [], + "clients_consensus": [], + "clients_metadata": [], + "params": { + "allowed_clients": [ + "06-solomachine", + "07-tendermint" + ] + }, + "create_localhost": false, + "next_client_sequence": "0" + }, + "connection_genesis": { + "connections": [], + "client_connection_paths": [], + "next_connection_sequence": "0", + "params": { + "max_expected_time_per_block": "30000000000" + } + }, + "channel_genesis": { + "channels": [], + "acknowledgements": [], + "commitments": [], + "receipts": [], + "send_sequences": [], + "recv_sequences": [], + "ack_sequences": [], + "next_channel_sequence": "0" + } + }, + "mint": { + "minter": { + "inflation": "0.130000000000000000", + "annual_provisions": "0.000000000000000000" + }, + "params": { + "mint_denom": "stake", + "inflation_rate_change": "0.130000000000000000", + "inflation_max": "0.200000000000000000", + "inflation_min": "0.070000000000000000", + "goal_bonded": "0.670000000000000000", + "blocks_per_year": "6311520" + } + }, + "params": null, + "slashing": { + "params": { + "signed_blocks_window": "15", + "min_signed_per_window": "0.500000000000000000", + "downtime_jail_duration": "2s", + "slash_fraction_double_sign": "0.050000000000000000", + "slash_fraction_downtime": "0.010000000000000000" + }, + "signing_infos": [], + "missed_blocks": [] + }, + "staking": { + "params": { + "unbonding_time": "1814400s", + "max_validators": 100, + "max_entries": 7, + "historical_entries": 10000, + "bond_denom": "stake" + }, + "last_total_power": "0", + "last_validator_powers": [], + "validators": [], + "delegations": [], + "unbonding_delegations": [], + "redelegations": [], + "exported": false + }, + "transfer": { + "port_id": "transfer", + "denom_traces": [], + "params": { + "send_enabled": true, + "receive_enabled": true + } + }, + "upgrade": {}, + "vesting": {} + } + } \ No newline at end of file diff --git a/tests/e2e/testnet-scripts/start-changeover.sh b/tests/e2e/testnet-scripts/start-changeover.sh new file mode 100644 index 0000000000..d28479a8e7 --- /dev/null +++ b/tests/e2e/testnet-scripts/start-changeover.sh @@ -0,0 +1,187 @@ +#!/bin/bash +set -eux + +# The gaiad binary +BIN=$1 + +# JSON array of validator information +# [{ +# mnemonic: "crackle snap pop ... etc", +# allocation: "10000000000stake,10000000000footoken", +# stake: "5000000000stake", +# val_id: "alice", +# ip_suffix: "1", +# priv_validator_key: "{\"address\": \"3566F464673B2F069758DAE86FC25D04017BB147\",\"pub_key\": {\"type\": \"tendermint/PubKeyEd25519\",\"value\": \"XrLjKdc4mB2gfqplvnoySjSJq2E90RynUwaO3WhJutk=\"},\"priv_key\": {\"type\": \"tendermint/PrivKeyEd25519\",\"value\": \"czGSLs/Ocau8aJ5J5zQHMxf3d7NR0xjMECN6YGTIWqtesuMp1ziYHaB+qmW+ejJKNImrYT3RHKdTBo7daEm62Q==\"}}" +# node_key: "{\"priv_key\":{\"type\":\"tendermint/PrivKeyEd25519\",\"value\":\"alIHj6hXnzpLAadgb7+E2eeecwxoNdzuZrfhMX36EaD5/LgzL0ZUoVp7AK3np0K5T35JWLLv0jJKmeRIhG0GjA==\"}}" +# }, ... ] +VALIDATORS=$2 + +# The chain ID +CHAIN_ID=$3 + +# This is the first 3 fields of the IP addresses which will be used internally by the validators of this blockchain +# Recommended to use something starting with 7, since it is squatted by the DoD and is unroutable on the internet +# For example: "7.7.7" +CHAIN_IP_PREFIX=$4 + +# A transformation to apply to the genesis file, as a jq string +GENESIS_TRANSFORM=$5 + +# A sed string modifying the tendermint config +TENDERMINT_CONFIG_TRANSFORM=$6 + + +# CREATE VALIDATORS AND DO GENESIS CEREMONY +# !!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!! # +# data dir from the sovereign chain is copied to other nodes (namely bob and carol) +# alice simply performs a chain upgrade +echo "killing nodes" +pkill -f "^"interchain-security-sd &> /dev/null || true + +mkdir -p /root/.sovereign/config + +# apply genesis changes to existing genesis -> this creates the changeover genesis file wih intial validator set +jq "$GENESIS_TRANSFORM" /sover/validatoralice/config/genesis.json > /root/.sovereign/config/genesis.json + + +# Get number of nodes from length of validators array +NODES=$(echo "$VALIDATORS" | jq '. | length') + +# SETUP NETWORK NAMESPACES, see: https://adil.medium.com/container-networking-under-the-hood-network-namespaces-6b2b8fe8dc2a + +# Create virtual bridge device (acts like a switch) +ip link add name virtual-bridge type bridge || true + +for i in $(seq 0 $(($NODES - 1))); +do + # first validator is already setup + if [[ "$i" -ne 0 ]]; then + VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$i].val_id") + VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[$i].ip_suffix") + NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" + IP_ADDR="$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX/24" + + # Create network namespace + ip netns add $NET_NAMESPACE_NAME + # Create virtual ethernet device to connect with bridge + ip link add $NET_NAMESPACE_NAME-in type veth peer name $NET_NAMESPACE_NAME-out + # Connect input end of virtual ethernet device to namespace + ip link set $NET_NAMESPACE_NAME-in netns $NET_NAMESPACE_NAME + # Assign ip address to namespace + ip netns exec $NET_NAMESPACE_NAME ip addr add $IP_ADDR dev $NET_NAMESPACE_NAME-in + # Connect output end of virtual ethernet device to bridge + ip link set $NET_NAMESPACE_NAME-out master virtual-bridge + fi +done + +# Enable bridge interface +ip link set virtual-bridge up + +for i in $(seq 0 $(($NODES - 1))); +do + # first validator is already setup + if [[ "$i" -ne 0 ]]; then + VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$i].val_id") + NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" + + # Enable in/out interfaces for the namespace + ip link set $NET_NAMESPACE_NAME-out up + ip netns exec $NET_NAMESPACE_NAME ip link set dev $NET_NAMESPACE_NAME-in up + # Enable loopback device + ip netns exec $NET_NAMESPACE_NAME ip link set dev lo up + fi +done + +# Assign IP for bridge, to route between default network namespace and bridge +# BRIDGE_IP="$CHAIN_IP_PREFIX.254/24" +# ip addr add $BRIDGE_IP dev virtual-bridge + + +# HANDLE VALIDATOR HOMES, COPY OLD DATA FOLDER +for i in $(seq 0 $(($NODES - 1))); +do + # first validator is already setup + if [[ "$i" -ne 0 ]]; then + VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$i].val_id") + echo "$VALIDATORS" | jq -r ".[$i].mnemonic" | $BIN keys add validator$VAL_ID \ + --home /$CHAIN_ID/validator$VAL_ID \ + --keyring-backend test \ + --recover > /dev/null + + # Copy in the genesis.json + cp /sover/validatoralice/config/genesis.json /$CHAIN_ID/validator$VAL_ID/config/genesis.json + cp -r /sover/validatoralice/data /$CHAIN_ID/validator$VAL_ID/ + + # Copy in validator state file + # echo '{"height": "0","round": 0,"step": 0}' > /$CHAIN_ID/validator$VAL_ID/data/priv_validator_state.json + + + PRIV_VALIDATOR_KEY=$(echo "$VALIDATORS" | jq -r ".[$i].priv_validator_key") + if [[ "$PRIV_VALIDATOR_KEY" ]]; then + echo "$PRIV_VALIDATOR_KEY" > /$CHAIN_ID/validator$VAL_ID/config/priv_validator_key.json + fi + + NODE_KEY=$(echo "$VALIDATORS" | jq -r ".[$i].node_key") + if [[ "$NODE_KEY" ]]; then + echo "$NODE_KEY" > /$CHAIN_ID/validator$VAL_ID/config/node_key.json + fi + + # Modify tendermint configs of validator + if [ "$TENDERMINT_CONFIG_TRANSFORM" != "" ] ; then + #'s/foo/bar/;s/abc/def/' + sed -i "$TENDERMINT_CONFIG_TRANSFORM" $CHAIN_ID/validator$VAL_ID/config/config.toml + fi + fi +done + + +# START VALIDATOR NODES -> this will perform the sovereign upgrade and start the chain +# ALICE is a validator on the sovereign and also the validator on the consumer chain +# BOB, CAROL are not validators on the sovereign, they will become validator once the chain switches to the consumer chain +echo "Starting validator nodes..." +for i in $(seq 0 $(($NODES - 1))); +do + VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$i].val_id") + VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[$i].ip_suffix") + NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" + + GAIA_HOME="--home /$CHAIN_ID/validator$VAL_ID" + RPC_ADDRESS="--rpc.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26658" + GRPC_ADDRESS="--grpc.address $CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:9091" + LISTEN_ADDRESS="--address tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26655" + P2P_ADDRESS="--p2p.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26656" + # LOG_LEVEL="--log_level trace" # switch to trace to see panic messages and rich and all debug msgs + LOG_LEVEL="--log_level info" + ENABLE_WEBGRPC="--grpc-web.enable=false" + + PERSISTENT_PEERS="" + + for j in $(seq 0 $(($NODES - 1))); + do + if [ $i -ne $j ]; then + PEER_VAL_ID=$(echo "$VALIDATORS" | jq -r ".[$j].val_id") + PEER_VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[$j].ip_suffix") + NODE_ID=$($BIN tendermint show-node-id --home /$CHAIN_ID/validator$PEER_VAL_ID) + ADDRESS="$NODE_ID@$CHAIN_IP_PREFIX.$PEER_VAL_IP_SUFFIX:26656" + # (jq -r '.body.memo' /$CHAIN_ID/validator$j/config/gentx/*) # Getting the address from the gentx should also work + PERSISTENT_PEERS="$PERSISTENT_PEERS,$ADDRESS" + fi + done + + # Remove leading comma and concat to flag + PERSISTENT_PEERS="--p2p.persistent_peers ${PERSISTENT_PEERS:1}" + + ARGS="$GAIA_HOME $LISTEN_ADDRESS $RPC_ADDRESS $GRPC_ADDRESS $LOG_LEVEL $P2P_ADDRESS $ENABLE_WEBGRPC $PERSISTENT_PEERS" + ip netns exec $NET_NAMESPACE_NAME $BIN $ARGS start &> /$CHAIN_ID/validator$VAL_ID/logs & +done + +QUERY_NODE_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[0].ip_suffix") +echo "NODE SUFFIX: $QUERY_NODE_SUFFIX" +# poll for chain start +set +e +until $BIN query block --node "tcp://$CHAIN_IP_PREFIX.$QUERY_NODE_SUFFIX:26658" | grep -q -v '{"block_id":{"hash":"","parts":{"total":0,"hash":""}},"block":null}'; do sleep 0.3 ; done +set -e + +echo "done!!!!!!!!" + +read -p "Press Return to Close..." \ No newline at end of file diff --git a/tests/e2e/testnet-scripts/start-sovereign.sh b/tests/e2e/testnet-scripts/start-sovereign.sh new file mode 100644 index 0000000000..9daed9a207 --- /dev/null +++ b/tests/e2e/testnet-scripts/start-sovereign.sh @@ -0,0 +1,133 @@ +#!/bin/bash +set -eux + +# The gaiad binary +BIN=$1 + +# JSON array of validator information +# [{ +# mnemonic: "crackle snap pop ... etc", +# allocation: "10000000000stake,10000000000footoken", +# stake: "5000000000stake", +# val_id: "alice", +# ip_suffix: "1", +# priv_validator_key: "{\"address\": \"3566F464673B2F069758DAE86FC25D04017BB147\",\"pub_key\": {\"type\": \"tendermint/PubKeyEd25519\",\"value\": \"XrLjKdc4mB2gfqplvnoySjSJq2E90RynUwaO3WhJutk=\"},\"priv_key\": {\"type\": \"tendermint/PrivKeyEd25519\",\"value\": \"czGSLs/Ocau8aJ5J5zQHMxf3d7NR0xjMECN6YGTIWqtesuMp1ziYHaB+qmW+ejJKNImrYT3RHKdTBo7daEm62Q==\"}}" +# node_key: "{\"priv_key\":{\"type\":\"tendermint/PrivKeyEd25519\",\"value\":\"alIHj6hXnzpLAadgb7+E2eeecwxoNdzuZrfhMX36EaD5/LgzL0ZUoVp7AK3np0K5T35JWLLv0jJKmeRIhG0GjA==\"}}" +# }, ... ] +VALIDATORS=$2 + +# The chain ID +CHAIN_ID=$3 + +# This is the first 3 fields of the IP addresses which will be used internally by the validators of this blockchain +# Recommended to use something starting with 7, since it is squatted by the DoD and is unroutable on the internet +# For example: "7.7.7" +CHAIN_IP_PREFIX=$4 + +# A transformation to apply to the genesis file, as a jq string +GENESIS_TRANSFORM=$5 + +# A sed string modifying the tendermint config +TENDERMINT_CONFIG_TRANSFORM=$6 + +# SETUP NETWORK NAMESPACES, see: https://adil.medium.com/container-networking-under-the-hood-network-namespaces-6b2b8fe8dc2a + +# Create virtual bridge device (acts like a switch) +ip link add name virtual-bridge type bridge || true + +# used globally in the whole script +VAL_ID=$(echo "$VALIDATORS" | jq -r ".[0].val_id") +VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[0].ip_suffix") +NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" +IP_ADDR="$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX/24" + +# Create network namespace +ip netns add $NET_NAMESPACE_NAME +# Create virtual ethernet device to connect with bridge +ip link add $NET_NAMESPACE_NAME-in type veth peer name $NET_NAMESPACE_NAME-out +# Connect input end of virtual ethernet device to namespace +ip link set $NET_NAMESPACE_NAME-in netns $NET_NAMESPACE_NAME +# Assign ip address to namespace +ip netns exec $NET_NAMESPACE_NAME ip addr add $IP_ADDR dev $NET_NAMESPACE_NAME-in +# Connect output end of virtual ethernet device to bridge +ip link set $NET_NAMESPACE_NAME-out master virtual-bridge + +# Enable bridge interface +ip link set virtual-bridge up + +NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" +# Enable in/out interfaces for the namespace +ip link set $NET_NAMESPACE_NAME-out up +ip netns exec $NET_NAMESPACE_NAME ip link set dev $NET_NAMESPACE_NAME-in up +# Enable loopback device +ip netns exec $NET_NAMESPACE_NAME ip link set dev lo up + +# Assign IP for bridge, to route between default network namespace and bridge +BRIDGE_IP="$CHAIN_IP_PREFIX.254/24" +ip addr add $BRIDGE_IP dev virtual-bridge + +# first we start a genesis.json with the first validator +# the first validator will also collect the gentx's once gnerated +echo "$VALIDATORS" | jq -r ".[0].mnemonic" | $BIN init --home /$CHAIN_ID/validator$VAL_ID --chain-id=$CHAIN_ID validator$VAL_ID --recover > /dev/null + +# !!!!!!!!! IMPORTANT !!!!!!!!! # +# move the sovereign genesis to the correct validator home dir +cp /testnet-scripts/sovereign-genesis.json /$CHAIN_ID/validator$VAL_ID/config/genesis.json + +# Apply jq transformations to genesis file +jq "$GENESIS_TRANSFORM" /$CHAIN_ID/validator$VAL_ID/config/genesis.json > /$CHAIN_ID/edited-genesis.json +mv /$CHAIN_ID/edited-genesis.json /$CHAIN_ID/genesis.json +cp /$CHAIN_ID/genesis.json /$CHAIN_ID/validator$VAL_ID/config/genesis.json + + + +# SETUP LOCAL VALIDATOR STATE +echo '{"height": "0","round": 0,"step": 0}' > /$CHAIN_ID/validator$VAL_ID/data/priv_validator_state.json + +PRIV_VALIDATOR_KEY=$(echo "$VALIDATORS" | jq -r ".[0].priv_validator_key") +if [[ "$PRIV_VALIDATOR_KEY" ]]; then + echo "$PRIV_VALIDATOR_KEY" > /$CHAIN_ID/validator$VAL_ID/config/priv_validator_key.json +fi + +NODE_KEY=$(echo "$VALIDATORS" | jq -r ".[0].node_key") +if [[ "$NODE_KEY" ]]; then + echo "$NODE_KEY" > /$CHAIN_ID/validator$VAL_ID/config/node_key.json +fi + +echo "$VALIDATORS" | jq -r ".[0].mnemonic" | $BIN keys add validator$VAL_ID \ +--home /$CHAIN_ID/validator$VAL_ID \ +--keyring-backend test \ +--recover > /dev/null + +# Modify tendermint configs of validator +if [ "$TENDERMINT_CONFIG_TRANSFORM" != "" ] ; then + #'s/foo/bar/;s/abc/def/' + sed -i "$TENDERMINT_CONFIG_TRANSFORM" $CHAIN_ID/validator$VAL_ID/config/config.toml +fi + + +# START VALIDATOR NODE +VAL_IP_SUFFIX=$(echo "$VALIDATORS" | jq -r ".[0].ip_suffix") +NET_NAMESPACE_NAME="$CHAIN_ID-$VAL_ID" + +GAIA_HOME="--home /$CHAIN_ID/validator$VAL_ID" +RPC_ADDRESS="--rpc.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26658" +GRPC_ADDRESS="--grpc.address $CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:9091" +LISTEN_ADDRESS="--address tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26655" +P2P_ADDRESS="--p2p.laddr tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26656" +# LOG_LEVEL="--log_level trace" # switch to trace to see panic messages and rich and all debug msgs +LOG_LEVEL="--log_level info" +ENABLE_WEBGRPC="--grpc-web.enable=false" + +ARGS="$GAIA_HOME $LISTEN_ADDRESS $RPC_ADDRESS $GRPC_ADDRESS $LOG_LEVEL $P2P_ADDRESS $ENABLE_WEBGRPC" +ip netns exec $NET_NAMESPACE_NAME $BIN $ARGS start &> /$CHAIN_ID/validator$VAL_ID/logs & + + +# poll for chain start +set +e +until $BIN query block --node "tcp://$CHAIN_IP_PREFIX.$VAL_IP_SUFFIX:26658" | grep -q -v '{"block_id":{"hash":"","parts":{"total":0,"hash":""}},"block":null}'; do sleep 0.3 ; done +set -e + +echo "done!!!!!!!!" + +read -p "Press Return to Close..." \ No newline at end of file diff --git a/tests/integration/changeover.go b/tests/integration/changeover.go index 3bf4e11932..13d18a4026 100644 --- a/tests/integration/changeover.go +++ b/tests/integration/changeover.go @@ -34,7 +34,7 @@ func (suite *CCVTestSuite) TestRecycleTransferChannel() { // Setup state s.t. the consumer keeper emulates a consumer that was previously standalone consumerKeeper.MarkAsPrevStandaloneChain(suite.consumerCtx()) suite.Require().True(consumerKeeper.IsPrevStandaloneChain(suite.consumerCtx())) - suite.consumerApp.GetConsumerKeeper().SetStandaloneTransferChannelID(suite.consumerCtx(), resp.ChannelId) + suite.consumerApp.GetConsumerKeeper().SetDistributionTransmissionChannel(suite.consumerCtx(), resp.ChannelId) // Now finish setting up CCV channel suite.ExecuteCCVChannelHandshake(suite.path) diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index 426a94b4b6..77a597c726 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -237,6 +237,7 @@ func GetTestConsumerAdditionProp() *providertypes.ConsumerAdditionProposal { time.Now(), consumertypes.DefaultConsumerRedistributeFrac, consumertypes.DefaultBlocksPerDistributionTransmission, + "", consumertypes.DefaultHistoricalEntries, types.DefaultCCVTimeoutPeriod, consumertypes.DefaultTransferTimeoutPeriod, diff --git a/x/ccv/consumer/ibc_module.go b/x/ccv/consumer/ibc_module.go index d7191d0be0..b3dde3d48a 100644 --- a/x/ccv/consumer/ibc_module.go +++ b/x/ccv/consumer/ibc_module.go @@ -140,15 +140,10 @@ func (am AppModule) OnChanOpenAck( /////////////////////////////////////////////////// // Initialize distribution token transfer channel - // First check if an existing transfer channel exists, if this consumer was a previously standalone chain. - if am.keeper.IsPrevStandaloneChain(ctx) { - transChannelID := am.keeper.GetStandaloneTransferChannelID(ctx) - found := am.keeper.TransferChannelExists(ctx, transChannelID) - if found { - // If existing transfer channel is found, persist that channel ID and return - am.keeper.SetDistributionTransmissionChannel(ctx, transChannelID) - return nil - } + // First check if an existing transfer channel already exists. + transChannelID := am.keeper.GetDistributionTransmissionChannel(ctx) + if found := am.keeper.TransferChannelExists(ctx, transChannelID); found { + return nil } // NOTE The handshake for this channel is handled by the ibc-go/transfer diff --git a/x/ccv/consumer/ibc_module_test.go b/x/ccv/consumer/ibc_module_test.go index fc6c3d2200..4c8377a8c8 100644 --- a/x/ccv/consumer/ibc_module_test.go +++ b/x/ccv/consumer/ibc_module_test.go @@ -214,7 +214,7 @@ func TestOnChanOpenAck(t *testing.T) { expPass bool }{ { - "success", + "success - empty transferChannelID", func(keeper *consumerkeeper.Keeper, params *params, mocks testkeeper.MockedKeepers) { // Expected msg distrTransferMsg := channeltypes.NewMsgChannelOpenInit( @@ -226,8 +226,13 @@ func TestOnChanOpenAck(t *testing.T) { "", // signer unused ) + transferChannelID := "" + keeper.SetDistributionTransmissionChannel(params.ctx, transferChannelID) + // Expected mock calls gomock.InOrder( + mocks.MockChannelKeeper.EXPECT().GetChannel( + params.ctx, transfertypes.PortID, transferChannelID).Return(channeltypes.Channel{}, false).Times(1), mocks.MockChannelKeeper.EXPECT().GetChannel( params.ctx, params.portID, params.channelID).Return(channeltypes.Channel{ ConnectionHops: []string{"connectionID"}, diff --git a/x/ccv/consumer/keeper/changeover.go b/x/ccv/consumer/keeper/changeover.go index 50e1a57184..57770a077d 100644 --- a/x/ccv/consumer/keeper/changeover.go +++ b/x/ccv/consumer/keeper/changeover.go @@ -48,5 +48,6 @@ func (k Keeper) ChangeoverToConsumer(ctx sdk.Context) (initialValUpdates []abci. // Therefore we set the PreCCV state to false so the endblocker caller doesn't call this method again. k.DeletePreCCV(ctx) + k.Logger(ctx).Info("ICS changeover complete - you are now a consumer chain!") return initialValUpdates } diff --git a/x/ccv/consumer/keeper/keeper.go b/x/ccv/consumer/keeper/keeper.go index 54c1cd5577..d420bb9bd4 100644 --- a/x/ccv/consumer/keeper/keeper.go +++ b/x/ccv/consumer/keeper/keeper.go @@ -618,17 +618,3 @@ func (k Keeper) IsPrevStandaloneChain(ctx sdk.Context) bool { store := ctx.KVStore(k.storeKey) return store.Has(types.PrevStandaloneChainKey()) } - -// SetStandaloneTransferChannelID sets the channelID of an existing transfer channel, -// for a chain which used to be a standalone chain. -func (k Keeper) SetStandaloneTransferChannelID(ctx sdk.Context, channelID string) { - store := ctx.KVStore(k.storeKey) - store.Set(types.StandaloneTransferChannelIDKey(), []byte(channelID)) -} - -// GetStandaloneTransferChannelID returns the channelID of an existing transfer channel, -// for a chain which used to be a standalone chain. -func (k Keeper) GetStandaloneTransferChannelID(ctx sdk.Context) string { - store := ctx.KVStore(k.storeKey) - return string(store.Get(types.StandaloneTransferChannelIDKey())) -} diff --git a/x/ccv/consumer/keeper/keeper_test.go b/x/ccv/consumer/keeper/keeper_test.go index 4280f1de37..fe523ce7cf 100644 --- a/x/ccv/consumer/keeper/keeper_test.go +++ b/x/ccv/consumer/keeper/keeper_test.go @@ -541,19 +541,6 @@ func TestGetAllOutstandingDowntimes(t *testing.T) { require.Equal(t, result, expectedGetAllOrder) } -// TestStandaloneTransferChannelID tests the getter and setter for the existing transfer channel id -func TestStandaloneTransferChannelID(t *testing.T) { - ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - // Test that the default value is empty - require.Equal(t, "", ck.GetStandaloneTransferChannelID(ctx)) - - // Test that the value can be set and retrieved - ck.SetStandaloneTransferChannelID(ctx, "channelID1234") - require.Equal(t, "channelID1234", ck.GetStandaloneTransferChannelID(ctx)) -} - func TestPrevStandaloneChainFlag(t *testing.T) { ck, ctx, ctrl, _ := testkeeper.GetConsumerKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() diff --git a/x/ccv/consumer/keeper/migration_test.go b/x/ccv/consumer/keeper/migration_test.go index ee14d2bcc8..29574a4808 100644 --- a/x/ccv/consumer/keeper/migration_test.go +++ b/x/ccv/consumer/keeper/migration_test.go @@ -116,7 +116,7 @@ func (p *v1Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(consumertypes.KeyBlocksPerDistributionTransmission, p.BlocksPerDistributionTransmission, ccvtypes.ValidatePositiveInt64), paramtypes.NewParamSetPair(consumertypes.KeyDistributionTransmissionChannel, - p.DistributionTransmissionChannel, consumertypes.ValidateDistributionTransmissionChannel), + p.DistributionTransmissionChannel, ccvtypes.ValidateDistributionTransmissionChannel), paramtypes.NewParamSetPair(consumertypes.KeyProviderFeePoolAddrStr, p.ProviderFeePoolAddrStr, consumertypes.ValidateProviderFeePoolAddrStr), paramtypes.NewParamSetPair(ccvtypes.KeyCCVTimeoutPeriod, diff --git a/x/ccv/consumer/types/genesis_test.go b/x/ccv/consumer/types/genesis_test.go index f5e6ce075c..0398b39f54 100644 --- a/x/ccv/consumer/types/genesis_test.go +++ b/x/ccv/consumer/types/genesis_test.go @@ -210,7 +210,7 @@ func TestValidateInitialGenesisState(t *testing.T) { true, }, { - "invalid new consumer genesis state: invalid params", + "invalid new consumer genesis state: invalid params - ccvTimeoutPeriod", types.NewInitialGenesisState(cs, consensusState, valUpdates, types.NewParams( true, @@ -228,6 +228,25 @@ func TestValidateInitialGenesisState(t *testing.T) { )), true, }, + { + "invalid new consumer genesis state: invalid params - distributionTransmissionChannel", + types.NewInitialGenesisState(cs, consensusState, valUpdates, + types.NewParams( + true, + types.DefaultBlocksPerDistributionTransmission, + "badchannel/", + "", + ccv.DefaultCCVTimeoutPeriod, + types.DefaultTransferTimeoutPeriod, + types.DefaultConsumerRedistributeFrac, + types.DefaultHistoricalEntries, + types.DefaultConsumerUnbondingPeriod, + types.DefaultSoftOptOutThreshold, + []string{}, + []string{}, + )), + true, + }, } for _, c := range cases { diff --git a/x/ccv/consumer/types/params.go b/x/ccv/consumer/types/params.go index 509e3725b4..7ab51fbad8 100644 --- a/x/ccv/consumer/types/params.go +++ b/x/ccv/consumer/types/params.go @@ -113,7 +113,7 @@ func (p Params) Validate() error { if err := ccvtypes.ValidatePositiveInt64(p.BlocksPerDistributionTransmission); err != nil { return err } - if err := ValidateDistributionTransmissionChannel(p.DistributionTransmissionChannel); err != nil { + if err := ccvtypes.ValidateDistributionTransmissionChannel(p.DistributionTransmissionChannel); err != nil { return err } if err := ValidateProviderFeePoolAddrStr(p.ProviderFeePoolAddrStr); err != nil { @@ -153,7 +153,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeyBlocksPerDistributionTransmission, p.BlocksPerDistributionTransmission, ccvtypes.ValidatePositiveInt64), paramtypes.NewParamSetPair(KeyDistributionTransmissionChannel, - p.DistributionTransmissionChannel, ValidateDistributionTransmissionChannel), + p.DistributionTransmissionChannel, ccvtypes.ValidateDistributionTransmissionChannel), paramtypes.NewParamSetPair(KeyProviderFeePoolAddrStr, p.ProviderFeePoolAddrStr, ValidateProviderFeePoolAddrStr), paramtypes.NewParamSetPair(ccvtypes.KeyCCVTimeoutPeriod, @@ -175,15 +175,6 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { } } -func ValidateDistributionTransmissionChannel(i interface{}) error { - // Accept empty string as valid, since this will be the default value on genesis - if i == "" { - return nil - } - // Otherwise validate as usual for a channelID - return ccvtypes.ValidateChannelIdentifier(i) -} - func ValidateProviderFeePoolAddrStr(i interface{}) error { // Accept empty string as valid, since this will be the default value on genesis if i == "" { diff --git a/x/ccv/provider/client/proposal_handler.go b/x/ccv/provider/client/proposal_handler.go index 19848d9282..76491b86d2 100644 --- a/x/ccv/provider/client/proposal_handler.go +++ b/x/ccv/provider/client/proposal_handler.go @@ -58,6 +58,7 @@ Where proposal.json contains: "spawn_time": "2022-01-27T15:59:50.121607-08:00", "blocks_per_distribution_transmission": 1000, "consumer_redistribution_fraction": "0.75", + "distribution_transmission_channel": "", "historical_entries": 10000, "transfer_timeout_period": 3600000000000, "ccv_timeout_period": 2419200000000000, @@ -82,7 +83,8 @@ Where proposal.json contains: content := types.NewConsumerAdditionProposal( proposal.Title, proposal.Description, proposal.ChainId, proposal.InitialHeight, proposal.GenesisHash, proposal.BinaryHash, proposal.SpawnTime, - proposal.ConsumerRedistributionFraction, proposal.BlocksPerDistributionTransmission, proposal.HistoricalEntries, + proposal.ConsumerRedistributionFraction, proposal.BlocksPerDistributionTransmission, + proposal.DistributionTransmissionChannel, proposal.HistoricalEntries, proposal.CcvTimeoutPeriod, proposal.TransferTimeoutPeriod, proposal.UnbondingPeriod) from := clientCtx.GetFromAddress() @@ -224,6 +226,7 @@ type ConsumerAdditionProposalJSON struct { ConsumerRedistributionFraction string `json:"consumer_redistribution_fraction"` BlocksPerDistributionTransmission int64 `json:"blocks_per_distribution_transmission"` + DistributionTransmissionChannel string `json:"distribution_transmission_channel"` HistoricalEntries int64 `json:"historical_entries"` CcvTimeoutPeriod time.Duration `json:"ccv_timeout_period"` TransferTimeoutPeriod time.Duration `json:"transfer_timeout_period"` @@ -246,6 +249,7 @@ type ConsumerAdditionProposalReq struct { ConsumerRedistributionFraction string `json:"consumer_redistribution_fraction"` BlocksPerDistributionTransmission int64 `json:"blocks_per_distribution_transmission"` + DistributionTransmissionChannel string `json:"distribution_transmission_channel"` HistoricalEntries int64 `json:"historical_entries"` CcvTimeoutPeriod time.Duration `json:"ccv_timeout_period"` TransferTimeoutPeriod time.Duration `json:"transfer_timeout_period"` @@ -375,7 +379,8 @@ func postConsumerAdditionProposalHandlerFn(clientCtx client.Context) http.Handle content := types.NewConsumerAdditionProposal( req.Title, req.Description, req.ChainId, req.InitialHeight, req.GenesisHash, req.BinaryHash, req.SpawnTime, - req.ConsumerRedistributionFraction, req.BlocksPerDistributionTransmission, req.HistoricalEntries, + req.ConsumerRedistributionFraction, req.BlocksPerDistributionTransmission, + req.DistributionTransmissionChannel, req.HistoricalEntries, req.CcvTimeoutPeriod, req.TransferTimeoutPeriod, req.UnbondingPeriod) msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer) diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index dcff74385b..e50619c066 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -303,7 +303,7 @@ func (k Keeper) MakeConsumerGenesis( consumerGenesisParams := consumertypes.NewParams( true, prop.BlocksPerDistributionTransmission, - "", // distributionTransmissionChannel + prop.DistributionTransmissionChannel, "", // providerFeePoolAddrStr, prop.CcvTimeoutPeriod, prop.TransferTimeoutPeriod, diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index cb18a119b4..68ee7e9663 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -61,6 +61,7 @@ func TestHandleConsumerAdditionProposal(t *testing.T) { now, // Spawn time "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -85,6 +86,7 @@ func TestHandleConsumerAdditionProposal(t *testing.T) { now, "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -883,6 +885,7 @@ func TestBeginBlockInit(t *testing.T) { now.Add(-time.Hour*2).UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -893,6 +896,7 @@ func TestBeginBlockInit(t *testing.T) { now.Add(-time.Hour*1).UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -903,6 +907,7 @@ func TestBeginBlockInit(t *testing.T) { now.Add(time.Hour).UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -913,6 +918,7 @@ func TestBeginBlockInit(t *testing.T) { now.UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, diff --git a/x/ccv/provider/proposal_handler_test.go b/x/ccv/provider/proposal_handler_test.go index ddf3698b6f..1e8e9b34b5 100644 --- a/x/ccv/provider/proposal_handler_test.go +++ b/x/ccv/provider/proposal_handler_test.go @@ -41,6 +41,7 @@ func TestProviderProposalHandler(t *testing.T) { clienttypes.NewHeight(2, 3), []byte("gen_hash"), []byte("bin_hash"), now, "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -58,14 +59,14 @@ func TestProviderProposalHandler(t *testing.T) { }, { // no slash log for equivocation - name: "invalid equivocation posal", + name: "invalid equivocation proposal", content: providertypes.NewEquivocationProposal( "title", "description", []*evidencetypes.Equivocation{equivocation}), blockTime: hourFromNow, expValidEquivocation: false, }, { - name: "valid equivocation posal", + name: "valid equivocation proposal", content: providertypes.NewEquivocationProposal( "title", "description", []*evidencetypes.Equivocation{equivocation}), blockTime: hourFromNow, diff --git a/x/ccv/provider/types/proposal.go b/x/ccv/provider/types/proposal.go index 49fe00531f..913152e68c 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -36,7 +36,8 @@ func NewConsumerAdditionProposal(title, description, chainID string, initialHeight clienttypes.Height, genesisHash, binaryHash []byte, spawnTime time.Time, consumerRedistributionFraction string, - blocksPerDistributionTransmission, + blocksPerDistributionTransmission int64, + distributionTransmissionChannel string, historicalEntries int64, ccvTimeoutPeriod time.Duration, transferTimeoutPeriod time.Duration, @@ -52,6 +53,7 @@ func NewConsumerAdditionProposal(title, description, chainID string, SpawnTime: spawnTime, ConsumerRedistributionFraction: consumerRedistributionFraction, BlocksPerDistributionTransmission: blocksPerDistributionTransmission, + DistributionTransmissionChannel: distributionTransmissionChannel, HistoricalEntries: historicalEntries, CcvTimeoutPeriod: ccvTimeoutPeriod, TransferTimeoutPeriod: transferTimeoutPeriod, @@ -106,6 +108,10 @@ func (cccp *ConsumerAdditionProposal) ValidateBasic() error { return errorsmod.Wrap(ErrInvalidConsumerAdditionProposal, "blocks per distribution transmission cannot be < 1") } + if err := ccvtypes.ValidateDistributionTransmissionChannel(cccp.DistributionTransmissionChannel); err != nil { + return errorsmod.Wrap(ErrInvalidConsumerAdditionProposal, "distribution transmission channel") + } + if err := ccvtypes.ValidatePositiveInt64(cccp.HistoricalEntries); err != nil { return errorsmod.Wrap(ErrInvalidConsumerAdditionProposal, "historical entries cannot be < 1") } @@ -137,6 +143,7 @@ func (cccp *ConsumerAdditionProposal) String() string { SpawnTime: %s ConsumerRedistributionFraction: %s BlocksPerDistributionTransmission: %d + DistributionTransmissionChannel: %s HistoricalEntries: %d CcvTimeoutPeriod: %d TransferTimeoutPeriod: %d @@ -150,6 +157,7 @@ func (cccp *ConsumerAdditionProposal) String() string { cccp.SpawnTime, cccp.ConsumerRedistributionFraction, cccp.BlocksPerDistributionTransmission, + cccp.DistributionTransmissionChannel, cccp.HistoricalEntries, cccp.CcvTimeoutPeriod, cccp.TransferTimeoutPeriod, diff --git a/x/ccv/provider/types/proposal_test.go b/x/ccv/provider/types/proposal_test.go index db765a86ca..d1440eb9a0 100644 --- a/x/ccv/provider/types/proposal_test.go +++ b/x/ccv/provider/types/proposal_test.go @@ -32,6 +32,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -44,6 +45,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.0", // fraction can be 0.0 but not empty 10, + "", 10000, 100000000000, 100000000000, @@ -55,6 +57,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal(" ", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -66,6 +69,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", " ", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -86,6 +90,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { CcvTimeoutPeriod: 100000000000, TransferTimeoutPeriod: 100000000000, ConsumerRedistributionFraction: "0.75", + DistributionTransmissionChannel: "", HistoricalEntries: 10000, UnbondingPeriod: 100000000000, }, @@ -96,6 +101,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte(""), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -107,6 +113,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte(""), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -118,6 +125,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Time{}, "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -129,6 +137,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "", // fraction can be 0.0 but not empty 10, + "", 10000, 100000000000, 100000000000, @@ -140,17 +149,31 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 0, + "", 100000000000, 10000, 100000000000, 100000000000), false, }, + { + "distribution transmission channel is invalid", + types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), + "0.75", + 10, + "badchannel/", + 10000, + 100000000000, + 100000000000, + 100000000000), + false, + }, { "historical entries is invalid", types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", -2, 100000000000, 100000000000, @@ -162,6 +185,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 0, 100000000000, @@ -173,6 +197,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 0, @@ -184,6 +209,7 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { types.NewConsumerAdditionProposal("title", "description", "chainID", initialHeight, []byte("gen_hash"), []byte("bin_hash"), time.Now(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -207,6 +233,7 @@ func TestMarshalConsumerAdditionProposal(t *testing.T) { content := types.NewConsumerAdditionProposal("title", "description", "chainID", clienttypes.NewHeight(0, 1), []byte("gen_hash"), []byte("bin_hash"), time.Now().UTC(), "0.75", 10, + "", 10000, 100000000000, 100000000000, @@ -248,6 +275,7 @@ func TestConsumerAdditionProposalString(t *testing.T) { spawnTime, "0.75", 10001, + "", 500000, 100000000000, 10000000000, @@ -263,12 +291,14 @@ func TestConsumerAdditionProposalString(t *testing.T) { SpawnTime: %s ConsumerRedistributionFraction: %s BlocksPerDistributionTransmission: %d + DistributionTransmissionChannel: %s HistoricalEntries: %d CcvTimeoutPeriod: %d TransferTimeoutPeriod: %d UnbondingPeriod: %d`, initialHeight, []byte("gen_hash"), []byte("bin_hash"), spawnTime, "0.75", 10001, + "", 500000, 100000000000, 10000000000, diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index 37e175acea..acc95ca9af 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -74,6 +74,13 @@ type ConsumerAdditionProposal struct { // This param is a part of the cosmos sdk staking module. In the case of // a ccv enabled consumer chain, the ccv module acts as the staking module. HistoricalEntries int64 `protobuf:"varint,13,opt,name=historical_entries,json=historicalEntries,proto3" json:"historical_entries,omitempty"` + // The ID of a token transfer channel used for the Reward Distribution + // sub-protocol. If DistributionTransmissionChannel == "", a new transfer + // channel is created on top of the same connection as the CCV channel. + // Note that transfer_channel_id is the ID of the channel end on the consumer chain. + // it is most relevant for chains performing a sovereign to consumer changeover + // in order to maintan the existing ibc transfer channel + DistributionTransmissionChannel string `protobuf:"bytes,14,opt,name=distribution_transmission_channel,json=distributionTransmissionChannel,proto3" json:"distribution_transmission_channel,omitempty"` } func (m *ConsumerAdditionProposal) Reset() { *m = ConsumerAdditionProposal{} } @@ -1220,106 +1227,107 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1579 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xcd, 0x72, 0xdc, 0xc6, - 0x11, 0x26, 0xb8, 0xfc, 0xdb, 0x59, 0xfe, 0x48, 0x10, 0x65, 0x2d, 0x15, 0x66, 0xb9, 0x86, 0x13, - 0x17, 0x53, 0x2e, 0x63, 0x43, 0xfa, 0x92, 0x52, 0xc5, 0xe5, 0x22, 0x57, 0x96, 0xc5, 0x30, 0xb6, - 0xd6, 0x20, 0x43, 0x55, 0x92, 0x03, 0x6a, 0x30, 0x68, 0xed, 0x4e, 0x11, 0xc0, 0x40, 0x33, 0x03, - 0x48, 0x7b, 0xc9, 0x39, 0x47, 0xa7, 0x72, 0x71, 0x25, 0x17, 0xe7, 0x09, 0xf2, 0x1a, 0x3e, 0xfa, - 0x98, 0x93, 0x93, 0xa2, 0x0e, 0x39, 0xe4, 0x25, 0x52, 0x33, 0xf8, 0x5f, 0x52, 0xca, 0xaa, 0x92, - 0xdc, 0x30, 0x3d, 0xdd, 0x5f, 0xf7, 0x4c, 0x77, 0x7f, 0x3d, 0x40, 0x87, 0x34, 0x92, 0xc0, 0xc9, - 0x04, 0xd3, 0xc8, 0x15, 0x40, 0x12, 0x4e, 0xe5, 0x74, 0x40, 0x48, 0x3a, 0x88, 0x39, 0x4b, 0xa9, - 0x0f, 0x7c, 0x90, 0x1e, 0x94, 0xdf, 0x76, 0xcc, 0x99, 0x64, 0xe6, 0x7b, 0x37, 0xd8, 0xd8, 0x84, - 0xa4, 0x76, 0xa9, 0x97, 0x1e, 0xdc, 0xdf, 0x1e, 0xb3, 0x31, 0xd3, 0xfa, 0x03, 0xf5, 0x95, 0x99, - 0xde, 0xdf, 0x1b, 0x33, 0x36, 0x0e, 0x60, 0xa0, 0x57, 0x5e, 0xf2, 0x6c, 0x20, 0x69, 0x08, 0x42, - 0xe2, 0x30, 0xce, 0x15, 0x7a, 0xb3, 0x0a, 0x7e, 0xc2, 0xb1, 0xa4, 0x2c, 0x2a, 0x00, 0xa8, 0x47, - 0x06, 0x84, 0x71, 0x18, 0x90, 0x80, 0x42, 0x24, 0x55, 0x78, 0xd9, 0x57, 0xae, 0x30, 0x50, 0x0a, - 0x01, 0x1d, 0x4f, 0x64, 0x26, 0x16, 0x03, 0x09, 0x91, 0x0f, 0x3c, 0xa4, 0x99, 0x72, 0xb5, 0xca, - 0x0d, 0x76, 0x6b, 0xfb, 0x84, 0x4f, 0x63, 0xc9, 0x06, 0x97, 0x30, 0x15, 0xf9, 0xee, 0xfb, 0x84, - 0x89, 0x90, 0x89, 0x01, 0xa8, 0x83, 0x45, 0x04, 0x06, 0xe9, 0x81, 0x07, 0x12, 0x1f, 0x94, 0x82, - 0x22, 0xee, 0x5c, 0xcf, 0xc3, 0xa2, 0xd2, 0x21, 0x8c, 0xe6, 0x71, 0x5b, 0x7f, 0x5c, 0x41, 0xdd, - 0x21, 0x8b, 0x44, 0x12, 0x02, 0x3f, 0xf2, 0x7d, 0xaa, 0x8e, 0x34, 0xe2, 0x2c, 0x66, 0x02, 0x07, - 0xe6, 0x36, 0x5a, 0x96, 0x54, 0x06, 0xd0, 0x35, 0xfa, 0xc6, 0x7e, 0xdb, 0xc9, 0x16, 0x66, 0x1f, - 0x75, 0x7c, 0x10, 0x84, 0xd3, 0x58, 0x29, 0x77, 0x17, 0xf5, 0x5e, 0x5d, 0x64, 0xee, 0xa0, 0xb5, - 0x2c, 0x0b, 0xd4, 0xef, 0xb6, 0xf4, 0xf6, 0xaa, 0x5e, 0x9f, 0xf8, 0xe6, 0x67, 0x68, 0x93, 0x46, - 0x54, 0x52, 0x1c, 0xb8, 0x13, 0x50, 0xb7, 0xd1, 0x5d, 0xea, 0x1b, 0xfb, 0x9d, 0xc3, 0xfb, 0x36, - 0xf5, 0x88, 0xad, 0x2e, 0xd0, 0xce, 0xaf, 0x2d, 0x3d, 0xb0, 0x1f, 0x6b, 0x8d, 0xe3, 0xa5, 0x6f, - 0xbf, 0xdf, 0x5b, 0x70, 0x36, 0x72, 0xbb, 0x4c, 0x68, 0xbe, 0x8b, 0xd6, 0xc7, 0x10, 0x81, 0xa0, - 0xc2, 0x9d, 0x60, 0x31, 0xe9, 0x2e, 0xf7, 0x8d, 0xfd, 0x75, 0xa7, 0x93, 0xcb, 0x1e, 0x63, 0x31, - 0x31, 0xf7, 0x50, 0xc7, 0xa3, 0x11, 0xe6, 0xd3, 0x4c, 0x63, 0x45, 0x6b, 0xa0, 0x4c, 0xa4, 0x15, - 0x86, 0x08, 0x89, 0x18, 0xbf, 0x88, 0x5c, 0x95, 0xed, 0xee, 0x6a, 0x1e, 0x48, 0x96, 0x69, 0xbb, - 0xc8, 0xb4, 0x7d, 0x5e, 0x94, 0xc2, 0xf1, 0x9a, 0x0a, 0xe4, 0xab, 0xbf, 0xef, 0x19, 0x4e, 0x5b, - 0xdb, 0xa9, 0x1d, 0xf3, 0x0b, 0x74, 0x2b, 0x89, 0x3c, 0x16, 0xf9, 0x34, 0x1a, 0xbb, 0x31, 0x70, - 0xca, 0xfc, 0xee, 0x9a, 0x86, 0xda, 0xb9, 0x06, 0xf5, 0x30, 0x2f, 0x9a, 0x0c, 0xe9, 0x6b, 0x85, - 0xb4, 0x55, 0x1a, 0x8f, 0xb4, 0xad, 0xf9, 0x25, 0x32, 0x09, 0x49, 0x75, 0x48, 0x2c, 0x91, 0x05, - 0x62, 0x7b, 0x7e, 0xc4, 0x5b, 0x84, 0xa4, 0xe7, 0x99, 0x75, 0x0e, 0xf9, 0x5b, 0x74, 0x4f, 0x72, - 0x1c, 0x89, 0x67, 0xc0, 0x67, 0x71, 0xd1, 0xfc, 0xb8, 0x77, 0x0b, 0x8c, 0x26, 0xf8, 0x63, 0xd4, - 0x27, 0x79, 0x01, 0xb9, 0x1c, 0x7c, 0x2a, 0x24, 0xa7, 0x5e, 0xa2, 0x6c, 0xdd, 0x67, 0x1c, 0x13, - 0x5d, 0x23, 0x1d, 0x5d, 0x04, 0xbd, 0x42, 0xcf, 0x69, 0xa8, 0x3d, 0xca, 0xb5, 0xcc, 0x27, 0xe8, - 0x47, 0x5e, 0xc0, 0xc8, 0xa5, 0x50, 0xc1, 0xb9, 0x0d, 0x24, 0xed, 0x3a, 0xa4, 0x42, 0x28, 0xb4, - 0xf5, 0xbe, 0xb1, 0xdf, 0x72, 0xde, 0xcd, 0x74, 0x47, 0xc0, 0x1f, 0xd6, 0x34, 0xcf, 0x6b, 0x8a, - 0xe6, 0x87, 0xc8, 0x9c, 0x50, 0x21, 0x19, 0xa7, 0x04, 0x07, 0x2e, 0x44, 0x92, 0x53, 0x10, 0xdd, - 0x0d, 0x6d, 0x7e, 0xbb, 0xda, 0xf9, 0x34, 0xdb, 0x78, 0xb0, 0xf6, 0xfb, 0x6f, 0xf6, 0x16, 0xbe, - 0xfe, 0x66, 0x6f, 0xc1, 0xfa, 0xab, 0x81, 0xee, 0x0d, 0xcb, 0x60, 0x43, 0x96, 0xe2, 0xe0, 0xff, - 0xd9, 0x14, 0x47, 0xa8, 0x2d, 0x24, 0x8b, 0xb3, 0x32, 0x5c, 0x7a, 0x8b, 0x32, 0x5c, 0x53, 0x66, - 0x6a, 0xc3, 0xfa, 0xb3, 0x81, 0xb6, 0x3f, 0x7d, 0x9e, 0xd0, 0x94, 0x11, 0xfc, 0x3f, 0xe9, 0xe1, - 0x53, 0xb4, 0x01, 0x35, 0x3c, 0xd1, 0x6d, 0xf5, 0x5b, 0xfb, 0x9d, 0xc3, 0x1f, 0xdb, 0x19, 0xa1, - 0xd8, 0x25, 0xcf, 0xe4, 0xa4, 0x62, 0xd7, 0xbd, 0x3b, 0x4d, 0x5b, 0xeb, 0x5f, 0x06, 0xba, 0xf5, - 0x59, 0xc0, 0x3c, 0x1c, 0x9c, 0x05, 0x58, 0x4c, 0xd4, 0x85, 0x4f, 0xd5, 0xa9, 0x39, 0xe4, 0x95, - 0xae, 0xa3, 0x9b, 0xfb, 0xd4, 0xca, 0x4c, 0xf7, 0xde, 0x27, 0xe8, 0x76, 0x59, 0x7b, 0xe5, 0xe5, - 0xea, 0xc3, 0x1c, 0xdf, 0xb9, 0xfa, 0x7e, 0x6f, 0xab, 0xc8, 0xe1, 0x50, 0x5f, 0xf4, 0x43, 0x67, - 0x8b, 0x34, 0x04, 0xbe, 0xd9, 0x43, 0x1d, 0xea, 0x11, 0x57, 0xc0, 0x73, 0x37, 0x4a, 0x42, 0x9d, - 0x97, 0x25, 0xa7, 0x4d, 0x3d, 0x72, 0x06, 0xcf, 0xbf, 0x48, 0x42, 0xf3, 0x23, 0xf4, 0x4e, 0x31, - 0x3c, 0xdc, 0x14, 0x07, 0xae, 0xb2, 0x77, 0xb1, 0xef, 0x73, 0x9d, 0xa6, 0x75, 0xe7, 0x4e, 0xb1, - 0x7b, 0x81, 0x03, 0xe5, 0xec, 0xc8, 0xf7, 0xb9, 0xf5, 0xcf, 0x65, 0xb4, 0x32, 0xc2, 0x1c, 0x87, - 0xc2, 0x3c, 0x47, 0x5b, 0x12, 0xc2, 0x38, 0xc0, 0x12, 0xdc, 0x8c, 0xd7, 0xf2, 0x93, 0x7e, 0xa0, - 0xf9, 0xae, 0x3e, 0x0f, 0xec, 0xda, 0x04, 0x48, 0x0f, 0xec, 0xa1, 0x96, 0x9e, 0x49, 0x2c, 0xc1, - 0xd9, 0x2c, 0x30, 0x32, 0xa1, 0xf9, 0x33, 0xd4, 0x95, 0x3c, 0x11, 0xb2, 0x62, 0x9c, 0xaa, 0xd5, - 0xb2, 0x54, 0xbe, 0x53, 0xec, 0x67, 0x4d, 0x5a, 0xb6, 0xd8, 0xcd, 0xe4, 0xd2, 0xfa, 0x6f, 0xc8, - 0xe5, 0x0c, 0xdd, 0x51, 0xcc, 0x3c, 0x8b, 0xb9, 0x34, 0x3f, 0xe6, 0x6d, 0x65, 0xdf, 0x04, 0xfd, - 0x12, 0x99, 0xa9, 0x20, 0xb3, 0x98, 0xcb, 0x6f, 0x11, 0x67, 0x2a, 0x48, 0x13, 0xd2, 0x47, 0xbb, - 0x42, 0x15, 0x9f, 0x1b, 0x82, 0xd4, 0x54, 0x15, 0x07, 0x10, 0x51, 0x31, 0x29, 0xc0, 0x57, 0xe6, - 0x07, 0xdf, 0xd1, 0x40, 0x9f, 0x2b, 0x1c, 0xa7, 0x80, 0xc9, 0xbd, 0x0c, 0x51, 0xef, 0x66, 0x2f, - 0x65, 0x82, 0x56, 0x75, 0x82, 0x7e, 0x70, 0x03, 0x44, 0x99, 0xa5, 0x43, 0x74, 0x37, 0xc4, 0x2f, - 0x5d, 0x39, 0xe1, 0x4c, 0xca, 0x00, 0x7c, 0x37, 0xc6, 0xe4, 0x12, 0xa4, 0xd0, 0x73, 0xa5, 0xe5, - 0xdc, 0x09, 0xf1, 0xcb, 0xf3, 0x62, 0x6f, 0x94, 0x6d, 0x99, 0x02, 0xbd, 0x5f, 0xa3, 0xe1, 0x17, - 0x98, 0xfb, 0xae, 0x0f, 0x11, 0x0b, 0x5d, 0x0e, 0x63, 0xc5, 0x8f, 0x38, 0x63, 0x64, 0x80, 0x72, - 0x94, 0xe4, 0x8d, 0xac, 0x5e, 0x06, 0x65, 0x13, 0x0f, 0x19, 0x8d, 0xf2, 0x79, 0x6b, 0x55, 0x6c, - 0xad, 0xd0, 0x1e, 0x2a, 0x30, 0xa7, 0x86, 0xf5, 0x08, 0xc0, 0xf2, 0xd0, 0xed, 0xc7, 0x38, 0xf2, - 0xc5, 0x04, 0x5f, 0xc2, 0xe7, 0x20, 0xb1, 0x8f, 0x25, 0x6e, 0xf4, 0xcc, 0x33, 0x00, 0x37, 0x66, - 0x2c, 0xc8, 0x7a, 0x26, 0xa3, 0xa0, 0xb2, 0x67, 0x1e, 0x01, 0x8c, 0x18, 0x0b, 0x54, 0xcf, 0x98, - 0x5d, 0xb4, 0x9a, 0x02, 0x17, 0x55, 0x05, 0x17, 0x4b, 0xeb, 0x27, 0xa8, 0xad, 0x49, 0xe3, 0x88, - 0x5c, 0x0a, 0x73, 0x17, 0xb5, 0x15, 0x12, 0x08, 0x01, 0xa2, 0x6b, 0xf4, 0x5b, 0xfb, 0x6d, 0xa7, - 0x12, 0x58, 0x12, 0xed, 0xbc, 0xee, 0x2d, 0x23, 0xcc, 0xa7, 0x68, 0x35, 0x06, 0x3d, 0x68, 0xb5, - 0x61, 0xe7, 0xf0, 0x63, 0x7b, 0x8e, 0xf7, 0xa2, 0xfd, 0x3a, 0x40, 0xa7, 0x40, 0xb3, 0x78, 0xf5, - 0x82, 0x9a, 0x99, 0x15, 0xc2, 0xbc, 0x98, 0x75, 0xfa, 0xf3, 0xb7, 0x72, 0x3a, 0x83, 0x57, 0xf9, - 0xfc, 0x00, 0x75, 0x8e, 0xb2, 0x63, 0xff, 0x92, 0x0a, 0x79, 0xfd, 0x5a, 0xd6, 0xeb, 0xd7, 0xf2, - 0x0b, 0xb4, 0x39, 0x9c, 0xe0, 0x28, 0x82, 0xe0, 0x9c, 0x69, 0xe2, 0x33, 0x7f, 0x88, 0x10, 0xc9, - 0x24, 0x8a, 0x30, 0xb3, 0xb4, 0xb4, 0x73, 0xc9, 0x89, 0xdf, 0x18, 0x55, 0x8b, 0x8d, 0x51, 0x65, - 0x39, 0x68, 0xeb, 0x42, 0x90, 0x5f, 0x15, 0x6f, 0x96, 0x27, 0xb1, 0x30, 0xef, 0xa2, 0x15, 0xd5, - 0xab, 0x39, 0xd0, 0x92, 0xb3, 0x9c, 0x0a, 0x72, 0xe2, 0x9b, 0xfb, 0xf5, 0x77, 0x11, 0x8b, 0x5d, - 0xea, 0x8b, 0xee, 0x62, 0xbf, 0xb5, 0xbf, 0xe4, 0x6c, 0x26, 0x95, 0xf9, 0x89, 0x2f, 0xac, 0x5f, - 0xa3, 0x4e, 0x0d, 0xd0, 0xdc, 0x44, 0x8b, 0x25, 0xd6, 0x22, 0xf5, 0xcd, 0x07, 0x68, 0xa7, 0x02, - 0x6a, 0xd2, 0x7d, 0x86, 0xd8, 0x76, 0xee, 0x95, 0x0a, 0x0d, 0xc6, 0x17, 0xd6, 0x13, 0xb4, 0x7d, - 0x52, 0x91, 0x4b, 0x39, 0x4c, 0x1a, 0x27, 0x34, 0x9a, 0xc3, 0x78, 0x17, 0xb5, 0xcb, 0xc7, 0xbf, - 0x3e, 0xfd, 0x92, 0x53, 0x09, 0xac, 0x10, 0xdd, 0xba, 0x10, 0xe4, 0x0c, 0x22, 0xbf, 0x02, 0x7b, - 0xcd, 0x05, 0x1c, 0xcf, 0x02, 0xcd, 0xfd, 0xb8, 0xac, 0xdc, 0xfd, 0xc1, 0x40, 0xdd, 0x53, 0x98, - 0x1e, 0x09, 0x41, 0xc7, 0x51, 0x08, 0x91, 0x54, 0x64, 0x81, 0x09, 0xa8, 0x4f, 0xf3, 0x3d, 0xb4, - 0x51, 0x36, 0x5a, 0xd9, 0x5f, 0xeb, 0xce, 0x7a, 0x21, 0xd4, 0x8d, 0xf5, 0x00, 0xa1, 0x98, 0x43, - 0xea, 0x12, 0xf7, 0x12, 0xa6, 0x79, 0x18, 0xbb, 0xf5, 0x59, 0x93, 0xfd, 0x5b, 0xd8, 0xa3, 0xc4, - 0x0b, 0x28, 0x39, 0x85, 0xa9, 0xb3, 0xa6, 0xf4, 0x87, 0xa7, 0x30, 0x55, 0x6f, 0x87, 0x98, 0xbd, - 0x00, 0xae, 0x07, 0x44, 0xcb, 0xc9, 0x16, 0xd6, 0x9f, 0x0c, 0x74, 0xef, 0x02, 0x07, 0xd4, 0xc7, - 0x92, 0xf1, 0xe2, 0xbe, 0x47, 0x89, 0xa7, 0x2c, 0xde, 0x70, 0xaf, 0xd7, 0xa2, 0x5d, 0xbc, 0x21, - 0xda, 0x4f, 0xd0, 0x7a, 0x99, 0x61, 0x15, 0x6f, 0x6b, 0x8e, 0x78, 0x3b, 0x85, 0xc5, 0x29, 0x4c, - 0xad, 0xdf, 0xd5, 0x62, 0x3b, 0x9e, 0xd6, 0x9a, 0x97, 0xff, 0x87, 0xd8, 0x4a, 0xb7, 0xf5, 0xd8, - 0x48, 0xdd, 0xfe, 0xda, 0x01, 0x5a, 0xd7, 0x0f, 0x60, 0xfd, 0xc5, 0x40, 0xdb, 0x75, 0xaf, 0xe2, - 0x9c, 0x8d, 0x78, 0x12, 0xc1, 0x9b, 0xbc, 0x57, 0xf5, 0xb3, 0x58, 0xaf, 0x9f, 0xa7, 0x68, 0xb3, - 0x11, 0x94, 0xc8, 0x6f, 0xe3, 0xa7, 0x73, 0x51, 0x48, 0x8d, 0x1e, 0x9c, 0x8d, 0xfa, 0x39, 0xc4, - 0xf1, 0xd3, 0x6f, 0xaf, 0x7a, 0xc6, 0x77, 0x57, 0x3d, 0xe3, 0x1f, 0x57, 0x3d, 0xe3, 0xab, 0x57, - 0xbd, 0x85, 0xef, 0x5e, 0xf5, 0x16, 0xfe, 0xf6, 0xaa, 0xb7, 0xf0, 0x9b, 0x8f, 0xc7, 0x54, 0x4e, - 0x12, 0xcf, 0x26, 0x2c, 0x1c, 0xe4, 0x3f, 0x8e, 0x95, 0xaf, 0x0f, 0xcb, 0xff, 0xf0, 0xf4, 0x70, - 0xf0, 0xb2, 0xf9, 0x33, 0x2e, 0xa7, 0x31, 0x08, 0x6f, 0x45, 0x97, 0xf5, 0x47, 0xff, 0x0e, 0x00, - 0x00, 0xff, 0xff, 0x50, 0x7d, 0x10, 0xbd, 0xbd, 0x0f, 0x00, 0x00, + // 1598 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0x4b, 0x73, 0xdc, 0xc6, + 0x11, 0x26, 0xb8, 0x7c, 0xed, 0x2c, 0x1f, 0x12, 0x44, 0x59, 0x4b, 0x85, 0x59, 0xae, 0xe0, 0xc4, + 0xc5, 0x94, 0xcb, 0xd8, 0x90, 0xbe, 0xa4, 0x54, 0x71, 0xb9, 0xc8, 0x95, 0x65, 0xd1, 0x8c, 0xad, + 0x35, 0xc8, 0x50, 0x95, 0xe4, 0x80, 0x1a, 0x0c, 0x5a, 0xbb, 0x53, 0x04, 0x30, 0xd0, 0xcc, 0x00, + 0xd2, 0x5e, 0x72, 0xce, 0xd1, 0xb9, 0xb9, 0x92, 0x8b, 0xf3, 0x0b, 0xf2, 0x37, 0x7c, 0xf4, 0x31, + 0x27, 0x3b, 0x45, 0x1d, 0x72, 0xc8, 0x9f, 0x48, 0xcd, 0xe0, 0xcd, 0x87, 0xb2, 0xaa, 0xc4, 0x37, + 0xcc, 0x4c, 0xf7, 0xd7, 0xdd, 0xd3, 0xdd, 0x5f, 0x0f, 0xd0, 0x3e, 0x8d, 0x24, 0x70, 0x32, 0xc1, + 0x34, 0x72, 0x05, 0x90, 0x84, 0x53, 0x39, 0x1d, 0x10, 0x92, 0x0e, 0x62, 0xce, 0x52, 0xea, 0x03, + 0x1f, 0xa4, 0x7b, 0xe5, 0xb7, 0x1d, 0x73, 0x26, 0x99, 0xf9, 0xee, 0x35, 0x3a, 0x36, 0x21, 0xa9, + 0x5d, 0xca, 0xa5, 0x7b, 0xf7, 0x37, 0xc7, 0x6c, 0xcc, 0xb4, 0xfc, 0x40, 0x7d, 0x65, 0xaa, 0xf7, + 0x77, 0xc6, 0x8c, 0x8d, 0x03, 0x18, 0xe8, 0x95, 0x97, 0x3c, 0x1f, 0x48, 0x1a, 0x82, 0x90, 0x38, + 0x8c, 0x73, 0x81, 0xde, 0x65, 0x01, 0x3f, 0xe1, 0x58, 0x52, 0x16, 0x15, 0x00, 0xd4, 0x23, 0x03, + 0xc2, 0x38, 0x0c, 0x48, 0x40, 0x21, 0x92, 0xca, 0xbd, 0xec, 0x2b, 0x17, 0x18, 0x28, 0x81, 0x80, + 0x8e, 0x27, 0x32, 0xdb, 0x16, 0x03, 0x09, 0x91, 0x0f, 0x3c, 0xa4, 0x99, 0x70, 0xb5, 0xca, 0x15, + 0xb6, 0x6b, 0xe7, 0x84, 0x4f, 0x63, 0xc9, 0x06, 0xe7, 0x30, 0x15, 0xf9, 0xe9, 0x7b, 0x84, 0x89, + 0x90, 0x89, 0x01, 0xa8, 0xc0, 0x22, 0x02, 0x83, 0x74, 0xcf, 0x03, 0x89, 0xf7, 0xca, 0x8d, 0xc2, + 0xef, 0x5c, 0xce, 0xc3, 0xa2, 0x92, 0x21, 0x8c, 0xe6, 0x7e, 0x5b, 0x3f, 0x2c, 0xa1, 0xee, 0x90, + 0x45, 0x22, 0x09, 0x81, 0x1f, 0xf8, 0x3e, 0x55, 0x21, 0x8d, 0x38, 0x8b, 0x99, 0xc0, 0x81, 0xb9, + 0x89, 0x16, 0x25, 0x95, 0x01, 0x74, 0x8d, 0xbe, 0xb1, 0xdb, 0x76, 0xb2, 0x85, 0xd9, 0x47, 0x1d, + 0x1f, 0x04, 0xe1, 0x34, 0x56, 0xc2, 0xdd, 0x79, 0x7d, 0x56, 0xdf, 0x32, 0xb7, 0xd0, 0x4a, 0x96, + 0x05, 0xea, 0x77, 0x5b, 0xfa, 0x78, 0x59, 0xaf, 0x8f, 0x7c, 0xf3, 0x53, 0xb4, 0x4e, 0x23, 0x2a, + 0x29, 0x0e, 0xdc, 0x09, 0xa8, 0xdb, 0xe8, 0x2e, 0xf4, 0x8d, 0xdd, 0xce, 0xfe, 0x7d, 0x9b, 0x7a, + 0xc4, 0x56, 0x17, 0x68, 0xe7, 0xd7, 0x96, 0xee, 0xd9, 0x4f, 0xb4, 0xc4, 0xe1, 0xc2, 0xb7, 0xdf, + 0xef, 0xcc, 0x39, 0x6b, 0xb9, 0x5e, 0xb6, 0x69, 0x3e, 0x40, 0xab, 0x63, 0x88, 0x40, 0x50, 0xe1, + 0x4e, 0xb0, 0x98, 0x74, 0x17, 0xfb, 0xc6, 0xee, 0xaa, 0xd3, 0xc9, 0xf7, 0x9e, 0x60, 0x31, 0x31, + 0x77, 0x50, 0xc7, 0xa3, 0x11, 0xe6, 0xd3, 0x4c, 0x62, 0x49, 0x4b, 0xa0, 0x6c, 0x4b, 0x0b, 0x0c, + 0x11, 0x12, 0x31, 0x7e, 0x19, 0xb9, 0x2a, 0xdb, 0xdd, 0xe5, 0xdc, 0x91, 0x2c, 0xd3, 0x76, 0x91, + 0x69, 0xfb, 0xb4, 0x28, 0x85, 0xc3, 0x15, 0xe5, 0xc8, 0x57, 0x3f, 0xec, 0x18, 0x4e, 0x5b, 0xeb, + 0xa9, 0x13, 0xf3, 0x0b, 0x74, 0x2b, 0x89, 0x3c, 0x16, 0xf9, 0x34, 0x1a, 0xbb, 0x31, 0x70, 0xca, + 0xfc, 0xee, 0x8a, 0x86, 0xda, 0xba, 0x02, 0xf5, 0x28, 0x2f, 0x9a, 0x0c, 0xe9, 0x6b, 0x85, 0xb4, + 0x51, 0x2a, 0x8f, 0xb4, 0xae, 0xf9, 0x25, 0x32, 0x09, 0x49, 0xb5, 0x4b, 0x2c, 0x91, 0x05, 0x62, + 0x7b, 0x76, 0xc4, 0x5b, 0x84, 0xa4, 0xa7, 0x99, 0x76, 0x0e, 0xf9, 0x07, 0x74, 0x4f, 0x72, 0x1c, + 0x89, 0xe7, 0xc0, 0x2f, 0xe3, 0xa2, 0xd9, 0x71, 0xef, 0x16, 0x18, 0x4d, 0xf0, 0x27, 0xa8, 0x4f, + 0xf2, 0x02, 0x72, 0x39, 0xf8, 0x54, 0x48, 0x4e, 0xbd, 0x44, 0xe9, 0xba, 0xcf, 0x39, 0x26, 0xba, + 0x46, 0x3a, 0xba, 0x08, 0x7a, 0x85, 0x9c, 0xd3, 0x10, 0x7b, 0x9c, 0x4b, 0x99, 0x4f, 0xd1, 0xcf, + 0xbc, 0x80, 0x91, 0x73, 0xa1, 0x9c, 0x73, 0x1b, 0x48, 0xda, 0x74, 0x48, 0x85, 0x50, 0x68, 0xab, + 0x7d, 0x63, 0xb7, 0xe5, 0x3c, 0xc8, 0x64, 0x47, 0xc0, 0x1f, 0xd5, 0x24, 0x4f, 0x6b, 0x82, 0xe6, + 0x07, 0xc8, 0x9c, 0x50, 0x21, 0x19, 0xa7, 0x04, 0x07, 0x2e, 0x44, 0x92, 0x53, 0x10, 0xdd, 0x35, + 0xad, 0x7e, 0xbb, 0x3a, 0xf9, 0x24, 0x3b, 0x30, 0x3f, 0x43, 0x0f, 0x6e, 0x34, 0xea, 0x92, 0x09, + 0x8e, 0x22, 0x08, 0xba, 0xeb, 0x3a, 0x94, 0x1d, 0xff, 0x06, 0x9b, 0xc3, 0x4c, 0xec, 0xe1, 0xca, + 0x9f, 0xbe, 0xd9, 0x99, 0xfb, 0xfa, 0x9b, 0x9d, 0x39, 0xeb, 0xef, 0x06, 0xba, 0x37, 0x2c, 0x03, + 0x0f, 0x59, 0x8a, 0x83, 0x1f, 0xb3, 0xc1, 0x0e, 0x50, 0x5b, 0x48, 0x16, 0x67, 0x25, 0xbd, 0xf0, + 0x16, 0x25, 0xbd, 0xa2, 0xd4, 0xd4, 0x81, 0xf5, 0x57, 0x03, 0x6d, 0x7e, 0xf2, 0x22, 0xa1, 0x29, + 0x23, 0xf8, 0xff, 0xc2, 0x07, 0xc7, 0x68, 0x0d, 0x6a, 0x78, 0xa2, 0xdb, 0xea, 0xb7, 0x76, 0x3b, + 0xfb, 0x3f, 0xb7, 0x33, 0x72, 0xb2, 0x4b, 0xce, 0xca, 0x09, 0xca, 0xae, 0x5b, 0x77, 0x9a, 0xba, + 0xd6, 0xbf, 0x0d, 0x74, 0xeb, 0xd3, 0x80, 0x79, 0x38, 0x38, 0x09, 0xb0, 0x98, 0xa8, 0xe4, 0x4d, + 0x55, 0xd4, 0x1c, 0xf2, 0xae, 0xd1, 0xde, 0xcd, 0x1c, 0xb5, 0x52, 0xd3, 0x7d, 0xfc, 0x31, 0xba, + 0x5d, 0xd6, 0x71, 0x79, 0xb9, 0x3a, 0x98, 0xc3, 0x3b, 0x17, 0xdf, 0xef, 0x6c, 0x14, 0x39, 0x1c, + 0xea, 0x8b, 0x7e, 0xe4, 0x6c, 0x90, 0xc6, 0x86, 0x6f, 0xf6, 0x50, 0x87, 0x7a, 0xc4, 0x15, 0xf0, + 0xc2, 0x8d, 0x92, 0x50, 0xe7, 0x65, 0xc1, 0x69, 0x53, 0x8f, 0x9c, 0xc0, 0x8b, 0x2f, 0x92, 0xd0, + 0xfc, 0x10, 0xbd, 0x53, 0x0c, 0x22, 0x37, 0xc5, 0x81, 0xab, 0xf4, 0x5d, 0xec, 0xfb, 0x5c, 0xa7, + 0x69, 0xd5, 0xb9, 0x53, 0x9c, 0x9e, 0xe1, 0x40, 0x19, 0x3b, 0xf0, 0x7d, 0x6e, 0xfd, 0x6b, 0x11, + 0x2d, 0x8d, 0x30, 0xc7, 0xa1, 0x30, 0x4f, 0xd1, 0x86, 0x84, 0x30, 0x0e, 0xb0, 0x04, 0x37, 0xe3, + 0xc8, 0x3c, 0xd2, 0xf7, 0x35, 0x77, 0xd6, 0x67, 0x8b, 0x5d, 0x9b, 0x26, 0xe9, 0x9e, 0x3d, 0xd4, + 0xbb, 0x27, 0x12, 0x4b, 0x70, 0xd6, 0x0b, 0x8c, 0x6c, 0xd3, 0xfc, 0x15, 0xea, 0x4a, 0x9e, 0x08, + 0x59, 0xb1, 0x57, 0xd5, 0xb6, 0x59, 0x2a, 0xdf, 0x29, 0xce, 0xb3, 0x86, 0x2f, 0xdb, 0xf5, 0x7a, + 0xa2, 0x6a, 0xfd, 0x2f, 0x44, 0x75, 0x82, 0xee, 0x28, 0x96, 0xbf, 0x8c, 0xb9, 0x30, 0x3b, 0xe6, + 0x6d, 0xa5, 0xdf, 0x04, 0xfd, 0x12, 0x99, 0xa9, 0x20, 0x97, 0x31, 0x17, 0xdf, 0xc2, 0xcf, 0x54, + 0x90, 0x26, 0xa4, 0x8f, 0xb6, 0x85, 0x2a, 0x3e, 0x37, 0x04, 0xa9, 0x69, 0x2f, 0x0e, 0x20, 0xa2, + 0x62, 0x52, 0x80, 0x2f, 0xcd, 0x0e, 0xbe, 0xa5, 0x81, 0x3e, 0x57, 0x38, 0x4e, 0x01, 0x93, 0x5b, + 0x19, 0xa2, 0xde, 0xf5, 0x56, 0xca, 0x04, 0x2d, 0xeb, 0x04, 0xfd, 0xe4, 0x1a, 0x88, 0x32, 0x4b, + 0xfb, 0xe8, 0x6e, 0x88, 0x5f, 0xb9, 0x72, 0xc2, 0x99, 0x94, 0x01, 0xf8, 0x6e, 0x8c, 0xc9, 0x39, + 0x48, 0xa1, 0x67, 0x54, 0xcb, 0xb9, 0x13, 0xe2, 0x57, 0xa7, 0xc5, 0xd9, 0x28, 0x3b, 0x32, 0x05, + 0x7a, 0xaf, 0x46, 0xe9, 0x2f, 0x31, 0xf7, 0x5d, 0x1f, 0x22, 0x16, 0xba, 0x1c, 0xc6, 0x8a, 0xf7, + 0x70, 0xc6, 0xee, 0x00, 0xe5, 0x58, 0xca, 0x1b, 0x59, 0xbd, 0x32, 0xca, 0x26, 0x1e, 0x32, 0x1a, + 0xe5, 0xb3, 0xdb, 0xaa, 0x98, 0x5f, 0xa1, 0x3d, 0x52, 0x60, 0x4e, 0x0d, 0xeb, 0x31, 0x80, 0xe5, + 0xa1, 0xdb, 0x4f, 0x70, 0xe4, 0x8b, 0x09, 0x3e, 0x87, 0xcf, 0x41, 0x62, 0x1f, 0x4b, 0xdc, 0xe8, + 0x99, 0xe7, 0x00, 0x6e, 0xcc, 0x58, 0x90, 0xf5, 0x4c, 0x46, 0x41, 0x65, 0xcf, 0x3c, 0x06, 0x18, + 0x31, 0x16, 0xa8, 0x9e, 0x31, 0xbb, 0x68, 0x39, 0x05, 0x2e, 0xaa, 0x0a, 0x2e, 0x96, 0xd6, 0x2f, + 0x50, 0x5b, 0x93, 0xc6, 0x01, 0x39, 0x17, 0xe6, 0x36, 0x6a, 0x2b, 0x24, 0x10, 0x02, 0x44, 0xd7, + 0xe8, 0xb7, 0x76, 0xdb, 0x4e, 0xb5, 0x61, 0x49, 0xb4, 0x75, 0xd3, 0xbb, 0x48, 0x98, 0xcf, 0xd0, + 0x72, 0x0c, 0x7a, 0x68, 0x6b, 0xc5, 0xce, 0xfe, 0x47, 0xf6, 0x0c, 0x6f, 0x4f, 0xfb, 0x26, 0x40, + 0xa7, 0x40, 0xb3, 0x78, 0xf5, 0x1a, 0xbb, 0x34, 0x2b, 0x84, 0x79, 0x76, 0xd9, 0xe8, 0xaf, 0xdf, + 0xca, 0xe8, 0x25, 0xbc, 0xca, 0xe6, 0xfb, 0xa8, 0x73, 0x90, 0x85, 0xfd, 0x1b, 0x2a, 0xe4, 0xd5, + 0x6b, 0x59, 0xad, 0x5f, 0xcb, 0x67, 0x68, 0x3d, 0x1f, 0x71, 0xa7, 0x4c, 0x13, 0x9f, 0xf9, 0x53, + 0x84, 0xf2, 0xd9, 0xa8, 0x08, 0x33, 0x4b, 0x4b, 0x3b, 0xdf, 0x39, 0xf2, 0x1b, 0xa3, 0x6a, 0xbe, + 0x31, 0xaa, 0x2c, 0x07, 0x6d, 0x9c, 0x09, 0xf2, 0xdb, 0xe2, 0xfd, 0xf3, 0x34, 0x16, 0xe6, 0x5d, + 0xb4, 0xa4, 0x7a, 0x35, 0x07, 0x5a, 0x70, 0x16, 0x53, 0x41, 0x8e, 0x7c, 0x73, 0xb7, 0xfe, 0xc6, + 0x62, 0xb1, 0x4b, 0x7d, 0xd1, 0x9d, 0xef, 0xb7, 0x76, 0x17, 0x9c, 0xf5, 0xa4, 0x52, 0x3f, 0xf2, + 0x85, 0xf5, 0x3b, 0xd4, 0xa9, 0x01, 0x9a, 0xeb, 0x68, 0xbe, 0xc4, 0x9a, 0xa7, 0xbe, 0xf9, 0x10, + 0x6d, 0x55, 0x40, 0x4d, 0xba, 0xcf, 0x10, 0xdb, 0xce, 0xbd, 0x52, 0xa0, 0xc1, 0xf8, 0xc2, 0x7a, + 0x8a, 0x36, 0x8f, 0x2a, 0x72, 0x29, 0x87, 0x49, 0x23, 0x42, 0xa3, 0x39, 0x8c, 0xb7, 0x51, 0xbb, + 0xfc, 0x91, 0xd0, 0xd1, 0x2f, 0x38, 0xd5, 0x86, 0x15, 0xa2, 0x5b, 0x67, 0x82, 0x9c, 0x40, 0xe4, + 0x57, 0x60, 0x37, 0x5c, 0xc0, 0xe1, 0x65, 0xa0, 0x99, 0x1f, 0xaa, 0x95, 0xb9, 0x3f, 0x1b, 0xa8, + 0x7b, 0x0c, 0xd3, 0x03, 0x21, 0xe8, 0x38, 0x0a, 0x21, 0x92, 0x8a, 0x2c, 0x30, 0x01, 0xf5, 0x69, + 0xbe, 0x8b, 0xd6, 0xca, 0x46, 0x2b, 0xfb, 0x6b, 0xd5, 0x59, 0x2d, 0x36, 0x75, 0x63, 0x3d, 0x44, + 0x28, 0xe6, 0x90, 0xba, 0xc4, 0x3d, 0x87, 0x69, 0xee, 0xc6, 0x76, 0x7d, 0xd6, 0x64, 0xff, 0x29, + 0xf6, 0x28, 0xf1, 0x02, 0x4a, 0x8e, 0x61, 0xea, 0xac, 0x28, 0xf9, 0xe1, 0x31, 0x4c, 0xd5, 0xdb, + 0x21, 0x66, 0x2f, 0x81, 0xeb, 0x01, 0xd1, 0x72, 0xb2, 0x85, 0xf5, 0x17, 0x03, 0xdd, 0x3b, 0xc3, + 0x01, 0xf5, 0xb1, 0x64, 0xbc, 0xb8, 0xef, 0x51, 0xe2, 0x29, 0x8d, 0x37, 0xdc, 0xeb, 0x15, 0x6f, + 0xe7, 0xaf, 0xf1, 0xf6, 0x63, 0xb4, 0x5a, 0x66, 0x58, 0xf9, 0xdb, 0x9a, 0xc1, 0xdf, 0x4e, 0xa1, + 0x71, 0x0c, 0x53, 0xeb, 0x8f, 0x35, 0xdf, 0x0e, 0xa7, 0xb5, 0xe6, 0xe5, 0xff, 0xc5, 0xb7, 0xd2, + 0x6c, 0xdd, 0x37, 0x52, 0xd7, 0xbf, 0x12, 0x40, 0xeb, 0x6a, 0x00, 0xd6, 0xdf, 0x0c, 0xb4, 0x59, + 0xb7, 0x2a, 0x4e, 0xd9, 0x88, 0x27, 0x11, 0xbc, 0xc9, 0x7a, 0x55, 0x3f, 0xf3, 0xf5, 0xfa, 0x79, + 0x86, 0xd6, 0x1b, 0x4e, 0x89, 0xfc, 0x36, 0x7e, 0x39, 0x13, 0x85, 0xd4, 0xe8, 0xc1, 0x59, 0xab, + 0xc7, 0x21, 0x0e, 0x9f, 0x7d, 0x7b, 0xd1, 0x33, 0xbe, 0xbb, 0xe8, 0x19, 0xff, 0xbc, 0xe8, 0x19, + 0x5f, 0xbd, 0xee, 0xcd, 0x7d, 0xf7, 0xba, 0x37, 0xf7, 0x8f, 0xd7, 0xbd, 0xb9, 0xdf, 0x7f, 0x34, + 0xa6, 0x72, 0x92, 0x78, 0x36, 0x61, 0xe1, 0x20, 0xff, 0x09, 0xad, 0x6c, 0x7d, 0x50, 0xfe, 0xd3, + 0xa7, 0xfb, 0x83, 0x57, 0xcd, 0x1f, 0x7b, 0x39, 0x8d, 0x41, 0x78, 0x4b, 0xba, 0xac, 0x3f, 0xfc, + 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7b, 0x4c, 0xb0, 0x24, 0x09, 0x10, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -1342,6 +1350,13 @@ func (m *ConsumerAdditionProposal) MarshalToSizedBuffer(dAtA []byte) (int, error _ = i var l int _ = l + if len(m.DistributionTransmissionChannel) > 0 { + i -= len(m.DistributionTransmissionChannel) + copy(dAtA[i:], m.DistributionTransmissionChannel) + i = encodeVarintProvider(dAtA, i, uint64(len(m.DistributionTransmissionChannel))) + i-- + dAtA[i] = 0x72 + } if m.HistoricalEntries != 0 { i = encodeVarintProvider(dAtA, i, uint64(m.HistoricalEntries)) i-- @@ -2298,6 +2313,10 @@ func (m *ConsumerAdditionProposal) Size() (n int) { if m.HistoricalEntries != 0 { n += 1 + sovProvider(uint64(m.HistoricalEntries)) } + l = len(m.DistributionTransmissionChannel) + if l > 0 { + n += 1 + l + sovProvider(uint64(l)) + } return n } @@ -3080,6 +3099,38 @@ func (m *ConsumerAdditionProposal) Unmarshal(dAtA []byte) error { break } } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DistributionTransmissionChannel", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProvider + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProvider + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DistributionTransmissionChannel = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipProvider(dAtA[iNdEx:]) diff --git a/x/ccv/types/shared_params.go b/x/ccv/types/shared_params.go index 000a4c3815..04f1cf54e1 100644 --- a/x/ccv/types/shared_params.go +++ b/x/ccv/types/shared_params.go @@ -57,6 +57,16 @@ func ValidateString(i interface{}) error { return nil } +func ValidateDistributionTransmissionChannel(i interface{}) error { + // Accept empty string as valid, since this means a new + // distribution transmission channel will be created + if i == "" { + return nil + } + // Otherwise validate as usual for a channelID + return ValidateChannelIdentifier(i) +} + func ValidateChannelIdentifier(i interface{}) error { value, ok := i.(string) if !ok {