txscript: Make op callbacks take opcode and data.
This converts the callback function defined on the internal opcode struct to accept the opcode and data slice instead of a parsed opcode as the final step towards removing the parsed opcode struct and associated supporting code altogether. It also updates all of the callbacks and tests accordingly and finally removes the now unused parsedOpcode struct. The final results for the raw script analysis and tokenizer optimizations are as follows: benchmark old ns/op new ns/op delta BenchmarkIsPayToScriptHash-8 62393 0.51 -100.00% BenchmarkIsPubKeyHashScript-8 62228 0.56 -100.00% BenchmarkGetSigOpCount-8 61051 658 -98.92% BenchmarkExtractPkScriptAddrsLarge-8 60713 17.2 -99.97% BenchmarkExtractPkScriptAddrs-8 289 17.9 -93.81% BenchmarkIsWitnessPubKeyHash-8 61688 0.42 -100.00% BenchmarkIsUnspendable-8 656 520 -20.73% BenchmarkExtractAtomicSwapDataPushesLarge-8 61332 44.0 -99.93% BenchmarkExtractAtomicSwapDataPushes-8 990 260 -73.74% BenchmarkDisasmString-8 102902 39754 -61.37% BenchmarkGetPreciseSigOpCount-8 130223 715 -99.45% BenchmarkScriptParsing-8 63464 681 -98.93% BenchmarkIsMultisigScriptLarge-8 64166 5.83 -99.99% BenchmarkIsMultisigScript-8 630 58.5 -90.71% BenchmarkPushedData-8 64837 1779 -97.26% BenchmarkCalcSigHash-8 3627895 3605459 -0.62% BenchmarkIsPubKeyScript-8 62323 2.83 -100.00% BenchmarkIsPushOnlyScript-8 62412 569 -99.09% BenchmarkIsWitnessScriptHash-8 61243 0.56 -100.00% BenchmarkGetScriptClass-8 61515 16.4 -99.97% BenchmarkIsNullDataScript-8 62495 2.53 -100.00% BenchmarkIsMultisigSigScriptLarge-8 69328 2.52 -100.00% BenchmarkIsMultisigSigScript-8 2375 141 -94.06% BenchmarkGetWitnessSigOpCountP2WKH-8 504 72.0 -85.71% BenchmarkGetWitnessSigOpCountNested-8 1158 136 -88.26% BenchmarkIsWitnessPubKeyHash-8 68927 0.53 -100.00% BenchmarkIsWitnessScriptHash-8 62774 0.63 -100.00% benchmark old allocs new allocs delta BenchmarkIsPayToScriptHash-8 1 0 -100.00% BenchmarkIsPubKeyHashScript-8 1 0 -100.00% BenchmarkGetSigOpCount-8 1 0 -100.00% BenchmarkExtractPkScriptAddrsLarge-8 1 0 -100.00% BenchmarkExtractPkScriptAddrs-8 1 0 -100.00% BenchmarkIsWitnessPubKeyHash-8 1 0 -100.00% BenchmarkIsUnspendable-8 1 0 -100.00% BenchmarkExtractAtomicSwapDataPushesLarge-8 1 0 -100.00% BenchmarkExtractAtomicSwapDataPushes-8 2 1 -50.00% BenchmarkDisasmString-8 46 51 +10.87% BenchmarkGetPreciseSigOpCount-8 3 0 -100.00% BenchmarkScriptParsing-8 1 0 -100.00% BenchmarkIsMultisigScriptLarge-8 1 0 -100.00% BenchmarkIsMultisigScript-8 1 0 -100.00% BenchmarkPushedData-8 7 6 -14.29% BenchmarkCalcSigHash-8 1335 712 -46.67% BenchmarkIsPubKeyScript-8 1 0 -100.00% BenchmarkIsPushOnlyScript-8 1 0 -100.00% BenchmarkIsWitnessScriptHash-8 1 0 -100.00% BenchmarkGetScriptClass-8 1 0 -100.00% BenchmarkIsNullDataScript-8 1 0 -100.00% BenchmarkIsMultisigSigScriptLarge-8 5 0 -100.00% BenchmarkIsMultisigSigScript-8 3 0 -100.00% BenchmarkGetWitnessSigOpCountP2WKH-8 2 0 -100.00% BenchmarkGetWitnessSigOpCountNested-8 4 0 -100.00% BenchmarkIsWitnessPubKeyHash-8 1 0 -100.00% BenchmarkIsWitnessScriptHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsPayToScriptHash-8 311299 0 -100.00% BenchmarkIsPubKeyHashScript-8 311299 0 -100.00% BenchmarkGetSigOpCount-8 311299 0 -100.00% BenchmarkExtractPkScriptAddrsLarge-8 311299 0 -100.00% BenchmarkExtractPkScriptAddrs-8 768 0 -100.00% BenchmarkIsWitnessPubKeyHash-8 311299 0 -100.00% BenchmarkIsUnspendable-8 1 0 -100.00% BenchmarkExtractAtomicSwapDataPushesLarge-8 311299 0 -100.00% BenchmarkExtractAtomicSwapDataPushes-8 3168 96 -96.97% BenchmarkDisasmString-8 389324 130552 -66.47% BenchmarkGetPreciseSigOpCount-8 623367 0 -100.00% BenchmarkScriptParsing-8 311299 0 -100.00% BenchmarkIsMultisigScriptLarge-8 311299 0 -100.00% BenchmarkIsMultisigScript-8 2304 0 -100.00% BenchmarkPushedData-8 312816 1520 -99.51% BenchmarkCalcSigHash-8 1373812 1290507 -6.06% BenchmarkIsPubKeyScript-8 311299 0 -100.00% BenchmarkIsPushOnlyScript-8 311299 0 -100.00% BenchmarkIsWitnessScriptHash-8 311299 0 -100.00% BenchmarkGetScriptClass-8 311299 0 -100.00% BenchmarkIsNullDataScript-8 311299 0 -100.00% BenchmarkIsMultisigSigScriptLarge-8 330035 0 -100.00% BenchmarkIsMultisigSigScript-8 9472 0 -100.00% BenchmarkGetWitnessSigOpCountP2WKH-8 1408 0 -100.00% BenchmarkGetWitnessSigOpCountNested-8 3200 0 -100.00% BenchmarkIsWitnessPubKeyHash-8 311299 0 -100.00% BenchmarkIsWitnessScriptHash-8 311299 0 -100.00%
This commit is contained in:
parent
ef3d06e62b
commit
b95ba0ac95
3 changed files with 90 additions and 100 deletions
|
@ -385,8 +385,7 @@ func (vm *Engine) executeOpcode(op *opcode, data []byte) error {
|
|||
}
|
||||
}
|
||||
|
||||
pop := parsedOpcode{opcode: op, data: data}
|
||||
return op.opfunc(&pop, vm)
|
||||
return op.opfunc(op, data, vm)
|
||||
}
|
||||
|
||||
// checkValidPC returns an error if the current script position is not valid for
|
||||
|
|
|
@ -28,7 +28,7 @@ type opcode struct {
|
|||
value byte
|
||||
name string
|
||||
length int
|
||||
opfunc func(*parsedOpcode, *Engine) error
|
||||
opfunc func(*opcode, []byte, *Engine) error
|
||||
}
|
||||
|
||||
// These constants are the values of the official opcodes used on the btc wiki,
|
||||
|
@ -611,13 +611,6 @@ var opcodeOnelineRepls = map[string]string{
|
|||
"OP_16": "16",
|
||||
}
|
||||
|
||||
// parsedOpcode represents an opcode that has been parsed and includes any
|
||||
// potential data associated with it.
|
||||
type parsedOpcode struct {
|
||||
opcode *opcode
|
||||
data []byte
|
||||
}
|
||||
|
||||
// disasmOpcode writes a human-readable disassembly of the provided opcode and
|
||||
// data into the provided buffer. The compact flag indicates the disassembly
|
||||
// should print a more compact representation of data-carrying and small integer
|
||||
|
@ -676,45 +669,42 @@ func disasmOpcode(buf *strings.Builder, op *opcode, data []byte, compact bool) {
|
|||
// opcodes before executing in an initial parse step, the consensus rules
|
||||
// dictate the script doesn't fail until the program counter passes over a
|
||||
// disabled opcode (even when they appear in a branch that is not executed).
|
||||
func opcodeDisabled(op *parsedOpcode, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute disabled opcode %s",
|
||||
op.opcode.name)
|
||||
func opcodeDisabled(op *opcode, data []byte, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name)
|
||||
return scriptError(ErrDisabledOpcode, str)
|
||||
}
|
||||
|
||||
// opcodeReserved is a common handler for all reserved opcodes. It returns an
|
||||
// appropriate error indicating the opcode is reserved.
|
||||
func opcodeReserved(op *parsedOpcode, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute reserved opcode %s",
|
||||
op.opcode.name)
|
||||
func opcodeReserved(op *opcode, data []byte, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute reserved opcode %s", op.name)
|
||||
return scriptError(ErrReservedOpcode, str)
|
||||
}
|
||||
|
||||
// opcodeInvalid is a common handler for all invalid opcodes. It returns an
|
||||
// appropriate error indicating the opcode is invalid.
|
||||
func opcodeInvalid(op *parsedOpcode, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute invalid opcode %s",
|
||||
op.opcode.name)
|
||||
func opcodeInvalid(op *opcode, data []byte, vm *Engine) error {
|
||||
str := fmt.Sprintf("attempt to execute invalid opcode %s", op.name)
|
||||
return scriptError(ErrReservedOpcode, str)
|
||||
}
|
||||
|
||||
// opcodeFalse pushes an empty array to the data stack to represent false. Note
|
||||
// that 0, when encoded as a number according to the numeric encoding consensus
|
||||
// rules, is an empty array.
|
||||
func opcodeFalse(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeFalse(op *opcode, data []byte, vm *Engine) error {
|
||||
vm.dstack.PushByteArray(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// opcodePushData is a common handler for the vast majority of opcodes that push
|
||||
// raw data (bytes) to the data stack.
|
||||
func opcodePushData(op *parsedOpcode, vm *Engine) error {
|
||||
vm.dstack.PushByteArray(op.data)
|
||||
func opcodePushData(op *opcode, data []byte, vm *Engine) error {
|
||||
vm.dstack.PushByteArray(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// opcode1Negate pushes -1, encoded as a number, to the data stack.
|
||||
func opcode1Negate(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode1Negate(op *opcode, data []byte, vm *Engine) error {
|
||||
vm.dstack.PushInt(scriptNum(-1))
|
||||
return nil
|
||||
}
|
||||
|
@ -722,23 +712,24 @@ func opcode1Negate(op *parsedOpcode, vm *Engine) error {
|
|||
// opcodeN is a common handler for the small integer data push opcodes. It
|
||||
// pushes the numeric value the opcode represents (which will be from 1 to 16)
|
||||
// onto the data stack.
|
||||
func opcodeN(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeN(op *opcode, data []byte, vm *Engine) error {
|
||||
// The opcodes are all defined consecutively, so the numeric value is
|
||||
// the difference.
|
||||
vm.dstack.PushInt(scriptNum((op.opcode.value - (OP_1 - 1))))
|
||||
vm.dstack.PushInt(scriptNum((op.value - (OP_1 - 1))))
|
||||
return nil
|
||||
}
|
||||
|
||||
// opcodeNop is a common handler for the NOP family of opcodes. As the name
|
||||
// implies it generally does nothing, however, it will return an error when
|
||||
// the flag to discourage use of NOPs is set for select opcodes.
|
||||
func opcodeNop(op *parsedOpcode, vm *Engine) error {
|
||||
switch op.opcode.value {
|
||||
func opcodeNop(op *opcode, data []byte, vm *Engine) error {
|
||||
switch op.value {
|
||||
case OP_NOP1, OP_NOP4, OP_NOP5,
|
||||
OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10:
|
||||
|
||||
if vm.hasFlag(ScriptDiscourageUpgradableNops) {
|
||||
str := fmt.Sprintf("OP_NOP%d reserved for soft-fork "+
|
||||
"upgrades", op.opcode.value-(OP_NOP1-1))
|
||||
str := fmt.Sprintf("%v reserved for soft-fork "+
|
||||
"upgrades", op.name)
|
||||
return scriptError(ErrDiscourageUpgradableNOPs, str)
|
||||
}
|
||||
}
|
||||
|
@ -801,7 +792,7 @@ func popIfBool(vm *Engine) (bool, error) {
|
|||
//
|
||||
// Data stack transformation: [... bool] -> [...]
|
||||
// Conditional stack transformation: [...] -> [... OpCondValue]
|
||||
func opcodeIf(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeIf(op *opcode, data []byte, vm *Engine) error {
|
||||
condVal := OpCondFalse
|
||||
if vm.isBranchExecuting() {
|
||||
ok, err := popIfBool(vm)
|
||||
|
@ -835,7 +826,7 @@ func opcodeIf(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Data stack transformation: [... bool] -> [...]
|
||||
// Conditional stack transformation: [...] -> [... OpCondValue]
|
||||
func opcodeNotIf(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeNotIf(op *opcode, data []byte, vm *Engine) error {
|
||||
condVal := OpCondFalse
|
||||
if vm.isBranchExecuting() {
|
||||
ok, err := popIfBool(vm)
|
||||
|
@ -858,10 +849,10 @@ func opcodeNotIf(op *parsedOpcode, vm *Engine) error {
|
|||
// An error is returned if there has not already been a matching OP_IF.
|
||||
//
|
||||
// Conditional stack transformation: [... OpCondValue] -> [... !OpCondValue]
|
||||
func opcodeElse(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeElse(op *opcode, data []byte, vm *Engine) error {
|
||||
if len(vm.condStack) == 0 {
|
||||
str := fmt.Sprintf("encountered opcode %s with no matching "+
|
||||
"opcode to begin conditional execution", op.opcode.name)
|
||||
"opcode to begin conditional execution", op.name)
|
||||
return scriptError(ErrUnbalancedConditional, str)
|
||||
}
|
||||
|
||||
|
@ -884,10 +875,10 @@ func opcodeElse(op *parsedOpcode, vm *Engine) error {
|
|||
// An error is returned if there has not already been a matching OP_IF.
|
||||
//
|
||||
// Conditional stack transformation: [... OpCondValue] -> [...]
|
||||
func opcodeEndif(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeEndif(op *opcode, data []byte, vm *Engine) error {
|
||||
if len(vm.condStack) == 0 {
|
||||
str := fmt.Sprintf("encountered opcode %s with no matching "+
|
||||
"opcode to begin conditional execution", op.opcode.name)
|
||||
"opcode to begin conditional execution", op.name)
|
||||
return scriptError(ErrUnbalancedConditional, str)
|
||||
}
|
||||
|
||||
|
@ -900,14 +891,14 @@ func opcodeEndif(op *parsedOpcode, vm *Engine) error {
|
|||
// item on the stack or when that item evaluates to false. In the latter case
|
||||
// where the verification fails specifically due to the top item evaluating
|
||||
// to false, the returned error will use the passed error code.
|
||||
func abstractVerify(op *parsedOpcode, vm *Engine, c ErrorCode) error {
|
||||
func abstractVerify(op *opcode, vm *Engine, c ErrorCode) error {
|
||||
verified, err := vm.dstack.PopBool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !verified {
|
||||
str := fmt.Sprintf("%s failed", op.opcode.name)
|
||||
str := fmt.Sprintf("%s failed", op.name)
|
||||
return scriptError(c, str)
|
||||
}
|
||||
return nil
|
||||
|
@ -915,13 +906,13 @@ func abstractVerify(op *parsedOpcode, vm *Engine, c ErrorCode) error {
|
|||
|
||||
// opcodeVerify examines the top item on the data stack as a boolean value and
|
||||
// verifies it evaluates to true. An error is returned if it does not.
|
||||
func opcodeVerify(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
return abstractVerify(op, vm, ErrVerify)
|
||||
}
|
||||
|
||||
// opcodeReturn returns an appropriate error since it is always an error to
|
||||
// return early from a script.
|
||||
func opcodeReturn(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeReturn(op *opcode, data []byte, vm *Engine) error {
|
||||
return scriptError(ErrEarlyReturn, "script returned early")
|
||||
}
|
||||
|
||||
|
@ -951,7 +942,7 @@ func verifyLockTime(txLockTime, threshold, lockTime int64) error {
|
|||
// validating if the transaction outputs are spendable yet. If flag
|
||||
// ScriptVerifyCheckLockTimeVerify is not set, the code continues as if OP_NOP2
|
||||
// were executed.
|
||||
func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeCheckLockTimeVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
// If the ScriptVerifyCheckLockTimeVerify script flag is not set, treat
|
||||
// opcode as OP_NOP2 instead.
|
||||
if !vm.hasFlag(ScriptVerifyCheckLockTimeVerify) {
|
||||
|
@ -1025,7 +1016,7 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
|
|||
// validating if the transaction outputs are spendable yet. If flag
|
||||
// ScriptVerifyCheckSequenceVerify is not set, the code continues as if OP_NOP3
|
||||
// were executed.
|
||||
func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeCheckSequenceVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
// If the ScriptVerifyCheckSequenceVerify script flag is not set, treat
|
||||
// opcode as OP_NOP3 instead.
|
||||
if !vm.hasFlag(ScriptVerifyCheckSequenceVerify) {
|
||||
|
@ -1102,7 +1093,7 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2]
|
||||
// Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2 y3 x3]
|
||||
func opcodeToAltStack(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeToAltStack(op *opcode, data []byte, vm *Engine) error {
|
||||
so, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1117,7 +1108,7 @@ func opcodeToAltStack(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Main data stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 y3]
|
||||
// Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2]
|
||||
func opcodeFromAltStack(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeFromAltStack(op *opcode, data []byte, vm *Engine) error {
|
||||
so, err := vm.astack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1130,35 +1121,35 @@ func opcodeFromAltStack(op *parsedOpcode, vm *Engine) error {
|
|||
// opcode2Drop removes the top 2 items from the data stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3] -> [... x1]
|
||||
func opcode2Drop(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode2Drop(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.DropN(2)
|
||||
}
|
||||
|
||||
// opcode2Dup duplicates the top 2 items on the data stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2 x3]
|
||||
func opcode2Dup(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode2Dup(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.DupN(2)
|
||||
}
|
||||
|
||||
// opcode3Dup duplicates the top 3 items on the data stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x1 x2 x3]
|
||||
func opcode3Dup(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode3Dup(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.DupN(3)
|
||||
}
|
||||
|
||||
// opcode2Over duplicates the 2 items before the top 2 items on the data stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3 x4] -> [... x1 x2 x3 x4 x1 x2]
|
||||
func opcode2Over(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode2Over(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.OverN(2)
|
||||
}
|
||||
|
||||
// opcode2Rot rotates the top 6 items on the data stack to the left twice.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3 x4 x5 x6] -> [... x3 x4 x5 x6 x1 x2]
|
||||
func opcode2Rot(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode2Rot(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.RotN(2)
|
||||
}
|
||||
|
||||
|
@ -1166,7 +1157,7 @@ func opcode2Rot(op *parsedOpcode, vm *Engine) error {
|
|||
// before them.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3 x4] -> [... x3 x4 x1 x2]
|
||||
func opcode2Swap(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode2Swap(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.SwapN(2)
|
||||
}
|
||||
|
||||
|
@ -1174,7 +1165,7 @@ func opcode2Swap(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Stack transformation (x1==0): [... x1] -> [... x1]
|
||||
// Stack transformation (x1!=0): [... x1] -> [... x1 x1]
|
||||
func opcodeIfDup(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeIfDup(op *opcode, data []byte, vm *Engine) error {
|
||||
so, err := vm.dstack.PeekByteArray(0)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1194,7 +1185,7 @@ func opcodeIfDup(op *parsedOpcode, vm *Engine) error {
|
|||
// Stack transformation: [...] -> [... <num of items on the stack>]
|
||||
// Example with 2 items: [x1 x2] -> [x1 x2 2]
|
||||
// Example with 3 items: [x1 x2 x3] -> [x1 x2 x3 3]
|
||||
func opcodeDepth(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeDepth(op *opcode, data []byte, vm *Engine) error {
|
||||
vm.dstack.PushInt(scriptNum(vm.dstack.Depth()))
|
||||
return nil
|
||||
}
|
||||
|
@ -1202,28 +1193,28 @@ func opcodeDepth(op *parsedOpcode, vm *Engine) error {
|
|||
// opcodeDrop removes the top item from the data stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2]
|
||||
func opcodeDrop(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeDrop(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.DropN(1)
|
||||
}
|
||||
|
||||
// opcodeDup duplicates the top item on the data stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x3]
|
||||
func opcodeDup(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeDup(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.DupN(1)
|
||||
}
|
||||
|
||||
// opcodeNip removes the item before the top item on the data stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x3]
|
||||
func opcodeNip(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeNip(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.NipN(1)
|
||||
}
|
||||
|
||||
// opcodeOver duplicates the item before the top item on the data stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2]
|
||||
func opcodeOver(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeOver(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.OverN(1)
|
||||
}
|
||||
|
||||
|
@ -1233,7 +1224,7 @@ func opcodeOver(op *parsedOpcode, vm *Engine) error {
|
|||
// Stack transformation: [xn ... x2 x1 x0 n] -> [xn ... x2 x1 x0 xn]
|
||||
// Example with n=1: [x2 x1 x0 1] -> [x2 x1 x0 x1]
|
||||
// Example with n=2: [x2 x1 x0 2] -> [x2 x1 x0 x2]
|
||||
func opcodePick(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodePick(op *opcode, data []byte, vm *Engine) error {
|
||||
val, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1248,7 +1239,7 @@ func opcodePick(op *parsedOpcode, vm *Engine) error {
|
|||
// Stack transformation: [xn ... x2 x1 x0 n] -> [... x2 x1 x0 xn]
|
||||
// Example with n=1: [x2 x1 x0 1] -> [x2 x0 x1]
|
||||
// Example with n=2: [x2 x1 x0 2] -> [x1 x0 x2]
|
||||
func opcodeRoll(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeRoll(op *opcode, data []byte, vm *Engine) error {
|
||||
val, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1260,14 +1251,14 @@ func opcodeRoll(op *parsedOpcode, vm *Engine) error {
|
|||
// opcodeRot rotates the top 3 items on the data stack to the left.
|
||||
//
|
||||
// Stack transformation: [... x1 x2 x3] -> [... x2 x3 x1]
|
||||
func opcodeRot(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeRot(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.RotN(1)
|
||||
}
|
||||
|
||||
// opcodeSwap swaps the top two items on the stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... x2 x1]
|
||||
func opcodeSwap(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeSwap(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.SwapN(1)
|
||||
}
|
||||
|
||||
|
@ -1275,7 +1266,7 @@ func opcodeSwap(op *parsedOpcode, vm *Engine) error {
|
|||
// second-to-top item.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... x2 x1 x2]
|
||||
func opcodeTuck(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeTuck(op *opcode, data []byte, vm *Engine) error {
|
||||
return vm.dstack.Tuck()
|
||||
}
|
||||
|
||||
|
@ -1283,7 +1274,7 @@ func opcodeTuck(op *parsedOpcode, vm *Engine) error {
|
|||
// stack.
|
||||
//
|
||||
// Stack transformation: [... x1] -> [... x1 len(x1)]
|
||||
func opcodeSize(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeSize(op *opcode, data []byte, vm *Engine) error {
|
||||
so, err := vm.dstack.PeekByteArray(0)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1297,7 +1288,7 @@ func opcodeSize(op *parsedOpcode, vm *Engine) error {
|
|||
// bytes, and pushes the result, encoded as a boolean, back to the stack.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... bool]
|
||||
func opcodeEqual(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeEqual(op *opcode, data []byte, vm *Engine) error {
|
||||
a, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1318,8 +1309,8 @@ func opcodeEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// evaluates to true. An error is returned if it does not.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... bool] -> [...]
|
||||
func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error {
|
||||
err := opcodeEqual(op, vm)
|
||||
func opcodeEqualVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
err := opcodeEqual(op, data, vm)
|
||||
if err == nil {
|
||||
err = abstractVerify(op, vm, ErrEqualVerify)
|
||||
}
|
||||
|
@ -1330,7 +1321,7 @@ func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error {
|
|||
// it with its incremented value (plus 1).
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... x1 x2+1]
|
||||
func opcode1Add(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode1Add(op *opcode, data []byte, vm *Engine) error {
|
||||
m, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1344,7 +1335,7 @@ func opcode1Add(op *parsedOpcode, vm *Engine) error {
|
|||
// it with its decremented value (minus 1).
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... x1 x2-1]
|
||||
func opcode1Sub(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode1Sub(op *opcode, data []byte, vm *Engine) error {
|
||||
m, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1358,7 +1349,7 @@ func opcode1Sub(op *parsedOpcode, vm *Engine) error {
|
|||
// it with its negation.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... x1 -x2]
|
||||
func opcodeNegate(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeNegate(op *opcode, data []byte, vm *Engine) error {
|
||||
m, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1372,7 +1363,7 @@ func opcodeNegate(op *parsedOpcode, vm *Engine) error {
|
|||
// it with its absolute value.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... x1 abs(x2)]
|
||||
func opcodeAbs(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeAbs(op *opcode, data []byte, vm *Engine) error {
|
||||
m, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1397,7 +1388,7 @@ func opcodeAbs(op *parsedOpcode, vm *Engine) error {
|
|||
// Stack transformation (x2==0): [... x1 0] -> [... x1 1]
|
||||
// Stack transformation (x2!=0): [... x1 1] -> [... x1 0]
|
||||
// Stack transformation (x2!=0): [... x1 17] -> [... x1 0]
|
||||
func opcodeNot(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeNot(op *opcode, data []byte, vm *Engine) error {
|
||||
m, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1417,7 +1408,7 @@ func opcodeNot(op *parsedOpcode, vm *Engine) error {
|
|||
// Stack transformation (x2==0): [... x1 0] -> [... x1 0]
|
||||
// Stack transformation (x2!=0): [... x1 1] -> [... x1 1]
|
||||
// Stack transformation (x2!=0): [... x1 17] -> [... x1 1]
|
||||
func opcode0NotEqual(op *parsedOpcode, vm *Engine) error {
|
||||
func opcode0NotEqual(op *opcode, data []byte, vm *Engine) error {
|
||||
m, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1434,7 +1425,7 @@ func opcode0NotEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// them with their sum.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... x1+x2]
|
||||
func opcodeAdd(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeAdd(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1454,7 +1445,7 @@ func opcodeAdd(op *parsedOpcode, vm *Engine) error {
|
|||
// entry.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... x1-x2]
|
||||
func opcodeSub(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeSub(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1476,7 +1467,7 @@ func opcodeSub(op *parsedOpcode, vm *Engine) error {
|
|||
// Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 0]
|
||||
// Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 0]
|
||||
// Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1]
|
||||
func opcodeBoolAnd(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeBoolAnd(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1503,7 +1494,7 @@ func opcodeBoolAnd(op *parsedOpcode, vm *Engine) error {
|
|||
// Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 1]
|
||||
// Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 1]
|
||||
// Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1]
|
||||
func opcodeBoolOr(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeBoolOr(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1528,7 +1519,7 @@ func opcodeBoolOr(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Stack transformation (x1==x2): [... 5 5] -> [... 1]
|
||||
// Stack transformation (x1!=x2): [... 5 7] -> [... 0]
|
||||
func opcodeNumEqual(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeNumEqual(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1556,8 +1547,8 @@ func opcodeNumEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// to true. An error is returned if it does not.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... bool] -> [...]
|
||||
func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error {
|
||||
err := opcodeNumEqual(op, vm)
|
||||
func opcodeNumEqualVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
err := opcodeNumEqual(op, data, vm)
|
||||
if err == nil {
|
||||
err = abstractVerify(op, vm, ErrNumEqualVerify)
|
||||
}
|
||||
|
@ -1569,7 +1560,7 @@ func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Stack transformation (x1==x2): [... 5 5] -> [... 0]
|
||||
// Stack transformation (x1!=x2): [... 5 7] -> [... 1]
|
||||
func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeNumNotEqual(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1594,7 +1585,7 @@ func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// otherwise a 0.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... bool]
|
||||
func opcodeLessThan(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeLessThan(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1619,7 +1610,7 @@ func opcodeLessThan(op *parsedOpcode, vm *Engine) error {
|
|||
// with a 1, otherwise a 0.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... bool]
|
||||
func opcodeGreaterThan(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeGreaterThan(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1643,7 +1634,7 @@ func opcodeGreaterThan(op *parsedOpcode, vm *Engine) error {
|
|||
// replaced with a 1, otherwise a 0.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... bool]
|
||||
func opcodeLessThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeLessThanOrEqual(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1667,7 +1658,7 @@ func opcodeLessThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// item, they are replaced with a 1, otherwise a 0.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... bool]
|
||||
func opcodeGreaterThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeGreaterThanOrEqual(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1691,7 +1682,7 @@ func opcodeGreaterThanOrEqual(op *parsedOpcode, vm *Engine) error {
|
|||
// them with the minimum of the two.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... min(x1, x2)]
|
||||
func opcodeMin(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeMin(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1715,7 +1706,7 @@ func opcodeMin(op *parsedOpcode, vm *Engine) error {
|
|||
// them with the maximum of the two.
|
||||
//
|
||||
// Stack transformation: [... x1 x2] -> [... max(x1, x2)]
|
||||
func opcodeMax(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeMax(op *opcode, data []byte, vm *Engine) error {
|
||||
v0, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1743,7 +1734,7 @@ func opcodeMax(op *parsedOpcode, vm *Engine) error {
|
|||
// the third-to-top item is the value to test.
|
||||
//
|
||||
// Stack transformation: [... x1 min max] -> [... bool]
|
||||
func opcodeWithin(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeWithin(op *opcode, data []byte, vm *Engine) error {
|
||||
maxVal, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1777,7 +1768,7 @@ func calcHash(buf []byte, hasher hash.Hash) []byte {
|
|||
// replaces it with ripemd160(data).
|
||||
//
|
||||
// Stack transformation: [... x1] -> [... ripemd160(x1)]
|
||||
func opcodeRipemd160(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeRipemd160(op *opcode, data []byte, vm *Engine) error {
|
||||
buf, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1791,7 +1782,7 @@ func opcodeRipemd160(op *parsedOpcode, vm *Engine) error {
|
|||
// with sha1(data).
|
||||
//
|
||||
// Stack transformation: [... x1] -> [... sha1(x1)]
|
||||
func opcodeSha1(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeSha1(op *opcode, data []byte, vm *Engine) error {
|
||||
buf, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1806,7 +1797,7 @@ func opcodeSha1(op *parsedOpcode, vm *Engine) error {
|
|||
// it with sha256(data).
|
||||
//
|
||||
// Stack transformation: [... x1] -> [... sha256(x1)]
|
||||
func opcodeSha256(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeSha256(op *opcode, data []byte, vm *Engine) error {
|
||||
buf, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1821,7 +1812,7 @@ func opcodeSha256(op *parsedOpcode, vm *Engine) error {
|
|||
// it with ripemd160(sha256(data)).
|
||||
//
|
||||
// Stack transformation: [... x1] -> [... ripemd160(sha256(x1))]
|
||||
func opcodeHash160(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeHash160(op *opcode, data []byte, vm *Engine) error {
|
||||
buf, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1836,7 +1827,7 @@ func opcodeHash160(op *parsedOpcode, vm *Engine) error {
|
|||
// it with sha256(sha256(data)).
|
||||
//
|
||||
// Stack transformation: [... x1] -> [... sha256(sha256(x1))]
|
||||
func opcodeHash256(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeHash256(op *opcode, data []byte, vm *Engine) error {
|
||||
buf, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1850,7 +1841,7 @@ func opcodeHash256(op *parsedOpcode, vm *Engine) error {
|
|||
// seen OP_CODESEPARATOR which is used during signature checking.
|
||||
//
|
||||
// This opcode does not change the contents of the data stack.
|
||||
func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeCodeSeparator(op *opcode, data []byte, vm *Engine) error {
|
||||
vm.lastCodeSep = int(vm.tokenizer.ByteIndex())
|
||||
return nil
|
||||
}
|
||||
|
@ -1869,7 +1860,7 @@ func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error {
|
|||
// cryptographic methods against the provided public key.
|
||||
//
|
||||
// Stack transformation: [... signature pubkey] -> [... bool]
|
||||
func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error {
|
||||
pkBytes, err := vm.dstack.PopByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1984,9 +1975,9 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
|||
// The opcodeCheckSig function is invoked followed by opcodeVerify. See the
|
||||
// documentation for each of those opcodes for more details.
|
||||
//
|
||||
// Stack transformation: signature pubkey] -> [... bool] -> [...]
|
||||
func opcodeCheckSigVerify(op *parsedOpcode, vm *Engine) error {
|
||||
err := opcodeCheckSig(op, vm)
|
||||
// Stack transformation: [... signature pubkey] -> [... bool] -> [...]
|
||||
func opcodeCheckSigVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
err := opcodeCheckSig(op, data, vm)
|
||||
if err == nil {
|
||||
err = abstractVerify(op, vm, ErrCheckSigVerify)
|
||||
}
|
||||
|
@ -2021,7 +2012,7 @@ type parsedSigInfo struct {
|
|||
//
|
||||
// Stack transformation:
|
||||
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool]
|
||||
func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||
func opcodeCheckMultiSig(op *opcode, data []byte, vm *Engine) error {
|
||||
numKeys, err := vm.dstack.PopInt()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2247,8 +2238,8 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
|||
//
|
||||
// Stack transformation:
|
||||
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...]
|
||||
func opcodeCheckMultiSigVerify(op *parsedOpcode, vm *Engine) error {
|
||||
err := opcodeCheckMultiSig(op, vm)
|
||||
func opcodeCheckMultiSigVerify(op *opcode, data []byte, vm *Engine) error {
|
||||
err := opcodeCheckMultiSig(op, data, vm)
|
||||
if err == nil {
|
||||
err = abstractVerify(op, vm, ErrCheckMultiSigVerify)
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ func TestOpcodeDisabled(t *testing.T) {
|
|||
OP_LSHIFT, OP_RSHIFT,
|
||||
}
|
||||
for _, opcodeVal := range tests {
|
||||
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: nil}
|
||||
err := opcodeDisabled(&pop, nil)
|
||||
op := &opcodeArray[opcodeVal]
|
||||
err := opcodeDisabled(op, nil, nil)
|
||||
if !IsErrorCode(err, ErrDisabledOpcode) {
|
||||
t.Errorf("opcodeDisabled: unexpected error - got %v, "+
|
||||
"want %v", err, ErrDisabledOpcode)
|
||||
|
|
Loading…
Reference in a new issue