Skip to content

Commit

Permalink
blockchain: Implement reject new script vers vote.
Browse files Browse the repository at this point in the history
This implements the agenda for voting on rejecting new script versions
until they have explicitly been enabled by a consensus rule change as
defined in DCP0008 along with consensus tests.

In particular, once the vote has passed and is active, script versions
greater than 0 will no longer be accepted.

Note that this does not implement block version bumps in the mining and
validation code that will ultimately be needed since another consensus
vote is expected to land before voting begins.

The following is an overview of the changes:

- Modify block validation to enforce the rejection of newer script
  versions in accordance with the state of the vote
- Add tests to ensure proper behavior for new script versions as follows:
  - Ensure stake transactions still require version 0 scripts regardless
    of the state of the agenda
  - Regular transactions with script versions newer than 0 are rejected
    once the agenda is active
  - Transaction outputs that have a non-zero output and script that
    would make the output unspendable if it were version 0 are spendable
    with a nil signature script both before the agenda is active and
    remain spendable after it is active
  • Loading branch information
davecgh committed Aug 25, 2021
1 parent 1d4f35c commit 8c8d822
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 6 deletions.
4 changes: 4 additions & 0 deletions blockchain/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ const (
// range or not referencing one at all.
ErrBadTxInput = ErrorKind("ErrBadTxInput")

// ErrScriptVersionTooHigh indicates a transaction script version is higher
// than the maximum version allowed by the active consensus rules.
ErrScriptVersionTooHigh = ErrorKind("ErrScriptVersionTooHigh")

// ErrMissingTxOut indicates a transaction output referenced by an input
// either does not exist or has already been spent.
ErrMissingTxOut = ErrorKind("ErrMissingTxOut")
Expand Down
1 change: 1 addition & 0 deletions blockchain/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func TestErrorKindStringer(t *testing.T) {
{ErrDuplicateTxInputs, "ErrDuplicateTxInputs"},
{ErrTxVersionTooHigh, "ErrTxVersionTooHigh"},
{ErrBadTxInput, "ErrBadTxInput"},
{ErrScriptVersionTooHigh, "ErrScriptVersionTooHigh"},
{ErrMissingTxOut, "ErrMissingTxOut"},
{ErrUnfinalizedTx, "ErrUnfinalizedTx"},
{ErrDuplicateTx, "ErrDuplicateTx"},
Expand Down
30 changes: 28 additions & 2 deletions blockchain/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,12 +514,38 @@ func checkTransactionContext(tx *wire.MsgTx, params *chaincfg.Params, flags Agen
}
}

// Ensure that non-stake transactions have no outputs with opcodes that are
// not allowed outside of the stake transactions.
// Enforce additional rules on regular (non-stake) transactions.
isStakeTx := isVote || isTicket || isRevocation || isTreasuryAdd ||
isTreasurySpend || isTreasuryBase
if !isStakeTx {
// Note that prior to the explicit version upgrades agenda, transaction
// script versions are allowed to go up to a max uint16, so fall back to
// that value accordingly.
maxAllowedScriptVer := ^uint16(0)
switch {
case explicitUpgradesActive:
maxAllowedScriptVer = 0
}

for txOutIdx, txOut := range tx.TxOut {
// Reject transaction script versions greater than the highest
// currently supported version. Any future consensus changes that
// result in introduction of a new script version are expected to
// update this code accordingly so that the newer transaction script
// version can be used as a guaranteed proxy for an agenda having
// passed and become active.
//
// It is also worth noting that this check only applies to regular
// transactions because stake transactions are individually and
// separately enforced to be a specific script version.
if txOut.Version > maxAllowedScriptVer {
str := fmt.Sprintf("script version %d is greater than the max "+
"allowed version %d)", txOut.Version, maxAllowedScriptVer)
return ruleError(ErrScriptVersionTooHigh, str)
}

// Ensure that non-stake transactions have no outputs with opcodes
// that are not allowed outside of the stake transactions.
hasOp, err := txscript.ContainsStakeOpCodes(txOut.PkScript,
isTreasuryEnabled)
if err != nil {
Expand Down
94 changes: 90 additions & 4 deletions blockchain/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/database/v3"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/txscript/v4"
"github.com/decred/dcrd/wire"
)

Expand Down Expand Up @@ -1080,8 +1081,12 @@ func TestExplicitVerUpgradesSemantics(t *testing.T) {
// -------------------------------------------------------------------------
// Create block at stake validation height that has both a regular and stake
// transaction with a version allowed prior to the explicit version upgrades
// agenda but not after it activates. The outputs are created now so they
// can be spent after the agenda activates to ensure they remain spendable.
// agenda but not after it activates. Also, set one of the outputs of
// another regular transaction to a script version that is also allowed
// prior to the explicit version upgrades agenda but not after it activates.
//
// The outputs are created now so they can be spent after the agenda
// activates to ensure they remain spendable.
//
// The block should be accepted because the agenda is not active yet.
//
Expand All @@ -1103,22 +1108,49 @@ func TestExplicitVerUpgradesSemantics(t *testing.T) {
}

// Set transaction versions to values that will no longer be valid for
// new outputs after the explicit version upgrades agenda activates.
// new transactions after the explicit version upgrades agenda
// activates.
b.Transactions[2].Version = ^uint16(0)
b.STransactions[3].Version = ^uint16(0)

// Set output script version of a regular transaction output to a value
// that will no longer be valid for new outputs after the explicit
// version upgrades agenda activates. Also, set the script to false
// which ordinarily would make the output unspendable if the script
// version were 0.
b.Transactions[3].TxOut[0].Version = 1
b.Transactions[3].TxOut[0].PkScript = []byte{txscript.OP_FALSE}
})
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
g.SnapshotCoinbaseOuts("postSVH")

// -------------------------------------------------------------------------
// Create block that has a stake transaction with a script version that is
// not allowed regardless of the explicit version upgrades agenda.
//
// The block should be rejected because stake transaction script versions
// are separately enforced and are required to be a specific version
// independently of the consensus change.
//
// ... -> bsvh
// \-> bbadvote
// -------------------------------------------------------------------------

outs = g.OldestCoinbaseOuts()
g.NextBlock("bbadvote", &outs[0], outs[1:], func(b *wire.MsgBlock) {
b.STransactions[4].TxOut[2].Version = 12345
})
g.RejectTipBlock(ErrBadTxInput)

// -------------------------------------------------------------------------
// Create enough blocks to allow the stake output created at stake
// validation height to mature.
//
// ... -> bsvh -> btmp0 -> ... -> btmp#
// -------------------------------------------------------------------------

outs = g.OldestCoinbaseOuts()
g.SetTip("bsvh")
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("btmp%d", i)
g.NextBlock(blockName, &outs[0], outs[1:])
Expand Down Expand Up @@ -1154,6 +1186,15 @@ func TestExplicitVerUpgradesSemantics(t *testing.T) {
stakeSpend := chaingen.MakeSpendableStakeOut(bsvh, stakeSpendTxIdx, 2)
stakeSpendTx := g.CreateSpendTx(&stakeSpend, lowFee)
b.AddTransaction(stakeSpendTx)

// Notice the signature script is nil because the version is unsupported
// which means the scripts are never executed.
const secondRegSpendTxIdx = 3
regSpend = chaingen.MakeSpendableOut(bsvh, secondRegSpendTxIdx, 0)
regSpendTx = g.CreateSpendTx(&regSpend, lowFee)
regSpendTx.TxIn[0].SignatureScript = nil
b.AddTransaction(regSpendTx)

})
g.AcceptTipBlock()

Expand Down Expand Up @@ -1237,6 +1278,23 @@ func TestExplicitVerUpgradesSemantics(t *testing.T) {
})
g.RejectTipBlock(ErrTxVersionTooHigh)

