This introduces automatic ticket revocations as defined in DCP0009,
including:
- Allowing tickets to be revoked by anyone instead of requiring a valid
signature from the voting address
- Requiring blocks to contain ticket revocation transactions for all
tickets that become missed or expired as of that block
An overview of the changes is as follows:
- blockchain/scriptval.go
- ValidateTransactionScripts, checkBlockScripts
- Skip script validation for version 2 revocations if the automatic
ticket revocations agenda is active
- blockchain/stake/staketx.go, blockchain/stake/staketx_test.go
- Introduce TxVersionAutoRevocations const for version 2 revocations
- CalculateRewards, CalculateRevocationRewards
- Add CalculateRevocationRewards to handle the required updates for
revocations while leaving CalculateRewards as-is for votes
- If the automatic ticket revocations agenda is active, and there is
a remainder after distributing the returns, then select a
uniformly pseudorandom output index to receive each remaining
atom
- Add additional tests to cover the new code paths
- CheckSSRtx
- Add checks to ensure that the input does not have a signature
script and does not have a fee if the automatic ticket revocations
agenda is active
- Add additional tests to cover the new code paths
- blockchain/validate.go, blockchain/validate_test.go
- checkTransactionContext
- Add rule that revocation transactions MUST be version 2 if the
automatic ticket revocations agenda is active.
- checkTicketRedeemers
- Add rule that if the automatic ticket revocations agenda is
active, then the block MUST contain revocation transactions for
all tickets that will become missed or expired as of that block
- Add rule that if the automatic ticket revocations agenda is
active, then revocations can spend tickets that will become missed
or expired as of that block (previously they could not be spent
until the block following the block where they became missed or
expired)
- Update to accept the referenced ticket hashes rather than the
overall block to simplify adding comprehensive unit tests for the
function
- calcTicketReturnAmounts
- Update to calculate the amounts for all outputs rather than a
single output
- For revocations, if the automatic ticket revocations agenda is active,
and there is a remainder after distributing the returns, then select a
uniformly pseudorandom output index to receive each remaining
atom
- checkTicketRedeemerCommitments
- Add rule that if the transaction is a version 2 revocation and the
automatic ticket revocation agenda is active, then the fee MUST be
zero
- Use the updated calcTicketReturnAmounts function so that returns
include the full amount, which allows for ensuring a truly zero
fee
- checkRevocationInputs
- Add rule that if the automatic ticket revocations agenda is
active, then revocations can spend tickets after ticket maturity
+ 1 blocks (in the block that they become missed or expired)
rather than after ticket maturity + 2 blocks (in the block AFTER
they become missed or expired)
- internal/mempool/mempool.go, internal/mempool/mempool_test.go
- Pass best block header to CheckTransactionInputs
- Pass isAutoRevocationsEnabled to ValidateTransactionScripts
- internal/mining/mining.go, internal/mining/mining_harness_test.go,
internal/mining/mining_test.go, internal/mining/mining_view_test.go
- Add a helper function, createRevocationFromTicket, to create a
revocation transaction from a ticket hash
- If the automatic ticket revocations agenda is active, then create
version 2 revocation transactions for all tickets that will become
missed or expired as of the block being created
- Create and insert these revocations into the priority queue AFTER
votes are done being added to ensure that revocations are added for
votes that fail validation checks or are otherwise not included into
the block
- Pass best block header to CheckTransactionInputs
- Pass isAutoRevocationsEnabled to ValidateTransactionScripts
- internal/rpcserver/interface.go, internal/rpcserver/rpcserver.go,
internal/rpcserver/rpcserverhandlers_test.go
- handleCreateRawSSRtx
- If the automatic ticket revocations agenda is active:
- Validate that the fee is zero
- Set the transaction version to 2
- Pass previous header bytes and isAutoRevocationsEnabled flag to
CreateRevocationFromTicket in order to create version 2 revocation
transactions properly with the updated rules
- Add additional tests to cover the new code paths
- server.go
- Pass previous block header to CheckTransactionInputs
- Pass isAutoRevocationsEnabled to ValidateTransactionScripts
526 lines
16 KiB
Go
526 lines
16 KiB
Go
// Copyright (c) 2020 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package mining
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/chaincfg/v3"
|
|
"github.com/decred/dcrd/dcrutil/v4"
|
|
"github.com/decred/dcrd/wire"
|
|
)
|
|
|
|
// Tests the behavior of the mining view when returned from a tx source
|
|
// containing a transaction chain depicted as:
|
|
//
|
|
// /--> d --> f
|
|
// |
|
|
// |--> b --|
|
|
// <coinbase> --> a |-->e
|
|
// |--> c --|
|
|
//
|
|
//
|
|
func TestMiningView(t *testing.T) {
|
|
harness, spendableOuts, err := newMiningHarness(chaincfg.MainNetParams())
|
|
if err != nil {
|
|
t.Fatalf("unable to create mining harness: %v", err)
|
|
}
|
|
|
|
applyTxFee := func(fee int64) func(*wire.MsgTx) {
|
|
return func(tx *wire.MsgTx) {
|
|
tx.TxOut[0].Value -= fee
|
|
}
|
|
}
|
|
|
|
// All transactions have the same number of outputs to keep the size uniform.
|
|
txA, _ := harness.CreateSignedTx([]spendableOutput{
|
|
spendableOuts[0],
|
|
}, 2, applyTxFee(1000))
|
|
|
|
txB, _ := harness.CreateSignedTx([]spendableOutput{
|
|
txOutToSpendableOut(txA, 0, wire.TxTreeRegular),
|
|
}, 2, applyTxFee(5000))
|
|
|
|
txC, _ := harness.CreateSignedTx([]spendableOutput{
|
|
txOutToSpendableOut(txA, 1, wire.TxTreeRegular),
|
|
}, 2, applyTxFee(5000))
|
|
|
|
txD, _ := harness.CreateSignedTx([]spendableOutput{
|
|
txOutToSpendableOut(txB, 0, wire.TxTreeRegular),
|
|
}, 2, applyTxFee(5000))
|
|
|
|
txE, _ := harness.CreateSignedTx([]spendableOutput{
|
|
txOutToSpendableOut(txB, 1, wire.TxTreeRegular),
|
|
txOutToSpendableOut(txC, 0, wire.TxTreeRegular),
|
|
}, 2, applyTxFee(5000))
|
|
|
|
txF, _ := harness.CreateSignedTx([]spendableOutput{
|
|
txOutToSpendableOut(txD, 0, wire.TxTreeRegular),
|
|
}, 2, applyTxFee(0))
|
|
|
|
// Add all to the tx source. Note that txB is out of order to test the
|
|
// behavior of an orphan transaction with ancestors brought into the tx source.
|
|
allTxns := []*dcrutil.Tx{txB, txA, txC, txD, txE}
|
|
for _, tx := range allTxns {
|
|
_, err = harness.AddTransactionToTxSource(tx)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to tx source: %v", err)
|
|
}
|
|
}
|
|
|
|
txALen := int64(txA.MsgTx().SerializeSize())
|
|
txBLen := int64(txB.MsgTx().SerializeSize())
|
|
txCLen := int64(txC.MsgTx().SerializeSize())
|
|
|
|
txATotalSigOps, err := harness.CountTotalSigOps(txA)
|
|
if err != nil {
|
|
t.Fatalf("failed to count sigops for txA %v", err)
|
|
}
|
|
|
|
txBTotalSigOps, err := harness.CountTotalSigOps(txB)
|
|
if err != nil {
|
|
t.Fatalf("failed to count sigops for txB %v", err)
|
|
}
|
|
|
|
txCTotalSigOps, err := harness.CountTotalSigOps(txC)
|
|
if err != nil {
|
|
t.Fatalf("failed to count sigops for txC %v", err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
miningView TxMiningView
|
|
subject *dcrutil.Tx
|
|
expectedAncestorFees int64
|
|
expectedSizeBytes int64
|
|
expectedSigOps int
|
|
ancestors []*dcrutil.Tx
|
|
descendants map[chainhash.Hash]*dcrutil.Tx
|
|
orderedAncestors [][]*dcrutil.Tx
|
|
}{{
|
|
name: "Tx A",
|
|
subject: txA,
|
|
expectedAncestorFees: 0,
|
|
expectedSizeBytes: 0,
|
|
expectedSigOps: 0,
|
|
ancestors: nil,
|
|
descendants: map[chainhash.Hash]*dcrutil.Tx{
|
|
txB.MsgTx().TxHash(): txB,
|
|
txC.MsgTx().TxHash(): txC,
|
|
txD.MsgTx().TxHash(): txD,
|
|
txE.MsgTx().TxHash(): txE,
|
|
},
|
|
orderedAncestors: nil,
|
|
}, {
|
|
name: "Tx B",
|
|
subject: txB,
|
|
expectedAncestorFees: 1000,
|
|
expectedSizeBytes: txALen,
|
|
expectedSigOps: txATotalSigOps,
|
|
ancestors: []*dcrutil.Tx{txA},
|
|
descendants: map[chainhash.Hash]*dcrutil.Tx{
|
|
txD.MsgTx().TxHash(): txD,
|
|
txE.MsgTx().TxHash(): txE,
|
|
},
|
|
orderedAncestors: [][]*dcrutil.Tx{
|
|
{txA},
|
|
},
|
|
}, {
|
|
name: "Tx C",
|
|
subject: txC,
|
|
expectedAncestorFees: 1000,
|
|
expectedSizeBytes: txALen,
|
|
expectedSigOps: txATotalSigOps,
|
|
ancestors: []*dcrutil.Tx{txA},
|
|
descendants: map[chainhash.Hash]*dcrutil.Tx{
|
|
txE.MsgTx().TxHash(): txE,
|
|
},
|
|
orderedAncestors: [][]*dcrutil.Tx{
|
|
{txA},
|
|
},
|
|
}, {
|
|
name: "Tx D",
|
|
subject: txD,
|
|
expectedAncestorFees: 6000,
|
|
expectedSizeBytes: txALen + txBLen,
|
|
expectedSigOps: txATotalSigOps + txBTotalSigOps,
|
|
ancestors: []*dcrutil.Tx{txA, txB},
|
|
descendants: map[chainhash.Hash]*dcrutil.Tx{},
|
|
orderedAncestors: [][]*dcrutil.Tx{
|
|
{txA, txB},
|
|
},
|
|
}, {
|
|
name: "Tx E",
|
|
subject: txE,
|
|
expectedAncestorFees: 11000,
|
|
expectedSizeBytes: txALen + txBLen + txCLen,
|
|
expectedSigOps: txATotalSigOps + txBTotalSigOps +
|
|
txCTotalSigOps,
|
|
ancestors: []*dcrutil.Tx{txA, txB, txC},
|
|
descendants: map[chainhash.Hash]*dcrutil.Tx{},
|
|
orderedAncestors: [][]*dcrutil.Tx{
|
|
{txA, txB, txC},
|
|
{txA, txC, txB},
|
|
},
|
|
}}
|
|
|
|
for _, test := range tests {
|
|
txHash := test.subject.Hash()
|
|
txDesc := harness.txSource.pool[*txHash]
|
|
miningView := harness.txSource.MiningView()
|
|
|
|
// Make sure transaction is not rejected between view instances.
|
|
if miningView.isRejected(txHash) {
|
|
t.Fatalf("%v: expected test subject txn %v to not be rejected",
|
|
test.name, *txHash)
|
|
}
|
|
|
|
// Ensure stats are calculated before retrieving ancestors.
|
|
initialStat, hasStats := miningView.AncestorStats(txHash)
|
|
if !hasStats {
|
|
t.Fatalf("%v: expected mining view to have ancestor stats",
|
|
test.name)
|
|
}
|
|
|
|
if test.expectedAncestorFees != initialStat.Fees {
|
|
t.Fatalf("%v: expected subject txn to have bundle fees %v, got %v",
|
|
test.name, test.expectedAncestorFees, initialStat.Fees)
|
|
}
|
|
|
|
if test.expectedSizeBytes != initialStat.SizeBytes {
|
|
t.Fatalf("%v: expected subject txn to have SizeBytes=%v, got %v",
|
|
test.name, test.expectedSizeBytes, initialStat.SizeBytes)
|
|
}
|
|
|
|
if test.expectedSigOps != initialStat.TotalSigOps {
|
|
t.Fatalf("%v: expected subject txn to have SigOps=%v, got %v",
|
|
test.name, test.expectedSigOps, initialStat.TotalSigOps)
|
|
}
|
|
|
|
if len(test.ancestors) != initialStat.NumAncestors {
|
|
t.Fatalf("%v: expected subject txn to have NumAncestors=%v, got %v",
|
|
test.name, len(test.ancestors), initialStat.NumAncestors)
|
|
}
|
|
|
|
// Retrieving ancestors will cause the cached stats to be updated.
|
|
ancestors := miningView.ancestors(txHash)
|
|
stat, _ := miningView.AncestorStats(txHash)
|
|
|
|
if initialStat == stat {
|
|
t.Fatalf("%v: expected ancestor stats reference to have changed "+
|
|
"after retrieving list of ancestors", test.name)
|
|
}
|
|
|
|
// Get snapshot of transaction relationships as they exist in the tx source.
|
|
descendants := harness.txSource.miningView.descendants(txHash)
|
|
|
|
if len(test.ancestors) != len(ancestors) {
|
|
t.Fatalf("%v: expected subject txn to have %v ancestors, got %v",
|
|
test.name, len(test.ancestors), len(ancestors))
|
|
}
|
|
|
|
if len(ancestors) != stat.NumAncestors {
|
|
t.Fatalf("%v: expected subject txn to have NumAncestors=%v, got %v",
|
|
test.name, len(ancestors), stat.NumAncestors)
|
|
}
|
|
|
|
if len(descendants) != stat.NumDescendants {
|
|
t.Fatalf("%v: expected subject txn to have NumDescendants=%v, "+
|
|
"got %v", test.name, len(descendants), stat.NumDescendants)
|
|
}
|
|
|
|
for _, descendantHash := range descendants {
|
|
if _, exist := test.descendants[*descendantHash]; !exist {
|
|
t.Fatalf("%v: expected subject txn to have descendant=%v",
|
|
test.name, descendantHash)
|
|
}
|
|
}
|
|
|
|
// Ensure that transactions have a valid order, and that one of the test's
|
|
// orderedAncestors matches ancestors returned from the view exactly.
|
|
exactMatch := true
|
|
for _, ancestorGroups := range test.orderedAncestors {
|
|
exactMatch = true
|
|
for index, ancestor := range ancestorGroups {
|
|
if *ancestors[index].Tx.Hash() != *ancestor.Hash() {
|
|
exactMatch = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if exactMatch {
|
|
break
|
|
}
|
|
}
|
|
|
|
if !exactMatch {
|
|
t.Fatalf("%v: subject txn ancestors returned out of order",
|
|
test.name)
|
|
}
|
|
|
|
if miningView.hasParents(txHash) && len(test.ancestors) == 0 {
|
|
t.Fatalf("%v: expected subject txn to not have 0 parents, got %v",
|
|
test.name, len(test.ancestors))
|
|
}
|
|
|
|
// Make sure all parents are valid outpoints from this tx.
|
|
txInputHashes := make(map[chainhash.Hash]struct{})
|
|
for _, outpoint := range test.subject.MsgTx().TxIn {
|
|
txInputHashes[outpoint.PreviousOutPoint.Hash] = struct{}{}
|
|
}
|
|
|
|
for _, parentDesc := range miningView.parents(txHash) {
|
|
if _, exist := txInputHashes[*parentDesc.Tx.Hash()]; !exist {
|
|
t.Fatalf("%v: test subject %v has invalid parent %v",
|
|
test.name, *txHash, *parentDesc.Tx.Hash())
|
|
}
|
|
}
|
|
|
|
// if the transaction has children, make sure it's no longer the child's
|
|
// ancestor after removal.
|
|
removalMiningView := harness.txSource.MiningView()
|
|
if children := removalMiningView.children(txHash); len(children) > 0 {
|
|
removalMiningView.RemoveTransaction(txHash, false)
|
|
child := children[0]
|
|
siblings := removalMiningView.parents(child.Tx.Hash())
|
|
|
|
// Make sure ancestor stats have not changed.
|
|
oldStat, hasStats := miningView.AncestorStats(child.Tx.Hash())
|
|
if !hasStats {
|
|
t.Fatalf("%v: Expected ancestor stats to be available",
|
|
test.name)
|
|
}
|
|
|
|
newStat, hasStats := removalMiningView.AncestorStats(
|
|
child.Tx.Hash())
|
|
if !hasStats {
|
|
t.Fatalf("%v: Expected ancestor stats to be available",
|
|
test.name)
|
|
}
|
|
|
|
if *oldStat != *newStat {
|
|
t.Fatalf("%v: expected test subject's child bundle stats to "+
|
|
"remain unchanged.", test.name)
|
|
}
|
|
|
|
for _, sibling := range siblings {
|
|
if *sibling.Tx.Hash() == *txHash {
|
|
t.Fatalf("%v: expected test subject to no longer be an "+
|
|
"ancestor of %v after being removed from the view.",
|
|
test.name, *sibling.Tx.Hash())
|
|
}
|
|
}
|
|
}
|
|
|
|
// reset the mining view after removing the tx and ensure that all descendants
|
|
// have the fee removed for this transaction when calling Remove with the
|
|
// option to do so.
|
|
removalMiningView = harness.txSource.MiningView()
|
|
removalMiningView.RemoveTransaction(txHash, true)
|
|
|
|
for _, descendant := range descendants {
|
|
oldStat, hasStats := miningView.AncestorStats(descendant)
|
|
if !hasStats {
|
|
t.Fatalf("%v: Expected ancestor stats to be available",
|
|
test.name)
|
|
}
|
|
|
|
newStat, hasStats := removalMiningView.AncestorStats(descendant)
|
|
if !hasStats {
|
|
t.Fatalf("%v: Expected ancestor stats to be available",
|
|
test.name)
|
|
}
|
|
|
|
expectedFee := oldStat.Fees - txDesc.Fee
|
|
expectedSigOps := oldStat.TotalSigOps - txDesc.TotalSigOps
|
|
expectedSizeBytes := oldStat.SizeBytes -
|
|
int64(txDesc.Tx.MsgTx().SerializeSize())
|
|
expectedNumAncestors := oldStat.NumAncestors - 1
|
|
|
|
if newStat.Fees != expectedFee {
|
|
t.Fatalf("%v: expected descendant to have adjusted "+
|
|
"Fees=%v, but got %v", test.name, expectedFee, newStat.Fees)
|
|
}
|
|
|
|
if newStat.TotalSigOps != expectedSigOps {
|
|
t.Fatalf("%v: expected descendant to have adjusted "+
|
|
"NumSigOps=%v, but got %v", test.name,
|
|
expectedSigOps, newStat.TotalSigOps)
|
|
}
|
|
|
|
if newStat.SizeBytes != expectedSizeBytes {
|
|
t.Fatalf("%v: expected descendant to have adjusted "+
|
|
"SizeBytes=%v, but got %v", test.name,
|
|
expectedSizeBytes, newStat.SizeBytes)
|
|
}
|
|
|
|
if newStat.NumAncestors != expectedNumAncestors {
|
|
t.Fatalf("%v: expected descendant to have "+
|
|
"NumAncestors=%v, but got %v", test.name,
|
|
expectedNumAncestors, newStat.NumAncestors)
|
|
}
|
|
}
|
|
|
|
miningView.reject(txHash)
|
|
|
|
if !miningView.isRejected(txHash) {
|
|
t.Fatalf("%v: expected test subject txn %v to be rejected",
|
|
test.name, *txHash)
|
|
}
|
|
|
|
// Rejecting a transaction rejects all descendant transactions and removes the
|
|
// tx from the graph.
|
|
if ancestors := miningView.ancestors(txHash); len(ancestors) > 0 {
|
|
t.Fatalf("%v: expected subject txn to have 0 ancestors, got %v",
|
|
test.name, len(ancestors))
|
|
}
|
|
|
|
for _, descendant := range descendants {
|
|
if !miningView.isRejected(descendant) {
|
|
t.Fatalf("%v: expected descendant txn %v to be rejected.",
|
|
test.name, *descendant)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get mining view snapshot prior to removing the tx from the tx source.
|
|
miningViewSnapshot := harness.txSource.MiningView()
|
|
|
|
// Remove txC and its descendants from the tx source.
|
|
harness.RemoveTransactionFromTxSource(txC, true)
|
|
|
|
// Add a new transaction to the tx source.
|
|
_, err = harness.AddTransactionToTxSource(txF)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
|
|
miningView := harness.txSource.MiningView()
|
|
addedTxns := miningView.children(txD.Hash())
|
|
if len(addedTxns) != 1 {
|
|
t.Fatalf("AddTxnToTxSource: expected txD to have exactly 1 child, got %v",
|
|
len(addedTxns))
|
|
}
|
|
|
|
if addedTxns[0].Tx != txF {
|
|
t.Fatalf("AddTxnToTxSource: expected txF to exist in the mining view.")
|
|
}
|
|
|
|
// Ensure newly added transaction not added to the mining view snapshot.
|
|
if len(miningViewSnapshot.children(txD.Hash())) != 0 {
|
|
t.Fatalf("AddTxnToTxSource: expected txD to not have descendants in " +
|
|
"snapshot mining view.")
|
|
}
|
|
|
|
if len(miningView.children(txC.Hash())) != 0 {
|
|
t.Fatalf("RemoveTxnFromTxSource: expected txC to not have descendents " +
|
|
"after being removed from the tx source.")
|
|
}
|
|
|
|
// Ensure tx and children are still accessible in cloned view.
|
|
if len(miningViewSnapshot.children(txC.Hash())) != 1 {
|
|
t.Fatalf("RemoveTxnFromTxSource: expected txC to exist in snapshot view " +
|
|
"even though it was removed from the tx source.")
|
|
}
|
|
}
|
|
|
|
// TestAncestorTrackingLimits ensures that ancestor tracking is disabled for
|
|
// transactions that have too many ancestors.
|
|
func TestAncestorTrackingLimits(t *testing.T) {
|
|
harness, spendableOuts, err := newMiningHarness(chaincfg.MainNetParams())
|
|
if err != nil {
|
|
t.Fatalf("unable to create mining harness: %v", err)
|
|
}
|
|
|
|
// Create a chain of transactions.
|
|
var allTxns []*dcrutil.Tx
|
|
prevSpendableOut := spendableOuts[0]
|
|
for i := 0; i < ancestorTrackingLimit+2; i++ {
|
|
tx, _ := harness.CreateSignedTx([]spendableOutput{
|
|
prevSpendableOut,
|
|
}, 1)
|
|
|
|
allTxns = append(allTxns, tx)
|
|
prevSpendableOut = txOutToSpendableOut(tx, 0, wire.TxTreeRegular)
|
|
}
|
|
|
|
// Add all transactions to the tx source.
|
|
for _, tx := range allTxns {
|
|
_, err = harness.AddTransactionToTxSource(tx)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
}
|
|
|
|
// Remove each transaction from the tx source to demonstrate that once a
|
|
// transaction has less ancestors than the limit that it begins having ancestor
|
|
// stats tracked and it continues to do so until it is removed from the tx
|
|
// source.
|
|
allTxnsQueue := allTxns
|
|
for len(allTxnsQueue) > 0 {
|
|
for index, tx := range allTxnsQueue {
|
|
_, hasStats := harness.txSource.miningView.AncestorStats(tx.Hash())
|
|
if index <= ancestorTrackingLimit && !hasStats {
|
|
t.Fatalf("expected transaction %v at index %v to have ancestor"+
|
|
" tracking enabled", tx.Hash(), index)
|
|
}
|
|
|
|
if index > ancestorTrackingLimit && hasStats {
|
|
t.Fatalf("expected transaction %v at index %v to not have "+
|
|
"ancestor tracking enabled", tx.Hash(), index)
|
|
}
|
|
}
|
|
|
|
tx := allTxnsQueue[0]
|
|
allTxnsQueue = allTxnsQueue[1:]
|
|
|
|
// Simulate the transaction being included in a block.
|
|
newBlockHeight := harness.chain.bestState.Height + 1
|
|
harness.AddFakeUTXO(tx, newBlockHeight, wire.NullBlockIndex,
|
|
harness.chain.isTreasuryAgendaActive)
|
|
harness.chain.bestState.Height = newBlockHeight
|
|
|
|
harness.RemoveTransactionFromTxSource(tx, false)
|
|
harness.txSource.MaybeAcceptDependents(tx,
|
|
harness.chain.isTreasuryAgendaActive,
|
|
harness.chain.isAutoRevocationsAgendaActive)
|
|
}
|
|
|
|
// Add all transactions back to the tx source in reverse order to demonstrate
|
|
// that a transaction that initially has ancestor stats tracked is later
|
|
// excluded from the ancestor tracked set when it has too many ancestors.
|
|
for index := len(allTxns) - 1; index >= 0; index-- {
|
|
tx := allTxns[index]
|
|
txHash := tx.Hash()
|
|
outpoint := wire.OutPoint{Hash: *txHash, Index: 0, Tree: wire.TxTreeRegular}
|
|
harness.chain.utxos.LookupEntry(outpoint).Spend()
|
|
_, err = harness.AddTransactionToTxSource(tx)
|
|
if err != nil {
|
|
t.Fatalf("unable to add transaction to the tx source: %v", err)
|
|
}
|
|
|
|
_, hasStats := harness.txSource.miningView.AncestorStats(txHash)
|
|
if !hasStats {
|
|
t.Fatalf("expected transaction %v at index %v to have ancestor"+
|
|
" tracking enabled when added back to the tx source", txHash, index)
|
|
}
|
|
}
|
|
|
|
for index, tx := range allTxns {
|
|
txHash := tx.Hash()
|
|
_, hasStats := harness.txSource.miningView.AncestorStats(txHash)
|
|
if index <= ancestorTrackingLimit && !hasStats {
|
|
t.Fatalf("expected transaction %v at index %v to have ancestor"+
|
|
" tracking enabled", txHash, index)
|
|
}
|
|
|
|
if index > ancestorTrackingLimit && hasStats {
|
|
t.Fatalf("expected transaction %v at index %v to not have "+
|
|
"ancestor tracking enabled", txHash, index)
|
|
}
|
|
}
|
|
}
|