diff --git a/src/coins.h b/src/coins.h index f84c77099d..9468585d99 100644 --- a/src/coins.h +++ b/src/coins.h @@ -90,6 +90,9 @@ public: void FromTx(const CTransaction &tx, int nHeightIn) { fCoinBase = tx.IsCoinBase(); vout = tx.vout; + for (size_t i = 0; i < vout.size(); i++) { + CTxOutWitnessSerializer(vout[i]).SetNull(); + } nHeight = nHeightIn; nVersion = tx.nVersion; ClearUnspendable(); diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 8df0c13625..bccf79b015 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -154,13 +154,9 @@ public: if (ser_action.ForRead()) SetToBitcoinAmount(nAmount); } else { - // Though the range proof + nonce commitment are essentially witness data, - // we dont care too much about the space-savings here. Also, since they're - // signed by everything (output data), we don't take a malleability hit for - // doing this. + // We only serialize the value commitment here. + // The ECDH key and range proof are serialized through CTxOutWitnessSerializer. READWRITE(REF(CFlatData(&vchCommitment[0], &vchCommitment[nCommitmentSize]))); - READWRITE(vchRangeproof); - READWRITE(vchNonceCommitment); } } @@ -271,6 +267,34 @@ public: std::string ToString() const; }; +class CTxOutWitnessSerializer +{ + CTxOut& ref; + +public: + CTxOutWitnessSerializer(CTxOut& ref_) : ref(ref_) {} + + ADD_SERIALIZE_METHODS; + + bool IsNull() const { + return ref.nValue.vchRangeproof.empty() && ref.nValue.vchNonceCommitment.empty(); + } + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + if (!(nVersion & SERIALIZE_BITCOIN_BLOCK_OR_TX)) { + READWRITE(ref.nValue.vchRangeproof); + READWRITE(ref.nValue.vchNonceCommitment); + } + } + + void SetNull() { + std::vector().swap(ref.nValue.vchRangeproof); + std::vector().swap(ref.nValue.vchNonceCommitment); + } +}; + + class CTxInWitness { public: @@ -346,13 +370,17 @@ struct CMutableTransaction; * - std::vector vout * - if (flags & 1): * - CTxWitness wit; + * - if (flags & 2): + * - CTxOutWitness witout; * - uint32_t nLockTime */ static const CAmount TX_FEE_BITCOIN_TX_FLAG = -42; template inline void SerializeTransaction(TxType& tx, Stream& s, Operation ser_action, int nType, int nVersion) { + const bool fAllowWitness = !(nVersion & SERIALIZE_TRANSACTION_NO_WITNESS); + const bool fIsBitcoinTx = (nVersion & SERIALIZE_BITCOIN_BLOCK_OR_TX); READWRITE(*const_cast(&tx.nVersion)); - if ((ser_action.ForRead() || (!ser_action.ForRead() && tx.nTxFee != TX_FEE_BITCOIN_TX_FLAG)) && !(nVersion & SERIALIZE_BITCOIN_BLOCK_OR_TX)) + if ((ser_action.ForRead() || (!ser_action.ForRead() && tx.nTxFee != TX_FEE_BITCOIN_TX_FLAG)) && !fIsBitcoinTx) READWRITE(*const_cast(&tx.nTxFee)); else if (ser_action.ForRead()) const_cast(tx.nTxFee) = TX_FEE_BITCOIN_TX_FLAG; @@ -380,6 +408,21 @@ inline void SerializeTransaction(TxType& tx, Stream& s, Operation ser_action, in const_cast(&tx.wit)->vtxinwit.resize(tx.vin.size()); READWRITE(tx.wit); } + if ((flags & 2) && fAllowWitness && !fIsBitcoinTx) { + /* The witness output flag is present, and we support witnesses. */ + flags ^= 2; + bool fHadOutputWitness = false; + for (size_t i = 0; i < tx.vout.size(); i++) { + CTxOutWitnessSerializer witser(REF(tx.vout[i])); + READWRITE(witser); + if (!witser.IsNull()) { + fHadOutputWitness = true; + } + } + if (!fHadOutputWitness) { + throw std::ios_base::failure("Superfluous output witness record"); + } + } if (flags) { /* Unknown flag in the serialization */ throw std::ios_base::failure("Unknown transaction optional data"); @@ -392,6 +435,14 @@ inline void SerializeTransaction(TxType& tx, Stream& s, Operation ser_action, in if (!tx.wit.IsNull()) { flags |= 1; } + if (!fIsBitcoinTx) { + for (size_t i = 0; i < tx.vout.size(); i++) { + if (!CTxOutWitnessSerializer(*const_cast(&tx.vout[i])).IsNull()) { + flags |= 2; + break; + } + } + } } if (flags) { /* Use extended format in case witnesses are to be serialized. */ @@ -405,6 +456,12 @@ inline void SerializeTransaction(TxType& tx, Stream& s, Operation ser_action, in const_cast(&tx.wit)->vtxinwit.resize(tx.vin.size()); READWRITE(tx.wit); } + if (flags & 2) { + for (size_t i = 0; i < tx.vout.size(); i++) { + CTxOutWitnessSerializer witser(*const_cast(&tx.vout[i])); + READWRITE(witser); + } + } } READWRITE(*const_cast(&tx.nLockTime)); } diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 79a3b63fb5..9fa91582f8 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -71,9 +71,9 @@ BOOST_AUTO_TEST_CASE(rpc_rawparams) BOOST_CHECK_THROW(CallRPC("decoderawtransaction"), runtime_error); BOOST_CHECK_THROW(CallRPC("decoderawtransaction null"), runtime_error); BOOST_CHECK_THROW(CallRPC("decoderawtransaction DEADBEEF"), runtime_error); - string rawtx = "01000000000000000000000000000002000000000000000000000000000000000000000000000000000000000005f5e10000210336cf214cb1633268d90ef794165de68911661210cb82e61276639b10911e71361976a914261324748d65eaf0fa3e22b1bd79f2f487c9d98188ac00000000000000000000000000000000000000000000000000000000000bebc200002102248846a76f455da4b3d6ca78a021aabcca7e22e9abd5c9813fb27155dd2739871976a91457a463ab039b40e9457abaff84a9bdd39c9caf0088ac69000000"; + string rawtx = "0100000000000000000000000132313029282726252423222120191817161514131211100908070605040302010000000000ffffffff010000000000000000000000000000000000000000000000000000000000000000000069000000"; BOOST_CHECK_NO_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx)); - BOOST_CHECK_EQUAL(find_value(r.get_obj(), "size").get_int(), 208); + BOOST_CHECK_EQUAL(find_value(r.get_obj(), "size").get_int(), 93); BOOST_CHECK_EQUAL(find_value(r.get_obj(), "version").get_int(), 1); BOOST_CHECK_EQUAL(find_value(r.get_obj(), "locktime").get_int(), 105); BOOST_CHECK_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx+" extra"), runtime_error);