mirror of
https://github.com/tendermint/tendermint.git
synced 2026-04-26 10:41:11 +00:00
evidence: improve amnesia evidence handling (#5003)
fix bug so that PotentialAmnesiaEvidence is being gossiped handle inbound amnesia evidence correctly add method to check if potential amnesia evidence is on trial fix a bug with the height when we upgrade to amnesia evidence change evidence to using just pointers. More logging in the evidence module Co-authored-by: Marko <marbar3778@yahoo.com>
This commit is contained in:
@@ -234,8 +234,8 @@ func (m *mockEvidencePool) IsPending(evidence types.Evidence) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (m *mockEvidencePool) AddPOLC(types.ProofOfLockChange) error { return nil }
|
||||
func (m *mockEvidencePool) Header(int64) *types.Header { return nil }
|
||||
func (m *mockEvidencePool) AddPOLC(*types.ProofOfLockChange) error { return nil }
|
||||
func (m *mockEvidencePool) Header(int64) *types.Header { return nil }
|
||||
|
||||
//------------------------------------
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func (emptyEvidencePool) AddEvidence(types.Evidence) error { return nil }
|
||||
func (emptyEvidencePool) Update(*types.Block, sm.State) {}
|
||||
func (emptyEvidencePool) IsCommitted(types.Evidence) bool { return false }
|
||||
func (emptyEvidencePool) IsPending(types.Evidence) bool { return false }
|
||||
func (emptyEvidencePool) AddPOLC(types.ProofOfLockChange) error { return nil }
|
||||
func (emptyEvidencePool) AddPOLC(*types.ProofOfLockChange) error { return nil }
|
||||
func (emptyEvidencePool) Header(int64) *types.Header { return nil }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -70,7 +70,7 @@ type txNotifier interface {
|
||||
// interface to the evidence pool
|
||||
type evidencePool interface {
|
||||
AddEvidence(types.Evidence) error
|
||||
AddPOLC(types.ProofOfLockChange) error
|
||||
AddPOLC(*types.ProofOfLockChange) error
|
||||
}
|
||||
|
||||
// State handles execution of the consensus algorithm.
|
||||
@@ -1312,7 +1312,7 @@ func (cs *State) savePOLC(round int32, blockID types.BlockID) {
|
||||
cs.Logger.Error("Error on retrieval of pubkey", "err", err)
|
||||
return
|
||||
}
|
||||
polc, err := types.MakePOLCFromVoteSet(cs.Votes.Prevotes(round), pubKey, blockID)
|
||||
polc, err := types.NewPOLCFromVoteSet(cs.Votes.Prevotes(round), pubKey, blockID)
|
||||
if err != nil {
|
||||
cs.Logger.Error("Error on forming POLC", "err", err)
|
||||
return
|
||||
|
||||
@@ -710,13 +710,12 @@ func TestStateLockPOLUnlock(t *testing.T) {
|
||||
// polc should be in the evpool for round 1
|
||||
polc, err := evpool.RetrievePOLC(height, round)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, polc)
|
||||
assert.False(t, polc.IsAbsent())
|
||||
t.Log(polc.Address())
|
||||
// but not for round 0
|
||||
polc, err = evpool.RetrievePOLC(height, round-1)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, polc.IsAbsent())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, polc)
|
||||
}
|
||||
|
||||
// 4 vals, v1 locks on proposed block in the first round but the other validators only prevote
|
||||
@@ -820,6 +819,7 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) {
|
||||
// polc should be in the evpool for round 1
|
||||
polc, err := evpool.RetrievePOLC(height, round)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, polc)
|
||||
assert.False(t, polc.IsAbsent())
|
||||
|
||||
incrementRound(vs2, vs3, vs4)
|
||||
|
||||
@@ -24,15 +24,39 @@ uncommitted evidence at intervals of 60 seconds (set by the by broadcastEvidence
|
||||
It uses a concurrent list to store the evidence and before sending verifies that each evidence is still valid in the
|
||||
sense that it has not exceeded the max evidence age and height (see types/params.go#EvidenceParams).
|
||||
|
||||
Three are four buckets that evidence can be stored in: Pending, Committed, Awaiting and POLC's.
|
||||
|
||||
1. Pending is awaiting to be committed (evidence is usually broadcasted then)
|
||||
|
||||
2. Committed is for those already on the block and is to ensure that evidence isn't submitted twice
|
||||
|
||||
3. AwaitingTrial primarily refers to PotentialAmnesiaEvidence which must wait for a trial period before
|
||||
being ready to be submitted (see docs/architecture/adr-056)
|
||||
|
||||
4. POLC's store all the ProofOfLockChanges that the node has done as part of consensus. To change lock is to vote
|
||||
for a different block in a later round. The consensus module calls `AddPOLC()` to add to this bucket.
|
||||
|
||||
All evidence is proto encoded to disk.
|
||||
|
||||
Proposing
|
||||
|
||||
When a new block is being proposed (in state/execution.go#CreateProposalBlock),
|
||||
`PendingEvidence(maxNum)` is called to send up to the maxNum number of uncommitted evidence, from the evidence store,
|
||||
based on a priority that is a product of the age of the evidence and the voting power of the malicious validator.
|
||||
prioritized in order of age. All evidence is checked for expiration.
|
||||
|
||||
When a node receives evidence in a block it will use the evidence module as a cache first to see if it has
|
||||
already verified the evidence before trying to verify it again.
|
||||
|
||||
Once the proposed evidence is submitted,
|
||||
the evidence is marked as committed and is moved from the broadcasted set to the committed set (
|
||||
the committed set is used to verify whether new evidence has actually already been submitted).
|
||||
the evidence is marked as committed and is moved from the broadcasted set to the committed set.
|
||||
As a result it is also removed from the concurrent list so that it is no longer gossiped.
|
||||
|
||||
Minor Functionality
|
||||
|
||||
As all evidence (including POLC's) are bounded by an expiration date, those that exceed this are no longer needed
|
||||
and hence pruned. Currently, only committed evidence in which a marker to the height that the evidence was committed
|
||||
and hence very small is saved. All updates are made from the `Update(block, state)` function which should be called
|
||||
when a new block is committed.
|
||||
|
||||
*/
|
||||
package evidence
|
||||
|
||||
262
evidence/pool.go
262
evidence/pool.go
@@ -18,10 +18,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
baseKeyCommitted = byte(0x00)
|
||||
baseKeyPending = byte(0x01)
|
||||
baseKeyPOLC = byte(0x02)
|
||||
baseKeyAwaiting = byte(0x03)
|
||||
baseKeyCommitted = byte(0x00)
|
||||
baseKeyPending = byte(0x01)
|
||||
baseKeyPOLC = byte(0x02)
|
||||
baseKeyAwaitingTrial = byte(0x03)
|
||||
)
|
||||
|
||||
// Pool maintains a pool of valid evidence to be broadcasted and committed
|
||||
@@ -51,6 +51,8 @@ type Pool struct {
|
||||
// Validator.Address -> Last height it was in validator set
|
||||
type valToLastHeightMap map[string]int64
|
||||
|
||||
// Creates a new pool. If using an existing evidence store, it will add all pending evidence
|
||||
// to the concurrent list.
|
||||
func NewPool(stateDB, evidenceDB dbm.DB, blockStore *store.BlockStore) (*Pool, error) {
|
||||
var (
|
||||
state = sm.LoadState(stateDB)
|
||||
@@ -82,7 +84,7 @@ func NewPool(stateDB, evidenceDB dbm.DB, blockStore *store.BlockStore) (*Pool, e
|
||||
}
|
||||
|
||||
// PendingEvidence is used primarily as part of block proposal and returns up to maxNum of uncommitted evidence.
|
||||
// If maxNum is -1, all evidence is returned. Pending evidence is prioritised based on time.
|
||||
// If maxNum is -1, all evidence is returned. Pending evidence is prioritized based on time.
|
||||
func (evpool *Pool) PendingEvidence(maxNum uint32) []types.Evidence {
|
||||
evpool.removeExpiredPendingEvidence()
|
||||
evidence, err := evpool.listEvidence(baseKeyPending, int64(maxNum))
|
||||
@@ -92,6 +94,7 @@ func (evpool *Pool) PendingEvidence(maxNum uint32) []types.Evidence {
|
||||
return evidence
|
||||
}
|
||||
|
||||
// AllPendingEvidence returns all evidence ready to be proposed and committed.
|
||||
func (evpool *Pool) AllPendingEvidence() []types.Evidence {
|
||||
evpool.removeExpiredPendingEvidence()
|
||||
evidence, err := evpool.listEvidence(baseKeyPending, -1)
|
||||
@@ -113,29 +116,30 @@ func (evpool *Pool) Update(block *types.Block, state sm.State) {
|
||||
)
|
||||
}
|
||||
|
||||
// update the state
|
||||
evpool.updateState(state)
|
||||
|
||||
// remove evidence from pending and mark committed
|
||||
evpool.MarkEvidenceAsCommitted(block.Height, block.Evidence.Evidence)
|
||||
|
||||
// prune pending, committed and potential evidence and polc's periodically
|
||||
if block.Height%state.ConsensusParams.Evidence.MaxAgeNumBlocks == 0 {
|
||||
evpool.logger.Debug("Pruning no longer necessary evidence")
|
||||
evpool.pruneExpiredPOLC()
|
||||
evpool.removeExpiredPendingEvidence()
|
||||
}
|
||||
|
||||
if evpool.nextEvidenceTrialEndedHeight > 0 && block.Height < evpool.nextEvidenceTrialEndedHeight {
|
||||
evpool.upgradePotentialAmnesiaEvidence()
|
||||
}
|
||||
|
||||
// update the state
|
||||
evpool.mtx.Lock()
|
||||
defer evpool.mtx.Unlock()
|
||||
evpool.state = state
|
||||
evpool.updateValToLastHeight(block.Height, state)
|
||||
|
||||
if evpool.nextEvidenceTrialEndedHeight > 0 && block.Height > evpool.nextEvidenceTrialEndedHeight {
|
||||
evpool.logger.Debug("Upgrading all potential evidence that have served the trial period")
|
||||
evpool.nextEvidenceTrialEndedHeight = evpool.upgradePotentialAmnesiaEvidence()
|
||||
}
|
||||
}
|
||||
|
||||
// AddPOLC adds a proof of lock change to the evidence database
|
||||
// that may be needed in the future to verify votes
|
||||
func (evpool *Pool) AddPOLC(polc types.ProofOfLockChange) error {
|
||||
func (evpool *Pool) AddPOLC(polc *types.ProofOfLockChange) error {
|
||||
key := keyPOLC(polc)
|
||||
pbplc, err := polc.ToProto()
|
||||
if err != nil {
|
||||
@@ -157,6 +161,8 @@ func (evpool *Pool) AddEvidence(evidence types.Evidence) error {
|
||||
evList = []types.Evidence{evidence}
|
||||
)
|
||||
|
||||
evpool.logger.Debug("Attempting to add evidence", "ev", evidence)
|
||||
|
||||
valSet, err := sm.LoadValidators(evpool.stateDB, evidence.Height())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't load validators at height #%d: %w", evidence.Height(), err)
|
||||
@@ -187,8 +193,13 @@ func (evpool *Pool) AddEvidence(evidence types.Evidence) error {
|
||||
}
|
||||
|
||||
for _, ev := range evList {
|
||||
|
||||
if evpool.Has(ev) {
|
||||
continue
|
||||
// if it is an amnesia evidence we have but POLC is not absent then
|
||||
// we should still process it
|
||||
if ae, ok := ev.(*types.AmnesiaEvidence); !ok || ae.Polc.IsAbsent() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// For lunatic validator evidence, a header needs to be fetched.
|
||||
@@ -206,68 +217,32 @@ func (evpool *Pool) AddEvidence(evidence types.Evidence) error {
|
||||
}
|
||||
|
||||
// For potential amnesia evidence, if this node is indicted it shall retrieve a polc
|
||||
// to form AmensiaEvidence
|
||||
if pe, ok := ev.(types.PotentialAmnesiaEvidence); ok {
|
||||
var (
|
||||
height = pe.Height()
|
||||
exists = false
|
||||
polc types.ProofOfLockChange
|
||||
)
|
||||
pe.HeightStamp = evpool.State().LastBlockHeight
|
||||
|
||||
// a) first try to find a corresponding polc
|
||||
for round := pe.VoteB.Round; round > pe.VoteA.Round; round-- {
|
||||
polc, err = evpool.RetrievePOLC(height, round)
|
||||
if err != nil {
|
||||
evpool.logger.Error("Failed to retrieve polc for potential amnesia evidence", "err", err, "pae", pe.String())
|
||||
continue
|
||||
}
|
||||
if err == nil && !polc.IsAbsent() {
|
||||
// we should not need to verify it if both the polc and potential amnesia evidence have already
|
||||
// been verified. We replace the potential amnesia evidence.
|
||||
ae := types.MakeAmnesiaEvidence(pe, polc)
|
||||
err := evpool.AddEvidence(ae)
|
||||
if err != nil {
|
||||
evpool.logger.Error("Failed to create amnesia evidence from potential amnesia evidence", "err", err)
|
||||
// revert back to processing potential amnesia evidence
|
||||
exists = false
|
||||
} else {
|
||||
evpool.logger.Info("Formed amnesia evidence from own polc", "amnesiaEvidence", ae)
|
||||
}
|
||||
break
|
||||
}
|
||||
// to form AmensiaEvidence else start the trial period for the piece of evidence
|
||||
if pe, ok := ev.(*types.PotentialAmnesiaEvidence); ok {
|
||||
if err := evpool.handleInboundPotentialAmnesiaEvidence(pe); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// b) check if amnesia evidence can be made now or if we need to enact the trial period
|
||||
if !exists && pe.Primed(1, pe.HeightStamp) {
|
||||
err := evpool.AddEvidence(types.MakeAmnesiaEvidence(pe, types.EmptyPOLC()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !exists && evpool.State().LastBlockHeight+evpool.State().ConsensusParams.Evidence.ProofTrialPeriod <
|
||||
pe.Height()+evpool.State().ConsensusParams.Evidence.MaxAgeNumBlocks {
|
||||
// if we can't find a proof of lock change and we know that the trial period will finish before the
|
||||
// evidence has expired, then we commence the trial period by saving it in the awaiting bucket
|
||||
pbe, err := types.EvidenceToProto(pe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evBytes, err := pbe.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := keyAwaiting(pe)
|
||||
err = evpool.evidenceStore.Set(key, evBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// keep track of when the next pe has finished the trial period
|
||||
if evpool.nextEvidenceTrialEndedHeight == -1 {
|
||||
evpool.nextEvidenceTrialEndedHeight = ev.Height() + evpool.State().ConsensusParams.Evidence.ProofTrialPeriod
|
||||
}
|
||||
}
|
||||
// we don't need to do anymore processing so we can move on to the next piece of evidence
|
||||
continue
|
||||
} else if ae, ok := ev.(*types.AmnesiaEvidence); ok {
|
||||
if ae.Polc.IsAbsent() && ae.PotentialAmnesiaEvidence.VoteA.Round <
|
||||
ae.PotentialAmnesiaEvidence.VoteB.Round {
|
||||
if err := evpool.handleInboundPotentialAmnesiaEvidence(ae.PotentialAmnesiaEvidence); err != nil {
|
||||
return fmt.Errorf("failed to handle amnesia evidence, err: %w", err)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
// we are going to add this amnesia evidence and check if we already have an amnesia evidence or potential
|
||||
// amnesia evidence that addesses the same case
|
||||
aeWithoutPolc := types.NewAmnesiaEvidence(ae.PotentialAmnesiaEvidence, types.NewEmptyPOLC())
|
||||
if evpool.IsPending(aeWithoutPolc) {
|
||||
evpool.removePendingEvidence(aeWithoutPolc)
|
||||
} else if evpool.IsOnTrial(ae.PotentialAmnesiaEvidence) {
|
||||
key := keyAwaitingTrial(ae.PotentialAmnesiaEvidence)
|
||||
if err := evpool.evidenceStore.Delete(key); err != nil {
|
||||
evpool.logger.Error("Failed to remove potential amnesia evidence from database", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Save to store.
|
||||
@@ -319,7 +294,7 @@ func (evpool *Pool) MarkEvidenceAsCommitted(height int64, evidence []types.Evide
|
||||
|
||||
// Has checks whether the evidence exists either pending or already committed
|
||||
func (evpool *Pool) Has(evidence types.Evidence) bool {
|
||||
return evpool.IsPending(evidence) || evpool.IsCommitted(evidence)
|
||||
return evpool.IsPending(evidence) || evpool.IsCommitted(evidence) || evpool.IsOnTrial(evidence)
|
||||
}
|
||||
|
||||
// IsEvidenceExpired checks whether evidence is past the maximum age where it can be used
|
||||
@@ -359,31 +334,49 @@ func (evpool *Pool) IsPending(evidence types.Evidence) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsOnTrial checks whether a piece of evidence is in the awaiting bucket.
|
||||
// Only Potential Amnesia Evidence is stored here.
|
||||
func (evpool *Pool) IsOnTrial(evidence types.Evidence) bool {
|
||||
pe, ok := evidence.(*types.PotentialAmnesiaEvidence)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
key := keyAwaitingTrial(pe)
|
||||
ok, err := evpool.evidenceStore.Has(key)
|
||||
if err != nil {
|
||||
evpool.logger.Error("Unable to find evidence on trial", "err", err)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// RetrievePOLC attempts to find a polc at the given height and round, if not there than exist returns false, all
|
||||
// database errors are automatically logged
|
||||
func (evpool *Pool) RetrievePOLC(height int64, round int32) (polc types.ProofOfLockChange, err error) {
|
||||
func (evpool *Pool) RetrievePOLC(height int64, round int32) (*types.ProofOfLockChange, error) {
|
||||
var pbpolc tmproto.ProofOfLockChange
|
||||
key := keyPOLCFromHeightAndRound(height, round)
|
||||
polcBytes, err := evpool.evidenceStore.Get(key)
|
||||
if err != nil {
|
||||
evpool.logger.Error("Unable to retrieve polc", "err", err)
|
||||
return polc, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// polc doesn't exist
|
||||
if polcBytes == nil {
|
||||
return polc, fmt.Errorf("nil value in database for key: %s", key)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = proto.Unmarshal(polcBytes, &pbpolc)
|
||||
if err != nil {
|
||||
return polc, err
|
||||
return nil, err
|
||||
}
|
||||
plc, err := types.ProofOfLockChangeFromProto(&pbpolc)
|
||||
polc, err := types.ProofOfLockChangeFromProto(&pbpolc)
|
||||
if err != nil {
|
||||
return polc, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return *plc, err
|
||||
return polc, err
|
||||
}
|
||||
|
||||
// EvidenceFront goes to the first evidence in the clist
|
||||
@@ -580,16 +573,23 @@ func (evpool *Pool) pruneExpiredPOLC() {
|
||||
}
|
||||
}
|
||||
|
||||
func (evpool *Pool) updateState(state sm.State) {
|
||||
evpool.mtx.Lock()
|
||||
defer evpool.mtx.Unlock()
|
||||
evpool.state = state
|
||||
}
|
||||
|
||||
// upgrades any potential evidence that has undergone the trial period and is primed to be made into
|
||||
// amnesia evidence
|
||||
func (evpool *Pool) upgradePotentialAmnesiaEvidence() int64 {
|
||||
iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{baseKeyAwaiting})
|
||||
iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{baseKeyAwaitingTrial})
|
||||
if err != nil {
|
||||
evpool.logger.Error("Unable to iterate over POLC's", "err", err)
|
||||
return -1
|
||||
}
|
||||
defer iter.Close()
|
||||
trialPeriod := evpool.State().ConsensusParams.Evidence.ProofTrialPeriod
|
||||
currentHeight := evpool.State().LastBlockHeight
|
||||
// 1) Iterate through all potential amnesia evidence in order of height
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
paeBytes := iter.Value()
|
||||
@@ -602,26 +602,27 @@ func (evpool *Pool) upgradePotentialAmnesiaEvidence() int64 {
|
||||
}
|
||||
ev, err := types.EvidenceFromProto(&evpb)
|
||||
if err != nil {
|
||||
evpool.logger.Error("coverting to evidence from proto", "err", err)
|
||||
evpool.logger.Error("Converting from proto to evidence", "err", err)
|
||||
continue
|
||||
}
|
||||
// 3) Check if the trial period has lapsed and amnesia evidence can be formed
|
||||
if pe, ok := ev.(*types.PotentialAmnesiaEvidence); ok {
|
||||
if pe.Primed(trialPeriod, evpool.State().LastBlockHeight) {
|
||||
ae := types.MakeAmnesiaEvidence(*pe, types.EmptyPOLC())
|
||||
err := evpool.AddEvidence(ae)
|
||||
if pe.Primed(trialPeriod, currentHeight) {
|
||||
ae := types.NewAmnesiaEvidence(pe, types.NewEmptyPOLC())
|
||||
err := evpool.addPendingEvidence(ae)
|
||||
if err != nil {
|
||||
evpool.logger.Error("Unable to add amnesia evidence", "err", err)
|
||||
continue
|
||||
}
|
||||
evpool.logger.Info("Upgraded to amnesia evidence", "amnesiaEvidence", ae)
|
||||
err = evpool.evidenceStore.Delete(iter.Key())
|
||||
if err != nil {
|
||||
evpool.logger.Error("Unable to delete potential amnesia evidence", "err", err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
evpool.logger.Debug("Potential amnesia evidence not ready to be upgraded. Ready at height", "height",
|
||||
pe.HeightStamp+trialPeriod)
|
||||
evpool.logger.Debug("Potential amnesia evidence is not ready to be upgraded. Ready at", "height",
|
||||
pe.HeightStamp+trialPeriod, "currentHeight", currentHeight)
|
||||
// once we reach a piece of evidence that isn't ready send back the height with which it will be ready
|
||||
return pe.HeightStamp + trialPeriod
|
||||
}
|
||||
@@ -631,6 +632,81 @@ func (evpool *Pool) upgradePotentialAmnesiaEvidence() int64 {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (evpool *Pool) handleInboundPotentialAmnesiaEvidence(pe *types.PotentialAmnesiaEvidence) error {
|
||||
var (
|
||||
height = pe.Height()
|
||||
exists = false
|
||||
polc *types.ProofOfLockChange
|
||||
err error
|
||||
)
|
||||
|
||||
evpool.logger.Debug("Received Potential Amnesia Evidence", "pe", pe)
|
||||
|
||||
// a) first try to find a corresponding polc
|
||||
for round := pe.VoteB.Round; round > pe.VoteA.Round; round-- {
|
||||
polc, err = evpool.RetrievePOLC(height, round)
|
||||
if err != nil {
|
||||
evpool.logger.Error("Failed to retrieve polc for potential amnesia evidence", "err", err, "pae", pe.String())
|
||||
continue
|
||||
}
|
||||
if polc != nil && !polc.IsAbsent() {
|
||||
evpool.logger.Debug("Found polc for potential amnesia evidence", "polc", polc)
|
||||
// we should not need to verify it if both the polc and potential amnesia evidence have already
|
||||
// been verified. We replace the potential amnesia evidence.
|
||||
ae := types.NewAmnesiaEvidence(pe, polc)
|
||||
err := evpool.AddEvidence(ae)
|
||||
if err != nil {
|
||||
evpool.logger.Error("Failed to create amnesia evidence from potential amnesia evidence", "err", err)
|
||||
// revert back to processing potential amnesia evidence
|
||||
exists = false
|
||||
} else {
|
||||
evpool.logger.Info("Formed amnesia evidence from own polc", "amnesiaEvidence", ae)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// stamp height that the evidence was received
|
||||
pe.HeightStamp = evpool.State().LastBlockHeight
|
||||
|
||||
// b) check if amnesia evidence can be made now or if we need to enact the trial period
|
||||
if !exists && pe.Primed(1, pe.HeightStamp) {
|
||||
evpool.logger.Debug("PotentialAmnesiaEvidence can be instantly upgraded")
|
||||
err := evpool.AddEvidence(types.NewAmnesiaEvidence(pe, types.NewEmptyPOLC()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !exists && evpool.State().LastBlockHeight+evpool.State().ConsensusParams.Evidence.ProofTrialPeriod <
|
||||
pe.Height()+evpool.State().ConsensusParams.Evidence.MaxAgeNumBlocks {
|
||||
// if we can't find a proof of lock change and we know that the trial period will finish before the
|
||||
// evidence has expired, then we commence the trial period by saving it in the awaiting bucket
|
||||
pbe, err := types.EvidenceToProto(pe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evBytes, err := pbe.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := keyAwaitingTrial(pe)
|
||||
err = evpool.evidenceStore.Set(key, evBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evpool.logger.Debug("Valid potential amnesia evidence has been added. Starting trial period",
|
||||
"ev", pe)
|
||||
// keep track of when the next pe has finished the trial period
|
||||
if evpool.nextEvidenceTrialEndedHeight == -1 {
|
||||
evpool.nextEvidenceTrialEndedHeight = pe.Height() + evpool.State().ConsensusParams.Evidence.ProofTrialPeriod
|
||||
}
|
||||
|
||||
// add to the broadcast list so it can continue to be gossiped
|
||||
evpool.evidenceList.PushBack(pe)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func evMapKey(ev types.Evidence) string {
|
||||
return string(ev.Hash())
|
||||
}
|
||||
@@ -724,11 +800,11 @@ func keyPending(evidence types.Evidence) []byte {
|
||||
return append([]byte{baseKeyPending}, keySuffix(evidence)...)
|
||||
}
|
||||
|
||||
func keyAwaiting(evidence types.Evidence) []byte {
|
||||
return append([]byte{baseKeyAwaiting}, keySuffix(evidence)...)
|
||||
func keyAwaitingTrial(evidence types.Evidence) []byte {
|
||||
return append([]byte{baseKeyAwaitingTrial}, keySuffix(evidence)...)
|
||||
}
|
||||
|
||||
func keyPOLC(polc types.ProofOfLockChange) []byte {
|
||||
func keyPOLC(polc *types.ProofOfLockChange) []byte {
|
||||
return keyPOLCFromHeightAndRound(polc.Height(), polc.Round())
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
"github.com/tendermint/tendermint/libs/bytes"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
@@ -211,10 +212,27 @@ func TestAddingAndPruningPOLC(t *testing.T) {
|
||||
blockStore = initializeBlockStore(blockStoreDB, state, valAddr)
|
||||
height = state.ConsensusParams.Evidence.MaxAgeNumBlocks * 2
|
||||
evidenceTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
firstBlockID = types.BlockID{
|
||||
Hash: tmrand.Bytes(tmhash.Size),
|
||||
PartSetHeader: types.PartSetHeader{
|
||||
Total: 1,
|
||||
Hash: tmrand.Bytes(tmhash.Size),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
val := types.NewMockPV()
|
||||
voteA := makeVote(1, 1, 0, val.PrivKey.PubKey().Address(), firstBlockID, evidenceTime)
|
||||
vA := voteA.ToProto()
|
||||
err := val.SignVote(evidenceChainID, vA)
|
||||
require.NoError(t, err)
|
||||
voteA.Signature = vA.Signature
|
||||
|
||||
pubKey, _ := types.NewMockPV().GetPubKey()
|
||||
polc := types.NewMockPOLC(1, evidenceTime, pubKey)
|
||||
polc := &types.ProofOfLockChange{
|
||||
Votes: []*types.Vote{voteA},
|
||||
PubKey: pubKey,
|
||||
}
|
||||
|
||||
pool, err := NewPool(stateDB, evidenceDB, blockStore)
|
||||
require.NoError(t, err)
|
||||
@@ -227,10 +245,10 @@ func TestAddingAndPruningPOLC(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, polc.Equal(newPolc))
|
||||
|
||||
// should not be able to retrieve
|
||||
// should not be able to retrieve because it doesn't exist
|
||||
emptyPolc, err := pool.RetrievePOLC(2, 1)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, types.ProofOfLockChange{}, emptyPolc)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, emptyPolc)
|
||||
|
||||
lastCommit := makeCommit(height-1, valAddr)
|
||||
block := types.MakeBlock(height, []types.Tx{}, lastCommit, []types.Evidence{})
|
||||
@@ -242,8 +260,8 @@ func TestAddingAndPruningPOLC(t *testing.T) {
|
||||
pool.Update(block, state)
|
||||
|
||||
emptyPolc, err = pool.RetrievePOLC(1, 1)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, types.ProofOfLockChange{}, emptyPolc)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, emptyPolc)
|
||||
|
||||
}
|
||||
|
||||
@@ -284,15 +302,20 @@ func TestRecoverPendingEvidence(t *testing.T) {
|
||||
assert.True(t, pool.IsPending(goodEvidence))
|
||||
}
|
||||
|
||||
func TestPotentialAmnesiaEvidence(t *testing.T) {
|
||||
// Comprehensive set of test cases relating to the adding, upgrading and overall
|
||||
// processing of PotentialAmnesiaEvidence and AmnesiaEvidence
|
||||
func TestAddingPotentialAmnesiaEvidence(t *testing.T) {
|
||||
var (
|
||||
val = types.NewMockPV()
|
||||
pubKey = val.PrivKey.PubKey()
|
||||
valSet = &types.ValidatorSet{
|
||||
val = types.NewMockPV()
|
||||
val2 = types.NewMockPV()
|
||||
pubKey = val.PrivKey.PubKey()
|
||||
pubKey2 = val2.PrivKey.PubKey()
|
||||
valSet = &types.ValidatorSet{
|
||||
Validators: []*types.Validator{
|
||||
val.ExtractIntoValidator(0),
|
||||
val.ExtractIntoValidator(1),
|
||||
val2.ExtractIntoValidator(3),
|
||||
},
|
||||
Proposer: val.ExtractIntoValidator(0),
|
||||
Proposer: val.ExtractIntoValidator(1),
|
||||
}
|
||||
height = int64(30)
|
||||
stateDB = initializeStateFromValidatorSet(valSet, height)
|
||||
@@ -318,38 +341,56 @@ func TestPotentialAmnesiaEvidence(t *testing.T) {
|
||||
evidenceTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
// TEST SETUP
|
||||
pool, err := NewPool(stateDB, evidenceDB, blockStore)
|
||||
require.NoError(t, err)
|
||||
|
||||
pool.SetLogger(log.TestingLogger())
|
||||
|
||||
polc := types.NewMockPOLC(25, evidenceTime, pubKey)
|
||||
err = pool.AddPOLC(polc)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = pool.RetrievePOLC(25, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
voteA := makeVote(25, 0, 0, pubKey.Address(), firstBlockID)
|
||||
voteA := makeVote(height, 0, 0, pubKey.Address(), firstBlockID, evidenceTime)
|
||||
vA := voteA.ToProto()
|
||||
err = val.SignVote(evidenceChainID, vA)
|
||||
voteA.Signature = vA.Signature
|
||||
require.NoError(t, err)
|
||||
voteB := makeVote(25, 1, 0, pubKey.Address(), secondBlockID)
|
||||
voteB := makeVote(height, 1, 0, pubKey.Address(), secondBlockID, evidenceTime.Add(3*time.Second))
|
||||
vB := voteB.ToProto()
|
||||
err = val.SignVote(evidenceChainID, vB)
|
||||
voteB.Signature = vB.Signature
|
||||
require.NoError(t, err)
|
||||
voteC := makeVote(25, 0, 0, pubKey.Address(), firstBlockID)
|
||||
voteC.Timestamp.Add(1 * time.Second)
|
||||
voteC := makeVote(height, 2, 0, pubKey.Address(), firstBlockID, evidenceTime.Add(2*time.Second))
|
||||
vC := voteC.ToProto()
|
||||
err = val.SignVote(evidenceChainID, vC)
|
||||
voteC.Signature = vC.Signature
|
||||
require.NoError(t, err)
|
||||
ev := types.PotentialAmnesiaEvidence{
|
||||
ev := &types.PotentialAmnesiaEvidence{
|
||||
VoteA: voteA,
|
||||
VoteB: voteB,
|
||||
}
|
||||
|
||||
polc := &types.ProofOfLockChange{
|
||||
Votes: []*types.Vote{voteB},
|
||||
PubKey: pubKey2,
|
||||
}
|
||||
err = pool.AddPOLC(polc)
|
||||
require.NoError(t, err)
|
||||
|
||||
polc, err = pool.RetrievePOLC(height, 1)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, polc)
|
||||
|
||||
secondValVote := makeVote(height, 1, 0, pubKey2.Address(), secondBlockID, evidenceTime.Add(1*time.Second))
|
||||
vv2 := secondValVote.ToProto()
|
||||
err = val2.SignVote(evidenceChainID, vv2)
|
||||
require.NoError(t, err)
|
||||
secondValVote.Signature = vv2.Signature
|
||||
|
||||
validPolc := &types.ProofOfLockChange{
|
||||
Votes: []*types.Vote{secondValVote},
|
||||
PubKey: pubKey,
|
||||
}
|
||||
|
||||
// CASE A
|
||||
pool.logger.Info("CASE A")
|
||||
// we expect the evidence pool to find the polc but log an error as the polc is not valid -> vote was
|
||||
// not from a validator in this set. However, an error isn't thrown because the evidence pool
|
||||
// should still be able to save the regular potential amnesia evidence.
|
||||
@@ -358,34 +399,90 @@ func TestPotentialAmnesiaEvidence(t *testing.T) {
|
||||
|
||||
// evidence requires trial period until it is available -> we expect no evidence to be returned
|
||||
assert.Equal(t, 0, len(pool.PendingEvidence(1)))
|
||||
assert.True(t, pool.IsOnTrial(ev))
|
||||
|
||||
nextHeight := pool.nextEvidenceTrialEndedHeight
|
||||
assert.Greater(t, nextHeight, int64(0))
|
||||
|
||||
// CASE B
|
||||
pool.logger.Info("CASE B")
|
||||
// evidence is not ready to be upgraded so we return the height we expect the evidence to be.
|
||||
nextHeight = pool.upgradePotentialAmnesiaEvidence()
|
||||
assert.Equal(t, height+pool.state.ConsensusParams.Evidence.ProofTrialPeriod, nextHeight)
|
||||
|
||||
// CASE C
|
||||
pool.logger.Info("CASE C")
|
||||
// now evidence is ready to be upgraded to amnesia evidence -> we expect -1 to be the next height as their is
|
||||
// no more pending potential amnesia evidence left
|
||||
pool.state.LastBlockHeight = nextHeight
|
||||
nextHeight = pool.upgradePotentialAmnesiaEvidence()
|
||||
assert.Equal(t, int64(-1), nextHeight)
|
||||
lastCommit := makeCommit(height+1, pubKey.Address())
|
||||
block := types.MakeBlock(height+2, []types.Tx{}, lastCommit, []types.Evidence{})
|
||||
state.LastBlockHeight = height + 2
|
||||
|
||||
pool.Update(block, state)
|
||||
assert.Equal(t, int64(-1), pool.nextEvidenceTrialEndedHeight)
|
||||
|
||||
assert.Equal(t, 1, len(pool.PendingEvidence(1)))
|
||||
|
||||
// CASE D
|
||||
pool.logger.Info("CASE D")
|
||||
// evidence of voting back in the past which is instantly punishable -> amnesia evidence is made directly
|
||||
voteA.Timestamp.Add(1 * time.Second)
|
||||
|
||||
ev2 := types.PotentialAmnesiaEvidence{
|
||||
VoteA: voteB,
|
||||
VoteB: voteC,
|
||||
ev2 := &types.PotentialAmnesiaEvidence{
|
||||
VoteA: voteC,
|
||||
VoteB: voteB,
|
||||
}
|
||||
err = pool.AddEvidence(ev2)
|
||||
assert.NoError(t, err)
|
||||
expectedAe := &types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: ev2,
|
||||
Polc: types.NewEmptyPOLC(),
|
||||
}
|
||||
|
||||
assert.True(t, pool.IsPending(expectedAe))
|
||||
assert.Equal(t, 2, len(pool.AllPendingEvidence()))
|
||||
|
||||
// CASE E
|
||||
pool.logger.Info("CASE E")
|
||||
// test for receiving amnesia evidence
|
||||
ae := types.NewAmnesiaEvidence(ev, types.NewEmptyPOLC())
|
||||
// we need to run the trial period ourselves so amnesia evidence should not be added, instead
|
||||
// we should extract out the potential amnesia evidence and trying to add that before realising
|
||||
// that we already have it -> no error
|
||||
err = pool.AddEvidence(ae)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(pool.AllPendingEvidence()))
|
||||
|
||||
voteD := makeVote(height, 2, 0, pubKey.Address(), firstBlockID, evidenceTime.Add(4*time.Second))
|
||||
vD := voteD.ToProto()
|
||||
err = val.SignVote(evidenceChainID, vD)
|
||||
require.NoError(t, err)
|
||||
voteD.Signature = vD.Signature
|
||||
|
||||
// CASE F
|
||||
pool.logger.Info("CASE F")
|
||||
// a new amnesia evidence is seen. It has an empty polc so we should extract the potential amnesia evidence
|
||||
// and start our own trial
|
||||
newPe := types.NewPotentialAmnesiaEvidence(voteB, voteD)
|
||||
newAe := types.NewAmnesiaEvidence(newPe, types.NewEmptyPOLC())
|
||||
err = pool.AddEvidence(newAe)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(pool.AllPendingEvidence()))
|
||||
assert.True(t, pool.IsOnTrial(newPe))
|
||||
|
||||
// CASE G
|
||||
pool.logger.Info("CASE G")
|
||||
// Finally, we receive an amnesia evidence containing a valid polc for an earlier potential amnesia evidence
|
||||
// that we have already upgraded to. We should ad this new amnesia evidence in replace of the prior
|
||||
// amnesia evidence with an empty polc that we have
|
||||
aeWithPolc := &types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: ev,
|
||||
Polc: validPolc,
|
||||
}
|
||||
err = pool.AddEvidence(aeWithPolc)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, pool.IsPending(aeWithPolc))
|
||||
assert.Equal(t, 2, len(pool.AllPendingEvidence()))
|
||||
t.Log(pool.AllPendingEvidence())
|
||||
|
||||
}
|
||||
|
||||
func initializeStateFromValidatorSet(valSet *types.ValidatorSet, height int64) dbm.DB {
|
||||
@@ -465,13 +562,14 @@ func makeCommit(height int64, valAddr []byte) *types.Commit {
|
||||
return types.NewCommit(height, 0, types.BlockID{}, commitSigs)
|
||||
}
|
||||
|
||||
func makeVote(height int64, round, index int32, addr bytes.HexBytes, blockID types.BlockID) *types.Vote {
|
||||
func makeVote(height int64, round, index int32, addr bytes.HexBytes,
|
||||
blockID types.BlockID, time time.Time) *types.Vote {
|
||||
return &types.Vote{
|
||||
Type: tmproto.SignedMsgType(2),
|
||||
Height: height,
|
||||
Round: round,
|
||||
BlockID: blockID,
|
||||
Timestamp: time.Now(),
|
||||
Timestamp: time,
|
||||
ValidatorAddress: addr,
|
||||
ValidatorIndex: index,
|
||||
}
|
||||
|
||||
@@ -975,7 +975,7 @@ func (c *Client) compareNewHeaderWithWitnesses(h *types.SignedHeader) error {
|
||||
headerMatched = true
|
||||
case ErrConflictingHeaders: // potential fork
|
||||
c.logger.Error(err.Error(), "witness", e.Witness)
|
||||
c.sendConflictingHeadersEvidence(types.ConflictingHeadersEvidence{H1: h, H2: e.H2})
|
||||
c.sendConflictingHeadersEvidence(&types.ConflictingHeadersEvidence{H1: h, H2: e.H2})
|
||||
lastErrConfHeaders = e
|
||||
case errBadWitness:
|
||||
c.logger.Error(err.Error(), "witness", c.witnesses[e.WitnessIndex])
|
||||
@@ -1188,7 +1188,7 @@ func (c *Client) validateValidatorSet(vals *types.ValidatorSet) error {
|
||||
//
|
||||
// Evidence needs to be submitted to all full nodes since there's no way to
|
||||
// determine which full node is correct (honest).
|
||||
func (c *Client) sendConflictingHeadersEvidence(ev types.ConflictingHeadersEvidence) {
|
||||
func (c *Client) sendConflictingHeadersEvidence(ev *types.ConflictingHeadersEvidence) {
|
||||
err := c.primary.ReportEvidence(ev)
|
||||
if err != nil {
|
||||
c.logger.Error("Failed to report evidence to primary", "ev", ev, "primary", c.primary)
|
||||
|
||||
@@ -1083,7 +1083,7 @@ func TestClientReportsConflictingHeadersEvidence(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check evidence was sent to both full nodes.
|
||||
ev := types.ConflictingHeadersEvidence{H1: h2, H2: altH2}
|
||||
ev := &types.ConflictingHeadersEvidence{H1: h2, H2: altH2}
|
||||
assert.True(t, fullNode2.HasEvidence(ev))
|
||||
assert.True(t, fullNode.HasEvidence(ev))
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func TestBroadcastEvidence_ConflictingHeadersEvidence(t *testing.T) {
|
||||
t.Logf("h1 AppHash: %X", h1.AppHash)
|
||||
t.Logf("h2 AppHash: %X", h2.AppHash)
|
||||
|
||||
ev := types.ConflictingHeadersEvidence{
|
||||
ev := &types.ConflictingHeadersEvidence{
|
||||
H1: &h1.SignedHeader,
|
||||
H2: h2,
|
||||
}
|
||||
|
||||
@@ -142,15 +142,16 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block
|
||||
continue
|
||||
}
|
||||
}
|
||||
// if we don't already have amnesia evidence we need to add it to start our own timer unless
|
||||
// if we don't already have amnesia evidence we need to add it to start our own trial period unless
|
||||
// a) a valid polc has already been attached
|
||||
// b) the accused node voted back on an earlier round
|
||||
if ae, ok := ev.(types.AmnesiaEvidence); ok && ae.Polc.IsAbsent() && ae.PotentialAmnesiaEvidence.VoteA.Round <
|
||||
if ae, ok := ev.(*types.AmnesiaEvidence); ok && ae.Polc.IsAbsent() && ae.PotentialAmnesiaEvidence.VoteA.Round <
|
||||
ae.PotentialAmnesiaEvidence.VoteB.Round {
|
||||
if err := evidencePool.AddEvidence(ae); err != nil {
|
||||
if err := evidencePool.AddEvidence(ae.PotentialAmnesiaEvidence); err != nil {
|
||||
return types.NewErrEvidenceInvalid(ev,
|
||||
fmt.Errorf("unknown amnesia evidence, trying to add to evidence pool, err: %w", err))
|
||||
}
|
||||
return types.NewErrEvidenceInvalid(ev, errors.New("amnesia evidence is new and hasn't undergone trial period yet"))
|
||||
}
|
||||
|
||||
var header *types.Header
|
||||
@@ -208,7 +209,7 @@ func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence, commit
|
||||
state.LastBlockTime.Add(evidenceParams.MaxAgeDuration),
|
||||
)
|
||||
}
|
||||
if ev, ok := evidence.(types.LunaticValidatorEvidence); ok {
|
||||
if ev, ok := evidence.(*types.LunaticValidatorEvidence); ok {
|
||||
if err := ev.VerifyHeader(committedHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -227,7 +228,7 @@ func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence, commit
|
||||
// For PhantomValidatorEvidence, check evidence.Address was not part of the
|
||||
// validator set at height evidence.Height, but was a validator before OR
|
||||
// after.
|
||||
if phve, ok := evidence.(types.PhantomValidatorEvidence); ok {
|
||||
if phve, ok := evidence.(*types.PhantomValidatorEvidence); ok {
|
||||
// confirm that it hasn't been forged
|
||||
|
||||
_, val = valset.GetByAddress(addr)
|
||||
@@ -253,7 +254,7 @@ func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence, commit
|
||||
return fmt.Errorf("phantom validator %X not found", addr)
|
||||
}
|
||||
} else {
|
||||
if ae, ok := evidence.(types.AmnesiaEvidence); ok {
|
||||
if ae, ok := evidence.(*types.AmnesiaEvidence); ok {
|
||||
// check the validator set against the polc to make sure that a majority of valid votes was reached
|
||||
if !ae.Polc.IsAbsent() {
|
||||
err = ae.Polc.ValidateVotes(valset, state.ChainID)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package state_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -359,7 +358,7 @@ var blockID = types.BlockID{
|
||||
},
|
||||
}
|
||||
|
||||
func TestValidateAmnesiaEvidence(t *testing.T) {
|
||||
func TestValidateUnseenAmnesiaEvidence(t *testing.T) {
|
||||
var height int64 = 1
|
||||
state, stateDB, vals := makeState(1, int(height))
|
||||
addr, val := state.Validators.GetByIndex(0)
|
||||
@@ -373,18 +372,20 @@ func TestValidateAmnesiaEvidence(t *testing.T) {
|
||||
err = vals[val.Address.String()].SignVote(chainID, vB)
|
||||
voteB.Signature = vB.Signature
|
||||
require.NoError(t, err)
|
||||
ae := types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: types.PotentialAmnesiaEvidence{
|
||||
VoteA: voteA,
|
||||
VoteB: voteB,
|
||||
},
|
||||
Polc: types.EmptyPOLC(),
|
||||
pe := &types.PotentialAmnesiaEvidence{
|
||||
VoteA: voteA,
|
||||
VoteB: voteB,
|
||||
}
|
||||
ae := &types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: pe,
|
||||
Polc: types.NewEmptyPOLC(),
|
||||
}
|
||||
|
||||
evpool := &mocks.EvidencePool{}
|
||||
evpool.On("IsPending", ae).Return(false)
|
||||
evpool.On("IsCommitted", ae).Return(false)
|
||||
evpool.On("AddEvidence", ae).Return(fmt.Errorf("test error"))
|
||||
evpool.On("AddEvidence", ae).Return(nil)
|
||||
evpool.On("AddEvidence", pe).Return(nil)
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateDB, log.TestingLogger(),
|
||||
@@ -396,13 +397,60 @@ func TestValidateAmnesiaEvidence(t *testing.T) {
|
||||
block.Evidence.Evidence = []types.Evidence{ae}
|
||||
block.EvidenceHash = block.Evidence.Hash()
|
||||
err = blockExec.ValidateBlock(state, block)
|
||||
|
||||
errMsg := "Invalid evidence: unknown amnesia evidence, trying to add to evidence pool, err: test error"
|
||||
// if we don't have this evidence and it is has an empty polc then we expect to
|
||||
// start our own trial period first
|
||||
errMsg := "Invalid evidence: amnesia evidence is new and hasn't undergone trial period yet."
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, err.Error()[:len(errMsg)], errMsg)
|
||||
assert.Equal(t, errMsg, err.Error()[:len(errMsg)])
|
||||
}
|
||||
}
|
||||
|
||||
// Amnesia Evidence can be directly approved without needing to undergo the trial period
|
||||
func TestValidatePrimedAmnesiaEvidence(t *testing.T) {
|
||||
var height int64 = 1
|
||||
state, stateDB, vals := makeState(1, int(height))
|
||||
addr, val := state.Validators.GetByIndex(0)
|
||||
voteA := makeVote(height, 1, 0, addr, blockID)
|
||||
voteA.Timestamp = time.Now().Add(1 * time.Minute)
|
||||
vA := voteA.ToProto()
|
||||
err := vals[val.Address.String()].SignVote(chainID, vA)
|
||||
require.NoError(t, err)
|
||||
voteA.Signature = vA.Signature
|
||||
voteB := makeVote(height, 2, 0, addr, types.BlockID{})
|
||||
vB := voteB.ToProto()
|
||||
err = vals[val.Address.String()].SignVote(chainID, vB)
|
||||
voteB.Signature = vB.Signature
|
||||
require.NoError(t, err)
|
||||
pe := &types.PotentialAmnesiaEvidence{
|
||||
VoteA: voteB,
|
||||
VoteB: voteA,
|
||||
}
|
||||
ae := &types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: pe,
|
||||
Polc: types.NewEmptyPOLC(),
|
||||
}
|
||||
|
||||
evpool := &mocks.EvidencePool{}
|
||||
evpool.On("IsPending", ae).Return(false)
|
||||
evpool.On("IsCommitted", ae).Return(false)
|
||||
evpool.On("AddEvidence", ae).Return(nil)
|
||||
evpool.On("AddEvidence", pe).Return(nil)
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateDB, log.TestingLogger(),
|
||||
nil,
|
||||
nil,
|
||||
evpool)
|
||||
// A block with a couple pieces of evidence passes.
|
||||
block := makeBlock(state, height)
|
||||
block.Evidence.Evidence = []types.Evidence{ae}
|
||||
block.EvidenceHash = block.Evidence.Hash()
|
||||
err = blockExec.ValidateBlock(state, block)
|
||||
// No error because this type of amnesia evidence is punishable
|
||||
// without the need of a trial period
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestVerifyEvidenceWrongAddress(t *testing.T) {
|
||||
var height int64 = 1
|
||||
state, stateDB, _ := makeState(1, int(height))
|
||||
@@ -459,13 +507,13 @@ func TestVerifyEvidenceWithAmnesiaEvidence(t *testing.T) {
|
||||
voteC.Signature = vC.Signature
|
||||
require.NoError(t, err)
|
||||
//var ae types.Evidence
|
||||
badAe := types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: types.PotentialAmnesiaEvidence{
|
||||
badAe := &types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: &types.PotentialAmnesiaEvidence{
|
||||
VoteA: voteA,
|
||||
VoteB: voteB,
|
||||
},
|
||||
Polc: types.ProofOfLockChange{
|
||||
Votes: []types.Vote{*voteC},
|
||||
Polc: &types.ProofOfLockChange{
|
||||
Votes: []*types.Vote{voteC},
|
||||
PubKey: val.PubKey,
|
||||
},
|
||||
}
|
||||
@@ -487,25 +535,25 @@ func TestVerifyEvidenceWithAmnesiaEvidence(t *testing.T) {
|
||||
voteE.Signature = vE.Signature
|
||||
require.NoError(t, err)
|
||||
|
||||
goodAe := types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: types.PotentialAmnesiaEvidence{
|
||||
goodAe := &types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: &types.PotentialAmnesiaEvidence{
|
||||
VoteA: voteA,
|
||||
VoteB: voteB,
|
||||
},
|
||||
Polc: types.ProofOfLockChange{
|
||||
Votes: []types.Vote{*voteC, *voteD, *voteE},
|
||||
Polc: &types.ProofOfLockChange{
|
||||
Votes: []*types.Vote{voteC, voteD, voteE},
|
||||
PubKey: val.PubKey,
|
||||
},
|
||||
}
|
||||
err = sm.VerifyEvidence(stateDB, state, goodAe, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
goodAe = types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: types.PotentialAmnesiaEvidence{
|
||||
goodAe = &types.AmnesiaEvidence{
|
||||
PotentialAmnesiaEvidence: &types.PotentialAmnesiaEvidence{
|
||||
VoteA: voteA,
|
||||
VoteB: voteB,
|
||||
},
|
||||
Polc: types.EmptyPOLC(),
|
||||
Polc: types.NewEmptyPOLC(),
|
||||
}
|
||||
err = sm.VerifyEvidence(stateDB, state, goodAe, nil)
|
||||
assert.NoError(t, err)
|
||||
@@ -537,7 +585,7 @@ func TestVerifyEvidenceWithLunaticValidatorEvidence(t *testing.T) {
|
||||
err := vals[val.Address.String()].SignVote(chainID, v)
|
||||
vote.Signature = v.Signature
|
||||
require.NoError(t, err)
|
||||
ev := types.LunaticValidatorEvidence{
|
||||
ev := &types.LunaticValidatorEvidence{
|
||||
Header: h,
|
||||
Vote: vote,
|
||||
InvalidHeaderField: "ConsensusHash",
|
||||
@@ -559,7 +607,7 @@ func TestVerifyEvidenceWithPhantomValidatorEvidence(t *testing.T) {
|
||||
err := vals[val.Address.String()].SignVote(chainID, v)
|
||||
vote.Signature = v.Signature
|
||||
require.NoError(t, err)
|
||||
ev := types.PhantomValidatorEvidence{
|
||||
ev := &types.PhantomValidatorEvidence{
|
||||
Vote: vote,
|
||||
LastHeightValidatorWasInSet: 1,
|
||||
}
|
||||
@@ -577,7 +625,7 @@ func TestVerifyEvidenceWithPhantomValidatorEvidence(t *testing.T) {
|
||||
err = privVal.SignVote(chainID, v2)
|
||||
vote2.Signature = v2.Signature
|
||||
require.NoError(t, err)
|
||||
ev = types.PhantomValidatorEvidence{
|
||||
ev = &types.PhantomValidatorEvidence{
|
||||
Vote: vote2,
|
||||
LastHeightValidatorWasInSet: 1,
|
||||
}
|
||||
@@ -588,7 +636,7 @@ func TestVerifyEvidenceWithPhantomValidatorEvidence(t *testing.T) {
|
||||
assert.Equal(t, "last time validator was in the set at height 1, min: 2", err.Error())
|
||||
}
|
||||
|
||||
ev = types.PhantomValidatorEvidence{
|
||||
ev = &types.PhantomValidatorEvidence{
|
||||
Vote: vote2,
|
||||
LastHeightValidatorWasInSet: 2,
|
||||
}
|
||||
@@ -615,7 +663,7 @@ func TestVerifyEvidenceWithPhantomValidatorEvidence(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
stateDB.Set(valKey, bz)
|
||||
ev = types.PhantomValidatorEvidence{
|
||||
ev = &types.PhantomValidatorEvidence{
|
||||
Vote: vote2,
|
||||
LastHeightValidatorWasInSet: 2,
|
||||
}
|
||||
|
||||
@@ -90,11 +90,11 @@ func (b *Block) ValidateBasic() error {
|
||||
// NOTE: b.Evidence.Evidence may be nil, but we're just looping.
|
||||
for i, ev := range b.Evidence.Evidence {
|
||||
switch ev.(type) {
|
||||
case *ConflictingHeadersEvidence, ConflictingHeadersEvidence:
|
||||
case *ConflictingHeadersEvidence:
|
||||
// ConflictingHeadersEvidence must be broken up in pieces and never
|
||||
// committed as a single piece.
|
||||
return fmt.Errorf("found ConflictingHeadersEvidence (#%d)", i)
|
||||
case *PotentialAmnesiaEvidence, PotentialAmnesiaEvidence:
|
||||
case *PotentialAmnesiaEvidence:
|
||||
// PotentialAmnesiaEvidence does not contribute to anything on its own, so
|
||||
// reject it as well.
|
||||
return fmt.Errorf("found PotentialAmnesiaEvidence (#%d)", i)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -222,11 +222,7 @@ func TestLunaticValidatorEvidence(t *testing.T) {
|
||||
|
||||
header.Time = bTime
|
||||
|
||||
ev := &LunaticValidatorEvidence{
|
||||
Header: header,
|
||||
Vote: vote,
|
||||
InvalidHeaderField: "AppHash",
|
||||
}
|
||||
ev := NewLunaticValidatorEvidence(header, vote, "AppHash")
|
||||
|
||||
assert.Equal(t, header.Height, ev.Height())
|
||||
assert.Equal(t, defaultVoteTime, ev.Time())
|
||||
@@ -253,10 +249,7 @@ func TestPhantomValidatorEvidence(t *testing.T) {
|
||||
vote = makeVote(t, val, header.ChainID, 0, header.Height, 0, 2, blockID, defaultVoteTime)
|
||||
)
|
||||
|
||||
ev := &PhantomValidatorEvidence{
|
||||
Vote: vote,
|
||||
LastHeightValidatorWasInSet: header.Height - 1,
|
||||
}
|
||||
ev := NewPhantomValidatorEvidence(vote, header.Height-1)
|
||||
|
||||
assert.Equal(t, header.Height, ev.Height())
|
||||
assert.Equal(t, defaultVoteTime, ev.Time())
|
||||
@@ -315,16 +308,16 @@ func TestConflictingHeadersEvidence(t *testing.T) {
|
||||
}, height, 1, voteSet2, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
ev := &ConflictingHeadersEvidence{
|
||||
H1: &SignedHeader{
|
||||
Header: header1,
|
||||
Commit: commit1,
|
||||
},
|
||||
H2: &SignedHeader{
|
||||
Header: header2,
|
||||
Commit: commit2,
|
||||
},
|
||||
h1 := &SignedHeader{
|
||||
Header: header1,
|
||||
Commit: commit1,
|
||||
}
|
||||
h2 := &SignedHeader{
|
||||
Header: header2,
|
||||
Commit: commit2,
|
||||
}
|
||||
|
||||
ev := NewConflictingHeadersEvidence(h1, h2)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
ev.Address()
|
||||
@@ -356,13 +349,11 @@ func TestPotentialAmnesiaEvidence(t *testing.T) {
|
||||
blockID = makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
||||
blockID2 = makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
||||
vote1 = makeVote(t, val, chainID, 0, height, 0, 2, blockID, defaultVoteTime)
|
||||
vote2 = makeVote(t, val, chainID, 0, height, 1, 2, blockID2, defaultVoteTime.Add(1*time.Minute))
|
||||
vote2 = makeVote(t, val, chainID, 0, height, 1, 2, blockID2, defaultVoteTime.Add(1*time.Second))
|
||||
vote3 = makeVote(t, val, chainID, 0, height, 2, 2, blockID, defaultVoteTime)
|
||||
)
|
||||
|
||||
ev := &PotentialAmnesiaEvidence{
|
||||
VoteA: vote1,
|
||||
VoteB: vote2,
|
||||
}
|
||||
ev := NewPotentialAmnesiaEvidence(vote1, vote2)
|
||||
|
||||
assert.Equal(t, height, ev.Height())
|
||||
assert.Equal(t, vote2.Timestamp, ev.Time())
|
||||
@@ -379,6 +370,35 @@ func TestPotentialAmnesiaEvidence(t *testing.T) {
|
||||
assert.True(t, ev.Equal(ev))
|
||||
assert.NoError(t, ev.ValidateBasic())
|
||||
assert.NotEmpty(t, ev.String())
|
||||
|
||||
ev2 := &PotentialAmnesiaEvidence{
|
||||
VoteA: vote1,
|
||||
VoteB: vote2,
|
||||
HeightStamp: 5,
|
||||
}
|
||||
|
||||
assert.True(t, ev.Equal(ev2))
|
||||
assert.Equal(t, ev.Hash(), ev2.Hash())
|
||||
|
||||
ev3 := &PotentialAmnesiaEvidence{
|
||||
VoteA: vote2,
|
||||
VoteB: vote1,
|
||||
}
|
||||
|
||||
assert.Error(t, ev3.ValidateBasic())
|
||||
|
||||
ev3 = NewPotentialAmnesiaEvidence(vote2, vote1)
|
||||
assert.True(t, ev3.Equal(ev))
|
||||
|
||||
ev4 := &PotentialAmnesiaEvidence{
|
||||
VoteA: vote3,
|
||||
VoteB: vote2,
|
||||
}
|
||||
|
||||
assert.NoError(t, ev4.ValidateBasic())
|
||||
assert.NotEqual(t, ev.Hash(), ev4.Hash())
|
||||
assert.False(t, ev.Equal(ev4))
|
||||
|
||||
}
|
||||
|
||||
func TestProofOfLockChange(t *testing.T) {
|
||||
@@ -390,7 +410,8 @@ func TestProofOfLockChange(t *testing.T) {
|
||||
voteSet, valSet, privValidators, blockID := buildVoteSet(height, 1, 3, 7, 0, tmproto.PrecommitType)
|
||||
pubKey, err := privValidators[7].GetPubKey()
|
||||
require.NoError(t, err)
|
||||
polc := makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
polc, err := NewPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, height, polc.Height())
|
||||
assert.NoError(t, polc.ValidateBasic())
|
||||
@@ -410,34 +431,34 @@ func TestProofOfLockChange(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
|
||||
// test validate basic on a set of bad cases
|
||||
var badPOLCs []ProofOfLockChange
|
||||
var badPOLCs []*ProofOfLockChange
|
||||
// 2: node has already voted in next round
|
||||
pubKey, err = privValidators[0].GetPubKey()
|
||||
require.NoError(t, err)
|
||||
polc2 := makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
polc2 := newPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badPOLCs = append(badPOLCs, polc2)
|
||||
// 3: one vote was from a different round
|
||||
voteSet, _, privValidators, blockID = buildVoteSet(height, 1, 3, 7, 0, tmproto.PrecommitType)
|
||||
pubKey, err = privValidators[7].GetPubKey()
|
||||
require.NoError(t, err)
|
||||
polc = makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
polc = newPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badVote := makeVote(t, privValidators[8], chainID, 8, height, 2, 2, blockID, defaultVoteTime)
|
||||
polc.Votes = append(polc.Votes, *badVote)
|
||||
polc.Votes = append(polc.Votes, badVote)
|
||||
badPOLCs = append(badPOLCs, polc)
|
||||
// 4: one vote was from a different height
|
||||
polc = makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
polc = newPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badVote = makeVote(t, privValidators[8], chainID, 8, height+1, 1, 2, blockID, defaultVoteTime)
|
||||
polc.Votes = append(polc.Votes, *badVote)
|
||||
polc.Votes = append(polc.Votes, badVote)
|
||||
badPOLCs = append(badPOLCs, polc)
|
||||
// 5: one vote was from a different vote type
|
||||
polc = makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
polc = newPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badVote = makeVote(t, privValidators[8], chainID, 8, height, 1, 1, blockID, defaultVoteTime)
|
||||
polc.Votes = append(polc.Votes, *badVote)
|
||||
polc.Votes = append(polc.Votes, badVote)
|
||||
badPOLCs = append(badPOLCs, polc)
|
||||
// 5: one of the votes was for a nil block
|
||||
polc = makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
polc = newPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badVote = makeVote(t, privValidators[8], chainID, 8, height, 1, 2, BlockID{}, defaultVoteTime)
|
||||
polc.Votes = append(polc.Votes, *badVote)
|
||||
polc.Votes = append(polc.Votes, badVote)
|
||||
badPOLCs = append(badPOLCs, polc)
|
||||
|
||||
for idx, polc := range badPOLCs {
|
||||
@@ -467,17 +488,17 @@ func TestAmnesiaEvidence(t *testing.T) {
|
||||
vote2 = makeVote(t, val, chainID, 7, height, 1, 2, blockID,
|
||||
time.Now().Add(time.Second))
|
||||
vote3 = makeVote(t, val, chainID, 7, height, 2, 2, blockID2, time.Now())
|
||||
polc = makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
polc = newPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
)
|
||||
|
||||
require.False(t, polc.IsAbsent())
|
||||
|
||||
pe := PotentialAmnesiaEvidence{
|
||||
pe := &PotentialAmnesiaEvidence{
|
||||
VoteA: vote1,
|
||||
VoteB: vote2,
|
||||
}
|
||||
|
||||
emptyAmnesiaEvidence := MakeAmnesiaEvidence(pe, EmptyPOLC())
|
||||
emptyAmnesiaEvidence := NewAmnesiaEvidence(pe, NewEmptyPOLC())
|
||||
|
||||
assert.NoError(t, emptyAmnesiaEvidence.ValidateBasic())
|
||||
violated, reason := emptyAmnesiaEvidence.ViolatedConsensus()
|
||||
@@ -486,7 +507,7 @@ func TestAmnesiaEvidence(t *testing.T) {
|
||||
}
|
||||
assert.NoError(t, emptyAmnesiaEvidence.Verify(chainID, pubKey))
|
||||
|
||||
completeAmnesiaEvidence := MakeAmnesiaEvidence(pe, polc)
|
||||
completeAmnesiaEvidence := NewAmnesiaEvidence(pe, polc)
|
||||
|
||||
assert.NoError(t, completeAmnesiaEvidence.ValidateBasic())
|
||||
violated, reason = completeAmnesiaEvidence.ViolatedConsensus()
|
||||
@@ -497,40 +518,41 @@ func TestAmnesiaEvidence(t *testing.T) {
|
||||
assert.NoError(t, completeAmnesiaEvidence.Polc.ValidateVotes(valSet, chainID))
|
||||
|
||||
assert.True(t, completeAmnesiaEvidence.Equal(emptyAmnesiaEvidence))
|
||||
assert.Equal(t, completeAmnesiaEvidence.Hash(), emptyAmnesiaEvidence.Hash())
|
||||
assert.NotEmpty(t, completeAmnesiaEvidence.Hash())
|
||||
assert.NotEmpty(t, completeAmnesiaEvidence.Bytes())
|
||||
|
||||
pe2 := PotentialAmnesiaEvidence{
|
||||
pe2 := &PotentialAmnesiaEvidence{
|
||||
VoteA: vote3,
|
||||
VoteB: vote2,
|
||||
}
|
||||
|
||||
// validator has incorrectly voted for a previous round after voting for a later round
|
||||
ae := MakeAmnesiaEvidence(pe2, EmptyPOLC())
|
||||
ae := NewAmnesiaEvidence(pe2, NewEmptyPOLC())
|
||||
assert.NoError(t, ae.ValidateBasic())
|
||||
violated, reason = ae.ViolatedConsensus()
|
||||
if assert.True(t, violated) {
|
||||
assert.Equal(t, reason, "validator went back and voted on a previous round")
|
||||
}
|
||||
|
||||
var badAE []AmnesiaEvidence
|
||||
var badAE []*AmnesiaEvidence
|
||||
// 1) Polc is at an incorrect height
|
||||
voteSet, _, _ = buildVoteSetForBlock(height+1, 1, 2, 7, 0, tmproto.PrecommitType, blockID)
|
||||
polc = makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badAE = append(badAE, MakeAmnesiaEvidence(pe, polc))
|
||||
polc = newPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badAE = append(badAE, NewAmnesiaEvidence(pe, polc))
|
||||
// 2) Polc is of a later round
|
||||
voteSet, _, _ = buildVoteSetForBlock(height, 2, 2, 7, 0, tmproto.PrecommitType, blockID)
|
||||
polc = makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badAE = append(badAE, MakeAmnesiaEvidence(pe, polc))
|
||||
polc = newPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badAE = append(badAE, NewAmnesiaEvidence(pe, polc))
|
||||
// 3) Polc has a different public key
|
||||
voteSet, _, privValidators = buildVoteSetForBlock(height, 1, 2, 7, 0, tmproto.PrecommitType, blockID)
|
||||
pubKey2, _ := privValidators[7].GetPubKey()
|
||||
polc = makePOLCFromVoteSet(voteSet, pubKey2, blockID)
|
||||
badAE = append(badAE, MakeAmnesiaEvidence(pe, polc))
|
||||
polc = newPOLCFromVoteSet(voteSet, pubKey2, blockID)
|
||||
badAE = append(badAE, NewAmnesiaEvidence(pe, polc))
|
||||
// 4) Polc has a different block ID
|
||||
voteSet, _, _, blockID = buildVoteSet(height, 1, 2, 7, 0, tmproto.PrecommitType)
|
||||
polc = makePOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badAE = append(badAE, MakeAmnesiaEvidence(pe, polc))
|
||||
polc = newPOLCFromVoteSet(voteSet, pubKey, blockID)
|
||||
badAE = append(badAE, NewAmnesiaEvidence(pe, polc))
|
||||
|
||||
for idx, ae := range badAE {
|
||||
t.Log(ae.ValidateBasic())
|
||||
@@ -638,58 +660,56 @@ func TestEvidenceProto(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
testName string
|
||||
evidence Evidence
|
||||
wantErr bool
|
||||
wantErr2 bool
|
||||
testName string
|
||||
evidence Evidence
|
||||
toProtoErr bool
|
||||
fromProtoErr bool
|
||||
}{
|
||||
{"&DuplicateVoteEvidence empty fail", &DuplicateVoteEvidence{}, false, true},
|
||||
{"&DuplicateVoteEvidence nil voteB", &DuplicateVoteEvidence{VoteA: v, VoteB: nil}, false, true},
|
||||
{"&DuplicateVoteEvidence nil voteA", &DuplicateVoteEvidence{VoteA: nil, VoteB: v}, false, true},
|
||||
{"&DuplicateVoteEvidence success", &DuplicateVoteEvidence{VoteA: v2, VoteB: v}, false, false},
|
||||
{"&ConflictingHeadersEvidence empty fail", &ConflictingHeadersEvidence{}, false, true},
|
||||
{"&ConflictingHeadersEvidence nil H2", &ConflictingHeadersEvidence{H1: h1, H2: nil}, false, true},
|
||||
{"&ConflictingHeadersEvidence nil H1", &ConflictingHeadersEvidence{H1: nil, H2: h2}, false, true},
|
||||
{"ConflictingHeadersEvidence empty fail", ConflictingHeadersEvidence{}, false, true},
|
||||
{"ConflictingHeadersEvidence nil H2", ConflictingHeadersEvidence{H1: h1, H2: nil}, false, true},
|
||||
{"ConflictingHeadersEvidence nil H1", ConflictingHeadersEvidence{H1: nil, H2: h2}, false, true},
|
||||
{"ConflictingHeadersEvidence success", ConflictingHeadersEvidence{H1: h1, H2: h2}, false, false},
|
||||
{"LunaticValidatorEvidence empty fail", LunaticValidatorEvidence{}, false, true},
|
||||
{"LunaticValidatorEvidence only header fail", LunaticValidatorEvidence{Header: header1}, false, true},
|
||||
{"LunaticValidatorEvidence only vote fail", LunaticValidatorEvidence{Vote: v}, false, true},
|
||||
{"LunaticValidatorEvidence header & vote fail", LunaticValidatorEvidence{Header: header1, Vote: v}, false, true},
|
||||
{"LunaticValidatorEvidence success", LunaticValidatorEvidence{Header: header1,
|
||||
{"nil fail", nil, true, true},
|
||||
{"DuplicateVoteEvidence empty fail", &DuplicateVoteEvidence{}, false, true},
|
||||
{"DuplicateVoteEvidence nil voteB", &DuplicateVoteEvidence{VoteA: v, VoteB: nil}, false, true},
|
||||
{"DuplicateVoteEvidence nil voteA", &DuplicateVoteEvidence{VoteA: nil, VoteB: v}, false, true},
|
||||
{"DuplicateVoteEvidence success", &DuplicateVoteEvidence{VoteA: v2, VoteB: v}, false, false},
|
||||
{"ConflictingHeadersEvidence empty fail", &ConflictingHeadersEvidence{}, false, true},
|
||||
{"ConflictingHeadersEvidence nil H2", &ConflictingHeadersEvidence{H1: h1, H2: nil}, false, true},
|
||||
{"ConflictingHeadersEvidence nil H1", &ConflictingHeadersEvidence{H1: nil, H2: h2}, false, true},
|
||||
{"ConflictingHeadersEvidence success", &ConflictingHeadersEvidence{H1: h1, H2: h2}, false, false},
|
||||
{"LunaticValidatorEvidence success", &LunaticValidatorEvidence{Header: header1,
|
||||
Vote: v, InvalidHeaderField: "ValidatorsHash"}, false, true},
|
||||
{"&LunaticValidatorEvidence empty fail", &LunaticValidatorEvidence{}, false, true},
|
||||
{"LunaticValidatorEvidence only header fail", &LunaticValidatorEvidence{Header: header1}, false, true},
|
||||
{"LunaticValidatorEvidence only vote fail", &LunaticValidatorEvidence{Vote: v}, false, true},
|
||||
{"LunaticValidatorEvidence header & vote fail", &LunaticValidatorEvidence{Header: header1, Vote: v}, false, true},
|
||||
{"&LunaticValidatorEvidence empty fail", &LunaticValidatorEvidence{}, false, true},
|
||||
{"PotentialAmnesiaEvidence empty fail", PotentialAmnesiaEvidence{}, false, true},
|
||||
{"PotentialAmnesiaEvidence nil VoteB", PotentialAmnesiaEvidence{VoteA: v, VoteB: nil}, false, true},
|
||||
{"PotentialAmnesiaEvidence nil VoteA", PotentialAmnesiaEvidence{VoteA: nil, VoteB: v2}, false, true},
|
||||
{"&PotentialAmnesiaEvidence empty fail", &PotentialAmnesiaEvidence{}, false, true},
|
||||
{"&PotentialAmnesiaEvidence nil VoteB", &PotentialAmnesiaEvidence{VoteA: v, VoteB: nil}, false, true},
|
||||
{"&PotentialAmnesiaEvidence nil VoteA", &PotentialAmnesiaEvidence{VoteA: nil, VoteB: v2}, false, true},
|
||||
{"&PotentialAmnesiaEvidence success", &PotentialAmnesiaEvidence{VoteA: v2, VoteB: v}, false, false},
|
||||
{"&PhantomValidatorEvidence empty fail", &PhantomValidatorEvidence{}, false, true},
|
||||
{"&PhantomValidatorEvidence nil LastHeightValidatorWasInSet", &PhantomValidatorEvidence{Vote: v}, false, true},
|
||||
{"&PhantomValidatorEvidence nil Vote", &PhantomValidatorEvidence{LastHeightValidatorWasInSet: 2}, false, true},
|
||||
{"PhantomValidatorEvidence success", PhantomValidatorEvidence{Vote: v2, LastHeightValidatorWasInSet: 2},
|
||||
{"LunaticValidatorEvidence empty fail", &LunaticValidatorEvidence{}, false, true},
|
||||
{"PotentialAmnesiaEvidence empty fail", &PotentialAmnesiaEvidence{}, false, true},
|
||||
{"PotentialAmnesiaEvidence nil VoteB", &PotentialAmnesiaEvidence{VoteA: v, VoteB: nil}, false, true},
|
||||
{"PotentialAmnesiaEvidence nil VoteA", &PotentialAmnesiaEvidence{VoteA: nil, VoteB: v2}, false, true},
|
||||
{"PotentialAmnesiaEvidence success", &PotentialAmnesiaEvidence{VoteA: v2, VoteB: v}, false, false},
|
||||
{"PhantomValidatorEvidence empty fail", &PhantomValidatorEvidence{}, false, true},
|
||||
{"PhantomValidatorEvidence nil LastHeightValidatorWasInSet", &PhantomValidatorEvidence{Vote: v}, false, true},
|
||||
{"PhantomValidatorEvidence nil Vote", &PhantomValidatorEvidence{LastHeightValidatorWasInSet: 2}, false, true},
|
||||
{"PhantomValidatorEvidence success", &PhantomValidatorEvidence{Vote: v2, LastHeightValidatorWasInSet: 2},
|
||||
false, false},
|
||||
{"AmnesiaEvidence nil ProofOfLockChange", &AmnesiaEvidence{PotentialAmnesiaEvidence: &PotentialAmnesiaEvidence{},
|
||||
Polc: NewEmptyPOLC()}, false, true},
|
||||
{"AmnesiaEvidence nil Polc",
|
||||
&AmnesiaEvidence{PotentialAmnesiaEvidence: &PotentialAmnesiaEvidence{VoteA: v2, VoteB: v},
|
||||
Polc: &ProofOfLockChange{}}, false, false},
|
||||
{"AmnesiaEvidence success", &AmnesiaEvidence{PotentialAmnesiaEvidence: &PotentialAmnesiaEvidence{VoteA: v2, VoteB: v},
|
||||
Polc: NewEmptyPOLC()}, false, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.testName, func(t *testing.T) {
|
||||
pb, err := EvidenceToProto(tt.evidence)
|
||||
if tt.wantErr {
|
||||
if tt.toProtoErr {
|
||||
assert.Error(t, err, tt.testName)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err, tt.testName)
|
||||
|
||||
evi, err := EvidenceFromProto(pb)
|
||||
if tt.wantErr2 {
|
||||
if tt.fromProtoErr {
|
||||
assert.Error(t, err, tt.testName)
|
||||
return
|
||||
}
|
||||
@@ -709,29 +729,31 @@ func TestProofOfLockChangeProtoBuf(t *testing.T) {
|
||||
v2 := makeVote(t, val2, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime)
|
||||
|
||||
testCases := []struct {
|
||||
msg string
|
||||
polc ProofOfLockChange
|
||||
expErr bool
|
||||
expErr2 bool
|
||||
msg string
|
||||
polc *ProofOfLockChange
|
||||
toProtoErr bool
|
||||
fromProtoErr bool
|
||||
}{
|
||||
{"failure, empty key", ProofOfLockChange{Votes: []Vote{*v, *v2}}, true, true},
|
||||
{"failure, empty votes", ProofOfLockChange{PubKey: val3.PrivKey.PubKey()}, true, true},
|
||||
{"success empty ProofOfLockChange", EmptyPOLC(), false, false},
|
||||
{"success", ProofOfLockChange{Votes: []Vote{*v, *v2}, PubKey: val3.PrivKey.PubKey()}, false, false},
|
||||
{"failure, empty key", &ProofOfLockChange{Votes: []*Vote{v, v2}, PubKey: nil}, true, false},
|
||||
{"failure, empty votes", &ProofOfLockChange{PubKey: val3.PrivKey.PubKey()}, true, false},
|
||||
{"success empty ProofOfLockChange", NewEmptyPOLC(), false, false},
|
||||
{"success", &ProofOfLockChange{Votes: []*Vote{v, v2}, PubKey: val3.PrivKey.PubKey()}, false, false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
pbpolc, err := tc.polc.ToProto()
|
||||
if tc.expErr {
|
||||
if tc.toProtoErr {
|
||||
assert.Error(t, err, tc.msg)
|
||||
} else {
|
||||
assert.NoError(t, err, tc.msg)
|
||||
}
|
||||
|
||||
c, err := ProofOfLockChangeFromProto(pbpolc)
|
||||
if !tc.expErr2 {
|
||||
if !tc.fromProtoErr {
|
||||
assert.NoError(t, err, tc.msg)
|
||||
assert.Equal(t, &tc.polc, c, tc.msg)
|
||||
if !tc.toProtoErr {
|
||||
assert.Equal(t, tc.polc, c, tc.msg)
|
||||
}
|
||||
} else {
|
||||
assert.Error(t, err, tc.msg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user