mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-09 06:33:16 +00:00
Merge pull request #1265 from tendermint/bucky/new-wire-api
Bucky/new wire api
This commit is contained in:
13
Gopkg.lock
generated
13
Gopkg.lock
generated
@@ -224,6 +224,7 @@
|
||||
revision = "34011bf325bce385408353a30b101fe5e923eb6e"
|
||||
|
||||
[[projects]]
|
||||
branch = "develop"
|
||||
name = "github.com/tendermint/abci"
|
||||
packages = [
|
||||
"client",
|
||||
@@ -233,7 +234,7 @@
|
||||
"server",
|
||||
"types"
|
||||
]
|
||||
revision = "345a5a5a34aa31ad00626266f59e4ae7652ee274"
|
||||
revision = "9e0e00bef42aebf6b402f66bf0f3dc607de8a6f3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -248,8 +249,8 @@
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/go-crypto"
|
||||
packages = ["."]
|
||||
revision = "dd20358a264c772b4a83e477b0cfce4c88a7001d"
|
||||
version = "v0.4.1"
|
||||
revision = "c3e19f3ea26f5c3357e0bcbb799b0761ef923755"
|
||||
version = "v0.5.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/go-wire"
|
||||
@@ -257,8 +258,8 @@
|
||||
".",
|
||||
"data"
|
||||
]
|
||||
revision = "b6fc872b42d41158a60307db4da051dd6f179415"
|
||||
version = "v0.7.2"
|
||||
revision = "fa721242b042ecd4c6ed1a934ee740db4f74e45c"
|
||||
version = "v0.7.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
@@ -371,6 +372,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "a65ef86e3db67769c1fa4ae1f67af8d5b95474f4b95ec799d8572e19b44b206a"
|
||||
inputs-digest = "36505c5f341e1e80d7d55589a17727bbd9e89403624b4e02c9e8a21b1ed39d0f"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
@@ -71,15 +71,15 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/abci"
|
||||
revision = "345a5a5a34aa31ad00626266f59e4ae7652ee274"
|
||||
branch = "develop"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/go-crypto"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/go-wire"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
|
||||
@@ -289,17 +289,17 @@ func (privVal *ByzantinePrivValidator) GetPubKey() crypto.PubKey {
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) SignVote(chainID string, vote *types.Vote) (err error) {
|
||||
vote.Signature, err = privVal.Sign(types.SignBytes(chainID, vote))
|
||||
vote.Signature, err = privVal.Sign(vote.SignBytes(chainID))
|
||||
return err
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) SignProposal(chainID string, proposal *types.Proposal) (err error) {
|
||||
proposal.Signature, _ = privVal.Sign(types.SignBytes(chainID, proposal))
|
||||
proposal.Signature, _ = privVal.Sign(proposal.SignBytes(chainID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) (err error) {
|
||||
heartbeat.Signature, _ = privVal.Sign(types.SignBytes(chainID, heartbeat))
|
||||
heartbeat.Signature, _ = privVal.Sign(heartbeat.SignBytes(chainID))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package consensus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
@@ -190,13 +191,23 @@ type Handshaker struct {
|
||||
stateDB dbm.DB
|
||||
initialState sm.State
|
||||
store types.BlockStore
|
||||
appState json.RawMessage
|
||||
logger log.Logger
|
||||
|
||||
nBlocks int // number of blocks applied to the state
|
||||
}
|
||||
|
||||
func NewHandshaker(stateDB dbm.DB, state sm.State, store types.BlockStore) *Handshaker {
|
||||
return &Handshaker{stateDB, state, store, log.NewNopLogger(), 0}
|
||||
func NewHandshaker(stateDB dbm.DB, state sm.State,
|
||||
store types.BlockStore, appState json.RawMessage) *Handshaker {
|
||||
|
||||
return &Handshaker{
|
||||
stateDB: stateDB,
|
||||
initialState: state,
|
||||
store: store,
|
||||
appState: appState,
|
||||
logger: log.NewNopLogger(),
|
||||
nBlocks: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handshaker) SetLogger(l log.Logger) {
|
||||
@@ -249,9 +260,12 @@ func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight
|
||||
// If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain
|
||||
if appBlockHeight == 0 {
|
||||
validators := types.TM2PB.Validators(state.Validators)
|
||||
// TODO: get the genesis bytes (https://github.com/tendermint/tendermint/issues/1224)
|
||||
var genesisBytes []byte
|
||||
if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{validators, genesisBytes}); err != nil {
|
||||
req := abci.RequestInitChain{
|
||||
Validators: validators,
|
||||
AppStateBytes: h.appState,
|
||||
}
|
||||
_, err := proxyApp.Consensus().InitChainSync(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,14 +287,19 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo
|
||||
|
||||
// Get State
|
||||
stateDB := dbm.NewDB("state", dbType, config.DBDir())
|
||||
state, err := sm.MakeGenesisStateFromFile(config.GenesisFile())
|
||||
gdoc, err := sm.MakeGenesisDocFromFile(config.GenesisFile())
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
state, err := sm.MakeGenesisState(gdoc)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
|
||||
// Create proxyAppConn connection (consensus, mempool, query)
|
||||
clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir())
|
||||
proxyApp := proxy.NewAppConns(clientCreator, NewHandshaker(stateDB, state, blockStore))
|
||||
proxyApp := proxy.NewAppConns(clientCreator,
|
||||
NewHandshaker(stateDB, state, blockStore, gdoc.AppState))
|
||||
err = proxyApp.Start()
|
||||
if err != nil {
|
||||
cmn.Exit(cmn.Fmt("Error starting proxy app conns: %v", err))
|
||||
|
||||
@@ -362,7 +362,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
||||
}
|
||||
|
||||
// now start the app using the handshake - it should sync
|
||||
handshaker := NewHandshaker(stateDB, state, store)
|
||||
handshaker := NewHandshaker(stateDB, state, store, nil)
|
||||
proxyApp := proxy.NewAppConns(clientCreator2, handshaker)
|
||||
if err := proxyApp.Start(); err != nil {
|
||||
t.Fatalf("Error starting proxy app connections: %v", err)
|
||||
|
||||
@@ -1269,7 +1269,7 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error {
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
if !cs.Validators.GetProposer().PubKey.VerifyBytes(types.SignBytes(cs.state.ChainID, proposal), proposal.Signature) {
|
||||
if !cs.Validators.GetProposer().PubKey.VerifyBytes(proposal.SignBytes(cs.state.ChainID), proposal.Signature) {
|
||||
return ErrInvalidProposalSignature
|
||||
}
|
||||
|
||||
|
||||
@@ -218,5 +218,5 @@ func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blo
|
||||
if voteSet == nil {
|
||||
return nil // something we don't know about yet
|
||||
}
|
||||
return voteSet.SetPeerMaj23(peerID, blockID)
|
||||
return voteSet.SetPeerMaj23(types.P2PID(peerID), blockID)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) {
|
||||
return nil, errors.Wrap(err, "failed to make genesis state")
|
||||
}
|
||||
blockStore := bc.NewBlockStore(blockStoreDB)
|
||||
handshaker := NewHandshaker(stateDB, state, blockStore)
|
||||
handshaker := NewHandshaker(stateDB, state, blockStore, genDoc.AppState)
|
||||
proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app), handshaker)
|
||||
proxyApp.SetLogger(logger.With("module", "proxy"))
|
||||
if err := proxyApp.Start(); err != nil {
|
||||
|
||||
@@ -102,7 +102,7 @@ func makeVote(header *types.Header, vals *types.ValidatorSet, key crypto.PrivKey
|
||||
BlockID: types.BlockID{Hash: header.Hash()},
|
||||
}
|
||||
// Sign it
|
||||
signBytes := types.SignBytes(header.ChainID, vote)
|
||||
signBytes := vote.SignBytes(header.ChainID)
|
||||
vote.Signature = key.Sign(signBytes)
|
||||
return vote
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func NewNode(config *cfg.Config,
|
||||
// and sync tendermint and the app by performing a handshake
|
||||
// and replaying any necessary blocks
|
||||
consensusLogger := logger.With("module", "consensus")
|
||||
handshaker := cs.NewHandshaker(stateDB, state, blockStore)
|
||||
handshaker := cs.NewHandshaker(stateDB, state, blockStore, genDoc.AppState)
|
||||
handshaker.SetLogger(consensusLogger)
|
||||
proxyApp := proxy.NewAppConns(clientCreator, handshaker)
|
||||
proxyApp.SetLogger(logger.With("module", "proxy"))
|
||||
|
||||
@@ -187,7 +187,6 @@ func argsToJson(args map[string]interface{}) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Pass everything else to go-wire
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -86,7 +86,11 @@ func (s State) Equals(s2 State) bool {
|
||||
|
||||
// Bytes serializes the State using go-wire.
|
||||
func (s State) Bytes() []byte {
|
||||
return wire.BinaryBytes(s)
|
||||
bz, err := wire.MarshalBinary(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the State is equal to the empty State.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
@@ -70,12 +69,11 @@ func loadState(db dbm.DB, key []byte) (state State) {
|
||||
return state
|
||||
}
|
||||
|
||||
r, n, err := bytes.NewReader(buf), new(int), new(error)
|
||||
wire.ReadBinaryPtr(&state, r, 0, n, err)
|
||||
if *err != nil {
|
||||
err := wire.UnmarshalBinary(buf, &state)
|
||||
if err != nil {
|
||||
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
||||
cmn.Exit(cmn.Fmt(`LoadState: Data has been corrupted or its spec has changed:
|
||||
%v\n`, *err))
|
||||
%v\n`, err))
|
||||
}
|
||||
// TODO: ensure that buf is completely read.
|
||||
|
||||
@@ -113,7 +111,11 @@ func NewABCIResponses(block *types.Block) *ABCIResponses {
|
||||
|
||||
// Bytes serializes the ABCIResponse using go-wire
|
||||
func (a *ABCIResponses) Bytes() []byte {
|
||||
return wire.BinaryBytes(*a)
|
||||
bz, err := wire.MarshalBinary(*a)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
func (a *ABCIResponses) ResultsHash() []byte {
|
||||
@@ -131,12 +133,11 @@ func LoadABCIResponses(db dbm.DB, height int64) (*ABCIResponses, error) {
|
||||
}
|
||||
|
||||
abciResponses := new(ABCIResponses)
|
||||
r, n, err := bytes.NewReader(buf), new(int), new(error)
|
||||
wire.ReadBinaryPtr(abciResponses, r, 0, n, err)
|
||||
if *err != nil {
|
||||
err := wire.UnmarshalBinary(buf, abciResponses)
|
||||
if err != nil {
|
||||
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
||||
cmn.Exit(cmn.Fmt(`LoadABCIResponses: Data has been corrupted or its spec has
|
||||
changed: %v\n`, *err))
|
||||
changed: %v\n`, err))
|
||||
}
|
||||
// TODO: ensure that buf is completely read.
|
||||
|
||||
@@ -160,7 +161,11 @@ type ValidatorsInfo struct {
|
||||
|
||||
// Bytes serializes the ValidatorsInfo using go-wire
|
||||
func (valInfo *ValidatorsInfo) Bytes() []byte {
|
||||
return wire.BinaryBytes(*valInfo)
|
||||
bz, err := wire.MarshalBinary(*valInfo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
// LoadValidators loads the ValidatorSet for a given height.
|
||||
@@ -189,12 +194,11 @@ func loadValidatorsInfo(db dbm.DB, height int64) *ValidatorsInfo {
|
||||
}
|
||||
|
||||
v := new(ValidatorsInfo)
|
||||
r, n, err := bytes.NewReader(buf), new(int), new(error)
|
||||
wire.ReadBinaryPtr(v, r, 0, n, err)
|
||||
if *err != nil {
|
||||
err := wire.UnmarshalBinary(buf, v)
|
||||
if err != nil {
|
||||
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
||||
cmn.Exit(cmn.Fmt(`LoadValidators: Data has been corrupted or its spec has changed:
|
||||
%v\n`, *err))
|
||||
%v\n`, err))
|
||||
}
|
||||
// TODO: ensure that buf is completely read.
|
||||
|
||||
@@ -225,7 +229,11 @@ type ConsensusParamsInfo struct {
|
||||
|
||||
// Bytes serializes the ConsensusParamsInfo using go-wire
|
||||
func (params ConsensusParamsInfo) Bytes() []byte {
|
||||
return wire.BinaryBytes(params)
|
||||
bz, err := wire.MarshalBinary(params)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
// LoadConsensusParams loads the ConsensusParams for a given height.
|
||||
@@ -255,12 +263,11 @@ func loadConsensusParamsInfo(db dbm.DB, height int64) *ConsensusParamsInfo {
|
||||
}
|
||||
|
||||
paramsInfo := new(ConsensusParamsInfo)
|
||||
r, n, err := bytes.NewReader(buf), new(int), new(error)
|
||||
wire.ReadBinaryPtr(paramsInfo, r, 0, n, err)
|
||||
if *err != nil {
|
||||
err := wire.UnmarshalBinary(buf, paramsInfo)
|
||||
if err != nil {
|
||||
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
||||
cmn.Exit(cmn.Fmt(`LoadConsensusParams: Data has been corrupted or its spec has changed:
|
||||
%v\n`, *err))
|
||||
%v\n`, err))
|
||||
}
|
||||
// TODO: ensure that buf is completely read.
|
||||
|
||||
|
||||
@@ -67,10 +67,8 @@ func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r := bytes.NewReader(rawBytes)
|
||||
var n int
|
||||
var err error
|
||||
txResult := wire.ReadBinary(&types.TxResult{}, r, 0, &n, &err).(*types.TxResult)
|
||||
txResult := new(types.TxResult)
|
||||
err := wire.UnmarshalBinary(rawBytes, &txResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading TxResult: %v", err)
|
||||
}
|
||||
@@ -93,7 +91,10 @@ func (txi *TxIndex) AddBatch(b *txindex.Batch) error {
|
||||
}
|
||||
|
||||
// index tx by hash
|
||||
rawBytes := wire.BinaryBytes(result)
|
||||
rawBytes, err := wire.MarshalBinary(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storeBatch.Set(hash, rawBytes)
|
||||
}
|
||||
|
||||
@@ -115,7 +116,10 @@ func (txi *TxIndex) Index(result *types.TxResult) error {
|
||||
}
|
||||
|
||||
// index tx by hash
|
||||
rawBytes := wire.BinaryBytes(result)
|
||||
rawBytes, err := wire.MarshalBinary(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Set(hash, rawBytes)
|
||||
|
||||
b.Write()
|
||||
|
||||
@@ -4,11 +4,10 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
wire "github.com/tendermint/tendermint/wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/merkle"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
@@ -96,7 +95,11 @@ func (b *Block) Hash() cmn.HexBytes {
|
||||
// MakePartSet returns a PartSet containing parts of a serialized block.
|
||||
// This is the form in which the block is gossipped to peers.
|
||||
func (b *Block) MakePartSet(partSize int) *PartSet {
|
||||
return NewPartSetFromData(wire.BinaryBytes(b), partSize)
|
||||
bz, err := wire.MarshalBinary(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return NewPartSetFromData(bz, partSize)
|
||||
}
|
||||
|
||||
// HashesTo is a convenience function that checks if a block hashes to the given argument.
|
||||
@@ -249,7 +252,8 @@ type Commit struct {
|
||||
bitArray *cmn.BitArray
|
||||
}
|
||||
|
||||
// FirstPrecommit returns the first non-nil precommit in the commit
|
||||
// FirstPrecommit returns the first non-nil precommit in the commit.
|
||||
// If all precommits are nil, it returns an empty precommit with height 0.
|
||||
func (commit *Commit) FirstPrecommit() *Vote {
|
||||
if len(commit.Precommits) == 0 {
|
||||
return nil
|
||||
@@ -263,7 +267,9 @@ func (commit *Commit) FirstPrecommit() *Vote {
|
||||
return precommit
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return &Vote{
|
||||
Type: VoteTypePrecommit,
|
||||
}
|
||||
}
|
||||
|
||||
// Height returns the height of the commit
|
||||
@@ -493,17 +499,11 @@ func (blockID BlockID) Equals(other BlockID) bool {
|
||||
|
||||
// Key returns a machine-readable string representation of the BlockID
|
||||
func (blockID BlockID) Key() string {
|
||||
return string(blockID.Hash) + string(wire.BinaryBytes(blockID.PartsHeader))
|
||||
}
|
||||
|
||||
// WriteSignBytes writes the canonical bytes of the BlockID to the given writer for digital signing
|
||||
func (blockID BlockID) WriteSignBytes(w io.Writer, n *int, err *error) {
|
||||
if blockID.IsZero() {
|
||||
wire.WriteTo([]byte("null"), w, n, err)
|
||||
} else {
|
||||
wire.WriteJSON(CanonicalBlockID(blockID), w, n, err)
|
||||
bz, err := wire.MarshalBinary(blockID.PartsHeader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return string(blockID.Hash) + string(bz)
|
||||
}
|
||||
|
||||
// String returns a human readable string representation of the BlockID
|
||||
@@ -518,15 +518,24 @@ type hasher struct {
|
||||
}
|
||||
|
||||
func (h hasher) Hash() []byte {
|
||||
hasher, n, err := ripemd160.New(), new(int), new(error)
|
||||
wire.WriteBinary(h.item, hasher, n, err)
|
||||
if *err != nil {
|
||||
hasher := ripemd160.New()
|
||||
bz, err := wire.MarshalBinary(h.item)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = hasher.Write(bz)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return hasher.Sum(nil)
|
||||
|
||||
}
|
||||
|
||||
func tmHash(item interface{}) []byte {
|
||||
h := hasher{item}
|
||||
return h.Hash()
|
||||
}
|
||||
|
||||
func wireHasher(item interface{}) merkle.Hasher {
|
||||
return hasher{item}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package types
|
||||
import (
|
||||
"time"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
wire "github.com/tendermint/tendermint/wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
// canonical json is go-wire's json for structs with fields in alphabetical order
|
||||
// canonical json is wire's json for structs with fields in alphabetical order
|
||||
|
||||
// TimeFormat is used for generating the sigs
|
||||
const TimeFormat = wire.RFC3339Millis
|
||||
@@ -114,7 +114,7 @@ func CanonicalHeartbeat(heartbeat *Heartbeat) CanonicalJSONHeartbeat {
|
||||
}
|
||||
|
||||
func CanonicalTime(t time.Time) string {
|
||||
// note that sending time over go-wire resets it to
|
||||
// note that sending time over wire resets it to
|
||||
// local time, we need to force UTC here, so the
|
||||
// signatures match
|
||||
return t.UTC().Format(TimeFormat)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
wire "github.com/tendermint/tendermint/wire"
|
||||
"github.com/tendermint/tmlibs/merkle"
|
||||
)
|
||||
|
||||
@@ -148,10 +148,10 @@ func (dve *DuplicateVoteEvidence) Verify(chainID string) error {
|
||||
}
|
||||
|
||||
// Signatures must be valid
|
||||
if !dve.PubKey.VerifyBytes(SignBytes(chainID, dve.VoteA), dve.VoteA.Signature) {
|
||||
if !dve.PubKey.VerifyBytes(dve.VoteA.SignBytes(chainID), dve.VoteA.Signature) {
|
||||
return fmt.Errorf("DuplicateVoteEvidence Error verifying VoteA: %v", ErrVoteInvalidSignature)
|
||||
}
|
||||
if !dve.PubKey.VerifyBytes(SignBytes(chainID, dve.VoteB), dve.VoteB.Signature) {
|
||||
if !dve.PubKey.VerifyBytes(dve.VoteB.SignBytes(chainID), dve.VoteB.Signature) {
|
||||
return fmt.Errorf("DuplicateVoteEvidence Error verifying VoteB: %v", ErrVoteInvalidSignature)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ func makeVote(val *PrivValidatorFS, chainID string, valIndex int, height int64,
|
||||
Type: byte(step),
|
||||
BlockID: blockID,
|
||||
}
|
||||
sig := val.PrivKey.Sign(SignBytes(chainID, v))
|
||||
sig := val.PrivKey.Sign(v.SignBytes(chainID))
|
||||
v.Signature = sig
|
||||
return v
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestEvidence(t *testing.T) {
|
||||
|
||||
vote1 := makeVote(val, chainID, 0, 10, 2, 1, blockID)
|
||||
badVote := makeVote(val, chainID, 0, 10, 2, 1, blockID)
|
||||
badVote.Signature = val2.PrivKey.Sign(SignBytes(chainID, badVote))
|
||||
badVote.Signature = val2.PrivKey.Sign(badVote.SignBytes(chainID))
|
||||
|
||||
cases := []voteData{
|
||||
{vote1, makeVote(val, chainID, 0, 10, 2, 1, blockID2), true}, // different block ids
|
||||
|
||||
@@ -2,10 +2,9 @@ package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
@@ -23,13 +22,17 @@ type Heartbeat struct {
|
||||
Signature crypto.Signature `json:"signature"`
|
||||
}
|
||||
|
||||
// WriteSignBytes writes the Heartbeat for signing.
|
||||
// SignBytes returns the Heartbeat bytes for signing.
|
||||
// It panics if the Heartbeat is nil.
|
||||
func (heartbeat *Heartbeat) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
|
||||
wire.WriteJSON(CanonicalJSONOnceHeartbeat{
|
||||
func (heartbeat *Heartbeat) SignBytes(chainID string) []byte {
|
||||
bz, err := wire.MarshalJSON(CanonicalJSONOnceHeartbeat{
|
||||
chainID,
|
||||
CanonicalHeartbeat(heartbeat),
|
||||
}, w, n, err)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
// Copy makes a copy of the Heartbeat.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -34,23 +33,18 @@ func TestHeartbeatString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHeartbeatWriteSignBytes(t *testing.T) {
|
||||
var n int
|
||||
var err error
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1}
|
||||
hb.WriteSignBytes("0xdeadbeef", buf, &n, &err)
|
||||
require.Equal(t, buf.String(), `{"chain_id":"0xdeadbeef","heartbeat":{"height":10,"round":1,"sequence":0,"validator_address":"","validator_index":1}}`)
|
||||
bz := hb.SignBytes("0xdeadbeef")
|
||||
require.Equal(t, string(bz), `{"chain_id":"0xdeadbeef","heartbeat":{"height":10,"round":1,"sequence":0,"validator_address":"","validator_index":1}}`)
|
||||
|
||||
buf.Reset()
|
||||
plainHb := &Heartbeat{}
|
||||
plainHb.WriteSignBytes("0xdeadbeef", buf, &n, &err)
|
||||
require.Equal(t, buf.String(), `{"chain_id":"0xdeadbeef","heartbeat":{"height":0,"round":0,"sequence":0,"validator_address":"","validator_index":0}}`)
|
||||
bz = plainHb.SignBytes("0xdeadbeef")
|
||||
require.Equal(t, string(bz), `{"chain_id":"0xdeadbeef","heartbeat":{"height":0,"round":0,"sequence":0,"validator_address":"","validator_index":0}}`)
|
||||
|
||||
require.Panics(t, func() {
|
||||
buf.Reset()
|
||||
var nilHb *Heartbeat
|
||||
nilHb.WriteSignBytes("0xdeadbeef", buf, &n, &err)
|
||||
require.Equal(t, buf.String(), "null")
|
||||
bz := nilHb.SignBytes("0xdeadbeef")
|
||||
require.Equal(t, string(bz), "null")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
|
||||
"github.com/tendermint/go-wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/merkle"
|
||||
)
|
||||
@@ -73,10 +72,6 @@ func (psh PartSetHeader) Equals(other PartSetHeader) bool {
|
||||
return psh.Total == other.Total && bytes.Equal(psh.Hash, other.Hash)
|
||||
}
|
||||
|
||||
func (psh PartSetHeader) WriteSignBytes(w io.Writer, n *int, err *error) {
|
||||
wire.WriteJSON(CanonicalPartSetHeader(psh), w, n, err)
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
type PartSet struct {
|
||||
|
||||
@@ -234,10 +234,11 @@ func (privVal *PrivValidatorFS) save() {
|
||||
// Reset resets all fields in the PrivValidatorFS.
|
||||
// NOTE: Unsafe!
|
||||
func (privVal *PrivValidatorFS) Reset() {
|
||||
var sig crypto.Signature
|
||||
privVal.LastHeight = 0
|
||||
privVal.LastRound = 0
|
||||
privVal.LastStep = 0
|
||||
privVal.LastSignature = crypto.Signature{}
|
||||
privVal.LastSignature = sig
|
||||
privVal.LastSignBytes = nil
|
||||
privVal.Save()
|
||||
}
|
||||
@@ -297,7 +298,7 @@ func (privVal *PrivValidatorFS) checkHRS(height int64, round int, step int8) (bo
|
||||
// a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
|
||||
func (privVal *PrivValidatorFS) signVote(chainID string, vote *Vote) error {
|
||||
height, round, step := vote.Height, vote.Round, voteToStep(vote)
|
||||
signBytes := SignBytes(chainID, vote)
|
||||
signBytes := vote.SignBytes(chainID)
|
||||
|
||||
sameHRS, err := privVal.checkHRS(height, round, step)
|
||||
if err != nil {
|
||||
@@ -336,7 +337,7 @@ func (privVal *PrivValidatorFS) signVote(chainID string, vote *Vote) error {
|
||||
// a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
|
||||
func (privVal *PrivValidatorFS) signProposal(chainID string, proposal *Proposal) error {
|
||||
height, round, step := proposal.Height, proposal.Round, stepPropose
|
||||
signBytes := SignBytes(chainID, proposal)
|
||||
signBytes := proposal.SignBytes(chainID)
|
||||
|
||||
sameHRS, err := privVal.checkHRS(height, round, step)
|
||||
if err != nil {
|
||||
@@ -388,7 +389,7 @@ func (privVal *PrivValidatorFS) SignHeartbeat(chainID string, heartbeat *Heartbe
|
||||
privVal.mtx.Lock()
|
||||
defer privVal.mtx.Unlock()
|
||||
var err error
|
||||
heartbeat.Signature, err = privVal.Sign(SignBytes(chainID, heartbeat))
|
||||
heartbeat.Signature, err = privVal.Sign(heartbeat.SignBytes(chainID))
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||
proposal := newProposal(height, round, block1)
|
||||
err := privVal.SignProposal(chainID, proposal)
|
||||
assert.NoError(t, err, "expected no error signing proposal")
|
||||
signBytes := types.SignBytes(chainID, proposal)
|
||||
signBytes := proposal.SignBytes(chainID)
|
||||
sig := proposal.Signature
|
||||
timeStamp := clipToMS(proposal.Timestamp)
|
||||
|
||||
@@ -222,7 +222,7 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||
assert.NoError(t, err, "expected no error on signing same proposal")
|
||||
|
||||
assert.Equal(t, timeStamp, proposal.Timestamp)
|
||||
assert.Equal(t, signBytes, types.SignBytes(chainID, proposal))
|
||||
assert.Equal(t, signBytes, proposal.SignBytes(chainID))
|
||||
assert.Equal(t, sig, proposal.Signature)
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||
err = privVal.SignVote("mychainid", vote)
|
||||
assert.NoError(t, err, "expected no error signing vote")
|
||||
|
||||
signBytes := types.SignBytes(chainID, vote)
|
||||
signBytes := vote.SignBytes(chainID)
|
||||
sig := vote.Signature
|
||||
timeStamp := clipToMS(vote.Timestamp)
|
||||
|
||||
@@ -248,7 +248,7 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||
assert.NoError(t, err, "expected no error on signing same vote")
|
||||
|
||||
assert.Equal(t, timeStamp, vote.Timestamp)
|
||||
assert.Equal(t, signBytes, types.SignBytes(chainID, vote))
|
||||
assert.Equal(t, signBytes, vote.SignBytes(chainID))
|
||||
assert.Equal(t, sig, vote.Signature)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func (info *LastSignedInfo) Reset() {
|
||||
// Else it returns an error.
|
||||
func (lsi *LastSignedInfo) SignVote(signer types.Signer, chainID string, vote *types.Vote) error {
|
||||
height, round, step := vote.Height, vote.Round, voteToStep(vote)
|
||||
signBytes := types.SignBytes(chainID, vote)
|
||||
signBytes := vote.SignBytes(chainID)
|
||||
|
||||
sameHRS, err := lsi.Verify(height, round, step)
|
||||
if err != nil {
|
||||
@@ -151,7 +151,7 @@ func (lsi *LastSignedInfo) SignVote(signer types.Signer, chainID string, vote *t
|
||||
// Else it returns an error.
|
||||
func (lsi *LastSignedInfo) SignProposal(signer types.Signer, chainID string, proposal *types.Proposal) error {
|
||||
height, round, step := proposal.Height, proposal.Round, stepPropose
|
||||
signBytes := types.SignBytes(chainID, proposal)
|
||||
signBytes := proposal.SignBytes(chainID)
|
||||
|
||||
sameHRS, err := lsi.Verify(height, round, step)
|
||||
if err != nil {
|
||||
|
||||
@@ -61,6 +61,6 @@ func (upv *PrivValidatorUnencrypted) SignProposal(chainID string, proposal *type
|
||||
|
||||
func (upv *PrivValidatorUnencrypted) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error {
|
||||
var err error
|
||||
heartbeat.Signature, err = upv.PrivKey.Sign(types.SignBytes(chainID, heartbeat))
|
||||
heartbeat.Signature, err = upv.PrivKey.Sign(heartbeat.SignBytes(chainID))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -185,18 +185,19 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||
proposal := newProposal(height, round, block1)
|
||||
err := privVal.SignProposal(chainID, proposal)
|
||||
assert.NoError(t, err, "expected no error signing proposal")
|
||||
signBytes := SignBytes(chainID, proposal)
|
||||
signBytes := proposal.SignBytes(chainID)
|
||||
sig := proposal.Signature
|
||||
timeStamp := clipToMS(proposal.Timestamp)
|
||||
|
||||
// manipulate the timestamp. should get changed back
|
||||
proposal.Timestamp = proposal.Timestamp.Add(time.Millisecond)
|
||||
proposal.Signature = crypto.Signature{}
|
||||
var emptySig crypto.Signature
|
||||
proposal.Signature = emptySig
|
||||
err = privVal.SignProposal("mychainid", proposal)
|
||||
assert.NoError(t, err, "expected no error on signing same proposal")
|
||||
|
||||
assert.Equal(t, timeStamp, proposal.Timestamp)
|
||||
assert.Equal(t, signBytes, SignBytes(chainID, proposal))
|
||||
assert.Equal(t, signBytes, proposal.SignBytes(chainID))
|
||||
assert.Equal(t, sig, proposal.Signature)
|
||||
}
|
||||
|
||||
@@ -208,18 +209,19 @@ func TestDifferByTimestamp(t *testing.T) {
|
||||
err := privVal.SignVote("mychainid", vote)
|
||||
assert.NoError(t, err, "expected no error signing vote")
|
||||
|
||||
signBytes := SignBytes(chainID, vote)
|
||||
signBytes := vote.SignBytes(chainID)
|
||||
sig := vote.Signature
|
||||
timeStamp := clipToMS(vote.Timestamp)
|
||||
|
||||
// manipulate the timestamp. should get changed back
|
||||
vote.Timestamp = vote.Timestamp.Add(time.Millisecond)
|
||||
vote.Signature = crypto.Signature{}
|
||||
var emptySig crypto.Signature
|
||||
vote.Signature = emptySig
|
||||
err = privVal.SignVote("mychainid", vote)
|
||||
assert.NoError(t, err, "expected no error on signing same vote")
|
||||
|
||||
assert.Equal(t, timeStamp, vote.Timestamp)
|
||||
assert.Equal(t, signBytes, SignBytes(chainID, vote))
|
||||
assert.Equal(t, signBytes, vote.SignBytes(chainID))
|
||||
assert.Equal(t, sig, vote.Signature)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ package types
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -50,10 +49,14 @@ func (p *Proposal) String() string {
|
||||
p.POLBlockID, p.Signature, CanonicalTime(p.Timestamp))
|
||||
}
|
||||
|
||||
// WriteSignBytes writes the Proposal bytes for signing
|
||||
func (p *Proposal) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
|
||||
wire.WriteJSON(CanonicalJSONOnceProposal{
|
||||
// SignBytes returns the Proposal bytes for signing
|
||||
func (p *Proposal) SignBytes(chainID string) []byte {
|
||||
bz, err := wire.MarshalJSON(CanonicalJSONOnceProposal{
|
||||
ChainID: chainID,
|
||||
Proposal: CanonicalProposal(p),
|
||||
}, w, n, err)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
wire "github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
var testProposal *Proposal
|
||||
@@ -26,7 +26,7 @@ func init() {
|
||||
}
|
||||
|
||||
func TestProposalSignable(t *testing.T) {
|
||||
signBytes := SignBytes("test_chain_id", testProposal)
|
||||
signBytes := testProposal.SignBytes("test_chain_id")
|
||||
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,"timestamp":"2018-02-11T07:09:22.765Z"}}`
|
||||
@@ -48,24 +48,25 @@ func TestProposalVerifySignature(t *testing.T) {
|
||||
pubKey := privVal.GetPubKey()
|
||||
|
||||
prop := NewProposal(4, 2, PartSetHeader{777, []byte("proper")}, 2, BlockID{})
|
||||
signBytes := SignBytes("test_chain_id", prop)
|
||||
signBytes := prop.SignBytes("test_chain_id")
|
||||
|
||||
// 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)
|
||||
valid := pubKey.VerifyBytes(prop.SignBytes("test_chain_id"), signature)
|
||||
require.True(t, valid)
|
||||
|
||||
// serialize, deserialize and verify again....
|
||||
newProp := new(Proposal)
|
||||
bs := wire.BinaryBytes(prop)
|
||||
err = wire.ReadBinaryBytes(bs, &newProp)
|
||||
bs, err := wire.MarshalBinary(prop)
|
||||
require.NoError(t, err)
|
||||
err = wire.UnmarshalBinary(bs, &newProp)
|
||||
require.NoError(t, err)
|
||||
|
||||
// verify the transmitted proposal
|
||||
newSignBytes := SignBytes("test_chain_id", newProp)
|
||||
newSignBytes := newProp.SignBytes("test_chain_id")
|
||||
require.Equal(t, string(signBytes), string(newSignBytes))
|
||||
valid = pubKey.VerifyBytes(newSignBytes, signature)
|
||||
require.True(t, valid)
|
||||
@@ -73,14 +74,14 @@ func TestProposalVerifySignature(t *testing.T) {
|
||||
|
||||
func BenchmarkProposalWriteSignBytes(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
SignBytes("test_chain_id", testProposal)
|
||||
testProposal.SignBytes("test_chain_id")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkProposalSign(b *testing.B) {
|
||||
privVal := GenPrivValidatorFS("")
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := privVal.Signer.Sign(SignBytes("test_chain_id", testProposal))
|
||||
_, err := privVal.Signer.Sign(testProposal.SignBytes("test_chain_id"))
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
@@ -88,12 +89,12 @@ func BenchmarkProposalSign(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkProposalVerifySignature(b *testing.B) {
|
||||
signBytes := SignBytes("test_chain_id", testProposal)
|
||||
signBytes := testProposal.SignBytes("test_chain_id")
|
||||
privVal := GenPrivValidatorFS("")
|
||||
signature, _ := privVal.Signer.Sign(signBytes)
|
||||
pubKey := privVal.GetPubKey()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pubKey.VerifyBytes(SignBytes("test_chain_id", testProposal), signature)
|
||||
pubKey.VerifyBytes(testProposal.SignBytes("test_chain_id"), signature)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/abci/types"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
wire "github.com/tendermint/tendermint/wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/merkle"
|
||||
)
|
||||
@@ -18,7 +18,7 @@ type ABCIResult struct {
|
||||
|
||||
// Hash returns the canonical hash of the ABCIResult
|
||||
func (a ABCIResult) Hash() []byte {
|
||||
return wire.BinaryRipemd160(a)
|
||||
return tmHash(a)
|
||||
}
|
||||
|
||||
// ABCIResults wraps the deliver tx results to return a proof
|
||||
@@ -40,9 +40,13 @@ func NewResultFromResponse(response *abci.ResponseDeliverTx) ABCIResult {
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes serializes the ABCIResponse using go-wire
|
||||
// Bytes serializes the ABCIResponse using wire
|
||||
func (a ABCIResults) Bytes() []byte {
|
||||
return wire.BinaryBytes(a)
|
||||
bz, err := wire.MarshalBinary(a)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
// Hash returns a merkle hash of all results
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
// Signable is an interface for all signable things.
|
||||
// It typically removes signatures before serializing.
|
||||
// SignBytes returns the bytes to be signed
|
||||
// NOTE: chainIDs are part of the SignBytes but not
|
||||
// necessarily the object themselves.
|
||||
// NOTE: Expected to panic if there is an error marshalling.
|
||||
type Signable interface {
|
||||
WriteSignBytes(chainID string, w io.Writer, n *int, err *error)
|
||||
SignBytes(chainID string) []byte
|
||||
}
|
||||
|
||||
// SignBytes is a convenience method for getting the bytes to sign of a Signable.
|
||||
func SignBytes(chainID string, o Signable) []byte {
|
||||
buf, n, err := new(bytes.Buffer), new(int), new(error)
|
||||
o.WriteSignBytes(chainID, buf, n, err)
|
||||
if *err != nil {
|
||||
cmn.PanicCrisis(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
// HashSignBytes is a convenience method for getting the hash of the bytes of a signable
|
||||
func HashSignBytes(chainID string, o Signable) []byte {
|
||||
return tmHash(o.SignBytes(chainID))
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func MakeCommit(blockID BlockID, height int64, round int,
|
||||
}
|
||||
|
||||
func signAddVote(privVal *PrivValidatorFS, vote *Vote, voteSet *VoteSet) (signed bool, err error) {
|
||||
vote.Signature, err = privVal.Signer.Sign(SignBytes(voteSet.ChainID(), vote))
|
||||
vote.Signature, err = privVal.Signer.Sign(vote.SignBytes(voteSet.ChainID()))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
)
|
||||
|
||||
// Tx is an arbitrary byte array.
|
||||
// NOTE: Tx has no types at this level, so when go-wire encoded it's just length-prefixed.
|
||||
// NOTE: Tx has no types at this level, so when wire encoded it's just length-prefixed.
|
||||
// Alternatively, it may make sense to add types here and let
|
||||
// []byte be type 0x1 so we can have versioned txs if need be in the future.
|
||||
type Tx []byte
|
||||
|
||||
// Hash computes the RIPEMD160 hash of the go-wire encoded transaction.
|
||||
// Hash computes the RIPEMD160 hash of the wire encoded transaction.
|
||||
func (tx Tx) Hash() []byte {
|
||||
return wireHasher(tx).Hash()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
wire "github.com/tendermint/tendermint/wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
ctest "github.com/tendermint/tmlibs/test"
|
||||
)
|
||||
@@ -69,8 +69,9 @@ func TestValidTxProof(t *testing.T) {
|
||||
|
||||
// read-write must also work
|
||||
var p2 TxProof
|
||||
bin := wire.BinaryBytes(proof)
|
||||
err := wire.ReadBinaryBytes(bin, &p2)
|
||||
bin, err := wire.MarshalBinary(proof)
|
||||
assert.Nil(err)
|
||||
err = wire.UnmarshalBinary(bin, &p2)
|
||||
if assert.Nil(err, "%d: %d: %+v", h, i, err) {
|
||||
assert.Nil(p2.Validate(root), "%d: %d", h, i)
|
||||
}
|
||||
@@ -96,7 +97,8 @@ func testTxProofUnchangable(t *testing.T) {
|
||||
|
||||
// make sure it is valid to start with
|
||||
assert.Nil(proof.Validate(root))
|
||||
bin := wire.BinaryBytes(proof)
|
||||
bin, err := wire.MarshalBinary(proof)
|
||||
assert.Nil(err)
|
||||
|
||||
// try mutating the data and make sure nothing breaks
|
||||
for j := 0; j < 500; j++ {
|
||||
@@ -110,7 +112,7 @@ func testTxProofUnchangable(t *testing.T) {
|
||||
// this make sure the proof doesn't deserialize into something valid
|
||||
func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) {
|
||||
var proof TxProof
|
||||
err := wire.ReadBinaryBytes(bad, &proof)
|
||||
err := wire.UnmarshalBinary(bad, &proof)
|
||||
if err == nil {
|
||||
err = proof.Validate(root)
|
||||
if err == nil {
|
||||
|
||||
@@ -3,10 +3,8 @@ package types
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
@@ -14,9 +12,9 @@ import (
|
||||
// NOTE: The Accum is not included in Validator.Hash();
|
||||
// make sure to update that method if changes are made here
|
||||
type Validator struct {
|
||||
Address Address `json:"address"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
VotingPower int64 `json:"voting_power"`
|
||||
Address Address `json:"address"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
VotingPower int64 `json:"voting_power"`
|
||||
|
||||
Accum int64 `json:"accum"`
|
||||
}
|
||||
@@ -72,7 +70,7 @@ func (v *Validator) String() string {
|
||||
// Hash computes the unique ID of a validator with a given voting power.
|
||||
// It excludes the Accum value, which changes with every round.
|
||||
func (v *Validator) Hash() []byte {
|
||||
return wire.BinaryRipemd160(struct {
|
||||
return tmHash(struct {
|
||||
Address Address
|
||||
PubKey crypto.PubKey
|
||||
VotingPower int64
|
||||
@@ -83,25 +81,6 @@ func (v *Validator) Hash() []byte {
|
||||
})
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
var ValidatorCodec = validatorCodec{}
|
||||
|
||||
type validatorCodec struct{}
|
||||
|
||||
func (vc validatorCodec) Encode(o interface{}, w io.Writer, n *int, err *error) {
|
||||
wire.WriteBinary(o.(*Validator), w, n, err)
|
||||
}
|
||||
|
||||
func (vc validatorCodec) Decode(r io.Reader, n *int, err *error) interface{} {
|
||||
return wire.ReadBinary(&Validator{}, r, 0, n, err)
|
||||
}
|
||||
|
||||
func (vc validatorCodec) Compare(o1 interface{}, o2 interface{}) int {
|
||||
cmn.PanicSanity("ValidatorCodec.Compare not implemented")
|
||||
return 0
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// For testing...
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ func (valSet *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height
|
||||
}
|
||||
_, val := valSet.GetByIndex(idx)
|
||||
// Validate signature
|
||||
precommitSignBytes := SignBytes(chainID, precommit)
|
||||
precommitSignBytes := precommit.SignBytes(chainID)
|
||||
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit)
|
||||
}
|
||||
@@ -327,7 +327,7 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
|
||||
seen[vi] = true
|
||||
|
||||
// Validate signature old school
|
||||
precommitSignBytes := SignBytes(chainID, precommit)
|
||||
precommitSignBytes := precommit.SignBytes(chainID)
|
||||
if !ov.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return errors.Errorf("Invalid commit -- invalid signature: %v", precommit)
|
||||
}
|
||||
|
||||
@@ -6,33 +6,15 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
wire "github.com/tendermint/tendermint/wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
func randPubKey() crypto.PubKey {
|
||||
var pubKey [32]byte
|
||||
copy(pubKey[:], cmn.RandBytes(32))
|
||||
return crypto.PubKeyEd25519(pubKey).Wrap()
|
||||
}
|
||||
|
||||
func randValidator_() *Validator {
|
||||
val := NewValidator(randPubKey(), cmn.RandInt64())
|
||||
val.Accum = cmn.RandInt64()
|
||||
return val
|
||||
}
|
||||
|
||||
func randValidatorSet(numValidators int) *ValidatorSet {
|
||||
validators := make([]*Validator, numValidators)
|
||||
for i := 0; i < numValidators; i++ {
|
||||
validators[i] = randValidator_()
|
||||
}
|
||||
return NewValidatorSet(validators)
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
vset := randValidatorSet(10)
|
||||
vsetHash := vset.Hash()
|
||||
@@ -48,6 +30,26 @@ func TestCopy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkValidatorSetCopy(b *testing.B) {
|
||||
b.StopTimer()
|
||||
vset := NewValidatorSet([]*Validator{})
|
||||
for i := 0; i < 1000; i++ {
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
pubKey := privKey.PubKey()
|
||||
val := NewValidator(pubKey, 0)
|
||||
if !vset.Add(val) {
|
||||
panic("Failed to add validator")
|
||||
}
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
vset.Copy()
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
func TestProposerSelection1(t *testing.T) {
|
||||
vset := NewValidatorSet([]*Validator{
|
||||
newValidator([]byte("foo"), 1000),
|
||||
@@ -66,10 +68,6 @@ func TestProposerSelection1(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newValidator(address []byte, power int64) *Validator {
|
||||
return &Validator{Address: address, VotingPower: power}
|
||||
}
|
||||
|
||||
func TestProposerSelection2(t *testing.T) {
|
||||
addr0 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
addr1 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||
@@ -193,6 +191,48 @@ func TestProposerSelection3(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newValidator(address []byte, power int64) *Validator {
|
||||
return &Validator{Address: address, VotingPower: power}
|
||||
}
|
||||
|
||||
func randPubKey() crypto.PubKey {
|
||||
var pubKey [32]byte
|
||||
copy(pubKey[:], cmn.RandBytes(32))
|
||||
return crypto.PubKeyEd25519(pubKey).Wrap()
|
||||
}
|
||||
|
||||
func randValidator_() *Validator {
|
||||
val := NewValidator(randPubKey(), cmn.RandInt64())
|
||||
val.Accum = cmn.RandInt64()
|
||||
return val
|
||||
}
|
||||
|
||||
func randValidatorSet(numValidators int) *ValidatorSet {
|
||||
validators := make([]*Validator, numValidators)
|
||||
for i := 0; i < numValidators; i++ {
|
||||
validators[i] = randValidator_()
|
||||
}
|
||||
return NewValidatorSet(validators)
|
||||
}
|
||||
|
||||
func (valSet *ValidatorSet) toBytes() []byte {
|
||||
bz, err := wire.MarshalBinary(valSet)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
func (valSet *ValidatorSet) fromBytes(b []byte) {
|
||||
err := wire.UnmarshalBinary(b, &valSet)
|
||||
if err != nil {
|
||||
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
func TestValidatorSetTotalVotingPowerOverflows(t *testing.T) {
|
||||
vset := NewValidatorSet([]*Validator{
|
||||
{Address: []byte("a"), VotingPower: math.MaxInt64, Accum: 0},
|
||||
@@ -272,38 +312,60 @@ func TestSafeSubClip(t *testing.T) {
|
||||
assert.EqualValues(t, math.MaxInt64, safeSubClip(math.MaxInt64, -10))
|
||||
}
|
||||
|
||||
func BenchmarkValidatorSetCopy(b *testing.B) {
|
||||
b.StopTimer()
|
||||
vset := NewValidatorSet([]*Validator{})
|
||||
for i := 0; i < 1000; i++ {
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
pubKey := privKey.PubKey()
|
||||
val := NewValidator(pubKey, 0)
|
||||
if !vset.Add(val) {
|
||||
panic("Failed to add validator")
|
||||
}
|
||||
}
|
||||
b.StartTimer()
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
vset.Copy()
|
||||
}
|
||||
}
|
||||
func TestValidatorSetVerifyCommit(t *testing.T) {
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
pubKey := privKey.PubKey()
|
||||
v1 := NewValidator(pubKey, 1000)
|
||||
vset := NewValidatorSet([]*Validator{v1})
|
||||
|
||||
func (valSet *ValidatorSet) toBytes() []byte {
|
||||
buf, n, err := new(bytes.Buffer), new(int), new(error)
|
||||
wire.WriteBinary(valSet, buf, n, err)
|
||||
if *err != nil {
|
||||
cmn.PanicCrisis(*err)
|
||||
chainID := "mychainID"
|
||||
blockID := BlockID{Hash: []byte("hello")}
|
||||
height := int64(5)
|
||||
vote := &Vote{
|
||||
ValidatorAddress: v1.Address,
|
||||
ValidatorIndex: 0,
|
||||
Height: height,
|
||||
Round: 0,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Type: VoteTypePrecommit,
|
||||
BlockID: blockID,
|
||||
}
|
||||
vote.Signature = privKey.Sign(vote.SignBytes(chainID))
|
||||
commit := &Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: []*Vote{vote},
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (valSet *ValidatorSet) fromBytes(b []byte) {
|
||||
r, n, err := bytes.NewReader(b), new(int), new(error)
|
||||
wire.ReadBinary(valSet, r, 0, n, err)
|
||||
if *err != nil {
|
||||
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
||||
cmn.PanicCrisis(*err)
|
||||
badChainID := "notmychainID"
|
||||
badBlockID := BlockID{Hash: []byte("goodbye")}
|
||||
badHeight := height + 1
|
||||
badCommit := &Commit{
|
||||
BlockID: blockID,
|
||||
Precommits: []*Vote{nil},
|
||||
}
|
||||
|
||||
// test some error cases
|
||||
// TODO: test more cases!
|
||||
cases := []struct {
|
||||
chainID string
|
||||
blockID BlockID
|
||||
height int64
|
||||
commit *Commit
|
||||
}{
|
||||
{badChainID, blockID, height, commit},
|
||||
{chainID, badBlockID, height, commit},
|
||||
{chainID, blockID, badHeight, commit},
|
||||
{chainID, blockID, height, badCommit},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
err := vset.VerifyCommit(c.chainID, c.blockID, c.height, c.commit)
|
||||
assert.NotNil(t, err, i)
|
||||
}
|
||||
|
||||
// test a good one
|
||||
err := vset.VerifyCommit(chainID, blockID, height, commit)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
@@ -73,11 +72,15 @@ type Vote struct {
|
||||
Signature crypto.Signature `json:"signature"`
|
||||
}
|
||||
|
||||
func (vote *Vote) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
|
||||
wire.WriteJSON(CanonicalJSONOnceVote{
|
||||
func (vote *Vote) SignBytes(chainID string) []byte {
|
||||
bz, err := wire.MarshalJSON(CanonicalJSONOnceVote{
|
||||
chainID,
|
||||
CanonicalVote(vote),
|
||||
}, w, n, err)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
func (vote *Vote) Copy() *Vote {
|
||||
@@ -111,7 +114,7 @@ func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error {
|
||||
return ErrVoteInvalidValidatorAddress
|
||||
}
|
||||
|
||||
if !pubKey.VerifyBytes(SignBytes(chainID, vote), vote.Signature) {
|
||||
if !pubKey.VerifyBytes(vote.SignBytes(chainID), vote.Signature) {
|
||||
return ErrVoteInvalidSignature
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -8,10 +8,15 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
// UNSTABLE
|
||||
// XXX: duplicate of p2p.ID to avoid dependence between packages.
|
||||
// Perhaps we can have a minimal types package containing this (and other things?)
|
||||
// that both `types` and `p2p` import ?
|
||||
type P2PID string
|
||||
|
||||
/*
|
||||
VoteSet helps collect signatures from validators at each height+round for a
|
||||
predefined vote type.
|
||||
@@ -59,7 +64,7 @@ type VoteSet struct {
|
||||
sum int64 // Sum of voting power for seen votes, discounting conflicts
|
||||
maj23 *BlockID // First 2/3 majority seen
|
||||
votesByBlock map[string]*blockVotes // string(blockHash|blockParts) -> blockVotes
|
||||
peerMaj23s map[p2p.ID]BlockID // Maj23 for each peer
|
||||
peerMaj23s map[P2PID]BlockID // Maj23 for each peer
|
||||
}
|
||||
|
||||
// Constructs a new VoteSet struct used to accumulate votes for given height/round.
|
||||
@@ -78,7 +83,7 @@ func NewVoteSet(chainID string, height int64, round int, type_ byte, valSet *Val
|
||||
sum: 0,
|
||||
maj23: nil,
|
||||
votesByBlock: make(map[string]*blockVotes, valSet.Size()),
|
||||
peerMaj23s: make(map[p2p.ID]BlockID),
|
||||
peerMaj23s: make(map[P2PID]BlockID),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +296,7 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower
|
||||
// this can cause memory issues.
|
||||
// TODO: implement ability to remove peers too
|
||||
// NOTE: VoteSet must not be nil
|
||||
func (voteSet *VoteSet) SetPeerMaj23(peerID p2p.ID, blockID BlockID) error {
|
||||
func (voteSet *VoteSet) SetPeerMaj23(peerID P2PID, blockID BlockID) error {
|
||||
if voteSet == nil {
|
||||
cmn.PanicSanity("SetPeerMaj23() on nil VoteSet")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
wire "github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
func examplePrevote() *Vote {
|
||||
@@ -42,7 +42,7 @@ func exampleVote(t byte) *Vote {
|
||||
|
||||
func TestVoteSignable(t *testing.T) {
|
||||
vote := examplePrecommit()
|
||||
signBytes := SignBytes("test_chain_id", vote)
|
||||
signBytes := vote.SignBytes("test_chain_id")
|
||||
signStr := string(signBytes)
|
||||
|
||||
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}}`
|
||||
@@ -77,24 +77,25 @@ func TestVoteVerifySignature(t *testing.T) {
|
||||
pubKey := privVal.GetPubKey()
|
||||
|
||||
vote := examplePrecommit()
|
||||
signBytes := SignBytes("test_chain_id", vote)
|
||||
signBytes := vote.SignBytes("test_chain_id")
|
||||
|
||||
// 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)
|
||||
valid := pubKey.VerifyBytes(vote.SignBytes("test_chain_id"), signature)
|
||||
require.True(t, valid)
|
||||
|
||||
// serialize, deserialize and verify again....
|
||||
precommit := new(Vote)
|
||||
bs := wire.BinaryBytes(vote)
|
||||
err = wire.ReadBinaryBytes(bs, &precommit)
|
||||
bs, err := wire.MarshalBinary(vote)
|
||||
require.NoError(t, err)
|
||||
err = wire.UnmarshalBinary(bs, &precommit)
|
||||
require.NoError(t, err)
|
||||
|
||||
// verify the transmitted vote
|
||||
newSignBytes := SignBytes("test_chain_id", precommit)
|
||||
newSignBytes := precommit.SignBytes("test_chain_id")
|
||||
require.Equal(t, string(signBytes), string(newSignBytes))
|
||||
valid = pubKey.VerifyBytes(newSignBytes, signature)
|
||||
require.True(t, valid)
|
||||
|
||||
60
wire/wire.go
Normal file
60
wire/wire.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
/*
|
||||
// Expose access to a global wire codec
|
||||
// TODO: maybe introduce some Context object
|
||||
// containing logger, config, codec that can
|
||||
// be threaded through everything to avoid this global
|
||||
var cdc *wire.Codec
|
||||
|
||||
func init() {
|
||||
cdc = wire.NewCodec()
|
||||
crypto.RegisterWire(cdc)
|
||||
}
|
||||
*/
|
||||
|
||||
// Just a flow through to go-wire.
|
||||
// To be used later for the global codec
|
||||
|
||||
func MarshalBinary(o interface{}) ([]byte, error) {
|
||||
return wire.MarshalBinary(o)
|
||||
}
|
||||
|
||||
func UnmarshalBinary(bz []byte, ptr interface{}) error {
|
||||
return wire.UnmarshalBinary(bz, ptr)
|
||||
}
|
||||
|
||||
func MarshalJSON(o interface{}) ([]byte, error) {
|
||||
return wire.MarshalJSON(o)
|
||||
}
|
||||
|
||||
func UnmarshalJSON(jsonBz []byte, ptr interface{}) error {
|
||||
return wire.UnmarshalJSON(jsonBz, ptr)
|
||||
}
|
||||
|
||||
type ConcreteType = wire.ConcreteType
|
||||
|
||||
func RegisterInterface(o interface{}, ctypes ...ConcreteType) *wire.TypeInfo {
|
||||
return wire.RegisterInterface(o, ctypes...)
|
||||
}
|
||||
|
||||
const RFC3339Millis = wire.RFC3339Millis
|
||||
|
||||
/*
|
||||
|
||||
func RegisterInterface(ptr interface{}, opts *wire.InterfaceOptions) {
|
||||
cdc.RegisterInterface(ptr, opts)
|
||||
}
|
||||
|
||||
func RegisterConcrete(o interface{}, name string, opts *wire.ConcreteOptions) {
|
||||
cdc.RegisterConcrete(o, name, opts)
|
||||
}
|
||||
|
||||
//-------------------------------
|
||||
|
||||
const RFC3339Millis = wire.RFC3339Millis
|
||||
*/
|
||||
Reference in New Issue
Block a user