From a4a21c0b087b9e73ec33def05d4a8d06671eea16 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:31 -0500 Subject: [PATCH] txscript: Optimize GetPreciseSigOpCount. This converts the GetPreciseSigOpCount function to use a combination of raw script analysis and the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In particular it uses the recently converted isScriptHashScript, IsPushOnlyScript, and countSigOpsV0 functions along with the recently added finalOpcodeData functions. It also modifies the comment to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkGetPreciseSigOpCount-8 130223 742 -99.43% benchmark old allocs new allocs delta BenchmarkGetPreciseSigOpCount-8 3 0 -100.00% benchmark old bytes new bytes delta BenchmarkGetPreciseSigOpCount-8 623367 0 -100.00% --- txscript/script.go | 48 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 32654f78..aee1aaac 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -864,44 +864,44 @@ func finalOpcodeData(scriptVersion uint16, script []byte) []byte { // Pay-To-Script-Hash script in order to find the precise number of signature // operations in the transaction. If the script fails to parse, then the count // up to the point of failure is returned. -func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int { - // Don't check error since parseScript returns the parsed-up-to-error - // list of pops. - pops, _ := parseScript(scriptPubKey) +// +// WARNING: This function always treats the passed script as version 0. Great +// care must be taken if introducing a new script version because it is used in +// consensus which, unfortunately as of the time of this writing, does not check +// script versions before counting their signature operations which means nodes +// on existing rules will count new version scripts as if they were version 0. +// +// The third parameter is DEPRECATED and is unused. +func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, _ bool) int { + const scriptVersion = 0 - // Treat non P2SH transactions as normal. - if !(bip16 && isScriptHash(pops)) { - return getSigOpCount(pops, true) - } - - // The public key script is a pay-to-script-hash, so parse the signature - // script to get the final item. Scripts that fail to fully parse count - // as 0 signature operations. - sigPops, err := parseScript(scriptSig) - if err != nil { - return 0 + // Treat non P2SH transactions as normal. Note that signature operation + // counting includes all operations up to the first parse failure. + if !isScriptHashScript(scriptPubKey) { + return countSigOpsV0(scriptPubKey, true) } // The signature script must only push data to the stack for P2SH to be // a valid pair, so the signature operation count is 0 when that is not // the case. - if !isPushOnly(sigPops) || len(sigPops) == 0 { + if len(scriptSig) == 0 || !IsPushOnlyScript(scriptSig) { return 0 } // The P2SH script is the last item the signature script pushes to the // stack. When the script is empty, there are no signature operations. - shScript := sigPops[len(sigPops)-1].data - if len(shScript) == 0 { + // + // Notice that signature scripts that fail to fully parse count as 0 + // signature operations unlike public key and redeem scripts. + redeemScript := finalOpcodeData(scriptVersion, scriptSig) + if len(redeemScript) == 0 { return 0 } - // Parse the P2SH script and don't check the error since parseScript - // returns the parsed-up-to-error list of pops and the consensus rules - // dictate signature operations are counted up to the first parse - // failure. - shPops, _ := parseScript(shScript) - return getSigOpCount(shPops, true) + // Return the more precise sigops count for the redeem script. Note that + // signature operation counting includes all operations up to the first + // parse failure. + return countSigOpsV0(redeemScript, true) } // GetWitnessSigOpCount returns the number of signature operations generated by