From 66f639fac3bd373eab8d910f3d09132af48363fd Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sat, 25 Feb 2023 13:31:34 -0800 Subject: [PATCH 1/8] start heap package --- heap/heap.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 heap/heap.go diff --git a/heap/heap.go b/heap/heap.go new file mode 100644 index 0000000000..39fdb9942c --- /dev/null +++ b/heap/heap.go @@ -0,0 +1,101 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package heap + +import ( + "container/heap" + "fmt" + + "github.com/ava-labs/avalanchego/ids" + "golang.org/x/exp/constraints" +) + +var _ heap.Interface = (*Heap[interface{}, uint64])(nil) + +type Entry[I any, V constraints.Ordered] struct { + ID ids.ID // id of entry + Item I // associated item + Val V // Value to be prioritized + + Index int // Index of the entry in heap +} + +// Heap[I,V] is used to track objectes of [I] by [Val]. +type Heap[I any, V constraints.Ordered] struct { + isMinHeap bool // true for Min-Heap, false for Max-Heap + items []*Entry[I, V] // items in this heap + lookup map[ids.ID]*Entry[I, V] // ids in the heap mapping to an entry +} + +// New returns an instance of Heap[I,V] +func New[I any, V constraints.Ordered](items int, isMinHeap bool) *Heap[I, V] { + return &Heap[I, V]{ + isMinHeap: isMinHeap, + + items: make([]*Entry[I, V], 0, items), + lookup: make(map[ids.ID]*Entry[I, V], items), + } +} + +// Len returns the number of items in th. +func (th *Heap[I, V]) Len() int { return len(th.items) } + +// Less compares the priority of [i] and [j] based on th.isMinHeap. +func (th *Heap[I, V]) Less(i, j int) bool { + if th.isMinHeap { + return th.items[i].Val < th.items[j].Val + } + return th.items[i].Val > th.items[j].Val +} + +// Swap swaps the [i]th and [j]th element in th. +func (th *Heap[I, V]) Swap(i, j int) { + th.items[i], th.items[j] = th.items[j], th.items[i] + th.items[i].Index = i + th.items[j].Index = j +} + +// Push adds an *Entry interface to th. If [x.id] is already in +// th, returns. +func (th *Heap[I, V]) Push(x interface{}) { + entry, ok := x.(*Entry[I, V]) + if !ok { + panic(fmt.Errorf("unexpected %T, expected *Uint64Entry", x)) + } + if th.HasID(entry.ID) { + return + } + th.items = append(th.items, entry) + th.lookup[entry.ID] = entry +} + +// Pop removes the highest priority item from th and also deletes it from +// th's lookup map. +func (th *Heap[I, V]) Pop() interface{} { + n := len(th.items) + item := th.items[n-1] + th.items[n-1] = nil // avoid memory leak + th.items = th.items[0 : n-1] + delete(th.lookup, item.ID) + return item +} + +// GetID returns the entry in th associated with [id], and a bool if [id] was +// found in th. +func (th *Heap[I, V]) GetID(id ids.ID) (*Entry[I, V], bool) { + entry, ok := th.lookup[id] + return entry, ok +} + +// HasID returns whether [id] is found in th. +func (th *Heap[I, V]) HasID(id ids.ID) bool { + _, has := th.GetID(id) + return has +} + +// Items returns all items in the heap in sorted order. You should not modify +// the response. +func (th *Heap[I, V]) Items() []*Entry[I, V] { + return th.items +} From 1681d51170837da10fdd0b34fa6f9b75e5a3341f Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sat, 25 Feb 2023 13:34:21 -0800 Subject: [PATCH 2/8] add tests --- heap/heap_test.go | 214 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 heap/heap_test.go diff --git a/heap/heap_test.go b/heap/heap_test.go new file mode 100644 index 0000000000..c0a87817c7 --- /dev/null +++ b/heap/heap_test.go @@ -0,0 +1,214 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package heap + +import ( + "container/heap" + "testing" + + "github.com/ava-labs/avalanchego/ids" + "github.com/stretchr/testify/require" +) + +const testPayer = "testPayer" + +type MempoolTestItem struct { + id ids.ID + payer string + timestamp int64 + unitPrice uint64 +} + +func (mti *MempoolTestItem) ID() ids.ID { + return mti.id +} + +func (mti *MempoolTestItem) Payer() string { + return mti.payer +} + +func (mti *MempoolTestItem) UnitPrice() uint64 { + return mti.unitPrice +} + +func (mti *MempoolTestItem) Expiry() int64 { + return mti.timestamp +} + +func GenerateTestItem(payer string, t int64, unitPrice uint64) *MempoolTestItem { + id := ids.GenerateTestID() + return &MempoolTestItem{ + id: id, + payer: payer, + timestamp: t, + unitPrice: unitPrice, + } +} + +func TestUnit64HeapPushPopMin(t *testing.T) { + require := require.New(t) + minHeap := New[*MempoolTestItem, uint64](0, true) + require.Equal(minHeap.Len(), 0, "heap not initialized properly.") + mempoolItem1 := GenerateTestItem(testPayer, 1, 10) + mempoolItem2 := GenerateTestItem(testPayer, 2, 7) + mempoolItem3 := GenerateTestItem(testPayer, 3, 15) + + // Middle UnitPrice + med := &Entry[*MempoolTestItem, uint64]{ + ID: mempoolItem1.ID(), + Item: mempoolItem1, + Val: mempoolItem1.UnitPrice(), + Index: minHeap.Len(), + } + // Lesser UnitPrice + low := &Entry[*MempoolTestItem, uint64]{ + ID: mempoolItem2.ID(), + Item: mempoolItem2, + Val: mempoolItem2.UnitPrice(), + Index: minHeap.Len(), + } + // Greatest UnitPrice + high := &Entry[*MempoolTestItem, uint64]{ + ID: mempoolItem3.ID(), + Item: mempoolItem3, + Val: mempoolItem3.UnitPrice(), + Index: minHeap.Len(), + } + heap.Push(minHeap, med) + heap.Push(minHeap, low) + heap.Push(minHeap, high) + // Added all three + require.Equal(minHeap.Len(), 3, "Not pushed correctly.") + // Check if added to lookup table + _, ok := minHeap.lookup[med.ID] + require.True(ok, "Item not found in lookup.") + _, ok = minHeap.lookup[low.ID] + require.True(ok, "Item not found in lookup.") + _, ok = minHeap.lookup[high.ID] + require.True(ok, "Item not found in lookup.") + // Pop and check popped correctly. Order should be 2, 1, 3 + popped := heap.Pop(minHeap) + require.Equal(low, popped, "Incorrect item removed.") + popped = heap.Pop(minHeap) + require.Equal(med, popped, "Incorrect item removed.") + popped = heap.Pop(minHeap) + require.Equal(high, popped, "Incorrect item removed.") +} + +func TestUnit64HeapPushPopMax(t *testing.T) { + require := require.New(t) + maxHeap := New[*MempoolTestItem, uint64](0, false) + require.Equal(maxHeap.Len(), 0, "heap not initialized properly.") + + mempoolItem1 := GenerateTestItem(testPayer, 1, 10) + mempoolItem2 := GenerateTestItem(testPayer, 2, 7) + mempoolItem3 := GenerateTestItem(testPayer, 3, 15) + + // Middle UnitPrice + med := &Entry[*MempoolTestItem, uint64]{ + ID: mempoolItem1.ID(), + Item: mempoolItem1, + Val: mempoolItem1.UnitPrice(), + Index: maxHeap.Len(), + } + // Lesser UnitPrice + low := &Entry[*MempoolTestItem, uint64]{ + ID: mempoolItem2.ID(), + Item: mempoolItem2, + Val: mempoolItem2.UnitPrice(), + Index: maxHeap.Len(), + } + // Greatest UnitPrice + high := &Entry[*MempoolTestItem, uint64]{ + ID: mempoolItem3.ID(), + Item: mempoolItem3, + Val: mempoolItem3.UnitPrice(), + Index: maxHeap.Len(), + } + heap.Push(maxHeap, med) + heap.Push(maxHeap, low) + heap.Push(maxHeap, high) + // Added all three + require.Equal(maxHeap.Len(), 3, "Not pushed correctly.") + // Check if added to lookup table + _, ok := maxHeap.lookup[med.ID] + require.True(ok, "Item not found in lookup.") + _, ok = maxHeap.lookup[low.ID] + require.True(ok, "Item not found in lookup.") + _, ok = maxHeap.lookup[high.ID] + require.True(ok, "Item not found in lookup.") + // Pop and check popped correctly. Order should be 2, 1, 3 + popped := heap.Pop(maxHeap) + require.Equal(high, popped, "Incorrect item removed.") + popped = heap.Pop(maxHeap) + require.Equal(med, popped, "Incorrect item removed.") + popped = heap.Pop(maxHeap) + require.Equal(low, popped, "Incorrect item removed.") +} + +func TestUnit64HeapPushExists(t *testing.T) { + // Push an item already in heap + require := require.New(t) + minHeap := New[*MempoolTestItem, uint64](0, true) + require.Equal(minHeap.Len(), 0, "heap not initialized properly.") + mempoolItem := GenerateTestItem(testPayer, 1, 10) + entry := &Entry[*MempoolTestItem, uint64]{ + ID: mempoolItem.ID(), + Item: mempoolItem, + Val: mempoolItem.UnitPrice(), + Index: minHeap.Len(), + } + heap.Push(minHeap, entry) + // Pushed correctly + require.Equal(minHeap.Len(), 1, "Not pushed correctly.") + // Check if added to lookup table + _, ok := minHeap.lookup[entry.ID] + require.True(ok, "Item not found in lookup.") + heap.Push(minHeap, entry) + // Only 1 item + require.Equal(minHeap.Len(), 1, "Not pushed correctly.") +} + +func TestUnit64HeapGetID(t *testing.T) { + // Push an item and grab its ID + require := require.New(t) + minHeap := New[*MempoolTestItem, uint64](0, true) + require.Equal(minHeap.Len(), 0, "heap not initialized properly.") + + mempoolItem := GenerateTestItem(testPayer, 1, 10) + entry := &Entry[*MempoolTestItem, uint64]{ + ID: mempoolItem.ID(), + Item: mempoolItem, + Val: mempoolItem.UnitPrice(), + Index: minHeap.Len(), + } + _, ok := minHeap.GetID(mempoolItem.ID()) + require.False(ok, "Entry returned before pushing.") + heap.Push(minHeap, entry) + // Pushed correctly + require.Equal(minHeap.Len(), 1, "Not pushed correctly.") + entryReturned, ok := minHeap.GetID(mempoolItem.ID()) + require.True(ok, "Entry not returned.") + require.Equal(entry, entryReturned, "Returned incorrect entry") +} + +func TestUnit64HeapHasID(t *testing.T) { + require := require.New(t) + minHeap := New[*MempoolTestItem, uint64](0, true) + require.Equal(minHeap.Len(), 0, "heap not initialized properly.") + mempoolItem := GenerateTestItem(testPayer, 1, 10) + entry := &Entry[*MempoolTestItem, uint64]{ + ID: mempoolItem.ID(), + Item: mempoolItem, + Val: mempoolItem.UnitPrice(), + Index: minHeap.Len(), + } + ok := minHeap.HasID(mempoolItem.ID()) + require.False(ok, "Entry has ID before pushing.") + heap.Push(minHeap, entry) + // Pushed correctly + require.Equal(minHeap.Len(), 1, "Not pushed correctly.") + ok = minHeap.HasID(mempoolItem.ID()) + require.True(ok, "Entry was not found in heap.") +} From e8fc06215801e79700fe8b6914f9cadb42c3df0b Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sat, 25 Feb 2023 13:56:28 -0800 Subject: [PATCH 3/8] remove external heap usage --- heap/heap.go | 66 ++++++++++- heap/heap_test.go | 33 +++--- mempool/sorted_mempool.go | 45 ++++---- mempool/uint64_heap.go | 97 ---------------- mempool/uint64_heap_test.go | 214 ------------------------------------ 5 files changed, 104 insertions(+), 351 deletions(-) delete mode 100644 mempool/uint64_heap.go delete mode 100644 mempool/uint64_heap_test.go diff --git a/heap/heap.go b/heap/heap.go index 39fdb9942c..8e044139f2 100644 --- a/heap/heap.go +++ b/heap/heap.go @@ -11,7 +11,7 @@ import ( "golang.org/x/exp/constraints" ) -var _ heap.Interface = (*Heap[interface{}, uint64])(nil) +var _ heap.Interface = (*Heap[any, uint64])(nil) type Entry[I any, V constraints.Ordered] struct { ID ids.ID // id of entry @@ -21,7 +21,10 @@ type Entry[I any, V constraints.Ordered] struct { Index int // Index of the entry in heap } -// Heap[I,V] is used to track objectes of [I] by [Val]. +// Heap[I,V] is used to track objects of [I] by [Val]. +// +// This data structure does not perform any synchronization and is not +// safe to use concurrently without external locking. type Heap[I any, V constraints.Ordered] struct { isMinHeap bool // true for Min-Heap, false for Max-Heap items []*Entry[I, V] // items in this heap @@ -42,6 +45,9 @@ func New[I any, V constraints.Ordered](items int, isMinHeap bool) *Heap[I, V] { func (th *Heap[I, V]) Len() int { return len(th.items) } // Less compares the priority of [i] and [j] based on th.isMinHeap. +// +// This should never be called by an external caller and is required to +// confirm to `heap.Interface`. func (th *Heap[I, V]) Less(i, j int) bool { if th.isMinHeap { return th.items[i].Val < th.items[j].Val @@ -50,6 +56,9 @@ func (th *Heap[I, V]) Less(i, j int) bool { } // Swap swaps the [i]th and [j]th element in th. +// +// This should never be called by an external caller and is required to +// confirm to `heap.Interface`. func (th *Heap[I, V]) Swap(i, j int) { th.items[i], th.items[j] = th.items[j], th.items[i] th.items[i].Index = i @@ -58,7 +67,10 @@ func (th *Heap[I, V]) Swap(i, j int) { // Push adds an *Entry interface to th. If [x.id] is already in // th, returns. -func (th *Heap[I, V]) Push(x interface{}) { +// +// This should never be called by an external caller and is required to +// confirm to `heap.Interface`. +func (th *Heap[I, V]) Push(x any) { entry, ok := x.(*Entry[I, V]) if !ok { panic(fmt.Errorf("unexpected %T, expected *Uint64Entry", x)) @@ -72,7 +84,10 @@ func (th *Heap[I, V]) Push(x interface{}) { // Pop removes the highest priority item from th and also deletes it from // th's lookup map. -func (th *Heap[I, V]) Pop() interface{} { +// +// This should never be called by an external caller and is required to +// confirm to `heap.Interface`. +func (th *Heap[I, V]) Pop() any { n := len(th.items) item := th.items[n-1] th.items[n-1] = nil // avoid memory leak @@ -99,3 +114,46 @@ func (th *Heap[I, V]) HasID(id ids.ID) bool { func (th *Heap[I, V]) Items() []*Entry[I, V] { return th.items } + +// Add can be called by external users instead of using `containers.heap`, +// which makes using this heap less error-prone. +// +// Add is used interchangably with "Push". +func (th *Heap[I, V]) Add(e *Entry[I, V]) { + heap.Push(th, e) +} + +// Remove can be called by external users to remove an object from the heap at +// a specific index instead of using `containers.heap`, +// which makes using this heap less error-prone. +// +// Remove is used interchangably with "Pop". +func (th *Heap[I, V]) Remove() *Entry[I, V] { + if len(th.items) == 0 { + return nil + } + return heap.Pop(th).(*Entry[I, V]) +} + +// RemoveByIndex can be called by external users to remove an object from the heap at +// a specific index instead of using `containers.heap`, +// which makes using this heap less error-prone. +// +// RemoveByIndex is used interchangably with "Remove". +func (th *Heap[I, V]) RemoveByIndex(index int) *Entry[I, V] { + if index >= len(th.items) { + return nil + } + return heap.Remove(th, index).(*Entry[I, V]) +} + +// First returns the first item in the heap. This is the smallest item in +// a minHeap and the largest item in a maxHeap. +// +// If no items are in the heap, it will return nil. +func (th *Heap[I, V]) First() *Entry[I, V] { + if len(th.items) == 0 { + return nil + } + return th.items[0] +} diff --git a/heap/heap_test.go b/heap/heap_test.go index c0a87817c7..733c7a1732 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -4,7 +4,6 @@ package heap import ( - "container/heap" "testing" "github.com/ava-labs/avalanchego/ids" @@ -75,9 +74,9 @@ func TestUnit64HeapPushPopMin(t *testing.T) { Val: mempoolItem3.UnitPrice(), Index: minHeap.Len(), } - heap.Push(minHeap, med) - heap.Push(minHeap, low) - heap.Push(minHeap, high) + minHeap.Add(med) + minHeap.Add(low) + minHeap.Add(high) // Added all three require.Equal(minHeap.Len(), 3, "Not pushed correctly.") // Check if added to lookup table @@ -88,11 +87,11 @@ func TestUnit64HeapPushPopMin(t *testing.T) { _, ok = minHeap.lookup[high.ID] require.True(ok, "Item not found in lookup.") // Pop and check popped correctly. Order should be 2, 1, 3 - popped := heap.Pop(minHeap) + popped := minHeap.Remove() require.Equal(low, popped, "Incorrect item removed.") - popped = heap.Pop(minHeap) + popped = minHeap.Remove() require.Equal(med, popped, "Incorrect item removed.") - popped = heap.Pop(minHeap) + popped = minHeap.Remove() require.Equal(high, popped, "Incorrect item removed.") } @@ -126,9 +125,9 @@ func TestUnit64HeapPushPopMax(t *testing.T) { Val: mempoolItem3.UnitPrice(), Index: maxHeap.Len(), } - heap.Push(maxHeap, med) - heap.Push(maxHeap, low) - heap.Push(maxHeap, high) + maxHeap.Add(med) + maxHeap.Add(low) + maxHeap.Add(high) // Added all three require.Equal(maxHeap.Len(), 3, "Not pushed correctly.") // Check if added to lookup table @@ -139,11 +138,11 @@ func TestUnit64HeapPushPopMax(t *testing.T) { _, ok = maxHeap.lookup[high.ID] require.True(ok, "Item not found in lookup.") // Pop and check popped correctly. Order should be 2, 1, 3 - popped := heap.Pop(maxHeap) + popped := maxHeap.Remove() require.Equal(high, popped, "Incorrect item removed.") - popped = heap.Pop(maxHeap) + popped = maxHeap.Remove() require.Equal(med, popped, "Incorrect item removed.") - popped = heap.Pop(maxHeap) + popped = maxHeap.Remove() require.Equal(low, popped, "Incorrect item removed.") } @@ -159,13 +158,13 @@ func TestUnit64HeapPushExists(t *testing.T) { Val: mempoolItem.UnitPrice(), Index: minHeap.Len(), } - heap.Push(minHeap, entry) + minHeap.Add(entry) // Pushed correctly require.Equal(minHeap.Len(), 1, "Not pushed correctly.") // Check if added to lookup table _, ok := minHeap.lookup[entry.ID] require.True(ok, "Item not found in lookup.") - heap.Push(minHeap, entry) + minHeap.Add(entry) // Only 1 item require.Equal(minHeap.Len(), 1, "Not pushed correctly.") } @@ -185,7 +184,7 @@ func TestUnit64HeapGetID(t *testing.T) { } _, ok := minHeap.GetID(mempoolItem.ID()) require.False(ok, "Entry returned before pushing.") - heap.Push(minHeap, entry) + minHeap.Add(entry) // Pushed correctly require.Equal(minHeap.Len(), 1, "Not pushed correctly.") entryReturned, ok := minHeap.GetID(mempoolItem.ID()) @@ -206,7 +205,7 @@ func TestUnit64HeapHasID(t *testing.T) { } ok := minHeap.HasID(mempoolItem.ID()) require.False(ok, "Entry has ID before pushing.") - heap.Push(minHeap, entry) + minHeap.Add(entry) // Pushed correctly require.Equal(minHeap.Len(), 1, "Not pushed correctly.") ok = minHeap.HasID(mempoolItem.ID()) diff --git a/mempool/sorted_mempool.go b/mempool/sorted_mempool.go index 83e2f2e0ad..162cde3845 100644 --- a/mempool/sorted_mempool.go +++ b/mempool/sorted_mempool.go @@ -4,11 +4,12 @@ package mempool import ( - "container/heap" - "github.com/ava-labs/avalanchego/ids" + + "github.com/ava-labs/hypersdk/heap" ) +// Item is the interface that any item put in the mempool must adheare to. type Item interface { ID() ids.ID Payer() string @@ -18,12 +19,15 @@ type Item interface { // SortedMempool contains a max-heap and min-heap. The order within each // heap is determined by using GetValue. +// +// This data structure does not perform any synchronization and is not +// safe to use concurrently without external locking. type SortedMempool[T Item] struct { // GetValue informs heaps how to get the an entry's value for ordering. GetValue func(item T) uint64 - minHeap *Uint64Heap[T] // only includes lowest nonce - maxHeap *Uint64Heap[T] // only includes lowest nonce + minHeap *heap.Heap[T, uint64] // only includes lowest nonce + maxHeap *heap.Heap[T, uint64] // only includes lowest nonce } // NewSortedMempool returns an instance of SortedMempool with minHeap and maxHeap @@ -31,8 +35,8 @@ type SortedMempool[T Item] struct { func NewSortedMempool[T Item](items int, f func(item T) uint64) *SortedMempool[T] { return &SortedMempool[T]{ GetValue: f, - minHeap: NewUint64Heap[T](items, true), - maxHeap: NewUint64Heap[T](items, false), + minHeap: heap.New[T, uint64](items, true), + maxHeap: heap.New[T, uint64](items, false), } } @@ -41,13 +45,13 @@ func (sm *SortedMempool[T]) Add(item T) { itemID := item.ID() poolLen := sm.maxHeap.Len() val := sm.GetValue(item) - heap.Push(sm.maxHeap, &Uint64Entry[T]{ + sm.maxHeap.Add(&heap.Entry[T, uint64]{ ID: itemID, Val: val, Item: item, Index: poolLen, }) - heap.Push(sm.minHeap, &Uint64Entry[T]{ + sm.minHeap.Add(&heap.Entry[T, uint64]{ ID: itemID, Val: val, Item: item, @@ -61,19 +65,18 @@ func (sm *SortedMempool[T]) Remove(id ids.ID) { if !ok { return } - heap.Remove(sm.maxHeap, maxEntry.Index) // O(log N) + sm.maxHeap.Remove(maxEntry.Index) // O(log N) minEntry, ok := sm.minHeap.GetID(id) if !ok { // This should never happen, as that would mean the heaps are out of // sync. return } - heap.Remove(sm.minHeap, minEntry.Index) // O(log N) + sm.minHeap.Remove(minEntry.Index) // O(log N) } // SetMinVal removes all elements in sm with a value less than [val]. Returns // the list of removed elements. -// TODO: add lock to prevent concurrent access func (sm *SortedMempool[T]) SetMinVal(val uint64) []T { removed := []T{} for { @@ -93,36 +96,40 @@ func (sm *SortedMempool[T]) SetMinVal(val uint64) []T { // PeekMin returns the minimum value in sm. func (sm *SortedMempool[T]) PeekMin() (T, bool) { - if sm.minHeap.Len() == 0 { + first := sm.minHeap.First() + if first == nil { return *new(T), false } - return sm.minHeap.items[0].Item, true + return first.Item, true } // PopMin removes the minimum value in sm. func (sm *SortedMempool[T]) PopMin() (T, bool) { - if sm.minHeap.Len() == 0 { + first := sm.minHeap.First() + if first == nil { return *new(T), false } - item := sm.minHeap.items[0].Item + item := first.Item sm.Remove(item.ID()) return item, true } // PopMin returns the maximum value in sm. func (sm *SortedMempool[T]) PeekMax() (T, bool) { - if sm.Len() == 0 { + first := sm.maxHeap.First() + if first == nil { return *new(T), false } - return sm.maxHeap.items[0].Item, true + return first.Item, true } // PopMin removes the maximum value in sm. func (sm *SortedMempool[T]) PopMax() (T, bool) { - if sm.Len() == 0 { + first := sm.maxHeap.First() + if first == nil { return *new(T), false } - item := sm.maxHeap.items[0].Item + item := first.Item sm.Remove(item.ID()) return item, true } diff --git a/mempool/uint64_heap.go b/mempool/uint64_heap.go deleted file mode 100644 index b29759b584..0000000000 --- a/mempool/uint64_heap.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package mempool - -import ( - "fmt" - - "github.com/ava-labs/avalanchego/ids" -) - -type Uint64Entry[T any] struct { - ID ids.ID // id of entry - Item T // associated item - Val uint64 // Value to be prioritized - - Index int // Index of the entry in heap -} - -// Uint64Heap[T] is used to track pending transactions by [Val] -type Uint64Heap[T any] struct { - isMinHeap bool // true for Min-Heap, false for Max-Heap - items []*Uint64Entry[T] // items in this heap - lookup map[ids.ID]*Uint64Entry[T] // ids in the heap mapping to an entry -} - -// newUint64Heap returns an instance of Uint64Heap[T] -func NewUint64Heap[T any](items int, isMinHeap bool) *Uint64Heap[T] { - return &Uint64Heap[T]{ - isMinHeap: isMinHeap, - - items: make([]*Uint64Entry[T], 0, items), - lookup: make(map[ids.ID]*Uint64Entry[T], items), - } -} - -// Len returns the number of items in th. -func (th Uint64Heap[T]) Len() int { return len(th.items) } - -// Less compares the priority of [i] and [j] based on th.isMinHeap. -func (th Uint64Heap[T]) Less(i, j int) bool { - if th.isMinHeap { - return th.items[i].Val < th.items[j].Val - } - return th.items[i].Val > th.items[j].Val -} - -// Swap swaps the [i]th and [j]th element in th. -func (th Uint64Heap[T]) Swap(i, j int) { - th.items[i], th.items[j] = th.items[j], th.items[i] - th.items[i].Index = i - th.items[j].Index = j -} - -// Push adds an *uint64Entry interface to th. If [x.id] is already in -// th, returns. -func (th *Uint64Heap[T]) Push(x interface{}) { - entry, ok := x.(*Uint64Entry[T]) - if !ok { - panic(fmt.Errorf("unexpected %T, expected *Uint64Entry", x)) - } - if th.HasID(entry.ID) { - return - } - th.items = append(th.items, entry) - th.lookup[entry.ID] = entry -} - -// Pop removes the highest priority item from th and also deletes it from -// th's lookup map. -func (th *Uint64Heap[T]) Pop() interface{} { - n := len(th.items) - item := th.items[n-1] - th.items[n-1] = nil // avoid memory leak - th.items = th.items[0 : n-1] - delete(th.lookup, item.ID) - return item -} - -// GetID returns the entry in th associated with [id], and a bool if [id] was -// found in th. -func (th *Uint64Heap[T]) GetID(id ids.ID) (*Uint64Entry[T], bool) { - entry, ok := th.lookup[id] - return entry, ok -} - -// HasID returns whether [id] is found in th. -func (th *Uint64Heap[T]) HasID(id ids.ID) bool { - _, has := th.GetID(id) - return has -} - -// Items returns all items in the heap in sorted order. You should not modify -// the response. -func (th *Uint64Heap[T]) Items() []*Uint64Entry[T] { - return th.items -} diff --git a/mempool/uint64_heap_test.go b/mempool/uint64_heap_test.go deleted file mode 100644 index 725cc79d6a..0000000000 --- a/mempool/uint64_heap_test.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package mempool - -import ( - "container/heap" - "testing" - - "github.com/ava-labs/avalanchego/ids" - "github.com/stretchr/testify/require" -) - -const testPayer = "testPayer" - -type MempoolTestItem struct { - id ids.ID - payer string - timestamp int64 - unitPrice uint64 -} - -func (mti *MempoolTestItem) ID() ids.ID { - return mti.id -} - -func (mti *MempoolTestItem) Payer() string { - return mti.payer -} - -func (mti *MempoolTestItem) UnitPrice() uint64 { - return mti.unitPrice -} - -func (mti *MempoolTestItem) Expiry() int64 { - return mti.timestamp -} - -func GenerateTestItem(payer string, t int64, unitPrice uint64) *MempoolTestItem { - id := ids.GenerateTestID() - return &MempoolTestItem{ - id: id, - payer: payer, - timestamp: t, - unitPrice: unitPrice, - } -} - -func TestUnit64HeapPushPopMin(t *testing.T) { - require := require.New(t) - minHeap := NewUint64Heap[*MempoolTestItem](0, true) - require.Equal(minHeap.Len(), 0, "heap not initialized properly.") - mempoolItem1 := GenerateTestItem(testPayer, 1, 10) - mempoolItem2 := GenerateTestItem(testPayer, 2, 7) - mempoolItem3 := GenerateTestItem(testPayer, 3, 15) - - // Middle UnitPrice - med := &Uint64Entry[*MempoolTestItem]{ - ID: mempoolItem1.ID(), - Item: mempoolItem1, - Val: mempoolItem1.UnitPrice(), - Index: minHeap.Len(), - } - // Lesser UnitPrice - low := &Uint64Entry[*MempoolTestItem]{ - ID: mempoolItem2.ID(), - Item: mempoolItem2, - Val: mempoolItem2.UnitPrice(), - Index: minHeap.Len(), - } - // Greatest UnitPrice - high := &Uint64Entry[*MempoolTestItem]{ - ID: mempoolItem3.ID(), - Item: mempoolItem3, - Val: mempoolItem3.UnitPrice(), - Index: minHeap.Len(), - } - heap.Push(minHeap, med) - heap.Push(minHeap, low) - heap.Push(minHeap, high) - // Added all three - require.Equal(minHeap.Len(), 3, "Not pushed correctly.") - // Check if added to lookup table - _, ok := minHeap.lookup[med.ID] - require.True(ok, "Item not found in lookup.") - _, ok = minHeap.lookup[low.ID] - require.True(ok, "Item not found in lookup.") - _, ok = minHeap.lookup[high.ID] - require.True(ok, "Item not found in lookup.") - // Pop and check popped correctly. Order should be 2, 1, 3 - popped := heap.Pop(minHeap) - require.Equal(low, popped, "Incorrect item removed.") - popped = heap.Pop(minHeap) - require.Equal(med, popped, "Incorrect item removed.") - popped = heap.Pop(minHeap) - require.Equal(high, popped, "Incorrect item removed.") -} - -func TestUnit64HeapPushPopMax(t *testing.T) { - require := require.New(t) - maxHeap := NewUint64Heap[*MempoolTestItem](0, false) - require.Equal(maxHeap.Len(), 0, "heap not initialized properly.") - - mempoolItem1 := GenerateTestItem(testPayer, 1, 10) - mempoolItem2 := GenerateTestItem(testPayer, 2, 7) - mempoolItem3 := GenerateTestItem(testPayer, 3, 15) - - // Middle UnitPrice - med := &Uint64Entry[*MempoolTestItem]{ - ID: mempoolItem1.ID(), - Item: mempoolItem1, - Val: mempoolItem1.UnitPrice(), - Index: maxHeap.Len(), - } - // Lesser UnitPrice - low := &Uint64Entry[*MempoolTestItem]{ - ID: mempoolItem2.ID(), - Item: mempoolItem2, - Val: mempoolItem2.UnitPrice(), - Index: maxHeap.Len(), - } - // Greatest UnitPrice - high := &Uint64Entry[*MempoolTestItem]{ - ID: mempoolItem3.ID(), - Item: mempoolItem3, - Val: mempoolItem3.UnitPrice(), - Index: maxHeap.Len(), - } - heap.Push(maxHeap, med) - heap.Push(maxHeap, low) - heap.Push(maxHeap, high) - // Added all three - require.Equal(maxHeap.Len(), 3, "Not pushed correctly.") - // Check if added to lookup table - _, ok := maxHeap.lookup[med.ID] - require.True(ok, "Item not found in lookup.") - _, ok = maxHeap.lookup[low.ID] - require.True(ok, "Item not found in lookup.") - _, ok = maxHeap.lookup[high.ID] - require.True(ok, "Item not found in lookup.") - // Pop and check popped correctly. Order should be 2, 1, 3 - popped := heap.Pop(maxHeap) - require.Equal(high, popped, "Incorrect item removed.") - popped = heap.Pop(maxHeap) - require.Equal(med, popped, "Incorrect item removed.") - popped = heap.Pop(maxHeap) - require.Equal(low, popped, "Incorrect item removed.") -} - -func TestUnit64HeapPushExists(t *testing.T) { - // Push an item already in heap - require := require.New(t) - minHeap := NewUint64Heap[*MempoolTestItem](0, true) - require.Equal(minHeap.Len(), 0, "heap not initialized properly.") - mempoolItem := GenerateTestItem(testPayer, 1, 10) - entry := &Uint64Entry[*MempoolTestItem]{ - ID: mempoolItem.ID(), - Item: mempoolItem, - Val: mempoolItem.UnitPrice(), - Index: minHeap.Len(), - } - heap.Push(minHeap, entry) - // Pushed correctly - require.Equal(minHeap.Len(), 1, "Not pushed correctly.") - // Check if added to lookup table - _, ok := minHeap.lookup[entry.ID] - require.True(ok, "Item not found in lookup.") - heap.Push(minHeap, entry) - // Only 1 item - require.Equal(minHeap.Len(), 1, "Not pushed correctly.") -} - -func TestUnit64HeapGetID(t *testing.T) { - // Push an item and grab its ID - require := require.New(t) - minHeap := NewUint64Heap[*MempoolTestItem](0, true) - require.Equal(minHeap.Len(), 0, "heap not initialized properly.") - - mempoolItem := GenerateTestItem(testPayer, 1, 10) - entry := &Uint64Entry[*MempoolTestItem]{ - ID: mempoolItem.ID(), - Item: mempoolItem, - Val: mempoolItem.UnitPrice(), - Index: minHeap.Len(), - } - _, ok := minHeap.GetID(mempoolItem.ID()) - require.False(ok, "Entry returned before pushing.") - heap.Push(minHeap, entry) - // Pushed correctly - require.Equal(minHeap.Len(), 1, "Not pushed correctly.") - entryReturned, ok := minHeap.GetID(mempoolItem.ID()) - require.True(ok, "Entry not returned.") - require.Equal(entry, entryReturned, "Returned incorrect entry") -} - -func TestUnit64HeapHasID(t *testing.T) { - require := require.New(t) - minHeap := NewUint64Heap[*MempoolTestItem](0, true) - require.Equal(minHeap.Len(), 0, "heap not initialized properly.") - mempoolItem := GenerateTestItem(testPayer, 1, 10) - entry := &Uint64Entry[*MempoolTestItem]{ - ID: mempoolItem.ID(), - Item: mempoolItem, - Val: mempoolItem.UnitPrice(), - Index: minHeap.Len(), - } - ok := minHeap.HasID(mempoolItem.ID()) - require.False(ok, "Entry has ID before pushing.") - heap.Push(minHeap, entry) - // Pushed correctly - require.Equal(minHeap.Len(), 1, "Not pushed correctly.") - ok = minHeap.HasID(mempoolItem.ID()) - require.True(ok, "Entry was not found in heap.") -} From 60b0e65fbd763b79617e5e37076487f59192c5a7 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sat, 25 Feb 2023 13:59:08 -0800 Subject: [PATCH 4/8] remove extra heap --- examples/tokenvm/controller/order_book.go | 15 ++-- examples/tokenvm/utils/float64_heap.go | 100 ---------------------- 2 files changed, 7 insertions(+), 108 deletions(-) delete mode 100644 examples/tokenvm/utils/float64_heap.go diff --git a/examples/tokenvm/controller/order_book.go b/examples/tokenvm/controller/order_book.go index 8353436e85..90cf66a39d 100644 --- a/examples/tokenvm/controller/order_book.go +++ b/examples/tokenvm/controller/order_book.go @@ -4,12 +4,11 @@ package controller import ( - "container/heap" "sync" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/crypto" - "github.com/ava-labs/hypersdk/examples/tokenvm/utils" + "github.com/ava-labs/hypersdk/heap" "go.uber.org/zap" ) @@ -34,7 +33,7 @@ type OrderBook struct { // TODO: consider capping the number of orders in each heap (need to ensure // that doing so does not make it possible to send a bunch of small, spam // orders to clear -> may need to set a min order limit to watch) - orders map[string]*utils.Float64Heap[*Order] + orders map[string]*heap.Heap[*Order, float64] orderToPair map[ids.ID]string // needed to delete from [CloseOrder] actions l sync.RWMutex @@ -42,7 +41,7 @@ type OrderBook struct { } func NewOrderBook(c *Controller, trackedPairs []string) *OrderBook { - m := map[string]*utils.Float64Heap[*Order]{} + m := map[string]*heap.Heap[*Order, float64]{} trackAll := false if len(trackedPairs) == 1 && trackedPairs[0] == allPairs { trackAll = true @@ -50,7 +49,7 @@ func NewOrderBook(c *Controller, trackedPairs []string) *OrderBook { } else { for _, pair := range trackedPairs { // We use a max heap so we return the best rates in order. - m[pair] = utils.NewFloat64Heap[*Order](initialPairCapacity, true) + m[pair] = heap.New[*Order, float64](initialPairCapacity, true) c.inner.Logger().Info("tracking order book", zap.String("pair", pair)) } } @@ -71,10 +70,10 @@ func (o *OrderBook) Add(pair string, order *Order) { return case !ok && o.trackAll: o.c.inner.Logger().Info("tracking order book", zap.String("pair", pair)) - h = utils.NewFloat64Heap[*Order](initialPairCapacity, true) + h = heap.New[*Order, float64](initialPairCapacity, true) o.orders[pair] = h } - heap.Push(h, &utils.Float64Entry[*Order]{ + h.Add(&heap.Entry[*Order, float64]{ ID: order.ID, Val: float64(order.InTick) / float64(order.OutTick), Item: order, @@ -101,7 +100,7 @@ func (o *OrderBook) Remove(id ids.ID) { // This should never happen return } - heap.Remove(h, entry.Index) // O(log N) + h.RemoveByIndex(entry.Index) // O(log N) } func (o *OrderBook) UpdateRemaining(id ids.ID, remaining uint64) { diff --git a/examples/tokenvm/utils/float64_heap.go b/examples/tokenvm/utils/float64_heap.go deleted file mode 100644 index e5a75e501f..0000000000 --- a/examples/tokenvm/utils/float64_heap.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import ( - "fmt" - - "github.com/ava-labs/avalanchego/ids" -) - -// TODO: make Val generic and move to shared utils package in HyperSDK -type Float64Entry[T any] struct { - ID ids.ID // id of entry - Item T // associated item - Val float64 // Value to be prioritized - - Index int // Index of the entry in heap -} - -// Float64Heap[T] is used to track pending transactions by [Val] -type Float64Heap[T any] struct { - isMinHeap bool // true for Min-Heap, false for Max-Heap - items []*Float64Entry[T] // items in this heap - lookup map[ids.ID]*Float64Entry[T] // ids in the heap mapping to an entry -} - -// newFloat64Heap returns an instance of Float64Heap[T] -func NewFloat64Heap[T any](items int, isMinHeap bool) *Float64Heap[T] { - return &Float64Heap[T]{ - isMinHeap: isMinHeap, - - items: make([]*Float64Entry[T], 0, items), - lookup: make(map[ids.ID]*Float64Entry[T], items), - } -} - -// Len returns the number of items in th. -func (th Float64Heap[T]) Len() int { - return len(th.items) -} - -// Less compares the priority of [i] and [j] based on th.isMinHeap. -func (th Float64Heap[T]) Less(i, j int) bool { - if th.isMinHeap { - return th.items[i].Val < th.items[j].Val - } - return th.items[i].Val > th.items[j].Val -} - -// Swap swaps the [i]th and [j]th element in th. -func (th Float64Heap[T]) Swap(i, j int) { - th.items[i], th.items[j] = th.items[j], th.items[i] - th.items[i].Index = i - th.items[j].Index = j -} - -// Push addes an *Float64Entry interface to th. If [x.ID] is already in -// th, returns. -func (th *Float64Heap[T]) Push(x interface{}) { - entry, ok := x.(*Float64Entry[T]) - if !ok { - panic(fmt.Errorf("unexpected %T, expected *Float64Entry", x)) - } - if th.HasID(entry.ID) { - return - } - th.items = append(th.items, entry) - th.lookup[entry.ID] = entry -} - -// Pop removes the highest priority item from th and also deletes it from -// th's lookup map. -func (th *Float64Heap[T]) Pop() interface{} { - n := len(th.items) - item := th.items[n-1] - th.items[n-1] = nil // avoid memory leak - th.items = th.items[0 : n-1] - delete(th.lookup, item.ID) - return item -} - -// GetID returns the entry in th associated with [id], and a bool if [id] was -// found in th. -func (th *Float64Heap[T]) GetID(id ids.ID) (*Float64Entry[T], bool) { - entry, ok := th.lookup[id] - return entry, ok -} - -// HasID returns whether [id] is found in th. -func (th *Float64Heap[T]) HasID(id ids.ID) bool { - _, has := th.GetID(id) - return has -} - -// Items returns all items in the heap in sorted order. You should not modify -// the response. -func (th *Float64Heap[T]) Items() []*Float64Entry[T] { - return th.items -} From abe4b6e24cba7b3df3ae74ed1d933d4cf1311a39 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sat, 25 Feb 2023 14:13:45 -0800 Subject: [PATCH 5/8] fix tests --- heap/heap_test.go | 126 +++++++++++++-------------------- mempool/sorted_mempool.go | 4 +- mempool/sorted_mempool_test.go | 37 ++++++++++ 3 files changed, 87 insertions(+), 80 deletions(-) diff --git a/heap/heap_test.go b/heap/heap_test.go index 733c7a1732..594bf3484d 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -10,68 +10,38 @@ import ( "github.com/stretchr/testify/require" ) -const testPayer = "testPayer" - -type MempoolTestItem struct { - id ids.ID - payer string - timestamp int64 - unitPrice uint64 -} - -func (mti *MempoolTestItem) ID() ids.ID { - return mti.id -} - -func (mti *MempoolTestItem) Payer() string { - return mti.payer -} - -func (mti *MempoolTestItem) UnitPrice() uint64 { - return mti.unitPrice -} - -func (mti *MempoolTestItem) Expiry() int64 { - return mti.timestamp -} - -func GenerateTestItem(payer string, t int64, unitPrice uint64) *MempoolTestItem { - id := ids.GenerateTestID() - return &MempoolTestItem{ - id: id, - payer: payer, - timestamp: t, - unitPrice: unitPrice, - } +type testItem struct { + id ids.ID + value uint64 } func TestUnit64HeapPushPopMin(t *testing.T) { require := require.New(t) - minHeap := New[*MempoolTestItem, uint64](0, true) + minHeap := New[*testItem, uint64](0, true) require.Equal(minHeap.Len(), 0, "heap not initialized properly.") - mempoolItem1 := GenerateTestItem(testPayer, 1, 10) - mempoolItem2 := GenerateTestItem(testPayer, 2, 7) - mempoolItem3 := GenerateTestItem(testPayer, 3, 15) + mempoolItem1 := &testItem{ids.GenerateTestID(), 10} + mempoolItem2 := &testItem{ids.GenerateTestID(), 7} + mempoolItem3 := &testItem{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[*MempoolTestItem, uint64]{ - ID: mempoolItem1.ID(), + med := &Entry[*testItem, uint64]{ + ID: mempoolItem1.id, Item: mempoolItem1, - Val: mempoolItem1.UnitPrice(), + Val: mempoolItem1.value, Index: minHeap.Len(), } // Lesser UnitPrice - low := &Entry[*MempoolTestItem, uint64]{ - ID: mempoolItem2.ID(), + low := &Entry[*testItem, uint64]{ + ID: mempoolItem2.id, Item: mempoolItem2, - Val: mempoolItem2.UnitPrice(), + Val: mempoolItem2.value, Index: minHeap.Len(), } // Greatest UnitPrice - high := &Entry[*MempoolTestItem, uint64]{ - ID: mempoolItem3.ID(), + high := &Entry[*testItem, uint64]{ + ID: mempoolItem3.id, Item: mempoolItem3, - Val: mempoolItem3.UnitPrice(), + Val: mempoolItem3.value, Index: minHeap.Len(), } minHeap.Add(med) @@ -97,32 +67,32 @@ func TestUnit64HeapPushPopMin(t *testing.T) { func TestUnit64HeapPushPopMax(t *testing.T) { require := require.New(t) - maxHeap := New[*MempoolTestItem, uint64](0, false) + maxHeap := New[*testItem, uint64](0, false) require.Equal(maxHeap.Len(), 0, "heap not initialized properly.") - mempoolItem1 := GenerateTestItem(testPayer, 1, 10) - mempoolItem2 := GenerateTestItem(testPayer, 2, 7) - mempoolItem3 := GenerateTestItem(testPayer, 3, 15) + mempoolItem1 := &testItem{ids.GenerateTestID(), 10} + mempoolItem2 := &testItem{ids.GenerateTestID(), 7} + mempoolItem3 := &testItem{ids.GenerateTestID(), 15} // Middle UnitPrice - med := &Entry[*MempoolTestItem, uint64]{ - ID: mempoolItem1.ID(), + med := &Entry[*testItem, uint64]{ + ID: mempoolItem1.id, Item: mempoolItem1, - Val: mempoolItem1.UnitPrice(), + Val: mempoolItem1.value, Index: maxHeap.Len(), } // Lesser UnitPrice - low := &Entry[*MempoolTestItem, uint64]{ - ID: mempoolItem2.ID(), + low := &Entry[*testItem, uint64]{ + ID: mempoolItem2.id, Item: mempoolItem2, - Val: mempoolItem2.UnitPrice(), + Val: mempoolItem2.value, Index: maxHeap.Len(), } // Greatest UnitPrice - high := &Entry[*MempoolTestItem, uint64]{ - ID: mempoolItem3.ID(), + high := &Entry[*testItem, uint64]{ + ID: mempoolItem3.id, Item: mempoolItem3, - Val: mempoolItem3.UnitPrice(), + Val: mempoolItem3.value, Index: maxHeap.Len(), } maxHeap.Add(med) @@ -149,13 +119,13 @@ func TestUnit64HeapPushPopMax(t *testing.T) { func TestUnit64HeapPushExists(t *testing.T) { // Push an item already in heap require := require.New(t) - minHeap := New[*MempoolTestItem, uint64](0, true) + minHeap := New[*testItem, uint64](0, true) require.Equal(minHeap.Len(), 0, "heap not initialized properly.") - mempoolItem := GenerateTestItem(testPayer, 1, 10) - entry := &Entry[*MempoolTestItem, uint64]{ - ID: mempoolItem.ID(), + mempoolItem := &testItem{ids.GenerateTestID(), 10} + entry := &Entry[*testItem, uint64]{ + ID: mempoolItem.id, Item: mempoolItem, - Val: mempoolItem.UnitPrice(), + Val: mempoolItem.value, Index: minHeap.Len(), } minHeap.Add(entry) @@ -172,42 +142,42 @@ func TestUnit64HeapPushExists(t *testing.T) { func TestUnit64HeapGetID(t *testing.T) { // Push an item and grab its ID require := require.New(t) - minHeap := New[*MempoolTestItem, uint64](0, true) + minHeap := New[*testItem, uint64](0, true) require.Equal(minHeap.Len(), 0, "heap not initialized properly.") - mempoolItem := GenerateTestItem(testPayer, 1, 10) - entry := &Entry[*MempoolTestItem, uint64]{ - ID: mempoolItem.ID(), + mempoolItem := &testItem{ids.GenerateTestID(), 10} + entry := &Entry[*testItem, uint64]{ + ID: mempoolItem.id, Item: mempoolItem, - Val: mempoolItem.UnitPrice(), + Val: mempoolItem.value, Index: minHeap.Len(), } - _, ok := minHeap.GetID(mempoolItem.ID()) + _, ok := minHeap.GetID(mempoolItem.id) require.False(ok, "Entry returned before pushing.") minHeap.Add(entry) // Pushed correctly require.Equal(minHeap.Len(), 1, "Not pushed correctly.") - entryReturned, ok := minHeap.GetID(mempoolItem.ID()) + entryReturned, ok := minHeap.GetID(mempoolItem.id) require.True(ok, "Entry not returned.") require.Equal(entry, entryReturned, "Returned incorrect entry") } func TestUnit64HeapHasID(t *testing.T) { require := require.New(t) - minHeap := New[*MempoolTestItem, uint64](0, true) + minHeap := New[*testItem, uint64](0, true) require.Equal(minHeap.Len(), 0, "heap not initialized properly.") - mempoolItem := GenerateTestItem(testPayer, 1, 10) - entry := &Entry[*MempoolTestItem, uint64]{ - ID: mempoolItem.ID(), + mempoolItem := &testItem{ids.GenerateTestID(), 10} + entry := &Entry[*testItem, uint64]{ + ID: mempoolItem.id, Item: mempoolItem, - Val: mempoolItem.UnitPrice(), + Val: mempoolItem.value, Index: minHeap.Len(), } - ok := minHeap.HasID(mempoolItem.ID()) + ok := minHeap.HasID(mempoolItem.id) require.False(ok, "Entry has ID before pushing.") minHeap.Add(entry) // Pushed correctly require.Equal(minHeap.Len(), 1, "Not pushed correctly.") - ok = minHeap.HasID(mempoolItem.ID()) + ok = minHeap.HasID(mempoolItem.id) require.True(ok, "Entry was not found in heap.") } diff --git a/mempool/sorted_mempool.go b/mempool/sorted_mempool.go index 162cde3845..e834d4ae22 100644 --- a/mempool/sorted_mempool.go +++ b/mempool/sorted_mempool.go @@ -65,14 +65,14 @@ func (sm *SortedMempool[T]) Remove(id ids.ID) { if !ok { return } - sm.maxHeap.Remove(maxEntry.Index) // O(log N) + sm.maxHeap.RemoveByIndex(maxEntry.Index) // O(log N) minEntry, ok := sm.minHeap.GetID(id) if !ok { // This should never happen, as that would mean the heaps are out of // sync. return } - sm.minHeap.Remove(minEntry.Index) // O(log N) + sm.minHeap.RemoveByIndex(minEntry.Index) // O(log N) } // SetMinVal removes all elements in sm with a value less than [val]. Returns diff --git a/mempool/sorted_mempool_test.go b/mempool/sorted_mempool_test.go index 0c79d11a93..93b01fd67e 100644 --- a/mempool/sorted_mempool_test.go +++ b/mempool/sorted_mempool_test.go @@ -7,8 +7,45 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/ids" ) +const testPayer = "testPayer" + +type MempoolTestItem struct { + id ids.ID + payer string + timestamp int64 + unitPrice uint64 +} + +func (mti *MempoolTestItem) ID() ids.ID { + return mti.id +} + +func (mti *MempoolTestItem) Payer() string { + return mti.payer +} + +func (mti *MempoolTestItem) UnitPrice() uint64 { + return mti.unitPrice +} + +func (mti *MempoolTestItem) Expiry() int64 { + return mti.timestamp +} + +func GenerateTestItem(payer string, t int64, unitPrice uint64) *MempoolTestItem { + id := ids.GenerateTestID() + return &MempoolTestItem{ + id: id, + payer: payer, + timestamp: t, + unitPrice: unitPrice, + } +} + func TestSortedMempoolNew(t *testing.T) { // Creates empty min and max heaps require := require.New(t) From a877c09d29a7216bc5af8f9ce16e4d143fe4eff2 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sat, 25 Feb 2023 14:14:23 -0800 Subject: [PATCH 6/8] typo --- heap/heap.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/heap/heap.go b/heap/heap.go index 8e044139f2..405f05f6fb 100644 --- a/heap/heap.go +++ b/heap/heap.go @@ -118,7 +118,7 @@ func (th *Heap[I, V]) Items() []*Entry[I, V] { // Add can be called by external users instead of using `containers.heap`, // which makes using this heap less error-prone. // -// Add is used interchangably with "Push". +// Add is used interchangeably with "Push". func (th *Heap[I, V]) Add(e *Entry[I, V]) { heap.Push(th, e) } @@ -127,7 +127,7 @@ func (th *Heap[I, V]) Add(e *Entry[I, V]) { // a specific index instead of using `containers.heap`, // which makes using this heap less error-prone. // -// Remove is used interchangably with "Pop". +// Remove is used interchangeably with "Pop". func (th *Heap[I, V]) Remove() *Entry[I, V] { if len(th.items) == 0 { return nil @@ -139,7 +139,7 @@ func (th *Heap[I, V]) Remove() *Entry[I, V] { // a specific index instead of using `containers.heap`, // which makes using this heap less error-prone. // -// RemoveByIndex is used interchangably with "Remove". +// RemoveByIndex is used interchangeably with "Remove". func (th *Heap[I, V]) RemoveByIndex(index int) *Entry[I, V] { if index >= len(th.items) { return nil From 90cc7667eb7339f2e01e2dbdf31e5a21d794ff5a Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sat, 25 Feb 2023 14:34:02 -0800 Subject: [PATCH 7/8] re-add Push/Pop/Remove --- examples/tokenvm/controller/order_book.go | 8 +- heap/heap.go | 130 +++++----------------- heap/heap_test.go | 54 ++++----- heap/inner_heap.go | 105 +++++++++++++++++ mempool/sorted_mempool.go | 14 +-- mempool/sorted_mempool_test.go | 12 +- 6 files changed, 175 insertions(+), 148 deletions(-) create mode 100644 heap/inner_heap.go diff --git a/examples/tokenvm/controller/order_book.go b/examples/tokenvm/controller/order_book.go index 90cf66a39d..944fbfe6c8 100644 --- a/examples/tokenvm/controller/order_book.go +++ b/examples/tokenvm/controller/order_book.go @@ -73,7 +73,7 @@ func (o *OrderBook) Add(pair string, order *Order) { h = heap.New[*Order, float64](initialPairCapacity, true) o.orders[pair] = h } - h.Add(&heap.Entry[*Order, float64]{ + h.Push(&heap.Entry[*Order, float64]{ ID: order.ID, Val: float64(order.InTick) / float64(order.OutTick), Item: order, @@ -95,12 +95,12 @@ func (o *OrderBook) Remove(id ids.ID) { // This should never happen return } - entry, ok := h.GetID(id) // O(log 1) + entry, ok := h.Get(id) // O(log 1) if !ok { // This should never happen return } - h.RemoveByIndex(entry.Index) // O(log N) + h.Remove(entry.Index) // O(log N) } func (o *OrderBook) UpdateRemaining(id ids.ID, remaining uint64) { @@ -115,7 +115,7 @@ func (o *OrderBook) UpdateRemaining(id ids.ID, remaining uint64) { // This should never happen return } - entry, ok := h.GetID(id) + entry, ok := h.Get(id) if !ok { // This should never happen return diff --git a/heap/heap.go b/heap/heap.go index 405f05f6fb..cb46ba9db2 100644 --- a/heap/heap.go +++ b/heap/heap.go @@ -5,155 +5,77 @@ package heap import ( "container/heap" - "fmt" "github.com/ava-labs/avalanchego/ids" "golang.org/x/exp/constraints" ) -var _ heap.Interface = (*Heap[any, uint64])(nil) - -type Entry[I any, V constraints.Ordered] struct { - ID ids.ID // id of entry - Item I // associated item - Val V // Value to be prioritized - - Index int // Index of the entry in heap -} - // Heap[I,V] is used to track objects of [I] by [Val]. // // This data structure does not perform any synchronization and is not // safe to use concurrently without external locking. type Heap[I any, V constraints.Ordered] struct { - isMinHeap bool // true for Min-Heap, false for Max-Heap - items []*Entry[I, V] // items in this heap - lookup map[ids.ID]*Entry[I, V] // ids in the heap mapping to an entry + ih *innerHeap[I, V] } // New returns an instance of Heap[I,V] func New[I any, V constraints.Ordered](items int, isMinHeap bool) *Heap[I, V] { - return &Heap[I, V]{ - isMinHeap: isMinHeap, - - items: make([]*Entry[I, V], 0, items), - lookup: make(map[ids.ID]*Entry[I, V], items), - } -} - -// Len returns the number of items in th. -func (th *Heap[I, V]) Len() int { return len(th.items) } - -// Less compares the priority of [i] and [j] based on th.isMinHeap. -// -// This should never be called by an external caller and is required to -// confirm to `heap.Interface`. -func (th *Heap[I, V]) Less(i, j int) bool { - if th.isMinHeap { - return th.items[i].Val < th.items[j].Val - } - return th.items[i].Val > th.items[j].Val -} - -// Swap swaps the [i]th and [j]th element in th. -// -// This should never be called by an external caller and is required to -// confirm to `heap.Interface`. -func (th *Heap[I, V]) Swap(i, j int) { - th.items[i], th.items[j] = th.items[j], th.items[i] - th.items[i].Index = i - th.items[j].Index = j + return &Heap[I, V]{newInnerHeap[I, V](items, isMinHeap)} } -// Push adds an *Entry interface to th. If [x.id] is already in -// th, returns. -// -// This should never be called by an external caller and is required to -// confirm to `heap.Interface`. -func (th *Heap[I, V]) Push(x any) { - entry, ok := x.(*Entry[I, V]) - if !ok { - panic(fmt.Errorf("unexpected %T, expected *Uint64Entry", x)) - } - if th.HasID(entry.ID) { - return - } - th.items = append(th.items, entry) - th.lookup[entry.ID] = entry -} +// Len returns the number of items in ih. +func (h *Heap[I, V]) Len() int { return h.ih.Len() } -// Pop removes the highest priority item from th and also deletes it from -// th's lookup map. -// -// This should never be called by an external caller and is required to -// confirm to `heap.Interface`. -func (th *Heap[I, V]) Pop() any { - n := len(th.items) - item := th.items[n-1] - th.items[n-1] = nil // avoid memory leak - th.items = th.items[0 : n-1] - delete(th.lookup, item.ID) - return item -} - -// GetID returns the entry in th associated with [id], and a bool if [id] was +// Get returns the entry in th associated with [id], and a bool if [id] was // found in th. -func (th *Heap[I, V]) GetID(id ids.ID) (*Entry[I, V], bool) { - entry, ok := th.lookup[id] - return entry, ok +func (h *Heap[I, V]) Get(id ids.ID) (*Entry[I, V], bool) { + return h.ih.Get(id) } -// HasID returns whether [id] is found in th. -func (th *Heap[I, V]) HasID(id ids.ID) bool { - _, has := th.GetID(id) - return has +// Has returns whether [id] is found in th. +func (h *Heap[I, V]) Has(id ids.ID) bool { + return h.ih.Has(id) } // Items returns all items in the heap in sorted order. You should not modify // the response. -func (th *Heap[I, V]) Items() []*Entry[I, V] { - return th.items +func (h *Heap[I, V]) Items() []*Entry[I, V] { + return h.Items() } -// Add can be called by external users instead of using `containers.heap`, +// Push can be called by external users instead of using `containers.heap`, // which makes using this heap less error-prone. -// -// Add is used interchangeably with "Push". -func (th *Heap[I, V]) Add(e *Entry[I, V]) { - heap.Push(th, e) +func (h *Heap[I, V]) Push(e *Entry[I, V]) { + heap.Push(h.ih, e) } -// Remove can be called by external users to remove an object from the heap at +// Pop can be called by external users to remove an object from the heap at // a specific index instead of using `containers.heap`, // which makes using this heap less error-prone. -// -// Remove is used interchangeably with "Pop". -func (th *Heap[I, V]) Remove() *Entry[I, V] { - if len(th.items) == 0 { +func (h *Heap[I, V]) Pop() *Entry[I, V] { + if len(h.ih.items) == 0 { return nil } - return heap.Pop(th).(*Entry[I, V]) + return heap.Pop(h.ih).(*Entry[I, V]) } -// RemoveByIndex can be called by external users to remove an object from the heap at +// Remove can be called by external users to remove an object from the heap at // a specific index instead of using `containers.heap`, // which makes using this heap less error-prone. -// -// RemoveByIndex is used interchangeably with "Remove". -func (th *Heap[I, V]) RemoveByIndex(index int) *Entry[I, V] { - if index >= len(th.items) { +func (h *Heap[I, V]) Remove(index int) *Entry[I, V] { + if index >= len(h.ih.items) { return nil } - return heap.Remove(th, index).(*Entry[I, V]) + return heap.Remove(h.ih, index).(*Entry[I, V]) } // First returns the first item in the heap. This is the smallest item in // a minHeap and the largest item in a maxHeap. // // If no items are in the heap, it will return nil. -func (th *Heap[I, V]) First() *Entry[I, V] { - if len(th.items) == 0 { +func (h *Heap[I, V]) First() *Entry[I, V] { + if len(h.ih.items) == 0 { return nil } - return th.items[0] + return h.ih.items[0] } diff --git a/heap/heap_test.go b/heap/heap_test.go index 594bf3484d..6cb3dc71f9 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -44,24 +44,24 @@ func TestUnit64HeapPushPopMin(t *testing.T) { Val: mempoolItem3.value, Index: minHeap.Len(), } - minHeap.Add(med) - minHeap.Add(low) - minHeap.Add(high) + minHeap.Push(med) + minHeap.Push(low) + minHeap.Push(high) // Added all three require.Equal(minHeap.Len(), 3, "Not pushed correctly.") // Check if added to lookup table - _, ok := minHeap.lookup[med.ID] + ok := minHeap.Has(med.ID) require.True(ok, "Item not found in lookup.") - _, ok = minHeap.lookup[low.ID] + ok = minHeap.Has(low.ID) require.True(ok, "Item not found in lookup.") - _, ok = minHeap.lookup[high.ID] + ok = minHeap.Has(high.ID) require.True(ok, "Item not found in lookup.") // Pop and check popped correctly. Order should be 2, 1, 3 - popped := minHeap.Remove() + popped := minHeap.Pop() require.Equal(low, popped, "Incorrect item removed.") - popped = minHeap.Remove() + popped = minHeap.Pop() require.Equal(med, popped, "Incorrect item removed.") - popped = minHeap.Remove() + popped = minHeap.Pop() require.Equal(high, popped, "Incorrect item removed.") } @@ -95,24 +95,24 @@ func TestUnit64HeapPushPopMax(t *testing.T) { Val: mempoolItem3.value, Index: maxHeap.Len(), } - maxHeap.Add(med) - maxHeap.Add(low) - maxHeap.Add(high) + maxHeap.Push(med) + maxHeap.Push(low) + maxHeap.Push(high) // Added all three require.Equal(maxHeap.Len(), 3, "Not pushed correctly.") // Check if added to lookup table - _, ok := maxHeap.lookup[med.ID] + ok := maxHeap.Has(med.ID) require.True(ok, "Item not found in lookup.") - _, ok = maxHeap.lookup[low.ID] + ok = maxHeap.Has(low.ID) require.True(ok, "Item not found in lookup.") - _, ok = maxHeap.lookup[high.ID] + ok = maxHeap.Has(high.ID) require.True(ok, "Item not found in lookup.") // Pop and check popped correctly. Order should be 2, 1, 3 - popped := maxHeap.Remove() + popped := maxHeap.Pop() require.Equal(high, popped, "Incorrect item removed.") - popped = maxHeap.Remove() + popped = maxHeap.Pop() require.Equal(med, popped, "Incorrect item removed.") - popped = maxHeap.Remove() + popped = maxHeap.Pop() require.Equal(low, popped, "Incorrect item removed.") } @@ -128,13 +128,13 @@ func TestUnit64HeapPushExists(t *testing.T) { Val: mempoolItem.value, Index: minHeap.Len(), } - minHeap.Add(entry) + minHeap.Push(entry) // Pushed correctly require.Equal(minHeap.Len(), 1, "Not pushed correctly.") // Check if added to lookup table - _, ok := minHeap.lookup[entry.ID] + ok := minHeap.Has(entry.ID) require.True(ok, "Item not found in lookup.") - minHeap.Add(entry) + minHeap.Push(entry) // Only 1 item require.Equal(minHeap.Len(), 1, "Not pushed correctly.") } @@ -152,12 +152,12 @@ func TestUnit64HeapGetID(t *testing.T) { Val: mempoolItem.value, Index: minHeap.Len(), } - _, ok := minHeap.GetID(mempoolItem.id) + _, ok := minHeap.Get(mempoolItem.id) require.False(ok, "Entry returned before pushing.") - minHeap.Add(entry) + minHeap.Push(entry) // Pushed correctly require.Equal(minHeap.Len(), 1, "Not pushed correctly.") - entryReturned, ok := minHeap.GetID(mempoolItem.id) + entryReturned, ok := minHeap.Get(mempoolItem.id) require.True(ok, "Entry not returned.") require.Equal(entry, entryReturned, "Returned incorrect entry") } @@ -173,11 +173,11 @@ func TestUnit64HeapHasID(t *testing.T) { Val: mempoolItem.value, Index: minHeap.Len(), } - ok := minHeap.HasID(mempoolItem.id) + ok := minHeap.Has(mempoolItem.id) require.False(ok, "Entry has ID before pushing.") - minHeap.Add(entry) + minHeap.Push(entry) // Pushed correctly require.Equal(minHeap.Len(), 1, "Not pushed correctly.") - ok = minHeap.HasID(mempoolItem.id) + ok = minHeap.Has(mempoolItem.id) require.True(ok, "Entry was not found in heap.") } diff --git a/heap/inner_heap.go b/heap/inner_heap.go new file mode 100644 index 0000000000..35a893ab95 --- /dev/null +++ b/heap/inner_heap.go @@ -0,0 +1,105 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package heap + +import ( + "container/heap" + "fmt" + + "github.com/ava-labs/avalanchego/ids" + "golang.org/x/exp/constraints" +) + +var _ heap.Interface = (*innerHeap[any, uint64])(nil) + +type Entry[I any, V constraints.Ordered] struct { + ID ids.ID // id of entry + Item I // associated item + Val V // Value to be prioritized + + Index int // Index of the entry in heap +} + +type innerHeap[I any, V constraints.Ordered] struct { + isMinHeap bool // true for Min-Heap, false for Max-Heap + items []*Entry[I, V] // items in this heap + lookup map[ids.ID]*Entry[I, V] // ids in the heap mapping to an entry +} + +func newInnerHeap[I any, V constraints.Ordered](items int, isMinHeap bool) *innerHeap[I, V] { + return &innerHeap[I, V]{ + isMinHeap: isMinHeap, + + items: make([]*Entry[I, V], 0, items), + lookup: make(map[ids.ID]*Entry[I, V], items), + } +} + +// Len returns the number of items in ih. +func (ih *innerHeap[I, V]) Len() int { return len(ih.items) } + +// Less compares the priority of [i] and [j] based on th.isMinHeap. +// +// This should never be called by an external caller and is required to +// confirm to `heap.Interface`. +func (ih *innerHeap[I, V]) Less(i, j int) bool { + if ih.isMinHeap { + return ih.items[i].Val < ih.items[j].Val + } + return ih.items[i].Val > ih.items[j].Val +} + +// Swap swaps the [i]th and [j]th element in th. +// +// This should never be called by an external caller and is required to +// confirm to `heap.Interface`. +func (ih *innerHeap[I, V]) Swap(i, j int) { + ih.items[i], ih.items[j] = ih.items[j], ih.items[i] + ih.items[i].Index = i + ih.items[j].Index = j +} + +// Push adds an *Entry interface to th. If [x.id] is already in +// th, returns. +// +// This should never be called by an external caller and is required to +// confirm to `heap.Interface`. +func (ih *innerHeap[I, V]) Push(x any) { + entry, ok := x.(*Entry[I, V]) + if !ok { + panic(fmt.Errorf("unexpected %T, expected *Uint64Entry", x)) + } + if ih.Has(entry.ID) { + return + } + ih.items = append(ih.items, entry) + ih.lookup[entry.ID] = entry +} + +// Pop removes the highest priority item from th and also deletes it from +// th's lookup map. +// +// This should never be called by an external caller and is required to +// confirm to `heap.Interface`. +func (ih *innerHeap[I, V]) Pop() any { + n := len(ih.items) + item := ih.items[n-1] + ih.items[n-1] = nil // avoid memory leak + ih.items = ih.items[0 : n-1] + delete(ih.lookup, item.ID) + return item +} + +// Get returns the entry in th associated with [id], and a bool if [id] was +// found in th. +func (ih *innerHeap[I, V]) Get(id ids.ID) (*Entry[I, V], bool) { + entry, ok := ih.lookup[id] + return entry, ok +} + +// Has returns whether [id] is found in th. +func (ih *innerHeap[I, V]) Has(id ids.ID) bool { + _, has := ih.Get(id) + return has +} diff --git a/mempool/sorted_mempool.go b/mempool/sorted_mempool.go index e834d4ae22..e5297126af 100644 --- a/mempool/sorted_mempool.go +++ b/mempool/sorted_mempool.go @@ -45,13 +45,13 @@ func (sm *SortedMempool[T]) Add(item T) { itemID := item.ID() poolLen := sm.maxHeap.Len() val := sm.GetValue(item) - sm.maxHeap.Add(&heap.Entry[T, uint64]{ + sm.maxHeap.Push(&heap.Entry[T, uint64]{ ID: itemID, Val: val, Item: item, Index: poolLen, }) - sm.minHeap.Add(&heap.Entry[T, uint64]{ + sm.minHeap.Push(&heap.Entry[T, uint64]{ ID: itemID, Val: val, Item: item, @@ -61,18 +61,18 @@ func (sm *SortedMempool[T]) Add(item T) { // Remove removes [id] from sm. If the id does not exist, Remove returns. func (sm *SortedMempool[T]) Remove(id ids.ID) { - maxEntry, ok := sm.maxHeap.GetID(id) // O(1) + maxEntry, ok := sm.maxHeap.Get(id) // O(1) if !ok { return } - sm.maxHeap.RemoveByIndex(maxEntry.Index) // O(log N) - minEntry, ok := sm.minHeap.GetID(id) + sm.maxHeap.Remove(maxEntry.Index) // O(log N) + minEntry, ok := sm.minHeap.Get(id) if !ok { // This should never happen, as that would mean the heaps are out of // sync. return } - sm.minHeap.RemoveByIndex(minEntry.Index) // O(log N) + sm.minHeap.Remove(minEntry.Index) // O(log N) } // SetMinVal removes all elements in sm with a value less than [val]. Returns @@ -136,7 +136,7 @@ func (sm *SortedMempool[T]) PopMax() (T, bool) { // Has returns if [item] is in sm. func (sm *SortedMempool[T]) Has(item ids.ID) bool { - return sm.minHeap.HasID(item) + return sm.minHeap.Has(item) } // Len returns the number of elements in sm. diff --git a/mempool/sorted_mempool_test.go b/mempool/sorted_mempool_test.go index 93b01fd67e..f962cbe90f 100644 --- a/mempool/sorted_mempool_test.go +++ b/mempool/sorted_mempool_test.go @@ -62,8 +62,8 @@ func TestSortedMempoolAdd(t *testing.T) { sortedMempool.Add(mempoolItem) require.Equal(sortedMempool.minHeap.Len(), 1, "MaxHeap not pushed correctly") require.Equal(sortedMempool.maxHeap.Len(), 1, "MaxHeap not pushed correctly") - require.True(sortedMempool.minHeap.HasID(mempoolItem.ID()), "MinHeap does not have ID") - require.True(sortedMempool.maxHeap.HasID(mempoolItem.ID()), "MaxHeap does not have ID") + require.True(sortedMempool.minHeap.Has(mempoolItem.ID()), "MinHeap does not have ID") + require.True(sortedMempool.maxHeap.Has(mempoolItem.ID()), "MaxHeap does not have ID") } func TestSortedMempoolRemove(t *testing.T) { @@ -75,14 +75,14 @@ func TestSortedMempoolRemove(t *testing.T) { sortedMempool.Add(mempoolItem) require.Equal(sortedMempool.minHeap.Len(), 1, "MaxHeap not pushed correctly") require.Equal(sortedMempool.maxHeap.Len(), 1, "MaxHeap not pushed correctly") - require.True(sortedMempool.minHeap.HasID(mempoolItem.ID()), "MinHeap does not have ID") - require.True(sortedMempool.maxHeap.HasID(mempoolItem.ID()), "MaxHeap does not have ID") + require.True(sortedMempool.minHeap.Has(mempoolItem.ID()), "MinHeap does not have ID") + require.True(sortedMempool.maxHeap.Has(mempoolItem.ID()), "MaxHeap does not have ID") // Remove sortedMempool.Remove(mempoolItem.ID()) require.Equal(sortedMempool.minHeap.Len(), 0, "MaxHeap not removed.") require.Equal(sortedMempool.maxHeap.Len(), 0, "MaxHeap not removed.") - require.False(sortedMempool.minHeap.HasID(mempoolItem.ID()), "MinHeap still has ID") - require.False(sortedMempool.maxHeap.HasID(mempoolItem.ID()), "MaxHeap still has ID") + require.False(sortedMempool.minHeap.Has(mempoolItem.ID()), "MinHeap still has ID") + require.False(sortedMempool.maxHeap.Has(mempoolItem.ID()), "MaxHeap still has ID") } func TestSortedMempoolRemoveEmpty(t *testing.T) { From e949a8c6194845919be29cc294f425f41b42023c Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Sat, 25 Feb 2023 14:39:16 -0800 Subject: [PATCH 8/8] fix nit --- heap/heap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heap/heap.go b/heap/heap.go index cb46ba9db2..8eefe360ab 100644 --- a/heap/heap.go +++ b/heap/heap.go @@ -40,7 +40,7 @@ func (h *Heap[I, V]) Has(id ids.ID) bool { // Items returns all items in the heap in sorted order. You should not modify // the response. func (h *Heap[I, V]) Items() []*Entry[I, V] { - return h.Items() + return h.ih.items } // Push can be called by external users instead of using `containers.heap`,