Browse Source

change voting power change, not number of vals

pull/1004/head
Anton Kaliaev 7 years ago
parent
commit
0093f9877a
No known key found for this signature in database GPG Key ID: 7B6881D965918214
5 changed files with 63 additions and 28 deletions
  1. +1
    -1
      consensus/common_test.go
  2. +3
    -3
      consensus/reactor_test.go
  3. +5
    -4
      docs/app-development.rst
  4. +44
    -11
      state/execution.go
  5. +10
    -9
      state/state_test.go

+ 1
- 1
consensus/common_test.go 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++ {


+ 3
- 3
consensus/reactor_test.go 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
nVals := 6
nPeers := 7
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()


+ 5
- 4
docs/app-development.rst 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
1/3 of validators in one block because this will make it impossible for a light
client to prove the transition externally. See the `light client docs
will take care of updating the validator set. Note the change in voting power
must be strictly less than 1/3 because 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>`__ <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


+ 44
- 11
state/execution.go 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 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 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 len(updates) > maxUpdates {
return errors.New("Can not update more than 1/3 of validators at once")
if vp23 {
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


+ 10
- 9
state/state_test.go 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,
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{{PubKey: []byte("a"), Power: 10}}},
Height: height,
// 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) {


Loading…
Cancel
Save