From b4f50f4e48174086179b7f991afa2ea461d5cd84 Mon Sep 17 00:00:00 2001 From: Evgeny Khirin <32414982+evgeny-khirin@users.noreply.github.com> Date: Tue, 12 Mar 2019 10:48:57 +0200 Subject: [PATCH] [Nod-25] Add payload hash to wire transaction (#200) * [NOD-25] Intermediate commit * [NOD-25] Fixed tests crashes * [NOD-25] Fixed tests * [NOD-25] Removed temporary debug code * [NOD-25] Fixed error message --- blockdag/dag_test.go | 34 ++++++++++++++++++++------------ blockdag/error.go | 4 ++++ blockdag/error_test.go | 1 + blockdag/external_dag_test.go | 4 ++++ blockdag/test_utils.go | 3 +++ blockdag/utxoset_test.go | 13 ++++++++++-- blockdag/validate.go | 10 ++++++++++ blockdag/validate_test.go | 14 +++++++++++++ btcjson/dagsvrresults.go | 1 + btcjson/dagsvrwsntfns_test.go | 11 +++++++---- dagconfig/daghash/hashfuncs.go | 7 +++++++ mempool/estimatefee_test.go | 4 +++- mempool/mempool_test.go | 3 +++ mining/mining_test.go | 3 +++ server/rpc/rpcserver.go | 28 +++++++++++++++----------- server/rpc/rpcserverhelp.go | 1 + txscript/script.go | 12 ++++++++---- txscript/sign_test.go | 4 +++- util/testtools/testtools.go | 1 + wire/msgblock_test.go | 4 +++- wire/msgtx.go | 36 +++++++++++++++++++++++++--------- wire/msgtx_test.go | 18 ++++++++++++++--- 22 files changed, 168 insertions(+), 48 deletions(-) diff --git a/blockdag/dag_test.go b/blockdag/dag_test.go index 31f158a1b..31a4a6426 100644 --- a/blockdag/dag_test.go +++ b/blockdag/dag_test.go @@ -244,12 +244,12 @@ func TestCalcSequenceLock(t *testing.T) { // Create a utxo view with a fake utxo for the inputs used in the // transactions created below. This utxo is added such that it has an // age of 4 blocks. - targetTx := util.NewTx(&wire.MsgTx{ - TxOut: []*wire.TxOut{{ - PkScript: nil, - Value: 10, - }}, - }) + msgTx := wire.NewMsgTx(wire.TxVersion) + msgTx.TxOut = []*wire.TxOut{{ + PkScript: nil, + Value: 10, + }} + targetTx := util.NewTx(msgTx) utxoSet := NewFullUTXOSet() utxoSet.AddTx(targetTx.MsgTx(), int32(numBlocksToGenerate)-4) @@ -278,12 +278,12 @@ func TestCalcSequenceLock(t *testing.T) { // Add an additional transaction which will serve as our unconfirmed // output. - unConfTx := &wire.MsgTx{ - TxOut: []*wire.TxOut{{ - PkScript: nil, - Value: 5, - }}, - } + unConfTx := wire.NewMsgTx(wire.TxVersion) + unConfTx.TxOut = []*wire.TxOut{{ + PkScript: nil, + Value: 5, + }} + unConfUtxo := wire.OutPoint{ TxID: unConfTx.TxID(), Index: 0, @@ -309,6 +309,7 @@ func TestCalcSequenceLock(t *testing.T) { PreviousOutPoint: utxo, Sequence: wire.MaxTxInSequenceNum, }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, want: &SequenceLock{ @@ -329,6 +330,7 @@ func TestCalcSequenceLock(t *testing.T) { PreviousOutPoint: utxo, Sequence: LockTimeToSequence(true, 2), }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, want: &SequenceLock{ @@ -347,6 +349,7 @@ func TestCalcSequenceLock(t *testing.T) { PreviousOutPoint: utxo, Sequence: LockTimeToSequence(true, 1024), }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, want: &SequenceLock{ @@ -374,6 +377,7 @@ func TestCalcSequenceLock(t *testing.T) { Sequence: LockTimeToSequence(false, 5) | wire.SequenceLockTimeDisabled, }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, want: &SequenceLock{ @@ -392,6 +396,7 @@ func TestCalcSequenceLock(t *testing.T) { PreviousOutPoint: utxo, Sequence: LockTimeToSequence(false, 3), }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, want: &SequenceLock{ @@ -412,6 +417,7 @@ func TestCalcSequenceLock(t *testing.T) { PreviousOutPoint: utxo, Sequence: LockTimeToSequence(true, 2560), }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, want: &SequenceLock{ @@ -433,6 +439,7 @@ func TestCalcSequenceLock(t *testing.T) { PreviousOutPoint: utxo, Sequence: LockTimeToSequence(false, 11), }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, want: &SequenceLock{ @@ -459,6 +466,7 @@ func TestCalcSequenceLock(t *testing.T) { PreviousOutPoint: utxo, Sequence: LockTimeToSequence(false, 9), }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, want: &SequenceLock{ @@ -479,6 +487,7 @@ func TestCalcSequenceLock(t *testing.T) { PreviousOutPoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2), }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, mempool: true, @@ -497,6 +506,7 @@ func TestCalcSequenceLock(t *testing.T) { PreviousOutPoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1024), }}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }, utxoSet: utxoSet, mempool: true, diff --git a/blockdag/error.go b/blockdag/error.go index 26ebd1df0..2cf053f53 100644 --- a/blockdag/error.go +++ b/blockdag/error.go @@ -234,6 +234,9 @@ const ( // a Payload ErrInvalidPayload + // ErrInvalidPayloadHash invalid hash of transaction's payload + ErrInvalidPayloadHash + // ErrSubnetwork indicates that a block doesn't adhere to the subnetwork // registry rules ErrSubnetworkRegistry @@ -289,6 +292,7 @@ var errorCodeStrings = map[ErrorCode]string{ ErrTransactionsNotSorted: "ErrTransactionsNotSorted", ErrInvalidGas: "ErrInvalidGas", ErrInvalidPayload: "ErrInvalidPayload", + ErrInvalidPayloadHash: "ErrInvalidPayloadHash", } // String returns the ErrorCode as a human-readable name. diff --git a/blockdag/error_test.go b/blockdag/error_test.go index 055db3d5c..1e3c5653e 100644 --- a/blockdag/error_test.go +++ b/blockdag/error_test.go @@ -64,6 +64,7 @@ func TestErrorCodeStringer(t *testing.T) { {ErrTransactionsNotSorted, "ErrTransactionsNotSorted"}, {ErrInvalidGas, "ErrInvalidGas"}, {ErrInvalidPayload, "ErrInvalidPayload"}, + {ErrInvalidPayloadHash, "ErrInvalidPayloadHash"}, {0xffff, "Unknown ErrorCode (65535)"}, } diff --git a/blockdag/external_dag_test.go b/blockdag/external_dag_test.go index a99f4f233..6cc2ddaac 100644 --- a/blockdag/external_dag_test.go +++ b/blockdag/external_dag_test.go @@ -310,6 +310,7 @@ func TestGasLimit(t *testing.T) { }) tx1.SubnetworkID = *subnetworkID tx1.Gas = 10000 + tx1.PayloadHash = daghash.DoubleHashP(tx1.Payload) tx2 := wire.NewMsgTx(wire.TxVersion) tx2.AddTxIn(&wire.TxIn{ @@ -322,6 +323,7 @@ func TestGasLimit(t *testing.T) { }) tx2.SubnetworkID = *subnetworkID tx2.Gas = 10000 + tx2.PayloadHash = daghash.DoubleHashP(tx2.Payload) // Here we check that we can't process a block that has transactions that exceed the gas limit overLimitBlock, err := mining.PrepareBlockForTest(dag, ¶ms, dag.TipHashes(), []*wire.MsgTx{tx1, tx2}, true, 1) @@ -353,6 +355,7 @@ func TestGasLimit(t *testing.T) { }) overflowGasTx.SubnetworkID = *subnetworkID overflowGasTx.Gas = math.MaxUint64 + overflowGasTx.PayloadHash = daghash.DoubleHashP(overflowGasTx.Payload) // Here we check that we can't process a block that its transactions' gas overflows uint64 overflowGasBlock, err := mining.PrepareBlockForTest(dag, ¶ms, dag.TipHashes(), []*wire.MsgTx{tx1, overflowGasTx}, true, 1) @@ -385,6 +388,7 @@ func TestGasLimit(t *testing.T) { }) nonExistentSubnetworkTx.SubnetworkID = nonExistentSubnetwork nonExistentSubnetworkTx.Gas = 1 + nonExistentSubnetworkTx.PayloadHash = daghash.DoubleHashP(nonExistentSubnetworkTx.Payload) nonExistentSubnetworkBlock, err := mining.PrepareBlockForTest(dag, ¶ms, dag.TipHashes(), []*wire.MsgTx{nonExistentSubnetworkTx, overflowGasTx}, true, 1) if err != nil { diff --git a/blockdag/test_utils.go b/blockdag/test_utils.go index ce0bc8f75..adf0d375c 100644 --- a/blockdag/test_utils.go +++ b/blockdag/test_utils.go @@ -135,6 +135,9 @@ func createTxForTest(numInputs uint32, numOutputs uint32, outputValue uint64, su tx.SubnetworkID = subnetworkData.subnetworkID tx.Gas = subnetworkData.Gas tx.Payload = subnetworkData.Payload + if !subnetworkData.subnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) { + tx.PayloadHash = daghash.DoubleHashP(tx.Payload) + } } else { tx.SubnetworkID = *subnetworkid.SubnetworkIDNative tx.Gas = 0 diff --git a/blockdag/utxoset_test.go b/blockdag/utxoset_test.go index 8dee48b4d..8e6361da9 100644 --- a/blockdag/utxoset_test.go +++ b/blockdag/utxoset_test.go @@ -7,6 +7,7 @@ import ( "github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/dagconfig/daghash" + "github.com/daglabs/btcd/util/subnetworkid" "github.com/daglabs/btcd/wire" ) @@ -928,8 +929,16 @@ func TestUTXOSetAddEntry(t *testing.T) { } func TestUTXOSetRemoveTxOuts(t *testing.T) { - tx0 := &wire.MsgTx{TxOut: []*wire.TxOut{{PkScript: []byte{1}, Value: 10}}} - tx1 := &wire.MsgTx{TxOut: []*wire.TxOut{{PkScript: []byte{2}, Value: 20}}} + tx0 := &wire.MsgTx{ + TxOut: []*wire.TxOut{{ + PkScript: []byte{1}, Value: 10}}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, + } + tx1 := &wire.MsgTx{ + TxOut: []*wire.TxOut{{ + PkScript: []byte{2}, Value: 20}}, + SubnetworkID: *subnetworkid.SubnetworkIDNative, + } hash0 := tx0.TxID() hash1 := tx1.TxID() outPoint0 := wire.NewOutPoint(&hash0, 0) diff --git a/blockdag/validate.go b/blockdag/validate.go index 5ea892a22..30d8aebc1 100644 --- a/blockdag/validate.go +++ b/blockdag/validate.go @@ -236,6 +236,16 @@ func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID } } + // Check payload's hash + if !msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) { + payloadHash := daghash.DoubleHashH(msgTx.Payload) + if !msgTx.PayloadHash.IsEqual(&payloadHash) { + return ruleError(ErrInvalidPayloadHash, "invalid payload hash") + } + } else if msgTx.PayloadHash != nil { + return ruleError(ErrInvalidPayloadHash, "unexpected non-empty payload hash in native subnetwork") + } + // Transactions in native and subnetwork registry subnetworks must have Gas = 0 if (msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) || msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDRegistry)) && diff --git a/blockdag/validate_test.go b/blockdag/validate_test.go index 86239edd3..59969a2e5 100644 --- a/blockdag/validate_test.go +++ b/blockdag/validate_test.go @@ -694,6 +694,20 @@ func TestCheckTransactionSanity(t *testing.T) { &txSubnetworkData{subnetworkid.SubnetworkID{234}, 0, []byte{1}}, nil, ruleError(ErrInvalidPayload, "")}, + {"invalid payload hash", 1, 1, 0, + subnetworkid.SubnetworkID{123}, + &txSubnetworkData{subnetworkid.SubnetworkID{123}, 0, []byte{1}}, + func(tx *wire.MsgTx) { + tx.PayloadHash = &daghash.Hash{} + }, + ruleError(ErrInvalidPayloadHash, "")}, + {"invalid payload hash in native subnetwork", 1, 1, 0, + *subnetworkid.SubnetworkIDNative, + nil, + func(tx *wire.MsgTx) { + tx.PayloadHash = daghash.DoubleHashP(tx.Payload) + }, + ruleError(ErrInvalidPayloadHash, "")}, } for _, test := range tests { diff --git a/btcjson/dagsvrresults.go b/btcjson/dagsvrresults.go index 0ed01696a..60a1c9716 100644 --- a/btcjson/dagsvrresults.go +++ b/btcjson/dagsvrresults.go @@ -446,6 +446,7 @@ type TxRawResult struct { LockTime uint64 `json:"lockTime"` Subnetwork string `json:"subnetwork"` Gas uint64 `json:"gas"` + PayloadHash string `json:"payloadHash"` Payload string `json:"payload"` Vin []Vin `json:"vin"` Vout []Vout `json:"vout"` diff --git a/btcjson/dagsvrwsntfns_test.go b/btcjson/dagsvrwsntfns_test.go index 4c2628eca..b9b252981 100644 --- a/btcjson/dagsvrwsntfns_test.go +++ b/btcjson/dagsvrwsntfns_test.go @@ -15,6 +15,7 @@ import ( "github.com/daglabs/btcd/util/subnetworkid" "github.com/daglabs/btcd/btcjson" + "github.com/daglabs/btcd/dagconfig/daghash" ) // TestDAGSvrWsNtfns tests all of the dag server websocket-specific @@ -187,7 +188,7 @@ func TestDAGSvrWsNtfns(t *testing.T) { { name: "txAcceptedVerbose", newNtfn: func() (interface{}, error) { - return btcjson.NewCmd("txAcceptedVerbose", `{"hex":"001122","txid":"123","version":1,"locktime":4294967295,"subnetwork":"0000000000000000000000000000000000000001","gas":0,"payload":"","vin":null,"vout":null,"confirmations":0}`) + return btcjson.NewCmd("txAcceptedVerbose", `{"hex":"001122","txid":"123","version":1,"locktime":4294967295,"subnetwork":"0000000000000000000000000000000000000001","gas":0,"payloadHash":"","payload":"","vin":null,"vout":null,"confirmations":0}`) }, staticNtfn: func() interface{} { txResult := btcjson.TxRawResult{ @@ -202,7 +203,7 @@ func TestDAGSvrWsNtfns(t *testing.T) { } return btcjson.NewTxAcceptedVerboseNtfn(txResult) }, - marshalled: `{"jsonrpc":"1.0","method":"txAcceptedVerbose","params":[{"hex":"001122","txId":"123","version":1,"lockTime":4294967295,"subnetwork":"0000000000000000000000000000000000000001","gas":0,"payload":"","vin":null,"vout":null,"acceptedBy":null}],"id":null}`, + marshalled: `{"jsonrpc":"1.0","method":"txAcceptedVerbose","params":[{"hex":"001122","txId":"123","version":1,"lockTime":4294967295,"subnetwork":"0000000000000000000000000000000000000001","gas":0,"payloadHash":"","payload":"","vin":null,"vout":null,"acceptedBy":null}],"id":null}`, unmarshalled: &btcjson.TxAcceptedVerboseNtfn{ RawTx: btcjson.TxRawResult{ Hex: "001122", @@ -219,7 +220,7 @@ func TestDAGSvrWsNtfns(t *testing.T) { { name: "txAcceptedVerbose with subnetwork, gas and paylaod", newNtfn: func() (interface{}, error) { - return btcjson.NewCmd("txAcceptedVerbose", `{"hex":"001122","txId":"123","version":1,"lockTime":4294967295,"subnetwork":"000000000000000000000000000000000000432d","gas":10,"payload":"102030","vin":null,"vout":null,"acceptedBy":null}`) + return btcjson.NewCmd("txAcceptedVerbose", `{"hex":"001122","txId":"123","version":1,"lockTime":4294967295,"subnetwork":"000000000000000000000000000000000000432d","gas":10,"payloadHash":"bf8ccdb364499a3e628200c3d3512c2c2a43b7a7d4f1a40d7f716715e449f442","payload":"102030","vin":null,"vout":null,"acceptedBy":null}`) }, staticNtfn: func() interface{} { txResult := btcjson.TxRawResult{ @@ -228,6 +229,7 @@ func TestDAGSvrWsNtfns(t *testing.T) { Version: 1, LockTime: 4294967295, Subnetwork: subnetworkid.SubnetworkID{45, 67}.String(), + PayloadHash: daghash.DoubleHashP([]byte("102030")).String(), Payload: "102030", Gas: 10, Vin: nil, @@ -236,7 +238,7 @@ func TestDAGSvrWsNtfns(t *testing.T) { } return btcjson.NewTxAcceptedVerboseNtfn(txResult) }, - marshalled: `{"jsonrpc":"1.0","method":"txAcceptedVerbose","params":[{"hex":"001122","txId":"123","version":1,"lockTime":4294967295,"subnetwork":"000000000000000000000000000000000000432d","gas":10,"payload":"102030","vin":null,"vout":null,"acceptedBy":null}],"id":null}`, + marshalled: `{"jsonrpc":"1.0","method":"txAcceptedVerbose","params":[{"hex":"001122","txId":"123","version":1,"lockTime":4294967295,"subnetwork":"000000000000000000000000000000000000432d","gas":10,"payloadHash":"bf8ccdb364499a3e628200c3d3512c2c2a43b7a7d4f1a40d7f716715e449f442","payload":"102030","vin":null,"vout":null,"acceptedBy":null}],"id":null}`, unmarshalled: &btcjson.TxAcceptedVerboseNtfn{ RawTx: btcjson.TxRawResult{ Hex: "001122", @@ -244,6 +246,7 @@ func TestDAGSvrWsNtfns(t *testing.T) { Version: 1, LockTime: 4294967295, Subnetwork: subnetworkid.SubnetworkID{45, 67}.String(), + PayloadHash: daghash.DoubleHashP([]byte("102030")).String(), Payload: "102030", Gas: 10, Vin: nil, diff --git a/dagconfig/daghash/hashfuncs.go b/dagconfig/daghash/hashfuncs.go index 3325232cf..7c32be9d9 100644 --- a/dagconfig/daghash/hashfuncs.go +++ b/dagconfig/daghash/hashfuncs.go @@ -31,3 +31,10 @@ func DoubleHashH(b []byte) Hash { first := sha256.Sum256(b) return Hash(sha256.Sum256(first[:])) } + +// DoubleHashP calculates hash(hash(b)) and returns the resulting bytes as a +// pointer to Hash. +func DoubleHashP(b []byte) *Hash { + h := DoubleHashH(b) + return &h +} diff --git a/mempool/estimatefee_test.go b/mempool/estimatefee_test.go index 265f50086..63b64de77 100644 --- a/mempool/estimatefee_test.go +++ b/mempool/estimatefee_test.go @@ -12,6 +12,7 @@ import ( "github.com/daglabs/btcd/dagconfig/daghash" "github.com/daglabs/btcd/mining" "github.com/daglabs/btcd/util" + "github.com/daglabs/btcd/util/subnetworkid" "github.com/daglabs/btcd/wire" ) @@ -51,7 +52,8 @@ func (eft *estimateFeeTester) testTx(fee util.Amount) *TxDesc { return &TxDesc{ TxDesc: mining.TxDesc{ Tx: util.NewTx(&wire.MsgTx{ - Version: eft.version, + Version: eft.version, + SubnetworkID: *subnetworkid.SubnetworkIDNative, }), Height: eft.height, Fee: uint64(fee), diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index ab1d1cfe9..8e85174bd 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -175,6 +175,9 @@ func (p *poolHarness) CreateSignedTxForSubnetwork(inputs []spendableOutpoint, nu tx := wire.NewMsgTx(wire.TxVersion) tx.SubnetworkID = *subnetworkID tx.Gas = gas + if !subnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) { + tx.PayloadHash = daghash.DoubleHashP(tx.Payload) + } for _, input := range inputs { tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: input.outPoint, diff --git a/mining/mining_test.go b/mining/mining_test.go index af4deb61d..91360e7f1 100644 --- a/mining/mining_test.go +++ b/mining/mining_test.go @@ -233,6 +233,7 @@ func TestNewBlockTemplate(t *testing.T) { nonExistingSubnetworkTx := wire.NewMsgTx(wire.TxVersion) nonExistingSubnetworkTx.SubnetworkID = nonExistingSubnetwork nonExistingSubnetworkTx.Gas = 1 + nonExistingSubnetworkTx.PayloadHash = daghash.DoubleHashP(nonExistingSubnetworkTx.Payload) nonExistingSubnetworkTx.AddTxIn(&wire.TxIn{ PreviousOutPoint: wire.OutPoint{ TxID: template1CbTx.TxID(), @@ -249,6 +250,7 @@ func TestNewBlockTemplate(t *testing.T) { subnetworkTx1 := wire.NewMsgTx(wire.TxVersion) subnetworkTx1.SubnetworkID = existingSubnetwork subnetworkTx1.Gas = 1 + subnetworkTx1.PayloadHash = daghash.DoubleHashP(subnetworkTx1.Payload) subnetworkTx1.AddTxIn(&wire.TxIn{ PreviousOutPoint: wire.OutPoint{ TxID: template1CbTx.TxID(), @@ -265,6 +267,7 @@ func TestNewBlockTemplate(t *testing.T) { subnetworkTx2 := wire.NewMsgTx(wire.TxVersion) subnetworkTx2.SubnetworkID = existingSubnetwork subnetworkTx2.Gas = 100 // Subnetwork gas limit is 90 + subnetworkTx2.PayloadHash = daghash.DoubleHashP(subnetworkTx2.Payload) subnetworkTx2.AddTxIn(&wire.TxIn{ PreviousOutPoint: wire.OutPoint{ TxID: template1CbTx.TxID(), diff --git a/server/rpc/rpcserver.go b/server/rpc/rpcserver.go index 3b0cf17ba..768f799af 100644 --- a/server/rpc/rpcserver.go +++ b/server/rpc/rpcserver.go @@ -750,18 +750,24 @@ func createTxRawResult(dagParams *dagconfig.Params, mtx *wire.MsgTx, return nil, err } + var payloadHash string + if mtx.PayloadHash != nil { + payloadHash = mtx.PayloadHash.String() + } + txReply := &btcjson.TxRawResult{ - Hex: mtxHex, - TxID: txID, - Hash: mtx.TxHash().String(), - Size: int32(mtx.SerializeSize()), - Vin: createVinList(mtx), - Vout: createVoutList(mtx, dagParams, nil), - Version: mtx.Version, - LockTime: mtx.LockTime, - Subnetwork: mtx.SubnetworkID.String(), - Gas: mtx.Gas, - Payload: hex.EncodeToString(mtx.Payload), + Hex: mtxHex, + TxID: txID, + Hash: mtx.TxHash().String(), + Size: int32(mtx.SerializeSize()), + Vin: createVinList(mtx), + Vout: createVoutList(mtx, dagParams, nil), + Version: mtx.Version, + LockTime: mtx.LockTime, + Subnetwork: mtx.SubnetworkID.String(), + Gas: mtx.Gas, + PayloadHash: payloadHash, + Payload: hex.EncodeToString(mtx.Payload), } if blkHeader != nil { diff --git a/server/rpc/rpcserverhelp.go b/server/rpc/rpcserverhelp.go index a564db2aa..fbfebe055 100644 --- a/server/rpc/rpcserverhelp.go +++ b/server/rpc/rpcserverhelp.go @@ -208,6 +208,7 @@ var helpDescsEnUS = map[string]string{ "txRawResult-lockTime": "The transaction lock time", "txRawResult-subnetwork": "The transaction subnetwork", "txRawResult-gas": "The transaction gas", + "txRawResult-payloadHash": "The transaction payload hash", "txRawResult-payload": "The transaction payload", "txRawResult-vin": "The transaction inputs as JSON objects", "txRawResult-vout": "The transaction outputs as JSON objects", diff --git a/txscript/script.go b/txscript/script.go index 583fd53a7..bee7ad92c 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -257,10 +257,14 @@ func shallowCopyTx(tx *wire.MsgTx) wire.MsgTx { // pointers into the contiguous arrays. This avoids a lot of small // allocations. txCopy := wire.MsgTx{ - Version: tx.Version, - TxIn: make([]*wire.TxIn, len(tx.TxIn)), - TxOut: make([]*wire.TxOut, len(tx.TxOut)), - LockTime: tx.LockTime, + Version: tx.Version, + TxIn: make([]*wire.TxIn, len(tx.TxIn)), + TxOut: make([]*wire.TxOut, len(tx.TxOut)), + LockTime: tx.LockTime, + SubnetworkID: tx.SubnetworkID, + Gas: tx.Gas, + PayloadHash: tx.PayloadHash, + Payload: tx.Payload, } txIns := make([]wire.TxIn, len(tx.TxIn)) for i, oldTxIn := range tx.TxIn { diff --git a/txscript/sign_test.go b/txscript/sign_test.go index 4837dc275..74c166601 100644 --- a/txscript/sign_test.go +++ b/txscript/sign_test.go @@ -13,6 +13,7 @@ import ( "github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/dagconfig/daghash" "github.com/daglabs/btcd/util" + "github.com/daglabs/btcd/util/subnetworkid" "github.com/daglabs/btcd/wire" ) @@ -135,7 +136,8 @@ func TestSignTxOutput(t *testing.T) { Value: 3, }, }, - LockTime: 0, + LockTime: 0, + SubnetworkID: *subnetworkid.SubnetworkIDNative, } // Pay to Pubkey Hash (uncompressed) diff --git a/util/testtools/testtools.go b/util/testtools/testtools.go index 35f34c9f3..8ffdaf5c7 100644 --- a/util/testtools/testtools.go +++ b/util/testtools/testtools.go @@ -67,6 +67,7 @@ func RegisterSubnetworkForTest(dag *blockdag.BlockDAG, params *dagconfig.Params, registryTx.SubnetworkID = *subnetworkid.SubnetworkIDRegistry registryTx.Payload = make([]byte, 8) binary.LittleEndian.PutUint64(registryTx.Payload, gasLimit) + registryTx.PayloadHash = daghash.DoubleHashP(registryTx.Payload) // Add it to the DAG registryBlock, err := buildNextBlock([]daghash.Hash{*fundsBlock.Hash()}, []*wire.MsgTx{registryTx}) diff --git a/wire/msgblock_test.go b/wire/msgblock_test.go index 272bee1d1..9dd3a5d07 100644 --- a/wire/msgblock_test.go +++ b/wire/msgblock_test.go @@ -117,10 +117,12 @@ func TestConvertToPartial(t *testing.T) { } block := MsgBlock{} + payload := []byte{1} for _, transaction := range transactions { block.Transactions = append(block.Transactions, &MsgTx{ SubnetworkID: transaction.subnetworkID, - Payload: []byte{1}, + Payload: payload, + PayloadHash: daghash.DoubleHashP(payload), }) } diff --git a/wire/msgtx.go b/wire/msgtx.go index 8b8b4aa1f..975ef3032 100644 --- a/wire/msgtx.go +++ b/wire/msgtx.go @@ -273,6 +273,7 @@ type MsgTx struct { LockTime uint64 SubnetworkID subnetworkid.SubnetworkID Gas uint64 + PayloadHash *daghash.Hash Payload []byte } @@ -339,11 +340,10 @@ func (msg *MsgTx) TxHash() *daghash.Hash { // TxID generates the Hash for the transaction without the signature script, gas and payload fields. func (msg *MsgTx) TxID() daghash.TxID { - // Encode the transaction, replace signature script, payload and gas with - // zeroes, and calculate double sha256 on the result. - // Ignore the error returns since the only way the encode could fail - // is being out of memory or due to nil pointers, both of which would - // cause a run-time panic. + // Encode the transaction, replace signature script with zeroes, cut off + // payload and calculate double sha256 on the result. Ignore the error + // returns since the only way the encode could fail is being out of memory or + // due to nil pointers, both of which would cause a run-time panic. var encodingFlags txEncoding if !msg.IsCoinBase() { encodingFlags = txEncodingExcludeSignatureScript | txEncodingExcludeSubNetworkData @@ -365,6 +365,7 @@ func (msg *MsgTx) Copy() *MsgTx { LockTime: msg.LockTime, SubnetworkID: msg.SubnetworkID, Gas: msg.Gas, + PayloadHash: msg.PayloadHash, } if msg.Payload != nil { @@ -544,6 +545,14 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { return err } + var payloadHash daghash.Hash + err = readElement(r, &payloadHash) + if err != nil { + returnScriptBuffers() + return err + } + msg.PayloadHash = &payloadHash + payloadLength, err := ReadVarInt(r, pver) if err != nil { returnScriptBuffers() @@ -684,6 +693,11 @@ func (msg *MsgTx) encode(w io.Writer, pver uint32, encodingFlags txEncoding) err return err } + err = writeElement(w, msg.PayloadHash) + if err != nil { + return err + } + if encodingFlags&txEncodingExcludeSubNetworkData != txEncodingExcludeSubNetworkData { err = WriteVarInt(w, pver, uint64(len(msg.Payload))) w.Write(msg.Payload) @@ -696,6 +710,9 @@ func (msg *MsgTx) encode(w io.Writer, pver uint32, encodingFlags txEncoding) err } else if msg.Payload != nil { str := fmt.Sprintf("Transactions from subnetwork %s should have payload", msg.SubnetworkID) return messageError("MsgTx.BtcEncode", str) + } else if msg.PayloadHash != nil { + str := fmt.Sprintf("Transactions from subnetwork %s should have payload hash", msg.SubnetworkID) + return messageError("MsgTx.BtcEncode", str) } else if msg.Gas != 0 { str := fmt.Sprintf("Transactions from subnetwork %s should have 0 gas", msg.SubnetworkID) return messageError("MsgTx.BtcEncode", str) @@ -747,9 +764,13 @@ func (msg *MsgTx) serializeSize(encodingFlags txEncoding) int { // Gas 8 bytes n += 8 + // PayloadHash + n += daghash.HashSize + // Serialized varint size for the length of the payload if encodingFlags&txEncodingExcludeSubNetworkData != txEncodingExcludeSubNetworkData { n += VarIntSerializeSize(uint64(len(msg.Payload))) + n += len(msg.Payload) } else { n += VarIntSerializeSize(0) } @@ -763,10 +784,6 @@ func (msg *MsgTx) serializeSize(encodingFlags txEncoding) int { n += txOut.SerializeSize() } - if encodingFlags&txEncodingExcludeSubNetworkData != txEncodingExcludeSubNetworkData { - n += len(msg.Payload) - } - return n } @@ -848,6 +865,7 @@ func newRegistryMsgTx(version int32, gasLimit uint64) *MsgTx { tx := NewMsgTx(version) tx.SubnetworkID = *subnetworkid.SubnetworkIDRegistry tx.Payload = make([]byte, 8) + tx.PayloadHash = daghash.DoubleHashP(tx.Payload) binary.LittleEndian.PutUint64(tx.Payload, gasLimit) return tx } diff --git a/wire/msgtx_test.go b/wire/msgtx_test.go index e1e2ff577..157b7eaf8 100644 --- a/wire/msgtx_test.go +++ b/wire/msgtx_test.go @@ -180,19 +180,20 @@ func TestTxHashAndID(t *testing.T) { spew.Sprint(tx1ID), spew.Sprint(wantTxID1)) } - hash2Str := "ef55c85be28615b699bef1470d0d041982a6f3af5f900c978c3837b967b168b3" + hash2Str := "37fb9ab8fc0cb68a8cc2a3c94edd26897aa445596a5c97bc459ca9815d67490b" wantHash2, err := daghash.NewHashFromStr(hash2Str) if err != nil { t.Errorf("NewHashFromStr: %v", err) return } - id2Str := "12063f97b5fbbf441bd7962f88631a36a4b4a67649045c02ed840bedc97e88ea" + id2Str := "750499ae9e6d44961ef8bad8af27a44dd4bcbea166b71baf181e8d3997e1ff72" wantID2, err := daghash.NewTxIDFromStr(id2Str) if err != nil { t.Errorf("NewHashFromStr: %v", err) return } + payload := []byte{1, 2, 3} tx2 := &MsgTx{ Version: 1, TxIn: []*TxIn{ @@ -233,7 +234,8 @@ func TestTxHashAndID(t *testing.T) { }, LockTime: 0, SubnetworkID: subnetworkid.SubnetworkID{1, 2, 3}, - Payload: []byte{1, 2, 3}, + Payload: payload, + PayloadHash: daghash.DoubleHashP(payload), } // Ensure the hash produced is expected. @@ -418,6 +420,10 @@ func TestTxSerialize(t *testing.T) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Sub Network ID 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Gas + 0x7e, 0xf0, 0xca, 0x62, 0x6b, 0xbb, 0x05, 0x8d, + 0xd4, 0x43, 0xbb, 0x78, 0xe3, 0x3b, 0x88, 0x8b, + 0xde, 0xc8, 0x29, 0x5c, 0x96, 0xe5, 0x1f, 0x55, + 0x45, 0xf9, 0x63, 0x70, 0x87, 0x0c, 0x10, 0xb9, // Payload hash 0x08, // Payload length varint 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Payload / Gas limit } @@ -426,6 +432,7 @@ func TestTxSerialize(t *testing.T) { subnetworkTx.SubnetworkID = subnetworkid.SubnetworkID{0xff} subnetworkTx.Gas = 5 subnetworkTx.Payload = []byte{0, 1, 2} + subnetworkTx.PayloadHash = daghash.DoubleHashP(subnetworkTx.Payload) subnetworkTxEncoded := []byte{ 0x01, 0x00, 0x00, 0x00, // Version @@ -436,6 +443,10 @@ func TestTxSerialize(t *testing.T) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Sub Network ID 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Gas + 0x35, 0xf9, 0xf2, 0x93, 0x0e, 0xa3, 0x44, 0x61, + 0x88, 0x22, 0x79, 0x5e, 0xee, 0xc5, 0x68, 0xae, + 0x67, 0xab, 0x29, 0x87, 0xd8, 0xb1, 0x9e, 0x45, + 0x91, 0xe1, 0x05, 0x27, 0xba, 0xa1, 0xdf, 0x3d, // Payload hash 0x03, // Payload length varint 0x00, 0x01, 0x02, // Payload } @@ -612,6 +623,7 @@ func TestTxSerializeErrors(t *testing.T) { nativeTx.Gas = 0 nativeTx.Payload = []byte{1, 2, 3} + nativeTx.PayloadHash = daghash.DoubleHashP(nativeTx.Payload) w = bytes.NewBuffer(make([]byte, 0, registryTx.SerializeSize())) err = nativeTx.Serialize(w)