Repository: kaspanet/kaspad
Pull request #1542 (2021-02-18 10:03:04)

Close iterators
We use goleveldb for our database, and it explictly says that all iterators must be released:

> The iterator must be released after use, by calling Release method. 

https://pkg.go.dev/github.com/syndtr/goleveldb/leveldb#Transaction.NewIterator

I'm not sure if in practice this means we're leaking memory, because goleveldb uses `runtime.SetFinalizer` to call the Release, but we don't want to trust it works correctly, and also goleveldb never promised it's doing that, so they can always stop doing that.

I did not explictly invalidated the iterators, so some iterators could still be "used" after closing, and closing won't free their memory (assigning nil to all fields), but I'm open to discuss that with or without an explicit `isClosed` flag in all iterators.

codecov[bot]: # [Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/1542?src=pr&el=h1) Report
> Merging [#1542](https://codecov.io/gh/kaspanet/kaspad/pull/1542?src=pr&el=desc) (d5ffe47) into [v0.9.0-dev](https://codecov.io/gh/kaspanet/kaspad/commit/06fd6f1b95797b3259d79ebd00af9e4ab6148b4f?el=desc) (06fd6f1) will **increase** coverage by `0.01%`.
> The diff coverage is `78.78%`.

[![Impacted file tree graph](https://codecov.io/gh/kaspanet/kaspad/pull/1542/graphs/tree.svg?width=650&height=150&src=pr&token=2FBIEEJTNU)](https://codecov.io/gh/kaspanet/kaspad/pull/1542?src=pr&el=tree)

```diff
@@              Coverage Diff               @@
##           v0.9.0-dev    #1542      +/-   ##
==============================================
+ Coverage       61.57%   61.58%   +0.01%     
==============================================
  Files             508      508              
  Lines           19333    19366      +33     
==============================================
+ Hits            11904    11927      +23     
- Misses           5722     5731       +9     
- Partials         1707     1708       +1     
```


| [Impacted Files](https://codecov.io/gh/kaspanet/kaspad/pull/1542?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [domain/consensus/test\_consensus\_render\_to\_dot.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy90ZXN0X2NvbnNlbnN1c19yZW5kZXJfdG9fZG90Lmdv) | `0.00% <0.00%> (ø)` | |
| [...in/consensus/utils/utxo/utxo\_iterator\_with\_diff.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy91dGlscy91dHhvL3V0eG9faXRlcmF0b3Jfd2l0aF9kaWZmLmdv) | `76.19% <37.50%> (-9.11%)` | :arrow_down: |
| [domain/consensus/processes/syncmanager/antipast.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9wcm9jZXNzZXMvc3luY21hbmFnZXIvYW50aXBhc3QuZ28=) | `41.17% <66.66%> (+0.57%)` | :arrow_up: |
| [...onsensus/database/serialization/utxo\_collection.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9kYXRhYmFzZS9zZXJpYWxpemF0aW9uL3V0eG9fY29sbGVjdGlvbi5nbw==) | `71.42% <100.00%> (+1.42%)` | :arrow_up: |
| [.../consensus/datastructures/blockstore/blockstore.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9kYXRhc3RydWN0dXJlcy9ibG9ja3N0b3JlL2Jsb2Nrc3RvcmUuZ28=) | `55.55% <100.00%> (+0.41%)` | :arrow_up: |
| [...nsensus/datastructures/consensusstatestore/utxo.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9kYXRhc3RydWN0dXJlcy9jb25zZW5zdXNzdGF0ZXN0b3JlL3V0eG8uZ28=) | `44.79% <100.00%> (+1.78%)` | :arrow_up: |
| [...structures/consensusstatestore/virtual\_utxo\_set.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9kYXRhc3RydWN0dXJlcy9jb25zZW5zdXNzdGF0ZXN0b3JlL3ZpcnR1YWxfdXR4b19zZXQuZ28=) | `41.66% <100.00%> (+1.66%)` | :arrow_up: |
| [...astructures/pruningstore/imported\_pruning\_point.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9kYXRhc3RydWN0dXJlcy9wcnVuaW5nc3RvcmUvaW1wb3J0ZWRfcHJ1bmluZ19wb2ludC5nbw==) | `51.51% <100.00%> (+2.04%)` | :arrow_up: |
| [...sensus/datastructures/pruningstore/pruningstore.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9kYXRhc3RydWN0dXJlcy9wcnVuaW5nc3RvcmUvcHJ1bmluZ3N0b3JlLmdv) | `57.75% <100.00%> (+0.36%)` | :arrow_up: |
| [...s/consensusstatemanager/import\_pruning\_utxo\_set.go](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9wcm9jZXNzZXMvY29uc2Vuc3Vzc3RhdGVtYW5hZ2VyL2ltcG9ydF9wcnVuaW5nX3V0eG9fc2V0Lmdv) | `59.20% <100.00%> (+0.66%)` | :arrow_up: |
| ... and [7 more](https://codecov.io/gh/kaspanet/kaspad/pull/1542/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/1542?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/1542?src=pr&el=footer). Last update [06fd6f1...d5ffe47](https://codecov.io/gh/kaspanet/kaspad/pull/1542?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

elichai: > I think we want to make sure nobody uses iterators after Close, at the very least for the sanity of the thing, and later down the road to maybe release some resources.
> 
> Even just setting iterator.isClosed = true, and then erroring out on any function that returns an error can be a good start.

So do we want to add an `isClosed` boolean to all the iterators? (note that if you then call `Next()` we can only return false/true not an error)
svarogg: > So do we want to add an `isClosed` boolean to all the iterators? 
I believe we do. In addition we might release any resources held, but less critical at this point IMHO.

> (note that if you then call `Next()` we can only return false/true not an error)
Yeah, I thought about that and didn't really come up with a solution I'm 100% satisfied with.
I guess we should just return `false` when calling Next() on a closed iterator.

elichai: > I guess we should just return `false` when calling Next() on a closed iterator.

If the `Get()` of that iterator returns an error we can return true and then set the error (We already do that in different cases when there's an error inside `Next()`)
svarogg: > If the `Get()` of that iterator returns an error we can return true and then set the error (We already do that in different cases when there's an error inside `Next()`)

Yeah, I though about this, it indeed will achieve the desired behavior, at the price of correctness (Next returning true while there's no actual Next).
I'm starting to see the merits of this solution in a broader light, so I think I'm convinced. Let's do this as you suggested:
`.Next()` on closed iterator always returns `true`, then caller will call `.Get()`, that always returns an error.

Patch:
diff --git a/domain/consensus/database/cursor.go b/domain/consensus/database/cursor.go
index 1d2b319d..27b6a738 100644
--- a/domain/consensus/database/cursor.go
+++ b/domain/consensus/database/cursor.go
@@ -3,25 +3,40 @@ package database
 import (
 	"github.com/kaspanet/kaspad/domain/consensus/model"
 	"github.com/kaspanet/kaspad/infrastructure/db/database"
+	"github.com/pkg/errors"
 )
 
 type dbCursor struct {
-	cursor database.Cursor
+	cursor   database.Cursor
+	isClosed bool
 }
 
 func (d dbCursor) Next() bool {
+	if d.isClosed {
+		panic("Tried using a closed DBCursor")
+	}
+
 	return d.cursor.Next()
 }
 
 func (d dbCursor) First() bool {
+	if d.isClosed {
+		panic("Tried using a closed DBCursor")
+	}
 	return d.cursor.First()
 }
 
 func (d dbCursor) Seek(key model.DBKey) error {
+	if d.isClosed {
+		return errors.New("Tried using a closed DBCursor")
+	}
 	return d.cursor.Seek(dbKeyToDatabaseKey(key))
 }
 
 func (d dbCursor) Key() (model.DBKey, error) {
+	if d.isClosed {
+		return nil, errors.New("Tried using a closed DBCursor")
+	}
 	key, err := d.cursor.Key()
 	if err != nil {
 		return nil, err
@@ -31,11 +46,23 @@ func (d dbCursor) Key() (model.DBKey, error) {
 }
 
 func (d dbCursor) Value() ([]byte, error) {
+	if d.isClosed {
+		return nil, errors.New("Tried using a closed DBCursor")
+	}
 	return d.cursor.Value()
 }
 
 func (d dbCursor) Close() error {
-	return d.cursor.Close()
+	if d.isClosed {
+		return errors.New("Tried using a closed DBCursor")
+	}
+	d.isClosed = true
+	err := d.cursor.Close()
+	if err != nil {
+		return err
+	}
+	d.cursor = nil
+	return nil
 }
 
 func newDBCursor(cursor database.Cursor) model.DBCursor {
diff --git a/domain/consensus/database/serialization/utxo_collection.go b/domain/consensus/database/serialization/utxo_collection.go
index 76fa5431..f7930bad 100644
--- a/domain/consensus/database/serialization/utxo_collection.go
+++ b/domain/consensus/database/serialization/utxo_collection.go
@@ -9,6 +9,7 @@ func utxoCollectionToDBUTXOCollection(utxoCollection externalapi.UTXOCollection)
 	items := make([]*DbUtxoCollectionItem, utxoCollection.Len())
 	i := 0
 	utxoIterator := utxoCollection.Iterator()
+	defer utxoIterator.Close()
 	for ok := utxoIterator.First(); ok; ok = utxoIterator.Next() {
 		outpoint, entry, err := utxoIterator.Get()
 		if err != nil {
diff --git a/domain/consensus/datastructures/blockstore/blockstore.go b/domain/consensus/datastructures/blockstore/blockstore.go
index eeacf05e..343044e8 100644
--- a/domain/consensus/datastructures/blockstore/blockstore.go
+++ b/domain/consensus/datastructures/blockstore/blockstore.go
@@ -7,6 +7,7 @@ import (
 	"github.com/kaspanet/kaspad/domain/consensus/model"
 	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
 	"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
+	"github.com/pkg/errors"
 )
 
 var bucket = database.MakeBucket([]byte("blocks"))
@@ -214,18 +215,28 @@ func (bs *blockStore) serializeBlockCount(count uint64) ([]byte, error) {
 }
 
 type allBlockHashesIterator struct {
-	cursor model.DBCursor
+	cursor   model.DBCursor
+	isClosed bool
 }
 
 func (a allBlockHashesIterator) First() bool {
+	if a.isClosed {
+		panic("Tried using a closed AllBlockHashesIterator")
+	}
 	return a.cursor.First()
 }
 
 func (a allBlockHashesIterator) Next() bool {
+	if a.isClosed {
+		panic("Tried using a closed AllBlockHashesIterator")
+	}
 	return a.cursor.Next()
 }
 
 func (a allBlockHashesIterator) Get() (*externalapi.DomainHash, error) {
+	if a.isClosed {
+		return nil, errors.New("Tried using a closed AllBlockHashesIterator")
+	}
 	key, err := a.cursor.Key()
 	if err != nil {
 		return nil, err
@@ -235,6 +246,19 @@ func (a allBlockHashesIterator) Get() (*externalapi.DomainHash, error) {
 	return externalapi.NewDomainHashFromByteSlice(blockHashBytes)
 }
 
+func (a allBlockHashesIterator) Close() error {
+	if a.isClosed {
+		return errors.New("Tried using a closed AllBlockHashesIterator")
+	}
+	a.isClosed = true
+	err := a.cursor.Close()
+	if err != nil {
+		return err
+	}
+	a.cursor = nil
+	return nil
+}
+
 func (bs *blockStore) AllBlockHashesIterator(dbContext model.DBReader) (model.BlockIterator, error) {
 	cursor, err := dbContext.Cursor(bucket)
 	if err != nil {
diff --git a/domain/consensus/datastructures/pruningstore/imported_pruning_point.go b/domain/consensus/datastructures/pruningstore/imported_pruning_point.go
index 1c654a55..5f7d19a6 100644
--- a/domain/consensus/datastructures/pruningstore/imported_pruning_point.go
+++ b/domain/consensus/datastructures/pruningstore/imported_pruning_point.go
@@ -6,6 +6,7 @@ import (
 	"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
 	"github.com/kaspanet/kaspad/domain/consensus/model"
 	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/pkg/errors"
 )
 
 var importedPruningPointUTXOsBucket = database.MakeBucket([]byte("imported-pruning-point-utxos"))
@@ -16,6 +17,7 @@ func (ps *pruningStore) ClearImportedPruningPointUTXOs(dbContext model.DBWriter)
 	if err != nil {
 		return err
 	}
+	defer cursor.Close()
 
 	for ok := cursor.First(); ok; ok = cursor.Next() {
 		key, err := cursor.Key()
@@ -60,7 +62,8 @@ func (ps *pruningStore) ImportedPruningPointUTXOIterator(dbContext model.DBReade
 }
 
 type utxoSetIterator struct {
-	cursor model.DBCursor
+	cursor   model.DBCursor
+	isClosed bool
 }
 
 func (ps *pruningStore) newCursorUTXOSetIterator(cursor model.DBCursor) externalapi.ReadOnlyUTXOSetIterator {
@@ -68,14 +71,23 @@ func (ps *pruningStore) newCursorUTXOSetIterator(cursor model.DBCursor) external
 }
 
 func (u *utxoSetIterator) First() bool {
+	if u.isClosed {
+		panic("Tried using a closed utxoSetIterator")
+	}
 	return u.cursor.First()
 }
 
 func (u *utxoSetIterator) Next() bool {
+	if u.isClosed {
+		panic("Tried using a closed utxoSetIterator")
+	}
 	return u.cursor.Next()
 }
 
 func (u *utxoSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry, err error) {
+	if u.isClosed {
+		return nil, nil, errors.New("Tried using a closed utxoSetIterator")
+	}
 	key, err := u.cursor.Key()
 	if err != nil {
 		panic(err)
@@ -99,6 +111,19 @@ func (u *utxoSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoEntry
 	return outpoint, utxoEntry, nil
 }
 
+func (u *utxoSetIterator) Close() error {
+	if u.isClosed {
+		return errors.New("Tried using a closed utxoSetIterator")
+	}
+	u.isClosed = true
+	err := u.cursor.Close()
+	if err != nil {
+		return err
+	}
+	u.cursor = nil
+	return nil
+}
+
 func (ps *pruningStore) importedPruningPointUTXOKey(outpoint *externalapi.DomainOutpoint) (model.DBKey, error) {
 	serializedOutpoint, err := serializeOutpoint(outpoint)
 	if err != nil {
@@ -175,6 +200,7 @@ func (ps *pruningStore) CommitImportedPruningPointUTXOSet(dbContext model.DBWrit
 	if err != nil {
 		return err
 	}
+	defer deleteCursor.Close()
 	for ok := deleteCursor.First(); ok; ok = deleteCursor.Next() {
 		key, err := deleteCursor.Key()
 		if err != nil {
@@ -191,6 +217,7 @@ func (ps *pruningStore) CommitImportedPruningPointUTXOSet(dbContext model.DBWrit
 	if err != nil {
 		return err
 	}
+	defer insertCursor.Close()
 	for ok := insertCursor.First(); ok; ok = insertCursor.Next() {
 		importedPruningPointUTXOSetKey, err := insertCursor.Key()
 		if err != nil {
diff --git a/domain/consensus/datastructures/pruningstore/pruningstore.go b/domain/consensus/datastructures/pruningstore/pruningstore.go
index 241f3585..b9e3c41b 100644
--- a/domain/consensus/datastructures/pruningstore/pruningstore.go
+++ b/domain/consensus/datastructures/pruningstore/pruningstore.go
@@ -124,6 +124,7 @@ func (ps *pruningStore) UpdatePruningPointUTXOSet(dbContext model.DBWriter,
 	if err != nil {
 		return err
 	}
+	defer deleteCursor.Close()
 	for ok := deleteCursor.First(); ok; ok = deleteCursor.Next() {
 		key, err := deleteCursor.Key()
 		if err != nil {
@@ -215,6 +216,7 @@ func (ps *pruningStore) PruningPointUTXOs(dbContext model.DBReader,
 	if err != nil {
 		return nil, err
 	}
+	defer cursor.Close()
 
 	if fromOutpoint != nil {
 		serializedFromOutpoint, err := serializeOutpoint(fromOutpoint)
@@ -229,6 +231,7 @@ func (ps *pruningStore) PruningPointUTXOs(dbContext model.DBReader,
 	}
 
 	pruningPointUTXOIterator := ps.newCursorUTXOSetIterator(cursor)
+	defer pruningPointUTXOIterator.Close()
 
 	outpointAndUTXOEntryPairs := make([]*externalapi.OutpointAndUTXOEntryPair, 0, limit)
 	for len(outpointAndUTXOEntryPairs) < limit && pruningPointUTXOIterator.Next() {
diff --git a/domain/consensus/model/blockiterator.go b/domain/consensus/model/blockiterator.go
index 051103a6..bfe57039 100644
--- a/domain/consensus/model/blockiterator.go
+++ b/domain/consensus/model/blockiterator.go
@@ -7,4 +7,5 @@ type BlockIterator interface {
 	First() bool
 	Next() bool
 	Get() (*externalapi.DomainHash, error)
+	Close() error
 }
diff --git a/domain/consensus/model/externalapi/readonlyutxoset.go b/domain/consensus/model/externalapi/readonlyutxoset.go
index d87d0b6a..61b1827e 100644
--- a/domain/consensus/model/externalapi/readonlyutxoset.go
+++ b/domain/consensus/model/externalapi/readonlyutxoset.go
@@ -6,4 +6,5 @@ type ReadOnlyUTXOSetIterator interface {
 	First() bool
 	Next() bool
 	Get() (outpoint *DomainOutpoint, utxoEntry UTXOEntry, err error)
+	Close() error
 }
diff --git a/domain/consensus/processes/consensusstatemanager/import_pruning_utxo_set.go b/domain/consensus/processes/consensusstatemanager/import_pruning_utxo_set.go
index 8c26c7c9..b03bc7b0 100644
--- a/domain/consensus/processes/consensusstatemanager/import_pruning_utxo_set.go
+++ b/domain/consensus/processes/consensusstatemanager/import_pruning_utxo_set.go
@@ -88,6 +88,7 @@ func (csm *consensusStateManager) importPruningPoint(newPruningPoint *externalap
 	if err != nil {
 		return err
 	}
+	defer importedPruningPointUTXOIterator.Close()
 
 	// Clone the pruningPoint block here because validateBlockTransactionsAgainstPastUTXO
 	// assumes that the block UTXOEntries are pre-filled during further validations
@@ -176,6 +177,7 @@ func (csm *consensusStateManager) importVirtualUTXOSetAndPruningPointUTXOSet() e
 	if err != nil {
 		return err
 	}
+	defer pruningPointUTXOSetIterator.Close()
 
 	log.Debugf("Importing the virtual UTXO set")
 	err = csm.consensusStateStore.ImportPruningPointUTXOSetIntoVirtualUTXOSet(csm.databaseContext, pruningPointUTXOSetIterator)
diff --git a/domain/consensus/processes/dagtraversalmanager/selected_child_iterator.go b/domain/consensus/processes/dagtraversalmanager/selected_child_iterator.go
index 511576a4..d10fd65c 100644
--- a/domain/consensus/processes/dagtraversalmanager/selected_child_iterator.go
+++ b/domain/consensus/processes/dagtraversalmanager/selected_child_iterator.go
@@ -14,14 +14,21 @@ type selectedChildIterator struct {
 	highHash, lowHash     *externalapi.DomainHash
 	current               *externalapi.DomainHash
 	err                   error
+	isClosed              bool
 }
 
 func (s *selectedChildIterator) First() bool {
+	if s.isClosed {
+		panic("Tried using a closed SelectedChildIterator")
+	}
 	s.current = s.lowHash
 	return s.Next()
 }
 
 func (s *selectedChildIterator) Next() bool {
+	if s.isClosed {
+		panic("Tried using a closed SelectedChildIterator")
+	}
 	if s.err != nil {
 		return true
 	}
@@ -50,9 +57,27 @@ func (s *selectedChildIterator) Next() bool {
 }
 
 func (s *selectedChildIterator) Get() (*externalapi.DomainHash, error) {
+	if s.isClosed {
+		return nil, errors.New("Tried using a closed SelectedChildIterator")
+	}
 	return s.current, s.err
 }
 
+func (s *selectedChildIterator) Close() error {
+	if s.isClosed {
+		return errors.New("Tried using a closed SelectedChildIterator")
+	}
+	s.isClosed = true
+	s.databaseContext = nil
+	s.dagTopologyManager = nil
+	s.reachabilityDataStore = nil
+	s.highHash = nil
+	s.lowHash = nil
+	s.current = nil
+	s.err = nil
+	return nil
+}
+
 // SelectedChildIterator returns a BlockIterator that iterates from lowHash (exclusive) to highHash (inclusive) over
 // highHash's selected parent chain
 func (dtm *dagTraversalManager) SelectedChildIterator(highHash, lowHash *externalapi.DomainHash) (model.BlockIterator, error) {
diff --git a/domain/consensus/processes/pruningmanager/pruningmanager.go b/domain/consensus/processes/pruningmanager/pruningmanager.go
index e393b938..f9aae1f8 100644
--- a/domain/consensus/processes/pruningmanager/pruningmanager.go
+++ b/domain/consensus/processes/pruningmanager/pruningmanager.go
@@ -133,6 +133,7 @@ func (pm *pruningManager) UpdatePruningPointByVirtual() error {
 	if err != nil {
 		return err
 	}
+	defer iterator.Close()
 
 	// Finding the next pruning point candidate: look for the latest
 	// selected child of the current candidate that is in depth of at
@@ -425,6 +426,7 @@ func (pm *pruningManager) validateUTXOSetFitsCommitment(pruningPointHash *extern
 	if err != nil {
 		return err
 	}
+	defer utxoSetIterator.Close()
 
 	utxoSetMultiset := multiset.New()
 	for ok := utxoSetIterator.First(); ok; ok = utxoSetIterator.Next() {
@@ -544,6 +546,7 @@ func (pm *pruningManager) updatePruningPointUTXOSet() error {
 	if err != nil {
 		return err
 	}
+	defer utxoSetIterator.Close()
 
 	log.Debugf("Updating the pruning point UTXO set")
 	err = pm.pruningStore.UpdatePruningPointUTXOSet(pm.databaseContext, utxoSetIterator)
@@ -563,6 +566,7 @@ func (pm *pruningManager) PruneAllBlocksBelow(pruningPointHash *externalapi.Doma
 	if err != nil {
 		return err
 	}
+	defer iterator.Close()
 
 	for ok := iterator.First(); ok; ok = iterator.Next() {
 		blockHash, err := iterator.Get()
diff --git a/domain/consensus/processes/syncmanager/antipast.go b/domain/consensus/processes/syncmanager/antipast.go
index 75ee8fcd..787768ad 100644
--- a/domain/consensus/processes/syncmanager/antipast.go
+++ b/domain/consensus/processes/syncmanager/antipast.go
@@ -59,6 +59,7 @@ func (sm *syncManager) antiPastHashesBetween(lowHash, highHash *externalapi.Doma
 	if err != nil {
 		return nil, err
 	}
+	defer iterator.Close()
 	for ok := iterator.First(); ok; ok = iterator.Next() {
 		current, err := iterator.Get()
 		if err != nil {
@@ -145,6 +146,7 @@ func (sm *syncManager) findHighHashAccordingToMaxBlueScoreDifference(lowHash *ex
 	if err != nil {
 		return nil, err
 	}
+	defer iterator.Close()
 	for ok := iterator.First(); ok; ok = iterator.Next() {
 		highHashCandidate, err := iterator.Get()
 		if err != nil {
@@ -191,6 +193,7 @@ func (sm *syncManager) missingBlockBodyHashes(highHash *externalapi.DomainHash)
 	if err != nil {
 		return nil, err
 	}
+	defer selectedChildIterator.Close()
 
 	lowHash := pruningPoint
 	foundHeaderOnlyBlock := false
diff --git a/domain/consensus/utils/utxo/utxo_iterator.go b/domain/consensus/utils/utxo/utxo_iterator.go
index 3947d2c2..276b0fc2 100644
--- a/domain/consensus/utils/utxo/utxo_iterator.go
+++ b/domain/consensus/utils/utxo/utxo_iterator.go
@@ -11,8 +11,9 @@ type utxoOutpointEntryPair struct {
 }
 
 type utxoCollectionIterator struct {
-	index int
-	pairs []utxoOutpointEntryPair
+	index    int
+	pairs    []utxoOutpointEntryPair
+	isClosed bool
 }
 
 func (uc utxoCollection) Iterator() externalapi.ReadOnlyUTXOSetIterator {
@@ -29,21 +30,33 @@ func (uc utxoCollection) Iterator() externalapi.ReadOnlyUTXOSetIterator {
 }
 
 func (uci *utxoCollectionIterator) First() bool {
+	if uci.isClosed {
+		panic("Tried using a closed utxoCollectionIterator")
+	}
 	uci.index = 0
 	return len(uci.pairs) > 0
 }
 
 func (uci *utxoCollectionIterator) Next() bool {
+	if uci.isClosed {
+		panic("Tried using a closed utxoCollectionIterator")
+	}
 	uci.index++
 	return uci.index < len(uci.pairs)
 }
 
 func (uci *utxoCollectionIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry, err error) {
+	if uci.isClosed {
+		return nil, nil, errors.New("Tried using a closed utxoCollectionIterator")
+	}
 	pair := uci.pairs[uci.index]
 	return &pair.outpoint, pair.entry, nil
 }
 
 func (uci *utxoCollectionIterator) WithDiff(diff externalapi.UTXODiff) (externalapi.ReadOnlyUTXOSetIterator, error) {
+	if uci.isClosed {
+		return nil, errors.New("Tried using a closed utxoCollectionIterator")
+	}
 	d, ok := diff.(*immutableUTXODiff)
 	if !ok {
 		return nil, errors.New("diff is not of type *immutableUTXODiff")
@@ -55,3 +68,12 @@ func (uci *utxoCollectionIterator) WithDiff(diff externalapi.UTXODiff) (external
 		toAddIterator: diff.ToAdd().Iterator(),
 	}, nil
 }
+
+func (uci *utxoCollectionIterator) Close() error {
+	if uci.isClosed {
+		return errors.New("Tried using a closed utxoCollectionIterator")
+	}
+	uci.isClosed = true
+	uci.pairs = nil
+	return nil
+}
diff --git a/domain/consensus/utils/utxo/utxo_iterator_with_diff.go b/domain/consensus/utils/utxo/utxo_iterator_with_diff.go
index 80e1570b..dd0faad2 100644
--- a/domain/consensus/utils/utxo/utxo_iterator_with_diff.go
+++ b/domain/consensus/utils/utxo/utxo_iterator_with_diff.go
@@ -14,6 +14,7 @@ type readOnlyUTXOIteratorWithDiff struct {
 	currentErr       error
 
 	toAddIterator externalapi.ReadOnlyUTXOSetIterator
+	isClosed      bool
 }
 
 // IteratorWithDiff applies a UTXODiff to given utxo iterator
@@ -40,9 +41,17 @@ func IteratorWithDiff(iterator externalapi.ReadOnlyUTXOSetIterator, diff externa
 }
 
 func (r *readOnlyUTXOIteratorWithDiff) First() bool {
+	if r.isClosed {
+		panic("Tried using a closed readOnlyUTXOIteratorWithDiff")
+	}
 	baseNotEmpty := r.baseIterator.First()
 	baseEmpty := !baseNotEmpty
 
+	err := r.toAddIterator.Close()
+	if err != nil {
+		r.currentErr = err
+		return true
+	}
 	r.toAddIterator = r.diff.ToAdd().Iterator()
 	toAddEmpty := r.diff.ToAdd().Len() == 0
 
@@ -61,6 +70,9 @@ func (r *readOnlyUTXOIteratorWithDiff) First() bool {
 }
 
 func (r *readOnlyUTXOIteratorWithDiff) Next() bool {
+	if r.isClosed {
+		panic("Tried using a closed readOnlyUTXOIteratorWithDiff")
+	}
 	for r.baseIterator.Next() { // keep looping until we reach an outpoint/entry pair that is not in r.diff.toRemove
 		r.currentOutpoint, r.currentUTXOEntry, r.currentErr = r.baseIterator.Get()
 		if !r.diff.mutableUTXODiff.toRemove.containsWithBlueScore(r.currentOutpoint, r.currentUTXOEntry.BlockBlueScore()) {
@@ -77,5 +89,30 @@ func (r *readOnlyUTXOIteratorWithDiff) Next() bool {
 }
 
 func (r *readOnlyUTXOIteratorWithDiff) Get() (outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry, err error) {
+	if r.isClosed {
+		return nil, nil, errors.New("Tried using a closed readOnlyUTXOIteratorWithDiff")
+	}
 	return r.currentOutpoint, r.currentUTXOEntry, r.currentErr
 }
+
+func (r *readOnlyUTXOIteratorWithDiff) Close() error {
+	if r.isClosed {
+		return errors.New("Tried using a closed readOnlyUTXOIteratorWithDiff")
+	}
+	r.isClosed = true
+	err := r.baseIterator.Close()
+	if err != nil {
+		return err
+	}
+	err = r.toAddIterator.Close()
+	if err != nil {
+		return err
+	}
+	r.baseIterator = nil
+	r.diff = nil
+	r.currentOutpoint = nil
+	r.currentUTXOEntry = nil
+	r.currentErr = nil
+	r.toAddIterator = nil
+	return nil
+}
diff --git a/domain/utxoindex/store.go b/domain/utxoindex/store.go
index 2357fae7..3891d6ef 100644
--- a/domain/utxoindex/store.go
+++ b/domain/utxoindex/store.go
@@ -249,6 +249,7 @@ func (uis *utxoIndexStore) getUTXOOutpointEntryPairs(scriptPublicKey *externalap
 	if err != nil {
 		return nil, err
 	}
+	defer cursor.Close()
 	utxoOutpointEntryPairs := make(UTXOOutpointEntryPairs)
 	for cursor.Next() {
 		key, err := cursor.Key()
@@ -297,6 +298,7 @@ func (uis *utxoIndexStore) deleteAll() error {
 	if err != nil {
 		return err
 	}
+	defer cursor.Close()
 	for cursor.Next() {
 		key, err := cursor.Key()
 		if err != nil {
diff --git a/domain/utxoindex/utxoindex.go b/domain/utxoindex/utxoindex.go
index 4fce206e..9d4f5c11 100644
--- a/domain/utxoindex/utxoindex.go
+++ b/domain/utxoindex/utxoindex.go
@@ -131,6 +131,7 @@ func (ui *UTXOIndex) Update(blockInsertionResult *externalapi.BlockInsertionResu
 
 func (ui *UTXOIndex) addUTXOs(toAdd externalapi.UTXOCollection) error {
 	iterator := toAdd.Iterator()
+	defer iterator.Close()
 	for ok := iterator.First(); ok; ok = iterator.Next() {
 		outpoint, entry, err := iterator.Get()
 		if err != nil {
@@ -148,6 +149,7 @@ func (ui *UTXOIndex) addUTXOs(toAdd externalapi.UTXOCollection) error {
 
 func (ui *UTXOIndex) removeUTXOs(toRemove externalapi.UTXOCollection) error {
 	iterator := toRemove.Iterator()
+	defer iterator.Close()
 	for ok := iterator.First(); ok; ok = iterator.Next() {
 		outpoint, entry, err := iterator.Get()
 		if err != nil {
diff --git a/infrastructure/db/database/ldb/cursor.go b/infrastructure/db/database/ldb/cursor.go
index 72c7e885..1cd3747d 100644
--- a/infrastructure/db/database/ldb/cursor.go
+++ b/infrastructure/db/database/ldb/cursor.go
@@ -106,7 +106,8 @@ func (c *LevelDBCursor) Close() error {
 		return errors.New("cannot close an already closed cursor")
 	}
 	c.isClosed = true
-
 	c.ldbIterator.Release()
+	c.ldbIterator = nil
+	c.bucket = nil
 	return nil
 }
diff --git a/infrastructure/network/addressmanager/store.go b/infrastructure/network/addressmanager/store.go
index 49c3cd60..227f84ef 100644
--- a/infrastructure/network/addressmanager/store.go
+++ b/infrastructure/network/addressmanager/store.go
@@ -43,6 +43,7 @@ func (as *addressStore) restoreNotBannedAddresses() error {
 	if err != nil {
 		return err
 	}
+	defer cursor.Close()
 	for ok := cursor.First(); ok; ok = cursor.Next() {
 		databaseKey, err := cursor.Key()
 		if err != nil {
@@ -66,6 +67,7 @@ func (as *addressStore) restoreBannedAddresses() error {
 	if err != nil {
 		return err
 	}
+	defer cursor.Close()
 	for ok := cursor.First(); ok; ok = cursor.Next() {
 		databaseKey, err := cursor.Key()
 		if err != nil {


Test patch:
diff --git a/domain/consensus/datastructures/consensusstatestore/utxo.go b/domain/consensus/datastructures/consensusstatestore/utxo.go
index 4d017f3b..a01b1ca0 100644
--- a/domain/consensus/datastructures/consensusstatestore/utxo.go
+++ b/domain/consensus/datastructures/consensusstatestore/utxo.go
@@ -37,6 +37,7 @@ func (css *consensusStateStore) commitVirtualUTXODiff(dbTx model.DBTransaction)
 	}
 
 	toRemoveIterator := css.virtualUTXODiffStaging.ToRemove().Iterator()
+	defer toRemoveIterator.Close()
 	for ok := toRemoveIterator.First(); ok; ok = toRemoveIterator.Next() {
 		toRemoveOutpoint, _, err := toRemoveIterator.Get()
 		if err != nil {
@@ -56,6 +57,7 @@ func (css *consensusStateStore) commitVirtualUTXODiff(dbTx model.DBTransaction)
 	}
 
 	toAddIterator := css.virtualUTXODiffStaging.ToAdd().Iterator()
+	defer toAddIterator.Close()
 	for ok := toAddIterator.First(); ok; ok = toAddIterator.Next() {
 		toAddOutpoint, toAddEntry, err := toAddIterator.Get()
 		if err != nil {
@@ -156,6 +158,7 @@ func (css *consensusStateStore) VirtualUTXOs(dbContext model.DBReader,
 	if err != nil {
 		return nil, err
 	}
+	defer cursor.Close()
 
 	if fromOutpoint != nil {
 		serializedFromOutpoint, err := serializeOutpoint(fromOutpoint)
@@ -170,6 +173,7 @@ func (css *consensusStateStore) VirtualUTXOs(dbContext model.DBReader,
 	}
 
 	iterator := newCursorUTXOSetIterator(cursor)
+	defer iterator.Close()
 
 	outpointAndUTXOEntryPairs := make([]*externalapi.OutpointAndUTXOEntryPair, 0, limit)
 	for len(outpointAndUTXOEntryPairs) < limit && iterator.Next() {
@@ -200,7 +204,8 @@ func (css *consensusStateStore) VirtualUTXOSetIterator(dbContext model.DBReader)
 }
 
 type utxoSetIterator struct {
-	cursor model.DBCursor
+	cursor   model.DBCursor
+	isClosed bool
 }
 
 func newCursorUTXOSetIterator(cursor model.DBCursor) externalapi.ReadOnlyUTXOSetIterator {
@@ -208,14 +213,23 @@ func newCursorUTXOSetIterator(cursor model.DBCursor) externalapi.ReadOnlyUTXOSet
 }
 
 func (u utxoSetIterator) First() bool {
+	if u.isClosed {
+		panic("Tried using a closed utxoSetIterator")
+	}
 	return u.cursor.First()
 }
 
 func (u utxoSetIterator) Next() bool {
+	if u.isClosed {
+		panic("Tried using a closed utxoSetIterator")
+	}
 	return u.cursor.Next()
 }
 
 func (u utxoSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry, err error) {
+	if u.isClosed {
+		return nil, nil, errors.New("Tried using a closed utxoSetIterator")
+	}
 	key, err := u.cursor.Key()
 	if err != nil {
 		panic(err)
@@ -238,3 +252,16 @@ func (u utxoSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoEntry
 
 	return outpoint, utxoEntry, nil
 }
+
+func (u utxoSetIterator) Close() error {
+	if u.isClosed {
+		return errors.New("Tried using a closed utxoSetIterator")
+	}
+	u.isClosed = true
+	err := u.cursor.Close()
+	if err != nil {
+		return err
+	}
+	u.cursor = nil
+	return nil
+}
diff --git a/domain/consensus/datastructures/consensusstatestore/virtual_utxo_set.go b/domain/consensus/datastructures/consensusstatestore/virtual_utxo_set.go
index 0fed8c4a..aa9bf1c5 100644
--- a/domain/consensus/datastructures/consensusstatestore/virtual_utxo_set.go
+++ b/domain/consensus/datastructures/consensusstatestore/virtual_utxo_set.go
@@ -45,6 +45,7 @@ func (css *consensusStateStore) ImportPruningPointUTXOSetIntoVirtualUTXOSet(dbCo
 	if err != nil {
 		return err
 	}
+	defer deleteCursor.Close()
 	for ok := deleteCursor.First(); ok; ok = deleteCursor.Next() {
 		key, err := deleteCursor.Key()
 		if err != nil {
diff --git a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo_test.go b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo_test.go
index 82989d96..410c84b1 100644
--- a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo_test.go
+++ b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo_test.go
@@ -86,6 +86,7 @@ func checkBlockUTXOCommitment(t *testing.T, consensus testapi.TestConsensus, blo
 	if err != nil {
 		t.Fatalf("Error restoring past UTXO of block %s: %+v", blockName, err)
 	}
+	defer utxoSetIterator.Close()
 
 	// Build a Multiset
 	ms := multiset.New()
diff --git a/domain/consensus/test_consensus_render_to_dot.go b/domain/consensus/test_consensus_render_to_dot.go
index 16f078f8..72e20d76 100644
--- a/domain/consensus/test_consensus_render_to_dot.go
+++ b/domain/consensus/test_consensus_render_to_dot.go
@@ -26,6 +26,7 @@ func (tc *testConsensus) convertToDot() (string, error) {
 	if err != nil {
 		return "", err
 	}
+	defer blocksIterator.Close()
 
 	for ok := blocksIterator.First(); ok; ok = blocksIterator.Next() {
 		hash, err := blocksIterator.Get()