Merge pull request #953 from tendermint/feature/time-fields
Add Timestamp to Proposal/Vote
This commit is contained in:
commit
0a2ecaa393
|
@ -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},
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
}
|
||||
|
|
|
@ -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{}},
|
||||
}
|
||||
|
|
|
@ -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()},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue