Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go/roothash/api/block: Use custom TimeStamp type for block's header #4183

Merged
merged 1 commit into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changelog/4183.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
go/roothash/api/block: Use custom `Timestamp` type for block's header

This enables prettier Oasis Node's `control status` CLI command's output
for runtimes' `latest_time` field and matches the format of consensus'
`latest_time` field.
3 changes: 2 additions & 1 deletion go/control/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/oasisprotocol/oasis-core/go/common/node"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
registry "github.com/oasisprotocol/oasis-core/go/registry/api"
block "github.com/oasisprotocol/oasis-core/go/roothash/api/block"
storage "github.com/oasisprotocol/oasis-core/go/storage/api"
upgrade "github.com/oasisprotocol/oasis-core/go/upgrade/api"
commonWorker "github.com/oasisprotocol/oasis-core/go/worker/common/api"
Expand Down Expand Up @@ -113,7 +114,7 @@ type RuntimeStatus struct {
// LatestHash is the hash of the latest runtime block.
LatestHash hash.Hash `json:"latest_hash"`
// LatestTime is the timestamp of the latest runtime block.
LatestTime uint64 `json:"latest_time"`
LatestTime block.Timestamp `json:"latest_time"`
// LatestStateRoot is the Merkle root of the runtime state tree.
LatestStateRoot storage.Root `json:"latest_state_root"`

Expand Down
2 changes: 1 addition & 1 deletion go/roothash/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ func SanityCheckBlocks(blocks map[common.Namespace]*block.Block) error {
for _, blk := range blocks {
hdr := blk.Header

if hdr.Timestamp > uint64(time.Now().Unix()+61*60) {
if hdr.Timestamp > block.Timestamp(time.Now().Unix()+61*60) {
return fmt.Errorf("roothash: sanity check failed: block header timestamp is more than 1h1m in the future")
}
}
Expand Down
4 changes: 2 additions & 2 deletions go/roothash/api/block/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func NewGenesisBlock(id common.Namespace, timestamp uint64) *Block {
var blk Block

blk.Header.Version = 0
blk.Header.Timestamp = timestamp
blk.Header.Timestamp = Timestamp(timestamp)
blk.Header.HeaderType = Normal
blk.Header.Namespace = id
blk.Header.PreviousHash.Empty()
Expand All @@ -35,7 +35,7 @@ func NewEmptyBlock(child *Block, timestamp uint64, htype HeaderType) *Block {
blk.Header.Version = child.Header.Version
blk.Header.Namespace = child.Header.Namespace
blk.Header.Round = child.Header.Round + 1
blk.Header.Timestamp = timestamp
blk.Header.Timestamp = Timestamp(timestamp)
blk.Header.HeaderType = htype
blk.Header.PreviousHash = child.Header.EncodedHash()
blk.Header.IORoot.Empty()
Expand Down
25 changes: 24 additions & 1 deletion go/roothash/api/block/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package block
import (
"bytes"
"errors"
"time"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
Expand All @@ -17,6 +18,28 @@ var ErrInvalidVersion = errors.New("roothash: invalid version")
// HeaderType is the type of header.
type HeaderType uint8

// Timestamp is a custom time stamp type that encodes like time.Time when
// marshaling to text.
type Timestamp uint64

// MarshalText encodes a Timestamp to text by converting it from Unix time to
// local time.
func (ts Timestamp) MarshalText() ([]byte, error) {
t := time.Unix(int64(ts), 0)
return t.MarshalText()
}

// UnmarshalText decodes a text slice into a Timestamp.
func (ts *Timestamp) UnmarshalText(data []byte) error {
var t time.Time
err := t.UnmarshalText(data)
if err != nil {
return err
}
*ts = Timestamp(t.Unix())
return nil
}

const (
// Invalid is an invalid header type and should never be stored.
Invalid HeaderType = 0
Expand Down Expand Up @@ -57,7 +80,7 @@ type Header struct { // nolint: maligned
Round uint64 `json:"round"`

// Timestamp is the block timestamp (POSIX time).
Timestamp uint64 `json:"timestamp"`
Timestamp Timestamp `json:"timestamp"`

// HeaderType is the header type.
HeaderType HeaderType `json:"header_type"`
Expand Down
68 changes: 68 additions & 0 deletions go/roothash/api/block/header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package block
import (
"math/big"
"testing"
"time"

"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -114,3 +115,70 @@ func TestVerifyStorageReceipt(t *testing.T) {
err = header.VerifyStorageReceipt(&receipt)
require.NoError(t, err, "correct receipt")
}

func TestTimestamp(t *testing.T) {
require := require.New(t)

// Set local time zone to a fixed value to be able to compare the
// marshaled time stamps across different systems and configurations.
loc, err := time.LoadLocation("Pacific/Honolulu")
require.NoErrorf(err, "Failed to load a fixed time zone")
time.Local = loc

testVectors := []struct {
timestamp Timestamp
timestampString string
timestampStringValid bool
timestampStringMatching bool
errMsg string
}{
// Valid.
{1, "1969-12-31T14:00:01-10:00", true, true, ""},
{1629075845, "2021-08-15T15:04:05-10:00", true, true, ""},
{4772384038, "2121-03-25T12:13:58-10:00", true, true, ""},

// Invalid - wrong syntax for marshalled time stamps.
{1629075845, "2021-08-15T15:04:05Z-10:00", false, false, "parsing time \"2021-08-15T15:04:05Z-10:00\": extra text: \"-10:00\""},
{1629032645, "2021-08-15T15:04:05+2:00", false, false, "parsing time \"2021-08-15T15:04:05+2:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"+2:00\" as \"Z07:00\""},

// Invalid - not marshaled using the correct time zone.
{1629039845, "2021-08-15T15:04:05Z", true, false, ""},
{1629032645, "2021-08-15T15:04:05+02:00", true, false, ""},
}

for _, v := range testVectors {
var unmarshaledTimestamp Timestamp
err := unmarshaledTimestamp.UnmarshalText([]byte(v.timestampString))
if !v.timestampStringValid {
require.EqualErrorf(
err,
v.errMsg,
"Unmarshaling invalid time stamp: '%s' should fail with expected error message",
v.timestampString,
)
} else {
require.NoErrorf(err, "Failed to unmarshal a valid time stamp: '%s'", v.timestampString)
require.Equalf(
v.timestamp,
unmarshaledTimestamp,
"Unmarshaled time stamp doesn't equal expected time stamp: %s %#s", v.timestamp, unmarshaledTimestamp,
)
}

textTimestamp, err := v.timestamp.MarshalText()
require.NoError(err, "Failed to marshal a valid time stamp: '%s'", v.timestamp)
if v.timestampStringMatching {
require.Equal(
v.timestampString,
string(textTimestamp),
"Marshaled time stamp doesn't equal expected text time stamp",
)
} else {
require.NotEqual(
v.timestampString,
string(textTimestamp),
"Marshaled time stamp shouldn't equal the expected text time stamp for invalid test cases",
)
}
}
}
2 changes: 1 addition & 1 deletion go/roothash/tests/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func (s *runtimeState) generateExecutorCommitments(t *testing.T, consensus conse
Version: 0,
Namespace: child.Header.Namespace,
Round: child.Header.Round + 1,
Timestamp: uint64(time.Now().Unix()),
Timestamp: block.Timestamp(time.Now().Unix()),
HeaderType: block.Normal,
PreviousHash: child.Header.EncodedHash(),
IORoot: ioRootHash,
Expand Down