Browse Source

do not enforce 1/3 validator power change

leave it to the app

Refs #1022
pull/1201/head
Anton Kaliaev 7 years ago
parent
commit
106cdb74e5
No known key found for this signature in database GPG Key ID: 7B6881D965918214
3 changed files with 6 additions and 119 deletions
  1. +3
    -4
      docs/app-development.rst
  2. +3
    -48
      state/execution.go
  3. +0
    -67
      state/state_test.go

+ 3
- 4
docs/app-development.rst View File

@ -409,11 +409,10 @@ 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 the change in voting power 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
must be strictly less than 1/3 per block if you want a light client to be able
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. Tendermint core will fail with an
error if the change in voting power is more or equal than 1/3.
for details on how it tracks validators.
.. container:: toggle .. container:: toggle


+ 3
- 48
state/execution.go View File

@ -1,7 +1,6 @@
package state package state
import ( import (
"errors"
"fmt" "fmt"
fail "github.com/ebuchman/fail-test" fail "github.com/ebuchman/fail-test"
@ -238,18 +237,10 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
return abciResponses, nil return abciResponses, nil
} }
// 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.
func updateValidators(currentSet *types.ValidatorSet, updates []abci.Validator) error { 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 updates { 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 {
@ -288,42 +279,6 @@ 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
}
// updateState returns a new State updated according to the header and responses. // updateState returns a new State updated according to the header and responses.
func updateState(s State, blockID types.BlockID, header *types.Header, func updateState(s State, blockID types.BlockID, header *types.Header,
abciResponses *ABCIResponses) (State, error) { abciResponses *ABCIResponses) (State, error) {


+ 0
- 67
state/state_test.go View File

@ -375,73 +375,6 @@ 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, stateDB, state := setupTestCase(t)
state.Validators = genValSet(tc.initialValSetSize)
SaveState(stateDB, state)
height := state.LastBlockHeight + 1
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: tc.valUpdatesFn(state.Validators)},
}
state, err := updateState(state, types.BlockID{block.Hash(), types.PartSetHeader{}}, block.Header, abciResponses)
if tc.shouldErr {
assert.Error(t, err, "#%d", i)
} else {
assert.NoError(t, err, "#%d", i)
}
tearDown(t)
}
}
func pk() []byte { func pk() []byte {
return crypto.GenPrivKeyEd25519().PubKey().Bytes() return crypto.GenPrivKeyEd25519().PubKey().Bytes()
} }


Loading…
Cancel
Save