If -spkreuse=0, ensure transactions in mempool always have unique scriptPubKeys

Exceptions:
- Multiple inputs in the same transaction are allowed to spend against the same scriptPubKey
- The same scriptPubKey may be used in the mempool as both first an output, and then spent in a later transaction's input
This commit is contained in:
Luke Dashjr 2017-02-13 00:01:55 +00:00
parent 437badb77d
commit ada4eca6ec
7 changed files with 96 additions and 0 deletions

View File

@ -589,6 +589,7 @@ void SetupServerArgs(ArgsManager& argsman)
OptionsCategory::NODE_RELAY); OptionsCategory::NODE_RELAY);
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)", argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-spkreuse", strprintf("Accept transactions reusing addresses or other pubkey scripts (default: %s)", DEFAULT_SPKREUSE), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted peers with default permissions. This will relay transactions even if the transactions were already in the mempool. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted peers with default permissions. This will relay transactions even if the transactions were already in the mempool. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-whitelistrelay", strprintf("Add 'relay' permission to whitelisted peers with default permissions. This will accept relayed transactions even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-whitelistrelay", strprintf("Add 'relay' permission to whitelisted peers with default permissions. This will accept relayed transactions even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
@ -969,6 +970,16 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
if (!g_wallet_init_interface.ParameterInteraction()) return false; if (!g_wallet_init_interface.ParameterInteraction()) return false;
{
std::string strSpkReuse = gArgs.GetArg("-spkreuse", DEFAULT_SPKREUSE);
// Uses string values so future versions can implement other modes
if (strSpkReuse == "allow" || gArgs.GetBoolArg("-spkreuse", false)) {
SpkReuseMode = SRM_ALLOW;
} else {
SpkReuseMode = SRM_REJECT;
}
}
// Option to startup with mocktime set (used for regression testing): // Option to startup with mocktime set (used for regression testing):
SetMockTime(args.GetIntArg("-mocktime", 0)); // SetMockTime(0) is a no-op SetMockTime(args.GetIntArg("-mocktime", 0)); // SetMockTime(0) is a no-op

View File

@ -35,6 +35,15 @@ struct LockPoints {
CBlockIndex* maxInputBlock{nullptr}; CBlockIndex* maxInputBlock{nullptr};
}; };
enum MemPool_SPK_State {
MSS_UNSEEN = 0,
MSS_SPENT = 1,
MSS_CREATED = 2,
MSS_BOTH = 3,
};
typedef std::map<uint160, enum MemPool_SPK_State> SPKStates_t;
struct CompareIteratorByHash { struct CompareIteratorByHash {
// SFINAE for T where T is either a pointer type (e.g., a txiter) or a reference_wrapper<T> // SFINAE for T where T is either a pointer type (e.g., a txiter) or a reference_wrapper<T>
// (e.g. a wrapped CTxMemPoolEntry&) // (e.g. a wrapped CTxMemPoolEntry&)
@ -169,6 +178,8 @@ public:
mutable size_t vTxHashesIdx; //!< Index in mempool's vTxHashes mutable size_t vTxHashesIdx; //!< Index in mempool's vTxHashes
mutable Epoch::Marker m_epoch_marker; //!< epoch when last touched, useful for graph algorithms mutable Epoch::Marker m_epoch_marker; //!< epoch when last touched, useful for graph algorithms
SPKStates_t mapSPK;
}; };
#endif // BITCOIN_KERNEL_MEMPOOL_ENTRY_H #endif // BITCOIN_KERNEL_MEMPOOL_ENTRY_H

View File

@ -54,6 +54,7 @@ static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650};
* only increase the dust limit after prior releases were already not creating * only increase the dust limit after prior releases were already not creating
* outputs below the new threshold */ * outputs below the new threshold */
static constexpr unsigned int DUST_RELAY_TX_FEE{3000}; static constexpr unsigned int DUST_RELAY_TX_FEE{3000};
static const std::string DEFAULT_SPKREUSE{"allow"};
/** Default for -minrelaytxfee, minimum relay fee for transactions */ /** Default for -minrelaytxfee, minimum relay fee for transactions */
static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{1000}; static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{1000};
/** Default for -limitancestorcount, max number of in-mempool ancestors */ /** Default for -limitancestorcount, max number of in-mempool ancestors */

View File

