From fce24223cd8d60c32a1ee107e3e1a2c7d837ecf1 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Fri, 4 Aug 2017 18:43:45 -0500 Subject: [PATCH] multi: Separate tx serialization type from version. Decred's serialized format for transactions split the 32-bit version field into two 16-bit components such that the upper bits are used to encode a serialization type and the lower 16 bits are the actual transaction version. Unfortunately, when this was done, the in-memory transaction struct was not also updated to hide this complexity, which means that callers currently have to understand and take special care when dealing with the version field of the transaction. Since the main purpose of the wire package is precisely to hide these details, this remedies the situation by introducing a new field on the in-memory transaction struct named SerType which houses the serialization type and changes the Version field back to having the desired semantics of actually being the real transaction version. Also, since the maximum version can only be a 16-bit value, the Version field has been changed to a uint16 to properly reflect this. The serialization and deserialization functions now deal with properly converting to and from these fields to the actual serialized format as intended. Finally, these changes also include a fairly significant amount of related code cleanup and optimization along with some bug fixes in order to allow the transaction version to be bumped as intended. The following is an overview of all changes: - Introduce new SerType field to MsgTx to specify the serialization type - Change MsgTx.Version to a uint16 to properly reflect its maximum allowed value - Change the semantics of MsgTx.Version to be the actual transaction version as intended - Update all callers that had special code to deal with the previous Version field semantics to use the new semantics - Switch all of the code that deals with encoding and decoding the serialized version field to use more efficient masks and shifts instead of binary writes into buffers which cause allocations - Correct several issues that would prevent producing expected serializations for transactions with actual transaction versions that are not 1 - Simplify the various serialize functions to use a single func which accepts the serialization type to reduce code duplication - Make serialization type switch usage more consistent with the rest of the code base - Update the utxoview and related code to use uint16s for the transaction version as well since it should not care about the serialization type due to using its own - Make code more consistent in how it uses bytes.Buffer - Clean up several of the comments regarding hashes and add some new comments to better describe the serialization types --- blockchain/chainio.go | 8 +- blockchain/chainio_test.go | 6 + blockchain/fullblocktests/params.go | 1 + blockchain/stake/staketx_test.go | 18 ++ blockchain/stake/tickets_test.go | 2 + blockchain/utxoviewpoint.go | 7 +- blockchain/validate_test.go | 2 + chaincfg/genesis.go | 3 + mempool/policy.go | 16 +- mempool/policy_test.go | 15 +- rpcserver.go | 10 +- txscript/engine_test.go | 3 + txscript/script_test.go | 2 + txscript/sign_test.go | 1 + wire/bench_test.go | 9 +- wire/blockheader.go | 18 +- wire/blockheader_test.go | 14 +- wire/msgblock.go | 16 +- wire/msgblock_test.go | 9 +- wire/msgtx.go | 358 +++++++++++----------------- wire/msgtx_test.go | 54 +++-- 21 files changed, 265 insertions(+), 307 deletions(-) diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 51482acf..a944f743 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -1,5 +1,5 @@ // Copyright (c) 2015-2016 The btcsuite developers -// Copyright (c) 2016 The Decred developers +// Copyright (c) 2016-2017 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -264,7 +264,7 @@ type spentTxOut struct { pkScript []byte // The public key script for the output. stakeExtra []byte // Extra information for the staking system. amount int64 // The amount of the output. - txVersion int32 // The txVersion of creating tx. + txVersion uint16 // The version of creating tx. height uint32 // Height of the the block containing the tx. index uint32 // Index in the block of the transaction. scriptVersion uint16 // The version of the scripting language. @@ -393,7 +393,7 @@ func decodeSpentTxOut(serialized []byte, stxo *spentTxOut, amount int64, "after version") } - stxo.txVersion = int32(txVersion) + stxo.txVersion = uint16(txVersion) if stxo.txType == stake.TxTypeSStx { sz := readDeserializeSizeOfMinimalOutputs(serialized[offset:]) @@ -837,7 +837,7 @@ func deserializeUtxoEntry(serialized []byte) (*UtxoEntry, error) { // Create a new utxo entry with the details deserialized above to house // all of the utxos. - entry := newUtxoEntry(int32(version), uint32(blockHeight), + entry := newUtxoEntry(uint16(version), uint32(blockHeight), uint32(blockIndex), isCoinBase, hasExpiry, txType) // Add sparse output for unspent outputs 0 and 1 as needed based on the diff --git a/blockchain/chainio_test.go b/blockchain/chainio_test.go index 29e9fd0e..c58d5ea4 100644 --- a/blockchain/chainio_test.go +++ b/blockchain/chainio_test.go @@ -299,6 +299,7 @@ func TestSpendJournalSerialization(t *testing.T) { stakeExtra: nil, }}, blockTxns: []*wire.MsgTx{{ // Coinbase omitted. + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: wire.OutPoint{ @@ -351,6 +352,7 @@ func TestSpendJournalSerialization(t *testing.T) { stakeExtra: nil, }}, blockTxns: []*wire.MsgTx{{ // Coinbase omitted. + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: wire.OutPoint{ @@ -376,6 +378,7 @@ func TestSpendJournalSerialization(t *testing.T) { LockTime: 0, Expiry: 0, }, { + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: wire.OutPoint{ @@ -438,6 +441,7 @@ func TestSpendJournalSerialization(t *testing.T) { compressed: true, }}, blockTxns: []*wire.MsgTx{{ // Coinbase omitted. + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: wire.OutPoint{ @@ -542,6 +546,7 @@ func TestSpendJournalErrors(t *testing.T) { { name: "Force assertion due to missing stxos", blockTxns: []*wire.MsgTx{{ // Coinbase omitted. + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: wire.OutPoint{ @@ -559,6 +564,7 @@ func TestSpendJournalErrors(t *testing.T) { { name: "Force deserialization error in stxos", blockTxns: []*wire.MsgTx{{ // Coinbase omitted. + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: wire.OutPoint{ diff --git a/blockchain/fullblocktests/params.go b/blockchain/fullblocktests/params.go index 4644126e..2d872e9e 100644 --- a/blockchain/fullblocktests/params.go +++ b/blockchain/fullblocktests/params.go @@ -69,6 +69,7 @@ var ( Height: uint32(0), }, Transactions: []*wire.MsgTx{{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: wire.OutPoint{ diff --git a/blockchain/stake/staketx_test.go b/blockchain/stake/staketx_test.go index 62dfd487..149361f2 100644 --- a/blockchain/stake/staketx_test.go +++ b/blockchain/stake/staketx_test.go @@ -1634,6 +1634,7 @@ var sstxTxOut4VerBad = wire.TxOut{ // sstxMsgTx is a valid SStx MsgTx with an input and outputs and is used in various // tests var sstxMsgTx = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &sstxTxIn, @@ -1655,6 +1656,7 @@ var sstxMsgTx = &wire.MsgTx{ // sstxMsgTxExtraInputs is an invalid SStx MsgTx with too many inputs var sstxMsgTxExtraInput = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &sstxTxIn, &sstxTxIn, &sstxTxIn, &sstxTxIn, &sstxTxIn, &sstxTxIn, @@ -1680,6 +1682,7 @@ var sstxMsgTxExtraInput = &wire.MsgTx{ // sstxMsgTxExtraOutputs is an invalid SStx MsgTx with too many outputs var sstxMsgTxExtraOutputs = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &sstxTxIn, @@ -1721,6 +1724,7 @@ var sstxMsgTxExtraOutputs = &wire.MsgTx{ // sstxMismatchedInsOuts is an invalid SStx MsgTx with too many outputs for the // number of inputs it has var sstxMismatchedInsOuts = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &sstxTxIn, @@ -1735,6 +1739,7 @@ var sstxMismatchedInsOuts = &wire.MsgTx{ // sstxBadVersionOut is an invalid SStx MsgTx with an output containing a bad // version. var sstxBadVersionOut = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &sstxTxIn, @@ -1757,6 +1762,7 @@ var sstxBadVersionOut = &wire.MsgTx{ // sstxNullDataMissing is an invalid SStx MsgTx with no address push in the second // output var sstxNullDataMissing = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &sstxTxIn, @@ -1771,6 +1777,7 @@ var sstxNullDataMissing = &wire.MsgTx{ // sstxNullDataMisplaced is an invalid SStx MsgTx that has the commitment and // change outputs swapped var sstxNullDataMisplaced = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &sstxTxIn, @@ -1926,6 +1933,7 @@ var ssgenTxOut3BadVer = wire.TxOut{ // ssgenMsgTx is a valid SSGen MsgTx with an input and outputs and is used in // various testing scenarios var ssgenMsgTx = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssgenTxIn0, @@ -1943,6 +1951,7 @@ var ssgenMsgTx = &wire.MsgTx{ // ssgenMsgTxExtraInput is an invalid SSGen MsgTx with too many inputs var ssgenMsgTxExtraInput = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssgenTxIn0, @@ -1960,6 +1969,7 @@ var ssgenMsgTxExtraInput = &wire.MsgTx{ // ssgenMsgTxExtraOutputs is an invalid SSGen MsgTx with too many outputs var ssgenMsgTxExtraOutputs = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssgenTxIn0, @@ -1992,6 +2002,7 @@ var ssgenMsgTxExtraOutputs = &wire.MsgTx{ // ssgenMsgTxStakeBaseWrong is an invalid SSGen tx with the stakebase in the wrong // position var ssgenMsgTxStakeBaseWrong = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssgenTxIn1, @@ -2009,6 +2020,7 @@ var ssgenMsgTxStakeBaseWrong = &wire.MsgTx{ // ssgenMsgTxBadVerOut is an invalid SSGen tx that contains an output with a bad // version var ssgenMsgTxBadVerOut = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssgenTxIn0, @@ -2027,6 +2039,7 @@ var ssgenMsgTxBadVerOut = &wire.MsgTx{ // ssgenMsgTxWrongZeroethOut is an invalid SSGen tx with the first output being not // an OP_RETURN push var ssgenMsgTxWrongZeroethOut = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssgenTxIn0, @@ -2044,6 +2057,7 @@ var ssgenMsgTxWrongZeroethOut = &wire.MsgTx{ // ssgenMsgTxWrongFirstOut is an invalid SSGen tx with the second output being not // an OP_RETURN push var ssgenMsgTxWrongFirstOut = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssgenTxIn0, @@ -2154,6 +2168,7 @@ var ssrtxTxOut2BadVer = wire.TxOut{ // ssrtxMsgTx is a valid SSRtx MsgTx with an input and outputs and is used in // various testing scenarios var ssrtxMsgTx = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssrtxTxIn, @@ -2169,6 +2184,7 @@ var ssrtxMsgTx = &wire.MsgTx{ // ssrtxMsgTx is a valid SSRtx MsgTx with an input and outputs and is used in // various testing scenarios var ssrtxMsgTxTooManyInputs = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssrtxTxIn, @@ -2184,6 +2200,7 @@ var ssrtxMsgTxTooManyInputs = &wire.MsgTx{ // ssrtxMsgTx is a valid SSRtx MsgTx with an input and outputs and is used in // various testing scenarios var ssrtxMsgTxTooManyOutputs = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssrtxTxIn, @@ -2210,6 +2227,7 @@ var ssrtxMsgTxTooManyOutputs = &wire.MsgTx{ } var ssrtxMsgTxBadVerOut = &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ &ssrtxTxIn, diff --git a/blockchain/stake/tickets_test.go b/blockchain/stake/tickets_test.go index 52ee96b5..d7bd3750 100644 --- a/blockchain/stake/tickets_test.go +++ b/blockchain/stake/tickets_test.go @@ -1106,6 +1106,7 @@ var simNetGenesisMerkleRoot = genesisMerkleRoot // genesisCoinbaseTx legacy is the coinbase transaction for the genesis blocks for // the regression test network and test network. var genesisCoinbaseTxLegacy = wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { @@ -1153,6 +1154,7 @@ var genesisCoinbaseTxLegacy = wire.MsgTx{ var genesisMerkleRoot = genesisCoinbaseTxLegacy.TxHash() var regTestGenesisCoinbaseTx = wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index fac7a7a5..9026f0bd 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -1,4 +1,5 @@ // Copyright (c) 2015-2016 The btcsuite developers +// Copyright (c) 2015-2017 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -91,7 +92,7 @@ type UtxoEntry struct { sparseOutputs map[uint32]*utxoOutput // Sparse map of unspent outputs. stakeExtra []byte // Extra data for the staking system. - txVersion int32 // The tx version of this tx. + txVersion uint16 // The tx version of this tx. height uint32 // Height of block containing tx. index uint32 // Index of containing tx in block. txType stake.TxType // The stake type of the transaction. @@ -103,7 +104,7 @@ type UtxoEntry struct { // TxVersion returns the transaction version of the transaction the // utxo represents. -func (entry *UtxoEntry) TxVersion() int32 { +func (entry *UtxoEntry) TxVersion() uint16 { return entry.txVersion } @@ -260,7 +261,7 @@ func (entry *UtxoEntry) Clone() *UtxoEntry { // newUtxoEntry returns a new unspent transaction output entry with the provided // coinbase flag and block height ready to have unspent outputs added. -func newUtxoEntry(txVersion int32, height uint32, index uint32, isCoinBase bool, +func newUtxoEntry(txVersion uint16, height uint32, index uint32, isCoinBase bool, hasExpiry bool, tt stake.TxType) *UtxoEntry { return &UtxoEntry{ sparseOutputs: make(map[uint32]*utxoOutput), diff --git a/blockchain/validate_test.go b/blockchain/validate_test.go index 90627ea8..877dc457 100644 --- a/blockchain/validate_test.go +++ b/blockchain/validate_test.go @@ -2238,6 +2238,7 @@ var simNetGenesisMerkleRoot = genesisMerkleRoot // genesisCoinbaseTx legacy is the coinbase transaction for the genesis blocks for // the regression test network and test network. var genesisCoinbaseTxLegacy = wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { @@ -2285,6 +2286,7 @@ var genesisCoinbaseTxLegacy = wire.MsgTx{ var genesisMerkleRoot = genesisCoinbaseTxLegacy.TxHash() var regTestGenesisCoinbaseTx = wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { diff --git a/chaincfg/genesis.go b/chaincfg/genesis.go index 5a3274a1..94ed41e1 100644 --- a/chaincfg/genesis.go +++ b/chaincfg/genesis.go @@ -17,6 +17,7 @@ import ( // genesisCoinbaseTx is the coinbase transaction for the genesis blocks for // the main network. var genesisCoinbaseTx = wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { @@ -91,6 +92,7 @@ var genesisHash = genesisBlock.BlockHash() // genesisCoinbaseTxLegacy is the coinbase transaction for the genesis block for // the test network. var genesisCoinbaseTxLegacy = wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { @@ -162,6 +164,7 @@ var testNet2GenesisHash = testNet2GenesisBlock.BlockHash() // SimNet ------------------------------------------------------------------------- var regTestGenesisCoinbaseTx = wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { diff --git a/mempool/policy.go b/mempool/policy.go index 9ca06387..ad974383 100644 --- a/mempool/policy.go +++ b/mempool/policy.go @@ -350,21 +350,17 @@ func checkTransactionStandard(tx *dcrutil.Tx, txType stake.TxType, height int64, timeSource blockchain.MedianTimeSource, minRelayTxFee dcrutil.Amount, maxTxVersion uint16) error { - // The transaction must be a currently supported version. - // - // The version includes the real transaction version in the lower 16 - // bits and the transaction serialize type as the upper 16 bits. + // The transaction must be a currently supported version and serialize + // type. msgTx := tx.MsgTx() - serType := wire.TxSerializeType(uint32(msgTx.Version) >> 16) - txVersion := uint16(uint32(msgTx.Version) & 0xffff) - if serType != wire.TxSerializeFull { + if msgTx.SerType != wire.TxSerializeFull { str := fmt.Sprintf("transaction is not serialized with all "+ - "required data -- type %v", serType) + "required data -- type %v", msgTx.SerType) return txRuleError(wire.RejectNonstandard, str) } - if txVersion > maxTxVersion || txVersion < 1 { + if msgTx.Version > maxTxVersion || msgTx.Version < 1 { str := fmt.Sprintf("transaction version %d is not in the "+ - "valid range of %d-%d", txVersion, 1, maxTxVersion) + "valid range of %d-%d", msgTx.Version, 1, maxTxVersion) return txRuleError(wire.RejectNonstandard, str) } diff --git a/mempool/policy_test.go b/mempool/policy_test.go index 84143fd5..da94ad0c 100644 --- a/mempool/policy_test.go +++ b/mempool/policy_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2016 The Decred developers +// Copyright (c) 2016-2017 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -332,6 +332,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "Typical pay-to-pubkey-hash transaction", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{&dummyTxOut}, @@ -343,7 +344,8 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "Transaction serialize type not full", tx: wire.MsgTx{ - Version: int32(uint32(wire.TxSerializeNoWitness)<<16 | 1), + SerType: wire.TxSerializeNoWitness, + Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, @@ -355,6 +357,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "Transaction version too high", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: maxTxVersion + 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{&dummyTxOut}, @@ -367,6 +370,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "Transaction is not finalized", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, @@ -383,6 +387,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "Transaction size is too large", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ @@ -399,6 +404,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "Signature script size is too large", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, @@ -416,6 +422,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "Signature script that does more than push data", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, @@ -433,6 +440,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "Valid but non standard public key script", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ @@ -448,6 +456,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "More than four nulldata outputs", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ @@ -475,6 +484,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "Dust output", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ @@ -490,6 +500,7 @@ func TestCheckTransactionStandard(t *testing.T) { { name: "One nulldata output with 0 amount (standard)", tx: wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ diff --git a/rpcserver.go b/rpcserver.go index 6d96b07a..64e2befd 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1350,7 +1350,7 @@ func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx, txHash str Txid: txHash, Vin: createVinList(mtx), Vout: createVoutList(mtx, chainParams, nil), - Version: mtx.Version, + Version: int32(mtx.Version), LockTime: mtx.LockTime, Expiry: mtx.Expiry, BlockHeight: blkHeight, @@ -1391,7 +1391,7 @@ func handleDecodeRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan // Create and return the result. txReply := dcrjson.TxRawDecodeResult{ Txid: mtx.TxHash().String(), - Version: mtx.Version, + Version: int32(mtx.Version), Locktime: mtx.LockTime, Expiry: mtx.Expiry, Vin: createVinList(&mtx), @@ -3961,7 +3961,7 @@ func handleGetTxOut(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i // from there, otherwise attempt to fetch from the block database. var bestBlockHash string var confirmations int64 - var txVersion int32 + var txVersion uint16 var value int64 var scriptVersion uint16 var pkScript []byte @@ -4045,7 +4045,7 @@ func handleGetTxOut(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i BestBlock: bestBlockHash, Confirmations: confirmations, Value: dcrutil.Amount(value).ToUnit(dcrutil.AmountCoin), - Version: txVersion, + Version: int32(txVersion), ScriptPubKey: dcrjson.ScriptPubKeyResult{ Asm: disbuf, Hex: hex.EncodeToString(pkScript), @@ -4972,7 +4972,7 @@ func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan "Could not create vin list") } result.Vout = createVoutList(mtx, chainParams, filterAddrMap) - result.Version = mtx.Version + result.Version = int32(mtx.Version) result.LockTime = mtx.LockTime // Transactions grabbed from the mempool aren't yet in a block, diff --git a/txscript/engine_test.go b/txscript/engine_test.go index 48b83eb8..4c36ee66 100644 --- a/txscript/engine_test.go +++ b/txscript/engine_test.go @@ -33,6 +33,7 @@ func TestBadPC(t *testing.T) { } // tx with almost empty scripts. tx := &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { @@ -93,6 +94,7 @@ func TestCheckErrorCondition(t *testing.T) { // tx with almost empty scripts. tx := &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { @@ -185,6 +187,7 @@ func TestInvalidFlagCombinations(t *testing.T) { // tx with almost empty scripts. tx := &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { diff --git a/txscript/script_test.go b/txscript/script_test.go index ab4bfeef..7499c2c9 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -512,6 +512,8 @@ func TestIsPushOnlyScript(t *testing.T) { // TestCalcSignatureHash does some rudimentary testing of msg hash calculation. func TestCalcSignatureHash(t *testing.T) { tx := new(wire.MsgTx) + tx.SerType = wire.TxSerializeFull + tx.Version = 1 for i := 0; i < 3; i++ { txIn := new(wire.TxIn) txIn.Sequence = 0xFFFFFFFF diff --git a/txscript/sign_test.go b/txscript/sign_test.go index e303fda5..1adf7be5 100644 --- a/txscript/sign_test.go +++ b/txscript/sign_test.go @@ -169,6 +169,7 @@ func TestSignTxOutput(t *testing.T) { secSchnorr, } tx := &wire.MsgTx{ + SerType: wire.TxSerializeFull, Version: 1, TxIn: []*wire.TxIn{ { diff --git a/wire/bench_test.go b/wire/bench_test.go index f90e7d48..6a93171a 100644 --- a/wire/bench_test.go +++ b/wire/bench_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2016 The Decred developers +// Copyright (c) 2015-2017 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -19,6 +19,7 @@ import ( // genesisCoinbaseTx is the coinbase transaction for the genesis blocks for // the main network, regression test network, and test network (version 3). var genesisCoinbaseTx = MsgTx{ + SerType: TxSerializeFull, Version: 1, TxIn: []*TxIn{ { @@ -83,6 +84,7 @@ var blockOne = MsgBlock{ }, Transactions: []*MsgTx{ { + SerType: TxSerializeFull, Version: 1, TxIn: []*TxIn{ { @@ -316,7 +318,7 @@ func BenchmarkReadTxIn(b *testing.B) { var txIn TxIn for i := 0; i < b.N; i++ { r.Seek(0, 0) - readTxInPrefix(r, 0, 0, &txIn) + readTxInPrefix(r, 0, TxSerializeFull, 0, &txIn) scriptPool.Return(txIn.SignatureScript) } } @@ -373,7 +375,8 @@ func BenchmarkDeserializeTxSmall(b *testing.B) { // deserialize a very large transaction. func BenchmarkDeserializeTxLarge(b *testing.B) { bigTx := new(MsgTx) - bigTx.Version = DefaultMsgTxVersion() + bigTx.SerType = TxSerializeFull + bigTx.Version = TxVersion inputsLen := 1000 outputsLen := 2000 bigTx.TxIn = make([]*TxIn, inputsLen) diff --git a/wire/blockheader.go b/wire/blockheader.go index 508906aa..7b7b2b03 100644 --- a/wire/blockheader.go +++ b/wire/blockheader.go @@ -37,7 +37,7 @@ type BlockHeader struct { // Merkle tree reference to hash of all stake transactions for the block. StakeRoot chainhash.Hash - // Votes on the previous merkleroot and yet undecided parameters. (TODO) + // Votes on the previous merkleroot and yet undecided parameters. VoteBits uint16 // Final state of the PRNG used for ticket selection in the lottery. @@ -93,9 +93,8 @@ func (h *BlockHeader) BlockHash() chainhash.Hash { // transactions. Ignore the error returns since there is no way the // encode could fail except being out of memory which would cause a // run-time panic. - var buf bytes.Buffer - buf.Grow(MaxBlockHeaderPayload) - _ = writeBlockHeader(&buf, 0, h) + buf := bytes.NewBuffer(make([]byte, 0, MaxBlockHeaderPayload)) + _ = writeBlockHeader(buf, 0, h) return chainhash.HashH(buf.Bytes()) } @@ -145,17 +144,12 @@ func (h *BlockHeader) Serialize(w io.Writer) error { // Bytes returns a byte slice containing the serialized contents of the block // header. func (h *BlockHeader) Bytes() ([]byte, error) { - // Serialize the MsgBlock. - var w bytes.Buffer - w.Grow(MaxBlockHeaderPayload) - err := h.Serialize(&w) + buf := bytes.NewBuffer(make([]byte, 0, MaxBlockHeaderPayload)) + err := h.Serialize(buf) if err != nil { return nil, err } - serializedBlockHeader := w.Bytes() - - // Cache the serialized bytes and return them. - return serializedBlockHeader, nil + return buf.Bytes(), nil } // NewBlockHeader returns a new BlockHeader using the provided previous block diff --git a/wire/blockheader_test.go b/wire/blockheader_test.go index 3f680726..6e034395 100644 --- a/wire/blockheader_test.go +++ b/wire/blockheader_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2016 The Decred developers +// Copyright (c) 2015-2017 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -195,9 +195,8 @@ func TestBlockHeaderWire(t *testing.T) { for i, test := range tests { // Encode to wire format. // Former test (doesn't work because of capacity error) - var buf bytes.Buffer - buf.Grow(MaxBlockHeaderPayload) - err := writeBlockHeader(&buf, test.pver, test.in) + buf := bytes.NewBuffer(make([]byte, 0, MaxBlockHeaderPayload)) + err := writeBlockHeader(buf, test.pver, test.in) if err != nil { t.Errorf("writeBlockHeader #%d error %v", i, err) continue @@ -209,7 +208,7 @@ func TestBlockHeaderWire(t *testing.T) { } buf.Reset() - err = test.in.BtcEncode(&buf, pver) + err = test.in.BtcEncode(buf, pver) if err != nil { t.Errorf("BtcEncode #%d error %v", i, err) continue @@ -320,14 +319,13 @@ func TestBlockHeaderSerialize(t *testing.T) { } t.Logf("Running %d tests", len(tests)) - var buf bytes.Buffer - buf.Grow(MaxBlockHeaderPayload) + buf := bytes.NewBuffer(make([]byte, 0, MaxBlockHeaderPayload)) for i, test := range tests { // Clear existing contents. buf.Reset() // Serialize the block header. - err := test.in.Serialize(&buf) + err := test.in.Serialize(buf) if err != nil { t.Errorf("Serialize #%d error %v", i, err) continue diff --git a/wire/msgblock.go b/wire/msgblock.go index 7dce15ad..71bbf022 100644 --- a/wire/msgblock.go +++ b/wire/msgblock.go @@ -107,7 +107,7 @@ func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32) error { msg.Transactions = make([]*MsgTx, 0, txCount) for i := uint64(0); i < txCount; i++ { - tx := MsgTx{} + var tx MsgTx err := tx.BtcDecode(r, pver) if err != nil { return err @@ -131,7 +131,7 @@ func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32) error { msg.STransactions = make([]*MsgTx, 0, stakeTxCount) for i := uint64(0); i < stakeTxCount; i++ { - tx := MsgTx{} + var tx MsgTx err := tx.BtcDecode(r, pver) if err != nil { return err @@ -202,7 +202,7 @@ func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, []TxLoc, error) txLocs := make([]TxLoc, txCount) for i := uint64(0); i < txCount; i++ { txLocs[i].TxStart = fullLen - r.Len() - tx := MsgTx{} + var tx MsgTx err := tx.Deserialize(r) if err != nil { return nil, nil, err @@ -234,7 +234,7 @@ func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, []TxLoc, error) sTxLocs := make([]TxLoc, stakeTxCount) for i := uint64(0); i < stakeTxCount; i++ { sTxLocs[i].TxStart = fullLen - r.Len() - tx := MsgTx{} + var tx MsgTx err := tx.Deserialize(r) if err != nil { return nil, nil, err @@ -301,14 +301,12 @@ func (msg *MsgBlock) Serialize(w io.Writer) error { // Bytes returns the serialized form of the block in bytes. func (msg *MsgBlock) Bytes() ([]byte, error) { - // Serialize the MsgTx. - var w bytes.Buffer - w.Grow(msg.SerializeSize()) - err := msg.Serialize(&w) + buf := bytes.NewBuffer(make([]byte, 0, msg.SerializeSize())) + err := msg.Serialize(buf) if err != nil { return nil, err } - return w.Bytes(), nil + return buf.Bytes(), nil } // SerializeSize returns the number of bytes it would take to serialize the diff --git a/wire/msgblock_test.go b/wire/msgblock_test.go index a6b40bcb..02fe5b12 100644 --- a/wire/msgblock_test.go +++ b/wire/msgblock_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2016 The Decred developers +// Copyright (c) 2015-2017 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -314,9 +314,8 @@ func TestBlockSerialize(t *testing.T) { t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Serialize the block. - var buf bytes.Buffer - buf.Grow(test.in.SerializeSize()) - err := test.in.Serialize(&buf) + buf := bytes.NewBuffer(make([]byte, 0, test.in.SerializeSize())) + err := test.in.Serialize(buf) if err != nil { t.Errorf("Serialize #%d error %v", i, err) continue @@ -600,6 +599,7 @@ var testBlock = MsgBlock{ }, Transactions: []*MsgTx{ { + SerType: TxSerializeFull, Version: 1, TxIn: []*TxIn{ { @@ -642,6 +642,7 @@ var testBlock = MsgBlock{ }, STransactions: []*MsgTx{ { + SerType: TxSerializeFull, Version: 1, TxIn: []*TxIn{ { diff --git a/wire/msgtx.go b/wire/msgtx.go index a0153723..49d69abb 100644 --- a/wire/msgtx.go +++ b/wire/msgtx.go @@ -7,7 +7,6 @@ package wire import ( "bytes" - "encoding/binary" "fmt" "io" "strconv" @@ -51,11 +50,11 @@ const ( // inserted into a block yet. TxTreeUnknown int8 = -1 - // TxTreeRegular is the value for a normal transcation tree for a + // TxTreeRegular is the value for a normal transaction tree for a // transaction's location in a block. TxTreeRegular int8 = 0 - // TxTreeStake is the value for a stake transcation tree for a + // TxTreeStake is the value for a stake transaction tree for a // transaction's location in a block. TxTreeStake int8 = 1 ) @@ -113,66 +112,31 @@ const ( freeListMaxItems = 12500 ) -// TxSerializeType is a uint16 representing the serialized type of transaction -// this msgTx is. You can use a bitmask for this too, but Decred just splits -// the int32 version into 2x uint16s so that you have: -// { -// uint16 type -// uint16 version -// } +// TxSerializeType represents the serialized type of a transaction. type TxSerializeType uint16 -// The differente possible values for TxSerializeType. const ( - TxSerializeFull = TxSerializeType(iota) + // TxSerializeFull indicates a transaction be serialized with the prefix + // and all witness data. + TxSerializeFull TxSerializeType = iota + + // TxSerializeNoWitness indicates a transaction be serialized with only + // the prefix. TxSerializeNoWitness + + // TxSerializeOnlyWitness indicates a transaction be serialized with + // only the witness data. TxSerializeOnlyWitness + + // TxSerializeWitnessSigning indicates a transaction be serialized with + // only the witness scripts. TxSerializeWitnessSigning + + // TxSerializeWitnessValueSigning indicates a transaction be serialized + // with only the witness input values and scripts. TxSerializeWitnessValueSigning ) -// TODO replace all these with predeclared int32 or [4]byte cj - -// DefaultMsgTxVersion returns the default version int32 (serialize the tx -// fully, version number 1). -func DefaultMsgTxVersion() int32 { - verBytes := make([]byte, 4, 4) - binary.LittleEndian.PutUint16(verBytes[0:2], TxVersion) - binary.LittleEndian.PutUint16(verBytes[2:4], uint16(TxSerializeFull)) - ver := binary.LittleEndian.Uint32(verBytes) - return int32(ver) -} - -// NoWitnessMsgTxVersion returns the witness free serializing int32 (serialize -// the tx without witness, version number 1). -func NoWitnessMsgTxVersion() int32 { - verBytes := make([]byte, 4, 4) - binary.LittleEndian.PutUint16(verBytes[0:2], TxVersion) - binary.LittleEndian.PutUint16(verBytes[2:4], uint16(TxSerializeNoWitness)) - ver := binary.LittleEndian.Uint32(verBytes) - return int32(ver) -} - -// WitnessOnlyMsgTxVersion returns the witness only version int32 (serialize -// the tx witness, version number 1). -func WitnessOnlyMsgTxVersion() int32 { - verBytes := make([]byte, 4, 4) - binary.LittleEndian.PutUint16(verBytes[0:2], TxVersion) - binary.LittleEndian.PutUint16(verBytes[2:4], uint16(TxSerializeOnlyWitness)) - ver := binary.LittleEndian.Uint32(verBytes) - return int32(ver) -} - -// WitnessSigningMsgTxVersion returns the witness only version int32 (serialize -// the tx witness for signing, version number 1). -func WitnessSigningMsgTxVersion() int32 { - verBytes := make([]byte, 4, 4) - binary.LittleEndian.PutUint16(verBytes[0:2], TxVersion) - binary.LittleEndian.PutUint16(verBytes[2:4], uint16(TxSerializeWitnessSigning)) - ver := binary.LittleEndian.Uint32(verBytes) - return int32(ver) -} - // scriptFreeList defines a free list of byte slices (up to the maximum number // defined by the freeListMaxItems constant) that have a cap according to the // freeListMaxScriptSize constant. It is used to provide temporary buffers for @@ -261,17 +225,6 @@ func readScript(r io.Reader, pver uint32, maxAllowed uint32, fieldName string) ( return b, nil } -// WitnessValueSigningMsgTxVersion returns the witness only version int32 -// (serialize the tx witness for signing with value, version number 1). -func WitnessValueSigningMsgTxVersion() int32 { - verBytes := make([]byte, 4, 4) - binary.LittleEndian.PutUint16(verBytes[0:2], TxVersion) - binary.LittleEndian.PutUint16(verBytes[2:4], - uint16(TxSerializeWitnessValueSigning)) - ver := binary.LittleEndian.Uint32(verBytes) - return int32(ver) -} - // OutPoint defines a decred data type that is used to track previous // transaction outputs. type OutPoint struct { @@ -402,7 +355,8 @@ func NewTxOut(value int64, pkScript []byte) *TxOut { // inputs and outputs. type MsgTx struct { CachedHash *chainhash.Hash - Version int32 + SerType TxSerializeType + Version uint16 TxIn []*TxIn TxOut []*TxOut LockTime uint32 @@ -419,45 +373,44 @@ func (msg *MsgTx) AddTxOut(to *TxOut) { msg.TxOut = append(msg.TxOut, to) } -// msgTxVersionToVars converts an int32 version into serialization types and -// actual version. -func msgTxVersionToVars(version int32) (uint16, TxSerializeType) { - mVerBytes := make([]byte, 4, 4) - binary.LittleEndian.PutUint32(mVerBytes[0:4], uint32(version)) - mVer := binary.LittleEndian.Uint16(mVerBytes[0:2]) - mType := binary.LittleEndian.Uint16(mVerBytes[2:4]) - return mVer, TxSerializeType(mType) -} - -// shallowCopyForSerializing make a shallow copy of a tx with a new -// version, so that it can be hashed or serialized accordingly. -func (msg *MsgTx) shallowCopyForSerializing(version int32) *MsgTx { - return &MsgTx{ - Version: version, - TxIn: msg.TxIn, - TxOut: msg.TxOut, - LockTime: msg.LockTime, - Expiry: msg.Expiry, - } -} - -// TxHash generates the Hash name for the transaction prefix. -func (msg *MsgTx) TxHash() chainhash.Hash { - // TxHash should always calculate a non-witnessed hash. - mtxCopy := msg.shallowCopyForSerializing(NoWitnessMsgTxVersion()) - +// serialize returns the serialization of the transaction for the provided +// serialization type without modifying the original transaction. +func (msg *MsgTx) serialize(serType TxSerializeType) ([]byte, error) { + // Shallow copy so the serialization type can be changed without + // modifying the original transaction. + mtxCopy := *msg + mtxCopy.SerType = serType buf := bytes.NewBuffer(make([]byte, 0, mtxCopy.SerializeSize())) err := mtxCopy.Serialize(buf) if err != nil { - panic("MsgTx failed serializing for TxHash") + return nil, err } - - return chainhash.HashH(buf.Bytes()) + return buf.Bytes(), nil } -// CachedTxHash generates the Hash name for the transaction prefix and stores -// it if it does not exist. The cached hash is then returned. It can be -// recalculated later with RecacheTxHash. +// mustSerialize returns the serialization of the transaction for the provided +// serialization type without modifying the original transaction. It will panic +// if any errors occur. +func (msg *MsgTx) mustSerialize(serType TxSerializeType) []byte { + serialized, err := msg.serialize(serType) + if err != nil { + panic(fmt.Sprintf("MsgTx failed serializing for type %v", + serType)) + } + return serialized +} + +// TxHash generates the hash for the transaction prefix. Since it does not +// contain any witness data, it is not malleable and therefore is stable for +// use in unconfirmed transaction chains. +func (msg *MsgTx) TxHash() chainhash.Hash { + // TxHash should always calculate a non-witnessed hash. + return chainhash.HashH(msg.mustSerialize(TxSerializeNoWitness)) +} + +// CachedTxHash is equivalent to calling TxHash, however it caches the result so +// subsequent calls do not have to recalculate the hash. It can be recalculated +// later with RecacheTxHash. func (msg *MsgTx) CachedTxHash() *chainhash.Hash { if msg.CachedHash == nil { h := msg.TxHash() @@ -467,8 +420,9 @@ func (msg *MsgTx) CachedTxHash() *chainhash.Hash { return msg.CachedHash } -// RecacheTxHash generates the Hash name for the transaction prefix and stores -// it. The cached hash is then returned. +// RecacheTxHash is equivalent to calling TxHash, however it replaces the cached +// result so future calls to CachedTxHash will return this newly calculated +// hash. func (msg *MsgTx) RecacheTxHash() *chainhash.Hash { h := msg.TxHash() msg.CachedHash = &h @@ -476,65 +430,41 @@ func (msg *MsgTx) RecacheTxHash() *chainhash.Hash { return msg.CachedHash } -// TxHashWitness generates the Hash name for the transaction witness. +// TxHashWitness generates the hash for the transaction witness. func (msg *MsgTx) TxHashWitness() chainhash.Hash { // TxHashWitness should always calculate a witnessed hash. - mtxCopy := msg.shallowCopyForSerializing(WitnessOnlyMsgTxVersion()) - - buf := bytes.NewBuffer(make([]byte, 0, mtxCopy.SerializeSize())) - err := mtxCopy.Serialize(buf) - if err != nil { - panic("MsgTx failed serializing for TxHashWitness") - } - - return chainhash.HashH(buf.Bytes()) + return chainhash.HashH(msg.mustSerialize(TxSerializeOnlyWitness)) } -// TxHashWitnessSigning generates the Hash name for the transaction witness with -// the malleable portions (AmountIn, BlockHeight, BlockIndex) removed. These are +// TxHashWitnessSigning generates the hash for the transaction witness with the +// malleable portions (AmountIn, BlockHeight, BlockIndex) removed. These are // verified and set by the miner instead. func (msg *MsgTx) TxHashWitnessSigning() chainhash.Hash { - // TxHashWitness should always calculate a witnessed hash. - mtxCopy := msg.shallowCopyForSerializing(WitnessSigningMsgTxVersion()) - - buf := bytes.NewBuffer(make([]byte, 0, mtxCopy.SerializeSize())) - err := mtxCopy.Serialize(buf) - if err != nil { - panic("MsgTx failed serializing for TxHashWitnessSigning") - } - - return chainhash.HashH(buf.Bytes()) + return chainhash.HashH(msg.mustSerialize(TxSerializeWitnessSigning)) } -// TxHashWitnessValueSigning generates the Hash name for the transaction witness -// with BlockHeight and BlockIndex removed, allowing the signer to specify the +// TxHashWitnessValueSigning generates the hash for the transaction witness with +// BlockHeight and BlockIndex removed, allowing the signer to specify the // ValueIn. func (msg *MsgTx) TxHashWitnessValueSigning() chainhash.Hash { - // TxHashWitness should always calculate a witnessed hash. - mtxCopy := msg.shallowCopyForSerializing(WitnessValueSigningMsgTxVersion()) - - buf := bytes.NewBuffer(make([]byte, 0, mtxCopy.SerializeSize())) - err := mtxCopy.Serialize(buf) - if err != nil { - panic("MsgTx failed serializing for TxHashWitnessValueSigning") - } - - return chainhash.HashH(buf.Bytes()) + return chainhash.HashH(msg.mustSerialize(TxSerializeWitnessValueSigning)) } -// TxHashFull generates the Hash name for the transaction prefix || witness. It -// first obtains the hashes for both the transaction prefix and witness, then -// concatenates them and hashes these 64 bytes. -// Note that the inputs to the hashes, serialized prefix and serialized witnesses, -// have different uint32 versions because version is now actually two uint16s, -// with the last 16 bits referring to the serialization type. The first 16 bits -// refer to the actual version, and these must be the same in both serializations. +// TxHashFull generates the hash for the transaction prefix || witness. It first +// obtains the hashes for both the transaction prefix and witness, then +// concatenates them and hashes the result. func (msg *MsgTx) TxHashFull() chainhash.Hash { - concat := make([]byte, 64, 64) + // Note that the inputs to the hashes, the serialized prefix and + // witness, have different serialized versions because the serialized + // encoding of the version includes the real transaction version in the + // lower 16 bits and the transaction serialization type in the upper 16 + // bits. The real transaction version (lower 16 bits) will be the same + // in both serializations. + concat := make([]byte, chainhash.HashSize*2) prefixHash := msg.TxHash() witnessHash := msg.TxHashWitness() - copy(concat[0:32], prefixHash[:]) - copy(concat[32:64], witnessHash[:]) + copy(concat[0:], prefixHash[:]) + copy(concat[chainhash.HashSize:], witnessHash[:]) return chainhash.HashH(concat) } @@ -545,6 +475,7 @@ func (msg *MsgTx) Copy() *MsgTx { // Create new tx and start by copying primitive values and making space // for the transaction inputs and outputs. newTx := MsgTx{ + SerType: msg.SerType, Version: msg.Version, TxIn: make([]*TxIn, 0, len(msg.TxIn)), TxOut: make([]*TxOut, 0, len(msg.TxOut)), @@ -616,7 +547,7 @@ func (msg *MsgTx) Copy() *MsgTx { // buffers after this function has run because it is already done and the // scripts in the transaction inputs and outputs no longer point to the // buffers. -func writeTxScriptsToMsgTx(msg *MsgTx, totalScriptSize uint64, mType TxSerializeType) { +func writeTxScriptsToMsgTx(msg *MsgTx, totalScriptSize uint64, serType TxSerializeType) { // Create a single allocation to house all of the scripts and set each // input signature scripts and output public key scripts to the // appropriate subslice of the overall contiguous buffer. Then, return @@ -670,7 +601,7 @@ func writeTxScriptsToMsgTx(msg *MsgTx, totalScriptSize uint64, mType TxSerialize } // Handle the serialization types accordingly. - switch mType { + switch serType { case TxSerializeNoWitness: writeTxOuts() case TxSerializeOnlyWitness: @@ -711,7 +642,7 @@ func (msg *MsgTx) decodePrefix(r io.Reader, pver uint32) (uint64, error) { // and needs to be returned to the pool on error. ti := &txIns[i] msg.TxIn[i] = ti - err = readTxInPrefix(r, pver, msg.Version, ti) + err = readTxInPrefix(r, pver, msg.SerType, msg.Version, ti) if err != nil { return 0, err } @@ -927,12 +858,15 @@ func (msg *MsgTx) decodeWitnessValueSigning(r io.Reader, pver uint32) (uint64, e // See Deserialize for decoding transactions stored to disk, such as in a // database, as opposed to decoding transactions from the wire. func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { + // The serialized encoding of the version includes the real transaction + // version in the lower 16 bits and the transaction serialization type + // in the upper 16 bits. version, err := binarySerializer.Uint32(r, littleEndian) if err != nil { return err } - msg.Version = int32(version) - _, mType := msgTxVersionToVars(msg.Version) + msg.Version = uint16(version & 0xffff) + msg.SerType = TxSerializeType(version >> 16) // returnScriptBuffers is a closure that returns any script buffers that // were borrowed from the pool when there are any deserialization @@ -960,40 +894,40 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { // memory allocations, which reduces the amount of memory that // must be handled by the GC tremendously. If any of these // serializations fail, free the relevant memory. - switch { - case mType == TxSerializeNoWitness: + switch txSerType := msg.SerType; txSerType { + case TxSerializeNoWitness: totalScriptSize, err := msg.decodePrefix(r, pver) if err != nil { returnScriptBuffers() return err } - writeTxScriptsToMsgTx(msg, totalScriptSize, mType) + writeTxScriptsToMsgTx(msg, totalScriptSize, txSerType) - case mType == TxSerializeOnlyWitness: + case TxSerializeOnlyWitness: totalScriptSize, err := msg.decodeWitness(r, pver, false) if err != nil { returnScriptBuffers() return err } - writeTxScriptsToMsgTx(msg, totalScriptSize, mType) + writeTxScriptsToMsgTx(msg, totalScriptSize, txSerType) - case mType == TxSerializeWitnessSigning: + case TxSerializeWitnessSigning: totalScriptSize, err := msg.decodeWitnessSigning(r, pver) if err != nil { returnScriptBuffers() return err } - writeTxScriptsToMsgTx(msg, totalScriptSize, mType) + writeTxScriptsToMsgTx(msg, totalScriptSize, txSerType) - case mType == TxSerializeWitnessValueSigning: + case TxSerializeWitnessValueSigning: totalScriptSize, err := msg.decodeWitnessValueSigning(r, pver) if err != nil { returnScriptBuffers() return err } - writeTxScriptsToMsgTx(msg, totalScriptSize, mType) + writeTxScriptsToMsgTx(msg, totalScriptSize, txSerType) - case mType == TxSerializeFull: + case TxSerializeFull: totalScriptSizeIns, err := msg.decodePrefix(r, pver) if err != nil { returnScriptBuffers() @@ -1004,7 +938,9 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { returnScriptBuffers() return err } - writeTxScriptsToMsgTx(msg, totalScriptSizeIns+totalScriptSizeOuts, mType) + writeTxScriptsToMsgTx(msg, totalScriptSizeIns+ + totalScriptSizeOuts, txSerType) + default: return messageError("MsgTx.BtcDecode", "unsupported transaction type") } @@ -1131,38 +1067,41 @@ func (msg *MsgTx) encodeWitnessValueSigning(w io.Writer, pver uint32) error { // See Serialize for encoding transactions to be stored to disk, such as in a // database, as opposed to encoding transactions for the wire. func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32) error { - err := binarySerializer.PutUint32(w, littleEndian, uint32(msg.Version)) + // The serialized encoding of the version includes the real transaction + // version in the lower 16 bits and the transaction serialization type + // in the upper 16 bits. + serializedVersion := uint32(msg.Version) | uint32(msg.SerType)<<16 + err := binarySerializer.PutUint32(w, littleEndian, serializedVersion) if err != nil { return err } - _, mType := msgTxVersionToVars(msg.Version) - switch { - case mType == TxSerializeNoWitness: + switch msg.SerType { + case TxSerializeNoWitness: err := msg.encodePrefix(w, pver) if err != nil { return err } - case mType == TxSerializeOnlyWitness: + case TxSerializeOnlyWitness: err := msg.encodeWitness(w, pver) if err != nil { return err } - case mType == TxSerializeWitnessSigning: + case TxSerializeWitnessSigning: err := msg.encodeWitnessSigning(w, pver) if err != nil { return err } - case mType == TxSerializeWitnessValueSigning: + case TxSerializeWitnessValueSigning: err := msg.encodeWitnessValueSigning(w, pver) if err != nil { return err } - case mType == TxSerializeFull: + case TxSerializeFull: err := msg.encodePrefix(w, pver) if err != nil { return err @@ -1198,51 +1137,31 @@ func (msg *MsgTx) Serialize(w io.Writer) error { // Bytes returns the serialized form of the transaction in bytes. func (msg *MsgTx) Bytes() ([]byte, error) { - // Serialize the MsgTx. - var w bytes.Buffer - w.Grow(msg.SerializeSize()) - err := msg.Serialize(&w) + buf := bytes.NewBuffer(make([]byte, 0, msg.SerializeSize())) + err := msg.Serialize(buf) if err != nil { return nil, err } - return w.Bytes(), nil + return buf.Bytes(), nil } // BytesPrefix returns the serialized form of the transaction prefix in bytes. func (msg *MsgTx) BytesPrefix() ([]byte, error) { - mtxCopy := msg.shallowCopyForSerializing(NoWitnessMsgTxVersion()) - - var w bytes.Buffer - w.Grow(msg.SerializeSize()) - err := mtxCopy.Serialize(&w) - if err != nil { - return nil, err - } - return w.Bytes(), nil + return msg.serialize(TxSerializeNoWitness) } // BytesWitness returns the serialized form of the transaction prefix in bytes. func (msg *MsgTx) BytesWitness() ([]byte, error) { - mtxCopy := msg.shallowCopyForSerializing(WitnessOnlyMsgTxVersion()) - - var w bytes.Buffer - w.Grow(msg.SerializeSize()) - err := mtxCopy.Serialize(&w) - if err != nil { - return nil, err - } - return w.Bytes(), nil + return msg.serialize(TxSerializeOnlyWitness) } // SerializeSize returns the number of bytes it would take to serialize the // the transaction. func (msg *MsgTx) SerializeSize() int { - _, mType := msgTxVersionToVars(msg.Version) - // Unknown type return 0. n := 0 - switch { - case mType == TxSerializeNoWitness: + switch msg.SerType { + case TxSerializeNoWitness: // Version 4 bytes + LockTime 4 bytes + Expiry 4 bytes + // Serialized varint size for the number of transaction // inputs and outputs. @@ -1256,7 +1175,7 @@ func (msg *MsgTx) SerializeSize() int { n += txOut.SerializeSize() } - case mType == TxSerializeOnlyWitness: + case TxSerializeOnlyWitness: // Version 4 bytes + Serialized varint size for the // number of transaction signatures. n = 4 + VarIntSerializeSize(uint64(len(msg.TxIn))) @@ -1265,7 +1184,7 @@ func (msg *MsgTx) SerializeSize() int { n += txIn.SerializeSizeWitness() } - case mType == TxSerializeWitnessSigning: + case TxSerializeWitnessSigning: // Version 4 bytes + Serialized varint size for the // number of transaction signatures. n = 4 + VarIntSerializeSize(uint64(len(msg.TxIn))) @@ -1274,7 +1193,7 @@ func (msg *MsgTx) SerializeSize() int { n += txIn.SerializeSizeWitnessSigning() } - case mType == TxSerializeWitnessValueSigning: + case TxSerializeWitnessValueSigning: // Version 4 bytes + Serialized varint size for the // number of transaction signatures. n = 4 + VarIntSerializeSize(uint64(len(msg.TxIn))) @@ -1283,7 +1202,7 @@ func (msg *MsgTx) SerializeSize() int { n += txIn.SerializeSizeWitnessValueSigning() } - case mType == TxSerializeFull: + case TxSerializeFull: // Version 4 bytes + LockTime 4 bytes + Expiry 4 bytes + Serialized // varint size for the number of transaction inputs (x2) and // outputs. The number of inputs is added twice because it's @@ -1370,14 +1289,15 @@ func (msg *MsgTx) PkScriptLocs() []int { // future. func NewMsgTx() *MsgTx { return &MsgTx{ - Version: DefaultMsgTxVersion(), + SerType: TxSerializeFull, + Version: TxVersion, TxIn: make([]*TxIn, 0, defaultTxInOutAlloc), TxOut: make([]*TxOut, 0, defaultTxInOutAlloc), } } // ReadOutPoint reads the next sequence of bytes from r as an OutPoint. -func ReadOutPoint(r io.Reader, pver uint32, version int32, op *OutPoint) error { +func ReadOutPoint(r io.Reader, pver uint32, version uint16, op *OutPoint) error { _, err := io.ReadFull(r, op.Hash[:]) if err != nil { return err @@ -1399,7 +1319,7 @@ func ReadOutPoint(r io.Reader, pver uint32, version int32, op *OutPoint) error { // WriteOutPoint encodes op to the decred protocol encoding for an OutPoint // to w. -func WriteOutPoint(w io.Writer, pver uint32, version int32, op *OutPoint) error { +func WriteOutPoint(w io.Writer, pver uint32, version uint16, op *OutPoint) error { _, err := w.Write(op.Hash[:]) if err != nil { return err @@ -1415,8 +1335,8 @@ func WriteOutPoint(w io.Writer, pver uint32, version int32, op *OutPoint) error // readTxInPrefix reads the next sequence of bytes from r as a transaction input // (TxIn) in the transaction prefix. -func readTxInPrefix(r io.Reader, pver uint32, version int32, ti *TxIn) error { - if version == WitnessOnlyMsgTxVersion() { +func readTxInPrefix(r io.Reader, pver uint32, serType TxSerializeType, version uint16, ti *TxIn) error { + if serType == TxSerializeOnlyWitness { return messageError("readTxInPrefix", "tried to read a prefix input for a witness only tx") } @@ -1434,7 +1354,7 @@ func readTxInPrefix(r io.Reader, pver uint32, version int32, ti *TxIn) error { // readTxInWitness reads the next sequence of bytes from r as a transaction input // (TxIn) in the transaction witness. -func readTxInWitness(r io.Reader, pver uint32, version int32, ti *TxIn) error { +func readTxInWitness(r io.Reader, pver uint32, version uint16, ti *TxIn) error { // ValueIn. valueIn, err := binarySerializer.Uint64(r, littleEndian) if err != nil { @@ -1461,11 +1381,9 @@ func readTxInWitness(r io.Reader, pver uint32, version int32, ti *TxIn) error { } // readTxInWitnessSigning reads a TxIn witness for signing. -func readTxInWitnessSigning(r io.Reader, pver uint32, version int32, - ti *TxIn) error { - var err error - +func readTxInWitnessSigning(r io.Reader, pver uint32, version uint16, ti *TxIn) error { // Signature script. + var err error ti.SignatureScript, err = readScript(r, pver, MaxMessagePayload, "transaction input signature script") return err @@ -1473,8 +1391,7 @@ func readTxInWitnessSigning(r io.Reader, pver uint32, version int32, // readTxInWitnessValueSigning reads a TxIn witness for signing with value // included. -func readTxInWitnessValueSigning(r io.Reader, pver uint32, version int32, - ti *TxIn) error { +func readTxInWitnessValueSigning(r io.Reader, pver uint32, version uint16, ti *TxIn) error { // ValueIn. valueIn, err := binarySerializer.Uint64(r, littleEndian) if err != nil { @@ -1490,7 +1407,7 @@ func readTxInWitnessValueSigning(r io.Reader, pver uint32, version int32, // writeTxInPrefixs encodes ti to the decred protocol encoding for a transaction // input (TxIn) prefix to w. -func writeTxInPrefix(w io.Writer, pver uint32, version int32, ti *TxIn) error { +func writeTxInPrefix(w io.Writer, pver uint32, version uint16, ti *TxIn) error { err := WriteOutPoint(w, pver, version, &ti.PreviousOutPoint) if err != nil { return err @@ -1501,7 +1418,7 @@ func writeTxInPrefix(w io.Writer, pver uint32, version int32, ti *TxIn) error { // writeTxWitness encodes ti to the decred protocol encoding for a transaction // input (TxIn) witness to w. -func writeTxInWitness(w io.Writer, pver uint32, version int32, ti *TxIn) error { +func writeTxInWitness(w io.Writer, pver uint32, version uint16, ti *TxIn) error { // ValueIn. err := binarySerializer.PutUint64(w, littleEndian, uint64(ti.ValueIn)) if err != nil { @@ -1526,19 +1443,16 @@ func writeTxInWitness(w io.Writer, pver uint32, version int32, ti *TxIn) error { // writeTxInWitnessSigning encodes ti to the decred protocol encoding for a // transaction input (TxIn) witness to w for signing. -func writeTxInWitnessSigning(w io.Writer, pver uint32, version int32, ti *TxIn) error { +func writeTxInWitnessSigning(w io.Writer, pver uint32, version uint16, ti *TxIn) error { // Only write the signature script. return WriteVarBytes(w, pver, ti.SignatureScript) } // writeTxInWitnessValueSigning encodes ti to the decred protocol encoding for a // transaction input (TxIn) witness to w for signing with value included. -func writeTxInWitnessValueSigning(w io.Writer, pver uint32, version int32, - ti *TxIn) error { - var err error - +func writeTxInWitnessValueSigning(w io.Writer, pver uint32, version uint16, ti *TxIn) error { // ValueIn. - err = binarySerializer.PutUint64(w, littleEndian, uint64(ti.ValueIn)) + err := binarySerializer.PutUint64(w, littleEndian, uint64(ti.ValueIn)) if err != nil { return err } @@ -1549,7 +1463,7 @@ func writeTxInWitnessValueSigning(w io.Writer, pver uint32, version int32, // readTxOut reads the next sequence of bytes from r as a transaction output // (TxOut). -func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error { +func readTxOut(r io.Reader, pver uint32, version uint16, to *TxOut) error { value, err := binarySerializer.Uint64(r, littleEndian) if err != nil { return err @@ -1568,7 +1482,7 @@ func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error { // writeTxOut encodes to into the decred protocol encoding for a transaction // output (TxOut) to w. -func writeTxOut(w io.Writer, pver uint32, version int32, to *TxOut) error { +func writeTxOut(w io.Writer, pver uint32, version uint16, to *TxOut) error { err := binarySerializer.PutUint64(w, littleEndian, uint64(to.Value)) if err != nil { return err diff --git a/wire/msgtx_test.go b/wire/msgtx_test.go index 123a73a9..58403bb5 100644 --- a/wire/msgtx_test.go +++ b/wire/msgtx_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2016 The Decred developers +// Copyright (c) 2015-2017 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -378,9 +378,8 @@ func TestTxSerialize(t *testing.T) { t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Serialize the transaction. - var buf bytes.Buffer - buf.Grow(test.in.SerializeSize()) - err := test.in.Serialize(&buf) + buf := bytes.NewBuffer(make([]byte, 0, test.in.SerializeSize())) + err := test.in.Serialize(buf) if err != nil { t.Errorf("Serialize #%d error %v", i, err) continue @@ -437,7 +436,8 @@ func TestTxSerialize(t *testing.T) { // TestTxSerializePrefix tests MsgTx serialize and deserialize. func TestTxSerializePrefix(t *testing.T) { noTx := NewMsgTx() - noTx.Version = 65537 + noTx.SerType = TxSerializeNoWitness + noTx.Version = 1 noTxEncoded := []byte{ 0x01, 0x00, 0x01, 0x00, // Version 0x00, // Varint for number of input transactions @@ -472,9 +472,8 @@ func TestTxSerializePrefix(t *testing.T) { t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Serialize the transaction. - var buf bytes.Buffer - buf.Grow(test.in.SerializeSize()) - err := test.in.Serialize(&buf) + buf := bytes.NewBuffer(make([]byte, 0, test.in.SerializeSize())) + err := test.in.Serialize(buf) if err != nil { t.Errorf("Serialize #%d error %v", i, err) continue @@ -531,7 +530,8 @@ func TestTxSerializePrefix(t *testing.T) { // TestTxSerializeWitness tests MsgTx serialize and deserialize. func TestTxSerializeWitness(t *testing.T) { noTx := NewMsgTx() - noTx.Version = 131073 + noTx.SerType = TxSerializeOnlyWitness + noTx.Version = 1 noTxEncoded := []byte{ 0x01, 0x00, 0x02, 0x00, // Version 0x00, // Varint for number of input signatures @@ -563,9 +563,8 @@ func TestTxSerializeWitness(t *testing.T) { t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Serialize the transaction. - var buf bytes.Buffer - buf.Grow(test.in.SerializeSize()) - err := test.in.Serialize(&buf) + buf := bytes.NewBuffer(make([]byte, 0, test.in.SerializeSize())) + err := test.in.Serialize(buf) if err != nil { t.Errorf("Serialize #%d error %v", i, err) continue @@ -622,7 +621,8 @@ func TestTxSerializeWitness(t *testing.T) { // TestTxSerializeWitnessSigning tests MsgTx serialize and deserialize. func TestTxSerializeWitnessSigning(t *testing.T) { noTx := NewMsgTx() - noTx.Version = 196609 + noTx.SerType = TxSerializeWitnessSigning + noTx.Version = 1 noTxEncoded := []byte{ 0x01, 0x00, 0x03, 0x00, // Version 0x00, // Varint for number of input signatures @@ -654,9 +654,8 @@ func TestTxSerializeWitnessSigning(t *testing.T) { t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Serialize the transaction. - var buf bytes.Buffer - buf.Grow(test.in.SerializeSize()) - err := test.in.Serialize(&buf) + buf := bytes.NewBuffer(make([]byte, 0, test.in.SerializeSize())) + err := test.in.Serialize(buf) if err != nil { t.Errorf("Serialize #%d error %v", i, err) continue @@ -713,7 +712,8 @@ func TestTxSerializeWitnessSigning(t *testing.T) { // TestTxSerializeWitnessValueSigning tests MsgTx serialize and deserialize. func TestTxSerializeWitnessValueSigning(t *testing.T) { noTx := NewMsgTx() - noTx.Version = 262145 + noTx.SerType = TxSerializeWitnessValueSigning + noTx.Version = 1 noTxEncoded := []byte{ 0x01, 0x00, 0x04, 0x00, // Version 0x00, // Varint for number of input signatures @@ -745,9 +745,8 @@ func TestTxSerializeWitnessValueSigning(t *testing.T) { t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Serialize the transaction. - var buf bytes.Buffer - buf.Grow(test.in.SerializeSize()) - err := test.in.Serialize(&buf) + buf := bytes.NewBuffer(make([]byte, 0, test.in.SerializeSize())) + err := test.in.Serialize(buf) if err != nil { t.Errorf("Serialize #%d error %v", i, err) continue @@ -883,7 +882,7 @@ func TestTxOverflowErrors(t *testing.T) { // here instead of the latest values because the test data is using // bytes encoded with those versions. pver := uint32(1) - txVer := DefaultMsgTxVersion() + txVer := int32(1) tests := []struct { buf []byte // Wire encoding @@ -1033,6 +1032,7 @@ func TestTxSerializeSize(t *testing.T) { // multiTx is a MsgTx with an input and output and used in various tests. var multiTx = &MsgTx{ + SerType: TxSerializeFull, Version: 1, TxIn: []*TxIn{ { @@ -1091,7 +1091,8 @@ var multiTx = &MsgTx{ // multiTxPrefix is a MsgTx prefix with an input and output and used in various tests. var multiTxPrefix = &MsgTx{ - Version: 65537, + SerType: TxSerializeNoWitness, + Version: 1, TxIn: []*TxIn{ { PreviousOutPoint: OutPoint{ @@ -1143,7 +1144,8 @@ var multiTxPrefix = &MsgTx{ // multiTxWitness is a MsgTx witness with only input witness. var multiTxWitness = &MsgTx{ - Version: 131073, + SerType: TxSerializeOnlyWitness, + Version: 1, TxIn: []*TxIn{ { ValueIn: 0x1212121212121212, @@ -1159,7 +1161,8 @@ var multiTxWitness = &MsgTx{ // multiTxWitnessSigning is a MsgTx witness with only input witness sigscripts. var multiTxWitnessSigning = &MsgTx{ - Version: 196609, + SerType: TxSerializeWitnessSigning, + Version: 1, TxIn: []*TxIn{ { SignatureScript: []byte{ @@ -1173,7 +1176,8 @@ var multiTxWitnessSigning = &MsgTx{ // multiTxWitnessValueSigning is a MsgTx witness with only input witness // sigscripts. var multiTxWitnessValueSigning = &MsgTx{ - Version: 262145, + SerType: TxSerializeWitnessValueSigning, + Version: 1, TxIn: []*TxIn{ { ValueIn: 0x1212121212121212,