Skip to content

Commit

Permalink
mempool: Enforce explicit versions.
Browse files Browse the repository at this point in the history
This adds enforcement of the newly introduced explicit version upgrade
requirements when considering candidate transactions for acceptance to
the mempool, relaying, and inclusion into block templates.

This is acceptable because the aforementioned areas only enforce policy.
Also of note is that the existing policy already rejected transactions
via the standardness policy that will now be rejected at a consensus
level.

It also adds tests to ensure the new enforcement works as intended.
  • Loading branch information
davecgh committed Aug 26, 2021
1 parent 3491b34 commit a276147
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 7 deletions.
18 changes: 11 additions & 7 deletions internal/mempool/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1231,9 +1231,6 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit,
return nil, txRuleError(ErrDuplicate, str)
}

// Determine active agendas based on flags.
isTreasuryEnabled := checkTxFlags.IsTreasuryEnabled()

// Perform preliminary validation checks on the transaction. This makes use
// of blockchain which contains the invariant rules for what transactions
// are allowed into blocks.
Expand All @@ -1246,6 +1243,9 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit,
return nil, err
}

// Determine active agendas based on flags.
isTreasuryEnabled := checkTxFlags.IsTreasuryEnabled()

// A standalone transaction must not be a coinbase transaction.
if standalone.IsCoinBaseTx(msgTx, isTreasuryEnabled) {
str := fmt.Sprintf("transaction %v is an individual coinbase",
Expand Down Expand Up @@ -1798,8 +1798,10 @@ func (mp *TxPool) MaybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit bool)
}

// Create agenda flags for checking transactions based on which ones are
// active.
checkTxFlags := blockchain.AFNone
// active or should otherwise always be enforced.
//
// Note that explicit version upgrades are always enforced by policy.
checkTxFlags := blockchain.AFExplicitVerUpgrades
if isTreasuryEnabled {
checkTxFlags |= blockchain.AFTreasuryEnabled
}
Expand Down Expand Up @@ -2042,8 +2044,10 @@ func (mp *TxPool) ProcessTransaction(tx *dcrutil.Tx, allowOrphan, rateLimit, all
}

// Create agenda flags for checking transactions based on which ones are
// active.
checkTxFlags := blockchain.AFNone
// active or should otherwise always be enforced.
//
// Note that explicit version upgrades are always enforced by policy.
checkTxFlags := blockchain.AFExplicitVerUpgrades
if isTreasuryEnabled {
checkTxFlags |= blockchain.AFTreasuryEnabled
}
Expand Down
121 changes: 121 additions & 0 deletions internal/mempool/mempool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2747,3 +2747,124 @@ func TestStagedTransactionHeight(t *testing.T) {
initialBlockHeight, poolTransaction.Height)
}
}

