Skip to content

Commit cd94cbe

Browse files
codablockUdjinM6
authored andcommitted
Track which TXs are not locked yet and use this info in ProcessPendingRetryLockTxs (#2869)
* Track which TXs are not locked yet and use this info in ProcessPendingRetryLockTxs Instead of relying on ReadBlockFromDisk. This should be less disk+CPU intensive but require more RAM. It also fixes a bug in ProcessPendingRetryLockTxs which caused ChainLocked parents to not be considered for retrying of its children. * Handle review commments
1 parent c4549ac commit cd94cbe

File tree

2 files changed

+103
-84
lines changed

2 files changed

+103
-84
lines changed

src/llmq/quorums_instantsend.cpp

+91-82
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,8 @@ void CInstantSendManager::ProcessInstantSendLock(NodeId from, const uint256& has
768768
db.WriteInstantSendLockMined(hash, pindexMined->nHeight);
769769
}
770770

771-
pendingRetryTxs.emplace(islock.txid);
771+
// This will also add children TXs to pendingRetryTxs
772+
RemoveNonLockedTx(islock.txid);
772773
}
773774

774775
CInv inv(MSG_ISLOCK, hash);
@@ -837,12 +838,63 @@ void CInstantSendManager::SyncTransaction(const CTransaction& tx, const CBlockIn
837838
}
838839

839840
bool chainlocked = pindex && chainLocksHandler->HasChainLock(pindex->nHeight, pindex->GetBlockHash());
840-
if (!islockHash.IsNull() || chainlocked) {
841-
LOCK(cs);
842-
pendingRetryTxs.emplace(tx.GetHash());
843-
} else {
841+
if (islockHash.IsNull() && !chainlocked) {
844842
ProcessTx(tx, Params().GetConsensus());
845843
}
844+
845+
LOCK(cs);
846+
if (!chainlocked && islockHash.IsNull()) {
847+
// TX is not locked, so make sure it is tracked
848+
AddNonLockedTx(MakeTransactionRef(tx));
849+
nonLockedTxs.at(tx.GetHash()).pindexMined = posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK ? pindex : nullptr;
850+
} else {
851+
// TX is locked, so make sure we don't track it anymore
852+
RemoveNonLockedTx(tx.GetHash());
853+
}
854+
}
855+
856+
void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx)
857+
{
858+
AssertLockHeld(cs);
859+
auto it = nonLockedTxs.emplace(tx->GetHash(), NonLockedTxInfo()).first;
860+
auto& info = it->second;
861+
862+
if (!info.tx) {
863+
info.tx = tx;
864+
for (const auto& in : tx->vin) {
865+
nonLockedTxs[in.prevout.hash].children.emplace(tx->GetHash());
866+
}
867+
}
868+
}
869+
870+
void CInstantSendManager::RemoveNonLockedTx(const uint256& txid)
871+
{
872+
AssertLockHeld(cs);
873+
874+
auto it = nonLockedTxs.find(txid);
875+
if (it == nonLockedTxs.end()) {
876+
return;
877+
}
878+
auto& info = it->second;
879+
880+
// TX got locked, so we can retry locking children
881+
for (auto& childTxid : info.children) {
882+
pendingRetryTxs.emplace(childTxid);
883+
}
884+
885+
if (info.tx) {
886+
for (const auto& in : info.tx->vin) {
887+
auto jt = nonLockedTxs.find(in.prevout.hash);
888+
if (jt != nonLockedTxs.end()) {
889+
jt->second.children.erase(txid);
890+
if (!jt->second.tx && jt->second.children.empty()) {
891+
nonLockedTxs.erase(jt);
892+
}
893+
}
894+
}
895+
}
896+
897+
nonLockedTxs.erase(it);
846898
}
847899

848900
void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindexChainLock)
@@ -887,8 +939,20 @@ void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex)
887939
}
888940
}
889941

890-
// Retry all not yet locked mempool TXs and TX which where mined after the fully confirmed block
891-
pendingRetryAllTxs = true;
942+
// Find all previously unlocked TXs that got locked by this fully confirmed (ChainLock) block and remove them
943+
// from the nonLockedTxs map. Also collect all children of these TXs and mark them for retrying of IS locking.
944+
std::vector<uint256> toRemove;
945+
for (auto& p : nonLockedTxs) {
946+
auto pindexMined = p.second.pindexMined;
947+
948+
if (pindexMined && pindex->GetAncestor(pindexMined->nHeight) == pindexMined) {
949+
toRemove.emplace_back(p.first);
950+
}
951+
}
952+
for (auto& txid : toRemove) {
953+
// This will also add children to pendingRetryTxs
954+
RemoveNonLockedTx(txid);
955+
}
892956
}
893957

