Skip to content
This repository has been archived by the owner on Aug 24, 2022. It is now read-only.

Add slashing threshold to params and evidence #200

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
471 changes: 245 additions & 226 deletions abci/types/types.pb.go

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion abci/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ message EntropyParams {
int64 aeon_length = 1;
int64 inactivity_window_size = 2;
int64 required_activity_percentage = 3;
int64 slashing_threshold_percentage = 4;
}

message LastCommitInfo {
Expand Down Expand Up @@ -346,7 +347,7 @@ message Evidence {
int64 height = 3;
google.protobuf.Timestamp time = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
int64 total_voting_power = 5;
Validator complainant = 6; // Allowed to be null
int64 threshold = 6;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer need to send the validator who signed the complaint as we make the evidence from each validator each aeon unique so that duplicates can not be submitted into the blocks

}

//----------------------------------------
Expand Down
15 changes: 13 additions & 2 deletions beacon/entropy_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -740,8 +740,19 @@ func (entropyGenerator *EntropyGenerator) updateActivityTracking(entropy *types.
entropyGenerator.Logger.Error("updateActivityTracking: error getting pub key", "err", err)
continue
}
evidence := types.NewBeaconInactivityEvidence(entropy.Height, defAddress, pubKey.Address(),
entropyGenerator.aeon.Start)

// Calculate threshold for slashing
slashingFraction := float64(entropyGenerator.aeonEntropyParams.SlashingThresholdPercentage) * 0.01
slashingThreshold := int64(slashingFraction * float64(entropyGenerator.aeon.validators.Size()))

// Subtract off entropy channel capacity to obtain current working block height
infractionHeight := entropy.Height - entropyGenerator.beaconConfig.EntropyChannelCapacity
if infractionHeight <= 0 {
// Just in case window gets set to something smaller than the channel capacity
continue
}
evidence := types.NewBeaconInactivityEvidence(infractionHeight, defAddress, pubKey.Address(),
entropyGenerator.aeon.Start, slashingThreshold)
sig, err := entropyGenerator.aeon.privValidator.SignEvidence(entropyGenerator.chainID, evidence)
if err != nil {
entropyGenerator.Logger.Error("updateActivityTracking: error signing evidence", "err", err)
Expand Down
2 changes: 1 addition & 1 deletion beacon/entropy_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ func TestEntropyActivityTracking(t *testing.T) {
for _, ev := range evidence {
beaconInactivityEvidence, err := ev.(*types.BeaconInactivityEvidence)
assert.True(t, err)
assert.Nil(t, beaconInactivityEvidence.Verify(state.ChainID, blockEntropy, state.Validators))
assert.Nil(t, beaconInactivityEvidence.Verify(state.ChainID, blockEntropy, state.Validators, state.ConsensusParams.Entropy))
}
})
}
Expand Down
39 changes: 39 additions & 0 deletions evidence/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,42 @@ func TestAddEvidence(t *testing.T) {
}
}
}

func TestAddNonUniqueBeaconEvidence(t *testing.T) {
var (
evidenceDB = dbm.NewMemDB()
height = int64(10)
stateDB = dbm.NewMemDB()
blockStore = store.NewBlockStore(dbm.NewMemDB())
pool = NewPool(stateDB, evidenceDB, blockStore)
valAddr = []byte("val1")
valAddr2 = []byte("val2")
complainantPV = types.NewMockPV()
)
complainantPubKey, _ := complainantPV.GetPubKey()

// Add unique evidence
ev := types.NewBeaconInactivityEvidence(height, valAddr, complainantPubKey.Address(), 1, 1)
hasEvidence := pool.store.Has(ev)
assert.Equal(t, hasEvidence, false)
_, err := pool.store.AddNewEvidence(ev, 10)
assert.Nil(t, err)

testCases := []struct {
name string
evHeight int64
defAddr []byte
evidenceUnique bool
}{
{"Different height", height + 1, valAddr, false},
{"Different defendant address", height, valAddr2, true},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
ev := types.NewBeaconInactivityEvidence(tc.evHeight, tc.defAddr, complainantPubKey.Address(), 1, 1)
hasEvidence := pool.store.Has(ev)
assert.Equal(t, tc.evidenceUnique, !hasEvidence)
})
}
}
8 changes: 4 additions & 4 deletions evidence/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const (
)

