From 07f13c61322d1ac72c0e67c813491b6404ca22d8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 11 Sep 2024 11:41:54 -0400 Subject: [PATCH] Implement ExpiryEntry (#3382) --- vms/platformvm/state/expiry.go | 57 ++++++++++++++++++++++++++ vms/platformvm/state/expiry_test.go | 62 +++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 vms/platformvm/state/expiry.go create mode 100644 vms/platformvm/state/expiry_test.go diff --git a/vms/platformvm/state/expiry.go b/vms/platformvm/state/expiry.go new file mode 100644 index 000000000000..d2b392b62f4b --- /dev/null +++ b/vms/platformvm/state/expiry.go @@ -0,0 +1,57 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "encoding/binary" + "fmt" + + "github.com/google/btree" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" +) + +// expiryEntry = [timestamp] + [validationID] +const expiryEntryLength = database.Uint64Size + ids.IDLen + +var ( + errUnexpectedExpiryEntryLength = fmt.Errorf("expected expiry entry length %d", expiryEntryLength) + + _ btree.LessFunc[ExpiryEntry] = ExpiryEntry.Less +) + +type ExpiryEntry struct { + Timestamp uint64 + ValidationID ids.ID +} + +func (e *ExpiryEntry) Marshal() []byte { + data := make([]byte, expiryEntryLength) + binary.BigEndian.PutUint64(data, e.Timestamp) + copy(data[database.Uint64Size:], e.ValidationID[:]) + return data +} + +func (e *ExpiryEntry) Unmarshal(data []byte) error { + if len(data) != expiryEntryLength { + return errUnexpectedExpiryEntryLength + } + + e.Timestamp = binary.BigEndian.Uint64(data) + copy(e.ValidationID[:], data[database.Uint64Size:]) + return nil +} + +// Invariant: Less produces the same ordering as the marshalled bytes. +func (e ExpiryEntry) Less(o ExpiryEntry) bool { + switch { + case e.Timestamp < o.Timestamp: + return true + case e.Timestamp > o.Timestamp: + return false + default: + return e.ValidationID.Compare(o.ValidationID) == -1 + } +} diff --git a/vms/platformvm/state/expiry_test.go b/vms/platformvm/state/expiry_test.go new file mode 100644 index 000000000000..38a0d07f9ab0 --- /dev/null +++ b/vms/platformvm/state/expiry_test.go @@ -0,0 +1,62 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + "github.com/thepudds/fzgen/fuzzer" +) + +func FuzzExpiryEntryMarshal(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + require := require.New(t) + + var entry ExpiryEntry + fz := fuzzer.NewFuzzer(data) + fz.Fill(&entry) + + marshalledData := entry.Marshal() + + var parsedEntry ExpiryEntry + require.NoError(parsedEntry.Unmarshal(marshalledData)) + require.Equal(entry, parsedEntry) + }) +} + +func FuzzExpiryEntryLessAndMarshalOrdering(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ( + entry0 ExpiryEntry + entry1 ExpiryEntry + ) + fz := fuzzer.NewFuzzer(data) + fz.Fill(&entry0, &entry1) + + key0 := entry0.Marshal() + key1 := entry1.Marshal() + require.Equal( + t, + entry0.Less(entry1), + bytes.Compare(key0, key1) == -1, + ) + }) +} + +func FuzzExpiryEntryUnmarshal(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + require := require.New(t) + + var entry ExpiryEntry + if err := entry.Unmarshal(data); err != nil { + require.ErrorIs(err, errUnexpectedExpiryEntryLength) + return + } + + marshalledData := entry.Marshal() + require.Equal(data, marshalledData) + }) +}