diff --git a/docusaurus/docs/develop/developer_guide/adding_params.md b/docusaurus/docs/develop/developer_guide/adding_params.md index 0843a6d81..48de66434 100644 --- a/docusaurus/docs/develop/developer_guide/adding_params.md +++ b/docusaurus/docs/develop/developer_guide/adding_params.md @@ -6,27 +6,36 @@ title: Adding On-Chain Module Parameters # Adding On-Chain Module Parameters - [Step-by-Step Instructions](#step-by-step-instructions) + - [0. If the Module Doesn't Already Support a `MsgUpdateParam` Message](#0-if-the-module-doesnt-already-support-a-msgupdateparam-message) + - [0.1. Scaffold the `MsgUpdateParam` Message](#01-scaffold-the-msgupdateparam-message) + - [0.2. Update `MsgUpdateParam` and `MsgUpdateParamResponse` Fields](#02-update-msgupdateparam-and-msgupdateparamresponse-fields) + - [0.3 Comment Out AutoCLI](#03-comment-out-autocli) + - [0.4. Update the DAO Genesis Authorizations JSON File](#04-update-the-dao-genesis-authorizations-json-file) + - [0.5 Update the `NewMsgUpdateParam` Constructor and `MsgUpdateParam#ValidateBasic()`](#05-update-the-newmsgupdateparam-constructor-and-msgupdateparamvalidatebasic) + - [0.6 Update the Module's `msgServer#UpdateParam()` Handler](#06-update-the-modules-msgserverupdateparam-handler) + - [0.7 Update Module's Params Test Suite `ModuleParamConfig`](#07-update-modules-params-test-suite-moduleparamconfig) - [1. Define the Parameter in the Protocol Buffers File](#1-define-the-parameter-in-the-protocol-buffers-file) - - [2 Update the Parameter E2E Tests](#2-update-the-parameter-e2e-tests) - - [2.1 Scenario Example](#21-scenario-example) - - [2.2 Scenario Outline Example](#22-scenario-outline-example) - - [2.3 Step Definition Helpers Example](#23-step-definition-helpers-example) - - [2.4 Update switch statement to support new param](#24-update-switch-statement-to-support-new-param) - - [3. Update the Default Parameter Values](#3-update-the-default-parameter-values) - - [4. Add Parameter Default to Genesis Configuration](#4-add-parameter-default-to-genesis-configuration) - - [5. Modify the Makefile](#5-modify-the-makefile) - - [6. Create a new JSON File for the Individual Parameter Update](#6-create-a-new-json-file-for-the-individual-parameter-update) - - [7. Update the JSON File for Updating All Parameters for the Module](#7-update-the-json-file-for-updating-all-parameters-for-the-module) - - [8. Parameter Validation](#8-parameter-validation) - - [8.1 New Parameter Validation](#81-new-parameter-validation) - - [8.2 Parameter Validation in Workflow](#82-parameter-validation-in-workflow) - - [9. Add the Parameter to `ParamSetPairs()`](#9-add-the-parameter-to-paramsetpairs) - - [10. Update Unit Tests](#10-update-unit-tests) - - [10.1 Parameter Validation Tests](#101-parameter-validation-tests) - - [10.2 Parameter Update Tests](#102-parameter-update-tests) - - [11. Implement individual parameter updates](#11-implement-individual-parameter-updates) - - [11.1 Add `ParamNameNewParameterName` to `MsgUpdateParam#ValidateBasic()` in `x/types/message_update_param.go`](#111-add-paramnamenewparametername-to-msgupdateparamvalidatebasic-in-xtypesmessage_update_paramgo) - - [11.2 Add `ParamNameNewParameterName` to `msgServer#UpdateParam()` in `x/keeper/msg_server_update_param.go`](#112-add-paramnamenewparametername-to-msgserverupdateparam-in-xkeepermsg_server_update_paramgo) + - [2. Update the Default Parameter Values](#2-update-the-default-parameter-values) + - [2.1 Go Source Defaults](#21-go-source-defaults) + - [2.2 Genesis Configuration Parameter Defaults](#22-genesis-configuration-parameter-defaults) + - [3. Parameter Validation](#3-parameter-validation) + - [3.1 Define a Validation Function](#31-define-a-validation-function) + - [3.2 Call it in the `Params#Validate()`](#32-call-it-in-the-paramsvalidate) + - [3.3 Add a `ParamSetPair` to `ParamSetPairs()`](#33-add-a-paramsetpair-to-paramsetpairs) + - [4. Add Parameter Case to Switch Statements](#4-add-parameter-case-to-switch-statements) + - [4.1 `MsgUpdateParam#ValidateBasic()`](#41-msgupdateparamvalidatebasic) + - [4.2 `msgServer#UpdateParam()`](#42-msgserverupdateparam) + - [5. Update Unit Tests](#5-update-unit-tests) + - [5.1 Parameter Validation Tests](#51-parameter-validation-tests) + - [5.2 Parameter Update Tests](#52-parameter-update-tests) + - [6. Update the Parameter Integration Tests](#6-update-the-parameter-integration-tests) + - [6.1 Add a valid param](#61-add-a-valid-param) + - [6.2 Check for `as_` on `MsgUpdateParam`](#62-check-for-as_type-on-msgupdateparam) + - [6.3 Update the module's `ModuleParamConfig`](#63-update-the-modules-moduleparamconfig) + - [7. Update the Makefile and Supporting JSON Files](#7-update-the-makefile-and-supporting-json-files) + - [7.1 Update the Makefile](#71-update-the-makefile) + - [7.2 Create a new JSON File for the Individual Parameter Update](#72-create-a-new-json-file-for-the-individual-parameter-update) + - [7.3 Update the JSON File for Updating All Parameters for the Module](#73-update-the-json-file-for-updating-all-parameters-for-the-module) Adding a new on-chain module parameter involves multiple steps to ensure that the parameter is properly integrated into the system. This guide will walk you through @@ -43,277 +52,706 @@ https://github.com/ignite/cli/issues/3684#issuecomment-2299796210 ## Step-by-Step Instructions -### 1. Define the Parameter in the Protocol Buffers File +:::warning +The steps outlined below follow **the same example** where: -Open the appropriate `.proto` file for your module (e.g., `params.proto`) and define the new parameter. +- **Module name**: `examplemod` +- **New parameter name**: `new_parameter` +- **Default value**: `int64(42)` -```protobuf -message Params { - // Other existing parameters... +When following these steps, be sure to substitute these example values with your own! +::: - // Description of the new parameter. - uint64 new_parameter_name = 3 [(gogoproto.jsontag) = "new_parameter_name"]; -} +:::tip +At any point, you can always run `go test ./x/examplemod/...` to check whether everything is working or locate outstanding necessary changes. +::: + +### 0. If the Module Doesn't Already Support a `MsgUpdateParam` Message + +In order to support **individual parameter updates**, the module MUST have a `MsgUpdateParam` message. +If the module doesn't already support this message, it will need to be added. + +#### 0.1. Scaffold the `MsgUpdateParam` Message + +Use `ignite` to scaffold a new `MsgUpdateParam` message for the module. +Additional flags are used for convenience: + +```bash +ignite scaffold message update-param --module examplemod --signer authority name as_type --response params ``` -### 2 Update the Parameter Integration Tests +Try running `make proto_clean_pulsar` if you experience errors like these: -// TODO_DOCUMENT(@bryanchriswhite, #826) +```bash +✘ Error while running command /home/bwhite/go/bin/buf generate /tmp/proto-sdk2893110128/cosmos/upgrade/v1beta1/tx.proto ...: {..."message":"import \"gogoproto/gogo.proto\": file does not exist"} +: exit status 100 +``` -### 3. Update the Default Parameter Values +```bash +✘ Error while running command go mod tidy: go: +... +go: github.com/pokt-network/poktroll/api/poktroll/examplemod imports + cosmossdk.io/api/poktroll/shared: module cosmossdk.io/api@latest found (v0.7.6), but does not contain package cosmossdk.io/api/poktroll/shared +``` -In the corresponding Go file (e.g., `x//types/params.go`), define the default value, key, and parameter name for the -new parameter and include the default in the `NewParams` and `DefaultParams` functions. +```bash +✘ Error while running command /home/bwhite/go/bin/buf dep update /home/bwhite/Projects/pokt/poktroll/proto: Failure: decode proto/buf.lock: no digest specified for module buf.build/cosmos/ics23 +: exit status 1 +``` -```go -var ( - // Other existing parameters... - - KeyNewParameterName = []byte("NewParameterName") - ParamNewParameterName = "new_parameter_name" - DefaultNewParameterName uint64 = 100 // Example default value -) - -func NewParams( - // Other existing parameters... - newParameterName uint64, -) Params { - return Params{ - // Other existing parameters... - NewParameterName: newParameterName, +#### 0.2. Update `MsgUpdateParam` and `MsgUpdateParamResponse` Fields + +Update the `MsgUpdateParam` message fields in the module's `tx.proto` file (e.g. `proto/poktroll/examplemod/tx.proto`) to include the following comments and protobuf options: + +```diff ++ // MsgUpdateParam is the Msg/UpdateParam request type to update a single param. + message MsgUpdateParam { + option (cosmos.msg.v1.signer) = "authority"; +- string authority = 1; ++ ++ // authority is the address that controls the module (defaults to x/gov unless overwritten). ++ string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; ++ + string name = 2; +- string asType = 3; ++ oneof as_type { ++ // Add `as_` fields for each type in this module's Params type; e.g.: ++ // int64 as_int64 = 3 [(gogoproto.jsontag) = "as_int64"]; ++ // bytes as_bytes = 4 [(gogoproto.jsontag) = "as_bytes"]; ++ // cosmos.base.v1beta1.Coin as_coin = 5 [(gogoproto.jsontag) = "as_coin"]; ++ } } -} -func DefaultParams() Params { - return NewParams( - // Other existing default parameters... - DefaultNewParameterName, - ) -} + message MsgUpdateParamResponse { ``` -### 4. Add Parameter Default to Genesis Configuration +Update the `MsgUpdateParamResponse` message field (`params`) in the same `tx.proto` file: + +```diff + message MsgUpdateParamResponse { +- string params = 1; ++ Params params = 1; + } +``` -Add the new parameter to the genesis configuration file (e.g., `config.yml`). +#### 0.3 Comment Out AutoCLI -```yaml -genesis: - proof: - params: - # Other existing parameters... +When scaffolding the `MsgUpdateParam` message, generated code is added to `x/examplemod/module/autocli.go`. +Since governance parameters aren't updated via `poktrolld` CLI, comment out these new lines: - new_parameter_name: 100 +```diff + // ... + Tx: &autocliv1.ServiceCommandDescriptor{ + Service: modulev1.Msg_ServiceDesc.ServiceName, + EnhanceCustomCommand: true, // only required if you want to use the custom command + RpcCommandOptions: []*autocliv1.RpcCommandOptions{ + // ... ++ // { ++ // RpcMethod: "UpdateParam", ++ // Use: "update-param [name] [as-type]", ++ // Short: "Send a update-param tx", ++ / PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "name"}, {ProtoField: "asType"}}, ++ // }, + // this line is used by ignite scaffolding # autocli/tx + }, + }, + // ... ``` -### 5. Modify the Makefile +#### 0.4. Update the DAO Genesis Authorizations JSON File -Add a new target in the `Makefile` to update the new parameter. +Add a grant (array element) to `tools/scripts/authz/dao_genesis_authorizations.json` with the `authorization.msg` typeURL for this module's `MsgUpdateType`: -```makefile -.PHONY: params_update_proof_new_parameter_name -params_update_proof_new_parameter_name: ## Update the proof module new_parameter_name param - poktrolld tx authz exec ./tools/scripts/params/proof_new_parameter_name.json $(PARAM_FLAGS) +```diff ++ { ++ "granter": "pokt10d07y265gmmuvt4z0w9aw880jnsr700j8yv32t", ++ "grantee": "pokt1eeeksh2tvkh7wzmfrljnhw4wrhs55lcuvmekkw", ++ "authorization": { ++ "@type": "\/cosmos.authz.v1beta1.GenericAuthorization", ++ "msg": "\/poktroll.examplemod.MsgUpdateParam" // Replace examplemod with the module name ++ }, ++ "expiration": "2500-01-01T00:00:00Z" ++ }, ``` -### 6. Create a new JSON File for the Individual Parameter Update +#### 0.5 Update the `NewMsgUpdateParam` Constructor and `MsgUpdateParam#ValidateBasic()` + +Prepare `x/examplemod/types/message_update_param.go` to handle message construction and parameter validations by type: + +```diff +- func NewMsgUpdateParam(authority string, name string, asType string) *MsgUpdateParam { ++ func NewMsgUpdateParam(authority string, name string, asType any) (*MsgUpdateParam, error) { ++ var asTypeIface isMsgUpdateParam_AsType ++ ++ switch t := asType.(type) { ++ default: ++ return nil, ExamplemodParamInvalid.Wrapf("unexpected param value type: %T", asType) ++ } ++ + return &MsgUpdateParam{ + Authority: authority, + Name: name, +- AsType: asType, +- } ++ AsType: asTypeIface, ++ }, nil + } -Create a new JSON file (e.g., `proof_new_parameter_name.json`) in the tools/scripts/params -directory to specify how to update the new parameter. + func (msg *MsgUpdateParam) ValidateBasic() error { + _, err := cosmostypes.AccAddressFromBech32(msg.Authority) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid authority address (%s)", err) + } +- +- return nil ++ ++ // Parameter value MUST NOT be nil. ++ if msg.AsType == nil { ++ return ErrExamplemodParamInvalid.Wrap("missing param AsType") ++ } ++ ++ // Parameter name MUST be supported by this module. ++ switch msg.Name { ++ default: ++ return ErrExamplemodParamInvalid.Wrapf("unsupported param %q", msg.Name) ++ } + } +``` -```json -{ - "body": { - "messages": [ - { - "@type": "/poktroll.proof.MsgUpdateParam", - "authority": "pokt10d07y265gmmuvt4z0w9aw880jnsr700j8yv32t", - "name": "new_parameter_name", - "as_int64": "100" - } - ] +#### 0.6 Update the Module's `msgServer#UpdateParam()` Handler + +Prepare `x/examplemod/keeper/msg_server_update_param.go` to handle parameter updates by type: + +```diff +- func (k msgServer) UpdateParam(goCtx context.Context, msg *examplemodtypes.MsgUpdateParam) (*examplemodtypes.MsgUpdateParamResponse, error) { +- ctx := sdk.UnwrapSDKContext(goCtx) +- +- // TODO: Handling the message +- _ = ctx +- ++ // UpdateParam updates a single parameter in the proof module and returns ++ // all active parameters. ++ func (k msgServer) UpdateParam(ctx context.Context, msg *examplemodtypes.MsgUpdateParam) (*examplemodtypes.MsgUpdateParamResponse, error) { ++ logger := k.logger.With( ++ "method", "UpdateParam", ++ "param_name", msg.Name, ++ ) ++ ++ if err := msg.ValidateBasic(); err != nil { ++ return nil, status.Error(codes.InvalidArgument, err.Error()) ++ } ++ ++ if k.GetAuthority() != msg.Authority { ++ return nil, status.Error( ++ codes.InvalidArgument, ++ examplemodtypes.ErrExamplemodInvalidSigner.Wrapf( ++ "invalid authority; expected %s, got %s", ++ k.GetAuthority(), msg.Authority, ++ ).Error(), ++ ) ++ } ++ ++ params := k.GetParams(ctx) ++ ++ switch msg.Name { ++ default: ++ return nil, status.Error( ++ codes.InvalidArgument, ++ examplemodtypes.ErrExamplemodParamInvalid.Wrapf("unsupported param %q", msg.Name).Error(), ++ ) ++ } ++ ++ // Perform a global validation on all params, which includes the updated param. ++ // This is needed to ensure that the updated param is valid in the context of all other params. ++ if err := params.Validate(); err != nil { ++ return nil, status.Error(codes.InvalidArgument, err.Error()) ++ } ++ ++ if err := k.SetParams(ctx, params); err != nil { ++ err = fmt.Errorf("unable to set params: %w", err) ++ logger.Error(err.Error()) ++ return nil, status.Error(codes.Internal, err.Error()) ++ } ++ ++ updatedParams := k.GetParams(ctx) ++ ++ return &types.MsgUpdateParamResponse{ ++ Params: &updatedParams, ++ }, nil } -} ``` -### 7. Update the JSON File for Updating All Parameters for the Module +#### 0.7 Update Module's Params Test Suite `ModuleParamConfig` -Add a line to the existing module's `MsgUpdateParam` JSON file (e.g., `proof_all.json`) -with the default value for the new parameter. +Add `MsgUpdateParam` & `MsgUpdateParamResponse` to the module's `ModuleParamConfig#ParamsMsg` in `testutil/integration/suites/param_config.go`: -```json -{ - "body": { - "messages": [ - { - "@type": "/poktroll.proof.MsgUpdateParams", - "authority": "pokt10d07y265gmmuvt4z0w9aw880jnsr700j8yv32t", - "params": { - "proof_request_probability": "0.25", - "proof_requirement_threshold": { - "denom": "upokt", - "amount": "20000000" - }, - "proof_missing_penalty": { - "amount": "320000000", - "denom": "upokt" - }, - "proof_submission_fee": { - "amount": "1000000", - "denom": "upokt" - }, - "new_parameter_name": "100" // Add this line - } - } - ] +```diff + ExamplemodModuleParamConfig = ModuleParamConfig{ + ParamsMsgs: ModuleParamsMessages{ + MsgUpdateParams: gatewaytypes.MsgUpdateParams{}, + MsgUpdateParamsResponse: gatewaytypes.MsgUpdateParamsResponse{}, ++ MsgUpdateParam: gatewaytypes.MsgUpdateParam{}, ++ MsgUpdateParamResponse: gatewaytypes.MsgUpdateParamResponse{}, + QueryParamsRequest: gatewaytypes.QueryParamsRequest{}, + QueryParamsResponse: gatewaytypes.QueryParamsResponse{}, + }, + // ... } -} ``` -### 8. Parameter Validation +### 1. Define the Parameter in the Protocol Buffers File -#### 8.1 New Parameter Validation +Define the new parameter in the module's `params.proto` file (e.g., `proto/poktroll/examplemod/params.proto`): -Implement a validation function for the new parameter in the corresponding `params.go` -file you've been working on. +```diff + message Params { + // Other existing parameters... -```go -func ValidateNewParameterName(v interface{}) error { - _, ok := v.(uint64) - if !ok { - return fmt.Errorf("invalid parameter type: %T", v) ++ // Description of the new parameter. ++ int64 new_parameter = 3 [(gogoproto.jsontag) = "new_parameter", (gogoproto.moretags) = "yaml:\"new_parameter\""]; } - return nil -} ``` -#### 8.2 Parameter Validation in Workflow +:::warning +Be sure to update the `gogoproto.jsontag` and `gogoproto.moretags` option values to match the new parameter name! +::: -Integrate the usage of the new `ValidateNewParameterName` function in the corresponding -`Params#ValidateBasic()` function where this is used. +:::tip +Don't forget to run `make proto_regen` to update generated protobuf go code. +::: -```go -func (params *Params) ValidateBasic() error { - // ... - if err := ValidateNewParameterName(params.NewParameterName); err != nil { - return err +### 2. Update the Default Parameter Values + +#### 2.1 Go Source Defaults + +In the corresponding Go file (e.g., `x/examplemod/types/params.go`), define the default +value, key, and parameter name for the new parameter and include the default in the +`NewParams` and `DefaultParams` functions: + +```diff + var ( + // Other existing parameter keys, names, and defaults... + ++ KeyNewParameter = []byte("NewParameter") ++ ParamNewParameter = "new_parameter" ++ DefaultNewParameter int64 = 42 + ) + + func NewParams( + // Other existing parameters... ++ newParameter int64, + ) Params { + return Params{ + // Other existing parameters... ++ NewParameter: newParameter, + } + } + + func DefaultParams() Params { + return NewParams( + // Other existing default parameters... ++ DefaultNewParameter, + ) } - // ... -} ``` -### 9. Add the Parameter to `ParamSetPairs()` +#### 2.2 Genesis Configuration Parameter Defaults -Include the new parameter in the `ParamSetPairs` function. +Add the new parameter to the genesis configuration file (e.g., `config.yml`): -```go -func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { - return paramtypes.ParamSetPairs{ - // Other existing parameters... +```diff + genesis: + examplemod: + params: + # Other existing parameters... + ++ new_parameter: 42 +``` + +### 3. Parameter Validation + +#### 3.1 Define a Validation Function + +Implement a validation function for the new parameter in `x/examplemod/types/params.go`: + +```diff ++ // ValidateNewParameter validates the NewParameter param. ++ func ValidateNewParameter(newParamAny any) error { ++ newParam, ok := newParamAny.(int64) ++ if !ok { ++ return ErrExamplemodParamInvalid.Wrapf("invalid parameter type: %T", newParamAny) ++ } ++ ++ // Any additional validation... ++ ++ return nil ++ } +``` + +#### 3.2 Call it in the `Params#Validate()` + +Integrate the usage of the new `ValidateNewParameter` function in the corresponding +`Params#Validate()` function where this is used: - paramtypes.NewParamSetPair( - KeyNewParameterName, - &p.NewParameterName, - ValidateNewParameterName, - ), +```diff + func (params *Params) Validate() error { + // ... ++ if err := ValidateNewParameter(params.NewParameter); err != nil { ++ return err ++ } + // ... } -} ``` -### 10. Update Unit Tests +#### 3.3 Add a `ParamSetPair` to `ParamSetPairs()` -Add tests which exercise validation of the new parameter in your test files -(e.g., `/types/params_test.go` and `msg_server_update_param_test.go`). +Include a call to `NewParamSetPair()`, passing the parameter's key, value pointer, and validation function in the `ParamSetPairs` function return: -#### 10.1 Parameter Validation Tests +```diff + func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + // Other existing param set pairs... -```go -func TestParams_ValidateNewParameterName(t *testing.T) { - tests := []struct { - desc string - newParameterName interface{} - expectedErr error - }{ - { - desc: "invalid type", - newParameterName: int64(-1), - expectedErr: fmt.Errorf("invalid parameter type: int64"), - }, - { - desc: "valid newParameterName", - newParameterName: uint64(100), - }, ++ paramtypes.NewParamSetPair( ++ KeyNewParameter, ++ &p.NewParameter, ++ ValidateNewParameter, ++ ), + } } +``` - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - err := ValidateNewParameterName(tt.newParameterName) - if tt.expectedErr != nil { - require.Error(t, err) - require.Contains(t, err.Error(), tt.expectedErr.Error()) - } else { - require.NoError(t, err) - } - }) +### 4. Add Parameter Case to Switch Statements + +#### 4.1 `MsgUpdateParam#ValidateBasic()` + +Add the parameter type and name (e.g. `ParamNameNewParameter`) to new cases in the switch statements in `NewMsgUpdateParam()` and `MsgUpdateParam#ValidateBasic()` in `x/examplemod/types/message_update_param.go`: + +```diff + func NewMsgUpdateParam(authority string, name string, asType any) (*MsgUpdateParam, error) { + // ... + switch t := asType.(type) { ++ case int64: ++ asTypeIface = &MsgUpdateParam_AsCoin{AsInt64: t} + default: + return nil, ErrExamplemodParamInvalid.Wrapf("unexpected param value type: %T", asType)) + } + // ... } -} + ++ // ValidateBasic performs a basic validation of the MsgUpdateParam fields. It ensures: ++ // 1. The parameter name is supported. ++ // 2. The parameter type matches the expected type for a given parameter name. ++ // 3. The parameter value is valid (according to its respective validation function). + func (msg *MsgUpdateParam) ValidateBasic() error { + // ... + switch msg.Name { ++ case ParamNewParameter: ++ if err := genericParamTypeIs[*MsgUpdateParam_AsInt64](msg); err != nil { ++ return err ++ } ++ return ValidateNewParameter(msg.GetAsInt64()) + default: + return ErrExamplemodParamInvalid.Wrapf("unsupported param %q", msg.Name) + } + } ++ ++ func genericParamTypeIs[T any](msg *MsgUpdateParam) error { ++ if _, ok := msg.AsType.(T); !ok { ++ return ErrParamInvalid.Wrapf( ++ "invalid type for param %q; expected %T, got %T", ++ msg.Name, *new(T), msg.AsType, ++ ) ++ } ++ ++ return nil ++ } + ``` -#### 10.2 Parameter Update Tests +#### 4.2 `msgServer#UpdateParam()` -The example presented below corresponds to `/keeper/msg_server_update_param_test.go`. +Add the parameter name (e.g. `ParamNameNewParameter`) to a new case in the switch statement in `msgServer#UpdateParam()` in `x/examplemod/keeper/msg_server_update_param.go`: -```go -func TestMsgUpdateParam_UpdateNewParameterNameOnly(t *testing.T) { - var expectedNewParameterName uint64 = 100 - - // Set the parameters to their default values - k, msgSrv, ctx := setupMsgServer(t) - defaultParams := prooftypes.DefaultParams() - require.NoError(t, k.SetParams(ctx, defaultParams)) - - // Ensure the default values are different from the new values we want to set - require.NotEqual(t, expectedNewParameterName, defaultParams.NewParameterName) - - // Update the new parameter - updateParamMsg := &prooftypes.MsgUpdateParam{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Name: prooftypes.ParamNewParameterName, - AsType: &prooftypes.MsgUpdateParam_AsInt64{AsInt64: int64(expectedNewParameterName)}, +:::warning +Every error return from `msgServer` methods (e.g. `#UpdateParams()`) **SHOULD** be encapsulated in a gRPC status error! +::: + +```diff + // UpdateParam updates a single parameter in the proof module and returns + // all active parameters. + func (k msgServer) UpdateParam( + ctx context.Context, + msg *examplemodtypes.MsgUpdateParam, + ) (*examplemodtypes.MsgUpdateParamResponse, error) { + if err := msg.ValidateBasic(); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + // ... + switch msg.Name { ++ case examplemodtypes.ParamNewParameter: ++ logger = logger.with("param_value", msg.GetAsInt64()) ++ params.NewParameter = msg.GetAsInt64() + default: + return nil, status.Error( + codes.InvalidArgument, + examplemodtypes.ErrExamplemodParamInvalid.Wrapf("unsupported param %q", msg.Name).Error(), + ) + } + // ... + if err := params.Validate(); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + // ... } - res, err := msgSrv.UpdateParam(ctx, updateParamMsg) - require.NoError(t, err) +``` - require.Equal(t, expectedNewParameterName, res.Params.NewParameterName) +### 5. Update Unit Tests - // READ ME: THIS TEST SHOULD ALSO ASSERT THAT ALL OTHER PARAMS OF THE SAME MODULE REMAIN UNCHANGED -} +#### 5.1 Parameter Validation Tests + +Add unit tests which exercise validation of the new parameter(s) in `x/examplemod/keeper/params_test.go`. +Ensure there is a test function for each parameter which covers all cases of invalid input: + +```diff + func TestGetParams(t *testing.T) { + // ... + } + ++ func TestParams_ValidateNewParameter(t *testing.T) { ++ tests := []struct { ++ desc string ++ newParameter any ++ expectedErr error ++ }{ ++ { ++ desc: "invalid type", ++ newParameter: "420", ++ expectedErr: examplemodtypes.ErrExamplemodParamInvalid.Wrapf("invalid parameter type: string"), ++ }, ++ { ++ desc: "valid NewParameterName", ++ newParameter: int64(420), ++ }, ++ } ++ ++ for _, test := range tests { ++ t.Run(test.desc, func(t *testing.T) { ++ err := examplemodtypes.ValidateNewParameter(test.newParameter) ++ if test.expectedErr != nil { ++ require.Error(t, err) ++ require.Contains(t, err.Error(), test.expectedErr.Error()) ++ } else { ++ require.NoError(t, err) ++ } ++ }) ++ } ++ } ``` -### 11. Implement individual parameter updates +#### 5.2 Parameter Update Tests -#### 11.1 Add `ParamNameNewParameterName` to `MsgUpdateParam#ValidateBasic()` in `x/types/message_update_param.go` +Add test cases to `x/examplemod/keeper/msg_update_params_test.go` to ensure coverage over any invalid parameter combinations. +Add a case for the "minimal params" if some subset of the params are "required". +If one already exist, update it if applicable; e.g.: ```go - // Parameter name must be supported by this module. - switch msg.Name { - case ParamNumBlocksPerSession, - ParamNewParameterName: - return msg.paramTypeIsInt64() ++ { ++ desc: "valid: send minimal params", // For parameters which MUST NEVER be their zero value or nil. ++ input: &examplemodtypes.MsgUpdateParams{ ++ Authority: k.GetAuthority(), ++ Params: examplemodtypes.Params{ ++ NewParameter: 42, ++ }, ++ }, ++ shouldError: false, ++ }, ``` -#### 11.2 Add `ParamNameNewParameterName` to `msgServer#UpdateParam()` in `x/keeper/msg_server_update_param.go` +Add a unit test which exercise individually updating the new parameter in `x/examplemod/keeper/msg_server_update_param_test.go`. +This test asserts that updating was successful and that no other parameter was effected: ```go -case types.ParamNewParameterName: - value, ok := msg.AsType.(*types.MsgUpdateParam_AsInt64) - if !ok { - return nil, types.ErrProofParamInvalid.Wrapf("unsupported value type for %s param: %T", msg.Name, msg.AsType) ++ func TestMsgUpdateParam_UpdateNewParameterOnly(t *testing.T) { ++ var expectedNewParameter int64 = 420 ++ ++ // Set the parameters to their default values ++ k, msgSrv, ctx := setupMsgServer(t) ++ defaultParams := examplemodtypes.DefaultParams() ++ require.NoError(t, k.SetParams(ctx, defaultParams)) ++ ++ // Ensure the default values are different from the new values we want to set ++ require.NotEqual(t, expectedNewParameter, defaultParams.NewParameter) ++ ++ // Update the new parameter ++ updateParamMsg := &examplemodtypes.MsgUpdateParam{ ++ Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), ++ Name: examplemodtypes.ParamNewParameter, ++ AsType: &examplemodtypes.MsgUpdateParam_AsInt64{AsInt64: expectedNewParameter}, ++ } ++ res, err := msgSrv.UpdateParam(ctx, updateParamMsg) ++ require.NoError(t, err) ++ require.Equal(t, expectedNewParameter, res.Params.NewParameter) ++ ++ // Ensure the other parameters are unchanged ++ testkeeper.AssertDefaultParamsEqualExceptFields(t, &defaultParams, res.Params, string(examplemodtypes.KeyNewParameter)) ++ } +``` + +:::warning +If creating `msg_server_update_param_test.go`, be sure to: + +1. use the `keeper_test` package (i.e. `package keeper_test`). +2. add the testutil keeper import: `testkeeper "github.com/pokt-network/poktroll/testutil/keeper"` + +::: + +Update `x/examplemod/types/message_update_param_test.go` to use the new `MsgUpdateParam#AsType` fields. +Start with the following cases and add those which cover all invalid values for the new param (and its `AsType`; e.g. `AsCoin` cannot be nil): + +```diff + func TestMsgUpdateParam_ValidateBasic(t *testing.T) { + tests := []struct { +- name string ++ desc string + msg MsgUpdateParam +- err error ++ expectedErr error + }{ + { +- name: "invalid address", ++ desc: "invalid: authority address invalid", + msg: MsgUpdateParam{ + Authority: "invalid_address", ++ Name: "", // Doesn't matter for this test ++ AsType: &MsgUpdateParam_AsInt64{AsInt64: 0}, + }, +- err: sdkerrors.ErrInvalidAddress, ++ expectedErr: sdkerrors.ErrInvalidAddress, ++ }, { ++ desc: "invalid: param name incorrect (non-existent)", ++ msg: MsgUpdateParam{ ++ Authority: sample.AccAddress(), ++ Name: "non_existent", ++ AsType: &MsgUpdateParam_AsInt64{AsInt64: DefaultNewParameter}, ++ }, ++ expectedErr: ErrExamplemodParamInvalid, + }, { +- name: "valid address", ++ desc: "valid: correct address, param name, and type", + msg: MsgUpdateParam{ + Authority: sample.AccAddress(), ++ Name: ParamNewParameter, ++ AsType: &MsgUpdateParam_AsInt64{AsInt64: DefaultNewParameter}, + }, + }, + } + // ... } - newParameter := uint64(value.AsInt64) +``` + +### 6. Update the Parameter Integration Tests - if err := types.ValidateNewParameter(newParameter); err != nil { - return nil, err +Integration tests which cover parameter updates utilize the `ModuleParamConfig`s defined in [`testutil/integration/params/param_configs.go`](https://github.com/pokt-network/poktroll/blob/main/testutil/integration/suites/param_configs.go) to dynamically (i.e. using reflection) construct and send parameter update messages in a test environment. +When adding parameters to a module, it is necessary to update that module's `ModuleParamConfig` to include the new parameter, othwerwise it will not be covered by the integration test suite. + +#### 6.1 Add a valid param + +Update `ModuleParamConfig#ValidParams` to include a valid and non-default value for the new parameter in the module's `tx.proto` file (e.g. `proto/poktroll/examplemod/tx.proto`): + +```diff + ExamplemodModuleParamConfig = ModuleParamConfig{ + // ... + ValidParams: examplemodtypes.Params{ ++ NewParameter: 420, + }, + // ... } +``` - params.NewParameter = newParameter +#### 6.2 Check for `as_` on `MsgUpdateParam` + +Ensure an `as_` field exists on `MsgUpdateParam` corresponding to the type of the new parameter (`proto/poktroll/examplemod/tx.proto`): + +```diff + message MsgUpdateParam { + ... + oneof as_type { +- // Add `as_` fields for each type in this module's Params type; e.g.: ++ int64 as_int64 = 3 [(gogoproto.jsontag) = "as_int64"]; + } + } +``` + +#### 6.3 Update the module's `ModuleParamConfig` + +Ensure that all available `as_` types for the module are present on the module's `ModuleParamConfig#ParamTypes` field: + +```diff + ExamplemodModuleParamConfig = ModuleParamConfig{ + // ... + ValidParams: examplemodtypes.Params{}, ++ ParamTypes: map[ParamType]any{ ++ ParamTypeInt64: examplemodtypes.MsgUpdateParam_AsInt64{}, ++ }, + DefaultParams: examplemodtypes.DefaultParams(), + // ... + } +``` + +### 7. Update the Makefile and Supporting JSON Files + +#### 7.1 Update the Makefile + +Add a new target in `makefiles/params.mk` to update the new parameter: + +```makefile +.PHONY: params_update_examplemod_new_parameter +params_update_examplemod_new_parameter: ## Update the examplemod module new_parameter param + poktrolld tx authz exec ./tools/scripts/params/examplemod_new_parameter.json $(PARAM_FLAGS) +``` + +:::warning +Reminder to substitute `examplemod` and `new_parameter` with your module and param names! +::: + +#### 7.2 Create a new JSON File for the Individual Parameter Update + +Create a new JSON file (e.g., `proof_new_parameter_name.json`) in the tools/scripts/params directory to specify how to update the new parameter: + +```json +{ + "body": { + "messages": [ + { + "@type": "/poktroll.examplemod.MsgUpdateParam", // Replace module name + "authority": "pokt10d07y265gmmuvt4z0w9aw880jnsr700j8yv32t", + "name": "new_parameter", // Replace new parameter name + "as_int64": "42" // Replace default value + } + ] + } +} +``` + +#### 7.3 Update the JSON File for Updating All Parameters for the Module + +Add a line to the existing module's `MsgUpdateParam` JSON file (e.g., `proof_all.json`) +with the default value for the new parameter. + +```diff + { + "body": { + "messages": [ + { + "@type": "/poktroll.examplemod.MsgUpdateParams", // Replace module name + "authority": "pokt10d07y265gmmuvt4z0w9aw880jnsr700j8yv32t", + "params": { + // Other existing parameters... ++ "new_parameter": "42" // Replace name and default value + } + } + ] + } + } ``` diff --git a/docusaurus/docs/protocol/governance/params.md b/docusaurus/docs/protocol/governance/params.md index 5dea25f1d..2fde79c03 100644 --- a/docusaurus/docs/protocol/governance/params.md +++ b/docusaurus/docs/protocol/governance/params.md @@ -15,14 +15,6 @@ DO NOT EDIT: this file was generated by make docs_update_gov_params_page - [Adding a new parameter](#adding-a-new-parameter) - [Parameters](#parameters) -## Access Control - -// TODO_DOCUMENT(@bryanchriswhite) tl;dr, authz. - -## Updating governance parameter values - -// TODO_DOCUMENT(@bryanchriswhite) - ## Updating this page ``` diff --git a/tools/scripts/docusaurus/params_doc_template.md b/tools/scripts/docusaurus/params_doc_template.md index 0feb77186..fdbc914af 100644 --- a/tools/scripts/docusaurus/params_doc_template.md +++ b/tools/scripts/docusaurus/params_doc_template.md @@ -15,14 +15,6 @@ DO NOT EDIT: this file was generated by make docs_update_gov_params_page - [Adding a new parameter](#adding-a-new-parameter) - [Parameters](#parameters) -## Access Control - -// TODO_DOCUMENT(@bryanchriswhite) tl;dr, authz. - -## Updating governance parameter values - -// TODO_DOCUMENT(@bryanchriswhite) - ## Updating this page ```