Files
tendermint/internal/state/store_test.go
William Banfield 0aa3b0b6fc Proposer-Based Timestamps Merge (#7605)
This pull request merges in the changes for implementing Proposer-based timestamps into `master`. The power was primarily being done in the `wb/proposer-based-timestamps` branch, with changes being merged into that branch during development. This pull request represents an amalgamation of the changes made into that development branch. All of the changes that were placed into that branch have been cleanly rebased on top of the latest `master`. The changes compile and the tests pass insofar as our tests in general pass.

### Note To Reviewers
 These changes have been extensively reviewed during development. There is not much new here. In the interest of making effective use of time, I would recommend against trying to perform a complete audit of the changes presented and instead examine for mistakes that may have occurred during the process of rebasing the changes. I gave the complete change set a first pass for any issues, but additional eyes would be very appreciated. 

In sum, this change set does the following:
closes #6942 
merges in #6849
2022-01-26 16:00:23 +00:00

322 lines
11 KiB
Go

package state_test
import (
"context"
"fmt"
"math/rand"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tm-db"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
sm "github.com/tendermint/tendermint/internal/state"
"github.com/tendermint/tendermint/internal/test/factory"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
"github.com/tendermint/tendermint/types"
)
const (
// make sure this is the same as in state/store.go
valSetCheckpointInterval = 100000
)
func TestStoreBootstrap(t *testing.T) {
stateDB := dbm.NewMemDB()
stateStore := sm.NewStore(stateDB)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
val, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
val2, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
val3, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
vals := types.NewValidatorSet([]*types.Validator{val, val2, val3})
bootstrapState := makeRandomStateFromValidatorSet(vals, 100, 100)
require.NoError(t, stateStore.Bootstrap(bootstrapState))
// bootstrap should also save the previous validator
_, err = stateStore.LoadValidators(99)
require.NoError(t, err)
_, err = stateStore.LoadValidators(100)
require.NoError(t, err)
_, err = stateStore.LoadValidators(101)
require.NoError(t, err)
state, err := stateStore.Load()
require.NoError(t, err)
require.Equal(t, bootstrapState, state)
}
func TestStoreLoadValidators(t *testing.T) {
stateDB := dbm.NewMemDB()
stateStore := sm.NewStore(stateDB)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
val, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
val2, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
val3, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
vals := types.NewValidatorSet([]*types.Validator{val, val2, val3})
// 1) LoadValidators loads validators using a height where they were last changed
// Note that only the next validators at height h + 1 are saved
require.NoError(t, stateStore.Save(makeRandomStateFromValidatorSet(vals, 1, 1)))
require.NoError(t, stateStore.Save(makeRandomStateFromValidatorSet(vals.CopyIncrementProposerPriority(1), 2, 1)))
loadedVals, err := stateStore.LoadValidators(3)
require.NoError(t, err)
require.Equal(t, vals.CopyIncrementProposerPriority(3), loadedVals)
// 2) LoadValidators loads validators using a checkpoint height
// add a validator set at the checkpoint
err = stateStore.Save(makeRandomStateFromValidatorSet(vals, valSetCheckpointInterval, 1))
require.NoError(t, err)
// check that a request will go back to the last checkpoint
_, err = stateStore.LoadValidators(valSetCheckpointInterval + 1)
require.Error(t, err)
require.Equal(t, fmt.Sprintf("couldn't find validators at height %d (height %d was originally requested): "+
"value retrieved from db is empty",
valSetCheckpointInterval, valSetCheckpointInterval+1), err.Error())
// now save a validator set at that checkpoint
err = stateStore.Save(makeRandomStateFromValidatorSet(vals, valSetCheckpointInterval-1, 1))
require.NoError(t, err)
loadedVals, err = stateStore.LoadValidators(valSetCheckpointInterval)
require.NoError(t, err)
// validator set gets updated with the one given hence we expect it to equal next validators (with an increment of one)
// as opposed to being equal to an increment of 100000 - 1 (if we didn't save at the checkpoint)
require.Equal(t, vals.CopyIncrementProposerPriority(2), loadedVals)
require.NotEqual(t, vals.CopyIncrementProposerPriority(valSetCheckpointInterval), loadedVals)
}
// This benchmarks the speed of loading validators from different heights if there is no validator set change.
// NOTE: This isn't too indicative of validator retrieval speed as the db is always (regardless of height) only
// performing two operations: 1) retrieve validator info at height x, which has a last validator set change of 1
// and 2) retrieve the validator set at the aforementioned height 1.
func BenchmarkLoadValidators(b *testing.B) {
const valSetSize = 100
cfg, err := config.ResetTestRoot("state_")
require.NoError(b, err)
defer os.RemoveAll(cfg.RootDir)
dbType := dbm.BackendType(cfg.DBBackend)
stateDB, err := dbm.NewDB("state", dbType, cfg.DBDir())
require.NoError(b, err)
stateStore := sm.NewStore(stateDB)
state, err := sm.MakeGenesisStateFromFile(cfg.GenesisFile())
if err != nil {
b.Fatal(err)
}
state.Validators = genValSet(valSetSize)
state.NextValidators = state.Validators.CopyIncrementProposerPriority(1)
err = stateStore.Save(state)
require.NoError(b, err)
b.ResetTimer()
for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ...
i := i
err = stateStore.Save(makeRandomStateFromValidatorSet(state.NextValidators,
int64(i)-1, state.LastHeightValidatorsChanged))
if err != nil {
b.Fatalf("error saving store: %v", err)
}
b.Run(fmt.Sprintf("height=%d", i), func(b *testing.B) {
for n := 0; n < b.N; n++ {
_, err := stateStore.LoadValidators(int64(i))
if err != nil {
b.Fatal(err)
}
}
})
}
}
func TestStoreLoadConsensusParams(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
stateDB := dbm.NewMemDB()
stateStore := sm.NewStore(stateDB)
err := stateStore.Save(makeRandomStateFromConsensusParams(ctx, t, types.DefaultConsensusParams(), 1, 1))
require.NoError(t, err)
params, err := stateStore.LoadConsensusParams(1)
require.NoError(t, err)
require.Equal(t, types.DefaultConsensusParams(), &params)
// we give the state store different params but say that the height hasn't changed, hence
// it should save a pointer to the params at height 1
differentParams := types.DefaultConsensusParams()
differentParams.Block.MaxBytes = 20000
err = stateStore.Save(makeRandomStateFromConsensusParams(ctx, t, differentParams, 10, 1))
require.NoError(t, err)
res, err := stateStore.LoadConsensusParams(10)
require.NoError(t, err)
require.Equal(t, res, params)
require.NotEqual(t, res, differentParams)
}
func TestPruneStates(t *testing.T) {
testcases := map[string]struct {
startHeight int64
endHeight int64
pruneHeight int64
expectErr bool
remainingValSetHeight int64
remainingParamsHeight int64
}{
"error when prune height is 0": {1, 100, 0, true, 0, 0},
"error when prune height is negative": {1, 100, -10, true, 0, 0},
"error when prune height does not exist": {1, 100, 101, true, 0, 0},
"prune all": {1, 100, 100, false, 93, 95},
"prune from non 1 height": {10, 50, 40, false, 33, 35},
"prune some": {1, 10, 8, false, 3, 5},
// we test this because we flush to disk every 1000 "states"
"prune more than 1000 state": {1, 1010, 1010, false, 1003, 1005},
"prune across checkpoint": {99900, 100002, 100002, false, 100000, 99995},
}
for name, tc := range testcases {
tc := tc
t.Run(name, func(t *testing.T) {
db := dbm.NewMemDB()
stateStore := sm.NewStore(db)
pk := ed25519.GenPrivKey().PubKey()
// Generate a bunch of state data. Validators change for heights ending with 3, and
// parameters when ending with 5.
validator := &types.Validator{Address: tmrand.Bytes(crypto.AddressSize), VotingPower: 100, PubKey: pk}
validatorSet := &types.ValidatorSet{
Validators: []*types.Validator{validator},
Proposer: validator,
}
valsChanged := int64(0)
paramsChanged := int64(0)
for h := tc.startHeight; h <= tc.endHeight; h++ {
if valsChanged == 0 || h%10 == 2 {
valsChanged = h + 1 // Have to add 1, since NextValidators is what's stored
}
if paramsChanged == 0 || h%10 == 5 {
paramsChanged = h
}
state := sm.State{
InitialHeight: 1,
LastBlockHeight: h - 1,
Validators: validatorSet,
NextValidators: validatorSet,
ConsensusParams: types.ConsensusParams{
Block: types.BlockParams{MaxBytes: 10e6},
},
LastHeightValidatorsChanged: valsChanged,
LastHeightConsensusParamsChanged: paramsChanged,
}
if state.LastBlockHeight >= 1 {
state.LastValidators = state.Validators
}
err := stateStore.Save(state)
require.NoError(t, err)
err = stateStore.SaveABCIResponses(h, &tmstate.ABCIResponses{
DeliverTxs: []*abci.ResponseDeliverTx{
{Data: []byte{1}},
{Data: []byte{2}},
{Data: []byte{3}},
},
})
require.NoError(t, err)
}
// Test assertions
err := stateStore.PruneStates(tc.pruneHeight)
if tc.expectErr {
require.Error(t, err)
return
}
require.NoError(t, err)
for h := tc.pruneHeight; h <= tc.endHeight; h++ {
vals, err := stateStore.LoadValidators(h)
require.NoError(t, err, h)
require.NotNil(t, vals, h)
params, err := stateStore.LoadConsensusParams(h)
require.NoError(t, err, h)
require.NotNil(t, params, h)
abci, err := stateStore.LoadABCIResponses(h)
require.NoError(t, err, h)
require.NotNil(t, abci, h)
}
emptyParams := types.ConsensusParams{}
for h := tc.startHeight; h < tc.pruneHeight; h++ {
vals, err := stateStore.LoadValidators(h)
if h == tc.remainingValSetHeight {
require.NoError(t, err, h)
require.NotNil(t, vals, h)
} else {
require.Error(t, err, h)
require.Nil(t, vals, h)
}
params, err := stateStore.LoadConsensusParams(h)
if h == tc.remainingParamsHeight {
require.NoError(t, err, h)
require.NotEqual(t, emptyParams, params, h)
} else {
require.Error(t, err, h)
require.Equal(t, emptyParams, params, h)
}
abci, err := stateStore.LoadABCIResponses(h)
require.Error(t, err, h)
require.Nil(t, abci, h)
}
})
}
}
func TestABCIResponsesResultsHash(t *testing.T) {
responses := &tmstate.ABCIResponses{
BeginBlock: &abci.ResponseBeginBlock{},
DeliverTxs: []*abci.ResponseDeliverTx{
{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
},
EndBlock: &abci.ResponseEndBlock{},
}
root := sm.ABCIResponsesResultsHash(responses)
// root should be Merkle tree root of DeliverTxs responses
results := types.NewResults(responses.DeliverTxs)
assert.Equal(t, root, results.Hash())
// test we can prove first DeliverTx
proof := results.ProveResult(0)
bz, err := results[0].Marshal()
require.NoError(t, err)
assert.NoError(t, proof.Verify(root, bz))
}