Multiple refactorings/fixes for LLMQ bases InstantSend and ChainLocks (#2779)

* Remove unused parameters from CInstantSendManager::ProcessTx

* Pass txHash in CheckCanLock by reference instead of pointer

* Dont' allow locking of TXs without inputs

* Remove unused local variable nInstantSendConfirmationsRequired

* Don't subtract 1 from nInstantSendConfirmationsRequired

This was necessary in the old system but is not necessary in the new system.
It also prevented proper retroactive signing of chained TXs in regtest as
it resulted in child TXs to return true immediately for CheckCanLock when
it should actually have waited for the parent TX to become locked first.

* Access chainActive.Height() while cs_main is locked

* Properly read and write lastChainLockBlock

"pindex" is NOT the chainlocked block after the while loop finishes. We
must use the pindex (renamed to pindexChainLock now) given on method entry.

Also, the GetLastChainLockBlock() result was not assigned to,
lastChainLockBlock which resulted in the while loop to run unnecessarily
long.

* Generalize filtering in NewPoWValidBlock and SyncTransaction

We're actually interested in all TXs that have inputs, so no need to
explicitly check for tx types.

* Use tx.IsCoinBase() instead of checking for index 0

* Handle cases where a TX is not received yet in wait_for_instantlock

* Wait on all nodes for the locks

Otherwise we end up with the sender having it locked but other nodes
not yet, failing the test.

* Fix LogPrintf call in CChainLocksHandler::DoInvalidateBlock
This commit is contained in:
Alexander Block 2019-03-19 08:38:16 +01:00 committed by UdjinM6
parent a5d2edbe04
commit 5299d39338
8 changed files with 43 additions and 48 deletions

View File

@ -55,7 +55,9 @@ class InstantSendTest(DashTestFramework):
# instantsend to receiver # instantsend to receiver
receiver_addr = receiver.getnewaddress() receiver_addr = receiver.getnewaddress()
is_id = sender.instantsendtoaddress(receiver_addr, 0.9) is_id = sender.instantsendtoaddress(receiver_addr, 0.9)
self.wait_for_instantlock(is_id, sender) for node in self.nodes:
if node is not isolated:
self.wait_for_instantlock(is_id, node)
# send doublespend transaction to isolated node # send doublespend transaction to isolated node
isolated.sendrawtransaction(dblspnd_tx['hex']) isolated.sendrawtransaction(dblspnd_tx['hex'])
# generate block on isolated node with doublespend transaction # generate block on isolated node with doublespend transaction

View File

@ -535,10 +535,14 @@ class DashTestFramework(BitcoinTestFramework):
start = time() start = time()
locked = False locked = False
while True: while True:
is_tx = node.getrawtransaction(txid, True) try:
if is_tx['instantlock']: is_tx = node.getrawtransaction(txid, True)
locked = True if is_tx['instantlock']:
break locked = True
break
except:
# TX not received yet?
pass
if time() > start + 10: if time() > start + 10:
break break
sleep(0.5) sleep(0.5)

View File

@ -333,11 +333,8 @@ void CChainLocksHandler::NewPoWValidBlock(const CBlockIndex* pindex, const std::
auto txs = std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>(); auto txs = std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>();
for (const auto& tx : block->vtx) { for (const auto& tx : block->vtx) {
if (tx->nVersion == 3) { if (tx->IsCoinBase() || tx->vin.empty()) {
if (tx->nType == TRANSACTION_COINBASE || continue;
tx->nType == TRANSACTION_QUORUM_COMMITMENT) {
continue;
}
} }
txs->emplace(tx->GetHash()); txs->emplace(tx->GetHash());
txFirstSeenTime.emplace(tx->GetHash(), curTime); txFirstSeenTime.emplace(tx->GetHash(), curTime);
@ -347,11 +344,8 @@ void CChainLocksHandler::NewPoWValidBlock(const CBlockIndex* pindex, const std::
void CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock) void CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock)
{ {
if (tx.nVersion == 3) { if (tx.IsCoinBase() || tx.vin.empty()) {
if (tx.nType == TRANSACTION_COINBASE || return;
tx.nType == TRANSACTION_QUORUM_COMMITMENT) {
return;
}
} }
LOCK(cs); LOCK(cs);
@ -492,7 +486,7 @@ void CChainLocksHandler::DoInvalidateBlock(const CBlockIndex* pindex, bool activ
CValidationState state; CValidationState state;
if (!InvalidateBlock(state, params, pindex2)) { if (!InvalidateBlock(state, params, pindex2)) {
LogPrintf("CChainLocksHandler::UpdatedBlockTip -- InvalidateBlock failed: %s\n", FormatStateMessage(state)); LogPrintf("CChainLocksHandler::%s -- InvalidateBlock failed: %s\n", __func__, FormatStateMessage(state));
// This should not have happened and we are in a state were it's not safe to continue anymore // This should not have happened and we are in a state were it's not safe to continue anymore
assert(false); assert(false);
} }
@ -500,7 +494,7 @@ void CChainLocksHandler::DoInvalidateBlock(const CBlockIndex* pindex, bool activ
CValidationState state; CValidationState state;
if (activateBestChain && !ActivateBestChain(state, params)) { if (activateBestChain && !ActivateBestChain(state, params)) {
LogPrintf("CChainLocksHandler::UpdatedBlockTip -- ActivateBestChain failed: %s\n", FormatStateMessage(state)); LogPrintf("CChainLocksHandler::%s -- ActivateBestChain failed: %s\n", __func__, FormatStateMessage(state));
// This should not have happened and we are in a state were it's not safe to continue anymore // This should not have happened and we are in a state were it's not safe to continue anymore
assert(false); assert(false);
} }

View File

@ -174,7 +174,7 @@ void CInstantSendManager::UnregisterAsRecoveredSigsListener()
quorumSigningManager->UnregisterRecoveredSigsListener(this); quorumSigningManager->UnregisterRecoveredSigsListener(this);
} }
bool CInstantSendManager::ProcessTx(CNode* pfrom, const CTransaction& tx, CConnman& connman, const Consensus::Params& params) bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Params& params)
{ {
if (!IsNewInstantSendEnabled()) { if (!IsNewInstantSendEnabled()) {
return true; return true;
@ -246,13 +246,15 @@ bool CInstantSendManager::CheckCanLock(const CTransaction& tx, bool printDebug,
return false; return false;
} }
int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired; if (tx.vin.empty()) {
// can't lock TXs without inputs (e.g. quorum commitments)
return false;
}
uint256 txHash = tx.GetHash();
CAmount nValueIn = 0; CAmount nValueIn = 0;
for (const auto& in : tx.vin) { for (const auto& in : tx.vin) {
CAmount v = 0; CAmount v = 0;
if (!CheckCanLock(in.prevout, printDebug, &txHash, &v, params)) { if (!CheckCanLock(in.prevout, printDebug, tx.GetHash(), &v, params)) {
return false; return false;
} }
@ -274,7 +276,7 @@ bool CInstantSendManager::CheckCanLock(const CTransaction& tx, bool printDebug,
return true; return true;
} }
bool CInstantSendManager::CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256* _txHash, CAmount* retValue, const Consensus::Params& params) bool CInstantSendManager::CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256& txHash, CAmount* retValue, const Consensus::Params& params)
{ {
int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired; int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired;
@ -283,44 +285,36 @@ bool CInstantSendManager::CheckCanLock(const COutPoint& outpoint, bool printDebu
return true; return true;
} }
static uint256 txHashNull;
const uint256* txHash = &txHashNull;
if (_txHash) {
txHash = _txHash;
}
auto mempoolTx = mempool.get(outpoint.hash); auto mempoolTx = mempool.get(outpoint.hash);
if (mempoolTx) { if (mempoolTx) {
if (printDebug) { if (printDebug) {
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: parent mempool TX %s is not locked\n", __func__, LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: parent mempool TX %s is not locked\n", __func__,
txHash->ToString(), outpoint.hash.ToString()); txHash.ToString(), outpoint.hash.ToString());
} }
return false; return false;
} }
Coin coin; Coin coin;
const CBlockIndex* pindexMined = nullptr; const CBlockIndex* pindexMined = nullptr;
int nTxAge;
{ {
LOCK(cs_main); LOCK(cs_main);
if (!pcoinsTip->GetCoin(outpoint, coin) || coin.IsSpent()) { if (!pcoinsTip->GetCoin(outpoint, coin) || coin.IsSpent()) {
if (printDebug) { if (printDebug) {
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: failed to find UTXO %s\n", __func__, LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: failed to find UTXO %s\n", __func__,
txHash->ToString(), outpoint.ToStringShort()); txHash.ToString(), outpoint.ToStringShort());
} }
return false; return false;
} }
pindexMined = chainActive[coin.nHeight]; pindexMined = chainActive[coin.nHeight];
nTxAge = chainActive.Height() - coin.nHeight + 1;
} }
int nTxAge = chainActive.Height() - coin.nHeight + 1; if (nTxAge < nInstantSendConfirmationsRequired) {
// 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 (!llmq::chainLocksHandler->HasChainLock(pindexMined->nHeight, pindexMined->GetBlockHash())) {
if (printDebug) { if (printDebug) {
LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nConfirmationsRequired=%d\n", __func__, LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nInstantSendConfirmationsRequired=%d\n", __func__,
txHash->ToString(), outpoint.ToStringShort(), nTxAge, nConfirmationsRequired); txHash.ToString(), outpoint.ToStringShort(), nTxAge, nInstantSendConfirmationsRequired);
} }
return false; return false;
} }
@ -719,18 +713,19 @@ void CInstantSendManager::SyncTransaction(const CTransaction& tx, const CBlockIn
} }
} }
void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindex) void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindexChainLock)
{ {
uint256 lastChainLockBlock; uint256 lastChainLockBlock;
{ {
LOCK(cs); LOCK(cs);
db.GetLastChainLockBlock(); lastChainLockBlock = db.GetLastChainLockBlock();
} }
// Let's find all islocks that correspond to TXs which are part of the freshly ChainLocked chain and then delete // 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 // the islocks. We do this because the ChainLocks imply locking and thus it's not needed to further track
// or propagate the islocks // or propagate the islocks
std::unordered_set<uint256> toDelete; std::unordered_set<uint256> toDelete;
auto pindex = pindexChainLock;
while (pindex && pindex->GetBlockHash() != lastChainLockBlock) { while (pindex && pindex->GetBlockHash() != lastChainLockBlock) {
CBlock block; CBlock block;
{ {
@ -758,7 +753,7 @@ void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindex)
{ {
LOCK(cs); LOCK(cs);
db.WriteLastChainLockBlock(pindex ? pindex->GetBlockHash() : uint256()); db.WriteLastChainLockBlock(pindexChainLock->GetBlockHash());
} }
RetryLockMempoolTxs(uint256()); RetryLockMempoolTxs(uint256());
@ -849,7 +844,7 @@ void CInstantSendManager::RetryLockMempoolTxs(const uint256& lockedParentTx)
tx->GetHash().ToString()); tx->GetHash().ToString());
} }
ProcessTx(nullptr, *tx, *g_connman, Params().GetConsensus()); ProcessTx(*tx, Params().GetConsensus());
} }
} }

