mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-27 16:32:33 +00:00
[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.
This commit is contained in:
parent
ed5df3bf7d
commit
f6eb9edecf
@ -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
|
||||
|
434
blockdag/utxoset.go
Normal file
434
blockdag/utxoset.go
Normal file
@ -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())
|
||||
}
|
784
blockdag/utxoset_test.go
Normal file
784
blockdag/utxoset_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user