mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-03 20:56: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,
|
||||
SubnetworkID: domainTransaction.SubnetworkID,
|
||||
Gas: domainTransaction.Gas,
|
||||
PayloadHash: &domainTransaction.PayloadHash,
|
||||
PayloadHash: domainTransaction.PayloadHash,
|
||||
Payload: domainTransaction.Payload,
|
||||
}
|
||||
}
|
||||
@ -121,7 +121,7 @@ func MsgTxToDomainTransaction(msgTx *MsgTx) *externalapi.DomainTransaction {
|
||||
LockTime: msgTx.LockTime,
|
||||
SubnetworkID: msgTx.SubnetworkID,
|
||||
Gas: msgTx.Gas,
|
||||
PayloadHash: *msgTx.PayloadHash,
|
||||
PayloadHash: msgTx.PayloadHash,
|
||||
Payload: msgTx.Payload,
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,10 @@ package appmessage
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
@ -19,38 +20,10 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
// outpoint can be.
|
||||
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
|
||||
// transaction inputs and outputs. The array will dynamically grow as needed,
|
||||
// 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{
|
||||
PreviousOutpoint: *prevOut,
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: MaxTxInSequenceNum,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +136,7 @@ type MsgTx struct {
|
||||
LockTime uint64
|
||||
SubnetworkID externalapi.DomainSubnetworkID
|
||||
Gas uint64
|
||||
PayloadHash *externalapi.DomainHash
|
||||
PayloadHash externalapi.DomainHash
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
@ -310,9 +283,9 @@ func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externa
|
||||
txOut = make([]*TxOut, 0, defaultTxInOutAlloc)
|
||||
}
|
||||
|
||||
var payloadHash *externalapi.DomainHash
|
||||
var payloadHash externalapi.DomainHash
|
||||
if *subnetworkID != subnetworks.SubnetworkIDNative {
|
||||
payloadHash = hashes.HashData(payload)
|
||||
payloadHash = *hashes.HashData(payload)
|
||||
}
|
||||
|
||||
return &MsgTx{
|
||||
|
@ -1,7 +1,6 @@
|
||||
package transactionvalidator
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"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
|
||||
// this input can be spent.
|
||||
sequenceNum := input.Sequence
|
||||
relativeLock := int64(sequenceNum & appmessage.SequenceLockTimeMask)
|
||||
relativeLock := int64(sequenceNum & constants.SequenceLockTimeMask)
|
||||
|
||||
switch {
|
||||
// Relative time locks are disabled for this input, so we can
|
||||
// skip any further calculation.
|
||||
case sequenceNum&appmessage.SequenceLockTimeDisabled == appmessage.SequenceLockTimeDisabled:
|
||||
case sequenceNum&constants.SequenceLockTimeDisabled == constants.SequenceLockTimeDisabled:
|
||||
continue
|
||||
case sequenceNum&appmessage.SequenceLockTimeIsSeconds == appmessage.SequenceLockTimeIsSeconds:
|
||||
case sequenceNum&constants.SequenceLockTimeIsSeconds == constants.SequenceLockTimeIsSeconds:
|
||||
// This input requires a relative time lock expressed
|
||||
// in seconds before it can be spent. Therefore, we
|
||||
// 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
|
||||
// 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
|
||||
// subtract one from the relative lock to maintain the original
|
||||
// lockTime semantics.
|
||||
timeLockMilliseconds := (relativeLock << appmessage.SequenceLockTimeGranularity) - 1
|
||||
timeLockMilliseconds := (relativeLock << constants.SequenceLockTimeGranularity) - 1
|
||||
timeLock := medianTime + timeLockMilliseconds
|
||||
if timeLock > sequenceLock.Milliseconds {
|
||||
sequenceLock.Milliseconds = timeLock
|
||||
|
@ -1,5 +1,7 @@
|
||||
package constants
|
||||
|
||||
import "math"
|
||||
|
||||
const (
|
||||
// BlockVersion represents the current version of blocks mined and the maximum block version
|
||||
// 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 = 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"
|
||||
"hash"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
)
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// another input being unlocked, the opcode execution will still fail when the
|
||||
// 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,
|
||||
"transaction input is finalized")
|
||||
}
|
||||
@ -1205,7 +1205,7 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
||||
// To provide for future soft-fork extensibility, if the
|
||||
// operand has the disabled lock-time flag set,
|
||||
// CHECKSEQUENCEVERIFY behaves as a NOP.
|
||||
if sequence&uint64(appmessage.SequenceLockTimeDisabled) != 0 {
|
||||
if sequence&uint64(constants.SequenceLockTimeDisabled) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1214,17 +1214,17 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
||||
// number does not have this bit set prevents using this property
|
||||
// to get around a CHECKSEQUENCEVERIFY check.
|
||||
txSequence := vm.tx.Inputs[vm.txIdx].Sequence
|
||||
if txSequence&appmessage.SequenceLockTimeDisabled != 0 {
|
||||
if txSequence&constants.SequenceLockTimeDisabled != 0 {
|
||||
str := fmt.Sprintf("transaction sequence has sequence "+
|
||||
"locktime disabled bit set: 0x%x", txSequence)
|
||||
return scriptError(ErrUnsatisfiedLockTime, str)
|
||||
}
|
||||
|
||||
// Mask off non-consensus bits before doing comparisons.
|
||||
lockTimeMask := uint64(appmessage.SequenceLockTimeIsSeconds |
|
||||
appmessage.SequenceLockTimeMask)
|
||||
lockTimeMask := uint64(constants.SequenceLockTimeIsSeconds |
|
||||
constants.SequenceLockTimeMask)
|
||||
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
|
||||
|
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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
|
||||
"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
|
||||
// pointers into the contiguous arrays. This avoids a lot of small
|
||||
// allocations.
|
||||
// Specifically avoid using appmessage.NewMsgTx() to prevent correcting errors by
|
||||
// auto-generating various fields.
|
||||
txCopy := externalapi.DomainTransaction{
|
||||
Version: tx.Version,
|
||||
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"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
|
||||
consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
@ -174,7 +175,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
||||
dummyTxIn := consensusexternalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: dummyPrevOut,
|
||||
SignatureScript: dummySigScript,
|
||||
Sequence: appmessage.MaxTxInSequenceNum,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}
|
||||
addrHash := [20]byte{0x01}
|
||||
addr, err := util.NewAddressPubKeyHash(addrHash[:], util.Bech32PrefixKaspaTest)
|
||||
@ -205,7 +206,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
||||
},
|
||||
{
|
||||
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,
|
||||
isStandard: false,
|
||||
code: RejectNonstandard,
|
||||
@ -238,7 +239,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
||||
PreviousOutpoint: dummyPrevOut,
|
||||
SignatureScript: bytes.Repeat([]byte{0x00},
|
||||
maxStandardSigScriptSize+1),
|
||||
Sequence: appmessage.MaxTxInSequenceNum,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
||||
height: 300000,
|
||||
isStandard: false,
|
||||
@ -250,7 +251,7 @@ func TestCheckTransactionStandard(t *testing.T) {
|
||||
PreviousOutpoint: dummyPrevOut,
|
||||
SignatureScript: []byte{
|
||||
txscript.OpCheckSigVerify},
|
||||
Sequence: appmessage.MaxTxInSequenceNum,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
|
||||
height: 300000,
|
||||
isStandard: false,
|
||||
|
@ -45,7 +45,7 @@ func (x *TransactionMessage) toAppMessage() (appmessage.Message, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var payloadHash *externalapi.DomainHash
|
||||
payloadHash := &externalapi.DomainHash{}
|
||||
if x.PayloadHash != nil {
|
||||
payloadHash, err = x.PayloadHash.toDomain()
|
||||
if err != nil {
|
||||
@ -60,7 +60,7 @@ func (x *TransactionMessage) toAppMessage() (appmessage.Message, error) {
|
||||
LockTime: x.LockTime,
|
||||
SubnetworkID: *subnetworkID,
|
||||
Gas: x.Gas,
|
||||
PayloadHash: payloadHash,
|
||||
PayloadHash: *payloadHash,
|
||||
Payload: x.Payload,
|
||||
}, 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{
|
||||
Version: msgTx.Version,
|
||||
Inputs: protoInputs,
|
||||
@ -97,7 +93,7 @@ func (x *TransactionMessage) fromAppMessage(msgTx *appmessage.MsgTx) {
|
||||
LockTime: msgTx.LockTime,
|
||||
SubnetworkID: domainSubnetworkIDToProto(&msgTx.SubnetworkID),
|
||||
Gas: msgTx.Gas,
|
||||
PayloadHash: payloadHash,
|
||||
PayloadHash: domainHashToProto(&msgTx.PayloadHash),
|
||||
Payload: msgTx.Payload,
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user