mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-16 05:00:42 +02:00
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:
parent
437badb77d
commit
ada4eca6ec
11
src/init.cpp
11
src/init.cpp
@ -589,6 +589,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
||||
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)",
|
||||
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("-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;
|
||||
|
||||
{
|
||||
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):
|
||||
SetMockTime(args.GetIntArg("-mocktime", 0)); // SetMockTime(0) is a no-op
|
||||
|
||||
|
@ -35,6 +35,15 @@ struct LockPoints {
|
||||
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 {
|
||||
// SFINAE for T where T is either a pointer type (e.g., a txiter) or a reference_wrapper<T>
|
||||
// (e.g. a wrapped CTxMemPoolEntry&)
|
||||
@ -169,6 +178,8 @@ public:
|
||||
|
||||
mutable size_t vTxHashesIdx; //!< Index in mempool's vTxHashes
|
||||
mutable Epoch::Marker m_epoch_marker; //!< epoch when last touched, useful for graph algorithms
|
||||
|
||||
SPKStates_t mapSPK;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_KERNEL_MEMPOOL_ENTRY_H
|
||||
|
@ -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
|
||||
* outputs below the new threshold */
|
||||
static constexpr unsigned int DUST_RELAY_TX_FEE{3000};
|
||||
static const std::string DEFAULT_SPKREUSE{"allow"};
|
||||
/** Default for -minrelaytxfee, minimum relay fee for transactions */
|
||||
static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{1000};
|
||||
/** Default for -limitancestorcount, max number of in-mempool ancestors */
|
||||
|
@ -10,10 +10,12 @@
|
||||
#include <consensus/consensus.h>
|
||||
#include <consensus/tx_verify.h>
|
||||
#include <consensus/validation.h>
|
||||
#include <crypto/ripemd160.h>
|
||||
#include <policy/fees.h>
|
||||
#include <policy/policy.h>
|
||||
#include <policy/settings.h>
|
||||
#include <reverse_iterator.h>
|
||||
#include <script/script.h>
|
||||
#include <util/check.h>
|
||||
#include <util/moneystr.h>
|
||||
#include <util/overflow.h>
|
||||
@ -46,6 +48,13 @@ bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp)
|
||||
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,
|
||||
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);
|
||||
newit->vTxHashesIdx = vTxHashes.size() - 1;
|
||||
|
||||
for (auto& vSPK : entry.mapSPK) {
|
||||
mapUsedSPK[vSPK.first] = MemPool_SPK_State(mapUsedSPK[vSPK.first] | vSPK.second);
|
||||
}
|
||||
|
||||
TRACE3(mempool, added,
|
||||
entry.GetTx().GetHash().data(),
|
||||
entry.GetTxSize(),
|
||||
@ -525,6 +538,14 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
|
||||
} else
|
||||
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();
|
||||
m_total_fee -= it->GetFee();
|
||||
cachedInnerUsage -= it->DynamicMemoryUsage();
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <policy/feerate.h>
|
||||
#include <policy/packages.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/script.h>
|
||||
#include <random.h>
|
||||
#include <sync.h>
|
||||
#include <util/epochguard.h>
|
||||
@ -196,6 +197,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
uint160 ScriptHashkey(const CScript& script);
|
||||
|
||||
// Multi_index tag names
|
||||
struct descendant_score {};
|
||||
struct entry_time {};
|
||||
@ -413,6 +416,9 @@ public:
|
||||
using Limits = kernel::MemPoolLimits;
|
||||
|
||||
uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
|
||||
SPKStates_t mapUsedSPK;
|
||||
|
||||
private:
|
||||
typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap;
|
||||
|
||||
|
@ -109,6 +109,7 @@ static constexpr int PRUNE_LOCK_BUFFER{10};
|
||||
GlobalMutex g_best_block_mutex;
|
||||
std::condition_variable g_best_block_cv;
|
||||
uint256 g_best_block;
|
||||
SpkReuseModes SpkReuseMode;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
auto spk_reuse_mode = SpkReuseMode;
|
||||
SPKStates_t mapSPK;
|
||||
|
||||
// Check for conflicts with in-memory transactions
|
||||
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);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)) {
|
||||
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(),
|
||||
fSpendsCoinbase, nSigOpsCost, lock_points.value()));
|
||||
ws.m_vsize = entry->GetTxSize();
|
||||
entry->mapSPK = mapSPK;
|
||||
|
||||
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)
|
||||
MaybeRejectDbg(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
|
||||
|
@ -93,6 +93,13 @@ extern std::condition_variable g_best_block_cv;
|
||||
/** Used to notify getblocktemplate RPC of new tips. */
|
||||
extern uint256 g_best_block;
|
||||
|
||||
enum SpkReuseModes {
|
||||
SRM_ALLOW,
|
||||
SRM_REJECT,
|
||||
};
|
||||
|
||||
extern SpkReuseModes SpkReuseMode;
|
||||
|
||||
/** Documentation for argument 'checklevel'. */
|
||||
extern const std::vector<std::string> CHECKLEVEL_DOC;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user