Svarog 950dd0cc8d
[NOD-1556] Add some logs (#1110)
* [NOD-1556] Add logs regarding block status and virtual blue score

* [NOD-1556] UTXODiffAlgebra: add the offending outpoint to the text of errors

* [NOD-1556] Make checkIntersectionWithRule return ok as well
2020-11-19 11:17:05 +02:00

254 lines
12 KiB
Go

package utxoalgebra
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/pkg/errors"
)
// checkIntersection checks if there is an intersection between two model.UTXOCollections
func checkIntersection(collection1 model.UTXOCollection, collection2 model.UTXOCollection) bool {
for outpoint := range collection1 {
if CollectionContains(collection2, &outpoint) {
return true
}
}
return false
}
// checkIntersectionWithRule checks if there is an intersection between two model.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 model.UTXOCollection, collection2 model.UTXOCollection,
extraRule func(*externalapi.DomainOutpoint, *externalapi.UTXOEntry, *externalapi.UTXOEntry) bool) (
*externalapi.DomainOutpoint, bool) {
for outpoint, utxoEntry := range collection1 {
if diffEntry, ok := CollectionGet(collection2, &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 model.UTXOCollections
// having same blue score, returns the result and the remainder from collection1
func intersectionWithRemainderHavingBlueScore(collection1, collection2 model.UTXOCollection) (result, remainder model.UTXOCollection) {
result = make(model.UTXOCollection, minInt(len(collection1), len(collection2)))
remainder = make(model.UTXOCollection, len(collection1))
intersectionWithRemainderHavingBlueScoreInPlace(collection1, collection2, result, remainder)
return
}
// intersectionWithRemainderHavingBlueScoreInPlace calculates an intersection between two model.UTXOCollections
// having same blue score, puts it into result and into remainder from collection1
func intersectionWithRemainderHavingBlueScoreInPlace(collection1, collection2, result, remainder model.UTXOCollection) {
for outpoint, utxoEntry := range collection1 {
if collectionContainsWithBlueScore(collection2, &outpoint, utxoEntry.BlockBlueScore) {
collectionAdd(result, &outpoint, utxoEntry)
} else {
collectionAdd(remainder, &outpoint, utxoEntry)
}
}
}
// subtractionHavingBlueScore calculates a subtraction between collection1 and collection2
// having same blue score, returns the result
func subtractionHavingBlueScore(collection1, collection2 model.UTXOCollection) (result model.UTXOCollection) {
result = make(model.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 model.UTXOCollection) {
for outpoint, utxoEntry := range collection1 {
if !collectionContainsWithBlueScore(collection2, &outpoint, utxoEntry.BlockBlueScore) {
collectionAdd(result, &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 model.UTXOCollection) (result, remainder model.UTXOCollection) {
result = make(model.UTXOCollection, len(collection1))
remainder = make(model.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 model.UTXOCollection) {
for outpoint, utxoEntry := range collection1 {
if !collectionContainsWithBlueScore(collection2, &outpoint, utxoEntry.BlockBlueScore) {
collectionAdd(result, &outpoint, utxoEntry)
} else {
collectionAdd(remainder, &outpoint, utxoEntry)
}
}
}
// DiffFrom returns a new utxoDiff with the difference between this utxoDiff and another
// Assumes that:
// Both utxoDiffs are from the same base
// If a txOut exists in both utxoDiffs, 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 *model.UTXODiff) (*model.UTXODiff, 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 &&
(collectionContainsWithBlueScore(this.ToAdd, outpoint, diffEntry.BlockBlueScore) ||
collectionContainsWithBlueScore(other.ToRemove, 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 &&
(collectionContainsWithBlueScore(this.ToRemove, outpoint, diffEntry.BlockBlueScore) ||
collectionContainsWithBlueScore(other.ToAdd, 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 := model.UTXODiff{
ToAdd: make(model.UTXOCollection, len(this.ToRemove)+len(other.ToAdd)),
ToRemove: make(model.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(model.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 *model.UTXODiff, diff *model.UTXODiff) error {
if offendingOutpoint, ok := checkIntersectionWithRule(diff.ToRemove, this.ToRemove,
func(outpoint *externalapi.DomainOutpoint, entryToAdd, existingEntry *externalapi.UTXOEntry) bool {
return !collectionContainsWithBlueScore(this.ToAdd, outpoint, entryToAdd.BlockBlueScore)
}); ok {
return errors.Errorf(
"withDiffInPlace: outpoint %s both in this.ToRemove and in diff.ToRemove", offendingOutpoint)
}
if offendingOutpoint, ok := checkIntersectionWithRule(diff.ToAdd, this.ToAdd,
func(outpoint *externalapi.DomainOutpoint, entryToAdd, existingEntry *externalapi.UTXOEntry) bool {
return !collectionContainsWithBlueScore(diff.ToRemove, outpoint, existingEntry.BlockBlueScore)
}); ok {
return errors.Errorf(
"withDiffInPlace: outpoint %s both in this.ToAdd and in diff.ToAdd", offendingOutpoint)
}
intersection := make(model.UTXOCollection, minInt(len(diff.ToRemove), len(this.ToAdd)))
// If not exists neither in ToAdd nor in ToRemove - add to ToRemove
intersectionWithRemainderHavingBlueScoreInPlace(diff.ToRemove, this.ToAdd, intersection, this.ToRemove)
// If already exists in ToAdd with the same blueScore - remove from ToAdd
collectionRemoveMultiple(this.ToAdd, intersection)
intersection = make(model.UTXOCollection, minInt(len(diff.ToAdd), len(this.ToRemove)))
// If not exists neither in ToAdd nor in ToRemove, or exists in ToRemove with different blueScore - add to ToAdd
intersectionWithRemainderHavingBlueScoreInPlace(diff.ToAdd, this.ToRemove, intersection, this.ToAdd)
// If already exists in ToRemove with the same blueScore - remove from ToRemove
collectionRemoveMultiple(this.ToRemove, intersection)
return nil
}
// WithDiff applies provided diff to this diff, creating a new utxoDiff, that would be the result if
// first d, and than diff were applied to some base
func WithDiff(this *model.UTXODiff, diff *model.UTXODiff) (*model.UTXODiff, error) {
clone := DiffClone(this)
err := WithDiffInPlace(clone, diff)
if err != nil {
return nil, err
}
return clone, nil
}