kaspad/txscript/reference_test.go
Svarog 396842ae40 [NOD-207] Rename any place that says 'OutPoint' to 'Outpoint' (#323)
* [NOD-207] Rename any place that says 'OutPoint' to 'Outpoint'

* [NOD-207] Fix any place that says output point
2019-06-05 16:23:57 +03:00

384 lines
12 KiB
Go

// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package txscript
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"strconv"
"strings"
"testing"
"github.com/daglabs/btcd/util/daghash"
"github.com/daglabs/btcd/wire"
)
// scriptTestName returns a descriptive test name for the given reference script
// test data.
func scriptTestName(test []interface{}) (string, error) {
// The test must consist of a signature script, public key script, flags,
// and expected error. Finally, it may optionally contain a comment.
if len(test) < 4 || len(test) > 5 {
return "", fmt.Errorf("invalid test length %d", len(test))
}
// Use the comment for the test name if one is specified, otherwise,
// construct the name based on the signature script, public key script,
// and flags.
var name string
if len(test) == 5 {
name = fmt.Sprintf("test (%s)", test[4])
} else {
name = fmt.Sprintf("test ([%s, %s, %s])", test[0],
test[1], test[2])
}
return name, nil
}
// parse hex string into a []byte.
func parseHex(tok string) ([]byte, error) {
if !strings.HasPrefix(tok, "0x") {
return nil, errors.New("not a hex number")
}
return hex.DecodeString(tok[2:])
}
// shortFormOps holds a map of opcode names to values for use in short form
// parsing. It is declared here so it only needs to be created once.
var shortFormOps map[string]byte
// parseShortForm parses a string as as used in the Bitcoin Core reference tests
// into the script it came from.
//
// The format used for these tests is pretty simple if ad-hoc:
// - Opcodes other than the push opcodes and unknown are present as
// either OP_NAME or just NAME
// - Plain numbers are made into push operations
// - Numbers beginning with 0x are inserted into the []byte as-is (so
// 0x14 is OP_DATA_20)
// - Single quoted strings are pushed as data
// - Anything else is an error
func parseShortForm(script string) ([]byte, error) {
// Only create the short form opcode map once.
if shortFormOps == nil {
ops := make(map[string]byte)
for opcodeName, opcodeValue := range OpcodeByName {
if strings.Contains(opcodeName, "OP_UNKNOWN") {
continue
}
ops[opcodeName] = opcodeValue
// The opcodes named OP_# can't have the OP_ prefix
// stripped or they would conflict with the plain
// numbers. Also, since OP_FALSE and OP_TRUE are
// aliases for the OP_0, and OP_1, respectively, they
// have the same value, so detect those by name and
// allow them.
if (opcodeName == "OP_FALSE" || opcodeName == "OP_TRUE") ||
(opcodeValue != Op0 && (opcodeValue < Op1 ||
opcodeValue > Op16)) {
ops[strings.TrimPrefix(opcodeName, "OP_")] = opcodeValue
}
}
shortFormOps = ops
}
// Split only does one separator so convert all \n and tab into space.
script = strings.Replace(script, "\n", " ", -1)
script = strings.Replace(script, "\t", " ", -1)
tokens := strings.Split(script, " ")
builder := NewScriptBuilder()
for _, tok := range tokens {
if len(tok) == 0 {
continue
}
// if parses as a plain number
if num, err := strconv.ParseInt(tok, 10, 64); err == nil {
builder.AddInt64(num)
continue
} else if bts, err := parseHex(tok); err == nil {
// Concatenate the bytes manually since the test code
// intentionally creates scripts that are too large and
// would cause the builder to error otherwise.
if builder.err == nil {
builder.script = append(builder.script, bts...)
}
} else if len(tok) >= 2 &&
tok[0] == '\'' && tok[len(tok)-1] == '\'' {
builder.AddFullData([]byte(tok[1 : len(tok)-1]))
} else if opcode, ok := shortFormOps[tok]; ok {
builder.AddOp(opcode)
} else {
return nil, fmt.Errorf("bad token %q", tok)
}
}
return builder.Script()
}
// parseScriptFlags parses the provided flags string from the format used in the
// reference tests into ScriptFlags suitable for use in the script engine.
func parseScriptFlags(flagStr string) (ScriptFlags, error) {
var flags ScriptFlags
sFlags := strings.Split(flagStr, ",")
for _, flag := range sFlags {
switch flag {
case "":
// Nothing.
case "DISCOURAGE_UPGRADABLE_NOPS":
flags |= ScriptDiscourageUpgradableNops
default:
return flags, fmt.Errorf("invalid flag: %s", flag)
}
}
return flags, nil
}
// parseExpectedResult parses the provided expected result string into allowed
// script error codes. An error is returned if the expected result string is
// not supported.
func parseExpectedResult(expected string) ([]ErrorCode, error) {
switch expected {
case "OK":
return nil, nil
case "UNKNOWN_ERROR":
return []ErrorCode{ErrNumberTooBig, ErrMinimalData}, nil
case "PUBKEYFORMAT":
return []ErrorCode{ErrPubKeyFormat}, nil
case "SIG_DER":
return []ErrorCode{ErrSigDER, ErrInvalidSigHashType}, nil
case "EVAL_FALSE":
return []ErrorCode{ErrEvalFalse, ErrEmptyStack}, nil
case "EMPTY_STACK":
return []ErrorCode{ErrEmptyStack}, nil
case "EQUALVERIFY":
return []ErrorCode{ErrEqualVerify}, nil
case "NULLFAIL":
return []ErrorCode{ErrNullFail}, nil
case "SIG_HIGH_S":
return []ErrorCode{ErrSigHighS}, nil
case "SIG_HASHTYPE":
return []ErrorCode{ErrInvalidSigHashType}, nil
case "SIG_PUSHONLY":
return []ErrorCode{ErrNotPushOnly}, nil
case "CLEANSTACK":
return []ErrorCode{ErrCleanStack}, nil
case "BAD_OPCODE":
return []ErrorCode{ErrReservedOpcode, ErrMalformedPush}, nil
case "UNBALANCED_CONDITIONAL":
return []ErrorCode{ErrUnbalancedConditional,
ErrInvalidStackOperation}, nil
case "OP_RETURN":
return []ErrorCode{ErrEarlyReturn}, nil
case "VERIFY":
return []ErrorCode{ErrVerify}, nil
case "INVALID_STACK_OPERATION", "INVALID_ALTSTACK_OPERATION":
return []ErrorCode{ErrInvalidStackOperation}, nil
case "DISABLED_OPCODE":
return []ErrorCode{ErrDisabledOpcode}, nil
case "DISCOURAGE_UPGRADABLE_NOPS":
return []ErrorCode{ErrDiscourageUpgradableNOPs}, nil
case "PUSH_SIZE":
return []ErrorCode{ErrElementTooBig}, nil
case "OP_COUNT":
return []ErrorCode{ErrTooManyOperations}, nil
case "STACK_SIZE":
return []ErrorCode{ErrStackOverflow}, nil
case "SCRIPT_SIZE":
return []ErrorCode{ErrScriptTooBig}, nil
case "PUBKEY_COUNT":
return []ErrorCode{ErrInvalidPubKeyCount}, nil
case "SIG_COUNT":
return []ErrorCode{ErrInvalidSignatureCount}, nil
case "MINIMALDATA":
return []ErrorCode{ErrMinimalData}, nil
case "NEGATIVE_LOCKTIME":
return []ErrorCode{ErrNegativeLockTime}, nil
case "UNSATISFIED_LOCKTIME":
return []ErrorCode{ErrUnsatisfiedLockTime}, nil
case "MINIMALIF":
return []ErrorCode{ErrMinimalIf}, nil
}
return nil, fmt.Errorf("unrecognized expected result in test data: %v",
expected)
}
// createSpendTx generates a basic spending transaction given the passed
// signature and public key scripts.
func createSpendingTx(sigScript, pkScript []byte) *wire.MsgTx {
outpoint := wire.NewOutpoint(&daghash.TxID{}, ^uint32(0))
txIn := wire.NewTxIn(outpoint, []byte{Op0, Op0})
txOut := wire.NewTxOut(0, pkScript)
coinbaseTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
outpoint = wire.NewOutpoint(coinbaseTx.TxID(), 0)
txIn = wire.NewTxIn(outpoint, sigScript)
txOut = wire.NewTxOut(0, nil)
spendingTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
return spendingTx
}
// testScripts ensures all of the passed script tests execute with the expected
// results with or without using a signature cache, as specified by the
// parameter.
func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) {
// Create a signature cache to use only if requested.
var sigCache *SigCache
if useSigCache {
sigCache = NewSigCache(10)
}
for i, test := range tests {
// "Format is: [[wit..., amount]?, scriptSig, scriptPubKey,
// flags, expected_scripterror, ... comments]"
// Skip single line comments.
if len(test) == 1 {
continue
}
// Construct a name for the test based on the comment and test
// data.
name, err := scriptTestName(test)
if err != nil {
t.Errorf("TestScripts: invalid test #%d: %v", i, err)
continue
}
// Extract and parse the signature script from the test fields.
scriptSigStr, ok := test[0].(string)
if !ok {
t.Errorf("%s: signature script is not a string", name)
continue
}
scriptSig, err := parseShortForm(scriptSigStr)
if err != nil {
t.Errorf("%s: can't parse signature script: %v", name,
err)
continue
}
// Extract and parse the public key script from the test fields.
scriptPubKeyStr, ok := test[1].(string)
if !ok {
t.Errorf("%s: public key script is not a string", name)
continue
}
scriptPubKey, err := parseShortForm(scriptPubKeyStr)
if err != nil {
t.Errorf("%s: can't parse public key script: %v", name,
err)
continue
}
// Extract and parse the script flags from the test fields.
flagsStr, ok := test[2].(string)
if !ok {
t.Errorf("%s: flags field is not a string", name)
continue
}
flags, err := parseScriptFlags(flagsStr)
if err != nil {
t.Errorf("%s: %v", name, err)
continue
}
// Extract and parse the expected result from the test fields.
//
// Convert the expected result string into the allowed script
// error codes. This is necessary because txscript is more
// fine grained with its errors than the reference test data, so
// some of the reference test data errors map to more than one
// possibility.
resultStr, ok := test[3].(string)
if !ok {
t.Errorf("%s: result field is not a string", name)
continue
}
allowedErrorCodes, err := parseExpectedResult(resultStr)
if err != nil {
t.Errorf("%s: %v", name, err)
continue
}
// Generate a transaction pair such that one spends from the
// other and the provided signature and public key scripts are
// used, then create a new engine to execute the scripts.
tx := createSpendingTx(scriptSig, scriptPubKey)
vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache)
if err == nil {
err = vm.Execute()
}
// Ensure there were no errors when the expected result is OK.
if resultStr == "OK" {
if err != nil {
t.Errorf("%s failed to execute: %v", name, err)
}
continue
}
// At this point an error was expected so ensure the result of
// the execution matches it.
success := false
for _, code := range allowedErrorCodes {
if IsErrorCode(err, code) {
success = true
break
}
}
if !success {
if serr, ok := err.(Error); ok {
t.Errorf("%s: want error codes %v, got %v", name,
allowedErrorCodes, serr.ErrorCode)
continue
}
t.Errorf("%s: want error codes %v, got err: %v (%T)",
name, allowedErrorCodes, err, err)
continue
}
}
}
// TestScripts ensures all of the tests in script_tests.json execute with the
// expected results as defined in the test data.
func TestScripts(t *testing.T) {
file, err := ioutil.ReadFile("data/script_tests.json")
if err != nil {
t.Fatalf("TestScripts: %v\n", err)
}
var tests [][]interface{}
err = json.Unmarshal(file, &tests)
if err != nil {
t.Fatalf("TestScripts couldn't Unmarshal: %v", err)
}
// Run all script tests with and without the signature cache.
testScripts(t, tests, true)
testScripts(t, tests, false)
}
// testVecF64ToUint32 properly handles conversion of float64s read from the JSON
// test data to unsigned 32-bit integers. This is necessary because some of the
// test data uses -1 as a shortcut to mean max uint32 and direct conversion of a
// negative float to an unsigned int is implementation dependent and therefore
// doesn't result in the expected value on all platforms. This function woks
// around that limitation by converting to a 32-bit signed integer first and
// then to a 32-bit unsigned integer which results in the expected behavior on
// all platforms.
func testVecF64ToUint32(f float64) uint32 {
return uint32(int32(f))
}