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