[NOD-179] Added ECMH-Multiset to all UTXO structs (#304)

* [NOD-172] Port EMCH from bchd

* [NOD-172] Fix hdkeychain.TestErrors and add btcec.TestRecoverCompact

* [NOD-172] Make ECMH immutable

* [NOD-172] Fix gofmt errors

* [NOD-172] Add TestMultiset_NewMultisetFromDataSlice and fix Point to be immutable

* [NOD-172] Fix gofmt errors

* [NOD-172] Add test for checking that the Union of a multiset and its inverse is zero

* [NOD-179] Add ECMH Point to all UTXO-structs

* [NOD-179] Fix utxo set tests

* [NOD-179] Fix mempool tests

* [NOD-179] Remove RemoveTxOuts

* [NOD-179] Move serializeBlockUTXODiffData to the top of the file

* [NOD-179] Fix serializeBlockUTXODiffData comment format

* [NOD-179] Fix AddTx comment and name return values
This commit is contained in:
Ori Newman 2019-05-23 15:11:42 +03:00 committed by Svarog
parent da7c9c7dfb
commit aa51b5f071
19 changed files with 983 additions and 575 deletions

View File

@ -823,7 +823,10 @@ func (dag *BlockDAG) applyDAGChanges(node *blockNode, block *util.Block, newBloc
// It is now safe to meld the UTXO set to base. // It is now safe to meld the UTXO set to base.
diffSet := newVirtualUTXO.(*DiffUTXOSet) diffSet := newVirtualUTXO.(*DiffUTXOSet)
virtualUTXODiff = diffSet.UTXODiff virtualUTXODiff = diffSet.UTXODiff
dag.meldVirtualUTXO(diffSet) err = dag.meldVirtualUTXO(diffSet)
if err != nil {
return nil, fmt.Errorf("failed melding the virtual UTXO: %s", err)
}
dag.index.SetStatusFlags(node, statusValid) dag.index.SetStatusFlags(node, statusValid)
@ -833,10 +836,10 @@ func (dag *BlockDAG) applyDAGChanges(node *blockNode, block *util.Block, newBloc
return virtualUTXODiff, nil return virtualUTXODiff, nil
} }
func (dag *BlockDAG) meldVirtualUTXO(newVirtualUTXODiffSet *DiffUTXOSet) { func (dag *BlockDAG) meldVirtualUTXO(newVirtualUTXODiffSet *DiffUTXOSet) error {
dag.utxoLock.Lock() dag.utxoLock.Lock()
defer dag.utxoLock.Unlock() defer dag.utxoLock.Unlock()
newVirtualUTXODiffSet.meldToBase() return newVirtualUTXODiffSet.meldToBase()
} }
func (node *blockNode) diffFromTxs(pastUTXO UTXOSet, transactions []*util.Tx) (*UTXODiff, error) { func (node *blockNode) diffFromTxs(pastUTXO UTXOSet, transactions []*util.Tx) (*UTXODiff, error) {
@ -966,7 +969,10 @@ func (node *blockNode) applyBlueBlocks(selectedParentUTXO UTXOSet, blueBlocks []
if isSelectedParent { if isSelectedParent {
isAccepted = true isAccepted = true
} else { } else {
isAccepted = pastUTXO.AddTx(tx.MsgTx(), node.height) isAccepted, err = pastUTXO.AddTx(tx.MsgTx(), node.height)
if err != nil {
return nil, nil, err
}
} }
blockTxsAcceptanceData[i] = TxAcceptanceData{Tx: tx, IsAccepted: isAccepted} blockTxsAcceptanceData[i] = TxAcceptanceData{Tx: tx, IsAccepted: isAccepted}
} }

View File

@ -244,7 +244,11 @@ func TestCalcSequenceLock(t *testing.T) {
msgTx := wire.NewNativeMsgTx(wire.TxVersion, nil, []*wire.TxOut{{PkScript: nil, Value: 10}}) msgTx := wire.NewNativeMsgTx(wire.TxVersion, nil, []*wire.TxOut{{PkScript: nil, Value: 10}})
targetTx := util.NewTx(msgTx) targetTx := util.NewTx(msgTx)
utxoSet := NewFullUTXOSet() utxoSet := NewFullUTXOSet()
utxoSet.AddTx(targetTx.MsgTx(), uint64(numBlocksToGenerate)-4) if isAccepted, err := utxoSet.AddTx(targetTx.MsgTx(), uint64(numBlocksToGenerate)-4); err != nil {
t.Fatalf("AddTx unexpectedly failed. Error: %s", err)
} else if !isAccepted {
t.Fatalf("AddTx unexpectedly didn't add tx %s", targetTx.ID())
}
// Create a utxo that spends the fake utxo created above for use in the // Create a utxo that spends the fake utxo created above for use in the
// transactions created in the tests. It has an age of 4 blocks. Note // transactions created in the tests. It has an age of 4 blocks. Note
@ -276,8 +280,11 @@ func TestCalcSequenceLock(t *testing.T) {
TxID: *unConfTx.TxID(), TxID: *unConfTx.TxID(),
Index: 0, Index: 0,
} }
if isAccepted, err := utxoSet.AddTx(unConfTx, UnminedChainHeight); err != nil {
utxoSet.AddTx(unConfTx, UnminedChainHeight) t.Fatalf("AddTx unexpectedly failed. Error: %s", err)
} else if !isAccepted {
t.Fatalf("AddTx unexpectedly didn't add tx %s", unConfTx.TxID())
}
tests := []struct { tests := []struct {
name string name string

View File

@ -221,93 +221,6 @@ func recycleOutpointKey(key *[]byte) {
outpointKeyPool.Put(key) outpointKeyPool.Put(key)
} }
// utxoEntryHeaderCode returns the calculated header code to be used when
// serializing the provided utxo entry.
func utxoEntryHeaderCode(entry *UTXOEntry) uint64 {
// As described in the serialization format comments, the header code
// encodes the height shifted over one bit and the block reward flag in the
// lowest bit.
headerCode := uint64(entry.BlockChainHeight()) << 1
if entry.IsBlockReward() {
headerCode |= 0x01
}
return headerCode
}
// serializeUTXOEntry returns the entry serialized to a format that is suitable
// for long-term storage. The format is described in detail above.
func serializeUTXOEntry(entry *UTXOEntry) ([]byte, error) {
// Encode the header code.
headerCode := utxoEntryHeaderCode(entry)
// Calculate the size needed to serialize the entry.
size := serializeSizeVLQ(headerCode) +
compressedTxOutSize(uint64(entry.Amount()), entry.PkScript())
// Serialize the header code followed by the compressed unspent
// transaction output.
serialized := make([]byte, size)
offset := putVLQ(serialized, headerCode)
offset += putCompressedTxOut(serialized[offset:], uint64(entry.Amount()),
entry.PkScript())
return serialized, nil
}
// deserializeOutPoint decodes an outPoint from the passed serialized byte
// slice into a new wire.OutPoint using a format that is suitable for long-
// term storage. this format is described in detail above.
func deserializeOutPoint(serialized []byte) (*wire.OutPoint, error) {
if len(serialized) <= daghash.HashSize {
return nil, errDeserialize("unexpected end of data")
}
txID := daghash.TxID{}
txID.SetBytes(serialized[:daghash.HashSize])
index, _ := deserializeVLQ(serialized[daghash.HashSize:])
return wire.NewOutPoint(&txID, uint32(index)), nil
}
// deserializeUTXOEntry decodes a UTXO entry from the passed serialized byte
// slice into a new UTXOEntry using a format that is suitable for long-term
// storage. The format is described in detail above.
func deserializeUTXOEntry(serialized []byte) (*UTXOEntry, error) {
// Deserialize the header code.
code, offset := deserializeVLQ(serialized)
if offset >= len(serialized) {
return nil, errDeserialize("unexpected end of data after header")
}
// Decode the header code.
//
// Bit 0 indicates whether the containing transaction is a block reward.
// Bits 1-x encode height of containing transaction.
isBlockReward := code&0x01 != 0
blockChainHeight := code >> 1
// Decode the compressed unspent transaction output.
amount, pkScript, _, err := decodeCompressedTxOut(serialized[offset:])
if err != nil {
return nil, errDeserialize(fmt.Sprintf("unable to decode "+
"UTXO: %s", err))
}
entry := &UTXOEntry{
amount: amount,
pkScript: pkScript,
blockChainHeight: blockChainHeight,
packedFlags: 0,
}
if isBlockReward {
entry.packedFlags |= tfBlockReward
}
return entry, nil
}
// dbPutUTXODiff uses an existing database transaction to update the UTXO set // dbPutUTXODiff uses an existing database transaction to update the UTXO set
// in the database based on the provided UTXO view contents and state. In // in the database based on the provided UTXO view contents and state. In
// particular, only the entries that have been marked as modified are written // particular, only the entries that have been marked as modified are written
@ -325,13 +238,10 @@ func dbPutUTXODiff(dbTx database.Tx, diff *UTXODiff) error {
for outPoint, entry := range diff.toAdd { for outPoint, entry := range diff.toAdd {
// Serialize and store the UTXO entry. // Serialize and store the UTXO entry.
serialized, err := serializeUTXOEntry(entry) serialized := serializeUTXOEntry(entry)
if err != nil {
return err
}
key := outpointKey(outPoint) key := outpointKey(outPoint)
err = utxoBucket.Put(*key, serialized) err := utxoBucket.Put(*key, serialized)
// NOTE: The key is intentionally not recycled here since the // NOTE: The key is intentionally not recycled here since the
// database interface contract prohibits modifications. It will // database interface contract prohibits modifications. It will
// be garbage collected normally when the database is done with // be garbage collected normally when the database is done with

View File

@ -74,12 +74,7 @@ func TestUtxoSerialization(t *testing.T) {
for i, test := range tests { for i, test := range tests {
// Ensure the utxo entry serializes to the expected value. // Ensure the utxo entry serializes to the expected value.
gotBytes, err := serializeUTXOEntry(test.entry) gotBytes := serializeUTXOEntry(test.entry)
if err != nil {
t.Errorf("serializeUTXOEntry #%d (%s) unexpected "+
"error: %v", i, test.name, err)
continue
}
if !bytes.Equal(gotBytes, test.serialized) { if !bytes.Equal(gotBytes, test.serialized) {
t.Errorf("serializeUTXOEntry #%d (%s): mismatched "+ t.Errorf("serializeUTXOEntry #%d (%s): mismatched "+
"bytes - got %x, want %x", i, test.name, "bytes - got %x, want %x", i, test.name,

View File

@ -13,6 +13,7 @@ import (
"github.com/daglabs/btcd/blockdag" "github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/mining" "github.com/daglabs/btcd/mining"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util" "github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire" "github.com/daglabs/btcd/wire"
) )
@ -192,9 +193,13 @@ func TestChainedTransactions(t *testing.T) {
} }
cbTx := block1.Transactions[0] cbTx := block1.Transactions[0]
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
if err != nil {
t.Fatalf("Failed to build signature script: %s", err)
}
txIn := &wire.TxIn{ txIn := &wire.TxIn{
PreviousOutPoint: wire.OutPoint{TxID: *cbTx.TxID(), Index: 0}, PreviousOutPoint: wire.OutPoint{TxID: *cbTx.TxID(), Index: 0},
SignatureScript: nil, SignatureScript: signatureScript,
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
} }
txOut := &wire.TxOut{ txOut := &wire.TxOut{
@ -205,11 +210,16 @@ func TestChainedTransactions(t *testing.T) {
chainedTxIn := &wire.TxIn{ chainedTxIn := &wire.TxIn{
PreviousOutPoint: wire.OutPoint{TxID: *tx.TxID(), Index: 0}, PreviousOutPoint: wire.OutPoint{TxID: *tx.TxID(), Index: 0},
SignatureScript: nil, SignatureScript: signatureScript,
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
} }
pkScript, err := txscript.PayToScriptHashScript(blockdag.OpTrueScript)
if err != nil {
t.Fatalf("Failed to build public key script: %s", err)
}
chainedTxOut := &wire.TxOut{ chainedTxOut := &wire.TxOut{
PkScript: blockdag.OpTrueScript, PkScript: pkScript,
Value: uint64(1), Value: uint64(1),
} }
chainedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{chainedTxIn}, []*wire.TxOut{chainedTxOut}) chainedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{chainedTxIn}, []*wire.TxOut{chainedTxOut})
@ -236,11 +246,11 @@ func TestChainedTransactions(t *testing.T) {
nonChainedTxIn := &wire.TxIn{ nonChainedTxIn := &wire.TxIn{
PreviousOutPoint: wire.OutPoint{TxID: *cbTx.TxID(), Index: 0}, PreviousOutPoint: wire.OutPoint{TxID: *cbTx.TxID(), Index: 0},
SignatureScript: nil, SignatureScript: signatureScript,
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
} }
nonChainedTxOut := &wire.TxOut{ nonChainedTxOut := &wire.TxOut{
PkScript: blockdag.OpTrueScript, PkScript: pkScript,
Value: uint64(1), Value: uint64(1),
} }
nonChainedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{nonChainedTxIn}, []*wire.TxOut{nonChainedTxOut}) nonChainedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{nonChainedTxIn}, []*wire.TxOut{nonChainedTxOut})
@ -292,26 +302,38 @@ func TestGasLimit(t *testing.T) {
t.Fatalf("ProcessBlock: funds block got unexpectedly orphan") t.Fatalf("ProcessBlock: funds block got unexpectedly orphan")
} }
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
if err != nil {
t.Fatalf("Failed to build signature script: %s", err)
}
pkScript, err := txscript.PayToScriptHashScript(blockdag.OpTrueScript)
if err != nil {
t.Fatalf("Failed to build public key script: %s", err)
}
cbTxValue := fundsBlock.Transactions[0].TxOut[0].Value cbTxValue := fundsBlock.Transactions[0].TxOut[0].Value
cbTxID := fundsBlock.Transactions[0].TxID() cbTxID := fundsBlock.Transactions[0].TxID()
tx1In := &wire.TxIn{ tx1In := &wire.TxIn{
PreviousOutPoint: *wire.NewOutPoint(cbTxID, 0), PreviousOutPoint: *wire.NewOutPoint(cbTxID, 0),
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
} }
tx1Out := &wire.TxOut{ tx1Out := &wire.TxOut{
Value: cbTxValue, Value: cbTxValue,
PkScript: blockdag.OpTrueScript, PkScript: pkScript,
} }
tx1 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{tx1In}, []*wire.TxOut{tx1Out}, subnetworkID, 10000, []byte{}) tx1 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{tx1In}, []*wire.TxOut{tx1Out}, subnetworkID, 10000, []byte{})
tx2In := &wire.TxIn{ tx2In := &wire.TxIn{
PreviousOutPoint: *wire.NewOutPoint(cbTxID, 1), PreviousOutPoint: *wire.NewOutPoint(cbTxID, 1),
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
} }
tx2Out := &wire.TxOut{ tx2Out := &wire.TxOut{
Value: cbTxValue, Value: cbTxValue,
PkScript: blockdag.OpTrueScript, PkScript: pkScript,
} }
tx2 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{tx2In}, []*wire.TxOut{tx2Out}, subnetworkID, 10000, []byte{}) tx2 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{tx2In}, []*wire.TxOut{tx2Out}, subnetworkID, 10000, []byte{})
@ -337,10 +359,11 @@ func TestGasLimit(t *testing.T) {
overflowGasTxIn := &wire.TxIn{ overflowGasTxIn := &wire.TxIn{
PreviousOutPoint: *wire.NewOutPoint(cbTxID, 1), PreviousOutPoint: *wire.NewOutPoint(cbTxID, 1),
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
} }
overflowGasTxOut := &wire.TxOut{ overflowGasTxOut := &wire.TxOut{
Value: cbTxValue, Value: cbTxValue,
PkScript: blockdag.OpTrueScript, PkScript: pkScript,
} }
overflowGasTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{overflowGasTxIn}, []*wire.TxOut{overflowGasTxOut}, overflowGasTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{overflowGasTxIn}, []*wire.TxOut{overflowGasTxOut},
subnetworkID, math.MaxUint64, []byte{}) subnetworkID, math.MaxUint64, []byte{})
@ -368,10 +391,11 @@ func TestGasLimit(t *testing.T) {
nonExistentSubnetworkTxIn := &wire.TxIn{ nonExistentSubnetworkTxIn := &wire.TxIn{
PreviousOutPoint: *wire.NewOutPoint(cbTxID, 0), PreviousOutPoint: *wire.NewOutPoint(cbTxID, 0),
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
} }
nonExistentSubnetworkTxOut := &wire.TxOut{ nonExistentSubnetworkTxOut := &wire.TxOut{
Value: cbTxValue, Value: cbTxValue,
PkScript: blockdag.OpTrueScript, PkScript: pkScript,
} }
nonExistentSubnetworkTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{nonExistentSubnetworkTxIn}, nonExistentSubnetworkTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{nonExistentSubnetworkTxIn},
[]*wire.TxOut{nonExistentSubnetworkTxOut}, nonExistentSubnetwork, 1, []byte{}) []*wire.TxOut{nonExistentSubnetworkTxOut}, nonExistentSubnetwork, 1, []byte{})

View File

@ -8,18 +8,24 @@ import (
"github.com/daglabs/btcd/blockdag" "github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/mining" "github.com/daglabs/btcd/mining"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util" "github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/util/daghash" "github.com/daglabs/btcd/util/daghash"
"github.com/daglabs/btcd/wire" "github.com/daglabs/btcd/wire"
) )
func createTransaction(value uint64, originTx *wire.MsgTx, outputIndex uint32) *wire.MsgTx { func createTransaction(t *testing.T, value uint64, originTx *wire.MsgTx, outputIndex uint32) *wire.MsgTx {
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
if err != nil {
t.Fatalf("Error creating signature script: %s", err)
}
txIn := &wire.TxIn{ txIn := &wire.TxIn{
PreviousOutPoint: wire.OutPoint{ PreviousOutPoint: wire.OutPoint{
TxID: *originTx.TxID(), TxID: *originTx.TxID(),
Index: outputIndex, Index: outputIndex,
}, },
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
} }
txOut := wire.NewTxOut(value, blockdag.OpTrueScript) txOut := wire.NewTxOut(value, blockdag.OpTrueScript)
tx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}) tx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
@ -68,9 +74,9 @@ func TestTxIndexConnectBlock(t *testing.T) {
} }
block1 := prepareAndProcessBlock([]*daghash.Hash{params.GenesisHash}, nil, "1") block1 := prepareAndProcessBlock([]*daghash.Hash{params.GenesisHash}, nil, "1")
block2Tx := createTransaction(block1.Transactions[0].TxOut[0].Value, block1.Transactions[0], 0) block2Tx := createTransaction(t, block1.Transactions[0].TxOut[0].Value, block1.Transactions[0], 0)
block2 := prepareAndProcessBlock([]*daghash.Hash{block1.BlockHash()}, []*wire.MsgTx{block2Tx}, "2") block2 := prepareAndProcessBlock([]*daghash.Hash{block1.BlockHash()}, []*wire.MsgTx{block2Tx}, "2")
block3Tx := createTransaction(block2.Transactions[0].TxOut[0].Value, block2.Transactions[0], 0) block3Tx := createTransaction(t, block2.Transactions[0].TxOut[0].Value, block2.Transactions[0], 0)
block3 := prepareAndProcessBlock([]*daghash.Hash{block2.BlockHash()}, []*wire.MsgTx{block3Tx}, "3") block3 := prepareAndProcessBlock([]*daghash.Hash{block2.BlockHash()}, []*wire.MsgTx{block3Tx}, "3")
block3TxID := block3Tx.TxID() block3TxID := block3Tx.TxID()

View File

@ -202,7 +202,10 @@ func GetVirtualFromParentsForTest(dag *BlockDAG, parentHashes []*daghash.Hash) (
return nil, err return nil, err
} }
diffPastUTXO := pastUTXO.clone().(*DiffUTXOSet) diffPastUTXO := pastUTXO.clone().(*DiffUTXOSet)
diffPastUTXO.meldToBase() err = diffPastUTXO.meldToBase()
if err != nil {
return nil, err
}
virtual.utxoSet = diffPastUTXO.base virtual.utxoSet = diffPastUTXO.base
return virtual, nil return virtual, nil

View File

@ -1,17 +1,15 @@
package blockdag package blockdag
import ( import (
"bytes"
"encoding/binary"
"fmt" "fmt"
"io"
"sync" "sync"
"github.com/daglabs/btcd/database" "github.com/daglabs/btcd/database"
"github.com/daglabs/btcd/util/daghash" "github.com/daglabs/btcd/util/daghash"
"github.com/daglabs/btcd/wire"
) )
var multisetPointSize = 32
type blockUTXODiffData struct { type blockUTXODiffData struct {
diff *UTXODiff diff *UTXODiff
diffChild *blockNode diffChild *blockNode
@ -133,159 +131,6 @@ func (diffStore *utxoDiffStore) diffDataFromDB(hash *daghash.Hash) (*blockUTXODi
return diffData, nil return diffData, nil
} }
func (diffStore *utxoDiffStore) deserializeBlockUTXODiffData(serializedDiffDataBytes []byte) (*blockUTXODiffData, error) {
diffData := &blockUTXODiffData{}
serializedDiffData := bytes.NewBuffer(serializedDiffDataBytes)
var hasDiffChild bool
err := wire.ReadElement(serializedDiffData, &hasDiffChild)
if err != nil {
return nil, err
}
if hasDiffChild {
hash := &daghash.Hash{}
err := wire.ReadElement(serializedDiffData, hash)
if err != nil {
return nil, err
}
diffData.diffChild = diffStore.dag.index.LookupNode(hash)
}
diffData.diff = &UTXODiff{}
diffData.diff.toAdd, err = deserializeDiffEntries(serializedDiffData)
if err != nil {
return nil, err
}
diffData.diff.toRemove, err = deserializeDiffEntries(serializedDiffData)
if err != nil {
return nil, err
}
return diffData, nil
}
func deserializeDiffEntries(r io.Reader) (utxoCollection, error) {
count, err := wire.ReadVarInt(r)
if err != nil {
return nil, err
}
collection := utxoCollection{}
for i := uint64(0); i < count; i++ {
outPointSize, err := wire.ReadVarInt(r)
if err != nil {
return nil, err
}
serializedOutPoint := make([]byte, outPointSize)
err = binary.Read(r, byteOrder, serializedOutPoint)
if err != nil {
return nil, err
}
outPoint, err := deserializeOutPoint(serializedOutPoint)
if err != nil {
return nil, err
}
utxoEntrySize, err := wire.ReadVarInt(r)
if err != nil {
return nil, err
}
serializedEntry := make([]byte, utxoEntrySize)
err = binary.Read(r, byteOrder, serializedEntry)
if err != nil {
return nil, err
}
utxoEntry, err := deserializeUTXOEntry(serializedEntry)
if err != nil {
return nil, err
}
collection.add(*outPoint, utxoEntry)
}
return collection, nil
}
// serializeBlockUTXODiffData serializes diff data in the following format:
// Name | Data type | Description
// ------------ | --------- | -----------
// hasDiffChild | Boolean | Indicates if a diff child exist
// diffChild | Hash | The diffChild's hash. Empty if hasDiffChild is true.
// diff | UTXODiff | The diff data's diff
func serializeBlockUTXODiffData(diffData *blockUTXODiffData) ([]byte, error) {
w := &bytes.Buffer{}
hasDiffChild := diffData.diffChild != nil
err := wire.WriteElement(w, hasDiffChild)
if err != nil {
return nil, err
}
if hasDiffChild {
err := wire.WriteElement(w, diffData.diffChild.hash)
if err != nil {
return nil, err
}
}
err = serializeUTXODiff(w, diffData.diff)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}
// serializeUTXODiff serializes UTXODiff by serializing
// UTXODiff.toAdd and UTXODiff.toRemove one after the other.
func serializeUTXODiff(w io.Writer, diff *UTXODiff) error {
err := serializeUTXOCollection(w, diff.toAdd)
if err != nil {
return err
}
err = serializeUTXOCollection(w, diff.toRemove)
if err != nil {
return err
}
return nil
}
// serializeUTXOCollection serializes utxoCollection by iterating over
// the utxo entries and serializing them and their corresponding outpoint
// prefixed by a varint that indicates their size.
func serializeUTXOCollection(w io.Writer, collection utxoCollection) error {
err := wire.WriteVarInt(w, uint64(len(collection)))
if err != nil {
return err
}
for outPoint, utxoEntry := range collection {
serializedOutPoint := *outpointKey(outPoint)
err = wire.WriteVarInt(w, uint64(len(serializedOutPoint)))
if err != nil {
return err
}
err := binary.Write(w, byteOrder, serializedOutPoint)
if err != nil {
return err
}
serializedUTXOEntry, err := serializeUTXOEntry(utxoEntry)
if err != nil {
return err
}
err = wire.WriteVarInt(w, uint64(len(serializedUTXOEntry)))
if err != nil {
return err
}
err = binary.Write(w, byteOrder, serializedUTXOEntry)
if err != nil {
return err
}
}
return nil
}
// flushToDB writes all dirty diff data to the database. If all writes // flushToDB writes all dirty diff data to the database. If all writes
// succeed, this clears the dirty set. // succeed, this clears the dirty set.
func (diffStore *utxoDiffStore) flushToDB(dbTx database.Tx) error { func (diffStore *utxoDiffStore) flushToDB(dbTx database.Tx) error {

308
blockdag/utxoio.go Normal file
View File

@ -0,0 +1,308 @@
package blockdag
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"math/big"
"github.com/daglabs/btcd/btcec"
"github.com/daglabs/btcd/util/daghash"
"github.com/daglabs/btcd/wire"
)
// serializeBlockUTXODiffData serializes diff data in the following format:
// Name | Data type | Description
// ------------ | --------- | -----------
// hasDiffChild | Boolean | Indicates if a diff child exist
// diffChild | Hash | The diffChild's hash. Empty if hasDiffChild is true.
// diff | UTXODiff | The diff data's diff
func serializeBlockUTXODiffData(diffData *blockUTXODiffData) ([]byte, error) {
w := &bytes.Buffer{}
hasDiffChild := diffData.diffChild != nil
err := wire.WriteElement(w, hasDiffChild)
if err != nil {
return nil, err
}
if hasDiffChild {
err := wire.WriteElement(w, diffData.diffChild.hash)
if err != nil {
return nil, err
}
}
err = serializeUTXODiff(w, diffData.diff)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}
// utxoEntryHeaderCode returns the calculated header code to be used when
// serializing the provided utxo entry.
func utxoEntryHeaderCode(entry *UTXOEntry) uint64 {
// As described in the serialization format comments, the header code
// encodes the height shifted over one bit and the block reward flag in the
// lowest bit.
headerCode := uint64(entry.BlockChainHeight()) << 1
if entry.IsBlockReward() {
headerCode |= 0x01
}
return headerCode
}
func (diffStore *utxoDiffStore) deserializeBlockUTXODiffData(serializedDiffDataBytes []byte) (*blockUTXODiffData, error) {
diffData := &blockUTXODiffData{}
serializedDiffData := bytes.NewBuffer(serializedDiffDataBytes)
var hasDiffChild bool
err := wire.ReadElement(serializedDiffData, &hasDiffChild)
if err != nil {
return nil, err
}
if hasDiffChild {
hash := &daghash.Hash{}
err := wire.ReadElement(serializedDiffData, hash)
if err != nil {
return nil, err
}
diffData.diffChild = diffStore.dag.index.LookupNode(hash)
}
diffData.diff = &UTXODiff{}
diffData.diff.toAdd, err = deserializeDiffEntries(serializedDiffData)
if err != nil {
return nil, err
}
diffData.diff.toRemove, err = deserializeDiffEntries(serializedDiffData)
if err != nil {
return nil, err
}
diffData.diff.diffMultiset, err = deserializeMultiset(serializedDiffData)
if err != nil {
return nil, err
}
return diffData, nil
}
func deserializeDiffEntries(r io.Reader) (utxoCollection, error) {
count, err := wire.ReadVarInt(r)
if err != nil {
return nil, err
}
collection := utxoCollection{}
for i := uint64(0); i < count; i++ {
outPointSize, err := wire.ReadVarInt(r)
if err != nil {
return nil, err
}
serializedOutPoint := make([]byte, outPointSize)
err = binary.Read(r, byteOrder, serializedOutPoint)
if err != nil {
return nil, err
}
outPoint, err := deserializeOutPoint(serializedOutPoint)
if err != nil {
return nil, err
}
utxoEntrySize, err := wire.ReadVarInt(r)
if err != nil {
return nil, err
}
serializedEntry := make([]byte, utxoEntrySize)
err = binary.Read(r, byteOrder, serializedEntry)
if err != nil {
return nil, err
}
utxoEntry, err := deserializeUTXOEntry(serializedEntry)
if err != nil {
return nil, err
}
collection.add(*outPoint, utxoEntry)
}
return collection, nil
}
// deserializeMultiset deserializes an EMCH multiset.
// See serializeMultiset for more details.
func deserializeMultiset(r io.Reader) (*btcec.Multiset, error) {
xBytes := make([]byte, multisetPointSize)
yBytes := make([]byte, multisetPointSize)
err := binary.Read(r, byteOrder, xBytes)
if err != nil {
return nil, err
}
err = binary.Read(r, byteOrder, yBytes)
if err != nil {
return nil, err
}
var x, y big.Int
x.SetBytes(xBytes)
y.SetBytes(yBytes)
return btcec.NewMultisetFromPoint(btcec.S256(), &x, &y), nil
}
// serializeUTXODiff serializes UTXODiff by serializing
// UTXODiff.toAdd, UTXODiff.toRemove and UTXODiff.Multiset one after the other.
func serializeUTXODiff(w io.Writer, diff *UTXODiff) error {
err := serializeUTXOCollection(w, diff.toAdd)
if err != nil {
return err
}
err = serializeUTXOCollection(w, diff.toRemove)
if err != nil {
return err
}
err = serializeMultiset(w, diff.diffMultiset)
if err != nil {
return err
}
return nil
}
// serializeUTXOCollection serializes utxoCollection by iterating over
// the utxo entries and serializing them and their corresponding outpoint
// prefixed by a varint that indicates their size.
func serializeUTXOCollection(w io.Writer, collection utxoCollection) error {
err := wire.WriteVarInt(w, uint64(len(collection)))
if err != nil {
return err
}
for outPoint, utxoEntry := range collection {
err := serializeUTXO(w, utxoEntry, &outPoint)
if err != nil {
return err
}
}
return nil
}
// serializeMultiset serializes an ECMH multiset. The serialization
// is done by taking the (x,y) coordinnates of the multiset point and
// padding each one of them with 32 byte (it'll be 32 byte in most
// cases anyway except one of the coordinates is zero) and writing
// them one after the other.
func serializeMultiset(w io.Writer, ms *btcec.Multiset) error {
x, y := ms.Point()
xBytes := make([]byte, multisetPointSize)
copy(xBytes, x.Bytes())
yBytes := make([]byte, multisetPointSize)
copy(yBytes, y.Bytes())
err := binary.Write(w, byteOrder, xBytes)
if err != nil {
return err
}
err = binary.Write(w, byteOrder, yBytes)
if err != nil {
return err
}
return nil
}
// serializeUTXO serializes a utxo entry-outpoint pair
func serializeUTXO(w io.Writer, entry *UTXOEntry, outPoint *wire.OutPoint) error {
serializedOutPoint := *outpointKey(*outPoint)
err := wire.WriteVarInt(w, uint64(len(serializedOutPoint)))
if err != nil {
return err
}
err = binary.Write(w, byteOrder, serializedOutPoint)
if err != nil {
return err
}
serializedUTXOEntry := serializeUTXOEntry(entry)
err = wire.WriteVarInt(w, uint64(len(serializedUTXOEntry)))
if err != nil {
return err
}
err = binary.Write(w, byteOrder, serializedUTXOEntry)
if err != nil {
return err
}
return nil
}
// serializeUTXOEntry returns the entry serialized to a format that is suitable
// for long-term storage. The format is described in detail above.
func serializeUTXOEntry(entry *UTXOEntry) []byte {
// Encode the header code.
headerCode := utxoEntryHeaderCode(entry)
// Calculate the size needed to serialize the entry.
size := serializeSizeVLQ(headerCode) +
compressedTxOutSize(uint64(entry.Amount()), entry.PkScript())
// Serialize the header code followed by the compressed unspent
// transaction output.
serialized := make([]byte, size)
offset := putVLQ(serialized, headerCode)
offset += putCompressedTxOut(serialized[offset:], uint64(entry.Amount()),
entry.PkScript())
return serialized
}
// deserializeOutPoint decodes an outPoint from the passed serialized byte
// slice into a new wire.OutPoint using a format that is suitable for long-
// term storage. this format is described in detail above.
func deserializeOutPoint(serialized []byte) (*wire.OutPoint, error) {
if len(serialized) <= daghash.HashSize {
return nil, errDeserialize("unexpected end of data")
}
txID := daghash.TxID{}
txID.SetBytes(serialized[:daghash.HashSize])
index, _ := deserializeVLQ(serialized[daghash.HashSize:])
return wire.NewOutPoint(&txID, uint32(index)), nil
}
// deserializeUTXOEntry decodes a UTXO entry from the passed serialized byte
// slice into a new UTXOEntry using a format that is suitable for long-term
// storage. The format is described in detail above.
func deserializeUTXOEntry(serialized []byte) (*UTXOEntry, error) {
// Deserialize the header code.
code, offset := deserializeVLQ(serialized)
if offset >= len(serialized) {
return nil, errDeserialize("unexpected end of data after header")
}
// Decode the header code.
//
// Bit 0 indicates whether the containing transaction is a block reward.
// Bits 1-x encode height of containing transaction.
isBlockReward := code&0x01 != 0
blockChainHeight := code >> 1
// Decode the compressed unspent transaction output.
amount, pkScript, _, err := decodeCompressedTxOut(serialized[offset:])
if err != nil {
return nil, errDeserialize(fmt.Sprintf("unable to decode "+
"UTXO: %s", err))
}
entry := &UTXOEntry{
amount: amount,
pkScript: pkScript,
blockChainHeight: blockChainHeight,
packedFlags: 0,
}
if isBlockReward {
entry.packedFlags |= tfBlockReward
}
return entry, nil
}

View File

@ -1,12 +1,14 @@
package blockdag package blockdag
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"math" "math"
"sort" "sort"
"strings" "strings"
"github.com/daglabs/btcd/btcec"
"github.com/daglabs/btcd/wire" "github.com/daglabs/btcd/wire"
) )
@ -128,15 +130,17 @@ func (uc utxoCollection) clone() utxoCollection {
// UTXODiff represents a diff between two UTXO Sets. // UTXODiff represents a diff between two UTXO Sets.
type UTXODiff struct { type UTXODiff struct {
toAdd utxoCollection toAdd utxoCollection
toRemove utxoCollection toRemove utxoCollection
diffMultiset *btcec.Multiset
} }
// NewUTXODiff creates a new, empty utxoDiff // NewUTXODiff creates a new, empty utxoDiff
func NewUTXODiff() *UTXODiff { func NewUTXODiff() *UTXODiff {
return &UTXODiff{ return &UTXODiff{
toAdd: utxoCollection{}, toAdd: utxoCollection{},
toRemove: utxoCollection{}, toRemove: utxoCollection{},
diffMultiset: btcec.NewMultiset(btcec.S256()),
} }
} }
@ -216,6 +220,9 @@ func (d *UTXODiff) diffFrom(other *UTXODiff) (*UTXODiff, error) {
} }
} }
// Create a new diffMultiset as the subtraction of the two diffs.
result.diffMultiset = other.diffMultiset.Subtract(d.diffMultiset)
return result, nil return result, nil
} }
@ -290,31 +297,61 @@ func (d *UTXODiff) WithDiff(diff *UTXODiff) (*UTXODiff, error) {
} }
} }
// Apply diff.diffMultiset to d.diffMultiset
result.diffMultiset = d.diffMultiset.Union(diff.diffMultiset)
return result, nil return result, nil
} }
// clone returns a clone of this utxoDiff // clone returns a clone of this utxoDiff
func (d *UTXODiff) clone() *UTXODiff { func (d *UTXODiff) clone() *UTXODiff {
return &UTXODiff{ return &UTXODiff{
toAdd: d.toAdd.clone(), toAdd: d.toAdd.clone(),
toRemove: d.toRemove.clone(), toRemove: d.toRemove.clone(),
diffMultiset: d.diffMultiset.Clone(),
} }
} }
//RemoveTxOuts marks the transaction's outputs to removal // AddEntry adds a UTXOEntry to the diff
func (d *UTXODiff) RemoveTxOuts(tx *wire.MsgTx) { func (d *UTXODiff) AddEntry(outPoint wire.OutPoint, entry *UTXOEntry) error {
for idx := range tx.TxOut { if d.toRemove.contains(outPoint) {
d.toRemove.add(*wire.NewOutPoint(tx.TxID(), uint32(idx)), nil) d.toRemove.remove(outPoint)
} else if _, exists := d.toAdd[outPoint]; exists {
return fmt.Errorf("AddEntry: Cannot add outpoint %s twice", outPoint)
} else {
d.toAdd.add(outPoint, entry)
} }
var err error
newMs, err := addUTXOToMultiset(d.diffMultiset, entry, &outPoint)
if err != nil {
return err
}
d.diffMultiset = newMs
return nil
} }
//AddEntry adds an UTXOEntry to the diff // RemoveEntry removes a UTXOEntry from the diff
func (d *UTXODiff) AddEntry(outpoint wire.OutPoint, entry *UTXOEntry) { func (d *UTXODiff) RemoveEntry(outPoint wire.OutPoint, entry *UTXOEntry) error {
d.toAdd.add(outpoint, entry) if d.toAdd.contains(outPoint) {
d.toAdd.remove(outPoint)
} else if _, exists := d.toRemove[outPoint]; exists {
return fmt.Errorf("removeEntry: Cannot remove outpoint %s twice", outPoint)
} else {
d.toRemove.add(outPoint, entry)
}
var err error
newMs, err := removeUTXOFromMultiset(d.diffMultiset, entry, &outPoint)
if err != nil {
return err
}
d.diffMultiset = newMs
return nil
} }
func (d UTXODiff) String() string { func (d UTXODiff) String() string {
return fmt.Sprintf("toAdd: %s; toRemove: %s", d.toAdd, d.toRemove) return fmt.Sprintf("toAdd: %s; toRemove: %s, Multiset-Hash: %s", d.toAdd, d.toRemove, d.diffMultiset.Hash())
} }
// NewUTXOEntry creates a new utxoEntry representing the given txOut // NewUTXOEntry creates a new utxoEntry representing the given txOut
@ -349,9 +386,10 @@ type UTXOSet interface {
diffFrom(other UTXOSet) (*UTXODiff, error) diffFrom(other UTXOSet) (*UTXODiff, error)
WithDiff(utxoDiff *UTXODiff) (UTXOSet, error) WithDiff(utxoDiff *UTXODiff) (UTXOSet, error)
diffFromTx(tx *wire.MsgTx, node *blockNode) (*UTXODiff, error) diffFromTx(tx *wire.MsgTx, node *blockNode) (*UTXODiff, error)
AddTx(tx *wire.MsgTx, blockHeight uint64) (ok bool) AddTx(tx *wire.MsgTx, blockHeight uint64) (ok bool, err error)
clone() UTXOSet clone() UTXOSet
Get(outPoint wire.OutPoint) (*UTXOEntry, bool) Get(outPoint wire.OutPoint) (*UTXOEntry, bool)
Multiset() *btcec.Multiset
} }
// diffFromTx is a common implementation for diffFromTx, that works // diffFromTx is a common implementation for diffFromTx, that works
@ -364,7 +402,10 @@ func diffFromTx(u UTXOSet, tx *wire.MsgTx, containingNode *blockNode) (*UTXODiff
if !isBlockReward { if !isBlockReward {
for _, txIn := range tx.TxIn { for _, txIn := range tx.TxIn {
if entry, ok := u.Get(txIn.PreviousOutPoint); ok { if entry, ok := u.Get(txIn.PreviousOutPoint); ok {
diff.toRemove.add(txIn.PreviousOutPoint, entry) err := diff.RemoveEntry(txIn.PreviousOutPoint, entry)
if err != nil {
return nil, err
}
} else { } else {
return nil, ruleError(ErrMissingTxOut, fmt.Sprintf( return nil, ruleError(ErrMissingTxOut, fmt.Sprintf(
"Transaction %s is invalid because spends outpoint %s that is not in utxo set", "Transaction %s is invalid because spends outpoint %s that is not in utxo set",
@ -375,7 +416,10 @@ func diffFromTx(u UTXOSet, tx *wire.MsgTx, containingNode *blockNode) (*UTXODiff
for i, txOut := range tx.TxOut { for i, txOut := range tx.TxOut {
entry := NewUTXOEntry(txOut, isBlockReward, containingNode.height) entry := NewUTXOEntry(txOut, isBlockReward, containingNode.height)
outPoint := *wire.NewOutPoint(tx.TxID(), uint32(i)) outPoint := *wire.NewOutPoint(tx.TxID(), uint32(i))
diff.toAdd.add(outPoint, entry) err := diff.AddEntry(outPoint, entry)
if err != nil {
return nil, err
}
} }
return diff, nil return diff, nil
} }
@ -383,12 +427,14 @@ func diffFromTx(u UTXOSet, tx *wire.MsgTx, containingNode *blockNode) (*UTXODiff
// FullUTXOSet represents a full list of transaction outputs and their values // FullUTXOSet represents a full list of transaction outputs and their values
type FullUTXOSet struct { type FullUTXOSet struct {
utxoCollection utxoCollection
UTXOMultiset *btcec.Multiset
} }
// NewFullUTXOSet creates a new utxoSet with full list of transaction outputs and their values // NewFullUTXOSet creates a new utxoSet with full list of transaction outputs and their values
func NewFullUTXOSet() *FullUTXOSet { func NewFullUTXOSet() *FullUTXOSet {
return &FullUTXOSet{ return &FullUTXOSet{
utxoCollection: utxoCollection{}, utxoCollection: utxoCollection{},
UTXOMultiset: btcec.NewMultiset(btcec.S256()),
} }
} }
@ -412,17 +458,22 @@ func (fus *FullUTXOSet) WithDiff(other *UTXODiff) (UTXOSet, error) {
return NewDiffUTXOSet(fus, other.clone()), nil 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 // AddTx adds a transaction to this utxoSet and returns isAccepted=true iff it's valid in this UTXO's context.
func (fus *FullUTXOSet) AddTx(tx *wire.MsgTx, blockHeight uint64) bool { // It returns error if something unexpected happens, such as serialization error (isAccepted=false doesn't
// necessarily means there's an error).
func (fus *FullUTXOSet) AddTx(tx *wire.MsgTx, blockHeight uint64) (isAccepted bool, err error) {
isBlockReward := tx.IsBlockReward() isBlockReward := tx.IsBlockReward()
if !isBlockReward { if !isBlockReward {
if !fus.containsInputs(tx) { if !fus.containsInputs(tx) {
return false return false, nil
} }
for _, txIn := range tx.TxIn { for _, txIn := range tx.TxIn {
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.TxID, txIn.PreviousOutPoint.Index) outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.TxID, txIn.PreviousOutPoint.Index)
fus.remove(outPoint) err := fus.removeAndUpdateMultiset(outPoint)
if err != nil {
return false, err
}
} }
} }
@ -430,10 +481,13 @@ func (fus *FullUTXOSet) AddTx(tx *wire.MsgTx, blockHeight uint64) bool {
outPoint := *wire.NewOutPoint(tx.TxID(), uint32(i)) outPoint := *wire.NewOutPoint(tx.TxID(), uint32(i))
entry := NewUTXOEntry(txOut, isBlockReward, blockHeight) entry := NewUTXOEntry(txOut, isBlockReward, blockHeight)
fus.add(outPoint, entry) err := fus.addAndUpdateMultiset(outPoint, entry)
if err != nil {
return false, err
}
} }
return true return true, nil
} }
// diffFromTx returns a diff that is equivalent to provided transaction, // diffFromTx returns a diff that is equivalent to provided transaction,
@ -455,7 +509,7 @@ func (fus *FullUTXOSet) containsInputs(tx *wire.MsgTx) bool {
// clone returns a clone of this utxoSet // clone returns a clone of this utxoSet
func (fus *FullUTXOSet) clone() UTXOSet { func (fus *FullUTXOSet) clone() UTXOSet {
return &FullUTXOSet{utxoCollection: fus.utxoCollection.clone()} return &FullUTXOSet{utxoCollection: fus.utxoCollection.clone(), UTXOMultiset: fus.UTXOMultiset.Clone()}
} }
// Get returns the UTXOEntry associated with the given OutPoint, and a boolean indicating if such entry was found // Get returns the UTXOEntry associated with the given OutPoint, and a boolean indicating if such entry was found
@ -464,6 +518,38 @@ func (fus *FullUTXOSet) Get(outPoint wire.OutPoint) (*UTXOEntry, bool) {
return utxoEntry, ok return utxoEntry, ok
} }
// Multiset returns the ecmh-Multiset of this utxoSet
func (fus *FullUTXOSet) Multiset() *btcec.Multiset {
return fus.UTXOMultiset
}
// addAndUpdateMultiset adds a UTXOEntry to this utxoSet and updates its multiset accordingly
func (fus *FullUTXOSet) addAndUpdateMultiset(outPoint wire.OutPoint, entry *UTXOEntry) error {
fus.add(outPoint, entry)
newMs, err := addUTXOToMultiset(fus.UTXOMultiset, entry, &outPoint)
if err != nil {
return err
}
fus.UTXOMultiset = newMs
return nil
}
// removeAndUpdateMultiset removes a UTXOEntry from this utxoSet and updates its multiset accordingly
func (fus *FullUTXOSet) removeAndUpdateMultiset(outPoint wire.OutPoint) error {
entry, ok := fus.Get(outPoint)
if !ok {
return fmt.Errorf("Couldn't find outpoint %s", outPoint)
}
fus.remove(outPoint)
var err error
newMs, err := removeUTXOFromMultiset(fus.UTXOMultiset, entry, &outPoint)
if err != nil {
return err
}
fus.UTXOMultiset = newMs
return nil
}
// DiffUTXOSet represents a utxoSet with a base fullUTXOSet and a UTXODiff // DiffUTXOSet represents a utxoSet with a base fullUTXOSet and a UTXODiff
type DiffUTXOSet struct { type DiffUTXOSet struct {
base *FullUTXOSet base *FullUTXOSet
@ -504,27 +590,31 @@ func (dus *DiffUTXOSet) WithDiff(other *UTXODiff) (UTXOSet, error) {
} }
// AddTx adds a transaction to this utxoSet and returns true iff it's valid in this UTXO's context // 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, blockHeight uint64) bool { func (dus *DiffUTXOSet) AddTx(tx *wire.MsgTx, blockHeight uint64) (bool, error) {
isBlockReward := tx.IsBlockReward() isBlockReward := tx.IsBlockReward()
if !isBlockReward && !dus.containsInputs(tx) { if !isBlockReward && !dus.containsInputs(tx) {
return false return false, nil
} }
dus.appendTx(tx, blockHeight, isBlockReward) err := dus.appendTx(tx, blockHeight, isBlockReward)
if err != nil {
return false, err
}
return true return true, nil
} }
func (dus *DiffUTXOSet) appendTx(tx *wire.MsgTx, blockHeight uint64, isBlockReward bool) { func (dus *DiffUTXOSet) appendTx(tx *wire.MsgTx, blockHeight uint64, isBlockReward bool) error {
if !isBlockReward { if !isBlockReward {
for _, txIn := range tx.TxIn { for _, txIn := range tx.TxIn {
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.TxID, txIn.PreviousOutPoint.Index) outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.TxID, txIn.PreviousOutPoint.Index)
if dus.UTXODiff.toAdd.contains(outPoint) { entry, ok := dus.Get(outPoint)
dus.UTXODiff.toAdd.remove(outPoint) if !ok {
} else { return fmt.Errorf("Couldn't find entry for outpoint %s", outPoint)
prevUTXOEntry := dus.base.utxoCollection[outPoint] }
dus.UTXODiff.toRemove.add(outPoint, prevUTXOEntry) err := dus.UTXODiff.RemoveEntry(outPoint, entry)
if err != nil {
return err
} }
} }
} }
@ -533,12 +623,12 @@ func (dus *DiffUTXOSet) appendTx(tx *wire.MsgTx, blockHeight uint64, isBlockRewa
outPoint := *wire.NewOutPoint(tx.TxID(), uint32(i)) outPoint := *wire.NewOutPoint(tx.TxID(), uint32(i))
entry := NewUTXOEntry(txOut, isBlockReward, blockHeight) entry := NewUTXOEntry(txOut, isBlockReward, blockHeight)
if dus.UTXODiff.toRemove.contains(outPoint) { err := dus.UTXODiff.AddEntry(outPoint, entry)
dus.UTXODiff.toRemove.remove(outPoint) if err != nil {
} else { return err
dus.UTXODiff.toAdd.add(outPoint, entry)
} }
} }
return nil
} }
func (dus *DiffUTXOSet) containsInputs(tx *wire.MsgTx) bool { func (dus *DiffUTXOSet) containsInputs(tx *wire.MsgTx) bool {
@ -556,16 +646,23 @@ func (dus *DiffUTXOSet) containsInputs(tx *wire.MsgTx) bool {
} }
// meldToBase updates the base fullUTXOSet with all changes in diff // meldToBase updates the base fullUTXOSet with all changes in diff
func (dus *DiffUTXOSet) meldToBase() { func (dus *DiffUTXOSet) meldToBase() error {
for outPoint := range dus.UTXODiff.toRemove { for outPoint := range dus.UTXODiff.toRemove {
dus.base.remove(outPoint) if _, ok := dus.base.Get(outPoint); ok {
dus.base.remove(outPoint)
} else {
return fmt.Errorf("Couldn't remove outpoint %s because it doesn't exist in the DiffUTXOSet base", outPoint)
}
} }
for outPoint, utxoEntry := range dus.UTXODiff.toAdd { for outPoint, utxoEntry := range dus.UTXODiff.toAdd {
dus.base.add(outPoint, utxoEntry) dus.base.add(outPoint, utxoEntry)
} }
dus.base.UTXOMultiset = dus.base.UTXOMultiset.Union(dus.UTXODiff.diffMultiset)
dus.UTXODiff = NewUTXODiff() dus.UTXODiff = NewUTXODiff()
return nil
} }
// diffFromTx returns a diff that is equivalent to provided transaction, // diffFromTx returns a diff that is equivalent to provided transaction,
@ -575,7 +672,7 @@ func (dus *DiffUTXOSet) diffFromTx(tx *wire.MsgTx, node *blockNode) (*UTXODiff,
} }
func (dus *DiffUTXOSet) String() string { func (dus *DiffUTXOSet) String() string {
return fmt.Sprintf("{Base: %s, To Add: %s, To Remove: %s}", dus.base, dus.UTXODiff.toAdd, dus.UTXODiff.toRemove) return fmt.Sprintf("{Base: %s, To Add: %s, To Remove: %s, Multiset-Hash:%s}", dus.base, dus.UTXODiff.toAdd, dus.UTXODiff.toRemove, dus.Multiset().Hash())
} }
// clone returns a clone of this UTXO Set // clone returns a clone of this UTXO Set
@ -595,3 +692,26 @@ func (dus *DiffUTXOSet) Get(outPoint wire.OutPoint) (*UTXOEntry, bool) {
txOut, ok := dus.UTXODiff.toAdd.get(outPoint) txOut, ok := dus.UTXODiff.toAdd.get(outPoint)
return txOut, ok return txOut, ok
} }
// Multiset returns the ecmh-Multiset of this utxoSet
func (dus *DiffUTXOSet) Multiset() *btcec.Multiset {
return dus.base.UTXOMultiset.Union(dus.UTXODiff.diffMultiset)
}
func addUTXOToMultiset(ms *btcec.Multiset, entry *UTXOEntry, outPoint *wire.OutPoint) (*btcec.Multiset, error) {
w := &bytes.Buffer{}
err := serializeUTXO(w, entry, outPoint)
if err != nil {
return nil, err
}
return ms.Add(w.Bytes()), nil
}
func removeUTXOFromMultiset(ms *btcec.Multiset, entry *UTXOEntry, outPoint *wire.OutPoint) (*btcec.Multiset, error) {
w := &bytes.Buffer{}
err := serializeUTXO(w, entry, outPoint)
if err != nil {
return nil, err
}
return ms.Remove(w.Bytes()), nil
}

View File

@ -5,6 +5,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/daglabs/btcd/btcec"
"github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/util/daghash" "github.com/daglabs/btcd/util/daghash"
"github.com/daglabs/btcd/wire" "github.com/daglabs/btcd/wire"
@ -77,20 +78,26 @@ func TestUTXODiff(t *testing.T) {
outPoint1 := *wire.NewOutPoint(txID1, 0) outPoint1 := *wire.NewOutPoint(txID1, 0)
utxoEntry0 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0) utxoEntry0 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
utxoEntry1 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}, false, 1) utxoEntry1 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}, false, 1)
diff := UTXODiff{
toAdd: utxoCollection{outPoint0: utxoEntry0},
toRemove: utxoCollection{outPoint1: utxoEntry1},
}
// Test utxoDiff creation // Test utxoDiff creation
newDiff := NewUTXODiff() diff := NewUTXODiff()
if len(newDiff.toAdd) != 0 || len(newDiff.toRemove) != 0 { if len(diff.toAdd) != 0 || len(diff.toRemove) != 0 {
t.Errorf("new diff is not empty") t.Errorf("new diff is not empty")
} }
err := diff.AddEntry(outPoint0, utxoEntry0)
if err != nil {
t.Fatalf("Error adding entry to utxo diff: %s", err)
}
err = diff.RemoveEntry(outPoint1, utxoEntry1)
if err != nil {
t.Fatalf("Error adding entry to utxo diff: %s", err)
}
// Test utxoDiff cloning // Test utxoDiff cloning
clonedDiff := *diff.clone() clonedDiff := diff.clone()
if &clonedDiff == &diff { if clonedDiff == diff {
t.Errorf("cloned diff is reference-equal to the original") t.Errorf("cloned diff is reference-equal to the original")
} }
if !reflect.DeepEqual(clonedDiff, diff) { if !reflect.DeepEqual(clonedDiff, diff) {
@ -99,7 +106,7 @@ func TestUTXODiff(t *testing.T) {
} }
// Test utxoDiff string representation // Test utxoDiff string representation
expectedDiffString := "toAdd: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ]; toRemove: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20 ]" expectedDiffString := "toAdd: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ]; toRemove: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20 ], Multiset-Hash: 7cb61e48005b0c817211d04589d719bff87d86a6a6ce2454515f57265382ded7"
diffString := clonedDiff.String() diffString := clonedDiff.String()
if diffString != expectedDiffString { if diffString != expectedDiffString {
t.Errorf("unexpected diff string. "+ t.Errorf("unexpected diff string. "+
@ -288,42 +295,111 @@ func TestUTXODiffRules(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
this := addMultisetToDiff(t, test.this)
other := addMultisetToDiff(t, test.other)
expectedDiffFromResult := addMultisetToDiff(t, test.expectedDiffFromResult)
expectedWithDiffResult := addMultisetToDiff(t, test.expectedWithDiffResult)
// diffFrom from this to other // diffFrom from this to other
diffResult, err := test.this.diffFrom(test.other) diffResult, err := this.diffFrom(other)
// Test whether diffFrom returned an error // Test whether diffFrom returned an error
isDiffFromOk := err == nil isDiffFromOk := err == nil
expectedIsDiffFromOk := test.expectedDiffFromResult != nil expectedIsDiffFromOk := expectedDiffFromResult != nil
if isDiffFromOk != expectedIsDiffFromOk { if isDiffFromOk != expectedIsDiffFromOk {
t.Errorf("unexpected diffFrom error in test \"%s\". "+ t.Errorf("unexpected diffFrom error in test \"%s\". "+
"Expected: \"%t\", got: \"%t\".", test.name, expectedIsDiffFromOk, isDiffFromOk) "Expected: \"%t\", got: \"%t\".", test.name, expectedIsDiffFromOk, isDiffFromOk)
} }
// If not error, test the diffFrom result // If not error, test the diffFrom result
if isDiffFromOk && !reflect.DeepEqual(diffResult, test.expectedDiffFromResult) { if isDiffFromOk && !expectedDiffFromResult.equal(diffResult) {
t.Errorf("unexpected diffFrom result in test \"%s\". "+ t.Errorf("unexpected diffFrom result in test \"%s\". "+
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedDiffFromResult, diffResult) "Expected: \"%v\", got: \"%v\".", test.name, expectedDiffFromResult, diffResult)
} }
// WithDiff from this to other // WithDiff from this to other
withDiffResult, err := test.this.WithDiff(test.other) withDiffResult, err := this.WithDiff(other)
// Test whether WithDiff returned an error // Test whether WithDiff returned an error
isWithDiffOk := err == nil isWithDiffOk := err == nil
expectedIsWithDiffOk := test.expectedWithDiffResult != nil expectedIsWithDiffOk := expectedWithDiffResult != nil
if isWithDiffOk != expectedIsWithDiffOk { if isWithDiffOk != expectedIsWithDiffOk {
t.Errorf("unexpected WithDiff error in test \"%s\". "+ t.Errorf("unexpected WithDiff error in test \"%s\". "+
"Expected: \"%t\", got: \"%t\".", test.name, expectedIsWithDiffOk, isWithDiffOk) "Expected: \"%t\", got: \"%t\".", test.name, expectedIsWithDiffOk, isWithDiffOk)
} }
// Ig not error, test the WithDiff result // If not error, test the WithDiff result
if isWithDiffOk && !reflect.DeepEqual(withDiffResult, test.expectedWithDiffResult) { if isWithDiffOk && !withDiffResult.equal(expectedWithDiffResult) {
t.Errorf("unexpected WithDiff result in test \"%s\". "+ t.Errorf("unexpected WithDiff result in test \"%s\". "+
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedWithDiffResult, withDiffResult) "Expected: \"%v\", got: \"%v\".", test.name, expectedWithDiffResult, withDiffResult)
} }
} }
} }
func areMultisetsEqual(a *btcec.Multiset, b *btcec.Multiset) bool {
aX, aY := a.Point()
bX, bY := b.Point()
return aX.Cmp(bX) == 0 && aY.Cmp(bY) == 0
}
func (d *UTXODiff) equal(other *UTXODiff) bool {
return reflect.DeepEqual(d.toAdd, other.toAdd) &&
reflect.DeepEqual(d.toRemove, other.toRemove) &&
areMultisetsEqual(d.diffMultiset, other.diffMultiset)
}
func (fus *FullUTXOSet) equal(other *FullUTXOSet) bool {
return reflect.DeepEqual(fus.utxoCollection, other.utxoCollection) &&
areMultisetsEqual(fus.UTXOMultiset, other.UTXOMultiset)
}
func (dus *DiffUTXOSet) equal(other *DiffUTXOSet) bool {
return dus.base.equal(other.base) && dus.UTXODiff.equal(other.UTXODiff)
}
func addMultisetToDiff(t *testing.T, diff *UTXODiff) *UTXODiff {
if diff == nil {
return nil
}
diffWithMs := NewUTXODiff()
for outPoint, entry := range diff.toAdd {
err := diffWithMs.AddEntry(outPoint, entry)
if err != nil {
t.Fatalf("Error with diffWithMs.AddEntry: %s", err)
}
}
for outPoint, entry := range diff.toRemove {
err := diffWithMs.RemoveEntry(outPoint, entry)
if err != nil {
t.Fatalf("Error with diffWithMs.removeEntry: %s", err)
}
}
return diffWithMs
}
func addMultisetToFullUTXOSet(t *testing.T, fus *FullUTXOSet) *FullUTXOSet {
if fus == nil {
return nil
}
fusWithMs := NewFullUTXOSet()
for outPoint, entry := range fus.utxoCollection {
err := fusWithMs.addAndUpdateMultiset(outPoint, entry)
if err != nil {
t.Fatalf("Error with diffWithMs.AddEntry: %s", err)
}
}
return fusWithMs
}
func addMultisetToDiffUTXOSet(t *testing.T, diffSet *DiffUTXOSet) *DiffUTXOSet {
if diffSet == nil {
return nil
}
diffWithMs := addMultisetToDiff(t, diffSet.UTXODiff)
baseWithMs := addMultisetToFullUTXOSet(t, diffSet.base)
return NewDiffUTXOSet(baseWithMs, diffWithMs)
}
// TestFullUTXOSet makes sure that fullUTXOSet is working as expected. // TestFullUTXOSet makes sure that fullUTXOSet is working as expected.
func TestFullUTXOSet(t *testing.T) { func TestFullUTXOSet(t *testing.T) {
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000") txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
@ -334,10 +410,10 @@ func TestFullUTXOSet(t *testing.T) {
txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20} txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20}
utxoEntry0 := NewUTXOEntry(txOut0, true, 0) utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
utxoEntry1 := NewUTXOEntry(txOut1, false, 1) utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
diff := &UTXODiff{ diff := addMultisetToDiff(t, &UTXODiff{
toAdd: utxoCollection{outPoint0: utxoEntry0}, toAdd: utxoCollection{outPoint0: utxoEntry0},
toRemove: utxoCollection{outPoint1: utxoEntry1}, toRemove: utxoCollection{outPoint1: utxoEntry1},
} })
// Test fullUTXOSet creation // Test fullUTXOSet creation
emptySet := NewFullUTXOSet() emptySet := NewFullUTXOSet()
@ -361,12 +437,16 @@ func TestFullUTXOSet(t *testing.T) {
// Test fullUTXOSet addTx // Test fullUTXOSet addTx
txIn0 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{TxID: *txID0, Index: 0}, Sequence: 0} txIn0 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{TxID: *txID0, Index: 0}, Sequence: 0}
transaction0 := wire.NewNativeMsgTx(1, []*wire.TxIn{txIn0}, []*wire.TxOut{txOut0}) transaction0 := wire.NewNativeMsgTx(1, []*wire.TxIn{txIn0}, []*wire.TxOut{txOut0})
if ok = emptySet.AddTx(transaction0, 0); ok { if isAccepted, err := emptySet.AddTx(transaction0, 0); err != nil {
t.Errorf("AddTx unexpectedly failed: %s", err)
} else if isAccepted {
t.Errorf("addTx unexpectedly succeeded") t.Errorf("addTx unexpectedly succeeded")
} }
emptySet = &FullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}} emptySet = addMultisetToFullUTXOSet(t, &FullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}})
if ok = emptySet.AddTx(transaction0, 0); !ok { if isAccepted, err := emptySet.AddTx(transaction0, 0); err != nil {
t.Errorf("addTx unexpectedly failed") t.Errorf("addTx unexpectedly failed. Error: %s", err)
} else if !isAccepted {
t.Fatalf("AddTx unexpectedly didn't add tx %s", transaction0.TxID())
} }
// Test fullUTXOSet collection // Test fullUTXOSet collection
@ -394,14 +474,16 @@ func TestDiffUTXOSet(t *testing.T) {
txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20} txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20}
utxoEntry0 := NewUTXOEntry(txOut0, true, 0) utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
utxoEntry1 := NewUTXOEntry(txOut1, false, 1) utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
diff := &UTXODiff{ diff := addMultisetToDiff(t, &UTXODiff{
toAdd: utxoCollection{outPoint0: utxoEntry0}, toAdd: utxoCollection{outPoint0: utxoEntry0},
toRemove: utxoCollection{outPoint1: utxoEntry1}, toRemove: utxoCollection{outPoint1: utxoEntry1},
} })
// Test diffUTXOSet creation // Test diffUTXOSet creation
emptySet := NewDiffUTXOSet(NewFullUTXOSet(), NewUTXODiff()) emptySet := NewDiffUTXOSet(NewFullUTXOSet(), NewUTXODiff())
if len(emptySet.collection()) != 0 { if collection, err := emptySet.collection(); err != nil {
t.Errorf("Error getting emptySet collection: %s", err)
} else if len(collection) != 0 {
t.Errorf("new set is not empty") t.Errorf("new set is not empty")
} }
@ -430,11 +512,12 @@ func TestDiffUTXOSet(t *testing.T) {
// .collection() the given diffSet and compare it to expectedCollection // .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) // .clone() the given diffSet and compare its value to itself (expected: equals) and its reference to itself (expected: not equal)
tests := []struct { tests := []struct {
name string name string
diffSet *DiffUTXOSet diffSet *DiffUTXOSet
expectedMeldSet *DiffUTXOSet expectedMeldSet *DiffUTXOSet
expectedString string expectedString string
expectedCollection utxoCollection expectedCollection utxoCollection
expectedMeldToBaseError string
}{ }{
{ {
name: "empty base, empty diff", name: "empty base, empty diff",
@ -452,7 +535,7 @@ func TestDiffUTXOSet(t *testing.T) {
toRemove: utxoCollection{}, toRemove: utxoCollection{},
}, },
}, },
expectedString: "{Base: [ ], To Add: [ ], To Remove: [ ]}", expectedString: "{Base: [ ], To Add: [ ], To Remove: [ ], Multiset-Hash:0000000000000000000000000000000000000000000000000000000000000000}",
expectedCollection: utxoCollection{}, expectedCollection: utxoCollection{},
}, },
{ {
@ -471,7 +554,7 @@ func TestDiffUTXOSet(t *testing.T) {
toRemove: utxoCollection{}, toRemove: utxoCollection{},
}, },
}, },
expectedString: "{Base: [ ], To Add: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], To Remove: [ ]}", expectedString: "{Base: [ ], To Add: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], To Remove: [ ], Multiset-Hash:da4768bd0359c3426268d6707c1fc17a68c45ef1ea734331b07568418234487f}",
expectedCollection: utxoCollection{outPoint0: utxoEntry0}, expectedCollection: utxoCollection{outPoint0: utxoEntry0},
}, },
{ {
@ -483,15 +566,10 @@ func TestDiffUTXOSet(t *testing.T) {
toRemove: utxoCollection{outPoint0: utxoEntry0}, toRemove: utxoCollection{outPoint0: utxoEntry0},
}, },
}, },
expectedMeldSet: &DiffUTXOSet{ expectedMeldSet: nil,
base: &FullUTXOSet{utxoCollection: utxoCollection{}}, expectedString: "{Base: [ ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], Multiset-Hash:046242cb1bb1e6d3fd91d0f181e1b2d4a597ac57fa2584fc3c2eb0e0f46c9369}",
UTXODiff: &UTXODiff{ expectedCollection: utxoCollection{},
toAdd: utxoCollection{}, expectedMeldToBaseError: "Couldn't remove outpoint 0000000000000000000000000000000000000000000000000000000000000000:0 because it doesn't exist in the DiffUTXOSet base",
toRemove: utxoCollection{},
},
},
expectedString: "{Base: [ ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ]}",
expectedCollection: utxoCollection{},
}, },
{ {
name: "one member in base toAdd, one member in diff toAdd", name: "one member in base toAdd, one member in diff toAdd",
@ -514,7 +592,7 @@ func TestDiffUTXOSet(t *testing.T) {
toRemove: utxoCollection{}, toRemove: utxoCollection{},
}, },
}, },
expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], To Add: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20 ], To Remove: [ ]}", expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], To Add: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20 ], To Remove: [ ], Multiset-Hash:556cc61fd4d7e74d7807ca2298c5320375a6a20310a18920e54667220924baff}",
expectedCollection: utxoCollection{ expectedCollection: utxoCollection{
outPoint0: utxoEntry0, outPoint0: utxoEntry0,
outPoint1: utxoEntry1, outPoint1: utxoEntry1,
@ -538,41 +616,56 @@ func TestDiffUTXOSet(t *testing.T) {
toRemove: utxoCollection{}, toRemove: utxoCollection{},
}, },
}, },
expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ]}", expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10 ], Multiset-Hash:0000000000000000000000000000000000000000000000000000000000000000}",
expectedCollection: utxoCollection{}, expectedCollection: utxoCollection{},
}, },
} }
for _, test := range tests { for _, test := range tests {
// Test meldToBase diffSet := addMultisetToDiffUTXOSet(t, test.diffSet)
meldSet := test.diffSet.clone().(*DiffUTXOSet) expectedMeldSet := addMultisetToDiffUTXOSet(t, test.expectedMeldSet)
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 // Test string representation
setString := test.diffSet.String() setString := diffSet.String()
if setString != test.expectedString { if setString != test.expectedString {
t.Errorf("unexpected string in test \"%s\". "+ t.Errorf("unexpected string in test \"%s\". "+
"Expected: \"%s\", got: \"%s\".", test.name, test.expectedString, setString) "Expected: \"%s\", got: \"%s\".", test.name, test.expectedString, setString)
} }
// Test meldToBase
meldSet := diffSet.clone().(*DiffUTXOSet)
err := meldSet.meldToBase()
errString := ""
if err != nil {
errString = err.Error()
}
if test.expectedMeldToBaseError != errString {
t.Errorf("meldToBase in test \"%s\" expected error \"%s\" but got: \"%s\"", test.name, test.expectedMeldToBaseError, errString)
}
if err != nil {
continue
}
if !meldSet.equal(expectedMeldSet) {
t.Errorf("unexpected melded set in test \"%s\". "+
"Expected: \"%v\", got: \"%v\".", test.name, expectedMeldSet, meldSet)
}
// Test collection // Test collection
setCollection := test.diffSet.collection() setCollection, err := diffSet.collection()
if !reflect.DeepEqual(setCollection, test.expectedCollection) { if err != nil {
t.Errorf("Error getting diffSet collection: %s", err)
} else if !reflect.DeepEqual(setCollection, test.expectedCollection) {
t.Errorf("unexpected set collection in test \"%s\". "+ t.Errorf("unexpected set collection in test \"%s\". "+
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedCollection, setCollection) "Expected: \"%v\", got: \"%v\".", test.name, test.expectedCollection, setCollection)
} }
// Test cloning // Test cloning
clonedSet := test.diffSet.clone().(*DiffUTXOSet) clonedSet := diffSet.clone().(*DiffUTXOSet)
if !reflect.DeepEqual(clonedSet, test.diffSet) { if !reflect.DeepEqual(clonedSet, diffSet) {
t.Errorf("unexpected set clone in test \"%s\". "+ t.Errorf("unexpected set clone in test \"%s\". "+
"Expected: \"%v\", got: \"%v\".", test.name, test.diffSet, clonedSet) "Expected: \"%v\", got: \"%v\".", test.name, diffSet, clonedSet)
} }
if clonedSet == test.diffSet { if clonedSet == diffSet {
t.Errorf("cloned set is reference-equal to the original") t.Errorf("cloned set is reference-equal to the original")
} }
} }
@ -640,7 +733,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
// transaction1 spends transaction0 // transaction1 spends transaction0
id1 := transaction0.TxID() id1 := transaction0.TxID()
outPoint1 := *wire.NewOutPoint(id1, 0) outPoint1 := *wire.NewOutPoint(id1, 0)
txIn1 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{TxID: *id1, Index: 0}, Sequence: 0} txIn1 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: outPoint1, Sequence: 0}
txOut1 := &wire.TxOut{PkScript: []byte{1}, Value: 20} txOut1 := &wire.TxOut{PkScript: []byte{1}, Value: 20}
utxoEntry1 := NewUTXOEntry(txOut1, false, 1) utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
transaction1 := wire.NewNativeMsgTx(1, []*wire.TxIn{txIn1}, []*wire.TxOut{txOut1}) transaction1 := wire.NewNativeMsgTx(1, []*wire.TxIn{txIn1}, []*wire.TxOut{txOut1})
@ -648,7 +741,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
// transaction2 spends transaction1 // transaction2 spends transaction1
id2 := transaction1.TxID() id2 := transaction1.TxID()
outPoint2 := *wire.NewOutPoint(id2, 0) outPoint2 := *wire.NewOutPoint(id2, 0)
txIn2 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{TxID: *id2, Index: 0}, Sequence: 0} txIn2 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: outPoint2, Sequence: 0}
txOut2 := &wire.TxOut{PkScript: []byte{2}, Value: 30} txOut2 := &wire.TxOut{PkScript: []byte{2}, Value: 30}
utxoEntry2 := NewUTXOEntry(txOut2, false, 2) utxoEntry2 := NewUTXOEntry(txOut2, false, 2)
transaction2 := wire.NewNativeMsgTx(1, []*wire.TxIn{txIn2}, []*wire.TxOut{txOut2}) transaction2 := wire.NewNativeMsgTx(1, []*wire.TxIn{txIn2}, []*wire.TxOut{txOut2})
@ -771,31 +864,43 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
}, },
} }
testLoop:
for _, test := range tests { for _, test := range tests {
diffSet := test.startSet.clone() startSet := addMultisetToDiffUTXOSet(t, test.startSet)
expectedSet := addMultisetToDiffUTXOSet(t, test.expectedSet)
diffSet := startSet.clone()
// Apply all transactions to diffSet, in order, with the initial block height startHeight // Apply all transactions to diffSet, in order, with the initial block height startHeight
for i, transaction := range test.toAdd { for i, transaction := range test.toAdd {
diffSet.AddTx(transaction, test.startHeight+uint64(i)) _, err := diffSet.AddTx(transaction, test.startHeight+uint64(i))
if err != nil {
t.Errorf("Error adding tx %s in test \"%s\": %s", transaction.TxID(), test.name, err)
continue testLoop
}
} }
// Make sure that the result diffSet equals to the expectedSet // Make sure that the result diffSet equals to the expectedSet
if !reflect.DeepEqual(diffSet, test.expectedSet) { if !diffSet.(*DiffUTXOSet).equal(expectedSet) {
t.Errorf("unexpected diffSet in test \"%s\". "+ t.Errorf("unexpected diffSet in test \"%s\". "+
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedSet, diffSet) "Expected: \"%v\", got: \"%v\".", test.name, expectedSet, diffSet)
} }
} }
} }
func TestDiffFromTx(t *testing.T) { func TestDiffFromTx(t *testing.T) {
fus := &FullUTXOSet{ fus := addMultisetToFullUTXOSet(t, &FullUTXOSet{
utxoCollection: utxoCollection{}, utxoCollection: utxoCollection{},
} })
cbTx, err := createCoinbaseTxForTest(1, 1, 0, &dagconfig.SimNetParams) cbTx, err := createCoinbaseTxForTest(1, 1, 0, &dagconfig.SimNetParams)
if err != nil { if err != nil {
t.Errorf("createCoinbaseTxForTest: %v", err) t.Errorf("createCoinbaseTxForTest: %v", err)
} }
fus.AddTx(cbTx, 1) if isAccepted, err := fus.AddTx(cbTx, 1); err != nil {
t.Fatalf("AddTx unexpectedly failed. Error: %s", err)
} else if !isAccepted {
t.Fatalf("AddTx unexpectedly didn't add tx %s", cbTx.TxID())
}
node := &blockNode{height: 2} //Fake node node := &blockNode{height: 2} //Fake node
cbOutpoint := wire.OutPoint{TxID: *cbTx.TxID(), Index: 0} cbOutpoint := wire.OutPoint{TxID: *cbTx.TxID(), Index: 0}
txIns := []*wire.TxIn{&wire.TxIn{ txIns := []*wire.TxIn{&wire.TxIn{
@ -841,11 +946,16 @@ func TestDiffFromTx(t *testing.T) {
} }
//Test that we get an error if the outpoint is inside diffUTXOSet's toRemove //Test that we get an error if the outpoint is inside diffUTXOSet's toRemove
dus := NewDiffUTXOSet(fus, &UTXODiff{ diff2 := addMultisetToDiff(t, &UTXODiff{
toAdd: utxoCollection{}, toAdd: utxoCollection{},
toRemove: utxoCollection{}, toRemove: utxoCollection{},
}) })
dus.AddTx(tx, 2) dus := NewDiffUTXOSet(fus, diff2)
if isAccepted, err := dus.AddTx(tx, 2); err != nil {
t.Fatalf("AddTx unexpectedly failed. Error: %s", err)
} else if !isAccepted {
t.Fatalf("AddTx unexpectedly didn't add tx %s", tx.TxID())
}
_, err = dus.diffFromTx(tx, node) _, err = dus.diffFromTx(tx, node)
if err == nil { if err == nil {
t.Errorf("diffFromTx: expected an error but got <nil>") t.Errorf("diffFromTx: expected an error but got <nil>")
@ -858,11 +968,14 @@ func (fus *FullUTXOSet) collection() utxoCollection {
} }
// collection returns a collection of all UTXOs in this set // collection returns a collection of all UTXOs in this set
func (dus *DiffUTXOSet) collection() utxoCollection { func (dus *DiffUTXOSet) collection() (utxoCollection, error) {
clone := dus.clone().(*DiffUTXOSet) clone := dus.clone().(*DiffUTXOSet)
clone.meldToBase() err := clone.meldToBase()
if err != nil {
return nil, err
}
return clone.base.collection() return clone.base.collection(), nil
} }
func TestUTXOSetAddEntry(t *testing.T) { func TestUTXOSetAddEntry(t *testing.T) {
@ -880,6 +993,7 @@ func TestUTXOSetAddEntry(t *testing.T) {
outPointToAdd *wire.OutPoint outPointToAdd *wire.OutPoint
utxoEntryToAdd *UTXOEntry utxoEntryToAdd *UTXOEntry
expectedUTXODiff *UTXODiff expectedUTXODiff *UTXODiff
expectedError string
}{ }{
{ {
name: "add an entry", name: "add an entry",
@ -907,62 +1021,23 @@ func TestUTXOSetAddEntry(t *testing.T) {
toAdd: utxoCollection{*outPoint0: utxoEntry0, *outPoint1: utxoEntry1}, toAdd: utxoCollection{*outPoint0: utxoEntry0, *outPoint1: utxoEntry1},
toRemove: utxoCollection{}, toRemove: utxoCollection{},
}, },
expectedError: "AddEntry: Cannot add outpoint 0000000000000000000000000000000000000000000000000000000000000000:0 twice",
}, },
} }
for _, test := range tests { for _, test := range tests {
utxoDiff.AddEntry(*test.outPointToAdd, test.utxoEntryToAdd) expectedUTXODiff := addMultisetToDiff(t, test.expectedUTXODiff)
if !reflect.DeepEqual(utxoDiff, test.expectedUTXODiff) { err := utxoDiff.AddEntry(*test.outPointToAdd, test.utxoEntryToAdd)
t.Fatalf("utxoDiff.AddEntry: unexpected utxoDiff in test '%s'. "+ errString := ""
"Expected: %v, got: %v", test.name, test.expectedUTXODiff, utxoDiff) if err != nil {
} errString = err.Error()
} }
} if errString != test.expectedError {
t.Fatalf("utxoDiff.AddEntry: unexpected err in test \"%s\". Expected: %s but got: %s", test.name, test.expectedError, err)
func TestUTXOSetRemoveTxOuts(t *testing.T) { }
tx0 := wire.NewNativeMsgTx(1, nil, []*wire.TxOut{{PkScript: []byte{1}, Value: 10}}) if err == nil && !utxoDiff.equal(expectedUTXODiff) {
tx1 := wire.NewNativeMsgTx(1, nil, []*wire.TxOut{{PkScript: []byte{2}, Value: 20}}) t.Fatalf("utxoDiff.AddEntry: unexpected utxoDiff in test \"%s\". "+
outPoint0 := wire.NewOutPoint(tx0.TxID(), 0) "Expected: %v, got: %v", test.name, expectedUTXODiff, utxoDiff)
outPoint1 := wire.NewOutPoint(tx1.TxID(), 0)
utxoDiff := NewUTXODiff()
tests := []struct {
name string
txToRemove *wire.MsgTx
expectedUTXODiff *UTXODiff
}{
{
name: "remove a transaction",
txToRemove: tx0,
expectedUTXODiff: &UTXODiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{*outPoint0: nil},
},
},
{
name: "remove another transaction",
txToRemove: tx1,
expectedUTXODiff: &UTXODiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{*outPoint0: nil, *outPoint1: nil},
},
},
{
name: "remove first entry again",
txToRemove: tx0,
expectedUTXODiff: &UTXODiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{*outPoint0: nil, *outPoint1: nil},
},
},
}
for _, test := range tests {
utxoDiff.RemoveTxOuts(test.txToRemove)
if !reflect.DeepEqual(utxoDiff, test.expectedUTXODiff) {
t.Fatalf("utxoDiff.AddEntry: unexpected utxoDiff in test '%s'. "+
"Expected: %v, got: %v", test.name, test.expectedUTXODiff, utxoDiff)
} }
} }
} }

View File

@ -50,14 +50,15 @@ func NewMultisetFromDataSlice(curve *KoblitzCurve, datas [][]byte) *Multiset {
return ms return ms
} }
func (ms *Multiset) clone() *Multiset { // Clone returns a clone of this multiset.
func (ms *Multiset) Clone() *Multiset {
return NewMultisetFromPoint(ms.curve, ms.x, ms.y) return NewMultisetFromPoint(ms.curve, ms.x, ms.y)
} }
// Add hashes the data onto the curve and returns // Add hashes the data onto the curve and returns
// a multiset with the new resulting point. // a multiset with the new resulting point.
func (ms *Multiset) Add(data []byte) *Multiset { func (ms *Multiset) Add(data []byte) *Multiset {
newMs := ms.clone() newMs := ms.Clone()
x, y := hashToPoint(ms.curve, data) x, y := hashToPoint(ms.curve, data)
newMs.addPoint(x, y) newMs.addPoint(x, y)
return newMs return newMs
@ -76,7 +77,7 @@ func (ms *Multiset) addPoint(x, y *big.Int) {
// all the elements that were added, you will not get // all the elements that were added, you will not get
// back to the point at infinity (empty set). // back to the point at infinity (empty set).
func (ms *Multiset) Remove(data []byte) *Multiset { func (ms *Multiset) Remove(data []byte) *Multiset {
newMs := ms.clone() newMs := ms.Clone()
x, y := hashToPoint(ms.curve, data) x, y := hashToPoint(ms.curve, data)
newMs.removePoint(x, y) newMs.removePoint(x, y)
return newMs return newMs
@ -90,8 +91,8 @@ func (ms *Multiset) removePoint(x, y *big.Int) {
// Union will add the point of the passed multiset instance to the point // Union will add the point of the passed multiset instance to the point
// of this multiset and will return a multiset with the resulting point. // of this multiset and will return a multiset with the resulting point.
func (ms *Multiset) Union(otherMultiset *Multiset) *Multiset { func (ms *Multiset) Union(otherMultiset *Multiset) *Multiset {
newMs := ms.clone() newMs := ms.Clone()
otherMsCopy := otherMultiset.clone() otherMsCopy := otherMultiset.Clone()
newMs.addPoint(otherMsCopy.x, otherMsCopy.y) newMs.addPoint(otherMsCopy.x, otherMsCopy.y)
return newMs return newMs
} }
@ -99,8 +100,8 @@ func (ms *Multiset) Union(otherMultiset *Multiset) *Multiset {
// Subtract will remove the point of the passed multiset instance from the point // Subtract will remove the point of the passed multiset instance from the point
// of this multiset and will return a multiset with the resulting point. // of this multiset and will return a multiset with the resulting point.
func (ms *Multiset) Subtract(otherMultiset *Multiset) *Multiset { func (ms *Multiset) Subtract(otherMultiset *Multiset) *Multiset {
newMs := ms.clone() newMs := ms.Clone()
otherMsCopy := otherMultiset.clone() otherMsCopy := otherMultiset.Clone()
newMs.removePoint(otherMsCopy.x, otherMsCopy.y) newMs.removePoint(otherMsCopy.x, otherMsCopy.y)
return newMs return newMs
} }

View File

@ -494,11 +494,16 @@ func (mp *TxPool) removeTransaction(tx *util.Tx, removeRedeemers bool, restoreIn
for i := uint32(0); i < uint32(len(tx.MsgTx().TxOut)); i++ { for i := uint32(0); i < uint32(len(tx.MsgTx().TxOut)); i++ {
prevOut := wire.OutPoint{TxID: *txID, Index: i} prevOut := wire.OutPoint{TxID: *txID, Index: i}
if txRedeemer, exists := mp.outpoints[prevOut]; exists { if txRedeemer, exists := mp.outpoints[prevOut]; exists {
mp.removeTransaction(txRedeemer, true, false) err := mp.removeTransaction(txRedeemer, true, false)
if err != nil {
return err
}
} }
} }
} }
msgTx := tx.MsgTx()
// Remove the transaction if needed. // Remove the transaction if needed.
if txDesc, exists := mp.fetchTransaction(txID); exists { if txDesc, exists := mp.fetchTransaction(txID); exists {
// Remove unconfirmed address index entries associated with the // Remove unconfirmed address index entries associated with the
@ -508,20 +513,36 @@ func (mp *TxPool) removeTransaction(tx *util.Tx, removeRedeemers bool, restoreIn
} }
diff := blockdag.NewUTXODiff() diff := blockdag.NewUTXODiff()
diff.RemoveTxOuts(txDesc.Tx.MsgTx())
for idx := range msgTx.TxOut {
outPoint := *wire.NewOutPoint(txID, uint32(idx))
entry, exists := mp.mpUTXOSet.Get(outPoint)
if exists {
err := diff.RemoveEntry(outPoint, entry)
if err != nil {
return err
}
}
}
// Mark the referenced outpoints as unspent by the pool. // Mark the referenced outpoints as unspent by the pool.
for _, txIn := range txDesc.Tx.MsgTx().TxIn { for _, txIn := range msgTx.TxIn {
if restoreInputs { if restoreInputs {
if prevTxDesc, exists := mp.pool[txIn.PreviousOutPoint.TxID]; exists { if prevTxDesc, exists := mp.pool[txIn.PreviousOutPoint.TxID]; exists {
prevOut := prevTxDesc.Tx.MsgTx().TxOut[txIn.PreviousOutPoint.Index] prevOut := prevTxDesc.Tx.MsgTx().TxOut[txIn.PreviousOutPoint.Index]
entry := blockdag.NewUTXOEntry(prevOut, false, blockdag.UnminedChainHeight) entry := blockdag.NewUTXOEntry(prevOut, false, blockdag.UnminedChainHeight)
diff.AddEntry(txIn.PreviousOutPoint, entry) err := diff.AddEntry(txIn.PreviousOutPoint, entry)
if err != nil {
return err
}
} }
if prevTxDesc, exists := mp.depends[txIn.PreviousOutPoint.TxID]; exists { if prevTxDesc, exists := mp.depends[txIn.PreviousOutPoint.TxID]; exists {
prevOut := prevTxDesc.Tx.MsgTx().TxOut[txIn.PreviousOutPoint.Index] prevOut := prevTxDesc.Tx.MsgTx().TxOut[txIn.PreviousOutPoint.Index]
entry := blockdag.NewUTXOEntry(prevOut, false, blockdag.UnminedChainHeight) entry := blockdag.NewUTXOEntry(prevOut, false, blockdag.UnminedChainHeight)
diff.AddEntry(txIn.PreviousOutPoint, entry) err := diff.AddEntry(txIn.PreviousOutPoint, entry)
if err != nil {
return err
}
} }
} }
delete(mp.outpoints, txIn.PreviousOutPoint) delete(mp.outpoints, txIn.PreviousOutPoint)
@ -535,7 +556,7 @@ func (mp *TxPool) removeTransaction(tx *util.Tx, removeRedeemers bool, restoreIn
// Process dependent transactions // Process dependent transactions
prevOut := wire.OutPoint{TxID: *txID} prevOut := wire.OutPoint{TxID: *txID}
for txOutIdx := range tx.MsgTx().TxOut { for txOutIdx := range msgTx.TxOut {
// Skip to the next available output if there are none. // Skip to the next available output if there are none.
prevOut.Index = uint32(txOutIdx) prevOut.Index = uint32(txOutIdx)
depends, exists := mp.dependsByPrev[prevOut] depends, exists := mp.dependsByPrev[prevOut]
@ -606,7 +627,7 @@ func (mp *TxPool) RemoveDoubleSpends(tx *util.Tx) {
// helper for maybeAcceptTransaction. // helper for maybeAcceptTransaction.
// //
// This function MUST be called with the mempool lock held (for writes). // This function MUST be called with the mempool lock held (for writes).
func (mp *TxPool) addTransaction(tx *util.Tx, height uint64, fee uint64, parentsInPool []*wire.OutPoint) *TxDesc { func (mp *TxPool) addTransaction(tx *util.Tx, height uint64, fee uint64, parentsInPool []*wire.OutPoint) (*TxDesc, error) {
// Add the transaction to the pool and mark the referenced outpoints // Add the transaction to the pool and mark the referenced outpoints
// as spent by the pool. // as spent by the pool.
txD := &TxDesc{ txD := &TxDesc{
@ -636,7 +657,11 @@ func (mp *TxPool) addTransaction(tx *util.Tx, height uint64, fee uint64, parents
for _, txIn := range tx.MsgTx().TxIn { for _, txIn := range tx.MsgTx().TxIn {
mp.outpoints[txIn.PreviousOutPoint] = tx mp.outpoints[txIn.PreviousOutPoint] = tx
} }
mp.mpUTXOSet.AddTx(tx.MsgTx(), blockdag.UnminedChainHeight) if isAccepted, err := mp.mpUTXOSet.AddTx(tx.MsgTx(), blockdag.UnminedChainHeight); err != nil {
return nil, err
} else if !isAccepted {
return nil, fmt.Errorf("unexpectedly failed to add tx %s to the mempool utxo set", tx.ID())
}
atomic.StoreInt64(&mp.lastUpdated, time.Now().Unix()) atomic.StoreInt64(&mp.lastUpdated, time.Now().Unix())
// Add unconfirmed address index entries associated with the transaction // Add unconfirmed address index entries associated with the transaction
@ -650,7 +675,7 @@ func (mp *TxPool) addTransaction(tx *util.Tx, height uint64, fee uint64, parents
mp.cfg.FeeEstimator.ObserveTransaction(txD) mp.cfg.FeeEstimator.ObserveTransaction(txD)
} }
return txD return txD, nil
} }
// checkPoolDoubleSpend checks whether or not the passed transaction is // checkPoolDoubleSpend checks whether or not the passed transaction is
@ -986,7 +1011,10 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
} }
// Add to transaction pool. // Add to transaction pool.
txD := mp.addTransaction(tx, bestHeight, txFee, parentsInPool) txD, err := mp.addTransaction(tx, bestHeight, txFee, parentsInPool)
if err != nil {
return nil, nil, err
}
log.Debugf("Accepted transaction %s (pool size: %d)", txID, log.Debugf("Accepted transaction %s (pool size: %d)", txID,
len(mp.pool)) len(mp.pool))
@ -1326,7 +1354,7 @@ func (mp *TxPool) HandleNewBlock(block *util.Block, txChan chan NewBlockMsg) err
// no longer an orphan. Transactions which depend on a confirmed // no longer an orphan. Transactions which depend on a confirmed
// transaction are NOT removed recursively because they are still // transaction are NOT removed recursively because they are still
// valid. // valid.
for _, tx := range block.Transactions()[1:] { for _, tx := range block.Transactions()[util.FeeTransactionIndex:] {
err := mp.RemoveTransaction(tx, false, false) err := mp.RemoveTransaction(tx, false, false)
if err != nil { if err != nil {
mp.mpUTXOSet = oldUTXOSet mp.mpUTXOSet = oldUTXOSet

View File

@ -6,7 +6,6 @@ package mempool
import ( import (
"bytes" "bytes"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math" "math"
@ -22,8 +21,8 @@ import (
"bou.ke/monkey" "bou.ke/monkey"
"github.com/daglabs/btcd/blockdag" "github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/blockdag/indexers" "github.com/daglabs/btcd/blockdag/indexers"
"github.com/daglabs/btcd/btcec"
"github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/mining"
"github.com/daglabs/btcd/txscript" "github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util" "github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/util/daghash" "github.com/daglabs/btcd/util/daghash"
@ -102,15 +101,9 @@ func txOutToSpendableOutpoint(tx *util.Tx, outputNum uint32) spendableOutpoint {
// signing transactions as well as a fake chain that provides utxos for use in // signing transactions as well as a fake chain that provides utxos for use in
// generating valid transactions. // generating valid transactions.
type poolHarness struct { type poolHarness struct {
// signKey is the signing key used for creating transactions throughout signatureScript []byte
// the tests. payScript []byte
// chainParams *dagconfig.Params
// payAddr is the p2sh address for the signing key and is used for the
// payment address throughout the tests.
signKey *btcec.PrivateKey
payAddr util.Address
payScript []byte
chainParams *dagconfig.Params
chain *fakeChain chain *fakeChain
txPool *TxPool txPool *TxPool
@ -177,7 +170,7 @@ func (p *poolHarness) CreateSignedTxForSubnetwork(inputs []spendableOutpoint, nu
for _, input := range inputs { for _, input := range inputs {
txIns = append(txIns, &wire.TxIn{ txIns = append(txIns, &wire.TxIn{
PreviousOutPoint: input.outPoint, PreviousOutPoint: input.outPoint,
SignatureScript: nil, SignatureScript: p.signatureScript,
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
}) })
} }
@ -200,12 +193,7 @@ func (p *poolHarness) CreateSignedTxForSubnetwork(inputs []spendableOutpoint, nu
// Sign the new transaction. // Sign the new transaction.
for i := range tx.TxIn { for i := range tx.TxIn {
sigScript, err := txscript.SignatureScript(tx, i, p.payScript, tx.TxIn[i].SignatureScript = p.signatureScript
txscript.SigHashAll, p.signKey, true)
if err != nil {
return nil, err
}
tx.TxIn[i].SignatureScript = sigScript
} }
return util.NewTx(tx), nil return util.NewTx(tx), nil
@ -233,7 +221,7 @@ func (p *poolHarness) CreateTxChain(firstOutput spendableOutpoint, numTxns uint3
// with the harness. // with the harness.
txIn := &wire.TxIn{ txIn := &wire.TxIn{
PreviousOutPoint: prevOutPoint, PreviousOutPoint: prevOutPoint,
SignatureScript: nil, SignatureScript: p.signatureScript,
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
} }
txOut := &wire.TxOut{ txOut := &wire.TxOut{
@ -242,14 +230,6 @@ func (p *poolHarness) CreateTxChain(firstOutput spendableOutpoint, numTxns uint3
} }
tx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}) tx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
// Sign the new transaction.
sigScript, err := txscript.SignatureScript(tx, 0, p.payScript,
txscript.SigHashAll, p.signKey, true)
if err != nil {
return nil, err
}
tx.TxIn[0].SignatureScript = sigScript
txChain = append(txChain, util.NewTx(tx)) txChain = append(txChain, util.NewTx(tx))
// Next transaction uses outputs from this one. // Next transaction uses outputs from this one.
@ -259,36 +239,58 @@ func (p *poolHarness) CreateTxChain(firstOutput spendableOutpoint, numTxns uint3
return txChain, nil return txChain, nil
} }
func (tc *testContext) mineTransactions(transactions []*util.Tx, coinbaseOutputs uint64) *util.Block {
msgTxs := make([]*wire.MsgTx, len(transactions))
for i, tx := range transactions {
msgTxs[i] = tx.MsgTx()
}
block, err := mining.PrepareBlockForTest(tc.harness.txPool.cfg.DAG, tc.harness.txPool.cfg.DAGParams, tc.harness.txPool.cfg.DAG.TipHashes(), msgTxs, true, coinbaseOutputs)
if err != nil {
tc.t.Fatalf("PrepareBlockForTest: %s", err)
}
utilBlock := util.NewBlock(block)
isOrphan, err := tc.harness.txPool.cfg.DAG.ProcessBlock(utilBlock, blockdag.BFNoPoWCheck)
if err != nil {
tc.t.Fatalf("ProcessBlock: %s", err)
}
if isOrphan {
tc.t.Fatalf("block %s was unexpectedly orphan", block.BlockHash())
}
// Handle new block by pool
ch := make(chan NewBlockMsg)
go func() {
err = tc.harness.txPool.HandleNewBlock(utilBlock, ch)
close(ch)
}()
// process messages pushed by HandleNewBlock
for range ch {
}
// ensure that HandleNewBlock has not failed
if err != nil {
tc.t.Fatalf("HandleNewBlock failed to handle block %s", err)
}
return utilBlock
}
// newPoolHarness returns a new instance of a pool harness initialized with a // newPoolHarness returns a new instance of a pool harness initialized with a
// fake chain and a TxPool bound to it that is configured with a policy suitable // fake chain and a TxPool bound to it that is configured with a policy suitable
// for testing. Also, the fake chain is populated with the returned spendable // for testing. Also, the fake chain is populated with the returned spendable
// outputs so the caller can easily create new valid transactions which build // outputs so the caller can easily create new valid transactions which build
// off of it. // off of it.
func newPoolHarness(dagParams *dagconfig.Params, numOutputs uint32, dbName string) (*poolHarness, []spendableOutpoint, func(), error) { func newPoolHarness(t *testing.T, dagParams *dagconfig.Params, numOutputs uint32, dbName string) (*testContext, []spendableOutpoint, func(), error) {
// Use a hard coded key pair for deterministic results. pkScript, err := txscript.PayToScriptHashScript(blockdag.OpTrueScript)
keyBytes, err := hex.DecodeString("700868df1838811ffbdf918fb482c1f7e" +
"ad62db4b97bd7012c23e726485e577d")
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
signKey, signPub := btcec.PrivKeyFromBytes(btcec.S256(), keyBytes)
// Generate associated pay-to-script-hash address and resulting payment params := *dagParams
// script. params.BlockRewardMaturity = 0
pubKeyBytes := signPub.SerializeCompressed()
payPubKeyAddr, err := util.NewAddressPubKey(pubKeyBytes, dagParams.Prefix)
if err != nil {
return nil, nil, nil, err
}
payAddr := payPubKeyAddr.AddressPubKeyHash()
pkScript, err := txscript.PayToAddrScript(payAddr)
if err != nil {
return nil, nil, nil, err
}
// Create a new database and chain instance to run tests against. // Create a new database and chain instance to run tests against.
dag, teardownFunc, err := blockdag.DAGSetup(dbName, blockdag.Config{ dag, teardownFunc, err := blockdag.DAGSetup(dbName, blockdag.Config{
DAGParams: dagParams, DAGParams: &params,
}) })
if err != nil { if err != nil {
return nil, nil, nil, fmt.Errorf("Failed to setup DAG instance: %v", err) return nil, nil, nil, fmt.Errorf("Failed to setup DAG instance: %v", err)
@ -299,13 +301,17 @@ func newPoolHarness(dagParams *dagconfig.Params, numOutputs uint32, dbName strin
} }
}() }()
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
if err != nil {
return nil, nil, nil, fmt.Errorf("Failed to build harness signature script: %s", err)
}
// Create a new fake chain and harness bound to it. // Create a new fake chain and harness bound to it.
chain := &fakeChain{} chain := &fakeChain{}
harness := poolHarness{ harness := &poolHarness{
signKey: signKey, signatureScript: signatureScript,
payAddr: payAddr, payScript: pkScript,
payScript: pkScript, chainParams: dagParams,
chainParams: dagParams,
chain: chain, chain: chain,
txPool: New(&Config{ txPool: New(&Config{
@ -328,6 +334,10 @@ func newPoolHarness(dagParams *dagconfig.Params, numOutputs uint32, dbName strin
}), }),
} }
tc := &testContext{harness: harness, t: t}
block := tc.mineTransactions(nil, uint64(numOutputs))
coinbase := block.Transactions()[0]
// Create a single coinbase transaction and add it to the harness // Create a single coinbase transaction and add it to the harness
// chain's utxo set and set the harness chain height such that the // chain's utxo set and set the harness chain height such that the
// coinbase will mature in the next block. This ensures the txpool // coinbase will mature in the next block. This ensures the txpool
@ -335,11 +345,6 @@ func newPoolHarness(dagParams *dagconfig.Params, numOutputs uint32, dbName strin
// mature in the next block. // mature in the next block.
outpoints := make([]spendableOutpoint, 0, numOutputs) outpoints := make([]spendableOutpoint, 0, numOutputs)
curHeight := harness.chain.BestHeight() curHeight := harness.chain.BestHeight()
coinbase, err := harness.CreateCoinbaseTx(curHeight+1, numOutputs)
if err != nil {
return nil, nil, nil, err
}
harness.txPool.mpUTXOSet.AddTx(coinbase.MsgTx(), curHeight+1)
for i := uint32(0); i < numOutputs; i++ { for i := uint32(0); i < numOutputs; i++ {
outpoints = append(outpoints, txOutToSpendableOutpoint(coinbase, i)) outpoints = append(outpoints, txOutToSpendableOutpoint(coinbase, i))
} }
@ -350,7 +355,7 @@ func newPoolHarness(dagParams *dagconfig.Params, numOutputs uint32, dbName strin
} }
harness.chain.SetMedianTimePast(time.Now()) harness.chain.SetMedianTimePast(time.Now())
return &harness, outpoints, teardownFunc, nil return tc, outpoints, teardownFunc, nil
} }
// testContext houses a test-related state that is useful to pass to helper // testContext houses a test-related state that is useful to pass to helper
@ -457,24 +462,20 @@ func (p *poolHarness) createTx(outpoint spendableOutpoint, fee uint64, numOutput
tx := wire.NewNativeMsgTx(wire.TxVersion, txIns, txOuts) tx := wire.NewNativeMsgTx(wire.TxVersion, txIns, txOuts)
// Sign the new transaction. // Sign the new transaction.
sigScript, err := txscript.SignatureScript(tx, 0, p.payScript, tx.TxIn[0].SignatureScript = p.signatureScript
txscript.SigHashAll, p.signKey, true)
if err != nil {
return nil, err
}
tx.TxIn[0].SignatureScript = sigScript
return util.NewTx(tx), nil return util.NewTx(tx), nil
} }
func TestProcessTransaction(t *testing.T) { func TestProcessTransaction(t *testing.T) {
params := dagconfig.SimNetParams params := dagconfig.SimNetParams
params.BlockRewardMaturity = 0 params.BlockRewardMaturity = 0
harness, spendableOuts, teardownFunc, err := newPoolHarness(&params, 6, "TestProcessTransaction") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &params, 6, "TestProcessTransaction")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness}
harness := tc.harness
//Checks that a transaction cannot be added to the transaction pool if it's already there //Checks that a transaction cannot be added to the transaction pool if it's already there
tx, err := harness.createTx(spendableOuts[0], 0, 1) tx, err := harness.createTx(spendableOuts[0], 0, 1)
@ -623,7 +624,11 @@ func TestProcessTransaction(t *testing.T) {
t.Fatalf("PayToAddrScript: unexpected error: %v", err) t.Fatalf("PayToAddrScript: unexpected error: %v", err)
} }
p2shTx := util.NewTx(wire.NewNativeMsgTx(1, nil, []*wire.TxOut{{Value: 5000000000, PkScript: p2shPKScript}})) p2shTx := util.NewTx(wire.NewNativeMsgTx(1, nil, []*wire.TxOut{{Value: 5000000000, PkScript: p2shPKScript}}))
harness.txPool.mpUTXOSet.AddTx(p2shTx.MsgTx(), curHeight+1) if isAccepted, err := harness.txPool.mpUTXOSet.AddTx(p2shTx.MsgTx(), curHeight+1); err != nil {
t.Fatalf("AddTx unexpectedly failed. Error: %s", err)
} else if !isAccepted {
t.Fatalf("AddTx unexpectedly didn't add tx %s", p2shTx.ID())
}
txIns := []*wire.TxIn{{ txIns := []*wire.TxIn{{
PreviousOutPoint: wire.OutPoint{TxID: *p2shTx.ID(), Index: 0}, PreviousOutPoint: wire.OutPoint{TxID: *p2shTx.ID(), Index: 0},
@ -754,11 +759,12 @@ func TestProcessTransaction(t *testing.T) {
} }
func TestAddrIndex(t *testing.T) { func TestAddrIndex(t *testing.T) {
harness, spendableOuts, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 2, "TestAddrIndex") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 2, "TestAddrIndex")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
harness := tc.harness
harness.txPool.cfg.AddrIndex = &indexers.AddrIndex{} harness.txPool.cfg.AddrIndex = &indexers.AddrIndex{}
enteredAddUnconfirmedTx := false enteredAddUnconfirmedTx := false
guard := monkey.Patch((*indexers.AddrIndex).AddUnconfirmedTx, func(idx *indexers.AddrIndex, tx *util.Tx, utxoSet blockdag.UTXOSet) { guard := monkey.Patch((*indexers.AddrIndex).AddUnconfirmedTx, func(idx *indexers.AddrIndex, tx *util.Tx, utxoSet blockdag.UTXOSet) {
@ -795,11 +801,12 @@ func TestAddrIndex(t *testing.T) {
} }
func TestFeeEstimatorCfg(t *testing.T) { func TestFeeEstimatorCfg(t *testing.T) {
harness, spendableOuts, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 2, "TestFeeEstimatorCfg") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 2, "TestFeeEstimatorCfg")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
harness := tc.harness
harness.txPool.cfg.FeeEstimator = &FeeEstimator{} harness.txPool.cfg.FeeEstimator = &FeeEstimator{}
enteredObserveTransaction := false enteredObserveTransaction := false
guard := monkey.Patch((*FeeEstimator).ObserveTransaction, func(ef *FeeEstimator, t *TxDesc) { guard := monkey.Patch((*FeeEstimator).ObserveTransaction, func(ef *FeeEstimator, t *TxDesc) {
@ -822,12 +829,12 @@ func TestFeeEstimatorCfg(t *testing.T) {
} }
func TestDoubleSpends(t *testing.T) { func TestDoubleSpends(t *testing.T) {
harness, spendableOuts, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 2, "TestDoubleSpends") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 2, "TestDoubleSpends")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
//Add two transactions to the mempool //Add two transactions to the mempool
tx1, err := harness.createTx(spendableOuts[0], 0, 1) tx1, err := harness.createTx(spendableOuts[0], 0, 1)
@ -872,12 +879,12 @@ func TestDoubleSpends(t *testing.T) {
//TestFetchTransaction checks that FetchTransaction //TestFetchTransaction checks that FetchTransaction
//returns only transaction from the main pool and not from the orphan pool //returns only transaction from the main pool and not from the orphan pool
func TestFetchTransaction(t *testing.T) { func TestFetchTransaction(t *testing.T) {
harness, spendableOuts, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestFetchTransaction") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestFetchTransaction")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
orphanedTx, err := harness.CreateSignedTx([]spendableOutpoint{{ orphanedTx, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000), amount: util.Amount(5000000000),
@ -918,12 +925,12 @@ func TestFetchTransaction(t *testing.T) {
// they are all orphans. Finally, it adds the linking transaction and ensures // they are all orphans. Finally, it adds the linking transaction and ensures
// the entire orphan chain is moved to the transaction pool. // the entire orphan chain is moved to the transaction pool.
func TestSimpleOrphanChain(t *testing.T) { func TestSimpleOrphanChain(t *testing.T) {
harness, spendableOuts, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestSimpleOrphanChain") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestSimpleOrphanChain")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
// Create a chain of transactions rooted with the first spendable output // Create a chain of transactions rooted with the first spendable output
// provided by the harness. // provided by the harness.
@ -980,12 +987,12 @@ func TestSimpleOrphanChain(t *testing.T) {
// TestOrphanReject ensures that orphans are properly rejected when the allow // TestOrphanReject ensures that orphans are properly rejected when the allow
// orphans flag is not set on ProcessTransaction. // orphans flag is not set on ProcessTransaction.
func TestOrphanReject(t *testing.T) { func TestOrphanReject(t *testing.T) {
harness, outputs, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanReject") tc, outputs, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestOrphanReject")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
// Create a chain of transactions rooted with the first spendable output // Create a chain of transactions rooted with the first spendable output
// provided by the harness. // provided by the harness.
@ -1035,12 +1042,12 @@ func TestOrphanReject(t *testing.T) {
// it will check if we are beyond nextExpireScan, and if so, it will remove // it will check if we are beyond nextExpireScan, and if so, it will remove
// all expired orphan transactions // all expired orphan transactions
func TestOrphanExpiration(t *testing.T) { func TestOrphanExpiration(t *testing.T) {
harness, _, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanExpiration") tc, _, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestOrphanExpiration")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
expiredTx, err := harness.CreateSignedTx([]spendableOutpoint{{ expiredTx, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000), amount: util.Amount(5000000000),
@ -1080,12 +1087,13 @@ func TestOrphanExpiration(t *testing.T) {
//TestMaxOrphanTxSize ensures that a transaction that is //TestMaxOrphanTxSize ensures that a transaction that is
//bigger than MaxOrphanTxSize will get rejected //bigger than MaxOrphanTxSize will get rejected
func TestMaxOrphanTxSize(t *testing.T) { func TestMaxOrphanTxSize(t *testing.T) {
harness, _, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestMaxOrphanTxSize") tc, _, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestMaxOrphanTxSize")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
harness.txPool.cfg.Policy.MaxOrphanTxSize = 1 harness.txPool.cfg.Policy.MaxOrphanTxSize = 1
tx, err := harness.CreateSignedTx([]spendableOutpoint{{ tx, err := harness.CreateSignedTx([]spendableOutpoint{{
@ -1108,12 +1116,13 @@ func TestMaxOrphanTxSize(t *testing.T) {
} }
func TestRemoveTransaction(t *testing.T) { func TestRemoveTransaction(t *testing.T) {
harness, outputs, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestRemoveTransaction") tc, outputs, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 2, "TestRemoveTransaction")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
chainedTxns, err := harness.CreateTxChain(outputs[0], 5) chainedTxns, err := harness.CreateTxChain(outputs[0], 5)
if err != nil { if err != nil {
t.Fatalf("unable to create transaction chain: %v", err) t.Fatalf("unable to create transaction chain: %v", err)
@ -1130,22 +1139,36 @@ func TestRemoveTransaction(t *testing.T) {
} }
//Checks that when removeRedeemers is false, the specified transaction is the only transaction that gets removed //Checks that when removeRedeemers is false, the specified transaction is the only transaction that gets removed
harness.txPool.RemoveTransaction(chainedTxns[3], false, true) tc.mineTransactions(chainedTxns[:1], 1)
testPoolMembership(tc, chainedTxns[3], false, false, false) testPoolMembership(tc, chainedTxns[0], false, false, false)
testPoolMembership(tc, chainedTxns[4], false, true, false) testPoolMembership(tc, chainedTxns[1], false, true, false)
testPoolMembership(tc, chainedTxns[2], false, true, true)
testPoolMembership(tc, chainedTxns[3], false, true, true)
testPoolMembership(tc, chainedTxns[4], false, true, true)
//Checks that when removeRedeemers is true, all of the transaction that are dependent on it get removed //Checks that when removeRedeemers is true, all of the transaction that are dependent on it get removed
harness.txPool.RemoveTransaction(chainedTxns[1], true, true) err = harness.txPool.RemoveTransaction(chainedTxns[1], true, true)
testPoolMembership(tc, chainedTxns[0], false, true, false) if err != nil {
t.Fatalf("RemoveTransaction: %v", err)
}
testPoolMembership(tc, chainedTxns[1], false, false, false) testPoolMembership(tc, chainedTxns[1], false, false, false)
testPoolMembership(tc, chainedTxns[2], false, false, false) testPoolMembership(tc, chainedTxns[2], false, false, false)
testPoolMembership(tc, chainedTxns[3], false, false, false)
testPoolMembership(tc, chainedTxns[4], false, false, false)
fakeWithDiffErr := "error from WithDiff" fakeWithDiffErr := "error from WithDiff"
guard := monkey.Patch((*blockdag.DiffUTXOSet).WithDiff, func(_ *blockdag.DiffUTXOSet, _ *blockdag.UTXODiff) (blockdag.UTXOSet, error) { guard := monkey.Patch((*blockdag.DiffUTXOSet).WithDiff, func(_ *blockdag.DiffUTXOSet, _ *blockdag.UTXODiff) (blockdag.UTXOSet, error) {
return nil, errors.New(fakeWithDiffErr) return nil, errors.New(fakeWithDiffErr)
}) })
defer guard.Unpatch() defer guard.Unpatch()
err = harness.txPool.RemoveTransaction(chainedTxns[0], false, false)
tx, err := harness.CreateSignedTx(outputs[1:], 1)
_, err = harness.txPool.ProcessTransaction(tx, true,
false, 0)
if err != nil {
t.Fatalf("ProcessTransaction: %v", err)
}
err = harness.txPool.RemoveTransaction(tx, false, false)
if err == nil || err.Error() != fakeWithDiffErr { if err == nil || err.Error() != fakeWithDiffErr {
t.Errorf("RemoveTransaction: expected error %v but got %v", fakeWithDiffErr, err) t.Errorf("RemoveTransaction: expected error %v but got %v", fakeWithDiffErr, err)
} }
@ -1154,12 +1177,12 @@ func TestRemoveTransaction(t *testing.T) {
// TestOrphanEviction ensures that exceeding the maximum number of orphans // TestOrphanEviction ensures that exceeding the maximum number of orphans
// evicts entries to make room for the new ones. // evicts entries to make room for the new ones.
func TestOrphanEviction(t *testing.T) { func TestOrphanEviction(t *testing.T) {
harness, outputs, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanEviction") tc, outputs, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestOrphanEviction")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
// Create a chain of transactions rooted with the first spendable output // Create a chain of transactions rooted with the first spendable output
// provided by the harness that is long enough to be able to force // provided by the harness that is long enough to be able to force
@ -1216,12 +1239,12 @@ func TestOrphanEviction(t *testing.T) {
// Attempt to remove orphans by tag, // Attempt to remove orphans by tag,
// and ensure the state of all other orphans are unaffected. // and ensure the state of all other orphans are unaffected.
func TestRemoveOrphansByTag(t *testing.T) { func TestRemoveOrphansByTag(t *testing.T) {
harness, _, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestRemoveOrphansByTag") tc, _, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestRemoveOrphansByTag")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
orphanedTx1, err := harness.CreateSignedTx([]spendableOutpoint{{ orphanedTx1, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000), amount: util.Amount(5000000000),
@ -1273,13 +1296,14 @@ func TestRemoveOrphansByTag(t *testing.T) {
// redeems it and when there is not. // redeems it and when there is not.
func TestBasicOrphanRemoval(t *testing.T) { func TestBasicOrphanRemoval(t *testing.T) {
const maxOrphans = 4 const maxOrphans = 4
harness, spendableOuts, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestBasicOrphanRemoval") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestBasicOrphanRemoval")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
harness := tc.harness
harness.txPool.cfg.Policy.MaxOrphanTxs = maxOrphans harness.txPool.cfg.Policy.MaxOrphanTxs = maxOrphans
tc := &testContext{t, harness}
// Create a chain of transactions rooted with the first spendable output // Create a chain of transactions rooted with the first spendable output
// provided by the harness. // provided by the harness.
@ -1347,13 +1371,13 @@ func TestBasicOrphanRemoval(t *testing.T) {
// from other orphans) are removed as expected. // from other orphans) are removed as expected.
func TestOrphanChainRemoval(t *testing.T) { func TestOrphanChainRemoval(t *testing.T) {
const maxOrphans = 10 const maxOrphans = 10
harness, spendableOuts, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanChainRemoval") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestOrphanChainRemoval")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
harness := tc.harness
harness.txPool.cfg.Policy.MaxOrphanTxs = maxOrphans harness.txPool.cfg.Policy.MaxOrphanTxs = maxOrphans
tc := &testContext{t, harness}
// Create a chain of transactions rooted with the first spendable output // Create a chain of transactions rooted with the first spendable output
// provided by the harness. // provided by the harness.
@ -1409,13 +1433,13 @@ func TestOrphanChainRemoval(t *testing.T) {
// output that is spend by another transaction entering the pool are removed. // output that is spend by another transaction entering the pool are removed.
func TestMultiInputOrphanDoubleSpend(t *testing.T) { func TestMultiInputOrphanDoubleSpend(t *testing.T) {
const maxOrphans = 4 const maxOrphans = 4
harness, outputs, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestMultiInputOrphanDoubleSpend") tc, outputs, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestMultiInputOrphanDoubleSpend")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
harness := tc.harness
harness.txPool.cfg.Policy.MaxOrphanTxs = maxOrphans harness.txPool.cfg.Policy.MaxOrphanTxs = maxOrphans
tc := &testContext{t, harness}
// Create a chain of transactions rooted with the first spendable output // Create a chain of transactions rooted with the first spendable output
// provided by the harness. // provided by the harness.
@ -1496,11 +1520,12 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
// TestCheckSpend tests that CheckSpend returns the expected spends found in // TestCheckSpend tests that CheckSpend returns the expected spends found in
// the mempool. // the mempool.
func TestCheckSpend(t *testing.T) { func TestCheckSpend(t *testing.T) {
harness, outputs, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestCheckSpend") tc, outputs, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestCheckSpend")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
harness := tc.harness
// The mempool is empty, so none of the spendable outputs should have a // The mempool is empty, so none of the spendable outputs should have a
// spend there. // spend there.
@ -1562,11 +1587,12 @@ func TestCheckSpend(t *testing.T) {
} }
func TestCount(t *testing.T) { func TestCount(t *testing.T) {
harness, outputs, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestCount") tc, outputs, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 1, "TestCount")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
harness := tc.harness
if harness.txPool.Count() != 0 { if harness.txPool.Count() != 0 {
t.Errorf("TestCount: txPool should be initialized with 0 transactions") t.Errorf("TestCount: txPool should be initialized with 0 transactions")
} }
@ -1672,12 +1698,12 @@ func TestExtractRejectCode(t *testing.T) {
// TestHandleNewBlock // TestHandleNewBlock
func TestHandleNewBlock(t *testing.T) { func TestHandleNewBlock(t *testing.T) {
harness, spendableOuts, teardownFunc, err := newPoolHarness(&dagconfig.MainNetParams, 2, "TestHandleNewBlock") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &dagconfig.MainNetParams, 2, "TestHandleNewBlock")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
tc := &testContext{t, harness} harness := tc.harness
// Create parent transaction for orphan transaction below // Create parent transaction for orphan transaction below
blockTx1, err := harness.CreateSignedTx(spendableOuts[:1], 1) blockTx1, err := harness.CreateSignedTx(spendableOuts[:1], 1)
@ -1711,8 +1737,10 @@ func TestHandleNewBlock(t *testing.T) {
// Create block and add its transactions to UTXO set // Create block and add its transactions to UTXO set
block := util.NewBlock(&dummyBlock) block := util.NewBlock(&dummyBlock)
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
if !harness.txPool.mpUTXOSet.AddTx(tx.MsgTx(), 1) { if isAccepted, err := harness.txPool.mpUTXOSet.AddTx(tx.MsgTx(), 1); err != nil {
t.Fatalf("Failed to add transaction %v to UTXO set: %v", i, tx.ID()) t.Fatalf("Failed to add transaction (%v,%v) to UTXO set: %v", i, tx.ID(), err)
} else if !isAccepted {
t.Fatalf("AddTx unexpectedly didn't add tx %s", tx.ID())
} }
} }
@ -1831,12 +1859,12 @@ var dummyBlock = wire.MsgBlock{
func TestTransactionGas(t *testing.T) { func TestTransactionGas(t *testing.T) {
params := dagconfig.SimNetParams params := dagconfig.SimNetParams
params.BlockRewardMaturity = 1 params.BlockRewardMaturity = 1
harness, spendableOuts, teardownFunc, err := newPoolHarness(&params, 6, "TestTransactionGas") tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &params, 6, "TestTransactionGas")
if err != nil { if err != nil {
t.Fatalf("unable to create test pool: %v", err) t.Fatalf("unable to create test pool: %v", err)
} }
defer teardownFunc() defer teardownFunc()
// tc := &testContext{t, harness} harness := tc.harness
const gasLimit = 10000 const gasLimit = 10000
subnetworkID, err := testtools.RegisterSubnetworkForTest(harness.txPool.cfg.DAG, &params, gasLimit) subnetworkID, err := testtools.RegisterSubnetworkForTest(harness.txPool.cfg.DAG, &params, gasLimit)

View File

@ -236,9 +236,12 @@ func CreateCoinbaseTx(params *dagconfig.Params, coinbaseScript []byte, nextBlock
return nil, err return nil, err
} }
} else { } else {
var err error
scriptBuilder := txscript.NewScriptBuilder() scriptBuilder := txscript.NewScriptBuilder()
pkScript, err = scriptBuilder.AddOp(txscript.OpTrue).Script() opTrueScript, err := scriptBuilder.AddOp(txscript.OpTrue).Script()
if err != nil {
return nil, err
}
pkScript, err = txscript.PayToScriptHashScript(opTrueScript)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -197,13 +197,19 @@ func TestNewBlockTemplate(t *testing.T) {
template1CbTx := template1.Block.Transactions[0] template1CbTx := template1.Block.Transactions[0]
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
if err != nil {
t.Fatalf("Error creating signature script: %s", err)
}
// tx is a regular transaction, and should not be filtered by the miner // tx is a regular transaction, and should not be filtered by the miner
txIn := &wire.TxIn{ txIn := &wire.TxIn{
PreviousOutPoint: wire.OutPoint{ PreviousOutPoint: wire.OutPoint{
TxID: *template1CbTx.TxID(), TxID: *template1CbTx.TxID(),
Index: 0, Index: 0,
}, },
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
} }
txOut := &wire.TxOut{ txOut := &wire.TxOut{
PkScript: pkScript, PkScript: pkScript,
@ -217,7 +223,8 @@ func TestNewBlockTemplate(t *testing.T) {
TxID: *template1CbTx.TxID(), TxID: *template1CbTx.TxID(),
Index: 1, Index: 1,
}, },
Sequence: 0, Sequence: 0,
SignatureScript: signatureScript,
} }
txOut = &wire.TxOut{ txOut = &wire.TxOut{
PkScript: pkScript, PkScript: pkScript,
@ -235,7 +242,8 @@ func TestNewBlockTemplate(t *testing.T) {
TxID: *template1CbTx.TxID(), TxID: *template1CbTx.TxID(),
Index: 2, Index: 2,
}, },
Sequence: 0, Sequence: 0,
SignatureScript: signatureScript,
} }
txOut = &wire.TxOut{ txOut = &wire.TxOut{
PkScript: pkScript, PkScript: pkScript,
@ -250,7 +258,8 @@ func TestNewBlockTemplate(t *testing.T) {
TxID: *template1CbTx.TxID(), TxID: *template1CbTx.TxID(),
Index: 3, Index: 3,
}, },
Sequence: 0, Sequence: 0,
SignatureScript: signatureScript,
} }
txOut = &wire.TxOut{ txOut = &wire.TxOut{
PkScript: pkScript, PkScript: pkScript,
@ -264,7 +273,8 @@ func TestNewBlockTemplate(t *testing.T) {
TxID: *template1CbTx.TxID(), TxID: *template1CbTx.TxID(),
Index: 4, Index: 4,
}, },
Sequence: 0, Sequence: 0,
SignatureScript: signatureScript,
} }
txOut = &wire.TxOut{ txOut = &wire.TxOut{
PkScript: pkScript, PkScript: pkScript,

View File

@ -53,7 +53,11 @@ func newUTXOSet(sourceTxns []*wire.MsgTx, sourceTxHeights []uint64) blockdag.UTX
utxoSet := blockdag.NewFullUTXOSet() utxoSet := blockdag.NewFullUTXOSet()
for i, tx := range sourceTxns { for i, tx := range sourceTxns {
utxoSet.AddTx(tx, sourceTxHeights[i]) if isAccepted, err := utxoSet.AddTx(tx, sourceTxHeights[i]); err != nil {
panic(fmt.Sprintf("AddTx unexpectedly failed. Error: %s", err))
} else if !isAccepted {
panic(fmt.Sprintf("AddTx unexpectedly didn't add tx %s", tx.TxID()))
}
} }
return utxoSet return utxoSet
} }

View File

@ -352,6 +352,30 @@ func PayToAddrScript(addr util.Address) ([]byte, error) {
return nil, scriptError(ErrUnsupportedAddress, str) return nil, scriptError(ErrUnsupportedAddress, str)
} }
// PayToScriptHashScript takes a script and returns an equivalent pay-to-script-hash script
func PayToScriptHashScript(redeemScript []byte) ([]byte, error) {
redeemScriptHash := util.Hash160(redeemScript)
script, err := NewScriptBuilder().
AddOp(OpHash160).AddData(redeemScriptHash).
AddOp(OpEqual).Script()
if err != nil {
return nil, err
}
return script, nil
}
// PayToScriptHashSignatureScript generates a signature script that fits a pay-to-script-hash script
func PayToScriptHashSignatureScript(redeemScript []byte, signature []byte) ([]byte, error) {
redeemScriptAsData, err := NewScriptBuilder().AddData(redeemScript).Script()
if err != nil {
return nil, err
}
signatureScript := make([]byte, len(signature)+len(redeemScriptAsData))
copy(signatureScript, signature)
copy(signatureScript[len(signature):], redeemScriptAsData)
return signatureScript, nil
}
// NullDataScript creates a provably-prunable script containing OP_RETURN // NullDataScript creates a provably-prunable script containing OP_RETURN
// followed by the passed data. An Error with the error code ErrTooMuchNullData // followed by the passed data. An Error with the error code ErrTooMuchNullData
// will be returned if the length of the passed data exceeds MaxDataCarrierSize. // will be returned if the length of the passed data exceeds MaxDataCarrierSize.

View File

@ -10,6 +10,7 @@ import (
"github.com/daglabs/btcd/blockdag" "github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util" "github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/util/subnetworkid" "github.com/daglabs/btcd/util/subnetworkid"
"github.com/daglabs/btcd/wire" "github.com/daglabs/btcd/wire"
@ -53,12 +54,22 @@ func RegisterSubnetworkForTest(dag *blockdag.BlockDAG, params *dagconfig.Params,
fundsBlockCbTx := fundsBlock.Transactions()[0].MsgTx() fundsBlockCbTx := fundsBlock.Transactions()[0].MsgTx()
// Create a block with a valid subnetwork registry transaction // Create a block with a valid subnetwork registry transaction
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
if err != nil {
return nil, fmt.Errorf("Failed to build signature script: %s", err)
}
txIn := &wire.TxIn{ txIn := &wire.TxIn{
PreviousOutPoint: *wire.NewOutPoint(fundsBlockCbTx.TxID(), 0), PreviousOutPoint: *wire.NewOutPoint(fundsBlockCbTx.TxID(), 0),
Sequence: wire.MaxTxInSequenceNum, Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
pkScript, err := txscript.PayToScriptHashScript(blockdag.OpTrueScript)
if err != nil {
return nil, err
} }
txOut := &wire.TxOut{ txOut := &wire.TxOut{
PkScript: blockdag.OpTrueScript, PkScript: pkScript,
Value: fundsBlockCbTx.TxOut[0].Value, Value: fundsBlockCbTx.TxOut[0].Value,
} }
registryTx := wire.NewRegistryMsgTx(1, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}, gasLimit) registryTx := wire.NewRegistryMsgTx(1, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}, gasLimit)