kaspad/mining/mining_test.go
stasatdaglabs ffd886498a [NOD-208] Make block reward maturity use the same mechanism as confirmations (#327)
* [NOD-208] Added blockBlueScore to UTXOEntry.

* [NOD-208] Added blueBlockScore to NewUTXOEntry.

* [NOD-208] Fixed compilation errors in policy, utxoset, and dag tests.

* [NOD-208] Changed validateBlockRewardMaturity and CheckTransactionInputsAndCalulateFee to use blueScore.

* [NOD-208] Changed CalcBlockSubsidy to use blueScore.

* [NOD-208] Changed SequenceLockActive to use blueScore.

* [NOD-208] Removed ExtractCoinbaseHeight.

* [NOD-208] Removed reference to block height in ensureNoDuplicateTx.

* [NOD-208] Changed IsFinalizedTransaction to use blueScore.

* [NOD-208] Fixed merge errors.

* [NOD-208] Made UTXOEntry serialization use blueScore.

* [NOD-208] Changed CalcPriority and calcInputValueAge to use blueScore.

* [NOD-208] Changed calcSequenceLock to use blueScore.

* [NOD-208] Removed blockChainHeight from UTXOEntry.

* [NOD-208] Fixed compilation errors in feeEstimator. Fixed a bug in the test pool hardness.

* [NOD-208] Fixed oldestChainBlockWithBlueScoreGreaterThan not handling an extreme case.

* [NOD-208] Fixed TestDiffFromTx.

* [NOD-208] Got rid of priority and support of free transactions.

* [NOD-208] Fixed TestProcessTransaction.

* [NOD-208] Fixed TestTxFeePrioHeap.

* [NOD-208] Fixed TestAddrIndex and TestFeeEstimatorCfg.

* [NOD-208] Removed unused rateLimit parameter from ProcessTransaction.

* [NOD-208] Fixed tests that rely on CreateTxChain.

* [NOD-208] Fixed tests that rely on CreateSignedTxForSubnetwork.

* [NOD-208] Fixed TestFetchTransaction.

* [NOD-208] Fixed TestHandleNewBlock. Fixed HandleNewBlock erroneously processing fee transactions.

* [NOD-208] Fixed TestTxIndexConnectBlock.

* [NOD-208] Removed the use of Height() from the fee estimator.

* [NOD-208] Removed unused methods from rpcwebsocket.go.

* [NOD-208] Removed Height from util.Block.

* [NOD-208] Removed ErrForkTooOld. It doesn't make sense in a DAG.

* [NOD-208] Made blockHeap use blueScore instead of height.

* [NOD-208] Removed fee estimator.

* [NOD-208] Removed DAG.Height.

* [NOD-208] Made TestAncestorErrors test chainHeight instead of height.

* [NOD-208] Fixed a couple of comments that were still speaking about block height.

* [NOD-208] Replaced all uses of HighestTipHash with SelectedTipHash.

* [NOD-208] Remove blockNode highest and some remaining erroneous uses of height.

* [NOD-208] Fixed a couple of comments. Fixed outPoint -> outpoint merge error.

* [NOD-208] Fixed a couple more comments.

* [NOD-208] Used calcMinRequiredTxRelayFee instead of DefaultMinRelayTxFee for mempool tests.

* [NOD-208] Renamed mempool Config BestHeight to DAGChainHeight.

* [NOD-208] Fixed a bug in oldestChainBlockWithBlueScoreGreaterThan. Made calcSequenceLock use the node's selected parent chain rather than the virtual block's.

* [NOD-208] Removed chainHeight from blockNode String().
Renamed checkpointsByHeight to checkpointsByChainHeight and prevCheckpointHeight to prevCheckpointChainHeight.
Removed reference to chainHeight in blockIndexKey.
Fixed comments in dagio.go.

* [NOD-208] Removed indexers/blocklogger.go, as no one was using it.

* [NOD-208] Made blocklogger.go log blueScore instead of height.

* [NOD-208] Fixed typo.

* [NOD-208] Fixed comments, did minor renaming.

* [NOD-208] Made a "common sense" wrapper around sort.Search.

* [NOD-208] Fixed comment in SearchSlice.
2019-06-16 14:12:02 +03:00

346 lines
9.7 KiB
Go

// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package mining
import (
"container/heap"
"errors"
"math/rand"
"testing"
"github.com/daglabs/btcd/util/subnetworkid"
"bou.ke/monkey"
"github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util/daghash"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
)
// TestTxFeePrioHeap ensures the priority queue for transaction fees and
// priorities works as expected.
func TestTxFeePrioHeap(t *testing.T) {
// Create some fake priority items that exercise the expected sort
// edge conditions.
testItems := []*txPrioItem{
{feePerKB: 5678},
{feePerKB: 5678}, // Duplicate fee
{feePerKB: 1234},
{feePerKB: 10000}, // High fee
{feePerKB: 0}, // Zero fee
}
// Add random data in addition to the edge conditions already manually
// specified.
randSeed := rand.Int63()
defer func() {
if t.Failed() {
t.Logf("Random numbers using seed: %v", randSeed)
}
}()
prng := rand.New(rand.NewSource(randSeed))
for i := 0; i < 1000; i++ {
testItems = append(testItems, &txPrioItem{
feePerKB: uint64(prng.Float64() * util.SatoshiPerBitcoin),
})
}
// Test sorting by fee per KB
var highest *txPrioItem
priorityQueue := newTxPriorityQueue(len(testItems))
for _, prioItem := range testItems {
if highest == nil || prioItem.feePerKB >= highest.feePerKB {
highest = prioItem
}
heap.Push(priorityQueue, prioItem)
}
for i := 0; i < len(testItems); i++ {
prioItem := heap.Pop(priorityQueue).(*txPrioItem)
if prioItem.feePerKB > highest.feePerKB {
t.Fatalf("fee sort: item (fee per KB: %v) "+
"higher than than prev (fee per KB: %v)",
prioItem.feePerKB, highest.feePerKB)
}
highest = prioItem
}
}
func TestNewBlockTemplate(t *testing.T) {
params := dagconfig.SimNetParams
params.BlockRewardMaturity = 0
dag, teardownFunc, err := blockdag.DAGSetup("TestNewBlockTemplate", blockdag.Config{
DAGParams: &params,
})
if err != nil {
t.Fatalf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
pkScript, err := txscript.NewScriptBuilder().AddOp(txscript.OpTrue).Script()
if err != nil {
t.Fatalf("Failed to create pkScript: %v", err)
}
policy := Policy{
BlockMaxSize: 50000,
}
// First we create a block to have coinbase funds for the rest of the test.
txSource := &fakeTxSource{
txDescs: []*TxDesc{},
}
var createCoinbaseTxPatch *monkey.PatchGuard
createCoinbaseTxPatch = monkey.Patch(CreateCoinbaseTx, func(params *dagconfig.Params, coinbaseScript []byte, nextBlueScore uint64, addr util.Address) (*util.Tx, error) {
createCoinbaseTxPatch.Unpatch()
defer createCoinbaseTxPatch.Restore()
tx, err := CreateCoinbaseTx(params, coinbaseScript, nextBlueScore, addr)
if err != nil {
return nil, err
}
msgTx := tx.MsgTx()
//Here we split the coinbase to 10 outputs, so we'll be able to use it in many transactions
out := msgTx.TxOut[0]
out.Value /= 10
for i := 0; i < 9; i++ {
msgTx.AddTxOut(&*out)
}
return tx, nil
})
defer createCoinbaseTxPatch.Unpatch()
blockTemplateGenerator := NewBlkTmplGenerator(&policy,
&params, txSource, dag, blockdag.NewMedianTime(), txscript.NewSigCache(100000))
template1, err := blockTemplateGenerator.NewBlockTemplate(nil)
createCoinbaseTxPatch.Unpatch()
if err != nil {
t.Fatalf("NewBlockTemplate: %v", err)
}
isOrphan, err := dag.ProcessBlock(util.NewBlock(template1.Block), blockdag.BFNoPoWCheck)
if err != nil {
t.Fatalf("ProcessBlock: %v", err)
}
if isOrphan {
t.Fatalf("ProcessBlock: template1 got unexpectedly orphan")
}
cbScript, err := StandardCoinbaseScript(dag.VirtualBlueScore(), 0)
if err != nil {
t.Fatalf("standardCoinbaseScript: %v", err)
}
// We want to check that the miner filters coinbase transaction
cbTx, err := CreateCoinbaseTx(&params, cbScript, dag.VirtualBlueScore(), nil)
if err != nil {
t.Fatalf("createCoinbaseTx: %v", err)
}
template1CbTx := template1.Block.Transactions[0]
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
if err != nil {
t.Fatalf("Error creating signature script: %s", err)
}
// tx is a regular transaction, and should not be filtered by the miner
txIn := &wire.TxIn{
PreviousOutpoint: wire.Outpoint{
TxID: *template1CbTx.TxID(),
Index: 0,
},
Sequence: wire.MaxTxInSequenceNum,
SignatureScript: signatureScript,
}
txOut := &wire.TxOut{
PkScript: pkScript,
Value: 1,
}
tx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
// We want to check that the miner filters non finalized transactions
txIn = &wire.TxIn{
PreviousOutpoint: wire.Outpoint{
TxID: *template1CbTx.TxID(),
Index: 1,
},
Sequence: 0,
SignatureScript: signatureScript,
}
txOut = &wire.TxOut{
PkScript: pkScript,
Value: 1,
}
nonFinalizedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
nonFinalizedTx.LockTime = uint64(dag.ChainHeight() + 2)
existingSubnetwork := &subnetworkid.SubnetworkID{0xff}
nonExistingSubnetwork := &subnetworkid.SubnetworkID{0xfe}
// We want to check that the miner filters transactions with non-existing subnetwork id. (It should first push it to the priority queue, and then ignore it)
txIn = &wire.TxIn{
PreviousOutpoint: wire.Outpoint{
TxID: *template1CbTx.TxID(),
Index: 2,
},
Sequence: 0,
SignatureScript: signatureScript,
}
txOut = &wire.TxOut{
PkScript: pkScript,
Value: 1,
}
nonExistingSubnetworkTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut},
nonExistingSubnetwork, 1, []byte{})
// We want to check that the miner doesn't filters transactions that do not exceed the subnetwork gas limit
txIn = &wire.TxIn{
PreviousOutpoint: wire.Outpoint{
TxID: *template1CbTx.TxID(),
Index: 3,
},
Sequence: 0,
SignatureScript: signatureScript,
}
txOut = &wire.TxOut{
PkScript: pkScript,
Value: 1,
}
subnetworkTx1 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}, existingSubnetwork, 1, []byte{})
// We want to check that the miner filters transactions that exceed the subnetwork gas limit. (It should first push it to the priority queue, and then ignore it)
txIn = &wire.TxIn{
PreviousOutpoint: wire.Outpoint{
TxID: *template1CbTx.TxID(),
Index: 4,
},
Sequence: 0,
SignatureScript: signatureScript,
}
txOut = &wire.TxOut{
PkScript: pkScript,
Value: 1,
}
subnetworkTx2 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}, existingSubnetwork,
100, // Subnetwork gas limit is 90
[]byte{})
txSource.txDescs = []*TxDesc{
{
Tx: cbTx,
},
{
Tx: util.NewTx(tx),
},
{
Tx: util.NewTx(nonFinalizedTx),
},
{
Tx: util.NewTx(subnetworkTx1),
},
{
Tx: util.NewTx(subnetworkTx2),
},
{
Tx: util.NewTx(nonExistingSubnetworkTx),
},
}
standardCoinbaseScriptErrString := "standardCoinbaseScript err"
var standardCoinbaseScriptPatch *monkey.PatchGuard
standardCoinbaseScriptPatch = monkey.Patch(StandardCoinbaseScript, func(nextBlockHeight uint64, extraNonce uint64) ([]byte, error) {
return nil, errors.New(standardCoinbaseScriptErrString)
})
defer standardCoinbaseScriptPatch.Unpatch()
// We want to check that NewBlockTemplate will fail if standardCoinbaseScript returns an error
_, err = blockTemplateGenerator.NewBlockTemplate(nil)
standardCoinbaseScriptPatch.Unpatch()
if err == nil || err.Error() != standardCoinbaseScriptErrString {
t.Errorf("expected an error \"%v\" but got \"%v\"", standardCoinbaseScriptErrString, err)
}
if err == nil {
t.Errorf("expected an error but got <nil>")
}
// Here we check that the miner's priorty queue has the expected transactions after filtering.
popReturnedUnexpectedValue := false
expectedPops := map[daghash.TxID]bool{
*tx.TxID(): false,
*subnetworkTx1.TxID(): false,
*subnetworkTx2.TxID(): false,
*nonExistingSubnetworkTx.TxID(): false,
}
var popPatch *monkey.PatchGuard
popPatch = monkey.Patch((*txPriorityQueue).Pop, func(pq *txPriorityQueue) interface{} {
popPatch.Unpatch()
defer popPatch.Restore()
item, ok := pq.Pop().(*txPrioItem)
if _, expected := expectedPops[*item.tx.ID()]; expected && ok {
expectedPops[*item.tx.ID()] = true
} else {
popReturnedUnexpectedValue = true
}
return item
})
defer popPatch.Unpatch()
// Here we define nonExistingSubnetwork to be non-exist, and existingSubnetwork to have a gas limit of 90
gasLimitPatch := monkey.Patch((*blockdag.SubnetworkStore).GasLimit, func(_ *blockdag.SubnetworkStore, subnetworkID *subnetworkid.SubnetworkID) (uint64, error) {
if subnetworkID.IsEqual(nonExistingSubnetwork) {
return 0, errors.New("not found")
}
return 90, nil
})
defer gasLimitPatch.Unpatch()
template2, err := blockTemplateGenerator.NewBlockTemplate(nil)
popPatch.Unpatch()
gasLimitPatch.Unpatch()
if err != nil {
t.Errorf("NewBlockTemplate: unexpected error: %v", err)
}
if popReturnedUnexpectedValue {
t.Errorf("(*txPriorityQueue).Pop returned unexpected value")
}
for id, popped := range expectedPops {
if !popped {
t.Errorf("tx %v was expected to pop, but wasn't", id)
}
}
expectedTxs := map[daghash.TxID]bool{
*tx.TxID(): false,
*subnetworkTx1.TxID(): false,
}
for _, tx := range template2.Block.Transactions[2:] {
id := *tx.TxID()
if _, ok := expectedTxs[id]; !ok {
t.Errorf("Unexpected tx %v in template2's candidate block", id)
}
expectedTxs[id] = true
}
for id, exists := range expectedTxs {
if !exists {
t.Errorf("tx %v was expected to be in template2's candidate block, but wasn't", id)
}
}
}