update Evidence type - requires pubkey and valset to verify and convert to abci.Evidence

This commit is contained in:
Ethan Buchman
2018-06-05 22:04:26 -07:00
parent e5bca1df6f
commit 3d2c4fd309
9 changed files with 160 additions and 125 deletions
+50 -34
View File
@@ -73,7 +73,7 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b
return state, ErrInvalidBlock(err)
}
abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block, s.LastValidators)
abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block, s.LastValidators, blockExec.db)
if err != nil {
return state, ErrProxyAppConn(err)
}
@@ -158,7 +158,7 @@ func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) {
// Executes block's transactions on proxyAppConn.
// Returns a list of transaction results and updates to the validator set
func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
block *types.Block, valSet *types.ValidatorSet) (*ABCIResponses, error) {
block *types.Block, lastValSet *types.ValidatorSet, stateDB dbm.DB) (*ABCIResponses, error) {
var validTxs, invalidTxs = 0, 0
txIndex := 0
@@ -184,42 +184,14 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
}
proxyAppConn.SetResponseCallback(proxyCb)
// determine which validators did not sign last block.
// only applies after first block
if block.Height > 1 {
precommitLen := len(block.LastCommit.Precommits)
valSetLen := len(valSet.Validators)
if precommitLen != valSetLen {
// sanity check
panic(fmt.Sprintf("precommit length (%d) doesn't match valset length (%d) at height %d\n\n%v\n\n%v",
precommitLen, valSetLen, block.Height, block.LastCommit.Precommits, valSet.Validators))
}
}
signVals := make([]abci.SigningValidator, len(valSet.Validators))
for i, val := range valSet.Validators {
var vote *types.Vote
if i < len(block.LastCommit.Precommits) {
vote = block.LastCommit.Precommits[i]
}
val := abci.SigningValidator{
Validator: types.TM2PB.Validator(val),
SignedLastBlock: vote != nil,
}
signVals[i] = val
}
byzantineVals := make([]abci.Evidence, len(block.Evidence.Evidence))
for i, ev := range block.Evidence.Evidence {
byzantineVals[i] = types.TM2PB.Evidence(ev)
}
signVals, byzVals := getBeginBlockValidatorInfo(block, lastValSet, stateDB)
// Begin block
_, err := proxyAppConn.BeginBlockSync(abci.RequestBeginBlock{
Hash: block.Hash(),
Header: types.TM2PB.Header(block.Header),
Validators: signVals,
ByzantineValidators: byzantineVals,
ByzantineValidators: byzVals,
})
if err != nil {
logger.Error("Error in proxyAppConn.BeginBlock", "err", err)
@@ -251,6 +223,50 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
return abciResponses, nil
}
func getBeginBlockValidatorInfo(block *types.Block, lastValSet *types.ValidatorSet, stateDB dbm.DB) ([]abci.SigningValidator, []abci.Evidence) {
// Sanity check that commit length matches validator set size -
// only applies after first block
if block.Height > 1 {
precommitLen := len(block.LastCommit.Precommits)
valSetLen := len(lastValSet.Validators)
if precommitLen != valSetLen {
// sanity check
panic(fmt.Sprintf("precommit length (%d) doesn't match valset length (%d) at height %d\n\n%v\n\n%v",
precommitLen, valSetLen, block.Height, block.LastCommit.Precommits, lastValSet.Validators))
}
}
// determine which validators did not sign last block.
signVals := make([]abci.SigningValidator, len(lastValSet.Validators))
for i, val := range lastValSet.Validators {
var vote *types.Vote
if i < len(block.LastCommit.Precommits) {
vote = block.LastCommit.Precommits[i]
}
val := abci.SigningValidator{
Validator: types.TM2PB.Validator(val),
SignedLastBlock: vote != nil,
}
signVals[i] = val
}
byzVals := make([]abci.Evidence, len(block.Evidence.Evidence))
for i, ev := range block.Evidence.Evidence {
// We need the validator set. We already did this in validateBlock.
// TODO: Should we instead cache the valset in the evidence itself and add
// `SetValidatorSet()` and `ToABCI` methods ?
valset, err := LoadValidators(stateDB, ev.Height())
if err != nil {
panic(err) // shoudn't happen
}
byzVals[i] = types.TM2PB.Evidence(ev, valset, block.Time)
}
return signVals, byzVals
}
// 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.
@@ -371,8 +387,8 @@ func fireEvents(logger log.Logger, eventBus types.BlockEventPublisher, block *ty
// ExecCommitBlock executes and commits a block on the proxyApp without validating or mutating the state.
// It returns the application root hash (result of abci.Commit).
func ExecCommitBlock(appConnConsensus proxy.AppConnConsensus, block *types.Block,
logger log.Logger, valSet *types.ValidatorSet) ([]byte, error) {
_, err := execBlockOnProxyApp(logger, appConnConsensus, block, valSet)
logger log.Logger, lastValSet *types.ValidatorSet, stateDB dbm.DB) ([]byte, error) {
_, err := execBlockOnProxyApp(logger, appConnConsensus, block, lastValSet, stateDB)
if err != nil {
logger.Error("Error executing block on proxy app", "height", block.Height, "err", err)
return nil, err
+25 -13
View File
@@ -32,7 +32,7 @@ func TestApplyBlock(t *testing.T) {
require.Nil(t, err)
defer proxyApp.Stop()
state, stateDB := state(1), dbm.NewMemDB()
state, stateDB := state(1, 1)
blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(),
MockMempool{}, MockEvidencePool{})
@@ -55,7 +55,7 @@ func TestBeginBlockValidators(t *testing.T) {
require.Nil(t, err)
defer proxyApp.Stop()
state := state(2)
state, stateDB := state(2, 2)
prevHash := state.LastBlockID.Hash
prevParts := types.PartSetHeader{}
@@ -78,8 +78,9 @@ func TestBeginBlockValidators(t *testing.T) {
for _, tc := range testCases {
lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: tc.lastCommitPrecommits}
// block for height 2
block, _ := state.MakeBlock(2, makeTxs(2), lastCommit)
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), state.Validators)
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), state.Validators, stateDB)
require.Nil(t, err, tc.desc)
// -> app receives a list of validators with a bool indicating if they signed
@@ -106,30 +107,31 @@ func TestBeginBlockByzantineValidators(t *testing.T) {
require.Nil(t, err)
defer proxyApp.Stop()
state := state(2)
state, stateDB := state(2, 12)
prevHash := state.LastBlockID.Hash
prevParts := types.PartSetHeader{}
prevBlockID := types.BlockID{prevHash, prevParts}
height1, idx1, val1 := int64(8), 0, []byte("val1")
height2, idx2, val2 := int64(3), 1, []byte("val2")
height1, idx1, val1 := int64(8), 0, state.Validators.Validators[0].Address
height2, idx2, val2 := int64(3), 1, state.Validators.Validators[1].Address
ev1 := types.NewMockGoodEvidence(height1, idx1, val1)
ev2 := types.NewMockGoodEvidence(height2, idx2, val2)
now := time.Now()
valSet := state.Validators
testCases := []struct {
desc string
evidence []types.Evidence
expectedByzantineValidators []abci.Evidence
}{
{"none byzantine", []types.Evidence{}, []abci.Evidence{}},
{"one byzantine", []types.Evidence{ev1}, []abci.Evidence{types.TM2PB.Evidence(ev1)}},
{"one byzantine", []types.Evidence{ev1}, []abci.Evidence{types.TM2PB.Evidence(ev1, valSet, now)}},
{"multiple byzantine", []types.Evidence{ev1, ev2}, []abci.Evidence{
types.TM2PB.Evidence(ev1),
types.TM2PB.Evidence(ev2)}},
types.TM2PB.Evidence(ev1, valSet, now),
types.TM2PB.Evidence(ev2, valSet, now)}},
}
now := time.Now().UTC()
vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit}
vote1 := &types.Vote{ValidatorIndex: 1, Timestamp: now}
votes := []*types.Vote{vote0, vote1}
@@ -137,8 +139,9 @@ func TestBeginBlockByzantineValidators(t *testing.T) {
for _, tc := range testCases {
block, _ := state.MakeBlock(10, makeTxs(2), lastCommit)
block.Time = now
block.Evidence.Evidence = tc.evidence
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), state.Validators)
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), state.Validators, stateDB)
require.Nil(t, err, tc.desc)
// -> app must receive an index of the byzantine validator
@@ -156,7 +159,7 @@ func makeTxs(height int64) (txs []types.Tx) {
return txs
}
func state(nVals int) State {
func state(nVals, height int) (State, dbm.DB) {
vals := make([]types.GenesisValidator, nVals)
for i := 0; i < nVals; i++ {
secret := []byte(fmt.Sprintf("test%d", i))
@@ -170,7 +173,16 @@ func state(nVals int) State {
Validators: vals,
AppHash: nil,
})
return s
// save validators to db for 2 heights
stateDB := dbm.NewMemDB()
SaveState(stateDB, s)
for i := 1; i < height; i++ {
s.LastBlockHeight += 1
SaveState(stateDB, s)
}
return s, stateDB
}
func makeBlock(state State, height int64) *types.Block {
+3 -2
View File
@@ -173,11 +173,12 @@ func LoadValidators(db dbm.DB, height int64) (*types.ValidatorSet, error) {
}
if valInfo.ValidatorSet == nil {
valInfo = loadValidatorsInfo(db, valInfo.LastHeightChanged)
if valInfo == nil {
valInfo2 := loadValidatorsInfo(db, valInfo.LastHeightChanged)
if valInfo2 == nil {
cmn.PanicSanity(fmt.Sprintf(`Couldn't find validators at height %d as
last changed from height %d`, valInfo.LastHeightChanged, height))
}
valInfo = valInfo2
}
return valInfo.ValidatorSet, nil
+17 -14
View File
@@ -73,6 +73,9 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
}
}
// TODO: Each check requires loading an old validator set.
// We should cap the amount of evidence per block
// to prevent potential proposer DoS.
for _, ev := range block.Evidence.Evidence {
if err := VerifyEvidence(stateDB, state, ev); err != nil {
return types.NewEvidenceInvalidErr(ev, err)
@@ -82,11 +85,11 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
return nil
}
// XXX: What's cheaper (ie. what should be checked first):
// evidence internal validity (ie. sig checks) or validator existed (fetch historical val set from db)
// VerifyEvidence verifies the evidence fully by checking it is internally
// consistent and sufficiently recent.
// VerifyEvidence verifies the evidence fully by checking:
// - it is sufficiently recent (MaxAge)
// - it is from a key who was a validator at the given height
// - it is internally consistent
// - it was properly signed by the alleged equivocator
func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence) error {
height := state.LastBlockHeight
@@ -97,10 +100,6 @@ func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence) error
evidence.Height(), height-maxAge)
}
if err := evidence.Verify(state.ChainID); err != nil {
return err
}
valset, err := LoadValidators(stateDB, evidence.Height())
if err != nil {
// TODO: if err is just that we cant find it cuz we pruned, ignore.
@@ -108,14 +107,18 @@ func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence) error
return err
}
// The address must have been an active validator at the height
// The address must have been an active validator at the height.
// NOTE: we will ignore evidence from H if the key was not a validator
// at H, even if it is a validator at some nearby H'
ev := evidence
height, addr, idx := ev.Height(), ev.Address(), ev.Index()
valIdx, val := valset.GetByAddress(addr)
height, addr := ev.Height(), ev.Address()
_, val := valset.GetByAddress(addr)
if val == nil {
return fmt.Errorf("Address %X was not a validator at height %d", addr, height)
} else if idx != valIdx {
return fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx)
}
if err := evidence.Verify(s.ChainID, val.PubKey); err != nil {
return err
}
return nil
+1 -1
View File
@@ -9,7 +9,7 @@ import (
)
func TestValidateBlock(t *testing.T) {
state := state(1)
state, _ := state(1, 1)
blockExec := NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nil, nil, nil)