diff --git a/README.md b/README.md index c385652d7..d039aeeab 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # What is But? -ButKoin is a decentralized secure cryptocurrency that is easy to mine devoid from 51% attack and it provide easy means of transactions between individuals. It is a Multi-algorithm coin. ButKoin uses a Smartnode collateral and reward system that prevent hyperinflation. +ButKoin is an experimental decentralized secure cryptocurrency that is easy to mine devoid from 51% attack and it provide easy means of transactions between individuals. It is a Multi-algorithm coin. ButKoin uses a Smartnode collateral and reward system that prevent hyperinflation. # Supported Algorithms @@ -16,6 +16,8 @@ ButKoin is a decentralized secure cryptocurrency that is easy to mine devoid fro | Lyra2z330 | GPU | | Sha256 | Asic | | Scrypt | Asic | +| ButkScrypt | Asic | + License ------- diff --git a/src/Makefile.am b/src/Makefile.am index f7c716d47..af576ecb6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -254,6 +254,7 @@ BITCOIN_CORE_H = \ wallet/coincontrol.h \ wallet/crypter.h \ wallet/db.h \ + wallet/fees.h \ wallet/rpcwallet.h \ wallet/wallet.h \ wallet/walletdb.h \ @@ -375,6 +376,7 @@ libbut_wallet_a_SOURCES = \ privatesend/privatesend-util.cpp \ wallet/crypter.cpp \ wallet/db.cpp \ + wallet/fees.cpp \ wallet/rpcdump.cpp \ wallet/rpcwallet.cpp \ wallet/wallet.cpp \ diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 25c289e79..9f2f049ad 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -29,6 +29,7 @@ bench_bench_but_SOURCES = \ bench/ccoins_caching.cpp \ bench/merkle_root.cpp \ bench/mempool_eviction.cpp \ + bench/util_time.cpp \ bench/base58.cpp \ bench/lockedpool.cpp \ bench/poly1305.cpp \ diff --git a/src/bench/util_time.cpp b/src/bench/util_time.cpp new file mode 100644 index 000000000..6900ff3f3 --- /dev/null +++ b/src/bench/util_time.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include + +static void BenchTimeDeprecated(benchmark::State& state) +{ + while (state.KeepRunning()) { + (void)GetTime(); + } +} + +static void BenchTimeMock(benchmark::State& state) +{ + SetMockTime(111); + while (state.KeepRunning()) { + (void)GetTime(); + } + SetMockTime(0); +} + +static void BenchTimeMillis(benchmark::State& state) +{ + while (state.KeepRunning()) { + (void)GetTime(); + } +} + +static void BenchTimeMillisSys(benchmark::State& state) +{ + while (state.KeepRunning()) { + (void)GetTimeMillis(); + } +} + +BENCHMARK(BenchTimeDeprecated/*, 100000000*/); +BENCHMARK(BenchTimeMillis/*, 6000000*/); +BENCHMARK(BenchTimeMillisSys/*, 6000000*/); +BENCHMARK(BenchTimeMock/*, 300000000*/); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 438474e40..4336d9a03 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -461,7 +461,7 @@ class CMainParams : public CChainParams { consensus.v2DiffChangeHeight = 5; consensus.v3DiffChangeHeight = 10; consensus.AlgoChangeHeight = 28; - consensus.nSwitchHeight = 170000; + consensus.nSwitchHeight = 150000; consensus.nLocalTargetAdjustment = 4; //target adjustment per algo consensus.nLocalDifficultyAdjustment = 4; //difficulty adjustment per algo @@ -477,10 +477,10 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 // The best chain should have at least this much work. - consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000000000000000000000fade80"); // 153 + consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000000008d254bb784e3f00"); // 127918 // By default assume that the signatures in ancestors of this block are valid. - consensus.defaultAssumeValid = uint256S("0x0000012eb451fbdf5e8b5c588c0912b3e70e2ea5a37ac88e4ca8c1ad27279509"); // 153 + consensus.defaultAssumeValid = uint256S("0x0000000000000307a7ca1e00c041513a63eab4f62a083aae6d19466478737894"); // 127918 /** * The message start string is designed to be unlikely to occur in normal data. @@ -527,7 +527,10 @@ class CMainParams : public CChainParams { consensus.nFounderPayment = FounderPayment(rewardStructures, 250); consensus.nCollaterals = SmartnodeCollaterals( { - {400000, 6000000 * COIN}, {INT_MAX, 8000000 * COIN} + {150000, 6000000 * COIN}, + {500000, 15000000 * COIN}, + {700000, 20000000 * COIN}, + {INT_MAX, 25000000 * COIN} }, { {5761, 0}, {INT_MAX, 20} @@ -552,7 +555,9 @@ class CMainParams : public CChainParams { miningRequiresPeers = true; nPoolMinParticipants = 3; + nPoolNewMinParticipants = 3; nPoolMaxParticipants = 5; + nPoolNewMaxParticipants = 20; nFulfilledRequestExpireTime = 60*60; // fulfilled requests expire in 1 hour vSporkAddresses = {"XdccBnRd4AMEnfEhVgoLUL6aPjC3kkgJih"}; @@ -561,18 +566,16 @@ class CMainParams : public CChainParams { checkpointData = (CCheckpointData) { { - {0, uint256S("0x001787e5f9c3cd249f84f0142071f6098d9e3b7ec8591ff73543ddc4900c1dc2")}, - {10, uint256S("0x005cab51de3bef6840c7866dbc1d2f69738cff67155a975fa3f1800aa4bbfcf9")}, - {50, uint256S("0x0037fcf4a01c6c445f2ca1201aa67bca5c3a769cd9225199764c5d2b20e10e9f")}, - {100, uint256S("0x00008474034c9e03197c723b565b14cfdbedefbac17b6f25fb0a37214be66062")}, + {0, uint256S("0x001787e5f9c3cd249f84f0142071f6098d9e3b7ec8591ff73543ddc4900c1dc2")}, + {127918, uint256S("0x0000000000000307a7ca1e00c041513a63eab4f62a083aae6d19466478737894")}, } }; chainTxData = ChainTxData{ - // Data from RPC: getchaintxstats 4096 00000000000000000008a89e854d57e5667df88f1cdef6fde2fbca1de5b639ad - /* nTime */ 1642793439, - /* nTxCount */ 465, - /* dTxRate */ 0.003272177305365523, + // Data from RPC: getchaintxstats 127917 00000000000000000008a89e854d57e5667df88f1cdef6fde2fbca1de5b639ad + /* nTime */ 1652019164, + /* nTxCount */ 281951, + /* dTxRate */ 0.03009734730359049, }; } }; @@ -695,6 +698,14 @@ class CTestNetParams : public CChainParams { }; consensus.nFounderPayment = FounderPayment(rewardStructures, 200); + consensus.nCollaterals = SmartnodeCollaterals( + { + {40000, 15000000 * COIN}, {INT_MAX, 20000000 * COIN} + }, + { + {200, 0}, {INT_MAX, 20} + } + ); fDefaultConsistencyChecks = false; fRequireStandard = false; fRequireRoutableExternalIP = true; @@ -703,10 +714,12 @@ class CTestNetParams : public CChainParams { fAllowMultiplePorts = true; nPoolMinParticipants = 3; + nPoolNewMinParticipants = 2; nPoolMaxParticipants = 5; + nPoolNewMaxParticipants = 20; nFulfilledRequestExpireTime = 5*60; // fulfilled requests expire in 5 minutes - vSporkAddresses = {"yVeVxPpzbebak4EZdASJdrnCSPZfAucLNv"}; + vSporkAddresses = {"yVcjXscgATKsvq8JYPKus2opsj6gxEuV7t"}; nMinSporkKeys = 1; fBIP9CheckSmartnodesUpgraded = true; @@ -846,8 +859,10 @@ class CDevNetParams : public CChainParams { fAllowMultipleAddressesFromGroup = true; fAllowMultiplePorts = true; - nPoolMinParticipants = 3; + nPoolMinParticipants = 2; + nPoolNewMinParticipants = 2; nPoolMaxParticipants = 5; + nPoolNewMaxParticipants = 20; nFulfilledRequestExpireTime = 5*60; // fulfilled requests expire in 5 minutes vSporkAddresses = {"yjPtiKh2uwk3bDutTEA2q9mCtXyiZRWn55"}; diff --git a/src/chainparams.h b/src/chainparams.h index d2014d4b5..4ee591d68 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -92,7 +92,9 @@ class CChainParams void UpdateLLMQChainLocks(Consensus::LLMQType llmqType); void UpdateLLMQParams(size_t totalMnCount, int height, bool lowLLMQParams = false); int PoolMinParticipants() const { return nPoolMinParticipants; } + int PoolNewMinParticipants() const { return nPoolNewMinParticipants; } int PoolMaxParticipants() const { return nPoolMaxParticipants; } + int PoolNewMaxParticipants() const { return nPoolNewMaxParticipants; } int FulfilledRequestExpireTime() const { return nFulfilledRequestExpireTime; } const std::vector& SporkAddresses() const { return vSporkAddresses; } int MinSporkKeys() const { return nMinSporkKeys; } @@ -121,7 +123,9 @@ class CChainParams CCheckpointData checkpointData; ChainTxData chainTxData; int nPoolMinParticipants; + int nPoolNewMinParticipants; int nPoolMaxParticipants; + int nPoolNewMaxParticipants; int nFulfilledRequestExpireTime; std::vector vSporkAddresses; int nMinSporkKeys; diff --git a/src/evo/mnauth.cpp b/src/evo/mnauth.cpp index 546c9e8f4..b7eecc62c 100644 --- a/src/evo/mnauth.cpp +++ b/src/evo/mnauth.cpp @@ -8,6 +8,7 @@ #include "smartnode/activesmartnode.h" #include "evo/deterministicmns.h" #include "smartnode/smartnode-sync.h" +#include #include "net.h" #include "net_processing.h" #include "netmessagemaker.h" @@ -100,6 +101,16 @@ void CMNAuth::ProcessMessage(CNode* pnode, const std::string& strCommand, CDataS return; } + if (!pnode->fInbound) { + mmetaman.GetMetaInfo(mnauth.proRegTxHash)->SetLastOutboundSuccess(GetAdjustedTime()); + if (pnode->fSmartnodeProbe) { + LogPrint(BCLog::NET, "CMNAuth::ProcessMessage -- Smartnode probe successful for %s, disconnecting. peer=%d\n", + mnauth.proRegTxHash.ToString(), pnode->GetId()); + pnode->fDisconnect = true; + return; + } + } + connman.ForEachNode([&](CNode* pnode2) { if (pnode2->verifiedProRegTxHash == mnauth.proRegTxHash) { LogPrint(BCLog::NET, "CMNAuth::ProcessMessage -- Smartnode %s has already verified as peer %d, dropping new connection. peer=%d\n", diff --git a/src/governance/governance-vote.cpp b/src/governance/governance-vote.cpp index 5d40e73fb..e4a15e047 100644 --- a/src/governance/governance-vote.cpp +++ b/src/governance/governance-vote.cpp @@ -165,7 +165,8 @@ bool CGovernanceVote::Sign(const CKey& key, const CKeyID& keyID) { std::string strError; - if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { + // Harden Spork6 so that it is active on testnet and no other networks + if (Params().NetworkIDString() == CBaseChainParams::TESTNET) { uint256 hash = GetSignatureHash(); if (!CHashSigner::SignHash(hash, key, vchSig)) { @@ -199,21 +200,13 @@ bool CGovernanceVote::CheckSignature(const CKeyID& keyID) const { std::string strError; - if (sporkManager.IsSporkActive(SPORK_6_NEW_SIGS)) { + // Harden Spork6 so that it is active on testnet and no other networks + if (Params().NetworkIDString() == CBaseChainParams::TESTNET) { uint256 hash = GetSignatureHash(); if (!CHashSigner::VerifyHash(hash, keyID, vchSig, strError)) { - // could be a signature in old format - std::string strMessage = smartnodeOutpoint.ToStringShort() + "|" + nParentHash.ToString() + "|" + - std::to_string(nVoteSignal) + "|" + - std::to_string(nVoteOutcome) + "|" + - std::to_string(nTime); - - if (!CMessageSigner::VerifyMessage(keyID, vchSig, strMessage, strError)) { - // nope, not in old format either - LogPrint(BCLog::GOBJECT, "CGovernanceVote::IsValid -- VerifyMessage() failed, error: %s\n", strError); - return false; - } + LogPrint(BCLog::GOBJECT, "CGovernanceVote::IsValid -- VerifyHash() failed, error: %s\n", strError); + return false; } } else { std::string strMessage = smartnodeOutpoint.ToStringShort() + "|" + nParentHash.ToString() + "|" + diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index dcdb5a654..e79652156 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -136,7 +136,7 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, const std::string& strComm { LOCK(cs_main); - connman.RemoveAskFor(nHash); + EraseObjectRequest(pfrom->GetId(), CInv(MSG_GOVERNANCE_OBJECT, nHash)); } if (pfrom->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) { @@ -211,7 +211,7 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, const std::string& strComm { LOCK(cs_main); - connman.RemoveAskFor(nHash); + EraseObjectRequest(pfrom->GetId(), CInv(MSG_GOVERNANCE_OBJECT_VOTE, nHash)); } if (pfrom->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) { @@ -1034,8 +1034,8 @@ int CGovernanceManager::RequestGovernanceObjectVotes(const std::vector& // stop early to prevent setAskFor overflow { LOCK(cs_main); - size_t nProjectedSize = pnode->setAskFor.size() + nProjectedVotes; - if (nProjectedSize > SETASKFOR_MAX_SZ / 2) continue; + size_t nProjectedSize = GetRequestedObjectCount(pnode->GetId()) + nProjectedVotes; + if (nProjectedSize > MAX_INV_SZ) continue; // to early to ask the same node if (mapAskedRecently[nHashGovobj].count(pnode->addr)) continue; } diff --git a/src/init.cpp b/src/init.cpp index 6e6b5688a..37c2be7e5 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -621,7 +621,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-privatesendsessions=", strprintf(_("Use N separate smartnodes in parallel to mix funds (%u-%u, default: %u)"), MIN_PRIVATESEND_SESSIONS, MAX_PRIVATESEND_SESSIONS, DEFAULT_PRIVATESEND_SESSIONS)); strUsage += HelpMessageOpt("-privatesendrounds=", strprintf(_("Use N separate smartnodes for each denominated input to mix funds (%u-%u, default: %u)"), MIN_PRIVATESEND_ROUNDS, MAX_PRIVATESEND_ROUNDS, DEFAULT_PRIVATESEND_ROUNDS)); strUsage += HelpMessageOpt("-privatesendamount=", strprintf(_("Target PrivateSend balance (%u-%u, default: %u)"), MIN_PRIVATESEND_AMOUNT, MAX_PRIVATESEND_AMOUNT, DEFAULT_PRIVATESEND_AMOUNT)); - strUsage += HelpMessageOpt("-privatesenddenoms=", strprintf(_("Create up to N inputs of each denominated amount (%u-%u, default: %u)"), MIN_PRIVATESEND_DENOMS, MAX_PRIVATESEND_DENOMS, DEFAULT_PRIVATESEND_DENOMS)); + strUsage += HelpMessageOpt("-privatesenddenoms=", strprintf(_("Create up to N inputs of each denominated amount (%u-%u, default: %u)"), MIN_PRIVATESEND_DENOMS_GOAL, MAX_PRIVATESEND_DENOMS_GOAL, DEFAULT_PRIVATESEND_DENOMS_GOAL)); #endif // ENABLE_WALLET strUsage += HelpMessageGroup(_("InstantSend options:")); @@ -2101,14 +2101,14 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) privateSendClient.nPrivateSendSessions = std::min(std::max((int)gArgs.GetArg("-privatesendsessions", DEFAULT_PRIVATESEND_SESSIONS), MIN_PRIVATESEND_SESSIONS), MAX_PRIVATESEND_SESSIONS); privateSendClient.nPrivateSendRounds = std::min(std::max((int)gArgs.GetArg("-privatesendrounds", DEFAULT_PRIVATESEND_ROUNDS), MIN_PRIVATESEND_ROUNDS), nMaxRounds); privateSendClient.nPrivateSendAmount = std::min(std::max((int)gArgs.GetArg("-privatesendamount", DEFAULT_PRIVATESEND_AMOUNT), MIN_PRIVATESEND_AMOUNT), MAX_PRIVATESEND_AMOUNT); - privateSendClient.nPrivateSendDenoms = std::min(std::max((int)gArgs.GetArg("-privatesenddenoms", DEFAULT_PRIVATESEND_DENOMS), MIN_PRIVATESEND_DENOMS), MAX_PRIVATESEND_DENOMS); + privateSendClient.nPrivateSendDenomsGoal = std::min(std::max((int)gArgs.GetArg("-privatesenddenoms", DEFAULT_PRIVATESEND_DENOMS_GOAL), MIN_PRIVATESEND_DENOMS_GOAL), MAX_PRIVATESEND_DENOMS_GOAL); if (privateSendClient.fEnablePrivateSend) { LogPrintf("PrivateSend: autostart=%d, multisession=%d, " "sessions=%d, rounds=%d, amount=%d, denoms=%d\n", privateSendClient.fPrivateSendRunning, privateSendClient.fPrivateSendMultiSession, privateSendClient.nPrivateSendSessions, privateSendClient.nPrivateSendRounds, - privateSendClient.nPrivateSendAmount, privateSendClient.nPrivateSendDenoms); + privateSendClient.nPrivateSendAmount, privateSendClient.nPrivateSendDenomsGoal); } #endif // ENABLE_WALLET diff --git a/src/limitedmap.h b/src/limitedmap.h index 23fee1802..c33061186 100644 --- a/src/limitedmap.h +++ b/src/limitedmap.h @@ -27,13 +27,11 @@ class unordered_limitedmap protected: std::unordered_map map; typedef typename std::unordered_map::iterator iterator; - std::unordered_multimap rmap; - typedef typename std::unordered_multimap::iterator rmap_iterator; size_type nMaxSize; size_type nPruneAfterSize; public: - unordered_limitedmap(size_type nMaxSizeIn, size_type nPruneAfterSizeIn = 0) + explicit unordered_limitedmap(size_type nMaxSizeIn, size_type nPruneAfterSizeIn = 0) { assert(nMaxSizeIn > 0); nMaxSize = nMaxSizeIn; @@ -53,25 +51,20 @@ class unordered_limitedmap void insert(const value_type& x) { std::pair ret = map.insert(x); - if (ret.second) { + if (ret.second) prune(); - rmap.insert(make_pair(x.second, ret.first)); - } + } + void insert_or_update(const value_type& x) + { + std::pair ret = map.insert(x); + if (ret.second) + prune(); + else + ret.first->second = x.second; } void erase(const key_type& k) { - iterator itTarget = map.find(k); - if (itTarget == map.end()) - return; - std::pair itPair = rmap.equal_range(itTarget->second); - for (rmap_iterator it = itPair.first; it != itPair.second; ++it) - if (it->second == itTarget) { - rmap.erase(it); - map.erase(itTarget); - return; - } - // Shouldn't ever get here - assert(0); + map.erase(k); } void update(const_iterator itIn, const mapped_type& v) { @@ -79,19 +72,9 @@ class unordered_limitedmap // since it is a constant time operation in C++11. For more details, see // https://stackoverflow.com/questions/765148/how-to-remove-constness-of-const-iterator iterator itTarget = map.erase(itIn, itIn); - if (itTarget == map.end()) return; - std::pair itPair = rmap.equal_range(itTarget->second); - for (rmap_iterator it = itPair.first; it != itPair.second; ++it) - if (it->second == itTarget) { - rmap.erase(it); - itTarget->second = v; - rmap.insert(make_pair(v, itTarget)); - return; - } - // Shouldn't ever get here - assert(0); + itTarget->second = v; } size_type max_size() const { return nMaxSize; } size_type max_size(size_type nMaxSizeIn, size_type nPruneAfterSizeIn = 0) @@ -113,13 +96,13 @@ class unordered_limitedmap return; } - std::vector sortedIterators; + std::vector sortedIterators; sortedIterators.reserve(map.size()); - for (auto it = rmap.begin(); it != rmap.end(); ++it) { + for (auto it = map.begin(); it != map.end(); ++it) { sortedIterators.emplace_back(it); } - std::sort(sortedIterators.begin(), sortedIterators.end(), [](const rmap_iterator& it1, const rmap_iterator& it2) { - return it1->first < it2->first; + std::sort(sortedIterators.begin(), sortedIterators.end(), [](const iterator& it1, const iterator& it2) { + return it1->second < it2->second; }); size_type tooMuch = map.size() - nMaxSize; @@ -127,8 +110,7 @@ class unordered_limitedmap sortedIterators.resize(tooMuch); for (auto& it : sortedIterators) { - map.erase(it->second); - rmap.erase(it); + map.erase(it); } } }; diff --git a/src/llmq/quorums_blockprocessor.cpp b/src/llmq/quorums_blockprocessor.cpp index d0ee104dc..3bddb4951 100644 --- a/src/llmq/quorums_blockprocessor.cpp +++ b/src/llmq/quorums_blockprocessor.cpp @@ -37,7 +37,7 @@ void CQuorumBlockProcessor::ProcessMessage(CNode* pfrom, const std::string& strC auto hash = ::SerializeHash(qc); { LOCK(cs_main); - connman.RemoveAskFor(hash); + EraseObjectRequest(pfrom->GetId(), CInv(MSG_QUORUM_FINAL_COMMITMENT, hash)); } if (qc.IsNull()) { diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index d77ce931d..9a18905a3 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -104,7 +104,7 @@ void CChainLocksHandler::ProcessNewChainLock(NodeId from, const llmq::CChainLock { { LOCK(cs_main); - g_connman->RemoveAskFor(hash); + EraseObjectRequest(from, CInv(MSG_CLSIG, hash)); } { diff --git a/src/llmq/quorums_dkgsessionhandler.cpp b/src/llmq/quorums_dkgsessionhandler.cpp index afa05625b..2c9453ec6 100644 --- a/src/llmq/quorums_dkgsessionhandler.cpp +++ b/src/llmq/quorums_dkgsessionhandler.cpp @@ -18,8 +18,9 @@ namespace llmq { -CDKGPendingMessages::CDKGPendingMessages(size_t _maxMessagesPerNode) : - maxMessagesPerNode(_maxMessagesPerNode) +CDKGPendingMessages::CDKGPendingMessages(size_t _maxMessagesPerNode, int _invType) : + maxMessagesPerNode(_maxMessagesPerNode), + invType(_invType) { } @@ -50,7 +51,7 @@ void CDKGPendingMessages::PushPendingMessage(NodeId from, CDataStream& vRecv) return; } - g_connman->RemoveAskFor(hash); + EraseObjectRequest(from, CInv(invType, hash)); pendingMessages.emplace_back(std::make_pair(from, std::move(pm))); } @@ -90,10 +91,10 @@ CDKGSessionHandler::CDKGSessionHandler(const Consensus::LLMQParams& _params, ctp blsWorker(_blsWorker), dkgManager(_dkgManager), curSession(std::make_shared(_params, _blsWorker, _dkgManager)), - pendingContributions((size_t)_params.size * 2), // we allow size*2 messages as we need to make sure we see bad behavior (double messages) - pendingComplaints((size_t)_params.size * 2), - pendingJustifications((size_t)_params.size * 2), - pendingPrematureCommitments((size_t)_params.size * 2) + pendingContributions((size_t)_params.size * 2, MSG_QUORUM_CONTRIB), // we allow size*2 messages as we need to make sure we see bad behavior (double messages) + pendingComplaints((size_t)_params.size * 2, MSG_QUORUM_COMPLAINT), + pendingJustifications((size_t)_params.size * 2, MSG_QUORUM_JUSTIFICATION), + pendingPrematureCommitments((size_t)_params.size * 2, MSG_QUORUM_PREMATURE_COMMITMENT) { phaseHandlerThread = std::thread([this] { RenameThread(strprintf("but-q-phase-%d", (uint8_t)params.type).c_str()); @@ -389,7 +390,7 @@ std::set BatchVerifyMessageSigs(CDKGSession& session, const std::vector< return ret; } -template +template bool ProcessPendingMessageBatch(CDKGSession& session, CDKGPendingMessages& pendingMessages, size_t maxCount) { auto msgs = pendingMessages.PopAndDeserializeMessages(maxCount); @@ -416,7 +417,7 @@ bool ProcessPendingMessageBatch(CDKGSession& session, CDKGPendingMessages& pendi auto hash = ::SerializeHash(msg); { LOCK(cs_main); - g_connman->RemoveAskFor(hash); + EraseObjectRequest(p.first, CInv(MessageType, hash)); } bool ban = false; @@ -536,7 +537,7 @@ void CDKGSessionHandler::HandleDKGRound() curSession->Contribute(pendingContributions); }; auto fContributeWait = [this] { - return ProcessPendingMessageBatch(*curSession, pendingContributions, 8); + return ProcessPendingMessageBatch(*curSession, pendingContributions, 8); }; HandlePhase(QuorumPhase_Contribute, QuorumPhase_Complain, curQuorumHash, 0.05, fContributeStart, fContributeWait); @@ -545,7 +546,7 @@ void CDKGSessionHandler::HandleDKGRound() curSession->VerifyAndComplain(pendingComplaints); }; auto fComplainWait = [this] { - return ProcessPendingMessageBatch(*curSession, pendingComplaints, 8); + return ProcessPendingMessageBatch(*curSession, pendingComplaints, 8); }; HandlePhase(QuorumPhase_Complain, QuorumPhase_Justify, curQuorumHash, 0.05, fComplainStart, fComplainWait); @@ -554,7 +555,7 @@ void CDKGSessionHandler::HandleDKGRound() curSession->VerifyAndJustify(pendingJustifications); }; auto fJustifyWait = [this] { - return ProcessPendingMessageBatch(*curSession, pendingJustifications, 8); + return ProcessPendingMessageBatch(*curSession, pendingJustifications, 8); }; HandlePhase(QuorumPhase_Justify, QuorumPhase_Commit, curQuorumHash, 0.05, fJustifyStart, fJustifyWait); @@ -563,7 +564,7 @@ void CDKGSessionHandler::HandleDKGRound() curSession->VerifyAndCommit(pendingPrematureCommitments); }; auto fCommitWait = [this] { - return ProcessPendingMessageBatch(*curSession, pendingPrematureCommitments, 8); + return ProcessPendingMessageBatch(*curSession, pendingPrematureCommitments, 8); }; HandlePhase(QuorumPhase_Commit, QuorumPhase_Finalize, curQuorumHash, 0.1, fCommitStart, fCommitWait); diff --git a/src/llmq/quorums_dkgsessionhandler.h b/src/llmq/quorums_dkgsessionhandler.h index 3f8f5a5cf..4c94a9677 100644 --- a/src/llmq/quorums_dkgsessionhandler.h +++ b/src/llmq/quorums_dkgsessionhandler.h @@ -41,13 +41,14 @@ class CDKGPendingMessages private: mutable CCriticalSection cs; + int invType; size_t maxMessagesPerNode; std::list pendingMessages; std::map messagesPerNode; std::set seenMessages; public: - CDKGPendingMessages(size_t _maxMessagesPerNode); + explicit CDKGPendingMessages(size_t _maxMessagesPerNode, int _invType); void PushPendingMessage(NodeId from, CDataStream& vRecv); std::list PopPendingMessages(size_t maxCount); diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index b4e2cf0b3..72449d303 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -874,7 +874,7 @@ void CInstantSendManager::ProcessInstantSendLock(NodeId from, const uint256& has { { LOCK(cs_main); - g_connman->RemoveAskFor(hash); + EraseObjectRequest(from, CInv(MSG_ISLOCK, hash)); } CTransactionRef tx; @@ -1339,7 +1339,7 @@ void CInstantSendManager::AskNodesForLockedTx(const uint256& txid) txid.ToString(), pnode->GetId()); CInv inv(MSG_TX, txid); - pnode->AskFor(inv); + RequestObject(pnode->GetId(), inv, GetTime(), true); } } for (CNode* pnode : nodesToAskFor) { diff --git a/src/llmq/quorums_signing.cpp b/src/llmq/quorums_signing.cpp index b2755186b..a3ae5384a 100644 --- a/src/llmq/quorums_signing.cpp +++ b/src/llmq/quorums_signing.cpp @@ -670,7 +670,7 @@ void CSigningManager::ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& re { LOCK(cs_main); - connman.RemoveAskFor(recoveredSig.GetHash()); + EraseObjectRequest(nodeId, CInv(MSG_QUORUM_RECOVERED_SIG, recoveredSig.GetHash())); } if (db.HasRecoveredSigForHash(recoveredSig.GetHash())) { diff --git a/src/net.cpp b/src/net.cpp index 0aaeff4a9..4dccb9183 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -43,6 +44,7 @@ #include #endif +#include #include @@ -98,7 +100,6 @@ std::map mapLocalHost; static bool vfLimited[NET_MAX] = {}; std::string strSubVersion; -unordered_limitedmap mapAlreadyAskedFor(MAX_INV_SZ, MAX_INV_SZ * 2); void CConnman::AddOneShot(const std::string& strDest) { @@ -1715,7 +1716,7 @@ void CConnman::ThreadDNSAddressSeed() LOCK(cs_vNodes); int nRelevant = 0; for (auto pnode : vNodes) { - nRelevant += pnode->fSuccessfullyConnected && !pnode->fFeeler && !pnode->fOneShot && !pnode->m_manual_connection && !pnode->fInbound; + nRelevant += pnode->fSuccessfullyConnected && !pnode->fFeeler && !pnode->fOneShot && !pnode->m_manual_connection && !pnode->fInbound && !pnode->fSmartnodeProbe; } if (nRelevant >= 2) { LogPrintf("P2P peers available. Skipped DNS seeding.\n"); @@ -1799,6 +1800,7 @@ void CConnman::ProcessOneShot() strDest = vOneShots.front(); vOneShots.pop_front(); } + CAddress addr; CSemaphoreGrant grant(*semOutbound, true); if (grant) { @@ -1834,7 +1836,7 @@ int CConnman::GetExtraOutboundCount() if (pnode->fSmartnode) { continue; } - if (!pnode->fInbound && !pnode->m_manual_connection && !pnode->fFeeler && !pnode->fDisconnect && !pnode->fOneShot && pnode->fSuccessfullyConnected) { + if (!pnode->fInbound && !pnode->m_manual_connection && !pnode->fFeeler && !pnode->fDisconnect && !pnode->fOneShot && pnode->fSuccessfullyConnected && !pnode->fSmartnodeProbe) { ++nOutbound; } } @@ -2111,12 +2113,13 @@ void CConnman::ThreadOpenAddedConnections() void CConnman::ThreadOpenSmartnodeConnections() { - // Connecting to specific addresses, no smartnode connections available + // Connecting to specific addresses, no Smartnode connections available if (gArgs.IsArgSet("-connect") && gArgs.GetArgs("-connect").size() > 0) return; - bool didConnect = false; + auto& chainParams = Params(); + bool didConnect = false; while (!interruptNet) { int sleepTime = 1000; @@ -2125,19 +2128,23 @@ void CConnman::ThreadOpenSmartnodeConnections() } if (!interruptNet.sleep_for(std::chrono::milliseconds(sleepTime))) return; + didConnect = false; + if (!fNetworkActive || !smartnodeSync.IsBlockchainSynced()) + continue; + std::set connectedNodes; - std::set connectedProRegTxHashes; + std::map connectedProRegTxHashes; ForEachNode([&](const CNode* pnode) { connectedNodes.emplace(pnode->addr); if (!pnode->verifiedProRegTxHash.IsNull()) { - connectedProRegTxHashes.emplace(pnode->verifiedProRegTxHash); + connectedProRegTxHashes.emplace(pnode->verifiedProRegTxHash, pnode->fInbound); } }); + auto mnList = deterministicMNManager->GetListAtChainTip(); - CSemaphoreGrant grant(*semSmartnodeOutbound); if (interruptNet) return; @@ -2145,61 +2152,108 @@ void CConnman::ThreadOpenSmartnodeConnections() // NOTE: Process only one pending smartnode at a time - CService addr; + CDeterministicMNCPtr connectToDmn; + bool isProbe = false; { // don't hold lock while calling OpenSmartnodeConnection as cs_main is locked deep inside LOCK2(cs_vNodes, cs_vPendingSmartnodes); - std::vector pending; - for (const auto& group : smartnodeQuorumNodes) { - for (const auto& proRegTxHash : group.second) { - auto dmn = mnList.GetMN(proRegTxHash); - if (!dmn) { - continue; - } - const auto& addr2 = dmn->pdmnState->addr; - if (!connectedNodes.count(addr2) && !IsSmartnodeOrDisconnectRequested(addr2) && !connectedProRegTxHashes.count(proRegTxHash)) { + if (!vPendingSmartnodes.empty()) { + auto dmn = mnList.GetValidMN(vPendingSmartnodes.front()); + vPendingSmartnodes.erase(vPendingSmartnodes.begin()); + if (dmn && !connectedNodes.count(dmn->pdmnState->addr) && !IsSmartnodeOrDisconnectRequested(dmn->pdmnState->addr)) { + connectToDmn = dmn; + LogPrint(BCLog::NET, "CConnman::%s -- opening pending smartnode connection to %s, service=%s\n", __func__, dmn->proTxHash.ToString(), dmn->pdmnState->addr.ToString(false)); + } + } + + if (!connectToDmn) { + std::vector pending; + for (const auto& group : smartnodeQuorumNodes) { + for (const auto& proRegTxHash : group.second) { + auto dmn = mnList.GetMN(proRegTxHash); + if (!dmn) { + continue; + } + const auto& addr2 = dmn->pdmnState->addr; + if (!connectedNodes.count(addr2) && !IsSmartnodeOrDisconnectRequested(addr2) && !connectedProRegTxHashes.count(proRegTxHash)) { auto addrInfo = addrman.GetAddressInfo(addr2); - // back off trying connecting to an address if we already tried recently + // back off trying connecting to an address if we already tried recently if (addrInfo.IsValid() && nANow - addrInfo.nLastTry < 60) { - continue; + continue; + } + pending.emplace_back(dmn); } - pending.emplace_back(addr2); } } - } - if (!vPendingSmartnodes.empty()) { - auto addr2 = vPendingSmartnodes.front(); - vPendingSmartnodes.erase(vPendingSmartnodes.begin()); - if (!connectedNodes.count(addr2) && !IsSmartnodeOrDisconnectRequested(addr2)) { - pending.emplace_back(addr2); + if (!pending.empty()) { + connectToDmn = pending[GetRandInt(pending.size())]; + LogPrint(BCLog::NET, "CConnman::%s -- opening quorum connection to %s, service=%s\n", __func__, connectToDmn->proTxHash.ToString(), connectToDmn->pdmnState->addr.ToString(false)); } } - if (pending.empty()) { - // nothing to do, keep waiting - continue; + if (!connectToDmn) { + std::vector pending; + for (auto it = smartnodePendingProbes.begin(); it != smartnodePendingProbes.end(); ) { + auto dmn = mnList.GetMN(*it); + if (!dmn) { + it = smartnodePendingProbes.erase(it); + continue; + } + bool connectedAndOutbound = connectedProRegTxHashes.count(dmn->proTxHash) && !connectedProRegTxHashes[dmn->proTxHash]; + if (connectedAndOutbound) { + // we already have an outbound connection to this MN so there is no theed to probe it again + mmetaman.GetMetaInfo(dmn->proTxHash)->SetLastOutboundSuccess(nANow); + it = smartnodePendingProbes.erase(it); + continue; + } + + ++it; + + int64_t lastAttempt = mmetaman.GetMetaInfo(dmn->proTxHash)->GetLastOutboundAttempt(); + // back off trying connecting to an address if we already tried recently + if (nANow - lastAttempt < 60) { + continue; + } + pending.emplace_back(dmn); + } + + if (!pending.empty()) { + connectToDmn = pending[GetRandInt(pending.size())]; + smartnodePendingProbes.erase(connectToDmn->proTxHash); + isProbe = true; + + LogPrint(BCLog::NET, "CConnman::%s -- probing smartnode %s, service=%s\n", __func__, connectToDmn->proTxHash.ToString(), connectToDmn->pdmnState->addr.ToString(false)); + } } + } - std::random_shuffle(pending.begin(), pending.end()); - addr = pending.front(); + if (!connectToDmn) { + continue; } + didConnect = true; - - OpenSmartnodeConnection(CAddress(addr, NODE_NETWORK)); + + mmetaman.GetMetaInfo(connectToDmn->proTxHash)->SetLastOutboundAttempt(nANow); + + OpenSmartnodeConnection(CAddress(connectToDmn->pdmnState->addr, NODE_NETWORK), isProbe); // should be in the list now if connection was opened - ForNode(addr, CConnman::AllNodes, [&](CNode* pnode) { + bool connected = ForNode(connectToDmn->pdmnState->addr, CConnman::AllNodes, [&](CNode* pnode) { if (pnode->fDisconnect) { return false; } - grant.MoveTo(pnode->grantSmartnodeOutbound); return true; }); + if (!connected) { + LogPrint(BCLog::NET, "CConnman::%s -- connection failed for smartnode %s, service=%s\n", __func__, connectToDmn->proTxHash.ToString(), connectToDmn->pdmnState->addr.ToString(false)); + // reset last outbound success + mmetaman.GetMetaInfo(connectToDmn->proTxHash)->SetLastOutboundSuccess(0); + } } } // if successful, this moves the passed grant to the constructed node -bool CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection, bool fConnectToSmartnode) +bool CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection, bool fConnectToSmartnode, bool fSmartnodeProbe) { // // Initiate outbound network connection @@ -2239,6 +2293,8 @@ bool CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai pnode->m_manual_connection = true; if (fConnectToSmartnode) pnode->fSmartnode = true; + if (fSmartnodeProbe) + pnode->fSmartnodeProbe = true; m_msgproc->InitializeNode(pnode); { @@ -2249,8 +2305,8 @@ bool CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai return true; } -bool CConnman::OpenSmartnodeConnection(const CAddress &addrConnect) { - return OpenNetworkConnection(addrConnect, false, nullptr, nullptr, false, false, false, true); +void CConnman::OpenSmartnodeConnection(const CAddress &addrConnect, bool probe) { + OpenNetworkConnection(addrConnect, false, nullptr, nullptr, false, false, false, true, probe); } void CConnman::ThreadMessageHandler() @@ -2823,15 +2879,14 @@ bool CConnman::RemoveAddedNode(const std::string& strNode) } -bool CConnman::AddPendingSmartnode(const CService& service) +bool CConnman::AddPendingSmartnode(const uint256& proTxHash) { LOCK(cs_vPendingSmartnodes); - for(std::vector::const_iterator it = vPendingSmartnodes.begin(); it != vPendingSmartnodes.end(); ++it) { - if (service == *it) - return false; + if (std::find(vPendingSmartnodes.begin(), vPendingSmartnodes.end(), proTxHash) != vPendingSmartnodes.end()) { + return false; } - vPendingSmartnodes.push_back(service); + vPendingSmartnodes.push_back(proTxHash); return true; } @@ -3026,16 +3081,6 @@ void CConnman::RelayInvFiltered(CInv &inv, const uint256& relatedTxHash, const i } } -void CConnman::RemoveAskFor(const uint256& hash) -{ - mapAlreadyAskedFor.erase(hash); - - LOCK(cs_vNodes); - for (const auto& pnode : vNodes) { - pnode->RemoveAskFor(hash); - } -} - void CConnman::RecordBytesRecv(uint64_t bytes) { LOCK(cs_totalBytesRecv); @@ -3219,6 +3264,7 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn nPingUsecTime = 0; fPingQueued = false; fSmartnode = false; + fSmartnodeProbe = false; nMinPingUsecTime = std::numeric_limits::max(); fPauseRecv = false; fPauseSend = false; @@ -3243,62 +3289,6 @@ CNode::~CNode() delete pfilter; } -void CNode::AskFor(const CInv& inv, int64_t doubleRequestDelay) -{ - if (queueAskFor.size() > MAPASKFOR_MAX_SZ || setAskFor.size() > SETASKFOR_MAX_SZ) { - int64_t nNow = GetTime(); - if(nNow - nLastWarningTime > WARNING_INTERVAL) { - LogPrintf("CNode::AskFor -- WARNING: inventory message dropped: vecAskFor.size = %d, setAskFor.size = %d, MAPASKFOR_MAX_SZ = %d, SETASKFOR_MAX_SZ = %d, nSkipped = %d, peer=%d\n", - queueAskFor.size(), setAskFor.size(), MAPASKFOR_MAX_SZ, SETASKFOR_MAX_SZ, nNumWarningsSkipped, id); - nLastWarningTime = nNow; - nNumWarningsSkipped = 0; - } - else { - ++nNumWarningsSkipped; - } - return; - } - // a peer may not have multiple non-responded queue positions for a single inv item - if (!setAskFor.emplace(inv.hash).second) - return; - - // We're using queueAskFor as a priority queue, - // the key is the earliest time the request can be sent - int64_t nRequestTime; - auto it = mapAlreadyAskedFor.find(inv.hash); - if (it != mapAlreadyAskedFor.end()) - nRequestTime = it->second; - else - nRequestTime = 0; - - LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000), id); - - // Make sure not to reuse time indexes to keep things in the same order - int64_t nNow = GetTimeMicros() - 1000000; - static int64_t nLastTime; - ++nLastTime; - nNow = std::max(nNow, nLastTime); - nLastTime = nNow; - - // Each retry is 2 minutes after the last - nRequestTime = std::max(nRequestTime + doubleRequestDelay, nNow); - if (it != mapAlreadyAskedFor.end()) - mapAlreadyAskedFor.update(it, nRequestTime); - else - mapAlreadyAskedFor.insert(std::make_pair(inv.hash, nRequestTime)); - - queueAskFor.emplace(nRequestTime, inv); - setAskForInQueue.emplace(inv.hash); -} - -void CNode::RemoveAskFor(const uint256& hash) -{ - setAskFor.erase(hash); - // we don't really remove it from queueAskFor as it would be too expensive to rebuild the heap - // instead, we're ignoring the entry later as it won't be found in setAskForInQueue anymore - setAskForInQueue.erase(hash); -} - bool CConnman::NodeFullyConnected(const CNode* pnode) { return pnode && pnode->fSuccessfullyConnected && !pnode->fDisconnect; diff --git a/src/net.h b/src/net.h index 119ade12e..66eb48d70 100644 --- a/src/net.h +++ b/src/net.h @@ -92,10 +92,6 @@ static const bool DEFAULT_UPNP = USE_UPNP; #else static const bool DEFAULT_UPNP = false; #endif -/** The maximum number of entries in mapAskFor */ -static const size_t MAPASKFOR_MAX_SZ = MAX_INV_SZ; -/** The maximum number of entries in setAskFor (larger due to getdata latency)*/ -static const size_t SETASKFOR_MAX_SZ = 2 * MAX_INV_SZ; /** The maximum number of peer connections to maintain. * Smartnodes are forced to accept at least this many connections */ @@ -199,8 +195,8 @@ class CConnman void Interrupt(); bool GetNetworkActive() const { return fNetworkActive; }; void SetNetworkActive(bool active); - bool OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false, bool fConnectToSmartnode = false); - bool OpenSmartnodeConnection(const CAddress& addrConnect); + bool OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false, bool fConnectToSmartnode = false, bool fSmartnodeProbe = false); + void OpenSmartnodeConnection(const CAddress& addrConnect, bool probe = false); bool CheckIncomingNonce(uint64_t nonce); struct CFullyConnectedOnly { @@ -352,7 +348,6 @@ class CConnman void RelayInvFiltered(CInv &inv, const CTransaction &relatedTx, const int minProtoVersion = MIN_PEER_PROTO_VERSION); // This overload will not update node filters, so use it only for the cases when other messages will update related transaction data in filters void RelayInvFiltered(CInv &inv, const uint256 &relatedTxHash, const int minProtoVersion = MIN_PEER_PROTO_VERSION); - void RemoveAskFor(const uint256& hash); // Addrman functions size_t GetAddressCount() const; @@ -402,7 +397,6 @@ class CConnman bool RemoveAddedNode(const std::string& node); std::vector GetAddedNodeInfo(); - bool AddPendingSmartnode(const CService& addr); bool AddPendingSmartnode(const uint256& proTxHash); bool AddSmartnodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::set& proTxHashes); @@ -543,9 +537,10 @@ class CConnman CCriticalSection cs_vOneShots; std::vector vAddedNodes; CCriticalSection cs_vAddedNodes; - std::vector vPendingSmartnodes; + std::vector vPendingSmartnodes; std::map, std::set> smartnodeQuorumNodes; // protected by cs_vPendingSmartnodes mutable CCriticalSection cs_vPendingSmartnodes; + std::set smartnodePendingProbes; std::vector vNodes; std::list vNodesDisconnected; mutable CCriticalSection cs_vNodes; @@ -661,8 +656,6 @@ extern bool fDiscover; extern bool fListen; extern bool fRelayTxes; -extern unordered_limitedmap mapAlreadyAskedFor; - /** Subversion as sent to the P2P network in `version` messages */ extern std::string strSubVersion; @@ -817,6 +810,8 @@ class CNode bool fSentAddr; // If 'true' this node will be disconnected on CSmartnodeMan::ProcessSmartnodeConnections() bool fSmartnode; + // If 'true' this node will be disconnected after MNAUTH + bool fSmartnodeProbe; CSemaphoreGrant grantOutbound; CSemaphoreGrant grantSmartnodeOutbound; CCriticalSection cs_filter; @@ -856,9 +851,6 @@ class CNode // List of non-tx/non-block inventory items std::vector vInventoryOtherToSend; CCriticalSection cs_inventory; - std::unordered_set setAskFor; - std::unordered_set setAskForInQueue; - std::priority_queue, std::vector>, std::greater<>> queueAskFor; int64_t nNextInvSend; // Used for headers announcements - unfiltered blocks to relay // Also protected by cs_inventory @@ -1035,8 +1027,6 @@ class CNode vBlockHashesToAnnounce.push_back(hash); } - void AskFor(const CInv& inv, int64_t doubleRequestDelay = 2 * 60 * 1000000); - void RemoveAskFor(const uint256& hash); void CloseSocketDisconnect(); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index c5352e004..e28293970 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +//#include #include #include #include @@ -60,6 +60,23 @@ # error "But Core cannot be compiled without assertions." #endif +/** Maximum number of in-flight objects from a peer */ +static constexpr int32_t MAX_PEER_OBJECT_IN_FLIGHT = 100; +/** Maximum number of announced objects from a peer */ +static constexpr int32_t MAX_PEER_OBJECT_ANNOUNCEMENTS = 2 * MAX_INV_SZ; +/** How many microseconds to delay requesting transactions from inbound peers */ +static constexpr std::chrono::microseconds INBOUND_PEER_TX_DELAY{std::chrono::seconds{2}}; +/** How long to wait (in microseconds) before downloading a transaction from an additional peer */ +static constexpr std::chrono::microseconds GETDATA_TX_INTERVAL{std::chrono::seconds{60}}; +/** Maximum delay (in microseconds) for transaction requests to avoid biasing some peers over others. */ +static constexpr std::chrono::microseconds MAX_GETDATA_RANDOM_DELAY{std::chrono::seconds{2}}; +/** How long to wait (expiry * factor microseconds) before expiring an in-flight getdata request to a peer */ +static constexpr int64_t TX_EXPIRY_INTERVAL_FACTOR = 10; +static_assert(INBOUND_PEER_TX_DELAY >= MAX_GETDATA_RANDOM_DELAY, +"To preserve security, MAX_GETDATA_RANDOM_DELAY should not exceed INBOUND_PEER_DELAY"); +/** Limit to avoid sending big packets. Not used in processing incoming GETDATA for compatibility */ +static const unsigned int MAX_GETDATA_SZ = 1000; + std::atomic nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block struct IteratorComparator @@ -257,6 +274,69 @@ struct CNodeState { //! Time of last new block announcement int64_t m_last_block_announcement; + /* + * State associated with objects download. + * + * Tx download algorithm: + * + * When inv comes in, queue up (process_time, inv) inside the peer's + * CNodeState (m_object_process_time) as long as m_object_announced for the peer + * isn't too big (MAX_PEER_OBJECT_ANNOUNCEMENTS). + * + * The process_time for a objects is set to nNow for outbound peers, + * nNow + 2 seconds for inbound peers. This is the time at which we'll + * consider trying to request the objects from the peer in + * SendMessages(). The delay for inbound peers is to allow outbound peers + * a chance to announce before we request from inbound peers, to prevent + * an adversary from using inbound connections to blind us to a + * objects (InvBlock). + * + * When we call SendMessages() for a given peer, + * we will loop over the objects in m_object_process_time, looking + * at the objects whose process_time <= nNow. We'll request each + * such objects that we don't have already and that hasn't been + * requested from another peer recently, up until we hit the + * MAX_PEER_OBJECT_IN_FLIGHT limit for the peer. Then we'll update + * g_already_asked_for for each requested inv, storing the time of the + * GETDATA request. We use g_already_asked_for to coordinate objects + * requests amongst our peers. + * + * For objects that we still need but we have already recently + * requested from some other peer, we'll reinsert (process_time, inv) + * back into the peer's m_object_process_time at the point in the future at + * which the most recent GETDATA request would time out (ie + * GetObjectInterval + the request time stored in g_already_asked_for). + * We add an additional delay for inbound peers, again to prefer + * attempting download from outbound peers first. + * We also add an extra small random delay up to 2 seconds + * to avoid biasing some peers over others. (e.g., due to fixed ordering + * of peer processing in ThreadMessageHandler). + * + * When we receive a objects from a peer, we remove the inv from the + * peer's m_object_in_flight set and from their recently announced set + * (m_object_announced). We also clear g_already_asked_for for that entry, so + * that if somehow the objects is not accepted but also not added to + * the reject filter, then we will eventually redownload from other + * peers. + */ + struct ObjectDownloadState { + /* Track when to attempt download of announced objects (process + * time in micros -> inv) + */ + std::multimap m_object_process_time; + + //! Store all the objects a peer has recently announced + std::set m_object_announced; + + //! Store objects which were requested by us, with timestamp + std::map m_object_in_flight; + + //! Periodically check for stuck getdata requests + std::chrono::microseconds m_check_expiry_timer{0}; + }; + + ObjectDownloadState m_object_download; + CNodeState(CAddress addrIn, std::string addrNameIn) : address(addrIn), name(addrNameIn) { fCurrentlyConnected = false; nMisbehavior = 0; @@ -282,6 +362,10 @@ struct CNodeState { } }; +// Keeps track of the time (in microseconds) when transactions were requested last time +unordered_limitedmap g_already_asked_for(MAX_INV_SZ, MAX_INV_SZ * 2); +unordered_limitedmap g_erased_object_requests(MAX_INV_SZ, MAX_INV_SZ * 2); + /** Map maintaining per-node state. Requires cs_main. */ std::map mapNodeState; @@ -569,7 +653,148 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector())); + + if (nodestate) { + nodestate->m_object_download.m_object_announced.erase(inv); + nodestate->m_object_download.m_object_in_flight.erase(inv); + } +} + +void EraseObjectRequest(NodeId nodeId, const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + AssertLockHeld(cs_main); + auto* state = State(nodeId); + if (!state) { + return; + } + EraseObjectRequest(state, inv); +} + +std::chrono::microseconds GetObjectRequestTime(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + AssertLockHeld(cs_main); + auto it = g_already_asked_for.find(hash); + if (it != g_already_asked_for.end()) { + return it->second; + } + return {}; +} + +void UpdateObjectRequestTime(const uint256& hash, std::chrono::microseconds request_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + AssertLockHeld(cs_main); + auto it = g_already_asked_for.find(hash); + if (it == g_already_asked_for.end()) { + g_already_asked_for.insert(std::make_pair(hash, request_time)); + } else { + g_already_asked_for.update(it, request_time); + } +} + +std::chrono::microseconds GetObjectInterval(int invType) +{ + // some messages need to be re-requested faster when the first announcing peer did not answer to GETDATA + switch(invType) + { + case MSG_QUORUM_RECOVERED_SIG: + return std::chrono::seconds{15}; + case MSG_CLSIG: + return std::chrono::seconds{5}; + case MSG_ISLOCK: + return std::chrono::seconds{10}; + default: + return GETDATA_TX_INTERVAL; + } +} + +std::chrono::microseconds GetObjectExpiryInterval(int invType) +{ + return GetObjectInterval(invType) * TX_EXPIRY_INTERVAL_FACTOR; +} + +std::chrono::microseconds GetObjectRandomDelay(int invType) +{ + if (invType == MSG_TX) { + return GetRandMicros(MAX_GETDATA_RANDOM_DELAY); + } + return {}; +} + +std::chrono::microseconds CalculateObjectGetDataTime(const CInv& inv, std::chrono::microseconds current_time, bool use_inbound_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + AssertLockHeld(cs_main); + std::chrono::microseconds process_time; + const auto last_request_time = GetObjectRequestTime(inv.hash); + // First time requesting this tx + if (last_request_time.count() == 0) { + process_time = current_time; + } else { + // Randomize the delay to avoid biasing some peers over others (such as due to + // fixed ordering of peer processing in ThreadMessageHandler) + process_time = last_request_time + GetObjectInterval(inv.type) + GetObjectRandomDelay(inv.type); + } + + // We delay processing announcements from inbound peers + if (inv.type == MSG_TX && !fSmartnodeMode && use_inbound_delay) process_time += INBOUND_PEER_TX_DELAY; + + return process_time; +} + +void RequestObject(CNodeState* state, const CInv& inv, std::chrono::microseconds current_time, bool fForce = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + AssertLockHeld(cs_main); + CNodeState::ObjectDownloadState& peer_download_state = state->m_object_download; + if (peer_download_state.m_object_announced.size() >= MAX_PEER_OBJECT_ANNOUNCEMENTS || + peer_download_state.m_object_process_time.size() >= MAX_PEER_OBJECT_ANNOUNCEMENTS || + peer_download_state.m_object_announced.count(inv)) { + // Too many queued announcements from this peer, or we already have + // this announcement + return; + } + peer_download_state.m_object_announced.insert(inv); + + // Calculate the time to try requesting this transaction. Use + // fPreferredDownload as a proxy for outbound peers. + std::chrono::microseconds process_time = CalculateObjectGetDataTime(inv, current_time, !state->fPreferredDownload); + + peer_download_state.m_object_process_time.emplace(process_time, inv); + + if (fForce) { + // make sure this object is actually requested ASAP + g_erased_object_requests.erase(inv.hash); + g_already_asked_for.erase(inv.hash); + } + + LogPrint(BCLog::NET, "%s -- inv=(%s), current_time=%d, process_time=%d, delta=%d\n", __func__, inv.ToString(), current_time.count(), process_time.count(), (process_time - current_time).count()); +} + +void RequestObject(NodeId nodeId, const CInv& inv, std::chrono::microseconds current_time, bool fForce) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + AssertLockHeld(cs_main); + auto* state = State(nodeId); + if (!state) { + return; + } + RequestObject(state, inv, current_time, fForce); +} + +size_t GetRequestedObjectCount(NodeId nodeId) +{ + AssertLockHeld(cs_main); + auto* state = State(nodeId); + if (!state) { + return 0; + } + return state->m_object_download.m_object_process_time.size(); +} // This function is used for testing the stale tip eviction logic, see // DoS_tests.cpp @@ -1435,12 +1660,19 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam if (!vNotFound.empty()) { // Let the peer know that we didn't find what it asked for, so it doesn't - // have to wait around forever. Currently only SPV clients actually care - // about this message: it's needed when they are recursively walking the - // dependencies of relevant unconfirmed transactions. SPV clients want to - // do that because they want to know about (and store and rebroadcast and - // risk analyze) the dependencies of transactions relevant to them, without - // having to download the entire memory pool. + // have to wait around forever. + // SPV clients care about this message: it's needed when they are + // recursively walking the dependencies of relevant unconfirmed + // transactions. SPV clients want to do that because they want to know + // about (and store and rebroadcast and risk analyze) the dependencies + // of transactions relevant to them, without having to download the + // entire memory pool. + // Also, other nodes can use these messages to automatically request a + // transaction from some other peer that annnounced it, and stop + // waiting for us to respond. + // In normal operation, we often send NOTFOUND messages for parents of + // transactions that we relay; if a peer is missing a parent, they may + // assume we have them and request the parents from us. connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::NOTFOUND, vNotFound)); } } @@ -1984,7 +2216,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr State(pfrom->GetId())->fCurrentlyConnected = true; } - if (pfrom->nVersion >= LLMQS_PROTO_VERSION) { + if (pfrom->nVersion >= LLMQS_PROTO_VERSION && !pfrom->fSmartnodeProbe) { CMNAuth::PushMNAUTH(pfrom, *connman); } @@ -2044,6 +2276,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr pfrom->nTimeFirstMessageReceived = GetTimeMicros(); pfrom->fFirstMessageIsMNAUTH = strCommand == NetMsgType::MNAUTH; // Note: do not break the flow here + + if (pfrom->fSmartnodeProbe && !pfrom->fFirstMessageIsMNAUTH) { + LogPrint(BCLog::NET, "connection is a smartnode probe but first received message is not MNAUTH, peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; + return false; + } } if (strCommand == NetMsgType::ADDR) { @@ -2149,6 +2387,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LOCK(cs_main); + const auto current_time = GetTime(); + for (CInv &inv : vInv) { if(!inv.IsKnownType()) { @@ -2204,20 +2444,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } else if (!fAlreadyHave) { bool allowWhileInIBD = allowWhileInIBDObjs.count(inv.type); if (allowWhileInIBD || (!fImporting && !fReindex && !IsInitialBlockDownload())) { - int64_t doubleRequestDelay = 2 * 60 * 1000000; - // some messages need to be re-requested faster when the first announcing peer did not answer to GETDATA - switch (inv.type) { - case MSG_QUORUM_RECOVERED_SIG: - doubleRequestDelay = 15 * 1000000; - break; - case MSG_CLSIG: - doubleRequestDelay = 5 * 1000000; - break; - case MSG_ISLOCK: - doubleRequestDelay = 10 * 1000000; - break; - } - pfrom->AskFor(inv, doubleRequestDelay); + RequestObject(State(pfrom->GetId()), inv, current_time); } } } @@ -2447,10 +2674,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr CInv inv(nInvType, tx.GetHash()); pfrom->AddInventoryKnown(inv); - { - LOCK(cs_main); - connman->RemoveAskFor(inv.hash); - } // Process custom logic, no matter if tx will be accepted to mempool later or not if (nInvType == MSG_DSTX) { @@ -2504,6 +2727,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr bool fMissingInputs = false; CValidationState state; + EraseObjectRequest(pfrom->GetId(), inv); if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, true, &fMissingInputs)) { // Process custom txes, this changes AlreadyHave to "true" @@ -2544,14 +2768,15 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } } if (!fRejectedParents) { + const auto current_time = GetTime(); for (const CTxIn& txin : tx.vin) { CInv _inv(MSG_TX, txin.prevout.hash); pfrom->AddInventoryKnown(_inv); - if (!AlreadyHave(_inv)) pfrom->AskFor(_inv); + if (!AlreadyHave(_inv)) RequestObject(State(pfrom->GetId()), _inv, current_time); // We don't know if the previous tx was a regular or a mixing one, try both CInv _inv2(MSG_DSTX, txin.prevout.hash); pfrom->AddInventoryKnown(_inv2); - if (!AlreadyHave(_inv2)) pfrom->AskFor(_inv2); + if (!AlreadyHave(_inv2)) RequestObject(State(pfrom->GetId()), _inv2, current_time); } AddOrphanTx(ptx, pfrom->GetId()); @@ -3164,8 +3389,27 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (strCommand == NetMsgType::NOTFOUND) { - // We do not care about the NOTFOUND message, but logging an Unknown Command - // message would be undesirable as we transmit it ourselves. + // Remove the NOTFOUND transactions from the peer + LOCK(cs_main); + CNodeState *state = State(pfrom->GetId()); + std::vector vInv; + vRecv >> vInv; + if (vInv.size() <= MAX_PEER_OBJECT_IN_FLIGHT + MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + for (CInv &inv : vInv) { + if (inv.IsKnownType()) { + // If we receive a NOTFOUND message for a txid we requested, erase + // it from our data structures for this peer. + auto in_flight_it = state->m_object_download.m_object_in_flight.find(inv); + if (in_flight_it == state->m_object_download.m_object_in_flight.end()) { + // Skip any further work if this is a spurious NOTFOUND + // message. + continue; + } + state->m_object_download.m_object_in_flight.erase(in_flight_it); + state->m_object_download.m_object_announced.erase(inv); + } + } + } return true; } @@ -3924,6 +4168,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto, std::atomic& interruptM connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); // Detect whether we're stalling + const auto current_time = GetTime(); + // nNow is the current system time (GetTimeMicros is not mockable) and + // should be replaced by the mockable current_time eventually nNow = GetTimeMicros(); if (state.nStallingSince && state.nStallingSince < nNow - 1000000 * BLOCK_STALLING_TIMEOUT) { // Stalling only triggers when the block download window cannot move. During normal steady state, @@ -4009,32 +4256,69 @@ bool PeerLogicValidation::SendMessages(CNode* pto, std::atomic& interruptM // // Message: getdata (non-blocks) // - while (!pto->queueAskFor.empty() && pto->queueAskFor.top().first <= nNow) - { - const CInv& inv = pto->queueAskFor.top().second; - auto jt = pto->setAskForInQueue.find(inv.hash); - if (jt == pto->setAskForInQueue.end()) { - pto->queueAskFor.pop(); + // For robustness, expire old requests after a long timeout, so that + // we can resume downloading objects from a peer even if they + // were unresponsive in the past. + // Eventually we should consider disconnecting peers, but this is + // conservative. + if (state.m_object_download.m_check_expiry_timer <= current_time) { + for (auto it=state.m_object_download.m_object_in_flight.begin(); it != state.m_object_download.m_object_in_flight.end();) { + if (it->second <= current_time - GetObjectExpiryInterval(it->first.type)) { + LogPrint(BCLog::NET, "timeout of inflight object %s from peer=%d\n", it->first.ToString(), pto->GetId()); + state.m_object_download.m_object_announced.erase(it->first); + state.m_object_download.m_object_in_flight.erase(it++); + } else { + ++it; + } + } + // On average, we do this check every GetObjectExpiryInterval. Randomize + // so that we're not doing this for all peers at the same time. + state.m_object_download.m_check_expiry_timer = current_time + GetObjectExpiryInterval(MSG_TX)/2 + GetRandMicros(GetObjectExpiryInterval(MSG_TX)); + } + + // BUTK this code also handles non-TXs (Butk specific messages) + auto& object_process_time = state.m_object_download.m_object_process_time; + while (!object_process_time.empty() && object_process_time.begin()->first <= current_time && state.m_object_download.m_object_in_flight.size() < MAX_PEER_OBJECT_IN_FLIGHT) { + const CInv inv = object_process_time.begin()->second; + // Erase this entry from object_process_time (it may be added back for + // processing at a later time, see below) + object_process_time.erase(object_process_time.begin()); + if (g_erased_object_requests.count(inv.hash)) { + LogPrint(BCLog::NET, "%s -- GETDATA skipping inv=(%s), peer=%d\n", __func__, inv.ToString(), pto->GetId()); + state.m_object_download.m_object_announced.erase(inv); + state.m_object_download.m_object_in_flight.erase(inv); continue; } - if (!AlreadyHave(inv)) - { - LogPrint(BCLog::NET, "SendMessages -- GETDATA -- requesting inv = %s peer=%d\n", inv.ToString(), pto->GetId()); - vGetData.push_back(inv); - if (vGetData.size() >= 1000) - { - connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); - LogPrint(BCLog::NET, "SendMessages -- GETDATA -- pushed size = %lu peer=%d\n", vGetData.size(), pto->GetId()); - vGetData.clear(); + if (!AlreadyHave(inv)) { + // If this object was last requested more than GetObjectInterval ago, + // then request. + const auto last_request_time = GetObjectRequestTime(inv.hash); + if (last_request_time <= current_time - GetObjectInterval(inv.type)) { + LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId()); + vGetData.push_back(inv); + if (vGetData.size() >= MAX_GETDATA_SZ) { + connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); + vGetData.clear(); + } + UpdateObjectRequestTime(inv.hash, current_time); + state.m_object_download.m_object_in_flight.emplace(inv, current_time); + } else { + // This object is in flight from someone else; queue + // up processing to happen after the download times out + // (with a slight delay for inbound peers, to prefer + // requests to outbound peers). + const auto next_process_time = CalculateObjectGetDataTime(inv, current_time, !state.fPreferredDownload); + object_process_time.emplace(next_process_time, inv); + LogPrint(BCLog::NET, "%s -- GETDATA re-queue inv=(%s), next_process_time=%d, delta=%d, peer=%d\n", __func__, inv.ToString(), next_process_time.count(), (next_process_time - current_time).count(), pto->GetId()); + } } else { - //If we're not going to ask, don't expect a response. - LogPrint(BCLog::NET, "SendMessages -- GETDATA -- already have inv = %s peer=%d\n", inv.ToString(), pto->GetId()); - pto->setAskFor.erase(inv.hash); + // We have already seen this object, no need to download. + state.m_object_download.m_object_announced.erase(inv); + state.m_object_download.m_object_in_flight.erase(inv); + LogPrint(BCLog::NET, "%s -- GETDATA already seen inv=(%s), peer=%d\n", __func__, inv.ToString(), pto->GetId()); } - pto->queueAskFor.pop(); - pto->setAskForInQueue.erase(jt); } if (!vGetData.empty()) { connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); diff --git a/src/net_processing.h b/src/net_processing.h index 601a33346..64d4e9a8d 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -84,4 +84,8 @@ bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats); void Misbehaving(NodeId nodeid, int howmuch); bool IsBanned(NodeId nodeid); +void EraseObjectRequest(NodeId nodeId, const CInv& inv); +void RequestObject(NodeId nodeId, const CInv& inv, std::chrono::microseconds current_time, bool fForce=false); +size_t GetRequestedObjectCount(NodeId nodeId); + #endif // BITCOIN_NET_PROCESSING_H diff --git a/src/privatesend/privatesend-client.cpp b/src/privatesend/privatesend-client.cpp index 92beb905a..0b9af0d36 100644 --- a/src/privatesend/privatesend-client.cpp +++ b/src/privatesend/privatesend-client.cpp @@ -1,5 +1,4 @@ // Copyright (c) 2014-2020 The Dash Core developers -// Copyright (c) 2020 The But developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -18,6 +17,7 @@ #include #include #include +#include #include #include @@ -40,7 +40,9 @@ void CPrivateSendClientManager::ProcessMessage(CNode* pfrom, const std::string& if (strCommand == NetMsgType::DSQUEUE) { if (pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint(BCLog::PRIVATESEND, "DSQUEUE -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, + REJECT_OBSOLETE, strprintf( + "Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); return; } @@ -136,7 +138,9 @@ void CPrivateSendClientSession::ProcessMessage(CNode* pfrom, const std::string& if (strCommand == NetMsgType::DSSTATUSUPDATE) { if (pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint(BCLog::PRIVATESEND, "DSSTATUSUPDATE -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, + REJECT_OBSOLETE, strprintf( + "Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); return; } @@ -148,32 +152,14 @@ void CPrivateSendClientSession::ProcessMessage(CNode* pfrom, const std::string& CPrivateSendStatusUpdate psssup; vRecv >> psssup; - if (psssup.nState < POOL_STATE_MIN || psssup.nState > POOL_STATE_MAX) { - LogPrint(BCLog::PRIVATESEND, "DSSTATUSUPDATE -- psssup.nState is out of bounds: %d\n", psssup.nState); - return; - } - - if (psssup.nStatusUpdate < STATUS_REJECTED || psssup.nStatusUpdate > STATUS_ACCEPTED) { - LogPrint(BCLog::PRIVATESEND, "DSSTATUSUPDATE -- psssup.nStatusUpdate is out of bounds: %d\n", psssup.nStatusUpdate); - return; - } - - if (psssup.nMessageID < MSG_POOL_MIN || psssup.nMessageID > MSG_POOL_MAX) { - LogPrint(BCLog::PRIVATESEND, "DSSTATUSUPDATE -- psssup.nMessageID is out of bounds: %d\n", psssup.nMessageID); - return; - } - - LogPrint(BCLog::PRIVATESEND, "DSSTATUSUPDATE -- psssup.nSessionID %d psssup.nState: %d psssup.nStatusUpdate: %d psssup.nMessageID %d (%s)\n", - psssup.nSessionID, psssup.nState, psssup.nStatusUpdate, psssup.nMessageID, CPrivateSend::GetMessageByID(psssup.nMessageID)); - - if (!CheckPoolStateUpdate(psssup)) { - LogPrint(BCLog::PRIVATESEND, "DSSTATUSUPDATE -- CheckPoolStateUpdate failed\n"); - } + ProcessPoolStateUpdate(psssup); } else if (strCommand == NetMsgType::DSFINALTX) { if (pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint(BCLog::PRIVATESEND, "DSFINALTX -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, + REJECT_OBSOLETE, strprintf( + "Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); return; } @@ -199,7 +185,9 @@ void CPrivateSendClientSession::ProcessMessage(CNode* pfrom, const std::string& } else if (strCommand == NetMsgType::DSCOMPLETE) { if (pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint(BCLog::PRIVATESEND, "DSCOMPLETE -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, + REJECT_OBSOLETE, strprintf( + "Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); return; } @@ -313,8 +301,6 @@ std::string CPrivateSendClientSession::GetStatus(bool fWaitForBlock) return strprintf(_("Found enough users, signing ( waiting %s )"), strSuffix); case POOL_STATE_ERROR: return _("PrivateSend request incomplete:") + " " + strLastMessage + " " + _("Will retry..."); - case POOL_STATE_SUCCESS: - return _("PrivateSend request complete:") + " " + strLastMessage; default: return strprintf(_("Unknown state: id = %u"), nState); } @@ -338,7 +324,7 @@ std::string CPrivateSendClientManager::GetSessionDenoms() std::string strSessionDenoms; for (auto& session : deqSessions) { - strSessionDenoms += CPrivateSend::DenominationToString(session.nSessionDenom) + "; "; + strSessionDenoms += CPrivateSend::DenominationToString(session.nSessionDenom) + "; "; } return strSessionDenoms.empty() ? "N/A" : strSessionDenoms; } @@ -361,24 +347,6 @@ bool CPrivateSendClientManager::GetMixingSmartnodesInfo(std::vector= 10) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CheckPool -- timeout, RESETTING\n"); - UnlockCoins(); - if (nState == POOL_STATE_ERROR) { - keyHolderStorage.ReturnAll(); - } else { - keyHolderStorage.KeepAll(); - } - SetNull(); - } -} - // // Check session timeouts // @@ -386,32 +354,31 @@ bool CPrivateSendClientSession::CheckTimeout() { if (fSmartnodeMode) return false; - // catching hanging sessions - switch (nState) { - case POOL_STATE_ERROR: - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CheckTimeout -- Pool error -- Running CheckPool\n"); - CheckPool(); - break; - case POOL_STATE_SUCCESS: - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CheckTimeout -- Pool success -- Running CheckPool\n"); - CheckPool(); - break; - default: - break; + if (nState == POOL_STATE_IDLE) return false; + + if (nState == POOL_STATE_ERROR) { + if (GetTime() - nTimeLastSuccessfulStep >= 10) { + // reset after being in POOL_STATE_ERROR for 10 or more seconds + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- resetting session %d\n", __func__, nSessionID); + SetNull(); + } + return false; } int nLagTime = 10; // give the server a few extra seconds before resetting. int nTimeout = (nState == POOL_STATE_SIGNING) ? PRIVATESEND_SIGNING_TIMEOUT : PRIVATESEND_QUEUE_TIMEOUT; bool fTimeout = GetTime() - nTimeLastSuccessfulStep >= nTimeout + nLagTime; - if (nState == POOL_STATE_IDLE || !fTimeout) return false; + if (!fTimeout) return false; - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CheckTimeout -- %s timed out (%ds) -- resetting\n", - (nState == POOL_STATE_SIGNING) ? "Signing" : "Session", nTimeout); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- %s %d timed out (%ds)\n", __func__, + (nState == POOL_STATE_SIGNING) ? "Signing at session" : "Session", nSessionID, nTimeout); + + SetState(POOL_STATE_ERROR); UnlockCoins(); keyHolderStorage.ReturnAll(); - SetNull(); - SetState(POOL_STATE_ERROR); + nTimeLastSuccessfulStep = GetTime(); + strLastMessage = CPrivateSend::GetMessageByID(ERR_SESSION); return true; } @@ -494,39 +461,52 @@ bool CPrivateSendClientSession::SendDenominate(const std::vector POOL_STATE_MAX) { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- psssup.nState is out of bounds: %d\n", __func__, psssup.nState); + return; + } - // if rejected at any state - if (psssup.nStatusUpdate == STATUS_REJECTED) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CheckPoolStateUpdate -- entry is rejected by Smartnode\n"); - UnlockCoins(); - keyHolderStorage.ReturnAll(); - SetNull(); - SetState(POOL_STATE_ERROR); - strLastMessage = CPrivateSend::GetMessageByID(psssup.nMessageID); - return true; + if (psssup.nMessageID < MSG_POOL_MIN || psssup.nMessageID > MSG_POOL_MAX) { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- psssup.nMessageID is out of bounds: %d\n", __func__, psssup.nMessageID); + return; } - if (psssup.nStatusUpdate == STATUS_ACCEPTED && nState == psssup.nState) { - if (psssup.nState == POOL_STATE_QUEUE && nSessionID == 0 && psssup.nSessionID != 0) { - // new session id should be set only in POOL_STATE_QUEUE state - nSessionID = psssup.nSessionID; + std::string strMessageTmp = CPrivateSend::GetMessageByID(psssup.nMessageID); + strAutoDenomResult = _("Smartnode:") + " " + strMessageTmp; + + switch (psssup.nStatusUpdate) { + case STATUS_REJECTED: { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- rejected by Smartnode: %s\n", __func__, strMessageTmp); + SetState(POOL_STATE_ERROR); + UnlockCoins(); + keyHolderStorage.ReturnAll(); nTimeLastSuccessfulStep = GetTime(); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CheckPoolStateUpdate -- set nSessionID to %d\n", nSessionID); - return true; + strLastMessage = strMessageTmp; + break; + } + case STATUS_ACCEPTED: { + if (nState == psssup.nState && psssup.nState == POOL_STATE_QUEUE && nSessionID == 0 && psssup.nSessionID != 0) { + // new session id should be set only in POOL_STATE_QUEUE state + nSessionID = psssup.nSessionID; + nTimeLastSuccessfulStep = GetTime(); + strMessageTmp += strprintf(" Set nSessionID to %d.", nSessionID); + } + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- accepted by Smartnode: %s\n", __func__, strMessageTmp); + break; + } + default: { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- psssup.nStatusUpdate is out of bounds: %d\n", __func__, psssup.nStatusUpdate); + break; } } - - // only situations above are allowed, fail in any other case - return false; } // @@ -585,7 +565,7 @@ bool CPrivateSendClientSession::SignFinalTransaction(const CTransaction& finalTr if (!fFound) { // Something went wrong and we'll refuse to sign. It's possible we'll be charged collateral. But that's // better than signing if the transaction doesn't look like what we wanted. - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- an output is missing, refusing to sign! txout=%s\n", txout.ToString()); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- an output is missing, refusing to sign! txout=%s\n", __func__, txout.ToString()); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); @@ -619,7 +599,7 @@ bool CPrivateSendClientSession::SignFinalTransaction(const CTransaction& finalTr const CKeyStore& keystore = *vpwallets[0]; LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Signing my input %i\n", __func__, nMyInputIndex); - // TODO we're using amount=0 here but we should use the correct amount. This works because But ignores the amount while signing/verifying (only used in Bitcoin/Segwit) + // TODO we're using amount=0 here but we should use the correct amount. This works because Dash ignores the amount while signing/verifying (only used in Bitcoin/Segwit) if (!SignSignature(keystore, prevPubKey, finalMutableTransaction, nMyInputIndex, 0, int(SIGHASH_ALL | SIGHASH_ANYONECANPAY))) { // changes scriptSig LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Unable to sign my own transaction!\n", __func__); // not sure what to do here, it will timeout...? @@ -641,7 +621,7 @@ bool CPrivateSendClientSession::SignFinalTransaction(const CTransaction& finalTr } // push all of our signatures to the Smartnode - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- pushing sigs to the smartnode, finalMutableTransaction=%s", __func__, finalMutableTransaction.ToString()); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- pushing sigs to the Smartnode, finalMutableTransaction=%s", __func__, finalMutableTransaction.ToString()); CNetMsgMaker msgMaker(pnode->GetSendVersion()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::DSSIGNFINALTX, sigs)); SetState(POOL_STATE_SIGNING); @@ -674,21 +654,6 @@ void CPrivateSendClientManager::UpdatedSuccessBlock() nCachedLastSuccessBlock = nCachedBlockHeight; } -bool CPrivateSendClientManager::IsDenomSkipped(const CAmount& nDenomValue) -{ - return std::find(vecDenominationsSkipped.begin(), vecDenominationsSkipped.end(), nDenomValue) != vecDenominationsSkipped.end(); -} - -void CPrivateSendClientManager::AddSkippedDenom(const CAmount& nDenomValue) -{ - vecDenominationsSkipped.push_back(nDenomValue); -} - -void CPrivateSendClientManager::RemoveSkippedDenom(const CAmount& nDenomValue) -{ - vecDenominationsSkipped.erase(std::remove(vecDenominationsSkipped.begin(), vecDenominationsSkipped.end(), nDenomValue), vecDenominationsSkipped.end()); -} - bool CPrivateSendClientManager::WaitForAnotherBlock() { if (!smartnodeSync.IsBlockchainSynced()) return true; @@ -726,7 +691,7 @@ bool CPrivateSendClientManager::CheckAutomaticBackup() } if (vpwallets[0]->nKeysLeftSinceAutoBackup < PRIVATESEND_KEYS_THRESHOLD_STOP) { - // We should never get here via mixing itself but probably smth else is still actively using keypool + // We should never get here via mixing itself but probably something else is still actively using keypool LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientManager::CheckAutomaticBackup -- Very low number of keys left: %d, no mixing available.\n", vpwallets[0]->nKeysLeftSinceAutoBackup); strAutoDenomResult = strprintf(_("Very low number of keys left: %d") + ", " + _("no mixing available."), vpwallets[0]->nKeysLeftSinceAutoBackup); // It's getting really dangerous, stop mixing @@ -755,7 +720,7 @@ bool CPrivateSendClientManager::CheckAutomaticBackup() } } } else { - // Wait for smth else (e.g. GUI action) to create automatic backup for us + // Wait for something else (e.g. GUI action) to create automatic backup for us return false; } } @@ -770,7 +735,7 @@ bool CPrivateSendClientManager::CheckAutomaticBackup() // bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool fDryRun) { - if (fSmartnodeMode) return false; // no client-side mixing on smartnodes + if (fSmartnodeMode) return false; // no client-side mixing on Smartnodes if (nState != POOL_STATE_IDLE) return false; if (!smartnodeSync.IsBlockchainSynced()) { @@ -783,7 +748,8 @@ bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool CAmount nBalanceNeedsAnonymized; { - LOCK2(cs_main, vpwallets[0]->cs_wallet); + LOCK2(cs_main, mempool.cs); + LOCK(vpwallets[0]->cs_wallet); if (!fDryRun && vpwallets[0]->IsLocked(true)) { strAutoDenomResult = _("Wallet is locked."); @@ -801,7 +767,8 @@ bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool return false; } - if (deterministicMNManager->GetListAtChainTip().GetValidMNsCount() == 0) { + if (deterministicMNManager->GetListAtChainTip().GetValidMNsCount() == 0 && + Params().NetworkIDString() != CBaseChainParams::REGTEST) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::DoAutomaticDenominating -- No Smartnodes detected\n"); strAutoDenomResult = _("No Smartnodes detected."); return false; @@ -884,12 +851,12 @@ bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool // there are funds to denominate and denominated balance does not exceed // max amount to mix yet. if (nBalanceAnonimizableNonDenom >= nValueMin + CPrivateSend::GetCollateralAmount() && nBalanceToDenominate > 0) { - CreateDenominated(nBalanceToDenominate, connman); + CreateDenominated(nBalanceToDenominate); } //check if we have the collateral sized inputs if (!vpwallets[0]->HasCollateralInputs()) { - return !vpwallets[0]->HasCollateralInputs(false) && MakeCollateralAmounts(connman); + return !vpwallets[0]->HasCollateralInputs(false) && MakeCollateralAmounts(); } if (nSessionID) { @@ -947,7 +914,7 @@ bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool bool CPrivateSendClientManager::DoAutomaticDenominating(CConnman& connman, bool fDryRun) { - if (fSmartnodeMode) return false; // no client-side mixing on smartnodes + if (fSmartnodeMode) return false; // no client-side mixing on Smartnodes if (!fEnablePrivateSend || !fPrivateSendRunning) return false; if (!smartnodeSync.IsBlockchainSynced()) { @@ -1004,7 +971,7 @@ CDeterministicMNCPtr CPrivateSendClientManager::GetRandomNotUsedSmartnode() int nCountEnabled = mnList.GetValidMNsCount(); int nCountNotExcluded = nCountEnabled - vecSmartnodesUsed.size(); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientManager::%s -- %d enabled smartnodes, %d smartnodes to choose from\n", __func__, nCountEnabled, nCountNotExcluded); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientManager::%s -- %d enabled Smartnodes, %d Smartnodes to choose from\n", __func__, nCountEnabled, nCountNotExcluded); if(nCountNotExcluded < 1) { return nullptr; } @@ -1028,7 +995,7 @@ CDeterministicMNCPtr CPrivateSendClientManager::GetRandomNotUsedSmartnode() continue; } - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientManager::%s -- found, smartnode=%s\n", __func__, dmn->collateralOutpoint.ToStringShort()); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientManager::%s -- found, Smartnode=%s\n", __func__, dmn->collateralOutpoint.ToStringShort()); return dmn; } @@ -1058,16 +1025,14 @@ bool CPrivateSendClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymize continue; } - // mixing rate limit i.e. nLastDsq check should already pass in DSQUEUE ProcessMessage // in order for dsq to get into vecPrivateSendQueue, so we should be safe to mix already, // no need for additional verification here - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::JoinExistingQueue -- trying queue: %s\n", dsq.ToString()); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::JoinExistingQueue -- trying queue: %s\n", dsq.ToString()); std::vector > vecPSInOutPairsTmp; - // Try to match their denominations if possible, select exact number of denominations if (!vpwallets[0]->SelectPSInOutPairsByDenominations(dsq.nDenom, nBalanceNeedsAnonymized, vecPSInOutPairsTmp)) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::JoinExistingQueue -- Couldn't match denomination %d (%s)\n", dsq.nDenom, CPrivateSend::DenominationToString(dsq.nDenom)); @@ -1084,12 +1049,12 @@ bool CPrivateSendClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymize nSessionDenom = dsq.nDenom; mixingSmartnode = dmn; pendingDsaRequest = CPendingDsaRequest(dmn->pdmnState->addr, CPrivateSendAccept(nSessionDenom, txMyCollateral)); - connman.AddPendingSmartnode(dmn->pdmnState->addr); + connman.AddPendingSmartnode(dmn->proTxHash); // TODO: add new state POOL_STATE_CONNECTING and bump MIN_PRIVATESEND_PEER_PROTO_VERSION SetState(POOL_STATE_QUEUE); nTimeLastSuccessfulStep = GetTime(); LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::JoinExistingQueue -- pending connection (from queue): nSessionDenom: %d (%s), addr=%s\n", - nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom), dmn->pdmnState->addr.ToString()); + nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom), dmn->pdmnState->addr.ToString()); strAutoDenomResult = _("Trying to connect..."); return true; } @@ -1163,7 +1128,7 @@ bool CPrivateSendClientSession::StartNewQueue(CAmount nBalanceNeedsAnonymized, C } mixingSmartnode = dmn; - // connman.AddPendingSmartnode(dmn->proTxHash); + connman.AddPendingSmartnode(dmn->proTxHash); pendingDsaRequest = CPendingDsaRequest(dmn->pdmnState->addr, CPrivateSendAccept(nSessionDenom, txMyCollateral)); // TODO: add new state POOL_STATE_CONNECTING and bump MIN_PRIVATESEND_PEER_PROTO_VERSION SetState(POOL_STATE_QUEUE); @@ -1213,7 +1178,8 @@ void CPrivateSendClientManager::ProcessPendingDsaRequest(CConnman& connman) bool CPrivateSendClientSession::SubmitDenominate(CConnman& connman) { - LOCK2(cs_main, vpwallets[0]->cs_wallet); + LOCK2(cs_main, mempool.cs); + LOCK(vpwallets[0]->cs_wallet); std::string strError; std::vector > vecPSInOutPairs, vecPSInOutPairsTmp; @@ -1296,13 +1262,11 @@ bool CPrivateSendClientSession::PrepareDenominate(int nMinRounds, int nMaxRounds strErrorRet = "Incorrect session denom"; return false; } - CAmount nDenomAmount = CPrivateSend::DenominationToAmount(nSessionDenom); // NOTE: No need to randomize order of inputs because they were // initially shuffled in CWallet::SelectPSInOutPairsByDenominations already. int nSteps{0}; - vecPSInOutPairsRet.clear(); // Try to add up to PRIVATESEND_ENTRY_MAX_SIZE of every needed denomination @@ -1349,11 +1313,12 @@ bool CPrivateSendClientSession::PrepareDenominate(int nMinRounds, int nMaxRounds } // Create collaterals by looping through inputs grouped by addresses -bool CPrivateSendClientSession::MakeCollateralAmounts(CConnman& connman) +bool CPrivateSendClientSession::MakeCollateralAmounts() { if (!privateSendClient.fEnablePrivateSend || !privateSendClient.fPrivateSendRunning) return false; - LOCK2(cs_main, vpwallets[0]->cs_wallet); + LOCK2(cs_main, mempool.cs); + LOCK(vpwallets[0]->cs_wallet); // NOTE: We do not allow txes larger than 100kB, so we have to limit number of inputs here. // We still want to consume a lot of inputs to avoid creating only smaller denoms though. @@ -1372,106 +1337,116 @@ bool CPrivateSendClientSession::MakeCollateralAmounts(CConnman& connman) // First try to use only non-denominated funds for (const auto& item : vecTally) { - if (!MakeCollateralAmounts(item, false, connman)) continue; + if (!MakeCollateralAmounts(item, false)) continue; return true; } // There should be at least some denominated funds we should be able to break in pieces to continue mixing for (const auto& item : vecTally) { - if (!MakeCollateralAmounts(item, true, connman)) continue; + if (!MakeCollateralAmounts(item, true)) continue; return true; } - // If we got here then smth is terribly broken actually + // If we got here then something is terribly broken actually LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::MakeCollateralAmounts -- ERROR: Can't make collaterals!\n"); return false; } // Split up large inputs or create fee sized inputs -bool CPrivateSendClientSession::MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated, CConnman& connman) +bool CPrivateSendClientSession::MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated) { AssertLockHeld(cs_main); + AssertLockHeld(mempool.cs); AssertLockHeld(vpwallets[0]->cs_wallet); if (!privateSendClient.fEnablePrivateSend || !privateSendClient.fPrivateSendRunning) return false; - // denominated input is always a single one, so we can check its amount directly and return early + // Denominated input is always a single one, so we can check its amount directly and return early if (!fTryDenominated && tallyItem.vecOutPoints.size() == 1 && CPrivateSend::IsDenominatedAmount(tallyItem.nAmount)) { return false; } - CWalletTx wtx; - CAmount nFeeRet = 0; - int nChangePosRet = -1; - std::string strFail = ""; - std::vector vecSend; - - // make our collateral address - CReserveKey reservekeyCollateral(vpwallets[0]); - // make our change address - CReserveKey reservekeyChange(vpwallets[0]); - - CScript scriptCollateral; - CPubKey vchPubKey; - assert(reservekeyCollateral.GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked - scriptCollateral = GetScriptForDestination(vchPubKey.GetID()); - - vecSend.push_back((CRecipient){scriptCollateral, CPrivateSend::GetMaxCollateralAmount(), false}); - - // try to use non-denominated and not mn-like funds first, select them explicitly - CCoinControl coinControl; - coinControl.fAllowOtherInputs = false; - coinControl.fAllowWatchOnly = false; - coinControl.nCoinType = CoinType::ONLY_NONDENOMINATED; - // send change to the same address so that we were able create more denoms out of it later - coinControl.destChange = tallyItem.txdest; - for (const auto& outpoint : tallyItem.vecOutPoints) { - coinControl.Select(outpoint); - } - - bool fSuccess = vpwallets[0]->CreateTransaction(vecSend, wtx, reservekeyChange, - nFeeRet, nChangePosRet, strFail, coinControl); - if (!fSuccess) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::MakeCollateralAmounts -- ONLY_NONDENOMINATED: %s\n", strFail); - // If we failed then most likely there are not enough funds on this address. - if (fTryDenominated) { - // Try to also use denominated coins (we can't mix denominated without collaterals anyway). - coinControl.nCoinType = CoinType::ALL_COINS; - if (!vpwallets[0]->CreateTransaction(vecSend, wtx, reservekeyChange, - nFeeRet, nChangePosRet, strFail, coinControl)) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::MakeCollateralAmounts -- ALL_COINS Error: %s\n", strFail); - reservekeyCollateral.ReturnKey(); - return false; - } - } else { - // Nothing else we can do. - reservekeyCollateral.ReturnKey(); - return false; - } + // Skip single inputs that can be used as collaterals already + if (tallyItem.vecOutPoints.size() == 1 && CPrivateSend::IsCollateralAmount(tallyItem.nAmount)) { + return false; } - reservekeyCollateral.KeepKey(); + CTransactionBuilder txBuilder(vpwallets[0], tallyItem); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::MakeCollateralAmounts -- txid=%s\n", wtx.GetHash().GetHex()); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Start %s\n", __func__, txBuilder.ToString()); - // use the same nCachedLastSuccessBlock as for DS mixing to prevent race - CValidationState state; - if (!vpwallets[0]->CommitTransaction(wtx, reservekeyChange, &connman, state)) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::MakeCollateralAmounts -- CommitTransaction failed! Reason given: %s\n", state.GetRejectReason()); + // Skip way too tiny amounts. Smallest we want is minimum collateral amount in a one output tx + if (!txBuilder.CouldAddOutput(CPrivateSend::GetCollateralAmount())) { + return false; + } + + int nCase{0}; // Just for debug logs + if (txBuilder.CouldAddOutputs({CPrivateSend::GetMaxCollateralAmount(), CPrivateSend::GetCollateralAmount()})) { + nCase = 1; + // , see TransactionRecord::decomposeTransaction + // Out1 == CPrivateSend::GetMaxCollateralAmount() + // Out2 >= CPrivateSend::GetCollateralAmount() + + txBuilder.AddOutput(CPrivateSend::GetMaxCollateralAmount()); + // Note, here we first add a zero amount output to get the remainder after all fees and then assign it + CTransactionBuilderOutput* out = txBuilder.AddOutput(); + CAmount nAmountLeft = txBuilder.GetAmountLeft(); + // If remainder is denominated add one duff to the fee + out->UpdateAmount(CPrivateSend::IsDenominatedAmount(nAmountLeft) ? nAmountLeft - 1 : nAmountLeft); + + } else if (txBuilder.CouldAddOutputs({CPrivateSend::GetCollateralAmount(), CPrivateSend::GetCollateralAmount()})) { + nCase = 2; + // , see TransactionRecord::decomposeTransaction + // Out1 CPrivateSend::IsCollateralAmount() + // Out2 CPrivateSend::IsCollateralAmount() + + // First add two outputs to get the available value after all fees + CTransactionBuilderOutput* out1 = txBuilder.AddOutput(); + CTransactionBuilderOutput* out2 = txBuilder.AddOutput(); + + // Create two equal outputs from the available value. This adds one duff to the fee if txBuilder.GetAmountLeft() is odd. + CAmount nAmountOutputs = txBuilder.GetAmountLeft() / 2; + + assert(CPrivateSend::IsCollateralAmount(nAmountOutputs)); + + out1->UpdateAmount(nAmountOutputs); + out2->UpdateAmount(nAmountOutputs); + + } else { // still at least possible to add one CPrivateSend::GetCollateralAmount() output + nCase = 3; + // , see TransactionRecord::decomposeTransaction + // Out1 CPrivateSend::IsCollateralAmount() + // Out2 Skipped + CTransactionBuilderOutput* out = txBuilder.AddOutput(); + out->UpdateAmount(txBuilder.GetAmountLeft()); + + assert(CPrivateSend::IsCollateralAmount(out->GetAmount())); + } + + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Done with case %d: %s\n", __func__, nCase, txBuilder.ToString()); + + assert(txBuilder.IsDust(txBuilder.GetAmountLeft())); + + std::string strResult; + if (!txBuilder.Commit(strResult)) { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Commit failed: %s\n", __func__, strResult); return false; } privateSendClient.UpdatedSuccessBlock(); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- txid: %s\n", __func__, strResult); + return true; } // Create denominations by looping through inputs grouped by addresses -bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, CConnman& connman) +bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate) { if (!privateSendClient.fEnablePrivateSend || !privateSendClient.fPrivateSendRunning) return false; - LOCK2(cs_main, vpwallets[0]->cs_wallet); + LOCK2(cs_main, mempool.cs); + LOCK(vpwallets[0]->cs_wallet); // NOTE: We do not allow txes larger than 100kB, so we have to limit number of inputs here. // We still want to consume a lot of inputs to avoid creating only smaller denoms though. @@ -1491,7 +1466,7 @@ bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, bool fCreateMixingCollaterals = !vpwallets[0]->HasCollateralInputs(); for (const auto& item : vecTally) { - if (!CreateDenominated(nBalanceToDenominate, item, fCreateMixingCollaterals, connman)) continue; + if (!CreateDenominated(nBalanceToDenominate, item, fCreateMixingCollaterals)) continue; return true; } @@ -1500,124 +1475,190 @@ bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, } // Create denominations -bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman) +bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals) { + AssertLockHeld(cs_main); + AssertLockHeld(mempool.cs); + AssertLockHeld(vpwallets[0]->cs_wallet); + if (!privateSendClient.fEnablePrivateSend || !privateSendClient.fPrivateSendRunning) return false; - std::vector vecSend; - CKeyHolderStorage keyHolderStorageDenom; + // denominated input is always a single one, so we can check its amount directly and return early + if (tallyItem.vecOutPoints.size() == 1 && CPrivateSend::IsDenominatedAmount(tallyItem.nAmount)) { + return false; + } - CAmount nValueLeft = tallyItem.nAmount; - nValueLeft -= CPrivateSend::GetCollateralAmount(); // leave some room for fees + CTransactionBuilder txBuilder(vpwallets[0], tallyItem); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- 0 - %s nValueLeft: %f\n", CBitcoinAddress(tallyItem.txdest).ToString(), (float)nValueLeft / COIN); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Start %s\n", __func__, txBuilder.ToString()); // ****** Add an output for mixing collaterals ************ / - if (fCreateMixingCollaterals) { - CScript scriptCollateral = keyHolderStorageDenom.AddKey(vpwallets[0]); - vecSend.push_back((CRecipient){scriptCollateral, CPrivateSend::GetMaxCollateralAmount(), false}); - nValueLeft -= CPrivateSend::GetMaxCollateralAmount(); + if (fCreateMixingCollaterals && !txBuilder.AddOutput(CPrivateSend::GetMaxCollateralAmount())) { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Failed to add collateral output\n", __func__); + return false; } // ****** Add outputs for denoms ************ / - int nOutputsTotal = 0; bool fAddFinal = true; std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); - for (auto it = vecStandardDenoms.rbegin(); it != vecStandardDenoms.rend(); ++it) { - CAmount nDenomValue = *it; + std::map mapDenomCount; + for (auto nDenomValue : vecStandardDenoms) { + mapDenomCount.insert(std::pair(nDenomValue, vpwallets[0]->CountInputsWithAmount(nDenomValue))); + } + + // Will generate outputs for the createdenoms up to privatesendmaxdenoms per denom + + // This works in the way creating PS denoms has traditionally worked, assuming enough funds, + // it will start with the smallest denom then create 11 of those, then go up to the next biggest denom create 11 + // and repeat. Previously, once the largest denom was reached, as many would be created were created as possible and + // then any remaining was put into a change address and denominations were created in the same manner a block later. + // Now, in this system, so long as we don't reach PRIVATESEND_DENOM_OUTPUTS_THRESHOLD outputs the process repeats in + // the same transaction, creating up to nPrivateSendDenomsHardCap per denomination in a single transaction. + + while (txBuilder.CouldAddOutput(CPrivateSend::GetSmallestDenomination()) && txBuilder.CountOutputs() < PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) { + for (auto it = vecStandardDenoms.rbegin(); it != vecStandardDenoms.rend(); ++it) { + CAmount nDenomValue = *it; + auto currentDenomIt = mapDenomCount.find(nDenomValue); + + int nOutputs = 0; + + const auto& strFunc = __func__; + auto needMoreOutputs = [&]() { + if (txBuilder.CouldAddOutput(nDenomValue)) { + if (fAddFinal && nBalanceToDenominate > 0 && nBalanceToDenominate < nDenomValue) { + fAddFinal = false; // add final denom only once, only the smalest possible one + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - FINAL - nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n", + strFunc, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString()); + return true; + } else if (nBalanceToDenominate >= nDenomValue) { + return true; + } + } + return false; + }; + + // add each output up to 11 times or until it can't be added again or until we reach nPrivateSendDenomsGoal + while (needMoreOutputs() && nOutputs <= 10 && currentDenomIt->second < privateSendClient.nPrivateSendDenomsGoal) { + // Add output and subtract denomination amount + if (txBuilder.AddOutput(nDenomValue)) { + ++nOutputs; + ++currentDenomIt->second; + nBalanceToDenominate -= nDenomValue; + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n", + __func__, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString()); + } else { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - Error: AddOutput failed for nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n", + __func__, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString()); + return false; + } - // Note: denoms are skipped if there are already nPrivateSendDenoms of them - // and there are still larger denoms which can be used for mixing + } - // check skipped denoms - if (privateSendClient.IsDenomSkipped(nDenomValue)) { - strAutoDenomResult = strprintf(_("Too many %f denominations, skipping."), (float)nDenomValue / COIN); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- %s\n", strAutoDenomResult); - continue; + if (txBuilder.GetAmountLeft() == 0 || nBalanceToDenominate <= 0) break; } - // find new denoms to skip if any (ignore the largest one) - if (nDenomValue != vecStandardDenoms.front() && vpwallets[0]->CountInputsWithAmount(nDenomValue) > privateSendClient.nPrivateSendDenoms) { - strAutoDenomResult = strprintf(_("Too many %f denominations, removing."), (float)nDenomValue / COIN); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- %s\n", strAutoDenomResult); - privateSendClient.AddSkippedDenom(nDenomValue); - continue; + bool finished = true; + for (const auto it : mapDenomCount) { + // Check if this specific denom could use another loop, check that there aren't nPrivateSendDenomsGoal of this + // denom and that our nValueLeft/nBalanceToDenominate is enough to create one of these denoms, if so, loop again. + if (it.second < privateSendClient.nPrivateSendDenomsGoal && txBuilder.CouldAddOutput(it.first) && nBalanceToDenominate > 0) { + finished = false; + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - NOT finished - nDenomValue: %f, count: %d, nBalanceToDenominate: %f, %s\n", + __func__, (float) it.first / COIN, it.second, (float) nBalanceToDenominate / COIN, txBuilder.ToString()); + break; + } + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - FINSHED - nDenomValue: %f, count: %d, nBalanceToDenominate: %f, %s\n", + __func__, (float) it.first / COIN, it.second, (float) nBalanceToDenominate / COIN, txBuilder.ToString()); } - int nOutputs = 0; + if (finished) break; + } - auto needMoreOutputs = [&]() { - bool fRegular = (nValueLeft >= nDenomValue && nBalanceToDenominate >= nDenomValue); - bool fFinal = (fAddFinal - && nValueLeft >= nDenomValue - && nBalanceToDenominate > 0 - && nBalanceToDenominate < nDenomValue); - fAddFinal = false; // add final denom only once, only the smalest possible one - return fRegular || fFinal; - }; + // Now that nPrivateSendDenomsGoal worth of each denom have been created or the max number of denoms given the value of the input, do something with the remainder. + if (txBuilder.CouldAddOutput(CPrivateSend::GetSmallestDenomination()) && nBalanceToDenominate >= CPrivateSend::GetSmallestDenomination() && txBuilder.CountOutputs() < PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) { + CAmount nLargestDenomValue = vecStandardDenoms.front(); - // add each output up to 11 times until it can't be added again - while (needMoreOutputs() && nOutputs <= 10) { - CScript scriptDenom = keyHolderStorageDenom.AddKey(vpwallets[0]); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 2 - Process remainder: %s\n", __func__, txBuilder.ToString()); - vecSend.push_back((CRecipient){scriptDenom, nDenomValue, false}); + auto countPossibleOutputs = [&](CAmount nAmount) -> int { + std::vector vecOutputs; + while (true) { + // Create an potential output + vecOutputs.push_back(nAmount); + if (!txBuilder.CouldAddOutputs(vecOutputs) || txBuilder.CountOutputs() + vecOutputs.size() > PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) { + // If its not possible to add it due to insufficient amount left or total number of outputs exceeds + // PRIVATESEND_DENOM_OUTPUTS_THRESHOLD drop the output again and stop trying. + vecOutputs.pop_back(); + break; + } + } + return static_cast(vecOutputs.size()); + }; - //increment outputs and subtract denomination amount - nOutputs++; - nValueLeft -= nDenomValue; - nBalanceToDenominate -= nDenomValue; - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- 1 - totalOutputs: %d, nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f\n", nOutputsTotal + nOutputs, nOutputsTotal, nOutputs, (float)nValueLeft / COIN); + // Go big to small + for (auto nDenomValue : vecStandardDenoms) { + int nOutputs = 0; + + // Number of denoms we can create given our denom and the amount of funds we have left + int denomsToCreateValue = countPossibleOutputs(nDenomValue); + // Prefer overshooting the targed balance by larger denoms (hence `+1`) instead of a more + // accurate approximation by many smaller denoms. This is ok because when we get here we + // should have nPrivateSendDenomsGoal of each smaller denom already. Also, without `+1` + // we can end up in a situation when there is already nPrivateSendDenomsHardCap of smaller + // denoms yet we can't mix the remaining nBalanceToDenominate because it's smaller than + // nDenomValue (and thus denomsToCreateBal == 0), so the target would never get reached + // even when there is enough funds for that. + int denomsToCreateBal = (nBalanceToDenominate / nDenomValue) + 1; + // Use the smaller value + int denomsToCreate = denomsToCreateValue > denomsToCreateBal ? denomsToCreateBal : denomsToCreateValue; + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 2 - nBalanceToDenominate: %f, nDenomValue: %f, denomsToCreateValue: %d, denomsToCreateBal: %d\n", + __func__, (float) nBalanceToDenominate / COIN, (float) nDenomValue / COIN, denomsToCreateValue, denomsToCreateBal); + auto it = mapDenomCount.find(nDenomValue); + for (int i = 0; i < denomsToCreate; i++) { + // Never go above the cap unless it's the largest denom + if (nDenomValue != nLargestDenomValue && it->second >= privateSendClient.nPrivateSendDenomsHardCap) break; + + // Increment helpers, add output and subtract denomination amount + if (txBuilder.AddOutput(nDenomValue)) { + nOutputs++; + it->second++; + nBalanceToDenominate -= nDenomValue; + } else { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 2 - Error: AddOutput failed at %d/%d, %s\n", __func__, i + 1, denomsToCreate, txBuilder.ToString()); + break; + } + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 2 - nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n", + __func__, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString()); + if (txBuilder.CountOutputs() >= PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) break; + } + if (txBuilder.CountOutputs() >= PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) break; } + } - nOutputsTotal += nOutputs; - if (nValueLeft == 0 || nBalanceToDenominate <= 0) break; + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 3 - nBalanceToDenominate: %f, %s\n", __func__, (float) nBalanceToDenominate / COIN, txBuilder.ToString()); + + for (const auto it : mapDenomCount) { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 3 - DONE - nDenomValue: %f, count: %d\n", __func__, (float) it.first / COIN, it.second); } - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- 2 - nOutputsTotal: %d, nValueLeft: %f\n", nOutputsTotal, (float)nValueLeft / COIN); // No reasons to create mixing collaterals if we can't create denoms to mix - if (nOutputsTotal == 0) return false; - - // if we have anything left over, it will be automatically send back as change - there is no need to send it manually - - CCoinControl coinControl; - coinControl.fAllowOtherInputs = false; - coinControl.fAllowWatchOnly = false; - coinControl.nCoinType = CoinType::ONLY_NONDENOMINATED; - // send change to the same address so that we were able create more denoms out of it later - coinControl.destChange = tallyItem.txdest; - for (const auto& outpoint : tallyItem.vecOutPoints) { - coinControl.Select(outpoint); - } - - CWalletTx wtx; - CAmount nFeeRet = 0; - int nChangePosRet = -1; - std::string strFail = ""; - // make our change address - CReserveKey reservekeyChange(vpwallets[0]); - - bool fSuccess = vpwallets[0]->CreateTransaction(vecSend, wtx, reservekeyChange, - nFeeRet, nChangePosRet, strFail, coinControl); - if (!fSuccess) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- Error: %s\n", strFail); - keyHolderStorageDenom.ReturnAll(); + if ((fCreateMixingCollaterals && txBuilder.CountOutputs() == 1) || txBuilder.CountOutputs() == 0) { return false; } - keyHolderStorageDenom.KeepAll(); - - CValidationState state; - if (!vpwallets[0]->CommitTransaction(wtx, reservekeyChange, &connman, state)) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- CommitTransaction failed! Reason given: %s\n", state.GetRejectReason()); + std::string strResult; + if (!txBuilder.Commit(strResult)) { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Commit failed: %s\n", __func__, strResult); return false; } // use the same nCachedLastSuccessBlock as for DS mixing to prevent race privateSendClient.UpdatedSuccessBlock(); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- txid=%s\n", wtx.GetHash().GetHex()); + + LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- txid: %s\n", __func__, strResult); return true; } @@ -1675,7 +1716,7 @@ void CPrivateSendClientSession::GetJsonInfo(UniValue& obj) const obj.push_back(Pair("outpoint", mixingSmartnode->collateralOutpoint.ToStringShort())); obj.push_back(Pair("service", mixingSmartnode->pdmnState->addr.ToString())); } - obj.push_back(Pair("denomination", ValueFromAmount(CPrivateSend::DenominationToAmount(nSessionDenom)))); + obj.push_back(Pair("denomination", ValueFromAmount(CPrivateSend::DenominationToAmount(nSessionDenom)))); obj.push_back(Pair("state", GetStateString())); obj.push_back(Pair("entries_count", GetEntriesCount())); } @@ -1691,7 +1732,8 @@ void CPrivateSendClientManager::GetJsonInfo(UniValue& obj) const obj.push_back(Pair("max_sessions", nPrivateSendSessions)); obj.push_back(Pair("max_rounds", nPrivateSendRounds)); obj.push_back(Pair("max_amount", nPrivateSendAmount)); - obj.push_back(Pair("max_denoms", nPrivateSendDenoms)); + obj.push_back(Pair("denoms_goal", nPrivateSendDenomsGoal)); + obj.push_back(Pair("denoms_hardcap", nPrivateSendDenomsHardCap)); obj.push_back(Pair("queue_size", GetQueueSize())); UniValue arrSessions(UniValue::VARR); diff --git a/src/privatesend/privatesend-client.h b/src/privatesend/privatesend-client.h index 8026a7c3b..8b8ef70c3 100644 --- a/src/privatesend/privatesend-client.h +++ b/src/privatesend/privatesend-client.h @@ -1,5 +1,4 @@ // Copyright (c) 2014-2019 The Dash Core developers -// Copyright (c) 2020 The But developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -20,25 +19,37 @@ class UniValue; static const int MIN_PRIVATESEND_SESSIONS = 1; static const int MIN_PRIVATESEND_ROUNDS = 2; static const int MIN_PRIVATESEND_AMOUNT = 2; -static const int MIN_PRIVATESEND_DENOMS = 10; +static const int MIN_PRIVATESEND_DENOMS_GOAL = 10; +static const int MIN_PRIVATESEND_DENOMS_HARDCAP = 10; static const int MAX_PRIVATESEND_SESSIONS = 10; static const int MAX_PRIVATESEND_ROUNDS = 16; -static const int MAX_PRIVATESEND_DENOMS = 100000; -static const int MAX_PRIVATESEND_AMOUNT = 1000000; +static const int MAX_PRIVATESEND_DENOMS_GOAL = 100000; +static const int MAX_PRIVATESEND_DENOMS_HARDCAP = 100000; +static const int MAX_PRIVATESEND_AMOUNT = MAX_MONEY / COIN; static const int DEFAULT_PRIVATESEND_SESSIONS = 4; static const int DEFAULT_PRIVATESEND_ROUNDS = 4; static const int DEFAULT_PRIVATESEND_AMOUNT = 1000; -static const int DEFAULT_PRIVATESEND_DENOMS = 300; +static const int DEFAULT_PRIVATESEND_DENOMS_GOAL = 50; +static const int DEFAULT_PRIVATESEND_DENOMS_HARDCAP = 300; static const bool DEFAULT_PRIVATESEND_AUTOSTART = false; static const bool DEFAULT_PRIVATESEND_MULTISESSION = false; +// How many new denom outputs to create before we consider the "goal" loop in CreateDenominated +// a final one and start creating an actual tx. Same limit applies for the "hard cap" part of the algo. +// NOTE: We do not allow txes larger than 100kB, so we have to limit the number of outputs here. +// We still want to create a lot of outputs though. +// Knowing that each CTxOut is ~35b big, 400 outputs should take 400 x ~35b = ~17.5kb. +// More than 500 outputs starts to make qt quite laggy. +// Additionally to need all 500 outputs (assuming a max per denom of 50) you'd need to be trying to +// create denominations for over 3000 butk! +static const int PRIVATESEND_DENOM_OUTPUTS_THRESHOLD = 500; + // Warn user if mixing in gui or try to create backup if mixing in daemon mode // when we have only this many keys left static const int PRIVATESEND_KEYS_THRESHOLD_WARNING = 100; // Stop mixing completely, it's too dangerous to continue when we have only this many keys left static const int PRIVATESEND_KEYS_THRESHOLD_STOP = 50; - // Pseudorandomly mix up to this many times in addition to base round count static const int PRIVATESEND_RANDOM_ROUNDS = 3; @@ -102,12 +113,12 @@ class CPrivateSendClientSession : public CPrivateSendBaseSession CKeyHolderStorage keyHolderStorage; // storage for keys used in PrepareDenominate /// Create denominations - bool CreateDenominated(CAmount nBalanceToDenominate, CConnman& connman); - bool CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman); + bool CreateDenominated(CAmount nBalanceToDenominate); + bool CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals); /// Split up large inputs or make fee sized inputs - bool MakeCollateralAmounts(CConnman& connman); - bool MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated, CConnman& connman); + bool MakeCollateralAmounts(); + bool MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated); bool JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman); bool StartNewQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman); @@ -119,13 +130,11 @@ class CPrivateSendClientSession : public CPrivateSendBaseSession /// step 2: send denominated inputs and outputs prepared in step 1 bool SendDenominate(const std::vector >& vecPSInOutPairsIn, CConnman& connman); - /// Get Smartnode updates about the progress of mixing - bool CheckPoolStateUpdate(CPrivateSendStatusUpdate psssup); + /// Process Smartnode updates about the progress of mixing + void ProcessPoolStateUpdate(CPrivateSendStatusUpdate psssup); // Set the 'state' value, with some logging and capturing when the state changed void SetState(PoolState nStateNew); - /// Check for process - void CheckPool(); void CompletedTransaction(PoolMessage nMessageID); /// As a client, check and sign the final transaction @@ -178,8 +187,6 @@ class CPrivateSendClientManager : public CPrivateSendBaseManager // Keep track of the used Smartnodes std::vector vecSmartnodesUsed; - std::vector vecDenominationsSkipped; - // TODO: or map ?? std::deque deqSessions; mutable CCriticalSection cs_deqsessions; @@ -201,17 +208,17 @@ class CPrivateSendClientManager : public CPrivateSendBaseManager int nPrivateSendRounds; int nPrivateSendRandomRounds; int nPrivateSendAmount; - int nPrivateSendDenoms; + int nPrivateSendDenomsGoal; + int nPrivateSendDenomsHardCap; bool fEnablePrivateSend; bool fPrivateSendRunning; bool fPrivateSendMultiSession; - int nCachedNumBlocks; //used for the overview screen - bool fCreateAutoBackups; //builtin support for automatic backups + int nCachedNumBlocks; // used for the overview screen + bool fCreateAutoBackups; // builtin support for automatic backups CPrivateSendClientManager() : vecSmartnodesUsed(), - vecDenominationsSkipped(), deqSessions(), nCachedLastSuccessBlock(0), nMinBlocksToWait(1), @@ -220,7 +227,8 @@ class CPrivateSendClientManager : public CPrivateSendBaseManager nPrivateSendRounds(DEFAULT_PRIVATESEND_ROUNDS), nPrivateSendRandomRounds(PRIVATESEND_RANDOM_ROUNDS), nPrivateSendAmount(DEFAULT_PRIVATESEND_AMOUNT), - nPrivateSendDenoms(DEFAULT_PRIVATESEND_DENOMS), + nPrivateSendDenomsGoal(DEFAULT_PRIVATESEND_DENOMS_GOAL), + nPrivateSendDenomsHardCap(DEFAULT_PRIVATESEND_DENOMS_HARDCAP), fEnablePrivateSend(false), fPrivateSendRunning(false), fPrivateSendMultiSession(DEFAULT_PRIVATESEND_MULTISESSION), @@ -231,10 +239,6 @@ class CPrivateSendClientManager : public CPrivateSendBaseManager void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); - bool IsDenomSkipped(const CAmount& nDenomValue); - void AddSkippedDenom(const CAmount& nDenomValue); - void RemoveSkippedDenom(const CAmount& nDenomValue); - void ResetPool(); std::string GetStatuses(); diff --git a/src/privatesend/privatesend-server.cpp b/src/privatesend/privatesend-server.cpp index e51f55640..85d9fcca9 100644 --- a/src/privatesend/privatesend-server.cpp +++ b/src/privatesend/privatesend-server.cpp @@ -1,5 +1,4 @@ // Copyright (c) 2014-2020 The Dash Core developers -// Copyright (c) 2020 The But developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -28,13 +27,14 @@ CPrivateSendServer privateSendServer; void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) { if (!fSmartnodeMode) return; - if (fLiteMode) return; // ignore all But related functionality if (!smartnodeSync.IsBlockchainSynced()) return; if (strCommand == NetMsgType::DSACCEPT) { if (pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint(BCLog::PRIVATESEND, "DSACCEPT -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, + REJECT_OBSOLETE, strprintf( + "Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); PushStatus(pfrom, STATUS_REJECTED, ERR_VERSION, connman); return; } @@ -49,7 +49,7 @@ void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strComm CPrivateSendAccept dsa; vRecv >> dsa; - LogPrint(BCLog::PRIVATESEND, "DSACCEPT -- nDenom %d (%s) txCollateral %s", dsa.nDenom, CPrivateSend::DenominationToString(dsa.nDenom), dsa.txCollateral.ToString()); + LogPrint(BCLog::PRIVATESEND, "DSACCEPT -- nDenom %d (%s) txCollateral %s", dsa.nDenom, CPrivateSend::DenominationToString(dsa.nDenom), dsa.txCollateral.ToString()); auto mnList = deterministicMNManager->GetListAtChainTip(); auto dmn = mnList.GetValidMNByCollateral(activeSmartnodeInfo.outpoint); @@ -103,7 +103,9 @@ void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strComm } else if (strCommand == NetMsgType::DSQUEUE) { if (pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint(BCLog::PRIVATESEND, "DSQUEUE -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, + REJECT_OBSOLETE, strprintf( + "Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); return; } @@ -163,7 +165,9 @@ void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strComm } else if (strCommand == NetMsgType::DSVIN) { if (pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint(BCLog::PRIVATESEND, "DSVIN -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, + REJECT_OBSOLETE, strprintf( + "Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); PushStatus(pfrom, STATUS_REJECTED, ERR_VERSION, connman); return; } @@ -194,7 +198,9 @@ void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strComm } else if (strCommand == NetMsgType::DSSIGNFINALTX) { if (pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint(BCLog::PRIVATESEND, "DSSIGNFINALTX -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf("Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, + REJECT_OBSOLETE, strprintf( + "Version must be %d or greater", MIN_PRIVATESEND_PEER_PROTO_VERSION))); return; } @@ -224,7 +230,6 @@ void CPrivateSendServer::SetNull() { // MN side vecSessionCollaterals.clear(); - nSessionMaxParticipants = 0; CPrivateSendBaseSession::SetNull(); CPrivateSendBaseManager::SetNull(); @@ -239,13 +244,24 @@ void CPrivateSendServer::CheckPool(CConnman& connman) LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CheckPool -- entries count %lu\n", GetEntriesCount()); - // If entries are full, create finalized transaction - if (nState == POOL_STATE_ACCEPTING_ENTRIES && GetEntriesCount() >= nSessionMaxParticipants) { + // If we have an entry for each collateral, then create final tx + if (nState == POOL_STATE_ACCEPTING_ENTRIES && GetEntriesCount() == vecSessionCollaterals.size()) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CheckPool -- FINALIZE TRANSACTIONS\n"); CreateFinalTransaction(connman); return; } + // Check for Time Out + // If we timed out while accepting entries, then if we have more than minimum, create final tx + if (nState == POOL_STATE_ACCEPTING_ENTRIES && CPrivateSendServer::HasTimedOut() + && GetEntriesCount() >= CPrivateSend::GetMinPoolParticipants()) { + // Punish misbehaving participants + ChargeFees(connman); + // Try to complete this session ignoring the misbehaving ones + CreateFinalTransaction(connman); + return; + } + // If we have all of the signatures, try to compile the transaction if (nState == POOL_STATE_SIGNING && IsSignaturesComplete()) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CheckPool -- SIGNING\n"); @@ -295,7 +311,7 @@ void CPrivateSendServer::CommitFinalTransaction(CConnman& connman) TRY_LOCK(cs_main, lockMain); CValidationState validationState; mempool.PrioritiseTransaction(hashTx, 0.1 * COIN); - if (!lockMain || !AcceptToMemoryPool(mempool, validationState, finalTransaction, false, nullptr, false, maxTxFee)) { + if (!lockMain || !AcceptToMemoryPool(mempool, validationState, finalTransaction, false, nullptr /* pfMissingInputs */, false /* bypass_limits */, maxTxFee /* nAbsurdFee */)) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CommitFinalTransaction -- AcceptToMemoryPool() error: Transaction not valid\n"); SetNull(); // not much we can do in this case, just notify clients @@ -384,10 +400,10 @@ void CPrivateSendServer::ChargeFees(CConnman& connman) if (vecOffendersCollaterals.empty()) return; //mostly offending? Charge sometimes - if ((int)vecOffendersCollaterals.size() >= nSessionMaxParticipants - 1 && GetRandInt(100) > 33) return; + if ((int)vecOffendersCollaterals.size() >= vecSessionCollaterals.size() - 1 && GetRandInt(100) > 33) return; //everyone is an offender? That's not right - if ((int)vecOffendersCollaterals.size() >= nSessionMaxParticipants) return; + if ((int)vecOffendersCollaterals.size() >= vecSessionCollaterals.size()) return; //charge one of the offenders randomly std::random_shuffle(vecOffendersCollaterals.begin(), vecOffendersCollaterals.end()); @@ -407,7 +423,7 @@ void CPrivateSendServer::ChargeFees(CConnman& connman) Being that mixing has "no fees" we need to have some kind of cost associated with using it to stop abuse. Otherwise it could serve as an attack vector and - allow endless transaction that would bloat But and make it unusable. To + allow endless transaction that would bloat Dash and make it unusable. To stop these kinds of attacks 1 in 10 successful transactions are charged. This adds up to a cost of 0.001DRK per transaction on average. */ @@ -426,7 +442,7 @@ void CPrivateSendServer::ConsumeCollateral(CConnman& connman, const CTransaction { LOCK(cs_main); CValidationState validationState; - if (!AcceptToMemoryPool(mempool, validationState, txref, false, nullptr)) { + if (!AcceptToMemoryPool(mempool, validationState, txref, false, nullptr /* pfMissingInputs */, false /* bypass_limits */, 0 /* nAbsurdFee */)) { LogPrint(BCLog::PRIVATESEND, "%s -- AcceptToMemoryPool failed\n", __func__); } else { connman.RelayTransaction(*txref); @@ -434,8 +450,19 @@ void CPrivateSendServer::ConsumeCollateral(CConnman& connman, const CTransaction } } +bool CPrivateSendServer::HasTimedOut() +{ + if (!fSmartnodeMode) return false; + + if (nState == POOL_STATE_IDLE) return false; + + int nTimeout = (nState == POOL_STATE_SIGNING) ? PRIVATESEND_SIGNING_TIMEOUT : PRIVATESEND_QUEUE_TIMEOUT; + + return GetTime() - nTimeLastSuccessfulStep >= nTimeout; +} + // -// Check for various timeouts (queue objects, mixing, etc) +// Check for extraneous timeout // void CPrivateSendServer::CheckTimeout(CConnman& connman) { @@ -443,36 +470,11 @@ void CPrivateSendServer::CheckTimeout(CConnman& connman) CheckQueue(); - if (nState == POOL_STATE_IDLE) return; - - int nTimeout = (nState == POOL_STATE_SIGNING) ? PRIVATESEND_SIGNING_TIMEOUT : PRIVATESEND_QUEUE_TIMEOUT; - bool fTimeout = GetTime() - nTimeLastSuccessfulStep >= nTimeout; - // Too early to do anything - if (!fTimeout) return; - - // See if we have at least min number of participants, if so - we can still do smth - if (nState == POOL_STATE_QUEUE && vecSessionCollaterals.size() >= CPrivateSend::GetMinPoolParticipants()) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CheckTimeout -- Queue for %d participants timed out (%ds) -- falling back to %d participants\n", - nSessionMaxParticipants, nTimeout, vecSessionCollaterals.size()); - nSessionMaxParticipants = vecSessionCollaterals.size(); - return; - } - - if (nState == POOL_STATE_ACCEPTING_ENTRIES && GetEntriesCount() >= CPrivateSend::GetMinPoolParticipants()) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CheckTimeout -- Accepting entries for %d participants timed out (%ds) -- falling back to %d participants\n", - nSessionMaxParticipants, nTimeout, GetEntriesCount()); - // Punish misbehaving participants - ChargeFees(connman); - // Try to complete this session ignoring the misbehaving ones - nSessionMaxParticipants = GetEntriesCount(); - CheckPool(connman); - return; - } + if (!CPrivateSendServer::HasTimedOut()) return; - // All other cases - LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CheckTimeout -- %s timed out (%ds) -- resetting\n", - (nState == POOL_STATE_SIGNING) ? "Signing" : "Session", nTimeout); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CheckTimeout -- %s timed out -- resetting\n", + (nState == POOL_STATE_SIGNING) ? "Signing" : "Session"); ChargeFees(connman); SetNull(); } @@ -490,7 +492,8 @@ void CPrivateSendServer::CheckForCompleteQueue(CConnman& connman) SetState(POOL_STATE_ACCEPTING_ENTRIES); CPrivateSendQueue dsq(nSessionDenom, activeSmartnodeInfo.outpoint, GetAdjustedTime(), true); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CheckForCompleteQueue -- queue is ready, signing and relaying (%s)\n", dsq.ToString()); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CheckForCompleteQueue -- queue is ready, signing and relaying (%s) " + "with %d participants\n", dsq.ToString(), vecSessionCollaterals.size()); dsq.Sign(); dsq.Relay(connman); } @@ -525,7 +528,7 @@ bool CPrivateSendServer::IsInputScriptSigValid(const CTxIn& txin) if (nTxInIndex >= 0) { //might have to do this one input at a time? txNew.vin[nTxInIndex].scriptSig = txin.scriptSig; LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::IsInputScriptSigValid -- verifying scriptSig %s\n", ScriptToAsmStr(txin.scriptSig).substr(0, 24)); - // TODO we're using amount=0 here but we should use the correct amount. This works because But ignores the amount while signing/verifying (only used in Bitcoin/Segwit) + // TODO we're using amount=0 here but we should use the correct amount. This works because Dash ignores the amount while signing/verifying (only used in Bitcoin/Segwit) if (!VerifyScript(txNew.vin[nTxInIndex].scriptSig, sigPubKey, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, MutableTransactionSignatureChecker(&txNew, nTxInIndex, 0))) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::IsInputScriptSigValid -- VerifyScript() failed on input %d\n", nTxInIndex); return false; @@ -546,7 +549,7 @@ bool CPrivateSendServer::AddEntry(CConnman& connman, const CPrivateSendEntry& en { if (!fSmartnodeMode) return false; - if (GetEntriesCount() >= nSessionMaxParticipants) { + if (GetEntriesCount() >= vecSessionCollaterals.size()) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- ERROR: entries is full!\n", __func__); nMessageIDRet = ERR_ENTRIES_FULL; return false; @@ -595,7 +598,7 @@ bool CPrivateSendServer::AddEntry(CConnman& connman, const CPrivateSendEntry& en vecEntries.push_back(entry); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- adding entry %d of %d required\n", __func__, GetEntriesCount(), nSessionMaxParticipants); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- adding entry %d of %d required\n", __func__, GetEntriesCount(), CPrivateSend::GetMaxPoolParticipants()); nMessageIDRet = MSG_ENTRIES_ADDED; return true; @@ -654,7 +657,7 @@ bool CPrivateSendServer::IsAcceptableDSA(const CPrivateSendAccept& dsa, PoolMess { if (!fSmartnodeMode) return false; - // is denom even smth legit? + // is denom even something legit? if (!CPrivateSend::IsValidDenomination(dsa.nDenom)) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- denom not valid!\n", __func__); nMessageIDRet = ERR_DENOM; @@ -690,7 +693,6 @@ bool CPrivateSendServer::CreateNewSession(const CPrivateSendAccept& dsa, PoolMes nMessageIDRet = MSG_NOERR; nSessionID = GetRandInt(999999) + 1; nSessionDenom = dsa.nDenom; - nSessionMaxParticipants = CPrivateSend::GetMinPoolParticipants() + GetRandInt(CPrivateSend::GetMaxPoolParticipants() - CPrivateSend::GetMinPoolParticipants() + 1); SetState(POOL_STATE_QUEUE); @@ -704,8 +706,8 @@ bool CPrivateSendServer::CreateNewSession(const CPrivateSendAccept& dsa, PoolMes } vecSessionCollaterals.push_back(MakeTransactionRef(dsa.txCollateral)); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CreateNewSession -- new session created, nSessionID: %d nSessionDenom: %d (%s) vecSessionCollaterals.size(): %d nSessionMaxParticipants: %d\n", - nSessionID, nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom), vecSessionCollaterals.size(), CPrivateSend::GetMaxPoolParticipants()); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::CreateNewSession -- new session created, nSessionID: %d nSessionDenom: %d (%s) vecSessionCollaterals.size(): %d CPrivateSend::GetMaxPoolParticipants(): %d\n", + nSessionID, nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom), vecSessionCollaterals.size(), CPrivateSend::GetMaxPoolParticipants()); return true; } @@ -727,7 +729,7 @@ bool CPrivateSendServer::AddUserToExistingSession(const CPrivateSendAccept& dsa, if (dsa.nDenom != nSessionDenom) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::AddUserToExistingSession -- incompatible denom %d (%s) != nSessionDenom %d (%s)\n", - dsa.nDenom, CPrivateSend::DenominationToString(dsa.nDenom), nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom)); + dsa.nDenom, CPrivateSend::DenominationToString(dsa.nDenom), nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom)); nMessageIDRet = ERR_DENOM; return false; } @@ -737,21 +739,33 @@ bool CPrivateSendServer::AddUserToExistingSession(const CPrivateSendAccept& dsa, nMessageIDRet = MSG_NOERR; vecSessionCollaterals.push_back(MakeTransactionRef(dsa.txCollateral)); - LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::AddUserToExistingSession -- new user accepted, nSessionID: %d nSessionDenom: %d (%s) vecSessionCollaterals.size(): %d nSessionMaxParticipants: %d\n", - nSessionID, nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom), vecSessionCollaterals.size(), CPrivateSend::GetMaxPoolParticipants()); + LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::AddUserToExistingSession -- new user accepted, nSessionID: %d nSessionDenom: %d (%s) vecSessionCollaterals.size(): %d CPrivateSend::GetMaxPoolParticipants(): %d\n", + nSessionID, nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom), vecSessionCollaterals.size(), CPrivateSend::GetMaxPoolParticipants()); return true; } +// Returns true if either max size has been reached or if the mix timed out and min size was reached bool CPrivateSendServer::IsSessionReady() { - return nSessionMaxParticipants != 0 && (int)vecSessionCollaterals.size() >= nSessionMaxParticipants; + if (nState == POOL_STATE_QUEUE) { + if ((int)vecSessionCollaterals.size() >= CPrivateSend::GetMaxPoolParticipants()) { + return true; + } + if (CPrivateSendServer::HasTimedOut() && (int)vecSessionCollaterals.size() >= CPrivateSend::GetMinPoolParticipants()) { + return true; + } + } + if (nState == POOL_STATE_ACCEPTING_ENTRIES) { + return true; + } + return false; } void CPrivateSendServer::RelayFinalTransaction(const CTransaction& txFinal, CConnman& connman) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- nSessionID: %d nSessionDenom: %d (%s)\n", - __func__, nSessionID, nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom)); + __func__, nSessionID, nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom)); // final mixing tx with empty signatures should be relayed to mixing participants only for (const auto& entry : vecEntries) { @@ -792,7 +806,7 @@ void CPrivateSendServer::RelayStatus(PoolStatusUpdate nStatusUpdate, CConnman& c } if (nDisconnected == 0) return; // all is clear - // smth went wrong + // something went wrong LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- can't continue, %llu client(s) disconnected, nSessionID: %d nSessionDenom: %d (%s)\n", __func__, nDisconnected, nSessionID, nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom)); @@ -814,7 +828,7 @@ void CPrivateSendServer::RelayStatus(PoolStatusUpdate nStatusUpdate, CConnman& c void CPrivateSendServer::RelayCompletedTransaction(PoolMessage nMessageID, CConnman& connman) { LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::%s -- nSessionID: %d nSessionDenom: %d (%s)\n", - __func__, nSessionID, nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom)); + __func__, nSessionID, nSessionDenom, CPrivateSend::DenominationToString(nSessionDenom)); // final mixing tx with empty signatures should be relayed to mixing participants only for (const auto& entry : vecEntries) { @@ -835,8 +849,8 @@ void CPrivateSendServer::SetState(PoolState nStateNew) { if (!fSmartnodeMode) return; - if (nStateNew == POOL_STATE_ERROR || nStateNew == POOL_STATE_SUCCESS) { - LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::SetState -- Can't set state to ERROR or SUCCESS as a Smartnode. \n"); + if (nStateNew == POOL_STATE_ERROR) { + LogPrint(BCLog::PRIVATESEND, "CPrivateSendServer::SetState -- Can't set state to ERROR as a Smartnode. \n"); return; } @@ -847,13 +861,13 @@ void CPrivateSendServer::SetState(PoolState nStateNew) void CPrivateSendServer::DoMaintenance(CConnman& connman) { - if (fLiteMode) return; // disable all But specific functionality if (!fSmartnodeMode) return; // only run on smartnodes if (!smartnodeSync.IsBlockchainSynced() || ShutdownRequested()) return; - privateSendServer.CheckTimeout(connman); privateSendServer.CheckForCompleteQueue(connman); + privateSendServer.CheckPool(connman); + privateSendServer.CheckTimeout(connman); } void CPrivateSendServer::GetJsonInfo(UniValue& obj) const @@ -861,8 +875,7 @@ void CPrivateSendServer::GetJsonInfo(UniValue& obj) const obj.clear(); obj.setObject(); obj.push_back(Pair("queue_size", GetQueueSize())); - CAmount amount{0}; - obj.push_back(Pair("denomination", ValueFromAmount(CPrivateSend::DenominationToAmount(nSessionDenom)))); + obj.push_back(Pair("denomination", ValueFromAmount(CPrivateSend::DenominationToAmount(nSessionDenom)))); obj.push_back(Pair("state", GetStateString())); obj.push_back(Pair("entries_count", GetEntriesCount())); } diff --git a/src/privatesend/privatesend-server.h b/src/privatesend/privatesend-server.h index 331b69fbd..9b9b84ee5 100644 --- a/src/privatesend/privatesend-server.h +++ b/src/privatesend/privatesend-server.h @@ -1,5 +1,4 @@ // Copyright (c) 2014-2020 The Dash Core developers -// Copyright (c) 2020 The But developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -24,9 +23,6 @@ class CPrivateSendServer : public CPrivateSendBaseSession, public CPrivateSendBa // to behave honestly. If they don't it takes their money. std::vector vecSessionCollaterals; - // Maximum number of participants in a certain session, random between min and max. - int nSessionMaxParticipants; - bool fUnitTest; /// Add a clients entry to the pool @@ -73,11 +69,11 @@ class CPrivateSendServer : public CPrivateSendBaseSession, public CPrivateSendBa public: CPrivateSendServer() : vecSessionCollaterals(), - nSessionMaxParticipants(0), fUnitTest(false) {} void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); + bool HasTimedOut(); void CheckTimeout(CConnman& connman); void CheckForCompleteQueue(CConnman& connman); diff --git a/src/privatesend/privatesend-util.cpp b/src/privatesend/privatesend-util.cpp index daeaf711a..f6d19e431 100644 --- a/src/privatesend/privatesend-util.cpp +++ b/src/privatesend/privatesend-util.cpp @@ -1,9 +1,20 @@ // Copyright (c) 2014-2019 The Dash Core developers -// Copyright (c) 2020 The But developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include +#include +#include #include +#include