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) +}