Skip to content

Commit

Permalink
Rewrite llmq-is-retroactive.py to use mempool inconsistencies and ens…
Browse files Browse the repository at this point in the history
…ure that txes are signed by the nodes we expect

And add a bunch of comments to clarify what is/should be going on behind the scenes
  • Loading branch information
UdjinM6 authored and codablock committed Dec 5, 2019
1 parent 64f1517 commit e5c0a99
Showing 1 changed file with 88 additions and 51 deletions.
139 changes: 88 additions & 51 deletions test/functional/llmq-is-retroactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
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):
self.set_dash_test_params(6, 5, [], fast_dip3_enforcement=True)
self.set_dash_test_params(6, 5, [[], [], [], [], ["-minrelaytxfee=0.001"], ["-minrelaytxfee=0.001"]], fast_dip3_enforcement=True)

def run_test(self):
while self.nodes[0].getblockchaininfo()["bip9_softforks"]["dip0008"]["status"] != "active":
Expand All @@ -33,53 +36,75 @@ def run_test(self):
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)
# Make sure the TX wasn't accepted by nodes 4 and 5
self.wait_for_tx(txid, self.nodes[4], False, 5)
self.wait_for_tx(txid, self.nodes[5], False, 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(self.nodes[0], block)
self.wait_for_chainlocked_block_all_nodes(block)

self.log.info("testing normal signing with partially known TX")
isolate_node(self.nodes[2])
isolate_node(self.nodes[3])
isolate_node(self.nodes[4])
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
self.wait_for_instantlock(txid, self.nodes[0], False, 5)
reconnect_isolated_node(self.nodes[2], 0)
# Must reconnect early because they could be node2's "neighbours" in the quorum
# and sigshares from node2 won't be relayed anywhere otherwise.
# 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)
reconnect_isolated_node(self.nodes[4], 0)
self.wait_for_mnauth(self.nodes[2], 2)
self.nodes[2].sendrawtransaction(self.nodes[0].getrawtransaction(txid))
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))
# Make sure the TX wasn't accepted by nodes 4 and 5
self.wait_for_tx(txid, self.nodes[4], False, 5)
self.wait_for_tx(txid, self.nodes[5], False, 1)
# 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[0])
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
# Make node0 consider the TX as safe
# Make sure the TX wasn't accepted by nodes 4 and 5
self.wait_for_tx(txid, self.nodes[4], False, 5)
self.wait_for_tx(txid, self.nodes[5], False, 1)
# Make node 0 consider the TX as safe
self.bump_mocktime(10 * 60 + 1)
self.nodes[0].setmocktime(self.mocktime)
set_node_times(self.nodes, self.mocktime)
block = self.nodes[0].generate(1)[0]
reconnect_isolated_node(self.nodes[0], 1)
self.wait_for_chainlocked_block(self.nodes[0], block)
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[2])
isolate_node(self.nodes[3])
isolate_node(self.nodes[4])
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
# Make sure node1 receives the TX before we continue, otherwise it might announce the TX to node2 when
# reconnecting
wait_until(lambda: self.nodes[1].getrawtransaction(txid) != "", timeout=10, allow_exception=True)
reconnect_isolated_node(self.nodes[2], 0)
self.wait_for_mnauth(self.nodes[2], 2)
# 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])
# Make sure the TX wasn't accepted by nodes 4 and 5
self.wait_for_tx(txid, self.nodes[4], False, 5)
self.wait_for_tx(txid, self.nodes[5], False, 1)
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)
self.nodes[0].setmocktime(self.mocktime)
set_node_times(self.nodes, self.mocktime)
block = self.nodes[0].generate(1)[0]
self.wait_for_chainlocked_block(self.nodes[0], block)
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)
Expand All @@ -92,64 +117,76 @@ def run_test(self):
self.test_single_node_session_timeout(True)

def cycle_llmqs(self):
reconnect_isolated_node(self.nodes[3], 0)
reconnect_isolated_node(self.nodes[4], 0)
self.wait_for_mnauth(self.nodes[3], 2)
self.wait_for_mnauth(self.nodes[4], 2)
self.mine_quorum()
self.mine_quorum()
isolate_node(self.nodes[3])
isolate_node(self.nodes[4])
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[2])
isolate_node(self.nodes[3])
isolate_node(self.nodes[4])
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
# Make sure node1 receives the TX before we continue, otherwise it might announce the TX to node2 when
# reconnecting
wait_until(lambda: self.nodes[1].getrawtransaction(txid) != "", timeout=10, allow_exception=True)
# Make the signing session for the IS lock timeout
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 sure the TX wasn't accepted by nodes 4 and 5
self.wait_for_tx(txid, self.nodes[4], False, 1)
self.wait_for_tx(txid, self.nodes[5], False, 1)
# 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[2], 0)
self.wait_for_mnauth(self.nodes[2], 2)
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 node0 consider the TX as safe
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(self.nodes[0], block)
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[2])
isolate_node(self.nodes[3])
isolate_node(self.nodes[4])
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[2].sendrawtransaction(rawtx)
time.sleep(2) # make sure signing is done (it's async)
# Make the signing session for the IS lock timeout
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[2], 0)
self.wait_for_mnauth(self.nodes[2], 2)
reconnect_isolated_node(self.nodes[3], 0)
self.wait_for_mnauth(self.nodes[3], 2)
self.nodes[0].sendrawtransaction(rawtx)
self.wait_for_instantlock(txid, self.nodes[0], False, 5)
# 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)
# Make sure the TX wasn't accepted by nodes 4 and 5
self.wait_for_tx(txid, self.nodes[4], False, 1)
self.wait_for_tx(txid, self.nodes[5], False, 1)
# 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()
# Make node0 consider the TX as safe
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(self.nodes[0], block)
self.wait_for_chainlocked_block_all_nodes(block)

if __name__ == '__main__':
LLMQ_IS_RetroactiveSigning().main()

0 comments on commit e5c0a99

Please sign in to comment.