Compare commits

...

9 Commits

Author SHA1 Message Date
Anton Kaliaev
a2e7494b4b [docs] update state spec 2018-10-16 16:22:04 +04:00
Anton Kaliaev
56f943e890 add a test for DurationPretty 2018-10-16 13:38:27 +04:00
Anton Kaliaev
e9f30e1f22 make evidence age (time.Duration) pretty 2018-10-16 13:19:00 +04:00
Anton Kaliaev
7482113547 do not check peer's last block time
when deciding if we should send evidence to it
2018-10-16 11:28:25 +04:00
Anton Kaliaev
42b84972f5 use block.Time instead of fetching it from state 2018-10-16 11:01:26 +04:00
Anton Kaliaev
5481c6b150 ensure we record LastBlockTime in PeerState 2018-10-16 10:27:00 +04:00
Anton Kaliaev
f7e0cd1360 update changelog and spec 2018-10-16 10:27:00 +04:00
Anton Kaliaev
166dc01ab5 make MaxAge nonnullable 2018-10-16 10:27:00 +04:00
Anton Kaliaev
7e7e4c74ca make ConsensusParams.EvidenceParams.MaxAge time
Refs #2565
2018-10-16 10:26:59 +04:00
22 changed files with 645 additions and 361 deletions

View File

@@ -12,10 +12,17 @@ BREAKING CHANGES:
* [rpc] \#2298 `/abci_query` takes `prove` argument instead of `trusted` and switches the default * [rpc] \#2298 `/abci_query` takes `prove` argument instead of `trusted` and switches the default
behaviour to `prove=false` behaviour to `prove=false`
* [privval] \#2459 Split `SocketPVMsg`s implementations into Request and Response, where the Response may contain a error message (returned by the remote signer) * [privval] \#2459 Split `SocketPVMsg`s implementations into Request and Response, where the Response may contain a error message (returned by the remote signer)
* [genesis] \#2565 `consensus_params.evidence_params.max_age` is now `time.Duration` (nanosecond count)
```json
"evidence_params": {
"max_age": "48h0m0s"
}
```
* Apps * Apps
* [abci] \#2298 ResponseQuery.Proof is now a structured merkle.Proof, not just * [abci] \#2298 ResponseQuery.Proof is now a structured merkle.Proof, not just
arbitrary bytes arbitrary bytes
* [abci] \#2565 InitChain `ConsensusParams.EvidenceParams.MaxAge` is now `google.protobuf.Duration` (see https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/duration)
* Go API * Go API
* [node] Remove node.RunForever * [node] Remove node.RunForever

1
Gopkg.lock generated
View File

@@ -517,6 +517,7 @@
"github.com/gogo/protobuf/proto", "github.com/gogo/protobuf/proto",
"github.com/gogo/protobuf/types", "github.com/gogo/protobuf/types",
"github.com/golang/protobuf/proto", "github.com/golang/protobuf/proto",
"github.com/golang/protobuf/ptypes/duration",
"github.com/golang/protobuf/ptypes/timestamp", "github.com/golang/protobuf/ptypes/timestamp",
"github.com/gorilla/websocket", "github.com/gorilla/websocket",
"github.com/jmhodges/levigo", "github.com/jmhodges/levigo",

View File

