Skip to main content

Adding On-Chain Module Parameters

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 the process using a generic approach, illustrated by adding a parameter to the proof module.

See pokt-network/poktroll#595 for a real-world example.

note

TODO_POST_MAINNET(@bryanchriswhite): Once the next version of ignite is out, leverage: https://github.com/ignite/cli/issues/3684#issuecomment-2299796210

Step-by-Step Instructions

1. Define the Parameter in the Protocol Buffers File

Open the appropriate .proto file for your module (e.g., params.proto) and define the new parameter.

message Params {
// Other existing parameters...

// Description of the new parameter.
uint64 new_parameter_name = 3 [(gogoproto.jsontag) = "new_parameter_name"];
}

2 Update the Parameter E2E Tests

Update the E2E test files (e.g., update_params.feature and update_params_test.go) to include scenarios that test the new parameter.

2.1 Scenario Example

# NB: If you are reading this and the proof module has parameters
# that are not being updated in this test, please update the test.
Scenario: An authorized user updates all "proof" module params
Given the user has the pocketd binary installed
And all "proof" module params are set to their default values
And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "/poktroll.proof.MsgUpdateParams" message exists
When the "pnf" account sends an authz exec message to update all "proof" module params
| name | value | type |
| new_parameter_name | 100 | int64 |
Then all "proof" module params should be updated

2.2 Scenario Outline Example

# NB: If you are reading this and any module has parameters that
# are not being updated in this test, please update the test.
Scenario Outline: An authorized user updates individual <module> module params
Given the user has the pocketd binary installed
And all "<module>" module params are set to their default values
And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "<message_type>" message exists
When the "pnf" account sends an authz exec message to update "<module>" the module param
| name | value | type |
| <param_name> | <param_value> | <param_type> |
Then the "<module>" module param "<param_name>" should be updated

Examples:
| module | message_type | param_name | param_value | param_type |
| proof | /poktroll.proof.MsgUpdateParam | new_parameter_name | 100 | int64 |

2.3 Step Definition Helpers Example

The related changes to the step definition, presented below via an example, should be made in e2e/tests/parse_params_test.go.

func (s *suite) newProofMsgUpdateParams(params paramsMap) cosmostypes.Msg {
msgUpdateParams := &prooftypes.MsgUpdateParam{
Params: &prooftypes.Params{},
}

for paramName, paramValue := range params {
switch paramName {
case prooftypes.ParamNewParameterName:
msgUpdateParams.Params.NewParameterName = uint64(paramValue.value.(int64))
default:
s.Fatalf("unexpected %q type param name %q", paramValue.typeStr, paramName)
}
}

return msgUpdateParams
}

2.4 Update switch statement to support new param

The related changes to the step definition, presented below via an example, should be made in e2e/tests/parse_params_test.go.

case prooftypes.ModuleName:
params := prooftypes.DefaultParams()
paramsMap := s.expectedModuleParams[moduleName]

newParameter, ok := paramsMap[prooftypes.ParamNewParameterName]
if ok {
params.NewParameter = uint64(newParameter.value.(int64))
}

3. Update the Default Parameter Values

In the corresponding Go file (e.g., params.go), define the default value, key, and parameter name for the new parameter and include the default in the NewParams and DefaultParams functions.

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,
}
}

func DefaultParams() Params {
return NewParams(
// Other existing default parameters...
DefaultNewParameterName,
)
}

4. Add Parameter Default to Genesis Configuration

Add the new parameter to the genesis configuration file (e.g., config.yml).

genesis:
proof:
params:
# Other existing parameters...

new_parameter_name: 100

5. Modify the Makefile

Add a new target in the Makefile to update the new parameter.

.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)

6. 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.

{
"body": {
"messages": [
{
"@type": "/poktroll.proof.MsgUpdateParam",
"authority": "pokt10d07y265gmmuvt4z0w9aw880jnsr700j8yv32t",
"name": "new_parameter_name",
"as_int64": "100"
}
]
}
}

7. 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.

{
"body": {
"messages": [
{
"@type": "/poktroll.proof.MsgUpdateParams",
"authority": "pokt10d07y265gmmuvt4z0w9aw880jnsr700j8yv32t",
"params": {
"min_relay_difficulty_bits": "0",
"proof_request_probability": "0.25",
"proof_requirement_threshold": "20",
"new_parameter_name": "100" // Add this line
}
}
]
}
}

8. Parameter Validation

8.1 New Parameter Validation

Implement a validation function for the new parameter in the corresponding params.go file you've been working on.

func ValidateNewParameterName(v interface{}) error {
_, ok := v.(uint64)
if !ok {
return fmt.Errorf("invalid parameter type: %T", v)
}
return nil
}

8.2 Parameter Validation in Workflow

Integrate the usage of the new ValidateNewParameterName function in the corresponding Params#ValidateBasic() function where this is used.

func (params *Params) ValidateBasic() error {
// ...
if err := ValidateNewParameterName(params.NewParameterName); err != nil {
return err
}
// ...
}

9. Add the Parameter to ParamSetPairs()

Include the new parameter in the ParamSetPairs function.

func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs {
return paramtypes.ParamSetPairs{
// Other existing parameters...

paramtypes.NewParamSetPair(
KeyNewParameterName,
&p.NewParameterName,
ValidateNewParameterName,
),
}
}

10. Update Unit Tests

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).

10.1 Parameter Validation Tests

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),
},
}

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)
}
})
}
}

10.2 Parameter Update Tests

The example presented below corresponds to /keeper/msg_server_update_param_test.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)},
}
res, err := msgSrv.UpdateParam(ctx, updateParamMsg)
require.NoError(t, err)

require.Equal(t, expectedNewParameterName, res.Params.NewParameterName)

// READ ME: THIS TEST SHOULD ALSO ASSERT THAT ALL OTHER PARAMS OF THE SAME MODULE REMAIN UNCHANGED
}

11. Implement individual parameter updates

11.1 Add ParamNameNewParameterName to MsgUpdateParam#ValidateBasic() in x/types/message_update_param.go

  // Parameter name must be supported by this module.
switch msg.Name {
case ParamNumBlocksPerSession,
ParamNewParameterName:
return msg.paramTypeIsInt64()

11.2 Add ParamNameNewParameterName to msgServer#UpdateParam() in x/keeper/msg_server_update_param.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)
}
newParameter := uint64(value.AsInt64)

if err := types.ValidateNewParameter(newParameter); err != nil {
return nil, err
}

params.NewParameter = newParameter