From e69c6c3207c0c4f76f573c5b9cb10d59d6358609 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 Dec 2019 12:30:17 +0100 Subject: [PATCH 01/14] Merge #12392: Fix ignoring tx data requests when fPauseSend is set on a peer (#3225) c4af738 Fix ignoring tx data requests when fPauseSend is set on a peer (Matt Corallo) Pull request description: This resolves a bug introduced in 66aa1d58a158991a8014a91335b5bc9c00062f56 where, if when responding to a series of transaction requests in a getdata we hit the send buffer limit and set fPauseSend, we will skip one transaction per call to ProcessGetData. Bug found by Cory Fields (@theuni). Probably worth slipping into 0.16 :/. Tree-SHA512: a9313cef8ac6da31eb099c9925c8401a638220cf7bc9b7b7b83151ecae4b02630f2db45ef6668302b9bb0f38571afbd764993427f1ec9e4d74d9a3be6647d299 --- src/net_processing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 87c51c2da84da..5b3f5d767a79d 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1382,10 +1382,10 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam } } // release cs_main - if (it != pfrom->vRecvGetData.end()) { + if (it != pfrom->vRecvGetData.end() && !pfrom->fPauseSend) { const CInv &inv = *it; - it++; if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK) { + it++; ProcessGetBlockData(pfrom, consensusParams, inv, connman, interruptMsgProc); } } From 2fef21fd802570e3131ba6570abea7e86454a143 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 Dec 2019 10:06:13 +0100 Subject: [PATCH 02/14] Don't join thread in CQuorum::~CQuorum when called from within the thread (#3223) --- src/llmq/quorums.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index ec938f0a1c91c..1bf177d26648d 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -44,7 +44,9 @@ CQuorum::~CQuorum() { // most likely the thread is already done stopCachePopulatorThread = true; - if (cachePopulatorThread.joinable()) { + // watch out to not join the thread when we're called from inside the thread, which might happen on shutdown. This + // is because on shutdown the thread is the last owner of the shared CQuorum instance and thus the destroyer of it. + if (cachePopulatorThread.joinable() && cachePopulatorThread.get_id() != std::this_thread::get_id()) { cachePopulatorThread.join(); } } From 52131186017062423a5a00714060eef8237bc285 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Thu, 5 Dec 2019 21:28:33 +0300 Subject: [PATCH 03/14] Tests: Allow specifying different cmd-line params for each masternode (#3222) --- qa/rpc-tests/dip4-coinbasemerkleroots.py | 2 +- qa/rpc-tests/llmq-chainlocks.py | 2 +- qa/rpc-tests/llmq-dkgerrors.py | 2 +- qa/rpc-tests/llmq-is-cl-conflicts.py | 2 +- qa/rpc-tests/llmq-signing.py | 2 +- qa/rpc-tests/llmq-simplepose.py | 2 +- qa/rpc-tests/p2p-instantsend.py | 2 +- qa/rpc-tests/test_framework/test_framework.py | 17 ++++++++++------- 8 files changed, 17 insertions(+), 14 deletions(-) diff --git a/qa/rpc-tests/dip4-coinbasemerkleroots.py b/qa/rpc-tests/dip4-coinbasemerkleroots.py index 66fcf01f56a5b..e069ecbbb483c 100755 --- a/qa/rpc-tests/dip4-coinbasemerkleroots.py +++ b/qa/rpc-tests/dip4-coinbasemerkleroots.py @@ -39,7 +39,7 @@ def getmnlistdiff(self, baseBlockHash, blockHash): class LLMQCoinbaseCommitmentsTest(DashTestFramework): def __init__(self): - super().__init__(6, 5, [], fast_dip3_enforcement=True) + super().__init__(6, 5, fast_dip3_enforcement=True) def run_test(self): self.test_node = TestNode() diff --git a/qa/rpc-tests/llmq-chainlocks.py b/qa/rpc-tests/llmq-chainlocks.py index 4963c07542609..ede84327ef696 100755 --- a/qa/rpc-tests/llmq-chainlocks.py +++ b/qa/rpc-tests/llmq-chainlocks.py @@ -17,7 +17,7 @@ class LLMQChainLocksTest(DashTestFramework): def __init__(self): - super().__init__(6, 5, [], fast_dip3_enforcement=True) + super().__init__(6, 5, fast_dip3_enforcement=True) def run_test(self): diff --git a/qa/rpc-tests/llmq-dkgerrors.py b/qa/rpc-tests/llmq-dkgerrors.py index 3166f47a4c01e..71c8364645a34 100755 --- a/qa/rpc-tests/llmq-dkgerrors.py +++ b/qa/rpc-tests/llmq-dkgerrors.py @@ -15,7 +15,7 @@ class LLMQDKGErrors(DashTestFramework): def __init__(self): - super().__init__(6, 5, [], fast_dip3_enforcement=True) + super().__init__(6, 5, fast_dip3_enforcement=True) def run_test(self): diff --git a/qa/rpc-tests/llmq-is-cl-conflicts.py b/qa/rpc-tests/llmq-is-cl-conflicts.py index 4351728e856e3..a004c6c1ca04b 100755 --- a/qa/rpc-tests/llmq-is-cl-conflicts.py +++ b/qa/rpc-tests/llmq-is-cl-conflicts.py @@ -45,7 +45,7 @@ def on_getdata(self, conn, message): class LLMQ_IS_CL_Conflicts(DashTestFramework): def __init__(self): - super().__init__(6, 5, [], fast_dip3_enforcement=True) + super().__init__(6, 5, fast_dip3_enforcement=True) #disable_mocktime() def run_test(self): diff --git a/qa/rpc-tests/llmq-signing.py b/qa/rpc-tests/llmq-signing.py index 02e9637988b1e..11740e4322f63 100755 --- a/qa/rpc-tests/llmq-signing.py +++ b/qa/rpc-tests/llmq-signing.py @@ -17,7 +17,7 @@ class LLMQSigningTest(DashTestFramework): def __init__(self): - super().__init__(6, 5, [], fast_dip3_enforcement=True) + super().__init__(6, 5, fast_dip3_enforcement=True) def run_test(self): diff --git a/qa/rpc-tests/llmq-simplepose.py b/qa/rpc-tests/llmq-simplepose.py index 93f00514c9634..97ca90353d5c7 100755 --- a/qa/rpc-tests/llmq-simplepose.py +++ b/qa/rpc-tests/llmq-simplepose.py @@ -16,7 +16,7 @@ class LLMQSimplePoSeTest(DashTestFramework): def __init__(self): - super().__init__(6, 5, [], fast_dip3_enforcement=True) + super().__init__(6, 5, fast_dip3_enforcement=True) def run_test(self): diff --git a/qa/rpc-tests/p2p-instantsend.py b/qa/rpc-tests/p2p-instantsend.py index 88f9bd0b02a29..f74b4ddae8c36 100755 --- a/qa/rpc-tests/p2p-instantsend.py +++ b/qa/rpc-tests/p2p-instantsend.py @@ -14,7 +14,7 @@ class InstantSendTest(DashTestFramework): def __init__(self): - super().__init__(9, 5, [], fast_dip3_enforcement=True) + super().__init__(9, 5, fast_dip3_enforcement=True) # set sender, receiver, isolated nodes self.isolated_idx = 1 self.receiver_idx = 2 diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 9b0d6a275e18e..1043d986819c7 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -252,7 +252,7 @@ def __init__(self, proTxHash, ownerAddr, votingAddr, pubKeyOperator, keyOperator class DashTestFramework(BitcoinTestFramework): - def __init__(self, num_nodes, masterodes_count, extra_args, fast_dip3_enforcement=False): + def __init__(self, num_nodes, masterodes_count, extra_args=None, fast_dip3_enforcement=False): super().__init__() self.mn_count = masterodes_count self.num_nodes = num_nodes @@ -260,17 +260,20 @@ def __init__(self, num_nodes, masterodes_count, extra_args, fast_dip3_enforcemen self.setup_clean_chain = True self.is_network_split = False # additional args + if extra_args is None: + extra_args = [[]] * num_nodes + assert_equal(len(extra_args), num_nodes) self.extra_args = extra_args - self.extra_args += ["-sporkkey=cP4EKFyJsHT39LDqgdcB43Y3YXjNyjb5Fuas1GQSeAtjnZWmZEQK"] - + self.extra_args[0] += ["-sporkkey=cP4EKFyJsHT39LDqgdcB43Y3YXjNyjb5Fuas1GQSeAtjnZWmZEQK"] self.fast_dip3_enforcement = fast_dip3_enforcement if fast_dip3_enforcement: - self.extra_args += ["-dip3params=30:50"] + for i in range(0, num_nodes): + self.extra_args[i] += ["-dip3params=30:50"] def create_simple_node(self): idx = len(self.nodes) - args = self.extra_args + args = self.extra_args[idx] self.nodes.append(start_node(idx, self.options.tmpdir, args)) for i in range(0, idx): connect_nodes(self.nodes[i], idx) @@ -340,7 +343,7 @@ def start_masternodes(self): def do_start(idx): args = ['-masternode=1', - '-masternodeblsprivkey=%s' % self.mninfo[idx].keyOperator] + self.extra_args + '-masternodeblsprivkey=%s' % self.mninfo[idx].keyOperator] + self.extra_args[idx + start_idx] node = start_node(idx + start_idx, self.options.tmpdir, args) self.mninfo[idx].nodeIdx = idx + start_idx self.mninfo[idx].node = node @@ -378,7 +381,7 @@ def do_connect(idx): def setup_network(self): self.nodes = [] # create faucet node for collateral and transactions - self.nodes.append(start_node(0, self.options.tmpdir, self.extra_args)) + self.nodes.append(start_node(0, self.options.tmpdir, self.extra_args[0])) required_balance = MASTERNODE_COLLATERAL * self.mn_count + 1 while self.nodes[0].getbalance() < required_balance: set_mocktime(get_mocktime() + 1) From 41f0e9d028f7c9f5e57a263e988a85e99c0a95b2 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 Dec 2019 13:13:29 +0100 Subject: [PATCH 04/14] More fixes related to extra_args --- qa/rpc-tests/autois-mempool.py | 2 +- qa/rpc-tests/llmq-chainlocks.py | 2 +- qa/rpc-tests/p2p-autoinstantsend.py | 2 +- qa/rpc-tests/test_framework/test_framework.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qa/rpc-tests/autois-mempool.py b/qa/rpc-tests/autois-mempool.py index 7e3f04a0d5046..61614a7224fc4 100755 --- a/qa/rpc-tests/autois-mempool.py +++ b/qa/rpc-tests/autois-mempool.py @@ -23,7 +23,7 @@ class AutoISMempoolTest(DashTestFramework): def __init__(self): - super().__init__(8, 5, ["-maxmempool=%d" % MAX_MEMPOOL_SIZE, '-limitdescendantsize=10'], fast_dip3_enforcement=True) + super().__init__(8, 5, [["-maxmempool=%d" % MAX_MEMPOOL_SIZE, '-limitdescendantsize=10']] * 8, fast_dip3_enforcement=True) # set sender, receiver self.receiver_idx = 1 self.sender_idx = 2 diff --git a/qa/rpc-tests/llmq-chainlocks.py b/qa/rpc-tests/llmq-chainlocks.py index ede84327ef696..e6681f58c26f1 100755 --- a/qa/rpc-tests/llmq-chainlocks.py +++ b/qa/rpc-tests/llmq-chainlocks.py @@ -72,7 +72,7 @@ def run_test(self): good_tip = self.nodes[0].getbestblockhash() # Restart it so that it forgets all the chainlocks from the past stop_node(self.nodes[0], 0) - self.nodes[0] = start_node(0, self.options.tmpdir, self.extra_args) + self.nodes[0] = start_node(0, self.options.tmpdir, self.extra_args[0]) connect_nodes(self.nodes[0], 1) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # Now try to reorg the chain diff --git a/qa/rpc-tests/p2p-autoinstantsend.py b/qa/rpc-tests/p2p-autoinstantsend.py index 707265a1c2624..168d07ea26c6d 100755 --- a/qa/rpc-tests/p2p-autoinstantsend.py +++ b/qa/rpc-tests/p2p-autoinstantsend.py @@ -23,7 +23,7 @@ class AutoInstantSendTest(DashTestFramework): def __init__(self): - super().__init__(8, 5, [], fast_dip3_enforcement=True) + super().__init__(8, 5, fast_dip3_enforcement=True) # set sender, receiver, isolated nodes self.receiver_idx = 1 self.sender_idx = 2 diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 1043d986819c7..4c5ef359c76fa 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -332,7 +332,7 @@ def prepare_datadirs(self): copy_datadir(0, idx + start_idx, self.options.tmpdir) # restart faucet node - self.nodes[0] = start_node(0, self.options.tmpdir, self.extra_args) + self.nodes[0] = start_node(0, self.options.tmpdir, self.extra_args[0]) def start_masternodes(self): start_idx = len(self.nodes) From fdd19cf6675c0e917a17e901ee79d30236f0f8fa Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Thu, 5 Dec 2019 21:31:55 +0300 Subject: [PATCH 05/14] Tests: Fix the way nodes are connected to each other in setup_network/start_masternodes (#3221) * Tests: Connect to the control node only in start_masternodes Masternodes should take care of intra-quorum connections themselves * Reconnect non-masternodes back to the control node --- qa/rpc-tests/test_framework/test_framework.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 4c5ef359c76fa..7effeb8119c09 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -351,8 +351,8 @@ def do_start(idx): wait_to_sync(node, True) def do_connect(idx): - for i in range(0, idx + 1): - connect_nodes(self.nodes[idx + start_idx], i) + # Connect to the control node only, masternodes should take care of intra-quorum connections themselves + connect_nodes(self.mninfo[idx].node, 0) jobs = [] @@ -403,6 +403,12 @@ def setup_network(self): self.prepare_datadirs() self.start_masternodes() + # non-masternodes where disconnected from the control node during prepare_datadirs, + # let's reconnect them back to make sure they receive updates + num_simple_nodes = self.num_nodes - self.mn_count - 1 + for i in range(0, num_simple_nodes): + connect_nodes(self.nodes[i+1], 0) + set_mocktime(get_mocktime() + 1) set_node_times(self.nodes, get_mocktime()) self.nodes[0].generate(1) From 8dae12cc6081f59821e72c51f5b48e1b1829359f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 9 Oct 2019 11:15:53 +0200 Subject: [PATCH 06/14] More/better logging for InstantSend --- src/llmq/quorums_instantsend.cpp | 29 ++++++++++++++++++++++++++--- src/llmq/quorums_signing.cpp | 3 ++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index eeb8de7f29ccf..c5da3e8f98157 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -405,11 +405,17 @@ bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Par g_connman->RelayInvFiltered(inv, tx, LLMQS_PROTO_VERSION); } - 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; } if (!CheckCanLock(tx, true, params)) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: CheckCanLock returned false\n", __func__, + tx.GetHash().ToString()); return false; } @@ -424,7 +430,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; } @@ -433,19 +439,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("instantsend", "CInstantSendManager::%s -- txid=%s: already voted on all inputs, bailing out\n", __func__, + tx.GetHash().ToString()); return true; } + LogPrint("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("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("instantsend", "CInstantSendManager::%s -- txid=%s: voted on input %s with id %s\n", __func__, tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString()); } } @@ -1054,6 +1069,9 @@ void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx) nonLockedTxsByInputs.emplace(in.prevout.hash, std::make_pair(in.prevout.n, tx->GetHash())); } } + + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s\n", __func__, + tx->GetHash().ToString()); } void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChildren) @@ -1066,10 +1084,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++; } } @@ -1096,6 +1116,9 @@ void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChild } nonLockedTxs.erase(it); + + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s, retryChildren=%d, retryChildrenCount=%d\n", __func__, + txid.ToString(), retryChildren, retryChildrenCount); } void CInstantSendManager::RemoveConflictedTx(const CTransaction& tx) diff --git a/src/llmq/quorums_signing.cpp b/src/llmq/quorums_signing.cpp index 39fad65af763b..2940e051986cb 100644 --- a/src/llmq/quorums_signing.cpp +++ b/src/llmq/quorums_signing.cpp @@ -473,7 +473,8 @@ void CSigningManager::ProcessMessageRecoveredSig(CNode* pfrom, const CRecoveredS return; } - LogPrint("llmq", "CSigningManager::%s -- signHash=%s, node=%d\n", __func__, CLLMQUtils::BuildSignHash(recoveredSig).ToString(), pfrom->id); + LogPrint("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->id].emplace_back(recoveredSig); From a8b8891a1d8d3ebe18ba89e9977d5d6cd00b318c Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 Dec 2019 22:40:18 +0100 Subject: [PATCH 07/14] Add wait_for_xxx methods as found in develop But slightly modified so that they work with wait_until which does not assert in v0.14.0.x --- qa/rpc-tests/test_framework/test_framework.py | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 7effeb8119c09..4e80a8c669aec 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -14,6 +14,7 @@ from concurrent.futures import ThreadPoolExecutor from time import time, sleep +from test_framework.mininode import wait_until from .util import ( assert_equal, initialize_chain, @@ -555,22 +556,29 @@ def send_complex_tx(self, sender, receiver): return self.wait_for_instantlock(txid, sender) def wait_for_instantlock(self, txid, node): - # wait for instantsend locks - start = time() - locked = False - while True: + def check_instantlock(): try: - is_tx = node.getrawtransaction(txid, True) - if is_tx['instantlock']: - locked = True - break + return node.getrawtransaction(txid, True)["instantlock"] except: - # TX not received yet? - pass - if time() > start + 10: - break - sleep(0.5) - return locked + return False + return wait_until(check_instantlock, timeout=10, sleep=0.5) + + def wait_for_chainlocked_block(self, node, block_hash, expected=True, timeout=15): + def check_chainlocked_block(): + try: + block = node.getblock(block_hash) + return block["confirmations"] > 0 and block["chainlock"] + except: + return False + w = wait_until(check_chainlocked_block, timeout=timeout, sleep=0.1) + if not w and expected: + raise AssertionError("wait_for_chainlocked_block failed") + elif w and not expected: + raise AssertionError("waiting unexpectedly succeeded") + + def wait_for_chainlocked_block_all_nodes(self, block_hash, timeout=15): + for node in self.nodes: + self.wait_for_chainlocked_block(node, block_hash, timeout=timeout) def wait_for_sporks_same(self, timeout=30): st = time() From 22cfddaf120d6ec4f65f05ec5e83f2e63b236691 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 Dec 2019 10:05:58 +0100 Subject: [PATCH 08/14] Allow re-signing of IS locks when performing retroactive signing (#3219) * Implement re-signing of InstantSend inputs when TXs come in via blocks * Use GetAdjustedTime instead of GetTimeMillis in CSigSharesManager This allows use of mocktime in tests. * Expose verifiedProRegTxHash in getpeerinfo and implement wait_for_mnauth * Allow to wait for IS and CL to NOT happen * Bump timeout for wait_for_instantlock * Implement tests for retroactive signing of IS and CLs * Add wait_for_tx function to DashTestFramework * Add -whitelist=127.0.0.1 to node0 * Use node3 for isolated block generation * Don't test for non-receival of TXs on node4/node5 --- qa/pull-tester/rpc-tests.py | 1 + qa/rpc-tests/llmq-is-retroactive.py | 178 ++++++++++++++++++ qa/rpc-tests/test_framework/test_framework.py | 24 ++- src/llmq/quorums_instantsend.cpp | 18 +- src/llmq/quorums_instantsend.h | 2 +- src/llmq/quorums_signing.cpp | 19 +- src/llmq/quorums_signing.h | 2 +- src/llmq/quorums_signing_shares.cpp | 42 ++++- src/llmq/quorums_signing_shares.h | 6 +- src/net.cpp | 5 + src/net.h | 2 + src/rpc/net.cpp | 6 + 12 files changed, 283 insertions(+), 22 deletions(-) create mode 100755 qa/rpc-tests/llmq-is-retroactive.py diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 1522df7ba4e6a..b27d7e047acd8 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -48,6 +48,7 @@ 'llmq-chainlocks.py', # NOTE: needs dash_hash to pass 'llmq-simplepose.py', # NOTE: needs dash_hash to pass 'llmq-is-cl-conflicts.py', # NOTE: needs dash_hash to pass + 'llmq-is-retroactive.py', # NOTE: needs dash_hash to pass 'llmq-dkgerrors.py', # NOTE: needs dash_hash to pass 'dip4-coinbasemerkleroots.py', # NOTE: needs dash_hash to pass # vv Tests less than 60s vv diff --git a/qa/rpc-tests/llmq-is-retroactive.py b/qa/rpc-tests/llmq-is-retroactive.py new file mode 100755 index 0000000000000..7bdd7b4019d85 --- /dev/null +++ b/qa/rpc-tests/llmq-is-retroactive.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2018 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.mininode import * +from test_framework.test_framework import DashTestFramework +from test_framework.util import sync_blocks, set_node_times, \ + isolate_node, reconnect_isolated_node + +''' +llmq-is-retroactive.py + +Tests retroactive signing + +We have 6 nodes where node 0 is the control node, nodes 1-5 are masternodes. +Mempool inconsistencies are simulated via disconnecting/reconnecting node 3 +and by having a higher relay fee on nodes 4 and 5. +''' + +class LLMQ_IS_RetroactiveSigning(DashTestFramework): + def set_test_params(self): + # -whitelist is needed to avoid the trickling logic on node0 + self.set_dash_test_params(6, 5, [["-whitelist=127.0.0.1"], [], [], [], ["-minrelaytxfee=0.001"], ["-minrelaytxfee=0.001"]], fast_dip3_enforcement=True) + + def run_test(self): + while self.nodes[0].getblockchaininfo()["bip9_softforks"]["dip0008"]["status"] != "active": + self.nodes[0].generate(10) + sync_blocks(self.nodes, timeout=60*5) + + self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0) + self.nodes[0].spork("SPORK_19_CHAINLOCKS_ENABLED", 0) + self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 0) + self.nodes[0].spork("SPORK_3_INSTANTSEND_BLOCK_FILTERING", 0) + self.wait_for_sporks_same() + + self.mine_quorum() + self.mine_quorum() + + # Make sure that all nodes are chainlocked at the same height before starting actual tests + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) + + self.log.info("trying normal IS lock") + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) + # 3 nodes should be enough to create an IS lock even if nodes 4 and 5 (which have no tx itself) + # are the only "neighbours" in intra-quorum connections for one of them. + self.wait_for_instantlock(txid, self.nodes[0]) + self.bump_mocktime(1) + set_node_times(self.nodes, self.mocktime) + block = self.nodes[0].generate(1)[0] + self.wait_for_chainlocked_block_all_nodes(block) + + self.log.info("testing normal signing with partially known TX") + isolate_node(self.nodes[3]) + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) + # Make sure nodes 1 and 2 received the TX before we continue, + # otherwise it might announce the TX to node 3 when reconnecting + self.wait_for_tx(txid, self.nodes[1]) + self.wait_for_tx(txid, self.nodes[2]) + reconnect_isolated_node(self.nodes[3], 0) + self.wait_for_mnauth(self.nodes[3], 2) + # node 3 fully reconnected but the TX wasn't relayed to it, so there should be no IS lock + self.wait_for_instantlock(txid, self.nodes[0], False, 5) + # push the tx directly via rpc + self.nodes[3].sendrawtransaction(self.nodes[0].getrawtransaction(txid)) + # node 3 should vote on a tx now since it became aware of it via sendrawtransaction + # and this should be enough to complete an IS lock + self.wait_for_instantlock(txid, self.nodes[0]) + + self.log.info("testing retroactive signing with unknown TX") + isolate_node(self.nodes[3]) + rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1}) + rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex'] + rawtx = self.nodes[0].signrawtransaction(rawtx)['hex'] + txid = self.nodes[3].sendrawtransaction(rawtx) + # Make node 3 consider the TX as safe + self.bump_mocktime(10 * 60 + 1) + set_node_times(self.nodes, self.mocktime) + block = self.nodes[3].generatetoaddress(1, self.nodes[0].getnewaddress())[0] + reconnect_isolated_node(self.nodes[3], 0) + self.wait_for_chainlocked_block_all_nodes(block) + self.nodes[0].setmocktime(self.mocktime) + + self.log.info("testing retroactive signing with partially known TX") + isolate_node(self.nodes[3]) + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) + # Make sure nodes 1 and 2 received the TX before we continue, + # otherwise it might announce the TX to node 3 when reconnecting + self.wait_for_tx(txid, self.nodes[1]) + self.wait_for_tx(txid, self.nodes[2]) + reconnect_isolated_node(self.nodes[3], 0) + self.wait_for_mnauth(self.nodes[3], 2) + # node 3 fully reconnected but the TX wasn't relayed to it, so there should be no IS lock + self.wait_for_instantlock(txid, self.nodes[0], False, 5) + # Make node0 consider the TX as safe + self.bump_mocktime(10 * 60 + 1) + set_node_times(self.nodes, self.mocktime) + block = self.nodes[0].generate(1)[0] + self.wait_for_chainlocked_block_all_nodes(block) + + self.log.info("testing retroactive signing with partially known TX and all nodes session timeout") + self.test_all_nodes_session_timeout(False) + self.log.info("repeating test, but with cycled LLMQs") + self.test_all_nodes_session_timeout(True) + + self.log.info("testing retroactive signing with partially known TX and single node session timeout") + self.test_single_node_session_timeout(False) + self.log.info("repeating test, but with cycled LLMQs") + self.test_single_node_session_timeout(True) + + def cycle_llmqs(self): + self.mine_quorum() + self.mine_quorum() + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) + + def test_all_nodes_session_timeout(self, do_cycle_llmqs): + set_node_times(self.nodes, self.mocktime) + isolate_node(self.nodes[3]) + rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1}) + rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex'] + rawtx = self.nodes[0].signrawtransaction(rawtx)['hex'] + txid = self.nodes[0].sendrawtransaction(rawtx) + txid = self.nodes[3].sendrawtransaction(rawtx) + # Make sure nodes 1 and 2 received the TX before we continue + self.wait_for_tx(txid, self.nodes[1]) + self.wait_for_tx(txid, self.nodes[2]) + # Make sure signing is done on nodes 1 and 2 (it's async) + time.sleep(5) + # Make the signing session for the IS lock timeout on nodes 1-3 + self.bump_mocktime(61) + set_node_times(self.nodes, self.mocktime) + time.sleep(2) # make sure Cleanup() is called + reconnect_isolated_node(self.nodes[3], 0) + self.wait_for_mnauth(self.nodes[3], 2) + # node 3 fully reconnected but the signing session is already timed out on all nodes, so no IS lock + self.wait_for_instantlock(txid, self.nodes[0], False, 5) + if do_cycle_llmqs: + self.cycle_llmqs() + self.wait_for_instantlock(txid, self.nodes[0], False, 5) + # Make node 0 consider the TX as safe + self.bump_mocktime(10 * 60 + 1) + self.nodes[0].setmocktime(self.mocktime) + block = self.nodes[0].generate(1)[0] + self.wait_for_chainlocked_block_all_nodes(block) + + def test_single_node_session_timeout(self, do_cycle_llmqs): + set_node_times(self.nodes, self.mocktime) + isolate_node(self.nodes[3]) + rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1}) + rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex'] + rawtx = self.nodes[0].signrawtransaction(rawtx)['hex'] + txid = self.nodes[3].sendrawtransaction(rawtx) + time.sleep(2) # make sure signing is done on node 2 (it's async) + # Make the signing session for the IS lock timeout on node 3 + self.bump_mocktime(61) + set_node_times(self.nodes, self.mocktime) + time.sleep(2) # make sure Cleanup() is called + reconnect_isolated_node(self.nodes[3], 0) + self.wait_for_mnauth(self.nodes[3], 2) + self.nodes[0].sendrawtransaction(rawtx) + # Make sure nodes 1 and 2 received the TX + self.wait_for_tx(txid, self.nodes[1]) + self.wait_for_tx(txid, self.nodes[2]) + # Make sure signing is done on nodes 1 and 2 (it's async) + time.sleep(5) + # node 3 fully reconnected but the signing session is already timed out on it, so no IS lock + self.wait_for_instantlock(txid, self.nodes[0], False, 1) + if do_cycle_llmqs: + self.cycle_llmqs() + self.wait_for_instantlock(txid, self.nodes[0], False, 5) + # Make node 0 consider the TX as safe + self.bump_mocktime(10 * 60 + 1) + self.nodes[0].setmocktime(self.mocktime) + block = self.nodes[0].generate(1)[0] + self.wait_for_chainlocked_block_all_nodes(block) + +if __name__ == '__main__': + LLMQ_IS_RetroactiveSigning().main() diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 4e80a8c669aec..2cefe8dbf5784 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -555,13 +555,23 @@ def send_complex_tx(self, sender, receiver): self.sync_all() return self.wait_for_instantlock(txid, sender) - def wait_for_instantlock(self, txid, node): + def wait_for_tx(self, txid, node, expected=True, timeout=15): + def check_tx(): + try: + return node.getrawtransaction(txid) + except: + return False + if wait_until(check_tx, timeout=timeout, sleep=0.5, do_assert=expected) and not expected: + raise AssertionError("waiting unexpectedly succeeded") + + def wait_for_instantlock(self, txid, node, expected=True, timeout=15): def check_instantlock(): try: return node.getrawtransaction(txid, True)["instantlock"] except: return False - return wait_until(check_instantlock, timeout=10, sleep=0.5) + if wait_until(check_instantlock, timeout=timeout, sleep=0.5, do_assert=expected) and not expected: + raise AssertionError("waiting unexpectedly succeeded") def wait_for_chainlocked_block(self, node, block_hash, expected=True, timeout=15): def check_chainlocked_block(): @@ -712,6 +722,16 @@ def mine_quorum(self, expected_contributions=5, expected_complaints=0, expected_ return new_quorum + def wait_for_mnauth(self, node, count, timeout=10): + def test(): + pi = node.getpeerinfo() + c = 0 + for p in pi: + if "verified_proregtx_hash" in p and p["verified_proregtx_hash"] != "": + c += 1 + return c >= count + wait_until(test, timeout=timeout) + # Test framework for doing p2p comparison testing, which sets up some bitcoind # binaries: # 1 binary: test binary diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index c5da3e8f98157..6fd96826e0131 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -374,7 +374,7 @@ void CInstantSendManager::InterruptWorkerThread() workInterrupt(); } -bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Params& params) +bool CInstantSendManager::ProcessTx(const CTransaction& tx, bool allowReSigning, const Consensus::Params& params) { if (!IsNewInstantSendEnabled()) { return true; @@ -444,7 +444,7 @@ bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Par return false; } } - if (alreadyVotedCount == ids.size()) { + if (!allowReSigning && alreadyVotedCount == ids.size()) { LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: already voted on all inputs, bailing out\n", __func__, tx.GetHash().ToString()); return true; @@ -457,9 +457,9 @@ bool CInstantSendManager::ProcessTx(const CTransaction& tx, const Consensus::Par auto& in = tx.vin[i]; auto& id = ids[i]; inputRequestIds.emplace(id); - LogPrint("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())) { + LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: trying to vote on input %s with id %s. allowReSigning=%d\n", __func__, + tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString(), allowReSigning); + if (quorumSigningManager->AsyncSignIfMember(llmqType, id, tx.GetHash(), allowReSigning)) { LogPrint("instantsend", "CInstantSendManager::%s -- txid=%s: voted on input %s with id %s\n", __func__, tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString()); } @@ -1015,6 +1015,10 @@ void CInstantSendManager::SyncTransaction(const CTransaction& tx, const CBlockIn return; } + // This is different on develop as allowReSigning is passed in from the caller. In 0.14.0.x, we have to figure this out + // here to mimic develop. + bool allowReSigning = !inMempool && !isDisconnect; + uint256 islockHash; { LOCK(cs); @@ -1037,7 +1041,7 @@ void CInstantSendManager::SyncTransaction(const CTransaction& tx, const CBlockIn bool chainlocked = pindex && chainLocksHandler->HasChainLock(pindex->nHeight, pindex->GetBlockHash()); if (islockHash.IsNull() && !chainlocked) { - ProcessTx(tx, Params().GetConsensus()); + ProcessTx(tx, allowReSigning, Params().GetConsensus()); } LOCK(cs); @@ -1421,7 +1425,7 @@ bool CInstantSendManager::ProcessPendingRetryLockTxs() tx->GetHash().ToString()); } - ProcessTx(*tx, Params().GetConsensus()); + ProcessTx(*tx, false, Params().GetConsensus()); retryCount++; } diff --git a/src/llmq/quorums_instantsend.h b/src/llmq/quorums_instantsend.h index bb696b4adef73..11ff8583d1e09 100644 --- a/src/llmq/quorums_instantsend.h +++ b/src/llmq/quorums_instantsend.h @@ -120,7 +120,7 @@ class CInstantSendManager : public CRecoveredSigsListener void InterruptWorkerThread(); public: - bool ProcessTx(const CTransaction& tx, const Consensus::Params& params); + bool ProcessTx(const CTransaction& tx, bool allowReSigning, 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); diff --git a/src/llmq/quorums_signing.cpp b/src/llmq/quorums_signing.cpp index 2940e051986cb..d8a0f9e07e156 100644 --- a/src/llmq/quorums_signing.cpp +++ b/src/llmq/quorums_signing.cpp @@ -743,7 +743,7 @@ void CSigningManager::UnregisterRecoveredSigsListener(CRecoveredSigsListener* l) recoveredSigsListeners.erase(itRem, recoveredSigsListeners.end()); } -bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash) +bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash, bool allowReSign) { auto& params = Params().GetConsensus().llmqs.at(llmqType); @@ -754,24 +754,31 @@ bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint { LOCK(cs); - if (db.HasVotedOnId(llmqType, id)) { + bool hasVoted = db.HasVotedOnId(llmqType, id); + if (hasVoted) { uint256 prevMsgHash; db.GetVoteForId(llmqType, id, prevMsgHash); if (msgHash != prevMsgHash) { LogPrintf("CSigningManager::%s -- already voted for id=%s and msgHash=%s. Not voting on conflicting msgHash=%s\n", __func__, id.ToString(), prevMsgHash.ToString(), msgHash.ToString()); + return false; + } else if (allowReSign) { + LogPrint("llmq", "CSigningManager::%s -- already voted for id=%s and msgHash=%s. Resigning!\n", __func__, + id.ToString(), prevMsgHash.ToString()); } else { LogPrint("llmq", "CSigningManager::%s -- already voted for id=%s and msgHash=%s. Not voting again.\n", __func__, id.ToString(), prevMsgHash.ToString()); + return false; } - return false; } if (db.HasRecoveredSigForId(llmqType, id)) { // no need to sign it if we already have a recovered sig return true; } - db.WriteVoteForId(llmqType, id, msgHash); + if (!hasVoted) { + db.WriteVoteForId(llmqType, id, msgHash); + } } int tipHeight; @@ -796,6 +803,10 @@ bool CSigningManager::AsyncSignIfMember(Consensus::LLMQType llmqType, const uint return false; } + if (allowReSign) { + // make us re-announce all known shares (other nodes might have run into a timeout) + quorumSigSharesManager->ForceReAnnouncement(quorum, llmqType, id, msgHash); + } quorumSigSharesManager->AsyncSign(quorum, id, msgHash); return true; diff --git a/src/llmq/quorums_signing.h b/src/llmq/quorums_signing.h index c4c5343032fc7..92d18e4af0d42 100644 --- a/src/llmq/quorums_signing.h +++ b/src/llmq/quorums_signing.h @@ -167,7 +167,7 @@ class CSigningManager void RegisterRecoveredSigsListener(CRecoveredSigsListener* l); void UnregisterRecoveredSigsListener(CRecoveredSigsListener* l); - bool AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash); + bool AsyncSignIfMember(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash, bool allowReSign = false); bool HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash); bool HasRecoveredSigForId(Consensus::LLMQType llmqType, const uint256& id); bool HasRecoveredSigForSession(const uint256& signHash); diff --git a/src/llmq/quorums_signing_shares.cpp b/src/llmq/quorums_signing_shares.cpp index b5f7284605f4a..c1632500ce3c2 100644 --- a/src/llmq/quorums_signing_shares.cpp +++ b/src/llmq/quorums_signing_shares.cpp @@ -82,6 +82,13 @@ void CSigSharesInv::Set(uint16_t quorumMember, bool v) inv[quorumMember] = v; } +void CSigSharesInv::SetAll(bool v) +{ + for (size_t i = 0; i < inv.size(); i++) { + inv[i] = v; + } +} + std::string CBatchedSigShares::ToInvString() const { CSigSharesInv inv; @@ -679,7 +686,7 @@ void CSigSharesManager::ProcessSigShare(NodeId nodeId, const CSigShare& sigShare sigSharesToAnnounce.Add(sigShare.GetKey(), true); // Update the time we've seen the last sigShare - timeSeenForSessions[sigShare.GetSignHash()] = GetTimeMillis(); + timeSeenForSessions[sigShare.GetSignHash()] = GetAdjustedTime(); if (!quorumNodes.empty()) { // don't announce and wait for other nodes to request this share and directly send it to them @@ -778,7 +785,7 @@ void CSigSharesManager::CollectSigSharesToRequest(std::unordered_mapqc.quorumHash, id, msgHash); + auto sigs = sigShares.GetAllForSignHash(signHash); + if (sigs) { + for (auto& p : *sigs) { + // re-announce every sigshare to every node + sigSharesToAnnounce.Add(std::make_pair(signHash, p.first), true); + } + } + for (auto& p : nodeStates) { + CSigSharesNodeState& nodeState = p.second; + auto session = nodeState.GetSessionBySignHash(signHash); + if (!session) { + continue; + } + // pretend that the other node doesn't know about any shares so that we re-announce everything + session->knows.SetAll(false); + // we need to use a new session id as we don't know if the other node has run into a timeout already + session->sendSessionId = (uint32_t)-1; + } +} + void CSigSharesManager::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig) { LOCK(cs); diff --git a/src/llmq/quorums_signing_shares.h b/src/llmq/quorums_signing_shares.h index 654f88268f0d3..340c8ea07b28b 100644 --- a/src/llmq/quorums_signing_shares.h +++ b/src/llmq/quorums_signing_shares.h @@ -104,6 +104,7 @@ class CSigSharesInv void Init(size_t size); bool IsSet(uint16_t quorumMember) const; void Set(uint16_t quorumMember, bool v); + void SetAll(bool v); void Merge(const CSigSharesInv& inv2); size_t CountSet() const; @@ -329,8 +330,8 @@ class CSigSharesNodeState class CSigSharesManager : public CRecoveredSigsListener { - static const int64_t SESSION_NEW_SHARES_TIMEOUT = 60 * 1000; - static const int64_t SIG_SHARE_REQUEST_TIMEOUT = 5 * 1000; + static const int64_t SESSION_NEW_SHARES_TIMEOUT = 60; + static const int64_t SIG_SHARE_REQUEST_TIMEOUT = 5; // we try to keep total message size below 10k const size_t MAX_MSGS_CNT_QSIGSESANN = 100; @@ -377,6 +378,7 @@ class CSigSharesManager : public CRecoveredSigsListener void AsyncSign(const CQuorumCPtr& quorum, const uint256& id, const uint256& msgHash); void Sign(const CQuorumCPtr& quorum, const uint256& id, const uint256& msgHash); + void ForceReAnnouncement(const CQuorumCPtr& quorum, Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash); void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig); diff --git a/src/net.cpp b/src/net.cpp index 78b20eda184cf..0f6346a3c7e8d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -691,6 +691,11 @@ void CNode::copyStats(CNodeStats &stats) // Leave string empty if addrLocal invalid (not filled in yet) CService addrLocalUnlocked = GetAddrLocal(); stats.addrLocal = addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToString() : ""; + + { + LOCK(cs_mnauth); + X(verifiedProRegTxHash); + } } #undef X diff --git a/src/net.h b/src/net.h index 4bf1a8206f59c..b90a585cdfe17 100644 --- a/src/net.h +++ b/src/net.h @@ -660,6 +660,8 @@ class CNodeStats double dMinPing; std::string addrLocal; CAddress addr; + // In case this is a verified MN, this value is the proTx of the MN + uint256 verifiedProRegTxHash; }; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 1828a8a9652f0..e2efac81216a1 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -79,6 +79,9 @@ UniValue getpeerinfo(const JSONRPCRequest& request) " \"addr\":\"host:port\", (string) The ip address and port of the peer\n" " \"addrlocal\":\"ip:port\", (string) local address\n" " \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n" + " \"verified_proregtx_hash\": h, (hex) Only present when the peer is a masternode and succesfully\n" + " autheticated via MNAUTH. In this case, this field contains the\n" + " protx hash of the masternode\n" " \"relaytxes\":true|false, (boolean) Whether peer has asked us to relay transactions to it\n" " \"lastsend\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last send\n" " \"lastrecv\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last receive\n" @@ -135,6 +138,9 @@ UniValue getpeerinfo(const JSONRPCRequest& request) if (!(stats.addrLocal.empty())) obj.push_back(Pair("addrlocal", stats.addrLocal)); obj.push_back(Pair("services", strprintf("%016x", stats.nServices))); + if (!stats.verifiedProRegTxHash.IsNull()) { + obj.push_back(Pair("verified_proregtx_hash", stats.verifiedProRegTxHash.ToString())); + } obj.push_back(Pair("relaytxes", stats.fRelayTxes)); obj.push_back(Pair("lastsend", stats.nLastSend)); obj.push_back(Pair("lastrecv", stats.nLastRecv)); From 85bd162a3ea8c749569416b04d1ab6642112e17e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 Dec 2019 22:47:31 +0100 Subject: [PATCH 09/14] Make wait_for_xxx methods compatible with 0.14.0.x Mostly related to wait_until not asserting in 0.14.0.x --- qa/rpc-tests/test_framework/test_framework.py | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 2cefe8dbf5784..8ef8b030794b2 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -561,17 +561,30 @@ def check_tx(): return node.getrawtransaction(txid) except: return False - if wait_until(check_tx, timeout=timeout, sleep=0.5, do_assert=expected) and not expected: + w = wait_until(check_tx, timeout=timeout, sleep=0.5) + if not w and expected: + raise AssertionError("wait_for_instantlock failed") + elif w and not expected: raise AssertionError("waiting unexpectedly succeeded") - def wait_for_instantlock(self, txid, node, expected=True, timeout=15): + def wait_for_instantlock(self, txid, node, expected=True, timeout=15, do_assert=False): def check_instantlock(): try: return node.getrawtransaction(txid, True)["instantlock"] except: return False - if wait_until(check_instantlock, timeout=timeout, sleep=0.5, do_assert=expected) and not expected: - raise AssertionError("waiting unexpectedly succeeded") + w = wait_until(check_instantlock, timeout=timeout, sleep=0.1) + if not w and expected: + if do_assert: + raise AssertionError("wait_for_instantlock failed") + else: + return False + elif w and not expected: + if do_assert: + raise AssertionError("waiting unexpectedly succeeded") + else: + return False + return True def wait_for_chainlocked_block(self, node, block_hash, expected=True, timeout=15): def check_chainlocked_block(): @@ -730,7 +743,7 @@ def test(): if "verified_proregtx_hash" in p and p["verified_proregtx_hash"] != "": c += 1 return c >= count - wait_until(test, timeout=timeout) + assert wait_until(test, timeout=timeout) # Test framework for doing p2p comparison testing, which sets up some bitcoind # binaries: From 33721eaa1158ae6755b680d5eebf00c856f3394e Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 6 Dec 2019 23:16:41 +0100 Subject: [PATCH 10/14] Make llmq-is-retroactive test compatible with 0.14.0.x --- qa/rpc-tests/llmq-is-retroactive.py | 40 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/qa/rpc-tests/llmq-is-retroactive.py b/qa/rpc-tests/llmq-is-retroactive.py index 7bdd7b4019d85..3c11f520c2fea 100755 --- a/qa/rpc-tests/llmq-is-retroactive.py +++ b/qa/rpc-tests/llmq-is-retroactive.py @@ -6,7 +6,7 @@ from test_framework.mininode import * from test_framework.test_framework import DashTestFramework from test_framework.util import sync_blocks, set_node_times, \ - isolate_node, reconnect_isolated_node + isolate_node, reconnect_isolated_node, set_mocktime, get_mocktime ''' llmq-is-retroactive.py @@ -19,9 +19,9 @@ ''' class LLMQ_IS_RetroactiveSigning(DashTestFramework): - def set_test_params(self): + def __init__(self): # -whitelist is needed to avoid the trickling logic on node0 - self.set_dash_test_params(6, 5, [["-whitelist=127.0.0.1"], [], [], [], ["-minrelaytxfee=0.001"], ["-minrelaytxfee=0.001"]], fast_dip3_enforcement=True) + super().__init__(6, 5, [["-whitelist=127.0.0.1"], [], [], [], ["-minrelaytxfee=0.001"], ["-minrelaytxfee=0.001"]], fast_dip3_enforcement=True) def run_test(self): while self.nodes[0].getblockchaininfo()["bip9_softforks"]["dip0008"]["status"] != "active": @@ -45,8 +45,8 @@ def run_test(self): # 3 nodes should be enough to create an IS lock even if nodes 4 and 5 (which have no tx itself) # are the only "neighbours" in intra-quorum connections for one of them. self.wait_for_instantlock(txid, self.nodes[0]) - self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) + set_mocktime(get_mocktime() + 1) + set_node_times(self.nodes, get_mocktime()) block = self.nodes[0].generate(1)[0] self.wait_for_chainlocked_block_all_nodes(block) @@ -74,12 +74,12 @@ def run_test(self): rawtx = self.nodes[0].signrawtransaction(rawtx)['hex'] txid = self.nodes[3].sendrawtransaction(rawtx) # Make node 3 consider the TX as safe - self.bump_mocktime(10 * 60 + 1) - set_node_times(self.nodes, self.mocktime) + set_mocktime(get_mocktime() + 10 * 60 + 1) + set_node_times(self.nodes, get_mocktime()) block = self.nodes[3].generatetoaddress(1, self.nodes[0].getnewaddress())[0] reconnect_isolated_node(self.nodes[3], 0) self.wait_for_chainlocked_block_all_nodes(block) - self.nodes[0].setmocktime(self.mocktime) + self.nodes[0].setmocktime(get_mocktime()) self.log.info("testing retroactive signing with partially known TX") isolate_node(self.nodes[3]) @@ -93,8 +93,8 @@ def run_test(self): # node 3 fully reconnected but the TX wasn't relayed to it, so there should be no IS lock self.wait_for_instantlock(txid, self.nodes[0], False, 5) # Make node0 consider the TX as safe - self.bump_mocktime(10 * 60 + 1) - set_node_times(self.nodes, self.mocktime) + set_mocktime(get_mocktime() + 10 * 60 + 1) + set_node_times(self.nodes, get_mocktime()) block = self.nodes[0].generate(1)[0] self.wait_for_chainlocked_block_all_nodes(block) @@ -114,7 +114,7 @@ def cycle_llmqs(self): self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) def test_all_nodes_session_timeout(self, do_cycle_llmqs): - set_node_times(self.nodes, self.mocktime) + set_node_times(self.nodes, get_mocktime()) isolate_node(self.nodes[3]) rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1}) rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex'] @@ -127,8 +127,8 @@ def test_all_nodes_session_timeout(self, do_cycle_llmqs): # Make sure signing is done on nodes 1 and 2 (it's async) time.sleep(5) # Make the signing session for the IS lock timeout on nodes 1-3 - self.bump_mocktime(61) - set_node_times(self.nodes, self.mocktime) + set_mocktime(get_mocktime() + 61) + set_node_times(self.nodes, get_mocktime()) time.sleep(2) # make sure Cleanup() is called reconnect_isolated_node(self.nodes[3], 0) self.wait_for_mnauth(self.nodes[3], 2) @@ -138,13 +138,13 @@ def test_all_nodes_session_timeout(self, do_cycle_llmqs): self.cycle_llmqs() self.wait_for_instantlock(txid, self.nodes[0], False, 5) # Make node 0 consider the TX as safe - self.bump_mocktime(10 * 60 + 1) - self.nodes[0].setmocktime(self.mocktime) + set_mocktime(get_mocktime() + 10 * 60 + 1) + self.nodes[0].setmocktime(get_mocktime()) block = self.nodes[0].generate(1)[0] self.wait_for_chainlocked_block_all_nodes(block) def test_single_node_session_timeout(self, do_cycle_llmqs): - set_node_times(self.nodes, self.mocktime) + set_node_times(self.nodes, get_mocktime()) isolate_node(self.nodes[3]) rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1}) rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex'] @@ -152,8 +152,8 @@ def test_single_node_session_timeout(self, do_cycle_llmqs): txid = self.nodes[3].sendrawtransaction(rawtx) time.sleep(2) # make sure signing is done on node 2 (it's async) # Make the signing session for the IS lock timeout on node 3 - self.bump_mocktime(61) - set_node_times(self.nodes, self.mocktime) + set_mocktime(get_mocktime() + 61) + set_node_times(self.nodes, get_mocktime()) time.sleep(2) # make sure Cleanup() is called reconnect_isolated_node(self.nodes[3], 0) self.wait_for_mnauth(self.nodes[3], 2) @@ -169,8 +169,8 @@ def test_single_node_session_timeout(self, do_cycle_llmqs): self.cycle_llmqs() self.wait_for_instantlock(txid, self.nodes[0], False, 5) # Make node 0 consider the TX as safe - self.bump_mocktime(10 * 60 + 1) - self.nodes[0].setmocktime(self.mocktime) + set_mocktime(get_mocktime() + 10 * 60 + 1) + self.nodes[0].setmocktime(get_mocktime()) block = self.nodes[0].generate(1)[0] self.wait_for_chainlocked_block_all_nodes(block) From 401da32090fad0caa6a60f0b7d3e0a5fff51a21f Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 7 Dec 2019 00:34:50 +0100 Subject: [PATCH 11/14] More fixes in llmq-is-retroactive tests --- qa/rpc-tests/llmq-is-retroactive.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/qa/rpc-tests/llmq-is-retroactive.py b/qa/rpc-tests/llmq-is-retroactive.py index 3c11f520c2fea..355ceaaa3ce69 100755 --- a/qa/rpc-tests/llmq-is-retroactive.py +++ b/qa/rpc-tests/llmq-is-retroactive.py @@ -32,6 +32,7 @@ def run_test(self): self.nodes[0].spork("SPORK_19_CHAINLOCKS_ENABLED", 0) self.nodes[0].spork("SPORK_2_INSTANTSEND_ENABLED", 0) self.nodes[0].spork("SPORK_3_INSTANTSEND_BLOCK_FILTERING", 0) + self.nodes[0].spork("SPORK_20_INSTANTSEND_LLMQ_BASED", 0) self.wait_for_sporks_same() self.mine_quorum() @@ -44,7 +45,7 @@ def run_test(self): txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) # 3 nodes should be enough to create an IS lock even if nodes 4 and 5 (which have no tx itself) # are the only "neighbours" in intra-quorum connections for one of them. - self.wait_for_instantlock(txid, self.nodes[0]) + self.wait_for_instantlock(txid, self.nodes[0], do_assert=True) set_mocktime(get_mocktime() + 1) set_node_times(self.nodes, get_mocktime()) block = self.nodes[0].generate(1)[0] @@ -60,12 +61,12 @@ def run_test(self): reconnect_isolated_node(self.nodes[3], 0) self.wait_for_mnauth(self.nodes[3], 2) # node 3 fully reconnected but the TX wasn't relayed to it, so there should be no IS lock - self.wait_for_instantlock(txid, self.nodes[0], False, 5) + self.wait_for_instantlock(txid, self.nodes[0], False, 5, do_assert=True) # push the tx directly via rpc self.nodes[3].sendrawtransaction(self.nodes[0].getrawtransaction(txid)) # node 3 should vote on a tx now since it became aware of it via sendrawtransaction # and this should be enough to complete an IS lock - self.wait_for_instantlock(txid, self.nodes[0]) + self.wait_for_instantlock(txid, self.nodes[0], do_assert=True) self.log.info("testing retroactive signing with unknown TX") isolate_node(self.nodes[3]) @@ -91,7 +92,7 @@ def run_test(self): reconnect_isolated_node(self.nodes[3], 0) self.wait_for_mnauth(self.nodes[3], 2) # node 3 fully reconnected but the TX wasn't relayed to it, so there should be no IS lock - self.wait_for_instantlock(txid, self.nodes[0], False, 5) + self.wait_for_instantlock(txid, self.nodes[0], False, 5, do_assert=True) # Make node0 consider the TX as safe set_mocktime(get_mocktime() + 10 * 60 + 1) set_node_times(self.nodes, get_mocktime()) @@ -133,10 +134,10 @@ def test_all_nodes_session_timeout(self, do_cycle_llmqs): reconnect_isolated_node(self.nodes[3], 0) self.wait_for_mnauth(self.nodes[3], 2) # node 3 fully reconnected but the signing session is already timed out on all nodes, so no IS lock - self.wait_for_instantlock(txid, self.nodes[0], False, 5) + self.wait_for_instantlock(txid, self.nodes[0], False, 5, do_assert=True) if do_cycle_llmqs: self.cycle_llmqs() - self.wait_for_instantlock(txid, self.nodes[0], False, 5) + self.wait_for_instantlock(txid, self.nodes[0], False, 5, do_assert=True) # Make node 0 consider the TX as safe set_mocktime(get_mocktime() + 10 * 60 + 1) self.nodes[0].setmocktime(get_mocktime()) @@ -164,10 +165,10 @@ def test_single_node_session_timeout(self, do_cycle_llmqs): # Make sure signing is done on nodes 1 and 2 (it's async) time.sleep(5) # node 3 fully reconnected but the signing session is already timed out on it, so no IS lock - self.wait_for_instantlock(txid, self.nodes[0], False, 1) + self.wait_for_instantlock(txid, self.nodes[0], False, 1, do_assert=True) if do_cycle_llmqs: self.cycle_llmqs() - self.wait_for_instantlock(txid, self.nodes[0], False, 5) + self.wait_for_instantlock(txid, self.nodes[0], False, 5, do_assert=True) # Make node 0 consider the TX as safe set_mocktime(get_mocktime() + 10 * 60 + 1) self.nodes[0].setmocktime(get_mocktime()) From dc07a0c5e199336a8b7f7c39b4c77a621ba57a97 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 7 Dec 2019 11:53:34 +0100 Subject: [PATCH 12/14] [v0.14.0.x] Bump version and prepare release notes (#3228) * Bump version to 0.14.0.5 * Copy 0.14.0.4 release notes and prepare 0.14.0.5 release notes * Run gen-manpages.sh --- configure.ac | 2 +- doc/Doxyfile | 2 +- doc/man/dash-cli.1 | 6 +- doc/man/dash-qt.1 | 10 +- doc/man/dash-tx.1 | 6 +- doc/man/dashd.1 | 10 +- doc/release-notes.md | 80 +------- .../dash/release-notes-0.14.0.4.md | 177 ++++++++++++++++++ src/clientversion.h | 2 +- 9 files changed, 203 insertions(+), 92 deletions(-) create mode 100644 doc/release-notes/dash/release-notes-0.14.0.4.md diff --git a/configure.ac b/configure.ac index 90cf732610533..49c898473eb08 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 14) define(_CLIENT_VERSION_REVISION, 0) -define(_CLIENT_VERSION_BUILD, 4) +define(_CLIENT_VERSION_BUILD, 5) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2019) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/doc/Doxyfile b/doc/Doxyfile index 0a4b10a183c68..b181b39887d13 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -41,7 +41,7 @@ PROJECT_NAME = "Dash Core" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.14.0.4 +PROJECT_NUMBER = 0.14.0.5 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/doc/man/dash-cli.1 b/doc/man/dash-cli.1 index e29a9fb78ad96..0f4881af3dfda 100644 --- a/doc/man/dash-cli.1 +++ b/doc/man/dash-cli.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH DASH-CLI "1" "November 2019" "dash-cli v0.14.0.4" "User Commands" +.TH DASH-CLI "1" "December 2019" "dash-cli v0.14.0.5" "User Commands" .SH NAME -dash-cli \- manual page for dash-cli v0.14.0.4 +dash-cli \- manual page for dash-cli v0.14.0.5 .SH DESCRIPTION -Dash Core RPC client version v0.14.0.4 +Dash Core RPC client version v0.14.0.5 .SS "Usage:" .TP dash\-cli [options] [params] diff --git a/doc/man/dash-qt.1 b/doc/man/dash-qt.1 index f904eff9456f7..157cd408c3e45 100644 --- a/doc/man/dash-qt.1 +++ b/doc/man/dash-qt.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH DASH-QT "1" "November 2019" "dash-qt v0.14.0.4" "User Commands" +.TH DASH-QT "1" "December 2019" "dash-qt v0.14.0.5" "User Commands" .SH NAME -dash-qt \- manual page for dash-qt v0.14.0.4 +dash-qt \- manual page for dash-qt v0.14.0.5 .SH DESCRIPTION -Dash Core version v0.14.0.4 (64\-bit) +Dash Core version v0.14.0.5 (64\-bit) Usage: .IP dash\-qt [command\-line options] @@ -56,9 +56,9 @@ Set database cache size in megabytes (4 to 16384, default: 300) .IP Imports blocks from external blk000??.dat file on startup .HP -\fB\-maxorphantx=\fR +\fB\-maxorphantxsize=\fR .IP -Keep at most unconnectable transactions in memory (default: 100) +Maximum total size of all orphan transactions in megabytes (default: 10) .HP \fB\-maxmempool=\fR .IP diff --git a/doc/man/dash-tx.1 b/doc/man/dash-tx.1 index a9580e5f18703..e88dde1d3b2b1 100644 --- a/doc/man/dash-tx.1 +++ b/doc/man/dash-tx.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH DASH-TX "1" "November 2019" "dash-tx v0.14.0.4" "User Commands" +.TH DASH-TX "1" "December 2019" "dash-tx v0.14.0.5" "User Commands" .SH NAME -dash-tx \- manual page for dash-tx v0.14.0.4 +dash-tx \- manual page for dash-tx v0.14.0.5 .SH DESCRIPTION -Dash Core dash\-tx utility version v0.14.0.4 +Dash Core dash\-tx utility version v0.14.0.5 .SS "Usage:" .TP dash\-tx [options] [commands] diff --git a/doc/man/dashd.1 b/doc/man/dashd.1 index 7ae12a8395050..c72fb836e597d 100644 --- a/doc/man/dashd.1 +++ b/doc/man/dashd.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH DASHD "1" "November 2019" "dashd v0.14.0.4" "User Commands" +.TH DASHD "1" "December 2019" "dashd v0.14.0.5" "User Commands" .SH NAME -dashd \- manual page for dashd v0.14.0.4 +dashd \- manual page for dashd v0.14.0.5 .SH DESCRIPTION -Dash Core Daemon version v0.14.0.4 +Dash Core Daemon version v0.14.0.5 .SS "Usage:" .TP dashd [options] @@ -61,9 +61,9 @@ Set database cache size in megabytes (4 to 16384, default: 300) .IP Imports blocks from external blk000??.dat file on startup .HP -\fB\-maxorphantx=\fR +\fB\-maxorphantxsize=\fR .IP -Keep at most unconnectable transactions in memory (default: 100) +Maximum total size of all orphan transactions in megabytes (default: 10) .HP \fB\-maxmempool=\fR .IP diff --git a/doc/release-notes.md b/doc/release-notes.md index 2dfd8b0ece92f..8f224a0f52b89 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,4 +1,4 @@ -Dash Core version 0.14.0.4 +Dash Core version 0.14.0.5 ========================== Release is now available from: @@ -43,79 +43,14 @@ a reindex or re-sync the whole chain. Notable changes =============== -Fix respends of freshly received InstantSend transactions ---------------------------------------------------------- +TODO -A bug in Dash Core caused respends to not work before a received InstantSend transaction was confirmed in at least -one block. This is fixed in this release, so that InstantSend locked mempool transactions can be -respent immediately in Dash Core (other wallets were not affected). - -Deprecation of SPORK_16_INSTANTSEND_AUTOLOCKS ---------------------------------------------- - -With the activation of SPORK_20_INSTANTSEND_LLMQ_BASED a few month ago, all transactions started to be locked via -InstantSend, which already partly deprecated SPORK_16_INSTANTSEND_AUTOLOCKS. This release removes the last use -of SPORK_16_INSTANTSEND_AUTOLOCKS, which caused InstantSend to stop working when the mempool got too large. - -Improve orphan transaction limit handling ------------------------------------------ - -Instead of limiting orphan transaction by number of transaction, we limit orphans by total size in bytes -now. This allows to have thousands of orphan transactions before hitting the limit. - -Discrepancies in orphan sets between nodes and handling of those was one of the major limiting factors in -the stress tests performed by an unknown entity on mainnet. - -Improve re-requesting for already known transactions ----------------------------------------------------- - -Previously, Dash would re-request old transactions even though they were already known locally. This -happened when the outputs were respent very shortly after confirmation of the transaction. This lead to -wrongly handling these transactions as orphans, filling up the orphan set and hitting limits very fast. -This release fixes this for nodes which have txindex enabled, which is the case for all masternodes. Normal -nodes (without txindex) can ignore the issue as they are not involved in active InstantSend locking. - -Another issue fixed in this release is the re-requesting of transactions after an InstantSend lock invalidated -a conflicting transaction. - -Multiple improvements to PrivateSend ------------------------------------- - -Multiple improvements to PrivateSend are introduced in this release, leading to faster mixing and more -reasonable selection of UTXOs when sending PrivateSend funds. - -Fix for CVE-2017-18350 ----------------------- - -Bitcoin silently implemented a hidden fix for [CVE-2017-18350](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-November/017453.html). -in Bitcoin v0.15.1. This release of Dash Core includes a backport of this fix. - - -0.14.0.4 Change log +0.14.0.5 Change log =================== -See detailed [set of changes](https://github.com/dashpay/dash/compare/v0.14.0.3...dashpay:v0.14.0.4). - -- [`5f98ed7a5`](https://github.com/dashpay/dash/commit/5f98ed7a5) [v0.14.0.x] Bump version to 0.14.0.4 and draft release notes (#3203) -- [`c0dda38fe`](https://github.com/dashpay/dash/commit/c0dda38fe) Circumvent BIP69 sorting in fundrawtransaction.py test (#3100) -- [`64ae6365f`](https://github.com/dashpay/dash/commit/64ae6365f) Fix compile issues -- [`36473015b`](https://github.com/dashpay/dash/commit/36473015b) Merge #11397: net: Improve and document SOCKS code -- [`66e298728`](https://github.com/dashpay/dash/commit/66e298728) Slightly optimize ApproximateBestSubset and its usage for PS txes (#3184) -- [`16b6b6f7c`](https://github.com/dashpay/dash/commit/16b6b6f7c) Update activemn if protx info changed (#3176) -- [`ce6687130`](https://github.com/dashpay/dash/commit/ce6687130) Actually update spent index on DisconnectBlock (#3167) -- [`9b49bfda8`](https://github.com/dashpay/dash/commit/9b49bfda8) Only track last seen time instead of first and last seen time (#3165) -- [`ad720eef1`](https://github.com/dashpay/dash/commit/ad720eef1) Merge #17118: build: depends macOS: point --sysroot to SDK (#3161) -- [`909d6a4ba`](https://github.com/dashpay/dash/commit/909d6a4ba) Simulate BlockConnected/BlockDisconnected for PS caches -- [`db7f471c7`](https://github.com/dashpay/dash/commit/db7f471c7) Few fixes related to SelectCoinsGroupedByAddresses (#3144) -- [`1acd4742c`](https://github.com/dashpay/dash/commit/1acd4742c) Various fixes for mixing queues (#3138) -- [`0031d6b04`](https://github.com/dashpay/dash/commit/0031d6b04) Also consider txindex for transactions in AlreadyHave() (#3126) -- [`c4be5ac4d`](https://github.com/dashpay/dash/commit/c4be5ac4d) Ignore recent rejects filter for locked txes (#3124) -- [`f2d401aa8`](https://github.com/dashpay/dash/commit/f2d401aa8) Make orphan TX map limiting dependent on total TX size instead of TX count (#3121) -- [`87ff566a0`](https://github.com/dashpay/dash/commit/87ff566a0) Update/modernize macOS plist (#3074) -- [`2141d5f9d`](https://github.com/dashpay/dash/commit/2141d5f9d) Fix bip69 vs change position issue (#3063) -- [`75fddde67`](https://github.com/dashpay/dash/commit/75fddde67) Partially revert 3061 (#3150) -- [`c74f2cd8b`](https://github.com/dashpay/dash/commit/c74f2cd8b) Fix SelectCoinsMinConf to allow instant respends (#3061) -- [`2e7ec2369`](https://github.com/dashpay/dash/commit/2e7ec2369) [0.14.0.x] Remove check for mempool size in CInstantSendManager::CheckCanLock (#3119) +See detailed [set of changes](https://github.com/dashpay/dash/compare/v0.14.0.4...dashpay:v0.14.0.5). + +TODO Credits ======= @@ -123,8 +58,6 @@ Credits Thanks to everyone who directly contributed to this release: - Alexander Block (codablock) -- Nathan Marley (nmarley) -- PastaPastaPasta - UdjinM6 As well as everyone that submitted issues and reviewed pull requests. @@ -152,6 +85,7 @@ Dash Core tree 0.12.1.x was a fork of Bitcoin Core tree 0.12. These release are considered obsolete. Old release notes can be found here: +- [v0.14.0.4](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.4.md) released November/22/2019 - [v0.14.0.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.3.md) released August/15/2019 - [v0.14.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.2.md) released July/4/2019 - [v0.14.0.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.1.md) released May/31/2019 diff --git a/doc/release-notes/dash/release-notes-0.14.0.4.md b/doc/release-notes/dash/release-notes-0.14.0.4.md new file mode 100644 index 0000000000000..2dfd8b0ece92f --- /dev/null +++ b/doc/release-notes/dash/release-notes-0.14.0.4.md @@ -0,0 +1,177 @@ +Dash Core version 0.14.0.4 +========================== + +Release is now available from: + + + +This is a new minor version release, bringing various bugfixes and improvements. + +Please report bugs using the issue tracker at github: + + + + +Upgrading and downgrading +========================= + +How to Upgrade +-------------- + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes for older versions), then run the +installer (on Windows) or just copy over /Applications/Dash-Qt (on Mac) or +dashd/dash-qt (on Linux). If you upgrade after DIP0003 activation and you were +using version < 0.13 you will have to reindex (start with -reindex-chainstate +or -reindex) to make sure your wallet has all the new data synced. Upgrading from +version 0.13 should not require any additional actions. + +When upgrading from a version prior to 0.14.0.3, the +first startup of Dash Core will run a migration process which can take a few minutes +to finish. After the migration, a downgrade to an older version is only possible with +a reindex (or reindex-chainstate). + +Downgrade warning +----------------- + +### Downgrade to a version < 0.14.0.3 + +Downgrading to a version smaller than 0.14.0.3 is not supported anymore due to changes +in the "evodb" database format. If you need to use an older version, you have to perform +a reindex or re-sync the whole chain. + +Notable changes +=============== + +Fix respends of freshly received InstantSend transactions +--------------------------------------------------------- + +A bug in Dash Core caused respends to not work before a received InstantSend transaction was confirmed in at least +one block. This is fixed in this release, so that InstantSend locked mempool transactions can be +respent immediately in Dash Core (other wallets were not affected). + +Deprecation of SPORK_16_INSTANTSEND_AUTOLOCKS +--------------------------------------------- + +With the activation of SPORK_20_INSTANTSEND_LLMQ_BASED a few month ago, all transactions started to be locked via +InstantSend, which already partly deprecated SPORK_16_INSTANTSEND_AUTOLOCKS. This release removes the last use +of SPORK_16_INSTANTSEND_AUTOLOCKS, which caused InstantSend to stop working when the mempool got too large. + +Improve orphan transaction limit handling +----------------------------------------- + +Instead of limiting orphan transaction by number of transaction, we limit orphans by total size in bytes +now. This allows to have thousands of orphan transactions before hitting the limit. + +Discrepancies in orphan sets between nodes and handling of those was one of the major limiting factors in +the stress tests performed by an unknown entity on mainnet. + +Improve re-requesting for already known transactions +---------------------------------------------------- + +Previously, Dash would re-request old transactions even though they were already known locally. This +happened when the outputs were respent very shortly after confirmation of the transaction. This lead to +wrongly handling these transactions as orphans, filling up the orphan set and hitting limits very fast. +This release fixes this for nodes which have txindex enabled, which is the case for all masternodes. Normal +nodes (without txindex) can ignore the issue as they are not involved in active InstantSend locking. + +Another issue fixed in this release is the re-requesting of transactions after an InstantSend lock invalidated +a conflicting transaction. + +Multiple improvements to PrivateSend +------------------------------------ + +Multiple improvements to PrivateSend are introduced in this release, leading to faster mixing and more +reasonable selection of UTXOs when sending PrivateSend funds. + +Fix for CVE-2017-18350 +---------------------- + +Bitcoin silently implemented a hidden fix for [CVE-2017-18350](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-November/017453.html). +in Bitcoin v0.15.1. This release of Dash Core includes a backport of this fix. + + +0.14.0.4 Change log +=================== + +See detailed [set of changes](https://github.com/dashpay/dash/compare/v0.14.0.3...dashpay:v0.14.0.4). + +- [`5f98ed7a5`](https://github.com/dashpay/dash/commit/5f98ed7a5) [v0.14.0.x] Bump version to 0.14.0.4 and draft release notes (#3203) +- [`c0dda38fe`](https://github.com/dashpay/dash/commit/c0dda38fe) Circumvent BIP69 sorting in fundrawtransaction.py test (#3100) +- [`64ae6365f`](https://github.com/dashpay/dash/commit/64ae6365f) Fix compile issues +- [`36473015b`](https://github.com/dashpay/dash/commit/36473015b) Merge #11397: net: Improve and document SOCKS code +- [`66e298728`](https://github.com/dashpay/dash/commit/66e298728) Slightly optimize ApproximateBestSubset and its usage for PS txes (#3184) +- [`16b6b6f7c`](https://github.com/dashpay/dash/commit/16b6b6f7c) Update activemn if protx info changed (#3176) +- [`ce6687130`](https://github.com/dashpay/dash/commit/ce6687130) Actually update spent index on DisconnectBlock (#3167) +- [`9b49bfda8`](https://github.com/dashpay/dash/commit/9b49bfda8) Only track last seen time instead of first and last seen time (#3165) +- [`ad720eef1`](https://github.com/dashpay/dash/commit/ad720eef1) Merge #17118: build: depends macOS: point --sysroot to SDK (#3161) +- [`909d6a4ba`](https://github.com/dashpay/dash/commit/909d6a4ba) Simulate BlockConnected/BlockDisconnected for PS caches +- [`db7f471c7`](https://github.com/dashpay/dash/commit/db7f471c7) Few fixes related to SelectCoinsGroupedByAddresses (#3144) +- [`1acd4742c`](https://github.com/dashpay/dash/commit/1acd4742c) Various fixes for mixing queues (#3138) +- [`0031d6b04`](https://github.com/dashpay/dash/commit/0031d6b04) Also consider txindex for transactions in AlreadyHave() (#3126) +- [`c4be5ac4d`](https://github.com/dashpay/dash/commit/c4be5ac4d) Ignore recent rejects filter for locked txes (#3124) +- [`f2d401aa8`](https://github.com/dashpay/dash/commit/f2d401aa8) Make orphan TX map limiting dependent on total TX size instead of TX count (#3121) +- [`87ff566a0`](https://github.com/dashpay/dash/commit/87ff566a0) Update/modernize macOS plist (#3074) +- [`2141d5f9d`](https://github.com/dashpay/dash/commit/2141d5f9d) Fix bip69 vs change position issue (#3063) +- [`75fddde67`](https://github.com/dashpay/dash/commit/75fddde67) Partially revert 3061 (#3150) +- [`c74f2cd8b`](https://github.com/dashpay/dash/commit/c74f2cd8b) Fix SelectCoinsMinConf to allow instant respends (#3061) +- [`2e7ec2369`](https://github.com/dashpay/dash/commit/2e7ec2369) [0.14.0.x] Remove check for mempool size in CInstantSendManager::CheckCanLock (#3119) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- Alexander Block (codablock) +- Nathan Marley (nmarley) +- PastaPastaPasta +- UdjinM6 + +As well as everyone that submitted issues and reviewed pull requests. + +Older releases +============== + +Dash was previously known as Darkcoin. + +Darkcoin tree 0.8.x was a fork of Litecoin tree 0.8, original name was XCoin +which was first released on Jan/18/2014. + +Darkcoin tree 0.9.x was the open source implementation of masternodes based on +the 0.8.x tree and was first released on Mar/13/2014. + +Darkcoin tree 0.10.x used to be the closed source implementation of Darksend +which was released open source on Sep/25/2014. + +Dash Core tree 0.11.x was a fork of Bitcoin Core tree 0.9, +Darkcoin was rebranded to Dash. + +Dash Core tree 0.12.0.x was a fork of Bitcoin Core tree 0.10. + +Dash Core tree 0.12.1.x was a fork of Bitcoin Core tree 0.12. + +These release are considered obsolete. Old release notes can be found here: + +- [v0.14.0.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.3.md) released August/15/2019 +- [v0.14.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.2.md) released July/4/2019 +- [v0.14.0.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.1.md) released May/31/2019 +- [v0.14.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.md) released May/22/2019 +- [v0.13.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.13.3.md) released Apr/04/2019 +- [v0.13.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.13.2.md) released Mar/15/2019 +- [v0.13.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.13.1.md) released Feb/9/2019 +- [v0.13.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.13.0.md) released Jan/14/2019 +- [v0.12.3.4](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.3.4.md) released Dec/14/2018 +- [v0.12.3.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.3.3.md) released Sep/19/2018 +- [v0.12.3.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.3.2.md) released Jul/09/2018 +- [v0.12.3.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.3.1.md) released Jul/03/2018 +- [v0.12.2.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.2.3.md) released Jan/12/2018 +- [v0.12.2.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.2.2.md) released Dec/17/2017 +- [v0.12.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.2.md) released Nov/08/2017 +- [v0.12.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.1.md) released Feb/06/2017 +- [v0.12.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.0.md) released Aug/15/2015 +- [v0.11.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.11.2.md) released Mar/04/2015 +- [v0.11.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.11.1.md) released Feb/10/2015 +- [v0.11.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.11.0.md) released Jan/15/2015 +- [v0.10.x](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.10.0.md) released Sep/25/2014 +- [v0.9.x](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.9.0.md) released Mar/13/2014 + diff --git a/src/clientversion.h b/src/clientversion.h index 6176b27e19648..cc29ae5e24554 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -17,7 +17,7 @@ #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 14 #define CLIENT_VERSION_REVISION 0 -#define CLIENT_VERSION_BUILD 4 +#define CLIENT_VERSION_BUILD 5 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true From 20d4a2777885e644210333e1a9bbcff61c0ed1ab Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sat, 7 Dec 2019 18:53:03 +0300 Subject: [PATCH 13/14] [v0.14.0.x] Make sure mempool txes are properly processed by CChainLocksHandler despite node restarts (#3230) --- src/llmq/quorums_chainlocks.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/llmq/quorums_chainlocks.cpp b/src/llmq/quorums_chainlocks.cpp index cdb5d99ac14c2..bd4a83ee42855 100644 --- a/src/llmq/quorums_chainlocks.cpp +++ b/src/llmq/quorums_chainlocks.cpp @@ -347,15 +347,23 @@ void CChainLocksHandler::TrySignChainTip() void CChainLocksHandler::SyncTransaction(const CTransaction& tx, const CBlockIndex* pindex, int posInBlock) { - if (!masternodeSync.IsBlockchainSynced()) { - return; - } - bool handleTx = true; if (tx.IsCoinBase() || tx.vin.empty()) { handleTx = false; } + if (!masternodeSync.IsBlockchainSynced()) { + if (handleTx && posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK) { + auto info = mempool.info(tx.GetHash()); + if (!info.tx) { + return; + } + LOCK(cs); + txFirstSeenTime.emplace(tx.GetHash(), info.nTime); + } + return; + } + LOCK(cs); if (handleTx) { From 2ae1ce4800a22c717b67aea97680bbe1eeffa07b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Sat, 7 Dec 2019 16:55:51 +0100 Subject: [PATCH 14/14] [v0.14.0.x] Update release notes with notable changes and changelog (#3229) * Update release notes with notable changes and changelog * Add #3230 to changelog --- doc/release-notes.md | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/doc/release-notes.md b/doc/release-notes.md index 8f224a0f52b89..2bce689c1c318 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -6,6 +6,8 @@ Release is now available from: This is a new minor version release, bringing various bugfixes and improvements. +It is highly recommended to upgrade to this release as it contains a critical +fix for a possible DoS vector. Please report bugs using the issue tracker at github: @@ -43,14 +45,42 @@ a reindex or re-sync the whole chain. Notable changes =============== -TODO +Fix for a DoS vector +-------------------- + +This release fixes a serious DoS vector which allows to cause memory exhaustion until the point of +out-of-memory related crashes. We highly recommend upgrading all nodes. Thanks to Bitcoin ABC +developers for finding and reporting this issue to us. + +Better handling of non-locked transactions in mined blocks +---------------------------------------------------------- + +We observed multiple cases of ChainLocks failing on mainnet. We tracked this down to a situation where +PrivateSend mixing transactions were first rejected by parts of the network (0.14.0.4 nodes) while other parts +(<=0.14.0.3) accepted the transaction into the mempool. This caused InstantSend locking to fail for these +transactions, while non-upgraded miners still included the transactions into blocks after 10 minutes. +This caused blocks to not get ChainLocked for at least 10 minutes. This release improves an already existent +fallback mechanism (retroactive InstantSend locking) to also work for transaction which are already partially +known in the network. This should cause ChainLocks to succeed in such situations. 0.14.0.5 Change log =================== See detailed [set of changes](https://github.com/dashpay/dash/compare/v0.14.0.4...dashpay:v0.14.0.5). -TODO +- [`20d4a27778`](https://github.com/dashpay/dash/commit/dc07a0c5e1) Make sure mempool txes are properly processed by CChainLocksHandler despite node restarts (#3230) +- [`dc07a0c5e1`](https://github.com/dashpay/dash/commit/dc07a0c5e1) [v0.14.0.x] Bump version and prepare release notes (#3228) +- [`401da32090`](https://github.com/dashpay/dash/commit/401da32090) More fixes in llmq-is-retroactive tests +- [`33721eaa11`](https://github.com/dashpay/dash/commit/33721eaa11) Make llmq-is-retroactive test compatible with 0.14.0.x +- [`85bd162a3e`](https://github.com/dashpay/dash/commit/85bd162a3e) Make wait_for_xxx methods compatible with 0.14.0.x +- [`22cfddaf12`](https://github.com/dashpay/dash/commit/22cfddaf12) Allow re-signing of IS locks when performing retroactive signing (#3219) +- [`a8b8891a1d`](https://github.com/dashpay/dash/commit/a8b8891a1d) Add wait_for_xxx methods as found in develop +- [`8dae12cc60`](https://github.com/dashpay/dash/commit/8dae12cc60) More/better logging for InstantSend +- [`fdd19cf667`](https://github.com/dashpay/dash/commit/fdd19cf667) Tests: Fix the way nodes are connected to each other in setup_network/start_masternodes (#3221) +- [`41f0e9d028`](https://github.com/dashpay/dash/commit/41f0e9d028) More fixes related to extra_args +- [`5213118601`](https://github.com/dashpay/dash/commit/5213118601) Tests: Allow specifying different cmd-line params for each masternode (#3222) +- [`2fef21fd80`](https://github.com/dashpay/dash/commit/2fef21fd80) Don't join thread in CQuorum::~CQuorum when called from within the thread (#3223) +- [`e69c6c3207`](https://github.com/dashpay/dash/commit/e69c6c3207) Merge #12392: Fix ignoring tx data requests when fPauseSend is set on a peer (#3225) Credits =======