mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-07 14:46:44 +00:00
Dev 341 increase mining test coverage (#178)
* [DEV-341] Ensure 100% coverage for mining package * [DEV-341] Add TestNewBlockTemplate * [DEV-341] Add TestNewBlockTemplate * [DEV-341] Test GasLimit filtering in NewBlockTemplate * [DEV-341] Add comment to TestNewBlockTemplate
This commit is contained in:
parent
1e09d470f7
commit
0bc7a11551
@ -148,7 +148,7 @@ type BlockDAG struct {
|
|||||||
|
|
||||||
lastFinalityPoint *blockNode
|
lastFinalityPoint *blockNode
|
||||||
|
|
||||||
SubnetworkStore *subnetworkStore
|
SubnetworkStore *SubnetworkStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// HaveBlock returns whether or not the DAG instance has the block represented
|
// HaveBlock returns whether or not the DAG instance has the block represented
|
||||||
|
@ -12,12 +12,13 @@ import (
|
|||||||
"github.com/daglabs/btcd/wire"
|
"github.com/daglabs/btcd/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
type subnetworkStore struct {
|
// SubnetworkStore stores the subnetworks data
|
||||||
|
type SubnetworkStore struct {
|
||||||
db database.DB
|
db database.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSubnetworkStore(db database.DB) *subnetworkStore {
|
func newSubnetworkStore(db database.DB) *SubnetworkStore {
|
||||||
return &subnetworkStore{
|
return &SubnetworkStore{
|
||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +89,7 @@ func txToSubnetworkID(tx *wire.MsgTx) (*subnetworkid.SubnetworkID, error) {
|
|||||||
|
|
||||||
// subnetwork returns a registered subnetwork. If the subnetwork does not exist
|
// subnetwork returns a registered subnetwork. If the subnetwork does not exist
|
||||||
// this method returns an error.
|
// this method returns an error.
|
||||||
func (s *subnetworkStore) subnetwork(subnetworkID *subnetworkid.SubnetworkID) (*subnetwork, error) {
|
func (s *SubnetworkStore) subnetwork(subnetworkID *subnetworkid.SubnetworkID) (*subnetwork, error) {
|
||||||
var sNet *subnetwork
|
var sNet *subnetwork
|
||||||
var err error
|
var err error
|
||||||
dbErr := s.db.View(func(dbTx database.Tx) error {
|
dbErr := s.db.View(func(dbTx database.Tx) error {
|
||||||
@ -107,7 +108,7 @@ func (s *subnetworkStore) subnetwork(subnetworkID *subnetworkid.SubnetworkID) (*
|
|||||||
|
|
||||||
// GasLimit returns the gas limit of a registered subnetwork. If the subnetwork does not
|
// GasLimit returns the gas limit of a registered subnetwork. If the subnetwork does not
|
||||||
// exist this method returns an error.
|
// exist this method returns an error.
|
||||||
func (s *subnetworkStore) GasLimit(subnetworkID *subnetworkid.SubnetworkID) (uint64, error) {
|
func (s *SubnetworkStore) GasLimit(subnetworkID *subnetworkid.SubnetworkID) (uint64, error) {
|
||||||
sNet, err := s.subnetwork(subnetworkID)
|
sNet, err := s.subnetwork(subnetworkID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -89,6 +89,10 @@ func DAGSetup(dbName string, config Config) (*BlockDAG, func(), error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.SubnetworkID == nil {
|
||||||
|
config.SubnetworkID = &wire.SubnetworkIDSupportsAll
|
||||||
|
}
|
||||||
|
|
||||||
config.TimeSource = NewMedianTime()
|
config.TimeSource = NewMedianTime()
|
||||||
config.SigCache = txscript.NewSigCache(1000)
|
config.SigCache = txscript.NewSigCache(1000)
|
||||||
|
|
||||||
|
@ -292,12 +292,12 @@ func medianAdjustedTime(dagMedianTime time.Time, timeSource blockdag.MedianTimeS
|
|||||||
// It also houses additional state required in order to ensure the templates
|
// It also houses additional state required in order to ensure the templates
|
||||||
// are built on top of the current best chain and adhere to the consensus rules.
|
// are built on top of the current best chain and adhere to the consensus rules.
|
||||||
type BlkTmplGenerator struct {
|
type BlkTmplGenerator struct {
|
||||||
policy *Policy
|
policy *Policy
|
||||||
chainParams *dagconfig.Params
|
dagParams *dagconfig.Params
|
||||||
txSource TxSource
|
txSource TxSource
|
||||||
dag *blockdag.BlockDAG
|
dag *blockdag.BlockDAG
|
||||||
timeSource blockdag.MedianTimeSource
|
timeSource blockdag.MedianTimeSource
|
||||||
sigCache *txscript.SigCache
|
sigCache *txscript.SigCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlkTmplGenerator returns a new block template generator for the given
|
// NewBlkTmplGenerator returns a new block template generator for the given
|
||||||
@ -312,12 +312,12 @@ func NewBlkTmplGenerator(policy *Policy, params *dagconfig.Params,
|
|||||||
sigCache *txscript.SigCache) *BlkTmplGenerator {
|
sigCache *txscript.SigCache) *BlkTmplGenerator {
|
||||||
|
|
||||||
return &BlkTmplGenerator{
|
return &BlkTmplGenerator{
|
||||||
policy: policy,
|
policy: policy,
|
||||||
chainParams: params,
|
dagParams: params,
|
||||||
txSource: txSource,
|
txSource: txSource,
|
||||||
dag: dag,
|
dag: dag,
|
||||||
timeSource: timeSource,
|
timeSource: timeSource,
|
||||||
sigCache: sigCache,
|
sigCache: sigCache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,7 +403,7 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
coinbaseTx, err := createCoinbaseTx(g.chainParams, coinbaseScript,
|
coinbaseTx, err := createCoinbaseTx(g.dagParams, coinbaseScript,
|
||||||
nextBlockHeight, payToAddress)
|
nextBlockHeight, payToAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -589,7 +589,7 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
|
|||||||
// Ensure the transaction inputs pass all of the necessary
|
// Ensure the transaction inputs pass all of the necessary
|
||||||
// preconditions before allowing it to be added to the block.
|
// preconditions before allowing it to be added to the block.
|
||||||
_, err = blockdag.CheckTransactionInputs(tx, nextBlockHeight,
|
_, err = blockdag.CheckTransactionInputs(tx, nextBlockHeight,
|
||||||
blockUtxos, g.chainParams)
|
blockUtxos, g.dagParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracef("Skipping tx %s due to error in "+
|
log.Tracef("Skipping tx %s due to error in "+
|
||||||
"CheckTransactionInputs: %v", tx.ID(), err)
|
"CheckTransactionInputs: %v", tx.ID(), err)
|
||||||
@ -708,7 +708,7 @@ func (g *BlkTmplGenerator) UpdateBlockTime(msgBlock *wire.MsgBlock) error {
|
|||||||
msgBlock.Header.Timestamp = newTime
|
msgBlock.Header.Timestamp = newTime
|
||||||
|
|
||||||
// Recalculate the difficulty if running on a network that requires it.
|
// Recalculate the difficulty if running on a network that requires it.
|
||||||
if g.chainParams.ReduceMinDifficulty {
|
if g.dagParams.ReduceMinDifficulty {
|
||||||
difficulty, err := g.dag.CalcNextRequiredDifficulty(newTime)
|
difficulty, err := g.dag.CalcNextRequiredDifficulty(newTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -6,8 +6,19 @@ package mining
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"container/heap"
|
"container/heap"
|
||||||
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/daglabs/btcd/util/subnetworkid"
|
||||||
|
|
||||||
|
"bou.ke/monkey"
|
||||||
|
"github.com/daglabs/btcd/blockdag"
|
||||||
|
"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/util"
|
||||||
)
|
)
|
||||||
@ -108,3 +119,293 @@ func TestTxFeePrioHeap(t *testing.T) {
|
|||||||
highest = prioItem
|
highest = prioItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fakeTxSource is a simple implementation of TxSource interface
|
||||||
|
type fakeTxSource struct {
|
||||||
|
txDescs []*TxDesc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txs *fakeTxSource) LastUpdated() time.Time {
|
||||||
|
return time.Unix(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txs *fakeTxSource) MiningDescs() []*TxDesc {
|
||||||
|
return txs.txDescs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txs *fakeTxSource) HaveTransaction(txID *daghash.TxID) bool {
|
||||||
|
for _, desc := range txs.txDescs {
|
||||||
|
if *desc.Tx.ID() == *txID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBlockTemplate(t *testing.T) {
|
||||||
|
params := dagconfig.SimNetParams
|
||||||
|
params.CoinbaseMaturity = 0
|
||||||
|
|
||||||
|
dag, teardownFunc, err := blockdag.DAGSetup("TestNewBlockTemplate", blockdag.Config{
|
||||||
|
DAGParams: ¶ms,
|
||||||
|
})
|
||||||
|
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,
|
||||||
|
BlockPrioritySize: 750000,
|
||||||
|
TxMinFreeFee: util.Amount(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, nextBlockHeight int32, addr util.Address) (*util.Tx, error) {
|
||||||
|
createCoinbaseTxPatch.Unpatch()
|
||||||
|
defer createCoinbaseTxPatch.Restore()
|
||||||
|
tx, err := createCoinbaseTx(params, coinbaseScript, nextBlockHeight, 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,
|
||||||
|
¶ms, 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.Height()+1, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("standardCoinbaseScript: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to check that the miner filters coinbase transaction
|
||||||
|
cbTx, err := createCoinbaseTx(¶ms, cbScript, dag.Height()+1, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("createCoinbaseTx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
template1CbTx := template1.Block.Transactions[0]
|
||||||
|
|
||||||
|
// tx is a regular transaction, and should not be filtered by the miner
|
||||||
|
tx := wire.NewMsgTx(wire.TxVersion)
|
||||||
|
tx.AddTxIn(&wire.TxIn{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
TxID: template1CbTx.TxID(),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
Sequence: wire.MaxTxInSequenceNum,
|
||||||
|
})
|
||||||
|
tx.AddTxOut(&wire.TxOut{
|
||||||
|
PkScript: pkScript,
|
||||||
|
Value: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// We want to check that the miner filters non finalized transactions
|
||||||
|
nonFinalizedTx := wire.NewMsgTx(wire.TxVersion)
|
||||||
|
nonFinalizedTx.LockTime = uint64(dag.Height() + 2)
|
||||||
|
nonFinalizedTx.AddTxIn(&wire.TxIn{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
TxID: template1CbTx.TxID(),
|
||||||
|
Index: 1,
|
||||||
|
},
|
||||||
|
Sequence: 0,
|
||||||
|
})
|
||||||
|
nonFinalizedTx.AddTxOut(&wire.TxOut{
|
||||||
|
PkScript: pkScript,
|
||||||
|
Value: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
nonExistingSubnetworkTx := wire.NewMsgTx(wire.TxVersion)
|
||||||
|
nonExistingSubnetworkTx.SubnetworkID = nonExistingSubnetwork
|
||||||
|
nonExistingSubnetworkTx.Gas = 1
|
||||||
|
nonExistingSubnetworkTx.AddTxIn(&wire.TxIn{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
TxID: template1CbTx.TxID(),
|
||||||
|
Index: 2,
|
||||||
|
},
|
||||||
|
Sequence: 0,
|
||||||
|
})
|
||||||
|
nonExistingSubnetworkTx.AddTxOut(&wire.TxOut{
|
||||||
|
PkScript: pkScript,
|
||||||
|
Value: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// We want to check that the miner doesn't filters transactions that do not exceed the subnetwork gas limit
|
||||||
|
subnetworkTx1 := wire.NewMsgTx(wire.TxVersion)
|
||||||
|
subnetworkTx1.SubnetworkID = existingSubnetwork
|
||||||
|
subnetworkTx1.Gas = 1
|
||||||
|
subnetworkTx1.AddTxIn(&wire.TxIn{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
TxID: template1CbTx.TxID(),
|
||||||
|
Index: 3,
|
||||||
|
},
|
||||||
|
Sequence: 0,
|
||||||
|
})
|
||||||
|
subnetworkTx1.AddTxOut(&wire.TxOut{
|
||||||
|
PkScript: pkScript,
|
||||||
|
Value: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
subnetworkTx2 := wire.NewMsgTx(wire.TxVersion)
|
||||||
|
subnetworkTx2.SubnetworkID = existingSubnetwork
|
||||||
|
subnetworkTx2.Gas = 100 // Subnetwork gas limit is 90
|
||||||
|
subnetworkTx2.AddTxIn(&wire.TxIn{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
TxID: template1CbTx.TxID(),
|
||||||
|
Index: 4,
|
||||||
|
},
|
||||||
|
Sequence: 0,
|
||||||
|
})
|
||||||
|
subnetworkTx2.AddTxOut(&wire.TxOut{
|
||||||
|
PkScript: pkScript,
|
||||||
|
Value: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
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 int32, 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 == 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[1:] {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user