From d0768abcc420ad291f99957757654044eb96daf4 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 18 Oct 2016 18:33:20 -0700 Subject: [PATCH] BIP0141+blockchain: implement segwit block validation rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit implements the new block validation rules as defined by BIP0141. The new rules include the constraints that if a block has transactions with witness data, then there MUST be a commitment within the conies transaction to the root of a new merkle tree which commits to the wtxid of all transactions. Additionally, rather than limiting the size of a block by size in bytes, blocks are now limited by their total weight unit. Similarly, a newly define “sig op cost” is now used to limit the signature validation cost of transactions found within blocks. --- blockchain/chain.go | 56 ++++++++----- blockchain/chainio.go | 12 +-- blockchain/error.go | 98 +++++++++++++--------- blockchain/fullblocks_test.go | 2 +- blockchain/fullblocktests/generate.go | 6 +- blockchain/scriptval.go | 100 +++++++++++++++++++---- blockchain/scriptval_test.go | 2 +- blockchain/validate.go | 112 ++++++++++++++++++-------- blockchain/validate_test.go | 2 +- 9 files changed, 274 insertions(+), 116 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index 4969842f..b4fa0e97 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -42,25 +42,29 @@ type orphanBlock struct { // However, the returned snapshot must be treated as immutable since it is // shared by all callers. type BestState struct { - Hash chainhash.Hash // The hash of the block. - Height int32 // The height of the block. - Bits uint32 // The difficulty bits of the block. - BlockSize uint64 // The size of the block. - NumTxns uint64 // The number of txns in the block. - TotalTxns uint64 // The total number of txns in the chain. - MedianTime time.Time // Median time as per CalcPastMedianTime. + Hash chainhash.Hash // The hash of the block. + Height int32 // The height of the block. + Bits uint32 // The difficulty bits of the block. + BlockSize uint64 // The size of the block. + BlockWeight uint64 // The weight of the block. + NumTxns uint64 // The number of txns in the block. + TotalTxns uint64 // The total number of txns in the chain. + MedianTime time.Time // Median time as per CalcPastMedianTime. } // newBestState returns a new best stats instance for the given parameters. -func newBestState(node *blockNode, blockSize, numTxns, totalTxns uint64, medianTime time.Time) *BestState { +func newBestState(node *blockNode, blockSize, blockWeight, numTxns, + totalTxns uint64, medianTime time.Time) *BestState { + return &BestState{ - Hash: node.hash, - Height: node.height, - Bits: node.bits, - BlockSize: blockSize, - NumTxns: numTxns, - TotalTxns: totalTxns, - MedianTime: medianTime, + Hash: node.hash, + Height: node.height, + Bits: node.bits, + BlockSize: blockSize, + BlockWeight: blockWeight, + NumTxns: numTxns, + TotalTxns: totalTxns, + MedianTime: medianTime, } } @@ -80,6 +84,7 @@ type BlockChain struct { notifications NotificationCallback sigCache *txscript.SigCache indexManager IndexManager + hashCache *txscript.HashCache // The following fields are calculated based upon the provided chain // parameters. They are also set when the instance is created and @@ -616,8 +621,9 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U b.stateLock.RUnlock() numTxns := uint64(len(block.MsgBlock().Transactions)) blockSize := uint64(block.MsgBlock().SerializeSize()) - state := newBestState(node, blockSize, numTxns, curTotalTxns+numTxns, - medianTime) + blockWeight := uint64(GetBlockWeight(block)) + state := newBestState(node, blockSize, blockWeight, numTxns, + curTotalTxns+numTxns, medianTime) // Atomically insert info into the database. err = b.db.Update(func(dbTx database.Tx) error { @@ -744,9 +750,10 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view b.stateLock.RUnlock() numTxns := uint64(len(prevBlock.MsgBlock().Transactions)) blockSize := uint64(prevBlock.MsgBlock().SerializeSize()) + blockWeight := uint64(GetBlockWeight(prevBlock)) newTotalTxns := curTotalTxns - uint64(len(block.MsgBlock().Transactions)) - state := newBestState(prevNode, blockSize, numTxns, newTotalTxns, - medianTime) + state := newBestState(prevNode, blockSize, blockWeight, numTxns, + newTotalTxns, medianTime) err = b.db.Update(func(dbTx database.Tx) error { // Update best block state. @@ -1328,6 +1335,16 @@ type Config struct { // This field can be nil if the caller does not wish to make use of an // index manager. IndexManager IndexManager + + // HashCache defines a transaction hash mid-state cache to use when + // validating transactions. This cache has the potential to greatly + // speed up transaction validation as re-using the pre-calculated + // mid-state eliminates the O(N^2) validation complexity due to the + // SigHashAll flag. + // + // This field can be nil if the caller is not interested in using a + // signature cache. + HashCache *txscript.HashCache } // New returns a BlockChain instance using the provided configuration details. @@ -1378,6 +1395,7 @@ func New(config *Config) (*BlockChain, error) { maxRetargetTimespan: targetTimespan * adjustmentFactor, blocksPerRetarget: int32(targetTimespan / targetTimePerBlock), index: newBlockIndex(config.DB, params), + hashCache: config.HashCache, orphans: make(map[chainhash.Hash]*orphanBlock), prevOrphans: make(map[chainhash.Hash][]*orphanBlock), warningCaches: newThresholdCaches(vbNumBits), diff --git a/blockchain/chainio.go b/blockchain/chainio.go index dccedad2..92987983 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -1092,8 +1092,9 @@ func (b *BlockChain) createChainState() error { // genesis block, use its timestamp for the median time. numTxns := uint64(len(genesisBlock.MsgBlock().Transactions)) blockSize := uint64(genesisBlock.MsgBlock().SerializeSize()) - b.stateSnapshot = newBestState(b.bestNode, blockSize, numTxns, numTxns, - time.Unix(b.bestNode.timestamp, 0)) + blockWeight := uint64(GetBlockWeight(genesisBlock)) + b.stateSnapshot = newBestState(b.bestNode, blockSize, blockWeight, + numTxns, numTxns, time.Unix(b.bestNode.timestamp, 0)) // Create the initial the database chain state including creating the // necessary index buckets and inserting the genesis block. @@ -1197,11 +1198,12 @@ func (b *BlockChain) initChainState() error { // Initialize the state related to the best block. blockSize := uint64(len(blockBytes)) + blockWeight := uint64(GetBlockWeight(btcutil.NewBlock(&block))) numTxns := uint64(len(block.Transactions)) - b.stateSnapshot = newBestState(b.bestNode, blockSize, numTxns, - state.totalTxns, medianTime) - + b.stateSnapshot = newBestState(b.bestNode, blockSize, blockWeight, + numTxns, state.totalTxns, medianTime) isStateInitialized = true + return nil }) if err != nil { diff --git a/blockchain/error.go b/blockchain/error.go index db71a566..e4bf8f53 100644 --- a/blockchain/error.go +++ b/blockchain/error.go @@ -41,6 +41,10 @@ const ( // maximum allowed size. ErrBlockTooBig + // ErrBlockWeightTooHigh indicates that the block's computed weight + // metric exceeds the maximum allowed value. + ErrBlockWeightTooHigh + // ErrBlockVersionTooOld indicates the block version is too old and is // no longer accepted since the majority of the network has upgraded // to a newer version. @@ -198,48 +202,66 @@ const ( // such signature verification failures and execution past the end of // the stack. ErrScriptValidation + + // ErrUnexpectedWitness indicates that a block includes transactions + // with witness data, but doesn't also have a witness commitment within + // the coinbase transaction. + ErrUnexpectedWitness + + // ErrInvalidWitnessCommitment indicates that a block's witness + // commitment is not well formed. + ErrInvalidWitnessCommitment + + // ErrWitnessCommitmentMismatch indicates that the witness commitment + // included in the block's coinbase transaction doesn't match the + // manually computed witness commitment. + ErrWitnessCommitmentMismatch ) // Map of ErrorCode values back to their constant names for pretty printing. var errorCodeStrings = map[ErrorCode]string{ - ErrDuplicateBlock: "ErrDuplicateBlock", - ErrBlockTooBig: "ErrBlockTooBig", - ErrBlockVersionTooOld: "ErrBlockVersionTooOld", - ErrInvalidTime: "ErrInvalidTime", - ErrTimeTooOld: "ErrTimeTooOld", - ErrTimeTooNew: "ErrTimeTooNew", - ErrDifficultyTooLow: "ErrDifficultyTooLow", - ErrUnexpectedDifficulty: "ErrUnexpectedDifficulty", - ErrHighHash: "ErrHighHash", - ErrBadMerkleRoot: "ErrBadMerkleRoot", - ErrBadCheckpoint: "ErrBadCheckpoint", - ErrForkTooOld: "ErrForkTooOld", - ErrCheckpointTimeTooOld: "ErrCheckpointTimeTooOld", - ErrNoTransactions: "ErrNoTransactions", - ErrTooManyTransactions: "ErrTooManyTransactions", - ErrNoTxInputs: "ErrNoTxInputs", - ErrNoTxOutputs: "ErrNoTxOutputs", - ErrTxTooBig: "ErrTxTooBig", - ErrBadTxOutValue: "ErrBadTxOutValue", - ErrDuplicateTxInputs: "ErrDuplicateTxInputs", - ErrBadTxInput: "ErrBadTxInput", - ErrMissingTx: "ErrMissingTx", - ErrUnfinalizedTx: "ErrUnfinalizedTx", - ErrDuplicateTx: "ErrDuplicateTx", - ErrOverwriteTx: "ErrOverwriteTx", - ErrImmatureSpend: "ErrImmatureSpend", - ErrDoubleSpend: "ErrDoubleSpend", - ErrSpendTooHigh: "ErrSpendTooHigh", - ErrBadFees: "ErrBadFees", - ErrTooManySigOps: "ErrTooManySigOps", - ErrFirstTxNotCoinbase: "ErrFirstTxNotCoinbase", - ErrMultipleCoinbases: "ErrMultipleCoinbases", - ErrBadCoinbaseScriptLen: "ErrBadCoinbaseScriptLen", - ErrBadCoinbaseValue: "ErrBadCoinbaseValue", - ErrMissingCoinbaseHeight: "ErrMissingCoinbaseHeight", - ErrBadCoinbaseHeight: "ErrBadCoinbaseHeight", - ErrScriptMalformed: "ErrScriptMalformed", - ErrScriptValidation: "ErrScriptValidation", + ErrDuplicateBlock: "ErrDuplicateBlock", + ErrBlockTooBig: "ErrBlockTooBig", + ErrBlockVersionTooOld: "ErrBlockVersionTooOld", + ErrBlockWeightTooHigh: "ErrBlockWeightTooHigh", + ErrInvalidTime: "ErrInvalidTime", + ErrTimeTooOld: "ErrTimeTooOld", + ErrTimeTooNew: "ErrTimeTooNew", + ErrDifficultyTooLow: "ErrDifficultyTooLow", + ErrUnexpectedDifficulty: "ErrUnexpectedDifficulty", + ErrHighHash: "ErrHighHash", + ErrBadMerkleRoot: "ErrBadMerkleRoot", + ErrBadCheckpoint: "ErrBadCheckpoint", + ErrForkTooOld: "ErrForkTooOld", + ErrCheckpointTimeTooOld: "ErrCheckpointTimeTooOld", + ErrNoTransactions: "ErrNoTransactions", + ErrTooManyTransactions: "ErrTooManyTransactions", + ErrNoTxInputs: "ErrNoTxInputs", + ErrNoTxOutputs: "ErrNoTxOutputs", + ErrTxTooBig: "ErrTxTooBig", + ErrBadTxOutValue: "ErrBadTxOutValue", + ErrDuplicateTxInputs: "ErrDuplicateTxInputs", + ErrBadTxInput: "ErrBadTxInput", + ErrMissingTx: "ErrMissingTx", + ErrUnfinalizedTx: "ErrUnfinalizedTx", + ErrDuplicateTx: "ErrDuplicateTx", + ErrOverwriteTx: "ErrOverwriteTx", + ErrImmatureSpend: "ErrImmatureSpend", + ErrDoubleSpend: "ErrDoubleSpend", + ErrSpendTooHigh: "ErrSpendTooHigh", + ErrBadFees: "ErrBadFees", + ErrTooManySigOps: "ErrTooManySigOps", + ErrFirstTxNotCoinbase: "ErrFirstTxNotCoinbase", + ErrMultipleCoinbases: "ErrMultipleCoinbases", + ErrBadCoinbaseScriptLen: "ErrBadCoinbaseScriptLen", + ErrBadCoinbaseValue: "ErrBadCoinbaseValue", + ErrMissingCoinbaseHeight: "ErrMissingCoinbaseHeight", + ErrBadCoinbaseHeight: "ErrBadCoinbaseHeight", + ErrScriptMalformed: "ErrScriptMalformed", + ErrScriptValidation: "ErrScriptValidation", + ErrUnexpectedWitness: "ErrUnexpectedWitness", + ErrInvalidWitnessCommitment: "ErrInvalidWitnessCommitment", + ErrWitnessCommitmentMismatch: "ErrWitnessCommitmentMismatch", } // String returns the ErrorCode as a human-readable name. diff --git a/blockchain/fullblocks_test.go b/blockchain/fullblocks_test.go index 2aba2233..52386a52 100644 --- a/blockchain/fullblocks_test.go +++ b/blockchain/fullblocks_test.go @@ -117,7 +117,7 @@ func TestFullBlocks(t *testing.T) { // Ensure there is an error due to deserializing the block. var msgBlock wire.MsgBlock - err := msgBlock.BtcDecode(bytes.NewReader(item.RawBlock), 0) + err := msgBlock.BtcDecode(bytes.NewReader(item.RawBlock), 0, wire.BaseEncoding) if _, ok := err.(*wire.MessageError); !ok { t.Fatalf("block %q (hash %s, height %d) should have "+ "failed to decode", item.Name, blockHash, diff --git a/blockchain/fullblocktests/generate.go b/blockchain/fullblocktests/generate.go index d14559d0..71d1ed93 100644 --- a/blockchain/fullblocktests/generate.go +++ b/blockchain/fullblocktests/generate.go @@ -309,7 +309,7 @@ func calcMerkleRoot(txns []*wire.MsgTx) chainhash.Hash { for _, tx := range txns { utilTxns = append(utilTxns, btcutil.NewTx(tx)) } - merkles := blockchain.BuildMerkleTreeStore(utilTxns) + merkles := blockchain.BuildMerkleTreeStore(utilTxns, false) return *merkles[len(merkles)-1] } @@ -635,10 +635,10 @@ func nonCanonicalVarInt(val uint32) []byte { // encoding. func encodeNonCanonicalBlock(b *wire.MsgBlock) []byte { var buf bytes.Buffer - b.Header.BtcEncode(&buf, 0) + b.Header.BtcEncode(&buf, 0, wire.BaseEncoding) buf.Write(nonCanonicalVarInt(uint32(len(b.Transactions)))) for _, tx := range b.Transactions { - tx.BtcEncode(&buf, 0) + tx.BtcEncode(&buf, 0, wire.BaseEncoding) } return buf.Bytes() } diff --git a/blockchain/scriptval.go b/blockchain/scriptval.go index 3310646e..33d31adc 100644 --- a/blockchain/scriptval.go +++ b/blockchain/scriptval.go @@ -8,6 +8,7 @@ import ( "fmt" "math" "runtime" + "time" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -19,6 +20,7 @@ type txValidateItem struct { txInIndex int txIn *wire.TxIn tx *btcutil.Tx + sigHashes *txscript.TxSigHashes } // txValidator provides a type which asynchronously validates transaction @@ -31,6 +33,7 @@ type txValidator struct { utxoView *UtxoViewpoint flags txscript.ScriptFlags sigCache *txscript.SigCache + hashCache *txscript.HashCache } // sendResult sends the result of a script pair validation on the internal @@ -83,15 +86,19 @@ out: // Create a new script engine for the script pair. sigScript := txIn.SignatureScript + witness := txIn.Witness + inputAmount := txEntry.AmountByIndex(originTxIndex) vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(), - txVI.txInIndex, v.flags, v.sigCache) + txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes, + inputAmount) if err != nil { str := fmt.Sprintf("failed to parse input "+ "%s:%d which references output %s:%d - "+ - "%v (input script bytes %x, prev output "+ - "script bytes %x)", txVI.tx.Hash(), - txVI.txInIndex, originTxHash, - originTxIndex, err, sigScript, pkScript) + "%v (input witness %x, input script "+ + "bytes %x, prev output script bytes %x)", + txVI.tx.Hash(), txVI.txInIndex, originTxHash, + originTxIndex, err, witness, sigScript, + pkScript) err := ruleError(ErrScriptMalformed, str) v.sendResult(err) break out @@ -101,10 +108,11 @@ out: if err := vm.Execute(); err != nil { str := fmt.Sprintf("failed to validate input "+ "%s:%d which references output %s:%d - "+ - "%v (input script bytes %x, prev output "+ - "script bytes %x)", txVI.tx.Hash(), - txVI.txInIndex, originTxHash, - originTxIndex, err, sigScript, pkScript) + "%v (input witness %x, input script "+ + "bytes %x, prev output script bytes %x)", + txVI.tx.Hash(), txVI.txInIndex, + originTxHash, originTxIndex, err, + witness, sigScript, pkScript) err := ruleError(ErrScriptValidation, str) v.sendResult(err) break out @@ -179,20 +187,38 @@ func (v *txValidator) Validate(items []*txValidateItem) error { // newTxValidator returns a new instance of txValidator to be used for // validating transaction scripts asynchronously. -func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) *txValidator { +func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags, + sigCache *txscript.SigCache, hashCache *txscript.HashCache) *txValidator { return &txValidator{ validateChan: make(chan *txValidateItem), quitChan: make(chan struct{}), resultChan: make(chan error), utxoView: utxoView, sigCache: sigCache, + hashCache: hashCache, flags: flags, } } // ValidateTransactionScripts validates the scripts for the passed transaction // using multiple goroutines. -func ValidateTransactionScripts(tx *btcutil.Tx, utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error { +func ValidateTransactionScripts(tx *btcutil.Tx, utxoView *UtxoViewpoint, + flags txscript.ScriptFlags, sigCache *txscript.SigCache, + hashCache *txscript.HashCache) error { + + // If the hashcache doesn't yet has the sighash midstate for this + // transaction, then we'll compute them now so we can re-use them + // amongst all worker validation goroutines. + if !hashCache.ContainsHashes(tx.Hash()) { + hashCache.AddSigHashes(tx.MsgTx()) + } + + // The same pointer to the transaction's sighash midstate will be + // re-used amongst all validation goroutines. By pre-computing the + // sighash here instead of during validation, we ensure the sighashes + // are only computed once. + cachedHashes, _ := hashCache.GetSigHashes(tx.Hash()) + // Collect all of the transaction inputs and required information for // validation. txIns := tx.MsgTx().TxIn @@ -207,18 +233,22 @@ func ValidateTransactionScripts(tx *btcutil.Tx, utxoView *UtxoViewpoint, flags t txInIndex: txInIdx, txIn: txIn, tx: tx, + sigHashes: cachedHashes, } txValItems = append(txValItems, txVI) } // Validate all of the inputs. - validator := newTxValidator(utxoView, flags, sigCache) + validator := newTxValidator(utxoView, flags, sigCache, hashCache) return validator.Validate(txValItems) } // checkBlockScripts executes and validates the scripts for all transactions in // the passed block using multiple goroutines. -func checkBlockScripts(block *btcutil.Block, utxoView *UtxoViewpoint, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error { +func checkBlockScripts(block *btcutil.Block, utxoView *UtxoViewpoint, + scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache, + hashCache *txscript.HashCache) error { + // Collect all of the transaction inputs and required information for // validation for all transactions in the block into a single slice. numInputs := 0 @@ -227,6 +257,28 @@ func checkBlockScripts(block *btcutil.Block, utxoView *UtxoViewpoint, scriptFlag } txValItems := make([]*txValidateItem, 0, numInputs) for _, tx := range block.Transactions() { + hash := tx.Hash() + + // If the HashCache is present, and it doesn't yet contain the + // partial sighashes for this transaction, then we add the + // sighashes for the transaction. This allows us to take + // advantage of the potential speed savings due to the new + // digest algorithm (BIP0143). + if segwitActive && tx.MsgTx().HasWitness() && hashCache != nil && + !hashCache.ContainsHashes(hash) { + + hashCache.AddSigHashes(tx.MsgTx()) + } + + var cachedHashes *txscript.TxSigHashes + if segwitActive && tx.MsgTx().HasWitness() { + if hashCache != nil { + cachedHashes, _ = hashCache.GetSigHashes(hash) + } else { + cachedHashes = txscript.NewTxSigHashes(tx.MsgTx()) + } + } + for txInIdx, txIn := range tx.MsgTx().TxIn { // Skip coinbases. if txIn.PreviousOutPoint.Index == math.MaxUint32 { @@ -237,12 +289,30 @@ func checkBlockScripts(block *btcutil.Block, utxoView *UtxoViewpoint, scriptFlag txInIndex: txInIdx, txIn: txIn, tx: tx, + sigHashes: cachedHashes, } txValItems = append(txValItems, txVI) } } // Validate all of the inputs. - validator := newTxValidator(utxoView, scriptFlags, sigCache) - return validator.Validate(txValItems) + validator := newTxValidator(utxoView, scriptFlags, sigCache, hashCache) + start := time.Now() + if err := validator.Validate(txValItems); err != nil { + return err + } + elapsed := time.Since(start) + + log.Tracef("block %v took %v to verify", block.Hash(), elapsed) + + // If the HashCache is present, once we have validated the block, we no + // longer need the cached hashes for these transactions, so we purge + // them from the cache. + if hashCache != nil { + for _, tx := range block.Transactions() { + hashCache.PurgeSigHashes(tx.Hash()) + } + } + + return nil } diff --git a/blockchain/scriptval_test.go b/blockchain/scriptval_test.go index 2368dd61..62a44b24 100644 --- a/blockchain/scriptval_test.go +++ b/blockchain/scriptval_test.go @@ -43,7 +43,7 @@ func TestCheckBlockScripts(t *testing.T) { scriptFlags := txscript.ScriptBip16 err = blockchain.TstCheckBlockScripts(blocks[0], view, scriptFlags, - nil) + nil, nil) if err != nil { t.Errorf("Transaction script validation failed: %v\n", err) return diff --git a/blockchain/validate.go b/blockchain/validate.go index 7c25ff9e..8fb33df7 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -19,10 +19,6 @@ import ( ) const ( - // MaxSigOpsPerBlock is the maximum number of signature operations - // allowed for a block. It is a fraction of the max block payload size. - MaxSigOpsPerBlock = wire.MaxBlockPayload / 50 - // MaxTimeOffsetSeconds is the maximum number of seconds a block time // is allowed to be ahead of the current time. This is currently 2 // hours. @@ -220,10 +216,10 @@ func CheckTransactionSanity(tx *btcutil.Tx) error { // A transaction must not exceed the maximum allowed block payload when // serialized. - serializedTxSize := tx.MsgTx().SerializeSize() - if serializedTxSize > wire.MaxBlockPayload { + serializedTxSize := tx.MsgTx().SerializeSizeStripped() + if serializedTxSize > MaxBlockBaseSize { str := fmt.Sprintf("serialized transaction is too big - got "+ - "%d, max %d", serializedTxSize, wire.MaxBlockPayload) + "%d, max %d", serializedTxSize, MaxBlockBaseSize) return ruleError(ErrTxTooBig, str) } @@ -494,10 +490,10 @@ func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median // A block must not exceed the maximum allowed block payload when // serialized. - serializedSize := msgBlock.SerializeSize() - if serializedSize > wire.MaxBlockPayload { + serializedSize := msgBlock.SerializeSizeStripped() + if serializedSize > MaxBlockBaseSize { str := fmt.Sprintf("serialized block is too big - got %d, "+ - "max %d", serializedSize, wire.MaxBlockPayload) + "max %d", serializedSize, MaxBlockBaseSize) return ruleError(ErrBlockTooBig, str) } @@ -532,7 +528,7 @@ func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median // checks. Bitcoind builds the tree here and checks the merkle root // after the following checks, but there is no reason not to check the // merkle root matches here. - merkles := BuildMerkleTreeStore(block.Transactions()) + merkles := BuildMerkleTreeStore(block.Transactions(), false) calculatedMerkleRoot := merkles[len(merkles)-1] if !header.MerkleRoot.IsEqual(calculatedMerkleRoot) { str := fmt.Sprintf("block merkle root is invalid - block "+ @@ -541,6 +537,17 @@ func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median return ruleError(ErrBadMerkleRoot, str) } + // Next, validate the witness commitment (if any) within the block. + // This involves asserting that if the coinbase contains the special + // commitment output, then this merkle root matches a computed merkle + // root of all the wtxid's of the transactions within the block. In + // addition, various other checks against the coinbase's witness stack. + // TODO(roasbeef): only perform this check if we expect block to have a + // witness commitment + if err := ValidateWitnessCommitment(block); err != nil { + return err + } + // Check for duplicate transactions. This check will be fairly quick // since the transaction hashes are already cached due to building the // merkle tree above. @@ -562,11 +569,11 @@ func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median // We could potentially overflow the accumulator so check for // overflow. lastSigOps := totalSigOps - totalSigOps += CountSigOps(tx) - if totalSigOps < lastSigOps || totalSigOps > MaxSigOpsPerBlock { + totalSigOps += (CountSigOps(tx) * WitnessScaleFactor) + if totalSigOps < lastSigOps || totalSigOps > MaxBlockSigOpsCost { str := fmt.Sprintf("block contains too many signature "+ "operations - got %v, max %v", totalSigOps, - MaxSigOpsPerBlock) + MaxBlockSigOpsCost) return ruleError(ErrTooManySigOps, str) } } @@ -801,6 +808,42 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode return err } } + + // Query for the Version Bits state for the segwit soft-fork + // deployment. If segwit is active, we'll switch over to + // enforcing all the new rules. + segwitState, err := b.deploymentState(prevNode, + chaincfg.DeploymentSegwit) + if err != nil { + return err + } + + // If segwit is active, then we'll need to fully validate the + // new witness commitment for adherance to the rules. + if segwitState == ThresholdActive { + // Validate the witness commitment (if any) within the + // block. This involves asserting that if the coinbase + // contains the special commitment output, then this + // merkle root matches a computed merkle root of all + // the wtxid's of the transactions within the block. In + // addition, various other checks against the + // coinbase's witness stack. + if err := ValidateWitnessCommitment(block); err != nil { + return err + } + + // Once the witness commitment, witness nonce, and sig + // op cost have been validated, we can finally assert + // that the block's weight doesn't exceed the current + // consensus parameter. + blockWeight := GetBlockWeight(block) + if blockWeight > MaxBlockWeight { + str := fmt.Sprintf("block's weight metric is "+ + "too high - got %v, max %v", + blockWeight, MaxBlockWeight) + return ruleError(ErrBlockVersionTooOld, str) + } + } } return nil @@ -1033,6 +1076,8 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi // after the timestamp defined by txscript.Bip16Activation. See // https://en.bitcoin.it/wiki/BIP_0016 for more details. enforceBIP0016 := node.timestamp >= txscript.Bip16Activation.Unix() + // TODO(roasbeef): should check flag, consult bip 9 log etc + enforceSegWit := true // The number of signature operations must be less than the maximum // allowed per block. Note that the preliminary sanity checks on a @@ -1041,31 +1086,29 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi // signature operations in each of the input transaction public key // scripts. transactions := block.Transactions() - totalSigOps := 0 + totalSigOpCost := 0 for i, tx := range transactions { - numsigOps := CountSigOps(tx) - if enforceBIP0016 { - // 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. - numP2SHSigOps, err := CountP2SHSigOps(tx, i == 0, view) - if err != nil { - return err - } - numsigOps += numP2SHSigOps + // 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 := GetSigOpCost(tx, i == 0, view, enforceBIP0016, + enforceSegWit) + 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. - lastSigops := totalSigOps - totalSigOps += numsigOps - if totalSigOps < lastSigops || totalSigOps > MaxSigOpsPerBlock { + lastSigOpCost := totalSigOpCost + totalSigOpCost += sigOpCost + if totalSigOpCost < lastSigOpCost || totalSigOpCost > MaxBlockSigOpsCost { + // TODO(roasbeef): modify error str := fmt.Sprintf("block contains too many "+ "signature operations - got %v, max %v", - totalSigOps, MaxSigOpsPerBlock) + totalSigOpCost, MaxBlockSigOpsCost) return ruleError(ErrTooManySigOps, str) } } @@ -1196,6 +1239,8 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi } } + // TODO(roasbeef): check bip9 for segwit here, others also + scriptFlags |= txscript.ScriptVerifyWitness scriptFlags |= txscript.ScriptStrictMultiSig // Now that the inexpensive checks are done and have passed, verify the @@ -1203,7 +1248,8 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi // expensive ECDSA signature check scripts. Doing this last helps // prevent CPU exhaustion attacks. if runScripts { - err := checkBlockScripts(block, view, scriptFlags, b.sigCache) + err := checkBlockScripts(block, view, scriptFlags, b.sigCache, + b.hashCache) if err != nil { return err } diff --git a/blockchain/validate_test.go b/blockchain/validate_test.go index 2673950b..8ce3a14a 100644 --- a/blockchain/validate_test.go +++ b/blockchain/validate_test.go @@ -112,7 +112,7 @@ func TestCheckSerializedHeight(t *testing.T) { // Create an empty coinbase template to be used in the tests below. coinbaseOutpoint := wire.NewOutPoint(&chainhash.Hash{}, math.MaxUint32) coinbaseTx := wire.NewMsgTx(1) - coinbaseTx.AddTxIn(wire.NewTxIn(coinbaseOutpoint, nil)) + coinbaseTx.AddTxIn(wire.NewTxIn(coinbaseOutpoint, nil, nil)) // Expected rule errors. missingHeightError := blockchain.RuleError{