mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-03 10:32:05 +00:00
This pull request merges in the changes for implementing Proposer-based timestamps into `master`. The power was primarily being done in the `wb/proposer-based-timestamps` branch, with changes being merged into that branch during development. This pull request represents an amalgamation of the changes made into that development branch. All of the changes that were placed into that branch have been cleanly rebased on top of the latest `master`. The changes compile and the tests pass insofar as our tests in general pass. ### Note To Reviewers These changes have been extensively reviewed during development. There is not much new here. In the interest of making effective use of time, I would recommend against trying to perform a complete audit of the changes presented and instead examine for mistakes that may have occurred during the process of rebasing the changes. I gave the complete change set a first pass for any issues, but additional eyes would be very appreciated. In sum, this change set does the following: closes #6942 merges in #6849
629 lines
24 KiB
Go
629 lines
24 KiB
Go
package evidence_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
|
"github.com/tendermint/tendermint/internal/evidence"
|
|
"github.com/tendermint/tendermint/internal/evidence/mocks"
|
|
sm "github.com/tendermint/tendermint/internal/state"
|
|
smmocks "github.com/tendermint/tendermint/internal/state/mocks"
|
|
"github.com/tendermint/tendermint/internal/test/factory"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
const (
|
|
defaultVotingPower = 10
|
|
)
|
|
|
|
func TestVerifyLightClientAttack_Lunatic(t *testing.T) {
|
|
const (
|
|
height int64 = 10
|
|
commonHeight int64 = 4
|
|
totalVals = 10
|
|
byzVals = 4
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
attackTime := defaultEvidenceTime.Add(1 * time.Hour)
|
|
// create valid lunatic evidence
|
|
ev, trusted, common := makeLunaticEvidence(ctx,
|
|
t, height, commonHeight, totalVals, byzVals, totalVals-byzVals, defaultEvidenceTime, attackTime)
|
|
require.NoError(t, ev.ValidateBasic())
|
|
|
|
// good pass -> no error
|
|
err := evidence.VerifyLightClientAttack(ev, common.SignedHeader, trusted.SignedHeader, common.ValidatorSet,
|
|
defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour)
|
|
assert.NoError(t, err)
|
|
|
|
// trusted and conflicting hashes are the same -> an error should be returned
|
|
err = evidence.VerifyLightClientAttack(ev, common.SignedHeader, ev.ConflictingBlock.SignedHeader, common.ValidatorSet,
|
|
defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour)
|
|
assert.Error(t, err)
|
|
|
|
// evidence with different total validator power should fail
|
|
ev.TotalVotingPower = 1 * defaultVotingPower
|
|
err = evidence.VerifyLightClientAttack(ev, common.SignedHeader, trusted.SignedHeader, common.ValidatorSet,
|
|
defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour)
|
|
assert.NoError(t, err)
|
|
assert.Error(t, ev.ValidateABCI(common.ValidatorSet, trusted.SignedHeader, defaultEvidenceTime))
|
|
|
|
// evidence without enough malicious votes should fail
|
|
ev, trusted, common = makeLunaticEvidence(ctx,
|
|
t, height, commonHeight, totalVals, byzVals-1, totalVals-byzVals, defaultEvidenceTime, attackTime)
|
|
err = evidence.VerifyLightClientAttack(ev, common.SignedHeader, trusted.SignedHeader, common.ValidatorSet,
|
|
defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestVerify_LunaticAttackAgainstState(t *testing.T) {
|
|
const (
|
|
height int64 = 10
|
|
commonHeight int64 = 4
|
|
totalVals = 10
|
|
byzVals = 4
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
attackTime := defaultEvidenceTime.Add(1 * time.Hour)
|
|
// create valid lunatic evidence
|
|
ev, trusted, common := makeLunaticEvidence(ctx,
|
|
t, height, commonHeight, totalVals, byzVals, totalVals-byzVals, defaultEvidenceTime, attackTime)
|
|
|
|
// now we try to test verification against state
|
|
state := sm.State{
|
|
LastBlockTime: defaultEvidenceTime.Add(2 * time.Hour),
|
|
LastBlockHeight: height + 1,
|
|
ConsensusParams: *types.DefaultConsensusParams(),
|
|
}
|
|
stateStore := &smmocks.Store{}
|
|
stateStore.On("LoadValidators", commonHeight).Return(common.ValidatorSet, nil)
|
|
stateStore.On("Load").Return(state, nil)
|
|
blockStore := &mocks.BlockStore{}
|
|
blockStore.On("LoadBlockMeta", commonHeight).Return(&types.BlockMeta{Header: *common.Header})
|
|
blockStore.On("LoadBlockMeta", height).Return(&types.BlockMeta{Header: *trusted.Header})
|
|
blockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit)
|
|
blockStore.On("LoadBlockCommit", height).Return(trusted.Commit)
|
|
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore)
|
|
require.NoError(t, err)
|
|
|
|
evList := types.EvidenceList{ev}
|
|
// check that the evidence pool correctly verifies the evidence
|
|
assert.NoError(t, pool.CheckEvidence(evList))
|
|
|
|
// as it was not originally in the pending bucket, it should now have been added
|
|
pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
|
|
assert.Equal(t, 1, len(pendingEvs))
|
|
assert.Equal(t, ev, pendingEvs[0])
|
|
|
|
// if we submit evidence only against a single byzantine validator when we see there are more validators then this
|
|
// should return an error
|
|
ev.ByzantineValidators = ev.ByzantineValidators[:1]
|
|
t.Log(evList)
|
|
assert.Error(t, pool.CheckEvidence(evList))
|
|
// restore original byz vals
|
|
ev.ByzantineValidators = ev.GetByzantineValidators(common.ValidatorSet, trusted.SignedHeader)
|
|
|
|
// duplicate evidence should be rejected
|
|
evList = types.EvidenceList{ev, ev}
|
|
pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore)
|
|
require.NoError(t, err)
|
|
assert.Error(t, pool.CheckEvidence(evList))
|
|
|
|
// If evidence is submitted with an altered timestamp it should return an error
|
|
ev.Timestamp = defaultEvidenceTime.Add(1 * time.Minute)
|
|
pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore)
|
|
require.NoError(t, err)
|
|
assert.Error(t, pool.AddEvidence(ev))
|
|
ev.Timestamp = defaultEvidenceTime
|
|
|
|
// Evidence submitted with a different validator power should fail
|
|
ev.TotalVotingPower = 1
|
|
pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore)
|
|
require.NoError(t, err)
|
|
assert.Error(t, pool.AddEvidence(ev))
|
|
ev.TotalVotingPower = common.ValidatorSet.TotalVotingPower()
|
|
}
|
|
|
|
func TestVerify_ForwardLunaticAttack(t *testing.T) {
|
|
const (
|
|
nodeHeight int64 = 8
|
|
attackHeight int64 = 10
|
|
commonHeight int64 = 4
|
|
totalVals = 10
|
|
byzVals = 5
|
|
)
|
|
attackTime := defaultEvidenceTime.Add(1 * time.Hour)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
// create a forward lunatic attack
|
|
ev, trusted, common := makeLunaticEvidence(ctx,
|
|
t, attackHeight, commonHeight, totalVals, byzVals, totalVals-byzVals, defaultEvidenceTime, attackTime)
|
|
|
|
// now we try to test verification against state
|
|
state := sm.State{
|
|
LastBlockTime: defaultEvidenceTime.Add(2 * time.Hour),
|
|
LastBlockHeight: nodeHeight,
|
|
ConsensusParams: *types.DefaultConsensusParams(),
|
|
}
|
|
|
|
// modify trusted light block so that it is of a height less than the conflicting one
|
|
trusted.Header.Height = state.LastBlockHeight
|
|
trusted.Header.Time = state.LastBlockTime
|
|
|
|
stateStore := &smmocks.Store{}
|
|
stateStore.On("LoadValidators", commonHeight).Return(common.ValidatorSet, nil)
|
|
stateStore.On("Load").Return(state, nil)
|
|
blockStore := &mocks.BlockStore{}
|
|
blockStore.On("LoadBlockMeta", commonHeight).Return(&types.BlockMeta{Header: *common.Header})
|
|
blockStore.On("LoadBlockMeta", nodeHeight).Return(&types.BlockMeta{Header: *trusted.Header})
|
|
blockStore.On("LoadBlockMeta", attackHeight).Return(nil)
|
|
blockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit)
|
|
blockStore.On("LoadBlockCommit", nodeHeight).Return(trusted.Commit)
|
|
blockStore.On("Height").Return(nodeHeight)
|
|
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore)
|
|
require.NoError(t, err)
|
|
|
|
// check that the evidence pool correctly verifies the evidence
|
|
assert.NoError(t, pool.CheckEvidence(types.EvidenceList{ev}))
|
|
|
|
// now we use a time which isn't able to contradict the FLA - thus we can't verify the evidence
|
|
oldBlockStore := &mocks.BlockStore{}
|
|
oldHeader := trusted.Header
|
|
oldHeader.Time = defaultEvidenceTime
|
|
oldBlockStore.On("LoadBlockMeta", commonHeight).Return(&types.BlockMeta{Header: *common.Header})
|
|
oldBlockStore.On("LoadBlockMeta", nodeHeight).Return(&types.BlockMeta{Header: *oldHeader})
|
|
oldBlockStore.On("LoadBlockMeta", attackHeight).Return(nil)
|
|
oldBlockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit)
|
|
oldBlockStore.On("LoadBlockCommit", nodeHeight).Return(trusted.Commit)
|
|
oldBlockStore.On("Height").Return(nodeHeight)
|
|
require.Equal(t, defaultEvidenceTime, oldBlockStore.LoadBlockMeta(nodeHeight).Header.Time)
|
|
|
|
pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, oldBlockStore)
|
|
require.NoError(t, err)
|
|
assert.Error(t, pool.CheckEvidence(types.EvidenceList{ev}))
|
|
}
|
|
|
|
func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
conflictingVals, conflictingPrivVals := factory.ValidatorSet(ctx, t, 5, 10)
|
|
|
|
conflictingHeader := factory.MakeHeader(t, &types.Header{
|
|
ChainID: evidenceChainID,
|
|
Height: 10,
|
|
Time: defaultEvidenceTime,
|
|
ValidatorsHash: conflictingVals.Hash(),
|
|
})
|
|
|
|
trustedHeader := factory.MakeHeader(t, &types.Header{
|
|
ChainID: evidenceChainID,
|
|
Height: 10,
|
|
Time: defaultEvidenceTime,
|
|
ValidatorsHash: conflictingHeader.ValidatorsHash,
|
|
NextValidatorsHash: conflictingHeader.NextValidatorsHash,
|
|
ConsensusHash: conflictingHeader.ConsensusHash,
|
|
AppHash: conflictingHeader.AppHash,
|
|
LastResultsHash: conflictingHeader.LastResultsHash,
|
|
})
|
|
|
|
// we are simulating a duplicate vote attack where all the validators in the conflictingVals set
|
|
// except the last validator vote twice
|
|
blockID := factory.MakeBlockIDWithHash(conflictingHeader.Hash())
|
|
voteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals)
|
|
commit, err := factory.MakeCommit(ctx, blockID, 10, 1, voteSet, conflictingPrivVals[:4], defaultEvidenceTime)
|
|
require.NoError(t, err)
|
|
|
|
ev := &types.LightClientAttackEvidence{
|
|
ConflictingBlock: &types.LightBlock{
|
|
SignedHeader: &types.SignedHeader{
|
|
Header: conflictingHeader,
|
|
Commit: commit,
|
|
},
|
|
ValidatorSet: conflictingVals,
|
|
},
|
|
CommonHeight: 10,
|
|
ByzantineValidators: conflictingVals.Validators[:4],
|
|
TotalVotingPower: 50,
|
|
Timestamp: defaultEvidenceTime,
|
|
}
|
|
|
|
trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash"))
|
|
trustedVoteSet := types.NewVoteSet(evidenceChainID, 10, 1, tmproto.SignedMsgType(2), conflictingVals)
|
|
trustedCommit, err := factory.MakeCommit(ctx, trustedBlockID, 10, 1,
|
|
trustedVoteSet, conflictingPrivVals, defaultEvidenceTime)
|
|
require.NoError(t, err)
|
|
|
|
trustedSignedHeader := &types.SignedHeader{
|
|
Header: trustedHeader,
|
|
Commit: trustedCommit,
|
|
}
|
|
|
|
// good pass -> no error
|
|
require.NoError(t, evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, conflictingVals,
|
|
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour))
|
|
|
|
// trusted and conflicting hashes are the same -> an error should be returned
|
|
assert.Error(t, evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, conflictingVals,
|
|
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour))
|
|
|
|
// conflicting header has different next validators hash which should have been correctly derived from
|
|
// the previous round
|
|
ev.ConflictingBlock.Header.NextValidatorsHash = crypto.CRandBytes(tmhash.Size)
|
|
assert.Error(t, evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, nil,
|
|
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour))
|
|
|
|
// revert next validators hash
|
|
ev.ConflictingBlock.Header.NextValidatorsHash = trustedHeader.NextValidatorsHash
|
|
|
|
state := sm.State{
|
|
LastBlockTime: defaultEvidenceTime.Add(1 * time.Minute),
|
|
LastBlockHeight: 11,
|
|
ConsensusParams: *types.DefaultConsensusParams(),
|
|
}
|
|
stateStore := &smmocks.Store{}
|
|
stateStore.On("LoadValidators", int64(10)).Return(conflictingVals, nil)
|
|
stateStore.On("Load").Return(state, nil)
|
|
blockStore := &mocks.BlockStore{}
|
|
blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader})
|
|
blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit)
|
|
|
|
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore)
|
|
require.NoError(t, err)
|
|
|
|
evList := types.EvidenceList{ev}
|
|
err = pool.CheckEvidence(evList)
|
|
assert.NoError(t, err)
|
|
|
|
pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
|
|
assert.Equal(t, 1, len(pendingEvs))
|
|
}
|
|
|
|
func TestVerifyLightClientAttack_Amnesia(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
var height int64 = 10
|
|
conflictingVals, conflictingPrivVals := factory.ValidatorSet(ctx, t, 5, 10)
|
|
|
|
conflictingHeader := factory.MakeHeader(t, &types.Header{
|
|
ChainID: evidenceChainID,
|
|
Height: height,
|
|
Time: defaultEvidenceTime,
|
|
ValidatorsHash: conflictingVals.Hash(),
|
|
})
|
|
|
|
trustedHeader := factory.MakeHeader(t, &types.Header{
|
|
ChainID: evidenceChainID,
|
|
Height: height,
|
|
Time: defaultEvidenceTime,
|
|
ValidatorsHash: conflictingHeader.ValidatorsHash,
|
|
NextValidatorsHash: conflictingHeader.NextValidatorsHash,
|
|
ConsensusHash: conflictingHeader.ConsensusHash,
|
|
AppHash: conflictingHeader.AppHash,
|
|
LastResultsHash: conflictingHeader.LastResultsHash,
|
|
})
|
|
|
|
// we are simulating an amnesia attack where all the validators in the conflictingVals set
|
|
// except the last validator vote twice. However this time the commits are of different rounds.
|
|
blockID := makeBlockID(conflictingHeader.Hash(), 1000, []byte("partshash"))
|
|
voteSet := types.NewVoteSet(evidenceChainID, height, 0, tmproto.SignedMsgType(2), conflictingVals)
|
|
commit, err := factory.MakeCommit(ctx, blockID, height, 0, voteSet, conflictingPrivVals, defaultEvidenceTime)
|
|
require.NoError(t, err)
|
|
|
|
ev := &types.LightClientAttackEvidence{
|
|
ConflictingBlock: &types.LightBlock{
|
|
SignedHeader: &types.SignedHeader{
|
|
Header: conflictingHeader,
|
|
Commit: commit,
|
|
},
|
|
ValidatorSet: conflictingVals,
|
|
},
|
|
CommonHeight: height,
|
|
ByzantineValidators: nil, // with amnesia evidence no validators are submitted as abci evidence
|
|
TotalVotingPower: 50,
|
|
Timestamp: defaultEvidenceTime,
|
|
}
|
|
|
|
trustedBlockID := makeBlockID(trustedHeader.Hash(), 1000, []byte("partshash"))
|
|
trustedVoteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), conflictingVals)
|
|
trustedCommit, err := factory.MakeCommit(ctx, trustedBlockID, height, 1,
|
|
trustedVoteSet, conflictingPrivVals, defaultEvidenceTime)
|
|
require.NoError(t, err)
|
|
|
|
trustedSignedHeader := &types.SignedHeader{
|
|
Header: trustedHeader,
|
|
Commit: trustedCommit,
|
|
}
|
|
|
|
// good pass -> no error
|
|
require.NoError(t, evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, conflictingVals,
|
|
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour))
|
|
|
|
// trusted and conflicting hashes are the same -> an error should be returned
|
|
assert.Error(t, evidence.VerifyLightClientAttack(ev, trustedSignedHeader, ev.ConflictingBlock.SignedHeader, conflictingVals,
|
|
defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour))
|
|
|
|
state := sm.State{
|
|
LastBlockTime: defaultEvidenceTime.Add(1 * time.Minute),
|
|
LastBlockHeight: 11,
|
|
ConsensusParams: *types.DefaultConsensusParams(),
|
|
}
|
|
stateStore := &smmocks.Store{}
|
|
stateStore.On("LoadValidators", int64(10)).Return(conflictingVals, nil)
|
|
stateStore.On("Load").Return(state, nil)
|
|
blockStore := &mocks.BlockStore{}
|
|
blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader})
|
|
blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit)
|
|
|
|
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore)
|
|
require.NoError(t, err)
|
|
|
|
evList := types.EvidenceList{ev}
|
|
err = pool.CheckEvidence(evList)
|
|
assert.NoError(t, err)
|
|
|
|
pendingEvs, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
|
|
assert.Equal(t, 1, len(pendingEvs))
|
|
}
|
|
|
|
type voteData struct {
|
|
vote1 *types.Vote
|
|
vote2 *types.Vote
|
|
valid bool
|
|
}
|
|
|
|
func TestVerifyDuplicateVoteEvidence(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
val := types.NewMockPV()
|
|
val2 := types.NewMockPV()
|
|
valSet := types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(ctx, 1)})
|
|
|
|
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
|
|
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
|
|
blockID3 := makeBlockID([]byte("blockhash"), 10000, []byte("partshash"))
|
|
blockID4 := makeBlockID([]byte("blockhash"), 10000, []byte("partshash2"))
|
|
|
|
const chainID = "mychain"
|
|
|
|
vote1 := makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID, defaultEvidenceTime)
|
|
v1 := vote1.ToProto()
|
|
err := val.SignVote(ctx, chainID, v1)
|
|
require.NoError(t, err)
|
|
badVote := makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID, defaultEvidenceTime)
|
|
bv := badVote.ToProto()
|
|
err = val2.SignVote(ctx, chainID, bv)
|
|
require.NoError(t, err)
|
|
|
|
vote1.Signature = v1.Signature
|
|
badVote.Signature = bv.Signature
|
|
|
|
cases := []voteData{
|
|
{vote1, makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID2, defaultEvidenceTime), true}, // different block ids
|
|
{vote1, makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID3, defaultEvidenceTime), true},
|
|
{vote1, makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID4, defaultEvidenceTime), true},
|
|
{vote1, makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID, defaultEvidenceTime), false}, // wrong block id
|
|
{vote1, makeVote(ctx, t, val, "mychain2", 0, 10, 2, 1, blockID2, defaultEvidenceTime), false}, // wrong chain id
|
|
{vote1, makeVote(ctx, t, val, chainID, 0, 11, 2, 1, blockID2, defaultEvidenceTime), false}, // wrong height
|
|
{vote1, makeVote(ctx, t, val, chainID, 0, 10, 3, 1, blockID2, defaultEvidenceTime), false}, // wrong round
|
|
{vote1, makeVote(ctx, t, val, chainID, 0, 10, 2, 2, blockID2, defaultEvidenceTime), false}, // wrong step
|
|
{vote1, makeVote(ctx, t, val2, chainID, 0, 10, 2, 1, blockID2, defaultEvidenceTime), false}, // wrong validator
|
|
// a different vote time doesn't matter
|
|
{vote1, makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID2, time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), true},
|
|
{vote1, badVote, false}, // signed by wrong key
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
for _, c := range cases {
|
|
ev := &types.DuplicateVoteEvidence{
|
|
VoteA: c.vote1,
|
|
VoteB: c.vote2,
|
|
ValidatorPower: 1,
|
|
TotalVotingPower: 1,
|
|
Timestamp: defaultEvidenceTime,
|
|
}
|
|
if c.valid {
|
|
assert.Nil(t, evidence.VerifyDuplicateVote(ev, chainID, valSet), "evidence should be valid")
|
|
} else {
|
|
assert.NotNil(t, evidence.VerifyDuplicateVote(ev, chainID, valSet), "evidence should be invalid")
|
|
}
|
|
}
|
|
|
|
// create good evidence and correct validator power
|
|
goodEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(ctx, 10, defaultEvidenceTime, val, chainID)
|
|
require.NoError(t, err)
|
|
goodEv.ValidatorPower = 1
|
|
goodEv.TotalVotingPower = 1
|
|
badEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(ctx, 10, defaultEvidenceTime, val, chainID)
|
|
require.NoError(t, err)
|
|
badTimeEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(ctx, 10, defaultEvidenceTime.Add(1*time.Minute), val, chainID)
|
|
require.NoError(t, err)
|
|
badTimeEv.ValidatorPower = 1
|
|
badTimeEv.TotalVotingPower = 1
|
|
state := sm.State{
|
|
ChainID: chainID,
|
|
LastBlockTime: defaultEvidenceTime.Add(1 * time.Minute),
|
|
LastBlockHeight: 11,
|
|
ConsensusParams: *types.DefaultConsensusParams(),
|
|
}
|
|
stateStore := &smmocks.Store{}
|
|
stateStore.On("LoadValidators", int64(10)).Return(valSet, nil)
|
|
stateStore.On("Load").Return(state, nil)
|
|
blockStore := &mocks.BlockStore{}
|
|
blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}})
|
|
|
|
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore)
|
|
require.NoError(t, err)
|
|
|
|
evList := types.EvidenceList{goodEv}
|
|
err = pool.CheckEvidence(evList)
|
|
assert.NoError(t, err)
|
|
|
|
// evidence with a different validator power should fail
|
|
evList = types.EvidenceList{badEv}
|
|
err = pool.CheckEvidence(evList)
|
|
assert.Error(t, err)
|
|
|
|
// evidence with a different timestamp should fail
|
|
evList = types.EvidenceList{badTimeEv}
|
|
err = pool.CheckEvidence(evList)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func makeLunaticEvidence(
|
|
ctx context.Context,
|
|
t *testing.T,
|
|
height, commonHeight int64,
|
|
totalVals, byzVals, phantomVals int,
|
|
commonTime, attackTime time.Time,
|
|
) (ev *types.LightClientAttackEvidence, trusted *types.LightBlock, common *types.LightBlock) {
|
|
t.Helper()
|
|
|
|
commonValSet, commonPrivVals := factory.ValidatorSet(ctx, t, totalVals, defaultVotingPower)
|
|
|
|
require.Greater(t, totalVals, byzVals)
|
|
|
|
// extract out the subset of byzantine validators in the common validator set
|
|
byzValSet, byzPrivVals := commonValSet.Validators[:byzVals], commonPrivVals[:byzVals]
|
|
|
|
phantomValSet, phantomPrivVals := factory.ValidatorSet(ctx, t, phantomVals, defaultVotingPower)
|
|
|
|
conflictingVals := phantomValSet.Copy()
|
|
require.NoError(t, conflictingVals.UpdateWithChangeSet(byzValSet))
|
|
conflictingPrivVals := append(phantomPrivVals, byzPrivVals...)
|
|
|
|
conflictingPrivVals = orderPrivValsByValSet(ctx, t, conflictingVals, conflictingPrivVals)
|
|
|
|
commonHeader := factory.MakeHeader(t, &types.Header{
|
|
ChainID: evidenceChainID,
|
|
Height: commonHeight,
|
|
Time: commonTime,
|
|
})
|
|
|
|
trustedHeader := factory.MakeHeader(t, &types.Header{
|
|
ChainID: evidenceChainID,
|
|
Height: height,
|
|
Time: defaultEvidenceTime,
|
|
})
|
|
|
|
conflictingHeader := factory.MakeHeader(t, &types.Header{
|
|
ChainID: evidenceChainID,
|
|
Height: height,
|
|
Time: attackTime,
|
|
ValidatorsHash: conflictingVals.Hash(),
|
|
})
|
|
|
|
blockID := factory.MakeBlockIDWithHash(conflictingHeader.Hash())
|
|
voteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), conflictingVals)
|
|
commit, err := factory.MakeCommit(ctx, blockID, height, 1, voteSet, conflictingPrivVals, defaultEvidenceTime)
|
|
require.NoError(t, err)
|
|
|
|
ev = &types.LightClientAttackEvidence{
|
|
ConflictingBlock: &types.LightBlock{
|
|
SignedHeader: &types.SignedHeader{
|
|
Header: conflictingHeader,
|
|
Commit: commit,
|
|
},
|
|
ValidatorSet: conflictingVals,
|
|
},
|
|
CommonHeight: commonHeight,
|
|
TotalVotingPower: commonValSet.TotalVotingPower(),
|
|
ByzantineValidators: byzValSet,
|
|
Timestamp: commonTime,
|
|
}
|
|
|
|
common = &types.LightBlock{
|
|
SignedHeader: &types.SignedHeader{
|
|
Header: commonHeader,
|
|
// we can leave this empty because we shouldn't be checking this
|
|
Commit: &types.Commit{},
|
|
},
|
|
ValidatorSet: commonValSet,
|
|
}
|
|
trustedBlockID := factory.MakeBlockIDWithHash(trustedHeader.Hash())
|
|
trustedVals, privVals := factory.ValidatorSet(ctx, t, totalVals, defaultVotingPower)
|
|
trustedVoteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), trustedVals)
|
|
trustedCommit, err := factory.MakeCommit(ctx, trustedBlockID, height, 1, trustedVoteSet, privVals, defaultEvidenceTime)
|
|
require.NoError(t, err)
|
|
|
|
trusted = &types.LightBlock{
|
|
SignedHeader: &types.SignedHeader{
|
|
Header: trustedHeader,
|
|
Commit: trustedCommit,
|
|
},
|
|
ValidatorSet: trustedVals,
|
|
}
|
|
return ev, trusted, common
|
|
}
|
|
|
|
func makeVote(
|
|
ctx context.Context,
|
|
t *testing.T, val types.PrivValidator, chainID string, valIndex int32, height int64,
|
|
round int32, step int, blockID types.BlockID, time time.Time,
|
|
) *types.Vote {
|
|
pubKey, err := val.GetPubKey(ctx)
|
|
require.NoError(t, err)
|
|
v := &types.Vote{
|
|
ValidatorAddress: pubKey.Address(),
|
|
ValidatorIndex: valIndex,
|
|
Height: height,
|
|
Round: round,
|
|
Type: tmproto.SignedMsgType(step),
|
|
BlockID: blockID,
|
|
Timestamp: time,
|
|
}
|
|
|
|
vpb := v.ToProto()
|
|
err = val.SignVote(ctx, chainID, vpb)
|
|
require.NoError(t, err)
|
|
v.Signature = vpb.Signature
|
|
return v
|
|
}
|
|
|
|
func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID {
|
|
var (
|
|
h = make([]byte, tmhash.Size)
|
|
psH = make([]byte, tmhash.Size)
|
|
)
|
|
copy(h, hash)
|
|
copy(psH, partSetHash)
|
|
return types.BlockID{
|
|
Hash: h,
|
|
PartSetHeader: types.PartSetHeader{
|
|
Total: partSetSize,
|
|
Hash: psH,
|
|
},
|
|
}
|
|
}
|
|
|
|
func orderPrivValsByValSet(ctx context.Context, t *testing.T, vals *types.ValidatorSet, privVals []types.PrivValidator) []types.PrivValidator {
|
|
output := make([]types.PrivValidator, len(privVals))
|
|
for idx, v := range vals.Validators {
|
|
for _, p := range privVals {
|
|
pubKey, err := p.GetPubKey(ctx)
|
|
require.NoError(t, err)
|
|
if bytes.Equal(v.Address, pubKey.Address()) {
|
|
output[idx] = p
|
|
break
|
|
}
|
|
}
|
|
require.NotEmpty(t, output[idx])
|
|
}
|
|
return output
|
|
}
|