mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-05 13:05:09 +00:00
evidence: handling evidence from light client(s) (#4532)
Closes: #4530 This PR contains logic for both submitting an evidence by the light client (lite2 package) and receiving it on the Tendermint side (/broadcast_evidence RPC and/or EvidenceReactor#Receive). Upon receiving the ConflictingHeadersEvidence (introduced by this PR), the Tendermint validates it, then breaks it down into smaller pieces (DuplicateVoteEvidence, LunaticValidatorEvidence, PhantomValidatorEvidence, PotentialAmnesiaEvidence). Afterwards, each piece of evidence is verified against the state of the full node and added to the pool, from which it's reaped upon block creation. * rpc/client: do not pass height param if height ptr is nil * rpc/core: validate incoming evidence! * only accept ConflictingHeadersEvidence if one of the headers is committed from this full node's perspective This simplifies the code. Plus, if there are multiple forks, we'll likely to receive multiple ConflictingHeadersEvidence anyway. * swap CommitSig with Vote in LunaticValidatorEvidence Vote is needed to validate signature * no need to embed client http is a provider and should not be used as a client
This commit is contained in:
228
evidence/pool.go
228
evidence/pool.go
@@ -10,11 +10,11 @@ import (
|
||||
clist "github.com/tendermint/tendermint/libs/clist"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/store"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Pool maintains a pool of valid evidence
|
||||
// in an Store.
|
||||
// Pool maintains a pool of valid evidence in an Store.
|
||||
type Pool struct {
|
||||
logger log.Logger
|
||||
|
||||
@@ -22,23 +22,43 @@ type Pool struct {
|
||||
evidenceList *clist.CList // concurrent linked-list of evidence
|
||||
|
||||
// needed to load validators to verify evidence
|
||||
stateDB dbm.DB
|
||||
stateDB dbm.DB
|
||||
blockStore *store.BlockStore
|
||||
|
||||
// a map of active validators and respective last heights validator is active
|
||||
// if it was in validator set after EvidenceParams.MaxAgeNumBlocks or
|
||||
// currently is (ie. [MaxAgeNumBlocks, CurrentHeight])
|
||||
// In simple words, it means it's still bonded -> therefore slashable.
|
||||
valToLastHeight valToLastHeightMap
|
||||
|
||||
// latest state
|
||||
mtx sync.Mutex
|
||||
state sm.State
|
||||
}
|
||||
|
||||
func NewPool(stateDB, evidenceDB dbm.DB) *Pool {
|
||||
store := NewStore(evidenceDB)
|
||||
evpool := &Pool{
|
||||
stateDB: stateDB,
|
||||
state: sm.LoadState(stateDB),
|
||||
logger: log.NewNopLogger(),
|
||||
store: store,
|
||||
evidenceList: clist.New(),
|
||||
// Validator.Address -> Last height it was in validator set
|
||||
type valToLastHeightMap map[string]int64
|
||||
|
||||
func NewPool(stateDB, evidenceDB dbm.DB, blockStore *store.BlockStore) (*Pool, error) {
|
||||
var (
|
||||
store = NewStore(evidenceDB)
|
||||
state = sm.LoadState(stateDB)
|
||||
)
|
||||
|
||||
valToLastHeight, err := buildValToLastHeightMap(state, stateDB, blockStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return evpool
|
||||
|
||||
return &Pool{
|
||||
stateDB: stateDB,
|
||||
blockStore: blockStore,
|
||||
state: state,
|
||||
logger: log.NewNopLogger(),
|
||||
store: store,
|
||||
evidenceList: clist.New(),
|
||||
valToLastHeight: valToLastHeight,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (evpool *Pool) EvidenceFront() *clist.CElement {
|
||||
@@ -74,7 +94,6 @@ func (evpool *Pool) State() sm.State {
|
||||
|
||||
// Update loads the latest
|
||||
func (evpool *Pool) Update(block *types.Block, state sm.State) {
|
||||
|
||||
// sanity check
|
||||
if state.LastBlockHeight != block.Height {
|
||||
panic(
|
||||
@@ -92,43 +111,81 @@ func (evpool *Pool) Update(block *types.Block, state sm.State) {
|
||||
|
||||
// remove evidence from pending and mark committed
|
||||
evpool.MarkEvidenceAsCommitted(block.Height, block.Time, block.Evidence.Evidence)
|
||||
|
||||
evpool.updateValToLastHeight(block.Height, state)
|
||||
}
|
||||
|
||||
// AddEvidence checks the evidence is valid and adds it to the pool.
|
||||
// AddEvidence checks the evidence is valid and adds it to the pool. If
|
||||
// evidence is composite (ConflictingHeadersEvidence), it will be broken up
|
||||
// into smaller pieces.
|
||||
func (evpool *Pool) AddEvidence(evidence types.Evidence) error {
|
||||
var (
|
||||
state = evpool.State()
|
||||
evList = []types.Evidence{evidence}
|
||||
)
|
||||
|
||||
// check if evidence is already stored
|
||||
if evpool.store.Has(evidence) {
|
||||
return ErrEvidenceAlreadyStored{}
|
||||
}
|
||||
|
||||
if err := sm.VerifyEvidence(evpool.stateDB, evpool.State(), evidence); err != nil {
|
||||
return ErrInvalidEvidence{err}
|
||||
}
|
||||
|
||||
// fetch the validator and return its voting power as its priority
|
||||
// TODO: something better ?
|
||||
valset, err := sm.LoadValidators(evpool.stateDB, evidence.Height())
|
||||
valSet, err := sm.LoadValidators(evpool.stateDB, evidence.Height())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, val := valset.GetByAddress(evidence.Address())
|
||||
priority := val.VotingPower
|
||||
|
||||
_, err = evpool.store.AddNewEvidence(evidence, priority)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("can't load validators at height #%d: %w", evidence.Height(), err)
|
||||
}
|
||||
|
||||
evpool.logger.Info("Verified new evidence of byzantine behaviour", "evidence", evidence)
|
||||
// Break composite evidence into smaller pieces.
|
||||
if ce, ok := evidence.(types.CompositeEvidence); ok {
|
||||
evpool.logger.Info("Breaking up composite evidence", "ev", evidence)
|
||||
|
||||
// add evidence to clist
|
||||
evpool.evidenceList.PushBack(evidence)
|
||||
blockMeta := evpool.blockStore.LoadBlockMeta(evidence.Height())
|
||||
if blockMeta == nil {
|
||||
return fmt.Errorf("don't have block meta at height #%d", evidence.Height())
|
||||
}
|
||||
|
||||
if err := ce.VerifyComposite(&blockMeta.Header, valSet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
evList = ce.Split(&blockMeta.Header, valSet, evpool.valToLastHeight)
|
||||
}
|
||||
|
||||
for _, ev := range evList {
|
||||
if evpool.store.Has(evidence) {
|
||||
return ErrEvidenceAlreadyStored{}
|
||||
}
|
||||
|
||||
// For lunatic validator evidence, a header needs to be fetched.
|
||||
var header *types.Header
|
||||
if _, ok := ev.(*types.LunaticValidatorEvidence); ok {
|
||||
blockMeta := evpool.blockStore.LoadBlockMeta(ev.Height())
|
||||
if blockMeta == nil {
|
||||
return fmt.Errorf("don't have block meta at height #%d", ev.Height())
|
||||
}
|
||||
header = &blockMeta.Header
|
||||
}
|
||||
|
||||
// 1) Verify against state.
|
||||
if err := sm.VerifyEvidence(evpool.stateDB, state, ev, header); err != nil {
|
||||
return fmt.Errorf("failed to verify %v: %w", ev, err)
|
||||
}
|
||||
|
||||
// 2) Compute priority.
|
||||
_, val := valSet.GetByAddress(ev.Address())
|
||||
priority := val.VotingPower
|
||||
|
||||
// 3) Save to store.
|
||||
_, err := evpool.store.AddNewEvidence(ev, priority)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add new evidence %v: %w", ev, err)
|
||||
}
|
||||
|
||||
// 4) Add evidence to clist.
|
||||
evpool.evidenceList.PushBack(ev)
|
||||
|
||||
evpool.logger.Info("Verified new evidence of byzantine behaviour", "evidence", ev)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkEvidenceAsCommitted marks all the evidence as committed and removes it from the queue.
|
||||
// MarkEvidenceAsCommitted marks all the evidence as committed and removes it
|
||||
// from the queue.
|
||||
func (evpool *Pool) MarkEvidenceAsCommitted(height int64, lastBlockTime time.Time, evidence []types.Evidence) {
|
||||
// make a map of committed evidence to remove from the clist
|
||||
blockEvidenceMap := make(map[string]struct{})
|
||||
@@ -142,12 +199,25 @@ func (evpool *Pool) MarkEvidenceAsCommitted(height int64, lastBlockTime time.Tim
|
||||
evpool.removeEvidence(height, lastBlockTime, evidenceParams, blockEvidenceMap)
|
||||
}
|
||||
|
||||
// IsCommitted returns true if we have already seen this exact evidence and it is already marked as committed.
|
||||
// IsCommitted returns true if we have already seen this exact evidence and it
|
||||
// is already marked as committed.
|
||||
func (evpool *Pool) IsCommitted(evidence types.Evidence) bool {
|
||||
ei := evpool.store.getInfo(evidence)
|
||||
return ei.Evidence != nil && ei.Committed
|
||||
}
|
||||
|
||||
// ValidatorLastHeight returns the last height of the validator w/ the
|
||||
// given address. 0 - if address never was a validator or was such a
|
||||
// long time ago (> ConsensusParams.Evidence.MaxAgeDuration && >
|
||||
// ConsensusParams.Evidence.MaxAgeNumBlocks).
|
||||
func (evpool *Pool) ValidatorLastHeight(address []byte) int64 {
|
||||
h, ok := evpool.valToLastHeight[string(address)]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (evpool *Pool) removeEvidence(
|
||||
height int64,
|
||||
lastBlockTime time.Time,
|
||||
@@ -174,3 +244,83 @@ func (evpool *Pool) removeEvidence(
|
||||
func evMapKey(ev types.Evidence) string {
|
||||
return string(ev.Hash())
|
||||
}
|
||||
|
||||
func (evpool *Pool) updateValToLastHeight(blockHeight int64, state sm.State) {
|
||||
// Update current validators & add new ones.
|
||||
for _, val := range state.Validators.Validators {
|
||||
evpool.valToLastHeight[string(val.Address)] = blockHeight
|
||||
}
|
||||
|
||||
// Remove validators outside of MaxAgeNumBlocks & MaxAgeDuration.
|
||||
removeHeight := blockHeight - evpool.State().ConsensusParams.Evidence.MaxAgeNumBlocks
|
||||
if removeHeight >= 1 {
|
||||
valSet, err := sm.LoadValidators(evpool.stateDB, removeHeight)
|
||||
if err != nil {
|
||||
for _, val := range valSet.Validators {
|
||||
h, ok := evpool.valToLastHeight[string(val.Address)]
|
||||
if ok && h == removeHeight {
|
||||
delete(evpool.valToLastHeight, string(val.Address))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildValToLastHeightMap(state sm.State, stateDB dbm.DB, blockStore *store.BlockStore) (valToLastHeightMap, error) {
|
||||
var (
|
||||
valToLastHeight = make(map[string]int64)
|
||||
params = state.ConsensusParams.Evidence
|
||||
|
||||
numBlocks = int64(0)
|
||||
minAgeTime = time.Now().Add(-params.MaxAgeDuration)
|
||||
height = state.LastBlockHeight
|
||||
)
|
||||
|
||||
if height == 0 {
|
||||
return valToLastHeight, nil
|
||||
}
|
||||
|
||||
meta := blockStore.LoadBlockMeta(height)
|
||||
if meta == nil {
|
||||
return nil, fmt.Errorf("block meta for height %d not found", height)
|
||||
}
|
||||
blockTime := meta.Header.Time
|
||||
|
||||
// From state.LastBlockHeight, build a map of "active" validators until
|
||||
// MaxAgeNumBlocks is passed and block time is less than now() -
|
||||
// MaxAgeDuration.
|
||||
for height >= 1 && (numBlocks <= params.MaxAgeNumBlocks || !blockTime.Before(minAgeTime)) {
|
||||
valSet, err := sm.LoadValidators(stateDB, height)
|
||||
if err != nil {
|
||||
// last stored height -> return
|
||||
if _, ok := err.(sm.ErrNoValSetForHeight); ok {
|
||||
return valToLastHeight, nil
|
||||
}
|
||||
return nil, fmt.Errorf("validator set for height %d not found", height)
|
||||
}
|
||||
|
||||
for _, val := range valSet.Validators {
|
||||
key := string(val.Address)
|
||||
if _, ok := valToLastHeight[key]; !ok {
|
||||
valToLastHeight[key] = height
|
||||
}
|
||||
}
|
||||
|
||||
height--
|
||||
|
||||
if height > 0 {
|
||||
// NOTE: we assume here blockStore and state.Validators are in sync. I.e if
|
||||
// block N is stored, then validators for height N are also stored in
|
||||
// state.
|
||||
meta := blockStore.LoadBlockMeta(height)
|
||||
if meta == nil {
|
||||
return nil, fmt.Errorf("block meta for height %d not found", height)
|
||||
}
|
||||
blockTime = meta.Header.Time
|
||||
}
|
||||
|
||||
numBlocks++
|
||||
}
|
||||
|
||||
return valToLastHeight, nil
|
||||
}
|
||||
|
||||
@@ -2,87 +2,65 @@ package evidence
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/store"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
types.RegisterMockEvidences(cdc)
|
||||
RegisterMockEvidences()
|
||||
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func initializeValidatorState(valAddr []byte, height int64) dbm.DB {
|
||||
stateDB := dbm.NewMemDB()
|
||||
|
||||
// create validator set and state
|
||||
valSet := &types.ValidatorSet{
|
||||
Validators: []*types.Validator{
|
||||
{Address: valAddr},
|
||||
},
|
||||
}
|
||||
state := sm.State{
|
||||
LastBlockHeight: 0,
|
||||
LastBlockTime: tmtime.Now(),
|
||||
Validators: valSet,
|
||||
NextValidators: valSet.CopyIncrementProposerPriority(1),
|
||||
LastHeightValidatorsChanged: 1,
|
||||
ConsensusParams: types.ConsensusParams{
|
||||
Evidence: types.EvidenceParams{
|
||||
MaxAgeNumBlocks: 10000,
|
||||
MaxAgeDuration: 48 * time.Hour,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// save all states up to height
|
||||
for i := int64(0); i < height; i++ {
|
||||
state.LastBlockHeight = i
|
||||
sm.SaveState(stateDB, state)
|
||||
}
|
||||
|
||||
return stateDB
|
||||
}
|
||||
|
||||
func TestEvidencePool(t *testing.T) {
|
||||
|
||||
var (
|
||||
valAddr = []byte("val1")
|
||||
height = int64(100002)
|
||||
height = int64(52)
|
||||
stateDB = initializeValidatorState(valAddr, height)
|
||||
evidenceDB = dbm.NewMemDB()
|
||||
pool = NewPool(stateDB, evidenceDB)
|
||||
blockStoreDB = dbm.NewMemDB()
|
||||
blockStore = initializeBlockStore(blockStoreDB, sm.LoadState(stateDB), valAddr)
|
||||
evidenceTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
goodEvidence = types.NewMockEvidence(height, time.Now(), 0, valAddr)
|
||||
badEvidence = types.NewMockEvidence(1, evidenceTime, 0, valAddr)
|
||||
)
|
||||
|
||||
goodEvidence := types.NewMockEvidence(height, time.Now(), 0, valAddr)
|
||||
badEvidence := types.NewMockEvidence(1, evidenceTime, 0, valAddr)
|
||||
pool, err := NewPool(stateDB, evidenceDB, blockStore)
|
||||
require.NoError(t, err)
|
||||
|
||||
// bad evidence
|
||||
err := pool.AddEvidence(badEvidence)
|
||||
assert.Error(t, err)
|
||||
// err: evidence created at 2019-01-01 00:00:00 +0000 UTC has expired. Evidence can not be older than: ...
|
||||
err = pool.AddEvidence(badEvidence)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "is too old; min height is 32 and evidence can not be older than")
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
// good evidence
|
||||
evAdded := make(chan struct{})
|
||||
go func() {
|
||||
<-pool.EvidenceWaitChan()
|
||||
wg.Done()
|
||||
close(evAdded)
|
||||
}()
|
||||
|
||||
err = pool.AddEvidence(goodEvidence)
|
||||
assert.NoError(t, err)
|
||||
wg.Wait()
|
||||
require.NoError(t, err)
|
||||
|
||||
select {
|
||||
case <-evAdded:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("evidence was not added after 5s")
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, pool.evidenceList.Len())
|
||||
|
||||
@@ -93,16 +71,19 @@ func TestEvidencePool(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEvidencePoolIsCommitted(t *testing.T) {
|
||||
// Initialization:
|
||||
var (
|
||||
valAddr = []byte("validator_address")
|
||||
height = int64(42)
|
||||
height = int64(1)
|
||||
lastBlockTime = time.Now()
|
||||
stateDB = initializeValidatorState(valAddr, height)
|
||||
evidenceDB = dbm.NewMemDB()
|
||||
pool = NewPool(stateDB, evidenceDB)
|
||||
blockStoreDB = dbm.NewMemDB()
|
||||
blockStore = initializeBlockStore(blockStoreDB, sm.LoadState(stateDB), valAddr)
|
||||
)
|
||||
|
||||
pool, err := NewPool(stateDB, evidenceDB, blockStore)
|
||||
require.NoError(t, err)
|
||||
|
||||
// evidence not seen yet:
|
||||
evidence := types.NewMockEvidence(height, time.Now(), 0, valAddr)
|
||||
assert.False(t, pool.IsCommitted(evidence))
|
||||
@@ -116,17 +97,20 @@ func TestEvidencePoolIsCommitted(t *testing.T) {
|
||||
assert.True(t, pool.IsCommitted(evidence))
|
||||
}
|
||||
|
||||
func TestAddEvidence(t *testing.T) {
|
||||
|
||||
func TestEvidencePoolAddEvidence(t *testing.T) {
|
||||
var (
|
||||
valAddr = []byte("val1")
|
||||
height = int64(100002)
|
||||
height = int64(30)
|
||||
stateDB = initializeValidatorState(valAddr, height)
|
||||
evidenceDB = dbm.NewMemDB()
|
||||
pool = NewPool(stateDB, evidenceDB)
|
||||
blockStoreDB = dbm.NewMemDB()
|
||||
blockStore = initializeBlockStore(blockStoreDB, sm.LoadState(stateDB), valAddr)
|
||||
evidenceTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
pool, err := NewPool(stateDB, evidenceDB, blockStore)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
evHeight int64
|
||||
evTime time.Time
|
||||
@@ -142,10 +126,127 @@ func TestAddEvidence(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
ev := types.NewMockEvidence(tc.evHeight, tc.evTime, 0, valAddr)
|
||||
err := pool.AddEvidence(ev)
|
||||
if tc.expErr {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
t.Run(tc.evDescription, func(t *testing.T) {
|
||||
ev := types.NewMockEvidence(tc.evHeight, tc.evTime, 0, valAddr)
|
||||
err := pool.AddEvidence(ev)
|
||||
if tc.expErr {
|
||||
assert.Error(t, err)
|
||||
t.Log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvidencePoolUpdate(t *testing.T) {
|
||||
var (
|
||||
valAddr = []byte("validator_address")
|
||||
height = int64(1)
|
||||
stateDB = initializeValidatorState(valAddr, height)
|
||||
evidenceDB = dbm.NewMemDB()
|
||||
blockStoreDB = dbm.NewMemDB()
|
||||
state = sm.LoadState(stateDB)
|
||||
blockStore = initializeBlockStore(blockStoreDB, state, valAddr)
|
||||
)
|
||||
|
||||
pool, err := NewPool(stateDB, evidenceDB, blockStore)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create new block (no need to save it to blockStore)
|
||||
evidence := types.NewMockEvidence(height, time.Now(), 0, valAddr)
|
||||
lastCommit := makeCommit(height, valAddr)
|
||||
block := types.MakeBlock(height+1, []types.Tx{}, lastCommit, []types.Evidence{evidence})
|
||||
// update state (partially)
|
||||
state.LastBlockHeight = height + 1
|
||||
|
||||
pool.Update(block, state)
|
||||
|
||||
// a) Update marks evidence as committed
|
||||
assert.True(t, pool.IsCommitted(evidence))
|
||||
// b) Update updates valToLastHeight map
|
||||
assert.Equal(t, height+1, pool.ValidatorLastHeight(valAddr))
|
||||
}
|
||||
|
||||
func TestEvidencePoolNewPool(t *testing.T) {
|
||||
var (
|
||||
valAddr = []byte("validator_address")
|
||||
height = int64(1)
|
||||
stateDB = initializeValidatorState(valAddr, height)
|
||||
evidenceDB = dbm.NewMemDB()
|
||||
blockStoreDB = dbm.NewMemDB()
|
||||
state = sm.LoadState(stateDB)
|
||||
blockStore = initializeBlockStore(blockStoreDB, state, valAddr)
|
||||
)
|
||||
|
||||
pool, err := NewPool(stateDB, evidenceDB, blockStore)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, height, pool.ValidatorLastHeight(valAddr))
|
||||
assert.EqualValues(t, 0, pool.ValidatorLastHeight([]byte("non-existent-validator")))
|
||||
}
|
||||
|
||||
func initializeValidatorState(valAddr []byte, height int64) dbm.DB {
|
||||
stateDB := dbm.NewMemDB()
|
||||
|
||||
// create validator set and state
|
||||
valSet := &types.ValidatorSet{
|
||||
Validators: []*types.Validator{
|
||||
{Address: valAddr, VotingPower: 0},
|
||||
},
|
||||
}
|
||||
state := sm.State{
|
||||
LastBlockHeight: height,
|
||||
LastBlockTime: tmtime.Now(),
|
||||
LastValidators: valSet,
|
||||
Validators: valSet,
|
||||
NextValidators: valSet.CopyIncrementProposerPriority(1),
|
||||
LastHeightValidatorsChanged: 1,
|
||||
ConsensusParams: types.ConsensusParams{
|
||||
Block: types.BlockParams{
|
||||
MaxBytes: 22020096,
|
||||
MaxGas: -1,
|
||||
},
|
||||
Evidence: types.EvidenceParams{
|
||||
MaxAgeNumBlocks: 20,
|
||||
MaxAgeDuration: 48 * time.Hour,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// save all states up to height
|
||||
for i := int64(0); i <= height; i++ {
|
||||
state.LastBlockHeight = i
|
||||
sm.SaveState(stateDB, state)
|
||||
}
|
||||
|
||||
return stateDB
|
||||
}
|
||||
|
||||
// initializeBlockStore creates a block storage and populates it w/ a dummy
|
||||
// block at +height+.
|
||||
func initializeBlockStore(db dbm.DB, state sm.State, valAddr []byte) *store.BlockStore {
|
||||
blockStore := store.NewBlockStore(db)
|
||||
|
||||
for i := int64(1); i <= state.LastBlockHeight; i++ {
|
||||
lastCommit := makeCommit(i-1, valAddr)
|
||||
block, _ := state.MakeBlock(i, []types.Tx{}, lastCommit, nil,
|
||||
state.Validators.GetProposer().Address)
|
||||
|
||||
const parts = 1
|
||||
partSet := block.MakePartSet(parts)
|
||||
|
||||
seenCommit := makeCommit(i, valAddr)
|
||||
blockStore.SaveBlock(block, partSet, seenCommit)
|
||||
}
|
||||
|
||||
return blockStore
|
||||
}
|
||||
|
||||
func makeCommit(height int64, valAddr []byte) *types.Commit {
|
||||
commitSigs := []types.CommitSig{{
|
||||
BlockIDFlag: types.BlockIDFlagCommit,
|
||||
ValidatorAddress: valAddr,
|
||||
Timestamp: time.Now(),
|
||||
Signature: []byte("Signature"),
|
||||
}}
|
||||
return types.NewCommit(height, 0, types.BlockID{}, commitSigs)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/log/term"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
@@ -34,12 +36,18 @@ func evidenceLogger() log.Logger {
|
||||
// connect N evidence reactors through N switches
|
||||
func makeAndConnectReactors(config *cfg.Config, stateDBs []dbm.DB) []*Reactor {
|
||||
N := len(stateDBs)
|
||||
|
||||
reactors := make([]*Reactor, N)
|
||||
logger := evidenceLogger()
|
||||
for i := 0; i < N; i++ {
|
||||
|
||||
for i := 0; i < N; i++ {
|
||||
evidenceDB := dbm.NewMemDB()
|
||||
pool := NewPool(stateDBs[i], evidenceDB)
|
||||
blockStoreDB := dbm.NewMemDB()
|
||||
blockStore := initializeBlockStore(blockStoreDB, sm.LoadState(stateDBs[i]), []byte("myval"))
|
||||
pool, err := NewPool(stateDBs[i], evidenceDB, blockStore)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
reactors[i] = NewReactor(pool)
|
||||
reactors[i].SetLogger(logger.With("validator", i))
|
||||
}
|
||||
@@ -49,6 +57,7 @@ func makeAndConnectReactors(config *cfg.Config, stateDBs []dbm.DB) []*Reactor {
|
||||
return s
|
||||
|
||||
}, p2p.Connect2Switches)
|
||||
|
||||
return reactors
|
||||
}
|
||||
|
||||
@@ -67,7 +76,7 @@ func waitForEvidence(t *testing.T, evs types.EvidenceList, reactors []*Reactor)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
timer := time.After(Timeout)
|
||||
timer := time.After(timeout)
|
||||
select {
|
||||
case <-timer:
|
||||
t.Fatal("Timed out waiting for evidence")
|
||||
@@ -109,15 +118,15 @@ func sendEvidence(t *testing.T, evpool *Pool, valAddr []byte, n int) types.Evide
|
||||
for i := 0; i < n; i++ {
|
||||
ev := types.NewMockEvidence(int64(i+1), time.Now().UTC(), 0, valAddr)
|
||||
err := evpool.AddEvidence(ev)
|
||||
assert.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
evList[i] = ev
|
||||
}
|
||||
return evList
|
||||
}
|
||||
|
||||
var (
|
||||
NumEvidence = 10
|
||||
Timeout = 120 * time.Second // ridiculously high because CircleCI is slow
|
||||
numEvidence = 10
|
||||
timeout = 120 * time.Second // ridiculously high because CircleCI is slow
|
||||
)
|
||||
|
||||
func TestReactorBroadcastEvidence(t *testing.T) {
|
||||
@@ -128,7 +137,7 @@ func TestReactorBroadcastEvidence(t *testing.T) {
|
||||
stateDBs := make([]dbm.DB, N)
|
||||
valAddr := []byte("myval")
|
||||
// we need validators saved for heights at least as high as we have evidence for
|
||||
height := int64(NumEvidence) + 10
|
||||
height := int64(numEvidence) + 10
|
||||
for i := 0; i < N; i++ {
|
||||
stateDBs[i] = initializeValidatorState(valAddr, height)
|
||||
}
|
||||
@@ -146,7 +155,7 @@ func TestReactorBroadcastEvidence(t *testing.T) {
|
||||
|
||||
// send a bunch of valid evidence to the first reactor's evpool
|
||||
// and wait for them all to be received in the others
|
||||
evList := sendEvidence(t, reactors[0].evpool, valAddr, NumEvidence)
|
||||
evList := sendEvidence(t, reactors[0].evpool, valAddr, numEvidence)
|
||||
waitForEvidence(t, evList, reactors)
|
||||
}
|
||||
|
||||
@@ -162,8 +171,8 @@ func TestReactorSelectiveBroadcast(t *testing.T) {
|
||||
config := cfg.TestConfig()
|
||||
|
||||
valAddr := []byte("myval")
|
||||
height1 := int64(NumEvidence) + 10
|
||||
height2 := int64(NumEvidence) / 2
|
||||
height1 := int64(numEvidence) + 10
|
||||
height2 := int64(numEvidence) / 2
|
||||
|
||||
// DB1 is ahead of DB2
|
||||
stateDB1 := initializeValidatorState(valAddr, height1)
|
||||
@@ -186,10 +195,10 @@ func TestReactorSelectiveBroadcast(t *testing.T) {
|
||||
peer.Set(types.PeerStateKey, ps)
|
||||
|
||||
// send a bunch of valid evidence to the first reactor's evpool
|
||||
evList := sendEvidence(t, reactors[0].evpool, valAddr, NumEvidence)
|
||||
evList := sendEvidence(t, reactors[0].evpool, valAddr, numEvidence)
|
||||
|
||||
// only ones less than the peers height should make it through
|
||||
waitForEvidence(t, evList[:NumEvidence/2], reactors[1:2])
|
||||
waitForEvidence(t, evList[:numEvidence/2], reactors[1:2])
|
||||
|
||||
// peers should still be connected
|
||||
peers := reactors[1].Switch.Peers().List()
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
@@ -14,8 +15,6 @@ import (
|
||||
//-------------------------------------------
|
||||
|
||||
func TestStoreAddDuplicate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
db := dbm.NewMemDB()
|
||||
store := NewStore(db)
|
||||
|
||||
@@ -24,17 +23,15 @@ func TestStoreAddDuplicate(t *testing.T) {
|
||||
|
||||
added, err := store.AddNewEvidence(ev, priority)
|
||||
require.NoError(t, err)
|
||||
assert.True(added)
|
||||
assert.True(t, added)
|
||||
|
||||
// cant add twice
|
||||
added, err = store.AddNewEvidence(ev, priority)
|
||||
require.NoError(t, err)
|
||||
assert.False(added)
|
||||
assert.False(t, added)
|
||||
}
|
||||
|
||||
func TestStoreCommitDuplicate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
db := dbm.NewMemDB()
|
||||
store := NewStore(db)
|
||||
|
||||
@@ -45,65 +42,61 @@ func TestStoreCommitDuplicate(t *testing.T) {
|
||||
|
||||
added, err := store.AddNewEvidence(ev, priority)
|
||||
require.NoError(t, err)
|
||||
assert.False(added)
|
||||
assert.False(t, added)
|
||||
}
|
||||
|
||||
func TestStoreMark(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
db := dbm.NewMemDB()
|
||||
store := NewStore(db)
|
||||
|
||||
// before we do anything, priority/pending are empty
|
||||
priorityEv := store.PriorityEvidence()
|
||||
pendingEv := store.PendingEvidence(-1)
|
||||
assert.Equal(0, len(priorityEv))
|
||||
assert.Equal(0, len(pendingEv))
|
||||
assert.Equal(t, 0, len(priorityEv))
|
||||
assert.Equal(t, 0, len(pendingEv))
|
||||
|
||||
priority := int64(10)
|
||||
ev := types.NewMockEvidence(2, time.Now().UTC(), 1, []byte("val1"))
|
||||
|
||||
added, err := store.AddNewEvidence(ev, priority)
|
||||
require.NoError(t, err)
|
||||
assert.True(added)
|
||||
assert.True(t, added)
|
||||
|
||||
// get the evidence. verify. should be uncommitted
|
||||
ei := store.GetInfo(ev.Height(), ev.Hash())
|
||||
assert.Equal(ev, ei.Evidence)
|
||||
assert.Equal(priority, ei.Priority)
|
||||
assert.False(ei.Committed)
|
||||
assert.Equal(t, ev, ei.Evidence)
|
||||
assert.Equal(t, priority, ei.Priority)
|
||||
assert.False(t, ei.Committed)
|
||||
|
||||
// new evidence should be returns in priority/pending
|
||||
priorityEv = store.PriorityEvidence()
|
||||
pendingEv = store.PendingEvidence(-1)
|
||||
assert.Equal(1, len(priorityEv))
|
||||
assert.Equal(1, len(pendingEv))
|
||||
assert.Equal(t, 1, len(priorityEv))
|
||||
assert.Equal(t, 1, len(pendingEv))
|
||||
|
||||
// priority is now empty
|
||||
store.MarkEvidenceAsBroadcasted(ev)
|
||||
priorityEv = store.PriorityEvidence()
|
||||
pendingEv = store.PendingEvidence(-1)
|
||||
assert.Equal(0, len(priorityEv))
|
||||
assert.Equal(1, len(pendingEv))
|
||||
assert.Equal(t, 0, len(priorityEv))
|
||||
assert.Equal(t, 1, len(pendingEv))
|
||||
|
||||
// priority and pending are now empty
|
||||
store.MarkEvidenceAsCommitted(ev)
|
||||
priorityEv = store.PriorityEvidence()
|
||||
pendingEv = store.PendingEvidence(-1)
|
||||
assert.Equal(0, len(priorityEv))
|
||||
assert.Equal(0, len(pendingEv))
|
||||
assert.Equal(t, 0, len(priorityEv))
|
||||
assert.Equal(t, 0, len(pendingEv))
|
||||
|
||||
// evidence should show committed
|
||||
newPriority := int64(0)
|
||||
ei = store.GetInfo(ev.Height(), ev.Hash())
|
||||
assert.Equal(ev, ei.Evidence)
|
||||
assert.Equal(newPriority, ei.Priority)
|
||||
assert.True(ei.Committed)
|
||||
assert.Equal(t, ev, ei.Evidence)
|
||||
assert.Equal(t, newPriority, ei.Priority)
|
||||
assert.True(t, ei.Committed)
|
||||
}
|
||||
|
||||
func TestStorePriority(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
db := dbm.NewMemDB()
|
||||
store := NewStore(db)
|
||||
|
||||
@@ -123,11 +116,11 @@ func TestStorePriority(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
added, err := store.AddNewEvidence(c.ev, c.priority)
|
||||
require.NoError(t, err)
|
||||
assert.True(added)
|
||||
assert.True(t, added)
|
||||
}
|
||||
|
||||
evList := store.PriorityEvidence()
|
||||
for i, ev := range evList {
|
||||
assert.Equal(ev, cases[i].ev)
|
||||
assert.Equal(t, ev, cases[i].ev)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user