View File

@ -99,9 +99,9 @@ public:
void UnregisterAsRecoveredSigsListener(); void UnregisterAsRecoveredSigsListener();
public: public:
bool ProcessTx(CNode* pfrom, const CTransaction& tx, CConnman& connman, const Consensus::Params& params); bool ProcessTx(const CTransaction& tx, const Consensus::Params& params);
bool CheckCanLock(const CTransaction& tx, bool printDebug, 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 CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256& txHash, CAmount* retValue, const Consensus::Params& params);
bool IsLocked(const uint256& txHash); bool IsLocked(const uint256& txHash);
bool IsConflicted(const CTransaction& tx); bool IsConflicted(const CTransaction& tx);
bool GetConflictingTx(const CTransaction& tx, uint256& retConflictTxHash); bool GetConflictingTx(const CTransaction& tx, uint256& retConflictTxHash);
@ -120,7 +120,7 @@ public:
void UpdateWalletTransaction(const uint256& txid, const CTransactionRef& tx); void UpdateWalletTransaction(const uint256& txid, const CTransactionRef& tx);
void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock); void SyncTransaction(const CTransaction &tx, const CBlockIndex *pindex, int posInBlock);
void NotifyChainLock(const CBlockIndex* pindex); void NotifyChainLock(const CBlockIndex* pindexChainLock);
void RemoveFinalISLock(const uint256& hash, const CInstantSendLockPtr& islock); void RemoveFinalISLock(const uint256& hash, const CInstantSendLockPtr& islock);
void RemoveMempoolConflictsForLock(const uint256& hash, const CInstantSendLock& islock); void RemoveMempoolConflictsForLock(const uint256& hash, const CInstantSendLock& islock);

