txscript: Optimize IsPushOnlyScript.

This converts the IsPushOnlyScript function to make use of the new
tokenizer instead of the far less efficient parseScript thereby
significantly optimizing the function.

It also deprecates the isPushOnly function that requires opcodes in
favor of the new function and modifies the comment on IsPushOnlyScript
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
BenchmarkIsPushOnlyScript-8     62412         622           -99.00%

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

benchmark                       old bytes     new bytes     delta
BenchmarkIsPushOnlyScript-8     311299        0             -100.00%
This commit is contained in:
Dave Collins 2019-03-13 01:11:26 -05:00 committed by Roy Lee
parent 2b5edd2b5e
commit 02ddaf29fd

View file

@ -182,6 +182,8 @@ func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) {
} }
// isPushOnly returns true if the script only pushes data, false otherwise. // isPushOnly returns true if the script only pushes data, false otherwise.
//
// DEPRECATED. Use IsPushOnlyScript instead.
func isPushOnly(pops []parsedOpcode) bool { func isPushOnly(pops []parsedOpcode) bool {
// NOTE: This function does NOT verify opcodes directly since it is // NOTE: This function does NOT verify opcodes directly since it is
// internal and is only called with parsed opcodes for scripts that did // internal and is only called with parsed opcodes for scripts that did
@ -199,15 +201,27 @@ func isPushOnly(pops []parsedOpcode) bool {
return true return true
} }
// IsPushOnlyScript returns whether or not the passed script only pushes data. // IsPushOnlyScript returns whether or not the passed script only pushes data
// according to the consensus definition of pushing data.
// //
// False will be returned when the script does not parse. // 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 checking if it is a push only script which means nodes
// on existing rules will treat new version scripts as if they were version 0.
func IsPushOnlyScript(script []byte) bool { func IsPushOnlyScript(script []byte) bool {
pops, err := parseScript(script) const scriptVersion = 0
if err != nil { tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
// All opcodes up to OP_16 are data push instructions.
// NOTE: This does consider OP_RESERVED to be a data push instruction,
// but execution of OP_RESERVED will fail anyway and matches the
// behavior required by consensus.
if tokenizer.Opcode() > OP_16 {
return false return false
} }
return isPushOnly(pops) }
return tokenizer.Err() == nil
} }
// parseScriptTemplate is the same as parseScript but allows the passing of the // parseScriptTemplate is the same as parseScript but allows the passing of the