Skip to content

Commit

Permalink
Disallow adding back validator to empty validator set + test (cosmos#854
Browse files Browse the repository at this point in the history
  • Loading branch information
Manav-Aggarwal committed Apr 18, 2023
1 parent 3c47111 commit acf0090
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 89 deletions.
139 changes: 51 additions & 88 deletions node/full_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ func TestBlockchainInfo(t *testing.T) {
}
}

func createGenesisValidators(numNodes int, appCreator func(vKeyToRemove tmcrypto.PrivKey) *mocks.Application, require *require.Assertions) *FullClient {
func createGenesisValidators(numNodes int, appCreator func(require *require.Assertions, vKeyToRemove tmcrypto.PrivKey, waitCh chan interface{}) *mocks.Application, require *require.Assertions, waitCh chan interface{}) *FullClient {
vKeys := make([]tmcrypto.PrivKey, numNodes)
apps := make([]*mocks.Application, numNodes)
nodes := make([]*FullNode, numNodes)
Expand All @@ -659,7 +659,7 @@ func createGenesisValidators(numNodes int, appCreator func(vKeyToRemove tmcrypto
for i := 0; i < len(vKeys); i++ {
vKeys[i] = ed25519.GenPrivKey()
genesisValidators[i] = tmtypes.GenesisValidator{Address: vKeys[i].PubKey().Address(), PubKey: vKeys[i].PubKey(), Power: int64(i + 100), Name: fmt.Sprintf("gen #%d", i)}
apps[i] = appCreator(vKeys[0])
apps[i] = appCreator(require, vKeys[0], waitCh)
}

dalc := &mockda.DataAvailabilityLayerClient{}
Expand Down Expand Up @@ -710,6 +710,46 @@ func createGenesisValidators(numNodes int, appCreator func(vKeyToRemove tmcrypto
return rpc
}

func checkValSet(rpc *FullClient, assert *assert.Assertions, h int64, expectedValCount int) {
vals, err := rpc.Validators(context.Background(), &h, nil, nil)
assert.NoError(err)
assert.NotNil(vals)
assert.EqualValues(expectedValCount, vals.Total)
assert.Len(vals.Validators, expectedValCount)
assert.EqualValues(vals.BlockHeight, h)
}

func checkValSetLatest(rpc *FullClient, assert *assert.Assertions, lastBlockHeight int64, expectedValCount int) {
vals, err := rpc.Validators(context.Background(), nil, nil, nil)
assert.NoError(err)
assert.NotNil(vals)
assert.EqualValues(expectedValCount, vals.Total)
assert.Len(vals.Validators, expectedValCount)
assert.GreaterOrEqual(vals.BlockHeight, lastBlockHeight)
}

func createApp(require *require.Assertions, vKeyToRemove tmcrypto.PrivKey, waitCh chan interface{}) *mocks.Application {
app := &mocks.Application{}
app.On("InitChain", mock.Anything).Return(abci.ResponseInitChain{})
app.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{})
app.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{})
app.On("Commit", mock.Anything).Return(abci.ResponseCommit{})
app.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{})
app.On("GenerateFraudProof", mock.Anything).Return(abci.ResponseGenerateFraudProof{})

pbValKey, err := encoding.PubKeyToProto(vKeyToRemove.PubKey())
require.NoError(err)

app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}).Times(2)
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{{PubKey: pbValKey, Power: 0}}}).Once()
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}).Once()
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{{PubKey: pbValKey, Power: 100}}}).Once()
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}).Run(func(args mock.Arguments) {
waitCh <- nil
})
return app
}

// Tests moving from two validators to one validator and then back to two validators
func TestValidatorSetHandling(t *testing.T) {
assert := assert.New(t)
Expand All @@ -718,71 +758,30 @@ func TestValidatorSetHandling(t *testing.T) {
waitCh := make(chan interface{})

numNodes := 2
createApp := func(vKeyToRemove tmcrypto.PrivKey) *mocks.Application {
app := &mocks.Application{}
app.On("InitChain", mock.Anything).Return(abci.ResponseInitChain{})
app.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{})
app.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{})
app.On("Commit", mock.Anything).Return(abci.ResponseCommit{})
app.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{})
app.On("GenerateFraudProof", mock.Anything).Return(abci.ResponseGenerateFraudProof{})

pbValKey, err := encoding.PubKeyToProto(vKeyToRemove.PubKey())
require.NoError(err)

app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}).Times(2)
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{{PubKey: pbValKey, Power: 0}}}).Once()
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}).Once()
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{{PubKey: pbValKey, Power: 100}}}).Once()
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}).Run(func(args mock.Arguments) {
waitCh <- nil
})
return app
}
rpc := createGenesisValidators(numNodes, createApp, require)
rpc := createGenesisValidators(numNodes, createApp, require, waitCh)

<-waitCh
<-waitCh

// test first blocks
for h := int64(1); h <= 3; h++ {
vals, err := rpc.Validators(context.Background(), &h, nil, nil)
assert.NoError(err)
assert.NotNil(vals)
assert.EqualValues(numNodes, vals.Total)
assert.Len(vals.Validators, numNodes)
assert.EqualValues(vals.BlockHeight, h)
checkValSet(rpc, assert, h, numNodes)
}

