From f6eb9edecff19676080e10b672daec8a5edc779c Mon Sep 17 00:00:00 2001 From: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com> Date: Wed, 8 Aug 2018 11:06:09 +0300 Subject: [PATCH] [DEV-74] Added infrastructure for Diff-based UTXOs (#39) * [DEV-74] Implemented and written tests for utxoIterator. * [DEV-74] Improved utxoIterator tests. * [DEV-74] Implemented utxoCollection tests. * [DEV-74] Implemented utxoDiff and its tests. * [DEV-74] Implemented utxoSet. * [DEV -74] Added tests for fullUTXOSet. * [DEV-74] Added some tests for diffUTXOSet. * [DEV-74] Wrote tests for diffUTXOSet iterator. * [DEV-74] Added a negative test for diffUTXOSet.withDiff. * [DEV-74] Wrote tests for addTx. * [DEV-74] Wrote a toRemove test for addTx. * [DEV-74] Changed blockNode.utxoDiff to be of type utxoDiff. * [DEV-74] Removed superfluous whitespace. * [DEV-74] Renamed confusing "previousHash" to "hash". * [DEV-74] Fixed bad test and corrected failing test. * [DEV-74] Moved confusing "negatives" test to be part of the general utxoCollection test. * [DEV-74] Removed utxoDiff.inverted. * [DEV-74] Renamed blockNode.utxoDiff to blockNode.diff. * [DEV-74] Renamed diff to diffFrom for clarity's sake. * [DEV-74] Converted the type of utxoCollection from map[daghash.Hash]map[uint32]*wire.TxOut to map[wire.OutPoint]*UtxoEntry. * [DEV-74] Corrected test names in utxoCollection_test. * [DEV-74] Removed superfluous utxoCollection iterator and moved utxoIterator into utxoset.go. * [DEV-74] Renamed variables in utxoset.go. * [DEV-74] Renamed verifyTx to areInputsInUTXO and removed a superfulous test. * [DEV-74] Fixed bad test logic in TestDiffUTXOSet_addTx. * [DEV-74] Added a few comments. Added reference-equals checks to clone functions. * [DEV-74] Moved utxoCollection and utxoDiff into utxoset.go. * [DEV-74] Wrote explanations for utxoCollection and utxoDiff tests. * [DEV-74] Wrote explanations for all utxoSet tests besides addTx. * [DEV-74] Wrote explanations for TestDiffUTXOSet_addTx. * [DEV-74] Moved the documentation for utxoDiff into utxoset.go. * [DEV-74] Wrote an explanation on utxoSet. * [DEV-74] Moved diffChild next to diff, improved their comments, moved the explanations for diffFrom and withDiff to the appropriate methods, got rid of utxoIterator, and renamed areInputsInUTXO to containsInputs. * [DEV-74] Replaced boring old-fashioned reference equality with special, fancy reference equality for maps, slices, and channels. * [DEV-74] Wrote additional explanations for test cases. --- blockdag/blockindex.go | 17 +- blockdag/utxoset.go | 434 ++++++++++++++++++++++ blockdag/utxoset_test.go | 784 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1227 insertions(+), 8 deletions(-) create mode 100644 blockdag/utxoset.go create mode 100644 blockdag/utxoset_test.go diff --git a/blockdag/blockindex.go b/blockdag/blockindex.go index 4a561ae9c..5b0d315e1 100644 --- a/blockdag/blockindex.go +++ b/blockdag/blockindex.go @@ -61,9 +61,8 @@ func (status blockStatus) KnownInvalid() bool { return status&(statusValidateFailed|statusInvalidAncestor) != 0 } -// blockNode represents a block within the block chain and is primarily used to -// aid in selecting the best chain to be the main chain. The main chain is -// stored into the block database. +// blockNode represents a block within the block DAG. The DAG is stored into +// the block database. type blockNode struct { // NOTE: Additions, deletions, or modifications to the order of the // definitions in this struct should not be changed without considering @@ -82,17 +81,19 @@ type blockNode struct { // children are all the blocks that refer to this block as a parent children blockSet - // diffChild is the child that UTXODiff will be built from - diffChild *blockNode - // blues are all blue blocks in this block's worldview that are in its selected parent anticone blues []*blockNode // blueScore is the count of all the blue blocks in this block's past blueScore uint64 - // utxoDiff is the UTXO of the block represented as a diff to the virtual block - utxoDiff UtxoViewpoint + // diff is the UTXO representation of the block + // A block's UTXO is reconstituted by applying diffWith on every block in the chain of diffChildren + // from the virtual block down to the block. See diffChild + diff utxoDiff + + // diffChild is the child that diff will be built from. See diff + diffChild *blockNode // hash is the double sha 256 of the block. hash daghash.Hash diff --git a/blockdag/utxoset.go b/blockdag/utxoset.go new file mode 100644 index 000000000..997b8d278 --- /dev/null +++ b/blockdag/utxoset.go @@ -0,0 +1,434 @@ +package blockdag + +import ( + "fmt" + "github.com/daglabs/btcd/wire" + "errors" + "sort" + "strings" +) + +// utxoCollection represents a set of UTXOs indexed by their outPoints +type utxoCollection map[wire.OutPoint]*UtxoEntry + +func (uc utxoCollection) String() string { + utxoStrings := make([]string, len(uc)) + + i := 0 + for outPoint, utxoEntry := range uc { + utxoStrings[i] = fmt.Sprintf("(%s, %d) => %d", outPoint.Hash, outPoint.Index, utxoEntry.amount) + i++ + } + + // Sort strings for determinism. + sort.Strings(utxoStrings) + + return fmt.Sprintf("[ %s ]", strings.Join(utxoStrings, ", ")) +} + +// clone returns a clone of this collection +func (uc utxoCollection) clone() utxoCollection { + clone := utxoCollection{} + for outPoint, entry := range uc { + clone[outPoint] = entry + } + + return clone +} + +// utxoDiff represents a diff between two UTXO Sets. +type utxoDiff struct { + toAdd utxoCollection + toRemove utxoCollection +} + +// newUTXODiff creates a new, empty utxoDiff +func newUTXODiff() *utxoDiff { + return &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + } +} + +// 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 (d *utxoDiff) diffFrom(other *utxoDiff) (*utxoDiff, error) { + result := newUTXODiff() + + // 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 d.toAdd and other.toRemove + // - if utxoEntry is in d.toRemove and other.toAdd + + // All transactions in d.toAdd: + // If they are not in other.toAdd - should be added in result.toRemove + // If they are in other.toRemove - base utxoSet is not the same + for outPoint, utxoEntry := range d.toAdd { + if _, ok := other.toAdd[outPoint]; !ok { + result.toRemove[outPoint] = utxoEntry + } + if _, ok := other.toRemove[outPoint]; ok { + return nil, errors.New("diffFrom: transaction both in d.toAdd and in other.toRemove") + } + } + + // All transactions in d.toRemove: + // If they are not in other.toRemove - should be added in result.toAdd + // If they are in other.toAdd - base utxoSet is not the same + for outPoint, utxoEntry := range d.toRemove { + if _, ok := other.toRemove[outPoint]; !ok { + result.toAdd[outPoint] = utxoEntry + } + if _, ok := other.toAdd[outPoint]; ok { + return nil, errors.New("diffFrom: transaction both in d.toRemove and in other.toAdd") + } + } + + // All transactions in other.toAdd: + // If they are not in d.toAdd - should be added in result.toAdd + for outPoint, utxoEntry := range other.toAdd { + if _, ok := d.toAdd[outPoint]; !ok { + result.toAdd[outPoint] = utxoEntry + } + } + + // All transactions in other.toRemove: + // If they are not in d.toRemove - should be added in result.toRemove + for outPoint, utxoEntry := range other.toRemove { + if _, ok := d.toRemove[outPoint]; !ok { + result.toRemove[outPoint] = utxoEntry + } + } + + return result, 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 the same base +// +// withDiff 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 | toAdd | toRemove | - +// +// 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 +// withDiff results in nothing being added +// 2. This diff contains a UTXO in toRemove, and the other diff does not contain it +// withDiff results in the UTXO being added to toRemove +func (d *utxoDiff) withDiff(diff *utxoDiff) (*utxoDiff, error) { + result := newUTXODiff() + + // All transactions in d.toAdd: + // If they are not in diff.toRemove - should be added in result.toAdd + // If they are in diff.toAdd - should throw an error + // Otherwise - should be ignored + for outPoint, utxoEntry := range d.toAdd { + if _, ok := diff.toRemove[outPoint]; !ok { + result.toAdd[outPoint] = utxoEntry + } + if _, ok := diff.toAdd[outPoint]; ok { + return nil, errors.New("withDiff: transaction both in d.toAdd and in other.toAdd") + } + } + + // All transactions in d.toRemove: + // If they are not in diff.toAdd - should be added in result.toRemove + // If they are in diff.toRemove - should throw an error + // Otherwise - should be ignored + for outPoint, utxoEntry := range d.toRemove { + if _, ok := diff.toAdd[outPoint]; !ok { + result.toRemove[outPoint] = utxoEntry + } + if _, ok := diff.toRemove[outPoint]; ok { + return nil, errors.New("withDiff: transaction both in d.toRemove and in other.toRemove") + } + } + + // All transactions in diff.toAdd: + // If they are not in d.toRemove - should be added in result.toAdd + for outPoint, utxoEntry := range diff.toAdd { + if _, ok := d.toRemove[outPoint]; !ok { + result.toAdd[outPoint] = utxoEntry + } + } + + // All transactions in diff.toRemove: + // If they are not in d.toAdd - should be added in result.toRemove + for outPoint, utxoEntry := range diff.toRemove { + if _, ok := d.toAdd[outPoint]; !ok { + result.toRemove[outPoint] = utxoEntry + } + } + + return result, nil +} + +// clone returns a clone of this utxoDiff +func (d *utxoDiff) clone() *utxoDiff { + return &utxoDiff{ + toAdd: d.toAdd.clone(), + toRemove: d.toRemove.clone(), + } +} + +func (d utxoDiff) String() string { + return fmt.Sprintf("toAdd: %s; toRemove: %s", d.toAdd, d.toRemove) +} + +// newUTXOEntry creates a new utxoEntry representing the given txOut +func newUTXOEntry(txOut *wire.TxOut) *UtxoEntry { + entry := new(UtxoEntry) + entry.amount = txOut.Value + entry.pkScript = txOut.PkScript + + return entry +} + +// utxoSet represents a set of unspent transaction outputs +// Every DAG has exactly one fullUTXOSet. +// When a new block arrives, it is validated and applied to the fullUTXOSet in the following manner: +// 1. Get the block's PastUTXO: +// 2. Add all the block's transactions to the block's PastUTXO +// 3. For each of the block's parents, +// 3.1. Rebuild their utxoDiff +// 3.2. Set the block as their diffChild +// 4. Create and initialize a new virtual block +// 5. Get the new virtual's PastUTXO +// 6. Rebuild the utxoDiff for all the tips +// 7. Convert (meld) the new virtual's diffUTXOSet into a fullUTXOSet. This updates the DAG's fullUTXOSet +type utxoSet interface { + fmt.Stringer + diffFrom(other utxoSet) (*utxoDiff, error) + withDiff(utxoDiff *utxoDiff) (utxoSet, error) + addTx(tx *wire.MsgTx) (ok bool) + clone() utxoSet +} + +// fullUTXOSet represents a full list of transaction outputs and their values +type fullUTXOSet struct { + utxoCollection +} + +// newFullUTXOSet creates a new utxoSet with full list of transaction outputs and their values +func newFullUTXOSet() *fullUTXOSet { + return &fullUTXOSet{ + utxoCollection: utxoCollection{}, + } +} + +// diffFrom returns the difference between this utxoSet and another +// diffFrom can only work when other is a diffUTXOSet, and its base utxoSet is this. +func (fus *fullUTXOSet) diffFrom(other utxoSet) (*utxoDiff, error) { + otherDiffSet, ok := other.(*diffUTXOSet) + if !ok { + return nil, errors.New("can't diffFrom two fullUTXOSets") + } + + if otherDiffSet.base != fus { + return nil, errors.New("can diffFrom only with diffUTXOSet where this fullUTXOSet is the base") + } + + return otherDiffSet.utxoDiff, nil +} + +// withDiff returns a utxoSet which is a diff between this and another utxoSet +func (fus *fullUTXOSet) withDiff(other *utxoDiff) (utxoSet, error) { + return newDiffUTXOSet(fus, other.clone()), nil +} + +// addTx adds a transaction to this utxoSet and returns true iff it's valid in this UTXO's context +func (fus *fullUTXOSet) addTx(tx *wire.MsgTx) bool { + if !fus.containsInputs(tx) { + return false + } + + for _, txIn := range tx.TxIn { + outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index) + delete(fus.utxoCollection, outPoint) + } + + for i, txOut := range tx.TxOut { + hash := tx.TxHash() + outPoint := *wire.NewOutPoint(&hash, uint32(i)) + entry := newUTXOEntry(txOut) + + fus.utxoCollection[outPoint] = entry + } + + return true +} + +func (fus *fullUTXOSet) containsInputs(tx *wire.MsgTx) bool { + for _, txIn := range tx.TxIn { + outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index) + if _, ok := fus.utxoCollection[outPoint]; !ok { + return false + } + } + + return true +} + +// collection returns a collection of all UTXOs in this set +func (fus *fullUTXOSet) collection() utxoCollection { + return fus.utxoCollection.clone() +} + +// clone returns a clone of this utxoSet +func (fus *fullUTXOSet) clone() utxoSet { + return &fullUTXOSet{utxoCollection: fus.utxoCollection.clone()} +} + +// diffUTXOSet represents a utxoSet with a base fullUTXOSet and a UTXODiff +type diffUTXOSet struct { + base *fullUTXOSet + utxoDiff *utxoDiff +} + +// newDiffUTXOSet Creates a new utxoSet based on a base fullUTXOSet and a UTXODiff +func newDiffUTXOSet(base *fullUTXOSet, diff *utxoDiff) *diffUTXOSet { + return &diffUTXOSet{ + base: base, + utxoDiff: diff, + } +} + +// diffFrom returns the difference between this utxoSet and another. +// diffFrom can work if other is this's base fullUTXOSet, or a diffUTXOSet with the same base as this +func (dus *diffUTXOSet) diffFrom(other utxoSet) (*utxoDiff, error) { + otherDiffSet, ok := other.(*diffUTXOSet) + if !ok { + return nil, errors.New("can't diffFrom diffUTXOSet with fullUTXOSet") + } + + if otherDiffSet.base != dus.base { + return nil, errors.New("can't diffFrom with another diffUTXOSet with a different base") + } + + return dus.utxoDiff.diffFrom(otherDiffSet.utxoDiff) +} + +// withDiff return a new utxoSet which is a diffFrom between this and another utxoSet +func (dus *diffUTXOSet) withDiff(other *utxoDiff) (utxoSet, error) { + diff, err := dus.utxoDiff.withDiff(other) + if err != nil { + return nil, err + } + + return newDiffUTXOSet(dus.base, diff), nil +} + +// addTx adds a transaction to this utxoSet and returns true iff it's valid in this UTXO's context +func (dus *diffUTXOSet) addTx(tx *wire.MsgTx) bool { + if !dus.containsInputs(tx) { + return false + } + + for _, txIn := range tx.TxIn { + outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index) + if _, ok := dus.utxoDiff.toAdd[outPoint]; ok { + delete(dus.utxoDiff.toAdd, outPoint) + } else { + prevUTXOEntry := dus.base.utxoCollection[outPoint] + dus.utxoDiff.toRemove[outPoint] = prevUTXOEntry + } + } + + for i, txOut := range tx.TxOut { + hash := tx.TxHash() + outPoint := *wire.NewOutPoint(&hash, uint32(i)) + entry := newUTXOEntry(txOut) + + if _, ok := dus.utxoDiff.toRemove[outPoint]; ok { + delete(dus.utxoDiff.toRemove, outPoint) + } else { + dus.utxoDiff.toAdd[outPoint] = entry + } + } + + return true +} + +func (dus *diffUTXOSet) containsInputs(tx *wire.MsgTx) bool { + for _, txIn := range tx.TxIn { + outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index) + _, isInBase := dus.base.utxoCollection[outPoint] + _, isInDiffToAdd := dus.utxoDiff.toAdd[outPoint] + _, isInDiffToRemove := dus.utxoDiff.toRemove[outPoint] + if (!isInBase && !isInDiffToAdd) || isInDiffToRemove { + return false + } + } + + return true +} + +// meldToBase updates the base fullUTXOSet with all changes in diff +func (dus *diffUTXOSet) meldToBase() { + for outPoint := range dus.utxoDiff.toRemove { + delete(dus.base.utxoCollection, outPoint) + } + + for outPoint, utxoEntry := range dus.utxoDiff.toAdd { + dus.base.utxoCollection[outPoint] = utxoEntry + } + + dus.utxoDiff = newUTXODiff() +} + +func (dus *diffUTXOSet) String() string { + return fmt.Sprintf("{Base: %s, To Add: %s, To Remove: %s}", dus.base, dus.utxoDiff.toAdd, dus.utxoDiff.toRemove) +} + +// collection returns a collection of all UTXOs in this set +func (dus *diffUTXOSet) collection() utxoCollection { + clone := dus.clone().(*diffUTXOSet) + clone.meldToBase() + + return clone.base.collection() +} + +// clone returns a clone of this UTXO Set +func (dus *diffUTXOSet) clone() utxoSet { + return newDiffUTXOSet(dus.base.clone().(*fullUTXOSet), dus.utxoDiff.clone()) +} diff --git a/blockdag/utxoset_test.go b/blockdag/utxoset_test.go new file mode 100644 index 000000000..5c271ce70 --- /dev/null +++ b/blockdag/utxoset_test.go @@ -0,0 +1,784 @@ +package blockdag + +import ( + "github.com/daglabs/btcd/dagconfig/daghash" + "github.com/daglabs/btcd/wire" + "reflect" + "testing" +) + +// TestUTXOCollection makes sure that utxoCollection cloning and string representations work as expected. +func TestUTXOCollection(t *testing.T) { + hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000") + hash1, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111") + outPoint0 := *wire.NewOutPoint(hash0, 0) + outPoint1 := *wire.NewOutPoint(hash1, 0) + utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}) + utxoEntry1 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}) + + // For each of the following test cases, we will: + // .String() the given collection and compare it to expectedString + // .clone() the given collection and compare its value to itself (expected: equals) and its reference to itself (expected: not equal) + tests := []struct { + name string + collection utxoCollection + expectedString string + }{ + { + name: "empty collection", + collection: utxoCollection{}, + expectedString: "[ ]", + }, + { + name: "one member", + collection: utxoCollection{ + outPoint0: utxoEntry1, + }, + expectedString: "[ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 20 ]", + }, + { + name: "two members", + collection: utxoCollection{ + outPoint0: utxoEntry0, + outPoint1: utxoEntry1, + }, + expectedString: "[ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20 ]", + }, + } + + for _, test := range tests { + // Test utxoCollection string representation + collectionString := test.collection.String() + if collectionString != test.expectedString { + t.Errorf("unexpected string in test \"%s\". "+ + "Expected: \"%s\", got: \"%s\".", test.name, test.expectedString, collectionString) + } + + // Test utxoCollection cloning + collectionClone := test.collection.clone() + if reflect.ValueOf(collectionClone).Pointer() == reflect.ValueOf(test.collection).Pointer() { + t.Errorf("collection is reference-equal to its clone in test \"%s\". ", test.name) + } + if !reflect.DeepEqual(test.collection, collectionClone) { + t.Errorf("collection is not equal to its clone in test \"%s\". "+ + "Expected: \"%s\", got: \"%s\".", test.name, collectionString, collectionClone.String()) + } + } +} + +// TestUTXODiff makes sure that utxoDiff creation, cloning, and string representations work as expected. +func TestUTXODiff(t *testing.T) { + hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000") + hash1, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111") + outPoint0 := *wire.NewOutPoint(hash0, 0) + outPoint1 := *wire.NewOutPoint(hash1, 0) + utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}) + utxoEntry1 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}) + diff := utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{outPoint1: utxoEntry1}, + } + + // Test utxoDiff creation + newDiff := newUTXODiff() + if len(newDiff.toAdd) != 0 || len(newDiff.toRemove) != 0 { + t.Errorf("new diff is not empty") + } + + // Test utxoDiff cloning + clonedDiff := *diff.clone() + if &clonedDiff == &diff { + t.Errorf("cloned diff is reference-equal to the original") + } + if !reflect.DeepEqual(clonedDiff, diff) { + t.Errorf("cloned diff not equal to the original"+ + "Original: \"%v\", cloned: \"%v\".", diff, clonedDiff) + } + + // Test utxoDiff string representation + expectedDiffString := "toAdd: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ]; toRemove: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20 ]" + diffString := clonedDiff.String() + if diffString != expectedDiffString { + t.Errorf("unexpected diff string. "+ + "Expected: \"%s\", got: \"%s\".", expectedDiffString, diffString) + } +} + +// TestUTXODiffRules makes sure that all diffFrom and withDiff rules are followed. +// Each test case represents a cell in the two tables outlined in the documentation for utxoDiff. +func TestUTXODiffRules(t *testing.T) { + hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000") + outPoint0 := *wire.NewOutPoint(hash0, 0) + utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}) + + // For each of the following test cases, we will: + // this.diffFrom(other) and compare it to expectedDiffFromResult + // this.withDiff(other) and compare it to expectedWithDiffResult + // + // Note: an expected nil result means that we expect the respective operation to fail + tests := []struct { + name string + this *utxoDiff + other *utxoDiff + expectedDiffFromResult *utxoDiff + expectedWithDiffResult *utxoDiff + }{ + { + name: "one toAdd in this, one toAdd in other", + this: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + other: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + expectedDiffFromResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + expectedWithDiffResult: nil, + }, + { + name: "one toAdd in this, one toRemove in other", + this: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + other: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + expectedDiffFromResult: nil, + expectedWithDiffResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + { + name: "one toAdd in this, empty other", + this: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + other: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + expectedDiffFromResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + expectedWithDiffResult: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + }, + { + name: "one toRemove in this, one toAdd in other", + this: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + other: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + expectedDiffFromResult: nil, + expectedWithDiffResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + { + name: "one toRemove in this, one toRemove in other", + this: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + other: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + expectedDiffFromResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + expectedWithDiffResult: nil, + }, + { + name: "one toRemove in this, empty other", + this: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + other: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + expectedDiffFromResult: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + expectedWithDiffResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + }, + { + name: "empty this, one toAdd in other", + this: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + other: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + expectedDiffFromResult: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + expectedWithDiffResult: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + }, + { + name: "empty this, one toRemove in other", + this: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + other: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + expectedDiffFromResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + expectedWithDiffResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + }, + { + name: "empty this, empty other", + this: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + other: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + expectedDiffFromResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + expectedWithDiffResult: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + } + + for _, test := range tests { + // diffFrom from this to other + diffResult, err := test.this.diffFrom(test.other) + + // Test whether diffFrom returned an error + isDiffFromOk := err == nil + expectedIsDiffFromOk := test.expectedDiffFromResult != nil + if isDiffFromOk != expectedIsDiffFromOk { + t.Errorf("unexpected diffFrom error in test \"%s\". "+ + "Expected: \"%t\", got: \"%t\".", test.name, expectedIsDiffFromOk, isDiffFromOk) + } + + // If not error, test the diffFrom result + if isDiffFromOk && !reflect.DeepEqual(diffResult, test.expectedDiffFromResult) { + t.Errorf("unexpected diffFrom result in test \"%s\". "+ + "Expected: \"%v\", got: \"%v\".", test.name, test.expectedDiffFromResult, diffResult) + } + + // withDiff from this to other + withDiffResult, err := test.this.withDiff(test.other) + + // Test whether withDiff returned an error + isWithDiffOk := err == nil + expectedIsWithDiffOk := test.expectedWithDiffResult != nil + if isWithDiffOk != expectedIsWithDiffOk { + t.Errorf("unexpected withDiff error in test \"%s\". "+ + "Expected: \"%t\", got: \"%t\".", test.name, expectedIsWithDiffOk, isWithDiffOk) + } + + // Ig not error, test the withDiff result + if isWithDiffOk && !reflect.DeepEqual(withDiffResult, test.expectedWithDiffResult) { + t.Errorf("unexpected withDiff result in test \"%s\". "+ + "Expected: \"%v\", got: \"%v\".", test.name, test.expectedWithDiffResult, withDiffResult) + } + } +} + +// TestFullUTXOSet makes sure that fullUTXOSet is working as expected. +func TestFullUTXOSet(t *testing.T) { + hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000") + hash1, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111") + outPoint0 := *wire.NewOutPoint(hash0, 0) + outPoint1 := *wire.NewOutPoint(hash1, 0) + txOut0 := &wire.TxOut{PkScript: []byte{}, Value: 10} + txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20} + utxoEntry0 := newUTXOEntry(txOut0) + utxoEntry1 := newUTXOEntry(txOut1) + diff := &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{outPoint1: utxoEntry1}, + } + + // Test fullUTXOSet creation + emptySet := newFullUTXOSet() + if len(emptySet.collection()) != 0 { + t.Errorf("new set is not empty") + } + + // Test fullUTXOSet withDiff + withDiffResult, err := emptySet.withDiff(diff) + if err != nil { + t.Errorf("withDiff unexpectedly failed") + } + withDiffUTXOSet, ok := withDiffResult.(*diffUTXOSet) + if !ok { + t.Errorf("withDiff is of unexpected type") + } + if !reflect.DeepEqual(withDiffUTXOSet.base, emptySet) || !reflect.DeepEqual(withDiffUTXOSet.utxoDiff, diff) { + t.Errorf("withDiff is of unexpected composition") + } + + // Test fullUTXOSet addTx + txIn0 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{Hash: *hash0, Index: 0}, Sequence: 0} + transaction0 := wire.NewMsgTx(1) + transaction0.TxIn = []*wire.TxIn{txIn0} + transaction0.TxOut = []*wire.TxOut{txOut0} + if ok = emptySet.addTx(transaction0); ok { + t.Errorf("addTx unexpectedly succeeded") + } + emptySet = &fullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}} + if ok = emptySet.addTx(transaction0); !ok { + t.Errorf("addTx unexpectedly failed") + } + + // Test fullUTXOSet collection + if !reflect.DeepEqual(emptySet.collection(), emptySet.utxoCollection) { + t.Errorf("collection does not equal the set's utxoCollection") + } + + // Test fullUTXOSet cloning + clonedEmptySet := emptySet.clone().(*fullUTXOSet) + if !reflect.DeepEqual(clonedEmptySet, emptySet) { + t.Errorf("clone does not equal the original set") + } + if clonedEmptySet == emptySet { + t.Errorf("cloned set is reference-equal to the original") + } +} + +// TestDiffUTXOSet makes sure that diffUTXOSet is working as expected. +func TestDiffUTXOSet(t *testing.T) { + hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000") + hash1, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111") + outPoint0 := *wire.NewOutPoint(hash0, 0) + outPoint1 := *wire.NewOutPoint(hash1, 0) + txOut0 := &wire.TxOut{PkScript: []byte{}, Value: 10} + txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20} + utxoEntry0 := newUTXOEntry(txOut0) + utxoEntry1 := newUTXOEntry(txOut1) + diff := &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{outPoint1: utxoEntry1}, + } + + // Test diffUTXOSet creation + emptySet := newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()) + if len(emptySet.collection()) != 0 { + t.Errorf("new set is not empty") + } + + // Test diffUTXOSet withDiff + withDiffResult, err := emptySet.withDiff(diff) + if err != nil { + t.Errorf("withDiff unexpectedly failed") + } + withDiffUTXOSet, ok := withDiffResult.(*diffUTXOSet) + if !ok { + t.Errorf("withDiff is of unexpected type") + } + withDiff, _ := newUTXODiff().withDiff(diff) + if !reflect.DeepEqual(withDiffUTXOSet.base, emptySet.base) || !reflect.DeepEqual(withDiffUTXOSet.utxoDiff, withDiff) { + t.Errorf("withDiff is of unexpected composition") + } + _, err = newDiffUTXOSet(newFullUTXOSet(), diff).withDiff(diff) + if err == nil { + t.Errorf("withDiff unexpectedly succeeded") + } + + // Given a diffSet, each case tests that meldToBase, String, collection, and cloning work as expected + // For each of the following test cases, we will: + // .meldToBase() the given diffSet and compare it to expectedMeldSet + // .String() the given diffSet and compare it to expectedString + // .collection() the given diffSet and compare it to expectedCollection + // .clone() the given diffSet and compare its value to itself (expected: equals) and its reference to itself (expected: not equal) + tests := []struct { + name string + diffSet *diffUTXOSet + expectedMeldSet *diffUTXOSet + expectedString string + expectedCollection utxoCollection + }{ + { + name: "empty base, empty diff", + diffSet: &diffUTXOSet{ + base: newFullUTXOSet(), + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + expectedMeldSet: &diffUTXOSet{ + base: newFullUTXOSet(), + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + expectedString: "{Base: [ ], To Add: [ ], To Remove: [ ]}", + expectedCollection: utxoCollection{}, + }, + { + name: "empty base, one member in diff toAdd", + diffSet: &diffUTXOSet{ + base: newFullUTXOSet(), + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{outPoint0: utxoEntry0}, + toRemove: utxoCollection{}, + }, + }, + expectedMeldSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + expectedString: "{Base: [ ], To Add: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], To Remove: [ ]}", + expectedCollection: utxoCollection{outPoint0: utxoEntry0}, + }, + { + name: "empty base, one member in diff toRemove", + diffSet: &diffUTXOSet{ + base: newFullUTXOSet(), + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + }, + expectedMeldSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + expectedString: "{Base: [ ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ]}", + expectedCollection: utxoCollection{}, + }, + { + name: "one member in base toAdd, one member in diff toAdd", + diffSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{outPoint1: utxoEntry1}, + toRemove: utxoCollection{}, + }, + }, + expectedMeldSet: &diffUTXOSet{ + base: &fullUTXOSet{ + utxoCollection: utxoCollection{ + outPoint0: utxoEntry0, + outPoint1: utxoEntry1, + }, + }, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], To Add: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20 ], To Remove: [ ]}", + expectedCollection: utxoCollection{ + outPoint0: utxoEntry0, + outPoint1: utxoEntry1, + }, + }, + { + name: "one member in base toAdd, same one member in diff toRemove", + diffSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{outPoint0: utxoEntry0}, + }, + }, + expectedMeldSet: &diffUTXOSet{ + base: &fullUTXOSet{ + utxoCollection: utxoCollection{}, + }, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ]}", + expectedCollection: utxoCollection{}, + }, + } + + for _, test := range tests { + // Test meldToBase + meldSet := test.diffSet.clone().(*diffUTXOSet) + meldSet.meldToBase() + if !reflect.DeepEqual(meldSet, test.expectedMeldSet) { + t.Errorf("unexpected melded set in test \"%s\". "+ + "Expected: \"%v\", got: \"%v\".", test.name, test.expectedMeldSet, meldSet) + } + + // Test string representation + setString := test.diffSet.String() + if setString != test.expectedString { + t.Errorf("unexpected string in test \"%s\". "+ + "Expected: \"%s\", got: \"%s\".", test.name, test.expectedString, setString) + } + + // Test collection + setCollection := test.diffSet.collection() + if !reflect.DeepEqual(setCollection, test.expectedCollection) { + t.Errorf("unexpected set collection in test \"%s\". "+ + "Expected: \"%v\", got: \"%v\".", test.name, test.expectedCollection, setCollection) + } + + // Test cloning + clonedSet := test.diffSet.clone().(*diffUTXOSet) + if !reflect.DeepEqual(clonedSet, test.diffSet) { + t.Errorf("unexpected set clone in test \"%s\". "+ + "Expected: \"%v\", got: \"%v\".", test.name, test.diffSet, clonedSet) + } + if clonedSet == test.diffSet { + t.Errorf("cloned set is reference-equal to the original") + } + } +} + +// TestUTXOSetDiffRules makes sure that utxoSet diffFrom rules are followed. +// The rules are: +// 1. Neither fullUTXOSet nor diffUTXOSet can diffFrom a fullUTXOSet. +// 2. fullUTXOSet cannot diffFrom a diffUTXOSet with a base other that itself. +// 3. diffUTXOSet cannot diffFrom a diffUTXOSet with a different base. +func TestUTXOSetDiffRules(t *testing.T) { + fullSet := newFullUTXOSet() + diffSet := newDiffUTXOSet(fullSet, newUTXODiff()) + + // For each of the following test cases, we will call utxoSet.diffFrom(diffSet) and compare + // whether the function succeeded with expectedSuccess + // + // Note: since test cases are similar for both fullUTXOSet and diffUTXOSet, we test both using the same test cases + run := func(set utxoSet) { + tests := []struct { + name string + diffSet utxoSet + expectedSuccess bool + }{ + { + name: "diff from fullSet", + diffSet: newFullUTXOSet(), + expectedSuccess: false, + }, + { + name: "diff from diffSet with different base", + diffSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()), + expectedSuccess: false, + }, + { + name: "diff from diffSet with same base", + diffSet: newDiffUTXOSet(fullSet, newUTXODiff()), + expectedSuccess: true, + }, + } + + for _, test := range tests { + _, err := set.diffFrom(test.diffSet) + success := err == nil + if success != test.expectedSuccess { + t.Errorf("unexpected diffFrom success in test \"%s\". "+ + "Expected: \"%t\", got: \"%t\".", test.name, test.expectedSuccess, success) + } + } + } + + run(fullSet) // Perform the test cases above on a fullUTXOSet + run(diffSet) // Perform the test cases above on a diffUTXOSet +} + +// TestDiffUTXOSet_addTx makes sure that diffUTXOSet addTx works as expected +func TestDiffUTXOSet_addTx(t *testing.T) { + // transaction0 is coinbase. As such, it does not have any inputs + txOut0 := &wire.TxOut{PkScript: []byte{0}, Value: 10} + utxoEntry0 := newUTXOEntry(txOut0) + transaction0 := wire.NewMsgTx(1) + transaction0.TxIn = []*wire.TxIn{} + transaction0.TxOut = []*wire.TxOut{txOut0} + + // transaction1 spends transaction0 + hash1 := transaction0.TxHash() + outPoint1 := *wire.NewOutPoint(&hash1, 0) + txIn1 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{Hash: hash1, Index: 0}, Sequence: 0} + txOut1 := &wire.TxOut{PkScript: []byte{1}, Value: 20} + utxoEntry1 := newUTXOEntry(txOut1) + transaction1 := wire.NewMsgTx(1) + transaction1.TxIn = []*wire.TxIn{txIn1} + transaction1.TxOut = []*wire.TxOut{txOut1} + + // transaction2 spends transaction1 + hash2 := transaction1.TxHash() + outPoint2 := *wire.NewOutPoint(&hash2, 0) + txIn2 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{Hash: hash2, Index: 0}, Sequence: 0} + txOut2 := &wire.TxOut{PkScript: []byte{2}, Value: 30} + utxoEntry2 := newUTXOEntry(txOut2) + transaction2 := wire.NewMsgTx(1) + transaction2.TxIn = []*wire.TxIn{txIn2} + transaction2.TxOut = []*wire.TxOut{txOut2} + + // outpoint3 is the outpoint for transaction2 + hash3 := transaction2.TxHash() + outPoint3 := *wire.NewOutPoint(&hash3, 0) + + // For each of the following test cases, we will: + // 1. startSet.addTx() all the transactions in toAdd, in order + // 2. Compare the result set with expectedSet + tests := []struct { + name string + startSet *diffUTXOSet + toAdd []*wire.MsgTx + expectedSet *diffUTXOSet + }{ + { + name: "add coinbase transaction to empty set", + startSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()), + toAdd: []*wire.MsgTx{transaction0}, + expectedSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{outPoint1: utxoEntry0}, + toRemove: utxoCollection{}, + }, + }, + }, + { + name: "add regular transaction to empty set", + startSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()), + toAdd: []*wire.MsgTx{transaction1}, + expectedSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + }, + { + name: "add transaction to set with its input in base", + startSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + toAdd: []*wire.MsgTx{transaction1}, + expectedSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{outPoint2: utxoEntry1}, + toRemove: utxoCollection{outPoint1: utxoEntry0}, + }, + }, + }, + { + name: "add transaction to set with its input in diff toAdd", + startSet: &diffUTXOSet{ + base: newFullUTXOSet(), + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{outPoint1: utxoEntry0}, + toRemove: utxoCollection{}, + }, + }, + toAdd: []*wire.MsgTx{transaction1}, + expectedSet: &diffUTXOSet{ + base: newFullUTXOSet(), + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{outPoint2: utxoEntry1}, + toRemove: utxoCollection{}, + }, + }, + }, + { + name: "add transaction to set with its input in diff toAdd and its output in diff toRemove", + startSet: &diffUTXOSet{ + base: newFullUTXOSet(), + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{outPoint1: utxoEntry0}, + toRemove: utxoCollection{outPoint2: utxoEntry1}, + }, + }, + toAdd: []*wire.MsgTx{transaction1}, + expectedSet: &diffUTXOSet{ + base: newFullUTXOSet(), + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + }, + { + name: "add two transactions, one spending the other, to set with the first input in base", + startSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{}, + toRemove: utxoCollection{}, + }, + }, + toAdd: []*wire.MsgTx{transaction1, transaction2}, + expectedSet: &diffUTXOSet{ + base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}}, + utxoDiff: &utxoDiff{ + toAdd: utxoCollection{outPoint3: utxoEntry2}, + toRemove: utxoCollection{outPoint1: utxoEntry0}, + }, + }, + }, + } + + for _, test := range tests { + diffSet := test.startSet.clone() + + // Apply all transactions, in order, to diffSet + for _, transaction := range test.toAdd { + diffSet.addTx(transaction) + } + + // Make sure that the result diffSet equals to the expectedSet + if !reflect.DeepEqual(diffSet, test.expectedSet) { + t.Errorf("unexpected diffSet in test \"%s\". "+ + "Expected: \"%v\", got: \"%v\".", test.name, test.expectedSet, diffSet) + } + } +}