@@ -44,7 +44,7 @@ protoc_all: protoc_libs protoc_merkle protoc_abci protoc_grpc
## See https://stackoverflow.com/a/25518702 ## See https://stackoverflow.com/a/25518702
## Note the $< here is substituted for the %.proto ## Note the $< here is substituted for the %.proto
## Note the $@ here is substituted for the %.pb.go ## Note the $@ here is substituted for the %.pb.go
protoc $(INCLUDE) $< --gogo_out=Mgoogle/protobuf/timestamp.proto=github.com/golang/protobuf/ptypes/timestamp,plugins=grpc:. protoc $(INCLUDE) $< --gogo_out=Mgoogle/protobuf/timestamp.proto=github.com/golang/protobuf/ptypes/timestamp,Mgoogle/protobuf/duration.proto=github.com/golang/protobuf/ptypes/duration,plugins=grpc:.
######################################## ########################################
### Build ABCI ### Build ABCI

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@ package types;
// https://github.com/gogo/protobuf/blob/master/extensions.md // https://github.com/gogo/protobuf/blob/master/extensions.md
import "github.com/gogo/protobuf/gogoproto/gogo.proto"; import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "github.com/tendermint/tendermint/libs/common/types.proto"; import "github.com/tendermint/tendermint/libs/common/types.proto";
import "github.com/tendermint/tendermint/crypto/merkle/merkle.proto"; import "github.com/tendermint/tendermint/crypto/merkle/merkle.proto";
@@ -217,8 +218,8 @@ message BlockSize {
// EvidenceParams contains limits on the evidence. // EvidenceParams contains limits on the evidence.
message EvidenceParams { message EvidenceParams {
// Note: must be greater than 0 // Note: must be greater than 0 if provided
int64 max_age = 1; google.protobuf.Duration max_age = 1 [(gogoproto.nullable)=false, (gogoproto.stdduration)=true];
} }
message LastCommitInfo { message LastCommitInfo {

View File

@@ -13,6 +13,7 @@ import golang_proto "github.com/golang/protobuf/proto"
import fmt "fmt" import fmt "fmt"
import math "math" import math "math"
import _ "github.com/gogo/protobuf/gogoproto" import _ "github.com/gogo/protobuf/gogoproto"
import _ "github.com/golang/protobuf/ptypes/duration"
import _ "github.com/golang/protobuf/ptypes/timestamp" import _ "github.com/golang/protobuf/ptypes/timestamp"
import _ "github.com/tendermint/tendermint/crypto/merkle" import _ "github.com/tendermint/tendermint/crypto/merkle"
import _ "github.com/tendermint/tendermint/libs/common" import _ "github.com/tendermint/tendermint/libs/common"

View File

@@ -8,8 +8,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/tendermint/go-amino" amino "github.com/tendermint/go-amino"
cstypes "github.com/tendermint/tendermint/consensus/types" cstypes "github.com/tendermint/tendermint/consensus/types"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
tmevents "github.com/tendermint/tendermint/libs/events" tmevents "github.com/tendermint/tendermint/libs/events"
@@ -948,8 +947,8 @@ func (ps *PeerState) ToJSON() ([]byte, error) {
return cdc.MarshalJSON(ps) return cdc.MarshalJSON(ps)
} }
// GetHeight returns an atomic snapshot of the PeerRoundState's height // GetHeight returns an atomic snapshot of the PeerRoundState's height used by
// used by the mempool to ensure peers are caught up before broadcasting new txs // the mempool to ensure peers are caught up before broadcasting new txs.
func (ps *PeerState) GetHeight() int64 { func (ps *PeerState) GetHeight() int64 {
ps.mtx.Lock() ps.mtx.Lock()
defer ps.mtx.Unlock() defer ps.mtx.Unlock()

View File

@@ -443,11 +443,10 @@ Commit are included in the header of the next block.
### EvidenceParams ### EvidenceParams
- **Fields**: - **Fields**:
- `MaxAge (int64)`: Max age of evidence, in blocks. Evidence older than this - `MaxAge (google.protobuf.Duration)`: Max age of evidence. Evidence older
is considered stale and ignored. than this is considered stale and ignored.
- This should correspond with an app's "unbonding period" or other - This should correspond with an app's "unbonding period" or other
similar mechanism for handling Nothing-At-Stake attacks. similar mechanism for handling Nothing-At-Stake attacks.
- NOTE: this should change to time (instead of blocks)!
### Proof ### Proof

View File

@@ -101,7 +101,7 @@ type BlockGossip struct {
} }
type EvidenceParams struct { type EvidenceParams struct {
MaxAge int64 MaxAge time.Duration
} }
``` ```
@@ -129,5 +129,5 @@ size of each part is `ConsensusParams.BlockGossip.BlockPartSizeBytes`.
For evidence in a block to be valid, it must satisfy: For evidence in a block to be valid, it must satisfy:
``` ```
block.Header.Height - evidence.Height < ConsensusParams.EvidenceParams.MaxAge block.Header.Time - evidence.Time < ConsensusParams.EvidenceParams.MaxAge
``` ```

View File

@@ -3,6 +3,7 @@ package evidence
import ( import (
"fmt" "fmt"
"sync" "sync"
"time"
clist "github.com/tendermint/tendermint/libs/clist" clist "github.com/tendermint/tendermint/libs/clist"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
@@ -84,7 +85,7 @@ func (evpool *EvidencePool) Update(block *types.Block, state sm.State) {
evpool.mtx.Unlock() evpool.mtx.Unlock()
// remove evidence from pending and mark committed // remove evidence from pending and mark committed
evpool.MarkEvidenceAsCommitted(block.Height, block.Evidence.Evidence) evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence, block.Time)
} }
// AddEvidence checks the evidence is valid and adds it to the pool. // AddEvidence checks the evidence is valid and adds it to the pool.
@@ -117,8 +118,9 @@ func (evpool *EvidencePool) AddEvidence(evidence types.Evidence) (err error) {
return nil return nil
} }
// MarkEvidenceAsCommitted marks all the evidence as committed and removes it from the queue. // MarkEvidenceAsCommitted marks all the evidence as committed and removes it
func (evpool *EvidencePool) MarkEvidenceAsCommitted(height int64, evidence []types.Evidence) { // from the queue.
func (evpool *EvidencePool) MarkEvidenceAsCommitted(evidence []types.Evidence, lastBlockTime time.Time) {
// make a map of committed evidence to remove from the clist // make a map of committed evidence to remove from the clist
blockEvidenceMap := make(map[string]struct{}) blockEvidenceMap := make(map[string]struct{})
for _, ev := range evidence { for _, ev := range evidence {
@@ -127,20 +129,22 @@ func (evpool *EvidencePool) MarkEvidenceAsCommitted(height int64, evidence []typ
} }
// remove committed evidence from the clist // remove committed evidence from the clist
maxAge := evpool.State().ConsensusParams.EvidenceParams.MaxAge maxAge := evpool.State().ConsensusParams.EvidenceParams.MaxAge.Duration
evpool.removeEvidence(height, maxAge, blockEvidenceMap) evpool.removeEvidence(blockEvidenceMap, lastBlockTime, maxAge)
} }
func (evpool *EvidencePool) removeEvidence(height, maxAge int64, blockEvidenceMap map[string]struct{}) { func (evpool *EvidencePool) removeEvidence(
blockEvidenceMap map[string]struct{},
lastBlockTime time.Time,
maxAge time.Duration,
) {
for e := evpool.evidenceList.Front(); e != nil; e = e.Next() { for e := evpool.evidenceList.Front(); e != nil; e = e.Next() {
ev := e.Value.(types.Evidence) ev := e.Value.(types.Evidence)
evAge := lastBlockTime.Sub(ev.Time())
// Remove the evidence if it's already in a block // Remove the evidence if it's already in a block
// or if it's now too old. // or if it's now too old.
if _, ok := blockEvidenceMap[evMapKey(ev)]; ok || if _, ok := blockEvidenceMap[evMapKey(ev)]; ok || evAge > maxAge {
ev.Height() < height-maxAge {
// remove from clist // remove from clist
evpool.evidenceList.Remove(e) evpool.evidenceList.Remove(e)
e.DetachPrev() e.DetachPrev()

View File

@@ -39,7 +39,7 @@ func initializeValidatorState(valAddr []byte, height int64) dbm.DB {
LastHeightValidatorsChanged: 1, LastHeightValidatorsChanged: 1,
ConsensusParams: types.ConsensusParams{ ConsensusParams: types.ConsensusParams{
EvidenceParams: types.EvidenceParams{ EvidenceParams: types.EvidenceParams{
MaxAge: 1000000, MaxAge: tmtime.DurationPretty{1000000},
}, },
}, },
} }

View File

@@ -91,7 +91,7 @@ func (evR *EvidenceReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
} }
} }
// SetEventSwitch implements events.Eventable. // SetEventBus implements events.Eventable.
func (evR *EvidenceReactor) SetEventBus(b *types.EventBus) { func (evR *EvidenceReactor) SetEventBus(b *types.EventBus) {
evR.eventBus = b evR.eventBus = b
} }
@@ -153,28 +153,17 @@ func (evR *EvidenceReactor) broadcastEvidenceRoutine(peer p2p.Peer) {
// Returns the message to send the peer, or nil if the evidence is invalid for the peer. // Returns the message to send the peer, or nil if the evidence is invalid for the peer.
// If message is nil, return true if we should sleep and try again. // If message is nil, return true if we should sleep and try again.
func (evR EvidenceReactor) checkSendEvidenceMessage(peer p2p.Peer, ev types.Evidence) (msg EvidenceMessage, retry bool) { func (evR EvidenceReactor) checkSendEvidenceMessage(peer p2p.Peer, ev types.Evidence) (msg EvidenceMessage, retry bool) {
// make sure the peer is up to date
evHeight := ev.Height()
peerState, ok := peer.Get(types.PeerStateKey).(PeerState) peerState, ok := peer.Get(types.PeerStateKey).(PeerState)
if !ok { if !ok {
evR.Logger.Info("Found peer without PeerState", "peer", peer) evR.Logger.Info("Found peer without PeerState", "peer", peer)
return nil, true return nil, true
} }
// NOTE: We only send evidence to peers where // NOTE: We only send evidence to peers where evidenceHeight < peerHeight
// peerHeight - maxAge < evidenceHeight < peerHeight
maxAge := evR.evpool.State().ConsensusParams.EvidenceParams.MaxAge
peerHeight := peerState.GetHeight() peerHeight := peerState.GetHeight()
if peerHeight < evHeight { if peerHeight < ev.Height() {
// peer is behind. sleep while he catches up // peer is behind. sleep while he catches up
return nil, true return nil, true
} else if peerHeight > evHeight+maxAge {
// evidence is too old, skip
// NOTE: if evidence is too old for an honest peer,
// then we're behind and either it already got committed or it never will!
evR.Logger.Info("Not sending peer old evidence", "peerHeight", peerHeight, "evHeight", evHeight, "maxAge", maxAge, "peer", peer)
return nil, false
} }
// send evidence // send evidence

View File

@@ -132,7 +132,7 @@ func TestReactorBroadcastEvidence(t *testing.T) {
// set the peer height on each reactor // set the peer height on each reactor
for _, r := range reactors { for _, r := range reactors {
for _, peer := range r.Switch.Peers().List() { for _, peer := range r.Switch.Peers().List() {
ps := peerState{height} ps := testPeerState{height}
peer.Set(types.PeerStateKey, ps) peer.Set(types.PeerStateKey, ps)
} }
} }
@@ -143,11 +143,13 @@ func TestReactorBroadcastEvidence(t *testing.T) {
waitForEvidence(t, evList, reactors) waitForEvidence(t, evList, reactors)
} }
type peerState struct { type testPeerState struct {
height int64 height int64
} }
func (ps peerState) GetHeight() int64 { var _ PeerState = (*testPeerState)(nil)
func (ps testPeerState) GetHeight() int64 {
return ps.height return ps.height
} }
@@ -165,7 +167,7 @@ func TestReactorSelectiveBroadcast(t *testing.T) {
// make reactors from statedb // make reactors from statedb
reactors := makeAndConnectEvidenceReactors(config, []dbm.DB{stateDB1, stateDB2}) reactors := makeAndConnectEvidenceReactors(config, []dbm.DB{stateDB1, stateDB2})
peer := reactors[0].Switch.Peers().List()[0] peer := reactors[0].Switch.Peers().List()[0]
ps := peerState{height2} ps := testPeerState{height2}
peer.Set(types.PeerStateKey, ps) peer.Set(types.PeerStateKey, ps)
// send a bunch of valid evidence to the first reactor's evpool // send a bunch of valid evidence to the first reactor's evpool

View File

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -12,6 +13,7 @@ import (
"github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/ed25519"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
tmtime "github.com/tendermint/tendermint/types/time"
cfg "github.com/tendermint/tendermint/config" cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
@@ -386,14 +388,14 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
} }
} }
func makeParams(blockBytes, blockGas, evidenceAge int64) types.ConsensusParams { func makeParams(blockBytes, blockGas int64, evidenceAge time.Duration) types.ConsensusParams {
return types.ConsensusParams{ return types.ConsensusParams{
BlockSize: types.BlockSize{ BlockSize: types.BlockSize{
MaxBytes: blockBytes, MaxBytes: blockBytes,
MaxGas: blockGas, MaxGas: blockGas,
}, },
EvidenceParams: types.EvidenceParams{ EvidenceParams: types.EvidenceParams{
MaxAge: evidenceAge, MaxAge: tmtime.DurationPretty{evidenceAge},
}, },
} }
} }
@@ -403,7 +405,7 @@ func pk() []byte {
} }
func TestApplyUpdates(t *testing.T) { func TestApplyUpdates(t *testing.T) {
initParams := makeParams(1, 2, 3) initParams := makeParams(1, 2, 3*time.Second)
cases := [...]struct { cases := [...]struct {
init types.ConsensusParams init types.ConsensusParams
@@ -419,14 +421,14 @@ func TestApplyUpdates(t *testing.T) {
MaxGas: 55, MaxGas: 55,
}, },
}, },
makeParams(44, 55, 3)}, makeParams(44, 55, 3*time.Second)},
3: {initParams, 3: {initParams,
abci.ConsensusParams{ abci.ConsensusParams{
EvidenceParams: &abci.EvidenceParams{ EvidenceParams: &abci.EvidenceParams{
MaxAge: 66, MaxAge: 66 * time.Second,
}, },
}, },
makeParams(1, 2, 66)}, makeParams(1, 2, 66*time.Second)},
} }
for i, tc := range cases { for i, tc := range cases {

View File

@@ -168,13 +168,11 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
// - it is internally consistent // - it is internally consistent
// - it was properly signed by the alleged equivocator // - it was properly signed by the alleged equivocator
func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence) error { func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence) error {
height := state.LastBlockHeight evidenceAge := state.LastBlockTime.Sub(evidence.Time())
maxAge := state.ConsensusParams.EvidenceParams.MaxAge.Duration
evidenceAge := height - evidence.Height()
maxAge := state.ConsensusParams.EvidenceParams.MaxAge
if evidenceAge > maxAge { if evidenceAge > maxAge {
return fmt.Errorf("Evidence from height %d is too old. Min height is %d", return fmt.Errorf("Evidence from %v is too old. Expecting evidence no older than %v",
evidence.Height(), height-maxAge) evidence.Time(), state.LastBlockTime.Add(-maxAge))
} }
valset, err := LoadValidators(stateDB, evidence.Height()) valset, err := LoadValidators(stateDB, evidence.Height())

View File

@@ -3,8 +3,10 @@ package types
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"time"
"github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/crypto/tmhash"
tmtime "github.com/tendermint/tendermint/types/time"
amino "github.com/tendermint/go-amino" amino "github.com/tendermint/go-amino"
@@ -54,6 +56,7 @@ func (err *ErrEvidenceOverflow) Error() string {
// Evidence represents any provable malicious activity by a validator // Evidence represents any provable malicious activity by a validator
type Evidence interface { type Evidence interface {
Height() int64 // height of the equivocation Height() int64 // height of the equivocation
Time() time.Time // when the evidence was created
Address() []byte // address of the equivocating validator Address() []byte // address of the equivocating validator
Bytes() []byte // bytes which compromise the evidence Bytes() []byte // bytes which compromise the evidence
Hash() []byte // hash of the evidence Hash() []byte // hash of the evidence
@@ -102,6 +105,11 @@ func (dve *DuplicateVoteEvidence) Height() int64 {
return dve.VoteA.Height return dve.VoteA.Height
} }
// Time returns the time when the evidence was created.
func (dve *DuplicateVoteEvidence) Time() time.Time {
return dve.VoteA.Timestamp
}
// Address returns the address of the validator. // Address returns the address of the validator.
func (dve *DuplicateVoteEvidence) Address() []byte { func (dve *DuplicateVoteEvidence) Address() []byte {
return dve.PubKey.Address() return dve.PubKey.Address()
@@ -188,6 +196,7 @@ func NewMockGoodEvidence(height int64, idx int, address []byte) MockGoodEvidence
} }
func (e MockGoodEvidence) Height() int64 { return e.Height_ } func (e MockGoodEvidence) Height() int64 { return e.Height_ }
func (e MockGoodEvidence) Time() time.Time { return tmtime.Now() }
func (e MockGoodEvidence) Address() []byte { return e.Address_ } func (e MockGoodEvidence) Address() []byte { return e.Address_ }
func (e MockGoodEvidence) Hash() []byte { func (e MockGoodEvidence) Hash() []byte {
return []byte(fmt.Sprintf("%d-%x", e.Height_, e.Address_)) return []byte(fmt.Sprintf("%d-%x", e.Height_, e.Address_))

View File

@@ -1,9 +1,12 @@
package types package types
import ( import (
"time"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/crypto/tmhash"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
tmtime "github.com/tendermint/tendermint/types/time"
) )
const ( const (
@@ -29,7 +32,7 @@ type BlockSize struct {
// EvidenceParams determine how we handle evidence of malfeasance // EvidenceParams determine how we handle evidence of malfeasance
type EvidenceParams struct { type EvidenceParams struct {
MaxAge int64 `json:"max_age"` // only accept new evidence more recent than this MaxAge tmtime.DurationPretty `json:"max_age"` // only accept new evidence more recent than this
} }
// DefaultConsensusParams returns a default ConsensusParams. // DefaultConsensusParams returns a default ConsensusParams.
@@ -51,7 +54,7 @@ func DefaultBlockSize() BlockSize {
// DefaultEvidenceParams Params returns a default EvidenceParams. // DefaultEvidenceParams Params returns a default EvidenceParams.
func DefaultEvidenceParams() EvidenceParams { func DefaultEvidenceParams() EvidenceParams {
return EvidenceParams{ return EvidenceParams{
MaxAge: 100000, // 27.8 hrs at 1block/s MaxAge: tmtime.DurationPretty{48 * time.Hour},
} }
} }
@@ -72,9 +75,9 @@ func (params *ConsensusParams) Validate() error {
params.BlockSize.MaxGas) params.BlockSize.MaxGas)
} }
if params.EvidenceParams.MaxAge <= 0 { if params.EvidenceParams.MaxAge.Duration <= 0 {
return cmn.NewError("EvidenceParams.MaxAge must be greater than 0. Got %d", return cmn.NewError("EvidenceParams.MaxAge must be greater than 0. Got %d",
params.EvidenceParams.MaxAge) params.EvidenceParams.MaxAge.Duration)
} }
return nil return nil
@@ -109,7 +112,7 @@ func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusPar
res.BlockSize.MaxGas = params2.BlockSize.MaxGas res.BlockSize.MaxGas = params2.BlockSize.MaxGas
} }
if params2.EvidenceParams != nil { if params2.EvidenceParams != nil {
res.EvidenceParams.MaxAge = params2.EvidenceParams.MaxAge res.EvidenceParams.MaxAge = tmtime.DurationPretty{params2.EvidenceParams.MaxAge}
} }
return res return res
} }

View File

@@ -4,9 +4,11 @@ import (
"bytes" "bytes"
"sort" "sort"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
) )
func TestConsensusParamsValidation(t *testing.T) { func TestConsensusParamsValidation(t *testing.T) {
@@ -15,17 +17,17 @@ func TestConsensusParamsValidation(t *testing.T) {
valid bool valid bool
}{ }{
// test block size // test block size
0: {makeParams(1, 0, 1), true}, 0: {makeParams(1, 0, 10*time.Second), true},
1: {makeParams(0, 0, 1), false}, 1: {makeParams(0, 0, 10*time.Second), false},
2: {makeParams(47*1024*1024, 0, 1), true}, 2: {makeParams(47*1024*1024, 0, 10*time.Second), true},
3: {makeParams(10, 0, 1), true}, 3: {makeParams(10, 0, 10*time.Second), true},
4: {makeParams(100*1024*1024, 0, 1), true}, 4: {makeParams(100*1024*1024, 0, 10*time.Second), true},
5: {makeParams(101*1024*1024, 0, 1), false}, 5: {makeParams(101*1024*1024, 0, 10*time.Second), false},
6: {makeParams(1024*1024*1024, 0, 1), false}, 6: {makeParams(1024*1024*1024, 0, 10*time.Second), false},
7: {makeParams(1024*1024*1024, 0, -1), false}, 7: {makeParams(1024*1024*1024, 0, -10*time.Second), false},
// test evidence age // test evidence age
8: {makeParams(1, 0, 0), false}, 8: {makeParams(1, 0, 0), false},
9: {makeParams(1, 0, -1), false}, 9: {makeParams(1, 0, -1*time.Millisecond), false},
} }
for i, tc := range testCases { for i, tc := range testCases {
if tc.valid { if tc.valid {
@@ -36,28 +38,23 @@ func TestConsensusParamsValidation(t *testing.T) {
} }
} }
func makeParams(blockBytes, blockGas, evidenceAge int64) ConsensusParams { func makeParams(blockBytes, blockGas int64, evidenceAge time.Duration) ConsensusParams {
return ConsensusParams{ return ConsensusParams{
BlockSize: BlockSize{ BlockSize: BlockSize{
MaxBytes: blockBytes, MaxBytes: blockBytes,
MaxGas: blockGas, MaxGas: blockGas,
}, },
EvidenceParams: EvidenceParams{ EvidenceParams: EvidenceParams{
MaxAge: evidenceAge, MaxAge: tmtime.DurationPretty{evidenceAge},
}, },
} }
} }
func TestConsensusParamsHash(t *testing.T) { func TestConsensusParamsHash(t *testing.T) {
params := []ConsensusParams{ params := []ConsensusParams{
makeParams(4, 2, 3), makeParams(4, 2, 3*time.Second),
makeParams(1, 4, 3), makeParams(1, 4, 3*time.Second),
makeParams(1, 2, 4), makeParams(1, 2, 4*time.Second),
makeParams(2, 5, 7),
makeParams(1, 7, 6),
makeParams(9, 5, 4),
makeParams(7, 8, 9),
makeParams(4, 6, 5),
} }
hashes := make([][]byte, len(params)) hashes := make([][]byte, len(params))
@@ -83,23 +80,23 @@ func TestConsensusParamsUpdate(t *testing.T) {
}{ }{
// empty updates // empty updates
{ {
makeParams(1, 2, 3), makeParams(1, 2, 3*time.Second),
&abci.ConsensusParams{}, &abci.ConsensusParams{},
makeParams(1, 2, 3), makeParams(1, 2, 3*time.Second),
}, },
// fine updates // fine updates
{ {
makeParams(1, 2, 3), makeParams(1, 2, 3*time.Second),
&abci.ConsensusParams{ &abci.ConsensusParams{
BlockSize: &abci.BlockSize{ BlockSize: &abci.BlockSize{
MaxBytes: 100, MaxBytes: 100,
MaxGas: 200, MaxGas: 200,
}, },
EvidenceParams: &abci.EvidenceParams{ EvidenceParams: &abci.EvidenceParams{
MaxAge: 300, MaxAge: 300 * time.Second,
}, },
}, },
makeParams(100, 200, 300), makeParams(100, 200, 300*time.Second),
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@@ -9,6 +9,7 @@ import (
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/secp256k1"
tmtime "github.com/tendermint/tendermint/types/time"
) )
//------------------------------------------------------- //-------------------------------------------------------
@@ -119,7 +120,7 @@ func (tm2pb) ConsensusParams(params *ConsensusParams) *abci.ConsensusParams {
MaxGas: params.BlockSize.MaxGas, MaxGas: params.BlockSize.MaxGas,
}, },
EvidenceParams: &abci.EvidenceParams{ EvidenceParams: &abci.EvidenceParams{
MaxAge: params.EvidenceParams.MaxAge, MaxAge: params.EvidenceParams.MaxAge.Duration,
}, },
} }
} }
@@ -209,13 +210,18 @@ func (pb2tm) ValidatorUpdates(vals []abci.ValidatorUpdate) ([]*Validator, error)
} }
func (pb2tm) ConsensusParams(csp *abci.ConsensusParams) ConsensusParams { func (pb2tm) ConsensusParams(csp *abci.ConsensusParams) ConsensusParams {
return ConsensusParams{ params := ConsensusParams{}
BlockSize: BlockSize{
// we must defensively consider any structs may be nil
if csp.BlockSize != nil {
params.BlockSize = BlockSize{
MaxBytes: csp.BlockSize.MaxBytes, MaxBytes: csp.BlockSize.MaxBytes,
MaxGas: csp.BlockSize.MaxGas, MaxGas: csp.BlockSize.MaxGas,
}, }
EvidenceParams: EvidenceParams{
MaxAge: csp.EvidenceParams.MaxAge,
},
} }
if csp.EvidenceParams != nil {
params.EvidenceParams.MaxAge = tmtime.DurationPretty{csp.EvidenceParams.MaxAge}
}
return params
} }

View File

@@ -68,7 +68,7 @@ func TestABCIValidators(t *testing.T) {
func TestABCIConsensusParams(t *testing.T) { func TestABCIConsensusParams(t *testing.T) {
cp := DefaultConsensusParams() cp := DefaultConsensusParams()
cp.EvidenceParams.MaxAge = 0 // TODO add this to ABCI cp.EvidenceParams.MaxAge.Duration = 0 // TODO add this to ABCI
abciCP := TM2PB.ConsensusParams(cp) abciCP := TM2PB.ConsensusParams(cp)
cp2 := PB2TM.ConsensusParams(abciCP) cp2 := PB2TM.ConsensusParams(abciCP)

41
types/time/duration.go Normal file
View File

@@ -0,0 +1,41 @@
package time
import (
"encoding/json"
"errors"
"time"
)
// DurationPretty is a wrapper around time.Duration implementing custom
// marshaller/unmarshaller which make it pretty (e.g. "10s", not
// "10000000000").
type DurationPretty struct {
time.Duration
}
// MarshalJSON implements json.Marshaller.
func (d DurationPretty) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
// UnmarshalJSON implements json.Unmarshaller.
func (d *DurationPretty) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
d.Duration = time.Duration(value)
return nil
case string:
var err error
d.Duration, err = time.ParseDuration(value)
if err != nil {
return err
}
return nil
default:
return errors.New("invalid duration")
}
}

View File

@@ -0,0 +1,31 @@
package time
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestDurationPretty(t *testing.T) {
testCases := []struct {
jsonBytes []byte
expErr bool
expValue time.Duration
}{
{[]byte(`"10s"`), false, 10 * time.Second},
{[]byte(`"48h0m0s"`), false, 48 * time.Hour},
{[]byte(`"10kkk"`), true, 0},
{[]byte(`"kkk"`), true, 0},
}
for i, tc := range testCases {
var d DurationPretty
if tc.expErr {
assert.Error(t, d.UnmarshalJSON(tc.jsonBytes), "#%d", i)
} else {
assert.NoError(t, d.UnmarshalJSON(tc.jsonBytes), "#%d", i)
assert.Equal(t, tc.expValue, d.Duration, "#%d", i)
}
}
}