Merge branch '950-enforce-less-13-val-changes-per-block' into develop
This commit is contained in:
commit
d844799b3b
|
@ -347,7 +347,7 @@ func consensusLogger() log.Logger {
|
|||
}
|
||||
|
||||
func randConsensusNet(nValidators int, testName string, tickerFunc func() TimeoutTicker, appFunc func() abci.Application, configOpts ...func(*cfg.Config)) []*ConsensusState {
|
||||
genDoc, privVals := randGenesisDoc(nValidators, false, 10)
|
||||
genDoc, privVals := randGenesisDoc(nValidators, false, 30)
|
||||
css := make([]*ConsensusState, nValidators)
|
||||
logger := consensusLogger()
|
||||
for i := 0; i < nValidators; i++ {
|
||||
|
|
|
@ -180,7 +180,7 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
|||
t.Fatalf("expected voting power to change (before: %d, after: %d)", previousTotalVotingPower, css[0].GetRoundState().LastValidators.TotalVotingPower())
|
||||
}
|
||||
|
||||
updateValidatorTx = dummy.MakeValSetChangeTx(val1PubKey.Bytes(), 100)
|
||||
updateValidatorTx = dummy.MakeValSetChangeTx(val1PubKey.Bytes(), 26)
|
||||
previousTotalVotingPower = css[0].GetRoundState().LastValidators.TotalVotingPower()
|
||||
|
||||
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
|
||||
|
|
|
@ -403,14 +403,17 @@ pick up from when it restarts. See information on the Handshake, below.
|
|||
EndBlock
|
||||
^^^^^^^^
|
||||
|
||||
The EndBlock request can be used to run some code at the end of every
|
||||
block. Additionally, the response may contain a list of validators,
|
||||
which can be used to update the validator set. To add a new validator or
|
||||
update an existing one, simply include them in the list returned in the
|
||||
EndBlock response. To remove one, include it in the list with a
|
||||
``power`` equal to ``0``. Tendermint core will take care of updating the
|
||||
validator set. Note validator set changes are only available in v0.8.0
|
||||
and up.
|
||||
The EndBlock request can be used to run some code at the end of every block.
|
||||
Additionally, the response may contain a list of validators, which can be used
|
||||
to update the validator set. To add a new validator or update an existing one,
|
||||
simply include them in the list returned in the EndBlock response. To remove
|
||||
one, include it in the list with a ``power`` equal to ``0``. Tendermint core
|
||||
will take care of updating the validator set. Note the change in voting power
|
||||
must be strictly less than 1/3 per block. Otherwise it will be impossible for a
|
||||
light client to prove the transition externally. See the `light client docs
|
||||
<https://godoc.org/github.com/tendermint/tendermint/lite#hdr-How_We_Track_Validators>`__
|
||||
for details on how it tracks validators. Tendermint core will fail with an
|
||||
error if the change in voting power is more or equal than 1/3.
|
||||
|
||||
.. container:: toggle
|
||||
|
||||
|
@ -421,8 +424,8 @@ and up.
|
|||
.. code-block:: go
|
||||
|
||||
// Update the validator set
|
||||
func (app *PersistentDummyApplication) EndBlock(height uint64) (resEndBlock types.ResponseEndBlock) {
|
||||
return types.ResponseEndBlock{Diffs: app.changes}
|
||||
func (app *PersistentDummyApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock {
|
||||
return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates}
|
||||
}
|
||||
|
||||
.. container:: toggle
|
||||
|
|
|
@ -5,8 +5,8 @@ Light clients are an important part of the complete blockchain system
|
|||
for most applications. Tendermint provides unique speed and security
|
||||
properties for light client applications.
|
||||
|
||||
See our developing `light-client
|
||||
repository <https://github.com/tendermint/light-client>`__.
|
||||
See our `lite package
|
||||
<https://godoc.org/github.com/tendermint/tendermint/lite>`__.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
)
|
||||
|
||||
|
@ -112,9 +111,9 @@ func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn p
|
|||
return nil, err
|
||||
}
|
||||
|
||||
valUpdates := abciResponses.EndBlock.ValidatorUpdates
|
||||
|
||||
logger.Info("Executed block", "height", block.Height, "validTxs", validTxs, "invalidTxs", invalidTxs)
|
||||
|
||||
valUpdates := abciResponses.EndBlock.ValidatorUpdates
|
||||
if len(valUpdates) > 0 {
|
||||
logger.Info("Updates to validators", "updates", abci.ValidatorsString(valUpdates))
|
||||
}
|
||||
|
@ -122,10 +121,19 @@ func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn p
|
|||
return abciResponses, nil
|
||||
}
|
||||
|
||||
func updateValidators(validators *types.ValidatorSet, changedValidators []*abci.Validator) error {
|
||||
// TODO: prevent change of 1/3+ at once
|
||||
func updateValidators(currentSet *types.ValidatorSet, updates []*abci.Validator) error {
|
||||
// If more or equal than 1/3 of total voting power changed in one block, then
|
||||
// a light client could never prove the transition externally. See
|
||||
// ./lite/doc.go for details on how a light client tracks validators.
|
||||
vp23, err := changeInVotingPowerMoreOrEqualToOneThird(currentSet, updates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vp23 {
|
||||
return errors.New("the change in voting power must be strictly less than 1/3")
|
||||
}
|
||||
|
||||
for _, v := range changedValidators {
|
||||
for _, v := range updates {
|
||||
pubkey, err := crypto.PubKeyFromBytes(v.PubKey) // NOTE: expects go-wire encoded pubkey
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -135,34 +143,70 @@ func updateValidators(validators *types.ValidatorSet, changedValidators []*abci.
|
|||
power := int64(v.Power)
|
||||
// mind the overflow from int64
|
||||
if power < 0 {
|
||||
return errors.New(cmn.Fmt("Power (%d) overflows int64", v.Power))
|
||||
return fmt.Errorf("Power (%d) overflows int64", v.Power)
|
||||
}
|
||||
|
||||
_, val := validators.GetByAddress(address)
|
||||
_, val := currentSet.GetByAddress(address)
|
||||
if val == nil {
|
||||
// add val
|
||||
added := validators.Add(types.NewValidator(pubkey, power))
|
||||
added := currentSet.Add(types.NewValidator(pubkey, power))
|
||||
if !added {
|
||||
return errors.New(cmn.Fmt("Failed to add new validator %X with voting power %d", address, power))
|
||||
return fmt.Errorf("Failed to add new validator %X with voting power %d", address, power)
|
||||
}
|
||||
} else if v.Power == 0 {
|
||||
// remove val
|
||||
_, removed := validators.Remove(address)
|
||||
_, removed := currentSet.Remove(address)
|
||||
if !removed {
|
||||
return errors.New(cmn.Fmt("Failed to remove validator %X)"))
|
||||
return fmt.Errorf("Failed to remove validator %X", address)
|
||||
}
|
||||
} else {
|
||||
// update val
|
||||
val.VotingPower = power
|
||||
updated := validators.Update(val)
|
||||
updated := currentSet.Update(val)
|
||||
if !updated {
|
||||
return errors.New(cmn.Fmt("Failed to update validator %X with voting power %d", address, power))
|
||||
return fmt.Errorf("Failed to update validator %X with voting power %d", address, power)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changeInVotingPowerMoreOrEqualToOneThird(currentSet *types.ValidatorSet, updates []*abci.Validator) (bool, error) {
|
||||
threshold := currentSet.TotalVotingPower() * 1 / 3
|
||||
acc := int64(0)
|
||||
|
||||
for _, v := range updates {
|
||||
pubkey, err := crypto.PubKeyFromBytes(v.PubKey) // NOTE: expects go-wire encoded pubkey
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
address := pubkey.Address()
|
||||
power := int64(v.Power)
|
||||
// mind the overflow from int64
|
||||
if power < 0 {
|
||||
return false, fmt.Errorf("Power (%d) overflows int64", v.Power)
|
||||
}
|
||||
|
||||
_, val := currentSet.GetByAddress(address)
|
||||
if val == nil {
|
||||
acc += power
|
||||
} else {
|
||||
np := val.VotingPower - power
|
||||
if np < 0 {
|
||||
np = -np
|
||||
}
|
||||
acc += np
|
||||
}
|
||||
|
||||
if acc >= threshold {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// return a bit array of validators that signed the last commit
|
||||
// NOTE: assumes commits have already been authenticated
|
||||
/* function is currently unused
|
||||
|
@ -243,8 +287,8 @@ func (s *State) validateBlock(b *types.Block) error {
|
|||
}
|
||||
} else {
|
||||
if len(b.LastCommit.Precommits) != s.LastValidators.Size() {
|
||||
return errors.New(cmn.Fmt("Invalid block commit size. Expected %v, got %v",
|
||||
s.LastValidators.Size(), len(b.LastCommit.Precommits)))
|
||||
return fmt.Errorf("Invalid block commit size. Expected %v, got %v",
|
||||
s.LastValidators.Size(), len(b.LastCommit.Precommits))
|
||||
}
|
||||
err := s.LastValidators.VerifyCommit(
|
||||
s.ChainID, s.LastBlockID, b.Height-1, b.LastCommit)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
|
@ -131,79 +132,105 @@ func TestValidatorSimpleSaveLoad(t *testing.T) {
|
|||
}
|
||||
|
||||
// TestValidatorChangesSaveLoad tests saving and loading a validator set with changes.
|
||||
func TestValidatorChangesSaveLoad(t *testing.T) {
|
||||
func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
||||
tearDown, _, state := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
// nolint: vetshadow
|
||||
assert := assert.New(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
|
||||
pubkeys := make([]crypto.PubKey, N+1)
|
||||
_, val := state.Validators.GetByIndex(0)
|
||||
pubkeys[0] = val.PubKey
|
||||
for i := 1; i < N+1; i++ {
|
||||
pubkeys[i] = crypto.GenPrivKeyEd25519().PubKey()
|
||||
}
|
||||
|
||||
// build the validator history by running SetBlockAndValidators
|
||||
// with the right validator set for each height
|
||||
highestHeight := changeHeights[N-1] + 5
|
||||
changeIndex := 0
|
||||
pubkey := pubkeys[changeIndex]
|
||||
_, 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++
|
||||
pubkey = pubkeys[changeIndex]
|
||||
power += 1
|
||||
}
|
||||
header, parts, responses := makeHeaderPartsResponses(state, i, pubkey)
|
||||
state.SetBlockAndValidators(header, parts, responses)
|
||||
header, parts, responses := makeHeaderPartsResponsesValPowerChange(state, i, power)
|
||||
err := state.SetBlockAndValidators(header, parts, responses)
|
||||
assert.Nil(t, err)
|
||||
state.saveValidatorsInfo()
|
||||
}
|
||||
|
||||
// make all the test cases by using the same validator until after the change
|
||||
testCases := make([]valChangeTestCase, highestHeight)
|
||||
// on each change height, increment the power by one.
|
||||
testCases := make([]int64, highestHeight)
|
||||
changeIndex = 0
|
||||
pubkey = pubkeys[changeIndex]
|
||||
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++
|
||||
pubkey = pubkeys[changeIndex]
|
||||
power += 1
|
||||
}
|
||||
testCases[i-1] = valChangeTestCase{i, pubkey}
|
||||
testCases[i-1] = power
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
v, err := state.LoadValidators(testCase.height)
|
||||
assert.Nil(err, fmt.Sprintf("expected no err at height %d", testCase.height))
|
||||
assert.Equal(v.Size(), 1, "validator set size is greater than 1: %d", v.Size())
|
||||
addr, _ := v.GetByIndex(0)
|
||||
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(addr, testCase.vals.Address(), fmt.Sprintf(`unexpected pubkey at
|
||||
height %d`, testCase.height))
|
||||
assert.Equal(t, val.VotingPower, power, fmt.Sprintf(`unexpected powerat
|
||||
height %d`, i))
|
||||
}
|
||||
}
|
||||
|
||||
// TestConsensusParamsChangesSaveLoad tests saving and loading consensus params with changes.
|
||||
// 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)
|
||||
// nolint: vetshadow
|
||||
assert := assert.New(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.
|
||||
// each valset is just one validator
|
||||
// create list of them
|
||||
params := make([]types.ConsensusParams, N+1)
|
||||
params[0] = state.ConsensusParams
|
||||
|
@ -225,7 +252,8 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||
cp = params[changeIndex]
|
||||
}
|
||||
header, parts, responses := makeHeaderPartsResponsesParams(state, i, cp)
|
||||
state.SetBlockAndValidators(header, parts, responses)
|
||||
err := state.SetBlockAndValidators(header, parts, responses)
|
||||
require.Nil(t, err)
|
||||
state.saveConsensusParamsInfo()
|
||||
}
|
||||
|
||||
|
@ -245,8 +273,8 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
p, err := state.LoadConsensusParams(testCase.height)
|
||||
assert.Nil(err, fmt.Sprintf("expected no err at height %d", testCase.height))
|
||||
assert.Equal(testCase.params, p, fmt.Sprintf(`unexpected consensus params at
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -270,6 +298,78 @@ func makeParams(blockBytes, blockTx, blockGas, txBytes,
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
|
@ -316,17 +416,17 @@ func TestApplyUpdates(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func makeHeaderPartsResponses(state *State, height int64,
|
||||
func makeHeaderPartsResponsesValPubKeyChange(state *State, height int64,
|
||||
pubkey crypto.PubKey) (*types.Header, types.PartSetHeader, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(state, height)
|
||||
_, val := state.Validators.GetByIndex(0)
|
||||
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{
|
||||
|
@ -339,9 +439,26 @@ func makeHeaderPartsResponses(state *State, height int64,
|
|||
return block.Header, types.PartSetHeader{}, abciResponses
|
||||
}
|
||||
|
||||
type valChangeTestCase struct {
|
||||
height int64
|
||||
vals crypto.PubKey
|
||||
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,
|
||||
|
|
Loading…
Reference in New Issue