txscript: Optimize IsNullData

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

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

benchmark                       old ns/op     new ns/op     delta
BenchmarkIsNullDataScript-8     62495         2.65          -100.00%

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

benchmark                       old bytes     new bytes     delta
BenchmarkIsNullDataScript-8     311299        0             -100.00%
This commit is contained in:
Conner Fromknecht 2021-02-04 15:15:34 -08:00 committed by Roy Lee
parent 4878db49cf
commit b4f144ad8c
2 changed files with 37 additions and 5 deletions

View file

@ -154,11 +154,8 @@ func isWitnessProgram(pops []parsedOpcode) bool {
// IsNullData returns true if the passed script is a null data script, false
// otherwise.
func IsNullData(script []byte) bool {
pops, err := parseScript(script)
if err != nil {
return false
}
return isNullData(pops)
const scriptVersion = 0
return isNullDataScript(scriptVersion, script)
}
// ExtractWitnessProgramInfo attempts to extract the witness program version,

View file

@ -461,6 +461,41 @@ func isNullData(pops []parsedOpcode) bool {
len(pops[1].data) <= MaxDataCarrierSize
}
// isNullDataScript returns whether or not the passed script is a standard
// null data script.
//
// NOTE: This function is only valid for version 0 scripts. It will always
// return false for other script versions.
func isNullDataScript(scriptVersion uint16, script []byte) bool {
// The only currently supported script version is 0.
if scriptVersion != 0 {
return false
}
// A null script is of the form:
// OP_RETURN <optional data>
//
// Thus, it can either be a single OP_RETURN or an OP_RETURN followed by a
// data push up to MaxDataCarrierSize bytes.
// The script can't possibly be a a null data script if it doesn't start
// with OP_RETURN. Fail fast to avoid more work below.
if len(script) < 1 || script[0] != OP_RETURN {
return false
}
// Single OP_RETURN.
if len(script) == 1 {
return true
}
// OP_RETURN followed by data push up to MaxDataCarrierSize bytes.
tokenizer := MakeScriptTokenizer(scriptVersion, script[1:])
return tokenizer.Next() && tokenizer.Done() &&
(isSmallInt(tokenizer.Opcode()) || tokenizer.Opcode() <= OP_PUSHDATA4) &&
len(tokenizer.Data()) <= MaxDataCarrierSize
}
// scriptType returns the type of the script being inspected from the known
// standard types.
func typeOfScript(pops []parsedOpcode) ScriptClass {