Dev 105 get rid of utxoview (#58)

* [DEV-105] use utxodiff in mempool instead of utxoview

* [DEV-105] get rid of utxoview

* [DEV-105] fix tests to use utxoset

* [DEV-105] remove utxoview type

* [DEV-105] move DagSetup to test_utils.go

* [DEV-105] add comments and add blockdag/test_utils_test.go

* [DEV-105] add restoreInputs arg to removeTransaction

* [DEV-105] give more descriptive names to vars

* [DEV-115] close txChan outside of HandleNewBlock

* [DEV-105] rename DagSetup -> DAGSetup
This commit is contained in:
Ori Newman 2018-09-17 11:47:35 +03:00 committed by stasatdaglabs
parent 85265e1d9b
commit 5fb220c38a
28 changed files with 751 additions and 1089 deletions

View File

@ -76,7 +76,7 @@ func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) er
}
// Notify the caller that the new block was accepted into the block
// chain. The caller would typically want to react by relaying the
// DAG. The caller would typically want to react by relaying the
// inventory to other peers.
dag.dagLock.Unlock()
dag.sendNotification(NTBlockAccepted, block)

View File

@ -7,7 +7,6 @@ package blockdag
import (
"compress/bzip2"
"encoding/binary"
"fmt"
"io"
"os"
"path/filepath"
@ -16,47 +15,11 @@ import (
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/database"
_ "github.com/daglabs/btcd/database/ffldb"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
const (
// testDbType is the database backend type to use for the tests.
testDbType = "ffldb"
// testDbRoot is the root directory used to create all test databases.
testDbRoot = "testdbs"
// blockDataNet is the expected network in the test block data.
blockDataNet = wire.MainNet
)
// filesExists returns whether or not the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// isSupportedDbType returns whether or not the passed database type is
// currently supported.
func isSupportedDbType(dbType string) bool {
supportedDrivers := database.SupportedDrivers()
for _, driver := range supportedDrivers {
if dbType == driver {
return true
}
}
return false
}
// loadBlocks reads files containing bitcoin block data (gzipped but otherwise
// in the format bitcoind writes) from disk and returns them as an array of
// util.Block. This is largely borrowed from the test code in btcdb.
@ -115,80 +78,8 @@ func loadBlocks(filename string) (blocks []*util.Block, err error) {
return
}
// dagSetup is used to create a new db and chain instance with the genesis
// block already inserted. In addition to the new chain instance, it returns
// a teardown function the caller should invoke when done testing to clean up.
func dagSetup(dbName string, params *dagconfig.Params) (*BlockDAG, func(), error) {
if !isSupportedDbType(testDbType) {
return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
}
// Handle memory database specially since it doesn't need the disk
// specific handling.
var db database.DB
var teardown func()
if testDbType == "memdb" {
ndb, err := database.Create(testDbType)
if err != nil {
return nil, nil, fmt.Errorf("error creating db: %v", err)
}
db = ndb
// Setup a teardown function for cleaning up. This function is
// returned to the caller to be invoked when it is done testing.
teardown = func() {
db.Close()
}
} else {
// Create the root directory for test databases.
if !fileExists(testDbRoot) {
if err := os.MkdirAll(testDbRoot, 0700); err != nil {
err := fmt.Errorf("unable to create test db "+
"root: %v", err)
return nil, nil, err
}
}
// Create a new database to store the accepted blocks into.
dbPath := filepath.Join(testDbRoot, dbName)
_ = os.RemoveAll(dbPath)
ndb, err := database.Create(testDbType, dbPath, blockDataNet)
if err != nil {
return nil, nil, fmt.Errorf("error creating db: %v", err)
}
db = ndb
// Setup a teardown function for cleaning up. This function is
// returned to the caller to be invoked when it is done testing.
teardown = func() {
db.Close()
os.RemoveAll(dbPath)
os.RemoveAll(testDbRoot)
}
}
// Copy the chain params to ensure any modifications the tests do to
// the chain parameters do not affect the global instance.
paramsCopy := *params
// Create the main chain instance.
chain, err := New(&Config{
DB: db,
DAGParams: &paramsCopy,
Checkpoints: nil,
TimeSource: NewMedianTime(),
SigCache: txscript.NewSigCache(1000),
})
if err != nil {
teardown()
err := fmt.Errorf("failed to create chain instance: %v", err)
return nil, nil, err
}
return chain, teardown, nil
}
// loadUTXOView returns a utxo view loaded from a file.
func loadUTXOView(filename string) (*UTXOView, error) {
// loadUTXOSet returns a utxo view loaded from a file.
func loadUTXOSet(filename string) (UTXOSet, error) {
// The utxostore file format is:
// <tx hash><output index><serialized utxo len><serialized utxo>
//
@ -210,7 +101,7 @@ func loadUTXOView(filename string) (*UTXOView, error) {
}
defer fi.Close()
view := NewUTXOView()
utxoSet := NewFullUTXOSet()
for {
// Hash of the utxo entry.
var hash daghash.Hash
@ -249,10 +140,10 @@ func loadUTXOView(filename string) (*UTXOView, error) {
if err != nil {
return nil, err
}
view.Entries()[wire.OutPoint{Hash: hash, Index: index}] = entry
utxoSet.utxoCollection[wire.OutPoint{Hash: hash, Index: index}] = entry
}
return view, nil
return utxoSet, nil
}
// TstSetCoinbaseMaturity makes the ability to set the coinbase maturity

View File

@ -306,7 +306,7 @@ type SequenceLock struct {
}
// CalcSequenceLock computes a relative lock-time SequenceLock for the passed
// transaction using the passed UTXOView to obtain the past median time
// transaction using the passed UTXOSet to obtain the past median time
// for blocks in which the referenced inputs of the transactions were included
// within. The generated SequenceLock lock can be used in conjunction with a
// block height, and adjusted median block time to determine if all the inputs
@ -314,18 +314,18 @@ type SequenceLock struct {
// the candidate transaction to be included in a block.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) CalcSequenceLock(tx *util.Tx, utxoView *UTXOView, mempool bool) (*SequenceLock, error) {
func (dag *BlockDAG) CalcSequenceLock(tx *util.Tx, utxoSet UTXOSet, mempool bool) (*SequenceLock, error) {
dag.dagLock.Lock()
defer dag.dagLock.Unlock()
return dag.calcSequenceLock(dag.virtual.SelectedTip(), tx, utxoView, mempool)
return dag.calcSequenceLock(dag.virtual.SelectedTip(), utxoSet, tx, mempool)
}
// calcSequenceLock computes the relative lock-times for the passed
// transaction. See the exported version, CalcSequenceLock for further details.
//
// This function MUST be called with the chain state lock held (for writes).
func (dag *BlockDAG) calcSequenceLock(node *blockNode, tx *util.Tx, utxoView *UTXOView, mempool bool) (*SequenceLock, error) {
func (dag *BlockDAG) calcSequenceLock(node *blockNode, utxoSet UTXOSet, tx *util.Tx, mempool bool) (*SequenceLock, error) {
// A value of -1 for each relative lock type represents a relative time
// lock value that will allow a transaction to be included in a block
// at any given height or time.
@ -344,8 +344,8 @@ func (dag *BlockDAG) calcSequenceLock(node *blockNode, tx *util.Tx, utxoView *UT
mTx := tx.MsgTx()
for txInIndex, txIn := range mTx.TxIn {
utxo := utxoView.LookupEntry(txIn.PreviousOutPoint)
if utxo == nil {
entry, ok := utxoSet.Get(txIn.PreviousOutPoint)
if !ok {
str := fmt.Sprintf("output %v referenced from "+
"transaction %s:%d either does not exist or "+
"has already been spent", txIn.PreviousOutPoint,
@ -356,7 +356,7 @@ func (dag *BlockDAG) calcSequenceLock(node *blockNode, tx *util.Tx, utxoView *UT
// If the input height is set to the mempool height, then we
// assume the transaction makes it into the next block when
// evaluating its sequence blocks.
inputHeight := utxo.BlockHeight()
inputHeight := entry.BlockHeight()
if inputHeight == 0x7fffffff {
inputHeight = nextHeight
}
@ -605,8 +605,8 @@ func (dag *BlockDAG) applyUTXOChanges(node *blockNode, block *util.Block) (*utxo
}
// It is now safe to meld the UTXO set to base.
diffSet := newVirtualUTXO.(*diffUTXOSet)
utxoDiff := diffSet.utxoDiff
diffSet := newVirtualUTXO.(*DiffUTXOSet)
utxoDiff := diffSet.UTXODiff
diffSet.meldToBase()
// It is now safe to commit all the provisionalNodes
@ -695,26 +695,26 @@ 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) {
func (p *provisionalNode) verifyAndBuildUTXO(virtual *VirtualBlock, db database.DB) (UTXOSet, error) {
pastUTXO, err := p.pastUTXO(virtual, db)
if err != nil {
return nil, err
}
diff := newUTXODiff()
diff := NewUTXODiff()
for _, tx := range p.transactions {
txDiff, err := pastUTXO.diffFromTx(tx.MsgTx(), p.original)
if err != nil {
return nil, err
}
diff, err = diff.withDiff(txDiff)
diff, err = diff.WithDiff(txDiff)
if err != nil {
return nil, err
}
}
utxo, err := pastUTXO.withDiff(diff)
utxo, err := pastUTXO.WithDiff(diff)
if err != nil {
return nil, err
}
@ -722,7 +722,7 @@ func (p *provisionalNode) verifyAndBuildUTXO(virtual *VirtualBlock, db database.
}
// pastUTXO returns the UTXO of a given block's (in provisionalNode format) past
func (p *provisionalNode) pastUTXO(virtual *VirtualBlock, db database.DB) (utxoSet, error) {
func (p *provisionalNode) pastUTXO(virtual *VirtualBlock, db database.DB) (UTXOSet, error) {
pastUTXO, err := p.selectedParent.restoreUTXO(virtual)
if err != nil {
return nil, err
@ -765,14 +765,14 @@ func (p *provisionalNode) pastUTXO(virtual *VirtualBlock, db database.DB) (utxoS
// Add all transactions to the pastUTXO
// Purposefully ignore failures - these are just unaccepted transactions
for _, tx := range blueBlockTransactions {
_ = pastUTXO.addTx(tx.MsgTx(), p.original.height)
_ = pastUTXO.AddTx(tx.MsgTx(), p.original.height)
}
return pastUTXO, nil
}
// restoreUTXO restores the UTXO of a given block (in provisionalNode format) from its diff
func (p *provisionalNode) restoreUTXO(virtual *VirtualBlock) (utxoSet, error) {
func (p *provisionalNode) restoreUTXO(virtual *VirtualBlock) (UTXOSet, error) {
stack := []*provisionalNode{p}
current := p
@ -781,11 +781,11 @@ func (p *provisionalNode) restoreUTXO(virtual *VirtualBlock) (utxoSet, error) {
stack = append(stack, current)
}
utxo := utxoSet(virtual.utxoSet)
utxo := UTXOSet(virtual.UTXOSet)
var err error
for i := len(stack) - 1; i >= 0; i-- {
utxo, err = utxo.withDiff(stack[i].diff)
utxo, err = utxo.WithDiff(stack[i].diff)
if err != nil {
return nil, err
}
@ -796,8 +796,8 @@ func (p *provisionalNode) restoreUTXO(virtual *VirtualBlock) (utxoSet, error) {
// updateParents adds this block (in provisionalNode format) to the children sets of its parents
// and updates the diff of any parent whose DiffChild is this block
func (p *provisionalNode) updateParents(virtual *VirtualBlock, newBlockUTXO utxoSet) error {
virtualDiffFromNewBlock, err := virtual.utxoSet.diffFrom(newBlockUTXO)
func (p *provisionalNode) updateParents(virtual *VirtualBlock, newBlockUTXO UTXOSet) error {
virtualDiffFromNewBlock, err := virtual.UTXOSet.diffFrom(newBlockUTXO)
if err != nil {
return err
}
@ -824,7 +824,7 @@ func (p *provisionalNode) updateParents(virtual *VirtualBlock, newBlockUTXO utxo
}
// updateTipsUTXO builds and applies new diff UTXOs for all the DAG's tips (in provisionalNode format)
func updateTipsUTXO(tipProvisionals []*provisionalNode, virtual *VirtualBlock, virtualUTXO utxoSet) error {
func updateTipsUTXO(tipProvisionals []*provisionalNode, virtual *VirtualBlock, virtualUTXO UTXOSet) error {
for _, tipProvisional := range tipProvisionals {
tipUTXO, err := tipProvisional.restoreUTXO(virtual)
if err != nil {
@ -1245,6 +1245,16 @@ func (dag *BlockDAG) locateHeaders(locator BlockLocator, hashStop *daghash.Hash,
return headers
}
// RLock locks the DAG for reading.
func (dag *BlockDAG) RLock() {
dag.dagLock.RLock()
}
// RUnlock unlocks the DAG for reading.
func (dag *BlockDAG) RUnlock() {
dag.dagLock.RUnlock()
}
// LocateHeaders returns the headers of the blocks after the first known block
// in the locator until the provided stop hash is reached, or up to a max of
// wire.MaxBlockHeadersPerMsg headers.

View File

@ -43,7 +43,7 @@ func TestHaveBlock(t *testing.T) {
}
// Create a new database and chain instance to run tests against.
chain, teardownFunc, err := dagSetup("haveblock",
dag, teardownFunc, err := DAGSetup("haveblock",
&dagconfig.MainNetParams)
if err != nil {
t.Errorf("Failed to setup chain instance: %v", err)
@ -53,10 +53,10 @@ func TestHaveBlock(t *testing.T) {
// Since we're not dealing with the real block chain, set the coinbase
// maturity to 1.
chain.TstSetCoinbaseMaturity(1)
dag.TstSetCoinbaseMaturity(1)
for i := 1; i < len(blocks); i++ {
isOrphan, err := chain.ProcessBlock(blocks[i], BFNone)
isOrphan, err := dag.ProcessBlock(blocks[i], BFNone)
if err != nil {
t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
return
@ -80,7 +80,7 @@ func TestHaveBlock(t *testing.T) {
}
blocks = append(blocks, blockTmp...)
}
isOrphan, err := chain.ProcessBlock(blocks[6], BFNone)
isOrphan, err := dag.ProcessBlock(blocks[6], BFNone)
// Block 3c should fail to connect since its parents are related. (It points to 1 and 2, and 1 is the parent of 2)
if err == nil {
@ -94,7 +94,7 @@ func TestHaveBlock(t *testing.T) {
}
// Insert an orphan block.
isOrphan, err = chain.ProcessBlock(util.NewBlock(&Block100000),
isOrphan, err = dag.ProcessBlock(util.NewBlock(&Block100000),
BFNone)
if err != nil {
t.Errorf("Unable to process block: %v", err)
@ -130,7 +130,7 @@ func TestHaveBlock(t *testing.T) {
continue
}
result, err := chain.HaveBlock(hash)
result, err := dag.HaveBlock(hash)
if err != nil {
t.Errorf("HaveBlock #%d unexpected error: %v", i, err)
return
@ -153,15 +153,15 @@ func TestCalcSequenceLock(t *testing.T) {
blockVersion := int32(0x10000000)
// Generate enough synthetic blocks for the rest of the test
chain := newTestDAG(netParams)
node := chain.virtual.SelectedTip()
dag := newTestDAG(netParams)
node := dag.virtual.SelectedTip()
blockTime := node.Header().Timestamp
numBlocksToGenerate := uint32(5)
for i := uint32(0); i < numBlocksToGenerate; i++ {
blockTime = blockTime.Add(time.Second)
node = newTestNode(setFromSlice(node), blockVersion, 0, blockTime, netParams.K)
chain.index.AddNode(node)
chain.virtual.SetTips(setFromSlice(node))
dag.index.AddNode(node)
dag.virtual.SetTips(setFromSlice(node))
}
// Create a utxo view with a fake utxo for the inputs used in the
@ -173,8 +173,8 @@ func TestCalcSequenceLock(t *testing.T) {
Value: 10,
}},
})
utxoView := NewUTXOView()
utxoView.AddTxOuts(targetTx, int32(numBlocksToGenerate)-4)
utxoSet := NewFullUTXOSet()
utxoSet.AddTx(targetTx.MsgTx(), int32(numBlocksToGenerate)-4)
// 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
@ -214,11 +214,11 @@ func TestCalcSequenceLock(t *testing.T) {
// Adding a utxo with a height of 0x7fffffff indicates that the output
// is currently unmined.
utxoView.AddTxOuts(util.NewTx(unConfTx), 0x7fffffff)
utxoSet.AddTx(unConfTx, 0x7fffffff)
tests := []struct {
tx *wire.MsgTx
view *UTXOView
utxoSet UTXOSet
mempool bool
want *SequenceLock
}{
@ -233,7 +233,7 @@ func TestCalcSequenceLock(t *testing.T) {
Sequence: wire.MaxTxInSequenceNum,
}},
},
view: utxoView,
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: -1,
BlockHeight: -1,
@ -253,7 +253,7 @@ func TestCalcSequenceLock(t *testing.T) {
Sequence: LockTimeToSequence(true, 2),
}},
},
view: utxoView,
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime - 1,
BlockHeight: -1,
@ -271,7 +271,7 @@ func TestCalcSequenceLock(t *testing.T) {
Sequence: LockTimeToSequence(true, 1024),
}},
},
view: utxoView,
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime + 1023,
BlockHeight: -1,
@ -298,7 +298,7 @@ func TestCalcSequenceLock(t *testing.T) {
wire.SequenceLockTimeDisabled,
}},
},
view: utxoView,
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
BlockHeight: prevUtxoHeight + 3,
@ -316,7 +316,7 @@ func TestCalcSequenceLock(t *testing.T) {
Sequence: LockTimeToSequence(false, 3),
}},
},
view: utxoView,
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: -1,
BlockHeight: prevUtxoHeight + 2,
@ -336,7 +336,7 @@ func TestCalcSequenceLock(t *testing.T) {
Sequence: LockTimeToSequence(true, 2560),
}},
},
view: utxoView,
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
BlockHeight: -1,
@ -357,7 +357,7 @@ func TestCalcSequenceLock(t *testing.T) {
Sequence: LockTimeToSequence(false, 11),
}},
},
view: utxoView,
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: -1,
BlockHeight: prevUtxoHeight + 10,
@ -383,7 +383,7 @@ func TestCalcSequenceLock(t *testing.T) {
Sequence: LockTimeToSequence(false, 9),
}},
},
view: utxoView,
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
BlockHeight: prevUtxoHeight + 8,
@ -403,7 +403,7 @@ func TestCalcSequenceLock(t *testing.T) {
Sequence: LockTimeToSequence(false, 2),
}},
},
view: utxoView,
utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{
Seconds: -1,
@ -421,7 +421,7 @@ func TestCalcSequenceLock(t *testing.T) {
Sequence: LockTimeToSequence(true, 1024),
}},
},
view: utxoView,
utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{
Seconds: nextMedianTime + 1023,
@ -433,7 +433,7 @@ func TestCalcSequenceLock(t *testing.T) {
t.Logf("Running %v SequenceLock tests", len(tests))
for i, test := range tests {
utilTx := util.NewTx(test.tx)
seqLock, err := chain.CalcSequenceLock(utilTx, test.view, test.mempool)
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet, test.mempool)
if err != nil {
t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err)
}
@ -668,12 +668,12 @@ func TestPastUTXOErrors(t *testing.T) {
// TestRestoreUTXOErrors tests all error-cases in restoreUTXO.
// The non-error-cases are tested in the more general tests.
func TestRestoreUTXOErrors(t *testing.T) {
targetErrorMessage := "withDiff error"
targetErrorMessage := "WithDiff error"
testErrorThroughPatching(
t,
targetErrorMessage,
(*fullUTXOSet).withDiff,
func(fus *fullUTXOSet, other *utxoDiff) (utxoSet, error) {
(*fullUTXOSet).WithDiff,
func(fus *fullUTXOSet, other *utxoDiff) (UTXOSet, error) {
return nil, errors.New(targetErrorMessage)
},
)
@ -698,7 +698,7 @@ func testErrorThroughPatching(t *testing.T, expectedErrorMessage string, targetF
}
// Create a new database and dag instance to run tests against.
dag, teardownFunc, err := dagSetup("testErrorThroughPatching", &dagconfig.MainNetParams)
dag, teardownFunc, err := DAGSetup("testErrorThroughPatching", &dagconfig.MainNetParams)
if err != nil {
t.Fatalf("Failed to setup dag instance: %v", err)
}

View File

@ -756,13 +756,13 @@ func (dag *BlockDAG) createDAGState() error {
genesisCoinbaseTxIn := genesisCoinbase.TxIn[0]
genesisCoinbaseTxOut := genesisCoinbase.TxOut[0]
genesisCoinbaseOutpoint := *wire.NewOutPoint(&genesisCoinbaseTxIn.PreviousOutPoint.Hash, genesisCoinbaseTxIn.PreviousOutPoint.Index)
genesisCoinbaseUTXOEntry := newUTXOEntry(genesisCoinbaseTxOut, true, 0)
genesisCoinbaseUTXOEntry := NewUTXOEntry(genesisCoinbaseTxOut, true, 0)
node.diff = &utxoDiff{
toAdd: utxoCollection{genesisCoinbaseOutpoint: genesisCoinbaseUTXOEntry},
toRemove: utxoCollection{},
}
dag.virtual.utxoSet.addTx(genesisCoinbase, 0)
dag.virtual.UTXOSet.AddTx(genesisCoinbase, 0)
dag.virtual.SetTips(setFromSlice(node))
// Add the new node to the index which is used for faster lookups.
@ -979,7 +979,7 @@ func (dag *BlockDAG) initDAGState() error {
}
// Apply the loaded utxoCollection to the virtual block.
dag.virtual.utxoSet.utxoCollection = fullUTXOCollection
dag.virtual.UTXOSet.utxoCollection = fullUTXOCollection
// Apply the stored tips to the virtual block.
tips := newSet()

View File

@ -19,8 +19,8 @@ import (
"github.com/daglabs/btcd/database"
_ "github.com/daglabs/btcd/database/ffldb"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
const (
@ -57,10 +57,10 @@ func isSupportedDbType(dbType string) bool {
return false
}
// dagSetup is used to create a new db and chain instance with the genesis
// DAGSetup is used to create a new db and chain instance with the genesis
// block already inserted. In addition to the new chain instance, it returns
// a teardown function the caller should invoke when done testing to clean up.
func dagSetup(dbName string, params *dagconfig.Params) (*blockdag.BlockDAG, func(), error) {
func DAGSetup(dbName string, params *dagconfig.Params) (*blockdag.BlockDAG, func(), error) {
if !isSupportedDbType(testDbType) {
return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
}
@ -142,7 +142,7 @@ func TestFullBlocks(t *testing.T) {
}
// Create a new database and chain instance to run tests against.
dag, teardownFunc, err := dagSetup("fullblocktest",
dag, teardownFunc, err := DAGSetup("fullblocktest",
&dagconfig.RegressionNetParams)
if err != nil {
t.Errorf("Failed to setup chain instance: %v", err)

View File

@ -14,8 +14,8 @@ import (
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/database"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
const (
@ -833,15 +833,15 @@ func (idx *AddrIndex) indexUnconfirmedAddresses(pkScript []byte, tx *util.Tx) {
// addresses not being indexed.
//
// This function is safe for concurrent access.
func (idx *AddrIndex) AddUnconfirmedTx(tx *util.Tx, utxoView *blockdag.UTXOView) {
func (idx *AddrIndex) AddUnconfirmedTx(tx *util.Tx, utxoSet blockdag.UTXOSet) {
// Index addresses of all referenced previous transaction outputs.
//
// The existence checks are elided since this is only called after the
// transaction has already been validated and thus all inputs are
// already known to exist.
for _, txIn := range tx.MsgTx().TxIn {
entry := utxoView.LookupEntry(txIn.PreviousOutPoint)
if entry == nil {
entry, ok := utxoSet.Get(txIn.PreviousOutPoint)
if !ok {
// Ignore missing entries. This should never happen
// in practice since the function comments specifically
// call out all inputs must be available.

View File

@ -7,12 +7,13 @@ package indexers
import (
"fmt"
"bytes"
"github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/database"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"bytes"
"github.com/daglabs/btcd/wire"
)
var (
@ -309,42 +310,6 @@ func dbFetchTx(dbTx database.Tx, hash *daghash.Hash) (*wire.MsgTx, error) {
return &msgTx, nil
}
// makeUtxoView creates a mock unspent transaction output view by using the
// transaction index in order to look up all inputs referenced by the
// transactions in the block. This is sometimes needed when catching indexes up
// because many of the txouts could actually already be spent however the
// associated scripts are still required to index them.
func makeUtxoView(dbTx database.Tx, block *util.Block, interrupt <-chan struct{}) (*blockdag.UTXOView, error) {
view := blockdag.NewUTXOView()
for txIdx, tx := range block.Transactions() {
// Coinbases do not reference any inputs. Since the block is
// required to have already gone through full validation, it has
// already been proven on the first transaction in the block is
// a coinbase.
if txIdx == 0 {
continue
}
// Use the transaction index to load all of the referenced
// inputs and add their outputs to the view.
for _, txIn := range tx.MsgTx().TxIn {
originOut := &txIn.PreviousOutPoint
originTx, err := dbFetchTx(dbTx, &originOut.Hash)
if err != nil {
return nil, err
}
view.AddTxOuts(util.NewTx(originTx), 0)
}
if interruptRequested(interrupt) {
return nil, errInterruptRequested
}
}
return view, nil
}
// ConnectBlock must be invoked when a block is extending the main chain. It
// keeps track of the state of each index it is managing, performs some sanity
// checks, and invokes each indexer.

View File

@ -18,7 +18,7 @@ func TestNotifications(t *testing.T) {
}
// Create a new database and chain instance to run tests against.
chain, teardownFunc, err := dagSetup("notifications",
chain, teardownFunc, err := DAGSetup("notifications",
&dagconfig.MainNetParams)
if err != nil {
t.Fatalf("Failed to setup chain instance: %v", err)

View File

@ -11,8 +11,8 @@ import (
"time"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
// txValidateItem holds a transaction along with which input to validate.
@ -29,7 +29,7 @@ type txValidator struct {
validateChan chan *txValidateItem
quitChan chan struct{}
resultChan chan error
utxoView *UTXOView
utxoSet UTXOSet
flags txscript.ScriptFlags
sigCache *txscript.SigCache
}
@ -55,8 +55,8 @@ out:
case txVI := <-v.validateChan:
// Ensure the referenced input utxo is available.
txIn := txVI.txIn
utxo := v.utxoView.LookupEntry(txIn.PreviousOutPoint)
if utxo == nil {
entry, ok := v.utxoSet.Get(txIn.PreviousOutPoint)
if !ok {
str := fmt.Sprintf("unable to find unspent "+
"output %v referenced from "+
"transaction %s:%d",
@ -69,7 +69,7 @@ out:
// Create a new script engine for the script pair.
sigScript := txIn.SignatureScript
pkScript := utxo.PkScript()
pkScript := entry.PkScript()
vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
txVI.txInIndex, v.flags, v.sigCache)
if err != nil {
@ -166,12 +166,12 @@ func (v *txValidator) Validate(items []*txValidateItem) error {
// newTxValidator returns a new instance of txValidator to be used for
// validating transaction scripts asynchronously.
func newTxValidator(utxoView *UTXOView, flags txscript.ScriptFlags, sigCache *txscript.SigCache) *txValidator {
func newTxValidator(utxoSet UTXOSet, flags txscript.ScriptFlags, sigCache *txscript.SigCache) *txValidator {
return &txValidator{
validateChan: make(chan *txValidateItem),
quitChan: make(chan struct{}),
resultChan: make(chan error),
utxoView: utxoView,
utxoSet: utxoSet,
sigCache: sigCache,
flags: flags,
}
@ -179,7 +179,7 @@ func newTxValidator(utxoView *UTXOView, flags txscript.ScriptFlags, sigCache *tx
// ValidateTransactionScripts validates the scripts for the passed transaction
// using multiple goroutines.
func ValidateTransactionScripts(tx *util.Tx, utxoView *UTXOView, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
func ValidateTransactionScripts(tx *util.Tx, utxoSet UTXOSet, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
// Collect all of the transaction inputs and required information for
// validation.
txIns := tx.MsgTx().TxIn
@ -199,13 +199,13 @@ func ValidateTransactionScripts(tx *util.Tx, utxoView *UTXOView, flags txscript.
}
// Validate all of the inputs.
validator := newTxValidator(utxoView, flags, sigCache)
validator := newTxValidator(utxoSet, flags, sigCache)
return validator.Validate(txValItems)
}
// checkBlockScripts executes and validates the scripts for all transactions in
// the passed block using multiple goroutines.
func checkBlockScripts(block *util.Block, utxoView *UTXOView, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
func checkBlockScripts(block *util.Block, utxoSet UTXOSet, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
// Collect all of the transaction inputs and required information for
// validation for all transactions in the block into a single slice.
numInputs := 0
@ -230,7 +230,7 @@ func checkBlockScripts(block *util.Block, utxoView *UTXOView, scriptFlags txscri
}
// Validate all of the inputs.
validator := newTxValidator(utxoView, scriptFlags, sigCache)
validator := newTxValidator(utxoSet, scriptFlags, sigCache)
start := time.Now()
if err := validator.Validate(txValItems); err != nil {
return err

View File

@ -34,14 +34,14 @@ func TestCheckBlockScripts(t *testing.T) {
}
storeDataFile := fmt.Sprintf("%d.utxostore", testBlockNum)
view, err := loadUTXOView(storeDataFile)
utxoSet, err := loadUTXOSet(storeDataFile)
if err != nil {
t.Errorf("Error loading txstore: %v\n", err)
return
}
scriptFlags := txscript.ScriptNoFlags
err = checkBlockScripts(blocks[0], view, scriptFlags, nil)
err = checkBlockScripts(blocks[0], utxoSet, scriptFlags, nil)
if err != nil {
t.Errorf("Transaction script validation failed: %v\n", err)
return

105
blockdag/test_utils.go Normal file
View File

@ -0,0 +1,105 @@
package blockdag
import (
"fmt"
"os"
"path/filepath"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/database"
_ "github.com/daglabs/btcd/database/ffldb"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/wire"
)
const (
// testDbType is the database backend type to use for the tests.
testDbType = "ffldb"
// testDbRoot is the root directory used to create all test databases.
testDbRoot = "testdbs"
// blockDataNet is the expected network in the test block data.
blockDataNet = wire.MainNet
)
// isSupportedDbType returns whether or not the passed database type is
// currently supported.
func isSupportedDbType(dbType string) bool {
supportedDrivers := database.SupportedDrivers()
for _, driver := range supportedDrivers {
if dbType == driver {
return true
}
}
return false
}
// filesExists returns whether or not the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// DAGSetup is used to create a new db and chain instance with the genesis
// block already inserted. In addition to the new chain instance, it returns
// a teardown function the caller should invoke when done testing to clean up.
func DAGSetup(dbName string, params *dagconfig.Params) (*BlockDAG, func(), error) {
if !isSupportedDbType(testDbType) {
return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
}
// Handle memory database specially since it doesn't need the disk
// specific handling.
var db database.DB
var teardown func()
// Create the root directory for test databases.
if !fileExists(testDbRoot) {
if err := os.MkdirAll(testDbRoot, 0700); err != nil {
err := fmt.Errorf("unable to create test db "+
"root: %v", err)
return nil, nil, err
}
}
// Create a new database to store the accepted blocks into.
dbPath := filepath.Join(testDbRoot, dbName)
_ = os.RemoveAll(dbPath)
ndb, err := database.Create(testDbType, dbPath, blockDataNet)
if err != nil {
return nil, nil, fmt.Errorf("error creating db: %v", err)
}
db = ndb
// Setup a teardown function for cleaning up. This function is
// returned to the caller to be invoked when it is done testing.
teardown = func() {
db.Close()
os.RemoveAll(dbPath)
os.RemoveAll(testDbRoot)
}
// Copy the chain params to ensure any modifications the tests do to
// the chain parameters do not affect the global instance.
paramsCopy := *params
// Create the DAG instance.
dag, err := New(&Config{
DB: db,
DAGParams: &paramsCopy,
Checkpoints: nil,
TimeSource: NewMedianTime(),
SigCache: txscript.NewSigCache(1000),
})
if err != nil {
teardown()
err := fmt.Errorf("failed to create dag instance: %v", err)
return nil, nil, err
}
return dag, teardown, nil
}

View File

@ -0,0 +1,54 @@
package blockdag
import (
"errors"
"os"
"strings"
"testing"
"bou.ke/monkey"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/database"
)
func TestIsSupportedDbType(t *testing.T) {
if !isSupportedDbType("ffldb") {
t.Errorf("ffldb should be a supported DB driver")
}
if isSupportedDbType("madeUpDb") {
t.Errorf("madeUpDb should not be a supported DB driver")
}
}
// TestDAGSetupErrors tests all error-cases in DAGSetup.
// The non-error-cases are tested in the more general tests.
func TestDAGSetupErrors(t *testing.T) {
os.RemoveAll(testDbRoot)
testDAGSetupErrorThroughPatching(t, "unable to create test db root: ", os.MkdirAll, func(path string, perm os.FileMode) error {
return errors.New("Made up error")
})
testDAGSetupErrorThroughPatching(t, "failed to create dag instance: ", New, func(config *Config) (*BlockDAG, error) {
return nil, errors.New("Made up error")
})
testDAGSetupErrorThroughPatching(t, "unsupported db type ", isSupportedDbType, func(dbType string) bool {
return false
})
testDAGSetupErrorThroughPatching(t, "error creating db: ", database.Create, func(dbType string, args ...interface{}) (database.DB, error) {
return nil, errors.New("Made up error")
})
}
func testDAGSetupErrorThroughPatching(t *testing.T, expectedErrorMessage string, targetFunction interface{}, replacementFunction interface{}) {
monkey.Patch(targetFunction, replacementFunction)
_, tearDown, err := DAGSetup("TestDAGSetup", &dagconfig.MainNetParams)
if tearDown != nil {
defer tearDown()
}
if err == nil || !strings.HasPrefix(err.Error(), expectedErrorMessage) {
t.Errorf("DAGSetup: expected error to have prefix '%s' but got error '%v'", expectedErrorMessage, err)
}
monkey.Unpatch(targetFunction)
}

View File

@ -9,6 +9,93 @@ import (
"github.com/daglabs/btcd/wire"
)
// UTXOEntry houses details about an individual transaction output in a utxo
// set such as whether or not it was contained in a coinbase tx, the height of
// the block that contains the tx, whether or not it is spent, its public key
// script, and how much it pays.
type UTXOEntry struct {
// NOTE: Additions, deletions, or modifications to the order of the
// definitions in this struct should not be changed without considering
// how it affects alignment on 64-bit platforms. The current order is
// specifically crafted to result in minimal padding. There will be a
// lot of these in memory, so a few extra bytes of padding adds up.
amount int64
pkScript []byte // The public key script for the output.
blockHeight int32 // Height of block containing tx.
// packedFlags contains additional info about output such as whether it
// is a coinbase, whether it is spent, and whether it has been modified
// since it was loaded. This approach is used in order to reduce memory
// usage since there will be a lot of these in memory.
packedFlags txoFlags
}
// IsCoinBase returns whether or not the output was contained in a coinbase
// transaction.
func (entry *UTXOEntry) IsCoinBase() bool {
return entry.packedFlags&tfCoinBase == tfCoinBase
}
// BlockHeight returns the height of the block containing the output.
func (entry *UTXOEntry) BlockHeight() int32 {
return entry.blockHeight
}
// IsSpent returns whether or not the output has been spent based upon the
// current state of the unspent transaction output view it was obtained from.
func (entry *UTXOEntry) IsSpent() bool {
return entry.packedFlags&tfSpent == tfSpent
}
// Spend marks the output as spent. Spending an output that is already spent
// has no effect.
func (entry *UTXOEntry) Spend() {
// Nothing to do if the output is already spent.
if entry.IsSpent() {
return
}
// Mark the output as spent.
entry.packedFlags |= tfSpent
}
// Amount returns the amount of the output.
func (entry *UTXOEntry) Amount() int64 {
return entry.amount
}
// PkScript returns the public key script for the output.
func (entry *UTXOEntry) PkScript() []byte {
return entry.pkScript
}
// Clone returns a shallow copy of the utxo entry.
func (entry *UTXOEntry) Clone() *UTXOEntry {
if entry == nil {
return nil
}
return &UTXOEntry{
amount: entry.amount,
pkScript: entry.pkScript,
blockHeight: entry.blockHeight,
packedFlags: entry.packedFlags,
}
}
// txoFlags is a bitmask defining additional information and state for a
// transaction output in a UTXO set.
type txoFlags uint8
const (
// tfCoinBase indicates that a txout was contained in a coinbase tx.
tfCoinBase txoFlags = 1 << iota
// tfSpent indicates that a txout is spent.
tfSpent
)
// utxoCollection represents a set of UTXOs indexed by their outPoints
type utxoCollection map[wire.OutPoint]*UTXOEntry
@ -66,8 +153,8 @@ type utxoDiff struct {
toRemove utxoCollection
}
// newUTXODiff creates a new, empty utxoDiff
func newUTXODiff() *utxoDiff {
// NewUTXODiff creates a new, empty utxoDiff
func NewUTXODiff() *utxoDiff {
return &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
@ -103,7 +190,7 @@ func newUTXODiff() *utxoDiff {
// 2. This diff contains a UTXO in toRemove, and the other diff does not contain it
// diffFrom results in the UTXO being added to toAdd
func (d *utxoDiff) diffFrom(other *utxoDiff) (*utxoDiff, error) {
result := newUTXODiff()
result := NewUTXODiff()
// Note that the following cases are not accounted for, as they are impossible
// as long as the base utxoSet is the same:
@ -153,10 +240,10 @@ func (d *utxoDiff) diffFrom(other *utxoDiff) (*utxoDiff, error) {
return result, nil
}
// withDiff applies provided diff to this diff, creating a new utxoDiff, that would be the result if
// WithDiff applies provided diff to this diff, creating a new utxoDiff, that would be the result if
// first d, and than diff were applied to the same base
//
// withDiff follows a set of rules represented by the following 3 by 3 table:
// WithDiff follows a set of rules represented by the following 3 by 3 table:
//
// | | this | |
// ---------+-----------+-----------+-----------+-----------
@ -176,11 +263,11 @@ func (d *utxoDiff) diffFrom(other *utxoDiff) (*utxoDiff, error) {
//
// Examples:
// 1. This diff contains a UTXO in toAdd, and the other diff contains it in toRemove
// withDiff results in nothing being added
// WithDiff results in nothing being added
// 2. This diff contains a UTXO in toRemove, and the other diff does not contain it
// withDiff results in the UTXO being added to toRemove
func (d *utxoDiff) withDiff(diff *utxoDiff) (*utxoDiff, error) {
result := newUTXODiff()
// WithDiff results in the UTXO being added to toRemove
func (d *utxoDiff) WithDiff(diff *utxoDiff) (*utxoDiff, error) {
result := NewUTXODiff()
// All transactions in d.toAdd:
// If they are not in diff.toRemove - should be added in result.toAdd
@ -191,7 +278,7 @@ func (d *utxoDiff) withDiff(diff *utxoDiff) (*utxoDiff, error) {
result.toAdd.add(outPoint, utxoEntry)
}
if diff.toAdd.contains(outPoint) {
return nil, errors.New("withDiff: transaction both in d.toAdd and in other.toAdd")
return nil, errors.New("WithDiff: transaction both in d.toAdd and in other.toAdd")
}
}
@ -204,7 +291,7 @@ func (d *utxoDiff) withDiff(diff *utxoDiff) (*utxoDiff, error) {
result.toRemove.add(outPoint, utxoEntry)
}
if diff.toRemove.contains(outPoint) {
return nil, errors.New("withDiff: transaction both in d.toRemove and in other.toRemove")
return nil, errors.New("WithDiff: transaction both in d.toRemove and in other.toRemove")
}
}
@ -235,17 +322,29 @@ func (d *utxoDiff) clone() *utxoDiff {
}
}
//RemoveTxOuts marks the transaction's outputs to removal
func (d *utxoDiff) RemoveTxOuts(tx *wire.MsgTx) {
for idx := range tx.TxOut {
hash := tx.TxHash()
d.toRemove.add(*wire.NewOutPoint(&hash, uint32(idx)), nil)
}
}
//AddEntry adds an UTXOEntry to the diff
func (d *utxoDiff) AddEntry(outpoint wire.OutPoint, entry *UTXOEntry) {
d.toAdd.add(outpoint, entry)
}
func (d utxoDiff) String() string {
return fmt.Sprintf("toAdd: %s; toRemove: %s", d.toAdd, d.toRemove)
}
// newUTXOEntry creates a new utxoEntry representing the given txOut
func newUTXOEntry(txOut *wire.TxOut, isCoinbase bool, blockHeight int32) *UTXOEntry {
// NewUTXOEntry creates a new utxoEntry representing the given txOut
func NewUTXOEntry(txOut *wire.TxOut, isCoinbase bool, blockHeight int32) *UTXOEntry {
entry := &UTXOEntry{
amount: txOut.Value,
pkScript: txOut.PkScript,
blockHeight: blockHeight,
packedFlags: tfModified,
}
if isCoinbase {
@ -255,7 +354,7 @@ func newUTXOEntry(txOut *wire.TxOut, isCoinbase bool, blockHeight int32) *UTXOEn
return entry
}
// utxoSet represents a set of unspent transaction outputs
// UTXOSet represents a set of unspent transaction outputs
// Every DAG has exactly one fullUTXOSet.
// When a new block arrives, it is validated and applied to the fullUTXOSet in the following manner:
// 1. Get the block's PastUTXO:
@ -267,26 +366,26 @@ func newUTXOEntry(txOut *wire.TxOut, isCoinbase bool, blockHeight int32) *UTXOEn
// 5. Get the new virtual's PastUTXO
// 6. Rebuild the utxoDiff for all the tips
// 7. Convert (meld) the new virtual's diffUTXOSet into a fullUTXOSet. This updates the DAG's fullUTXOSet
type utxoSet interface {
type UTXOSet interface {
fmt.Stringer
diffFrom(other utxoSet) (*utxoDiff, error)
withDiff(utxoDiff *utxoDiff) (utxoSet, error)
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)
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()
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 {
if entry, ok := u.Get(txIn.PreviousOutPoint); ok {
diff.toRemove.add(txIn.PreviousOutPoint, entry)
} else {
return nil, fmt.Errorf(
@ -297,7 +396,7 @@ func diffFromTx(u utxoSet, tx *wire.MsgTx, containingNode *blockNode) (*utxoDiff
}
for i, txOut := range tx.TxOut {
hash := tx.TxHash()
entry := newUTXOEntry(txOut, isCoinbase, containingNode.height)
entry := NewUTXOEntry(txOut, isCoinbase, containingNode.height)
outPoint := *wire.NewOutPoint(&hash, uint32(i))
diff.toAdd.add(outPoint, entry)
}
@ -309,8 +408,8 @@ type fullUTXOSet struct {
utxoCollection
}
// newFullUTXOSet creates a new utxoSet with full list of transaction outputs and their values
func newFullUTXOSet() *fullUTXOSet {
// NewFullUTXOSet creates a new utxoSet with full list of transaction outputs and their values
func NewFullUTXOSet() *fullUTXOSet {
return &fullUTXOSet{
utxoCollection: utxoCollection{},
}
@ -318,8 +417,8 @@ func newFullUTXOSet() *fullUTXOSet {
// diffFrom returns the difference between this utxoSet and another
// diffFrom can only work when other is a diffUTXOSet, and its base utxoSet is this.
func (fus *fullUTXOSet) diffFrom(other utxoSet) (*utxoDiff, error) {
otherDiffSet, ok := other.(*diffUTXOSet)
func (fus *fullUTXOSet) diffFrom(other UTXOSet) (*utxoDiff, error) {
otherDiffSet, ok := other.(*DiffUTXOSet)
if !ok {
return nil, errors.New("can't diffFrom two fullUTXOSets")
}
@ -328,16 +427,16 @@ func (fus *fullUTXOSet) diffFrom(other utxoSet) (*utxoDiff, error) {
return nil, errors.New("can diffFrom only with diffUTXOSet where this fullUTXOSet is the base")
}
return otherDiffSet.utxoDiff, nil
return otherDiffSet.UTXODiff, nil
}
// withDiff returns a utxoSet which is a diff between this and another utxoSet
func (fus *fullUTXOSet) withDiff(other *utxoDiff) (utxoSet, error) {
return newDiffUTXOSet(fus, other.clone()), nil
// WithDiff returns a utxoSet which is a diff between this and another utxoSet
func (fus *fullUTXOSet) WithDiff(other *utxoDiff) (UTXOSet, error) {
return NewDiffUTXOSet(fus, other.clone()), nil
}
// addTx adds a transaction to this utxoSet and returns true iff it's valid in this UTXO's context
func (fus *fullUTXOSet) addTx(tx *wire.MsgTx, blockHeight int32) bool {
// AddTx adds a transaction to this utxoSet and returns true iff it's valid in this UTXO's context
func (fus *fullUTXOSet) AddTx(tx *wire.MsgTx, blockHeight int32) bool {
isCoinbase := IsCoinBaseTx(tx)
if !isCoinbase {
if !fus.containsInputs(tx) {
@ -353,7 +452,7 @@ func (fus *fullUTXOSet) 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)
fus.add(outPoint, entry)
}
@ -384,33 +483,33 @@ func (fus *fullUTXOSet) collection() utxoCollection {
}
// clone returns a clone of this utxoSet
func (fus *fullUTXOSet) clone() utxoSet {
func (fus *fullUTXOSet) clone() UTXOSet {
return &fullUTXOSet{utxoCollection: fus.utxoCollection.clone()}
}
func (fus *fullUTXOSet) get(outPoint wire.OutPoint) (*UTXOEntry, bool) {
func (fus *fullUTXOSet) Get(outPoint wire.OutPoint) (*UTXOEntry, bool) {
utxoEntry, ok := fus.utxoCollection[outPoint]
return utxoEntry, ok
}
// diffUTXOSet represents a utxoSet with a base fullUTXOSet and a UTXODiff
type diffUTXOSet struct {
// DiffUTXOSet represents a utxoSet with a base fullUTXOSet and a UTXODiff
type DiffUTXOSet struct {
base *fullUTXOSet
utxoDiff *utxoDiff
UTXODiff *utxoDiff
}
// newDiffUTXOSet Creates a new utxoSet based on a base fullUTXOSet and a UTXODiff
func newDiffUTXOSet(base *fullUTXOSet, diff *utxoDiff) *diffUTXOSet {
return &diffUTXOSet{
// NewDiffUTXOSet Creates a new utxoSet based on a base fullUTXOSet and a UTXODiff
func NewDiffUTXOSet(base *fullUTXOSet, diff *utxoDiff) *DiffUTXOSet {
return &DiffUTXOSet{
base: base,
utxoDiff: diff,
UTXODiff: diff,
}
}
// diffFrom returns the difference between this utxoSet and another.
// diffFrom can work if other is this's base fullUTXOSet, or a diffUTXOSet with the same base as this
func (dus *diffUTXOSet) diffFrom(other utxoSet) (*utxoDiff, error) {
otherDiffSet, ok := other.(*diffUTXOSet)
func (dus *DiffUTXOSet) diffFrom(other UTXOSet) (*utxoDiff, error) {
otherDiffSet, ok := other.(*DiffUTXOSet)
if !ok {
return nil, errors.New("can't diffFrom diffUTXOSet with fullUTXOSet")
}
@ -419,21 +518,21 @@ func (dus *diffUTXOSet) diffFrom(other utxoSet) (*utxoDiff, error) {
return nil, errors.New("can't diffFrom with another diffUTXOSet with a different base")
}
return dus.utxoDiff.diffFrom(otherDiffSet.utxoDiff)
return dus.UTXODiff.diffFrom(otherDiffSet.UTXODiff)
}
// withDiff return a new utxoSet which is a diffFrom between this and another utxoSet
func (dus *diffUTXOSet) withDiff(other *utxoDiff) (utxoSet, error) {
diff, err := dus.utxoDiff.withDiff(other)
// WithDiff return a new utxoSet which is a diffFrom between this and another utxoSet
func (dus *DiffUTXOSet) WithDiff(other *utxoDiff) (UTXOSet, error) {
diff, err := dus.UTXODiff.WithDiff(other)
if err != nil {
return nil, err
}
return newDiffUTXOSet(dus.base, diff), nil
return NewDiffUTXOSet(dus.base, diff), nil
}
// addTx adds a transaction to this utxoSet and returns true iff it's valid in this UTXO's context
func (dus *diffUTXOSet) addTx(tx *wire.MsgTx, blockHeight int32) bool {
// 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 && !dus.containsInputs(tx) {
return false
@ -444,16 +543,16 @@ func (dus *diffUTXOSet) addTx(tx *wire.MsgTx, blockHeight int32) bool {
return true
}
func (dus *diffUTXOSet) appendTx(tx *wire.MsgTx, blockHeight int32, isCoinBase bool) {
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)
if dus.utxoDiff.toAdd.contains(outPoint) {
dus.utxoDiff.toAdd.remove(outPoint)
if dus.UTXODiff.toAdd.contains(outPoint) {
dus.UTXODiff.toAdd.remove(outPoint)
} else {
prevUTXOEntry := dus.base.utxoCollection[outPoint]
dus.utxoDiff.toRemove.add(outPoint, prevUTXOEntry)
dus.UTXODiff.toRemove.add(outPoint, prevUTXOEntry)
}
}
}
@ -461,22 +560,22 @@ func (dus *diffUTXOSet) appendTx(tx *wire.MsgTx, blockHeight int32, isCoinBase b
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)
if dus.UTXODiff.toRemove.contains(outPoint) {
dus.UTXODiff.toRemove.remove(outPoint)
} else {
dus.utxoDiff.toAdd.add(outPoint, entry)
dus.UTXODiff.toAdd.add(outPoint, entry)
}
}
}
func (dus *diffUTXOSet) containsInputs(tx *wire.MsgTx) bool {
func (dus *DiffUTXOSet) containsInputs(tx *wire.MsgTx) bool {
for _, txIn := range tx.TxIn {
outPoint := *wire.NewOutPoint(&txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index)
isInBase := dus.base.contains(outPoint)
isInDiffToAdd := dus.utxoDiff.toAdd.contains(outPoint)
isInDiffToRemove := dus.utxoDiff.toRemove.contains(outPoint)
isInDiffToAdd := dus.UTXODiff.toAdd.contains(outPoint)
isInDiffToRemove := dus.UTXODiff.toRemove.contains(outPoint)
if (!isInBase && !isInDiffToAdd) || isInDiffToRemove {
return false
}
@ -486,50 +585,50 @@ func (dus *diffUTXOSet) containsInputs(tx *wire.MsgTx) bool {
}
// meldToBase updates the base fullUTXOSet with all changes in diff
func (dus *diffUTXOSet) meldToBase() {
for outPoint := range dus.utxoDiff.toRemove {
func (dus *DiffUTXOSet) meldToBase() {
for outPoint := range dus.UTXODiff.toRemove {
dus.base.remove(outPoint)
}
for outPoint, utxoEntry := range dus.utxoDiff.toAdd {
for outPoint, utxoEntry := range dus.UTXODiff.toAdd {
dus.base.add(outPoint, utxoEntry)
}
dus.utxoDiff = newUTXODiff()
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) {
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)
func (dus *DiffUTXOSet) String() string {
return fmt.Sprintf("{Base: %s, To Add: %s, To Remove: %s}", dus.base, dus.UTXODiff.toAdd, dus.UTXODiff.toRemove)
}
// collection returns a collection of all UTXOs in this set
func (dus *diffUTXOSet) collection() utxoCollection {
clone := dus.clone().(*diffUTXOSet)
func (dus *DiffUTXOSet) collection() utxoCollection {
clone := dus.clone().(*DiffUTXOSet)
clone.meldToBase()
return clone.base.collection()
}
// clone returns a clone of this UTXO Set
func (dus *diffUTXOSet) clone() utxoSet {
return newDiffUTXOSet(dus.base.clone().(*fullUTXOSet), dus.utxoDiff.clone())
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.
// 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) {
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)
txOut, ok := dus.UTXODiff.toAdd.get(outPoint)
return txOut, ok
}

View File

@ -20,8 +20,8 @@ func TestUTXOCollection(t *testing.T) {
hash1, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")
outPoint0 := *wire.NewOutPoint(hash0, 0)
outPoint1 := *wire.NewOutPoint(hash1, 0)
utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
utxoEntry1 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}, false, 1)
utxoEntry0 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
utxoEntry1 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}, false, 1)
// For each of the following test cases, we will:
// .String() the given collection and compare it to expectedString
@ -79,15 +79,15 @@ func TestUTXODiff(t *testing.T) {
hash1, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")
outPoint0 := *wire.NewOutPoint(hash0, 0)
outPoint1 := *wire.NewOutPoint(hash1, 0)
utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
utxoEntry1 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}, false, 1)
utxoEntry0 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
utxoEntry1 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}, false, 1)
diff := utxoDiff{
toAdd: utxoCollection{outPoint0: utxoEntry0},
toRemove: utxoCollection{outPoint1: utxoEntry1},
}
// Test utxoDiff creation
newDiff := newUTXODiff()
newDiff := NewUTXODiff()
if len(newDiff.toAdd) != 0 || len(newDiff.toRemove) != 0 {
t.Errorf("new diff is not empty")
}
@ -111,16 +111,16 @@ func TestUTXODiff(t *testing.T) {
}
}
// TestUTXODiffRules makes sure that all diffFrom and withDiff rules are followed.
// TestUTXODiffRules makes sure that all diffFrom and WithDiff rules are followed.
// Each test case represents a cell in the two tables outlined in the documentation for utxoDiff.
func TestUTXODiffRules(t *testing.T) {
hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")
outPoint0 := *wire.NewOutPoint(hash0, 0)
utxoEntry0 := newUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
utxoEntry0 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
// For each of the following test cases, we will:
// this.diffFrom(other) and compare it to expectedDiffFromResult
// this.withDiff(other) and compare it to expectedWithDiffResult
// this.WithDiff(other) and compare it to expectedWithDiffResult
//
// Note: an expected nil result means that we expect the respective operation to fail
tests := []struct {
@ -309,20 +309,20 @@ func TestUTXODiffRules(t *testing.T) {
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedDiffFromResult, diffResult)
}
// withDiff from this to other
withDiffResult, err := test.this.withDiff(test.other)
// WithDiff from this to other
withDiffResult, err := test.this.WithDiff(test.other)
// Test whether withDiff returned an error
// Test whether WithDiff returned an error
isWithDiffOk := err == nil
expectedIsWithDiffOk := test.expectedWithDiffResult != nil
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)
}
// Ig not error, test the withDiff result
// Ig not error, test the WithDiff result
if isWithDiffOk && !reflect.DeepEqual(withDiffResult, test.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)
}
}
@ -336,30 +336,30 @@ func TestFullUTXOSet(t *testing.T) {
outPoint1 := *wire.NewOutPoint(hash1, 0)
txOut0 := &wire.TxOut{PkScript: []byte{}, Value: 10}
txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20}
utxoEntry0 := newUTXOEntry(txOut0, true, 0)
utxoEntry1 := newUTXOEntry(txOut1, false, 1)
utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
diff := &utxoDiff{
toAdd: utxoCollection{outPoint0: utxoEntry0},
toRemove: utxoCollection{outPoint1: utxoEntry1},
}
// Test fullUTXOSet creation
emptySet := newFullUTXOSet()
emptySet := NewFullUTXOSet()
if len(emptySet.collection()) != 0 {
t.Errorf("new set is not empty")
}
// Test fullUTXOSet withDiff
withDiffResult, err := emptySet.withDiff(diff)
// Test fullUTXOSet WithDiff
withDiffResult, err := emptySet.WithDiff(diff)
if err != nil {
t.Errorf("withDiff unexpectedly failed")
t.Errorf("WithDiff unexpectedly failed")
}
withDiffUTXOSet, ok := withDiffResult.(*diffUTXOSet)
withDiffUTXOSet, ok := withDiffResult.(*DiffUTXOSet)
if !ok {
t.Errorf("withDiff is of unexpected type")
t.Errorf("WithDiff is of unexpected type")
}
if !reflect.DeepEqual(withDiffUTXOSet.base, emptySet) || !reflect.DeepEqual(withDiffUTXOSet.utxoDiff, diff) {
t.Errorf("withDiff is of unexpected composition")
if !reflect.DeepEqual(withDiffUTXOSet.base, emptySet) || !reflect.DeepEqual(withDiffUTXOSet.UTXODiff, diff) {
t.Errorf("WithDiff is of unexpected composition")
}
// Test fullUTXOSet addTx
@ -367,11 +367,11 @@ func TestFullUTXOSet(t *testing.T) {
transaction0 := wire.NewMsgTx(1)
transaction0.TxIn = []*wire.TxIn{txIn0}
transaction0.TxOut = []*wire.TxOut{txOut0}
if ok = emptySet.addTx(transaction0, 0); ok {
if ok = emptySet.AddTx(transaction0, 0); ok {
t.Errorf("addTx unexpectedly succeeded")
}
emptySet = &fullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}}
if ok = emptySet.addTx(transaction0, 0); !ok {
if ok = emptySet.AddTx(transaction0, 0); !ok {
t.Errorf("addTx unexpectedly failed")
}
@ -398,35 +398,35 @@ func TestDiffUTXOSet(t *testing.T) {
outPoint1 := *wire.NewOutPoint(hash1, 0)
txOut0 := &wire.TxOut{PkScript: []byte{}, Value: 10}
txOut1 := &wire.TxOut{PkScript: []byte{}, Value: 20}
utxoEntry0 := newUTXOEntry(txOut0, true, 0)
utxoEntry1 := newUTXOEntry(txOut1, false, 1)
utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
diff := &utxoDiff{
toAdd: utxoCollection{outPoint0: utxoEntry0},
toRemove: utxoCollection{outPoint1: utxoEntry1},
}
// Test diffUTXOSet creation
emptySet := newDiffUTXOSet(newFullUTXOSet(), newUTXODiff())
emptySet := NewDiffUTXOSet(NewFullUTXOSet(), NewUTXODiff())
if len(emptySet.collection()) != 0 {
t.Errorf("new set is not empty")
}
// Test diffUTXOSet withDiff
withDiffResult, err := emptySet.withDiff(diff)
// Test diffUTXOSet WithDiff
withDiffResult, err := emptySet.WithDiff(diff)
if err != nil {
t.Errorf("withDiff unexpectedly failed")
t.Errorf("WithDiff unexpectedly failed")
}
withDiffUTXOSet, ok := withDiffResult.(*diffUTXOSet)
withDiffUTXOSet, ok := withDiffResult.(*DiffUTXOSet)
if !ok {
t.Errorf("withDiff is of unexpected type")
t.Errorf("WithDiff is of unexpected type")
}
withDiff, _ := newUTXODiff().withDiff(diff)
if !reflect.DeepEqual(withDiffUTXOSet.base, emptySet.base) || !reflect.DeepEqual(withDiffUTXOSet.utxoDiff, withDiff) {
t.Errorf("withDiff is of unexpected composition")
withDiff, _ := NewUTXODiff().WithDiff(diff)
if !reflect.DeepEqual(withDiffUTXOSet.base, emptySet.base) || !reflect.DeepEqual(withDiffUTXOSet.UTXODiff, withDiff) {
t.Errorf("WithDiff is of unexpected composition")
}
_, err = newDiffUTXOSet(newFullUTXOSet(), diff).withDiff(diff)
_, err = NewDiffUTXOSet(NewFullUTXOSet(), diff).WithDiff(diff)
if err == nil {
t.Errorf("withDiff unexpectedly succeeded")
t.Errorf("WithDiff unexpectedly succeeded")
}
// Given a diffSet, each case tests that meldToBase, String, collection, and cloning work as expected
@ -437,23 +437,23 @@ func TestDiffUTXOSet(t *testing.T) {
// .clone() the given diffSet and compare its value to itself (expected: equals) and its reference to itself (expected: not equal)
tests := []struct {
name string
diffSet *diffUTXOSet
expectedMeldSet *diffUTXOSet
diffSet *DiffUTXOSet
expectedMeldSet *DiffUTXOSet
expectedString string
expectedCollection utxoCollection
}{
{
name: "empty base, empty diff",
diffSet: &diffUTXOSet{
base: newFullUTXOSet(),
utxoDiff: &utxoDiff{
diffSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
},
expectedMeldSet: &diffUTXOSet{
base: newFullUTXOSet(),
utxoDiff: &utxoDiff{
expectedMeldSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
@ -463,16 +463,16 @@ func TestDiffUTXOSet(t *testing.T) {
},
{
name: "empty base, one member in diff toAdd",
diffSet: &diffUTXOSet{
base: newFullUTXOSet(),
utxoDiff: &utxoDiff{
diffSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &utxoDiff{
toAdd: utxoCollection{outPoint0: utxoEntry0},
toRemove: utxoCollection{},
},
},
expectedMeldSet: &diffUTXOSet{
expectedMeldSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
@ -482,16 +482,16 @@ func TestDiffUTXOSet(t *testing.T) {
},
{
name: "empty base, one member in diff toRemove",
diffSet: &diffUTXOSet{
base: newFullUTXOSet(),
utxoDiff: &utxoDiff{
diffSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{outPoint0: utxoEntry0},
},
},
expectedMeldSet: &diffUTXOSet{
expectedMeldSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
@ -501,21 +501,21 @@ func TestDiffUTXOSet(t *testing.T) {
},
{
name: "one member in base toAdd, one member in diff toAdd",
diffSet: &diffUTXOSet{
diffSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{outPoint1: utxoEntry1},
toRemove: utxoCollection{},
},
},
expectedMeldSet: &diffUTXOSet{
expectedMeldSet: &DiffUTXOSet{
base: &fullUTXOSet{
utxoCollection: utxoCollection{
outPoint0: utxoEntry0,
outPoint1: utxoEntry1,
},
},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
@ -528,18 +528,18 @@ func TestDiffUTXOSet(t *testing.T) {
},
{
name: "one member in base toAdd, same one member in diff toRemove",
diffSet: &diffUTXOSet{
diffSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint0: utxoEntry0}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{outPoint0: utxoEntry0},
},
},
expectedMeldSet: &diffUTXOSet{
expectedMeldSet: &DiffUTXOSet{
base: &fullUTXOSet{
utxoCollection: utxoCollection{},
},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
@ -551,7 +551,7 @@ func TestDiffUTXOSet(t *testing.T) {
for _, test := range tests {
// Test meldToBase
meldSet := test.diffSet.clone().(*diffUTXOSet)
meldSet := test.diffSet.clone().(*DiffUTXOSet)
meldSet.meldToBase()
if !reflect.DeepEqual(meldSet, test.expectedMeldSet) {
t.Errorf("unexpected melded set in test \"%s\". "+
@ -573,7 +573,7 @@ func TestDiffUTXOSet(t *testing.T) {
}
// Test cloning
clonedSet := test.diffSet.clone().(*diffUTXOSet)
clonedSet := test.diffSet.clone().(*DiffUTXOSet)
if !reflect.DeepEqual(clonedSet, test.diffSet) {
t.Errorf("unexpected set clone in test \"%s\". "+
"Expected: \"%v\", got: \"%v\".", test.name, test.diffSet, clonedSet)
@ -590,32 +590,32 @@ func TestDiffUTXOSet(t *testing.T) {
// 2. fullUTXOSet cannot diffFrom a diffUTXOSet with a base other that itself.
// 3. diffUTXOSet cannot diffFrom a diffUTXOSet with a different base.
func TestUTXOSetDiffRules(t *testing.T) {
fullSet := newFullUTXOSet()
diffSet := newDiffUTXOSet(fullSet, newUTXODiff())
fullSet := NewFullUTXOSet()
diffSet := NewDiffUTXOSet(fullSet, NewUTXODiff())
// For each of the following test cases, we will call utxoSet.diffFrom(diffSet) and compare
// whether the function succeeded with expectedSuccess
//
// Note: since test cases are similar for both fullUTXOSet and diffUTXOSet, we test both using the same test cases
run := func(set utxoSet) {
run := func(set UTXOSet) {
tests := []struct {
name string
diffSet utxoSet
diffSet UTXOSet
expectedSuccess bool
}{
{
name: "diff from fullSet",
diffSet: newFullUTXOSet(),
diffSet: NewFullUTXOSet(),
expectedSuccess: false,
},
{
name: "diff from diffSet with different base",
diffSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()),
diffSet: NewDiffUTXOSet(NewFullUTXOSet(), NewUTXODiff()),
expectedSuccess: false,
},
{
name: "diff from diffSet with same base",
diffSet: newDiffUTXOSet(fullSet, newUTXODiff()),
diffSet: NewDiffUTXOSet(fullSet, NewUTXODiff()),
expectedSuccess: true,
},
}
@ -640,7 +640,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
hash0, _ := daghash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")
txIn0 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{Hash: *hash0, Index: math.MaxUint32}, Sequence: 0}
txOut0 := &wire.TxOut{PkScript: []byte{0}, Value: 10}
utxoEntry0 := newUTXOEntry(txOut0, true, 0)
utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
transaction0 := wire.NewMsgTx(1)
transaction0.TxIn = []*wire.TxIn{txIn0}
transaction0.TxOut = []*wire.TxOut{txOut0}
@ -650,7 +650,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
outPoint1 := *wire.NewOutPoint(&hash1, 0)
txIn1 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{Hash: hash1, Index: 0}, Sequence: 0}
txOut1 := &wire.TxOut{PkScript: []byte{1}, Value: 20}
utxoEntry1 := newUTXOEntry(txOut1, false, 1)
utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
transaction1 := wire.NewMsgTx(1)
transaction1.TxIn = []*wire.TxIn{txIn1}
transaction1.TxOut = []*wire.TxOut{txOut1}
@ -660,7 +660,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
outPoint2 := *wire.NewOutPoint(&hash2, 0)
txIn2 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutPoint: wire.OutPoint{Hash: hash2, Index: 0}, Sequence: 0}
txOut2 := &wire.TxOut{PkScript: []byte{2}, Value: 30}
utxoEntry2 := newUTXOEntry(txOut2, false, 2)
utxoEntry2 := NewUTXOEntry(txOut2, false, 2)
transaction2 := wire.NewMsgTx(1)
transaction2.TxIn = []*wire.TxIn{txIn2}
transaction2.TxOut = []*wire.TxOut{txOut2}
@ -674,19 +674,19 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
// 2. Compare the result set with expectedSet
tests := []struct {
name string
startSet *diffUTXOSet
startSet *DiffUTXOSet
startHeight int32
toAdd []*wire.MsgTx
expectedSet *diffUTXOSet
expectedSet *DiffUTXOSet
}{
{
name: "add coinbase transaction to empty set",
startSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()),
startSet: NewDiffUTXOSet(NewFullUTXOSet(), NewUTXODiff()),
startHeight: 0,
toAdd: []*wire.MsgTx{transaction0},
expectedSet: &diffUTXOSet{
expectedSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{outPoint1: utxoEntry0},
toRemove: utxoCollection{},
},
@ -694,12 +694,12 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
},
{
name: "add regular transaction to empty set",
startSet: newDiffUTXOSet(newFullUTXOSet(), newUTXODiff()),
startSet: NewDiffUTXOSet(NewFullUTXOSet(), NewUTXODiff()),
startHeight: 0,
toAdd: []*wire.MsgTx{transaction1},
expectedSet: &diffUTXOSet{
expectedSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
@ -707,18 +707,18 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
},
{
name: "add transaction to set with its input in base",
startSet: &diffUTXOSet{
startSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
},
startHeight: 1,
toAdd: []*wire.MsgTx{transaction1},
expectedSet: &diffUTXOSet{
expectedSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{outPoint2: utxoEntry1},
toRemove: utxoCollection{outPoint1: utxoEntry0},
},
@ -726,18 +726,18 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
},
{
name: "add transaction to set with its input in diff toAdd",
startSet: &diffUTXOSet{
base: newFullUTXOSet(),
utxoDiff: &utxoDiff{
startSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &utxoDiff{
toAdd: utxoCollection{outPoint1: utxoEntry0},
toRemove: utxoCollection{},
},
},
startHeight: 1,
toAdd: []*wire.MsgTx{transaction1},
expectedSet: &diffUTXOSet{
base: newFullUTXOSet(),
utxoDiff: &utxoDiff{
expectedSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &utxoDiff{
toAdd: utxoCollection{outPoint2: utxoEntry1},
toRemove: utxoCollection{},
},
@ -745,18 +745,18 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
},
{
name: "add transaction to set with its input in diff toAdd and its output in diff toRemove",
startSet: &diffUTXOSet{
base: newFullUTXOSet(),
utxoDiff: &utxoDiff{
startSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &utxoDiff{
toAdd: utxoCollection{outPoint1: utxoEntry0},
toRemove: utxoCollection{outPoint2: utxoEntry1},
},
},
startHeight: 1,
toAdd: []*wire.MsgTx{transaction1},
expectedSet: &diffUTXOSet{
base: newFullUTXOSet(),
utxoDiff: &utxoDiff{
expectedSet: &DiffUTXOSet{
base: NewFullUTXOSet(),
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
@ -764,18 +764,18 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
},
{
name: "add two transactions, one spending the other, to set with the first input in base",
startSet: &diffUTXOSet{
startSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
},
},
startHeight: 1,
toAdd: []*wire.MsgTx{transaction1, transaction2},
expectedSet: &diffUTXOSet{
expectedSet: &DiffUTXOSet{
base: &fullUTXOSet{utxoCollection: utxoCollection{outPoint1: utxoEntry0}},
utxoDiff: &utxoDiff{
UTXODiff: &utxoDiff{
toAdd: utxoCollection{outPoint3: utxoEntry2},
toRemove: utxoCollection{outPoint1: utxoEntry0},
},
@ -788,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
@ -842,7 +842,7 @@ func createCoinbaseTx(blockHeight int32, numOutputs uint32) (*wire.MsgTx, error)
func TestApplyUTXOChanges(t *testing.T) {
// Create a new database and dag instance to run tests against.
dag, teardownFunc, err := dagSetup("TestApplyUTXOChanges", &dagconfig.MainNetParams)
dag, teardownFunc, err := DAGSetup("TestApplyUTXOChanges", &dagconfig.MainNetParams)
if err != nil {
t.Fatalf("Failed to setup dag instance: %v", err)
}
@ -919,7 +919,7 @@ func TestDiffFromTx(t *testing.T) {
if err != nil {
t.Errorf("createCoinbaseTx: %v", err)
}
fus.addTx(cbTx, 1)
fus.AddTx(cbTx, 1)
node := &blockNode{height: 2} //Fake node
cbOutpoint := wire.OutPoint{Hash: cbTx.TxHash(), Index: 0}
tx := wire.NewMsgTx(wire.TxVersion)
@ -937,13 +937,13 @@ func TestDiffFromTx(t *testing.T) {
t.Errorf("diffFromTx: %v", err)
}
if !reflect.DeepEqual(diff.toAdd, utxoCollection{
wire.OutPoint{Hash: tx.TxHash(), Index: 0}: newUTXOEntry(tx.TxOut[0], false, 2),
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),
wire.OutPoint{Hash: cbTx.TxHash(), Index: 0}: NewUTXOEntry(cbTx.TxOut[0], true, 1),
}) {
t.Errorf("diff.toRemove doesn't have the expected values")
}
@ -965,11 +965,11 @@ func TestDiffFromTx(t *testing.T) {
}
//Test that we get an error if the outpoint is inside diffUTXOSet's toRemove
dus := newDiffUTXOSet(fus, &utxoDiff{
dus := NewDiffUTXOSet(fus, &utxoDiff{
toAdd: utxoCollection{},
toRemove: utxoCollection{},
})
dus.addTx(tx, 2)
dus.AddTx(tx, 2)
_, err = dus.diffFromTx(tx, node)
if err == nil {
t.Errorf("diffFromTx: expected an error but got <nil>")

View File

@ -1,380 +0,0 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockdag
import (
"fmt"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/database"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
// txoFlags is a bitmask defining additional information and state for a
// transaction output in a utxo view.
type txoFlags uint8
const (
// tfCoinBase indicates that a txout was contained in a coinbase tx.
tfCoinBase txoFlags = 1 << iota
// tfSpent indicates that a txout is spent.
tfSpent
// tfModified indicates that a txout has been modified since it was
// loaded.
tfModified
)
// UTXOEntry houses details about an individual transaction output in a utxo
// view such as whether or not it was contained in a coinbase tx, the height of
// the block that contains the tx, whether or not it is spent, its public key
// script, and how much it pays.
type UTXOEntry struct {
// NOTE: Additions, deletions, or modifications to the order of the
// definitions in this struct should not be changed without considering
// how it affects alignment on 64-bit platforms. The current order is
// specifically crafted to result in minimal padding. There will be a
// lot of these in memory, so a few extra bytes of padding adds up.
amount int64
pkScript []byte // The public key script for the output.
blockHeight int32 // Height of block containing tx.
// packedFlags contains additional info about output such as whether it
// is a coinbase, whether it is spent, and whether it has been modified
// since it was loaded. This approach is used in order to reduce memory
// usage since there will be a lot of these in memory.
packedFlags txoFlags
}
// IsCoinBase returns whether or not the output was contained in a coinbase
// transaction.
func (entry *UTXOEntry) IsCoinBase() bool {
return entry.packedFlags&tfCoinBase == tfCoinBase
}
// BlockHeight returns the height of the block containing the output.
func (entry *UTXOEntry) BlockHeight() int32 {
return entry.blockHeight
}
// IsSpent returns whether or not the output has been spent based upon the
// current state of the unspent transaction output view it was obtained from.
func (entry *UTXOEntry) IsSpent() bool {
return entry.packedFlags&tfSpent == tfSpent
}
// Spend marks the output as spent. Spending an output that is already spent
// has no effect.
func (entry *UTXOEntry) Spend() {
// Nothing to do if the output is already spent.
if entry.IsSpent() {
return
}
// Mark the output as spent and modified.
entry.packedFlags |= tfSpent | tfModified
}
// Amount returns the amount of the output.
func (entry *UTXOEntry) Amount() int64 {
return entry.amount
}
// PkScript returns the public key script for the output.
func (entry *UTXOEntry) PkScript() []byte {
return entry.pkScript
}
// Clone returns a shallow copy of the utxo entry.
func (entry *UTXOEntry) Clone() *UTXOEntry {
if entry == nil {
return nil
}
return &UTXOEntry{
amount: entry.amount,
pkScript: entry.pkScript,
blockHeight: entry.blockHeight,
packedFlags: entry.packedFlags,
}
}
// UTXOView represents a view into the set of unspent transaction outputs
// from a specific point of view in the chain. For example, it could be for
// the end of the main chain, some point in the history of the main chain, or
// down a side chain.
//
// The unspent outputs are needed by other transactions for things such as
// script validation and double spend prevention.
type UTXOView struct {
entries map[wire.OutPoint]*UTXOEntry
}
// NewUTXOView returns a new empty unspent transaction output view.
func NewUTXOView() *UTXOView {
return &UTXOView{
entries: make(map[wire.OutPoint]*UTXOEntry),
}
}
// LookupEntry returns information about a given transaction output according to
// the current state of the view. It will return nil if the passed output does
// not exist in the view or is otherwise not available such as when it has been
// disconnected during a reorg.
func (view *UTXOView) LookupEntry(outpoint wire.OutPoint) *UTXOEntry {
return view.entries[outpoint]
}
// addTxOut adds the specified output to the view if it is not provably
// unspendable. When the view already has an entry for the output, it will be
// marked unspent. All fields will be updated for existing entries since it's
// possible it has changed during a reorg.
func (view *UTXOView) addTxOut(outpoint wire.OutPoint, txOut *wire.TxOut, isCoinBase bool, blockHeight int32) {
// Don't add provably unspendable outputs.
if txscript.IsUnspendable(txOut.PkScript) {
return
}
// Update existing entries. All fields are updated because it's
// possible (although extremely unlikely) that the existing entry is
// being replaced by a different transaction with the same hash. This
// is allowed so long as the previous transaction is fully spent.
entry := view.LookupEntry(outpoint)
if entry == nil {
entry = new(UTXOEntry)
view.entries[outpoint] = entry
}
entry.amount = txOut.Value
entry.pkScript = txOut.PkScript
entry.blockHeight = blockHeight
entry.packedFlags = tfModified
if isCoinBase {
entry.packedFlags |= tfCoinBase
}
}
// AddTxOut adds the specified output of the passed transaction to the view if
// it exists and is not provably unspendable. When the view already has an
// entry for the output, it will be marked unspent. All fields will be updated
// for existing entries since it's possible it has changed during a reorg.
func (view *UTXOView) AddTxOut(tx *util.Tx, txOutIdx uint32, blockHeight int32) {
// Can't add an output for an out of bounds index.
if txOutIdx >= uint32(len(tx.MsgTx().TxOut)) {
return
}
// Update existing entries. All fields are updated because it's
// possible (although extremely unlikely) that the existing entry is
// being replaced by a different transaction with the same hash. This
// is allowed so long as the previous transaction is fully spent.
prevOut := wire.OutPoint{Hash: *tx.Hash(), Index: txOutIdx}
txOut := tx.MsgTx().TxOut[txOutIdx]
view.addTxOut(prevOut, txOut, IsCoinBase(tx), blockHeight)
}
// AddTxOuts adds all outputs in the passed transaction which are not provably
// unspendable to the view. When the view already has entries for any of the
// outputs, they are simply marked unspent. All fields will be updated for
// existing entries since it's possible it has changed during a reorg.
func (view *UTXOView) AddTxOuts(tx *util.Tx, blockHeight int32) {
// Loop all of the transaction outputs and add those which are not
// provably unspendable.
isCoinBase := IsCoinBase(tx)
prevOut := wire.OutPoint{Hash: *tx.Hash()}
for txOutIdx, txOut := range tx.MsgTx().TxOut {
// Update existing entries. All fields are updated because it's
// possible (although extremely unlikely) that the existing
// entry is being replaced by a different transaction with the
// same hash. This is allowed so long as the previous
// transaction is fully spent.
prevOut.Index = uint32(txOutIdx)
view.addTxOut(prevOut, txOut, isCoinBase, blockHeight)
}
}
// connectTransaction updates the view by adding all new utxos created by the
// passed transaction and marking all utxos that the transactions spend as
// spent. In addition, when the 'stxos' argument is not nil, it will be updated
// to append an entry for each spent txout. An error will be returned if the
// view does not contain the required utxos.
func (view *UTXOView) connectTransaction(tx *util.Tx, blockHeight int32, stxos *[]spentTxOut) error {
// Coinbase transactions don't have any inputs to spend.
if IsCoinBase(tx) {
// Add the transaction's outputs as available utxos.
view.AddTxOuts(tx, blockHeight)
return nil
}
// Spend the referenced utxos by marking them spent in the view and,
// if a slice was provided for the spent txout details, append an entry
// to it.
for _, txIn := range tx.MsgTx().TxIn {
// Ensure the referenced utxo exists in the view. This should
// never happen unless there is a bug is introduced in the code.
entry := view.entries[txIn.PreviousOutPoint]
if entry == nil {
return AssertError(fmt.Sprintf("view missing input %v",
txIn.PreviousOutPoint))
}
// Only create the stxo details if requested.
if stxos != nil {
// Populate the stxo details using the utxo entry.
var stxo = spentTxOut{
amount: entry.Amount(),
pkScript: entry.PkScript(),
height: entry.BlockHeight(),
isCoinBase: entry.IsCoinBase(),
}
*stxos = append(*stxos, stxo)
}
// Mark the entry as spent. This is not done until after the
// relevant details have been accessed since spending it might
// clear the fields from memory in the future.
entry.Spend()
}
// Add the transaction's outputs as available utxos.
view.AddTxOuts(tx, blockHeight)
return nil
}
// RemoveEntry removes the given transaction output from the current state of
// the view. It will have no effect if the passed output does not exist in the
// view.
func (view *UTXOView) RemoveEntry(outpoint wire.OutPoint) {
delete(view.entries, outpoint)
}
// Entries returns the underlying map that stores of all the utxo entries.
func (view *UTXOView) Entries() map[wire.OutPoint]*UTXOEntry {
return view.entries
}
// fetchUTXOs fetches unspent transaction output data about the provided
// set of outpoints from the point of view of the end of the main chain at the
// time of the call.
//
// Upon completion of this function, the view will contain an entry for each
// requested outpoint. Spent outputs, or those which otherwise don't exist,
// will result in a nil entry in the view.
func (view *UTXOView) fetchUTXOs(db database.DB, outpoints map[wire.OutPoint]struct{}) error {
// Nothing to do if there are no requested outputs.
if len(outpoints) == 0 {
return nil
}
// Load the requested set of unspent transaction outputs from the point
// of view of the end of the main chain.
//
// NOTE: Missing entries are not considered an error here and instead
// will result in nil entries in the view. This is intentionally done
// so other code can use the presence of an entry in the store as a way
// to unnecessarily avoid attempting to reload it from the database.
return db.View(func(dbTx database.Tx) error {
for outpoint := range outpoints {
entry, err := dbFetchUTXOEntry(dbTx, outpoint)
if err != nil {
return err
}
view.entries[outpoint] = entry
}
return nil
})
}
// fetchInputUTXOs loads the unspent transaction outputs for the inputs
// referenced by the transactions in the given block into the view from the
// database as needed. In particular, referenced entries that are earlier in
// the block are added to the view and entries that are already in the view are
// not modified.
func (view *UTXOView) fetchInputUTXOs(db database.DB, block *util.Block) error {
// Build a map of in-flight transactions because some of the inputs in
// this block could be referencing other transactions earlier in this
// block which are not yet in the chain.
txInFlight := map[daghash.Hash]int{}
transactions := block.Transactions()
for i, tx := range transactions {
txInFlight[*tx.Hash()] = i
}
// Loop through all of the transaction inputs (except for the coinbase
// which has no inputs) collecting them into sets of what is needed and
// what is already known (in-flight).
neededSet := make(map[wire.OutPoint]struct{})
for i, tx := range transactions[1:] {
for _, txIn := range tx.MsgTx().TxIn {
// It is acceptable for a transaction input to reference
// the output of another transaction in this block only
// if the referenced transaction comes before the
// current one in this block. Add the outputs of the
// referenced transaction as available utxos when this
// is the case. Otherwise, the utxo details are still
// needed.
//
// NOTE: The >= is correct here because i is one less
// than the actual position of the transaction within
// the block due to skipping the coinbase.
originHash := &txIn.PreviousOutPoint.Hash
if inFlightIndex, ok := txInFlight[*originHash]; ok &&
i >= inFlightIndex {
originTx := transactions[inFlightIndex]
view.AddTxOuts(originTx, block.Height())
continue
}
// Don't request entries that are already in the view
// from the database.
if _, ok := view.entries[txIn.PreviousOutPoint]; ok {
continue
}
neededSet[txIn.PreviousOutPoint] = struct{}{}
}
}
// Request the input utxos from the database.
return view.fetchUTXOs(db, neededSet)
}
// FetchUTXOView loads unspent transaction outputs for the inputs referenced by
// the passed transaction from the point of view of the end of the main chain.
// It also attempts to fetch the utxos for the outputs of the transaction itself
// so the returned view can be examined for duplicate transactions.
//
// This function is safe for concurrent access however the returned view is NOT.
func (dag *BlockDAG) FetchUTXOView(tx *util.Tx) (*UTXOView, error) {
// Create a set of needed outputs based on those referenced by the
// inputs of the passed transaction and the outputs of the transaction
// itself.
neededSet := make(map[wire.OutPoint]struct{})
prevOut := wire.OutPoint{Hash: *tx.Hash()}
for txOutIdx := range tx.MsgTx().TxOut {
prevOut.Index = uint32(txOutIdx)
neededSet[prevOut] = struct{}{}
}
if !IsCoinBase(tx) {
for _, txIn := range tx.MsgTx().TxIn {
neededSet[txIn.PreviousOutPoint] = struct{}{}
}
}
// Request the utxos from the point of view of the end of the main
// chain.
view := NewUTXOView()
dag.dagLock.RLock()
err := view.fetchUTXOs(dag.db, neededSet)
dag.dagLock.RUnlock()
return view, err
}

View File

@ -336,7 +336,7 @@ func CountSigOps(tx *util.Tx) int {
// transactions which are of the pay-to-script-hash type. This uses the
// precise, signature operation counting mechanism from the script engine which
// requires access to the input transaction scripts.
func CountP2SHSigOps(tx *util.Tx, isCoinBaseTx bool, utxoView *UTXOView) (int, error) {
func CountP2SHSigOps(tx *util.Tx, isCoinBaseTx bool, utxoSet UTXOSet) (int, error) {
// Coinbase transactions have no interesting inputs.
if isCoinBaseTx {
return 0, nil
@ -348,8 +348,8 @@ func CountP2SHSigOps(tx *util.Tx, isCoinBaseTx bool, utxoView *UTXOView) (int, e
totalSigOps := 0
for txInIndex, txIn := range msgTx.TxIn {
// Ensure the referenced input transaction is available.
utxo := utxoView.LookupEntry(txIn.PreviousOutPoint)
if utxo == nil || utxo.IsSpent() {
entry, ok := utxoSet.Get(txIn.PreviousOutPoint)
if !ok || entry.IsSpent() {
str := fmt.Sprintf("output %v referenced from "+
"transaction %s:%d either does not exist or "+
"has already been spent", txIn.PreviousOutPoint,
@ -359,7 +359,7 @@ func CountP2SHSigOps(tx *util.Tx, isCoinBaseTx bool, utxoView *UTXOView) (int, e
// We're only interested in pay-to-script-hash types, so skip
// this input if it's not one.
pkScript := utxo.PkScript()
pkScript := entry.PkScript()
if !txscript.IsPayToScriptHash(pkScript) {
continue
}
@ -822,7 +822,7 @@ func (dag *BlockDAG) ensureNoDuplicateTx(node *blockNode, block *util.Block) err
//
// NOTE: The transaction MUST have already been sanity checked with the
// CheckTransactionSanity function prior to calling this function.
func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoView *UTXOView, dagParams *dagconfig.Params) (int64, error) {
func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoSet UTXOSet, dagParams *dagconfig.Params) (int64, error) {
// Coinbase transactions have no inputs.
if IsCoinBase(tx) {
return 0, nil
@ -832,8 +832,8 @@ func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoView *UTXOView, dag
var totalSatoshiIn int64
for txInIndex, txIn := range tx.MsgTx().TxIn {
// Ensure the referenced input transaction is available.
utxo := utxoView.LookupEntry(txIn.PreviousOutPoint)
if utxo == nil || utxo.IsSpent() {
entry, ok := utxoSet.Get(txIn.PreviousOutPoint)
if !ok || entry.IsSpent() {
str := fmt.Sprintf("output %v referenced from "+
"transaction %s:%d either does not exist or "+
"has already been spent", txIn.PreviousOutPoint,
@ -843,8 +843,8 @@ func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoView *UTXOView, dag
// Ensure the transaction is not spending coins which have not
// yet reached the required coinbase maturity.
if utxo.IsCoinBase() {
originHeight := utxo.BlockHeight()
if entry.IsCoinBase() {
originHeight := entry.BlockHeight()
blocksSincePrev := txHeight - originHeight
coinbaseMaturity := int32(dagParams.CoinbaseMaturity)
if blocksSincePrev < coinbaseMaturity {
@ -864,7 +864,7 @@ func CheckTransactionInputs(tx *util.Tx, txHeight int32, utxoView *UTXOView, dag
// a transaction are in a unit value known as a satoshi. One
// bitcoin is a quantity of satoshi as defined by the
// SatoshiPerBitcoin constant.
originTxSatoshi := utxo.Amount()
originTxSatoshi := entry.Amount()
if originTxSatoshi < 0 {
str := fmt.Sprintf("transaction output has negative "+
"value of %v", util.Amount(originTxSatoshi))
@ -956,17 +956,6 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block) error
return err
}
// Load all of the utxos referenced by the inputs for all transactions
// in the block don't already exist in the utxo view from the database.
//
// These utxo entries are needed for verification of things such as
// transaction inputs, counting pay-to-script-hashes, and scripts.
view := NewUTXOView()
err = view.fetchInputUTXOs(dag.db, block)
if err != nil {
return err
}
// The number of signature operations must be less than the maximum
// allowed per block. Note that the preliminary sanity checks on a
// block also include a check similar to this one, but this check
@ -983,7 +972,7 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block) error
// countP2SHSigOps for whether or not the transaction is
// a coinbase transaction rather than having to do a
// full coinbase check again.
numP2SHSigOps, err := CountP2SHSigOps(tx, i == 0, view)
numP2SHSigOps, err := CountP2SHSigOps(tx, i == 0, dag.VirtualBlock().UTXOSet)
if err != nil {
return err
}
@ -1008,11 +997,9 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block) error
// still relatively cheap as compared to running the scripts) checks
// against all the inputs when the signature operations are out of
// bounds.
targetSpentOutputCount := countSpentOutputs(block)
stxos := make([]spentTxOut, 0, targetSpentOutputCount)
var totalFees int64
for _, tx := range transactions {
txFee, err := CheckTransactionInputs(tx, node.height, view,
txFee, err := CheckTransactionInputs(tx, node.height, dag.VirtualBlock().UTXOSet,
dag.dagParams)
if err != nil {
return err
@ -1026,21 +1013,6 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block) error
return ruleError(ErrBadFees, "total fees for block "+
"overflows accumulator")
}
// Add all of the outputs for this transaction which are not
// provably unspendable as available utxos. Also, the passed
// spent txos slice is updated to contain an entry for each
// spent txout in the order each transaction spends them.
err = view.connectTransaction(tx, node.height, &stxos)
if err != nil {
return err
}
}
// Sanity check the correct number of stxos are provided.
if len(stxos) != targetSpentOutputCount {
return AssertError("connectBlock called with inconsistent " +
"spent transaction out information")
}
// The total output values of the coinbase transaction must not exceed
@ -1086,8 +1058,7 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block) error
// A transaction can only be included within a block
// once the sequence locks of *all* its inputs are
// active.
sequenceLock, err := dag.calcSequenceLock(node, tx, view,
false)
sequenceLock, err := dag.calcSequenceLock(node, dag.VirtualBlock().UTXOSet, tx, false)
if err != nil {
return err
}
@ -1105,7 +1076,7 @@ func (dag *BlockDAG) checkConnectBlock(node *blockNode, block *util.Block) error
// expensive ECDSA signature check scripts. Doing this last helps
// prevent CPU exhaustion attacks.
if runScripts {
err := checkBlockScripts(block, view, scriptFlags, dag.sigCache)
err := checkBlockScripts(block, dag.VirtualBlock().UTXOSet, scriptFlags, dag.sigCache)
if err != nil {
return err
}

View File

@ -67,7 +67,7 @@ func TestSequenceLocksActive(t *testing.T) {
// ensure it fails.
func TestCheckConnectBlockTemplate(t *testing.T) {
// Create a new database and chain instance to run tests against.
chain, teardownFunc, err := dagSetup("checkconnectblocktemplate",
dag, teardownFunc, err := DAGSetup("checkconnectblocktemplate",
&dagconfig.MainNetParams)
if err != nil {
t.Errorf("Failed to setup chain instance: %v", err)
@ -77,7 +77,7 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
// Since we're not dealing with the real block chain, set the coinbase
// maturity to 1.
chain.TstSetCoinbaseMaturity(1)
dag.TstSetCoinbaseMaturity(1)
// Load up blocks such that there is a side chain.
// (genesis block) -> 1 -> 2 -> 3 -> 4
@ -97,7 +97,7 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
}
for i := 1; i <= 3; i++ {
_, err := chain.ProcessBlock(blocks[i], BFNone)
_, err := dag.ProcessBlock(blocks[i], BFNone)
if err != nil {
t.Fatalf("CheckConnectBlockTemplate: Received unexpected error "+
"processing block %d: %v", i, err)
@ -105,21 +105,21 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
}
// Block 3 should fail to connect since it's already inserted.
err = chain.CheckConnectBlockTemplate(blocks[3])
err = dag.CheckConnectBlockTemplate(blocks[3])
if err == nil {
t.Fatal("CheckConnectBlockTemplate: Did not received expected error " +
"on block 3")
}
// Block 4 should connect successfully to tip of chain.
err = chain.CheckConnectBlockTemplate(blocks[4])
err = dag.CheckConnectBlockTemplate(blocks[4])
if err != nil {
t.Fatalf("CheckConnectBlockTemplate: Received unexpected error on "+
"block 4: %v", err)
}
// Block 3a should fail to connect since does not build on chain tip.
err = chain.CheckConnectBlockTemplate(blocks[5])
err = dag.CheckConnectBlockTemplate(blocks[5])
if err == nil {
t.Fatal("CheckConnectBlockTemplate: Did not received expected error " +
"on block 3a")
@ -128,7 +128,7 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
// Block 4 should connect even if proof of work is invalid.
invalidPowBlock := *blocks[4].MsgBlock()
invalidPowBlock.Header.Nonce++
err = chain.CheckConnectBlockTemplate(util.NewBlock(&invalidPowBlock))
err = dag.CheckConnectBlockTemplate(util.NewBlock(&invalidPowBlock))
if err != nil {
t.Fatalf("CheckConnectBlockTemplate: Received unexpected error on "+
"block 4 with bad nonce: %v", err)
@ -137,7 +137,7 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
// Invalid block building on chain tip should fail to connect.
invalidBlock := *blocks[4].MsgBlock()
invalidBlock.Header.Bits--
err = chain.CheckConnectBlockTemplate(util.NewBlock(&invalidBlock))
err = dag.CheckConnectBlockTemplate(util.NewBlock(&invalidBlock))
if err == nil {
t.Fatal("CheckConnectBlockTemplate: Did not received expected error " +
"on block 4 with invalid difficulty bits")

View File

@ -15,7 +15,7 @@ import (
type VirtualBlock struct {
mtx sync.Mutex
phantomK uint32
utxoSet *fullUTXOSet
UTXOSet *fullUTXOSet
blockNode
}
@ -24,7 +24,7 @@ func newVirtualBlock(tips blockSet, phantomK uint32) *VirtualBlock {
// The mutex is intentionally not held since this is a constructor.
var virtual VirtualBlock
virtual.phantomK = phantomK
virtual.utxoSet = newFullUTXOSet()
virtual.UTXOSet = NewFullUTXOSet()
virtual.setTips(tips)
return &virtual
@ -34,7 +34,7 @@ func newVirtualBlock(tips blockSet, phantomK uint32) *VirtualBlock {
func (v *VirtualBlock) clone() *VirtualBlock {
return &VirtualBlock{
phantomK: v.phantomK,
utxoSet: v.utxoSet.clone().(*fullUTXOSet),
UTXOSet: v.UTXOSet.clone().(*fullUTXOSet),
blockNode: v.blockNode,
}
}
@ -122,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.get(outPoint)
return v.UTXOSet.get(outPoint)
}

View File

@ -19,8 +19,8 @@ import (
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/mining"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
const (
@ -40,6 +40,13 @@ const (
orphanExpireScanInterval = time.Minute * 5
)
// NewBlockMsg is the type that is used in NewBlockMsg to transfer
// data about transaction removed and added to the mempool
type NewBlockMsg struct {
AcceptedTxs []*TxDesc
Tx *util.Tx
}
// Tag represents an identifier to use for tagging orphan transactions. The
// caller may choose any scheme it desires, however it is common to use peer IDs
// so that orphans can be identified by which peer first relayed them.
@ -51,13 +58,9 @@ type Config struct {
// to policy.
Policy Policy
// ChainParams identifies which chain parameters the txpool is
// DAGParams identifies which chain parameters the txpool is
// associated with.
ChainParams *dagconfig.Params
// FetchUTXOView defines the function to use to fetch unspent
// transaction output information.
FetchUtxoView func(*util.Tx) (*blockdag.UTXOView, error)
DAGParams *dagconfig.Params
// BestHeight defines the function to use to access the block height of
// the current best chain.
@ -70,8 +73,8 @@ type Config struct {
// CalcSequenceLock defines the function to use in order to generate
// the current sequence lock for the given transaction using the passed
// utxo view.
CalcSequenceLock func(*util.Tx, *blockdag.UTXOView) (*blockdag.SequenceLock, error)
// utxo set.
CalcSequenceLock func(*util.Tx, blockdag.UTXOSet) (*blockdag.SequenceLock, error)
// IsDeploymentActive returns true if the target deploymentID is
// active, and false otherwise. The mempool uses this function to gauge
@ -90,6 +93,9 @@ type Config struct {
// FeeEstimatator provides a feeEstimator. If it is not nil, the mempool
// records all new transactions it observes into the feeEstimator.
FeeEstimator *FeeEstimator
// DAG is the BlockDAG we want to use (mainly for UTXO checks)
DAG *blockdag.BlockDAG
}
// Policy houses the policy (configuration parameters) which is used to
@ -172,6 +178,8 @@ type TxPool struct {
// the scan will only run when an orphan is added to the pool as opposed
// to on an unconditional timer.
nextExpireScan time.Time
mpUTXOSet blockdag.UTXOSet
}
// Ensure the TxPool type implements the mining.TxSource interface.
@ -448,14 +456,14 @@ func (mp *TxPool) HaveTransaction(hash *daghash.Hash) bool {
// RemoveTransaction. See the comment for RemoveTransaction for more details.
//
// This function MUST be called with the mempool lock held (for writes).
func (mp *TxPool) removeTransaction(tx *util.Tx, removeRedeemers bool) {
func (mp *TxPool) removeTransaction(tx *util.Tx, removeRedeemers bool, restoreInputs bool) error {
txHash := tx.Hash()
if removeRedeemers {
// Remove any transactions which rely on this one.
for i := uint32(0); i < uint32(len(tx.MsgTx().TxOut)); i++ {
prevOut := wire.OutPoint{Hash: *txHash, Index: i}
if txRedeemer, exists := mp.outpoints[prevOut]; exists {
mp.removeTransaction(txRedeemer, true)
mp.removeTransaction(txRedeemer, true, false)
}
}
}
@ -468,13 +476,29 @@ func (mp *TxPool) removeTransaction(tx *util.Tx, removeRedeemers bool) {
mp.cfg.AddrIndex.RemoveUnconfirmedTx(txHash)
}
diff := blockdag.NewUTXODiff()
diff.RemoveTxOuts(txDesc.Tx.MsgTx())
// Mark the referenced outpoints as unspent by the pool.
for _, txIn := range txDesc.Tx.MsgTx().TxIn {
if restoreInputs {
if prevTxDesc, exists := mp.pool[txIn.PreviousOutPoint.Hash]; exists {
prevOut := prevTxDesc.Tx.MsgTx().TxOut[txIn.PreviousOutPoint.Index]
entry := blockdag.NewUTXOEntry(prevOut, false, mining.UnminedHeight)
diff.AddEntry(txIn.PreviousOutPoint, entry)
}
}
delete(mp.outpoints, txIn.PreviousOutPoint)
}
delete(mp.pool, *txHash)
var err error
mp.mpUTXOSet, err = mp.mpUTXOSet.WithDiff(diff)
if err != nil {
return err
}
atomic.StoreInt64(&mp.lastUpdated, time.Now().Unix())
}
return nil
}
// RemoveTransaction removes the passed transaction from the mempool. When the
@ -483,11 +507,11 @@ func (mp *TxPool) removeTransaction(tx *util.Tx, removeRedeemers bool) {
// they would otherwise become orphans.
//
// This function is safe for concurrent access.
func (mp *TxPool) RemoveTransaction(tx *util.Tx, removeRedeemers bool) {
func (mp *TxPool) RemoveTransaction(tx *util.Tx, removeRedeemers bool, restoreInputs bool) error {
// Protect concurrent access.
mp.mtx.Lock()
mp.removeTransaction(tx, removeRedeemers)
mp.mtx.Unlock()
defer mp.mtx.Unlock()
return mp.removeTransaction(tx, removeRedeemers, restoreInputs)
}
// RemoveDoubleSpends removes all transactions which spend outputs spent by the
@ -503,7 +527,7 @@ func (mp *TxPool) RemoveDoubleSpends(tx *util.Tx) {
for _, txIn := range tx.MsgTx().TxIn {
if txRedeemer, ok := mp.outpoints[txIn.PreviousOutPoint]; ok {
if !txRedeemer.Hash().IsEqual(tx.Hash()) {
mp.removeTransaction(txRedeemer, true)
mp.removeTransaction(txRedeemer, true, false)
}
}
}
@ -515,7 +539,9 @@ func (mp *TxPool) RemoveDoubleSpends(tx *util.Tx) {
// helper for maybeAcceptTransaction.
//
// This function MUST be called with the mempool lock held (for writes).
func (mp *TxPool) addTransaction(utxoView *blockdag.UTXOView, tx *util.Tx, height int32, fee int64) *TxDesc {
func (mp *TxPool) addTransaction(tx *util.Tx, height int32, fee int64) *TxDesc {
mp.cfg.DAG.RLock()
defer mp.cfg.DAG.RUnlock()
// Add the transaction to the pool and mark the referenced outpoints
// as spent by the pool.
txD := &TxDesc{
@ -526,19 +552,20 @@ func (mp *TxPool) addTransaction(utxoView *blockdag.UTXOView, tx *util.Tx, heigh
Fee: fee,
FeePerKB: fee * 1000 / int64(tx.MsgTx().SerializeSize()),
},
StartingPriority: mining.CalcPriority(tx.MsgTx(), utxoView, height),
StartingPriority: mining.CalcPriority(tx.MsgTx(), mp.mpUTXOSet, height),
}
mp.pool[*tx.Hash()] = txD
for _, txIn := range tx.MsgTx().TxIn {
mp.outpoints[txIn.PreviousOutPoint] = tx
}
mp.mpUTXOSet.AddTx(tx.MsgTx(), mining.UnminedHeight)
atomic.StoreInt64(&mp.lastUpdated, time.Now().Unix())
// Add unconfirmed address index entries associated with the transaction
// if enabled.
if mp.cfg.AddrIndex != nil {
mp.cfg.AddrIndex.AddUnconfirmedTx(tx, utxoView)
mp.cfg.AddrIndex.AddUnconfirmedTx(tx, mp.mpUTXOSet)
}
// Record this tx for fee estimation if enabled.
@ -552,7 +579,7 @@ func (mp *TxPool) addTransaction(utxoView *blockdag.UTXOView, tx *util.Tx, heigh
// checkPoolDoubleSpend checks whether or not the passed transaction is
// attempting to spend coins already spent by other transactions in the pool.
// Note it does not check for double spends against transactions already in the
// main chain.
// DAG.
//
// This function MUST be called with the mempool lock held (for reads).
func (mp *TxPool) checkPoolDoubleSpend(tx *util.Tx) error {
@ -579,37 +606,6 @@ func (mp *TxPool) CheckSpend(op wire.OutPoint) *util.Tx {
return txR
}
// fetchInputUtxos loads utxo details about the input transactions referenced by
// the passed transaction. First, it loads the details form the viewpoint of
// the main chain, then it adjusts them based upon the contents of the
// transaction pool.
//
// This function MUST be called with the mempool lock held (for reads).
func (mp *TxPool) fetchInputUtxos(tx *util.Tx) (*blockdag.UTXOView, error) {
utxoView, err := mp.cfg.FetchUtxoView(tx)
if err != nil {
return nil, err
}
// Attempt to populate any missing inputs from the transaction pool.
for _, txIn := range tx.MsgTx().TxIn {
prevOut := &txIn.PreviousOutPoint
entry := utxoView.LookupEntry(*prevOut)
if entry != nil && !entry.IsSpent() {
continue
}
if poolTxDesc, exists := mp.pool[prevOut.Hash]; exists {
// AddTxOut ignores out of range index values, so it is
// safe to call without bounds checking here.
utxoView.AddTxOut(poolTxDesc.Tx, prevOut.Index,
mining.UnminedHeight)
}
}
return utxoView, nil
}
// FetchTransaction returns the requested transaction from the transaction pool.
// This only fetches from the main transaction pool and does not include
// orphans.
@ -634,6 +630,8 @@ func (mp *TxPool) FetchTransaction(txHash *daghash.Hash) (*util.Tx, error) {
//
// This function MUST be called with the mempool lock held (for writes).
func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDupOrphans bool) ([]*daghash.Hash, *TxDesc, error) {
mp.cfg.DAG.RLock()
defer mp.cfg.DAG.RUnlock()
txHash := tx.Hash()
// Don't accept the transaction if it already exists in the pool. This
@ -705,29 +703,16 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
return nil, nil, err
}
// Fetch all of the unspent transaction outputs referenced by the inputs
// to this transaction. This function also attempts to fetch the
// transaction itself to be used for detecting a duplicate transaction
// without needing to do a separate lookup.
utxoView, err := mp.fetchInputUtxos(tx)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
return nil, nil, chainRuleError(cerr)
}
return nil, nil, err
}
// Don't allow the transaction if it exists in the main chain and is not
// Don't allow the transaction if it exists in the DAG and is not
// not already fully spent.
prevOut := wire.OutPoint{Hash: *txHash}
for txOutIdx := range tx.MsgTx().TxOut {
prevOut.Index = uint32(txOutIdx)
entry := utxoView.LookupEntry(prevOut)
if entry != nil && !entry.IsSpent() {
entry, ok := mp.mpUTXOSet.Get(prevOut)
if ok && !entry.IsSpent() {
return nil, nil, txRuleError(wire.RejectDuplicate,
"transaction already exists")
}
utxoView.RemoveEntry(prevOut)
}
// Transaction is an orphan if any of the referenced transaction outputs
@ -735,13 +720,13 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
// is not handled by this function, and the caller should use
// maybeAddOrphan if this behavior is desired.
var missingParents []*daghash.Hash
for outpoint, entry := range utxoView.Entries() {
if entry == nil || entry.IsSpent() {
for _, txIn := range tx.MsgTx().TxIn {
if _, ok := mp.mpUTXOSet.Get(txIn.PreviousOutPoint); !ok {
// Must make a copy of the hash here since the iterator
// is replaced and taking its address directly would
// result in all of the entries pointing to the same
// memory location and thus all be the final hash.
hashCopy := outpoint.Hash
hashCopy := txIn.PreviousOutPoint.Hash
missingParents = append(missingParents, &hashCopy)
}
}
@ -752,7 +737,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
// Don't allow the transaction into the mempool unless its sequence
// lock is active, meaning that it'll be allowed into the next block
// with respect to its defined relative lock times.
sequenceLock, err := mp.cfg.CalcSequenceLock(tx, utxoView)
sequenceLock, err := mp.cfg.CalcSequenceLock(tx, mp.mpUTXOSet)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
return nil, nil, chainRuleError(cerr)
@ -770,7 +755,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
// Also returns the fees associated with the transaction which will be
// used later.
txFee, err := blockdag.CheckTransactionInputs(tx, nextBlockHeight,
utxoView, mp.cfg.ChainParams)
mp.mpUTXOSet, mp.cfg.DAGParams)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
return nil, nil, chainRuleError(cerr)
@ -781,7 +766,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
// Don't allow transactions with non-standard inputs if the network
// parameters forbid their acceptance.
if !mp.cfg.Policy.AcceptNonStd {
err := checkInputsStandard(tx, utxoView)
err := checkInputsStandard(tx, mp.mpUTXOSet)
if err != nil {
// Attempt to extract a reject code from the error so
// it can be retained. When not possible, fall back to
@ -805,7 +790,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
// the coinbase address itself can contain signature operations, the
// maximum allowed signature operations per transaction is less than
// the maximum allowed signature operations per block.
sigOpCount, err := blockdag.CountP2SHSigOps(tx, false, utxoView)
sigOpCount, err := blockdag.CountP2SHSigOps(tx, false, mp.mpUTXOSet)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
return nil, nil, chainRuleError(cerr)
@ -844,7 +829,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
// memory pool from blocks that have been disconnected during a reorg
// are exempted.
if isNew && !mp.cfg.Policy.DisableRelayPriority && txFee < minFee {
currentPriority := mining.CalcPriority(tx.MsgTx(), utxoView,
currentPriority := mining.CalcPriority(tx.MsgTx(), mp.mpUTXOSet,
nextBlockHeight)
if currentPriority <= mining.MinHighPriority {
str := fmt.Sprintf("transaction %v has insufficient "+
@ -880,7 +865,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
// Verify crypto signatures for each input and reject the transaction if
// any don't verify.
err = blockdag.ValidateTransactionScripts(tx, utxoView,
err = blockdag.ValidateTransactionScripts(tx, mp.mpUTXOSet,
txscript.StandardVerifyFlags, mp.cfg.SigCache)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
@ -890,7 +875,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
}
// Add to transaction pool.
txD := mp.addTransaction(utxoView, tx, bestHeight, txFee)
txD := mp.addTransaction(tx, bestHeight, txFee)
log.Debugf("Accepted transaction %v (pool size: %v)", txHash,
len(mp.pool))
@ -1165,12 +1150,8 @@ func (mp *TxPool) RawMempoolVerbose() map[string]*btcjson.GetRawMempoolVerboseRe
// the transaction. Use zero if one or more of the
// input transactions can't be found for some reason.
tx := desc.Tx
var currentPriority float64
utxos, err := mp.fetchInputUtxos(tx)
if err == nil {
currentPriority = mining.CalcPriority(tx.MsgTx(), utxos,
bestHeight+1)
}
currentPriority := mining.CalcPriority(tx.MsgTx(), mp.mpUTXOSet,
bestHeight+1)
mpd := &btcjson.GetRawMempoolVerboseResult{
Size: int32(tx.MsgTx().SerializeSize()),
@ -1203,9 +1184,43 @@ func (mp *TxPool) LastUpdated() time.Time {
return time.Unix(atomic.LoadInt64(&mp.lastUpdated), 0)
}
// HandleNewBlock removes all the transactions in the new block
// from the mempool and the orphan pool, and it also removes
// from the mempool transactions that double spend a
// transaction that is already in the DAG
func (mp *TxPool) HandleNewBlock(block *util.Block, txChan chan NewBlockMsg) error {
oldUTXOSet := mp.mpUTXOSet
// Remove all of the transactions (except the coinbase) in the
// connected block from the transaction pool. Secondly, remove any
// transactions which are now double spends as a result of these
// new transactions. Finally, remove any transaction that is
// no longer an orphan. Transactions which depend on a confirmed
// transaction are NOT removed recursively because they are still
// valid.
for _, tx := range block.Transactions()[1:] {
err := mp.RemoveTransaction(tx, false, false)
if err != nil {
mp.mpUTXOSet = oldUTXOSet
return err
}
mp.RemoveDoubleSpends(tx)
mp.RemoveOrphan(tx)
acceptedTxs := mp.ProcessOrphans(tx)
txChan <- NewBlockMsg{
AcceptedTxs: acceptedTxs,
Tx: tx,
}
}
return nil
}
// New returns a new memory pool for validating and storing standalone
// transactions until they are mined into a block.
func New(cfg *Config) *TxPool {
virtualUTXO := cfg.DAG.VirtualBlock().UTXOSet
mpUTXO := blockdag.NewDiffUTXOSet(virtualUTXO, blockdag.NewUTXODiff())
return &TxPool{
cfg: *cfg,
pool: make(map[daghash.Hash]*TxDesc),
@ -1213,5 +1228,6 @@ func New(cfg *Config) *TxPool {
orphansByPrev: make(map[wire.OutPoint]map[daghash.Hash]*util.Tx),
nextExpireScan: time.Now().Add(orphanExpireScanInterval),
outpoints: make(map[wire.OutPoint]*util.Tx),
mpUTXOSet: mpUTXO,
}
}

View File

@ -6,6 +6,7 @@ package mempool
import (
"encoding/hex"
"fmt"
"reflect"
"runtime"
"sync"
@ -17,8 +18,8 @@ import (
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
// fakeChain is used by the pool harness to provide generated test utxos and
@ -26,42 +27,10 @@ import (
// transactions to appear as though they are spending completely valid utxos.
type fakeChain struct {
sync.RWMutex
utxos *blockdag.UTXOView
currentHeight int32
medianTimePast time.Time
}
// FetchUTXOView loads utxo details about the inputs referenced by the passed
// transaction from the point of view of the fake chain. It also attempts to
// fetch the utxos for the outputs of the transaction itself so the returned
// view can be examined for duplicate transactions.
//
// This function is safe for concurrent access however the returned view is NOT.
func (s *fakeChain) FetchUtxoView(tx *util.Tx) (*blockdag.UTXOView, error) {
s.RLock()
defer s.RUnlock()
// All entries are cloned to ensure modifications to the returned view
// do not affect the fake chain's view.
// Add an entry for the tx itself to the new view.
viewpoint := blockdag.NewUTXOView()
prevOut := wire.OutPoint{Hash: *tx.Hash()}
for txOutIdx := range tx.MsgTx().TxOut {
prevOut.Index = uint32(txOutIdx)
entry := s.utxos.LookupEntry(prevOut)
viewpoint.Entries()[prevOut] = entry.Clone()
}
// Add entries for all of the inputs to the tx to the new view.
for _, txIn := range tx.MsgTx().TxIn {
entry := s.utxos.LookupEntry(txIn.PreviousOutPoint)
viewpoint.Entries()[txIn.PreviousOutPoint] = entry.Clone()
}
return viewpoint, nil
}
// BestHeight returns the current height associated with the fake chain
// instance.
func (s *fakeChain) BestHeight() int32 {
@ -98,7 +67,7 @@ func (s *fakeChain) SetMedianTimePast(mtp time.Time) {
// CalcSequenceLock returns the current sequence lock for the passed
// transaction associated with the fake chain instance.
func (s *fakeChain) CalcSequenceLock(tx *util.Tx,
view *blockdag.UTXOView) (*blockdag.SequenceLock, error) {
utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
return &blockdag.SequenceLock{
Seconds: -1,
@ -276,7 +245,7 @@ func (p *poolHarness) CreateTxChain(firstOutput spendableOutput, numTxns uint32)
// for testing. Also, the fake chain is populated with the returned spendable
// outputs so the caller can easily create new valid transactions which build
// off of it.
func newPoolHarness(chainParams *dagconfig.Params) (*poolHarness, []spendableOutput, error) {
func newPoolHarness(dagParams *dagconfig.Params, dbName string) (*poolHarness, []spendableOutput, error) {
// Use a hard coded key pair for deterministic results.
keyBytes, err := hex.DecodeString("700868df1838811ffbdf918fb482c1f7e" +
"ad62db4b97bd7012c23e726485e577d")
@ -288,7 +257,7 @@ func newPoolHarness(chainParams *dagconfig.Params) (*poolHarness, []spendableOut
// Generate associated pay-to-script-hash address and resulting payment
// script.
pubKeyBytes := signPub.SerializeCompressed()
payPubKeyAddr, err := util.NewAddressPubKey(pubKeyBytes, chainParams)
payPubKeyAddr, err := util.NewAddressPubKey(pubKeyBytes, dagParams)
if err != nil {
return nil, nil, err
}
@ -298,16 +267,25 @@ func newPoolHarness(chainParams *dagconfig.Params) (*poolHarness, []spendableOut
return nil, nil, err
}
// Create a new database and chain instance to run tests against.
dag, teardownFunc, err := blockdag.DAGSetup(dbName,
&dagconfig.MainNetParams)
if err != nil {
return nil, nil, fmt.Errorf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
// Create a new fake chain and harness bound to it.
chain := &fakeChain{utxos: blockdag.NewUTXOView()}
chain := &fakeChain{}
harness := poolHarness{
signKey: signKey,
payAddr: payAddr,
payScript: pkScript,
chainParams: chainParams,
chainParams: dagParams,
chain: chain,
txPool: New(&Config{
DAG: dag,
Policy: Policy{
DisableRelayPriority: true,
FreeTxRelayLimit: 15.0,
@ -317,8 +295,7 @@ func newPoolHarness(chainParams *dagconfig.Params) (*poolHarness, []spendableOut
MinRelayTxFee: 1000, // 1 Satoshi per byte
MaxTxVersion: 1,
},
ChainParams: chainParams,
FetchUtxoView: chain.FetchUtxoView,
DAGParams: dagParams,
BestHeight: chain.BestHeight,
MedianTimePast: chain.MedianTimePast,
CalcSequenceLock: chain.CalcSequenceLock,
@ -339,11 +316,11 @@ func newPoolHarness(chainParams *dagconfig.Params) (*poolHarness, []spendableOut
if err != nil {
return nil, nil, err
}
harness.chain.utxos.AddTxOuts(coinbase, curHeight+1)
harness.txPool.mpUTXOSet.AddTx(coinbase.MsgTx(), curHeight+1)
for i := uint32(0); i < numOutputs; i++ {
outputs = append(outputs, txOutToSpendableOut(coinbase, i))
}
harness.chain.SetHeight(int32(chainParams.CoinbaseMaturity) + curHeight)
harness.chain.SetHeight(int32(dagParams.CoinbaseMaturity) + curHeight)
harness.chain.SetMedianTimePast(time.Now())
return &harness, outputs, nil
@ -394,7 +371,7 @@ func testPoolMembership(tc *testContext, tx *util.Tx, inOrphanPool, inTxPool boo
func TestSimpleOrphanChain(t *testing.T) {
t.Parallel()
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams)
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, "TestSimpleOrphanChain")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -457,7 +434,7 @@ func TestSimpleOrphanChain(t *testing.T) {
func TestOrphanReject(t *testing.T) {
t.Parallel()
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams)
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, "TestOrphanReject")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -512,7 +489,7 @@ func TestOrphanReject(t *testing.T) {
func TestOrphanEviction(t *testing.T) {
t.Parallel()
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams)
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, "TestOrphanEviction")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -577,7 +554,7 @@ func TestBasicOrphanRemoval(t *testing.T) {
t.Parallel()
const maxOrphans = 4
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams)
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, "TestBasicOrphanRemoval")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -652,7 +629,7 @@ func TestOrphanChainRemoval(t *testing.T) {
t.Parallel()
const maxOrphans = 10
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams)
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, "TestOrphanChainRemoval")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -715,7 +692,7 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
t.Parallel()
const maxOrphans = 4
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams)
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, "TestMultiInputOrphanDoubleSpend")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -803,7 +780,7 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
func TestCheckSpend(t *testing.T) {
t.Parallel()
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams)
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, "TestCheckSpend")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}

View File

@ -85,7 +85,7 @@ func calcMinRequiredTxRelayFee(serializedSize int64, minRelayTxFee util.Amount)
// context of this function is one whose referenced public key script is of a
// standard form and, for pay-to-script-hash, does not have more than
// maxStandardP2SHSigOps signature operations.
func checkInputsStandard(tx *util.Tx, utxoView *blockdag.UTXOView) error {
func checkInputsStandard(tx *util.Tx, utxoSet blockdag.UTXOSet) error {
// NOTE: The reference implementation also does a coinbase check here,
// but coinbases have already been rejected prior to calling this
// function so no need to recheck.
@ -94,7 +94,7 @@ func checkInputsStandard(tx *util.Tx, utxoView *blockdag.UTXOView) error {
// It is safe to elide existence and index checks here since
// they have already been checked prior to calling this
// function.
entry := utxoView.LookupEntry(txIn.PreviousOutPoint)
entry, _ := utxoSet.Get(txIn.PreviousOutPoint)
originPkScript := entry.PkScript()
switch txscript.GetScriptClass(originPkScript) {
case txscript.ScriptHashTy:

View File

@ -13,8 +13,8 @@ import (
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
const (
@ -212,21 +212,6 @@ type BlockTemplate struct {
ValidPayAddress bool
}
// mergeUtxoView adds all of the entries in viewB to viewA. The result is that
// viewA will contain all of its original entries plus all of the entries
// in viewB. It will replace any entries in viewB which also exist in viewA
// if the entry in viewA is spent.
func mergeUtxoView(viewA *blockdag.UTXOView, viewB *blockdag.UTXOView) {
viewAEntries := viewA.Entries()
for outpoint, entryB := range viewB.Entries() {
if entryA, exists := viewAEntries[outpoint]; !exists ||
entryA == nil || entryA.IsSpent() {
viewAEntries[outpoint] = entryB
}
}
}
// standardCoinbaseScript returns a standard script suitable for use as the
// signature script of the coinbase transaction of a new block. In particular,
// it starts with the block height that is required by version 2 blocks and adds
@ -279,21 +264,6 @@ func createCoinbaseTx(params *dagconfig.Params, coinbaseScript []byte, nextBlock
return util.NewTx(tx), nil
}
// spendTransaction updates the passed view by marking the inputs to the passed
// transaction as spent. It also adds all outputs in the passed transaction
// which are not provably unspendable as available unspent transaction outputs.
func spendTransaction(utxoView *blockdag.UTXOView, tx *util.Tx, height int32) error {
for _, txIn := range tx.MsgTx().TxIn {
entry := utxoView.LookupEntry(txIn.PreviousOutPoint)
if entry != nil {
entry.Spend()
}
}
utxoView.AddTxOuts(tx, height)
return nil
}
// logSkippedDeps logs any dependencies which are also skipped as a result of
// skipping a transaction while generating a block template at the trace level.
func logSkippedDeps(tx *util.Tx, deps map[daghash.Hash]*txPrioItem) {
@ -471,7 +441,7 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
// avoided.
blockTxns := make([]*util.Tx, 0, len(sourceTxns))
blockTxns = append(blockTxns, coinbaseTx)
blockUtxos := blockdag.NewUTXOView()
blockUtxos := blockdag.NewDiffUTXOSet(g.dag.VirtualBlock().UTXOSet, blockdag.NewUTXODiff())
// dependers is used to track transactions which depend on another
// transaction in the source pool. This, in conjunction with the
@ -510,26 +480,14 @@ mempoolLoop:
continue
}
// Fetch all of the utxos referenced by the this transaction.
// NOTE: This intentionally does not fetch inputs from the
// mempool since a transaction which depends on other
// transactions in the mempool must come after those
// dependencies in the final generated block.
utxos, err := g.dag.FetchUTXOView(tx)
if err != nil {
log.Warnf("Unable to fetch utxo view for tx %s: %v",
tx.Hash(), err)
continue
}
// Setup dependencies for any transactions which reference
// other transactions in the mempool so they can be properly
// ordered below.
prioItem := &txPrioItem{tx: tx}
for _, txIn := range tx.MsgTx().TxIn {
originHash := &txIn.PreviousOutPoint.Hash
entry := utxos.LookupEntry(txIn.PreviousOutPoint)
if entry == nil || entry.IsSpent() {
entry, ok := blockUtxos.Get(txIn.PreviousOutPoint)
if !ok || entry.IsSpent() {
if !g.txSource.HaveTransaction(originHash) {
log.Tracef("Skipping tx %s because it "+
"references unspent output %s "+
@ -562,7 +520,7 @@ mempoolLoop:
// Calculate the final transaction priority using the input
// value age sum as well as the adjusted transaction size. The
// formula is: sum(inputValue * inputAge) / adjustedTxSize
prioItem.priority = CalcPriority(tx.MsgTx(), utxos,
prioItem.priority = CalcPriority(tx.MsgTx(), blockUtxos,
nextBlockHeight)
// Calculate the fee in Satoshi/kB.
@ -574,11 +532,6 @@ mempoolLoop:
if prioItem.dependsOn == nil {
heap.Push(priorityQueue, prioItem)
}
// Merge the referenced outputs from the input transactions to
// this transaction into the block utxo view. This allows the
// code below to avoid a second lookup.
mergeUtxoView(blockUtxos, utxos)
}
log.Tracef("Priority queue len %d, dependers len %d",
@ -707,7 +660,7 @@ mempoolLoop:
// an entry for it to ensure any transactions which reference
// this one have it available as an input and can ensure they
// aren't double spending.
spendTransaction(blockUtxos, tx, nextBlockHeight)
blockUtxos.AddTx(tx.MsgTx(), nextBlockHeight)
// Add the transaction to the block, increment counters, and
// save the fees and signature operation counts to the block

View File

@ -6,8 +6,8 @@ package mining
import (
"github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
const (
@ -54,13 +54,13 @@ func minInt(a, b int) int {
// age is the sum of this value for each txin. Any inputs to the transaction
// which are currently in the mempool and hence not mined into a block yet,
// contribute no additional input age to the transaction.
func calcInputValueAge(tx *wire.MsgTx, utxoView *blockdag.UTXOView, nextBlockHeight int32) float64 {
func calcInputValueAge(tx *wire.MsgTx, utxoSet blockdag.UTXOSet, nextBlockHeight int32) float64 {
var totalInputAge float64
for _, txIn := range tx.TxIn {
// Don't attempt to accumulate the total input age if the
// referenced transaction output doesn't exist.
entry := utxoView.LookupEntry(txIn.PreviousOutPoint)
if entry != nil && !entry.IsSpent() {
entry, ok := utxoSet.Get(txIn.PreviousOutPoint)
if ok && !entry.IsSpent() {
// Inputs with dependencies currently in the mempool
// have their block height set to a special constant.
// Their input age should computed as zero since their
@ -86,7 +86,7 @@ func calcInputValueAge(tx *wire.MsgTx, utxoView *blockdag.UTXOView, nextBlockHei
// of each of its input values multiplied by their age (# of confirmations).
// Thus, the final formula for the priority is:
// sum(inputValue * inputAge) / adjustedTxSize
func CalcPriority(tx *wire.MsgTx, utxoView *blockdag.UTXOView, nextBlockHeight int32) float64 {
func CalcPriority(tx *wire.MsgTx, utxoSet blockdag.UTXOSet, nextBlockHeight int32) float64 {
// In order to encourage spending multiple old unspent transaction
// outputs thereby reducing the total set, don't count the constant
// overhead for each input as well as enough bytes of the signature
@ -118,6 +118,6 @@ func CalcPriority(tx *wire.MsgTx, utxoView *blockdag.UTXOView, nextBlockHeight i
return 0.0
}
inputValueAge := calcInputValueAge(tx, utxoView, nextBlockHeight)
inputValueAge := calcInputValueAge(tx, utxoSet, nextBlockHeight)
return inputValueAge / float64(serializedTxSize-overhead)
}

View File

@ -14,8 +14,8 @@ import (
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
// newHashFromStr converts the passed big-endian hex string into a
@ -42,20 +42,20 @@ func hexToBytes(s string) []byte {
return b
}
// newUtxoViewpoint returns a new utxo view populated with outputs of the
// newUTXOSet returns a new utxo view populated with outputs of the
// provided source transactions as if there were available at the respective
// block height specified in the heights slice. The length of the source txns
// and source tx heights must match or it will panic.
func newUtxoViewpoint(sourceTxns []*wire.MsgTx, sourceTxHeights []int32) *blockdag.UTXOView {
func newUTXOSet(sourceTxns []*wire.MsgTx, sourceTxHeights []int32) blockdag.UTXOSet {
if len(sourceTxns) != len(sourceTxHeights) {
panic("each transaction must have its block height specified")
}
view := blockdag.NewUTXOView()
utxoSet := blockdag.NewFullUTXOSet()
for i, tx := range sourceTxns {
view.AddTxOuts(util.NewTx(tx), sourceTxHeights[i])
utxoSet.AddTx(tx, sourceTxHeights[i])
}
return view
return utxoSet
}
func createTxIn(originTx *wire.MsgTx, outputIndex uint32) *wire.TxIn {
@ -128,16 +128,16 @@ func TestCalcPriority(t *testing.T) {
}
tests := []struct {
name string // test description
tx *wire.MsgTx // tx to calc priority for
utxoView *blockdag.UTXOView // inputs to tx
nextHeight int32 // height for priority calc
want float64 // expected priority
name string // test description
tx *wire.MsgTx // tx to calc priority for
utxoSet blockdag.UTXOSet // inputs to tx
nextHeight int32 // height for priority calc
want float64 // expected priority
}{
{
name: "one height 7 input, prio tx height 169",
tx: commonRedeemTx1,
utxoView: newUtxoViewpoint([]*wire.MsgTx{commonSourceTx1},
utxoSet: newUTXOSet([]*wire.MsgTx{commonSourceTx1},
[]int32{7}),
nextHeight: 169,
want: 1.5576923076923077e+10,
@ -145,7 +145,7 @@ func TestCalcPriority(t *testing.T) {
{
name: "one height 100 input, prio tx height 169",
tx: commonRedeemTx1,
utxoView: newUtxoViewpoint([]*wire.MsgTx{commonSourceTx1},
utxoSet: newUTXOSet([]*wire.MsgTx{commonSourceTx1},
[]int32{100}),
nextHeight: 169,
want: 6.634615384615385e+09,
@ -153,7 +153,7 @@ func TestCalcPriority(t *testing.T) {
{
name: "one height 7 input, prio tx height 100000",
tx: commonRedeemTx1,
utxoView: newUtxoViewpoint([]*wire.MsgTx{commonSourceTx1},
utxoSet: newUTXOSet([]*wire.MsgTx{commonSourceTx1},
[]int32{7}),
nextHeight: 100000,
want: 9.61471153846154e+12,
@ -161,7 +161,7 @@ func TestCalcPriority(t *testing.T) {
{
name: "one height 100 input, prio tx height 100000",
tx: commonRedeemTx1,
utxoView: newUtxoViewpoint([]*wire.MsgTx{commonSourceTx1},
utxoSet: newUTXOSet([]*wire.MsgTx{commonSourceTx1},
[]int32{100}),
nextHeight: 100000,
want: 9.60576923076923e+12,
@ -169,7 +169,7 @@ func TestCalcPriority(t *testing.T) {
}
for i, test := range tests {
got := CalcPriority(test.tx, test.utxoView, test.nextHeight)
got := CalcPriority(test.tx, test.utxoSet, test.nextHeight)
if got != test.want {
t.Errorf("CalcPriority #%d (%q): unexpected priority "+
"got %v want %v", i, test.name, got, test.want)

View File

@ -6,6 +6,7 @@ package netsync
import (
"container/list"
"fmt"
"net"
"sync"
"sync/atomic"
@ -1160,10 +1161,10 @@ out:
log.Trace("Block handler done")
}
// handleBlockchainNotification handles notifications from blockchain. It does
// handleBlockDAGNotification handles notifications from blockDAG. It does
// things such as request orphan block parents and relay accepted blocks to
// connected peers.
func (sm *SyncManager) handleBlockchainNotification(notification *blockdag.Notification) {
func (sm *SyncManager) handleBlockDAGNotification(notification *blockdag.Notification) {
switch notification.Type {
// A block has been accepted into the block chain. Relay it to other
// peers.
@ -1192,20 +1193,17 @@ func (sm *SyncManager) handleBlockchainNotification(notification *blockdag.Notif
break
}
// Remove all of the transactions (except the coinbase) in the
// connected block from the transaction pool. Secondly, remove any
// transactions which are now double spends as a result of these
// new transactions. Finally, remove any transaction that is
// no longer an orphan. Transactions which depend on a confirmed
// transaction are NOT removed recursively because they are still
// valid.
for _, tx := range block.Transactions()[1:] {
sm.txMemPool.RemoveTransaction(tx, false)
sm.txMemPool.RemoveDoubleSpends(tx)
sm.txMemPool.RemoveOrphan(tx)
sm.peerNotifier.TransactionConfirmed(tx)
acceptedTxs := sm.txMemPool.ProcessOrphans(tx)
sm.peerNotifier.AnnounceNewTransactions(acceptedTxs)
ch := make(chan mempool.NewBlockMsg)
go func() {
err := sm.txMemPool.HandleNewBlock(block, ch)
close(ch)
if err != nil {
panic(fmt.Sprintf("HandleNewBlock failed to handle block %v", block.Hash()))
}
}()
for msg := range ch {
sm.peerNotifier.TransactionConfirmed(msg.Tx)
sm.peerNotifier.AnnounceNewTransactions(msg.AcceptedTxs)
}
// Register block with the fee estimator, if it exists.
@ -1239,7 +1237,7 @@ func (sm *SyncManager) handleBlockchainNotification(notification *blockdag.Notif
// Remove the transaction and all transactions
// that depend on it if it wasn't accepted into
// the transaction pool.
sm.txMemPool.RemoveTransaction(tx, true)
sm.txMemPool.RemoveTransaction(tx, true, true)
}
}
@ -1410,7 +1408,7 @@ func New(config *Config) (*SyncManager, error) {
log.Info("Checkpoints are disabled")
}
sm.dag.Subscribe(sm.handleBlockchainNotification)
sm.dag.Subscribe(sm.handleBlockDAGNotification)
return &sm, nil
}

View File

@ -35,10 +35,10 @@ import (
"github.com/daglabs/btcd/peer"
"github.com/daglabs/btcd/server/serverutils"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/version"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/util/bloom"
"github.com/daglabs/btcd/version"
"github.com/daglabs/btcd/wire"
)
const (
@ -2436,17 +2436,17 @@ func NewServer(listenAddrs []string, db database.DB, dagParams *dagconfig.Params
MinRelayTxFee: config.MainConfig().MinRelayTxFee,
MaxTxVersion: 1,
},
ChainParams: dagParams,
FetchUtxoView: s.DAG.FetchUTXOView,
DAGParams: dagParams,
BestHeight: func() int32 { return s.DAG.VirtualBlock().SelectedTipHeight() },
MedianTimePast: func() time.Time { return s.DAG.VirtualBlock().SelectedTip().CalcPastMedianTime() },
CalcSequenceLock: func(tx *util.Tx, view *blockdag.UTXOView) (*blockdag.SequenceLock, error) {
return s.DAG.CalcSequenceLock(tx, view, true)
CalcSequenceLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
return s.DAG.CalcSequenceLock(tx, utxoSet, true)
},
IsDeploymentActive: s.DAG.IsDeploymentActive,
SigCache: s.SigCache,
AddrIndex: s.AddrIndex,
FeeEstimator: s.FeeEstimator,
DAG: s.DAG,
}
s.TxMemPool = mempool.New(&txC)

View File

@ -3198,7 +3198,10 @@ func handleSendRawTransaction(s *Server, cmd interface{}, closeChan <-chan struc
// Also, since an error is being returned to the caller, ensure the
// transaction is removed from the memory pool.
if len(acceptedTxs) == 0 || !acceptedTxs[0].Tx.Hash().IsEqual(tx.Hash()) {
s.cfg.TxMemPool.RemoveTransaction(tx, true)
err := s.cfg.TxMemPool.RemoveTransaction(tx, true, true)
if err != nil {
return nil, err
}
errStr := fmt.Sprintf("transaction %v is not in accepted list",
tx.Hash())