tendermint/state/state_test.go
Ethan Frey f870a49f42 Add ABCIResults with Hash and Proof to State
State maintains LastResultsHash
Verify that we can produce unique hashes for each result,
and provide valid proofs from the root hash.
2017-12-26 19:24:25 -05:00

513 lines
15 KiB
Go

package state
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/types"
)
// setupTestCase does setup common to all test cases
func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, *State) {
config := cfg.ResetTestRoot("state_")
stateDB := dbm.NewDB("state", config.DBBackend, config.DBDir())
state, err := GetState(stateDB, config.GenesisFile())
assert.NoError(t, err, "expected no error on GetState")
state.SetLogger(log.TestingLogger())
tearDown := func(t *testing.T) {}
return tearDown, stateDB, state
}
// TestStateCopy tests the correct copying behaviour of State.
func TestStateCopy(t *testing.T) {
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
assert := assert.New(t)
stateCopy := state.Copy()
assert.True(state.Equals(stateCopy),
cmn.Fmt(`expected state and its copy to be identical. got %v\n expected %v\n`,
stateCopy, state))
stateCopy.LastBlockHeight++
assert.False(state.Equals(stateCopy), cmn.Fmt(`expected states to be different. got same
%v`, state))
}
// TestStateSaveLoad tests saving and loading State from a db.
func TestStateSaveLoad(t *testing.T) {
tearDown, stateDB, state := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
assert := assert.New(t)
state.LastBlockHeight++
state.Save()
loadedState := LoadState(stateDB)
assert.True(state.Equals(loadedState),
cmn.Fmt(`expected state and its copy to be identical. got %v\n expected %v\n`,
loadedState, state))
}
// TestABCIResponsesSaveLoad tests saving and loading ABCIResponses.
func TestABCIResponsesSaveLoad(t *testing.T) {
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
assert := assert.New(t)
state.LastBlockHeight++
// build mock responses
block := makeBlock(state, 2)
abciResponses := NewABCIResponses(block)
abciResponses.DeliverTx[0] = &abci.ResponseDeliverTx{Data: []byte("foo"), Tags: []*abci.KVPair{}}
abciResponses.DeliverTx[1] = &abci.ResponseDeliverTx{Data: []byte("bar"), Log: "ok", Tags: []*abci.KVPair{}}
abciResponses.EndBlock = &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{
{
PubKey: crypto.GenPrivKeyEd25519().PubKey().Bytes(),
Power: 10,
},
}}
abciResponses.txs = nil
state.SaveABCIResponses(abciResponses)
loadedAbciResponses := state.LoadABCIResponses()
assert.Equal(abciResponses, loadedAbciResponses,
cmn.Fmt(`ABCIResponses don't match: Got %v, Expected %v`, loadedAbciResponses,
abciResponses))
}
// TestValidatorSimpleSaveLoad tests saving and loading validators.
func TestValidatorSimpleSaveLoad(t *testing.T) {
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
assert := assert.New(t)
// can't load anything for height 0
v, err := state.LoadValidators(0)
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at height 0")
// should be able to load for height 1
v, err = state.LoadValidators(1)
assert.Nil(err, "expected no err at height 1")
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
// increment height, save; should be able to load for next height
state.LastBlockHeight++
state.saveValidatorsInfo()
v, err = state.LoadValidators(state.LastBlockHeight + 1)
assert.Nil(err, "expected no err")
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
// increment height, save; should be able to load for next height
state.LastBlockHeight += 10
state.saveValidatorsInfo()
v, err = state.LoadValidators(state.LastBlockHeight + 1)
assert.Nil(err, "expected no err")
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
// should be able to load for next next height
_, err = state.LoadValidators(state.LastBlockHeight + 2)
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height")
}
// TestValidatorChangesSaveLoad tests saving and loading a validator set with changes.
func TestOneValidatorChangesSaveLoad(t *testing.T) {
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
// change vals at these heights
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
N := len(changeHeights)
// build the validator history by running SetBlockAndValidators
// with the right validator set for each height
highestHeight := changeHeights[N-1] + 5
changeIndex := 0
_, val := state.Validators.GetByIndex(0)
power := val.VotingPower
for i := int64(1); i < highestHeight; i++ {
// when we get to a change height,
// use the next pubkey
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] {
changeIndex++
power += 1
}
header, parts, responses := makeHeaderPartsResponsesValPowerChange(state, i, power)
err := state.SetBlockAndValidators(header, parts, responses)
assert.Nil(t, err)
state.saveValidatorsInfo()
}
// on each change height, increment the power by one.
testCases := make([]int64, highestHeight)
changeIndex = 0
power = val.VotingPower
for i := int64(1); i < highestHeight+1; i++ {
// we we get to the height after a change height
// use the next pubkey (note our counter starts at 0 this time)
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex]+1 {
changeIndex++
power += 1
}
testCases[i-1] = power
}
for i, power := range testCases {
v, err := state.LoadValidators(int64(i + 1))
assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", i))
assert.Equal(t, v.Size(), 1, "validator set size is greater than 1: %d", v.Size())
_, val := v.GetByIndex(0)
assert.Equal(t, val.VotingPower, power, fmt.Sprintf(`unexpected powerat
height %d`, i))
}
}
// TestValidatorChangesSaveLoad tests saving and loading a validator set with
// changes.
func TestManyValidatorChangesSaveLoad(t *testing.T) {
const valSetSize = 7
tearDown, _, state := setupTestCase(t)
state.Validators = genValSet(valSetSize)
state.Save()
defer tearDown(t)
const height = 1
pubkey := crypto.GenPrivKeyEd25519().PubKey()
// swap the first validator with a new one ^^^ (validator set size stays the same)
header, parts, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey)
err := state.SetBlockAndValidators(header, parts, responses)
require.Nil(t, err)
state.saveValidatorsInfo()
v, err := state.LoadValidators(height + 1)
assert.Nil(t, err)
assert.Equal(t, valSetSize, v.Size())
index, val := v.GetByAddress(pubkey.Address())
assert.NotNil(t, val)
if index < 0 {
t.Fatal("expected to find newly added validator")
}
}
func genValSet(size int) *types.ValidatorSet {
vals := make([]*types.Validator, size)
for i := 0; i < size; i++ {
vals[i] = types.NewValidator(crypto.GenPrivKeyEd25519().PubKey(), 10)
}
return types.NewValidatorSet(vals)
}
// TestConsensusParamsChangesSaveLoad tests saving and loading consensus params
// with changes.
func TestConsensusParamsChangesSaveLoad(t *testing.T) {
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
// change vals at these heights
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
N := len(changeHeights)
// each valset is just one validator
// create list of them
params := make([]types.ConsensusParams, N+1)
params[0] = state.ConsensusParams
for i := 1; i < N+1; i++ {
params[i] = *types.DefaultConsensusParams()
params[i].BlockSize.MaxBytes += i
}
// build the params history by running SetBlockAndValidators
// with the right params set for each height
highestHeight := changeHeights[N-1] + 5
changeIndex := 0
cp := params[changeIndex]
for i := int64(1); i < highestHeight; i++ {
// when we get to a change height,
// use the next params
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] {
changeIndex++
cp = params[changeIndex]
}
header, parts, responses := makeHeaderPartsResponsesParams(state, i, cp)
err := state.SetBlockAndValidators(header, parts, responses)
require.Nil(t, err)
state.saveConsensusParamsInfo()
}
// make all the test cases by using the same params until after the change
testCases := make([]paramsChangeTestCase, highestHeight)
changeIndex = 0
cp = params[changeIndex]
for i := int64(1); i < highestHeight+1; i++ {
// we we get to the height after a change height
// use the next pubkey (note our counter starts at 0 this time)
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex]+1 {
changeIndex++
cp = params[changeIndex]
}
testCases[i-1] = paramsChangeTestCase{i, cp}
}
for _, testCase := range testCases {
p, err := state.LoadConsensusParams(testCase.height)
assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", testCase.height))
assert.Equal(t, testCase.params, p, fmt.Sprintf(`unexpected consensus params at
height %d`, testCase.height))
}
}
func TestABCIResults(t *testing.T) {
a := ABCIResult{Code: 0, Data: nil}
b := ABCIResult{Code: 0, Data: []byte{}}
c := ABCIResult{Code: 0, Data: []byte("one")}
d := ABCIResult{Code: 14, Data: nil}
e := ABCIResult{Code: 14, Data: []byte("foo")}
f := ABCIResult{Code: 14, Data: []byte("bar")}
// nil and []byte{} should produce same hash
assert.Equal(t, a.Hash(), b.Hash())
// a and b should be the same, don't go in results
results := ABCIResults{a, c, d, e, f}
// make sure each result hashes properly
var last []byte
for i, res := range results {
h := res.Hash()
assert.NotEqual(t, last, h, "%d", i)
last = h
}
// make sure that we can get a root hash from results
// and verify proofs
root := results.Hash()
assert.NotEmpty(t, root)
for i, res := range results {
proof := results.ProveResult(i)
valid := proof.Verify(i, len(results), res.Hash(), root)
assert.True(t, valid, "%d", i)
}
}
func makeParams(blockBytes, blockTx, blockGas, txBytes,
txGas, partSize int) types.ConsensusParams {
return types.ConsensusParams{
BlockSize: types.BlockSize{
MaxBytes: blockBytes,
MaxTxs: blockTx,
MaxGas: int64(blockGas),
},
TxSize: types.TxSize{
MaxBytes: txBytes,
MaxGas: int64(txGas),
},
BlockGossip: types.BlockGossip{
BlockPartSizeBytes: partSize,
},
}
}
func TestLessThanOneThirdOfVotingPowerPerBlockEnforced(t *testing.T) {
testCases := []struct {
initialValSetSize int
shouldErr bool
valUpdatesFn func(vals *types.ValidatorSet) []*abci.Validator
}{
///////////// 1 val (vp: 10) => less than 3 is ok ////////////////////////
// adding 1 validator => 10
0: {1, false, func(vals *types.ValidatorSet) []*abci.Validator {
return []*abci.Validator{
{PubKey: pk(), Power: 2},
}
}},
1: {1, true, func(vals *types.ValidatorSet) []*abci.Validator {
return []*abci.Validator{
{PubKey: pk(), Power: 3},
}
}},
2: {1, true, func(vals *types.ValidatorSet) []*abci.Validator {
return []*abci.Validator{
{PubKey: pk(), Power: 100},
}
}},
///////////// 3 val (vp: 30) => less than 10 is ok ////////////////////////
// adding and removing validator => 20
3: {3, true, func(vals *types.ValidatorSet) []*abci.Validator {
_, firstVal := vals.GetByIndex(0)
return []*abci.Validator{
{PubKey: firstVal.PubKey.Bytes(), Power: 0},
{PubKey: pk(), Power: 10},
}
}},
// adding 1 validator => 10
4: {3, true, func(vals *types.ValidatorSet) []*abci.Validator {
return []*abci.Validator{
{PubKey: pk(), Power: 10},
}
}},
// adding 2 validators => 8
5: {3, false, func(vals *types.ValidatorSet) []*abci.Validator {
return []*abci.Validator{
{PubKey: pk(), Power: 4},
{PubKey: pk(), Power: 4},
}
}},
}
for i, tc := range testCases {
tearDown, _, state := setupTestCase(t)
state.Validators = genValSet(tc.initialValSetSize)
state.Save()
height := state.LastBlockHeight + 1
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
Height: height,
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: tc.valUpdatesFn(state.Validators)},
}
err := state.SetBlockAndValidators(block.Header, types.PartSetHeader{}, abciResponses)
if tc.shouldErr {
assert.Error(t, err, "#%d", i)
} else {
assert.NoError(t, err, "#%d", i)
}
tearDown(t)
}
}
func pk() []byte {
return crypto.GenPrivKeyEd25519().PubKey().Bytes()
}
func TestApplyUpdates(t *testing.T) {
initParams := makeParams(1, 2, 3, 4, 5, 6)
cases := [...]struct {
init types.ConsensusParams
updates *abci.ConsensusParams
expected types.ConsensusParams
}{
0: {initParams, nil, initParams},
1: {initParams, &abci.ConsensusParams{}, initParams},
2: {initParams,
&abci.ConsensusParams{
TxSize: &abci.TxSize{
MaxBytes: 123,
},
},
makeParams(1, 2, 3, 123, 5, 6)},
3: {initParams,
&abci.ConsensusParams{
BlockSize: &abci.BlockSize{
MaxTxs: 44,
MaxGas: 55,
},
},
makeParams(1, 44, 55, 4, 5, 6)},
4: {initParams,
&abci.ConsensusParams{
BlockSize: &abci.BlockSize{
MaxTxs: 789,
},
TxSize: &abci.TxSize{
MaxGas: 888,
},
BlockGossip: &abci.BlockGossip{
BlockPartSizeBytes: 2002,
},
},
makeParams(1, 789, 3, 4, 888, 2002)},
}
for i, tc := range cases {
res := tc.init.Update(tc.updates)
assert.Equal(t, tc.expected, res, "case %d", i)
}
}
func makeHeaderPartsResponsesValPubKeyChange(state *State, height int64,
pubkey crypto.PubKey) (*types.Header, types.PartSetHeader, *ABCIResponses) {
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
Height: height,
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{}},
}
// if the pubkey is new, remove the old and add the new
_, val := state.Validators.GetByIndex(0)
if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) {
abciResponses.EndBlock = &abci.ResponseEndBlock{
ValidatorUpdates: []*abci.Validator{
{val.PubKey.Bytes(), 0},
{pubkey.Bytes(), 10},
},
}
}
return block.Header, types.PartSetHeader{}, abciResponses
}
func makeHeaderPartsResponsesValPowerChange(state *State, height int64,
power int64) (*types.Header, types.PartSetHeader, *ABCIResponses) {
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
Height: height,
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{}},
}
// if the pubkey is new, remove the old and add the new
_, val := state.Validators.GetByIndex(0)
if val.VotingPower != power {
abciResponses.EndBlock = &abci.ResponseEndBlock{
ValidatorUpdates: []*abci.Validator{
{val.PubKey.Bytes(), power},
},
}
}
return block.Header, types.PartSetHeader{}, abciResponses
}
func makeHeaderPartsResponsesParams(state *State, height int64,
params types.ConsensusParams) (*types.Header, types.PartSetHeader, *ABCIResponses) {
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
Height: height,
EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(&params)},
}
return block.Header, types.PartSetHeader{}, abciResponses
}
type paramsChangeTestCase struct {
height int64
params types.ConsensusParams
}