From af2264665e1ec519ce3dabaa0792d186eafc67fb Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 7 May 2019 20:14:12 +0100 Subject: [PATCH] Prevent pegin double spends from entering the mempool This bug got introduced with the 0.17 rebase. In 0.14.1 the double pegin claim check was done in the beginning of the validation logic, where the coins view was the actual UTXO set. In 0.17, Core moved several checks because the intermediate method CheckInputs was removed and some logic moved to tx_verify.cpp, where we also put the pegin check. In the mempool acceptance check, however (as opposed to the ConnectBlock check), CheckTxInputs is called with only a partial UTXO view for optimization reasons. This commit adds an extra pegin check in the beginning of the mempool code where we have a full UTXO view. --- src/validation.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 2609ad31e9..c18eb77ebc 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -681,10 +681,27 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } // do all inputs exist? - for (const CTxIn& txin : tx.vin) { + for (unsigned int i = 0; i < tx.vin.size(); i++) { + const CTxIn& txin = tx.vin[i]; + // ELEMENTS: - // Don't look for coins that only exist in parent chain + // For pegin inputs check whether the pegins have already been claimed before. + // This only checks the UTXO set for already claimed pegins. For mempool conflicts, + // we rely on the GetConflictTx check done above. if (txin.m_is_pegin) { + // Quick sanity check on witness first. + if (tx.witness.vtxinwit.size() <= i || + tx.witness.vtxinwit[i].m_pegin_witness.stack.size() < 6 || + uint256(tx.witness.vtxinwit[i].m_pegin_witness.stack[2]).IsNull() || + tx.vin[i].prevout.hash.IsNull()) { + return state.Invalid(false, REJECT_INVALID, "pegin-no-witness"); + } + + std::pair pegin = std::make_pair(uint256(tx.witness.vtxinwit[i].m_pegin_witness.stack[2]), tx.vin[i].prevout); + // This assumes non-null prevout and genesis block hash + if (view.IsPeginSpent(pegin)) { + return state.Invalid(false, REJECT_INVALID, "pegin-already-claimed"); + } continue; }