mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[DEV-104] Disable chained txs (#57)
* [DEV-104] Disable chained transactions * [DEV-104] add TestApplyUTXOChanges * [DEV-104] remove isCompatible * [DEV-104] add TestDiffFromTx * [DEV-104] reorder variables in TestApplyUTXOChanges * [DEV-104] rename node -> containgNode in diffFromTx
This commit is contained in:
parent
37cd482db3
commit
86feb42cc4
@ -696,18 +696,28 @@ func (pns provisionalNodeSet) newProvisionalNode(node *blockNode, withRelatives
|
||||
|
||||
// verifyAndBuildUTXO verifies all transactions in the given block (in provisionalNode format) and builds its UTXO
|
||||
func (p *provisionalNode) verifyAndBuildUTXO(virtual *VirtualBlock, db database.DB) (utxoSet, error) {
|
||||
utxo, err := p.pastUTXO(virtual, db)
|
||||
pastUTXO, err := p.pastUTXO(virtual, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diff := newUTXODiff()
|
||||
|
||||
for _, tx := range p.transactions {
|
||||
ok := utxo.addTx(tx.MsgTx(), p.original.height)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("transaction %v is not compatible with UTXO", tx)
|
||||
txDiff, err := pastUTXO.diffFromTx(tx.MsgTx(), p.original)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
diff, err = diff.withDiff(txDiff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
utxo, err := pastUTXO.withDiff(diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utxo, nil
|
||||
}
|
||||
|
||||
|
@ -5,14 +5,15 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"bou.ke/monkey"
|
||||
"errors"
|
||||
"github.com/daglabs/btcd/database"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"bou.ke/monkey"
|
||||
"github.com/daglabs/btcd/database"
|
||||
|
||||
"math/rand"
|
||||
|
||||
"github.com/daglabs/btcd/dagconfig"
|
||||
@ -650,20 +651,6 @@ func TestIntervalBlockHashes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPastUTXOErrors tests all error-cases in restoreUTXO.
|
||||
// The non-error-cases are tested in the more general tests.
|
||||
func TestVerifyAndBuildUTXOErrors(t *testing.T) {
|
||||
targetErrorMessage := "not compatible with UTXO"
|
||||
testErrorThroughPatching(
|
||||
t,
|
||||
targetErrorMessage,
|
||||
(*diffUTXOSet).addTx,
|
||||
func(fus *diffUTXOSet, tx *wire.MsgTx, blockHeight int32) bool {
|
||||
return false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestPastUTXOErrors tests all error-cases in restoreUTXO.
|
||||
// The non-error-cases are tested in the more general tests.
|
||||
func TestPastUTXOErrors(t *testing.T) {
|
||||
|
@ -3,9 +3,10 @@ package blockdag
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
// utxoCollection represents a set of UTXOs indexed by their outPoints
|
||||
@ -36,6 +37,13 @@ func (uc utxoCollection) remove(outPoint wire.OutPoint) {
|
||||
delete(uc, outPoint)
|
||||
}
|
||||
|
||||
// get returns the UTXOEntry represented by provided outPoint,
|
||||
// and a boolean value indicating if said UTXOEntry is in the set or not
|
||||
func (uc utxoCollection) get(outPoint wire.OutPoint) (*UTXOEntry, bool) {
|
||||
entry, ok := uc[outPoint]
|
||||
return entry, ok
|
||||
}
|
||||
|
||||
// contains returns a boolean value indicating whether a UTXO entry is in the set
|
||||
func (uc utxoCollection) contains(outPoint wire.OutPoint) bool {
|
||||
_, ok := uc[outPoint]
|
||||
@ -263,8 +271,37 @@ type utxoSet interface {
|
||||
fmt.Stringer
|
||||
diffFrom(other utxoSet) (*utxoDiff, error)
|
||||
withDiff(utxoDiff *utxoDiff) (utxoSet, error)
|
||||
diffFromTx(tx *wire.MsgTx, node *blockNode) (*utxoDiff, error)
|
||||
addTx(tx *wire.MsgTx, blockHeight int32) (ok bool)
|
||||
clone() utxoSet
|
||||
get(outPoint wire.OutPoint) (*UTXOEntry, bool)
|
||||
}
|
||||
|
||||
// diffFromTx is a common implementation for diffFromTx, that works
|
||||
// for both diff-based and full UTXO sets
|
||||
// Returns a diff that is equivalent to provided transaction,
|
||||
// or an error if provided transaction is not valid in the context of this UTXOSet
|
||||
func diffFromTx(u utxoSet, tx *wire.MsgTx, containingNode *blockNode) (*utxoDiff, error) {
|
||||
diff := newUTXODiff()
|
||||
isCoinbase := IsCoinBaseTx(tx)
|
||||
if !isCoinbase {
|
||||
for _, txIn := range tx.TxIn {
|
||||
if entry, ok := u.get(txIn.PreviousOutPoint); ok {
|
||||
diff.toRemove.add(txIn.PreviousOutPoint, entry)
|
||||
} else {
|
||||
return nil, fmt.Errorf(
|
||||
"Transaction %s is invalid because spends outpoint %s that is not in utxo set",
|
||||
tx.TxHash(), txIn.PreviousOutPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, txOut := range tx.TxOut {
|
||||
hash := tx.TxHash()
|
||||
entry := newUTXOEntry(txOut, isCoinbase, containingNode.height)
|
||||
outPoint := *wire.NewOutPoint(&hash, uint32(i))
|
||||
diff.toAdd.add(outPoint, entry)
|
||||
}
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
// fullUTXOSet represents a full list of transaction outputs and their values
|
||||
@ -324,6 +361,12 @@ func (fus *fullUTXOSet) addTx(tx *wire.MsgTx, blockHeight int32) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// diffFromTx returns a diff that is equivalent to provided transaction,
|
||||
// or an error if provided transaction is not valid in the context of this UTXOSet
|
||||
func (fus *fullUTXOSet) diffFromTx(tx *wire.MsgTx, node *blockNode) (*utxoDiff, error) {
|
||||
return diffFromTx(fus, tx, node)
|
||||
}
|
||||
|
||||
func (fus *fullUTXOSet) containsInputs(tx *wire.MsgTx) bool {
|
||||
for _, txIn := range tx.TxIn {
|
||||
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index)
|
||||
@ -345,7 +388,7 @@ func (fus *fullUTXOSet) clone() utxoSet {
|
||||
return &fullUTXOSet{utxoCollection: fus.utxoCollection.clone()}
|
||||
}
|
||||
|
||||
func (fus *fullUTXOSet) getUTXOEntry(outPoint wire.OutPoint) (*UTXOEntry, bool) {
|
||||
func (fus *fullUTXOSet) get(outPoint wire.OutPoint) (*UTXOEntry, bool) {
|
||||
utxoEntry, ok := fus.utxoCollection[outPoint]
|
||||
return utxoEntry, ok
|
||||
}
|
||||
@ -391,11 +434,18 @@ 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
|
||||
func (dus *diffUTXOSet) addTx(tx *wire.MsgTx, blockHeight int32) bool {
|
||||
isCoinbase := IsCoinBaseTx(tx)
|
||||
if !isCoinbase {
|
||||
if !dus.containsInputs(tx) {
|
||||
return false
|
||||
}
|
||||
isCoinBase := IsCoinBaseTx(tx)
|
||||
if !isCoinBase && !dus.containsInputs(tx) {
|
||||
return false
|
||||
}
|
||||
|
||||
dus.appendTx(tx, blockHeight, isCoinBase)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (dus *diffUTXOSet) appendTx(tx *wire.MsgTx, blockHeight int32, isCoinBase bool) {
|
||||
if !isCoinBase {
|
||||
|
||||
for _, txIn := range tx.TxIn {
|
||||
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index)
|
||||
@ -411,7 +461,7 @@ func (dus *diffUTXOSet) addTx(tx *wire.MsgTx, blockHeight int32) bool {
|
||||
for i, txOut := range tx.TxOut {
|
||||
hash := tx.TxHash()
|
||||
outPoint := *wire.NewOutPoint(&hash, uint32(i))
|
||||
entry := newUTXOEntry(txOut, isCoinbase, blockHeight)
|
||||
entry := newUTXOEntry(txOut, isCoinBase, blockHeight)
|
||||
|
||||
if dus.utxoDiff.toRemove.contains(outPoint) {
|
||||
dus.utxoDiff.toRemove.remove(outPoint)
|
||||
@ -419,8 +469,6 @@ func (dus *diffUTXOSet) addTx(tx *wire.MsgTx, blockHeight int32) bool {
|
||||
dus.utxoDiff.toAdd.add(outPoint, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (dus *diffUTXOSet) containsInputs(tx *wire.MsgTx) bool {
|
||||
@ -450,6 +498,12 @@ func (dus *diffUTXOSet) meldToBase() {
|
||||
dus.utxoDiff = newUTXODiff()
|
||||
}
|
||||
|
||||
// diffFromTx returns a diff that is equivalent to provided transaction,
|
||||
// or an error if provided transaction is not valid in the context of this UTXOSet
|
||||
func (dus *diffUTXOSet) diffFromTx(tx *wire.MsgTx, node *blockNode) (*utxoDiff, error) {
|
||||
return diffFromTx(dus, tx, node)
|
||||
}
|
||||
|
||||
func (dus *diffUTXOSet) String() string {
|
||||
return fmt.Sprintf("{Base: %s, To Add: %s, To Remove: %s}", dus.base, dus.utxoDiff.toAdd, dus.utxoDiff.toRemove)
|
||||
}
|
||||
@ -466,3 +520,16 @@ func (dus *diffUTXOSet) collection() utxoCollection {
|
||||
func (dus *diffUTXOSet) clone() utxoSet {
|
||||
return newDiffUTXOSet(dus.base.clone().(*fullUTXOSet), dus.utxoDiff.clone())
|
||||
}
|
||||
|
||||
// get returns the UTXOEntry associated with provided outPoint in this UTXOSet.
|
||||
// Returns false in second output if this UTXOEntry was not found
|
||||
func (dus *diffUTXOSet) get(outPoint wire.OutPoint) (*UTXOEntry, bool) {
|
||||
if dus.utxoDiff.toRemove.contains(outPoint) {
|
||||
return nil, false
|
||||
}
|
||||
if txOut, ok := dus.base.get(outPoint); ok {
|
||||
return txOut, true
|
||||
}
|
||||
txOut, ok := dus.utxoDiff.toAdd.get(outPoint)
|
||||
return txOut, ok
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
"math"
|
||||
|
||||
"github.com/daglabs/btcd/dagconfig"
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
"github.com/daglabs/btcd/txscript"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
var OpTrueScript = []byte{txscript.OpTrue}
|
||||
|
||||
// TestUTXOCollection makes sure that utxoCollection cloning and string representations work as expected.
|
||||
func TestUTXOCollection(t *testing.T) {
|
||||
hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")
|
||||
@ -782,7 +788,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
|
||||
// Apply all transactions to diffSet, in order, with the initial block height startHeight
|
||||
for i, transaction := range test.toAdd {
|
||||
diffSet.addTx(transaction, test.startHeight + int32(i))
|
||||
diffSet.addTx(transaction, test.startHeight+int32(i))
|
||||
}
|
||||
|
||||
// Make sure that the result diffSet equals to the expectedSet
|
||||
@ -792,3 +798,180 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// createCoinbaseTx returns a coinbase transaction with the requested number of
|
||||
// outputs paying an appropriate subsidy based on the passed block height to the
|
||||
// address associated with the harness. It automatically uses a standard
|
||||
// signature script that starts with the block height
|
||||
func createCoinbaseTx(blockHeight int32, numOutputs uint32) (*wire.MsgTx, error) {
|
||||
// Create standard coinbase script.
|
||||
extraNonce := int64(0)
|
||||
coinbaseScript, err := txscript.NewScriptBuilder().
|
||||
AddInt64(int64(blockHeight)).AddInt64(extraNonce).Script()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := wire.NewMsgTx(wire.TxVersion)
|
||||
tx.AddTxIn(&wire.TxIn{
|
||||
// Coinbase transactions have no inputs, so previous outpoint is
|
||||
// zero hash and max index.
|
||||
PreviousOutPoint: *wire.NewOutPoint(&daghash.Hash{},
|
||||
wire.MaxPrevOutIndex),
|
||||
SignatureScript: coinbaseScript,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
})
|
||||
totalInput := CalcBlockSubsidy(blockHeight, &dagconfig.MainNetParams)
|
||||
amountPerOutput := totalInput / int64(numOutputs)
|
||||
remainder := totalInput - amountPerOutput*int64(numOutputs)
|
||||
for i := uint32(0); i < numOutputs; i++ {
|
||||
// Ensure the final output accounts for any remainder that might
|
||||
// be left from splitting the input amount.
|
||||
amount := amountPerOutput
|
||||
if i == numOutputs-1 {
|
||||
amount = amountPerOutput + remainder
|
||||
}
|
||||
tx.AddTxOut(&wire.TxOut{
|
||||
PkScript: OpTrueScript,
|
||||
Value: amount,
|
||||
})
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func TestApplyUTXOChanges(t *testing.T) {
|
||||
// Create a new database and dag instance to run tests against.
|
||||
dag, teardownFunc, err := dagSetup("TestApplyUTXOChanges", &dagconfig.MainNetParams)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup dag instance: %v", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
cbTx, err := createCoinbaseTx(1, 1)
|
||||
if err != nil {
|
||||
t.Errorf("createCoinbaseTx: %v", err)
|
||||
}
|
||||
|
||||
chainedTx := wire.NewMsgTx(wire.TxVersion)
|
||||
chainedTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{Hash: cbTx.TxHash(), Index: 0},
|
||||
SignatureScript: nil,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
})
|
||||
chainedTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: OpTrueScript,
|
||||
Value: int64(1),
|
||||
})
|
||||
|
||||
//Fake block header
|
||||
blockHeader := wire.NewBlockHeader(1, []daghash.Hash{dag.genesis.hash}, &daghash.Hash{}, 0, 0)
|
||||
|
||||
msgBlock1 := &wire.MsgBlock{
|
||||
Header: *blockHeader,
|
||||
Transactions: []*wire.MsgTx{cbTx, chainedTx},
|
||||
}
|
||||
|
||||
block1 := util.NewBlock(msgBlock1)
|
||||
|
||||
var node1 blockNode
|
||||
initBlockNode(&node1, blockHeader, setFromSlice(dag.genesis), dagconfig.MainNetParams.K)
|
||||
|
||||
//Checks that dag.applyUTXOChanges fails because we don't allow a transaction to spend another transaction from the same block
|
||||
_, err = dag.applyUTXOChanges(&node1, block1)
|
||||
if err == nil {
|
||||
t.Errorf("applyUTXOChanges expected an error\n")
|
||||
}
|
||||
|
||||
nonChainedTx := wire.NewMsgTx(wire.TxVersion)
|
||||
nonChainedTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{Hash: dag.dagParams.GenesisBlock.Transactions[0].TxHash(), Index: 0},
|
||||
SignatureScript: nil, //Fake SigScript, because we don't check scripts validity in this test
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
})
|
||||
nonChainedTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: OpTrueScript,
|
||||
Value: int64(1),
|
||||
})
|
||||
|
||||
msgBlock2 := &wire.MsgBlock{
|
||||
Header: *blockHeader,
|
||||
Transactions: []*wire.MsgTx{cbTx, nonChainedTx},
|
||||
}
|
||||
|
||||
block2 := util.NewBlock(msgBlock2)
|
||||
|
||||
var node2 blockNode
|
||||
initBlockNode(&node2, blockHeader, setFromSlice(dag.genesis), dagconfig.MainNetParams.K)
|
||||
|
||||
//Checks that dag.applyUTXOChanges doesn't fail because we all of its transaction are dependant on transactions from previous blocks
|
||||
_, err = dag.applyUTXOChanges(&node2, block2)
|
||||
if err != nil {
|
||||
t.Errorf("applyUTXOChanges: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffFromTx(t *testing.T) {
|
||||
fus := &fullUTXOSet{
|
||||
utxoCollection: utxoCollection{},
|
||||
}
|
||||
cbTx, err := createCoinbaseTx(1, 1)
|
||||
if err != nil {
|
||||
t.Errorf("createCoinbaseTx: %v", err)
|
||||
}
|
||||
fus.addTx(cbTx, 1)
|
||||
node := &blockNode{height: 2} //Fake node
|
||||
cbOutpoint := wire.OutPoint{Hash: cbTx.TxHash(), Index: 0}
|
||||
tx := wire.NewMsgTx(wire.TxVersion)
|
||||
tx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: cbOutpoint,
|
||||
SignatureScript: nil,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
})
|
||||
tx.AddTxOut(&wire.TxOut{
|
||||
PkScript: OpTrueScript,
|
||||
Value: int64(1),
|
||||
})
|
||||
diff, err := fus.diffFromTx(tx, node)
|
||||
if err != nil {
|
||||
t.Errorf("diffFromTx: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(diff.toAdd, utxoCollection{
|
||||
wire.OutPoint{Hash: tx.TxHash(), Index: 0}: newUTXOEntry(tx.TxOut[0], false, 2),
|
||||
}) {
|
||||
t.Errorf("diff.toAdd doesn't have the expected values")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(diff.toRemove, utxoCollection{
|
||||
wire.OutPoint{Hash: cbTx.TxHash(), Index: 0}: newUTXOEntry(cbTx.TxOut[0], true, 1),
|
||||
}) {
|
||||
t.Errorf("diff.toRemove doesn't have the expected values")
|
||||
}
|
||||
|
||||
//Test that we get an error if we don't have the outpoint inside the utxo set
|
||||
invalidTx := wire.NewMsgTx(wire.TxVersion)
|
||||
invalidTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{Hash: daghash.Hash{}, Index: 0},
|
||||
SignatureScript: nil,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
})
|
||||
invalidTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: OpTrueScript,
|
||||
Value: int64(1),
|
||||
})
|
||||
_, err = fus.diffFromTx(invalidTx, node)
|
||||
if err == nil {
|
||||
t.Errorf("diffFromTx: expected an error but got <nil>")
|
||||
}
|
||||
|
||||
//Test that we get an error if the outpoint is inside diffUTXOSet's toRemove
|
||||
dus := newDiffUTXOSet(fus, &utxoDiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
})
|
||||
dus.addTx(tx, 2)
|
||||
_, err = dus.diffFromTx(tx, node)
|
||||
if err == nil {
|
||||
t.Errorf("diffFromTx: expected an error but got <nil>")
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package blockdag
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
@ -112,7 +113,7 @@ func (v *VirtualBlock) TipHashes() []daghash.Hash {
|
||||
|
||||
// SelectedTipHash returns the hash of the selected tip of the virtual block.
|
||||
func (v *VirtualBlock) SelectedTipHash() daghash.Hash {
|
||||
return v.SelectedTip().hash;
|
||||
return v.SelectedTip().hash
|
||||
}
|
||||
|
||||
// GetUTXOEntry returns the requested unspent transaction output. The returned
|
||||
@ -121,5 +122,5 @@ func (v *VirtualBlock) SelectedTipHash() daghash.Hash {
|
||||
// This function is safe for concurrent access. However, the returned entry (if
|
||||
// any) is NOT.
|
||||
func (v *VirtualBlock) GetUTXOEntry(outPoint wire.OutPoint) (*UTXOEntry, bool) {
|
||||
return v.utxoSet.getUTXOEntry(outPoint)
|
||||
return v.utxoSet.get(outPoint)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user