// -------------------------------------------------------------------------
// Create block that has a regular transaction with an output that has a
// script version that is no longer allowed for new transactions after the
// explicit version upgrades agenda is active.
//
// The block should be rejected because the agenda is active.
//
// ... -> bbase
// \-> b0bad3
// -------------------------------------------------------------------------

g.SetTip("bbase")
g.NextBlock("b0bad3", &outs[0], outs[1:], replaceVers, func(b *wire.MsgBlock) {
b.Transactions[1].TxOut[0].Version = ^uint16(0)
})
g.RejectTipBlock(ErrScriptVersionTooHigh)

// -------------------------------------------------------------------------
// Create block that spends both the regular and stake transactions created
// with a version that is no longer allowed for new transactions after the
Expand Down Expand Up @@ -1266,4 +1324,32 @@ func TestExplicitVerUpgradesSemantics(t *testing.T) {
})
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()

// -------------------------------------------------------------------------
// Create block that spends the regular transaction output created with a
// script version that is no longer allowed for new outputs after the
// explicit version upgrades agenda is active but already existed prior to
// its activation.
//
// The block should be accepted because all existing utxos must remain
// spendable after the agenda activates.
//
// ... -> bbase -> b0 -> b1
// -------------------------------------------------------------------------

outs = g.OldestCoinbaseOuts()
g.NextBlock("b1", &outs[0], outs[1:], replaceVers, func(b *wire.MsgBlock) {
// Notice the output is still spendable even though the signature script
// is nil because the version is unsupported which means the scripts are
// never executed.
const regSpendTxIdx = 3
bsvh := g.BlockByName("bsvh")
regSpend := chaingen.MakeSpendableOut(bsvh, regSpendTxIdx, 0)
regSpendTx := g.CreateSpendTx(&regSpend, lowFee)
regSpendTx.TxIn[0].SignatureScript = nil
b.AddTransaction(regSpendTx)
})
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()

}

0 comments on commit 8c8d822

Please sign in to comment.