Consensus paramspull/665/head
@ -0,0 +1,85 @@ | |||
# ADR 005: Consensus Params | |||
## Context | |||
Consensus critical parameters controlling blockchain capacity have until now been hard coded, loaded from a local config, or neglected. | |||
Since they may be need to be different in different networks, and potentially to evolve over time within | |||
networks, we seek to initialize them in a genesis file, and expose them through the ABCI. | |||
While we have some specific parameters now, like maximum block and transaction size, we expect to have more in the future, | |||
such as a period over which evidence is valid, or the frequency of checkpoints. | |||
## Decision | |||
### ConsensusParams | |||
No consensus critical parameters should ever be found in the `config.toml`. | |||
A new `ConsensusParams` is optionally included in the `genesis.json` file, | |||
and loaded into the `State`. Any items not included are set to their default value. | |||
A value of 0 is undefined (see ABCI, below). A value of -1 is used to indicate the parameter does not apply. | |||
The parameters are used to determine the validity of a block (and tx) via the union of all relevant parameters. | |||
``` | |||
type ConsensusParams struct { | |||
BlockSizeParams | |||
TxSizeParams | |||
BlockGossipParams | |||
} | |||
type BlockSizeParams struct { | |||
MaxBytes int | |||
MaxTxs int | |||
MaxGas int | |||
} | |||
type TxSizeParams struct { | |||
MaxBytes int | |||
MaxGas int | |||
} | |||
type BlockGossipParams struct { | |||
BlockPartSizeBytes int | |||
} | |||
``` | |||
The `ConsensusParams` can evolve over time by adding new structs that cover different aspects of the consensus rules. | |||
The `BlockPartSizeBytes` and the `BlockSizeParams.MaxBytes` are enforced to be greater than 0. | |||
The former because we need a part size, the latter so that we always have at least some sanity check over the size of blocks. | |||
### ABCI | |||
#### InitChain | |||
InitChain currently takes the initial validator set. It should be extended to also take parts of the ConsensusParams. | |||
There is some case to be made for it to take the entire Genesis, except there may be things in the genesis, | |||
like the BlockPartSize, that the app shouldn't really know about. | |||
#### EndBlock | |||
The EndBlock response includes a `ConsensusParams`, which includes BlockSizeParams and TxSizeParams, but not BlockGossipParams. | |||
Other param struct can be added to `ConsensusParams` in the future. | |||
The `0` value is used to denote no change. | |||
Any other value will update that parameter in the `State.ConsensusParams`, to be applied for the next block. | |||
Tendermint should have hard-coded upper limits as sanity checks. | |||
## Status | |||
Proposed. | |||
## Consequences | |||
### Positive | |||
- Alternative capacity limits and consensus parameters can be specified without re-compiling the software. | |||
- They can also change over time under the control of the application | |||
### Negative | |||
- More exposed parameters is more complexity | |||
- Different rules at different heights in the blockchain complicates fast sync | |||
### Neutral | |||
- The TxSizeParams, which checks validity, may be in conflict with the config's `max_block_size_tx`, which determines proposal sizes |
@ -0,0 +1,89 @@ | |||
package types | |||
import ( | |||
"encoding/json" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
crypto "github.com/tendermint/go-crypto" | |||
) | |||
func TestGenesis(t *testing.T) { | |||
// test some bad ones from raw json | |||
testCases := [][]byte{ | |||
[]byte{}, // empty | |||
[]byte{1, 1, 1, 1, 1}, // junk | |||
[]byte(`{}`), // empty | |||
[]byte(`{"chain_id": "mychain"}`), // missing validators | |||
[]byte(`{"chain_id": "mychain", "validators": []`), // missing validators | |||
[]byte(`{"chain_id": "mychain", "validators": [{}]`), // missing validators | |||
[]byte(`{"validators":[{"pub_key": | |||
{"type":"ed25519","data":"961EAB8752E51A03618502F55C2B6E09C38C65635C64CCF3173ED452CF86C957"}, | |||
"power":10,"name":""}]}`), // missing chain_id | |||
} | |||
for _, testCase := range testCases { | |||
_, err := GenesisDocFromJSON(testCase) | |||
assert.Error(t, err, "expected error for empty genDoc json") | |||
} | |||
// test a good one by raw json | |||
genDocBytes := []byte(`{"genesis_time":"0001-01-01T00:00:00Z","chain_id":"test-chain-QDKdJr","consensus_params":null,"validators":[{"pub_key":{"type":"ed25519","data":"961EAB8752E51A03618502F55C2B6E09C38C65635C64CCF3173ED452CF86C957"},"power":10,"name":""}],"app_hash":""}`) | |||
_, err := GenesisDocFromJSON(genDocBytes) | |||
assert.NoError(t, err, "expected no error for good genDoc json") | |||
// create a base gendoc from struct | |||
baseGenDoc := &GenesisDoc{ | |||
ChainID: "abc", | |||
Validators: []GenesisValidator{{crypto.GenPrivKeyEd25519().PubKey(), 10, "myval"}}, | |||
} | |||
genDocBytes, err = json.Marshal(baseGenDoc) | |||
assert.NoError(t, err, "error marshalling genDoc") | |||
// test base gendoc and check consensus params were filled | |||
genDoc, err := GenesisDocFromJSON(genDocBytes) | |||
assert.NoError(t, err, "expected no error for valid genDoc json") | |||
assert.NotNil(t, genDoc.ConsensusParams, "expected consensus params to be filled in") | |||
// create json with consensus params filled | |||
genDocBytes, err = json.Marshal(genDoc) | |||
assert.NoError(t, err, "error marshalling genDoc") | |||
genDoc, err = GenesisDocFromJSON(genDocBytes) | |||
assert.NoError(t, err, "expected no error for valid genDoc json") | |||
// test with invalid consensus params | |||
genDoc.ConsensusParams.BlockSizeParams.MaxBytes = 0 | |||
genDocBytes, err = json.Marshal(genDoc) | |||
assert.NoError(t, err, "error marshalling genDoc") | |||
genDoc, err = GenesisDocFromJSON(genDocBytes) | |||
assert.Error(t, err, "expected error for genDoc json with block size of 0") | |||
} | |||
func newConsensusParams(blockSize, partSize int) ConsensusParams { | |||
return ConsensusParams{ | |||
BlockSizeParams: BlockSizeParams{MaxBytes: blockSize}, | |||
BlockGossipParams: BlockGossipParams{BlockPartSizeBytes: partSize}, | |||
} | |||
} | |||
func TestConsensusParams(t *testing.T) { | |||
testCases := []struct { | |||
params ConsensusParams | |||
valid bool | |||
}{ | |||
{newConsensusParams(1, 1), true}, | |||
{newConsensusParams(1, 0), false}, | |||
{newConsensusParams(0, 1), false}, | |||
{newConsensusParams(0, 0), false}, | |||
} | |||
for _, testCase := range testCases { | |||
if testCase.valid { | |||
assert.NoError(t, testCase.params.Validate(), "expected no error for valid params") | |||
} else { | |||
assert.Error(t, testCase.params.Validate(), "expected error for non valid params") | |||
} | |||
} | |||
} |
@ -0,0 +1,87 @@ | |||
package types | |||
import ( | |||
"github.com/pkg/errors" | |||
) | |||
const ( | |||
maxBlockSizeBytes = 104857600 // 100MB | |||
) | |||
// ConsensusParams contains consensus critical parameters | |||
// that determine the validity of blocks. | |||
type ConsensusParams struct { | |||
BlockSizeParams `json:"block_size_params"` | |||
TxSizeParams `json:"tx_size_params"` | |||
BlockGossipParams `json:"block_gossip_params"` | |||
} | |||
// BlockSizeParams contain limits on the block size. | |||
type BlockSizeParams struct { | |||
MaxBytes int `json:"max_bytes"` // NOTE: must not be 0 | |||
MaxTxs int `json:"max_txs"` | |||
MaxGas int `json:"max_gas"` | |||
} | |||
// TxSizeParams contain limits on the tx size. | |||
type TxSizeParams struct { | |||
MaxBytes int `json:"max_bytes"` | |||
MaxGas int `json:"max_gas"` | |||
} | |||
// BlockGossipParams determine consensus critical elements of how blocks are gossiped | |||
type BlockGossipParams struct { | |||
BlockPartSizeBytes int `json:"block_part_size_bytes"` // NOTE: must not be 0 | |||
} | |||
// DefaultConsensusParams returns a default ConsensusParams. | |||
func DefaultConsensusParams() *ConsensusParams { | |||
return &ConsensusParams{ | |||
DefaultBlockSizeParams(), | |||
DefaultTxSizeParams(), | |||
DefaultBlockGossipParams(), | |||
} | |||
} | |||
// DefaultBlockSizeParams returns a default BlockSizeParams. | |||
func DefaultBlockSizeParams() BlockSizeParams { | |||
return BlockSizeParams{ | |||
MaxBytes: 22020096, // 21MB | |||
MaxTxs: 100000, | |||
MaxGas: -1, | |||
} | |||
} | |||
// DefaultTxSizeParams returns a default TxSizeParams. | |||
func DefaultTxSizeParams() TxSizeParams { | |||
return TxSizeParams{ | |||
MaxBytes: 10240, // 10kB | |||
MaxGas: -1, | |||
} | |||
} | |||
// DefaultBlockGossipParams returns a default BlockGossipParams. | |||
func DefaultBlockGossipParams() BlockGossipParams { | |||
return BlockGossipParams{ | |||
BlockPartSizeBytes: 65536, // 64kB, | |||
} | |||
} | |||
// Validate validates the ConsensusParams to ensure all values | |||
// are within their allowed limits, and returns an error if they are not. | |||
func (params *ConsensusParams) Validate() error { | |||
// ensure some values are greater than 0 | |||
if params.BlockSizeParams.MaxBytes <= 0 { | |||
return errors.Errorf("BlockSizeParams.MaxBytes must be greater than 0. Got %d", params.BlockSizeParams.MaxBytes) | |||
} | |||
if params.BlockGossipParams.BlockPartSizeBytes <= 0 { | |||
return errors.Errorf("BlockGossipParams.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossipParams.BlockPartSizeBytes) | |||
} | |||
// ensure blocks aren't too big | |||
if params.BlockSizeParams.MaxBytes > maxBlockSizeBytes { | |||
return errors.Errorf("BlockSizeParams.MaxBytes is too big. %d > %d", | |||
params.BlockSizeParams.MaxBytes, maxBlockSizeBytes) | |||
} | |||
return nil | |||
} |