Skip to content

Commit

Permalink
First pass hardfork cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
cpacia committed May 15, 2020
1 parent 8ac2990 commit b669fd6
Show file tree
Hide file tree
Showing 13 changed files with 26 additions and 566 deletions.
10 changes: 0 additions & 10 deletions blockchain/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,6 @@ const (
// allowed size when serialized.
ErrTxTooSmall

// ErrTxTooManySigOps indicates a transaction exceeds the maximum allowable
// number of signature operations.
ErrTxTooManySigOps

// ErrTxTooManySigChecks indicates a transaction exceeds the maximum allowable
// number of signature checks.
ErrTxTooManySigChecks
Expand Down Expand Up @@ -166,10 +162,6 @@ const (
// exceeding the maximum possible value.
ErrBadFees

// ErrTooManySigOps indicates the total number of signature operations
// for a transaction or block exceed the maximum allowed limits.
ErrTooManySigOps

// ErrTooManySigChecks indicates that the block's signature checks exceeds
// the limit.
ErrTooManySigChecks
Expand Down Expand Up @@ -253,7 +245,6 @@ var errorCodeStrings = map[ErrorCode]string{
ErrNoTxOutputs: "ErrNoTxOutputs",
ErrTxTooBig: "ErrTxTooBig",
ErrTxTooSmall: "ErrTxTooSmall",
ErrTxTooManySigOps: "ErrTxTooManySigOps",
ErrBadTxOutValue: "ErrBadTxOutValue",
ErrDuplicateTxInputs: "ErrDuplicateTxInputs",
ErrBadTxInput: "ErrBadTxInput",
Expand All @@ -265,7 +256,6 @@ var errorCodeStrings = map[ErrorCode]string{
ErrImmatureSpend: "ErrImmatureSpend",
ErrSpendTooHigh: "ErrSpendTooHigh",
ErrBadFees: "ErrBadFees",
ErrTooManySigOps: "ErrTooManySigOps",
ErrFirstTxNotCoinbase: "ErrFirstTxNotCoinbase",
ErrMultipleCoinbases: "ErrMultipleCoinbases",
ErrBadCoinbaseScriptLen: "ErrBadCoinbaseScriptLen",
Expand Down
1 change: 0 additions & 1 deletion blockchain/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ func TestErrorCodeStringer(t *testing.T) {
{ErrImmatureSpend, "ErrImmatureSpend"},
{ErrSpendTooHigh, "ErrSpendTooHigh"},
{ErrBadFees, "ErrBadFees"},
{ErrTooManySigOps, "ErrTooManySigOps"},
{ErrFirstTxNotCoinbase, "ErrFirstTxNotCoinbase"},
{ErrMultipleCoinbases, "ErrMultipleCoinbases"},
{ErrBadCoinbaseScriptLen, "ErrBadCoinbaseScriptLen"},
Expand Down
5 changes: 1 addition & 4 deletions blockchain/fullblocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"os"
"path/filepath"
"testing"
"time"

"github.com/gcash/bchd/blockchain"
"github.com/gcash/bchd/blockchain/fullblocktests"
Expand Down Expand Up @@ -141,8 +140,6 @@ func TestFullBlocks(t *testing.T) {
}

// Create a new database and chain instance to run tests against.
params := &chaincfg.RegressionNetParams
params.PhononActivationTime = uint64(time.Now().Add(time.Hour).Unix())
chain, teardownFunc, err := chainSetup("fullblocktest",
&chaincfg.RegressionNetParams, 1000000)
if err != nil {
Expand Down Expand Up @@ -622,7 +619,7 @@ func TestPhononActivation(t *testing.T) {

// Create a new database and chain instance to run tests against.
params := &chaincfg.RegressionNetParams
params.PhononActivationTime = 0
params.PhononForkHeight = 0
params.GravitonForkHeight = 0
params.MagneticAnonomalyForkHeight = 0
params.UahfForkHeight = 0
Expand Down
51 changes: 0 additions & 51 deletions blockchain/fullblocktests/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,57 +669,6 @@ func repeatOpcode(opcode uint8, numRepeats int) []byte {
return bytes.Repeat([]byte{opcode}, numRepeats)
}

// assertScriptSigOpsCount panics if the provided script does not have the
// specified number of signature operations.
func assertScriptSigOpsCount(script []byte, expected int) {
var scriptFlags txscript.ScriptFlags
scriptFlags |= txscript.ScriptVerifySigPushOnly |
txscript.ScriptVerifyCleanStack |
txscript.ScriptVerifyCheckDataSig

numSigOps := txscript.GetSigOpCount(script, scriptFlags)
if numSigOps != expected {
_, file, line, _ := runtime.Caller(1)
panic(fmt.Sprintf("assertion failed at %s:%d: generated number "+
"of sigops for script is %d instead of expected %d",
file, line, numSigOps, expected))
}
}

// countBlockSigOps returns the number of legacy signature operations in the
// scripts in the passed block.
func countBlockSigOps(block *wire.MsgBlock) int {
totalSigOps := 0
var scriptFlags txscript.ScriptFlags
scriptFlags |= txscript.ScriptVerifySigPushOnly |
txscript.ScriptVerifyCleanStack |
txscript.ScriptVerifyCheckDataSig

for _, tx := range block.Transactions {
for _, txIn := range tx.TxIn {
numSigOps := txscript.GetSigOpCount(txIn.SignatureScript, scriptFlags)
totalSigOps += numSigOps
}
for _, txOut := range tx.TxOut {
numSigOps := txscript.GetSigOpCount(txOut.PkScript, scriptFlags)
totalSigOps += numSigOps
}
}

return totalSigOps
}

// assertTipBlockSigOpsCount panics if the current tip block associated with the
// generator does not have the specified number of signature operations.
func (g *testGenerator) assertTipBlockSigOpsCount(expected int) {
numSigOps := countBlockSigOps(g.tip)
if numSigOps != expected {
panic(fmt.Sprintf("generated number of sigops for block %q "+
"(height %d) is %d instead of expected %d", g.tipName,
g.tipHeight, numSigOps, expected))
}
}

// assertTipBlockSize panics if the if the current tip block associated with the
// generator does not have the specified size when serialized.
func (g *testGenerator) assertTipBlockSize(expected int) {
Expand Down
155 changes: 3 additions & 152 deletions blockchain/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@ const (
// prior to the August 1st, 2018 UAHF hardfork
LegacyMaxBlockSize = 1000000

// MaxBlockSigOpsPerMB is the maximum number of allowed sigops allowed
// per one (or partial) megabyte of block size after the UAHF hard fork
MaxBlockSigOpsPerMB = 20000

// MaxTransactionSize is the maximum allowable size of a transaction
// after the UAHF hard fork
MaxTransactionSize = oneMegabyte
Expand All @@ -61,10 +57,6 @@ const (
// network after the magneticanomaly hardfork
MinTransactionSize = 100

// MaxTransactionSigOps is the maximum allowable number of sigops per
// transaction after the UAHF hard fork
MaxTransactionSigOps = 20000

// BlockMaxBytesMaxSigChecksRatio is the ratio between the maximum allowable
// block size and the maximum allowable * SigChecks (executed signature check
// operations) in the block. (network rule).
Expand Down Expand Up @@ -101,14 +93,6 @@ func (b *BlockChain) MaxBlockSize(uahfActive bool) int {
return LegacyMaxBlockSize
}

// MaxBlockSigOps returns the maximum allowable number of signature
// operations in a block. The value is a function of the serialized
// block size in bytes.
func MaxBlockSigOps(nBlockBytes uint32) int {
nBlockMBytesRoundedUp := 1 + ((int(nBlockBytes) - 1) / oneMegabyte)
return nBlockMBytesRoundedUp * MaxBlockSigOpsPerMB
}

// isNullOutpoint determines whether or not a previous transaction output point
// is set.
func isNullOutpoint(outpoint *wire.OutPoint) bool {
Expand Down Expand Up @@ -397,102 +381,6 @@ func CheckProofOfWork(block *bchutil.Block, powLimit *big.Int) error {
return checkProofOfWork(&block.MsgBlock().Header, powLimit, BFNone)
}

// CountSigOps returns the number of signature operations for all transaction
// input and output scripts in the provided transaction. This uses the
// quicker, but imprecise, signature operation counting mechanism from
// txscript.
func CountSigOps(tx *bchutil.Tx, scriptFlags txscript.ScriptFlags) int {
msgTx := tx.MsgTx()

// Accumulate the number of signature operations in all transaction
// inputs.
totalSigOps := 0
for _, txIn := range msgTx.TxIn {
numSigOps := txscript.GetSigOpCount(txIn.SignatureScript, scriptFlags)
totalSigOps += numSigOps
}

// Accumulate the number of signature operations in all transaction
// outputs.
for _, txOut := range msgTx.TxOut {
numSigOps := txscript.GetSigOpCount(txOut.PkScript, scriptFlags)
totalSigOps += numSigOps
}

return totalSigOps
}

// GetSigOps returns the unified sig op count for the passed transaction
// respecting current active soft-forks which modified sig op cost counting.
func GetSigOps(tx *bchutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint, scriptFlags txscript.ScriptFlags) (int, error) {
numSigOps := CountSigOps(tx, scriptFlags)
if scriptFlags.HasFlag(txscript.ScriptBip16) {
numP2SHSigOps, err := CountP2SHSigOps(tx, isCoinBaseTx, utxoView, scriptFlags)
if err != nil {
return 0, nil
}
numSigOps += numP2SHSigOps
}
return numSigOps, nil
}

// CountP2SHSigOps returns the number of signature operations for all input
// transactions which are of the pay-to-script-hash type. This uses the
// precise, signature operation counting mechanism from the script engine which
// requires access to the input transaction scripts.
func CountP2SHSigOps(tx *bchutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint, scriptFlags txscript.ScriptFlags) (int, error) {
// Coinbase transactions have no interesting inputs.
if isCoinBaseTx {
return 0, nil
}

// Accumulate the number of signature operations in all transaction
// inputs.
msgTx := tx.MsgTx()
totalSigOps := 0
for txInIndex, txIn := range msgTx.TxIn {
// Ensure the referenced input transaction is available.
utxo := utxoView.LookupEntry(txIn.PreviousOutPoint)
if utxo == nil {
str := fmt.Sprintf("output %v referenced from "+
"transaction %s:%d does not exist", txIn.PreviousOutPoint,
tx.Hash(), txInIndex)
return 0, ruleError(ErrMissingTxOut, str)
} else if utxo.IsSpent() {
str := fmt.Sprintf("output %v referenced from "+
"transaction %s:%d has already been spent", txIn.PreviousOutPoint,
tx.Hash(), txInIndex)
return 0, ruleError(ErrSpentTxOut, str)
}

// We're only interested in pay-to-script-hash types, so skip
// this input if it's not one.
pkScript := utxo.PkScript()
if !txscript.IsPayToScriptHash(pkScript) {
continue
}

// Count the precise number of signature operations in the
// referenced public key script.
sigScript := txIn.SignatureScript
numSigOps := txscript.GetPreciseSigOpCount(sigScript, pkScript,
scriptFlags)

// We could potentially overflow the accumulator so check for
// overflow.
lastSigOps := totalSigOps
totalSigOps += numSigOps
if totalSigOps < lastSigOps {
str := fmt.Sprintf("the public key script from output "+
"%v contains too many signature operations - "+
"overflow", txIn.PreviousOutPoint)
return 0, ruleError(ErrTooManySigOps, str)
}
}

return totalSigOps, nil
}

// checkBlockHeaderSanity performs some preliminary checks on a block header to
// ensure it is sane before continuing with processing. These checks are
// context free.
Expand Down Expand Up @@ -1106,7 +994,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *bchutil.Block, vi

// If Phonon hardfork is active we must enforce the new sig check rules and
// OP_REVERSEBYTES.
phononActive := uint64(node.parent.CalcPastMedianTime().Unix()) >= b.chainParams.PhononActivationTime
phononActive := node.height > b.chainParams.PhononForkHeight

// BIP0030 added a rule to prevent blocks which contain duplicate
// transactions that 'overwrite' older transactions which are not fully
Expand Down Expand Up @@ -1202,52 +1090,15 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *bchutil.Block, vi
scriptFlags |= txscript.ScriptReportSigChecks | txscript.ScriptVerifyReverseBytes
}

// The number of signature operations must be less than the maximum
// allowed per block. Note that the preliminary sanity checks on a
// block also include a check similar to this one, but this check
// expands the count to include a precise count of pay-to-script-hash
// signature operations in each of the input transaction public key
// scripts.
//
// Also note that as of the phonon hardfork this check is replaced
// with the more accurate SigCheck code.
transactions := block.Transactions()
if !phononActive {
totalSigOpCost := 0
nBlockBytes := block.MsgBlock().SerializeSize()
maxSigOps := MaxBlockSigOps(uint32(nBlockBytes))
for i, tx := range transactions {
// Since the first (and only the first) transaction has
// already been verified to be a coinbase transaction,
// use i == 0 as an optimization for the flag to
// countP2SHSigOps for whether or not the transaction is
// a coinbase transaction rather than having to do a
// full coinbase check again.
sigOpCost, err := GetSigOps(tx, i == 0, view, scriptFlags)
if err != nil {
return err
}

// Check for overflow or going over the limits. We have to do
// this on every loop iteration to avoid overflow.
lastSigOpCost := totalSigOpCost
totalSigOpCost += sigOpCost
if totalSigOpCost < lastSigOpCost || totalSigOpCost > maxSigOps {
str := fmt.Sprintf("block contains too many "+
"signature operations - got %v, max %v",
totalSigOpCost, maxSigOps)
return ruleError(ErrTooManySigOps, str)
}
}
}

// Perform several checks on the inputs for each transaction. Also
// accumulate the total fees. This could technically be combined with
// the loop above instead of running another loop over the transactions,
// but by separating it we can avoid running the more expensive (though
// still relatively cheap as compared to running the scripts) checks
// against all the inputs when the signature operations are out of
// bounds.
transactions := block.Transactions()

var totalFees int64
for _, tx := range transactions {
txFee, err := CheckTransactionInputs(tx, node.height, view, b.chainParams)
Expand Down
Loading

0 comments on commit b669fd6

Please sign in to comment.