diff --git a/types/heartbeat.go b/types/heartbeat.go index 40a7b01b0..64676ea64 100644 --- a/types/heartbeat.go +++ b/types/heartbeat.go @@ -10,8 +10,11 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) -// Heartbeat is a simple vote-like structure so validators can alert others that -// they are alive and waiting for transactions. +// Heartbeat is a simple vote-like structure so validators can +// alert others that they are alive and waiting for transactions. +// Note: We aren't adding ",omitempty" to Heartbeat's +// json field tags because we always want the JSON +// representation to be in its canonical form. type Heartbeat struct { ValidatorAddress data.Bytes `json:"validator_address"` ValidatorIndex int `json:"validator_index"` @@ -22,6 +25,7 @@ type Heartbeat struct { } // WriteSignBytes writes the Heartbeat 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{ chainID, @@ -31,6 +35,9 @@ func (heartbeat *Heartbeat) WriteSignBytes(chainID string, w io.Writer, n *int, // Copy makes a copy of the Heartbeat. func (heartbeat *Heartbeat) Copy() *Heartbeat { + if heartbeat == nil { + return nil + } heartbeatCopy := *heartbeat return &heartbeatCopy } diff --git a/types/heartbeat_test.go b/types/heartbeat_test.go new file mode 100644 index 000000000..8a0967128 --- /dev/null +++ b/types/heartbeat_test.go @@ -0,0 +1,56 @@ +package types + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/go-crypto" +) + +func TestHeartbeatCopy(t *testing.T) { + hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1} + hbCopy := hb.Copy() + require.Equal(t, hbCopy, hb, "heartbeat copy should be the same") + hbCopy.Round = hb.Round + 10 + require.NotEqual(t, hbCopy, hb, "heartbeat copy mutation should not change original") + + var nilHb *Heartbeat + nilHbCopy := nilHb.Copy() + require.Nil(t, nilHbCopy, "copy of nil should also return nil") +} + +func TestHeartbeatString(t *testing.T) { + var nilHb *Heartbeat + require.Contains(t, nilHb.String(), "nil", "expecting a string and no panic") + + hb := &Heartbeat{ValidatorIndex: 1, Height: 11, Round: 2} + require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) {}}") + + var key crypto.PrivKeyEd25519 + hb.Signature = key.Sign([]byte("Tendermint")) + require.Equal(t, hb.String(), "Heartbeat{1:000000000000 11/02 (0) {/FF41E371B9BF.../}}") +} + +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, string(buf.Bytes()), `{"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, string(buf.Bytes()), `{"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, string(buf.Bytes()), "null") + }) +}