// 3rd EndBlock removes the first validator from the list
for h := int64(4); h <= 5; h++ {
vals, err := rpc.Validators(context.Background(), &h, nil, nil)
assert.NoError(err)
assert.NotNil(vals)
assert.EqualValues(numNodes-1, vals.Total)
assert.Len(vals.Validators, numNodes-1)
assert.EqualValues(vals.BlockHeight, h)
checkValSet(rpc, assert, h, numNodes-1)
}

// 5th EndBlock adds validator back
for h := int64(6); h <= 9; h++ {
<-waitCh
<-waitCh
vals, err := rpc.Validators(context.Background(), &h, nil, nil)
assert.NoError(err)
assert.NotNil(vals)
assert.EqualValues(numNodes, vals.Total)
assert.Len(vals.Validators, numNodes)
assert.EqualValues(vals.BlockHeight, h)
checkValSet(rpc, assert, h, numNodes)
}

// check for "latest block"
vals, err := rpc.Validators(context.Background(), nil, nil, nil)
assert.NoError(err)
assert.NotNil(vals)
assert.EqualValues(numNodes, vals.Total)
assert.Len(vals.Validators, numNodes)
assert.GreaterOrEqual(vals.BlockHeight, int64(9))
checkValSetLatest(rpc, assert, int64(9), numNodes)
}

// Tests moving from a centralized validator to empty validator set
Expand All @@ -791,61 +790,25 @@ func TestValidatorSetHandlingBased(t *testing.T) {
require := require.New(t)

waitCh := make(chan interface{})

numNodes := 1
createApp := func(vKeyToRemove tmcrypto.PrivKey) *mocks.Application {
app := &mocks.Application{}
app.On("InitChain", mock.Anything).Return(abci.ResponseInitChain{})
app.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{})
app.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{})
app.On("Commit", mock.Anything).Return(abci.ResponseCommit{})
app.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{})
app.On("GenerateFraudProof", mock.Anything).Return(abci.ResponseGenerateFraudProof{})

pbValKey, err := encoding.PubKeyToProto(vKeyToRemove.PubKey())
require.NoError(err)

app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}).Times(2)
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{{PubKey: pbValKey, Power: 0}}}).Once()
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}).Run(func(args mock.Arguments) {
waitCh <- nil
})
return app
}

rpc := createGenesisValidators(numNodes, createApp, require)
rpc := createGenesisValidators(numNodes, createApp, require, waitCh)

<-waitCh

// test first blocks
for h := int64(1); h <= 3; h++ {
vals, err := rpc.Validators(context.Background(), &h, nil, nil)
assert.NoError(err)
assert.NotNil(vals)
assert.EqualValues(numNodes, vals.Total)
assert.Len(vals.Validators, numNodes)
assert.EqualValues(vals.BlockHeight, h)
checkValSet(rpc, assert, h, numNodes)
}

// 3rd EndBlock removes the first validator and makes the rollup based
for h := int64(4); h <= 9; h++ {
<-waitCh
vals, err := rpc.Validators(context.Background(), &h, nil, nil)
assert.NoError(err)
assert.NotNil(vals)
assert.EqualValues(numNodes-1, vals.Total)
assert.Len(vals.Validators, numNodes-1)
assert.EqualValues(vals.BlockHeight, h)
checkValSet(rpc, assert, h, numNodes-1)
}

// check for "latest block"
<-waitCh
vals, err := rpc.Validators(context.Background(), nil, nil, nil)
assert.NoError(err)
assert.NotNil(vals)
assert.EqualValues(numNodes-1, vals.Total)
assert.Len(vals.Validators, numNodes-1)
assert.GreaterOrEqual(vals.BlockHeight, int64(9))
checkValSetLatest(rpc, assert, 9, numNodes-1)
}

// copy-pasted from store/store_test.go
Expand Down
3 changes: 3 additions & 0 deletions state/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

var ErrFraudProofGenerated = errors.New("failed to ApplyBlock: halting node due to fraud")
var ErrEmptyValSetGenerated = errors.New("applying the validator changes would result in empty set")
var ErrAddingValidatorToBased = errors.New("cannot add validators to empty validator set")

// BlockExecutor creates and applies blocks and maintains state.
type BlockExecutor struct {
Expand Down Expand Up @@ -227,6 +228,8 @@ func (e *BlockExecutor) updateState(state types.State, block *types.Block, abciR
nValSet.IncrementProposerPriority(1)
}
// TODO(tzdybal): right now, it's for backward compatibility, may need to change this
} else if len(validatorUpdates) > 0 {
return state, ErrAddingValidatorToBased
}

s := types.State{
Expand Down
2 changes: 1 addition & 1 deletion types/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (h *SignedHeader) ValidateBasic() error {
}

// Handle Based Rollup case
if len(h.Validators.Validators) == 0 {
if h.Validators == nil || len(h.Validators.Validators) == 0 {
return nil
}

Expand Down

0 comments on commit acf0090

Please sign in to comment.