kaspad/domain/utxoindex/utxoindex.go
Elichai Turkel 6a12428504
Close iterators (#1542)
* Add Close() function to all the iterators

* Add defer iterator.Close() whenever we open an iterator

* Add isClosed to all iterators and panic/return error if used after closing

Co-authored-by: Svarog <feanorr@gmail.com>
2021-03-01 11:15:59 +02:00

178 lines
4.3 KiB
Go

package utxoindex
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/infrastructure/db/database"
"github.com/kaspanet/kaspad/infrastructure/logger"
"sync"
)
// UTXOIndex maintains an index between transaction scriptPublicKeys
// and UTXOs
type UTXOIndex struct {
consensus externalapi.Consensus
store *utxoIndexStore
mutex sync.Mutex
}
// New creates a new UTXO index.
//
// NOTE: While this is called no new blocks can be added to the consensus.
func New(consensus externalapi.Consensus, database database.Database) (*UTXOIndex, error) {
utxoIndex := &UTXOIndex{
consensus: consensus,
store: newUTXOIndexStore(database),
}
isSynced, err := utxoIndex.isSynced()
if err != nil {
return nil, err
}
if !isSynced {
err = utxoIndex.Reset()
if err != nil {
return nil, err
}
}
return utxoIndex, nil
}
// Reset deletes the whole UTXO index and resyncs it from consensus.
func (ui *UTXOIndex) Reset() error {
err := ui.store.deleteAll()
if err != nil {
return err
}
virtualInfo, err := ui.consensus.GetVirtualInfo()
if err != nil {
return err
}
var fromOutpoint *externalapi.DomainOutpoint
for {
const step = 1000
virtualUTXOs, err := ui.consensus.GetVirtualUTXOs(virtualInfo.ParentHashes, fromOutpoint, step)
if err != nil {
return err
}
err = ui.store.addAndCommitOutpointsWithoutTransaction(virtualUTXOs)
if err != nil {
return err
}
if len(virtualUTXOs) < step {
break
}
fromOutpoint = virtualUTXOs[len(virtualUTXOs)-1].Outpoint
}
// This has to be done last to mark that the reset went smoothly and no reset has to be called next time.
return ui.store.updateAndCommitVirtualParentsWithoutTransaction(virtualInfo.ParentHashes)
}
func (ui *UTXOIndex) isSynced() (bool, error) {
utxoIndexVirtualParents, err := ui.store.getVirtualParents()
if err != nil {
if database.IsNotFoundError(err) {
return false, nil
}
return false, err
}
virtualInfo, err := ui.consensus.GetVirtualInfo()
if err != nil {
return false, err
}
return externalapi.HashesEqual(virtualInfo.ParentHashes, utxoIndexVirtualParents), nil
}
// Update updates the UTXO index with the given DAG selected parent chain changes
func (ui *UTXOIndex) Update(blockInsertionResult *externalapi.BlockInsertionResult) (*UTXOChanges, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "UTXOIndex.Update")
defer onEnd()
ui.mutex.Lock()
defer ui.mutex.Unlock()
log.Tracef("Updating UTXO index with VirtualUTXODiff: %+v", blockInsertionResult.VirtualUTXODiff)
err := ui.removeUTXOs(blockInsertionResult.VirtualUTXODiff.ToRemove())
if err != nil {
return nil, err
}
err = ui.addUTXOs(blockInsertionResult.VirtualUTXODiff.ToAdd())
if err != nil {
return nil, err
}
ui.store.updateVirtualParents(blockInsertionResult.VirtualParents)
added, removed, _ := ui.store.stagedData()
utxoIndexChanges := &UTXOChanges{
Added: added,
Removed: removed,
}
err = ui.store.commit()
if err != nil {
return nil, err
}
log.Tracef("UTXO index updated with the UTXOChanged: %+v", utxoIndexChanges)
return utxoIndexChanges, nil
}
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 {
return err
}
log.Tracef("Adding outpoint %s to UTXO index", outpoint)
err = ui.store.add(entry.ScriptPublicKey(), outpoint, entry)
if err != nil {
return err
}
}
return nil
}
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 {
return err
}
log.Tracef("Removing outpoint %s from UTXO index", outpoint)
err = ui.store.remove(entry.ScriptPublicKey(), outpoint)
if err != nil {
return err
}
}
return nil
}
// UTXOs returns all the UTXOs for the given scriptPublicKey
func (ui *UTXOIndex) UTXOs(scriptPublicKey *externalapi.ScriptPublicKey) (UTXOOutpointEntryPairs, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "UTXOIndex.UTXOs")
defer onEnd()
ui.mutex.Lock()
defer ui.mutex.Unlock()
return ui.store.getUTXOOutpointEntryPairs(scriptPublicKey)
}