diff --git a/block/manager.go b/block/manager.go index f4301d90e6e..b336252aee8 100644 --- a/block/manager.go +++ b/block/manager.go @@ -564,8 +564,10 @@ func (m *Manager) publishBlock(ctx context.Context) error { return err } + blockHeight := uint64(block.SignedHeader.Header.Height()) + // Only update the stored height after successfully submitting to DA layer and committing to the DB - m.store.SetHeight(uint64(block.SignedHeader.Header.Height())) + m.store.SetHeight(blockHeight) // Commit the new state and block which writes to disk on the proxy app _, _, err = m.executor.Commit(ctx, newState, block, responses) @@ -574,13 +576,13 @@ func (m *Manager) publishBlock(ctx context.Context) error { } // SaveBlockResponses commits the DB tx - err = m.store.SaveBlockResponses(uint64(block.SignedHeader.Header.Height()), responses) + err = m.store.SaveBlockResponses(blockHeight, responses) if err != nil { return err } // SaveValidators commits the DB tx - err = m.store.SaveValidators(uint64(block.SignedHeader.Header.Height()), m.lastState.Validators) + err = m.store.SaveValidators(blockHeight, m.lastState.Validators) if err != nil { return err } diff --git a/node/full_client_test.go b/node/full_client_test.go index ba535bd4fb9..44bef157ddd 100644 --- a/node/full_client_test.go +++ b/node/full_client_test.go @@ -650,21 +650,16 @@ func TestBlockchainInfo(t *testing.T) { } } -func TestValidatorSetHandling(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - waitCh := make(chan interface{}) - - vKeys := make([]tmcrypto.PrivKey, 2) - apps := make([]*mocks.Application, 2) - nodes := make([]*FullNode, 2) +func createGenesisValidators(numNodes int, appCreator func(vKeyToRemove tmcrypto.PrivKey) *mocks.Application, require *require.Assertions) *FullClient { + vKeys := make([]tmcrypto.PrivKey, numNodes) + apps := make([]*mocks.Application, numNodes) + nodes := make([]*FullNode, numNodes) genesisValidators := make([]tmtypes.GenesisValidator, len(vKeys)) 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] = createApp(vKeys[0], waitCh, require) + apps[i] = appCreator(vKeys[0]) } dalc := &mockda.DataAvailabilityLayerClient{} @@ -712,6 +707,39 @@ func TestValidatorSetHandling(t *testing.T) { err := nodes[i].Start() require.NoError(err) } + return rpc +} + +// Tests moving from two validators to one validator and then back to two validators +func TestValidatorSetHandling(t *testing.T) { + assert := assert.New(t) + require := require.New(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) <-waitCh <-waitCh @@ -721,8 +749,8 @@ func TestValidatorSetHandling(t *testing.T) { vals, err := rpc.Validators(context.Background(), &h, nil, nil) assert.NoError(err) assert.NotNil(vals) - assert.EqualValues(len(genesisValidators), vals.Total) - assert.Len(vals.Validators, len(genesisValidators)) + assert.EqualValues(numNodes, vals.Total) + assert.Len(vals.Validators, numNodes) assert.EqualValues(vals.BlockHeight, h) } @@ -731,8 +759,8 @@ func TestValidatorSetHandling(t *testing.T) { vals, err := rpc.Validators(context.Background(), &h, nil, nil) assert.NoError(err) assert.NotNil(vals) - assert.EqualValues(len(genesisValidators)-1, vals.Total) - assert.Len(vals.Validators, len(genesisValidators)-1) + assert.EqualValues(numNodes-1, vals.Total) + assert.Len(vals.Validators, numNodes-1) assert.EqualValues(vals.BlockHeight, h) } @@ -743,8 +771,8 @@ func TestValidatorSetHandling(t *testing.T) { vals, err := rpc.Validators(context.Background(), &h, nil, nil) assert.NoError(err) assert.NotNil(vals) - assert.EqualValues(len(genesisValidators), vals.Total) - assert.Len(vals.Validators, len(genesisValidators)) + assert.EqualValues(numNodes, vals.Total) + assert.Len(vals.Validators, numNodes) assert.EqualValues(vals.BlockHeight, h) } @@ -752,31 +780,72 @@ func TestValidatorSetHandling(t *testing.T) { vals, err := rpc.Validators(context.Background(), nil, nil, nil) assert.NoError(err) assert.NotNil(vals) - assert.EqualValues(len(genesisValidators), vals.Total) - assert.Len(vals.Validators, len(genesisValidators)) + assert.EqualValues(numNodes, vals.Total) + assert.Len(vals.Validators, numNodes) assert.GreaterOrEqual(vals.BlockHeight, int64(9)) } -func createApp(keyToRemove tmcrypto.PrivKey, waitCh chan interface{}, require *require.Assertions) *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{}) +// Tests moving from a centralized validator to empty validator set +func TestValidatorSetHandlingBased(t *testing.T) { + assert := assert.New(t) + require := require.New(t) - pbValKey, err := encoding.PubKeyToProto(keyToRemove.PubKey()) - require.NoError(err) + waitCh := make(chan interface{}) - 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 + 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) + + <-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) + } + + // 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) + } + + // 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)) } // copy-pasted from store/store_test.go diff --git a/state/executor.go b/state/executor.go index 9f7ba5ab42f..3c24774bda3 100644 --- a/state/executor.go +++ b/state/executor.go @@ -23,6 +23,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") // BlockExecutor creates and applies blocks and maintains state. type BlockExecutor struct { @@ -210,14 +211,22 @@ func (e *BlockExecutor) updateState(state types.State, block *types.Block, abciR if len(validatorUpdates) > 0 { err := nValSet.UpdateWithChangeSet(validatorUpdates) if err != nil { - return state, nil + if err.Error() != ErrEmptyValSetGenerated.Error() { + return state, err + } + nValSet = &tmtypes.ValidatorSet{ + Validators: make([]*tmtypes.Validator, 0), + Proposer: nil, + } } // Change results from this height but only applies to the next next height. lastHeightValSetChanged = block.SignedHeader.Header.Height() + 1 + 1 } + if len(nValSet.Validators) > 0 { + nValSet.IncrementProposerPriority(1) + } // TODO(tzdybal): right now, it's for backward compatibility, may need to change this - nValSet.IncrementProposerPriority(1) } s := types.State{ diff --git a/store/store.go b/store/store.go index 34070944d30..18a1873ce2c 100644 --- a/store/store.go +++ b/store/store.go @@ -224,6 +224,12 @@ func (s *DefaultStore) LoadValidators(height uint64) (*tmtypes.ValidatorSet, err if err != nil { return nil, fmt.Errorf("failed to unmarshal to protobuf: %w", err) } + if len(pbValSet.Validators) == 0 { + return &tmtypes.ValidatorSet{ + Validators: make([]*tmtypes.Validator, 0), + Proposer: nil, + }, nil + } return tmtypes.ValidatorSetFromProto(&pbValSet) } diff --git a/types/validation.go b/types/validation.go index 89b66e401ae..865bc542c03 100644 --- a/types/validation.go +++ b/types/validation.go @@ -57,6 +57,11 @@ func (h *SignedHeader) ValidateBasic() error { return err } + // Handle Based Rollup case + if len(h.Validators.Validators) == 0 { + return nil + } + err = h.Validators.ValidateBasic() if err != nil { return err