894958
for (auto& p : removeISLocks) {
@@ -922,96 +986,35 @@ void CInstantSendManager::RemoveMempoolConflictsForLock(const uint256& hash, con
922986

923987
bool CInstantSendManager::ProcessPendingRetryLockTxs()
924988
{
925-
bool retryAllTxs;
926-
decltype(pendingRetryTxs) parentTxs;
989+
decltype(pendingRetryTxs) retryTxs;
927990
{
928991
LOCK(cs);
929-
retryAllTxs = pendingRetryAllTxs;
930-
parentTxs = std::move(pendingRetryTxs);
931-
pendingRetryAllTxs = false;
992+
retryTxs = std::move(pendingRetryTxs);
932993
}
933994

934-
if (!retryAllTxs && parentTxs.empty()) {
995+
if (retryTxs.empty()) {
935996
return false;
936997
}
937998

938999
if (!IsNewInstantSendEnabled()) {
9391000
return false;
9401001
}
9411002

942-
// Let's retry all unlocked TXs from mempool and and recently connected blocks
943-
944-
std::unordered_map<uint256, CTransactionRef> txs;
945-
946-
{
947-
LOCK(mempool.cs);
948-
949-
if (retryAllTxs) {
950-
txs.reserve(mempool.mapTx.size());
951-
for (auto it = mempool.mapTx.begin(); it != mempool.mapTx.end(); ++it) {
952-
txs.emplace(it->GetTx().GetHash(), it->GetSharedTx());
953-
}
954-
} else {
955-
for (const auto& parentTx : parentTxs) {
956-
auto it = mempool.mapNextTx.lower_bound(COutPoint(parentTx, 0));
957-
while (it != mempool.mapNextTx.end() && it->first->hash == parentTx) {
958-
txs.emplace(it->second->GetHash(), mempool.get(it->second->GetHash()));
959-
++it;
960-
}
961-
}
962-
}
963-
}
964-
965-
const CBlockIndex* pindexWalk = nullptr;
966-
{
967-
LOCK(cs_main);
968-
pindexWalk = chainActive.Tip();
969-
}
970-
971-
// scan blocks until we hit the last chainlocked block we know of. Also stop scanning after a depth of 6 to avoid
972-
// signing thousands of TXs at once. Also, after a depth of 6, blocks get eligible for ChainLocking even if unsafe
973-
// TXs are included, so there is no need to retroactively sign these.
974-
int depth = 0;
975-
while (pindexWalk && depth < 6) {
976-
if (chainLocksHandler->HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) {
977-
break;
978-
}
979-
980-
CBlock block;
1003+
int retryCount = 0;
1004+
for (const auto& txid : retryTxs) {
1005+
CTransactionRef tx;
9811006
{
982-
LOCK(cs_main);
983-
if (!ReadBlockFromDisk(block, pindexWalk, Params().GetConsensus())) {
984-
pindexWalk = pindexWalk->pprev;
1007+
LOCK(cs);
1008+
auto it = nonLockedTxs.find(txid);
1009+
if (it == nonLockedTxs.end()) {
9851010
continue;
9861011
}
987-
}
1012+
tx = it->second.tx;
9881013

989-
for (const auto& tx : block.vtx) {
990-
if (retryAllTxs) {
991-
txs.emplace(tx->GetHash(), tx);
992-
} else {
993-
bool isChild = false;
994-
for (auto& in : tx->vin) {
995-
if (parentTxs.count(in.prevout.hash)) {
996-
isChild = true;
997-
break;
998-
}
999-
}
1000-
if (isChild) {
1001-
txs.emplace(tx->GetHash(), tx);
1002-
}
1014+
if (!tx) {
1015+
continue;
10031016
}
1004-
}
10051017

1006-
pindexWalk = pindexWalk->pprev;
1007-
depth++;
1008-
}
1009-
1010-
bool didWork = false;
1011-
for (auto& p : txs) {
1012-
auto& tx = p.second;
1013-
{
1014-
LOCK(cs);
10151018
if (txToCreatingInstantSendLocks.count(tx->GetHash())) {
10161019
// we're already in the middle of locking this one
10171020
continue;
@@ -1036,10 +1039,16 @@ bool CInstantSendManager::ProcessPendingRetryLockTxs()
10361039
}
10371040

10381041
ProcessTx(*tx, Params().GetConsensus());
1039-
didWork = true;
1042+
retryCount++;
1043+
}
1044+
1045+
if (retryCount != 0) {
1046+
LOCK(cs);
1047+
LogPrint("instantsend", "CInstantSendManager::%s -- retried %d TXs. nonLockedTxs.size=%d\n", __func__,
1048+
retryCount, nonLockedTxs.size());
10401049
}
10411050

1042-
return didWork;
1051+
return retryCount != 0;
10431052
}
10441053

10451054
bool CInstantSendManager::AlreadyHave(const CInv& inv)

src/llmq/quorums_instantsend.h

+12-2
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,16 @@ class CInstantSendManager : public CRecoveredSigsListener
9393
// Incoming and not verified yet
9494
std::unordered_map<uint256, std::pair<NodeId, CInstantSendLock>> pendingInstantSendLocks;
9595

96-
// a set of recently IS locked TXs for which we can retry locking of children
96+
// TXs which are neither IS locked nor ChainLocked. We use this to determine for which TXs we need to retry IS locking
97+
// of child TXs
98+
struct NonLockedTxInfo {
99+
const CBlockIndex* pindexMined{nullptr};
100+
CTransactionRef tx;
101+
std::unordered_set<uint256, StaticSaltedHasher> children;
102+
};
103+
std::unordered_map<uint256, NonLockedTxInfo, StaticSaltedHasher> nonLockedTxs;
104+
97105
std::unordered_set<uint256, StaticSaltedHasher> pendingRetryTxs;
98-
bool pendingRetryAllTxs{false};
99106

100107
public:
101108
CInstantSendManager(CDBWrapper& _llmqDb);
@@ -127,6 +134,9 @@ class CInstantSendManager : public CRecoveredSigsListener
127134
void UpdateWalletTransaction(const uint256& txid, const CTransactionRef& tx);
128135

129136
void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock);
137+
void AddNonLockedTx(const CTransactionRef& tx);
138+
void RemoveNonLockedTx(const uint256& txid);
139+
130140
void NotifyChainLock(const CBlockIndex* pindexChainLock);
131141
void UpdatedBlockTip(const CBlockIndex* pindexNew);
132142

0 commit comments

Comments
 (0)