mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-30 01:02:13 +00:00
parent
616f7e74db
commit
cf0b5d3715
@ -194,8 +194,8 @@ func TestReactorVotingPowerChange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReactorValidatorSetChanges(t *testing.T) {
|
func TestReactorValidatorSetChanges(t *testing.T) {
|
||||||
nPeers := 7
|
nPeers := 9
|
||||||
nVals := 4
|
nVals := 6
|
||||||
css := randConsensusNetWithPeers(nVals, nPeers, "consensus_val_set_changes_test", newMockTickerFunc(true), newPersistentDummy)
|
css := randConsensusNetWithPeers(nVals, nPeers, "consensus_val_set_changes_test", newMockTickerFunc(true), newPersistentDummy)
|
||||||
|
|
||||||
logger := log.TestingLogger()
|
logger := log.TestingLogger()
|
||||||
|
@ -403,14 +403,16 @@ pick up from when it restarts. See information on the Handshake, below.
|
|||||||
EndBlock
|
EndBlock
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|
||||||
The EndBlock request can be used to run some code at the end of every
|
The EndBlock request can be used to run some code at the end of every block.
|
||||||
block. Additionally, the response may contain a list of validators,
|
Additionally, the response may contain a list of validators, which can be used
|
||||||
which can be used to update the validator set. To add a new validator or
|
to update the validator set. To add a new validator or update an existing one,
|
||||||
update an existing one, simply include them in the list returned in the
|
simply include them in the list returned in the EndBlock response. To remove
|
||||||
EndBlock response. To remove one, include it in the list with a
|
one, include it in the list with a ``power`` equal to ``0``. Tendermint core
|
||||||
``power`` equal to ``0``. Tendermint core will take care of updating the
|
will take care of updating the validator set. Note you can not update more than
|
||||||
validator set. Note validator set changes are only available in v0.8.0
|
1/3 of validators in one block because this will make it impossible for a light
|
||||||
and up.
|
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.
|
||||||
|
|
||||||
.. container:: toggle
|
.. 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
|
for most applications. Tendermint provides unique speed and security
|
||||||
properties for light client applications.
|
properties for light client applications.
|
||||||
|
|
||||||
See our developing `light-client
|
See our `lite package
|
||||||
repository <https://github.com/tendermint/light-client>`__.
|
<https://godoc.org/github.com/tendermint/tendermint/lite>`__.
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
--------
|
--------
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
crypto "github.com/tendermint/go-crypto"
|
crypto "github.com/tendermint/go-crypto"
|
||||||
"github.com/tendermint/tendermint/proxy"
|
"github.com/tendermint/tendermint/proxy"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -112,9 +111,9 @@ func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn p
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
valUpdates := abciResponses.EndBlock.ValidatorUpdates
|
|
||||||
|
|
||||||
logger.Info("Executed block", "height", block.Height, "validTxs", validTxs, "invalidTxs", invalidTxs)
|
logger.Info("Executed block", "height", block.Height, "validTxs", validTxs, "invalidTxs", invalidTxs)
|
||||||
|
|
||||||
|
valUpdates := abciResponses.EndBlock.ValidatorUpdates
|
||||||
if len(valUpdates) > 0 {
|
if len(valUpdates) > 0 {
|
||||||
logger.Info("Updates to validators", "updates", abci.ValidatorsString(valUpdates))
|
logger.Info("Updates to validators", "updates", abci.ValidatorsString(valUpdates))
|
||||||
}
|
}
|
||||||
@ -122,10 +121,22 @@ func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn p
|
|||||||
return abciResponses, nil
|
return abciResponses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateValidators(validators *types.ValidatorSet, changedValidators []*abci.Validator) error {
|
func updateValidators(currentSet *types.ValidatorSet, updates []*abci.Validator) error {
|
||||||
// TODO: prevent change of 1/3+ at once
|
// ## prevent update of 1/3+ at once
|
||||||
|
//
|
||||||
|
// If more than 1/3 validators 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.
|
||||||
|
maxUpdates := currentSet.Size() / 3
|
||||||
|
if maxUpdates == 0 { // if current set size is less than 3
|
||||||
|
maxUpdates = 1
|
||||||
|
}
|
||||||
|
if len(updates) > maxUpdates {
|
||||||
|
return errors.New("Can not update more than 1/3 of validators at once")
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range changedValidators {
|
for _, v := range updates {
|
||||||
pubkey, err := crypto.PubKeyFromBytes(v.PubKey) // NOTE: expects go-wire encoded pubkey
|
pubkey, err := crypto.PubKeyFromBytes(v.PubKey) // NOTE: expects go-wire encoded pubkey
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -135,28 +146,28 @@ func updateValidators(validators *types.ValidatorSet, changedValidators []*abci.
|
|||||||
power := int64(v.Power)
|
power := int64(v.Power)
|
||||||
// mind the overflow from int64
|
// mind the overflow from int64
|
||||||
if power < 0 {
|
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 {
|
if val == nil {
|
||||||
// add val
|
// add val
|
||||||
added := validators.Add(types.NewValidator(pubkey, power))
|
added := currentSet.Add(types.NewValidator(pubkey, power))
|
||||||
if !added {
|
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 {
|
} else if v.Power == 0 {
|
||||||
// remove val
|
// remove val
|
||||||
_, removed := validators.Remove(address)
|
_, removed := currentSet.Remove(address)
|
||||||
if !removed {
|
if !removed {
|
||||||
return errors.New(cmn.Fmt("Failed to remove validator %X)"))
|
return fmt.Errorf("Failed to remove validator %X", address)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// update val
|
// update val
|
||||||
val.VotingPower = power
|
val.VotingPower = power
|
||||||
updated := validators.Update(val)
|
updated := currentSet.Update(val)
|
||||||
if !updated {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,8 +254,8 @@ func (s *State) validateBlock(b *types.Block) error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(b.LastCommit.Precommits) != s.LastValidators.Size() {
|
if len(b.LastCommit.Precommits) != s.LastValidators.Size() {
|
||||||
return errors.New(cmn.Fmt("Invalid block commit size. Expected %v, got %v",
|
return fmt.Errorf("Invalid block commit size. Expected %v, got %v",
|
||||||
s.LastValidators.Size(), len(b.LastCommit.Precommits)))
|
s.LastValidators.Size(), len(b.LastCommit.Precommits))
|
||||||
}
|
}
|
||||||
err := s.LastValidators.VerifyCommit(
|
err := s.LastValidators.VerifyCommit(
|
||||||
s.ChainID, s.LastBlockID, b.Height-1, b.LastCommit)
|
s.ChainID, s.LastBlockID, b.Height-1, b.LastCommit)
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
|
|
||||||
@ -130,81 +131,56 @@ func TestValidatorSimpleSaveLoad(t *testing.T) {
|
|||||||
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height")
|
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestValidatorChangesSaveLoad tests saving and loading a validator set with changes.
|
// TestValidatorChangesSaveLoad tests saving and loading a validator set with
|
||||||
|
// changes.
|
||||||
func TestValidatorChangesSaveLoad(t *testing.T) {
|
func TestValidatorChangesSaveLoad(t *testing.T) {
|
||||||
|
const valSetSize = 6
|
||||||
tearDown, _, state := setupTestCase(t)
|
tearDown, _, state := setupTestCase(t)
|
||||||
|
state.Validators = genValSet(valSetSize)
|
||||||
|
state.Save()
|
||||||
defer tearDown(t)
|
defer tearDown(t)
|
||||||
// nolint: vetshadow
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
// change vals at these heights
|
const height = 1
|
||||||
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
|
pubkey := crypto.GenPrivKeyEd25519().PubKey()
|
||||||
N := len(changeHeights)
|
// swap the first validator with a new one ^^^ (validator set size stays the same)
|
||||||
|
header, parts, responses := makeHeaderPartsResponses(state, height, pubkey)
|
||||||
// each valset is just one validator.
|
err := state.SetBlockAndValidators(header, parts, responses)
|
||||||
// create list of them
|
require.Nil(t, err)
|
||||||
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]
|
|
||||||
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]
|
|
||||||
}
|
|
||||||
header, parts, responses := makeHeaderPartsResponses(state, i, pubkey)
|
|
||||||
state.SetBlockAndValidators(header, parts, responses)
|
|
||||||
state.saveValidatorsInfo()
|
state.saveValidatorsInfo()
|
||||||
}
|
|
||||||
|
|
||||||
// make all the test cases by using the same validator until after the change
|
v, err := state.LoadValidators(height + 1)
|
||||||
testCases := make([]valChangeTestCase, highestHeight)
|
assert.Nil(t, err)
|
||||||
changeIndex = 0
|
assert.Equal(t, valSetSize, v.Size())
|
||||||
pubkey = pubkeys[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++
|
|
||||||
pubkey = pubkeys[changeIndex]
|
|
||||||
}
|
|
||||||
testCases[i-1] = valChangeTestCase{i, pubkey}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
index, val := v.GetByAddress(pubkey.Address())
|
||||||
v, err := state.LoadValidators(testCase.height)
|
assert.NotNil(t, val)
|
||||||
assert.Nil(err, fmt.Sprintf("expected no err at height %d", testCase.height))
|
if index < 0 {
|
||||||
assert.Equal(v.Size(), 1, "validator set size is greater than 1: %d", v.Size())
|
t.Fatal("expected to find newly added validator")
|
||||||
addr, _ := v.GetByIndex(0)
|
|
||||||
|
|
||||||
assert.Equal(addr, testCase.vals.Address(), fmt.Sprintf(`unexpected pubkey at
|
|
||||||
height %d`, testCase.height))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestConsensusParamsChangesSaveLoad tests saving and loading consensus params with changes.
|
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) {
|
func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
||||||
tearDown, _, state := setupTestCase(t)
|
tearDown, _, state := setupTestCase(t)
|
||||||
|
const valSetSize = 20
|
||||||
|
state.Validators = genValSet(valSetSize)
|
||||||
|
state.Save()
|
||||||
defer tearDown(t)
|
defer tearDown(t)
|
||||||
// nolint: vetshadow
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
// change vals at these heights
|
// change vals at these heights
|
||||||
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
|
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
|
||||||
N := len(changeHeights)
|
N := len(changeHeights)
|
||||||
|
|
||||||
// each valset is just one validator.
|
// create list of new vals
|
||||||
// create list of them
|
|
||||||
params := make([]types.ConsensusParams, N+1)
|
params := make([]types.ConsensusParams, N+1)
|
||||||
params[0] = state.ConsensusParams
|
params[0] = state.ConsensusParams
|
||||||
for i := 1; i < N+1; i++ {
|
for i := 1; i < N+1; i++ {
|
||||||
@ -225,7 +201,8 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||||||
cp = params[changeIndex]
|
cp = params[changeIndex]
|
||||||
}
|
}
|
||||||
header, parts, responses := makeHeaderPartsResponsesParams(state, i, cp)
|
header, parts, responses := makeHeaderPartsResponsesParams(state, i, cp)
|
||||||
state.SetBlockAndValidators(header, parts, responses)
|
err := state.SetBlockAndValidators(header, parts, responses)
|
||||||
|
require.Nil(t, err)
|
||||||
state.saveConsensusParamsInfo()
|
state.saveConsensusParamsInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,8 +222,8 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
p, err := state.LoadConsensusParams(testCase.height)
|
p, err := state.LoadConsensusParams(testCase.height)
|
||||||
assert.Nil(err, fmt.Sprintf("expected no err at height %d", testCase.height))
|
assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", testCase.height))
|
||||||
assert.Equal(testCase.params, p, fmt.Sprintf(`unexpected consensus params at
|
assert.Equal(t, testCase.params, p, fmt.Sprintf(`unexpected consensus params at
|
||||||
height %d`, testCase.height))
|
height %d`, testCase.height))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,6 +247,20 @@ func makeParams(blockBytes, blockTx, blockGas, txBytes,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLessThanOneThirdOfValidatorUpdatesEnforced(t *testing.T) {
|
||||||
|
tearDown, _, state := setupTestCase(t)
|
||||||
|
defer tearDown(t)
|
||||||
|
|
||||||
|
height := state.LastBlockHeight + 1
|
||||||
|
block := makeBlock(state, height)
|
||||||
|
abciResponses := &ABCIResponses{
|
||||||
|
Height: height,
|
||||||
|
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{{PubKey: []byte("a"), Power: 10}}},
|
||||||
|
}
|
||||||
|
err := state.SetBlockAndValidators(block.Header, types.PartSetHeader{}, abciResponses)
|
||||||
|
assert.NotNil(t, err, "expected err when trying to update more than 1/3 of validators")
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplyUpdates(t *testing.T) {
|
func TestApplyUpdates(t *testing.T) {
|
||||||
initParams := makeParams(1, 2, 3, 4, 5, 6)
|
initParams := makeParams(1, 2, 3, 4, 5, 6)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user