Skip to content

Commit

Permalink
Move Maybe to its own package (ava-labs#1817)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Laine authored Aug 8, 2023
1 parent cfb957c commit 9d7d1b0
Show file tree
Hide file tree
Showing 27 changed files with 396 additions and 371 deletions.
12 changes: 6 additions & 6 deletions x/merkledb/maybe.go → utils/maybe/maybe.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package merkledb
package maybe

// Maybe T = Some T | Nothing.
// A data wrapper that allows values to be something [Some T] or nothing [Nothing].
Expand Down Expand Up @@ -42,23 +42,23 @@ func (m Maybe[T]) Value() T {
return m.value
}

// MaybeBind returns Nothing, if [m] is Nothing.
// Bind returns Nothing iff [m] is Nothing.
// Otherwise applies [f] to the value of [m] and returns the result as a Some.
func MaybeBind[T, U any](m Maybe[T], f func(T) U) Maybe[U] {
func Bind[T, U any](m Maybe[T], f func(T) U) Maybe[U] {
if m.IsNothing() {
return Nothing[U]()
}
return Some(f(m.Value()))
}

// MaybeEqual returns true if both m1 and m2 are nothing or have the same value according to the equality function
func MaybeEqual[T any](m1 Maybe[T], m2 Maybe[T], equality func(T, T) bool) bool {
// Equal returns true if both m1 and m2 are nothing or have the same value according to [equalFunc].
func Equal[T any](m1 Maybe[T], m2 Maybe[T], equalFunc func(T, T) bool) bool {
if m1.IsNothing() {
return m2.IsNothing()
}

if m2.IsNothing() {
return false
}
return equality(m1.Value(), m2.Value())
return equalFunc(m1.Value(), m2.Value())
}
14 changes: 7 additions & 7 deletions x/merkledb/maybe_test.go → utils/maybe/maybe_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package merkledb
package maybe

import (
"testing"
Expand All @@ -18,7 +18,7 @@ func TestMaybeClone(t *testing.T) {
val := []byte{1, 2, 3}
originalVal := slices.Clone(val)
m := Some(val)
mClone := MaybeBind(m, slices.Clone[[]byte])
mClone := Bind(m, slices.Clone[[]byte])
m.value[0] = 0
require.NotEqual(mClone.value, m.value)
require.Equal(originalVal, mClone.value)
Expand All @@ -27,23 +27,23 @@ func TestMaybeClone(t *testing.T) {
// Case: Value is nothing
{
m := Nothing[[]byte]()
mClone := MaybeBind(m, slices.Clone[[]byte])
mClone := Bind(m, slices.Clone[[]byte])
require.True(mClone.IsNothing())
}
}

func TestMaybeEquality(t *testing.T) {
require := require.New(t)
require.True(MaybeEqual(Nothing[int](), Nothing[int](), func(i int, i2 int) bool {
require.True(Equal(Nothing[int](), Nothing[int](), func(i int, i2 int) bool {
return i == i2
}))
require.False(MaybeEqual(Nothing[int](), Some(1), func(i int, i2 int) bool {
require.False(Equal(Nothing[int](), Some(1), func(i int, i2 int) bool {
return i == i2
}))
require.False(MaybeEqual(Some(1), Nothing[int](), func(i int, i2 int) bool {
require.False(Equal(Some(1), Nothing[int](), func(i int, i2 int) bool {
return i == i2
}))
require.True(MaybeEqual(Some(1), Some(1), func(i int, i2 int) bool {
require.True(Equal(Some(1), Some(1), func(i int, i2 int) bool {
return i == i2
}))
}
13 changes: 7 additions & 6 deletions x/merkledb/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"sync"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/maybe"
)

const (
Expand Down Expand Up @@ -274,7 +275,7 @@ func (c *codecImpl) encodeInt64(dst io.Writer, value int64) error {
return err
}

func (c *codecImpl) encodeMaybeByteSlice(dst io.Writer, maybeValue Maybe[[]byte]) error {
func (c *codecImpl) encodeMaybeByteSlice(dst io.Writer, maybeValue maybe.Maybe[[]byte]) error {
if err := c.encodeBool(dst, !maybeValue.IsNothing()); err != nil {
return err
}
Expand All @@ -284,21 +285,21 @@ func (c *codecImpl) encodeMaybeByteSlice(dst io.Writer, maybeValue Maybe[[]byte]
return c.encodeByteSlice(dst, maybeValue.Value())
}

func (c *codecImpl) decodeMaybeByteSlice(src *bytes.Reader) (Maybe[[]byte], error) {
func (c *codecImpl) decodeMaybeByteSlice(src *bytes.Reader) (maybe.Maybe[[]byte], error) {
if minMaybeByteSliceLen > src.Len() {
return Nothing[[]byte](), io.ErrUnexpectedEOF
return maybe.Nothing[[]byte](), io.ErrUnexpectedEOF
}

if hasValue, err := c.decodeBool(src); err != nil || !hasValue {
return Nothing[[]byte](), err
return maybe.Nothing[[]byte](), err
}

bytes, err := c.decodeByteSlice(src)
if err != nil {
return Nothing[[]byte](), err
return maybe.Nothing[[]byte](), err
}

return Some(bytes), nil
return maybe.Some(bytes), nil
}

func (c *codecImpl) decodeByteSlice(src *bytes.Reader) ([]byte, error) {
Expand Down
11 changes: 6 additions & 5 deletions x/merkledb/codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/hashing"
"github.com/ava-labs/avalanchego/utils/maybe"
)

// TODO add more codec tests
Expand All @@ -36,7 +37,7 @@ func newRandomProofNode(r *rand.Rand) ProofNode {
}

hasValue := rand.Intn(2) == 1 // #nosec G404
var valueOrHash Maybe[[]byte]
var valueOrHash maybe.Maybe[[]byte]
if hasValue {
// use the hash instead when length is greater than the hash length
if len(val) >= HashLength {
Expand All @@ -50,7 +51,7 @@ func newRandomProofNode(r *rand.Rand) ProofNode {
// variable on the struct
val = nil
}
valueOrHash = Some(val)
valueOrHash = maybe.Some(val)
}

return ProofNode{
Expand Down Expand Up @@ -229,7 +230,7 @@ func FuzzCodecDBNodeDeterministic(f *testing.F) {

r := rand.New(rand.NewSource(int64(randSeed))) // #nosec G404

value := Nothing[[]byte]()
value := maybe.Nothing[[]byte]()
if hasValue {
if len(valueBytes) == 0 {
// We do this because when we encode a value of []byte{}
Expand All @@ -240,7 +241,7 @@ func FuzzCodecDBNodeDeterministic(f *testing.F) {
// private variable on the struct
valueBytes = nil
}
value = Some(valueBytes)
value = maybe.Some(valueBytes)
}

numChildren := r.Intn(NodeBranchFactor) // #nosec G404
Expand Down Expand Up @@ -294,7 +295,7 @@ func TestCodec_DecodeDBNode(t *testing.T) {
require.ErrorIs(err, io.ErrUnexpectedEOF)

proof := dbNode{
value: Some([]byte{1}),
value: maybe.Some([]byte{1}),
children: map[byte]child{},
}

Expand Down
41 changes: 21 additions & 20 deletions x/merkledb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/ava-labs/avalanchego/trace"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/avalanchego/utils/maybe"
"github.com/ava-labs/avalanchego/utils/set"
)

Expand Down Expand Up @@ -61,7 +62,7 @@ type ChangeProofer interface {
startRootID ids.ID,
endRootID ids.ID,
start []byte,
end Maybe[[]byte],
end maybe.Maybe[[]byte],
maxLength int,
) (*ChangeProof, error)

Expand All @@ -83,7 +84,7 @@ type ChangeProofer interface {
ctx context.Context,
proof *ChangeProof,
start []byte,
end Maybe[[]byte],
end maybe.Maybe[[]byte],
expectedEndRootID ids.ID,
) error

Expand All @@ -98,7 +99,7 @@ type RangeProofer interface {
ctx context.Context,
rootID ids.ID,
start []byte,
end Maybe[[]byte],
end maybe.Maybe[[]byte],
maxLength int,
) (*RangeProof, error)

Expand Down Expand Up @@ -201,7 +202,7 @@ func newDatabase(
// add current root to history (has no changes)
trieDB.history.record(&changeSummary{
rootID: root,
values: map[path]*change[Maybe[[]byte]]{},
values: map[path]*change[maybe.Maybe[[]byte]]{},
nodes: map[path]*change[*node]{},
})

Expand Down Expand Up @@ -267,7 +268,7 @@ func (db *merkleDB) rebuild(ctx context.Context) error {
}
if n.hasValue() {
serializedPath := path.Serialize()
if err := currentView.Insert(ctx, serializedPath.Value, n.value.value); err != nil {
if err := currentView.Insert(ctx, serializedPath.Value, n.value.Value()); err != nil {
return err
}
currentViewSize++
Expand Down Expand Up @@ -433,7 +434,7 @@ func (db *merkleDB) getValue(key path, lock bool) ([]byte, error) {
if n.value.IsNothing() {
return nil, database.ErrNotFound
}
return n.value.value, nil
return n.value.Value(), nil
}

func (db *merkleDB) GetMerkleRoot(ctx context.Context) (ids.ID, error) {
Expand Down Expand Up @@ -481,7 +482,7 @@ func (db *merkleDB) getProof(ctx context.Context, key []byte) (*Proof, error) {
func (db *merkleDB) GetRangeProof(
ctx context.Context,
start []byte,
end Maybe[[]byte],
end maybe.Maybe[[]byte],
maxLength int,
) (*RangeProof, error) {
db.commitLock.RLock()
Expand All @@ -496,7 +497,7 @@ func (db *merkleDB) GetRangeProofAtRoot(
ctx context.Context,
rootID ids.ID,
start []byte,
end Maybe[[]byte],
end maybe.Maybe[[]byte],
maxLength int,
) (*RangeProof, error) {
db.commitLock.RLock()
Expand All @@ -510,7 +511,7 @@ func (db *merkleDB) getRangeProofAtRoot(
ctx context.Context,
rootID ids.ID,
start []byte,
end Maybe[[]byte],
end maybe.Maybe[[]byte],
maxLength int,
) (*RangeProof, error) {
if db.closed {
Expand All @@ -532,7 +533,7 @@ func (db *merkleDB) GetChangeProof(
startRootID ids.ID,
endRootID ids.ID,
start []byte,
end Maybe[[]byte],
end maybe.Maybe[[]byte],
maxLength int,
) (*ChangeProof, error) {
if end.HasValue() && bytes.Compare(start, end.Value()) == 1 {
Expand Down Expand Up @@ -578,13 +579,13 @@ func (db *merkleDB) GetChangeProof(
result.KeyChanges = append(result.KeyChanges, KeyChange{
Key: serializedKey,
// create a copy so edits of the []byte don't affect the db
Value: MaybeBind(change.after, slices.Clone[[]byte]),
Value: maybe.Bind(change.after, slices.Clone[[]byte]),
})
}

largestKey := end
if len(result.KeyChanges) > 0 {
largestKey = Some(result.KeyChanges[len(result.KeyChanges)-1].Key)
largestKey = maybe.Some(result.KeyChanges[len(result.KeyChanges)-1].Key)
}

// Since we hold [db.commitlock] we must still have sufficient
Expand Down Expand Up @@ -958,7 +959,7 @@ func (db *merkleDB) VerifyChangeProof(
ctx context.Context,
proof *ChangeProof,
start []byte,
end Maybe[[]byte],
end maybe.Maybe[[]byte],
expectedEndRootID ids.ID,
) error {
if end.HasValue() && bytes.Compare(start, end.Value()) > 0 {
Expand Down Expand Up @@ -1004,20 +1005,20 @@ func (db *merkleDB) VerifyChangeProof(
// Find the greatest key in [proof.KeyChanges]
// Note that [proof.EndProof] is a proof for this key.
// [largestPath] is also used when we add children of proof nodes to [trie] below.
largestPath := MaybeBind(end, newPath)
largestPath := maybe.Bind(end, newPath)
if len(proof.KeyChanges) > 0 {
// If [proof] has key-value pairs, we should insert children
// greater than [end] to ancestors of the node containing [end]
// so that we get the expected root ID.
largestPath = Some(newPath(proof.KeyChanges[len(proof.KeyChanges)-1].Key))
largestPath = maybe.Some(newPath(proof.KeyChanges[len(proof.KeyChanges)-1].Key))
}

// Make sure the end proof, if given, is well-formed.
if err := verifyProofPath(proof.EndProof, largestPath.Value()); err != nil {
return err
}

keyValues := make(map[path]Maybe[[]byte], len(proof.KeyChanges))
keyValues := make(map[path]maybe.Maybe[[]byte], len(proof.KeyChanges))
for _, keyValue := range proof.KeyChanges {
keyValues[newPath(keyValue.Key)] = keyValue.Value
}
Expand Down Expand Up @@ -1069,9 +1070,9 @@ func (db *merkleDB) VerifyChangeProof(
// keys are less than [insertChildrenLessThan] or whose keys are greater
// than [insertChildrenGreaterThan] into the trie so that we get the
// expected root ID (if this proof is valid).
insertChildrenLessThan := Nothing[path]()
insertChildrenLessThan := maybe.Nothing[path]()
if len(smallestPath) > 0 {
insertChildrenLessThan = Some(smallestPath)
insertChildrenLessThan = maybe.Some(smallestPath)
}
if err := addPathInfo(
view,
Expand Down Expand Up @@ -1165,7 +1166,7 @@ func (db *merkleDB) initializeRootIfNeeded() (ids.ID, error) {
func (db *merkleDB) getHistoricalViewForRange(
rootID ids.ID,
start []byte,
end Maybe[[]byte],
end maybe.Maybe[[]byte],
) (*trieView, error) {
currentRootID := db.getMerkleRoot()

Expand Down Expand Up @@ -1299,7 +1300,7 @@ func (db *merkleDB) prepareChangeProofView(proof *ChangeProof) (*trieView, error
if err := view.remove(kv.Key); err != nil {
return nil, err
}
} else if err := view.insert(kv.Key, kv.Value.value); err != nil {
} else if err := view.insert(kv.Key, kv.Value.Value()); err != nil {
return nil, err
}
}
Expand Down
Loading

0 comments on commit 9d7d1b0

Please sign in to comment.