func keyLookup(evidence types.Evidence) []byte {
return keyLookupFromHeightAndHash(evidence.Height(), evidence.Hash())
return keyLookupFromHeightAndHash(evidence.ValidatorHeight(), evidence.Hash())
}

// big endian padded hex
Expand All @@ -52,11 +52,11 @@ func keyLookupFromHeightAndHash(height int64, hash []byte) []byte {
}

func keyOutqueue(evidence types.Evidence, priority int64) []byte {
return _key("%s/%s/%s/%X", baseKeyOutqueue, bE(priority), bE(evidence.Height()), evidence.Hash())
return _key("%s/%s/%s/%X", baseKeyOutqueue, bE(priority), bE(evidence.ValidatorHeight()), evidence.Hash())
}

func keyPending(evidence types.Evidence) []byte {
return _key("%s/%s/%X", baseKeyPending, bE(evidence.Height()), evidence.Hash())
return _key("%s/%s/%X", baseKeyPending, bE(evidence.ValidatorHeight()), evidence.Hash())
}

func _key(format string, o ...interface{}) []byte {
Expand Down Expand Up @@ -218,5 +218,5 @@ func (store *Store) MarkEvidenceAsCommitted(evidence types.Evidence) {

// getInfo is convenience for calling GetInfo if we have the full evidence.
func (store *Store) getInfo(evidence types.Evidence) Info {
return store.GetInfo(evidence.Height(), evidence.Hash())
return store.GetInfo(evidence.ValidatorHeight(), evidence.Hash())
}
10 changes: 7 additions & 3 deletions state/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,19 @@ func verifyDuplicateVoteEvidence(stateDB dbm.DB, chainID string, evidence *types
}

func verifyBeaconInactivityEvidence(stateDB dbm.DB, blockStore BlockStore, chainID string, evidence *types.BeaconInactivityEvidence) (int64, error) {
blockMeta := blockStore.LoadBlockMeta(evidence.ValidatorHeight())
blockMeta := blockStore.LoadBlockMeta(evidence.AeonStart)
if blockMeta == nil {
return 0, fmt.Errorf("could not retrieve block header for height %v", evidence.ValidatorHeight())
}
valset, err := LoadDKGValidators(stateDB, evidence.ValidatorHeight()-1)
valset, err := LoadDKGValidators(stateDB, evidence.ValidatorHeight())
if err != nil {
return 0, err
}
if err := evidence.Verify(chainID, blockMeta.Header.Entropy, valset); err != nil {
params, err := LoadConsensusParams(stateDB, evidence.ValidatorHeight())
if err != nil {
return 0, err
}
if err := evidence.Verify(chainID, blockMeta.Header.Entropy, valset, params.Entropy); err != nil {
return 0, err
}
_, val := valset.GetByAddress(evidence.Address())
Expand Down
37 changes: 28 additions & 9 deletions types/evidence.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,19 +482,21 @@ type BeaconInactivityEvidence struct {
DefendantAddress crypto.Address // Address of validator accused of inactivity
ComplainantAddress crypto.Address // Address of validator submitting complaint complaint
AeonStart int64 // Height for fetching validators
Threshold int64 // Threshold of complaints for slashing (depends on validator size)
ComplainantSignature []byte
}

var _ Evidence = &BeaconInactivityEvidence{}

// NewBeaconInactivityEvidence creates BeaconInactivityEvidence
func NewBeaconInactivityEvidence(height int64, defAddress crypto.Address, comAddress crypto.Address, aeon int64) *BeaconInactivityEvidence {
func NewBeaconInactivityEvidence(height int64, defAddress crypto.Address, comAddress crypto.Address, aeon int64, threshold int64) *BeaconInactivityEvidence {
return &BeaconInactivityEvidence{
CreationHeight: height,
CreationTime: time.Now(),
DefendantAddress: defAddress,
ComplainantAddress: comAddress,
AeonStart: aeon,
Threshold: threshold,
}
}

Expand All @@ -510,9 +512,10 @@ func (bie *BeaconInactivityEvidence) Height() int64 {
return bie.CreationHeight
}

// Height returns validator height
// ValidatorHeight returns validator height. Validators running DRB at aeon start
// correspond to the DKG validators at aeon start -1
func (bie *BeaconInactivityEvidence) ValidatorHeight() int64 {
return bie.AeonStart
return bie.AeonStart - 1
Copy link
Contributor Author

@jinmannwong jinmannwong Oct 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug fix.

}

// Time return
Expand All @@ -530,20 +533,36 @@ func (bie *BeaconInactivityEvidence) Bytes() []byte {
return cdcEncode(bie)
}

// Hash returns the hash of the evidence.
// Hash returns the hash of the unique fields in evidence. Prevents submission
// of multiple evidence by using a different creation time or signature
func (bie *BeaconInactivityEvidence) Hash() []byte {
return tmhash.Sum(cdcEncode(bie))
uniqueInfo := struct {
AeonStart int64
DefendantAddress []byte
ComplainantAddress []byte
Threshold int64
}{bie.AeonStart, bie.DefendantAddress, bie.ComplainantAddress, bie.Threshold}
return tmhash.Sum(cdcEncode(uniqueInfo))
}

// Verify validates information contained in Evidence. Ensures signature verifies with complainant address, valid aeon start and
// that the evidence was created after the aeon start
func (bie *BeaconInactivityEvidence) Verify(chainID string, blockEntropy BlockEntropy, valset *ValidatorSet) error {
func (bie *BeaconInactivityEvidence) Verify(chainID string, blockEntropy BlockEntropy, valset *ValidatorSet,
params EntropyParams) error {
// Check aeon start is correct
if blockEntropy.NextAeonStart != bie.ValidatorHeight() {
if blockEntropy.NextAeonStart != bie.AeonStart {
return fmt.Errorf("incorrect aeon start. Got %v, expected %v", bie.ValidatorHeight(), blockEntropy.NextAeonStart)
}
if bie.CreationHeight <= bie.AeonStart {
return fmt.Errorf("CreationHeight %v before AeonStart %v", bie.CreationHeight, bie.AeonStart)
// Creation height of evidence needs to be during the entropy generation aeon
if bie.CreationHeight <= bie.AeonStart || bie.CreationHeight > bie.AeonStart+params.AeonLength {
return fmt.Errorf("invalid creation height %v for aeon start %v", bie.CreationHeight, bie.AeonStart)
}

// Check theshold is correct
slashingFraction := float64(params.SlashingThresholdPercentage) * 0.01
slashingThreshold := int64(slashingFraction * float64(valset.Size()))
if slashingThreshold != bie.Threshold {
return fmt.Errorf("incorrect Threshold. Got %v, expected %v", bie.Threshold, slashingThreshold)
}

// Check both complainant and defendant addresses are in DKG validator set at aeon start - 1, and that they are in qual
Expand Down
35 changes: 35 additions & 0 deletions types/evidence_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"bytes"
"math"
"testing"
"time"
Expand Down Expand Up @@ -222,3 +223,37 @@ func TestEvidenceProto(t *testing.T) {
})
}
}

func TestUniqueBeaconInactivityEvidence(t *testing.T) {
defPubKey, _ := NewMockPV().GetPubKey()
comPV := NewMockPV()
comPubKey, _ := comPV.GetPubKey()
bie1 := *NewBeaconInactivityEvidence(10, defPubKey.Address(), comPubKey.Address(), 1, 5)

tests := []struct {
testName string
modifyEvidence func(*BeaconInactivityEvidence)
expectSameHash bool
}{
{"Different creation height", func(ev *BeaconInactivityEvidence) { ev.CreationHeight = 1 }, true},
{"Different time", func(ev *BeaconInactivityEvidence) { ev.CreationTime = time.Now() }, true},
{"Different threshold", func(ev *BeaconInactivityEvidence) { ev.Threshold++ }, false},
{"Different defendant address", func(ev *BeaconInactivityEvidence) {
tempPubKey, _ := NewMockPV().GetPubKey()
ev.DefendantAddress = tempPubKey.Address()
}, false},
{"Different complainant address", func(ev *BeaconInactivityEvidence) {
tempPubKey, _ := NewMockPV().GetPubKey()
ev.ComplainantAddress = tempPubKey.Address()
}, false},
{"Different aeon start", func(ev *BeaconInactivityEvidence) { ev.AeonStart++ }, false},
}
for _, tt := range tests {
tt := tt
t.Run(tt.testName, func(t *testing.T) {
modifiedEvidence := bie1
tt.modifyEvidence(&modifiedEvidence)
require.Equal(t, tt.expectSameHash, bytes.Equal(bie1.Hash(), modifiedEvidence.Hash()))
})
}
}
18 changes: 12 additions & 6 deletions types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ type EntropyParams struct {
AeonLength int64 `json:"aeon_length"`

// DRB slashing parameters
InactivityWindowSize int64 `json:"inactivity_window_size"`
RequiredActivityPercentage int64 `json:"required_activity_percentage"`
InactivityWindowSize int64 `json:"inactivity_window_size"`
RequiredActivityPercentage int64 `json:"required_activity_percentage"`
SlashingThresholdPercentage int64 `json:"slashing_threshold_percentage"`
}

// DefaultConsensusParams returns a default ConsensusParams.
Expand Down Expand Up @@ -105,9 +106,11 @@ func DefaultValidatorParams() ValidatorParams {
// DefaultEntropyParams returns a default EntropyParams.
func DefaultEntropyParams() EntropyParams {
return EntropyParams{
AeonLength: 100,
InactivityWindowSize: 100, // No. of blocks in which we track drb signature shares obtained
RequiredActivityPercentage: 50, // Minimum % of signature shares expected within window
AeonLength: 100,
InactivityWindowSize: 100, // No. of blocks in which we track drb signature shares obtained
RequiredActivityPercentage: 50, // Minimum % of signature shares expected within window
SlashingThresholdPercentage: 50, // Minimum % of complaints required for slashing

}
}

Expand Down Expand Up @@ -174,7 +177,9 @@ func (params *ConsensusParams) Validate() error {
if params.Entropy.RequiredActivityPercentage < 0 {
return errors.Errorf("entropyParams.RequiredActivityFraction can not be negative. Got %v", params.Entropy.RequiredActivityPercentage)
}

if params.Entropy.SlashingThresholdPercentage < 0 {
return errors.Errorf("entropyParams.SlashingThresholdPercentage can not be negative. Got %v", params.Entropy.SlashingThresholdPercentage)
}
return nil
}

Expand Down Expand Up @@ -229,6 +234,7 @@ func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusPar
res.Entropy.AeonLength = params2.Entropy.AeonLength
res.Entropy.InactivityWindowSize = params2.Entropy.InactivityWindowSize
res.Entropy.RequiredActivityPercentage = params2.Entropy.RequiredActivityPercentage
res.Entropy.SlashingThresholdPercentage = params2.Entropy.SlashingThresholdPercentage
}
return res
}
16 changes: 5 additions & 11 deletions types/protobuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,10 @@ func (tm2pb) ConsensusParams(params *ConsensusParams) *abci.ConsensusParams {
PubKeyTypes: params.Validator.PubKeyTypes,
},
Entropy: &abci.EntropyParams{
AeonLength: params.Entropy.AeonLength,
InactivityWindowSize: params.Entropy.InactivityWindowSize,
RequiredActivityPercentage: params.Entropy.RequiredActivityPercentage,
AeonLength: params.Entropy.AeonLength,
InactivityWindowSize: params.Entropy.InactivityWindowSize,
RequiredActivityPercentage: params.Entropy.RequiredActivityPercentage,
SlashingThresholdPercentage: params.Entropy.SlashingThresholdPercentage,
},
}
}
Expand Down Expand Up @@ -189,14 +190,7 @@ func (tm2pb) Evidence(ev Evidence, valSet *ValidatorSet, dkgValSet *ValidatorSet
panic(fmt.Sprintf("TM2PB Evidence: received nil relevant val set: evType %v, height %v", evType, ev.Height()))
}
relevantValSet = dkgValSet
_, com := relevantValSet.GetByAddress(evType.ComplainantAddress)
if com == nil {
panic(com)
}
evidence.Complainant = &abci.Validator{
Address: com.PubKey.Address(),
Power: com.VotingPower,
}
evidence.Threshold = evType.Threshold
default:
panic(fmt.Sprintf("Unknown evidence type: %v %v", ev, reflect.TypeOf(ev)))
}
Expand Down