From 7030d9eb47254499bba14f1c00abc6bf493efd91 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 6 Nov 2015 01:32:04 +0100 Subject: BIP144: Serialization, hashes, relay (sender side) Contains refactorings by Eric Lombrozo. Contains fixup by Nicolas Dorier. Contains cleanup of CInv::GetCommand by Alex Morcos --- src/core_io.h | 2 +- src/core_memusage.h | 24 +++++- src/core_read.cpp | 16 +++- src/main.cpp | 28 ++++--- src/net.h | 17 +++++ src/primitives/block.h | 2 - src/primitives/transaction.cpp | 16 +++- src/primitives/transaction.h | 151 +++++++++++++++++++++++++++++++++++--- src/protocol.cpp | 46 ++++-------- src/protocol.h | 30 +++++--- src/qt/coincontroldialog.cpp | 18 ++++- src/qt/walletmodeltransaction.cpp | 3 +- src/rpc/rawtransaction.cpp | 6 +- src/script/script.cpp | 12 +++ src/script/script.h | 14 ++++ src/streams.h | 33 +++++++++ src/test/data/tx_invalid.json | 4 - src/test/sighash_tests.cpp | 2 +- src/test/transaction_tests.cpp | 2 +- src/wallet/rpcwallet.cpp | 2 +- src/wallet/walletdb.h | 1 + 21 files changed, 339 insertions(+), 90 deletions(-) diff --git a/src/core_io.h b/src/core_io.h index e8c0c49e84..b559d44bf5 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -17,7 +17,7 @@ class UniValue; // core_read.cpp extern CScript ParseScript(const std::string& s); extern std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false); -extern bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx); +extern bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx, bool fTryNoWitness = false); extern bool DecodeHexBlk(CBlock&, const std::string& strHexBlk); extern uint256 ParseHashUV(const UniValue& v, const std::string& strName); extern uint256 ParseHashStr(const std::string&, const std::string& strName); diff --git a/src/core_memusage.h b/src/core_memusage.h index 450537d059..dd86f805fe 100644 --- a/src/core_memusage.h +++ b/src/core_memusage.h @@ -25,8 +25,28 @@ static inline size_t RecursiveDynamicUsage(const CTxOut& out) { return RecursiveDynamicUsage(out.scriptPubKey); } +static inline size_t RecursiveDynamicUsage(const CScriptWitness& scriptWit) { + size_t mem = memusage::DynamicUsage(scriptWit.stack); + for (std::vector >::const_iterator it = scriptWit.stack.begin(); it != scriptWit.stack.end(); it++) { + mem += memusage::DynamicUsage(*it); + } + return mem; +} + +static inline size_t RecursiveDynamicUsage(const CTxinWitness& txinwit) { + return RecursiveDynamicUsage(txinwit.scriptWitness); +} + +static inline size_t RecursiveDynamicUsage(const CTxWitness& txwit) { + size_t mem = memusage::DynamicUsage(txwit.vtxinwit); + for (std::vector::const_iterator it = txwit.vtxinwit.begin(); it != txwit.vtxinwit.end(); it++) { + mem += RecursiveDynamicUsage(*it); + } + return mem; +} + static inline size_t RecursiveDynamicUsage(const CTransaction& tx) { - size_t mem = memusage::DynamicUsage(tx.vin) + memusage::DynamicUsage(tx.vout); + size_t mem = memusage::DynamicUsage(tx.vin) + memusage::DynamicUsage(tx.vout) + RecursiveDynamicUsage(tx.wit); for (std::vector::const_iterator it = tx.vin.begin(); it != tx.vin.end(); it++) { mem += RecursiveDynamicUsage(*it); } @@ -37,7 +57,7 @@ static inline size_t RecursiveDynamicUsage(const CTransaction& tx) { } static inline size_t RecursiveDynamicUsage(const CMutableTransaction& tx) { - size_t mem = memusage::DynamicUsage(tx.vin) + memusage::DynamicUsage(tx.vout); + size_t mem = memusage::DynamicUsage(tx.vin) + memusage::DynamicUsage(tx.vout) + RecursiveDynamicUsage(tx.wit); for (std::vector::const_iterator it = tx.vin.begin(); it != tx.vin.end(); it++) { mem += RecursiveDynamicUsage(*it); } diff --git a/src/core_read.cpp b/src/core_read.cpp index 444a4c7eba..7cfda6dd6d 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -90,12 +90,26 @@ CScript ParseScript(const std::string& s) return result; } -bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx) +bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx, bool fTryNoWitness) { if (!IsHex(strHexTx)) return false; vector txData(ParseHex(strHexTx)); + + if (fTryNoWitness) { + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); + try { + ssData >> tx; + if (ssData.eof()) { + return true; + } + } + catch (const std::exception&) { + // Fall through. + } + } + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); try { ssData >> tx; diff --git a/src/main.cpp b/src/main.cpp index ae195ecc40..2184940232 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1029,8 +1029,8 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state) return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty"); if (tx.vout.empty()) return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty"); - // Size limits - if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE) + // Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability) + if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) > MAX_BLOCK_SIZE) return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize"); // Check for negative or overflow output values @@ -3396,7 +3396,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P // because we receive the wrong transactions for it. // Size limits - if (block.vtx.empty() || block.vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE) + if (block.vtx.empty() || block.vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) > MAX_BLOCK_SIZE) return state.DoS(100, false, REJECT_INVALID, "bad-blk-length", false, "size limits failed"); // First transaction must be coinbase, the rest must not be @@ -4508,6 +4508,7 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) switch (inv.type) { case MSG_TX: + case MSG_WITNESS_TX: { assert(recentRejects); if (chainActive.Tip()->GetBlockHash() != hashRecentRejectsChainTip) @@ -4528,6 +4529,7 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) pcoinsTip->HaveCoinsInCache(inv.hash); } case MSG_BLOCK: + case MSG_WITNESS_BLOCK: return mapBlockIndex.count(inv.hash); } // Don't know what it is, just say we already got one @@ -4552,7 +4554,7 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam boost::this_thread::interruption_point(); it++; - if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK) + if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK || inv.type == MSG_WITNESS_BLOCK) { bool send = false; BlockMap::iterator mi = mapBlockIndex.find(inv.hash); @@ -4593,6 +4595,8 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam if (!ReadBlockFromDisk(block, (*mi).second, consensusParams)) assert(!"cannot load block from disk"); if (inv.type == MSG_BLOCK) + pfrom->PushMessageWithFlag(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, block); + else if (inv.type == MSG_WITNESS_BLOCK) pfrom->PushMessage(NetMsgType::BLOCK, block); else if (inv.type == MSG_FILTERED_BLOCK) { @@ -4609,7 +4613,7 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam // however we MUST always provide at least what the remote peer needs typedef std::pair PairType; BOOST_FOREACH(PairType& pair, merkleBlock.vMatchedTxn) - pfrom->PushMessage(NetMsgType::TX, block.vtx[pair.first]); + pfrom->PushMessageWithFlag(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, block.vtx[pair.first]); } // else // no response @@ -4622,9 +4626,9 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam // instead we respond with the full, non-compact block. if (mi->second->nHeight >= chainActive.Height() - 10) { CBlockHeaderAndShortTxIDs cmpctblock(block); - pfrom->PushMessage(NetMsgType::CMPCTBLOCK, cmpctblock); + pfrom->PushMessageWithFlag(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::CMPCTBLOCK, cmpctblock); } else - pfrom->PushMessage(NetMsgType::BLOCK, block); + pfrom->PushMessageWithFlag(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, block); } // Trigger the peer node to send a getblocks request for the next batch of inventory @@ -4640,20 +4644,20 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam } } } - else if (inv.type == MSG_TX) + else if (inv.type == MSG_TX || inv.type == MSG_WITNESS_TX) { // Send stream from relay memory bool push = false; auto mi = mapRelay.find(inv.hash); if (mi != mapRelay.end()) { - pfrom->PushMessage(NetMsgType::TX, *mi->second); + pfrom->PushMessageWithFlag(inv.type == MSG_TX ? SERIALIZE_TRANSACTION_NO_WITNESS : 0, NetMsgType::TX, *mi->second); push = true; } else if (pfrom->timeLastMempoolReq) { auto txinfo = mempool.info(inv.hash); // To protect privacy, do not answer getdata using the mempool when // that TX couldn't have been INVed in reply to a MEMPOOL request. if (txinfo.tx && txinfo.nTime <= pfrom->timeLastMempoolReq) { - pfrom->PushMessage(NetMsgType::TX, *txinfo.tx); + pfrom->PushMessageWithFlag(inv.type == MSG_TX ? SERIALIZE_TRANSACTION_NO_WITNESS : 0, NetMsgType::TX, *txinfo.tx); push = true; } } @@ -4665,7 +4669,7 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam // Track requests for our stuff. GetMainSignals().Inventory(inv.hash); - if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK) + if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK || inv.type == MSG_WITNESS_BLOCK) break; } } @@ -5146,7 +5150,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, } resp.txn[i] = block.vtx[req.indexes[i]]; } - pfrom->PushMessage(NetMsgType::BLOCKTXN, resp); + pfrom->PushMessageWithFlag(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCKTXN, resp); } diff --git a/src/net.h b/src/net.h index aa9b2c11a3..cb35d8c4f2 100644 --- a/src/net.h +++ b/src/net.h @@ -598,6 +598,23 @@ public: } } + /** Send a message containing a1, serialized with flag flag. */ + template + void PushMessageWithFlag(int flag, const char* pszCommand, const T1& a1) + { + try + { + BeginMessage(pszCommand); + WithOrVersion(&ssSend, flag) << a1; + EndMessage(pszCommand); + } + catch (...) + { + AbortMessage(); + throw; + } + } + template void PushMessage(const char* pszCommand, const T1& a1, const T2& a2) { diff --git a/src/primitives/block.h b/src/primitives/block.h index 42276b2bc2..29307aed5b 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -38,7 +38,6 @@ public: template inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { READWRITE(this->nVersion); - nVersion = this->nVersion; READWRITE(hashPrevBlock); READWRITE(hashMerkleRoot); READWRITE(nTime); @@ -120,7 +119,6 @@ public: std::string ToString() const; }; - /** Describes a place in the block chain to another node such that if the * other node doesn't have the same branch, it can find a recent common trunk. * The further back it is, the further before the fork it may be. diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 947f2e6a73..b0230530e8 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -60,21 +60,26 @@ std::string CTxOut::ToString() const } CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), nLockTime(0) {} -CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime) {} +CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), wit(tx.wit), nLockTime(tx.nLockTime) {} uint256 CMutableTransaction::GetHash() const { - return SerializeHash(*this); + return SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS); } void CTransaction::UpdateHash() const { - *const_cast(&hash) = SerializeHash(*this); + *const_cast(&hash) = SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS); +} + +uint256 CTransaction::GetWitnessHash() const +{ + return SerializeHash(*this, SER_GETHASH, 0); } CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0) { } -CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime) { +CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), wit(tx.wit), nLockTime(tx.nLockTime) { UpdateHash(); } @@ -82,6 +87,7 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) { *const_cast(&nVersion) = tx.nVersion; *const_cast*>(&vin) = tx.vin; *const_cast*>(&vout) = tx.vout; + *const_cast(&wit) = tx.wit; *const_cast(&nLockTime) = tx.nLockTime; *const_cast(&hash) = tx.hash; return *this; @@ -136,6 +142,8 @@ std::string CTransaction::ToString() const nLockTime); for (unsigned int i = 0; i < vin.size(); i++) str += " " + vin[i].ToString() + "\n"; + for (unsigned int i = 0; i < wit.vtxinwit.size(); i++) + str += " " + wit.vtxinwit[i].scriptWitness.ToString() + "\n"; for (unsigned int i = 0; i < vout.size(); i++) str += " " + vout[i].ToString() + "\n"; return str; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 149816406a..d8ae41ad78 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -11,6 +11,8 @@ #include "serialize.h" #include "uint256.h" +static const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000; + /** An outpoint - a combination of a transaction hash and an index n into its vout */ class COutPoint { @@ -194,8 +196,137 @@ public: std::string ToString() const; }; +class CTxinWitness +{ +public: + CScriptWitness scriptWitness; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(scriptWitness.stack); + } + + bool IsNull() const { return scriptWitness.IsNull(); } + + CTxinWitness() { } +}; + +class CTxWitness +{ +public: + /** In case vtxinwit is missing, all entries are treated as if they were empty CTxInWitnesses */ + std::vector vtxinwit; + + ADD_SERIALIZE_METHODS; + + bool IsEmpty() const { return vtxinwit.empty(); } + + bool IsNull() const + { + for (size_t n = 0; n < vtxinwit.size(); n++) { + if (!vtxinwit[n].IsNull()) { + return false; + } + } + return true; + } + + void SetNull() + { + vtxinwit.clear(); + } + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + for (size_t n = 0; n < vtxinwit.size(); n++) { + READWRITE(vtxinwit[n]); + } + if (IsNull()) { + /* It's illegal to encode a witness when all vtxinwit entries are empty. */ + throw std::ios_base::failure("Superfluous witness record"); + } + } +}; + struct CMutableTransaction; +/** + * Basic transaction serialization format: + * - int32_t nVersion + * - std::vector vin + * - std::vector vout + * - uint32_t nLockTime + * + * Extended transaction serialization format: + * - int32_t nVersion + * - unsigned char dummy = 0x00 + * - unsigned char flags (!= 0) + * - std::vector vin + * - std::vector vout + * - if (flags & 1): + * - CTxWitness wit; + * - uint32_t nLockTime + */ +template +inline void SerializeTransaction(TxType& tx, Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(*const_cast(&tx.nVersion)); + unsigned char flags = 0; + if (ser_action.ForRead()) { + const_cast*>(&tx.vin)->clear(); + const_cast*>(&tx.vout)->clear(); + const_cast(&tx.wit)->SetNull(); + /* Try to read the vin. In case the dummy is there, this will be read as an empty vector. */ + READWRITE(*const_cast*>(&tx.vin)); + if (tx.vin.size() == 0 && !(nVersion & SERIALIZE_TRANSACTION_NO_WITNESS)) { + /* We read a dummy or an empty vin. */ + READWRITE(flags); + if (flags != 0) { + READWRITE(*const_cast*>(&tx.vin)); + READWRITE(*const_cast*>(&tx.vout)); + } + } else { + /* We read a non-empty vin. Assume a normal vout follows. */ + READWRITE(*const_cast*>(&tx.vout)); + } + if ((flags & 1) && !(nVersion & SERIALIZE_TRANSACTION_NO_WITNESS)) { + /* The witness flag is present, and we support witnesses. */ + flags ^= 1; + const_cast(&tx.wit)->vtxinwit.resize(tx.vin.size()); + READWRITE(tx.wit); + } + if (flags) { + /* Unknown flag in the serialization */ + throw std::ios_base::failure("Unknown transaction optional data"); + } + } else { + // Consistency check + assert(tx.wit.vtxinwit.size() <= tx.vin.size()); + if (!(nVersion & SERIALIZE_TRANSACTION_NO_WITNESS)) { + /* Check whether witnesses need to be serialized. */ + if (!tx.wit.IsNull()) { + flags |= 1; + } + } + if (flags) { + /* Use extended format in case witnesses are to be serialized. */ + std::vector vinDummy; + READWRITE(vinDummy); + READWRITE(flags); + } + READWRITE(*const_cast*>(&tx.vin)); + READWRITE(*const_cast*>(&tx.vout)); + if (flags & 1) { + const_cast(&tx.wit)->vtxinwit.resize(tx.vin.size()); + READWRITE(tx.wit); + } + } + READWRITE(*const_cast(&tx.nLockTime)); +} + /** The basic transaction that is broadcasted on the network and contained in * blocks. A transaction can contain multiple inputs and outputs. */ @@ -224,6 +355,7 @@ public: const int32_t nVersion; const std::vector vin; const std::vector vout; + CTxWitness wit; // Not const: can change without invalidating the txid cache const uint32_t nLockTime; /** Construct a CTransaction that qualifies as IsNull() */ @@ -238,13 +370,10 @@ public: template inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { - READWRITE(*const_cast(&this->nVersion)); - nVersion = this->nVersion; - READWRITE(*const_cast*>(&vin)); - READWRITE(*const_cast*>(&vout)); - READWRITE(*const_cast(&nLockTime)); - if (ser_action.ForRead()) + SerializeTransaction(*this, s, ser_action, nType, nVersion); + if (ser_action.ForRead()) { UpdateHash(); + } } bool IsNull() const { @@ -255,6 +384,9 @@ public: return hash; } + // Compute a hash that includes both transaction and witness data + uint256 GetWitnessHash() const; + // Return sum of txouts. CAmount GetValueOut() const; // GetValueIn() is a method on CCoinsViewCache, because @@ -290,6 +422,7 @@ struct CMutableTransaction int32_t nVersion; std::vector vin; std::vector vout; + CTxWitness wit; uint32_t nLockTime; CMutableTransaction(); @@ -299,11 +432,7 @@ struct CMutableTransaction template inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { - READWRITE(this->nVersion); - nVersion = this->nVersion; - READWRITE(vin); - READWRITE(vout); - READWRITE(nLockTime); + SerializeTransaction(*this, s, ser_action, nType, nVersion); } /** Compute the hash of this CMutableTransaction. This is computed on the diff --git a/src/protocol.cpp b/src/protocol.cpp index 2f90fb764c..247c6c2120 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -41,15 +41,6 @@ const char *GETBLOCKTXN="getblocktxn"; const char *BLOCKTXN="blocktxn"; }; -static const char* ppszTypeName[] = -{ - "ERROR", // Should never occur - NetMsgType::TX, - NetMsgType::BLOCK, - "filtered block", // Should never occur - "compact block" // Should never occur -}; - /** All known message types. Keep this in the same order as the list of * messages above and in protocol.h. */ @@ -166,37 +157,26 @@ CInv::CInv(int typeIn, const uint256& hashIn) hash = hashIn; } -CInv::CInv(const std::string& strType, const uint256& hashIn) -{ - unsigned int i; - for (i = 1; i < ARRAYLEN(ppszTypeName); i++) - { - if (strType == ppszTypeName[i]) - { - type = i; - break; - } - } - if (i == ARRAYLEN(ppszTypeName)) - throw std::out_of_range(strprintf("CInv::CInv(string, uint256): unknown type '%s'", strType)); - hash = hashIn; -} - bool operator<(const CInv& a, const CInv& b) { return (a.type < b.type || (a.type == b.type && a.hash < b.hash)); } -bool CInv::IsKnownType() const +std::string CInv::GetCommand() const { - return (type >= 1 && type < (int)ARRAYLEN(ppszTypeName)); -} - -const char* CInv::GetCommand() const -{ - if (!IsKnownType()) + std::string cmd; + if (type & MSG_WITNESS_FLAG) + cmd.append("witness-"); + int masked = type & MSG_TYPE_MASK; + switch (masked) + { + case MSG_TX: return cmd.append(NetMsgType::TX); + case MSG_BLOCK: return cmd.append(NetMsgType::BLOCK); + case MSG_FILTERED_BLOCK: return cmd.append(NetMsgType::MERKLEBLOCK); + case MSG_CMPCT_BLOCK: return cmd.append(NetMsgType::CMPCTBLOCK); + default: throw std::out_of_range(strprintf("CInv::GetCommand(): type=%d unknown type", type)); - return ppszTypeName[type]; + } } std::string CInv::ToString() const diff --git a/src/protocol.h b/src/protocol.h index a72813e959..dd07092f5f 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -309,13 +309,29 @@ public: unsigned int nTime; }; +/** getdata message types */ +const uint32_t MSG_WITNESS_FLAG = 1 << 30; +const uint32_t MSG_TYPE_MASK = 0xffffffff >> 2; +enum GetDataMsg +{ + UNDEFINED = 0, + MSG_TX, + MSG_BLOCK, + MSG_TYPE_MAX = MSG_BLOCK, + // The following can only occur in getdata. Invs always use TX or BLOCK. + MSG_FILTERED_BLOCK, + MSG_CMPCT_BLOCK, + MSG_WITNESS_BLOCK = MSG_BLOCK | MSG_WITNESS_FLAG, + MSG_WITNESS_TX = MSG_TX | MSG_WITNESS_FLAG, + MSG_FILTERED_WITNESS_BLOCK = MSG_FILTERED_BLOCK | MSG_WITNESS_FLAG, +}; + /** inv message data */ class CInv { public: CInv(); CInv(int typeIn, const uint256& hashIn); - CInv(const std::string& strType, const uint256& hashIn); ADD_SERIALIZE_METHODS; @@ -328,8 +344,7 @@ public: friend bool operator<(const CInv& a, const CInv& b); - bool IsKnownType() const; - const char* GetCommand() const; + std::string GetCommand() const; std::string ToString() const; // TODO: make private (improves encapsulation) @@ -338,13 +353,4 @@ public: uint256 hash; }; -enum { - MSG_TX = 1, - MSG_BLOCK, - // Nodes may always request a MSG_FILTERED_BLOCK/MSG_CMPCT_BLOCK in a getdata, however, - // MSG_FILTERED_BLOCK/MSG_CMPCT_BLOCK should not appear in any invs except as a part of getdata. - MSG_FILTERED_BLOCK, - MSG_CMPCT_BLOCK, -}; - #endif // BITCOIN_PROTOCOL_H diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index f909499952..837f8ba6c1 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -485,6 +485,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) unsigned int nQuantity = 0; int nQuantityUncompressed = 0; bool fAllowFree = false; + bool fWitness = false; std::vector vCoinControl; std::vector vOutputs; @@ -513,7 +514,14 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) // Bytes CTxDestination address; - if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) + int witnessversion = 0; + std::vector witnessprogram; + if (out.tx->vout[out.i].scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) + { + nBytesInputs += (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4); + fWitness = true; + } + else if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { CPubKey pubkey; CKeyID *keyid = boost::get(&address); @@ -534,6 +542,14 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) { // Bytes nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here + if (fWitness) + { + // there is some fudging in these numbers related to the actual virtual transaction size calculation that will keep this estimate from being exact. + // usually, the result will be an overestimate within a couple of satoshis so that the confirmation dialog ends up displaying a slightly smaller fee. + // also, the witness stack size value value is a variable sized integer. usually, the number of stack items will be well under the single byte var int limit. + nBytes += 2; // account for the serialized marker and flag bytes + nBytes += nQuantity; // account for the witness byte that holds the number of stack items for each input. + } // Priority double mempoolEstimatePriority = mempool.estimateSmartPriority(nTxConfirmTarget); diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index 8c970ee8aa..ffadf89cc8 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -4,6 +4,7 @@ #include "walletmodeltransaction.h" +#include "policy/policy.h" #include "wallet/wallet.h" WalletModelTransaction::WalletModelTransaction(const QList &recipients) : @@ -33,7 +34,7 @@ CWalletTx *WalletModelTransaction::getTransaction() unsigned int WalletModelTransaction::getTransactionSize() { - return (!walletTransaction ? 0 : (::GetSerializeSize(*(CTransaction*)walletTransaction, SER_NETWORK, PROTOCOL_VERSION))); + return (!walletTransaction ? 0 : ::GetVirtualTransactionSize(*walletTransaction)); } CAmount WalletModelTransaction::getTransactionFee() diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 9723e394d6..e4a6e0f39b 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -276,7 +276,7 @@ UniValue gettxoutproof(const UniValue& params, bool fHelp) if (ntxFound != setTxids.size()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "(Not all) transactions not found in specified block"); - CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION); + CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); CMerkleBlock mb(block, setTxids); ssMB << mb; std::string strHex = HexStr(ssMB.begin(), ssMB.end()); @@ -296,7 +296,7 @@ UniValue verifytxoutproof(const UniValue& params, bool fHelp) "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof is invalid\n" ); - CDataStream ssMB(ParseHexV(params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION); + CDataStream ssMB(ParseHexV(params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); CMerkleBlock merkleBlock; ssMB >> merkleBlock; @@ -487,7 +487,7 @@ UniValue decoderawtransaction(const UniValue& params, bool fHelp) CTransaction tx; - if (!DecodeHexTx(tx, params[0].get_str())) + if (!DecodeHexTx(tx, params[0].get_str(), true)) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); UniValue result(UniValue::VOBJ); diff --git a/src/script/script.cpp b/src/script/script.cpp index 9f2809e593..6b1eb52bbf 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -231,3 +231,15 @@ bool CScript::IsPushOnly() const { return this->IsPushOnly(begin()); } + +std::string CScriptWitness::ToString() const +{ + std::string ret = "CScriptWitness("; + for (unsigned int i = 0; i < stack.size(); i++) { + if (i) { + ret += ", "; + } + ret += HexStr(stack[i]); + } + return ret + ")"; +} diff --git a/src/script/script.h b/src/script/script.h index a2941ce901..0b6d822d81 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -643,6 +643,20 @@ public: } }; +struct CScriptWitness +{ + // Note that this encodes the data elements being pushed, rather than + // encoding them as a CScript that pushes them. + std::vector > stack; + + // Some compilers complain without a default constructor + CScriptWitness() { } + + bool IsNull() const { return stack.empty(); } + + std::string ToString() const; +}; + class CReserveScript { public: diff --git a/src/streams.h b/src/streams.h index ed14f3f412..7132364eb1 100644 --- a/src/streams.h +++ b/src/streams.h @@ -22,6 +22,39 @@ #include #include +template +class OverrideStream +{ + Stream* stream; +public: + const int nType; + const int nVersion; + + OverrideStream(Stream* stream_, int nType_, int nVersion_) : stream(stream_), nType(nType_), nVersion(nVersion_) {} + + template + OverrideStream& operator<<(const T& obj) + { + // Serialize to this stream + ::Serialize(*this->stream, obj, nType, nVersion); + return (*this); + } + + template + OverrideStream& operator>>(T& obj) + { + // Unserialize from this stream + ::Unserialize(*this->stream, obj, nType, nVersion); + return (*this); + } +}; + +template +OverrideStream WithOrVersion(S* s, int nVersionFlag) +{ + return OverrideStream(s, s->GetType(), s->GetVersion() | nVersionFlag); +} + /** Double ended buffer combining vector and stream-like interfaces. * * >> and << read and write unformatted data using the above serialization templates. diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index 2d7d9b9585..4719f2b388 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -30,10 +30,6 @@ "010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000", "P2SH"], ["Tests for CheckTransaction()"], -["No inputs"], -[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]], -"0100000000010000000000000000015100000000", "P2SH"], - ["No outputs"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x05ab9e14d983742513f0f451e105ffb4198d1dd4 EQUAL"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022100f16703104aab4e4088317c862daec83440242411b039d14280e03dd33b487ab802201318a7be236672c5c56083eb7a5a195bc57a40af7923ff8545016cd3b571e2a601232103c40e5d339df3f30bf753e7e04450ae4ef76c9e45587d1d993bdc4cd06f0651c7acffffffff0000000000", "P2SH"], diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index e43b2ff6c4..375accbac7 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -82,7 +82,7 @@ uint256 static SignatureHashOld(CScript scriptCode, const CTransaction& txTo, un } // Serialize and hash - CHashWriter ss(SER_GETHASH, 0); + CHashWriter ss(SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS); ss << txTmp << nHashType; return ss.GetHash(); } diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index d9195bf345..318fddc1ae 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -208,7 +208,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid) } string transaction = test[1].get_str(); - CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION); + CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION ); CTransaction tx; stream >> tx; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 2d4e95911d..3666c37ac3 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2451,7 +2451,7 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp) // parse hex string from parameter CTransaction origTx; - if (!DecodeHexTx(origTx, params[0].get_str())) + if (!DecodeHexTx(origTx, params[0].get_str(), true)) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); if (origTx.vout.size() == 0) diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 71b0ff26db..d083722dd2 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -7,6 +7,7 @@ #define BITCOIN_WALLET_WALLETDB_H #include "amount.h" +#include "primitives/transaction.h" #include "wallet/db.h" #include "key.h" -- cgit v1.2.3