mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
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:
parent
85265e1d9b
commit
5fb220c38a
@ -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)
|
||||
|
@ -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: ¶msCopy,
|
||||
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
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
105
blockdag/test_utils.go
Normal 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: ¶msCopy,
|
||||
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
|
||||
}
|
54
blockdag/test_utils_test.go
Normal file
54
blockdag/test_utils_test.go
Normal 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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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>")
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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())
|
||||
|
Loading…
x
Reference in New Issue
Block a user