// TestExplicitVersionSemantics ensures the mempool has the following semantics
// in regards to transaction and script versions:
//
// - Rejects new regular and stake txns with an unsupported tx version
// - Rejects new regular and stake txns with an output that has an unsupported
// script version
// - Accepts new txns that spend an existing regular tx output that has a newer
// script version that is no longer allowed for new outputs (until/unless
// explicitly enabled via a future consensus vote)
func TestExplicitVersionSemantics(t *testing.T) {
t.Parallel()

// Create a new harness that accept non-standard transactions.
harness, outputs, err := newPoolHarness(chaincfg.MainNetParams())
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
txPool := harness.txPool
txPool.cfg.Policy.AcceptNonStd = true
tc := &testContext{t, harness}

// Ensure a regular transaction with the version set to the max value is
// rejected. Then verify it is not in the orphan pool, not in the
// transaction pool, and not reported as available.
maxVerTx, err := harness.CreateSignedTx([]spendableOutput{outputs[0]}, 1,
func(tx *wire.MsgTx) {
tx.Version = ^uint16(0)
})
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
_, err = txPool.ProcessTransaction(maxVerTx, false, false, true, 0)
if !errors.Is(err, blockchain.ErrTxVersionTooHigh) {
t.Fatalf("did not receive expected err -- got %[1]v (%[1]T)", err)
}
testPoolMembership(tc, maxVerTx, false, false)

// Ensure a stake transaction (ticket purchase) with the version set to the
// max value is rejected. Then verify it is not in the orphan pool, not in
// the transaction pool, and not reported as available.
ticket, err := harness.CreateTicketPurchase(outputs[0], 40000,
func(tx *wire.MsgTx) {
tx.Version = ^uint16(0)
})
if err != nil {
t.Fatalf("unable to create ticket purchase transaction %v", err)
}
_, err = txPool.ProcessTransaction(ticket, false, false, true, 0)
if !errors.Is(err, blockchain.ErrTxVersionTooHigh) {
t.Fatalf("did not receive expected err -- got %[1]v (%[1]T)", err)
}
testPoolMembership(tc, ticket, false, false)

// Ensure a regular transaction with the script version for the first output
// set to the max value is rejected. Then verify it is not in the orphan
// pool, not in the transaction pool, and not reported as available.
maxScrVerTx, err := harness.CreateSignedTx([]spendableOutput{outputs[0]},
1, func(tx *wire.MsgTx) {
tx.TxOut[0].Version = ^uint16(0)
})
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
_, err = txPool.ProcessTransaction(maxScrVerTx, false, false, true, 0)
if !errors.Is(err, blockchain.ErrScriptVersionTooHigh) {
t.Fatalf("did not receive expected err -- got %[1]v (%[1]T)", err)
}
testPoolMembership(tc, maxScrVerTx, false, false)

// Ensure a stake transaction (ticket purchase) with the script version of
// an output set to the max value is rejected. Then verify it is not in the
// orphan pool, not in the transaction pool, and not reported as available.
//
// Note that newer script versions for stake transactions were already
// disallowed prior to the explicit version upgrades consensus vote, so this
// just asserts that behavior has not changed.
ticket2, err := harness.CreateTicketPurchase(outputs[0], 40000,
func(tx *wire.MsgTx) {
tx.TxOut[0].Version = ^uint16(0)
})
if err != nil {
t.Fatalf("unable to create ticket purchase transaction %v", err)
}
_, err = txPool.ProcessTransaction(ticket2, false, false, true, 0)
if !errors.Is(err, blockchain.ErrScriptVersionTooHigh) {
t.Fatalf("did not receive expected err -- got %[1]v (%[1]T)", err)
}
testPoolMembership(tc, ticket2, false, false)

// Create and add a mock a regular tree utxo with a script version set to
// the max value so the code below can spend it to prove existing utxos
// created with script versions that are no longer valid for new outputs
// remain spendable.
mockInputMsgTx := wire.NewMsgTx()
mockInputMsgTx.AddTxOut(&wire.TxOut{
Value: 1000000000,
Version: ^uint16(0),
PkScript: []byte{txscript.OP_FALSE},
})
mockInputTx := dcrutil.NewTx(mockInputMsgTx)
harness.AddFakeUTXO(mockInputTx, harness.chain.BestHeight())

// Ensure spending an existing regular transaction output that has a newer
// script version that are no longer valid for new outputs (until explicitly
// enabled in the future) is accepted. Then verify it is not in the orphan
// pool, is in the transaction pool, and is reported as available.
spendableOut := txOutToSpendableOut(mockInputTx, 0, wire.TxTreeRegular)
spendMaxScrVerTx, err := harness.CreateTx(spendableOut)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
_, err = txPool.ProcessTransaction(spendMaxScrVerTx, false, false, true, 0)
if err != nil {
t.Fatalf("unexpected err: %[1]v (%[1]T)", err)
}
testPoolMembership(tc, spendMaxScrVerTx, false, true)

// NOTE: No attempt to spend a stake output with a script version too high
// is made because those have never permitted by consensus.
}

0 comments on commit a276147

Please sign in to comment.