Avoid propagating InstantSend related old recovered sigs (#3145)

* More/better logging for InstantSend

* Implement CRecoveredSigsDb::TruncateRecoveredSig

* Truncate recovered sigs for ISLOCKs instead of completely removing them

This makes AlreadyHave() return true even when the recovered sig is deleted
locally. This avoids re-requesting and re-processing of old recovered sigs.

* Also truncate recovered sigs for freshly received ISLOCKs

* Fix comment
This commit is contained in:
Alexander Block 2019-10-16 16:10:06 +02:00 committed by UdjinM6
parent 24fee30513
commit efd8d2c82b
4 changed files with 81 additions and 23 deletions

View File

@ -403,10 +403,16 @@ bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Par
}
if (!CheckCanLock(tx, true, params)) {
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: CheckCanLock returned false\n", __func__,
tx.GetHash().ToString());
return false;
}
if (IsConflicted(tx)) {
auto conflictingLock = GetConflictingLock(tx);
if (conflictingLock) {
auto islockHash = ::SerializeHash(*conflictingLock);
LogPrintf("CInstantSendManager::%s -- txid=%s: conflicts with islock %s, txid=%s\n", __func__,
tx.GetHash().ToString(), islockHash.ToString(), conflictingLock->txid.ToString());
return false;
}
@ -421,7 +427,7 @@ bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Par
uint256 otherTxHash;
if (quorumSigningManager->GetVoteForId(llmqType, id, otherTxHash)) {
if (otherTxHash != tx.GetHash()) {
LogPrintf("CInstantSendManager::%s -- txid=%s: input %s is conflicting with islock %s\n", __func__,
LogPrintf("CInstantSendManager::%s -- txid=%s: input %s is conflicting with previous vote for tx %s\n", __func__,
tx.GetHash().ToString(), in.prevout.ToStringShort(), otherTxHash.ToString());
return false;
}
@ -430,19 +436,28 @@ bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Par
// don't even try the actual signing if any input is conflicting
if (quorumSigningManager->IsConflicting(llmqType, id, tx.GetHash())) {
LogPrintf("CInstantSendManager::%s -- txid=%s: quorumSigningManager->IsConflicting returned true. id=%s\n", __func__,
tx.GetHash().ToString(), id.ToString());
return false;
}
}
if (alreadyVotedCount == ids.size()) {
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: already voted on all inputs, bailing out\n", __func__,
tx.GetHash().ToString());
return true;
}
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: trying to vote on %d inputs\n", __func__,
tx.GetHash().ToString(), tx.vin.size());
for (size_t i = 0; i < tx.vin.size(); i++) {
auto& in = tx.vin[i];
auto& id = ids[i];
inputRequestIds.emplace(id);
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: trying to vote on input %s with id %s\n", __func__,
tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString());
if (quorumSigningManager->AsyncSignIfMember(llmqType, id, tx.GetHash())) {
LogPrintf("CInstantSendManager::%s -- txid=%s: voted on input %s with id %s\n", __func__,
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: voted on input %s with id %s\n", __func__,
tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString());
}
}
@ -915,6 +930,10 @@ void CInstantSendManager::ProcessInstantSendLock(NodeId from, const uint256& has
// This will also add children TXs to pendingRetryTxs
RemoveNonLockedTx(islock.txid, true);
// We don't need the recovered sigs for the inputs anymore. This prevents unnecessary propagation of these sigs.
// We only need the ISLOCK from now on to detect conflicts
TruncateRecoveredSigsForInputs(islock);
}
CInv inv(MSG_ISLOCK, hash);
@ -1036,6 +1055,9 @@ void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx, const CBlock
nonLockedTxsByInputs.emplace(in.prevout.hash, std::make_pair(in.prevout.n, tx->GetHash()));
}
}
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, pindexMined=%s\n", __func__,
tx->GetHash().ToString(), pindexMined ? pindexMined->GetBlockHash().ToString() : "");
}
void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChildren)
@ -1048,10 +1070,12 @@ void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChild
}
auto& info = it->second;
size_t retryChildrenCount = 0;
if (retryChildren) {
// TX got locked, so we can retry locking children
for (auto& childTxid : info.children) {
pendingRetryTxs.emplace(childTxid);
retryChildrenCount++;
}
}
@ -1078,6 +1102,9 @@ void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChild
}
nonLockedTxs.erase(it);
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, retryChildren=%d, retryChildrenCount=%d\n", __func__,
txid.ToString(), retryChildren, retryChildrenCount);
}
void CInstantSendManager::RemoveConflictedTx(const CTransaction& tx)
@ -1091,6 +1118,17 @@ void CInstantSendManager::RemoveConflictedTx(const CTransaction& tx)
}
}
void CInstantSendManager::TruncateRecoveredSigsForInputs(const llmq::CInstantSendLock& islock)
{
auto& consensusParams = Params().GetConsensus();
for (auto& in : islock.inputs) {
auto inputRequestId = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in));
inputRequestIds.erase(inputRequestId);
quorumSigningManager->TruncateRecoveredSig(consensusParams.llmqTypeInstantSend, inputRequestId);
}
}
void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindexChainLock)
{
HandleFullyConfirmedBlock(pindexChainLock);
@ -1131,17 +1169,13 @@ void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex)
LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s: removed islock as it got fully confirmed\n", __func__,
islock->txid.ToString(), islockHash.ToString());
for (auto& in : islock->inputs) {
auto inputRequestId = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in));
inputRequestIds.erase(inputRequestId);
// No need to keep recovered sigs for fully confirmed IS locks, as there is no chance for conflicts
// from now on. All inputs are spent now and can't be spend in any other TX.
TruncateRecoveredSigsForInputs(*islock);
// no need to keep recovered sigs for fully confirmed IS locks, as there is no chance for conflicts
// from now on. All inputs are spent now and can't be spend in any other TX.
quorumSigningManager->RemoveRecoveredSig(consensusParams.llmqTypeInstantSend, inputRequestId);
}
// same as in the loop
quorumSigningManager->RemoveRecoveredSig(consensusParams.llmqTypeInstantSend, islock->GetRequestId());
// And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered
// fully confirmed now
quorumSigningManager->TruncateRecoveredSig(consensusParams.llmqTypeInstantSend, islock->GetRequestId());
}
// Find all previously unlocked TXs that got locked by this fully confirmed (ChainLock) block and remove them

