kaspad/domain/consensus/utils/utxo/diff_algebra.go
Svarog a585f32763
[NOD-1551] Make UTXODiff immutable + skip cloning it in datastore (#1167)
* [NOD-1551] Make UTXO-Diff implemented fully in utils/utxo

* [NOD-1551] Fixes everywhere except database

* [NOD-1551] Fix database

* [NOD-1551] Add comments

* [NOD-1551] Partial commit

* [NOD-1551] Comlete making UTXOEntry immutable + don't clone it in UTXOCollectionClone

* [NOD-1551] Rename ToUnmutable -> ToImmutable

* [NOD-1551] Track immutable references generated from mutable UTXODiff, and invalidate them if the mutable one changed

* [NOD-1551] Clone scriptPubKey in NewUTXOEntry

* [NOD-1551] Remove redundant code

* [NOD-1551] Remove redundant call for .CloneMutable and then .ToImmutable

* [NOD-1551] Make utxoEntry pointert-receiver + clone ScriptPubKey in getter
2020-12-03 13:24:24 +02:00

253 lines
11 KiB
Go

package utxo
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/pkg/errors"
)
// checkIntersection checks if there is an intersection between two utxoCollections
func checkIntersection(collection1 utxoCollection, collection2 utxoCollection) bool {
for outpoint := range collection1 {
if collection2.Contains(&outpoint) {
return true
}
}
return false
}
// checkIntersectionWithRule checks if there is an intersection between two utxoCollections satisfying arbitrary rule
// returns the first outpoint in the two collections' intersection satsifying the rule, and a boolean indicating whether
// such outpoint exists
func checkIntersectionWithRule(collection1 utxoCollection, collection2 utxoCollection,
extraRule func(*externalapi.DomainOutpoint, externalapi.UTXOEntry, externalapi.UTXOEntry) bool) (
*externalapi.DomainOutpoint, bool) {
for outpoint, utxoEntry := range collection1 {
if diffEntry, ok := collection2.Get(&outpoint); ok {
if extraRule(&outpoint, utxoEntry, diffEntry) {
return &outpoint, true
}
}
}
return nil, false
}
// minInt returns the smaller of x or y integer values
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
// intersectionWithRemainderHavingBlueScore calculates an intersection between two utxoCollections
// having same blue score, returns the result and the remainder from collection1
func intersectionWithRemainderHavingBlueScore(collection1, collection2 utxoCollection) (result, remainder utxoCollection) {
result = make(utxoCollection, minInt(len(collection1), len(collection2)))
remainder = make(utxoCollection, len(collection1))
intersectionWithRemainderHavingBlueScoreInPlace(collection1, collection2, result, remainder)
return
}
// intersectionWithRemainderHavingBlueScoreInPlace calculates an intersection between two utxoCollections
// having same blue score, puts it into result and into remainder from collection1
func intersectionWithRemainderHavingBlueScoreInPlace(collection1, collection2, result, remainder utxoCollection) {
for outpoint, utxoEntry := range collection1 {
if collection2.containsWithBlueScore(&outpoint, utxoEntry.BlockBlueScore()) {
result.add(&outpoint, utxoEntry)
} else {
remainder.add(&outpoint, utxoEntry)
}
}
}
// subtractionHavingBlueScore calculates a subtraction between collection1 and collection2
// having same blue score, returns the result
func subtractionHavingBlueScore(collection1, collection2 utxoCollection) (result utxoCollection) {
result = make(utxoCollection, len(collection1))
subtractionHavingBlueScoreInPlace(collection1, collection2, result)
return
}
// subtractionHavingBlueScoreInPlace calculates a subtraction between collection1 and collection2
// having same blue score, puts it into result
func subtractionHavingBlueScoreInPlace(collection1, collection2, result utxoCollection) {
for outpoint, utxoEntry := range collection1 {
if !collection2.containsWithBlueScore(&outpoint, utxoEntry.BlockBlueScore()) {
result.add(&outpoint, utxoEntry)
}
}
}
// subtractionWithRemainderHavingBlueScore calculates a subtraction between collection1 and collection2
// having same blue score, returns the result and the remainder from collection1
func subtractionWithRemainderHavingBlueScore(collection1, collection2 utxoCollection) (result, remainder utxoCollection) {
result = make(utxoCollection, len(collection1))
remainder = make(utxoCollection, len(collection1))
subtractionWithRemainderHavingBlueScoreInPlace(collection1, collection2, result, remainder)
return
}
// subtractionWithRemainderHavingBlueScoreInPlace calculates a subtraction between collection1 and collection2
// having same blue score, puts it into result and into remainder from collection1
func subtractionWithRemainderHavingBlueScoreInPlace(collection1, collection2, result, remainder utxoCollection) {
for outpoint, utxoEntry := range collection1 {
if !collection2.containsWithBlueScore(&outpoint, utxoEntry.BlockBlueScore()) {
result.add(&outpoint, utxoEntry)
} else {
remainder.add(&outpoint, utxoEntry)
}
}
}
// DiffFrom returns a new mutableUTXODiff with the difference between this mutableUTXODiff and another
// Assumes that:
// Both mutableUTXODiffs are from the same base
// If a txOut exists in both mutableUTXODiffs, its underlying values would be the same
//
// diffFrom follows a set of rules represented by the following 3 by 3 table:
//
// | | this | |
// ---------+-----------+-----------+-----------+-----------
// | | toAdd | toRemove | None
// ---------+-----------+-----------+-----------+-----------
// other | toAdd | - | X | toAdd
// ---------+-----------+-----------+-----------+-----------
// | toRemove | X | - | toRemove
// ---------+-----------+-----------+-----------+-----------
// | None | toRemove | toAdd | -
//
// Key:
// - Don't add anything to the result
// X Return an error
// toAdd Add the UTXO into the toAdd collection of the result
// toRemove Add the UTXO into the toRemove collection of the result
//
// Examples:
// 1. This diff contains a UTXO in toAdd, and the other diff contains it in toRemove
// diffFrom results in an error
// 2. This diff contains a UTXO in toRemove, and the other diff does not contain it
// diffFrom results in the UTXO being added to toAdd
func diffFrom(this, other *mutableUTXODiff) (*mutableUTXODiff, error) {
// Note that the following cases are not accounted for, as they are impossible
// as long as the base utxoSet is the same:
// - if utxoEntry is in this.toAdd and other.toRemove
// - if utxoEntry is in this.toRemove and other.toAdd
// check that NOT (entries with unequal blue scores AND utxoEntry is in this.toAdd and/or other.toRemove) -> Error
isNotAddedOutputRemovedWithBlueScore := func(outpoint *externalapi.DomainOutpoint, utxoEntry, diffEntry externalapi.UTXOEntry) bool {
return !(diffEntry.BlockBlueScore() != utxoEntry.BlockBlueScore() &&
(this.toAdd.containsWithBlueScore(outpoint, diffEntry.BlockBlueScore()) ||
other.toRemove.containsWithBlueScore(outpoint, utxoEntry.BlockBlueScore())))
}
if offendingOutpoint, ok :=
checkIntersectionWithRule(this.toRemove, other.toAdd, isNotAddedOutputRemovedWithBlueScore); ok {
return nil, errors.Errorf("diffFrom: outpoint %s both in this.toAdd and in other.toRemove", offendingOutpoint)
}
//check that NOT (entries with unequal blue score AND utxoEntry is in this.toRemove and/or other.toAdd) -> Error
isNotRemovedOutputAddedWithBlueScore :=
func(outpoint *externalapi.DomainOutpoint, utxoEntry, diffEntry externalapi.UTXOEntry) bool {
return !(diffEntry.BlockBlueScore() != utxoEntry.BlockBlueScore() &&
(this.toRemove.containsWithBlueScore(outpoint, diffEntry.BlockBlueScore()) ||
other.toAdd.containsWithBlueScore(outpoint, utxoEntry.BlockBlueScore())))
}
if offendingOutpoint, ok :=
checkIntersectionWithRule(this.toAdd, other.toRemove, isNotRemovedOutputAddedWithBlueScore); ok {
return nil, errors.Errorf("diffFrom: outpoint %s both in this.toRemove and in other.toAdd", offendingOutpoint)
}
// if have the same entry in this.toRemove and other.toRemove
// and existing entry is with different blue score, in this case - this is an error
if offendingOutpoint, ok := checkIntersectionWithRule(this.toRemove, other.toRemove,
func(outpoint *externalapi.DomainOutpoint, utxoEntry, diffEntry externalapi.UTXOEntry) bool {
return utxoEntry.BlockBlueScore() != diffEntry.BlockBlueScore()
}); ok {
return nil, errors.Errorf("diffFrom: outpoint %s both in this.toRemove and other.toRemove with different "+
"blue scores, with no corresponding entry in this.toAdd", offendingOutpoint)
}
result := &mutableUTXODiff{
toAdd: make(utxoCollection, len(this.toRemove)+len(other.toAdd)),
toRemove: make(utxoCollection, len(this.toAdd)+len(other.toRemove)),
}
// All transactions in this.toAdd:
// If they are not in other.toAdd - should be added in result.toRemove
inBothToAdd := make(utxoCollection, len(this.toAdd))
subtractionWithRemainderHavingBlueScoreInPlace(this.toAdd, other.toAdd, result.toRemove, inBothToAdd)
// If they are in other.toRemove - base utxoSet is not the same
if checkIntersection(inBothToAdd, this.toRemove) != checkIntersection(inBothToAdd, other.toRemove) {
return nil, errors.New(
"diffFrom: outpoint both in this.toAdd, other.toAdd, and only one of this.toRemove and other.toRemove")
}
// All transactions in other.toRemove:
// If they are not in this.toRemove - should be added in result.toRemove
subtractionHavingBlueScoreInPlace(other.toRemove, this.toRemove, result.toRemove)
// All transactions in this.toRemove:
// If they are not in other.toRemove - should be added in result.toAdd
subtractionHavingBlueScoreInPlace(this.toRemove, other.toRemove, result.toAdd)
// All transactions in other.toAdd:
// If they are not in this.toAdd - should be added in result.toAdd
subtractionHavingBlueScoreInPlace(other.toAdd, this.toAdd, result.toAdd)
return result, nil
}
// WithDiffInPlace applies provided diff to this diff in-place, that would be the result if
// first d, and than diff were applied to the same base
func withDiffInPlace(this *mutableUTXODiff, other *mutableUTXODiff) error {
if offendingOutpoint, ok := checkIntersectionWithRule(other.toRemove, this.toRemove,
func(outpoint *externalapi.DomainOutpoint, entryToAdd, existingEntry externalapi.UTXOEntry) bool {
return !this.toAdd.containsWithBlueScore(outpoint, entryToAdd.BlockBlueScore())
}); ok {
return errors.Errorf(
"withDiffInPlace: outpoint %s both in this.toRemove and in other.toRemove", offendingOutpoint)
}
if offendingOutpoint, ok := checkIntersectionWithRule(other.toAdd, this.toAdd,
func(outpoint *externalapi.DomainOutpoint, entryToAdd, existingEntry externalapi.UTXOEntry) bool {
return !other.toRemove.containsWithBlueScore(outpoint, existingEntry.BlockBlueScore())
}); ok {
return errors.Errorf(
"withDiffInPlace: outpoint %s both in this.toAdd and in other.toAdd", offendingOutpoint)
}
intersection := make(utxoCollection, minInt(len(other.toRemove), len(this.toAdd)))
// If not exists neither in toAdd nor in toRemove - add to toRemove
intersectionWithRemainderHavingBlueScoreInPlace(other.toRemove, this.toAdd, intersection, this.toRemove)
// If already exists in toAdd with the same blueScore - remove from toAdd
this.toAdd.removeMultiple(intersection)
intersection = make(utxoCollection, minInt(len(other.toAdd), len(this.toRemove)))
// If not exists neither in toAdd nor in toRemove, or exists in toRemove with different blueScore - add to toAdd
intersectionWithRemainderHavingBlueScoreInPlace(other.toAdd, this.toRemove, intersection, this.toAdd)
// If already exists in toRemove with the same blueScore - remove from toRemove
this.toRemove.removeMultiple(intersection)
return nil
}
// WithDiff applies provided diff to this diff, creating a new mutableUTXODiff, that would be the result if
// first d, and than diff were applied to some base
func withDiff(this *mutableUTXODiff, diff *mutableUTXODiff) (*mutableUTXODiff, error) {
clone := this.clone()
err := withDiffInPlace(clone, diff)
if err != nil {
return nil, err
}
return clone, nil
}