Merge pull request #953 from tendermint/feature/time-fields

Add Timestamp to Proposal/Vote
This commit is contained in:
Anton Kaliaev 2017-12-13 12:18:55 -06:00 committed by GitHub
commit 0a2ecaa393
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 161 additions and 22 deletions

View File

@ -74,6 +74,7 @@ func (vs *validatorStub) signVote(voteType byte, hash []byte, header types.PartS
ValidatorAddress: vs.PrivValidator.GetAddress(),
Height: vs.Height,
Round: vs.Round,
Timestamp: time.Now().UTC(),
Type: voteType,
BlockID: types.BlockID{hash, header},
}

View File

@ -1466,6 +1466,7 @@ func (cs *ConsensusState) signVote(type_ byte, hash []byte, header types.PartSet
ValidatorIndex: valIndex,
Height: cs.Height,
Round: cs.Round,
Timestamp: time.Now().UTC(),
Type: type_,
BlockID: types.BlockID{hash, header},
}

View File

@ -2,6 +2,7 @@ package types
import (
"testing"
"time"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/types"
@ -54,6 +55,7 @@ func makeVoteHR(t *testing.T, height int64, round int, privVals []*types.PrivVal
ValidatorIndex: valIndex,
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
Type: types.VoteTypePrecommit,
BlockID: types.BlockID{[]byte("fakehash"), types.PartSetHeader{}},
}

View File

@ -97,6 +97,7 @@ func makeVote(header *types.Header, vals *types.ValidatorSet, key crypto.PrivKey
ValidatorIndex: idx,
Height: header.Height,
Round: 1,
Timestamp: time.Now().UTC(),
Type: types.VoteTypePrecommit,
BlockID: types.BlockID{Hash: header.Hash()},
}

View File

@ -1,11 +1,17 @@
package types
import (
"time"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
)
// canonical json is go-wire's json for structs with fields in alphabetical order
// timeFormat is used for generating the sigs
const timeFormat = wire.RFC3339Millis
type CanonicalJSONBlockID struct {
Hash data.Bytes `json:"hash,omitempty"`
PartsHeader CanonicalJSONPartSetHeader `json:"parts,omitempty"`
@ -22,13 +28,15 @@ type CanonicalJSONProposal struct {
POLBlockID CanonicalJSONBlockID `json:"pol_block_id"`
POLRound int `json:"pol_round"`
Round int `json:"round"`
Timestamp string `json:"timestamp"`
}
type CanonicalJSONVote struct {
BlockID CanonicalJSONBlockID `json:"block_id"`
Height int64 `json:"height"`
Round int `json:"round"`
Type byte `json:"type"`
BlockID CanonicalJSONBlockID `json:"block_id"`
Height int64 `json:"height"`
Round int `json:"round"`
Timestamp string `json:"timestamp"`
Type byte `json:"type"`
}
type CanonicalJSONHeartbeat struct {
@ -78,6 +86,7 @@ func CanonicalProposal(proposal *Proposal) CanonicalJSONProposal {
return CanonicalJSONProposal{
BlockPartsHeader: CanonicalPartSetHeader(proposal.BlockPartsHeader),
Height: proposal.Height,
Timestamp: CanonicalTime(proposal.Timestamp),
POLBlockID: CanonicalBlockID(proposal.POLBlockID),
POLRound: proposal.POLRound,
Round: proposal.Round,
@ -86,10 +95,11 @@ func CanonicalProposal(proposal *Proposal) CanonicalJSONProposal {
func CanonicalVote(vote *Vote) CanonicalJSONVote {
return CanonicalJSONVote{
CanonicalBlockID(vote.BlockID),
vote.Height,
vote.Round,
vote.Type,
BlockID: CanonicalBlockID(vote.BlockID),
Height: vote.Height,
Round: vote.Round,
Timestamp: CanonicalTime(vote.Timestamp),
Type: vote.Type,
}
}
@ -102,3 +112,10 @@ func CanonicalHeartbeat(heartbeat *Heartbeat) CanonicalJSONHeartbeat {
heartbeat.ValidatorIndex,
}
}
func CanonicalTime(t time.Time) string {
// note that sending time over go-wire resets it to
// local time, we need to force UTC here, so the
// signatures match
return t.UTC().Format(timeFormat)
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -165,6 +166,7 @@ func newVote(addr data.Bytes, idx int, height int64, round int, typ byte, blockI
Height: height,
Round: round,
Type: typ,
Timestamp: time.Now().UTC(),
BlockID: blockID,
}
}

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io"
"time"
"github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
@ -22,6 +23,7 @@ var (
type Proposal struct {
Height int64 `json:"height"`
Round int `json:"round"`
Timestamp time.Time `json:"timestamp"`
BlockPartsHeader PartSetHeader `json:"block_parts_header"`
POLRound int `json:"pol_round"` // -1 if null.
POLBlockID BlockID `json:"pol_block_id"` // zero if null.
@ -34,6 +36,7 @@ func NewProposal(height int64, round int, blockPartsHeader PartSetHeader, polRou
return &Proposal{
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
BlockPartsHeader: blockPartsHeader,
POLRound: polRound,
POLBlockID: polBlockID,
@ -42,8 +45,9 @@ func NewProposal(height int64, round int, blockPartsHeader PartSetHeader, polRou
// String returns a string representation of the Proposal.
func (p *Proposal) String() string {
return fmt.Sprintf("Proposal{%v/%v %v (%v,%v) %v}", p.Height, p.Round,
p.BlockPartsHeader, p.POLRound, p.POLBlockID, p.Signature)
return fmt.Sprintf("Proposal{%v/%v %v (%v,%v) %v @ %s}",
p.Height, p.Round, p.BlockPartsHeader, p.POLRound,
p.POLBlockID, p.Signature, CanonicalTime(p.Timestamp))
}
// WriteSignBytes writes the Proposal bytes for signing

View File

@ -2,25 +2,75 @@ package types
import (
"testing"
"time"
"github.com/stretchr/testify/require"
wire "github.com/tendermint/go-wire"
)
var testProposal = &Proposal{
Height: 12345,
Round: 23456,
BlockPartsHeader: PartSetHeader{111, []byte("blockparts")},
POLRound: -1,
var testProposal *Proposal
func init() {
var stamp, err = time.Parse(timeFormat, "2018-02-11T07:09:22.765Z")
if err != nil {
panic(err)
}
testProposal = &Proposal{
Height: 12345,
Round: 23456,
BlockPartsHeader: PartSetHeader{111, []byte("blockparts")},
POLRound: -1,
Timestamp: stamp,
}
}
func TestProposalSignable(t *testing.T) {
signBytes := SignBytes("test_chain_id", testProposal)
signStr := string(signBytes)
expected := `{"chain_id":"test_chain_id","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_block_id":{},"pol_round":-1,"round":23456}}`
expected := `{"chain_id":"test_chain_id","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_block_id":{},"pol_round":-1,"round":23456,"timestamp":"2018-02-11T07:09:22.765Z"}}`
if signStr != expected {
t.Errorf("Got unexpected sign string for Proposal. Expected:\n%v\nGot:\n%v", expected, signStr)
}
}
func TestProposalString(t *testing.T) {
str := testProposal.String()
expected := `Proposal{12345/23456 111:626C6F636B70 (-1,:0:000000000000) {<nil>} @ 2018-02-11T07:09:22.765Z}`
if str != expected {
t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str)
}
}
func TestProposalVerifySignature(t *testing.T) {
privVal := GenPrivValidatorFS("")
pubKey := privVal.GetPubKey()
prop := NewProposal(4, 2, PartSetHeader{777, []byte("proper")}, 2, BlockID{})
signBytes := SignBytes("test_chain_id", prop)
// sign it
signature, err := privVal.Signer.Sign(signBytes)
require.NoError(t, err)
// verify the same proposal
valid := pubKey.VerifyBytes(SignBytes("test_chain_id", prop), signature)
require.True(t, valid)
// serialize, deserialize and verify again....
newProp := new(Proposal)
bs := wire.BinaryBytes(prop)
err = wire.ReadBinaryBytes(bs, &newProp)
require.NoError(t, err)
// verify the transmitted proposal
newSignBytes := SignBytes("test_chain_id", newProp)
require.Equal(t, string(signBytes), string(newSignBytes))
valid = pubKey.VerifyBytes(newSignBytes, signature)
require.True(t, valid)
}
func BenchmarkProposalWriteSignBytes(b *testing.B) {
for i := 0; i < b.N; i++ {
SignBytes("test_chain_id", testProposal)

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io"
"time"
"github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
@ -53,6 +54,7 @@ type Vote struct {
ValidatorIndex int `json:"validator_index"`
Height int64 `json:"height"`
Round int `json:"round"`
Timestamp time.Time `json:"timestamp"`
Type byte `json:"type"`
BlockID BlockID `json:"block_id"` // zero if vote is nil.
Signature crypto.Signature `json:"signature"`
@ -84,8 +86,9 @@ func (vote *Vote) String() string {
cmn.PanicSanity("Unknown vote type")
}
return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %v}",
return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %v @ %s}",
vote.ValidatorIndex, cmn.Fingerprint(vote.ValidatorAddress),
vote.Height, vote.Round, vote.Type, typeString,
cmn.Fingerprint(vote.BlockID.Hash), vote.Signature)
cmn.Fingerprint(vote.BlockID.Hash), vote.Signature,
CanonicalTime(vote.Timestamp))
}

View File

@ -3,6 +3,7 @@ package types
import (
"bytes"
"testing"
"time"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
@ -92,6 +93,7 @@ func TestAddVote(t *testing.T) {
Height: height,
Round: round,
Type: VoteTypePrevote,
Timestamp: time.Now().UTC(),
BlockID: BlockID{nil, PartSetHeader{}},
}
_, err := signAddVote(val0, vote, voteSet)
@ -121,6 +123,7 @@ func Test2_3Majority(t *testing.T) {
Height: height,
Round: round,
Type: VoteTypePrevote,
Timestamp: time.Now().UTC(),
BlockID: BlockID{nil, PartSetHeader{}},
}
// 6 out of 10 voted for nil.
@ -176,6 +179,7 @@ func Test2_3MajorityRedux(t *testing.T) {
ValidatorIndex: -1, // NOTE: must fill in
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
Type: VoteTypePrevote,
BlockID: BlockID{blockHash, blockPartsHeader},
}
@ -270,6 +274,7 @@ func TestBadVotes(t *testing.T) {
ValidatorIndex: -1,
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
Type: VoteTypePrevote,
BlockID: BlockID{nil, PartSetHeader{}},
}
@ -331,6 +336,7 @@ func TestConflicts(t *testing.T) {
ValidatorIndex: -1,
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
Type: VoteTypePrevote,
BlockID: BlockID{nil, PartSetHeader{}},
}
@ -459,6 +465,7 @@ func TestMakeCommit(t *testing.T) {
ValidatorIndex: -1,
Height: height,
Round: round,
Timestamp: time.Now().UTC(),
Type: VoteTypePrecommit,
BlockID: BlockID{blockHash, blockPartsHeader},
}

View File

@ -2,14 +2,25 @@ package types
import (
"testing"
"time"
"github.com/stretchr/testify/require"
wire "github.com/tendermint/go-wire"
)
func TestVoteSignable(t *testing.T) {
vote := &Vote{
func exampleVote() *Vote {
var stamp, err = time.Parse(timeFormat, "2017-12-25T03:00:01.234Z")
if err != nil {
panic(err)
}
return &Vote{
ValidatorAddress: []byte("addr"),
ValidatorIndex: 56789,
Height: 12345,
Round: 23456,
Round: 2,
Timestamp: stamp,
Type: byte(2),
BlockID: BlockID{
Hash: []byte("hash"),
@ -19,12 +30,52 @@ func TestVoteSignable(t *testing.T) {
},
},
}
}
func TestVoteSignable(t *testing.T) {
vote := exampleVote()
signBytes := SignBytes("test_chain_id", vote)
signStr := string(signBytes)
expected := `{"chain_id":"test_chain_id","vote":{"block_id":{"hash":"68617368","parts":{"hash":"70617274735F68617368","total":1000000}},"height":12345,"round":23456,"type":2}}`
expected := `{"chain_id":"test_chain_id","vote":{"block_id":{"hash":"68617368","parts":{"hash":"70617274735F68617368","total":1000000}},"height":12345,"round":2,"timestamp":"2017-12-25T03:00:01.234Z","type":2}}`
if signStr != expected {
// NOTE: when this fails, you probably want to fix up consensus/replay_test too
t.Errorf("Got unexpected sign string for Vote. Expected:\n%v\nGot:\n%v", expected, signStr)
}
}
func TestVoteString(t *testing.T) {
str := exampleVote().String()
expected := `Vote{56789:616464720000 12345/02/2(Precommit) 686173680000 {<nil>} @ 2017-12-25T03:00:01.234Z}`
if str != expected {
t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str)
}
}
func TestVoteVerifySignature(t *testing.T) {
privVal := GenPrivValidatorFS("")
pubKey := privVal.GetPubKey()
vote := exampleVote()
signBytes := SignBytes("test_chain_id", vote)
// sign it
signature, err := privVal.Signer.Sign(signBytes)
require.NoError(t, err)
// verify the same vote
valid := pubKey.VerifyBytes(SignBytes("test_chain_id", vote), signature)
require.True(t, valid)
// serialize, deserialize and verify again....
precommit := new(Vote)
bs := wire.BinaryBytes(vote)
err = wire.ReadBinaryBytes(bs, &precommit)
require.NoError(t, err)
// verify the transmitted vote
newSignBytes := SignBytes("test_chain_id", precommit)
require.Equal(t, string(signBytes), string(newSignBytes))
valid = pubKey.VerifyBytes(newSignBytes, signature)
require.True(t, valid)
}