View File

@ -149,6 +149,7 @@ public:
void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined);
void RemoveNonLockedTx(const uint256& txid, bool retryChildren);
void RemoveConflictedTx(const CTransaction& tx);
void TruncateRecoveredSigsForInputs(const CInstantSendLock& islock);
void NotifyChainLock(const CBlockIndex* pindexChainLock);
void UpdatedBlockTip(const CBlockIndex* pindexNew);

View File

@ -259,7 +259,7 @@ void CRecoveredSigsDb::WriteRecoveredSig(const llmq::CRecoveredSig& recSig)
}
}
void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType llmqType, const uint256& id, bool deleteTimeKey)
void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType llmqType, const uint256& id, bool deleteHashKey, bool deleteTimeKey)
{
AssertLockHeld(cs);
@ -276,7 +276,9 @@ void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType l
auto k4 = std::make_tuple(std::string("rs_s"), signHash);
batch.Erase(k1);
batch.Erase(k2);
batch.Erase(k3);
if (deleteHashKey) {
batch.Erase(k3);
}
batch.Erase(k4);
if (deleteTimeKey) {
@ -292,14 +294,27 @@ void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType l
hasSigForIdCache.erase(std::make_pair((Consensus::LLMQType)recSig.llmqType, recSig.id));
hasSigForSessionCache.erase(signHash);
hasSigForHashCache.erase(recSig.GetHash());
if (deleteHashKey) {
hasSigForHashCache.erase(recSig.GetHash());
}
}
// Completely remove any traces of the recovered sig
void CRecoveredSigsDb::RemoveRecoveredSig(Consensus::LLMQType llmqType, const uint256& id)
{
LOCK(cs);
CDBBatch batch(db);
RemoveRecoveredSig(batch, llmqType, id, true);
RemoveRecoveredSig(batch, llmqType, id, true, true);
db.WriteBatch(batch);
}
// Remove the recovered sig itself and all keys required to get from id -> recSig
// This will leave the byHash key in-place so that HasRecoveredSigForHash still returns true
void CRecoveredSigsDb::TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id)
{
LOCK(cs);
CDBBatch batch(db);
RemoveRecoveredSig(batch, llmqType, id, false, false);
db.WriteBatch(batch);
}
@ -339,7 +354,7 @@ void CRecoveredSigsDb::CleanupOldRecoveredSigs(int64_t maxAge)
{
LOCK(cs);
for (auto& e : toDelete) {
RemoveRecoveredSig(batch, e.first, e.second, false);
RemoveRecoveredSig(batch, e.first, e.second, true, false);
if (batch.SizeEstimate() >= (1 << 24)) {
db.WriteBatch(batch);
@ -473,7 +488,8 @@ void CSigningManager::ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredS
return;
}
LogPrint(BCLog::LLMQ, "CSigningManager::%s -- signHash=%s, node=%d\n", __func__, CLLMQUtils::BuildSignHash(recoveredSig).ToString(), pfrom->GetId());
LogPrint(BCLog::LLMQ, "CSigningManager::%s -- signHash=%s, id=%s, msgHash=%s, node=%d\n", __func__,
CLLMQUtils::BuildSignHash(recoveredSig).ToString(), recoveredSig.id.ToString(), recoveredSig.msgHash.ToString(), pfrom->GetId());
LOCK(cs);
pendingRecoveredSigs[pfrom->GetId()].emplace_back(recoveredSig);
@ -656,6 +672,10 @@ void CSigningManager::ProcessRecoveredSig(NodeId nodeId, const CRecoveredSig& re
connman.RemoveAskFor(recoveredSig.GetHash());
}
if (db.HasRecoveredSigForHash(recoveredSig.GetHash())) {
return;
}
std::vector<CRecoveredSigsListener*> listeners;
{
LOCK(cs);
@ -709,9 +729,9 @@ void CSigningManager::PushReconstructedRecoveredSig(const llmq::CRecoveredSig& r
pendingReconstructedRecoveredSigs.emplace_back(recoveredSig, quorum);
}
void CSigningManager::RemoveRecoveredSig(Consensus::LLMQType llmqType, const uint256& id)
void CSigningManager::TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id)
{
db.RemoveRecoveredSig(llmqType, id);
db.TruncateRecoveredSig(llmqType, id);
}
void CSigningManager::Cleanup()

View File

@ -85,6 +85,7 @@ public:
bool GetRecoveredSigById(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret);
void WriteRecoveredSig(const CRecoveredSig& recSig);
void RemoveRecoveredSig(Consensus::LLMQType llmqType, const uint256& id);
void TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id);
void CleanupOldRecoveredSigs(int64_t maxAge);
@ -97,7 +98,7 @@ public:
private:
bool ReadRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret);
void RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType llmqType, const uint256& id, bool deleteTimeKey);
void RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType llmqType, const uint256& id, bool deleteHashKey, bool deleteTimeKey);
};
class CRecoveredSigsListener
@ -148,7 +149,9 @@ public:
// This is called when a recovered signature can be safely removed from the DB. This is only safe when some other
// mechanism prevents possible conflicts. As an example, ChainLocks prevent conflicts in confirmed TXs InstantSend votes
void RemoveRecoveredSig(Consensus::LLMQType llmqType, const uint256& id);
// This won't completely remove all traces of the recovered sig but instead leave the hash entry in the DB. This
// allows AlreadyHave to keep returning true. Cleanup will later remove the remains
void TruncateRecoveredSig(Consensus::LLMQType llmqType, const uint256& id);
private:
void ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredSig& recoveredSig, CConnman& connman);