txscript: Optimize IsPayToScriptHash.

This converts the IsPayToScriptHash function to analyze the raw script
instead of using the far less efficient parseScript thereby
significantly optimizing the function.

In order to accomplish this, it introduces two new functions.  The first
one is named extractScriptHash and works with the raw script bytes to
simultaneously determine if the script is a p2sh script, and in the case
it is, extract and return the hash.  The second new function is named
isScriptHashScript and is defined in terms of the former.

The extract function approach was chosen because it is common for
callers to want to only extract relevant details from a script if the
script is of the specific type.  Extracting those details requires
performing the exact same checks to ensure the script is of the correct
type, so it is more efficient to combine the two into one and define the
type determination in terms of the result so long as the extraction does
not require allocations.

Finally, this also deprecates the isScriptHash function that requires
opcodes in favor of the new functions and modifies the comment on
IsPayToScriptHash to explicitly call out the script version semantics.

The following is a before and after comparison of analyzing a large
script that is not a p2sh script:

benchmark                        old ns/op     new ns/op     delta
BenchmarkIsPayToScriptHash-8     62393         0.60          -100.00%

benchmark                        old allocs     new allocs     delta
BenchmarkIsPayToScriptHash-8     1              0              -100.00%

benchmark                        old bytes     new bytes     delta
BenchmarkIsPayToScriptHash-8     311299        0             -100.00%
This commit is contained in:
Dave Collins 2019-03-13 01:11:14 -05:00 committed by Roy Lee
parent e7228a2e5f
commit 69aefa65e6
2 changed files with 35 additions and 5 deletions

View file

@ -56,6 +56,8 @@ func isSmallInt(op byte) bool {
// isScriptHash returns true if the script passed is a pay-to-script-hash // isScriptHash returns true if the script passed is a pay-to-script-hash
// transaction, false otherwise. // transaction, false otherwise.
//
// DEPRECATED. Use isScriptHashScript or extractScriptHash instead.
func isScriptHash(pops []parsedOpcode) bool { func isScriptHash(pops []parsedOpcode) bool {
return len(pops) == 3 && return len(pops) == 3 &&
pops[0].opcode.value == OP_HASH160 && pops[0].opcode.value == OP_HASH160 &&
@ -77,12 +79,14 @@ func IsPayToPubKeyHash(script []byte) bool {
// IsPayToScriptHash returns true if the script is in the standard // IsPayToScriptHash returns true if the script is in the standard
// pay-to-script-hash (P2SH) format, false otherwise. // pay-to-script-hash (P2SH) format, false otherwise.
//
// 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 determining if the script is a P2SH which means nodes
// on existing rules will analyze new version scripts as if they were version 0.
func IsPayToScriptHash(script []byte) bool { func IsPayToScriptHash(script []byte) bool {
pops, err := parseScript(script) return isScriptHashScript(script)
if err != nil {
return false
}
return isScriptHash(pops)
} }
// isWitnessScriptHash returns true if the passed script is a // isWitnessScriptHash returns true if the passed script is a

View file

@ -167,6 +167,32 @@ func isPubKeyHashScript(script []byte) bool {
return extractPubKeyHash(script) != nil return extractPubKeyHash(script) != nil
} }
// extractScriptHash extracts the script hash from the passed script if it is a
// standard pay-to-script-hash script. It will return nil otherwise.
//
// NOTE: This function is only valid for version 0 opcodes. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func extractScriptHash(script []byte) []byte {
// A pay-to-script-hash script is of the form:
// OP_HASH160 <20-byte scripthash> OP_EQUAL
if len(script) == 23 &&
script[0] == OP_HASH160 &&
script[1] == OP_DATA_20 &&
script[22] == OP_EQUAL {
return script[2:22]
}
return nil
}
// isScriptHashScript returns whether or not the passed script is a standard
// pay-to-script-hash script.
func isScriptHashScript(script []byte) bool {
return extractScriptHash(script) != nil
}
// isPubkey returns true if the script passed is a pay-to-pubkey transaction, // isPubkey returns true if the script passed is a pay-to-pubkey transaction,
// false otherwise. // false otherwise.
func isPubkey(pops []parsedOpcode) bool { func isPubkey(pops []parsedOpcode) bool {