change voting power change, not number of vals

This commit is contained in:
Anton Kaliaev 2017-12-25 14:07:45 -06:00
parent cf0b5d3715
commit 0093f9877a
No known key found for this signature in database
GPG Key ID: 7B6881D965918214
5 changed files with 63 additions and 28 deletions

View File

@ -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 { 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) css := make([]*ConsensusState, nValidators)
logger := consensusLogger() logger := consensusLogger()
for i := 0; i < nValidators; i++ { for i := 0; i < nValidators; i++ {

View File

@ -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()) 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() previousTotalVotingPower = css[0].GetRoundState().LastValidators.TotalVotingPower()
waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx) waitForAndValidateBlock(t, nVals, activeVals, eventChans, css, updateValidatorTx)
@ -194,8 +194,8 @@ func TestReactorVotingPowerChange(t *testing.T) {
} }
func TestReactorValidatorSetChanges(t *testing.T) { func TestReactorValidatorSetChanges(t *testing.T) {
nPeers := 9 nPeers := 7
nVals := 6 nVals := 4
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()

View File

@ -408,11 +408,12 @@ 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, 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 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 one, include it in the list with a ``power`` equal to ``0``. Tendermint core
will take care of updating the validator set. Note you can not update more than will take care of updating the validator set. Note the change in voting power
1/3 of validators in one block because this will make it impossible for a light must be strictly less than 1/3 because otherwise it will be impossible for a
client to prove the transition externally. See the `light client docs 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>`__ <https://godoc.org/github.com/tendermint/tendermint/lite#hdr-How_We_Track_Validators>`__
for details on how it tracks validators. for details on how it tracks validators. Tendermint core will report an error
if that is the case.
.. container:: toggle .. container:: toggle

View File

@ -122,18 +122,15 @@ func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn p
} }
func updateValidators(currentSet *types.ValidatorSet, updates []*abci.Validator) error { func updateValidators(currentSet *types.ValidatorSet, updates []*abci.Validator) error {
// ## prevent update of 1/3+ at once // 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
// If more than 1/3 validators changed in one block, then a light // ./lite/doc.go for details on how a light client tracks validators.
// client could never prove the transition externally. See vp23, err := changeInVotingPowerMoreOrEqualToOneThird(currentSet, updates)
// ./lite/doc.go for details on how a light client tracks if err != nil {
// validators. return err
maxUpdates := currentSet.Size() / 3
if maxUpdates == 0 { // if current set size is less than 3
maxUpdates = 1
} }
if len(updates) > maxUpdates { if vp23 {
return errors.New("Can not update more than 1/3 of validators at once") return errors.New("the change in voting power must be strictly less than 1/3")
} }
for _, v := range updates { for _, v := range updates {
@ -174,6 +171,42 @@ func updateValidators(currentSet *types.ValidatorSet, updates []*abci.Validator)
return nil 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 // return a bit array of validators that signed the last commit
// NOTE: assumes commits have already been authenticated // NOTE: assumes commits have already been authenticated
/* function is currently unused /* function is currently unused

View File

@ -134,7 +134,7 @@ func TestValidatorSimpleSaveLoad(t *testing.T) {
// TestValidatorChangesSaveLoad tests saving and loading a validator set with // TestValidatorChangesSaveLoad tests saving and loading a validator set with
// changes. // changes.
func TestValidatorChangesSaveLoad(t *testing.T) { func TestValidatorChangesSaveLoad(t *testing.T) {
const valSetSize = 6 const valSetSize = 7
tearDown, _, state := setupTestCase(t) tearDown, _, state := setupTestCase(t)
state.Validators = genValSet(valSetSize) state.Validators = genValSet(valSetSize)
state.Save() state.Save()
@ -171,16 +171,14 @@ func genValSet(size int) *types.ValidatorSet {
// with changes. // 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)
// 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)
// create list of new vals // each valset is just one validator
// 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++ {
@ -247,18 +245,21 @@ func makeParams(blockBytes, blockTx, blockGas, txBytes,
} }
} }
func TestLessThanOneThirdOfValidatorUpdatesEnforced(t *testing.T) { func TestLessThanOneThirdOfVotingPowerPerBlockEnforced(t *testing.T) {
tearDown, _, state := setupTestCase(t) tearDown, _, state := setupTestCase(t)
defer tearDown(t) defer tearDown(t)
height := state.LastBlockHeight + 1 height := state.LastBlockHeight + 1
block := makeBlock(state, height) block := makeBlock(state, height)
abciResponses := &ABCIResponses{ abciResponses := &ABCIResponses{
Height: height, Height: height,
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{{PubKey: []byte("a"), Power: 10}}}, // 1 val (vp: 10) => less than 3 is ok
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{
{PubKey: crypto.GenPrivKeyEd25519().PubKey().Bytes(), Power: 3},
}},
} }
err := state.SetBlockAndValidators(block.Header, types.PartSetHeader{}, abciResponses) err := state.SetBlockAndValidators(block.Header, types.PartSetHeader{}, abciResponses)
assert.NotNil(t, err, "expected err when trying to update more than 1/3 of validators") assert.Error(t, err)
} }
func TestApplyUpdates(t *testing.T) { func TestApplyUpdates(t *testing.T) {