aboutsummaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp278
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;