mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-05 13:46:42 +00:00
[NOD-1526] Restore txscript tests (#1019)
* [NOD-1526] Fix compilation errors * [NOD-1526] Make MsgTx.PayloadHash non-pointer * [NOD-1526] Fixed many tests * [NOD-1526] Fix reference_test.go * [NOD-1526] Removed last instances of appmessage in consensus * [NOD-1526] No need to check for subnetwork
This commit is contained in:
parent
135ffbd4f2
commit
37fbdcb453
@ -77,7 +77,7 @@ func DomainTransactionToMsgTx(domainTransaction *externalapi.DomainTransaction)
|
|||||||
LockTime: domainTransaction.LockTime,
|
LockTime: domainTransaction.LockTime,
|
||||||
SubnetworkID: domainTransaction.SubnetworkID,
|
SubnetworkID: domainTransaction.SubnetworkID,
|
||||||
Gas: domainTransaction.Gas,
|
Gas: domainTransaction.Gas,
|
||||||
PayloadHash: &domainTransaction.PayloadHash,
|
PayloadHash: domainTransaction.PayloadHash,
|
||||||
Payload: domainTransaction.Payload,
|
Payload: domainTransaction.Payload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@ func MsgTxToDomainTransaction(msgTx *MsgTx) *externalapi.DomainTransaction {
|
|||||||
LockTime: msgTx.LockTime,
|
LockTime: msgTx.LockTime,
|
||||||
SubnetworkID: msgTx.SubnetworkID,
|
SubnetworkID: msgTx.SubnetworkID,
|
||||||
Gas: msgTx.Gas,
|
Gas: msgTx.Gas,
|
||||||
PayloadHash: *msgTx.PayloadHash,
|
PayloadHash: msgTx.PayloadHash,
|
||||||
Payload: msgTx.Payload,
|
Payload: msgTx.Payload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,10 @@ package appmessage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||||
@ -19,38 +20,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TxVersion is the current latest supported transaction version.
|
|
||||||
TxVersion = 1
|
|
||||||
|
|
||||||
// MaxTxInSequenceNum is the maximum sequence number the sequence field
|
|
||||||
// of a transaction input can be.
|
|
||||||
MaxTxInSequenceNum uint64 = math.MaxUint64
|
|
||||||
|
|
||||||
// MaxPrevOutIndex is the maximum index the index field of a previous
|
// MaxPrevOutIndex is the maximum index the index field of a previous
|
||||||
// outpoint can be.
|
// outpoint can be.
|
||||||
MaxPrevOutIndex uint32 = 0xffffffff
|
MaxPrevOutIndex uint32 = 0xffffffff
|
||||||
|
|
||||||
// SequenceLockTimeDisabled is a flag that if set on a transaction
|
|
||||||
// input's sequence number, the sequence number will not be interpreted
|
|
||||||
// as a relative locktime.
|
|
||||||
SequenceLockTimeDisabled = 1 << 31
|
|
||||||
|
|
||||||
// SequenceLockTimeIsSeconds is a flag that if set on a transaction
|
|
||||||
// input's sequence number, the relative locktime has units of 512
|
|
||||||
// seconds.
|
|
||||||
SequenceLockTimeIsSeconds = 1 << 22
|
|
||||||
|
|
||||||
// SequenceLockTimeMask is a mask that extracts the relative locktime
|
|
||||||
// when masked against the transaction input sequence number.
|
|
||||||
SequenceLockTimeMask = 0x0000ffff
|
|
||||||
|
|
||||||
// SequenceLockTimeGranularity is the defined time based granularity
|
|
||||||
// for milliseconds-based relative time locks. When converting from milliseconds
|
|
||||||
// to a sequence number, the value is right shifted by this amount,
|
|
||||||
// therefore the granularity of relative time locks in 524288 or 2^19
|
|
||||||
// seconds. Enforced relative lock times are multiples of 524288 milliseconds.
|
|
||||||
SequenceLockTimeGranularity = 19
|
|
||||||
|
|
||||||
// defaultTxInOutAlloc is the default size used for the backing array for
|
// defaultTxInOutAlloc is the default size used for the backing array for
|
||||||
// transaction inputs and outputs. The array will dynamically grow as needed,
|
// transaction inputs and outputs. The array will dynamically grow as needed,
|
||||||
// but this figure is intended to provide enough space for the number of
|
// but this figure is intended to provide enough space for the number of
|
||||||
@ -130,7 +103,7 @@ func NewTxIn(prevOut *Outpoint, signatureScript []byte) *TxIn {
|
|||||||
return &TxIn{
|
return &TxIn{
|
||||||
PreviousOutpoint: *prevOut,
|
PreviousOutpoint: *prevOut,
|
||||||
SignatureScript: signatureScript,
|
SignatureScript: signatureScript,
|
||||||
Sequence: MaxTxInSequenceNum,
|
Sequence: constants.MaxTxInSequenceNum,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +136,7 @@ type MsgTx struct {
|
|||||||
LockTime uint64
|
LockTime uint64
|
||||||
SubnetworkID externalapi.DomainSubnetworkID
|
SubnetworkID externalapi.DomainSubnetworkID
|
||||||
Gas uint64
|
Gas uint64
|
||||||
PayloadHash *externalapi.DomainHash
|
PayloadHash externalapi.DomainHash
|
||||||
Payload []byte
|
Payload []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,9 +283,9 @@ func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externa
|
|||||||
txOut = make([]*TxOut, 0, defaultTxInOutAlloc)
|
txOut = make([]*TxOut, 0, defaultTxInOutAlloc)
|
||||||
}
|
}
|
||||||
|
|
||||||
var payloadHash *externalapi.DomainHash
|
var payloadHash externalapi.DomainHash
|
||||||
if *subnetworkID != subnetworks.SubnetworkIDNative {
|
if *subnetworkID != subnetworks.SubnetworkIDNative {
|
||||||
payloadHash = hashes.HashData(payload)
|
payloadHash = *hashes.HashData(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &MsgTx{
|
return &MsgTx{
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package transactionvalidator
|
package transactionvalidator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
@ -248,14 +247,14 @@ func (v *transactionValidator) calcTxSequenceLockFromReferencedUTXOEntries(
|
|||||||
// mask in order to obtain the time lock delta required before
|
// mask in order to obtain the time lock delta required before
|
||||||
// this input can be spent.
|
// this input can be spent.
|
||||||
sequenceNum := input.Sequence
|
sequenceNum := input.Sequence
|
||||||
relativeLock := int64(sequenceNum & appmessage.SequenceLockTimeMask)
|
relativeLock := int64(sequenceNum & constants.SequenceLockTimeMask)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// Relative time locks are disabled for this input, so we can
|
// Relative time locks are disabled for this input, so we can
|
||||||
// skip any further calculation.
|
// skip any further calculation.
|
||||||
case sequenceNum&appmessage.SequenceLockTimeDisabled == appmessage.SequenceLockTimeDisabled:
|
case sequenceNum&constants.SequenceLockTimeDisabled == constants.SequenceLockTimeDisabled:
|
||||||
continue
|
continue
|
||||||
case sequenceNum&appmessage.SequenceLockTimeIsSeconds == appmessage.SequenceLockTimeIsSeconds:
|
case sequenceNum&constants.SequenceLockTimeIsSeconds == constants.SequenceLockTimeIsSeconds:
|
||||||
// This input requires a relative time lock expressed
|
// This input requires a relative time lock expressed
|
||||||
// in seconds before it can be spent. Therefore, we
|
// in seconds before it can be spent. Therefore, we
|
||||||
// need to query for the block prior to the one in
|
// need to query for the block prior to the one in
|
||||||
@ -290,11 +289,11 @@ func (v *transactionValidator) calcTxSequenceLockFromReferencedUTXOEntries(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Time based relative time-locks have a time granularity of
|
// Time based relative time-locks have a time granularity of
|
||||||
// appmessage.SequenceLockTimeGranularity, so we shift left by this
|
// constants.SequenceLockTimeGranularity, so we shift left by this
|
||||||
// amount to convert to the proper relative time-lock. We also
|
// amount to convert to the proper relative time-lock. We also
|
||||||
// subtract one from the relative lock to maintain the original
|
// subtract one from the relative lock to maintain the original
|
||||||
// lockTime semantics.
|
// lockTime semantics.
|
||||||
timeLockMilliseconds := (relativeLock << appmessage.SequenceLockTimeGranularity) - 1
|
timeLockMilliseconds := (relativeLock << constants.SequenceLockTimeGranularity) - 1
|
||||||
timeLock := medianTime + timeLockMilliseconds
|
timeLock := medianTime + timeLockMilliseconds
|
||||||
if timeLock > sequenceLock.Milliseconds {
|
if timeLock > sequenceLock.Milliseconds {
|
||||||
sequenceLock.Milliseconds = timeLock
|
sequenceLock.Milliseconds = timeLock
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package constants
|
package constants
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// BlockVersion represents the current version of blocks mined and the maximum block version
|
// BlockVersion represents the current version of blocks mined and the maximum block version
|
||||||
// this node is able to validate
|
// this node is able to validate
|
||||||
@ -46,4 +48,29 @@ const (
|
|||||||
|
|
||||||
// CoinbasePayloadScriptPublicKeyMaxLength is the maximum allowed script public key in the coinbase's payload
|
// CoinbasePayloadScriptPublicKeyMaxLength is the maximum allowed script public key in the coinbase's payload
|
||||||
CoinbasePayloadScriptPublicKeyMaxLength = 150
|
CoinbasePayloadScriptPublicKeyMaxLength = 150
|
||||||
|
|
||||||
|
// MaxTxInSequenceNum is the maximum sequence number the sequence field
|
||||||
|
// of a transaction input can be.
|
||||||
|
MaxTxInSequenceNum uint64 = math.MaxUint64
|
||||||
|
|
||||||
|
// SequenceLockTimeDisabled is a flag that if set on a transaction
|
||||||
|
// input's sequence number, the sequence number will not be interpreted
|
||||||
|
// as a relative locktime.
|
||||||
|
SequenceLockTimeDisabled = 1 << 31
|
||||||
|
|
||||||
|
// SequenceLockTimeIsSeconds is a flag that if set on a transaction
|
||||||
|
// input's sequence number, the relative locktime has units of 512
|
||||||
|
// seconds.
|
||||||
|
SequenceLockTimeIsSeconds = 1 << 22
|
||||||
|
|
||||||
|
// SequenceLockTimeMask is a mask that extracts the relative locktime
|
||||||
|
// when masked against the transaction input sequence number.
|
||||||
|
SequenceLockTimeMask = 0x0000ffff
|
||||||
|
|
||||||
|
// SequenceLockTimeGranularity is the defined time based granularity
|
||||||
|
// for milliseconds-based relative time locks. When converting from milliseconds
|
||||||
|
// to a sequence number, the value is right shifted by this amount,
|
||||||
|
// therefore the granularity of relative time locks in 524288 or 2^19
|
||||||
|
// seconds. Enforced relative lock times are multiples of 524288 milliseconds.
|
||||||
|
SequenceLockTimeGranularity = 19
|
||||||
)
|
)
|
||||||
|
338
domain/consensus/utils/txscript/engine_test.go
Normal file
338
domain/consensus/utils/txscript/engine_test.go
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
// 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBadPC sets the pc to a deliberately bad result then confirms that Step()
|
||||||
|
// and Disasm fail correctly.
|
||||||
|
func TestBadPC(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
script, off int
|
||||||
|
}{
|
||||||
|
{script: 2, off: 0},
|
||||||
|
{script: 0, off: 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
// tx with almost empty scripts.
|
||||||
|
inputs := []*externalapi.DomainTransactionInput{
|
||||||
|
{
|
||||||
|
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||||
|
TransactionID: externalapi.DomainTransactionID([32]byte{
|
||||||
|
0xc9, 0x97, 0xa5, 0xe5,
|
||||||
|
0x6e, 0x10, 0x41, 0x02,
|
||||||
|
0xfa, 0x20, 0x9c, 0x6a,
|
||||||
|
0x85, 0x2d, 0xd9, 0x06,
|
||||||
|
0x60, 0xa2, 0x0b, 0x2d,
|
||||||
|
0x9c, 0x35, 0x24, 0x23,
|
||||||
|
0xed, 0xce, 0x25, 0x85,
|
||||||
|
0x7f, 0xcd, 0x37, 0x04,
|
||||||
|
}),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
SignatureScript: mustParseShortForm(""),
|
||||||
|
Sequence: 4294967295,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
outputs := []*externalapi.DomainTransactionOutput{{
|
||||||
|
Value: 1000000000,
|
||||||
|
ScriptPublicKey: nil,
|
||||||
|
}}
|
||||||
|
tx := &externalapi.DomainTransaction{
|
||||||
|
Version: 1,
|
||||||
|
Inputs: inputs,
|
||||||
|
Outputs: outputs,
|
||||||
|
}
|
||||||
|
scriptPubKey := mustParseShortForm("NOP")
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to create script: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set to after all scripts
|
||||||
|
vm.scriptIdx = test.script
|
||||||
|
vm.scriptOff = test.off
|
||||||
|
|
||||||
|
_, err = vm.Step()
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Step with invalid pc (%v) succeeds!", test)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = vm.DisasmPC()
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("DisasmPC with invalid pc (%v) succeeds!",
|
||||||
|
test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckErrorCondition(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
script string
|
||||||
|
finalScript bool
|
||||||
|
stepCount int
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{"OP_1", true, 1, nil},
|
||||||
|
{"NOP", true, 0, scriptError(ErrScriptUnfinished, "")},
|
||||||
|
{"NOP", true, 1, scriptError(ErrEmptyStack, "")},
|
||||||
|
{"OP_1 OP_1", true, 2, scriptError(ErrCleanStack, "")},
|
||||||
|
{"OP_0", true, 1, scriptError(ErrEvalFalse, "")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
func() {
|
||||||
|
inputs := []*externalapi.DomainTransactionInput{{
|
||||||
|
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||||
|
TransactionID: externalapi.DomainTransactionID([32]byte{
|
||||||
|
0xc9, 0x97, 0xa5, 0xe5,
|
||||||
|
0x6e, 0x10, 0x41, 0x02,
|
||||||
|
0xfa, 0x20, 0x9c, 0x6a,
|
||||||
|
0x85, 0x2d, 0xd9, 0x06,
|
||||||
|
0x60, 0xa2, 0x0b, 0x2d,
|
||||||
|
0x9c, 0x35, 0x24, 0x23,
|
||||||
|
0xed, 0xce, 0x25, 0x85,
|
||||||
|
0x7f, 0xcd, 0x37, 0x04,
|
||||||
|
}),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
SignatureScript: nil,
|
||||||
|
Sequence: 4294967295,
|
||||||
|
}}
|
||||||
|
outputs := []*externalapi.DomainTransactionOutput{{
|
||||||
|
Value: 1000000000,
|
||||||
|
ScriptPublicKey: nil,
|
||||||
|
}}
|
||||||
|
tx := &externalapi.DomainTransaction{
|
||||||
|
Version: 1,
|
||||||
|
Inputs: inputs,
|
||||||
|
Outputs: outputs,
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPubKey := mustParseShortForm(test.script)
|
||||||
|
|
||||||
|
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TestCheckErrorCondition: %d: failed to create script: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < test.stepCount; j++ {
|
||||||
|
_, err = vm.Step()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TestCheckErrorCondition: %d: failed to execute step No. %d: %v", i, j+1, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if j != test.stepCount-1 {
|
||||||
|
err = vm.CheckErrorCondition(false)
|
||||||
|
if !IsErrorCode(err, ErrScriptUnfinished) {
|
||||||
|
t.Fatalf("TestCheckErrorCondition: %d: got unexepected error %v on %dth iteration",
|
||||||
|
i, err, j)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vm.CheckErrorCondition(test.finalScript)
|
||||||
|
if e := checkScriptError(err, test.expectedErr); e != nil {
|
||||||
|
t.Errorf("TestCheckErrorCondition: %d: %s", i, e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCheckPubKeyEncoding ensures the internal checkPubKeyEncoding function
|
||||||
|
// works as expected.
|
||||||
|
func TestCheckPubKeyEncoding(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
key []byte
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "uncompressed ok",
|
||||||
|
key: hexToBytes("0411db93e1dcdb8a016b49840f8c53bc1eb68" +
|
||||||
|
"a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf" +
|
||||||
|
"9744464f82e160bfa9b8b64f9d4c03f999b8643f656b" +
|
||||||
|
"412a3"),
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compressed ok",
|
||||||
|
key: hexToBytes("02ce0b14fb842b1ba549fdd675c98075f12e9" +
|
||||||
|
"c510f8ef52bd021a9a1f4809d3b4d"),
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compressed ok",
|
||||||
|
key: hexToBytes("032689c7c2dab13309fb143e0e8fe39634252" +
|
||||||
|
"1887e976690b6b47f5b2a4b7d448e"),
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hybrid",
|
||||||
|
key: hexToBytes("0679be667ef9dcbbac55a06295ce870b07029" +
|
||||||
|
"bfcdb2dce28d959f2815b16f81798483ada7726a3c46" +
|
||||||
|
"55da4fbfc0e1108a8fd17b448a68554199c47d08ffb1" +
|
||||||
|
"0d4b8"),
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
key: nil,
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
vm := Engine{}
|
||||||
|
for _, test := range tests {
|
||||||
|
err := vm.checkPubKeyEncoding(test.key)
|
||||||
|
if err != nil && test.isValid {
|
||||||
|
t.Errorf("checkSignatureLength test '%s' failed "+
|
||||||
|
"when it should have succeeded: %v", test.name,
|
||||||
|
err)
|
||||||
|
} else if err == nil && !test.isValid {
|
||||||
|
t.Errorf("checkSignatureEncooding test '%s' succeeded "+
|
||||||
|
"when it should have failed", test.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisasmPC(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// tx with almost empty scripts.
|
||||||
|
inputs := []*externalapi.DomainTransactionInput{{
|
||||||
|
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||||
|
TransactionID: externalapi.DomainTransactionID([32]byte{
|
||||||
|
0xc9, 0x97, 0xa5, 0xe5,
|
||||||
|
0x6e, 0x10, 0x41, 0x02,
|
||||||
|
0xfa, 0x20, 0x9c, 0x6a,
|
||||||
|
0x85, 0x2d, 0xd9, 0x06,
|
||||||
|
0x60, 0xa2, 0x0b, 0x2d,
|
||||||
|
0x9c, 0x35, 0x24, 0x23,
|
||||||
|
0xed, 0xce, 0x25, 0x85,
|
||||||
|
0x7f, 0xcd, 0x37, 0x04,
|
||||||
|
}),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
SignatureScript: mustParseShortForm("OP_2"),
|
||||||
|
Sequence: 4294967295,
|
||||||
|
}}
|
||||||
|
outputs := []*externalapi.DomainTransactionOutput{{
|
||||||
|
Value: 1000000000,
|
||||||
|
ScriptPublicKey: nil,
|
||||||
|
}}
|
||||||
|
tx := &externalapi.DomainTransaction{
|
||||||
|
Version: 1,
|
||||||
|
Inputs: inputs,
|
||||||
|
Outputs: outputs,
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPubKey := mustParseShortForm("OP_DROP NOP TRUE")
|
||||||
|
|
||||||
|
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create script: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
expected string
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{"00:0000: OP_2", nil},
|
||||||
|
{"01:0000: OP_DROP", nil},
|
||||||
|
{"01:0001: OP_NOP", nil},
|
||||||
|
{"01:0002: OP_1", nil},
|
||||||
|
{"", scriptError(ErrInvalidProgramCounter, "")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
actual, err := vm.DisasmPC()
|
||||||
|
if e := checkScriptError(err, test.expectedErr); e != nil {
|
||||||
|
t.Errorf("TestDisasmPC: %d: %s", i, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("TestDisasmPC: %d: expected: '%s'. Got: '%s'", i, test.expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore results from vm.Step() to keep going even when no opcodes left, to hit error case
|
||||||
|
_, _ = vm.Step()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisasmScript(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// tx with almost empty scripts.
|
||||||
|
inputs := []*externalapi.DomainTransactionInput{{
|
||||||
|
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||||
|
TransactionID: externalapi.DomainTransactionID([32]byte{
|
||||||
|
0xc9, 0x97, 0xa5, 0xe5,
|
||||||
|
0x6e, 0x10, 0x41, 0x02,
|
||||||
|
0xfa, 0x20, 0x9c, 0x6a,
|
||||||
|
0x85, 0x2d, 0xd9, 0x06,
|
||||||
|
0x60, 0xa2, 0x0b, 0x2d,
|
||||||
|
0x9c, 0x35, 0x24, 0x23,
|
||||||
|
0xed, 0xce, 0x25, 0x85,
|
||||||
|
0x7f, 0xcd, 0x37, 0x04,
|
||||||
|
}),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
SignatureScript: mustParseShortForm("OP_2"),
|
||||||
|
Sequence: 4294967295,
|
||||||
|
}}
|
||||||
|
outputs := []*externalapi.DomainTransactionOutput{{
|
||||||
|
Value: 1000000000,
|
||||||
|
ScriptPublicKey: nil,
|
||||||
|
}}
|
||||||
|
tx := &externalapi.DomainTransaction{
|
||||||
|
Version: 1,
|
||||||
|
Inputs: inputs,
|
||||||
|
Outputs: outputs,
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPubKey := mustParseShortForm("OP_DROP NOP TRUE")
|
||||||
|
|
||||||
|
vm, err := NewEngine(scriptPubKey, tx, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create script: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
index int
|
||||||
|
expected string
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{-1, "", scriptError(ErrInvalidIndex, "")},
|
||||||
|
{0, "00:0000: OP_2\n", nil},
|
||||||
|
{1, "01:0000: OP_DROP\n01:0001: OP_NOP\n01:0002: OP_1\n", nil},
|
||||||
|
{2, "", scriptError(ErrInvalidIndex, "")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
actual, err := vm.DisasmScript(test.index)
|
||||||
|
if e := checkScriptError(err, test.expectedErr); e != nil {
|
||||||
|
t.Errorf("TestDisasmScript: %d: %s", test.index, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("TestDisasmScript: %d: expected: '%s'. Got: '%s'", test.index, test.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,12 +12,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
|
||||||
"github.com/kaspanet/go-secp256k1"
|
"github.com/kaspanet/go-secp256k1"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||||
|
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// An opcode defines the information related to a txscript opcode. opfunc, if
|
// An opcode defines the information related to a txscript opcode. opfunc, if
|
||||||
@ -1149,7 +1149,7 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
|
|||||||
|
|
||||||
// The lock time feature can also be disabled, thereby bypassing
|
// The lock time feature can also be disabled, thereby bypassing
|
||||||
// OP_CHECKLOCKTIMEVERIFY, if every transaction input has been finalized by
|
// OP_CHECKLOCKTIMEVERIFY, if every transaction input has been finalized by
|
||||||
// setting its sequence to the maximum value (appmessage.MaxTxInSequenceNum). This
|
// setting its sequence to the maximum value (constants.MaxTxInSequenceNum). This
|
||||||
// condition would result in the transaction being allowed into the blockDAG
|
// condition would result in the transaction being allowed into the blockDAG
|
||||||
// making the opcode ineffective.
|
// making the opcode ineffective.
|
||||||
//
|
//
|
||||||
@ -1161,7 +1161,7 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
|
|||||||
// NOTE: This implies that even if the transaction is not finalized due to
|
// NOTE: This implies that even if the transaction is not finalized due to
|
||||||
// another input being unlocked, the opcode execution will still fail when the
|
// another input being unlocked, the opcode execution will still fail when the
|
||||||
// input being used by the opcode is locked.
|
// input being used by the opcode is locked.
|
||||||
if vm.tx.Inputs[vm.txIdx].Sequence == appmessage.MaxTxInSequenceNum {
|
if vm.tx.Inputs[vm.txIdx].Sequence == constants.MaxTxInSequenceNum {
|
||||||
return scriptError(ErrUnsatisfiedLockTime,
|
return scriptError(ErrUnsatisfiedLockTime,
|
||||||
"transaction input is finalized")
|
"transaction input is finalized")
|
||||||
}
|
}
|
||||||
@ -1205,7 +1205,7 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
|||||||
// To provide for future soft-fork extensibility, if the
|
// To provide for future soft-fork extensibility, if the
|
||||||
// operand has the disabled lock-time flag set,
|
// operand has the disabled lock-time flag set,
|
||||||
// CHECKSEQUENCEVERIFY behaves as a NOP.
|
// CHECKSEQUENCEVERIFY behaves as a NOP.
|
||||||
if sequence&uint64(appmessage.SequenceLockTimeDisabled) != 0 {
|
if sequence&uint64(constants.SequenceLockTimeDisabled) != 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1214,17 +1214,17 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
|||||||
// number does not have this bit set prevents using this property
|
// number does not have this bit set prevents using this property
|
||||||
// to get around a CHECKSEQUENCEVERIFY check.
|
// to get around a CHECKSEQUENCEVERIFY check.
|
||||||
txSequence := vm.tx.Inputs[vm.txIdx].Sequence
|
txSequence := vm.tx.Inputs[vm.txIdx].Sequence
|
||||||
if txSequence&appmessage.SequenceLockTimeDisabled != 0 {
|
if txSequence&constants.SequenceLockTimeDisabled != 0 {
|
||||||
str := fmt.Sprintf("transaction sequence has sequence "+
|
str := fmt.Sprintf("transaction sequence has sequence "+
|
||||||
"locktime disabled bit set: 0x%x", txSequence)
|
"locktime disabled bit set: 0x%x", txSequence)
|
||||||
return scriptError(ErrUnsatisfiedLockTime, str)
|
return scriptError(ErrUnsatisfiedLockTime, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mask off non-consensus bits before doing comparisons.
|
// Mask off non-consensus bits before doing comparisons.
|
||||||
lockTimeMask := uint64(appmessage.SequenceLockTimeIsSeconds |
|
lockTimeMask := uint64(constants.SequenceLockTimeIsSeconds |
|
||||||
appmessage.SequenceLockTimeMask)
|
constants.SequenceLockTimeMask)
|
||||||
return verifyLockTime(txSequence&lockTimeMask,
|
return verifyLockTime(txSequence&lockTimeMask,
|
||||||
appmessage.SequenceLockTimeIsSeconds, sequence&lockTimeMask)
|
constants.SequenceLockTimeIsSeconds, sequence&lockTimeMask)
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcodeToAltStack removes the top item from the main data stack and pushes it
|
// opcodeToAltStack removes the top item from the main data stack and pushes it
|
||||||
|
398
domain/consensus/utils/txscript/reference_test.go
Normal file
398
domain/consensus/utils/txscript/reference_test.go
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
// 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"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 "", errors.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 into a script as follows:
|
||||||
|
// - 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, errors.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, errors.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 "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, errors.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, scriptPubKey []byte) *externalapi.DomainTransaction {
|
||||||
|
outpoint := externalapi.DomainOutpoint{
|
||||||
|
TransactionID: externalapi.DomainTransactionID{},
|
||||||
|
Index: ^uint32(0),
|
||||||
|
}
|
||||||
|
input := &externalapi.DomainTransactionInput{
|
||||||
|
PreviousOutpoint: outpoint,
|
||||||
|
SignatureScript: []byte{Op0, Op0},
|
||||||
|
Sequence: constants.MaxTxInSequenceNum,
|
||||||
|
}
|
||||||
|
output := &externalapi.DomainTransactionOutput{Value: 0, ScriptPublicKey: scriptPubKey}
|
||||||
|
coinbaseTx := &externalapi.DomainTransaction{
|
||||||
|
Version: 1,
|
||||||
|
Inputs: []*externalapi.DomainTransactionInput{input},
|
||||||
|
Outputs: []*externalapi.DomainTransactionOutput{output},
|
||||||
|
}
|
||||||
|
|
||||||
|
outpoint = externalapi.DomainOutpoint{
|
||||||
|
TransactionID: *consensusserialization.TransactionID(coinbaseTx),
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
input = &externalapi.DomainTransactionInput{
|
||||||
|
PreviousOutpoint: outpoint,
|
||||||
|
SignatureScript: sigScript,
|
||||||
|
Sequence: constants.MaxTxInSequenceNum,
|
||||||
|
}
|
||||||
|
output = &externalapi.DomainTransactionOutput{Value: 0, ScriptPublicKey: nil}
|
||||||
|
spendingTx := &externalapi.DomainTransaction{
|
||||||
|
Version: 1,
|
||||||
|
Inputs: []*externalapi.DomainTransactionInput{input},
|
||||||
|
Outputs: []*externalapi.DomainTransactionOutput{output},
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
var scriptErr Error
|
||||||
|
if ok := errors.As(err, &scriptErr); ok {
|
||||||
|
t.Errorf("%s: want error codes %v, got %v", name,
|
||||||
|
allowedErrorCodes, scriptErr.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable non-test logs
|
||||||
|
logLevel := log.Level()
|
||||||
|
log.SetLevel(logger.LevelOff)
|
||||||
|
defer log.SetLevel(logLevel)
|
||||||
|
|
||||||
|
// Run all script tests with and without the signature cache.
|
||||||
|
testScripts(t, tests, true)
|
||||||
|
testScripts(t, tests, false)
|
||||||
|
}
|
@ -7,6 +7,7 @@ package txscript
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -255,8 +256,6 @@ func shallowCopyTx(tx *externalapi.DomainTransaction) externalapi.DomainTransact
|
|||||||
// for the copied inputs and outputs and point the final slice of
|
// for the copied inputs and outputs and point the final slice of
|
||||||
// pointers into the contiguous arrays. This avoids a lot of small
|
// pointers into the contiguous arrays. This avoids a lot of small
|
||||||
// allocations.
|
// allocations.
|
||||||
// Specifically avoid using appmessage.NewMsgTx() to prevent correcting errors by
|
|
||||||
// auto-generating various fields.
|
|
||||||
txCopy := externalapi.DomainTransaction{
|
txCopy := externalapi.DomainTransaction{
|
||||||
Version: tx.Version,
|
Version: tx.Version,
|
||||||
Inputs: make([]*externalapi.DomainTransactionInput, len(tx.Inputs)),
|
Inputs: make([]*externalapi.DomainTransactionInput, len(tx.Inputs)),
|
||||||
|
3973
domain/consensus/utils/txscript/script_test.go
Normal file
3973
domain/consensus/utils/txscript/script_test.go
Normal file
File diff suppressed because it is too large
Load Diff
1101
domain/consensus/utils/txscript/sign_test.go
Normal file
1101
domain/consensus/utils/txscript/sign_test.go
Normal file
File diff suppressed because it is too large
Load Diff
506
domain/consensus/utils/txscript/standard_test.go
Normal file
506
domain/consensus/utils/txscript/standard_test.go
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||||
|
"github.com/kaspanet/kaspad/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mustParseShortForm parses the passed short form script and returns the
|
||||||
|
// resulting bytes. It panics if an error occurs. This is only used in the
|
||||||
|
// tests as a helper since the only way it can fail is if there is an error in
|
||||||
|
// the test source code.
|
||||||
|
func mustParseShortForm(script string) []byte {
|
||||||
|
s, err := parseShortForm(script)
|
||||||
|
if err != nil {
|
||||||
|
panic("invalid short form script in test source: err " +
|
||||||
|
err.Error() + ", script: " + script)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAddressPubKeyHash returns a new util.AddressPubKeyHash from the
|
||||||
|
// provided hash. It panics if an error occurs. This is only used in the tests
|
||||||
|
// as a helper since the only way it can fail is if there is an error in the
|
||||||
|
// test source code.
|
||||||
|
func newAddressPubKeyHash(pkHash []byte) util.Address {
|
||||||
|
addr, err := util.NewAddressPubKeyHash(pkHash, util.Bech32PrefixKaspa)
|
||||||
|
if err != nil {
|
||||||
|
panic("invalid public key hash in test source")
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAddressScriptHash returns a new util.AddressScriptHash from the
|
||||||
|
// provided hash. It panics if an error occurs. This is only used in the tests
|
||||||
|
// as a helper since the only way it can fail is if there is an error in the
|
||||||
|
// test source code.
|
||||||
|
func newAddressScriptHash(scriptHash []byte) util.Address {
|
||||||
|
addr, err := util.NewAddressScriptHashFromHash(scriptHash,
|
||||||
|
util.Bech32PrefixKaspa)
|
||||||
|
if err != nil {
|
||||||
|
panic("invalid script hash in test source")
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtractScriptPubKeyAddrs ensures that extracting the type, addresses, and
|
||||||
|
// number of required signatures from scriptPubKeys works as intended.
|
||||||
|
func TestExtractScriptPubKeyAddrs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
script []byte
|
||||||
|
addr util.Address
|
||||||
|
class ScriptClass
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "standard p2pkh",
|
||||||
|
script: hexToBytes("76a914ad06dd6ddee55cbca9a9e3713bd" +
|
||||||
|
"7587509a3056488ac"),
|
||||||
|
addr: newAddressPubKeyHash(hexToBytes("ad06dd6ddee5" +
|
||||||
|
"5cbca9a9e3713bd7587509a30564")),
|
||||||
|
class: PubKeyHashTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "standard p2sh",
|
||||||
|
script: hexToBytes("a91463bcc565f9e68ee0189dd5cc67f1b" +
|
||||||
|
"0e5f02f45cb87"),
|
||||||
|
addr: newAddressScriptHash(hexToBytes("63bcc565f9e6" +
|
||||||
|
"8ee0189dd5cc67f1b0e5f02f45cb")),
|
||||||
|
class: ScriptHashTy,
|
||||||
|
},
|
||||||
|
|
||||||
|
// The below are nonstandard script due to things such as
|
||||||
|
// invalid pubkeys, failure to parse, and not being of a
|
||||||
|
// standard form.
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "p2pk with uncompressed pk missing OP_CHECKSIG",
|
||||||
|
script: hexToBytes("410411db93e1dcdb8a016b49840f8c53b" +
|
||||||
|
"c1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddf" +
|
||||||
|
"b84ccf9744464f82e160bfa9b8b64f9d4c03f999b864" +
|
||||||
|
"3f656b412a3"),
|
||||||
|
addr: nil,
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid signature from a sigscript - no addresses",
|
||||||
|
script: hexToBytes("47304402204e45e16932b8af514961a1d" +
|
||||||
|
"3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41022" +
|
||||||
|
"0181522ec8eca07de4860a4acdd12909d831cc56cbba" +
|
||||||
|
"c4622082221a8768d1d0901"),
|
||||||
|
addr: nil,
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
// Note the technically the pubkey is the second item on the
|
||||||
|
// stack, but since the address extraction intentionally only
|
||||||
|
// works with standard scriptPubKeys, this should not return any
|
||||||
|
// addresses.
|
||||||
|
{
|
||||||
|
name: "valid sigscript to reedeem p2pk - no addresses",
|
||||||
|
script: hexToBytes("493046022100ddc69738bf2336318e4e0" +
|
||||||
|
"41a5a77f305da87428ab1606f023260017854350ddc0" +
|
||||||
|
"22100817af09d2eec36862d16009852b7e3a0f6dd765" +
|
||||||
|
"98290b7834e1453660367e07a014104cd4240c198e12" +
|
||||||
|
"523b6f9cb9f5bed06de1ba37e96a1bbd13745fcf9d11" +
|
||||||
|
"c25b1dff9a519675d198804ba9962d3eca2d5937d58e" +
|
||||||
|
"5a75a71042d40388a4d307f887d"),
|
||||||
|
addr: nil,
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty script",
|
||||||
|
script: []byte{},
|
||||||
|
addr: nil,
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "script that does not parse",
|
||||||
|
script: []byte{OpData45},
|
||||||
|
addr: nil,
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests.", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
class, addr, _ := ExtractScriptPubKeyAddress(
|
||||||
|
test.script, &dagconfig.MainnetParams)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(addr, test.addr) {
|
||||||
|
t.Errorf("ExtractScriptPubKeyAddress #%d (%s) unexpected "+
|
||||||
|
"address\ngot %v\nwant %v", i, test.name,
|
||||||
|
addr, test.addr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if class != test.class {
|
||||||
|
t.Errorf("ExtractScriptPubKeyAddress #%d (%s) unexpected "+
|
||||||
|
"script type - got %s, want %s", i, test.name,
|
||||||
|
class, test.class)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCalcScriptInfo ensures the CalcScriptInfo provides the expected results
|
||||||
|
// for various valid and invalid script pairs.
|
||||||
|
func TestCalcScriptInfo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
sigScript string
|
||||||
|
scriptPubKey string
|
||||||
|
|
||||||
|
isP2SH bool
|
||||||
|
|
||||||
|
scriptInfo ScriptInfo
|
||||||
|
scriptInfoErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Invented scripts, the hashes do not match
|
||||||
|
// Truncated version of test below:
|
||||||
|
name: "scriptPubKey doesn't parse",
|
||||||
|
sigScript: "1 81 DATA_8 2DUP EQUAL NOT VERIFY ABS " +
|
||||||
|
"SWAP ABS EQUAL",
|
||||||
|
scriptPubKey: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
|
||||||
|
"3152205ec4f59c",
|
||||||
|
isP2SH: true,
|
||||||
|
scriptInfoErr: scriptError(ErrMalformedPush, ""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sigScript doesn't parse",
|
||||||
|
// Truncated version of p2sh script below.
|
||||||
|
sigScript: "1 81 DATA_8 2DUP EQUAL NOT VERIFY ABS " +
|
||||||
|
"SWAP ABS",
|
||||||
|
scriptPubKey: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
|
||||||
|
"3152205ec4f59c74 EQUAL",
|
||||||
|
isP2SH: true,
|
||||||
|
scriptInfoErr: scriptError(ErrMalformedPush, ""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Invented scripts, the hashes do not match
|
||||||
|
name: "p2sh standard script",
|
||||||
|
sigScript: "1 81 DATA_25 DUP HASH160 DATA_20 0x010203" +
|
||||||
|
"0405060708090a0b0c0d0e0f1011121314 EQUALVERIFY " +
|
||||||
|
"CHECKSIG",
|
||||||
|
scriptPubKey: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
|
||||||
|
"3152205ec4f59c74 EQUAL",
|
||||||
|
isP2SH: true,
|
||||||
|
scriptInfo: ScriptInfo{
|
||||||
|
ScriptPubKeyClass: ScriptHashTy,
|
||||||
|
NumInputs: 3,
|
||||||
|
ExpectedInputs: 3, // nonstandard p2sh.
|
||||||
|
SigOps: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "p2sh nonstandard script",
|
||||||
|
sigScript: "1 81 DATA_8 2DUP EQUAL NOT VERIFY ABS " +
|
||||||
|
"SWAP ABS EQUAL",
|
||||||
|
scriptPubKey: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
|
||||||
|
"3152205ec4f59c74 EQUAL",
|
||||||
|
isP2SH: true,
|
||||||
|
scriptInfo: ScriptInfo{
|
||||||
|
ScriptPubKeyClass: ScriptHashTy,
|
||||||
|
NumInputs: 3,
|
||||||
|
ExpectedInputs: -1, // nonstandard p2sh.
|
||||||
|
SigOps: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
sigScript := mustParseShortForm(test.sigScript)
|
||||||
|
scriptPubKey := mustParseShortForm(test.scriptPubKey)
|
||||||
|
|
||||||
|
si, err := CalcScriptInfo(sigScript, scriptPubKey, test.isP2SH)
|
||||||
|
if e := checkScriptError(err, test.scriptInfoErr); e != nil {
|
||||||
|
t.Errorf("scriptinfo test %q: %v", test.name, e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if *si != test.scriptInfo {
|
||||||
|
t.Errorf("%s: scriptinfo doesn't match expected. "+
|
||||||
|
"got: %q expected %q", test.name, *si,
|
||||||
|
test.scriptInfo)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bogusAddress implements the util.Address interface so the tests can ensure
|
||||||
|
// unsupported address types are handled properly.
|
||||||
|
type bogusAddress struct{}
|
||||||
|
|
||||||
|
// EncodeAddress simply returns an empty string. It exists to satisfy the
|
||||||
|
// util.Address interface.
|
||||||
|
func (b *bogusAddress) EncodeAddress() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptAddress simply returns an empty byte slice. It exists to satisfy the
|
||||||
|
// util.Address interface.
|
||||||
|
func (b *bogusAddress) ScriptAddress() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsForPrefix lies blatantly to satisfy the util.Address interface.
|
||||||
|
func (b *bogusAddress) IsForPrefix(prefix util.Bech32Prefix) bool {
|
||||||
|
return true // why not?
|
||||||
|
}
|
||||||
|
|
||||||
|
// String simply returns an empty string. It exists to satisfy the
|
||||||
|
// util.Address interface.
|
||||||
|
func (b *bogusAddress) String() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bogusAddress) Prefix() util.Bech32Prefix {
|
||||||
|
return util.Bech32PrefixUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPayToAddrScript ensures the PayToAddrScript function generates the
|
||||||
|
// correct scripts for the various types of addresses.
|
||||||
|
func TestPayToAddrScript(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// 1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX
|
||||||
|
p2pkhMain, err := util.NewAddressPubKeyHash(hexToBytes("e34cce70c86"+
|
||||||
|
"373273efcc54ce7d2a491bb4a0e84"), util.Bech32PrefixKaspa)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create public key hash address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from transaction:
|
||||||
|
// b0539a45de13b3e0403909b8bd1a555b8cbe45fd4e3f3fda76f3a5f52835c29d
|
||||||
|
p2shMain, _ := util.NewAddressScriptHashFromHash(hexToBytes("e8c300"+
|
||||||
|
"c87986efa84c37c0519929019ef86eb5b4"), util.Bech32PrefixKaspa)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create script hash address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors used in the tests below defined here for convenience and to
|
||||||
|
// keep the horizontal test size shorter.
|
||||||
|
errUnsupportedAddress := scriptError(ErrUnsupportedAddress, "")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in util.Address
|
||||||
|
expected string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
// pay-to-pubkey-hash address on mainnet
|
||||||
|
{
|
||||||
|
p2pkhMain,
|
||||||
|
"DUP HASH160 DATA_20 0xe34cce70c86373273efcc54ce7d2a4" +
|
||||||
|
"91bb4a0e8488 CHECKSIG",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
// pay-to-script-hash address on mainnet
|
||||||
|
{
|
||||||
|
p2shMain,
|
||||||
|
"HASH160 DATA_20 0xe8c300c87986efa84c37c0519929019ef8" +
|
||||||
|
"6eb5b4 EQUAL",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Supported address types with nil pointers.
|
||||||
|
{(*util.AddressPubKeyHash)(nil), "", errUnsupportedAddress},
|
||||||
|
{(*util.AddressScriptHash)(nil), "", errUnsupportedAddress},
|
||||||
|
|
||||||
|
// Unsupported address type.
|
||||||
|
{&bogusAddress{}, "", errUnsupportedAddress},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
scriptPubKey, err := PayToAddrScript(test.in)
|
||||||
|
if e := checkScriptError(err, test.err); e != nil {
|
||||||
|
t.Errorf("PayToAddrScript #%d unexpected error - "+
|
||||||
|
"got %v, want %v", i, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := mustParseShortForm(test.expected)
|
||||||
|
if !bytes.Equal(scriptPubKey, expected) {
|
||||||
|
t.Errorf("PayToAddrScript #%d got: %x\nwant: %x",
|
||||||
|
i, scriptPubKey, expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scriptClassTests houses several test scripts used to ensure various class
|
||||||
|
// determination is working as expected. It's defined as a test global versus
|
||||||
|
// inside a function scope since this spans both the standard tests and the
|
||||||
|
// consensus tests (pay-to-script-hash is part of consensus).
|
||||||
|
var scriptClassTests = []struct {
|
||||||
|
name string
|
||||||
|
script string
|
||||||
|
class ScriptClass
|
||||||
|
}{
|
||||||
|
// p2pk
|
||||||
|
{
|
||||||
|
name: "Pay Pubkey",
|
||||||
|
script: "DATA_65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e" +
|
||||||
|
"97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e16" +
|
||||||
|
"0bfa9b8b64f9d4c03f999b8643f656b412a3 CHECKSIG",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
// tx 599e47a8114fe098103663029548811d2651991b62397e057f0c863c2bc9f9ea
|
||||||
|
{
|
||||||
|
name: "Pay PubkeyHash",
|
||||||
|
script: "DUP HASH160 DATA_20 0x660d4ef3a743e3e696ad990364e555" +
|
||||||
|
"c271ad504b EQUALVERIFY CHECKSIG",
|
||||||
|
class: PubKeyHashTy,
|
||||||
|
},
|
||||||
|
// mutlisig
|
||||||
|
{
|
||||||
|
name: "multisig",
|
||||||
|
script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da4" +
|
||||||
|
"5329a00357b3a7886211ab414d55a 1 CHECKMULTISIG",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
// tx e5779b9e78f9650debc2893fd9636d827b26b4ddfa6a8172fe8708c924f5c39d
|
||||||
|
{
|
||||||
|
name: "P2SH",
|
||||||
|
script: "HASH160 DATA_20 0x433ec2ac1ffa1b7b7d027f564529c57197f" +
|
||||||
|
"9ae88 EQUAL",
|
||||||
|
class: ScriptHashTy,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Nulldata. It is standard in Bitcoin but not in Kaspa
|
||||||
|
name: "nulldata",
|
||||||
|
script: "RETURN 0",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
|
||||||
|
// The next few are almost multisig (it is the more complex script type)
|
||||||
|
// but with various changes to make it fail.
|
||||||
|
{
|
||||||
|
// Multisig but invalid nsigs.
|
||||||
|
name: "strange 1",
|
||||||
|
script: "DUP DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da45" +
|
||||||
|
"329a00357b3a7886211ab414d55a 1 CHECKMULTISIG",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multisig but invalid pubkey.
|
||||||
|
name: "strange 2",
|
||||||
|
script: "1 1 1 CHECKMULTISIG",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multisig but no matching npubkeys opcode.
|
||||||
|
name: "strange 3",
|
||||||
|
script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da4532" +
|
||||||
|
"9a00357b3a7886211ab414d55a DATA_33 0x0232abdc893e7f0" +
|
||||||
|
"631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a " +
|
||||||
|
"CHECKMULTISIG",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multisig but with multisigverify.
|
||||||
|
name: "strange 4",
|
||||||
|
script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da4532" +
|
||||||
|
"9a00357b3a7886211ab414d55a 1 CHECKMULTISIGVERIFY",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multisig but wrong length.
|
||||||
|
name: "strange 5",
|
||||||
|
script: "1 CHECKMULTISIG",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "doesn't parse",
|
||||||
|
script: "DATA_5 0x01020304",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multisig script with wrong number of pubkeys",
|
||||||
|
script: "2 " +
|
||||||
|
"DATA_33 " +
|
||||||
|
"0x027adf5df7c965a2d46203c781bd4dd8" +
|
||||||
|
"21f11844136f6673af7cc5a4a05cd29380 " +
|
||||||
|
"DATA_33 " +
|
||||||
|
"0x02c08f3de8ee2de9be7bd770f4c10eb0" +
|
||||||
|
"d6ff1dd81ee96eedd3a9d4aeaf86695e80 " +
|
||||||
|
"3 CHECKMULTISIG",
|
||||||
|
class: NonStandardTy,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestScriptClass ensures all the scripts in scriptClassTests have the expected
|
||||||
|
// class.
|
||||||
|
func TestScriptClass(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, test := range scriptClassTests {
|
||||||
|
script := mustParseShortForm(test.script)
|
||||||
|
class := GetScriptClass(script)
|
||||||
|
if class != test.class {
|
||||||
|
t.Errorf("%s: expected %s got %s (script %x)", test.name,
|
||||||
|
test.class, class, script)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStringifyClass ensures the script class string returns the expected
|
||||||
|
// string for each script class.
|
||||||
|
func TestStringifyClass(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
class ScriptClass
|
||||||
|
stringed string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nonstandardty",
|
||||||
|
class: NonStandardTy,
|
||||||
|
stringed: "nonstandard",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pubkeyhash",
|
||||||
|
class: PubKeyHashTy,
|
||||||
|
stringed: "pubkeyhash",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scripthash",
|
||||||
|
class: ScriptHashTy,
|
||||||
|
stringed: "scripthash",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "broken",
|
||||||
|
class: ScriptClass(255),
|
||||||
|
stringed: "Invalid",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
typeString := test.class.String()
|
||||||
|
if typeString != test.stringed {
|
||||||
|
t.Errorf("%s: got %#q, want %#q", test.name,
|
||||||
|
typeString, test.stringed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
|
||||||
consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||||
"github.com/kaspanet/kaspad/util"
|
"github.com/kaspanet/kaspad/util"
|
||||||
@ -174,7 +175,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
|||||||
dummyTxIn := consensusexternalapi.DomainTransactionInput{
|
dummyTxIn := consensusexternalapi.DomainTransactionInput{
|
||||||
PreviousOutpoint: dummyPrevOut,
|
PreviousOutpoint: dummyPrevOut,
|
||||||
SignatureScript: dummySigScript,
|
SignatureScript: dummySigScript,
|
||||||
Sequence: appmessage.MaxTxInSequenceNum,
|
Sequence: constants.MaxTxInSequenceNum,
|
||||||
}
|
}
|
||||||
addrHash := [20]byte{0x01}
|
addrHash := [20]byte{0x01}
|
||||||
addr, err := util.NewAddressPubKeyHash(addrHash[:], util.Bech32PrefixKaspaTest)
|
addr, err := util.NewAddressPubKeyHash(addrHash[:], util.Bech32PrefixKaspaTest)
|
||||||
@ -205,7 +206,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Transaction version too high",
|
name: "Transaction version too high",
|
||||||
tx: consensusexternalapi.DomainTransaction{Version: appmessage.TxVersion + 1, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
tx: consensusexternalapi.DomainTransaction{Version: constants.TransactionVersion + 1, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
||||||
height: 300000,
|
height: 300000,
|
||||||
isStandard: false,
|
isStandard: false,
|
||||||
code: RejectNonstandard,
|
code: RejectNonstandard,
|
||||||
@ -238,7 +239,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
|||||||
PreviousOutpoint: dummyPrevOut,
|
PreviousOutpoint: dummyPrevOut,
|
||||||
SignatureScript: bytes.Repeat([]byte{0x00},
|
SignatureScript: bytes.Repeat([]byte{0x00},
|
||||||
maxStandardSigScriptSize+1),
|
maxStandardSigScriptSize+1),
|
||||||
Sequence: appmessage.MaxTxInSequenceNum,
|
Sequence: constants.MaxTxInSequenceNum,
|
||||||
}}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
}}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
||||||
height: 300000,
|
height: 300000,
|
||||||
isStandard: false,
|
isStandard: false,
|
||||||
@ -250,7 +251,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
|||||||
PreviousOutpoint: dummyPrevOut,
|
PreviousOutpoint: dummyPrevOut,
|
||||||
SignatureScript: []byte{
|
SignatureScript: []byte{
|
||||||
txscript.OpCheckSigVerify},
|
txscript.OpCheckSigVerify},
|
||||||
Sequence: appmessage.MaxTxInSequenceNum,
|
Sequence: constants.MaxTxInSequenceNum,
|
||||||
}}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
}}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
||||||
height: 300000,
|
height: 300000,
|
||||||
isStandard: false,
|
isStandard: false,
|
||||||
|
@ -45,7 +45,7 @@ func (x *TransactionMessage) toAppMessage() (appmessage.Message, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var payloadHash *externalapi.DomainHash
|
payloadHash := &externalapi.DomainHash{}
|
||||||
if x.PayloadHash != nil {
|
if x.PayloadHash != nil {
|
||||||
payloadHash, err = x.PayloadHash.toDomain()
|
payloadHash, err = x.PayloadHash.toDomain()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -60,7 +60,7 @@ func (x *TransactionMessage) toAppMessage() (appmessage.Message, error) {
|
|||||||
LockTime: x.LockTime,
|
LockTime: x.LockTime,
|
||||||
SubnetworkID: *subnetworkID,
|
SubnetworkID: *subnetworkID,
|
||||||
Gas: x.Gas,
|
Gas: x.Gas,
|
||||||
PayloadHash: payloadHash,
|
PayloadHash: *payloadHash,
|
||||||
Payload: x.Payload,
|
Payload: x.Payload,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -86,10 +86,6 @@ func (x *TransactionMessage) fromAppMessage(msgTx *appmessage.MsgTx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var payloadHash *Hash
|
|
||||||
if msgTx.PayloadHash != nil {
|
|
||||||
payloadHash = domainHashToProto(msgTx.PayloadHash)
|
|
||||||
}
|
|
||||||
*x = TransactionMessage{
|
*x = TransactionMessage{
|
||||||
Version: msgTx.Version,
|
Version: msgTx.Version,
|
||||||
Inputs: protoInputs,
|
Inputs: protoInputs,
|
||||||
@ -97,7 +93,7 @@ func (x *TransactionMessage) fromAppMessage(msgTx *appmessage.MsgTx) {
|
|||||||
LockTime: msgTx.LockTime,
|
LockTime: msgTx.LockTime,
|
||||||
SubnetworkID: domainSubnetworkIDToProto(&msgTx.SubnetworkID),
|
SubnetworkID: domainSubnetworkIDToProto(&msgTx.SubnetworkID),
|
||||||
Gas: msgTx.Gas,
|
Gas: msgTx.Gas,
|
||||||
PayloadHash: payloadHash,
|
PayloadHash: domainHashToProto(&msgTx.PayloadHash),
|
||||||
Payload: msgTx.Payload,
|
Payload: msgTx.Payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user