kaspad/domain/utxoindex/utxoindex.go
Ori Newman b8ca33d91d
Add selected chain store and optimize block locator with it (#1394)
* Add selected chain store and optimize block locator with it

* Fix build error

* Fix comments

* Fix IsStaged

* Rename CalculateSelectedParentChainChanges to CalculateChainPath and SelectedParentChainChanges->SelectedChainPath

* Use binary.LittleEndian directly to allow compiler optimizations

* Remove boolean from HeadersSelectedChainStore interface

* Prevent endless loop in block locator
2021-01-11 15:51:45 +02:00

177 lines
5.6 KiB
Go

package utxoindex
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"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
func New(consensus externalapi.Consensus, database database.Database) *UTXOIndex {
store := newUTXOIndexStore(database)
return &UTXOIndex{
consensus: consensus,
store: store,
}
}
// Update updates the UTXO index with the given DAG selected parent chain changes
func (ui *UTXOIndex) Update(chainChanges *externalapi.SelectedChainPath) (*UTXOChanges, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "UTXOIndex.Update")
defer onEnd()
ui.mutex.Lock()
defer ui.mutex.Unlock()
log.Tracef("Updating UTXO index with chainChanges: %+v", chainChanges)
for _, removedBlockHash := range chainChanges.Removed {
err := ui.removeBlock(removedBlockHash)
if err != nil {
return nil, err
}
}
for _, addedBlockHash := range chainChanges.Added {
err := ui.addBlock(addedBlockHash)
if err != nil {
return nil, err
}
}
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) addBlock(blockHash *externalapi.DomainHash) error {
log.Tracef("Adding block %s to UTXO index", blockHash)
acceptanceData, err := ui.consensus.GetBlockAcceptanceData(blockHash)
if err != nil {
return err
}
blockInfo, err := ui.consensus.GetBlockInfo(blockHash)
if err != nil {
return err
}
for _, blockAcceptanceData := range acceptanceData {
for _, transactionAcceptanceData := range blockAcceptanceData.TransactionAcceptanceData {
if !transactionAcceptanceData.IsAccepted {
continue
}
err := ui.addTransaction(transactionAcceptanceData.Transaction,
transactionAcceptanceData.TransactionInputUTXOEntries, blockInfo.BlueScore)
if err != nil {
return err
}
}
}
return nil
}
func (ui *UTXOIndex) removeBlock(blockHash *externalapi.DomainHash) error {
log.Tracef("Removing block %s from UTXO index", blockHash)
acceptanceData, err := ui.consensus.GetBlockAcceptanceData(blockHash)
if err != nil {
return err
}
for _, blockAcceptanceData := range acceptanceData {
for _, transactionAcceptanceData := range blockAcceptanceData.TransactionAcceptanceData {
if !transactionAcceptanceData.IsAccepted {
continue
}
err := ui.removeTransaction(transactionAcceptanceData.Transaction,
transactionAcceptanceData.TransactionInputUTXOEntries)
if err != nil {
return err
}
}
}
return nil
}
func (ui *UTXOIndex) addTransaction(transaction *externalapi.DomainTransaction,
transactionInputUTXOEntries []externalapi.UTXOEntry, blockBlueScore uint64) error {
transactionID := consensushashing.TransactionID(transaction)
log.Tracef("Adding transaction %s to UTXO index", transactionID)
isCoinbase := transactionhelper.IsCoinBase(transaction)
for i, transactionInput := range transaction.Inputs {
log.Tracef("Removing outpoint %s:%d from UTXO index",
transactionInput.PreviousOutpoint.TransactionID, transactionInput.PreviousOutpoint.Index)
inputUTXOEntry := transactionInputUTXOEntries[i]
err := ui.store.remove(inputUTXOEntry.ScriptPublicKey(), &transactionInput.PreviousOutpoint)
if err != nil {
return err
}
}
for index, transactionOutput := range transaction.Outputs {
log.Tracef("Adding outpoint %s:%d to UTXO index", transactionID, index)
outpoint := externalapi.NewDomainOutpoint(transactionID, uint32(index))
utxoEntry := utxo.NewUTXOEntry(transactionOutput.Value, transactionOutput.ScriptPublicKey, isCoinbase, blockBlueScore)
err := ui.store.add(transactionOutput.ScriptPublicKey, outpoint, utxoEntry)
if err != nil {
return err
}
}
return nil
}
func (ui *UTXOIndex) removeTransaction(transaction *externalapi.DomainTransaction,
transactionInputUTXOEntries []externalapi.UTXOEntry) error {
transactionID := consensushashing.TransactionID(transaction)
log.Tracef("Removing transaction %s from UTXO index", transactionID)
for index, transactionOutput := range transaction.Outputs {
log.Tracef("Removing outpoint %s:%d from UTXO index", transactionID, index)
outpoint := externalapi.NewDomainOutpoint(transactionID, uint32(index))
err := ui.store.remove(transactionOutput.ScriptPublicKey, outpoint)
if err != nil {
return err
}
}
for i, transactionInput := range transaction.Inputs {
log.Tracef("Adding outpoint %s:%d to UTXO index",
transactionInput.PreviousOutpoint.TransactionID, transactionInput.PreviousOutpoint.Index)
inputUTXOEntry := transactionInputUTXOEntries[i]
err := ui.store.add(inputUTXOEntry.ScriptPublicKey(), &transactionInput.PreviousOutpoint, transactionInput.UTXOEntry)
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)
}