@ -10,10 +10,12 @@
#include <consensus/consensus.h> #include <consensus/consensus.h>
#include <consensus/tx_verify.h> #include <consensus/tx_verify.h>
#include <consensus/validation.h> #include <consensus/validation.h>
#include <crypto/ripemd160.h>
#include <policy/fees.h> #include <policy/fees.h>
#include <policy/policy.h> #include <policy/policy.h>
#include <policy/settings.h> #include <policy/settings.h>
#include <reverse_iterator.h> #include <reverse_iterator.h>
#include <script/script.h>
#include <util/check.h> #include <util/check.h>
#include <util/moneystr.h> #include <util/moneystr.h>
#include <util/overflow.h> #include <util/overflow.h>
@ -46,6 +48,13 @@ bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp)
return true; return true;
} }
uint160 ScriptHashkey(const CScript& script)
{
uint160 hash;
CRIPEMD160().Write(script.data(), script.size()).Finalize(hash.begin());
return hash;
}
void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants, void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants,
const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove) const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove)
{ {
@ -482,6 +491,10 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces
vTxHashes.emplace_back(tx.GetWitnessHash(), newit); vTxHashes.emplace_back(tx.GetWitnessHash(), newit);
newit->vTxHashesIdx = vTxHashes.size() - 1; newit->vTxHashesIdx = vTxHashes.size() - 1;
for (auto& vSPK : entry.mapSPK) {
mapUsedSPK[vSPK.first] = MemPool_SPK_State(mapUsedSPK[vSPK.first] | vSPK.second);
}
TRACE3(mempool, added, TRACE3(mempool, added,
entry.GetTx().GetHash().data(), entry.GetTx().GetHash().data(),
entry.GetTxSize(), entry.GetTxSize(),
@ -525,6 +538,14 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
} else } else
vTxHashes.clear(); vTxHashes.clear();
for (auto& vSPK : it->mapSPK) {
if (vSPK.second == mapUsedSPK.find(vSPK.first)->second) {
mapUsedSPK.erase(vSPK.first);
} else {
mapUsedSPK[vSPK.first] = MemPool_SPK_State(mapUsedSPK[vSPK.first] & ~vSPK.second);
}
}
totalTxSize -= it->GetTxSize(); totalTxSize -= it->GetTxSize();
m_total_fee -= it->GetFee(); m_total_fee -= it->GetFee();
cachedInnerUsage -= it->DynamicMemoryUsage(); cachedInnerUsage -= it->DynamicMemoryUsage();

View File

@ -26,6 +26,7 @@
#include <policy/feerate.h> #include <policy/feerate.h>
#include <policy/packages.h> #include <policy/packages.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <script/script.h>
#include <random.h> #include <random.h>
#include <sync.h> #include <sync.h>
#include <util/epochguard.h> #include <util/epochguard.h>
@ -196,6 +197,8 @@ public:
} }
}; };
uint160 ScriptHashkey(const CScript& script);
// Multi_index tag names // Multi_index tag names
struct descendant_score {}; struct descendant_score {};
struct entry_time {}; struct entry_time {};
@ -413,6 +416,9 @@ public:
using Limits = kernel::MemPoolLimits; using Limits = kernel::MemPoolLimits;
uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs);
SPKStates_t mapUsedSPK;
private: private:
typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap; typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap;

View File

@ -109,6 +109,7 @@ static constexpr int PRUNE_LOCK_BUFFER{10};
GlobalMutex g_best_block_mutex; GlobalMutex g_best_block_mutex;
std::condition_variable g_best_block_cv; std::condition_variable g_best_block_cv;
uint256 g_best_block; uint256 g_best_block;
SpkReuseModes SpkReuseMode;
const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locator) const const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locator) const
{ {
@ -748,6 +749,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-same-nonwitness-data-in-mempool"); return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-same-nonwitness-data-in-mempool");
} }
auto spk_reuse_mode = SpkReuseMode;
SPKStates_t mapSPK;
// Check for conflicts with in-memory transactions // Check for conflicts with in-memory transactions
for (const CTxIn &txin : tx.vin) for (const CTxIn &txin : tx.vin)
{ {
@ -778,6 +782,19 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
} }
} }
if (spk_reuse_mode != SRM_ALLOW) {
for (const CTxOut& txout : tx.vout) {
uint160 hashSPK = ScriptHashkey(txout.scriptPubKey);
if (m_pool.mapUsedSPK.find(hashSPK) != m_pool.mapUsedSPK.end()) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-spk-reused");
}
if (mapSPK.find(hashSPK) != mapSPK.end()) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-spk-reused-twinoutputs");
}
mapSPK[hashSPK] = MemPool_SPK_State(mapSPK[hashSPK] | MSS_CREATED);
}
}
m_view.SetBackend(m_viewmempool); m_view.SetBackend(m_viewmempool);
const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip(); const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip();
@ -830,6 +847,27 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return false; // state filled in by CheckTxInputs return false; // state filled in by CheckTxInputs
} }
if (spk_reuse_mode != SRM_ALLOW) {
for (const CTxIn& txin : tx.vin) {
const Coin &coin = m_view.AccessCoin(txin.prevout);
uint160 hashSPK = ScriptHashkey(coin.out.scriptPubKey);
SPKStates_t::iterator mssit = mapSPK.find(hashSPK);
if (mssit != mapSPK.end()) {
if (mssit->second & MSS_CREATED) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-spk-reused-change");
}
}
mssit = m_pool.mapUsedSPK.find(hashSPK);
if (mssit != m_pool.mapUsedSPK.end()) {
if (mssit->second & MSS_SPENT) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-spk-reused-spend");
}
}
mapSPK[hashSPK] = MemPool_SPK_State(mapSPK[hashSPK] | MSS_SPENT);
}
}
if (m_pool.m_require_standard && !AreInputsStandard(tx, m_view, "bad-txns-input-", reason, ignore_rejects)) { if (m_pool.m_require_standard && !AreInputsStandard(tx, m_view, "bad-txns-input-", reason, ignore_rejects)) {
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, reason); return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, reason);
} }
@ -859,6 +897,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(),
fSpendsCoinbase, nSigOpsCost, lock_points.value())); fSpendsCoinbase, nSigOpsCost, lock_points.value()));
ws.m_vsize = entry->GetTxSize(); ws.m_vsize = entry->GetTxSize();
entry->mapSPK = mapSPK;
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)
MaybeRejectDbg(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops", MaybeRejectDbg(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",

View File

@ -93,6 +93,13 @@ extern std::condition_variable g_best_block_cv;
/** Used to notify getblocktemplate RPC of new tips. */ /** Used to notify getblocktemplate RPC of new tips. */
extern uint256 g_best_block; extern uint256 g_best_block;
enum SpkReuseModes {
SRM_ALLOW,
SRM_REJECT,
};
extern SpkReuseModes SpkReuseMode;
/** Documentation for argument 'checklevel'. */ /** Documentation for argument 'checklevel'. */
extern const std::vector<std::string> CHECKLEVEL_DOC; extern const std::vector<std::string> CHECKLEVEL_DOC;