Browse Source

evidence: cap evidence to an absolute number (#4780)

The number of evidence that can be committed in a single block is capped by a new evidence parameter called MaxNum
pull/4825/head
Callum Waters 5 years ago
committed by GitHub
parent
commit
a620e5fd96
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 130 additions and 99 deletions
  1. +2
    -0
      CHANGELOG_PENDING.md
  2. +1
    -0
      abci/types/types.pb.go
  3. +1
    -0
      abci/types/types.proto
  4. +1
    -1
      consensus/reactor_test.go
  5. +5
    -5
      consensus/replay_stubs.go
  6. +10
    -3
      evidence/pool.go
  7. +1
    -1
      evidence/pool_test.go
  8. +2
    -2
      evidence/reactor_test.go
  9. +6
    -4
      node/node_test.go
  10. +8
    -0
      proto/types/params.pb.go
  11. +1
    -0
      proto/types/params.proto
  12. +1
    -3
      state/execution.go
  13. +2
    -2
      state/mocks/evidence_pool.go
  14. +7
    -6
      state/services.go
  15. +1
    -0
      state/tx_filter.go
  16. +2
    -4
      state/tx_filter_test.go
  17. +4
    -5
      state/validation.go
  18. +3
    -6
      state/validation_test.go
  19. +2
    -1
      tools/tm-signer-harness/internal/test_harness_test.go
  20. +2
    -2
      types/block.go
  21. +13
    -11
      types/block_test.go
  22. +3
    -18
      types/evidence.go
  23. +21
    -0
      types/params.go
  24. +30
    -25
      types/params_test.go
  25. +1
    -0
      types/protobuf.go

+ 2
- 0
CHANGELOG_PENDING.md View File

@ -29,6 +29,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi
- Blockchain Protocol - Blockchain Protocol
- [types] [\#4792](https://github.com/tendermint/tendermint/pull/4792) Sort validators by voting power to enable faster commit verification (@melekes) - [types] [\#4792](https://github.com/tendermint/tendermint/pull/4792) Sort validators by voting power to enable faster commit verification (@melekes)
- [evidence] [\#4780](https://github.com/tendermint/tendermint/pull/4780) Cap evidence to an absolute number (@cmwaters)
Add `max_num` to consensus evidence parameters (default: 50 items).
### FEATURES: ### FEATURES:


+ 1
- 0
abci/types/types.pb.go View File

@ -2644,6 +2644,7 @@ type EvidenceParams struct {
// Note: must be greater than 0 // Note: must be greater than 0
MaxAgeNumBlocks int64 `protobuf:"varint,1,opt,name=max_age_num_blocks,json=maxAgeNumBlocks,proto3" json:"max_age_num_blocks,omitempty"` MaxAgeNumBlocks int64 `protobuf:"varint,1,opt,name=max_age_num_blocks,json=maxAgeNumBlocks,proto3" json:"max_age_num_blocks,omitempty"`
MaxAgeDuration time.Duration `protobuf:"bytes,2,opt,name=max_age_duration,json=maxAgeDuration,proto3,stdduration" json:"max_age_duration"` MaxAgeDuration time.Duration `protobuf:"bytes,2,opt,name=max_age_duration,json=maxAgeDuration,proto3,stdduration" json:"max_age_duration"`
MaxNumEvidence uint32 `protobuf:"varint,1,opt,name=max_num_evidence,json=maxNumEvidence,proto3" json:"max_num_evidence,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`


+ 1
- 0
abci/types/types.proto View File

@ -302,6 +302,7 @@ message EvidenceParams {
int64 max_age_num_blocks = 1; int64 max_age_num_blocks = 1;
google.protobuf.Duration max_age_duration = 2 google.protobuf.Duration max_age_duration = 2
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; [(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
uint32 max_num = 3;
} }
// ValidatorParams contains limits on validators. // ValidatorParams contains limits on validators.


+ 1
- 1
consensus/reactor_test.go View File

@ -209,7 +209,7 @@ func newMockEvidencePool(val []byte) *mockEvidencePool {
} }
// NOTE: maxBytes is ignored // NOTE: maxBytes is ignored
func (m *mockEvidencePool) PendingEvidence(maxBytes int64) []types.Evidence {
func (m *mockEvidencePool) PendingEvidence(maxBytes uint32) []types.Evidence {
if m.height > 0 { if m.height > 0 {
return m.ev return m.ev
} }


+ 5
- 5
consensus/replay_stubs.go View File

@ -50,11 +50,11 @@ type emptyEvidencePool struct{}
var _ sm.EvidencePool = emptyEvidencePool{} var _ sm.EvidencePool = emptyEvidencePool{}
func (emptyEvidencePool) PendingEvidence(int64) []types.Evidence { return nil }
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) PendingEvidence(uint32) []types.Evidence { return nil }
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 }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// mockProxyApp uses ABCIResponses to give the right results. // mockProxyApp uses ABCIResponses to give the right results.


+ 10
- 3
evidence/pool.go View File

@ -82,8 +82,16 @@ 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. // 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 prioritised based on time.
func (evpool *Pool) PendingEvidence(maxNum int64) []types.Evidence {
evidence, err := evpool.listEvidence(baseKeyPending, maxNum)
func (evpool *Pool) PendingEvidence(maxNum uint32) []types.Evidence {
evidence, err := evpool.listEvidence(baseKeyPending, int64(maxNum))
if err != nil {
evpool.logger.Error("Unable to retrieve pending evidence", "err", err)
}
return evidence
}
func (evpool *Pool) AllPendingEvidence() []types.Evidence {
evidence, err := evpool.listEvidence(baseKeyPending, -1)
if err != nil { if err != nil {
evpool.logger.Error("Unable to retrieve pending evidence", "err", err) evpool.logger.Error("Unable to retrieve pending evidence", "err", err)
} }
@ -300,7 +308,6 @@ func (evpool *Pool) removePendingEvidence(evidence types.Evidence) {
} }
// listEvidence lists up to maxNum pieces of evidence for the given prefix key. // listEvidence lists up to maxNum pieces of evidence for the given prefix key.
// It is wrapped by PriorityEvidence and PendingEvidence for convenience.
// If maxNum is -1, there's no cap on the size of returned evidence. // If maxNum is -1, there's no cap on the size of returned evidence.
func (evpool *Pool) listEvidence(prefixKey byte, maxNum int64) ([]types.Evidence, error) { func (evpool *Pool) listEvidence(prefixKey byte, maxNum int64) ([]types.Evidence, error) {
var count int64 var count int64


+ 1
- 1
evidence/pool_test.go View File

@ -96,7 +96,7 @@ func TestProposingAndCommittingEvidence(t *testing.T) {
assert.False(t, pool.IsCommitted(evidence)) assert.False(t, pool.IsCommitted(evidence))
// test evidence is proposed // test evidence is proposed
proposedEvidence := pool.PendingEvidence(-1)
proposedEvidence := pool.AllPendingEvidence()
assert.Equal(t, proposedEvidence[0], evidence) assert.Equal(t, proposedEvidence[0], evidence)
// evidence seen and committed: // evidence seen and committed:


+ 2
- 2
evidence/reactor_test.go View File

@ -92,11 +92,11 @@ func _waitForEvidence(
reactors []*Reactor, reactors []*Reactor,
) { ) {
evpool := reactors[reactorIdx].evpool evpool := reactors[reactorIdx].evpool
for len(evpool.PendingEvidence(-1)) != len(evs) {
for len(evpool.AllPendingEvidence()) != len(evs) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
} }
reapedEv := evpool.PendingEvidence(-1)
reapedEv := evpool.AllPendingEvidence()
// put the reaped evidence in a map so we can quickly check we got everything // put the reaped evidence in a map so we can quickly check we got everything
evMap := make(map[string]types.Evidence) evMap := make(map[string]types.Evidence)
for _, e := range reapedEv { for _, e := range reapedEv {


+ 6
- 4
node/node_test.go View File

@ -219,6 +219,8 @@ func testFreeAddr(t *testing.T) string {
// create a proposal block using real and full // create a proposal block using real and full
// mempool and evidence pool and validate it. // mempool and evidence pool and validate it.
func TestCreateProposalBlock(t *testing.T) { func TestCreateProposalBlock(t *testing.T) {
const minEvSize = 12
config := cfg.ResetTestRoot("node_create_proposal") config := cfg.ResetTestRoot("node_create_proposal")
defer os.RemoveAll(config.RootDir) defer os.RemoveAll(config.RootDir)
cc := proxy.NewLocalClientCreator(kvstore.NewApplication()) cc := proxy.NewLocalClientCreator(kvstore.NewApplication())
@ -232,7 +234,9 @@ func TestCreateProposalBlock(t *testing.T) {
var height int64 = 1 var height int64 = 1
state, stateDB := state(1, height) state, stateDB := state(1, height)
maxBytes := 16384 maxBytes := 16384
maxEvidence := 10
state.ConsensusParams.Block.MaxBytes = int64(maxBytes) state.ConsensusParams.Block.MaxBytes = int64(maxBytes)
state.ConsensusParams.Evidence.MaxNum = uint32(maxEvidence)
proposerAddr, _ := state.Validators.GetByIndex(0) proposerAddr, _ := state.Validators.GetByIndex(0)
// Make Mempool // Make Mempool
@ -258,10 +262,8 @@ func TestCreateProposalBlock(t *testing.T) {
// fill the evidence pool with more evidence // fill the evidence pool with more evidence
// than can fit in a block // than can fit in a block
minEvSize := 12
numEv := (maxBytes / types.MaxEvidenceBytesDenominator) / minEvSize
for i := 0; i < numEv; i++ {
ev := types.NewMockRandomEvidence(1, time.Now(), proposerAddr, tmrand.Bytes(minEvSize))
for i := 0; i < maxEvidence+1; i++ {
ev := types.NewMockRandomEvidence(height, time.Now(), proposerAddr, tmrand.Bytes(minEvSize))
err := evidencePool.AddEvidence(ev) err := evidencePool.AddEvidence(ev)
require.NoError(t, err) require.NoError(t, err)
} }


+ 8
- 0
proto/types/params.pb.go View File

@ -146,6 +146,7 @@ type EvidenceParams struct {
// Note: must be greater than 0 // Note: must be greater than 0
MaxAgeNumBlocks int64 `protobuf:"varint,1,opt,name=max_age_num_blocks,json=maxAgeNumBlocks,proto3" json:"max_age_num_blocks,omitempty"` MaxAgeNumBlocks int64 `protobuf:"varint,1,opt,name=max_age_num_blocks,json=maxAgeNumBlocks,proto3" json:"max_age_num_blocks,omitempty"`
MaxAgeDuration time.Duration `protobuf:"bytes,2,opt,name=max_age_duration,json=maxAgeDuration,proto3,stdduration" json:"max_age_duration"` MaxAgeDuration time.Duration `protobuf:"bytes,2,opt,name=max_age_duration,json=maxAgeDuration,proto3,stdduration" json:"max_age_duration"`
MaxNumEvidence uint32 `protobuf:"varint,1,opt,name=max_num_evidence,json=maxNumEvidence,proto3" json:"max_num_evidence,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -189,6 +190,13 @@ func (m *EvidenceParams) GetMaxAgeDuration() time.Duration {
return 0 return 0
} }
func (m *EvidenceParams) GetMaxNumEvidence() uint32 {
if m != nil {
return m.MaxNumEvidence
}
return 0
}
// ValidatorParams restrict the public key types validators can use. // ValidatorParams restrict the public key types validators can use.
// NOTE: uses ABCI pubkey naming, not Amino names. // NOTE: uses ABCI pubkey naming, not Amino names.
type ValidatorParams struct { type ValidatorParams struct {


+ 1
- 0
proto/types/params.proto View File

@ -33,6 +33,7 @@ message EvidenceParams {
int64 max_age_num_blocks = 1; int64 max_age_num_blocks = 1;
google.protobuf.Duration max_age_duration = 2 google.protobuf.Duration max_age_duration = 2
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; [(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
uint32 max_num = 3;
} }
// ValidatorParams restrict the public key types validators can use. // ValidatorParams restrict the public key types validators can use.


+ 1
- 3
state/execution.go View File

@ -98,9 +98,7 @@ func (blockExec *BlockExecutor) CreateProposalBlock(
maxBytes := state.ConsensusParams.Block.MaxBytes maxBytes := state.ConsensusParams.Block.MaxBytes
maxGas := state.ConsensusParams.Block.MaxGas maxGas := state.ConsensusParams.Block.MaxGas
// Fetch a limited amount of valid evidence
maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBytes)
evidence := blockExec.evpool.PendingEvidence(maxNumEvidence)
evidence := blockExec.evpool.PendingEvidence(state.ConsensusParams.Evidence.MaxNum)
// Fetch a limited amount of valid txs // Fetch a limited amount of valid txs
maxDataBytes := types.MaxDataBytes(maxBytes, state.Validators.Size(), len(evidence)) maxDataBytes := types.MaxDataBytes(maxBytes, state.Validators.Size(), len(evidence))


+ 2
- 2
state/mocks/evidence_pool.go View File

@ -57,11 +57,11 @@ func (_m *EvidencePool) IsPending(_a0 types.Evidence) bool {
} }
// PendingEvidence provides a mock function with given fields: _a0 // PendingEvidence provides a mock function with given fields: _a0
func (_m *EvidencePool) PendingEvidence(_a0 int64) []types.Evidence {
func (_m *EvidencePool) PendingEvidence(_a0 uint32) []types.Evidence {
ret := _m.Called(_a0) ret := _m.Called(_a0)
var r0 []types.Evidence var r0 []types.Evidence
if rf, ok := ret.Get(0).(func(int64) []types.Evidence); ok {
if rf, ok := ret.Get(0).(func(uint32) []types.Evidence); ok {
r0 = rf(_a0) r0 = rf(_a0)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {


+ 7
- 6
state/services.go View File

@ -40,17 +40,18 @@ type BlockStore interface {
// EvidencePool defines the EvidencePool interface used by the ConsensusState. // EvidencePool defines the EvidencePool interface used by the ConsensusState.
// Get/Set/Commit // Get/Set/Commit
type EvidencePool interface { type EvidencePool interface {
PendingEvidence(int64) []types.Evidence
PendingEvidence(uint32) []types.Evidence
AddEvidence(types.Evidence) error AddEvidence(types.Evidence) error
Update(*types.Block, State) Update(*types.Block, State)
IsCommitted(types.Evidence) bool IsCommitted(types.Evidence) bool
IsPending(types.Evidence) bool IsPending(types.Evidence) bool
} }
// MockEvidencePool is an empty implementation of EvidencePool, useful for testing.
type MockEvidencePool struct{} type MockEvidencePool struct{}
func (me MockEvidencePool) PendingEvidence(int64) []types.Evidence { return nil }
func (me MockEvidencePool) AddEvidence(types.Evidence) error { return nil }
func (me MockEvidencePool) Update(*types.Block, State) {}
func (me MockEvidencePool) IsCommitted(types.Evidence) bool { return false }
func (me MockEvidencePool) IsPending(types.Evidence) bool { return false }
func (me MockEvidencePool) PendingEvidence(uint32) []types.Evidence { return nil }
func (me MockEvidencePool) AddEvidence(types.Evidence) error { return nil }
func (me MockEvidencePool) Update(*types.Block, State) {}
func (me MockEvidencePool) IsCommitted(types.Evidence) bool { return false }
func (me MockEvidencePool) IsPending(types.Evidence) bool { return false }

+ 1
- 0
state/tx_filter.go View File

@ -11,6 +11,7 @@ func TxPreCheck(state State) mempl.PreCheckFunc {
maxDataBytes := types.MaxDataBytesUnknownEvidence( maxDataBytes := types.MaxDataBytesUnknownEvidence(
state.ConsensusParams.Block.MaxBytes, state.ConsensusParams.Block.MaxBytes,
state.Validators.Size(), state.Validators.Size(),
state.ConsensusParams.Evidence.MaxNum,
) )
return mempl.PreCheckAminoMaxBytes(maxDataBytes) return mempl.PreCheckAminoMaxBytes(maxDataBytes)
} }


+ 2
- 4
state/tx_filter_test.go View File

@ -17,6 +17,7 @@ import (
func TestTxFilter(t *testing.T) { func TestTxFilter(t *testing.T) {
genDoc := randomGenesisDoc() genDoc := randomGenesisDoc()
genDoc.ConsensusParams.Block.MaxBytes = 3000 genDoc.ConsensusParams.Block.MaxBytes = 3000
genDoc.ConsensusParams.Evidence.MaxNum = 1
// Max size of Txs is much smaller than size of block, // Max size of Txs is much smaller than size of block,
// since we need to account for commits and evidence. // since we need to account for commits and evidence.
@ -24,10 +25,7 @@ func TestTxFilter(t *testing.T) {
tx types.Tx tx types.Tx
isErr bool isErr bool
}{ }{
{types.Tx(tmrand.Bytes(250)), false},
{types.Tx(tmrand.Bytes(1811)), false},
{types.Tx(tmrand.Bytes(1831)), false},
{types.Tx(tmrand.Bytes(1838)), true},
{types.Tx(tmrand.Bytes(1680)), false},
{types.Tx(tmrand.Bytes(1839)), true}, {types.Tx(tmrand.Bytes(1839)), true},
{types.Tx(tmrand.Bytes(3000)), true}, {types.Tx(tmrand.Bytes(3000)), true},
} }


+ 4
- 5
state/validation.go View File

@ -120,11 +120,10 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block
} }
// Limit the amount of evidence // Limit the amount of evidence
maxNumEvidence, _ := types.MaxEvidencePerBlock(state.ConsensusParams.Block.MaxBytes)
numEvidence := int64(len(block.Evidence.Evidence))
if numEvidence > maxNumEvidence {
return types.NewErrEvidenceOverflow(maxNumEvidence, numEvidence)
numEvidence := len(block.Evidence.Evidence)
// MaxNumEvidence is capped at uint16, so conversion is always safe.
if maxEvidence := int(state.ConsensusParams.Evidence.MaxNum); numEvidence > maxEvidence {
return types.NewErrEvidenceOverflow(maxEvidence, numEvidence)
} }
// Validate all evidence. // Validate all evidence.


+ 3
- 6
state/validation_test.go View File

@ -216,16 +216,15 @@ func TestValidateBlockEvidence(t *testing.T) {
for height := int64(1); height < validationTestsStopHeight; height++ { for height := int64(1); height < validationTestsStopHeight; height++ {
proposerAddr := state.Validators.GetProposer().Address proposerAddr := state.Validators.GetProposer().Address
goodEvidence := types.NewMockEvidence(height, time.Now(), proposerAddr) goodEvidence := types.NewMockEvidence(height, time.Now(), proposerAddr)
maxNumEvidence := state.ConsensusParams.Evidence.MaxNum
if height > 1 { if height > 1 {
/* /*
A block with too much evidence fails A block with too much evidence fails
*/ */
maxBlockSize := state.ConsensusParams.Block.MaxBytes
maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize)
require.True(t, maxNumEvidence > 2) require.True(t, maxNumEvidence > 2)
evidence := make([]types.Evidence, 0) evidence := make([]types.Evidence, 0)
// one more than the maximum allowed evidence // one more than the maximum allowed evidence
for i := int64(0); i <= maxNumEvidence; i++ {
for i := uint32(0); i <= maxNumEvidence; i++ {
evidence = append(evidence, goodEvidence) evidence = append(evidence, goodEvidence)
} }
block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr) block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr)
@ -237,12 +236,10 @@ func TestValidateBlockEvidence(t *testing.T) {
/* /*
A good block with several pieces of good evidence passes A good block with several pieces of good evidence passes
*/ */
maxBlockSize := state.ConsensusParams.Block.MaxBytes
maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize)
require.True(t, maxNumEvidence > 2) require.True(t, maxNumEvidence > 2)
evidence := make([]types.Evidence, 0) evidence := make([]types.Evidence, 0)
// precisely the amount of allowed evidence // precisely the amount of allowed evidence
for i := int64(0); i < maxNumEvidence; i++ {
for i := uint32(0); i < maxNumEvidence; i++ {
evidence = append(evidence, goodEvidence) evidence = append(evidence, goodEvidence)
} }


+ 2
- 1
tools/tm-signer-harness/internal/test_harness_test.go View File

@ -47,7 +47,8 @@ const (
}, },
"evidence": { "evidence": {
"max_age_num_blocks": "100000", "max_age_num_blocks": "100000",
"max_age_duration": "172800000000000"
"max_age_duration": "172800000000000",
"max_num_evidence": 50
}, },
"validator": { "validator": {
"pub_key_types": [ "pub_key_types": [


+ 2
- 2
types/block.go View File

@ -253,8 +253,8 @@ func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 {
// of evidence. // of evidence.
// //
// XXX: Panics on negative result. // XXX: Panics on negative result.
func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 {
_, maxEvidenceBytes := MaxEvidencePerBlock(maxBytes)
func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int, maxNumEvidence uint32) int64 {
maxEvidenceBytes := int64(maxNumEvidence) * MaxEvidenceBytes
maxDataBytes := maxBytes - maxDataBytes := maxBytes -
MaxAminoOverheadForBlock - MaxAminoOverheadForBlock -
MaxHeaderBytes - MaxHeaderBytes -


+ 13
- 11
types/block_test.go View File

@ -399,28 +399,30 @@ func TestBlockMaxDataBytes(t *testing.T) {
func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) { func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) {
testCases := []struct { testCases := []struct {
maxBytes int64
valsCount int
panics bool
result int64
maxBytes int64
maxEvidence uint32
valsCount int
panics bool
result int64
}{ }{
0: {-10, 1, true, 0},
1: {10, 1, true, 0},
2: {961, 1, true, 0},
3: {962, 1, false, 0},
4: {963, 1, false, 1},
0: {-10, 0, 1, true, 0},
1: {10, 0, 1, true, 0},
2: {865, 0, 1, true, 0},
3: {866, 0, 1, false, 0},
4: {1310, 1, 1, false, 0},
5: {1311, 1, 1, false, 1},
} }
for i, tc := range testCases { for i, tc := range testCases {
tc := tc tc := tc
if tc.panics { if tc.panics {
assert.Panics(t, func() { assert.Panics(t, func() {
MaxDataBytesUnknownEvidence(tc.maxBytes, tc.valsCount)
MaxDataBytesUnknownEvidence(tc.maxBytes, tc.valsCount, tc.maxEvidence)
}, "#%v", i) }, "#%v", i)
} else { } else {
assert.Equal(t, assert.Equal(t,
tc.result, tc.result,
MaxDataBytesUnknownEvidence(tc.maxBytes, tc.valsCount),
MaxDataBytesUnknownEvidence(tc.maxBytes, tc.valsCount, tc.maxEvidence),
"#%v", i) "#%v", i)
} }
} }


+ 3
- 18
types/evidence.go View File

@ -48,12 +48,12 @@ func (err *ErrEvidenceInvalid) Error() string {
// ErrEvidenceOverflow is for when there is too much evidence in a block. // ErrEvidenceOverflow is for when there is too much evidence in a block.
type ErrEvidenceOverflow struct { type ErrEvidenceOverflow struct {
MaxNum int64
GotNum int64
MaxNum int
GotNum int
} }
// NewErrEvidenceOverflow returns a new ErrEvidenceOverflow where got > max. // NewErrEvidenceOverflow returns a new ErrEvidenceOverflow where got > max.
func NewErrEvidenceOverflow(max, got int64) *ErrEvidenceOverflow {
func NewErrEvidenceOverflow(max, got int) *ErrEvidenceOverflow {
return &ErrEvidenceOverflow{max, got} return &ErrEvidenceOverflow{max, got}
} }
@ -97,21 +97,6 @@ func RegisterMockEvidences(cdc *amino.Codec) {
cdc.RegisterConcrete(MockRandomEvidence{}, "tendermint/MockRandomEvidence", nil) cdc.RegisterConcrete(MockRandomEvidence{}, "tendermint/MockRandomEvidence", nil)
} }
const (
MaxEvidenceBytesDenominator = 10
)
// MaxEvidencePerBlock returns the maximum number of evidences
// allowed in the block and their maximum total size (limitted to 1/10th
// of the maximum block size).
// TODO: change to a constant, or to a fraction of the validator set size.
// See https://github.com/tendermint/tendermint/issues/2590
func MaxEvidencePerBlock(blockMaxBytes int64) (int64, int64) {
maxBytes := blockMaxBytes / MaxEvidenceBytesDenominator
maxNum := maxBytes / MaxEvidenceBytes
return maxNum, maxBytes
}
//------------------------------------------- //-------------------------------------------
// DuplicateVoteEvidence contains evidence a validator signed two conflicting // DuplicateVoteEvidence contains evidence a validator signed two conflicting


+ 21
- 0
types/params.go View File

@ -19,6 +19,9 @@ const (
// MaxBlockPartsCount is the maximum number of block parts. // MaxBlockPartsCount is the maximum number of block parts.
MaxBlockPartsCount = (MaxBlockSizeBytes / BlockPartSizeBytes) + 1 MaxBlockPartsCount = (MaxBlockSizeBytes / BlockPartSizeBytes) + 1
// Restrict the upper bound of the amount of evidence (uses uint16 for safe conversion)
MaxEvidencePerBlock = 65535
) )
// ConsensusParams contains consensus critical parameters that determine the // ConsensusParams contains consensus critical parameters that determine the
@ -67,6 +70,12 @@ type EvidenceParams struct {
// mechanism for handling [Nothing-At-Stake // mechanism for handling [Nothing-At-Stake
// attacks](https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ#what-is-the-nothing-at-stake-problem-and-how-can-it-be-fixed). // attacks](https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ#what-is-the-nothing-at-stake-problem-and-how-can-it-be-fixed).
MaxAgeDuration time.Duration `json:"max_age_duration"` MaxAgeDuration time.Duration `json:"max_age_duration"`
// This sets the maximum number of evidence that can be committed in a single block.
// and should fall comfortably under the max block bytes when we consider the size of
// each evidence (See MaxEvidenceBytes). The maximum number is MaxEvidencePerBlock.
// Default is 50
MaxNum uint32 `json:"max_num"`
} }
// ValidatorParams restrict the public key types validators can use. // ValidatorParams restrict the public key types validators can use.
@ -98,6 +107,7 @@ func DefaultEvidenceParams() EvidenceParams {
return EvidenceParams{ return EvidenceParams{
MaxAgeNumBlocks: 100000, // 27.8 hrs at 1block/s MaxAgeNumBlocks: 100000, // 27.8 hrs at 1block/s
MaxAgeDuration: 48 * time.Hour, MaxAgeDuration: 48 * time.Hour,
MaxNum: 50,
} }
} }
@ -148,6 +158,16 @@ func (params *ConsensusParams) Validate() error {
params.Evidence.MaxAgeDuration) params.Evidence.MaxAgeDuration)
} }
if params.Evidence.MaxNum > MaxEvidencePerBlock {
return errors.Errorf("evidenceParams.MaxNumEvidence is greater than upper bound, %d > %d",
params.Evidence.MaxNum, MaxEvidencePerBlock)
}
if int64(params.Evidence.MaxNum)*MaxEvidenceBytes > params.Block.MaxBytes {
return errors.Errorf("total possible evidence size is bigger than block.MaxBytes, %d > %d",
int64(params.Evidence.MaxNum)*MaxEvidenceBytes, params.Block.MaxBytes)
}
if len(params.Validator.PubKeyTypes) == 0 { if len(params.Validator.PubKeyTypes) == 0 {
return errors.New("len(Validator.PubKeyTypes) must be greater than 0") return errors.New("len(Validator.PubKeyTypes) must be greater than 0")
} }
@ -204,6 +224,7 @@ func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusPar
if params2.Evidence != nil { if params2.Evidence != nil {
res.Evidence.MaxAgeNumBlocks = params2.Evidence.MaxAgeNumBlocks res.Evidence.MaxAgeNumBlocks = params2.Evidence.MaxAgeNumBlocks
res.Evidence.MaxAgeDuration = params2.Evidence.MaxAgeDuration res.Evidence.MaxAgeDuration = params2.Evidence.MaxAgeDuration
res.Evidence.MaxNum = params2.Evidence.MaxNumEvidence
} }
if params2.Validator != nil { if params2.Validator != nil {
// Copy params2.Validator.PubkeyTypes, and set result's value to the copy. // Copy params2.Validator.PubkeyTypes, and set result's value to the copy.


+ 30
- 25
types/params_test.go View File

@ -22,22 +22,24 @@ func TestConsensusParamsValidation(t *testing.T) {
valid bool valid bool
}{ }{
// test block params // test block params
0: {makeParams(1, 0, 10, 1, valEd25519), true},
1: {makeParams(0, 0, 10, 1, valEd25519), false},
2: {makeParams(47*1024*1024, 0, 10, 1, valEd25519), true},
3: {makeParams(10, 0, 10, 1, valEd25519), true},
4: {makeParams(100*1024*1024, 0, 10, 1, valEd25519), true},
5: {makeParams(101*1024*1024, 0, 10, 1, valEd25519), false},
6: {makeParams(1024*1024*1024, 0, 10, 1, valEd25519), false},
7: {makeParams(1024*1024*1024, 0, 10, -1, valEd25519), false},
8: {makeParams(1, 0, -10, 1, valEd25519), false},
0: {makeParams(1, 0, 10, 1, 0, valEd25519), true},
1: {makeParams(0, 0, 10, 1, 0, valEd25519), false},
2: {makeParams(47*1024*1024, 0, 10, 1, 0, valEd25519), true},
3: {makeParams(10, 0, 10, 1, 0, valEd25519), true},
4: {makeParams(100*1024*1024, 0, 10, 1, 0, valEd25519), true},
5: {makeParams(101*1024*1024, 0, 10, 1, 0, valEd25519), false},
6: {makeParams(1024*1024*1024, 0, 10, 1, 0, valEd25519), false},
7: {makeParams(1024*1024*1024, 0, 10, -1, 0, valEd25519), false},
8: {makeParams(1, 0, -10, 1, 0, valEd25519), false},
// test evidence params // test evidence params
9: {makeParams(1, 0, 10, 0, valEd25519), false},
10: {makeParams(1, 0, 10, -1, valEd25519), false},
9: {makeParams(1, 0, 10, 0, 0, valEd25519), false},
10: {makeParams(1, 0, 10, 1, 1, valEd25519), false},
11: {makeParams(1000, 0, 10, 1, 1, valEd25519), true},
12: {makeParams(1, 0, 10, -1, 0, valEd25519), false},
// test no pubkey type provided // test no pubkey type provided
11: {makeParams(1, 0, 10, 1, []string{}), false},
13: {makeParams(1, 0, 10, 1, 0, []string{}), false},
// test invalid pubkey type provided // test invalid pubkey type provided
12: {makeParams(1, 0, 10, 1, []string{"potatoes make good pubkeys"}), false},
14: {makeParams(1, 0, 10, 1, 0, []string{"potatoes make good pubkeys"}), false},
} }
for i, tc := range testCases { for i, tc := range testCases {
if tc.valid { if tc.valid {
@ -52,6 +54,7 @@ func makeParams(
blockBytes, blockGas int64, blockBytes, blockGas int64,
blockTimeIotaMs int64, blockTimeIotaMs int64,
evidenceAge int64, evidenceAge int64,
maxEvidence uint32,
pubkeyTypes []string, pubkeyTypes []string,
) ConsensusParams { ) ConsensusParams {
return ConsensusParams{ return ConsensusParams{
@ -63,6 +66,7 @@ func makeParams(
Evidence: EvidenceParams{ Evidence: EvidenceParams{
MaxAgeNumBlocks: evidenceAge, MaxAgeNumBlocks: evidenceAge,
MaxAgeDuration: time.Duration(evidenceAge), MaxAgeDuration: time.Duration(evidenceAge),
MaxNum: maxEvidence,
}, },
Validator: ValidatorParams{ Validator: ValidatorParams{
PubKeyTypes: pubkeyTypes, PubKeyTypes: pubkeyTypes,
@ -72,14 +76,14 @@ func makeParams(
func TestConsensusParamsHash(t *testing.T) { func TestConsensusParamsHash(t *testing.T) {
params := []ConsensusParams{ params := []ConsensusParams{
makeParams(4, 2, 10, 3, valEd25519),
makeParams(1, 4, 10, 3, valEd25519),
makeParams(1, 2, 10, 4, valEd25519),
makeParams(2, 5, 10, 7, valEd25519),
makeParams(1, 7, 10, 6, valEd25519),
makeParams(9, 5, 10, 4, valEd25519),
makeParams(7, 8, 10, 9, valEd25519),
makeParams(4, 6, 10, 5, valEd25519),
makeParams(4, 2, 10, 3, 1, valEd25519),
makeParams(1, 4, 10, 3, 1, valEd25519),
makeParams(1, 2, 10, 4, 1, valEd25519),
makeParams(2, 5, 10, 7, 1, valEd25519),
makeParams(1, 7, 10, 6, 1, valEd25519),
makeParams(9, 5, 10, 4, 1, valEd25519),
makeParams(7, 8, 10, 9, 1, valEd25519),
makeParams(4, 6, 10, 5, 1, valEd25519),
} }
hashes := make([][]byte, len(params)) hashes := make([][]byte, len(params))
@ -105,13 +109,13 @@ func TestConsensusParamsUpdate(t *testing.T) {
}{ }{
// empty updates // empty updates
{ {
makeParams(1, 2, 10, 3, valEd25519),
makeParams(1, 2, 10, 3, 0, valEd25519),
&abci.ConsensusParams{}, &abci.ConsensusParams{},
makeParams(1, 2, 10, 3, valEd25519),
makeParams(1, 2, 10, 3, 0, valEd25519),
}, },
// fine updates // fine updates
{ {
makeParams(1, 2, 10, 3, valEd25519),
makeParams(1, 2, 10, 3, 0, valEd25519),
&abci.ConsensusParams{ &abci.ConsensusParams{
Block: &abci.BlockParams{ Block: &abci.BlockParams{
MaxBytes: 100, MaxBytes: 100,
@ -120,12 +124,13 @@ func TestConsensusParamsUpdate(t *testing.T) {
Evidence: &abci.EvidenceParams{ Evidence: &abci.EvidenceParams{
MaxAgeNumBlocks: 300, MaxAgeNumBlocks: 300,
MaxAgeDuration: time.Duration(300), MaxAgeDuration: time.Duration(300),
MaxNumEvidence: 50,
}, },
Validator: &abci.ValidatorParams{ Validator: &abci.ValidatorParams{
PubKeyTypes: valSecp256k1, PubKeyTypes: valSecp256k1,
}, },
}, },
makeParams(100, 200, 10, 300, valSecp256k1),
makeParams(100, 200, 10, 300, 50, valSecp256k1),
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {


+ 1
- 0
types/protobuf.go View File

@ -139,6 +139,7 @@ func (tm2pb) ConsensusParams(params *ConsensusParams) *abci.ConsensusParams {
Evidence: &abci.EvidenceParams{ Evidence: &abci.EvidenceParams{
MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks, MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks,
MaxAgeDuration: params.Evidence.MaxAgeDuration, MaxAgeDuration: params.Evidence.MaxAgeDuration,
MaxNumEvidence: params.Evidence.MaxNum,
}, },
Validator: &abci.ValidatorParams{ Validator: &abci.ValidatorParams{
PubKeyTypes: params.Validator.PubKeyTypes, PubKeyTypes: params.Validator.PubKeyTypes,


Loading…
Cancel
Save