From 2bbac8ff77fbe5ca301ecdf4b1ffc20c16df3202 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Feb 2019 07:13:18 +0100 Subject: [PATCH 01/33] Introduce NotifyChainLock signal and invoke it when CLSIGs get processed --- src/llmq/quorums_chainlocks.cpp | 10 ++++++++++ src/llmq/quorums_chainlocks.h | 1 + src/validationinterface.cpp | 3 +++ src/validationinterface.h | 3 +++ 4 files changed, 17 insertions(+) diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index 94f251117d058..5a51f98e9ca7e 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -149,6 +149,11 @@ void CChainLocksHandler::ProcessNewChainLock(NodeId from, const llmq::CChainLock LogPrintf("CChainLocksHandler::%s -- processed new CLSIG (%s), peer=%d\n", __func__, clsig.ToString(), from); + + if (lastNotifyChainLockBlockIndex != bestChainLockBlockIndex) { + lastNotifyChainLockBlockIndex = bestChainLockBlockIndex; + GetMainSignals().NotifyChainLock(bestChainLockBlockIndex); + } } void CChainLocksHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew) @@ -204,6 +209,11 @@ void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBl if (bestChainLockBlockIndex == pindexNew) { // we first got the CLSIG, then the header, and then the block was connected. // In this case there is no need to continue here. + // However, NotifyChainLock might not have been called yet, so call it now if needed + if (lastNotifyChainLockBlockIndex != bestChainLockBlockIndex) { + lastNotifyChainLockBlockIndex = bestChainLockBlockIndex; + GetMainSignals().NotifyChainLock(bestChainLockBlockIndex); + } return; } diff --git a/src/llmq/quorums_chainlocks.h b/src/llmq/quorums_chainlocks.h index 31065a478f9d7..b7cd441e040a3 100644 --- a/src/llmq/quorums_chainlocks.h +++ b/src/llmq/quorums_chainlocks.h @@ -55,6 +55,7 @@ class CChainLocksHandler : public CRecoveredSigsListener CChainLockSig bestChainLockWithKnownBlock; const CBlockIndex* bestChainLockBlockIndex{nullptr}; + const CBlockIndex* lastNotifyChainLockBlockIndex{nullptr}; int32_t lastSignedHeight{-1}; uint256 lastSignedRequestId; diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 86f374ec16a12..dc928f1b428b6 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -18,6 +18,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) { g_signals.UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); g_signals.SyncTransaction.connect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3)); g_signals.NotifyTransactionLock.connect(boost::bind(&CValidationInterface::NotifyTransactionLock, pwalletIn, _1)); + g_signals.NotifyChainLock.connect(boost::bind(&CValidationInterface::NotifyChainLock, pwalletIn, _1)); g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1)); g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); g_signals.Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1)); @@ -40,6 +41,7 @@ void UnregisterValidationInterface(CValidationInterface* pwalletIn) { g_signals.Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1)); g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1)); + g_signals.NotifyChainLock.disconnect(boost::bind(&CValidationInterface::NotifyChainLock, pwalletIn, _1)); g_signals.NotifyTransactionLock.disconnect(boost::bind(&CValidationInterface::NotifyTransactionLock, pwalletIn, _1)); g_signals.SyncTransaction.disconnect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3)); g_signals.UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); @@ -61,6 +63,7 @@ void UnregisterAllValidationInterfaces() { g_signals.SetBestChain.disconnect_all_slots(); g_signals.UpdatedTransaction.disconnect_all_slots(); g_signals.NotifyTransactionLock.disconnect_all_slots(); + g_signals.NotifyChainLock.disconnect_all_slots(); g_signals.SyncTransaction.disconnect_all_slots(); g_signals.UpdatedBlockTip.disconnect_all_slots(); g_signals.NewPoWValidBlock.disconnect_all_slots(); diff --git a/src/validationinterface.h b/src/validationinterface.h index f28144ed9724e..5ad8e6ac773b1 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -39,6 +39,7 @@ class CValidationInterface { virtual void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {} virtual void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) {} virtual void NotifyTransactionLock(const CTransaction &tx) {} + virtual void NotifyChainLock(const CBlockIndex* pindex) {} virtual void NotifyGovernanceVote(const CGovernanceVote &vote) {} virtual void NotifyGovernanceObject(const CGovernanceObject &object) {} virtual void NotifyInstantSendDoubleSpendAttempt(const CTransaction ¤tTx, const CTransaction &previousTx) {} @@ -76,6 +77,8 @@ struct CMainSignals { boost::signals2::signal SyncTransaction; /** Notifies listeners of an updated transaction lock without new data. */ boost::signals2::signal NotifyTransactionLock; + /** Notifies listeners of a ChainLock. */ + boost::signals2::signal NotifyChainLock; /** Notifies listeners of a new governance vote. */ boost::signals2::signal NotifyGovernanceVote; /** Notifies listeners of a new governance object. */ From 5bbc1227490b562ff5fb88e795f57323c3401c0f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 26 Feb 2019 07:46:04 +0100 Subject: [PATCH 02/33] Implement PushReconstructedRecoveredSig in CSigningManager We can reconstruct recovered sigs from other P2P messages to avoid re-validation of those. We will do this later in InstantSend code. --- src/llmq/quorums_signing.cpp | 20 ++++++++++++++++++++ src/llmq/quorums_signing.h | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/src/llmq/quorums_signing.cpp b/src/llmq/quorums_signing.cpp index 2239d364da031..fc394533c49c0 100644 --- a/src/llmq/quorums_signing.cpp +++ b/src/llmq/quorums_signing.cpp @@ -440,11 +440,25 @@ void CSigningManager::CollectPendingRecoveredSigsToVerify( } } +bool CSigningManager::ProcessPendingReconstructedRecoveredSigs() +{ + decltype(pendingReconstructedRecoveredSigs) l; + { + LOCK(cs); + l = std::move(pendingReconstructedRecoveredSigs); + } + for (auto& p : l) { + ProcessRecoveredSig(-1, p.first, p.second, *g_connman); + } +} + bool CSigningManager::ProcessPendingRecoveredSigs(CConnman& connman) { std::unordered_map> recSigsByNode; std::unordered_map, CQuorumCPtr, StaticSaltedHasher> quorums; + ProcessPendingReconstructedRecoveredSigs(); + CollectPendingRecoveredSigsToVerify(32, recSigsByNode, quorums); if (recSigsByNode.empty()) { return false; @@ -550,6 +564,12 @@ void CSigningManager::ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& re } } +void CSigningManager::PushReconstructedRecoveredSig(const llmq::CRecoveredSig& recoveredSig, const llmq::CQuorumCPtr& quorum) +{ + LOCK(cs); + pendingReconstructedRecoveredSigs.emplace_back(recoveredSig, quorum); +} + void CSigningManager::Cleanup() { int64_t now = GetTimeMillis(); diff --git a/src/llmq/quorums_signing.h b/src/llmq/quorums_signing.h index 1d002435159f1..6a7325de61399 100644 --- a/src/llmq/quorums_signing.h +++ b/src/llmq/quorums_signing.h @@ -126,6 +126,7 @@ class CSigningManager // Incoming and not verified yet std::unordered_map> pendingRecoveredSigs; + std::list> pendingReconstructedRecoveredSigs; // must be protected by cs FastRandomContext rnd; @@ -142,6 +143,10 @@ class CSigningManager void ProcessMessage(CNode* pnode, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); + // This is called when a recovered signature was was reconstructed from another P2P message and is known to be valid + // This is the case for example when a signature appears as part of InstantSend or ChainLocks + void PushReconstructedRecoveredSig(const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum); + private: void ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredSig& recoveredSig, CConnman& connman); bool PreVerifyRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, bool& retBan); @@ -149,6 +154,7 @@ class CSigningManager void CollectPendingRecoveredSigsToVerify(size_t maxUniqueSessions, std::unordered_map>& retSigShares, std::unordered_map, CQuorumCPtr, StaticSaltedHasher>& retQuorums); + bool ProcessPendingReconstructedRecoveredSigs(); bool ProcessPendingRecoveredSigs(CConnman& connman); // called from the worker thread of CSigSharesManager void ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum, CConnman& connman); void Cleanup(); // called from the worker thread of CSigSharesManager From 83dbcc483f38d6c481c90791e153a8ac3cda6cd7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 21 Sep 2018 16:01:04 +0200 Subject: [PATCH 03/33] Implement CInstantSendManager and related P2P messages --- src/Makefile.am | 2 + src/chainparams.cpp | 2 + src/consensus/params.h | 1 + src/dsnotificationinterface.cpp | 7 + src/dsnotificationinterface.h | 1 + src/llmq/quorums_init.cpp | 10 + src/llmq/quorums_instantsend.cpp | 883 +++++++++++++++++++++++++++++++ src/llmq/quorums_instantsend.h | 149 ++++++ src/net_processing.cpp | 15 + src/protocol.cpp | 3 + src/protocol.h | 2 + 11 files changed, 1075 insertions(+) create mode 100644 src/llmq/quorums_instantsend.cpp create mode 100644 src/llmq/quorums_instantsend.h diff --git a/src/Makefile.am b/src/Makefile.am index 04a5efa3a8e74..e87247f8b49f4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -173,6 +173,7 @@ BITCOIN_CORE_H = \ llmq/quorums_dkgsessionmgr.h \ llmq/quorums_dkgsession.h \ llmq/quorums_init.h \ + llmq/quorums_instantsend.h \ llmq/quorums_signing.h \ llmq/quorums_signing_shares.h \ llmq/quorums_utils.h \ @@ -291,6 +292,7 @@ libdash_server_a_SOURCES = \ llmq/quorums_dkgsessionmgr.cpp \ llmq/quorums_dkgsession.cpp \ llmq/quorums_init.cpp \ + llmq/quorums_instantsend.cpp \ llmq/quorums_signing.cpp \ llmq/quorums_signing_shares.cpp \ llmq/quorums_utils.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 2cc5aafb2f888..314584f7d7885 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -623,6 +623,7 @@ class CDevNetParams : public CChainParams { consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60; consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85; consensus.llmqChainLocks = Consensus::LLMQ_50_60; + consensus.llmqForInstantSend = Consensus::LLMQ_50_60; fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; @@ -787,6 +788,7 @@ class CRegTestParams : public CChainParams { consensus.llmqs[Consensus::LLMQ_10_60] = llmq10_60; consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60; consensus.llmqChainLocks = Consensus::LLMQ_10_60; + consensus.llmqForInstantSend = Consensus::LLMQ_10_60; } void UpdateBIP9Parameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThreshold) diff --git a/src/consensus/params.h b/src/consensus/params.h index 083cbacfa20cc..b2efd4ca755c6 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -174,6 +174,7 @@ struct Params { std::map llmqs; LLMQType llmqChainLocks; + LLMQType llmqForInstantSend{LLMQ_NONE}; }; } // namespace Consensus diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index 9eeac0218ef54..954de50d0bae4 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -18,6 +18,7 @@ #include "llmq/quorums.h" #include "llmq/quorums_chainlocks.h" +#include "llmq/quorums_instantsend.h" #include "llmq/quorums_dkgsessionmgr.h" void CDSNotificationInterface::InitializeCurrentBlockTip() @@ -72,6 +73,7 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con void CDSNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) { + llmq::quorumInstantSendManager->SyncTransaction(tx, pindex, posInBlock); instantsend.SyncTransaction(tx, pindex, posInBlock); CPrivateSend::SyncTransaction(tx, pindex, posInBlock); } @@ -82,3 +84,8 @@ void CDSNotificationInterface::NotifyMasternodeListChanged(const CDeterministicM governance.CheckMasternodeOrphanVotes(connman); governance.UpdateCachesAndClean(); } + +void CDSNotificationInterface::NotifyChainLock(const CBlockIndex* pindex) +{ + llmq::quorumInstantSendManager->NotifyChainLock(pindex); +} diff --git a/src/dsnotificationinterface.h b/src/dsnotificationinterface.h index d800289d12154..f4e97d7ed5c6f 100644 --- a/src/dsnotificationinterface.h +++ b/src/dsnotificationinterface.h @@ -23,6 +23,7 @@ class CDSNotificationInterface : public CValidationInterface void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) override; void NotifyMasternodeListChanged(const CDeterministicMNList& newList) override; + void NotifyChainLock(const CBlockIndex* pindex); private: CConnman& connman; diff --git a/src/llmq/quorums_init.cpp b/src/llmq/quorums_init.cpp index ec03b46403e9b..a2620683b6fc8 100644 --- a/src/llmq/quorums_init.cpp +++ b/src/llmq/quorums_init.cpp @@ -10,6 +10,7 @@ #include "quorums_chainlocks.h" #include "quorums_debug.h" #include "quorums_dkgsessionmgr.h" +#include "quorums_instantsend.h" #include "quorums_signing.h" #include "quorums_signing_shares.h" @@ -29,10 +30,13 @@ void InitLLMQSystem(CEvoDB& evoDb, CScheduler* scheduler, bool unitTests) quorumSigSharesManager = new CSigSharesManager(); quorumSigningManager = new CSigningManager(unitTests); chainLocksHandler = new CChainLocksHandler(scheduler); + quorumInstantSendManager = new CInstantSendManager(scheduler); } void DestroyLLMQSystem() { + delete quorumInstantSendManager; + quorumInstantSendManager = nullptr; delete chainLocksHandler; chainLocksHandler = nullptr; delete quorumSigningManager; @@ -64,10 +68,16 @@ void StartLLMQSystem() if (chainLocksHandler) { chainLocksHandler->RegisterAsRecoveredSigsListener(); } + if (quorumInstantSendManager) { + quorumInstantSendManager->RegisterAsRecoveredSigsListener(); + } } void StopLLMQSystem() { + if (quorumInstantSendManager) { + quorumInstantSendManager->UnregisterAsRecoveredSigsListener(); + } if (chainLocksHandler) { chainLocksHandler->UnregisterAsRecoveredSigsListener(); } diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp new file mode 100644 index 0000000000000..737c22b29a10b --- /dev/null +++ b/src/llmq/quorums_instantsend.cpp @@ -0,0 +1,883 @@ +// Copyright (c) 2019 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "quorums_chainlocks.h" +#include "quorums_instantsend.h" +#include "quorums_utils.h" + +#include "bls/bls_batchverifier.h" +#include "chainparams.h" +#include "coins.h" +#include "txmempool.h" +#include "masternode-sync.h" +#include "net_processing.h" +#include "scheduler.h" +#include "spork.h" +#include "validation.h" +#include "wallet/wallet.h" + +// needed for nCompleteTXLocks +#include "instantx.h" + +#include + +namespace llmq +{ + +static const std::string INPUTLOCK_REQUESTID_PREFIX = "inlock"; +static const std::string IXLOCK_REQUESTID_PREFIX = "ixlock"; + +CInstantSendManager* quorumInstantSendManager; + +uint256 CInstantXLock::GetRequestId() const +{ + CHashWriter hw(SER_GETHASH, 0); + hw << IXLOCK_REQUESTID_PREFIX; + hw << inputs; + return hw.GetHash(); +} + +CInstantSendManager::CInstantSendManager(CScheduler* _scheduler) : + scheduler(_scheduler) +{ +} + +CInstantSendManager::~CInstantSendManager() +{ +} + +void CInstantSendManager::RegisterAsRecoveredSigsListener() +{ + quorumSigningManager->RegisterRecoveredSigsListener(this); +} + +void CInstantSendManager::UnregisterAsRecoveredSigsListener() +{ + quorumSigningManager->UnregisterRecoveredSigsListener(this); +} + +bool CInstantSendManager::ProcessTx(CNode* pfrom, const CTransaction& tx, CConnman& connman, const Consensus::Params& params) +{ + if (!IsNewInstantSendEnabled()) { + return true; + } + + auto llmqType = params.llmqForInstantSend; + if (llmqType == Consensus::LLMQ_NONE) { + return true; + } + if (!fMasternodeMode) { + return true; + } + + // Ignore any InstantSend messages until blockchain is synced + if (!masternodeSync.IsBlockchainSynced()) { + return true; + } + + if (IsConflicted(tx)) { + return false; + } + + if (!CheckCanLock(tx, true, params)) { + return false; + } + + std::vector ids; + ids.reserve(tx.vin.size()); + + for (const auto& in : tx.vin) { + auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); + ids.emplace_back(id); + } + + { + LOCK(cs); + size_t alreadyVotedCount = 0; + for (size_t i = 0; i < ids.size(); i++) { + auto it = inputVotes.find(ids[i]); + if (it != inputVotes.end()) { + if (it->second != tx.GetHash()) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: input %s is conflicting with ixlock %s\n", __func__, + tx.GetHash().ToString(), tx.vin[i].prevout.ToStringShort(), it->second.ToString()); + return false; + } + alreadyVotedCount++; + } + } + if (alreadyVotedCount == ids.size()) { + return true; + } + + for (auto& id : ids) { + inputVotes.emplace(id, tx.GetHash()); + } + } + + // don't even try the actual signing if any input is conflicting + for (auto& id : ids) { + if (quorumSigningManager->IsConflicting(llmqType, id, tx.GetHash())) { + return false; + } + } + for (auto& id : ids) { + quorumSigningManager->AsyncSignIfMember(llmqType, id, tx.GetHash()); + } + + // We might have received all input locks before we got the corresponding TX. In this case, we have to sign the + // ixlock now instead of waiting for the input locks. + TrySignInstantXLock(tx); + + return true; +} + +bool CInstantSendManager::CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params) +{ + int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired; + + uint256 txHash = tx.GetHash(); + CAmount nValueIn = 0; + for (const auto& in : tx.vin) { + CAmount v = 0; + if (!CheckCanLock(in.prevout, printDebug, &txHash, &v, params)) { + return false; + } + + nValueIn += v; + } + + // TODO decide if we should limit max input values. This was ok to do in the old system, but in the new system + // where we want to have all TXs locked at some point, this is counterproductive (especially when ChainLocks later + // depend on all TXs being locked first) +// CAmount maxValueIn = sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE); +// if (nValueIn > maxValueIn * COIN) { +// if (printDebug) { +// LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: TX input value too high. nValueIn=%f, maxValueIn=%d", __func__, +// tx.GetHash().ToString(), nValueIn / (double)COIN, maxValueIn); +// } +// return false; +// } + + return true; +} + +bool CInstantSendManager::CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256* _txHash, CAmount* retValue, const Consensus::Params& params) +{ + int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired; + + if (IsLocked(outpoint.hash)) { + // if prevout was ix locked, allow locking of descendants (no matter if prevout is in mempool or already mined) + return true; + } + + static uint256 txHashNull; + const uint256* txHash = &txHashNull; + if (_txHash) { + txHash = _txHash; + } + + auto mempoolTx = mempool.get(outpoint.hash); + if (mempoolTx) { + if (printDebug) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: parent mempool TX %s is not locked\n", __func__, + txHash->ToString(), outpoint.hash.ToString()); + } + return false; + } + + Coin coin; + const CBlockIndex* pindexMined = nullptr; + { + LOCK(cs_main); + if (!pcoinsTip->GetCoin(outpoint, coin) || coin.IsSpent()) { + if (printDebug) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: failed to find UTXO %s\n", __func__, + txHash->ToString(), outpoint.ToStringShort()); + } + return false; + } + pindexMined = chainActive[coin.nHeight]; + } + + int nTxAge = chainActive.Height() - coin.nHeight + 1; + // 1 less than the "send IX" gui requires, in case of a block propagating the network at the time + int nConfirmationsRequired = nInstantSendConfirmationsRequired - 1; + + if (nTxAge < nConfirmationsRequired) { + if (!llmq::chainLocksHandler->HasChainLock(pindexMined->nHeight, pindexMined->GetBlockHash())) { + if (printDebug) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nConfirmationsRequired=%d\n", __func__, + txHash->ToString(), outpoint.ToStringShort(), nTxAge, nConfirmationsRequired); + } + return false; + } + } + + if (retValue) { + *retValue = coin.out.nValue; + } + + return true; +} + +void CInstantSendManager::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) +{ + if (!IsNewInstantSendEnabled()) { + return; + } + + auto llmqType = Params().GetConsensus().llmqForInstantSend; + if (llmqType == Consensus::LLMQ_NONE) { + return; + } + auto& params = Params().GetConsensus().llmqs.at(llmqType); + + uint256 txid; + bool isInstantXLock = false; + { + LOCK(cs); + auto it = inputVotes.find(recoveredSig.id); + if (it != inputVotes.end()) { + txid = it->second; + } + if (creatingInstantXLocks.count(recoveredSig.id)) { + isInstantXLock = true; + } + } + if (!txid.IsNull()) { + HandleNewInputLockRecoveredSig(recoveredSig, txid); + } else if (isInstantXLock) { + HandleNewInstantXLockRecoveredSig(recoveredSig); + } +} + +void CInstantSendManager::HandleNewInputLockRecoveredSig(const CRecoveredSig& recoveredSig, const uint256& txid) +{ + auto llmqType = Params().GetConsensus().llmqForInstantSend; + + CTransactionRef tx; + uint256 hashBlock; + if (!GetTransaction(txid, tx, Params().GetConsensus(), hashBlock, true)) { + return; + } + + if (LogAcceptCategory("instantsend")) { + for (auto& in : tx->vin) { + auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); + if (id == recoveredSig.id) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: got recovered sig for input %s\n", __func__, + txid.ToString(), in.prevout.ToStringShort()); + break; + } + } + } + + TrySignInstantXLock(*tx); +} + +void CInstantSendManager::TrySignInstantXLock(const CTransaction& tx) +{ + auto llmqType = Params().GetConsensus().llmqForInstantSend; + + for (auto& in : tx.vin) { + auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); + if (!quorumSigningManager->HasRecoveredSig(llmqType, id, tx.GetHash())) { + return; + } + } + + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: got all recovered sigs, creating CInstantXLock\n", __func__, + tx.GetHash().ToString()); + + CInstantXLockInfo ixlockInfo; + ixlockInfo.time = GetTimeMillis(); + ixlockInfo.ixlock.txid = tx.GetHash(); + for (auto& in : tx.vin) { + ixlockInfo.ixlock.inputs.emplace_back(in.prevout); + } + + auto id = ixlockInfo.ixlock.GetRequestId(); + + if (quorumSigningManager->HasRecoveredSigForId(llmqType, id)) { + return; + } + + { + LOCK(cs); + auto e = creatingInstantXLocks.emplace(id, ixlockInfo); + if (!e.second) { + return; + } + txToCreatingInstantXLocks.emplace(tx.GetHash(), &e.first->second); + } + + quorumSigningManager->AsyncSignIfMember(llmqType, id, tx.GetHash()); +} + +void CInstantSendManager::HandleNewInstantXLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig) +{ + CInstantXLockInfo ixlockInfo; + + { + LOCK(cs); + auto it = creatingInstantXLocks.find(recoveredSig.id); + if (it == creatingInstantXLocks.end()) { + return; + } + + ixlockInfo = std::move(it->second); + creatingInstantXLocks.erase(it); + txToCreatingInstantXLocks.erase(ixlockInfo.ixlock.txid); + } + + if (ixlockInfo.ixlock.txid != recoveredSig.msgHash) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: ixlock conflicts with %s, dropping own version", __func__, + ixlockInfo.ixlock.txid.ToString(), recoveredSig.msgHash.ToString()); + return; + } + + ixlockInfo.ixlock.sig = recoveredSig.sig; + ProcessInstantXLock(-1, ::SerializeHash(ixlockInfo.ixlock), ixlockInfo.ixlock); +} + +void CInstantSendManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) +{ + if (!IsNewInstantSendEnabled()) { + return; + } + + if (strCommand == NetMsgType::IXLOCK) { + CInstantXLock ixlock; + vRecv >> ixlock; + ProcessMessageInstantXLock(pfrom, ixlock, connman); + } +} + +void CInstantSendManager::ProcessMessageInstantXLock(CNode* pfrom, const llmq::CInstantXLock& ixlock, CConnman& connman) +{ + bool ban = false; + if (!PreVerifyInstantXLock(pfrom->id, ixlock, ban)) { + if (ban) { + LOCK(cs_main); + Misbehaving(pfrom->id, 100); + } + return; + } + + auto hash = ::SerializeHash(ixlock); + + LOCK(cs); + if (pendingInstantXLocks.count(hash) || finalInstantXLocks.count(hash)) { + return; + } + + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: received ixlock, peer=%d\n", __func__, + ixlock.txid.ToString(), hash.ToString(), pfrom->id); + + pendingInstantXLocks.emplace(hash, std::make_pair(pfrom->id, std::move(ixlock))); + + if (!hasScheduledProcessPending) { + hasScheduledProcessPending = true; + scheduler->schedule([&] { + ProcessPendingInstantXLocks(); + }, boost::chrono::system_clock::now() + boost::chrono::milliseconds(100)); + } +} + +bool CInstantSendManager::PreVerifyInstantXLock(NodeId nodeId, const llmq::CInstantXLock& ixlock, bool& retBan) +{ + retBan = false; + + if (ixlock.txid.IsNull() || !ixlock.sig.IsValid() || ixlock.inputs.empty()) { + retBan = true; + return false; + } + + std::set dups; + for (auto& o : ixlock.inputs) { + if (!dups.emplace(o).second) { + retBan = true; + return false; + } + } + + return true; +} + +void CInstantSendManager::ProcessPendingInstantXLocks() +{ + auto llmqType = Params().GetConsensus().llmqForInstantSend; + + decltype(pendingInstantXLocks) pend; + + { + LOCK(cs); + hasScheduledProcessPending = false; + pend = std::move(pendingInstantXLocks); + } + + if (!IsNewInstantSendEnabled()) { + return; + } + + int tipHeight; + { + LOCK(cs_main); + tipHeight = chainActive.Height(); + } + + CBLSBatchVerifier batchVerifier(false, true, 8); + std::unordered_map> recSigs; + + for (const auto& p : pend) { + auto& hash = p.first; + auto nodeId = p.second.first; + auto& ixlock = p.second.second; + + auto id = ixlock.GetRequestId(); + + // no need to verify an IXLOCK if we already have verified the recovered sig that belongs to it + if (quorumSigningManager->HasRecoveredSig(llmqType, id, ixlock.txid)) { + continue; + } + + auto quorum = quorumSigningManager->SelectQuorumForSigning(llmqType, tipHeight, id); + if (!quorum) { + // should not happen, but if one fails to select, all others will also fail to select + return; + } + uint256 signHash = CLLMQUtils::BuildSignHash(llmqType, quorum->quorumHash, id, ixlock.txid); + batchVerifier.PushMessage(nodeId, hash, signHash, ixlock.sig, quorum->quorumPublicKey); + + // We can reconstruct the CRecoveredSig objects from the ixlock and pass it to the signing manager, which + // avoids unnecessary double-verification of the signature. We however only do this when verification here + // turns out to be good (which is checked further down) + if (!quorumSigningManager->HasRecoveredSigForId(llmqType, id)) { + CRecoveredSig recSig; + recSig.llmqType = llmqType; + recSig.quorumHash = quorum->quorumHash; + recSig.id = id; + recSig.msgHash = ixlock.txid; + recSig.sig = ixlock.sig; + recSigs.emplace(std::piecewise_construct, + std::forward_as_tuple(hash), + std::forward_as_tuple(std::move(quorum), std::move(recSig))); + } + } + + batchVerifier.Verify(); + + if (!batchVerifier.badSources.empty()) { + LOCK(cs_main); + for (auto& nodeId : batchVerifier.badSources) { + // Let's not be too harsh, as the peer might simply be unlucky and might have sent us an old lock which + // does not validate anymore due to changed quorums + Misbehaving(nodeId, 20); + } + } + for (const auto& p : pend) { + auto& hash = p.first; + auto nodeId = p.second.first; + auto& ixlock = p.second.second; + + if (batchVerifier.badMessages.count(hash)) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: invalid sig in ixlock, peer=%d\n", __func__, + ixlock.txid.ToString(), hash.ToString(), nodeId); + continue; + } + + ProcessInstantXLock(nodeId, hash, ixlock); + + // See comment further on top. We pass a reconstructed recovered sig to the signing manager to avoid + // double-verification of the sig. + auto it = recSigs.find(hash); + if (it != recSigs.end()) { + auto& quorum = it->second.first; + auto& recSig = it->second.second; + if (!quorumSigningManager->HasRecoveredSigForId(llmqType, recSig.id)) { + recSig.UpdateHash(); + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: passing reconstructed recSig to signing mgr, peer=%d\n", __func__, + ixlock.txid.ToString(), hash.ToString(), nodeId); + quorumSigningManager->PushReconstructedRecoveredSig(recSig, quorum); + } + } + } +} + +void CInstantSendManager::ProcessInstantXLock(NodeId from, const uint256& hash, const CInstantXLock& ixlock) +{ + { + LOCK(cs_main); + g_connman->RemoveAskFor(hash); + } + + CInstantXLockInfo ixlockInfo; + ixlockInfo.time = GetTimeMillis(); + ixlockInfo.ixlock = ixlock; + ixlockInfo.ixlockHash = hash; + + uint256 hashBlock; + // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally + if (GetTransaction(ixlock.txid, ixlockInfo.tx, Params().GetConsensus(), hashBlock)) { + if (!hashBlock.IsNull()) { + { + LOCK(cs_main); + ixlockInfo.pindexMined = mapBlockIndex.at(hashBlock); + } + + // Let's see if the TX that was locked by this ixlock is already mined in a ChainLocked block. If yes, + // we can simply ignore the ixlock, as the ChainLock implies locking of all TXs in that chain + if (llmq::chainLocksHandler->HasChainLock(ixlockInfo.pindexMined->nHeight, ixlockInfo.pindexMined->GetBlockHash())) { + LogPrint("instantsend", "CInstantSendManager::%s -- txlock=%s, ixlock=%s: dropping ixlock as it already got a ChainLock in block %s, peer=%d\n", __func__, + ixlock.txid.ToString(), hash.ToString(), hashBlock.ToString(), from); + return; + } + } + } + + { + LOCK(cs); + auto e = finalInstantXLocks.emplace(hash, ixlockInfo); + if (!e.second) { + return; + } + auto ixlockInfoPtr = &e.first->second; + + creatingInstantXLocks.erase(ixlockInfoPtr->ixlock.GetRequestId()); + txToCreatingInstantXLocks.erase(ixlockInfoPtr->ixlock.txid); + + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: processsing ixlock, peer=%d\n", __func__, + ixlock.txid.ToString(), hash.ToString(), from); + + if (!txToInstantXLock.emplace(ixlock.txid, ixlockInfoPtr).second) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: duplicate ixlock, other ixlock=%s, peer=%d\n", __func__, + ixlock.txid.ToString(), hash.ToString(), txToInstantXLock[ixlock.txid]->ixlockHash.ToString(), from); + txToInstantXLock.erase(hash); + return; + } + for (size_t i = 0; i < ixlock.inputs.size(); i++) { + auto& in = ixlock.inputs[i]; + if (!inputToInstantXLock.emplace(in, ixlockInfoPtr).second) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: conflicting input in ixlock. input=%s, other ixlock=%s, peer=%d\n", __func__, + ixlock.txid.ToString(), hash.ToString(), in.ToStringShort(), inputToInstantXLock[in]->ixlockHash.ToString(), from); + txToInstantXLock.erase(hash); + for (size_t j = 0; j < i; j++) { + inputToInstantXLock.erase(ixlock.inputs[j]); + } + return; + } + } + } + + CInv inv(MSG_IXLOCK, hash); + g_connman->RelayInv(inv); + + RemoveMempoolConflictsForLock(hash, ixlock); + RetryLockMempoolTxs(ixlock.txid); + + UpdateWalletTransaction(ixlock.txid); +} + +void CInstantSendManager::UpdateWalletTransaction(const uint256& txid) +{ +#ifdef ENABLE_WALLET + if (!pwalletMain) { + return; + } + + if (pwalletMain->UpdatedTransaction(txid)) { + // bumping this to update UI + nCompleteTXLocks++; + // notify an external script once threshold is reached + std::string strCmd = GetArg("-instantsendnotify", ""); + if (!strCmd.empty()) { + boost::replace_all(strCmd, "%s", txid.GetHex()); + boost::thread t(runCommand, strCmd); // thread runs free + } + } +#endif + + LOCK(cs); + auto it = txToInstantXLock.find(txid); + if (it == txToInstantXLock.end()) { + return; + } + if (it->second->tx == nullptr) { + return; + } + + GetMainSignals().NotifyTransactionLock(*it->second->tx); +} + +void CInstantSendManager::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock) +{ + if (!IsNewInstantSendEnabled()) { + return; + } + + { + LOCK(cs); + auto it = txToInstantXLock.find(tx.GetHash()); + if (it == txToInstantXLock.end()) { + return; + } + auto ixlockInfo = it->second; + if (ixlockInfo->tx == nullptr) { + ixlockInfo->tx = MakeTransactionRef(tx); + } + + if (posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK) { + UpdateIxLockMinedBlock(ixlockInfo, nullptr); + return; + } + UpdateIxLockMinedBlock(ixlockInfo, pindex); + } + + if (IsLocked(tx.GetHash())) { + RetryLockMempoolTxs(tx.GetHash()); + } +} + +void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindex) +{ + { + LOCK(cs); + + // Let's find all ixlocks that correspond to TXs which are part of the freshly ChainLocked chain and then delete + // the ixlocks. We do this because the ChainLocks imply locking and thus it's not needed to further track + // or propagate the ixlocks + std::unordered_set toDelete; + while (pindex && pindex != pindexLastChainLock) { + auto its = blockToInstantXLocks.equal_range(pindex->GetBlockHash()); + while (its.first != its.second) { + auto ixlockInfo = its.first->second; + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: removing ixlock as it got ChainLocked in block %s\n", __func__, + ixlockInfo->ixlock.txid.ToString(), ixlockInfo->ixlockHash.ToString(), pindex->GetBlockHash().ToString()); + toDelete.emplace(its.first->second->ixlockHash); + ++its.first; + } + + pindex = pindex->pprev; + } + + pindexLastChainLock = pindex; + + for (auto& ixlockHash : toDelete) { + RemoveFinalIxLock(ixlockHash); + } + } + + RetryLockMempoolTxs(uint256()); +} + +void CInstantSendManager::UpdateIxLockMinedBlock(llmq::CInstantXLockInfo* ixlockInfo, const CBlockIndex* pindex) +{ + AssertLockHeld(cs); + + if (ixlockInfo->pindexMined == pindex) { + return; + } + + if (ixlockInfo->pindexMined) { + auto its = blockToInstantXLocks.equal_range(ixlockInfo->pindexMined->GetBlockHash()); + while (its.first != its.second) { + if (its.first->second == ixlockInfo) { + its.first = blockToInstantXLocks.erase(its.first); + } else { + ++its.first; + } + } + } + + if (pindex) { + blockToInstantXLocks.emplace(pindex->GetBlockHash(), ixlockInfo); + } + + ixlockInfo->pindexMined = pindex; +} + +void CInstantSendManager::RemoveFinalIxLock(const uint256& hash) +{ + AssertLockHeld(cs); + + auto it = finalInstantXLocks.find(hash); + if (it == finalInstantXLocks.end()) { + return; + } + auto& ixlockInfo = it->second; + + txToInstantXLock.erase(ixlockInfo.ixlock.txid); + for (auto& in : ixlockInfo.ixlock.inputs) { + auto inputRequestId = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in)); + inputVotes.erase(inputRequestId); + inputToInstantXLock.erase(in); + } + UpdateIxLockMinedBlock(&ixlockInfo, nullptr); +} + +void CInstantSendManager::RemoveMempoolConflictsForLock(const uint256& hash, const CInstantXLock& ixlock) +{ + LOCK(mempool.cs); + + std::unordered_map toDelete; + + for (auto& in : ixlock.inputs) { + auto it = mempool.mapNextTx.find(in); + if (it == mempool.mapNextTx.end()) { + continue; + } + if (it->second->GetHash() != ixlock.txid) { + toDelete.emplace(it->second->GetHash(), mempool.get(it->second->GetHash())); + + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: mempool TX %s with input %s conflicts with ixlock\n", __func__, + ixlock.txid.ToString(), hash.ToString(), it->second->GetHash().ToString(), in.ToStringShort()); + } + } + + for (auto& p : toDelete) { + mempool.removeRecursive(*p.second, MemPoolRemovalReason::CONFLICT); + } +} + +void CInstantSendManager::RetryLockMempoolTxs(const uint256& lockedParentTx) +{ + // Let's retry all mempool TXs which don't have an ixlock yet and where the parents got ChainLocked now + + std::unordered_map txs; + + { + LOCK(mempool.cs); + + if (lockedParentTx.IsNull()) { + txs.reserve(mempool.mapTx.size()); + for (auto it = mempool.mapTx.begin(); it != mempool.mapTx.end(); ++it) { + txs.emplace(it->GetTx().GetHash(), it->GetSharedTx()); + } + } else { + auto it = mempool.mapNextTx.lower_bound(COutPoint(lockedParentTx, 0)); + while (it != mempool.mapNextTx.end() && it->first->hash == lockedParentTx) { + txs.emplace(it->second->GetHash(), mempool.get(it->second->GetHash())); + ++it; + } + } + } + for (auto& p : txs) { + auto& tx = p.second; + { + LOCK(cs); + if (txToCreatingInstantXLocks.count(tx->GetHash())) { + // we're already in the middle of locking this one + continue; + } + if (IsLocked(tx->GetHash())) { + continue; + } + if (IsConflicted(*tx)) { + // should not really happen as we have already filtered these out + continue; + } + } + + // CheckCanLock is already called by ProcessTx, so we should avoid calling it twice. But we also shouldn't spam + // the logs when retrying TXs that are not ready yet. + if (LogAcceptCategory("instantsend")) { + if (!CheckCanLock(*tx, false, Params().GetConsensus())) { + continue; + } + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: retrying to lock\n", __func__, + tx->GetHash().ToString()); + } + + ProcessTx(nullptr, *tx, *g_connman, Params().GetConsensus()); + } +} + +bool CInstantSendManager::AlreadyHave(const CInv& inv) +{ + if (!IsNewInstantSendEnabled()) { + return true; + } + + LOCK(cs); + return finalInstantXLocks.count(inv.hash) != 0 || pendingInstantXLocks.count(inv.hash) != 0; +} + +bool CInstantSendManager::GetInstantXLockByHash(const uint256& hash, llmq::CInstantXLock& ret) +{ + if (!IsNewInstantSendEnabled()) { + return false; + } + + LOCK(cs); + auto it = finalInstantXLocks.find(hash); + if (it == finalInstantXLocks.end()) { + return false; + } + ret = it->second.ixlock; + return true; +} + +bool CInstantSendManager::IsLocked(const uint256& txHash) +{ + if (!IsNewInstantSendEnabled()) { + return false; + } + + LOCK(cs); + return txToInstantXLock.count(txHash) != 0; +} + +bool CInstantSendManager::IsConflicted(const CTransaction& tx) +{ + LOCK(cs); + uint256 dummy; + return GetConflictingTx(tx, dummy); +} + +bool CInstantSendManager::GetConflictingTx(const CTransaction& tx, uint256& retConflictTxHash) +{ + if (!IsNewInstantSendEnabled()) { + return false; + } + + LOCK(cs); + for (const auto& in : tx.vin) { + auto it = inputToInstantXLock.find(in.prevout); + if (it == inputToInstantXLock.end()) { + continue; + } + + if (it->second->ixlock.txid != tx.GetHash()) { + retConflictTxHash = it->second->ixlock.txid; + return true; + } + } + return false; +} + +bool IsOldInstantSendEnabled() +{ + int spork2Value = sporkManager.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED); + if (spork2Value == 0) { + return true; + } + return false; +} + +bool IsNewInstantSendEnabled() +{ + int spork2Value = sporkManager.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED); + if (spork2Value == 1) { + return true; + } + return false; +} + +bool IsInstantSendEnabled() +{ + int spork2Value = sporkManager.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED); + return spork2Value == 0 || spork2Value == 1; +} + +} diff --git a/src/llmq/quorums_instantsend.h b/src/llmq/quorums_instantsend.h new file mode 100644 index 0000000000000..36ce131d40935 --- /dev/null +++ b/src/llmq/quorums_instantsend.h @@ -0,0 +1,149 @@ +// Copyright (c) 2019 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_QUORUMS_INSTANTX_H +#define DASH_QUORUMS_INSTANTX_H + +#include "quorums_signing.h" + +#include "coins.h" +#include "primitives/transaction.h" + +#include +#include + +class CScheduler; + +namespace llmq +{ + +class CInstantXLock +{ +public: + std::vector inputs; + uint256 txid; + CBLSSignature sig; + +public: + ADD_SERIALIZE_METHODS + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(inputs); + READWRITE(txid); + READWRITE(sig); + } + + uint256 GetRequestId() const; +}; + +class CInstantXLockInfo +{ +public: + // might be nullptr when ixlock is received before the TX itself + CTransactionRef tx; + CInstantXLock ixlock; + // only valid when recovered sig was received + uint256 ixlockHash; + // time when it was created/received + int64_t time; + + // might be null initially (when TX was not mined yet) and will later be filled by SyncTransaction + const CBlockIndex* pindexMined{nullptr}; +}; + +class CInstantSendManager : public CRecoveredSigsListener +{ +private: + CCriticalSection cs; + CScheduler* scheduler; + + /** + * These are the votes/signatures we performed locally. It's indexed by the LLMQ requestId, which is + * hash(TXLOCK_REQUESTID_PREFIX, prevout). The map values are the txids we voted for. This map is used to + * avoid voting for the same input twice. + */ + std::unordered_map inputVotes; + + /** + * These are the ixlocks that are currently in the middle of being created. Entries are created when we observed + * recovered signatures for all inputs of a TX. At the same time, we initiate signing of our sigshare for the ixlock. + * When the recovered sig for the ixlock later arrives, we can finish the ixlock and propagate it. + */ + std::unordered_map creatingInstantXLocks; + // maps from txid to the in-progress ixlock + std::unordered_map txToCreatingInstantXLocks; + + /** + * These are the final ixlocks, indexed by their own hash. The other maps are used to get from TXs, inputs and blocks + * to ixlocks. + */ + std::unordered_map finalInstantXLocks; + std::unordered_map txToInstantXLock; + std::unordered_map inputToInstantXLock; + std::unordered_multimap blockToInstantXLocks; + + const CBlockIndex* pindexLastChainLock{nullptr}; + + // Incoming and not verified yet + std::unordered_map> pendingInstantXLocks; + bool hasScheduledProcessPending{false}; + +public: + CInstantSendManager(CScheduler* _scheduler); + ~CInstantSendManager(); + + void RegisterAsRecoveredSigsListener(); + void UnregisterAsRecoveredSigsListener(); + +public: + bool ProcessTx(CNode* pfrom, const CTransaction& tx, CConnman& connman, const Consensus::Params& params); + bool CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params); + bool CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256* _txHash, CAmount* retValue, const Consensus::Params& params); + bool IsLocked(const uint256& txHash); + bool IsConflicted(const CTransaction& tx); + bool GetConflictingTx(const CTransaction& tx, uint256& retConflictTxHash); + + virtual void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig); + void HandleNewInputLockRecoveredSig(const CRecoveredSig& recoveredSig, const uint256& txid); + void HandleNewInstantXLockRecoveredSig(const CRecoveredSig& recoveredSig); + + void TrySignInstantXLock(const CTransaction& tx); + + void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); + void ProcessMessageInstantXLock(CNode* pfrom, const CInstantXLock& ixlock, CConnman& connman); + bool PreVerifyInstantXLock(NodeId nodeId, const CInstantXLock& ixlock, bool& retBan); + void ProcessPendingInstantXLocks(); + void ProcessInstantXLock(NodeId from, const uint256& hash, const CInstantXLock& ixlock); + void UpdateWalletTransaction(const uint256& txid); + + void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock); + void NotifyChainLock(const CBlockIndex* pindex); + void UpdateIxLockMinedBlock(CInstantXLockInfo* ixlockInfo, const CBlockIndex* pindex); + void RemoveFinalIxLock(const uint256& hash); + + void RemoveMempoolConflictsForLock(const uint256& hash, const CInstantXLock& ixlock); + void RetryLockMempoolTxs(const uint256& lockedParentTx); + + bool AlreadyHave(const CInv& inv); + bool GetInstantXLockByHash(const uint256& hash, CInstantXLock& ret); +}; + +extern CInstantSendManager* quorumInstantSendManager; + +// The meaning of spork 2 has changed in v0.14. Before that, spork 2 was simply time based and either enabled or not +// After 0.14, spork 2 can have 3 states. +// 0 = old system is active (0 is compatible with the value set on mainnet at time of deployment) +// 1 = new system is active (old nodes will interpret this as the old system being enabled, but then won't get enough IX lock votes) +// everything else = disabled +// TODO When the new system is fully deployed and enabled, we can remove this special handling of the spork in a future version +// and revert to the old behaviour. +bool IsOldInstantSendEnabled(); +bool IsNewInstantSendEnabled(); +bool IsInstantSendEnabled(); + +} + +#endif//DASH_QUORUMS_INSTANTX_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index b68c0bcf03c0b..b53beb3d128f8 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -50,6 +50,7 @@ #include "llmq/quorums_debug.h" #include "llmq/quorums_dkgsessionmgr.h" #include "llmq/quorums_init.h" +#include "llmq/quorums_instantsend.h" #include "llmq/quorums_signing.h" #include "llmq/quorums_signing_shares.h" @@ -976,6 +977,8 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) return llmq::quorumSigningManager->AlreadyHave(inv); case MSG_CLSIG: return llmq::chainLocksHandler->AlreadyHave(inv); + case MSG_IXLOCK: + return llmq::quorumInstantSendManager->AlreadyHave(inv); } // Don't know what it is, just say we already got one @@ -1296,6 +1299,14 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam } } + if (!push && (inv.type == MSG_IXLOCK)) { + llmq::CInstantXLock o; + if (llmq::quorumInstantSendManager->GetInstantXLockByHash(inv.hash, o)) { + connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::IXLOCK, o)); + push = true; + } + } + if (!push) vNotFound.push_back(inv); } @@ -1777,6 +1788,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr case MSG_CLSIG: doubleRequestDelay = 5 * 1000000; break; + case MSG_IXLOCK: + doubleRequestDelay = 5 * 1000000; + break; } pfrom->AskFor(inv, doubleRequestDelay); } @@ -2943,6 +2957,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr llmq::quorumSigSharesManager->ProcessMessage(pfrom, strCommand, vRecv, connman); llmq::quorumSigningManager->ProcessMessage(pfrom, strCommand, vRecv, connman); llmq::chainLocksHandler->ProcessMessage(pfrom, strCommand, vRecv, connman); + llmq::quorumInstantSendManager->ProcessMessage(pfrom, strCommand, vRecv, connman); } else { diff --git a/src/protocol.cpp b/src/protocol.cpp index 6df80cae74fa4..e63688c0a00c4 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -71,6 +71,7 @@ const char *QGETSIGSHARES="qgetsigs"; const char *QBSIGSHARES="qbsigs"; const char *QSIGREC="qsigrec"; const char *CLSIG="clsig"; +const char *IXLOCK="ixlock"; }; static const char* ppszTypeName[] = @@ -107,6 +108,7 @@ static const char* ppszTypeName[] = NetMsgType::QDEBUGSTATUS, NetMsgType::QSIGREC, NetMsgType::CLSIG, + NetMsgType::IXLOCK, }; /** All known message types. Keep this in the same order as the list of @@ -172,6 +174,7 @@ const static std::string allNetMessageTypes[] = { NetMsgType::QBSIGSHARES, NetMsgType::QSIGREC, NetMsgType::CLSIG, + NetMsgType::IXLOCK, }; const static std::vector allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes)); diff --git a/src/protocol.h b/src/protocol.h index b4d6eab3eccaf..0376f86f26ff2 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -277,6 +277,7 @@ extern const char *QGETSIGSHARES; extern const char *QBSIGSHARES; extern const char *QSIGREC; extern const char *CLSIG; +extern const char *IXLOCK; }; /* Get a vector of all valid message types (see above) */ @@ -379,6 +380,7 @@ enum GetDataMsg { MSG_QUORUM_DEBUG_STATUS = 27, MSG_QUORUM_RECOVERED_SIG = 28, MSG_CLSIG = 29, + MSG_IXLOCK = 30, }; /** inv message data */ From 1959f3e4a59b8282f480669dbaf420b51c81df51 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 13:43:04 +0100 Subject: [PATCH 04/33] Handle incoming TXs by calling CInstantXManager::ProcessTx This also includes handling of TXs that were previously orphanced --- src/net_processing.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index b53beb3d128f8..cdd2b522e8afa 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2094,6 +2094,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr instantsend.Vote(tx.GetHash(), connman); } + if (nInvType != MSG_TXLOCK_REQUEST) { + llmq::quorumInstantSendManager->ProcessTx(pfrom, tx, connman, chainparams.GetConsensus()); + } + mempool.check(pcoinsTip); connman.RelayTransaction(tx); for (unsigned int i = 0; i < tx.vout.size(); i++) { @@ -2138,6 +2142,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr vWorkQueue.emplace_back(orphanHash, i); } vEraseQueue.push_back(orphanHash); + + llmq::quorumInstantSendManager->ProcessTx(pfrom, orphanTx, connman, chainparams.GetConsensus()); } else if (!fMissingInputs2) { From 5ff4db0a05b15c507e0c3891fbab157f337e70b3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 13:44:53 +0100 Subject: [PATCH 05/33] Downgrade TXLOCKREQUEST to TX when new IX system is active The new system does not require explicit lock requests, so we downgrade TXLOCKREQUEST to TX and start propagating it instead of the original. --- src/net.cpp | 9 +++++++-- src/net_processing.cpp | 17 +++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index fd20a51ee0906..b54147c02d683 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -28,6 +28,7 @@ #include "instantx.h" #include "masternode-sync.h" #include "privatesend.h" +#include "llmq/quorums_instantsend.h" #ifdef WIN32 #include @@ -2786,8 +2787,12 @@ bool CConnman::DisconnectNode(NodeId id) void CConnman::RelayTransaction(const CTransaction& tx) { uint256 hash = tx.GetHash(); - int nInv = static_cast(CPrivateSend::GetDSTX(hash)) ? MSG_DSTX : - (instantsend.HasTxLockRequest(hash) ? MSG_TXLOCK_REQUEST : MSG_TX); + int nInv = MSG_TX; + if (CPrivateSend::GetDSTX(hash)) { + nInv = MSG_DSTX; + } else if (llmq::IsOldInstantSendEnabled() && instantsend.HasTxLockRequest(hash)) { + nInv = MSG_TXLOCK_REQUEST; + } CInv inv(nInv, hash); LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index cdd2b522e8afa..1123e9c48e1e3 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2015,11 +2015,16 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if(strCommand == NetMsgType::TX) { vRecv >> ptx; txLockRequest = CTxLockRequest(ptx); - fCanAutoLock = CInstantSend::CanAutoLock() && txLockRequest.IsSimple(); + fCanAutoLock = llmq::IsOldInstantSendEnabled() && CInstantSend::CanAutoLock() && txLockRequest.IsSimple(); } else if(strCommand == NetMsgType::TXLOCKREQUEST) { vRecv >> txLockRequest; ptx = txLockRequest.tx; nInvType = MSG_TXLOCK_REQUEST; + if (llmq::IsNewInstantSendEnabled()) { + // the new system does not require explicit lock requests + // changing the inv type to MSG_TX also results in re-broadcasting the TX as normal TX + nInvType = MSG_TX; + } } else if (strCommand == NetMsgType::DSTX) { vRecv >> dstx; ptx = dstx.tx; @@ -2035,7 +2040,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } // Process custom logic, no matter if tx will be accepted to mempool later or not - if (strCommand == NetMsgType::TXLOCKREQUEST || fCanAutoLock) { + if (nInvType == MSG_TXLOCK_REQUEST || fCanAutoLock) { if(!instantsend.ProcessTxLockRequest(txLockRequest, connman)) { LogPrint("instantsend", "TXLOCKREQUEST -- failed %s\n", txLockRequest.GetHash().ToString()); // Should not really happen for "fCanAutoLock == true" but just in case: @@ -2046,7 +2051,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Fallback for normal txes to process as usual fCanAutoLock = false; } - } else if (strCommand == NetMsgType::DSTX) { + } else if (nInvType == MSG_DSTX) { uint256 hashTx = tx.GetHash(); if(CPrivateSend::GetDSTX(hashTx)) { LogPrint("privatesend", "DSTX -- Already have %s, skipping...\n", hashTx.ToString()); @@ -2083,11 +2088,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, true, &fMissingInputs)) { // Process custom txes, this changes AlreadyHave to "true" - if (strCommand == NetMsgType::DSTX) { + if (nInvType == MSG_DSTX) { LogPrintf("DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n", tx.GetHash().ToString(), pfrom->id); CPrivateSend::AddDSTX(dstx); - } else if (strCommand == NetMsgType::TXLOCKREQUEST || fCanAutoLock) { + } else if (nInvType == MSG_TXLOCK_REQUEST || fCanAutoLock) { LogPrintf("TXLOCKREQUEST -- Transaction Lock Request accepted, txid=%s, peer=%d\n", tx.GetHash().ToString(), pfrom->id); instantsend.AcceptLockRequest(txLockRequest); @@ -2208,7 +2213,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } } - if (strCommand == NetMsgType::TXLOCKREQUEST && !AlreadyHave(inv)) { + if (nInvType == MSG_TXLOCK_REQUEST && !AlreadyHave(inv)) { // i.e. AcceptToMemoryPool failed, probably because it's conflicting // with existing normal tx or tx lock for another tx. For the same tx lock // AlreadyHave would have return "true" already. From 34a8b2997a6228f8b72ecb967120219b24cb50f3 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 13:46:36 +0100 Subject: [PATCH 06/33] Disable old IX code when the new system is active --- src/instantx.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/instantx.cpp b/src/instantx.cpp index 9096a6707a096..87bbd1a5b6bf1 100644 --- a/src/instantx.cpp +++ b/src/instantx.cpp @@ -25,6 +25,8 @@ #include "wallet/wallet.h" #endif // ENABLE_WALLET +#include "llmq/quorums_instantsend.h" + #include #include @@ -56,7 +58,7 @@ const std::string CInstantSend::SERIALIZATION_VERSION_STRING = "CInstantSend-Ver void CInstantSend::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) { if (fLiteMode) return; // disable all Dash specific functionality - if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return; + if (!llmq::IsOldInstantSendEnabled()) return; // NOTE: NetMsgType::TXLOCKREQUEST is handled via ProcessMessage() in net_processing.cpp @@ -224,7 +226,7 @@ void CInstantSend::Vote(const uint256& txHash, CConnman& connman) void CInstantSend::Vote(CTxLockCandidate& txLockCandidate, CConnman& connman) { if (!fMasternodeMode) return; - if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return; + if (!llmq::IsOldInstantSendEnabled()) return; AssertLockHeld(cs_main); AssertLockHeld(cs_instantsend); @@ -496,7 +498,7 @@ void CInstantSend::ProcessOrphanTxLockVotes() void CInstantSend::TryToFinalizeLockCandidate(const CTxLockCandidate& txLockCandidate) { - if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return; + if (!llmq::IsOldInstantSendEnabled()) return; AssertLockHeld(cs_main); AssertLockHeld(cs_instantsend); @@ -547,7 +549,7 @@ void CInstantSend::UpdateLockedTransaction(const CTxLockCandidate& txLockCandida void CInstantSend::LockTransactionInputs(const CTxLockCandidate& txLockCandidate) { - if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return; + if (!llmq::IsOldInstantSendEnabled()) return; LOCK(cs_instantsend); @@ -740,6 +742,10 @@ void CInstantSend::CheckAndRemove() bool CInstantSend::AlreadyHave(const uint256& hash) { + if (!llmq::IsOldInstantSendEnabled()) { + return true; + } + LOCK(cs_instantsend); return mapLockRequestAccepted.count(hash) || mapLockRequestRejected.count(hash) || @@ -766,6 +772,10 @@ bool CInstantSend::HasTxLockRequest(const uint256& txHash) bool CInstantSend::GetTxLockRequest(const uint256& txHash, CTxLockRequest& txLockRequestRet) { + if (!llmq::IsOldInstantSendEnabled()) { + return false; + } + LOCK(cs_instantsend); std::map::iterator it = mapTxLockCandidates.find(txHash); @@ -777,6 +787,10 @@ bool CInstantSend::GetTxLockRequest(const uint256& txHash, CTxLockRequest& txLoc bool CInstantSend::GetTxLockVote(const uint256& hash, CTxLockVote& txLockVoteRet) { + if (!llmq::IsOldInstantSendEnabled()) { + return false; + } + LOCK(cs_instantsend); std::map::iterator it = mapTxLockVotes.find(hash); @@ -828,7 +842,7 @@ int CInstantSend::GetTransactionLockSignatures(const uint256& txHash) { if (!fEnableInstantSend) return -1; if (GetfLargeWorkForkFound() || GetfLargeWorkInvalidChainFound()) return -2; - if (!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return -3; + if (!llmq::IsOldInstantSendEnabled()) return -3; LOCK(cs_instantsend); @@ -932,7 +946,7 @@ void CInstantSend::DoMaintenance() bool CInstantSend::CanAutoLock() { - if (!isAutoLockBip9Active) { + if (!isAutoLockBip9Active || !llmq::IsOldInstantSendEnabled()) { return false; } if (!sporkManager.IsSporkActive(SPORK_16_INSTANTSEND_AUTOLOCKS)) { From 3a6cc2cc17e15f5007aab08bdbe399448cccf532 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 13:47:18 +0100 Subject: [PATCH 07/33] Show "verified via LLMQ based InstantSend" in GUI TX status --- src/qt/transactiondesc.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index f6069f24cf218..7f3385dfd8250 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -21,6 +21,8 @@ #include "instantx.h" +#include "llmq/quorums_instantsend.h" + #include #include @@ -52,6 +54,11 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx) strTxStatus = tr("%1 confirmations").arg(nDepth); } + if (llmq::quorumInstantSendManager->IsLocked(wtx.GetHash())) { + strTxStatus += tr(" (verified via LLMQ based InstantSend)"); + return strTxStatus; + } + if(!instantsend.HasTxLockRequest(wtx.GetHash())) return strTxStatus; // regular tx int nSignatures = instantsend.GetTransactionLockSignatures(wtx.GetHash()); From 1d2d370cd09f039718a4e97c6bba70551e2cf387 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 13:59:57 +0100 Subject: [PATCH 08/33] Whenever we check for locked TXs, also check for the new system having a lock --- src/governance-object.cpp | 4 +++- src/rpc/blockchain.cpp | 3 ++- src/rpc/rawtransaction.cpp | 4 +++- src/txmempool.cpp | 4 +++- src/wallet/rpcwallet.cpp | 4 +++- src/wallet/wallet.cpp | 4 +++- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/governance-object.cpp b/src/governance-object.cpp index 902138fd40a23..e8c370ade3333 100644 --- a/src/governance-object.cpp +++ b/src/governance-object.cpp @@ -16,6 +16,8 @@ #include "util.h" #include "validation.h" +#include "llmq/quorums_instantsend.h" + #include #include @@ -618,7 +620,7 @@ bool CGovernanceObject::IsCollateralValid(std::string& strError, bool& fMissingC } if ((nConfirmationsIn < GOVERNANCE_FEE_CONFIRMATIONS) && - (!instantsend.IsLockedInstantSendTransaction(nCollateralHash))) { + (!instantsend.IsLockedInstantSendTransaction(nCollateralHash) || llmq::quorumInstantSendManager->IsLocked(nCollateralHash))) { strError = strprintf("Collateral requires at least %d confirmations to be relayed throughout the network (it has only %d)", GOVERNANCE_FEE_CONFIRMATIONS, nConfirmationsIn); if (nConfirmationsIn >= GOVERNANCE_MIN_RELAY_FEE_CONFIRMATIONS) { fMissingConfirmations = true; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 7aaf3bcd2b471..de5bd13ceba07 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -28,6 +28,7 @@ #include "evo/cbtx.h" #include "llmq/quorums_chainlocks.h" +#include "llmq/quorums_instantsend.h" #include @@ -409,7 +410,7 @@ void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) info.push_back(Pair("depends", depends)); info.push_back(Pair("instantsend", instantsend.HasTxLockRequest(tx.GetHash()))); - info.push_back(Pair("instantlock", instantsend.IsLockedInstantSendTransaction(tx.GetHash()))); + info.push_back(Pair("instantlock", instantsend.IsLockedInstantSendTransaction(tx.GetHash()) || llmq::quorumInstantSendManager->IsLocked(tx.GetHash()))); } UniValue mempoolToJSON(bool fVerbose = false) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 4a8bdb078dd1d..8ece8720fc62a 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -36,6 +36,7 @@ #include "llmq/quorums_chainlocks.h" #include "llmq/quorums_commitment.h" +#include "llmq/quorums_instantsend.h" #include @@ -199,7 +200,8 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) } } bool fLocked = instantsend.IsLockedInstantSendTransaction(txid); - entry.push_back(Pair("instantlock", fLocked)); + bool fLLMQLocked = llmq::quorumInstantSendManager->IsLocked(txid); + entry.push_back(Pair("instantlock", fLocked || fLLMQLocked)); entry.push_back(Pair("chainlock", chainLock)); } diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 295f31cbba544..9575ba99cab79 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -24,6 +24,8 @@ #include "evo/specialtx.h" #include "evo/providertx.h" +#include "llmq/quorums_instantsend.h" + CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, int64_t _nTime, double _entryPriority, unsigned int _entryHeight, CAmount _inChainInputValue, @@ -1497,7 +1499,7 @@ int CTxMemPool::Expire(int64_t time) { setEntries toremove; while (it != mapTx.get().end() && it->GetTime() < time) { // locked txes do not expire until mined and have sufficient confirmations - if (instantsend.IsLockedInstantSendTransaction(it->GetTx().GetHash())) { + if (instantsend.IsLockedInstantSendTransaction(it->GetTx().GetHash()) || llmq::quorumInstantSendManager->IsLocked(it->GetTx().GetHash())) { it++; continue; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 530f7d7257617..7846f4d5b1530 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -23,6 +23,7 @@ #include "privatesend-client.h" #include "llmq/quorums_chainlocks.h" +#include "llmq/quorums_instantsend.h" #include @@ -65,12 +66,13 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) AssertLockHeld(cs_main); // for mapBlockIndex int confirms = wtx.GetDepthInMainChain(); bool fLocked = instantsend.IsLockedInstantSendTransaction(wtx.GetHash()); + bool fLLMQLocked = llmq::quorumInstantSendManager->IsLocked(wtx.GetHash()); bool chainlock = false; if (confirms > 0) { chainlock = llmq::chainLocksHandler->HasChainLock(mapBlockIndex[wtx.hashBlock]->nHeight, wtx.hashBlock); } entry.push_back(Pair("confirmations", confirms)); - entry.push_back(Pair("instantlock", fLocked)); + entry.push_back(Pair("instantlock", fLocked || fLLMQLocked)); entry.push_back(Pair("chainlock", chainlock)); if (wtx.IsCoinBase()) entry.push_back(Pair("generated", true)); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 8c34403b35410..7a8fde9ff16db 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -36,6 +36,8 @@ #include "evo/providertx.h" +#include "llmq/quorums_instantsend.h" + #include #include @@ -5426,7 +5428,7 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const bool CMerkleTx::IsLockedByInstantSend() const { - return instantsend.IsLockedInstantSendTransaction(GetHash()); + return instantsend.IsLockedInstantSendTransaction(GetHash()) || llmq::quorumInstantSendManager->IsLocked(GetHash()); } int CMerkleTx::GetBlocksToMaturity() const From 68cfdc9325401a2c667674e56ba4b50364a31698 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 14:01:11 +0100 Subject: [PATCH 09/33] Also call ProcessTx from sendrawtransaction and RelayWalletTransaction --- src/rpc/rawtransaction.cpp | 1 + src/wallet/wallet.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 8ece8720fc62a..afdece3aabbb9 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1020,6 +1020,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason()); } } + llmq::quorumInstantSendManager->ProcessTx(nullptr, *tx, *g_connman, Params().GetConsensus()); } else if (fHaveChain) { throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain"); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7a8fde9ff16db..ea1cc14f20074 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1921,6 +1921,9 @@ bool CWalletTx::RelayWalletTransaction(CConnman* connman, const std::string& str instantsend.RejectLockRequest((CTxLockRequest)*this); } } + + llmq::quorumInstantSendManager->ProcessTx(nullptr, *this->tx, *connman, Params().GetConsensus()); + if (connman) { connman->RelayTransaction((CTransaction)*this); return true; From dc97835ec6a1d5a0adc35078d8a615867e26b406 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 14:03:19 +0100 Subject: [PATCH 10/33] Disable explicit lock requests when the new IX system is active --- src/qt/walletmodel.cpp | 8 +++++++- src/wallet/rpcwallet.cpp | 14 ++++++++++++-- src/wallet/wallet.cpp | 6 ++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 15f13a25b2d0e..487bcca3caec0 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -26,6 +26,7 @@ #include "instantx.h" #include "spork.h" #include "privatesend-client.h" +#include "llmq/quorums_instantsend.h" #include @@ -389,7 +390,12 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran CReserveKey *keyChange = transaction.getPossibleKeyChange(); CValidationState state; - if(!wallet->CommitTransaction(*newTx, *keyChange, g_connman.get(), state, recipients[0].fUseInstantSend ? NetMsgType::TXLOCKREQUEST : NetMsgType::TX)) + // the new IX system does not require explicit IX messages + std::string strCommand = NetMsgType::TX; + if (recipients[0].fUseInstantSend && llmq::IsOldInstantSendEnabled()) { + strCommand = NetMsgType::TXLOCKREQUEST; + } + if(!wallet->CommitTransaction(*newTx, *keyChange, g_connman.get(), state, strCommand)) return SendCoinsReturn(TransactionCommitFailed, QString::fromStdString(state.GetRejectReason())); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 7846f4d5b1530..9074d78c13c30 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -386,7 +386,12 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA throw JSONRPCError(RPC_WALLET_ERROR, strError); } CValidationState state; - if (!pwallet->CommitTransaction(wtxNew, reservekey, g_connman.get(), state, fUseInstantSend ? NetMsgType::TXLOCKREQUEST : NetMsgType::TX)) { + // the new IX system does not require explicit IX messages + std::string strCommand = NetMsgType::TX; + if (fUseInstantSend && llmq::IsOldInstantSendEnabled()) { + strCommand = NetMsgType::TXLOCKREQUEST; + } + if (!pwallet->CommitTransaction(wtxNew, reservekey, g_connman.get(), state, strCommand)) { strError = strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()); throw JSONRPCError(RPC_WALLET_ERROR, strError); } @@ -1136,7 +1141,12 @@ UniValue sendmany(const JSONRPCRequest& request) if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; - if (!pwallet->CommitTransaction(wtx, keyChange, g_connman.get(), state, fUseInstantSend ? NetMsgType::TXLOCKREQUEST : NetMsgType::TX)) { + // the new IX system does not require explicit IX messages + std::string strCommand = NetMsgType::TX; + if (fUseInstantSend && llmq::IsOldInstantSendEnabled()) { + strCommand = NetMsgType::TXLOCKREQUEST; + } + if (!pwallet->CommitTransaction(wtx, keyChange, g_connman.get(), state, strCommand)) { strFailReason = strprintf("Transaction commit failed:: %s", state.GetRejectReason()); throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ea1cc14f20074..22138bde0f49a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3345,6 +3345,12 @@ bool CWallet::ConvertList(std::vector vecTxIn, std::vector& vecA bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign, AvailableCoinsType nCoinType, bool fUseInstantSend, int nExtraPayloadSize) { + if (!llmq::IsOldInstantSendEnabled()) { + // The new system does not require special handling for InstantSend as this is all done in CInstantXManager. + // There is also no need for an extra fee anymore. + fUseInstantSend = false; + } + CAmount nFeePay = fUseInstantSend ? CTxLockRequest().GetMinFee(true) : 0; CAmount nValue = 0; From 7945192ff66e95fdd6c4035a17587f3a5e62a368 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 14:04:11 +0100 Subject: [PATCH 11/33] Enforce IX locks from the new system --- src/validation.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/validation.cpp b/src/validation.cpp index e57409bcf6fa3..5142d8e616a32 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -46,6 +46,7 @@ #include "evo/deterministicmns.h" #include "evo/cbtx.h" +#include "llmq/quorums_instantsend.h" #include "llmq/quorums_chainlocks.h" #include @@ -692,6 +693,18 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C REJECT_INVALID, "tx-txlock-conflict"); } + uint256 txConflictHash; + if (llmq::quorumInstantSendManager->GetConflictingTx(tx, txConflictHash)) { + CTransactionRef txConflict; + uint256 hashBlock; + if (GetTransaction(txConflictHash, txConflict, Params().GetConsensus(), hashBlock)) { + GetMainSignals().NotifyInstantSendDoubleSpendAttempt(tx, *txConflict); + } + return state.DoS(10, error("AcceptToMemoryPool : Transaction %s conflicts with locked TX %s", + hash.ToString(), txConflictHash.ToString()), + REJECT_INVALID, "tx-txlock-conflict"); + } + // Check for conflicts with in-memory transactions { LOCK(pool.cs); // protect pool.mapNextTx @@ -3278,6 +3291,15 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P strprintf("transaction %s conflicts with transaction lock %s", tx->GetHash().ToString(), hashLocked.ToString())); } } + uint256 txConflict; + if (llmq::quorumInstantSendManager->GetConflictingTx(*tx, txConflict)) { + // The node which relayed this will have to switch later, + // relaying instantsend data won't help it. + LOCK(cs_main); + mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime())); + return state.DoS(100, false, REJECT_INVALID, "conflict-tx-lock", false, + strprintf("transaction %s conflicts with transaction lock %s", tx->GetHash().ToString(), txConflict.ToString())); + } } } else { LogPrintf("CheckBlock(DASH): spork is off, skipping transaction locking checks\n"); From bd7edc8ae99646a27dcf2e0bae9bcb16239ff871 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 15:04:27 +0100 Subject: [PATCH 12/33] Track txids of new blocks and first-seen time of TXs in CChainLocksHandler --- src/dsnotificationinterface.cpp | 6 +++ src/dsnotificationinterface.h | 1 + src/llmq/quorums_chainlocks.cpp | 82 ++++++++++++++++++++++++++++++++- src/llmq/quorums_chainlocks.h | 7 +++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index 954de50d0bae4..418f1f9e2879d 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -71,9 +71,15 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con llmq::quorumDKGSessionManager->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); } +void CDSNotificationInterface::NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr& block) +{ + llmq::chainLocksHandler->NewPoWValidBlock(pindex, block); +} + void CDSNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) { llmq::quorumInstantSendManager->SyncTransaction(tx, pindex, posInBlock); + llmq::chainLocksHandler->SyncTransaction(tx, pindex, posInBlock); instantsend.SyncTransaction(tx, pindex, posInBlock); CPrivateSend::SyncTransaction(tx, pindex, posInBlock); } diff --git a/src/dsnotificationinterface.h b/src/dsnotificationinterface.h index f4e97d7ed5c6f..60c1e5bd3611f 100644 --- a/src/dsnotificationinterface.h +++ b/src/dsnotificationinterface.h @@ -21,6 +21,7 @@ class CDSNotificationInterface : public CValidationInterface void AcceptedBlockHeader(const CBlockIndex *pindexNew) override; void NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDownload) override; void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; + void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr& block) override; void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) override; void NotifyMasternodeListChanged(const CDeterministicMNList& newList) override; void NotifyChainLock(const CBlockIndex* pindex); diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index 5a51f98e9ca7e..67c9ddbeffeed 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -11,6 +11,7 @@ #include "net_processing.h" #include "scheduler.h" #include "spork.h" +#include "txmempool.h" #include "validation.h" namespace llmq @@ -244,6 +245,51 @@ void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBl quorumSigningManager->AsyncSignIfMember(Params().GetConsensus().llmqChainLocks, requestId, msgHash); } +void CChainLocksHandler::NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr& block) +{ + LOCK(cs); + if (blockTxs.count(pindex->GetBlockHash())) { + // should actually not happen (blocks are only written once to disk and this is when NewPoWValidBlock is called) + // but be extra safe here in case this behaviour changes. + return; + } + + // We listen for NewPoWValidBlock so that we can collect all TX ids of all included TXs of newly received blocks + // We need this information later when we try to sign a new tip, so that we can determine if all included TXs are + // safe. + + auto txs = std::make_shared>(); + for (const auto& tx : block->vtx) { + if (tx->nVersion == 3) { + if (tx->nType == TRANSACTION_COINBASE || + tx->nType == TRANSACTION_QUORUM_COMMITMENT) { + continue; + } + } + txs->emplace(tx->GetHash()); + } + blockTxs[pindex->GetBlockHash()] = txs; + + int64_t curTime = GetAdjustedTime(); + for (auto& tx : block->vtx) { + txFirstSeenTime.emplace(tx->GetHash(), curTime); + } +} + +void CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock) +{ + if (tx.nVersion == 3) { + if (tx.nType == TRANSACTION_COINBASE || + tx.nType == TRANSACTION_QUORUM_COMMITMENT) { + return; + } + } + + LOCK(cs); + int64_t curTime = GetAdjustedTime(); + txFirstSeenTime.emplace(tx.GetHash(), curTime); +} + // WARNING: cs_main and cs should not be held! void CChainLocksHandler::EnforceBestChainLock() { @@ -420,7 +466,9 @@ void CChainLocksHandler::Cleanup() } } - LOCK2(cs_main, cs); + // need mempool.cs due to GetTransaction calls + LOCK2(cs_main, mempool.cs); + LOCK(cs); for (auto it = seenChainLocks.begin(); it != seenChainLocks.end(); ) { if (GetTimeMillis() - it->second >= CLEANUP_SEEN_TIMEOUT) { @@ -430,6 +478,38 @@ void CChainLocksHandler::Cleanup() } } + for (auto it = blockTxs.begin(); it != blockTxs.end(); ) { + auto pindex = mapBlockIndex.at(it->first); + if (InternalHasChainLock(pindex->nHeight, pindex->GetBlockHash())) { + for (auto& txid : *it->second) { + txFirstSeenTime.erase(txid); + } + it = blockTxs.erase(it); + } else if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) { + it = blockTxs.erase(it); + } else { + ++it; + } + } + for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end(); ) { + CTransactionRef tx; + uint256 hashBlock; + if (!GetTransaction(it->first, tx, Params().GetConsensus(), hashBlock)) { + // tx has vanished, probably due to conflicts + it = txFirstSeenTime.erase(it); + } else if (!hashBlock.IsNull()) { + auto pindex = mapBlockIndex.at(hashBlock); + if (chainActive.Tip()->GetAncestor(pindex->nHeight) == pindex && chainActive.Height() - pindex->nHeight >= 6) { + // tx got confirmed >= 6 times, so we can stop keeping track of it + it = txFirstSeenTime.erase(it); + } else { + ++it; + } + } else { + ++it; + } + } + lastCleanupTime = GetTimeMillis(); } diff --git a/src/llmq/quorums_chainlocks.h b/src/llmq/quorums_chainlocks.h index b7cd441e040a3..410f2bf80bcb0 100644 --- a/src/llmq/quorums_chainlocks.h +++ b/src/llmq/quorums_chainlocks.h @@ -12,6 +12,7 @@ #include "chainparams.h" #include +#include class CBlockIndex; class CScheduler; @@ -61,6 +62,10 @@ class CChainLocksHandler : public CRecoveredSigsListener uint256 lastSignedRequestId; uint256 lastSignedMsgHash; + // We keep track of txids from recently received blocks so that we can check if all TXs got ixlocked + std::unordered_map>> blockTxs; + std::unordered_map txFirstSeenTime; + std::map seenChainLocks; int64_t lastCleanupTime{0}; @@ -79,6 +84,8 @@ class CChainLocksHandler : public CRecoveredSigsListener void ProcessNewChainLock(NodeId from, const CChainLockSig& clsig, const uint256& hash); void AcceptedBlockHeader(const CBlockIndex* pindexNew); void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork); + void NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr& block); + void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock); void EnforceBestChainLock(); virtual void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig); From 0a5e8eb862025d609164405f38f65a6e95e91557 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 28 Feb 2019 15:02:20 +0100 Subject: [PATCH 13/33] Move ChainLock signing into TrySignChainTip and call it periodically Later commits will introduce checks for "safe TXs" which might abort the signing on first try, but succeed a few seconds later, so we periodically retry to sign the tip. --- src/llmq/quorums_chainlocks.cpp | 52 ++++++++++++++++++++++++--------- src/llmq/quorums_chainlocks.h | 6 ++-- src/llmq/quorums_init.cpp | 4 +-- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index 67c9ddbeffeed..eed337bfd861d 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -35,12 +35,16 @@ CChainLocksHandler::~CChainLocksHandler() { } -void CChainLocksHandler::RegisterAsRecoveredSigsListener() +void CChainLocksHandler::Start() { quorumSigningManager->RegisterRecoveredSigsListener(this); + scheduler->scheduleEvery([&]() { + // regularely retry signing the current chaintip as it might have failed before due to missing ixlocks + TrySignChainTip(); + }, 5000); } -void CChainLocksHandler::UnregisterAsRecoveredSigsListener() +void CChainLocksHandler::Stop() { quorumSigningManager->UnregisterRecoveredSigsListener(this); } @@ -184,30 +188,52 @@ void CChainLocksHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew) void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork) { + // don't call TrySignChainTip directly but instead let the scheduler call it. This way we ensure that cs_main is + // never locked and TrySignChainTip is not called twice in parallel + LOCK(cs); + if (tryLockChainTipScheduled) { + return; + } + tryLockChainTipScheduled = true; + scheduler->scheduleFromNow([&]() { + TrySignChainTip(); + LOCK(cs); + tryLockChainTipScheduled = false; + }, 0); +} + +void CChainLocksHandler::TrySignChainTip() +{ + Cleanup(); + + const CBlockIndex* pindex; + { + LOCK(cs_main); + pindex = chainActive.Tip(); + } + if (!fMasternodeMode) { return; } - if (!pindexNew->pprev) { + if (!pindex->pprev) { return; } if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED)) { return; } - Cleanup(); - // DIP8 defines a process called "Signing attempts" which should run before the CLSIG is finalized // To simplify the initial implementation, we skip this process and directly try to create a CLSIG // This will fail when multiple blocks compete, but we accept this for the initial implementation. // Later, we'll add the multiple attempts process. - uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindexNew->nHeight)); - uint256 msgHash = pindexNew->GetBlockHash(); + uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindex->nHeight)); + uint256 msgHash = pindex->GetBlockHash(); { LOCK(cs); - if (bestChainLockBlockIndex == pindexNew) { + if (bestChainLockBlockIndex == pindex) { // we first got the CLSIG, then the header, and then the block was connected. // In this case there is no need to continue here. // However, NotifyChainLock might not have been called yet, so call it now if needed @@ -218,26 +244,26 @@ void CChainLocksHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBl return; } - if (InternalHasConflictingChainLock(pindexNew->nHeight, pindexNew->GetBlockHash())) { + if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) { if (!inEnforceBestChainLock) { // we accepted this block when there was no lock yet, but now a conflicting lock appeared. Invalidate it. LogPrintf("CChainLocksHandler::%s -- conflicting lock after block was accepted, invalidating now\n", __func__); - ScheduleInvalidateBlock(pindexNew); + ScheduleInvalidateBlock(pindex); } return; } - if (bestChainLock.nHeight >= pindexNew->nHeight) { + if (bestChainLock.nHeight >= pindex->nHeight) { // already got the same CLSIG or a better one return; } - if (pindexNew->nHeight == lastSignedHeight) { + if (pindex->nHeight == lastSignedHeight) { // already signed this one return; } - lastSignedHeight = pindexNew->nHeight; + lastSignedHeight = pindex->nHeight; lastSignedRequestId = requestId; lastSignedMsgHash = msgHash; } diff --git a/src/llmq/quorums_chainlocks.h b/src/llmq/quorums_chainlocks.h index 410f2bf80bcb0..93ed32404f888 100644 --- a/src/llmq/quorums_chainlocks.h +++ b/src/llmq/quorums_chainlocks.h @@ -49,6 +49,7 @@ class CChainLocksHandler : public CRecoveredSigsListener private: CScheduler* scheduler; CCriticalSection cs; + bool tryLockChainTipScheduled{false}; std::atomic inEnforceBestChainLock{false}; uint256 bestChainLockHash; @@ -74,8 +75,8 @@ class CChainLocksHandler : public CRecoveredSigsListener CChainLocksHandler(CScheduler* _scheduler); ~CChainLocksHandler(); - void RegisterAsRecoveredSigsListener(); - void UnregisterAsRecoveredSigsListener(); + void Start(); + void Stop(); bool AlreadyHave(const CInv& inv); bool GetChainLockByHash(const uint256& hash, CChainLockSig& ret); @@ -86,6 +87,7 @@ class CChainLocksHandler : public CRecoveredSigsListener void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork); void NewPoWValidBlock(const CBlockIndex* pindex, const std::shared_ptr& block); void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock); + void TrySignChainTip(); void EnforceBestChainLock(); virtual void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig); diff --git a/src/llmq/quorums_init.cpp b/src/llmq/quorums_init.cpp index a2620683b6fc8..578d9783e4bbc 100644 --- a/src/llmq/quorums_init.cpp +++ b/src/llmq/quorums_init.cpp @@ -66,7 +66,7 @@ void StartLLMQSystem() quorumSigSharesManager->StartWorkerThread(); } if (chainLocksHandler) { - chainLocksHandler->RegisterAsRecoveredSigsListener(); + chainLocksHandler->Start(); } if (quorumInstantSendManager) { quorumInstantSendManager->RegisterAsRecoveredSigsListener(); @@ -79,7 +79,7 @@ void StopLLMQSystem() quorumInstantSendManager->UnregisterAsRecoveredSigsListener(); } if (chainLocksHandler) { - chainLocksHandler->UnregisterAsRecoveredSigsListener(); + chainLocksHandler->Stop(); } if (quorumSigSharesManager) { quorumSigSharesManager->StopWorkerThread(); From 96291e7a0f9a363b8d55aa613898af0615b1d0e5 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Mar 2019 06:45:11 +0100 Subject: [PATCH 14/33] Cheaper/Faster bailout from TrySignChainTip when already signed before --- src/llmq/quorums_chainlocks.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index eed337bfd861d..eb016ed045795 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -227,9 +227,6 @@ void CChainLocksHandler::TrySignChainTip() // This will fail when multiple blocks compete, but we accept this for the initial implementation. // Later, we'll add the multiple attempts process. - uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindex->nHeight)); - uint256 msgHash = pindex->GetBlockHash(); - { LOCK(cs); @@ -244,6 +241,16 @@ void CChainLocksHandler::TrySignChainTip() return; } + if (pindex->nHeight == lastSignedHeight) { + // already signed this one + return; + } + + if (bestChainLock.nHeight >= pindex->nHeight) { + // already got the same CLSIG or a better one + return; + } + if (InternalHasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) { if (!inEnforceBestChainLock) { // we accepted this block when there was no lock yet, but now a conflicting lock appeared. Invalidate it. @@ -253,14 +260,15 @@ void CChainLocksHandler::TrySignChainTip() } return; } + } - if (bestChainLock.nHeight >= pindex->nHeight) { - // already got the same CLSIG or a better one - return; - } + uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindex->nHeight)); + uint256 msgHash = pindex->GetBlockHash(); - if (pindex->nHeight == lastSignedHeight) { - // already signed this one + { + LOCK(cs); + if (bestChainLock.nHeight >= pindex->nHeight) { + // might have happened while we didn't hold cs return; } lastSignedHeight = pindex->nHeight; From 2a7a5c6338c3b528a1627faf72a3d39c64a3adb6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Mar 2019 06:46:31 +0100 Subject: [PATCH 15/33] Only sign ChainLocks when all included TXs are "safe" Safe means that the TX is either ixlocked or known since at least 10 minutes. Also change miner code to only include safe TXs in block templates. --- src/llmq/quorums_chainlocks.cpp | 80 +++++++++++++++++++++++++++++++++ src/llmq/quorums_chainlocks.h | 5 +++ src/miner.cpp | 10 +++++ 3 files changed, 95 insertions(+) diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index eb016ed045795..4d26115caedc7 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -4,6 +4,7 @@ #include "quorums.h" #include "quorums_chainlocks.h" +#include "quorums_instantsend.h" #include "quorums_signing.h" #include "quorums_utils.h" @@ -262,6 +263,61 @@ void CChainLocksHandler::TrySignChainTip() } } + LogPrintf("CChainLocksHandler::%s -- trying to sign %s, height=%d\n", __func__, pindex->GetBlockHash().ToString(), pindex->nHeight); + + // When the new IX system is activated, we only try to ChainLock blocks which include safe transactions. A TX is + // considered safe when it is ixlocked or at least known since 10 minutes (from mempool or block). These checks are + // performed for the tip (which we try to sign) and the previous 5 blocks. If a ChainLocked block is found on the + // way down, we consider all TXs to be safe. + if (IsNewInstantSendEnabled() && sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) { + auto pindexWalk = pindex; + while (pindexWalk) { + if (pindex->nHeight - pindexWalk->nHeight > 5) { + // no need to check further down, 6 confs is safe to assume that TXs below this height won't be + // ixlocked anymore if they aren't already + LogPrintf("CChainLocksHandler::%s -- tip and previous 5 blocks all safe\n", __func__); + break; + } + if (HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) { + // we don't care about ixlocks for TXs that are ChainLocked already + LogPrintf("CChainLocksHandler::%s -- chainlock at height %d \n", __func__, pindexWalk->nHeight); + break; + } + + decltype(blockTxs.begin()->second) txids; + { + LOCK(cs); + auto it = blockTxs.find(pindexWalk->GetBlockHash()); + if (it == blockTxs.end()) { + // this should actually not happen as NewPoWValidBlock should have been called before + LogPrintf("CChainLocksHandler::%s -- blockTxs for %s not found\n", __func__, + pindexWalk->GetBlockHash().ToString()); + return; + } + txids = it->second; + } + + for (auto& txid : *txids) { + int64_t txAge = 0; + { + LOCK(cs); + auto it = txFirstSeenTime.find(txid); + if (it != txFirstSeenTime.end()) { + txAge = GetAdjustedTime() - it->second; + } + } + + if (txAge < WAIT_FOR_IXLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) { + LogPrintf("CChainLocksHandler::%s -- not signing block %s due to TX %s not being ixlocked and not old enough. age=%d\n", __func__, + pindexWalk->GetBlockHash().ToString(), txid.ToString(), txAge); + return; + } + } + + pindexWalk = pindexWalk->pprev; + } + } + uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, pindex->nHeight)); uint256 msgHash = pindex->GetBlockHash(); @@ -324,6 +380,30 @@ void CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockInd txFirstSeenTime.emplace(tx.GetHash(), curTime); } +bool CChainLocksHandler::IsTxSafeForMining(const uint256& txid) +{ + if (!sporkManager.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED) || !sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) { + return true; + } + if (!IsNewInstantSendEnabled()) { + return true; + } + + int64_t txAge = 0; + { + LOCK(cs); + auto it = txFirstSeenTime.find(txid); + if (it != txFirstSeenTime.end()) { + txAge = GetAdjustedTime() - it->second; + } + } + + if (txAge < WAIT_FOR_IXLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) { + return false; + } + return true; +} + // WARNING: cs_main and cs should not be held! void CChainLocksHandler::EnforceBestChainLock() { diff --git a/src/llmq/quorums_chainlocks.h b/src/llmq/quorums_chainlocks.h index 93ed32404f888..05fbd9a691ad1 100644 --- a/src/llmq/quorums_chainlocks.h +++ b/src/llmq/quorums_chainlocks.h @@ -46,6 +46,9 @@ class CChainLocksHandler : public CRecoveredSigsListener static const int64_t CLEANUP_INTERVAL = 1000 * 30; static const int64_t CLEANUP_SEEN_TIMEOUT = 24 * 60 * 60 * 1000; + // how long to wait for ixlocks until we consider a block with non-ixlocked TXs to be safe to sign + static const int64_t WAIT_FOR_IXLOCK_TIMEOUT = 10 * 60; + private: CScheduler* scheduler; CCriticalSection cs; @@ -94,6 +97,8 @@ class CChainLocksHandler : public CRecoveredSigsListener bool HasChainLock(int nHeight, const uint256& blockHash); bool HasConflictingChainLock(int nHeight, const uint256& blockHash); + bool IsTxSafeForMining(const uint256& txid); + private: // these require locks to be held already bool InternalHasChainLock(int nHeight, const uint256& blockHash); diff --git a/src/miner.cpp b/src/miner.cpp index c07061f329cae..1e2e8ba2c4598 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -34,6 +34,7 @@ #include "evo/deterministicmns.h" #include "llmq/quorums_blockprocessor.h" +#include "llmq/quorums_chainlocks.h" #include #include @@ -453,6 +454,11 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda continue; } + if (!llmq::chainLocksHandler->IsTxSafeForMining(mi->GetTx().GetHash())) { + ++mi; + continue; + } + // Now that mi is not stale, determine which transaction to evaluate: // the next entry from mapTx, or the best from mapModifiedTx? bool fUsingModified = false; @@ -601,6 +607,10 @@ void BlockAssembler::addPriorityTxs() continue; } + if (!llmq::chainLocksHandler->IsTxSafeForMining(iter->GetTx().GetHash())) { + continue; + } + // If this tx fits in the block add it, otherwise keep looping if (TestForBlock(iter)) { AddToBlock(iter); From baf8b81c4a78b19995bd2942de9a5d88bb25dd8d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Mar 2019 15:27:51 +0100 Subject: [PATCH 16/33] Fix no-wallet build --- src/llmq/quorums_instantsend.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index 737c22b29a10b..db84906752688 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -15,7 +15,10 @@ #include "scheduler.h" #include "spork.h" #include "validation.h" + +#ifdef ENABLE_WALLET #include "wallet/wallet.h" +#endif // needed for nCompleteTXLocks #include "instantx.h" From f44f09ca04f3415189b6d3bd0a5f17412b251687 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Mar 2019 15:29:49 +0100 Subject: [PATCH 17/33] Fix crash in BlockAssembler::addPackageTxs --- src/miner.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/miner.cpp b/src/miner.cpp index 1e2e8ba2c4598..64fd4fc93f53a 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -454,7 +454,8 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda continue; } - if (!llmq::chainLocksHandler->IsTxSafeForMining(mi->GetTx().GetHash())) { + if (mi != mempool.mapTx.get().end() && + !llmq::chainLocksHandler->IsTxSafeForMining(mi->GetTx().GetHash())) { ++mi; continue; } From 5b8344e8f85ae6335d594465ba5b8fbe9a73ee96 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Mar 2019 16:10:24 +0100 Subject: [PATCH 18/33] Use scheduleFromNow instead of schedule+boost::chrono --- src/llmq/quorums_instantsend.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index db84906752688..21eaf63f2266e 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -382,9 +382,9 @@ void CInstantSendManager::ProcessMessageInstantXLock(CNode* pfrom, const llmq::C if (!hasScheduledProcessPending) { hasScheduledProcessPending = true; - scheduler->schedule([&] { + scheduler->scheduleFromNow([&] { ProcessPendingInstantXLocks(); - }, boost::chrono::system_clock::now() + boost::chrono::milliseconds(100)); + }, 100); } } From e2f99f4ae1f9ff888ba748ca7b806946520f2567 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Mar 2019 16:13:47 +0100 Subject: [PATCH 19/33] Set llmqForInstantSend for mainnet and testnet --- src/chainparams.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 314584f7d7885..8d850068c2c10 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -308,6 +308,7 @@ class CMainParams : public CChainParams { consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60; consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85; consensus.llmqChainLocks = Consensus::LLMQ_400_60; + consensus.llmqForInstantSend = Consensus::LLMQ_50_60; fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; @@ -476,6 +477,7 @@ class CTestNetParams : public CChainParams { consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60; consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85; consensus.llmqChainLocks = Consensus::LLMQ_50_60; + consensus.llmqForInstantSend = Consensus::LLMQ_50_60; fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; From 9e9081110bd7b2390c27247f32379b58501143de Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Mar 2019 07:57:51 +0100 Subject: [PATCH 20/33] Add override keywork to CDSNotificationInterface::NotifyChainLock --- src/dsnotificationinterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dsnotificationinterface.h b/src/dsnotificationinterface.h index 60c1e5bd3611f..c345551153e38 100644 --- a/src/dsnotificationinterface.h +++ b/src/dsnotificationinterface.h @@ -24,7 +24,7 @@ class CDSNotificationInterface : public CValidationInterface void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr& block) override; void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock) override; void NotifyMasternodeListChanged(const CDeterministicMNList& newList) override; - void NotifyChainLock(const CBlockIndex* pindex); + void NotifyChainLock(const CBlockIndex* pindex) override; private: CConnman& connman; From 280690792ab450fc73c458fe7f1a3fd928ded1d8 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Mar 2019 07:58:12 +0100 Subject: [PATCH 21/33] Combine loops in CChainLocksHandler::NewPoWValidBlock --- src/llmq/quorums_chainlocks.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index 4d26115caedc7..fa3d408377348 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -344,6 +344,8 @@ void CChainLocksHandler::NewPoWValidBlock(const CBlockIndex* pindex, const std:: return; } + int64_t curTime = GetAdjustedTime(); + // We listen for NewPoWValidBlock so that we can collect all TX ids of all included TXs of newly received blocks // We need this information later when we try to sign a new tip, so that we can determine if all included TXs are // safe. @@ -357,13 +359,9 @@ void CChainLocksHandler::NewPoWValidBlock(const CBlockIndex* pindex, const std:: } } txs->emplace(tx->GetHash()); - } - blockTxs[pindex->GetBlockHash()] = txs; - - int64_t curTime = GetAdjustedTime(); - for (auto& tx : block->vtx) { txFirstSeenTime.emplace(tx->GetHash(), curTime); } + blockTxs[pindex->GetBlockHash()] = txs; } void CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock) From f5dcb00acf69efed63af8b033fa46cfe6535bb6c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Mar 2019 08:04:18 +0100 Subject: [PATCH 22/33] Introduce spork SPORK_20_INSTANTSEND_LLMQ_BASED to switch between new/old system --- src/llmq/quorums_instantsend.cpp | 15 +++------------ src/llmq/quorums_instantsend.h | 12 +++++------- src/spork.cpp | 3 +++ src/spork.h | 3 ++- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index 21eaf63f2266e..3eb7c550f366c 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -861,26 +861,17 @@ bool CInstantSendManager::GetConflictingTx(const CTransaction& tx, uint256& retC bool IsOldInstantSendEnabled() { - int spork2Value = sporkManager.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED); - if (spork2Value == 0) { - return true; - } - return false; + return sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED) && !sporkManager.IsSporkActive(SPORK_20_INSTANTSEND_LLMQ_BASED); } bool IsNewInstantSendEnabled() { - int spork2Value = sporkManager.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED); - if (spork2Value == 1) { - return true; - } - return false; + return sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED) && sporkManager.IsSporkActive(SPORK_20_INSTANTSEND_LLMQ_BASED); } bool IsInstantSendEnabled() { - int spork2Value = sporkManager.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED); - return spork2Value == 0 || spork2Value == 1; + return sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED); } } diff --git a/src/llmq/quorums_instantsend.h b/src/llmq/quorums_instantsend.h index 36ce131d40935..66a6a1cad3777 100644 --- a/src/llmq/quorums_instantsend.h +++ b/src/llmq/quorums_instantsend.h @@ -133,13 +133,11 @@ class CInstantSendManager : public CRecoveredSigsListener extern CInstantSendManager* quorumInstantSendManager; -// The meaning of spork 2 has changed in v0.14. Before that, spork 2 was simply time based and either enabled or not -// After 0.14, spork 2 can have 3 states. -// 0 = old system is active (0 is compatible with the value set on mainnet at time of deployment) -// 1 = new system is active (old nodes will interpret this as the old system being enabled, but then won't get enough IX lock votes) -// everything else = disabled -// TODO When the new system is fully deployed and enabled, we can remove this special handling of the spork in a future version -// and revert to the old behaviour. +// This involves 2 sporks: SPORK_2_INSTANTSEND_ENABLED and SPORK_20_INSTANTSEND_LLMQ_BASED +// SPORK_2_INSTANTSEND_ENABLED generally enables/disables InstantSend and SPORK_20_INSTANTSEND_LLMQ_BASED switches +// between the old and the new (LLMQ based) system +// TODO When the new system is fully deployed and enabled, we can remove this special handling in a future version +// and revert to only using SPORK_2_INSTANTSEND_ENABLED. bool IsOldInstantSendEnabled(); bool IsNewInstantSendEnabled(); bool IsInstantSendEnabled(); diff --git a/src/spork.cpp b/src/spork.cpp index fe4fbf2967f19..b12d04bcfe5a9 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -29,6 +29,7 @@ std::map mapSporkDefaults = { {SPORK_17_QUORUM_DKG_ENABLED, 4070908800ULL}, // OFF {SPORK_18_QUORUM_DEBUG_ENABLED, 4070908800ULL}, // OFF {SPORK_19_CHAINLOCKS_ENABLED, 4070908800ULL}, // OFF + {SPORK_20_INSTANTSEND_LLMQ_BASED, 4070908800ULL}, // OFF }; bool CSporkManager::SporkValueIsActive(int nSporkID, int64_t &nActiveValueRet) const @@ -291,6 +292,7 @@ int CSporkManager::GetSporkIDByName(const std::string& strName) if (strName == "SPORK_17_QUORUM_DKG_ENABLED") return SPORK_17_QUORUM_DKG_ENABLED; if (strName == "SPORK_18_QUORUM_DEBUG_ENABLED") return SPORK_18_QUORUM_DEBUG_ENABLED; if (strName == "SPORK_19_CHAINLOCKS_ENABLED") return SPORK_19_CHAINLOCKS_ENABLED; + if (strName == "SPORK_20_INSTANTSEND_LLMQ_BASED") return SPORK_20_INSTANTSEND_LLMQ_BASED; LogPrint("spork", "CSporkManager::GetSporkIDByName -- Unknown Spork name '%s'\n", strName); return -1; @@ -310,6 +312,7 @@ std::string CSporkManager::GetSporkNameByID(int nSporkID) case SPORK_17_QUORUM_DKG_ENABLED: return "SPORK_17_QUORUM_DKG_ENABLED"; case SPORK_18_QUORUM_DEBUG_ENABLED: return "SPORK_18_QUORUM_DEBUG_ENABLED"; case SPORK_19_CHAINLOCKS_ENABLED: return "SPORK_19_CHAINLOCKS_ENABLED"; + case SPORK_20_INSTANTSEND_LLMQ_BASED: return "SPORK_20_INSTANTSEND_LLMQ_BASED"; default: LogPrint("spork", "CSporkManager::GetSporkNameByID -- Unknown Spork ID %d\n", nSporkID); return "Unknown"; diff --git a/src/spork.h b/src/spork.h index 55d9d62305564..e02ac30623a95 100644 --- a/src/spork.h +++ b/src/spork.h @@ -28,9 +28,10 @@ static const int SPORK_16_INSTANTSEND_AUTOLOCKS = 10015; static const int SPORK_17_QUORUM_DKG_ENABLED = 10016; static const int SPORK_18_QUORUM_DEBUG_ENABLED = 10017; static const int SPORK_19_CHAINLOCKS_ENABLED = 10018; +static const int SPORK_20_INSTANTSEND_LLMQ_BASED = 10019; static const int SPORK_START = SPORK_2_INSTANTSEND_ENABLED; -static const int SPORK_END = SPORK_19_CHAINLOCKS_ENABLED; +static const int SPORK_END = SPORK_20_INSTANTSEND_LLMQ_BASED; extern std::map mapSporkDefaults; extern CSporkManager sporkManager; From 2299ee2836017c53d94590c8bfc5cd334cf9647e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Mar 2019 10:58:47 +0100 Subject: [PATCH 23/33] Rename IXLOCK to ISLOCK and InstantX to InstantSend --- src/llmq/quorums_chainlocks.cpp | 4 +- src/llmq/quorums_chainlocks.h | 2 +- src/llmq/quorums_instantsend.cpp | 302 +++++++++++++++---------------- src/llmq/quorums_instantsend.h | 62 +++---- src/llmq/quorums_signing.cpp | 2 +- src/net_processing.cpp | 12 +- src/protocol.cpp | 6 +- src/protocol.h | 4 +- src/wallet/wallet.cpp | 2 +- 9 files changed, 198 insertions(+), 198 deletions(-) diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index fa3d408377348..a077224c9e3e7 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -307,7 +307,7 @@ void CChainLocksHandler::TrySignChainTip() } } - if (txAge < WAIT_FOR_IXLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) { + if (txAge < WAIT_FOR_ISLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) { LogPrintf("CChainLocksHandler::%s -- not signing block %s due to TX %s not being ixlocked and not old enough. age=%d\n", __func__, pindexWalk->GetBlockHash().ToString(), txid.ToString(), txAge); return; @@ -396,7 +396,7 @@ bool CChainLocksHandler::IsTxSafeForMining(const uint256& txid) } } - if (txAge < WAIT_FOR_IXLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) { + if (txAge < WAIT_FOR_ISLOCK_TIMEOUT && !quorumInstantSendManager->IsLocked(txid)) { return false; } return true; diff --git a/src/llmq/quorums_chainlocks.h b/src/llmq/quorums_chainlocks.h index 05fbd9a691ad1..722621fc654f3 100644 --- a/src/llmq/quorums_chainlocks.h +++ b/src/llmq/quorums_chainlocks.h @@ -47,7 +47,7 @@ class CChainLocksHandler : public CRecoveredSigsListener static const int64_t CLEANUP_SEEN_TIMEOUT = 24 * 60 * 60 * 1000; // how long to wait for ixlocks until we consider a block with non-ixlocked TXs to be safe to sign - static const int64_t WAIT_FOR_IXLOCK_TIMEOUT = 10 * 60; + static const int64_t WAIT_FOR_ISLOCK_TIMEOUT = 10 * 60; private: CScheduler* scheduler; diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index 3eb7c550f366c..6104f5b3281a3 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -29,14 +29,14 @@ namespace llmq { static const std::string INPUTLOCK_REQUESTID_PREFIX = "inlock"; -static const std::string IXLOCK_REQUESTID_PREFIX = "ixlock"; +static const std::string ISLOCK_REQUESTID_PREFIX = "islock"; CInstantSendManager* quorumInstantSendManager; -uint256 CInstantXLock::GetRequestId() const +uint256 CInstantSendLock::GetRequestId() const { CHashWriter hw(SER_GETHASH, 0); - hw << IXLOCK_REQUESTID_PREFIX; + hw << ISLOCK_REQUESTID_PREFIX; hw << inputs; return hw.GetHash(); } @@ -102,7 +102,7 @@ bool CInstantSendManager::ProcessTx(CNode* pfrom, const CTransaction& tx, CConnm auto it = inputVotes.find(ids[i]); if (it != inputVotes.end()) { if (it->second != tx.GetHash()) { - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: input %s is conflicting with ixlock %s\n", __func__, + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: input %s is conflicting with islock %s\n", __func__, tx.GetHash().ToString(), tx.vin[i].prevout.ToStringShort(), it->second.ToString()); return false; } @@ -129,8 +129,8 @@ bool CInstantSendManager::ProcessTx(CNode* pfrom, const CTransaction& tx, CConnm } // We might have received all input locks before we got the corresponding TX. In this case, we have to sign the - // ixlock now instead of waiting for the input locks. - TrySignInstantXLock(tx); + // islock now instead of waiting for the input locks. + TrySignInstantSendLock(tx); return true; } @@ -237,21 +237,21 @@ void CInstantSendManager::HandleNewRecoveredSig(const CRecoveredSig& recoveredSi auto& params = Params().GetConsensus().llmqs.at(llmqType); uint256 txid; - bool isInstantXLock = false; + bool isInstantSendLock = false; { LOCK(cs); auto it = inputVotes.find(recoveredSig.id); if (it != inputVotes.end()) { txid = it->second; } - if (creatingInstantXLocks.count(recoveredSig.id)) { - isInstantXLock = true; + if (creatingInstantSendLocks.count(recoveredSig.id)) { + isInstantSendLock = true; } } if (!txid.IsNull()) { HandleNewInputLockRecoveredSig(recoveredSig, txid); - } else if (isInstantXLock) { - HandleNewInstantXLockRecoveredSig(recoveredSig); + } else if (isInstantSendLock) { + HandleNewInstantSendLockRecoveredSig(recoveredSig); } } @@ -276,10 +276,10 @@ void CInstantSendManager::HandleNewInputLockRecoveredSig(const CRecoveredSig& re } } - TrySignInstantXLock(*tx); + TrySignInstantSendLock(*tx); } -void CInstantSendManager::TrySignInstantXLock(const CTransaction& tx) +void CInstantSendManager::TrySignInstantSendLock(const CTransaction& tx) { auto llmqType = Params().GetConsensus().llmqForInstantSend; @@ -290,17 +290,17 @@ void CInstantSendManager::TrySignInstantXLock(const CTransaction& tx) } } - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: got all recovered sigs, creating CInstantXLock\n", __func__, + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: got all recovered sigs, creating CInstantSendLock\n", __func__, tx.GetHash().ToString()); - CInstantXLockInfo ixlockInfo; - ixlockInfo.time = GetTimeMillis(); - ixlockInfo.ixlock.txid = tx.GetHash(); + CInstantSendLockInfo islockInfo; + islockInfo.time = GetTimeMillis(); + islockInfo.islock.txid = tx.GetHash(); for (auto& in : tx.vin) { - ixlockInfo.ixlock.inputs.emplace_back(in.prevout); + islockInfo.islock.inputs.emplace_back(in.prevout); } - auto id = ixlockInfo.ixlock.GetRequestId(); + auto id = islockInfo.islock.GetRequestId(); if (quorumSigningManager->HasRecoveredSigForId(llmqType, id)) { return; @@ -308,40 +308,40 @@ void CInstantSendManager::TrySignInstantXLock(const CTransaction& tx) { LOCK(cs); - auto e = creatingInstantXLocks.emplace(id, ixlockInfo); + auto e = creatingInstantSendLocks.emplace(id, islockInfo); if (!e.second) { return; } - txToCreatingInstantXLocks.emplace(tx.GetHash(), &e.first->second); + txToCreatingInstantSendLocks.emplace(tx.GetHash(), &e.first->second); } quorumSigningManager->AsyncSignIfMember(llmqType, id, tx.GetHash()); } -void CInstantSendManager::HandleNewInstantXLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig) +void CInstantSendManager::HandleNewInstantSendLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig) { - CInstantXLockInfo ixlockInfo; + CInstantSendLockInfo islockInfo; { LOCK(cs); - auto it = creatingInstantXLocks.find(recoveredSig.id); - if (it == creatingInstantXLocks.end()) { + auto it = creatingInstantSendLocks.find(recoveredSig.id); + if (it == creatingInstantSendLocks.end()) { return; } - ixlockInfo = std::move(it->second); - creatingInstantXLocks.erase(it); - txToCreatingInstantXLocks.erase(ixlockInfo.ixlock.txid); + islockInfo = std::move(it->second); + creatingInstantSendLocks.erase(it); + txToCreatingInstantSendLocks.erase(islockInfo.islock.txid); } - if (ixlockInfo.ixlock.txid != recoveredSig.msgHash) { - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: ixlock conflicts with %s, dropping own version", __func__, - ixlockInfo.ixlock.txid.ToString(), recoveredSig.msgHash.ToString()); + if (islockInfo.islock.txid != recoveredSig.msgHash) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: islock conflicts with %s, dropping own version", __func__, + islockInfo.islock.txid.ToString(), recoveredSig.msgHash.ToString()); return; } - ixlockInfo.ixlock.sig = recoveredSig.sig; - ProcessInstantXLock(-1, ::SerializeHash(ixlockInfo.ixlock), ixlockInfo.ixlock); + islockInfo.islock.sig = recoveredSig.sig; + ProcessInstantSendLock(-1, ::SerializeHash(islockInfo.islock), islockInfo.islock); } void CInstantSendManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) @@ -350,17 +350,17 @@ void CInstantSendManager::ProcessMessage(CNode* pfrom, const std::string& strCom return; } - if (strCommand == NetMsgType::IXLOCK) { - CInstantXLock ixlock; - vRecv >> ixlock; - ProcessMessageInstantXLock(pfrom, ixlock, connman); + if (strCommand == NetMsgType::ISLOCK) { + CInstantSendLock islock; + vRecv >> islock; + ProcessMessageInstantSendLock(pfrom, islock, connman); } } -void CInstantSendManager::ProcessMessageInstantXLock(CNode* pfrom, const llmq::CInstantXLock& ixlock, CConnman& connman) +void CInstantSendManager::ProcessMessageInstantSendLock(CNode* pfrom, const llmq::CInstantSendLock& islock, CConnman& connman) { bool ban = false; - if (!PreVerifyInstantXLock(pfrom->id, ixlock, ban)) { + if (!PreVerifyInstantSendLock(pfrom->id, islock, ban)) { if (ban) { LOCK(cs_main); Misbehaving(pfrom->id, 100); @@ -368,37 +368,37 @@ void CInstantSendManager::ProcessMessageInstantXLock(CNode* pfrom, const llmq::C return; } - auto hash = ::SerializeHash(ixlock); + auto hash = ::SerializeHash(islock); LOCK(cs); - if (pendingInstantXLocks.count(hash) || finalInstantXLocks.count(hash)) { + if (pendingInstantSendLocks.count(hash) || finalInstantSendLocks.count(hash)) { return; } - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: received ixlock, peer=%d\n", __func__, - ixlock.txid.ToString(), hash.ToString(), pfrom->id); + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: received islock, peer=%d\n", __func__, + islock.txid.ToString(), hash.ToString(), pfrom->id); - pendingInstantXLocks.emplace(hash, std::make_pair(pfrom->id, std::move(ixlock))); + pendingInstantSendLocks.emplace(hash, std::make_pair(pfrom->id, std::move(islock))); if (!hasScheduledProcessPending) { hasScheduledProcessPending = true; scheduler->scheduleFromNow([&] { - ProcessPendingInstantXLocks(); + ProcessPendingInstantSendLocks(); }, 100); } } -bool CInstantSendManager::PreVerifyInstantXLock(NodeId nodeId, const llmq::CInstantXLock& ixlock, bool& retBan) +bool CInstantSendManager::PreVerifyInstantSendLock(NodeId nodeId, const llmq::CInstantSendLock& islock, bool& retBan) { retBan = false; - if (ixlock.txid.IsNull() || !ixlock.sig.IsValid() || ixlock.inputs.empty()) { + if (islock.txid.IsNull() || !islock.sig.IsValid() || islock.inputs.empty()) { retBan = true; return false; } std::set dups; - for (auto& o : ixlock.inputs) { + for (auto& o : islock.inputs) { if (!dups.emplace(o).second) { retBan = true; return false; @@ -408,16 +408,16 @@ bool CInstantSendManager::PreVerifyInstantXLock(NodeId nodeId, const llmq::CInst return true; } -void CInstantSendManager::ProcessPendingInstantXLocks() +void CInstantSendManager::ProcessPendingInstantSendLocks() { auto llmqType = Params().GetConsensus().llmqForInstantSend; - decltype(pendingInstantXLocks) pend; + decltype(pendingInstantSendLocks) pend; { LOCK(cs); hasScheduledProcessPending = false; - pend = std::move(pendingInstantXLocks); + pend = std::move(pendingInstantSendLocks); } if (!IsNewInstantSendEnabled()) { @@ -436,12 +436,12 @@ void CInstantSendManager::ProcessPendingInstantXLocks() for (const auto& p : pend) { auto& hash = p.first; auto nodeId = p.second.first; - auto& ixlock = p.second.second; + auto& islock = p.second.second; - auto id = ixlock.GetRequestId(); + auto id = islock.GetRequestId(); - // no need to verify an IXLOCK if we already have verified the recovered sig that belongs to it - if (quorumSigningManager->HasRecoveredSig(llmqType, id, ixlock.txid)) { + // no need to verify an ISLOCK if we already have verified the recovered sig that belongs to it + if (quorumSigningManager->HasRecoveredSig(llmqType, id, islock.txid)) { continue; } @@ -450,10 +450,10 @@ void CInstantSendManager::ProcessPendingInstantXLocks() // should not happen, but if one fails to select, all others will also fail to select return; } - uint256 signHash = CLLMQUtils::BuildSignHash(llmqType, quorum->quorumHash, id, ixlock.txid); - batchVerifier.PushMessage(nodeId, hash, signHash, ixlock.sig, quorum->quorumPublicKey); + uint256 signHash = CLLMQUtils::BuildSignHash(llmqType, quorum->quorumHash, id, islock.txid); + batchVerifier.PushMessage(nodeId, hash, signHash, islock.sig, quorum->quorumPublicKey); - // We can reconstruct the CRecoveredSig objects from the ixlock and pass it to the signing manager, which + // We can reconstruct the CRecoveredSig objects from the islock and pass it to the signing manager, which // avoids unnecessary double-verification of the signature. We however only do this when verification here // turns out to be good (which is checked further down) if (!quorumSigningManager->HasRecoveredSigForId(llmqType, id)) { @@ -461,8 +461,8 @@ void CInstantSendManager::ProcessPendingInstantXLocks() recSig.llmqType = llmqType; recSig.quorumHash = quorum->quorumHash; recSig.id = id; - recSig.msgHash = ixlock.txid; - recSig.sig = ixlock.sig; + recSig.msgHash = islock.txid; + recSig.sig = islock.sig; recSigs.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(std::move(quorum), std::move(recSig))); @@ -482,15 +482,15 @@ void CInstantSendManager::ProcessPendingInstantXLocks() for (const auto& p : pend) { auto& hash = p.first; auto nodeId = p.second.first; - auto& ixlock = p.second.second; + auto& islock = p.second.second; if (batchVerifier.badMessages.count(hash)) { - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: invalid sig in ixlock, peer=%d\n", __func__, - ixlock.txid.ToString(), hash.ToString(), nodeId); + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: invalid sig in islock, peer=%d\n", __func__, + islock.txid.ToString(), hash.ToString(), nodeId); continue; } - ProcessInstantXLock(nodeId, hash, ixlock); + ProcessInstantSendLock(nodeId, hash, islock); // See comment further on top. We pass a reconstructed recovered sig to the signing manager to avoid // double-verification of the sig. @@ -500,40 +500,40 @@ void CInstantSendManager::ProcessPendingInstantXLocks() auto& recSig = it->second.second; if (!quorumSigningManager->HasRecoveredSigForId(llmqType, recSig.id)) { recSig.UpdateHash(); - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: passing reconstructed recSig to signing mgr, peer=%d\n", __func__, - ixlock.txid.ToString(), hash.ToString(), nodeId); + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: passing reconstructed recSig to signing mgr, peer=%d\n", __func__, + islock.txid.ToString(), hash.ToString(), nodeId); quorumSigningManager->PushReconstructedRecoveredSig(recSig, quorum); } } } } -void CInstantSendManager::ProcessInstantXLock(NodeId from, const uint256& hash, const CInstantXLock& ixlock) +void CInstantSendManager::ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLock& islock) { { LOCK(cs_main); g_connman->RemoveAskFor(hash); } - CInstantXLockInfo ixlockInfo; - ixlockInfo.time = GetTimeMillis(); - ixlockInfo.ixlock = ixlock; - ixlockInfo.ixlockHash = hash; + CInstantSendLockInfo islockInfo; + islockInfo.time = GetTimeMillis(); + islockInfo.islock = islock; + islockInfo.islockHash = hash; uint256 hashBlock; // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally - if (GetTransaction(ixlock.txid, ixlockInfo.tx, Params().GetConsensus(), hashBlock)) { + if (GetTransaction(islock.txid, islockInfo.tx, Params().GetConsensus(), hashBlock)) { if (!hashBlock.IsNull()) { { LOCK(cs_main); - ixlockInfo.pindexMined = mapBlockIndex.at(hashBlock); + islockInfo.pindexMined = mapBlockIndex.at(hashBlock); } - // Let's see if the TX that was locked by this ixlock is already mined in a ChainLocked block. If yes, - // we can simply ignore the ixlock, as the ChainLock implies locking of all TXs in that chain - if (llmq::chainLocksHandler->HasChainLock(ixlockInfo.pindexMined->nHeight, ixlockInfo.pindexMined->GetBlockHash())) { - LogPrint("instantsend", "CInstantSendManager::%s -- txlock=%s, ixlock=%s: dropping ixlock as it already got a ChainLock in block %s, peer=%d\n", __func__, - ixlock.txid.ToString(), hash.ToString(), hashBlock.ToString(), from); + // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes, + // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain + if (llmq::chainLocksHandler->HasChainLock(islockInfo.pindexMined->nHeight, islockInfo.pindexMined->GetBlockHash())) { + LogPrint("instantsend", "CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a ChainLock in block %s, peer=%d\n", __func__, + islock.txid.ToString(), hash.ToString(), hashBlock.ToString(), from); return; } } @@ -541,45 +541,45 @@ void CInstantSendManager::ProcessInstantXLock(NodeId from, const uint256& hash, { LOCK(cs); - auto e = finalInstantXLocks.emplace(hash, ixlockInfo); + auto e = finalInstantSendLocks.emplace(hash, islockInfo); if (!e.second) { return; } - auto ixlockInfoPtr = &e.first->second; + auto islockInfoPtr = &e.first->second; - creatingInstantXLocks.erase(ixlockInfoPtr->ixlock.GetRequestId()); - txToCreatingInstantXLocks.erase(ixlockInfoPtr->ixlock.txid); + creatingInstantSendLocks.erase(islockInfoPtr->islock.GetRequestId()); + txToCreatingInstantSendLocks.erase(islockInfoPtr->islock.txid); - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: processsing ixlock, peer=%d\n", __func__, - ixlock.txid.ToString(), hash.ToString(), from); + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: processsing islock, peer=%d\n", __func__, + islock.txid.ToString(), hash.ToString(), from); - if (!txToInstantXLock.emplace(ixlock.txid, ixlockInfoPtr).second) { - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: duplicate ixlock, other ixlock=%s, peer=%d\n", __func__, - ixlock.txid.ToString(), hash.ToString(), txToInstantXLock[ixlock.txid]->ixlockHash.ToString(), from); - txToInstantXLock.erase(hash); + if (!txToInstantSendLock.emplace(islock.txid, islockInfoPtr).second) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: duplicate islock, other islock=%s, peer=%d\n", __func__, + islock.txid.ToString(), hash.ToString(), txToInstantSendLock[islock.txid]->islockHash.ToString(), from); + txToInstantSendLock.erase(hash); return; } - for (size_t i = 0; i < ixlock.inputs.size(); i++) { - auto& in = ixlock.inputs[i]; - if (!inputToInstantXLock.emplace(in, ixlockInfoPtr).second) { - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: conflicting input in ixlock. input=%s, other ixlock=%s, peer=%d\n", __func__, - ixlock.txid.ToString(), hash.ToString(), in.ToStringShort(), inputToInstantXLock[in]->ixlockHash.ToString(), from); - txToInstantXLock.erase(hash); + for (size_t i = 0; i < islock.inputs.size(); i++) { + auto& in = islock.inputs[i]; + if (!inputToInstantSendLock.emplace(in, islockInfoPtr).second) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: conflicting input in islock. input=%s, other islock=%s, peer=%d\n", __func__, + islock.txid.ToString(), hash.ToString(), in.ToStringShort(), inputToInstantSendLock[in]->islockHash.ToString(), from); + txToInstantSendLock.erase(hash); for (size_t j = 0; j < i; j++) { - inputToInstantXLock.erase(ixlock.inputs[j]); + inputToInstantSendLock.erase(islock.inputs[j]); } return; } } } - CInv inv(MSG_IXLOCK, hash); + CInv inv(MSG_ISLOCK, hash); g_connman->RelayInv(inv); - RemoveMempoolConflictsForLock(hash, ixlock); - RetryLockMempoolTxs(ixlock.txid); + RemoveMempoolConflictsForLock(hash, islock); + RetryLockMempoolTxs(islock.txid); - UpdateWalletTransaction(ixlock.txid); + UpdateWalletTransaction(islock.txid); } void CInstantSendManager::UpdateWalletTransaction(const uint256& txid) @@ -602,8 +602,8 @@ void CInstantSendManager::UpdateWalletTransaction(const uint256& txid) #endif LOCK(cs); - auto it = txToInstantXLock.find(txid); - if (it == txToInstantXLock.end()) { + auto it = txToInstantSendLock.find(txid); + if (it == txToInstantSendLock.end()) { return; } if (it->second->tx == nullptr) { @@ -621,20 +621,20 @@ void CInstantSendManager::SyncTransaction(const CTransaction& tx, const CBlockIn { LOCK(cs); - auto it = txToInstantXLock.find(tx.GetHash()); - if (it == txToInstantXLock.end()) { + auto it = txToInstantSendLock.find(tx.GetHash()); + if (it == txToInstantSendLock.end()) { return; } - auto ixlockInfo = it->second; - if (ixlockInfo->tx == nullptr) { - ixlockInfo->tx = MakeTransactionRef(tx); + auto islockInfo = it->second; + if (islockInfo->tx == nullptr) { + islockInfo->tx = MakeTransactionRef(tx); } if (posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK) { - UpdateIxLockMinedBlock(ixlockInfo, nullptr); + UpdateISLockMinedBlock(islockInfo, nullptr); return; } - UpdateIxLockMinedBlock(ixlockInfo, pindex); + UpdateISLockMinedBlock(islockInfo, pindex); } if (IsLocked(tx.GetHash())) { @@ -647,17 +647,17 @@ void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindex) { LOCK(cs); - // Let's find all ixlocks that correspond to TXs which are part of the freshly ChainLocked chain and then delete - // the ixlocks. We do this because the ChainLocks imply locking and thus it's not needed to further track - // or propagate the ixlocks + // Let's find all islocks that correspond to TXs which are part of the freshly ChainLocked chain and then delete + // the islocks. We do this because the ChainLocks imply locking and thus it's not needed to further track + // or propagate the islocks std::unordered_set toDelete; while (pindex && pindex != pindexLastChainLock) { - auto its = blockToInstantXLocks.equal_range(pindex->GetBlockHash()); + auto its = blockToInstantSendLocks.equal_range(pindex->GetBlockHash()); while (its.first != its.second) { - auto ixlockInfo = its.first->second; - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: removing ixlock as it got ChainLocked in block %s\n", __func__, - ixlockInfo->ixlock.txid.ToString(), ixlockInfo->ixlockHash.ToString(), pindex->GetBlockHash().ToString()); - toDelete.emplace(its.first->second->ixlockHash); + auto islockInfo = its.first->second; + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: removing islock as it got ChainLocked in block %s\n", __func__, + islockInfo->islock.txid.ToString(), islockInfo->islockHash.ToString(), pindex->GetBlockHash().ToString()); + toDelete.emplace(its.first->second->islockHash); ++its.first; } @@ -666,27 +666,27 @@ void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindex) pindexLastChainLock = pindex; - for (auto& ixlockHash : toDelete) { - RemoveFinalIxLock(ixlockHash); + for (auto& islockHash : toDelete) { + RemoveFinalISLock(islockHash); } } RetryLockMempoolTxs(uint256()); } -void CInstantSendManager::UpdateIxLockMinedBlock(llmq::CInstantXLockInfo* ixlockInfo, const CBlockIndex* pindex) +void CInstantSendManager::UpdateISLockMinedBlock(llmq::CInstantSendLockInfo* islockInfo, const CBlockIndex* pindex) { AssertLockHeld(cs); - if (ixlockInfo->pindexMined == pindex) { + if (islockInfo->pindexMined == pindex) { return; } - if (ixlockInfo->pindexMined) { - auto its = blockToInstantXLocks.equal_range(ixlockInfo->pindexMined->GetBlockHash()); + if (islockInfo->pindexMined) { + auto its = blockToInstantSendLocks.equal_range(islockInfo->pindexMined->GetBlockHash()); while (its.first != its.second) { - if (its.first->second == ixlockInfo) { - its.first = blockToInstantXLocks.erase(its.first); + if (its.first->second == islockInfo) { + its.first = blockToInstantSendLocks.erase(its.first); } else { ++its.first; } @@ -694,47 +694,47 @@ void CInstantSendManager::UpdateIxLockMinedBlock(llmq::CInstantXLockInfo* ixlock } if (pindex) { - blockToInstantXLocks.emplace(pindex->GetBlockHash(), ixlockInfo); + blockToInstantSendLocks.emplace(pindex->GetBlockHash(), islockInfo); } - ixlockInfo->pindexMined = pindex; + islockInfo->pindexMined = pindex; } -void CInstantSendManager::RemoveFinalIxLock(const uint256& hash) +void CInstantSendManager::RemoveFinalISLock(const uint256& hash) { AssertLockHeld(cs); - auto it = finalInstantXLocks.find(hash); - if (it == finalInstantXLocks.end()) { + auto it = finalInstantSendLocks.find(hash); + if (it == finalInstantSendLocks.end()) { return; } - auto& ixlockInfo = it->second; + auto& islockInfo = it->second; - txToInstantXLock.erase(ixlockInfo.ixlock.txid); - for (auto& in : ixlockInfo.ixlock.inputs) { + txToInstantSendLock.erase(islockInfo.islock.txid); + for (auto& in : islockInfo.islock.inputs) { auto inputRequestId = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in)); inputVotes.erase(inputRequestId); - inputToInstantXLock.erase(in); + inputToInstantSendLock.erase(in); } - UpdateIxLockMinedBlock(&ixlockInfo, nullptr); + UpdateISLockMinedBlock(&islockInfo, nullptr); } -void CInstantSendManager::RemoveMempoolConflictsForLock(const uint256& hash, const CInstantXLock& ixlock) +void CInstantSendManager::RemoveMempoolConflictsForLock(const uint256& hash, const CInstantSendLock& islock) { LOCK(mempool.cs); std::unordered_map toDelete; - for (auto& in : ixlock.inputs) { + for (auto& in : islock.inputs) { auto it = mempool.mapNextTx.find(in); if (it == mempool.mapNextTx.end()) { continue; } - if (it->second->GetHash() != ixlock.txid) { + if (it->second->GetHash() != islock.txid) { toDelete.emplace(it->second->GetHash(), mempool.get(it->second->GetHash())); - LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, ixlock=%s: mempool TX %s with input %s conflicts with ixlock\n", __func__, - ixlock.txid.ToString(), hash.ToString(), it->second->GetHash().ToString(), in.ToStringShort()); + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, islock=%s: mempool TX %s with input %s conflicts with islock\n", __func__, + islock.txid.ToString(), hash.ToString(), it->second->GetHash().ToString(), in.ToStringShort()); } } @@ -745,7 +745,7 @@ void CInstantSendManager::RemoveMempoolConflictsForLock(const uint256& hash, con void CInstantSendManager::RetryLockMempoolTxs(const uint256& lockedParentTx) { - // Let's retry all mempool TXs which don't have an ixlock yet and where the parents got ChainLocked now + // Let's retry all mempool TXs which don't have an islock yet and where the parents got ChainLocked now std::unordered_map txs; @@ -769,7 +769,7 @@ void CInstantSendManager::RetryLockMempoolTxs(const uint256& lockedParentTx) auto& tx = p.second; { LOCK(cs); - if (txToCreatingInstantXLocks.count(tx->GetHash())) { + if (txToCreatingInstantSendLocks.count(tx->GetHash())) { // we're already in the middle of locking this one continue; } @@ -803,21 +803,21 @@ bool CInstantSendManager::AlreadyHave(const CInv& inv) } LOCK(cs); - return finalInstantXLocks.count(inv.hash) != 0 || pendingInstantXLocks.count(inv.hash) != 0; + return finalInstantSendLocks.count(inv.hash) != 0 || pendingInstantSendLocks.count(inv.hash) != 0; } -bool CInstantSendManager::GetInstantXLockByHash(const uint256& hash, llmq::CInstantXLock& ret) +bool CInstantSendManager::GetInstantSendLockByHash(const uint256& hash, llmq::CInstantSendLock& ret) { if (!IsNewInstantSendEnabled()) { return false; } LOCK(cs); - auto it = finalInstantXLocks.find(hash); - if (it == finalInstantXLocks.end()) { + auto it = finalInstantSendLocks.find(hash); + if (it == finalInstantSendLocks.end()) { return false; } - ret = it->second.ixlock; + ret = it->second.islock; return true; } @@ -828,7 +828,7 @@ bool CInstantSendManager::IsLocked(const uint256& txHash) } LOCK(cs); - return txToInstantXLock.count(txHash) != 0; + return txToInstantSendLock.count(txHash) != 0; } bool CInstantSendManager::IsConflicted(const CTransaction& tx) @@ -846,13 +846,13 @@ bool CInstantSendManager::GetConflictingTx(const CTransaction& tx, uint256& retC LOCK(cs); for (const auto& in : tx.vin) { - auto it = inputToInstantXLock.find(in.prevout); - if (it == inputToInstantXLock.end()) { + auto it = inputToInstantSendLock.find(in.prevout); + if (it == inputToInstantSendLock.end()) { continue; } - if (it->second->ixlock.txid != tx.GetHash()) { - retConflictTxHash = it->second->ixlock.txid; + if (it->second->islock.txid != tx.GetHash()) { + retConflictTxHash = it->second->islock.txid; return true; } } diff --git a/src/llmq/quorums_instantsend.h b/src/llmq/quorums_instantsend.h index 66a6a1cad3777..3d114970efe0a 100644 --- a/src/llmq/quorums_instantsend.h +++ b/src/llmq/quorums_instantsend.h @@ -2,8 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef DASH_QUORUMS_INSTANTX_H -#define DASH_QUORUMS_INSTANTX_H +#ifndef DASH_QUORUMS_INSTANTSEND_H +#define DASH_QUORUMS_INSTANTSEND_H #include "quorums_signing.h" @@ -18,7 +18,7 @@ class CScheduler; namespace llmq { -class CInstantXLock +class CInstantSendLock { public: std::vector inputs; @@ -39,14 +39,14 @@ class CInstantXLock uint256 GetRequestId() const; }; -class CInstantXLockInfo +class CInstantSendLockInfo { public: - // might be nullptr when ixlock is received before the TX itself + // might be nullptr when islock is received before the TX itself CTransactionRef tx; - CInstantXLock ixlock; + CInstantSendLock islock; // only valid when recovered sig was received - uint256 ixlockHash; + uint256 islockHash; // time when it was created/received int64_t time; @@ -68,27 +68,27 @@ class CInstantSendManager : public CRecoveredSigsListener std::unordered_map inputVotes; /** - * These are the ixlocks that are currently in the middle of being created. Entries are created when we observed - * recovered signatures for all inputs of a TX. At the same time, we initiate signing of our sigshare for the ixlock. - * When the recovered sig for the ixlock later arrives, we can finish the ixlock and propagate it. + * These are the islocks that are currently in the middle of being created. Entries are created when we observed + * recovered signatures for all inputs of a TX. At the same time, we initiate signing of our sigshare for the islock. + * When the recovered sig for the islock later arrives, we can finish the islock and propagate it. */ - std::unordered_map creatingInstantXLocks; - // maps from txid to the in-progress ixlock - std::unordered_map txToCreatingInstantXLocks; + std::unordered_map creatingInstantSendLocks; + // maps from txid to the in-progress islock + std::unordered_map txToCreatingInstantSendLocks; /** - * These are the final ixlocks, indexed by their own hash. The other maps are used to get from TXs, inputs and blocks - * to ixlocks. + * These are the final islocks, indexed by their own hash. The other maps are used to get from TXs, inputs and blocks + * to islocks. */ - std::unordered_map finalInstantXLocks; - std::unordered_map txToInstantXLock; - std::unordered_map inputToInstantXLock; - std::unordered_multimap blockToInstantXLocks; + std::unordered_map finalInstantSendLocks; + std::unordered_map txToInstantSendLock; + std::unordered_map inputToInstantSendLock; + std::unordered_multimap blockToInstantSendLocks; const CBlockIndex* pindexLastChainLock{nullptr}; // Incoming and not verified yet - std::unordered_map> pendingInstantXLocks; + std::unordered_map> pendingInstantSendLocks; bool hasScheduledProcessPending{false}; public: @@ -108,27 +108,27 @@ class CInstantSendManager : public CRecoveredSigsListener virtual void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig); void HandleNewInputLockRecoveredSig(const CRecoveredSig& recoveredSig, const uint256& txid); - void HandleNewInstantXLockRecoveredSig(const CRecoveredSig& recoveredSig); + void HandleNewInstantSendLockRecoveredSig(const CRecoveredSig& recoveredSig); - void TrySignInstantXLock(const CTransaction& tx); + void TrySignInstantSendLock(const CTransaction& tx); void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); - void ProcessMessageInstantXLock(CNode* pfrom, const CInstantXLock& ixlock, CConnman& connman); - bool PreVerifyInstantXLock(NodeId nodeId, const CInstantXLock& ixlock, bool& retBan); - void ProcessPendingInstantXLocks(); - void ProcessInstantXLock(NodeId from, const uint256& hash, const CInstantXLock& ixlock); + void ProcessMessageInstantSendLock(CNode* pfrom, const CInstantSendLock& islock, CConnman& connman); + bool PreVerifyInstantSendLock(NodeId nodeId, const CInstantSendLock& islock, bool& retBan); + void ProcessPendingInstantSendLocks(); + void ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLock& islock); void UpdateWalletTransaction(const uint256& txid); void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock); void NotifyChainLock(const CBlockIndex* pindex); - void UpdateIxLockMinedBlock(CInstantXLockInfo* ixlockInfo, const CBlockIndex* pindex); - void RemoveFinalIxLock(const uint256& hash); + void UpdateISLockMinedBlock(CInstantSendLockInfo* islockInfo, const CBlockIndex* pindex); + void RemoveFinalISLock(const uint256& hash); - void RemoveMempoolConflictsForLock(const uint256& hash, const CInstantXLock& ixlock); + void RemoveMempoolConflictsForLock(const uint256& hash, const CInstantSendLock& islock); void RetryLockMempoolTxs(const uint256& lockedParentTx); bool AlreadyHave(const CInv& inv); - bool GetInstantXLockByHash(const uint256& hash, CInstantXLock& ret); + bool GetInstantSendLockByHash(const uint256& hash, CInstantSendLock& ret); }; extern CInstantSendManager* quorumInstantSendManager; @@ -144,4 +144,4 @@ bool IsInstantSendEnabled(); } -#endif//DASH_QUORUMS_INSTANTX_H +#endif//DASH_QUORUMS_INSTANTSEND_H diff --git a/src/llmq/quorums_signing.cpp b/src/llmq/quorums_signing.cpp index fc394533c49c0..8941c4504a4d5 100644 --- a/src/llmq/quorums_signing.cpp +++ b/src/llmq/quorums_signing.cpp @@ -543,7 +543,7 @@ void CSigningManager::ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& re } else { // Looks like we're trying to process a recSig that is already known. This might happen if the same // recSig comes in through regular QRECSIG messages and at the same time through some other message - // which allowed to reconstruct a recSig (e.g. IXLOCK). In this case, just bail out. + // which allowed to reconstruct a recSig (e.g. ISLOCK). In this case, just bail out. } return; } else { diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 1123e9c48e1e3..8df501141116a 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -977,7 +977,7 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) return llmq::quorumSigningManager->AlreadyHave(inv); case MSG_CLSIG: return llmq::chainLocksHandler->AlreadyHave(inv); - case MSG_IXLOCK: + case MSG_ISLOCK: return llmq::quorumInstantSendManager->AlreadyHave(inv); } @@ -1299,10 +1299,10 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam } } - if (!push && (inv.type == MSG_IXLOCK)) { - llmq::CInstantXLock o; - if (llmq::quorumInstantSendManager->GetInstantXLockByHash(inv.hash, o)) { - connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::IXLOCK, o)); + if (!push && (inv.type == MSG_ISLOCK)) { + llmq::CInstantSendLock o; + if (llmq::quorumInstantSendManager->GetInstantSendLockByHash(inv.hash, o)) { + connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::ISLOCK, o)); push = true; } } @@ -1788,7 +1788,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr case MSG_CLSIG: doubleRequestDelay = 5 * 1000000; break; - case MSG_IXLOCK: + case MSG_ISLOCK: doubleRequestDelay = 5 * 1000000; break; } diff --git a/src/protocol.cpp b/src/protocol.cpp index e63688c0a00c4..48407d640ef9c 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -71,7 +71,7 @@ const char *QGETSIGSHARES="qgetsigs"; const char *QBSIGSHARES="qbsigs"; const char *QSIGREC="qsigrec"; const char *CLSIG="clsig"; -const char *IXLOCK="ixlock"; +const char *ISLOCK="islock"; }; static const char* ppszTypeName[] = @@ -108,7 +108,7 @@ static const char* ppszTypeName[] = NetMsgType::QDEBUGSTATUS, NetMsgType::QSIGREC, NetMsgType::CLSIG, - NetMsgType::IXLOCK, + NetMsgType::ISLOCK, }; /** All known message types. Keep this in the same order as the list of @@ -174,7 +174,7 @@ const static std::string allNetMessageTypes[] = { NetMsgType::QBSIGSHARES, NetMsgType::QSIGREC, NetMsgType::CLSIG, - NetMsgType::IXLOCK, + NetMsgType::ISLOCK, }; const static std::vector allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes)); diff --git a/src/protocol.h b/src/protocol.h index 0376f86f26ff2..c2027b3e336e9 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -277,7 +277,7 @@ extern const char *QGETSIGSHARES; extern const char *QBSIGSHARES; extern const char *QSIGREC; extern const char *CLSIG; -extern const char *IXLOCK; +extern const char *ISLOCK; }; /* Get a vector of all valid message types (see above) */ @@ -380,7 +380,7 @@ enum GetDataMsg { MSG_QUORUM_DEBUG_STATUS = 27, MSG_QUORUM_RECOVERED_SIG = 28, MSG_CLSIG = 29, - MSG_IXLOCK = 30, + MSG_ISLOCK = 30, }; /** inv message data */ diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 22138bde0f49a..b91a8f7071028 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3346,7 +3346,7 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletT int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign, AvailableCoinsType nCoinType, bool fUseInstantSend, int nExtraPayloadSize) { if (!llmq::IsOldInstantSendEnabled()) { - // The new system does not require special handling for InstantSend as this is all done in CInstantXManager. + // The new system does not require special handling for InstantSend as this is all done in CInstantSendManager. // There is also no need for an extra fee anymore. fUseInstantSend = false; } From 55152cb3135a84a917968ef04fe280828ce50ade Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 3 Mar 2019 11:48:52 +0300 Subject: [PATCH 24/33] Move IS block filtering into ConnectBlock --- src/validation.cpp | 76 ++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 5142d8e616a32..ea042a42ddbf4 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2209,13 +2209,49 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd LogPrint("bench", " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs]\n", nInputs - 1, 0.001 * (nTime4 - nTime2), nInputs <= 1 ? 0 : 0.001 * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * 0.000001); - // DASH : MODIFIED TO CHECK MASTERNODE PAYMENTS AND SUPERBLOCKS + // DASH // It's possible that we simply don't have enough data and this could fail // (i.e. block itself could be a correct one and we need to store it), // that's why this is in ConnectBlock. Could be the other way around however - // the peer who sent us this block is missing some data and wasn't able // to recognize that block is actually invalid. + + // DASH : CHECK TRANSACTIONS FOR INSTANTSEND + + if (sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) { + // Require other nodes to comply, send them some data in case they are missing it. + for (const auto& tx : block.vtx) { + // skip txes that have no inputs + if (tx->vin.empty()) continue; + // LOOK FOR TRANSACTION LOCK IN OUR MAP OF OUTPOINTS + for (const auto& txin : tx->vin) { + uint256 hashLocked; + if (instantsend.GetLockedOutPointTxHash(txin.prevout, hashLocked) && hashLocked != tx->GetHash()) { + // The node which relayed this should switch to correct chain. + // TODO: relay instantsend data/proof. + LOCK(cs_main); + mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime())); + return state.DoS(10, error("ConnectBlock(DASH): transaction %s conflicts with transaction lock %s", tx->GetHash().ToString(), hashLocked.ToString()), + REJECT_INVALID, "conflict-tx-lock"); + } + } + uint256 txConflict; + if (llmq::quorumInstantSendManager->GetConflictingTx(*tx, txConflict)) { + // The node which relayed this should switch to correct chain. + // TODO: relay instantsend data/proof. + LOCK(cs_main); + mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime())); + return state.DoS(10, error("ConnectBlock(DASH): transaction %s conflicts with transaction lock %s", tx->GetHash().ToString(), txConflict.ToString()), + REJECT_INVALID, "conflict-tx-lock"); + } + } + } else { + LogPrintf("ConnectBlock(DASH): spork is off, skipping transaction locking checks\n"); + } + + // DASH : MODIFIED TO CHECK MASTERNODE PAYMENTS AND SUPERBLOCKS + // TODO: resync data (both ways?) and try to reprocess this block later. CAmount blockReward = nFees + GetBlockSubsidy(pindex->pprev->nBits, pindex->pprev->nHeight, chainparams.GetConsensus()); std::string strError = ""; @@ -3269,44 +3305,6 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P if (block.vtx[i]->IsCoinBase()) return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase"); - - // DASH : CHECK TRANSACTIONS FOR INSTANTSEND - - if(sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) { - // We should never accept block which conflicts with completed transaction lock, - // that's why this is in CheckBlock unlike coinbase payee/amount. - // Require other nodes to comply, send them some data in case they are missing it. - for(const auto& tx : block.vtx) { - // skip coinbase, it has no inputs - if (tx->IsCoinBase()) continue; - // LOOK FOR TRANSACTION LOCK IN OUR MAP OF OUTPOINTS - for (const auto& txin : tx->vin) { - uint256 hashLocked; - if(instantsend.GetLockedOutPointTxHash(txin.prevout, hashLocked) && hashLocked != tx->GetHash()) { - // The node which relayed this will have to switch later, - // relaying instantsend data won't help it. - LOCK(cs_main); - mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime())); - return state.DoS(100, false, REJECT_INVALID, "conflict-tx-lock", false, - strprintf("transaction %s conflicts with transaction lock %s", tx->GetHash().ToString(), hashLocked.ToString())); - } - } - uint256 txConflict; - if (llmq::quorumInstantSendManager->GetConflictingTx(*tx, txConflict)) { - // The node which relayed this will have to switch later, - // relaying instantsend data won't help it. - LOCK(cs_main); - mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime())); - return state.DoS(100, false, REJECT_INVALID, "conflict-tx-lock", false, - strprintf("transaction %s conflicts with transaction lock %s", tx->GetHash().ToString(), txConflict.ToString())); - } - } - } else { - LogPrintf("CheckBlock(DASH): spork is off, skipping transaction locking checks\n"); - } - - // END DASH - // Check transactions for (const auto& tx : block.vtx) if (!CheckTransaction(*tx, state)) From a8da11ac5dee4d7bc5b85c7a242bacad9d391d41 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 3 Mar 2019 10:31:23 +0300 Subject: [PATCH 25/33] update p2p-instantsend.py to test both "old" and "new" InstantSend --- qa/rpc-tests/p2p-instantsend.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/qa/rpc-tests/p2p-instantsend.py b/qa/rpc-tests/p2p-instantsend.py index 9a1b15b6b05cb..45ecfd3b2abf5 100755 --- a/qa/rpc-tests/p2p-instantsend.py +++ b/qa/rpc-tests/p2p-instantsend.py @@ -21,6 +21,20 @@ def __init__(self): self.sender_idx = self.num_nodes - 3 def run_test(self): + self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0) + self.wait_for_sporks_same() + self.mine_quorum() + + print("Test old InstantSend") + self.test_doublespend() + + self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 1) + self.wait_for_sporks_same() + + print("Test new InstantSend") + self.test_doublespend() + + def test_doublespend(self): # feed the sender with some balance sender_addr = self.nodes[self.sender_idx].getnewaddress() self.nodes[0].sendtoaddress(sender_addr, 1) @@ -73,7 +87,12 @@ def run_test(self): assert (res['hash'] != wrong_block) # wait for long time only for first node timeout = 1 - + # mine more blocks + # TODO: mine these blocks on an isolated node + set_mocktime(get_mocktime() + 1) + set_node_times(self.nodes, get_mocktime()) + self.nodes[0].generate(2) + self.sync_all() if __name__ == '__main__': InstantSendTest().main() From 843b6d7a952f3602746e434af68f36bf7c94501f Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 3 Mar 2019 13:30:34 +0300 Subject: [PATCH 26/33] update p2p-autoinstantsend.py to test both "old" and "new" InstantSend --- qa/rpc-tests/p2p-autoinstantsend.py | 42 ++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/qa/rpc-tests/p2p-autoinstantsend.py b/qa/rpc-tests/p2p-autoinstantsend.py index b19eb07a4fe80..31f97ce50f6ca 100755 --- a/qa/rpc-tests/p2p-autoinstantsend.py +++ b/qa/rpc-tests/p2p-autoinstantsend.py @@ -89,13 +89,14 @@ def set_autoix_spork_state(self, state): self.nodes[0].spork('SPORK_16_INSTANTSEND_AUTOLOCKS', value) # sends regular IX with high fee and may inputs (not-simple transaction) - def send_regular_IX(self): + def send_regular_IX(self, check_fee = True): receiver_addr = self.nodes[self.receiver_idx].getnewaddress() txid = self.nodes[0].instantsendtoaddress(receiver_addr, 1.0) - MIN_FEE = satoshi_round(-0.0001) - fee = self.nodes[0].gettransaction(txid)['fee'] - expected_fee = MIN_FEE * len(self.nodes[0].getrawtransaction(txid, True)['vin']) - assert_equal(fee, expected_fee) + if (check_fee): + MIN_FEE = satoshi_round(-0.0001) + fee = self.nodes[0].gettransaction(txid)['fee'] + expected_fee = MIN_FEE * len(self.nodes[0].getrawtransaction(txid, True)['vin']) + assert_equal(fee, expected_fee) return self.wait_for_instantlock(txid, self.nodes[0]) # sends simple trx, it should become IX if autolocks are allowed @@ -115,6 +116,21 @@ def send_complex_tx(self): def run_test(self): # make sure masternodes are synced sync_masternodes(self.nodes) + + self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0) + self.wait_for_sporks_same() + self.mine_quorum() + + print("Test old InstantSend") + self.test_auto(); + + self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 1) + self.wait_for_sporks_same() + + print("Test new InstantSend") + self.test_auto(True); + + def test_auto(self, new_is = False): # feed the sender with some balance sender_addr = self.nodes[self.sender_idx].getnewaddress() self.nodes[0].sendtoaddress(sender_addr, 1) @@ -126,9 +142,9 @@ def run_test(self): assert(not self.get_autoix_spork_state()) - assert(self.send_regular_IX()) - assert(not self.send_simple_tx()) - assert(not self.send_complex_tx()) + assert(self.send_regular_IX(not new_is)) + assert(self.send_simple_tx() if new_is else not self.send_simple_tx()) + assert(self.send_complex_tx() if new_is else not self.send_complex_tx()) self.activate_autoix_bip9() self.set_autoix_spork_state(True) @@ -136,16 +152,16 @@ def run_test(self): assert(self.get_autoix_bip9_status() == 'active') assert(self.get_autoix_spork_state()) - assert(self.send_regular_IX()) + assert(self.send_regular_IX(not new_is)) assert(self.send_simple_tx()) - assert(not self.send_complex_tx()) + assert(self.send_complex_tx() if new_is else not self.send_complex_tx()) self.set_autoix_spork_state(False) assert(not self.get_autoix_spork_state()) - assert(self.send_regular_IX()) - assert(not self.send_simple_tx()) - assert(not self.send_complex_tx()) + assert(self.send_regular_IX(not new_is)) + assert(self.send_simple_tx() if new_is else not self.send_simple_tx()) + assert(self.send_complex_tx() if new_is else not self.send_complex_tx()) if __name__ == '__main__': From 41a71fe4430078b81d1b7e2fc01d71470bd7f07f Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 3 Mar 2019 20:26:06 +0300 Subject: [PATCH 27/33] update autoix-mempool.py to test both "old" and "new" InstantSend (and fix CheckCanLock to respect mempool limits) --- qa/rpc-tests/autoix-mempool.py | 19 +++++++++++++++++-- src/instantx.h | 5 +++-- src/llmq/quorums_instantsend.cpp | 4 ++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/qa/rpc-tests/autoix-mempool.py b/qa/rpc-tests/autoix-mempool.py index fdd3a57f3d293..f79fe772a70fd 100755 --- a/qa/rpc-tests/autoix-mempool.py +++ b/qa/rpc-tests/autoix-mempool.py @@ -122,6 +122,21 @@ def fill_mempool(self): def run_test(self): # make sure masternodes are synced sync_masternodes(self.nodes) + + self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0) + self.wait_for_sporks_same() + self.mine_quorum() + + print("Test old InstantSend") + self.test_auto(); + + self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 1) + self.wait_for_sporks_same() + + print("Test new InstantSend") + self.test_auto(True); + + def test_auto(self, new_is = False): self.activate_autoix_bip9() self.set_autoix_spork_state(True) @@ -151,8 +166,8 @@ def run_test(self): # autoIX is not working now assert(not self.send_simple_tx(sender, receiver)) - # regular IX is still working - assert(self.send_regular_IX(sender, receiver)) + # regular IX is still working for old IS but not for new one + assert(not self.send_regular_IX(sender, receiver) if new_is else self.send_regular_IX(sender, receiver)) # generate one block to clean up mempool and retry auto and regular IX # generate 2 more blocks to have enough confirmations for IX diff --git a/src/instantx.h b/src/instantx.h index c8c0aa5854295..aac54e2d1acfc 100644 --- a/src/instantx.h +++ b/src/instantx.h @@ -45,11 +45,12 @@ extern int nCompleteTXLocks; */ class CInstantSend { -private: - static const std::string SERIALIZATION_VERSION_STRING; +public: /// Automatic locks of "simple" transactions are only allowed /// when mempool usage is lower than this threshold static const double AUTO_IX_MEMPOOL_THRESHOLD; +private: + static const std::string SERIALIZATION_VERSION_STRING; // Keep track of current block height int nCachedBlockHeight; diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index 6104f5b3281a3..7f0a322ac66c6 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -137,6 +137,10 @@ bool CInstantSendManager::ProcessTx(CNode* pfrom, const CTransaction& tx, CConnm bool CInstantSendManager::CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params) { + if (sporkManager.IsSporkActive(SPORK_16_INSTANTSEND_AUTOLOCKS) && (mempool.UsedMemoryShare() > CInstantSend::AUTO_IX_MEMPOOL_THRESHOLD)) { + return false; + } + int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired; uint256 txHash = tx.GetHash(); From 3e60d2d2dbadb30ffe0e1ca22a1ec41ad0a76a75 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Mar 2019 13:33:34 +0100 Subject: [PATCH 28/33] Adjust LLMQ based InstantSend tests for new spork20 --- qa/rpc-tests/autoix-mempool.py | 2 +- qa/rpc-tests/p2p-autoinstantsend.py | 2 +- qa/rpc-tests/p2p-instantsend.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qa/rpc-tests/autoix-mempool.py b/qa/rpc-tests/autoix-mempool.py index f79fe772a70fd..a8be9a321fc1a 100755 --- a/qa/rpc-tests/autoix-mempool.py +++ b/qa/rpc-tests/autoix-mempool.py @@ -130,7 +130,7 @@ def run_test(self): print("Test old InstantSend") self.test_auto(); - self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 1) + self.nodes[0].spork("SPORK_20_INSTANTSEND_LLMQ_BASED", 0) self.wait_for_sporks_same() print("Test new InstantSend") diff --git a/qa/rpc-tests/p2p-autoinstantsend.py b/qa/rpc-tests/p2p-autoinstantsend.py index 31f97ce50f6ca..bf930b921aab0 100755 --- a/qa/rpc-tests/p2p-autoinstantsend.py +++ b/qa/rpc-tests/p2p-autoinstantsend.py @@ -124,7 +124,7 @@ def run_test(self): print("Test old InstantSend") self.test_auto(); - self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 1) + self.nodes[0].spork("SPORK_20_INSTANTSEND_LLMQ_BASED", 0) self.wait_for_sporks_same() print("Test new InstantSend") diff --git a/qa/rpc-tests/p2p-instantsend.py b/qa/rpc-tests/p2p-instantsend.py index 45ecfd3b2abf5..96539191963ba 100755 --- a/qa/rpc-tests/p2p-instantsend.py +++ b/qa/rpc-tests/p2p-instantsend.py @@ -28,7 +28,7 @@ def run_test(self): print("Test old InstantSend") self.test_doublespend() - self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 1) + self.nodes[0].spork("SPORK_20_INSTANTSEND_LLMQ_BASED", 0) self.wait_for_sporks_same() print("Test new InstantSend") From f8f867a6bb72182602bcc629426d666243fbb42c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Mar 2019 13:34:06 +0100 Subject: [PATCH 29/33] Sync blocks after generating in mine_quorum() --- qa/rpc-tests/test_framework/test_framework.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index b6f354f1387b4..e0d5fbdd84942 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -498,36 +498,44 @@ def mine_quorum(self, expected_valid_count=10): set_mocktime(get_mocktime() + 1) set_node_times(self.nodes, get_mocktime()) self.nodes[0].generate(skip_count) + sync_blocks(self.nodes) # Make sure all reached phase 1 (init) self.wait_for_quorum_phase(1, None, 0) + # Give nodes some time to connect to neighbors + sleep(2) set_mocktime(get_mocktime() + 1) set_node_times(self.nodes, get_mocktime()) self.nodes[0].generate(2) + sync_blocks(self.nodes) # Make sure all reached phase 2 (contribute) and received all contributions self.wait_for_quorum_phase(2, "receivedContributions", expected_valid_count) set_mocktime(get_mocktime() + 1) set_node_times(self.nodes, get_mocktime()) self.nodes[0].generate(2) + sync_blocks(self.nodes) # Make sure all reached phase 3 (complain) and received all complaints self.wait_for_quorum_phase(3, "receivedComplaints" if expected_valid_count != 10 else None, expected_valid_count) set_mocktime(get_mocktime() + 1) set_node_times(self.nodes, get_mocktime()) self.nodes[0].generate(2) + sync_blocks(self.nodes) # Make sure all reached phase 4 (justify) self.wait_for_quorum_phase(4, None, 0) set_mocktime(get_mocktime() + 1) set_node_times(self.nodes, get_mocktime()) self.nodes[0].generate(2) + sync_blocks(self.nodes) # Make sure all reached phase 5 (commit) self.wait_for_quorum_phase(5, "receivedPrematureCommitments", expected_valid_count) set_mocktime(get_mocktime() + 1) set_node_times(self.nodes, get_mocktime()) self.nodes[0].generate(2) + sync_blocks(self.nodes) # Make sure all reached phase 6 (mining) self.wait_for_quorum_phase(6, None, 0) @@ -544,6 +552,7 @@ def mine_quorum(self, expected_valid_count=10): set_mocktime(get_mocktime() + 1) set_node_times(self.nodes, get_mocktime()) self.nodes[0].generate(1) + sync_blocks(self.nodes) sync_blocks(self.nodes) From fae33e03ae5c112a0adceb9120b135211f8b9a3b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 4 Mar 2019 15:15:38 +0100 Subject: [PATCH 30/33] Let ProcessPendingReconstructedRecoveredSigs return void instead of bool Return value is unused and the method actually never returned something. --- src/llmq/quorums_signing.cpp | 2 +- src/llmq/quorums_signing.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llmq/quorums_signing.cpp b/src/llmq/quorums_signing.cpp index 8941c4504a4d5..7c2f4e0ce5618 100644 --- a/src/llmq/quorums_signing.cpp +++ b/src/llmq/quorums_signing.cpp @@ -440,7 +440,7 @@ void CSigningManager::CollectPendingRecoveredSigsToVerify( } } -bool CSigningManager::ProcessPendingReconstructedRecoveredSigs() +void CSigningManager::ProcessPendingReconstructedRecoveredSigs() { decltype(pendingReconstructedRecoveredSigs) l; { diff --git a/src/llmq/quorums_signing.h b/src/llmq/quorums_signing.h index 6a7325de61399..3c379e4bf307c 100644 --- a/src/llmq/quorums_signing.h +++ b/src/llmq/quorums_signing.h @@ -154,7 +154,7 @@ class CSigningManager void CollectPendingRecoveredSigsToVerify(size_t maxUniqueSessions, std::unordered_map>& retSigShares, std::unordered_map, CQuorumCPtr, StaticSaltedHasher>& retQuorums); - bool ProcessPendingReconstructedRecoveredSigs(); + void ProcessPendingReconstructedRecoveredSigs(); bool ProcessPendingRecoveredSigs(CConnman& connman); // called from the worker thread of CSigSharesManager void ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& recoveredSig, const CQuorumCPtr& quorum, CConnman& connman); void Cleanup(); // called from the worker thread of CSigSharesManager From 4d3365ddb2667391a506db9094e78c9dcd36555c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 5 Mar 2019 17:35:18 +0100 Subject: [PATCH 31/33] Completely disable InstantSend while filling mempool in autoix-mempool.py Otherwise we overload Travis and tests start to randomly fail. --- qa/rpc-tests/autoix-mempool.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qa/rpc-tests/autoix-mempool.py b/qa/rpc-tests/autoix-mempool.py index a8be9a321fc1a..1d5e6ff90eed6 100755 --- a/qa/rpc-tests/autoix-mempool.py +++ b/qa/rpc-tests/autoix-mempool.py @@ -161,8 +161,12 @@ def test_auto(self, new_is = False): # fill mempool with transactions self.set_autoix_spork_state(False) + self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 4070908800) + self.wait_for_sporks_same() self.fill_mempool() self.set_autoix_spork_state(True) + self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 0) + self.wait_for_sporks_same() # autoIX is not working now assert(not self.send_simple_tx(sender, receiver)) From 041a1c26d091e5623e510f0716872289e8e59510 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 6 Mar 2019 07:58:27 +0100 Subject: [PATCH 32/33] Move safe TX checks into TestForBlock and TestPackageTransactions Otherwise we'll miss checks for ancestors. --- src/miner.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/miner.cpp b/src/miner.cpp index 64fd4fc93f53a..c4c849069323d 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -287,11 +287,15 @@ bool BlockAssembler::TestPackage(uint64_t packageSize, unsigned int packageSigOp // Perform transaction-level checks before adding to block: // - transaction finality (locktime) +// - safe TXs in regard to ChainLocks bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package) { BOOST_FOREACH (const CTxMemPool::txiter it, package) { if (!IsFinalTx(it->GetTx(), nHeight, nLockTimeCutoff)) return false; + if (!llmq::chainLocksHandler->IsTxSafeForMining(it->GetTx().GetHash())) { + return false; + } } return true; } @@ -333,6 +337,10 @@ bool BlockAssembler::TestForBlock(CTxMemPool::txiter iter) if (!IsFinalTx(iter->GetTx(), nHeight, nLockTimeCutoff)) return false; + if (!llmq::chainLocksHandler->IsTxSafeForMining(iter->GetTx().GetHash())) { + return false; + } + return true; } @@ -454,12 +462,6 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda continue; } - if (mi != mempool.mapTx.get().end() && - !llmq::chainLocksHandler->IsTxSafeForMining(mi->GetTx().GetHash())) { - ++mi; - continue; - } - // Now that mi is not stale, determine which transaction to evaluate: // the next entry from mapTx, or the best from mapModifiedTx? bool fUsingModified = false; @@ -530,7 +532,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda onlyUnconfirmed(ancestors); ancestors.insert(iter); - // Test if all tx's are Final + // Test if all tx's are Final and safe if (!TestPackageTransactions(ancestors)) { if (fUsingModified) { mapModifiedTx.get().erase(modit); @@ -608,10 +610,6 @@ void BlockAssembler::addPriorityTxs() continue; } - if (!llmq::chainLocksHandler->IsTxSafeForMining(iter->GetTx().GetHash())) { - continue; - } - // If this tx fits in the block add it, otherwise keep looping if (TestForBlock(iter)) { AddToBlock(iter); From 06fc655595711025355a27a0aff9b3edd9944e46 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 7 Mar 2019 11:43:04 +0100 Subject: [PATCH 33/33] Actually remove from finalInstantSendLocks in CInstantSendManager::RemoveFinalISLock --- src/llmq/quorums_instantsend.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index 7f0a322ac66c6..fec7033e23a50 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -721,6 +721,7 @@ void CInstantSendManager::RemoveFinalISLock(const uint256& hash) inputToInstantSendLock.erase(in); } UpdateISLockMinedBlock(&islockInfo, nullptr); + finalInstantSendLocks.erase(it); } void CInstantSendManager::RemoveMempoolConflictsForLock(const uint256& hash, const CInstantSendLock& islock)