View File

@ -2165,7 +2165,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
} }
if (nInvType != MSG_TXLOCK_REQUEST) { if (nInvType != MSG_TXLOCK_REQUEST) {
llmq::quorumInstantSendManager->ProcessTx(pfrom, tx, connman, chainparams.GetConsensus()); llmq::quorumInstantSendManager->ProcessTx(tx, chainparams.GetConsensus());
} }
mempool.check(pcoinsTip); mempool.check(pcoinsTip);
@ -2213,7 +2213,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
} }
vEraseQueue.push_back(orphanHash); vEraseQueue.push_back(orphanHash);
llmq::quorumInstantSendManager->ProcessTx(pfrom, orphanTx, connman, chainparams.GetConsensus()); llmq::quorumInstantSendManager->ProcessTx(orphanTx, chainparams.GetConsensus());
} }
else if (!fMissingInputs2) else if (!fMissingInputs2)
{ {

View File

@ -1020,7 +1020,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason()); throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason());
} }
} }
llmq::quorumInstantSendManager->ProcessTx(nullptr, *tx, *g_connman, Params().GetConsensus()); llmq::quorumInstantSendManager->ProcessTx(*tx, Params().GetConsensus());
} else if (fHaveChain) { } else if (fHaveChain) {
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain"); throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
} }

View File

@ -1921,7 +1921,7 @@ bool CWalletTx::RelayWalletTransaction(CConnman* connman, const std::string& str
} }
} }
llmq::quorumInstantSendManager->ProcessTx(nullptr, *this->tx, *connman, Params().GetConsensus()); llmq::quorumInstantSendManager->ProcessTx(*this->tx, Params().GetConsensus());
if (connman) { if (connman) {
connman->RelayTransaction((CTransaction)*this); connman->RelayTransaction((CTransaction)*this);