diff --git a/ledger/lruaccts.go b/ledger/lruaccts.go
index 9351604de2..7d3b601454 100644
--- a/ledger/lruaccts.go
+++ b/ledger/lruaccts.go
@@ -20,6 +20,7 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/ledger/store/trackerdb"
"github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/util"
)
// lruAccounts provides a storage class for the most recently used accounts data.
@@ -28,10 +29,10 @@ import (
type lruAccounts struct {
// accountsList contain the list of persistedAccountData, where the front ones are the most "fresh"
// and the ones on the back are the oldest.
- accountsList *persistedAccountDataList
+ accountsList *util.List[*trackerdb.PersistedAccountData]
// accounts provides fast access to the various elements in the list by using the account address
// if lruAccounts is set with pendingWrites 0, then accounts is nil
- accounts map[basics.Address]*persistedAccountDataListNode
+ accounts map[basics.Address]*util.ListNode[*trackerdb.PersistedAccountData]
// pendingAccounts are used as a way to avoid taking a write-lock. When the caller needs to "materialize" these,
// it would call flushPendingWrites and these would be merged into the accounts/accountsList
// if lruAccounts is set with pendingWrites 0, then pendingAccounts is nil
@@ -50,8 +51,8 @@ type lruAccounts struct {
// thread locking semantics : write lock
func (m *lruAccounts) init(log logging.Logger, pendingWrites int, pendingWritesWarnThreshold int) {
if pendingWrites > 0 {
- m.accountsList = newPersistedAccountList().allocateFreeNodes(pendingWrites)
- m.accounts = make(map[basics.Address]*persistedAccountDataListNode, pendingWrites)
+ m.accountsList = util.NewList[*trackerdb.PersistedAccountData]().AllocateFreeNodes(pendingWrites)
+ m.accounts = make(map[basics.Address]*util.ListNode[*trackerdb.PersistedAccountData], pendingWrites)
m.pendingAccounts = make(chan trackerdb.PersistedAccountData, pendingWrites)
m.notFound = make(map[basics.Address]struct{}, pendingWrites)
m.pendingNotFound = make(chan basics.Address, pendingWrites)
@@ -141,10 +142,10 @@ func (m *lruAccounts) write(acctData trackerdb.PersistedAccountData) {
// we update with a newer version.
el.Value = &acctData
}
- m.accountsList.moveToFront(el)
+ m.accountsList.MoveToFront(el)
} else {
// new entry.
- m.accounts[acctData.Addr] = m.accountsList.pushFront(&acctData)
+ m.accounts[acctData.Addr] = m.accountsList.PushFront(&acctData)
}
}
@@ -159,9 +160,9 @@ func (m *lruAccounts) prune(newSize int) (removed int) {
if len(m.accounts) <= newSize {
break
}
- back := m.accountsList.back()
+ back := m.accountsList.Back()
delete(m.accounts, back.Value.Addr)
- m.accountsList.remove(back)
+ m.accountsList.Remove(back)
removed++
}
diff --git a/ledger/lrukv.go b/ledger/lrukv.go
index 8c407a9fc5..ef283faec6 100644
--- a/ledger/lrukv.go
+++ b/ledger/lrukv.go
@@ -19,6 +19,7 @@ package ledger
import (
"github.com/algorand/go-algorand/ledger/store/trackerdb"
"github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/util"
)
//msgp:ignore cachedKVData
@@ -35,11 +36,11 @@ type cachedKVData struct {
type lruKV struct {
// kvList contain the list of persistedKVData, where the front ones are the most "fresh"
// and the ones on the back are the oldest.
- kvList *persistedKVDataList
+ kvList *util.List[*cachedKVData]
// kvs provides fast access to the various elements in the list by using the key
// if lruKV is set with pendingWrites 0, then kvs is nil
- kvs map[string]*persistedKVDataListNode
+ kvs map[string]*util.ListNode[*cachedKVData]
// pendingKVs are used as a way to avoid taking a write-lock. When the caller needs to "materialize" these,
// it would call flushPendingWrites and these would be merged into the kvs/kvList
@@ -57,8 +58,8 @@ type lruKV struct {
// thread locking semantics : write lock
func (m *lruKV) init(log logging.Logger, pendingWrites int, pendingWritesWarnThreshold int) {
if pendingWrites > 0 {
- m.kvList = newPersistedKVList().allocateFreeNodes(pendingWrites)
- m.kvs = make(map[string]*persistedKVDataListNode, pendingWrites)
+ m.kvList = util.NewList[*cachedKVData]().AllocateFreeNodes(pendingWrites)
+ m.kvs = make(map[string]*util.ListNode[*cachedKVData], pendingWrites)
m.pendingKVs = make(chan cachedKVData, pendingWrites)
}
m.log = log
@@ -116,10 +117,10 @@ func (m *lruKV) write(kvData trackerdb.PersistedKVData, key string) {
// we update with a newer version.
el.Value = &cachedKVData{PersistedKVData: kvData, key: key}
}
- m.kvList.moveToFront(el)
+ m.kvList.MoveToFront(el)
} else {
// new entry.
- m.kvs[key] = m.kvList.pushFront(&cachedKVData{PersistedKVData: kvData, key: key})
+ m.kvs[key] = m.kvList.PushFront(&cachedKVData{PersistedKVData: kvData, key: key})
}
}
@@ -134,9 +135,9 @@ func (m *lruKV) prune(newSize int) (removed int) {
if len(m.kvs) <= newSize {
break
}
- back := m.kvList.back()
+ back := m.kvList.Back()
delete(m.kvs, back.Value.key)
- m.kvList.remove(back)
+ m.kvList.Remove(back)
removed++
}
return
diff --git a/ledger/lruonlineaccts.go b/ledger/lruonlineaccts.go
index 40f08917b2..297fb03328 100644
--- a/ledger/lruonlineaccts.go
+++ b/ledger/lruonlineaccts.go
@@ -20,6 +20,7 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/ledger/store/trackerdb"
"github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/util"
)
// lruAccounts provides a storage class for the most recently used accounts data.
@@ -28,10 +29,10 @@ import (
type lruOnlineAccounts struct {
// accountsList contain the list of persistedAccountData, where the front ones are the most "fresh"
// and the ones on the back are the oldest.
- accountsList *persistedOnlineAccountDataList
+ accountsList *util.List[*trackerdb.PersistedOnlineAccountData]
// accounts provides fast access to the various elements in the list by using the account address
// if lruOnlineAccounts is set with pendingWrites 0, then accounts is nil
- accounts map[basics.Address]*persistedOnlineAccountDataListNode
+ accounts map[basics.Address]*util.ListNode[*trackerdb.PersistedOnlineAccountData]
// pendingAccounts are used as a way to avoid taking a write-lock. When the caller needs to "materialize" these,
// it would call flushPendingWrites and these would be merged into the accounts/accountsList
// if lruOnlineAccounts is set with pendingWrites 0, then pendingAccounts is nil
@@ -46,8 +47,8 @@ type lruOnlineAccounts struct {
// thread locking semantics : write lock
func (m *lruOnlineAccounts) init(log logging.Logger, pendingWrites int, pendingWritesWarnThreshold int) {
if pendingWrites > 0 {
- m.accountsList = newPersistedOnlineAccountList().allocateFreeNodes(pendingWrites)
- m.accounts = make(map[basics.Address]*persistedOnlineAccountDataListNode, pendingWrites)
+ m.accountsList = util.NewList[*trackerdb.PersistedOnlineAccountData]().AllocateFreeNodes(pendingWrites)
+ m.accounts = make(map[basics.Address]*util.ListNode[*trackerdb.PersistedOnlineAccountData], pendingWrites)
m.pendingAccounts = make(chan trackerdb.PersistedOnlineAccountData, pendingWrites)
}
m.log = log
@@ -105,10 +106,10 @@ func (m *lruOnlineAccounts) write(acctData trackerdb.PersistedOnlineAccountData)
// we update with a newer version.
el.Value = &acctData
}
- m.accountsList.moveToFront(el)
+ m.accountsList.MoveToFront(el)
} else {
// new entry.
- m.accounts[acctData.Addr] = m.accountsList.pushFront(&acctData)
+ m.accounts[acctData.Addr] = m.accountsList.PushFront(&acctData)
}
}
@@ -123,9 +124,9 @@ func (m *lruOnlineAccounts) prune(newSize int) (removed int) {
if len(m.accounts) <= newSize {
break
}
- back := m.accountsList.back()
+ back := m.accountsList.Back()
delete(m.accounts, back.Value.Addr)
- m.accountsList.remove(back)
+ m.accountsList.Remove(back)
removed++
}
return
diff --git a/ledger/lruresources.go b/ledger/lruresources.go
index f0a536350e..6a0367b7af 100644
--- a/ledger/lruresources.go
+++ b/ledger/lruresources.go
@@ -20,6 +20,7 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/ledger/store/trackerdb"
"github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/util"
)
//msgp:ignore cachedResourceData
@@ -35,11 +36,11 @@ type cachedResourceData struct {
type lruResources struct {
// resourcesList contain the list of persistedResourceData, where the front ones are the most "fresh"
// and the ones on the back are the oldest.
- resourcesList *persistedResourcesDataList
+ resourcesList *util.List[*cachedResourceData]
// resources provides fast access to the various elements in the list by using the account address
// if lruResources is set with pendingWrites 0, then resources is nil
- resources map[accountCreatable]*persistedResourcesDataListNode
+ resources map[accountCreatable]*util.ListNode[*cachedResourceData]
// pendingResources are used as a way to avoid taking a write-lock. When the caller needs to "materialize" these,
// it would call flushPendingWrites and these would be merged into the resources/resourcesList
@@ -61,8 +62,8 @@ type lruResources struct {
// thread locking semantics : write lock
func (m *lruResources) init(log logging.Logger, pendingWrites int, pendingWritesWarnThreshold int) {
if pendingWrites > 0 {
- m.resourcesList = newPersistedResourcesList().allocateFreeNodes(pendingWrites)
- m.resources = make(map[accountCreatable]*persistedResourcesDataListNode, pendingWrites)
+ m.resourcesList = util.NewList[*cachedResourceData]().AllocateFreeNodes(pendingWrites)
+ m.resources = make(map[accountCreatable]*util.ListNode[*cachedResourceData], pendingWrites)
m.pendingResources = make(chan cachedResourceData, pendingWrites)
m.notFound = make(map[accountCreatable]struct{}, pendingWrites)
m.pendingNotFound = make(chan accountCreatable, pendingWrites)
@@ -163,10 +164,10 @@ func (m *lruResources) write(resData trackerdb.PersistedResourcesData, addr basi
// we update with a newer version.
el.Value = &cachedResourceData{PersistedResourcesData: resData, address: addr}
}
- m.resourcesList.moveToFront(el)
+ m.resourcesList.MoveToFront(el)
} else {
// new entry.
- m.resources[accountCreatable{address: addr, index: resData.Aidx}] = m.resourcesList.pushFront(&cachedResourceData{PersistedResourcesData: resData, address: addr})
+ m.resources[accountCreatable{address: addr, index: resData.Aidx}] = m.resourcesList.PushFront(&cachedResourceData{PersistedResourcesData: resData, address: addr})
}
}
@@ -181,9 +182,9 @@ func (m *lruResources) prune(newSize int) (removed int) {
if len(m.resources) <= newSize {
break
}
- back := m.resourcesList.back()
+ back := m.resourcesList.Back()
delete(m.resources, accountCreatable{address: back.Value.address, index: back.Value.Aidx})
- m.resourcesList.remove(back)
+ m.resourcesList.Remove(back)
removed++
}
diff --git a/ledger/persistedaccts_list.go b/ledger/persistedaccts_list.go
deleted file mode 100644
index cd2a46b94a..0000000000
--- a/ledger/persistedaccts_list.go
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright (C) 2019-2023 Algorand, Inc.
-// This file is part of go-algorand
-//
-// go-algorand is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// go-algorand is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with go-algorand. If not, see .
-
-package ledger
-
-import "github.com/algorand/go-algorand/ledger/store/trackerdb"
-
-// persistedAccountDataList represents a doubly linked list.
-// must initiate with newPersistedAccountList.
-type persistedAccountDataList struct {
- root persistedAccountDataListNode // sentinel list element, only &root, root.prev, and root.next are used
- freeList *persistedAccountDataListNode // preallocated nodes location
-}
-
-type persistedAccountDataListNode struct {
- // Next and previous pointers in the doubly-linked list of elements.
- // To simplify the implementation, internally a list l is implemented
- // as a ring, such that &l.root is both the next element of the last
- // list element (l.Back()) and the previous element of the first list
- // element (l.Front()).
- next, prev *persistedAccountDataListNode
-
- Value *trackerdb.PersistedAccountData
-}
-
-func newPersistedAccountList() *persistedAccountDataList {
- l := new(persistedAccountDataList)
- l.root.next = &l.root
- l.root.prev = &l.root
- // used as a helper but does not store value
- l.freeList = new(persistedAccountDataListNode)
-
- return l
-}
-
-func (l *persistedAccountDataList) insertNodeToFreeList(otherNode *persistedAccountDataListNode) {
- otherNode.next = l.freeList.next
- otherNode.prev = nil
- otherNode.Value = nil
-
- l.freeList.next = otherNode
-}
-
-func (l *persistedAccountDataList) getNewNode() *persistedAccountDataListNode {
- if l.freeList.next == nil {
- return new(persistedAccountDataListNode)
- }
- newNode := l.freeList.next
- l.freeList.next = newNode.next
-
- return newNode
-}
-
-func (l *persistedAccountDataList) allocateFreeNodes(numAllocs int) *persistedAccountDataList {
- if l.freeList == nil {
- return l
- }
- for i := 0; i < numAllocs; i++ {
- l.insertNodeToFreeList(new(persistedAccountDataListNode))
- }
-
- return l
-}
-
-// Back returns the last element of list l or nil if the list is empty.
-func (l *persistedAccountDataList) back() *persistedAccountDataListNode {
- isEmpty := func(list *persistedAccountDataList) bool {
- // assumes we are inserting correctly to the list - using pushFront.
- return list.root.next == &list.root
- }
-
- if isEmpty(l) {
- return nil
- }
- return l.root.prev
-}
-
-// remove removes e from l if e is an element of list l.
-// It returns the element value e.Value.
-// The element must not be nil.
-func (l *persistedAccountDataList) remove(e *persistedAccountDataListNode) {
- e.prev.next = e.next
- e.next.prev = e.prev
- e.next = nil // avoid memory leaks
- e.prev = nil // avoid memory leaks
-
- l.insertNodeToFreeList(e)
-}
-
-// pushFront inserts a new element e with value v at the front of list l and returns e.
-func (l *persistedAccountDataList) pushFront(v *trackerdb.PersistedAccountData) *persistedAccountDataListNode {
- newNode := l.getNewNode()
- newNode.Value = v
- return l.insertValue(newNode, &l.root)
-}
-
-// insertValue inserts e after at, increments l.len, and returns e.
-func (l *persistedAccountDataList) insertValue(newNode *persistedAccountDataListNode, at *persistedAccountDataListNode) *persistedAccountDataListNode {
- n := at.next
- at.next = newNode
- newNode.prev = at
- newNode.next = n
- n.prev = newNode
-
- return newNode
-}
-
-// moveToFront moves element e to the front of list l.
-// If e is not an element of l, the list is not modified.
-// The element must not be nil.
-func (l *persistedAccountDataList) moveToFront(e *persistedAccountDataListNode) {
- if l.root.next == e {
- return
- }
- l.move(e, &l.root)
-}
-
-// move moves e to next to at and returns e.
-func (l *persistedAccountDataList) move(e, at *persistedAccountDataListNode) *persistedAccountDataListNode {
- if e == at {
- return e
- }
- e.prev.next = e.next
- e.next.prev = e.prev
-
- n := at.next
- at.next = e
- e.prev = at
- e.next = n
- n.prev = e
-
- return e
-}
diff --git a/ledger/persistedaccts_list_test.go b/ledger/persistedaccts_list_test.go
deleted file mode 100644
index d4c8599444..0000000000
--- a/ledger/persistedaccts_list_test.go
+++ /dev/null
@@ -1,255 +0,0 @@
-// Copyright (C) 2019-2023 Algorand, Inc.
-// This file is part of go-algorand
-//
-// go-algorand is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// go-algorand is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with go-algorand. If not, see .
-
-package ledger
-
-import (
- "testing"
-
- "github.com/algorand/go-algorand/data/basics"
- "github.com/algorand/go-algorand/ledger/store/trackerdb"
- "github.com/algorand/go-algorand/test/partitiontest"
-)
-
-type dataListNode interface {
- getNext() dataListNode
- getPrev() dataListNode
-}
-
-type dataList interface {
- getRoot() dataListNode
-}
-
-func (l *persistedAccountDataList) getRoot() dataListNode {
- return &l.root
-}
-
-func (l *persistedAccountDataListNode) getNext() dataListNode {
- // get rid of returning nil wrapped into an interface to let i = x.getNext(); i != nil work.
- if l.next == nil {
- return nil
- }
- return l.next
-}
-
-func (l *persistedAccountDataListNode) getPrev() dataListNode {
- if l.prev == nil {
- return nil
- }
- return l.prev
-}
-
-func checkLen(list dataList) int {
- if list.getRoot().getNext() == list.getRoot() {
- return 0
- }
-
- return countListSize(list.getRoot())
-}
-
-func countListSize(head dataListNode) (counter int) {
- for i := head.getNext(); i != head && i != nil; i = i.getNext() {
- counter++
- }
- return counter
-}
-
-func checkListLen(t *testing.T, l dataList, len int) bool {
- if n := checkLen(l); n != len {
- t.Errorf("l.Len() = %d, want %d", n, len)
- return true
- }
- return false
-}
-
-func TestRemoveFromListAD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedAccountList()
- e1 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{1}})
- e2 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{2}})
- e3 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{3}})
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e3, e2, e1})
-
- l.remove(e2)
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e3, e1})
- l.remove(e3)
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e1})
-}
-
-func TestAddingNewNodeWithAllocatedFreeListAD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedAccountList().allocateFreeNodes(10)
- checkListPointersAD(t, l, []*persistedAccountDataListNode{})
- if countListSize(l.freeList) != 10 {
- t.Errorf("free list did not allocate nodes")
- return
- }
- // test elements
- e1 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{1}})
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e1})
-
- if countListSize(l.freeList) != 9 {
- t.Errorf("free list did not provide a node on new list entry")
- return
- }
-}
-
-func checkListPointers(t *testing.T, l dataList, es []dataListNode) {
- root := l.getRoot()
-
- if failed := checkListLen(t, l, len(es)); failed {
- return
- }
-
- if failed := zeroListInspection(t, l, len(es), root); failed {
- return
- }
-
- pointerInspection(t, es, root)
-}
-
-// inspect that the list seems like the array
-func checkListPointersAD(t *testing.T, l *persistedAccountDataList, es []*persistedAccountDataListNode) {
- es2 := make([]dataListNode, len(es))
- for i, el := range es {
- es2[i] = el
- }
-
- checkListPointers(t, l, es2)
-}
-
-func zeroListInspection(t *testing.T, l dataList, len int, root dataListNode) bool {
- // zero length lists must be the zero value or properly initialized (sentinel circle)
- if len == 0 {
- if l.getRoot().getNext() != nil && l.getRoot().getNext() != root || l.getRoot().getPrev() != nil && l.getRoot().getPrev() != root {
- t.Errorf("l.root.next = %p, l.root.prev = %p; both should both be nil or %p", l.getRoot().getNext(), l.getRoot().getPrev(), root)
- }
- return true
- }
- return false
-}
-
-func pointerInspection(t *testing.T, es []dataListNode, root dataListNode) {
- // check internal and external prev/next connections
- for i, e := range es {
- prev := root
- if i > 0 {
- prev = es[i-1]
- }
- if p := e.getPrev(); p != prev {
- t.Errorf("elt[%d](%p).prev = %p, want %p", i, e, p, prev)
- }
-
- next := root
- if i < len(es)-1 {
- next = es[i+1]
- }
- if n := e.getNext(); n != next {
- t.Errorf("elt[%d](%p).next = %p, want %p", i, e, n, next)
- }
- }
-}
-
-func TestMultielementListPositioningAD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedAccountList()
- checkListPointersAD(t, l, []*persistedAccountDataListNode{})
- // test elements
- e2 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{2}})
- e1 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{1}})
- e3 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{3}})
- e4 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{4}})
- e5 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{5}})
-
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e5, e4, e3, e1, e2})
-
- l.move(e4, e1)
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e5, e3, e1, e4, e2})
-
- l.remove(e5)
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e3, e1, e4, e2})
-
- l.move(e1, e4) // swap in middle
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e3, e4, e1, e2})
-
- l.moveToFront(e4)
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e4, e3, e1, e2})
-
- l.remove(e2)
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e4, e3, e1})
-
- l.moveToFront(e3) // move from middle
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e3, e4, e1})
-
- l.moveToFront(e1) // move from end
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e1, e3, e4})
-
- l.moveToFront(e1) // no movement
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e1, e3, e4})
-
- e2 = l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{2}})
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e2, e1, e3, e4})
-
- l.remove(e3) // removing from middle
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e2, e1, e4})
-
- l.remove(e4) // removing from end
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e2, e1})
-
- l.move(e2, e1) // swapping between two elements
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e1, e2})
-
- l.remove(e1) // removing front
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e2})
-
- l.move(e2, l.back()) // swapping element with itself.
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e2})
-
- l.remove(e2) // remove last one
- checkListPointersAD(t, l, []*persistedAccountDataListNode{})
-}
-
-func TestSingleElementListPositioningAD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedAccountList()
- checkListPointersAD(t, l, []*persistedAccountDataListNode{})
- e := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{1}})
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e})
- l.moveToFront(e)
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e})
- l.remove(e)
- checkListPointersAD(t, l, []*persistedAccountDataListNode{})
-}
-
-func TestRemovedNodeShouldBeMovedToFreeListAD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedAccountList()
- e1 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{1}})
- e2 := l.pushFront(&trackerdb.PersistedAccountData{Addr: basics.Address{2}})
-
- checkListPointersAD(t, l, []*persistedAccountDataListNode{e2, e1})
-
- e := l.back()
- l.remove(e)
-
- for i := l.freeList.next; i != nil; i = i.next {
- if i == e {
- // stopping the tst with good results:
- return
- }
- }
- t.Error("expected the removed node to appear at the freelist")
-}
diff --git a/ledger/persistedkvs_test.go b/ledger/persistedkvs_test.go
deleted file mode 100644
index 5d9620d27b..0000000000
--- a/ledger/persistedkvs_test.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright (C) 2019-2023 Algorand, Inc.
-// This file is part of go-algorand
-//
-// go-algorand is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// go-algorand is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with go-algorand. If not, see .
-
-package ledger
-
-import (
- "testing"
-
- "github.com/algorand/go-algorand/test/partitiontest"
-)
-
-func (l *persistedKVDataList) getRoot() dataListNode {
- return &l.root
-}
-
-func (l *persistedKVDataListNode) getNext() dataListNode {
- // get rid of returning nil wrapped into an interface to let i = x.getNext(); i != nil work.
- if l.next == nil {
- return nil
- }
- return l.next
-}
-
-func (l *persistedKVDataListNode) getPrev() dataListNode {
- if l.prev == nil {
- return nil
- }
- return l.prev
-}
-
-// inspect that the list seems like the array
-func checkListPointersBD(t *testing.T, l *persistedKVDataList, es []*persistedKVDataListNode) {
- es2 := make([]dataListNode, len(es))
- for i, el := range es {
- es2[i] = el
- }
-
- checkListPointers(t, l, es2)
-}
-
-func TestRemoveFromListBD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedKVList()
- e1 := l.pushFront(&cachedKVData{key: "key1"})
- e2 := l.pushFront(&cachedKVData{key: "key2"})
- e3 := l.pushFront(&cachedKVData{key: "key3"})
- checkListPointersBD(t, l, []*persistedKVDataListNode{e3, e2, e1})
-
- l.remove(e2)
- checkListPointersBD(t, l, []*persistedKVDataListNode{e3, e1})
- l.remove(e3)
- checkListPointersBD(t, l, []*persistedKVDataListNode{e1})
-}
-
-func TestAddingNewNodeWithAllocatedFreeListBD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedKVList().allocateFreeNodes(10)
- checkListPointersBD(t, l, []*persistedKVDataListNode{})
- if countListSize(l.freeList) != 10 {
- t.Errorf("free list did not allocate nodes")
- return
- }
- // test elements
- e1 := l.pushFront(&cachedKVData{key: "key1"})
- checkListPointersBD(t, l, []*persistedKVDataListNode{e1})
-
- if countListSize(l.freeList) != 9 {
- t.Errorf("free list did not provide a node on new list entry")
- return
- }
-}
-
-func TestMultielementListPositioningBD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedKVList()
- checkListPointersBD(t, l, []*persistedKVDataListNode{})
- // test elements
- e2 := l.pushFront(&cachedKVData{key: "key1"})
- e1 := l.pushFront(&cachedKVData{key: "key2"})
- e3 := l.pushFront(&cachedKVData{key: "key3"})
- e4 := l.pushFront(&cachedKVData{key: "key4"})
- e5 := l.pushFront(&cachedKVData{key: "key5"})
-
- checkListPointersBD(t, l, []*persistedKVDataListNode{e5, e4, e3, e1, e2})
-
- l.move(e4, e1)
- checkListPointersBD(t, l, []*persistedKVDataListNode{e5, e3, e1, e4, e2})
-
- l.remove(e5)
- checkListPointersBD(t, l, []*persistedKVDataListNode{e3, e1, e4, e2})
-
- l.move(e1, e4) // swap in middle
- checkListPointersBD(t, l, []*persistedKVDataListNode{e3, e4, e1, e2})
-
- l.moveToFront(e4)
- checkListPointersBD(t, l, []*persistedKVDataListNode{e4, e3, e1, e2})
-
- l.remove(e2)
- checkListPointersBD(t, l, []*persistedKVDataListNode{e4, e3, e1})
-
- l.moveToFront(e3) // move from middle
- checkListPointersBD(t, l, []*persistedKVDataListNode{e3, e4, e1})
-
- l.moveToFront(e1) // move from end
- checkListPointersBD(t, l, []*persistedKVDataListNode{e1, e3, e4})
-
- l.moveToFront(e1) // no movement
- checkListPointersBD(t, l, []*persistedKVDataListNode{e1, e3, e4})
-
- e2 = l.pushFront(&cachedKVData{key: "key2"})
- checkListPointersBD(t, l, []*persistedKVDataListNode{e2, e1, e3, e4})
-
- l.remove(e3) // removing from middle
- checkListPointersBD(t, l, []*persistedKVDataListNode{e2, e1, e4})
-
- l.remove(e4) // removing from end
- checkListPointersBD(t, l, []*persistedKVDataListNode{e2, e1})
-
- l.move(e2, e1) // swapping between two elements
- checkListPointersBD(t, l, []*persistedKVDataListNode{e1, e2})
-
- l.remove(e1) // removing front
- checkListPointersBD(t, l, []*persistedKVDataListNode{e2})
-
- l.move(e2, l.back()) // swapping element with itself.
- checkListPointersBD(t, l, []*persistedKVDataListNode{e2})
-
- l.remove(e2) // remove last one
- checkListPointersBD(t, l, []*persistedKVDataListNode{})
-}
-
-func TestSingleElementListPositioningBD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedKVList()
- checkListPointersBD(t, l, []*persistedKVDataListNode{})
- e := l.pushFront(&cachedKVData{key: "key1"})
- checkListPointersBD(t, l, []*persistedKVDataListNode{e})
- l.moveToFront(e)
- checkListPointersBD(t, l, []*persistedKVDataListNode{e})
- l.remove(e)
- checkListPointersBD(t, l, []*persistedKVDataListNode{})
-}
-
-func TestRemovedNodeShouldBeMovedToFreeListBD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedKVList()
- e1 := l.pushFront(&cachedKVData{key: "key1"})
- e2 := l.pushFront(&cachedKVData{key: "key2"})
-
- checkListPointersBD(t, l, []*persistedKVDataListNode{e2, e1})
-
- e := l.back()
- l.remove(e)
-
- for i := l.freeList.next; i != nil; i = i.next {
- if i == e {
- // stopping the tst with good results:
- return
- }
- }
- t.Error("expected the removed node to appear at the freelist")
-}
diff --git a/ledger/persistedonlineaccts_list.go b/ledger/persistedonlineaccts_list.go
deleted file mode 100644
index 0f080e5916..0000000000
--- a/ledger/persistedonlineaccts_list.go
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright (C) 2019-2023 Algorand, Inc.
-// This file is part of go-algorand
-//
-// go-algorand is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// go-algorand is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with go-algorand. If not, see .
-
-package ledger
-
-import "github.com/algorand/go-algorand/ledger/store/trackerdb"
-
-// persistedOnlineAccountDataList represents a doubly linked list.
-// must initiate with newPersistedAccountList.
-type persistedOnlineAccountDataList struct {
- root persistedOnlineAccountDataListNode // sentinel list element, only &root, root.prev, and root.next are used
- freeList *persistedOnlineAccountDataListNode // preallocated nodes location
-}
-
-type persistedOnlineAccountDataListNode struct {
- // Next and previous pointers in the doubly-linked list of elements.
- // To simplify the implementation, internally a list l is implemented
- // as a ring, such that &l.root is both the next element of the last
- // list element (l.Back()) and the previous element of the first list
- // element (l.Front()).
- next, prev *persistedOnlineAccountDataListNode
-
- Value *trackerdb.PersistedOnlineAccountData
-}
-
-func newPersistedOnlineAccountList() *persistedOnlineAccountDataList {
- l := new(persistedOnlineAccountDataList)
- l.root.next = &l.root
- l.root.prev = &l.root
- // used as a helper but does not store value
- l.freeList = new(persistedOnlineAccountDataListNode)
-
- return l
-}
-
-func (l *persistedOnlineAccountDataList) insertNodeToFreeList(otherNode *persistedOnlineAccountDataListNode) {
- otherNode.next = l.freeList.next
- otherNode.prev = nil
- otherNode.Value = nil
-
- l.freeList.next = otherNode
-}
-
-func (l *persistedOnlineAccountDataList) getNewNode() *persistedOnlineAccountDataListNode {
- if l.freeList.next == nil {
- return new(persistedOnlineAccountDataListNode)
- }
- newNode := l.freeList.next
- l.freeList.next = newNode.next
-
- return newNode
-}
-
-func (l *persistedOnlineAccountDataList) allocateFreeNodes(numAllocs int) *persistedOnlineAccountDataList {
- if l.freeList == nil {
- return l
- }
- for i := 0; i < numAllocs; i++ {
- l.insertNodeToFreeList(new(persistedOnlineAccountDataListNode))
- }
-
- return l
-}
-
-// Back returns the last element of list l or nil if the list is empty.
-func (l *persistedOnlineAccountDataList) back() *persistedOnlineAccountDataListNode {
- isEmpty := func(list *persistedOnlineAccountDataList) bool {
- // assumes we are inserting correctly to the list - using pushFront.
- return list.root.next == &list.root
- }
-
- if isEmpty(l) {
- return nil
- }
- return l.root.prev
-}
-
-// remove removes e from l if e is an element of list l.
-// It returns the element value e.Value.
-// The element must not be nil.
-func (l *persistedOnlineAccountDataList) remove(e *persistedOnlineAccountDataListNode) {
- e.prev.next = e.next
- e.next.prev = e.prev
- e.next = nil // avoid memory leaks
- e.prev = nil // avoid memory leaks
-
- l.insertNodeToFreeList(e)
-}
-
-// pushFront inserts a new element e with value v at the front of list l and returns e.
-func (l *persistedOnlineAccountDataList) pushFront(v *trackerdb.PersistedOnlineAccountData) *persistedOnlineAccountDataListNode {
- newNode := l.getNewNode()
- newNode.Value = v
- return l.insertValue(newNode, &l.root)
-}
-
-// insertValue inserts e after at, increments l.len, and returns e.
-func (l *persistedOnlineAccountDataList) insertValue(newNode *persistedOnlineAccountDataListNode, at *persistedOnlineAccountDataListNode) *persistedOnlineAccountDataListNode {
- n := at.next
- at.next = newNode
- newNode.prev = at
- newNode.next = n
- n.prev = newNode
-
- return newNode
-}
-
-// moveToFront moves element e to the front of list l.
-// If e is not an element of l, the list is not modified.
-// The element must not be nil.
-func (l *persistedOnlineAccountDataList) moveToFront(e *persistedOnlineAccountDataListNode) {
- if l.root.next == e {
- return
- }
- l.move(e, &l.root)
-}
-
-// move moves e to next to at and returns e.
-func (l *persistedOnlineAccountDataList) move(e, at *persistedOnlineAccountDataListNode) *persistedOnlineAccountDataListNode {
- if e == at {
- return e
- }
- e.prev.next = e.next
- e.next.prev = e.prev
-
- n := at.next
- at.next = e
- e.prev = at
- e.next = n
- n.prev = e
-
- return e
-}
diff --git a/ledger/persistedonlineaccts_list_test.go b/ledger/persistedonlineaccts_list_test.go
deleted file mode 100644
index afbfbe9a8a..0000000000
--- a/ledger/persistedonlineaccts_list_test.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (C) 2019-2023 Algorand, Inc.
-// This file is part of go-algorand
-//
-// go-algorand is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// go-algorand is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with go-algorand. If not, see .
-
-package ledger
-
-import (
- "testing"
-
- "github.com/algorand/go-algorand/data/basics"
- "github.com/algorand/go-algorand/ledger/store/trackerdb"
- "github.com/algorand/go-algorand/test/partitiontest"
-)
-
-func (l *persistedOnlineAccountDataList) getRoot() dataListNode {
- return &l.root
-}
-
-func (l *persistedOnlineAccountDataListNode) getNext() dataListNode {
- // get rid of returning nil wrapped into an interface to let i = x.getNext(); i != nil work.
- if l.next == nil {
- return nil
- }
- return l.next
-}
-
-func (l *persistedOnlineAccountDataListNode) getPrev() dataListNode {
- if l.prev == nil {
- return nil
- }
- return l.prev
-}
-
-func TestRemoveFromListOAD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedOnlineAccountList()
- e1 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{1}})
- e2 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{2}})
- e3 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{3}})
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e3, e2, e1})
-
- l.remove(e2)
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e3, e1})
- l.remove(e3)
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e1})
-}
-
-func TestAddingNewNodeWithAllocatedFreeListOAD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedOnlineAccountList().allocateFreeNodes(10)
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{})
- if countListSize(l.freeList) != 10 {
- t.Errorf("free list did not allocate nodes")
- return
- }
- // test elements
- e1 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{1}})
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e1})
-
- if countListSize(l.freeList) != 9 {
- t.Errorf("free list did not provide a node on new list entry")
- return
- }
-}
-
-// inspect that the list seems like the array
-func checkListPointersOAD(t *testing.T, l *persistedOnlineAccountDataList, es []*persistedOnlineAccountDataListNode) {
- es2 := make([]dataListNode, len(es))
- for i, el := range es {
- es2[i] = el
- }
-
- checkListPointers(t, l, es2)
-}
-
-func TestMultielementListPositioningOAD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedOnlineAccountList()
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{})
- // test elements
- e2 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{2}})
- e1 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{1}})
- e3 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{3}})
- e4 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{4}})
- e5 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{5}})
-
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e5, e4, e3, e1, e2})
-
- l.move(e4, e1)
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e5, e3, e1, e4, e2})
-
- l.remove(e5)
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e3, e1, e4, e2})
-
- l.move(e1, e4) // swap in middle
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e3, e4, e1, e2})
-
- l.moveToFront(e4)
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e4, e3, e1, e2})
-
- l.remove(e2)
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e4, e3, e1})
-
- l.moveToFront(e3) // move from middle
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e3, e4, e1})
-
- l.moveToFront(e1) // move from end
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e1, e3, e4})
-
- l.moveToFront(e1) // no movement
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e1, e3, e4})
-
- e2 = l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{2}})
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e2, e1, e3, e4})
-
- l.remove(e3) // removing from middle
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e2, e1, e4})
-
- l.remove(e4) // removing from end
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e2, e1})
-
- l.move(e2, e1) // swapping between two elements
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e1, e2})
-
- l.remove(e1) // removing front
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e2})
-
- l.move(e2, l.back()) // swapping element with itself.
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e2})
-
- l.remove(e2) // remove last one
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{})
-}
-
-func TestSingleElementListPositioningOD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedOnlineAccountList()
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{})
- e := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{1}})
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e})
- l.moveToFront(e)
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e})
- l.remove(e)
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{})
-}
-
-func TestRemovedNodeShouldBeMovedToFreeListOAD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedOnlineAccountList()
- e1 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{1}})
- e2 := l.pushFront(&trackerdb.PersistedOnlineAccountData{Addr: basics.Address{2}})
-
- checkListPointersOAD(t, l, []*persistedOnlineAccountDataListNode{e2, e1})
-
- e := l.back()
- l.remove(e)
-
- for i := l.freeList.next; i != nil; i = i.next {
- if i == e {
- // stopping the tst with good results:
- return
- }
- }
- t.Error("expected the removed node to appear at the freelist")
-}
diff --git a/ledger/persistedresources_list.go b/ledger/persistedresources_list.go
deleted file mode 100644
index 2bf9cb6a2e..0000000000
--- a/ledger/persistedresources_list.go
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (C) 2019-2023 Algorand, Inc.
-// This file is part of go-algorand
-//
-// go-algorand is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// go-algorand is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with go-algorand. If not, see .
-
-package ledger
-
-// persistedResourcesDataList represents a doubly linked list.
-// must initiate with newPersistedResourcesList.
-type persistedResourcesDataList struct {
- root persistedResourcesDataListNode // sentinel list element, only &root, root.prev, and root.next are used
- freeList *persistedResourcesDataListNode // preallocated nodes location
-}
-
-type persistedResourcesDataListNode struct {
- // Next and previous pointers in the doubly-linked list of elements.
- // To simplify the implementation, internally a list l is implemented
- // as a ring, such that &l.root is both the next element of the last
- // list element (l.Back()) and the previous element of the first list
- // element (l.Front()).
- next, prev *persistedResourcesDataListNode
-
- Value *cachedResourceData
-}
-
-func newPersistedResourcesList() *persistedResourcesDataList {
- l := new(persistedResourcesDataList)
- l.root.next = &l.root
- l.root.prev = &l.root
- // used as a helper but does not store value
- l.freeList = new(persistedResourcesDataListNode)
-
- return l
-}
-
-func (l *persistedResourcesDataList) insertNodeToFreeList(otherNode *persistedResourcesDataListNode) {
- otherNode.next = l.freeList.next
- otherNode.prev = nil
- otherNode.Value = nil
-
- l.freeList.next = otherNode
-}
-
-func (l *persistedResourcesDataList) getNewNode() *persistedResourcesDataListNode {
- if l.freeList.next == nil {
- return new(persistedResourcesDataListNode)
- }
- newNode := l.freeList.next
- l.freeList.next = newNode.next
-
- return newNode
-}
-
-func (l *persistedResourcesDataList) allocateFreeNodes(numAllocs int) *persistedResourcesDataList {
- if l.freeList == nil {
- return l
- }
- for i := 0; i < numAllocs; i++ {
- l.insertNodeToFreeList(new(persistedResourcesDataListNode))
- }
-
- return l
-}
-
-// Back returns the last element of list l or nil if the list is empty.
-func (l *persistedResourcesDataList) back() *persistedResourcesDataListNode {
- isEmpty := func(list *persistedResourcesDataList) bool {
- // assumes we are inserting correctly to the list - using pushFront.
- return list.root.next == &list.root
- }
- if isEmpty(l) {
- return nil
- }
- return l.root.prev
-}
-
-// remove removes e from l if e is an element of list l.
-// It returns the element value e.Value.
-// The element must not be nil.
-func (l *persistedResourcesDataList) remove(e *persistedResourcesDataListNode) {
- e.prev.next = e.next
- e.next.prev = e.prev
- e.next = nil // avoid memory leaks
- e.prev = nil // avoid memory leaks
-
- l.insertNodeToFreeList(e)
-}
-
-// pushFront inserts a new element e with value v at the front of list l and returns e.
-func (l *persistedResourcesDataList) pushFront(v *cachedResourceData) *persistedResourcesDataListNode {
- newNode := l.getNewNode()
- newNode.Value = v
- return l.insertValue(newNode, &l.root)
-}
-
-// insertValue inserts e after at, increments l.len, and returns e.
-func (l *persistedResourcesDataList) insertValue(newNode *persistedResourcesDataListNode, at *persistedResourcesDataListNode) *persistedResourcesDataListNode {
- n := at.next
- at.next = newNode
- newNode.prev = at
- newNode.next = n
- n.prev = newNode
-
- return newNode
-}
-
-// moveToFront moves element e to the front of list l.
-// If e is not an element of l, the list is not modified.
-// The element must not be nil.
-func (l *persistedResourcesDataList) moveToFront(e *persistedResourcesDataListNode) {
- if l.root.next == e {
- return
- }
- l.move(e, &l.root)
-}
-
-// move moves e to next to at and returns e.
-func (l *persistedResourcesDataList) move(e, at *persistedResourcesDataListNode) *persistedResourcesDataListNode {
- if e == at {
- return e
- }
- e.prev.next = e.next
- e.next.prev = e.prev
-
- n := at.next
- at.next = e
- e.prev = at
- e.next = n
- n.prev = e
-
- return e
-}
diff --git a/ledger/persistedresources_list_test.go b/ledger/persistedresources_list_test.go
deleted file mode 100644
index 484af13956..0000000000
--- a/ledger/persistedresources_list_test.go
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright (C) 2019-2023 Algorand, Inc.
-// This file is part of go-algorand
-//
-// go-algorand is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// go-algorand is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with go-algorand. If not, see .
-
-package ledger
-
-import (
- "testing"
-
- "github.com/algorand/go-algorand/data/basics"
- "github.com/algorand/go-algorand/test/partitiontest"
-)
-
-func (l *persistedResourcesDataList) getRoot() dataListNode {
- return &l.root
-}
-
-func (l *persistedResourcesDataListNode) getNext() dataListNode {
- // get rid of returning nil wrapped into an interface to let i = x.getNext(); i != nil work.
- if l.next == nil {
- return nil
- }
- return l.next
-}
-
-func (l *persistedResourcesDataListNode) getPrev() dataListNode {
- if l.prev == nil {
- return nil
- }
- return l.prev
-}
-
-// inspect that the list seems like the array
-func checkListPointersRD(t *testing.T, l *persistedResourcesDataList, es []*persistedResourcesDataListNode) {
- es2 := make([]dataListNode, len(es))
- for i, el := range es {
- es2[i] = el
- }
-
- checkListPointers(t, l, es2)
-}
-
-func TestRemoveFromListRD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedResourcesList()
- e1 := l.pushFront(&cachedResourceData{address: basics.Address{1}})
- e2 := l.pushFront(&cachedResourceData{address: basics.Address{2}})
- e3 := l.pushFront(&cachedResourceData{address: basics.Address{3}})
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e3, e2, e1})
-
- l.remove(e2)
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e3, e1})
- l.remove(e3)
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e1})
-}
-
-func TestAddingNewNodeWithAllocatedFreeListRD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedResourcesList().allocateFreeNodes(10)
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{})
- if countListSize(l.freeList) != 10 {
- t.Errorf("free list did not allocate nodes")
- return
- }
- // test elements
- e1 := l.pushFront(&cachedResourceData{address: basics.Address{1}})
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e1})
-
- if countListSize(l.freeList) != 9 {
- t.Errorf("free list did not provide a node on new list entry")
- return
- }
-}
-
-func TestMultielementListPositioningRD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedResourcesList()
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{})
- // test elements
- e2 := l.pushFront(&cachedResourceData{address: basics.Address{2}})
- e1 := l.pushFront(&cachedResourceData{address: basics.Address{1}})
- e3 := l.pushFront(&cachedResourceData{address: basics.Address{3}})
- e4 := l.pushFront(&cachedResourceData{address: basics.Address{4}})
- e5 := l.pushFront(&cachedResourceData{address: basics.Address{5}})
-
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e5, e4, e3, e1, e2})
-
- l.move(e4, e1)
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e5, e3, e1, e4, e2})
-
- l.remove(e5)
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e3, e1, e4, e2})
-
- l.move(e1, e4) // swap in middle
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e3, e4, e1, e2})
-
- l.moveToFront(e4)
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e4, e3, e1, e2})
-
- l.remove(e2)
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e4, e3, e1})
-
- l.moveToFront(e3) // move from middle
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e3, e4, e1})
-
- l.moveToFront(e1) // move from end
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e1, e3, e4})
-
- l.moveToFront(e1) // no movement
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e1, e3, e4})
-
- e2 = l.pushFront(&cachedResourceData{address: basics.Address{2}})
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e2, e1, e3, e4})
-
- l.remove(e3) // removing from middle
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e2, e1, e4})
-
- l.remove(e4) // removing from end
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e2, e1})
-
- l.move(e2, e1) // swapping between two elements
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e1, e2})
-
- l.remove(e1) // removing front
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e2})
-
- l.move(e2, l.back()) // swapping element with itself.
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e2})
-
- l.remove(e2) // remove last one
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{})
-}
-
-func TestSingleElementListPositioningRD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedResourcesList()
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{})
- e := l.pushFront(&cachedResourceData{address: basics.Address{1}})
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e})
- l.moveToFront(e)
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e})
- l.remove(e)
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{})
-}
-
-func TestRemovedNodeShouldBeMovedToFreeListRD(t *testing.T) {
- partitiontest.PartitionTest(t)
- l := newPersistedResourcesList()
- e1 := l.pushFront(&cachedResourceData{address: basics.Address{1}})
- e2 := l.pushFront(&cachedResourceData{address: basics.Address{2}})
-
- checkListPointersRD(t, l, []*persistedResourcesDataListNode{e2, e1})
-
- e := l.back()
- l.remove(e)
-
- for i := l.freeList.next; i != nil; i = i.next {
- if i == e {
- // stopping the tst with good results:
- return
- }
- }
- t.Error("expected the removed node to appear at the freelist")
-}
diff --git a/ledger/persistedkvs.go b/util/list.go
similarity index 59%
rename from ledger/persistedkvs.go
rename to util/list.go
index b97234f343..be52459f7c 100644
--- a/ledger/persistedkvs.go
+++ b/util/list.go
@@ -14,47 +14,50 @@
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see .
-package ledger
+package util
-// persistedKVDataList represents a doubly linked list.
-// must initiate with newPersistedKVList.
-type persistedKVDataList struct {
- root persistedKVDataListNode // sentinel list element, only &root, root.prev, and root.next are used
- freeList *persistedKVDataListNode // preallocated nodes location
+// List represents a doubly linked list.
+// must initiate with NewList.
+type List[T any] struct {
+ root ListNode[T] // sentinel list element, only &root, root.prev, and root.next are used
+ freeList *ListNode[T] // preallocated nodes location
}
-type persistedKVDataListNode struct {
+// ListNode represent a list node holding next/prev pointers and a value of type T.
+type ListNode[T any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
- next, prev *persistedKVDataListNode
+ next, prev *ListNode[T]
- Value *cachedKVData
+ Value T
}
-func newPersistedKVList() *persistedKVDataList {
- l := new(persistedKVDataList)
+// NewList creates a new list for storing values of type T.
+func NewList[T any]() *List[T] {
+ l := new(List[T])
l.root.next = &l.root
l.root.prev = &l.root
// used as a helper but does not store value
- l.freeList = new(persistedKVDataListNode)
+ l.freeList = new(ListNode[T])
return l
}
-func (l *persistedKVDataList) insertNodeToFreeList(otherNode *persistedKVDataListNode) {
+func (l *List[T]) insertNodeToFreeList(otherNode *ListNode[T]) {
otherNode.next = l.freeList.next
otherNode.prev = nil
- otherNode.Value = nil
+ var empty T
+ otherNode.Value = empty
l.freeList.next = otherNode
}
-func (l *persistedKVDataList) getNewNode() *persistedKVDataListNode {
+func (l *List[T]) getNewNode() *ListNode[T] {
if l.freeList.next == nil {
- return new(persistedKVDataListNode)
+ return new(ListNode[T])
}
newNode := l.freeList.next
l.freeList.next = newNode.next
@@ -62,20 +65,21 @@ func (l *persistedKVDataList) getNewNode() *persistedKVDataListNode {
return newNode
}
-func (l *persistedKVDataList) allocateFreeNodes(numAllocs int) *persistedKVDataList {
+// AllocateFreeNodes adds N nodes to the free list
+func (l *List[T]) AllocateFreeNodes(numAllocs int) *List[T] {
if l.freeList == nil {
return l
}
for i := 0; i < numAllocs; i++ {
- l.insertNodeToFreeList(new(persistedKVDataListNode))
+ l.insertNodeToFreeList(new(ListNode[T]))
}
return l
}
// Back returns the last element of list l or nil if the list is empty.
-func (l *persistedKVDataList) back() *persistedKVDataListNode {
- isEmpty := func(list *persistedKVDataList) bool {
+func (l *List[T]) Back() *ListNode[T] {
+ isEmpty := func(list *List[T]) bool {
// assumes we are inserting correctly to the list - using pushFront.
return list.root.next == &list.root
}
@@ -85,10 +89,9 @@ func (l *persistedKVDataList) back() *persistedKVDataListNode {
return l.root.prev
}
-// remove removes e from l if e is an element of list l.
-// It returns the element value e.Value.
+// Remove removes e from l if e is an element of list l.
// The element must not be nil.
-func (l *persistedKVDataList) remove(e *persistedKVDataListNode) {
+func (l *List[T]) Remove(e *ListNode[T]) {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
@@ -97,15 +100,15 @@ func (l *persistedKVDataList) remove(e *persistedKVDataListNode) {
l.insertNodeToFreeList(e)
}
-// pushFront inserts a new element e with value v at the front of list l and returns e.
-func (l *persistedKVDataList) pushFront(v *cachedKVData) *persistedKVDataListNode {
+// PushFront inserts a new element e with value v at the front of list l and returns e.
+func (l *List[T]) PushFront(v T) *ListNode[T] {
newNode := l.getNewNode()
newNode.Value = v
return l.insertValue(newNode, &l.root)
}
// insertValue inserts e after at, increments l.len, and returns e.
-func (l *persistedKVDataList) insertValue(newNode *persistedKVDataListNode, at *persistedKVDataListNode) *persistedKVDataListNode {
+func (l *List[T]) insertValue(newNode *ListNode[T], at *ListNode[T]) *ListNode[T] {
n := at.next
at.next = newNode
newNode.prev = at
@@ -115,10 +118,10 @@ func (l *persistedKVDataList) insertValue(newNode *persistedKVDataListNode, at *
return newNode
}
-// moveToFront moves element e to the front of list l.
+// MoveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
-func (l *persistedKVDataList) moveToFront(e *persistedKVDataListNode) {
+func (l *List[T]) MoveToFront(e *ListNode[T]) {
if l.root.next == e {
return
}
@@ -126,7 +129,7 @@ func (l *persistedKVDataList) moveToFront(e *persistedKVDataListNode) {
}
// move moves e to next to at and returns e.
-func (l *persistedKVDataList) move(e, at *persistedKVDataListNode) *persistedKVDataListNode {
+func (l *List[T]) move(e, at *ListNode[T]) *ListNode[T] {
if e == at {
return e
}
diff --git a/util/list_test.go b/util/list_test.go
new file mode 100644
index 0000000000..4b87bef745
--- /dev/null
+++ b/util/list_test.go
@@ -0,0 +1,276 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package util
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+)
+
+func checkLen[T any](list *List[T]) int {
+ if list.root.next == &list.root {
+ return 0
+ }
+
+ return countListSize(&list.root)
+}
+
+func countListSize[T any](head *ListNode[T]) (counter int) {
+ for i := head.next; i != head && i != nil; i = i.next {
+ counter++
+ }
+ return counter
+}
+
+func checkListLen[T any](t *testing.T, l *List[T], len int) bool {
+ if n := checkLen(l); n != len {
+ t.Errorf("l.Len() = %d, want %d", n, len)
+ return true
+ }
+ return false
+}
+
+func checkListPointers[T any](t *testing.T, l *List[T], es []*ListNode[T]) {
+ root := &l.root
+
+ if failed := checkListLen(t, l, len(es)); failed {
+ return
+ }
+
+ if failed := zeroListInspection(t, l, len(es), root); failed {
+ return
+ }
+
+ pointerInspection(t, es, root)
+}
+
+func zeroListInspection[T any](t *testing.T, l *List[T], len int, root *ListNode[T]) bool {
+ // zero length lists must be the zero value or properly initialized (sentinel circle)
+ if len == 0 {
+ if l.root.next != nil && l.root.next != root || l.root.prev != nil && l.root.prev != root {
+ t.Errorf("l.root.next = %p, l.root.prev = %p; both should both be nil or %p", l.root.next, l.root.prev, root)
+ }
+ return true
+ }
+ return false
+}
+
+func pointerInspection[T any](t *testing.T, es []*ListNode[T], root *ListNode[T]) {
+ // check internal and external prev/next connections
+ for i, e := range es {
+ prev := root
+ if i > 0 {
+ prev = es[i-1]
+ }
+ if p := e.prev; p != prev {
+ t.Errorf("elt[%d](%p).prev = %p, want %p", i, e, p, prev)
+ }
+
+ next := root
+ if i < len(es)-1 {
+ next = es[i+1]
+ }
+ if n := e.next; n != next {
+ t.Errorf("elt[%d](%p).next = %p, want %p", i, e, n, next)
+ }
+ }
+}
+
+type testVal struct {
+ val int
+}
+
+func TestList_RemoveFromList(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]()
+ e1 := l.PushFront(&testVal{1})
+ e2 := l.PushFront(&testVal{2})
+ e3 := l.PushFront(&testVal{3})
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e2, e1})
+
+ l.Remove(e2)
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e1})
+ l.Remove(e3)
+ checkListPointers(t, l, []*ListNode[*testVal]{e1})
+}
+
+func TestList_AddingNewNodeWithAllocatedFreeListPtr(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]().AllocateFreeNodes(10)
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+ if countListSize(l.freeList) != 10 {
+ t.Errorf("free list did not allocate nodes")
+ return
+ }
+ // test elements
+ e1 := l.PushFront(&testVal{1})
+ checkListPointers(t, l, []*ListNode[*testVal]{e1})
+
+ if countListSize(l.freeList) != 9 {
+ t.Errorf("free list did not provide a node on new list entry")
+ return
+ }
+}
+
+func TestList_AddingNewNodeWithAllocatedFreeListValue(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[testVal]().AllocateFreeNodes(10)
+ checkListPointers(t, l, []*ListNode[testVal]{})
+ if countListSize(l.freeList) != 10 {
+ t.Errorf("free list did not allocate nodes")
+ return
+ }
+ // test elements
+ e1 := l.PushFront(testVal{1})
+ checkListPointers(t, l, []*ListNode[testVal]{e1})
+
+ if countListSize(l.freeList) != 9 {
+ t.Errorf("free list did not provide a node on new list entry")
+ return
+ }
+}
+
+func TestList_MultiElementListPositioning(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]()
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+ // test elements
+ e2 := l.PushFront(&testVal{2})
+ e1 := l.PushFront(&testVal{1})
+ e3 := l.PushFront(&testVal{3})
+ e4 := l.PushFront(&testVal{4})
+ e5 := l.PushFront(&testVal{5})
+ checkListPointers(t, l, []*ListNode[*testVal]{e5, e4, e3, e1, e2})
+
+ l.move(e4, e1)
+ checkListPointers(t, l, []*ListNode[*testVal]{e5, e3, e1, e4, e2})
+
+ l.Remove(e5)
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e1, e4, e2})
+
+ l.move(e1, e4) // swap in middle
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e4, e1, e2})
+
+ l.MoveToFront(e4)
+ checkListPointers(t, l, []*ListNode[*testVal]{e4, e3, e1, e2})
+
+ l.Remove(e2)
+ checkListPointers(t, l, []*ListNode[*testVal]{e4, e3, e1})
+
+ l.MoveToFront(e3) // move from middle
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e4, e1})
+
+ l.MoveToFront(e1) // move from end
+ checkListPointers(t, l, []*ListNode[*testVal]{e1, e3, e4})
+
+ l.MoveToFront(e1) // no movement
+ checkListPointers(t, l, []*ListNode[*testVal]{e1, e3, e4})
+
+ e2 = l.PushFront(&testVal{2})
+ checkListPointers(t, l, []*ListNode[*testVal]{e2, e1, e3, e4})
+
+ l.Remove(e3) // removing from middle
+ checkListPointers(t, l, []*ListNode[*testVal]{e2, e1, e4})
+
+ l.Remove(e4) // removing from end
+ checkListPointers(t, l, []*ListNode[*testVal]{e2, e1})
+
+ l.move(e2, e1) // swapping between two elements
+ checkListPointers(t, l, []*ListNode[*testVal]{e1, e2})
+
+ l.Remove(e1) // removing front
+ checkListPointers(t, l, []*ListNode[*testVal]{e2})
+
+ l.move(e2, l.Back()) // swapping element with itself.
+ checkListPointers(t, l, []*ListNode[*testVal]{e2})
+
+ l.Remove(e2) // remove last one
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+}
+
+func TestList_SingleElementListPositioning(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]()
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+ e := l.PushFront(&testVal{1})
+ checkListPointers(t, l, []*ListNode[*testVal]{e})
+ l.MoveToFront(e)
+ checkListPointers(t, l, []*ListNode[*testVal]{e})
+ l.Remove(e)
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+}
+
+func TestList_RemovedNodeShouldBeMovedToFreeList(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]()
+ e1 := l.PushFront(&testVal{1})
+ e2 := l.PushFront(&testVal{2})
+
+ checkListPointers(t, l, []*ListNode[*testVal]{e2, e1})
+
+ e := l.Back()
+ l.Remove(e)
+
+ for i := l.freeList.next; i != nil; i = i.next {
+ if i == e {
+ // stopping the test with good results:
+ return
+ }
+ }
+ t.Error("expected the removed node to appear at the freelist")
+}
+
+func TestList_PushMoveBackRemove(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[testVal]().AllocateFreeNodes(4)
+ e1 := l.PushFront(testVal{1})
+ e2 := l.PushFront(testVal{2})
+ checkListPointers(t, l, []*ListNode[testVal]{e2, e1})
+
+ l.MoveToFront(e1)
+ checkListPointers(t, l, []*ListNode[testVal]{e1, e2})
+
+ e := l.Back()
+ require.Equal(t, e, e2)
+ l.Remove(e)
+ checkListPointers(t, l, []*ListNode[testVal]{e1})
+
+ e = l.Back()
+ require.Equal(t, e, e1)
+ l.Remove(e)
+ checkListPointers(t, l, []*ListNode[testVal]{})
+
+ e = l.Back()
+ require.Nil(t, e)
+}