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%
This commit is contained in:
Dave Collins 2019-03-13 01:11:31 -05:00 committed by Roy Lee
parent 8de1bc3ecc
commit b607be0852

View file

@ -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