From 5f771c1aaa5db7427011de1478182ed0d95ecf37 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 15:15:34 -0800 Subject: [PATCH] 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% --- txscript/script.go | 7 ++----- txscript/standard.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 7f812207..691e31cc 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -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, diff --git a/txscript/standard.go b/txscript/standard.go index a53180f1..9825f61b 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -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 + // + // 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 {