diff options
Diffstat (limited to 'src/main.cpp')
-rw-r--r-- | src/main.cpp | 278 |
1 files changed, 247 insertions, 31 deletions
diff --git a/src/main.cpp b/src/main.cpp index 4d88078f41..ceb5cb66f3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -670,10 +670,11 @@ bool CheckFinalTx(const CTransaction &tx, int flags) // IsFinalTx() with one more than chainActive.Height(). const int nBlockHeight = chainActive.Height() + 1; - // Timestamps on the other hand don't get any special treatment, - // because we can't know what timestamp the next block will have, - // and there aren't timestamp applications where it matters. - // However this changes once median past time-locks are enforced: + // BIP113 will require that time-locked transactions have nLockTime set to + // less than the median time of the previous block they're contained in. + // When the next block is created its previous block will be the current + // chain tip, so we use that to calculate the median time passed to + // IsFinalTx() if LOCKTIME_MEDIAN_TIME_PAST is set. const int64_t nBlockTime = (flags & LOCKTIME_MEDIAN_TIME_PAST) ? chainActive.Tip()->GetMedianTimePast() : GetAdjustedTime(); @@ -831,15 +832,42 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-in-mempool"); // Check for conflicts with in-memory transactions + set<uint256> setConflicts; { LOCK(pool.cs); // protect pool.mapNextTx - for (unsigned int i = 0; i < tx.vin.size(); i++) + BOOST_FOREACH(const CTxIn &txin, tx.vin) { - COutPoint outpoint = tx.vin[i].prevout; - if (pool.mapNextTx.count(outpoint)) + if (pool.mapNextTx.count(txin.prevout)) { - // Disable replacement feature for now - return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict"); + const CTransaction *ptxConflicting = pool.mapNextTx[txin.prevout].ptx; + if (!setConflicts.count(ptxConflicting->GetHash())) + { + // Allow opt-out of transaction replacement by setting + // nSequence >= maxint-1 on all inputs. + // + // maxint-1 is picked to still allow use of nLockTime by + // non-replacable transactions. All inputs rather than just one + // is for the sake of multi-party protocols, where we don't + // want a single party to be able to disable replacement. + // + // The opt-out ignores descendants as anyone relying on + // first-seen mempool behavior should be checking all + // unconfirmed ancestors anyway; doing otherwise is hopelessly + // insecure. + bool fReplacementOptOut = true; + BOOST_FOREACH(const CTxIn &txin, ptxConflicting->vin) + { + if (txin.nSequence < std::numeric_limits<unsigned int>::max()-1) + { + fReplacementOptOut = false; + break; + } + } + if (fReplacementOptOut) + return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict"); + + setConflicts.insert(ptxConflicting->GetHash()); + } } } } @@ -957,6 +985,160 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return state.DoS(0, false, REJECT_NONSTANDARD, "too-long-mempool-chain", false, errString); } + // A transaction that spends outputs that would be replaced by it is invalid. Now + // that we have the set of all ancestors we can detect this + // pathological case by making sure setConflicts and setAncestors don't + // intersect. + BOOST_FOREACH(CTxMemPool::txiter ancestorIt, setAncestors) + { + const uint256 &hashAncestor = ancestorIt->GetTx().GetHash(); + if (setConflicts.count(hashAncestor)) + { + return state.DoS(10, error("AcceptToMemoryPool: %s spends conflicting transaction %s", + hash.ToString(), + hashAncestor.ToString()), + REJECT_INVALID, "bad-txns-spends-conflicting-tx"); + } + } + + // Check if it's economically rational to mine this transaction rather + // than the ones it replaces. + CAmount nConflictingFees = 0; + size_t nConflictingSize = 0; + uint64_t nConflictingCount = 0; + CTxMemPool::setEntries allConflicting; + + // If we don't hold the lock allConflicting might be incomplete; the + // subsequent RemoveStaged() and addUnchecked() calls don't guarantee + // mempool consistency for us. + LOCK(pool.cs); + if (setConflicts.size()) + { + CFeeRate newFeeRate(nFees, nSize); + set<uint256> setConflictsParents; + const int maxDescendantsToVisit = 100; + CTxMemPool::setEntries setIterConflicting; + BOOST_FOREACH(const uint256 &hashConflicting, setConflicts) + { + CTxMemPool::txiter mi = pool.mapTx.find(hashConflicting); + if (mi == pool.mapTx.end()) + continue; + + // Save these to avoid repeated lookups + setIterConflicting.insert(mi); + + // If this entry is "dirty", then we don't have descendant + // state for this transaction, which means we probably have + // lots of in-mempool descendants. + // Don't allow replacements of dirty transactions, to ensure + // that we don't spend too much time walking descendants. + // This should be rare. + if (mi->IsDirty()) { + return state.DoS(0, + error("AcceptToMemoryPool: rejecting replacement %s; cannot replace tx %s with untracked descendants", + hash.ToString(), + mi->GetTx().GetHash().ToString()), + REJECT_NONSTANDARD, "too many potential replacements"); + } + + // Don't allow the replacement to reduce the feerate of the + // mempool. + // + // We usually don't want to accept replacements with lower + // feerates than what they replaced as that would lower the + // feerate of the next block. Requiring that the feerate always + // be increased is also an easy-to-reason about way to prevent + // DoS attacks via replacements. + // + // The mining code doesn't (currently) take children into + // account (CPFP) so we only consider the feerates of + // transactions being directly replaced, not their indirect + // descendants. While that does mean high feerate children are + // ignored when deciding whether or not to replace, we do + // require the replacement to pay more overall fees too, + // mitigating most cases. + CFeeRate oldFeeRate(mi->GetFee(), mi->GetTxSize()); + if (newFeeRate <= oldFeeRate) + { + return state.DoS(0, + error("AcceptToMemoryPool: rejecting replacement %s; new feerate %s <= old feerate %s", + hash.ToString(), + newFeeRate.ToString(), + oldFeeRate.ToString()), + REJECT_INSUFFICIENTFEE, "insufficient fee"); + } + + BOOST_FOREACH(const CTxIn &txin, mi->GetTx().vin) + { + setConflictsParents.insert(txin.prevout.hash); + } + + nConflictingCount += mi->GetCountWithDescendants(); + } + // This potentially overestimates the number of actual descendants + // but we just want to be conservative to avoid doing too much + // work. + if (nConflictingCount <= maxDescendantsToVisit) { + // If not too many to replace, then calculate the set of + // transactions that would have to be evicted + BOOST_FOREACH(CTxMemPool::txiter it, setIterConflicting) { + pool.CalculateDescendants(it, allConflicting); + } + BOOST_FOREACH(CTxMemPool::txiter it, allConflicting) { + nConflictingFees += it->GetFee(); + nConflictingSize += it->GetTxSize(); + } + } else { + return state.DoS(0, + error("AcceptToMemoryPool: rejecting replacement %s; too many potential replacements (%d > %d)\n", + hash.ToString(), + nConflictingCount, + maxDescendantsToVisit), + REJECT_NONSTANDARD, "too many potential replacements"); + } + + for (unsigned int j = 0; j < tx.vin.size(); j++) + { + // We don't want to accept replacements that require low + // feerate junk to be mined first. Ideally we'd keep track of + // the ancestor feerates and make the decision based on that, + // but for now requiring all new inputs to be confirmed works. + if (!setConflictsParents.count(tx.vin[j].prevout.hash)) + { + // Rather than check the UTXO set - potentially expensive - + // it's cheaper to just check if the new input refers to a + // tx that's in the mempool. + if (pool.mapTx.find(tx.vin[j].prevout.hash) != pool.mapTx.end()) + return state.DoS(0, error("AcceptToMemoryPool: replacement %s adds unconfirmed input, idx %d", + hash.ToString(), j), + REJECT_NONSTANDARD, "replacement-adds-unconfirmed"); + } + } + + // The replacement must pay greater fees than the transactions it + // replaces - if we did the bandwidth used by those conflicting + // transactions would not be paid for. + if (nFees < nConflictingFees) + { + return state.DoS(0, error("AcceptToMemoryPool: rejecting replacement %s, less fees than conflicting txs; %s < %s", + hash.ToString(), FormatMoney(nFees), FormatMoney(nConflictingFees)), + REJECT_INSUFFICIENTFEE, "insufficient fee"); + } + + // Finally in addition to paying more fees than the conflicts the + // new transaction must pay for its own bandwidth. + CAmount nDeltaFees = nFees - nConflictingFees; + if (nDeltaFees < ::minRelayTxFee.GetFee(nSize)) + { + return state.DoS(0, + error("AcceptToMemoryPool: rejecting replacement %s, not enough additional fees to relay; %s < %s", + hash.ToString(), + FormatMoney(nDeltaFees), + FormatMoney(::minRelayTxFee.GetFee(nSize))), + REJECT_INSUFFICIENTFEE, "insufficient fee"); + } + } + // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true)) @@ -977,6 +1159,17 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa __func__, hash.ToString(), FormatStateMessage(state)); } + // Remove conflicting transactions from the mempool + BOOST_FOREACH(const CTxMemPool::txiter it, allConflicting) + { + LogPrint("mempool", "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n", + it->GetTx().GetHash().ToString(), + hash.ToString(), + FormatMoney(nFees - nConflictingFees), + (int)nSize - (int)nConflictingSize); + } + pool.RemoveStaged(allConflicting); + // Store transaction in memory pool.addUnchecked(hash, entry, setAncestors, !IsInitialBlockDownload()); @@ -1310,10 +1503,17 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach undo.nVersion = coins->nVersion; } } + // add outputs + inputs.ModifyNewCoins(tx.GetHash())->FromTx(tx, nHeight); + } + else { + // add outputs for coinbase tx + // In this case call the full ModifyCoins which will do a database + // lookup to be sure the coins do not already exist otherwise we do not + // know whether to mark them fresh or not. We want the duplicate coinbases + // before BIP30 to still be properly overwritten. + inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight); } - - // add outputs - inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight); } void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, int nHeight) @@ -2580,7 +2780,7 @@ bool FindBlockPos(CValidationState &state, CDiskBlockPos &pos, unsigned int nAdd if ((int)nFile != nLastBlockFile) { if (!fKnown) { - LogPrintf("Leaving block file %i: %s\n", nFile, vinfoBlockFile[nFile].ToString()); + LogPrintf("Leaving block file %i: %s\n", nLastBlockFile, vinfoBlockFile[nLastBlockFile].ToString()); } FlushBlockFile(!fKnown); nLastBlockFile = nFile; @@ -3977,6 +4177,19 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, } + if (!(nLocalServices & NODE_BLOOM) && + (strCommand == "filterload" || + strCommand == "filteradd" || + strCommand == "filterclear")) + { + if (pfrom->nVersion >= NO_BLOOM_VERSION) { + Misbehaving(pfrom->GetId(), 100); + return false; + } else if (GetBoolArg("-enforcenodebloom", false)) { + pfrom->fDisconnect = true; + return false; + } + } if (strCommand == "version") @@ -4199,6 +4412,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, return error("message inv size() = %u", vInv.size()); } + bool fBlocksOnly = GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY); + + // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistalwaysrelay is true + if (pfrom->fWhitelisted && GetBoolArg("-whitelistalwaysrelay", DEFAULT_WHITELISTALWAYSRELAY)) + fBlocksOnly = false; + LOCK(cs_main); std::vector<CInv> vToFetch; @@ -4213,9 +4432,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, bool fAlreadyHave = AlreadyHave(inv); LogPrint("net", "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom->id); - if (!fAlreadyHave && !fImporting && !fReindex && inv.type != MSG_BLOCK && !GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)) - pfrom->AskFor(inv); - if (inv.type == MSG_BLOCK) { UpdateBlockAvailability(pfrom->GetId(), inv.hash); if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) { @@ -4239,6 +4455,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, LogPrint("net", "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->id); } } + else + { + if (fBlocksOnly) + LogPrint("net", "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->id); + else if (!fAlreadyHave && !fImporting && !fReindex) + pfrom->AskFor(inv); + } // Track requests for our stuff GetMainSignals().Inventory(inv.hash); @@ -4363,6 +4586,14 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, else if (strCommand == "tx") { + // Stop processing the transaction early if + // We are in blocks only mode and peer is either not whitelisted or whitelistalwaysrelay is off + if (GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && (!pfrom->fWhitelisted || !GetBoolArg("-whitelistalwaysrelay", DEFAULT_WHITELISTALWAYSRELAY))) + { + LogPrint("net", "transaction sent in violation of protocol peer=%d\n", pfrom->id); + return true; + } + vector<uint256> vWorkQueue; vector<uint256> vEraseQueue; CTransaction tx; @@ -4720,21 +4951,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, } - else if (!(nLocalServices & NODE_BLOOM) && - (strCommand == "filterload" || - strCommand == "filteradd" || - strCommand == "filterclear") && - //TODO: Remove this line after reasonable network upgrade - pfrom->nVersion >= NO_BLOOM_VERSION) - { - if (pfrom->nVersion >= NO_BLOOM_VERSION) - Misbehaving(pfrom->GetId(), 100); - //TODO: Enable this after reasonable network upgrade - //else - // pfrom->fDisconnect = true; - } - - else if (strCommand == "filterload") { CBloomFilter filter; |