2019-03-11 09:42:34 +01:00
|
|
|
#!/usr/bin/env python3
|
2021-04-20 21:33:02 +02:00
|
|
|
# Copyright (c) 2015-2021 The Dash Core developers
|
2019-03-11 09:42:34 +01:00
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
'''
|
2020-07-17 01:44:20 +02:00
|
|
|
feature_llmq_simplepose.py
|
2019-03-11 09:42:34 +01:00
|
|
|
|
|
|
|
Checks simple PoSe system based on LLMQ commitments
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Merge #13054: tests: Enable automatic detection of undefined names in Python tests scripts. Remove wildcard imports.
68400d8b96 tests: Use explicit imports (practicalswift)
Pull request description:
Enable automatic detection of undefined names in Python tests scripts. Remove wildcard imports.
Wildcard imports make it unclear which names are present in the namespace, confusing both readers and many automated tools.
An additional benefit of not using wildcard imports in tests scripts is that readers of a test script then can infer the rough testing scope just by looking at the imports.
Before this commit:
```
$ contrib/devtools/lint-python.sh | head -10
./test/functional/feature_rbf.py:8:1: F403 'from test_framework.util import *' used; unable to detect undefined names
./test/functional/feature_rbf.py:9:1: F403 'from test_framework.script import *' used; unable to detect undefined names
./test/functional/feature_rbf.py:10:1: F403 'from test_framework.mininode import *' used; unable to detect undefined names
./test/functional/feature_rbf.py:15:12: F405 bytes_to_hex_str may be undefined, or defined from star imports: test_framework.mininode, test_framework.script, test_framework.util
./test/functional/feature_rbf.py:17:58: F405 CScript may be undefined, or defined from star imports: test_framework.mininode, test_framework.script, test_framework.util
./test/functional/feature_rbf.py:25:13: F405 COIN may be undefined, or defined from star imports: test_framework.mininode, test_framework.script, test_framework.util
./test/functional/feature_rbf.py:26:31: F405 satoshi_round may be undefined, or defined from star imports: test_framework.mininode, test_framework.script, test_framework.util
./test/functional/feature_rbf.py:26:60: F405 COIN may be undefined, or defined from star imports: test_framework.mininode, test_framework.script, test_framework.util
./test/functional/feature_rbf.py:30:41: F405 satoshi_round may be undefined, or defined from star imports: test_framework.mininode, test_framework.script, test_framework.util
./test/functional/feature_rbf.py:30:68: F405 COIN may be undefined, or defined from star imports: test_framework.mininode, test_framework.script, test_framework.util
$
```
After this commit:
```
$ contrib/devtools/lint-python.sh | head -10
$
```
Tree-SHA512: 3f826d39cffb6438388e5efcb20a9622ff8238247e882d68f7b38609877421b2a8e10e9229575f8eb6a8fa42dec4256986692e92922c86171f750a0e887438d9
2018-08-13 14:24:43 +02:00
|
|
|
import time
|
|
|
|
|
|
|
|
from test_framework.test_framework import DashTestFramework
|
|
|
|
from test_framework.util import connect_nodes, force_finish_mnsync, p2p_port, wait_until
|
|
|
|
|
|
|
|
|
2019-03-11 09:42:34 +01:00
|
|
|
class LLMQSimplePoSeTest(DashTestFramework):
|
2019-09-24 00:57:30 +02:00
|
|
|
def set_test_params(self):
|
2019-12-05 19:28:33 +01:00
|
|
|
self.set_dash_test_params(6, 5, fast_dip3_enforcement=True)
|
2020-01-07 13:49:51 +01:00
|
|
|
self.set_dash_llmq_test_params(5, 3)
|
2019-03-11 09:42:34 +01:00
|
|
|
|
|
|
|
def run_test(self):
|
|
|
|
|
2022-06-18 18:52:45 +02:00
|
|
|
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
|
2019-03-11 09:42:34 +01:00
|
|
|
self.wait_for_sporks_same()
|
|
|
|
|
|
|
|
# check if mining quorums with all nodes being online succeeds without punishment/banning
|
2020-11-28 20:16:31 +01:00
|
|
|
self.test_no_banning()
|
2020-03-30 14:38:35 +02:00
|
|
|
|
|
|
|
# Now lets isolate MNs one by one and verify that punishment/banning happens
|
2020-11-19 12:42:35 +01:00
|
|
|
self.test_banning(self.isolate_mn, 1)
|
2020-03-30 14:38:35 +02:00
|
|
|
|
2020-03-30 16:52:30 +02:00
|
|
|
self.repair_masternodes(False)
|
|
|
|
|
2022-06-18 18:52:45 +02:00
|
|
|
self.nodes[0].sporkupdate("SPORK_21_QUORUM_ALL_CONNECTED", 0)
|
|
|
|
self.nodes[0].sporkupdate("SPORK_23_QUORUM_POSE", 0)
|
2020-03-30 16:52:30 +02:00
|
|
|
self.wait_for_sporks_same()
|
|
|
|
|
|
|
|
self.reset_probe_timeouts()
|
|
|
|
|
|
|
|
# Make sure no banning happens with spork21 enabled
|
2020-11-28 20:16:31 +01:00
|
|
|
self.test_no_banning()
|
2020-03-30 16:52:30 +02:00
|
|
|
|
|
|
|
# Lets restart masternodes with closed ports and verify that they get banned even though they are connected to other MNs (via outbound connections)
|
2020-11-19 12:42:35 +01:00
|
|
|
self.test_banning(self.close_mn_port, 3)
|
2020-03-30 16:52:30 +02:00
|
|
|
|
|
|
|
self.repair_masternodes(True)
|
|
|
|
self.reset_probe_timeouts()
|
|
|
|
|
2020-11-19 12:42:35 +01:00
|
|
|
self.test_banning(self.force_old_mn_proto, 3)
|
|
|
|
|
2021-01-11 04:23:01 +01:00
|
|
|
# With PoSe off there should be no punishing for non-reachable and outdated nodes
|
2022-06-18 18:52:45 +02:00
|
|
|
self.nodes[0].sporkupdate("SPORK_23_QUORUM_POSE", 4070908800)
|
2021-01-11 04:23:01 +01:00
|
|
|
self.wait_for_sporks_same()
|
|
|
|
|
|
|
|
self.repair_masternodes(True)
|
|
|
|
self.force_old_mn_proto(self.mninfo[0])
|
|
|
|
self.test_no_banning(3)
|
|
|
|
|
|
|
|
self.repair_masternodes(True)
|
|
|
|
self.close_mn_port(self.mninfo[0])
|
|
|
|
self.test_no_banning(3)
|
|
|
|
|
2020-11-19 12:42:35 +01:00
|
|
|
def isolate_mn(self, mn):
|
|
|
|
mn.node.setnetworkactive(False)
|
|
|
|
wait_until(lambda: mn.node.getconnectioncount() == 0)
|
|
|
|
return True
|
|
|
|
|
|
|
|
def close_mn_port(self, mn):
|
|
|
|
self.stop_node(mn.node.index)
|
|
|
|
self.start_masternode(mn, ["-listen=0", "-nobind"])
|
|
|
|
connect_nodes(mn.node, 0)
|
|
|
|
# Make sure the to-be-banned node is still connected well via outbound connections
|
|
|
|
for mn2 in self.mninfo:
|
|
|
|
if mn2 is not mn:
|
|
|
|
connect_nodes(mn.node, mn2.node.index)
|
|
|
|
self.reset_probe_timeouts()
|
|
|
|
return False
|
2020-03-30 16:52:30 +02:00
|
|
|
|
2020-11-19 12:42:35 +01:00
|
|
|
def force_old_mn_proto(self, mn):
|
|
|
|
self.stop_node(mn.node.index)
|
|
|
|
self.start_masternode(mn, ["-pushversion=70216"])
|
|
|
|
connect_nodes(mn.node, 0)
|
|
|
|
self.reset_probe_timeouts()
|
|
|
|
return False
|
|
|
|
|
2021-01-11 04:23:01 +01:00
|
|
|
def test_no_banning(self, expected_connections=None):
|
2019-03-11 09:42:34 +01:00
|
|
|
for i in range(3):
|
2021-01-11 04:23:01 +01:00
|
|
|
self.mine_quorum(expected_connections=expected_connections)
|
2019-03-11 09:42:34 +01:00
|
|
|
for mn in self.mninfo:
|
2021-08-27 21:03:02 +02:00
|
|
|
assert not self.check_punished(mn) and not self.check_banned(mn)
|
2019-03-11 09:42:34 +01:00
|
|
|
|
2020-11-19 12:42:35 +01:00
|
|
|
def test_banning(self, invalidate_proc, expected_connections):
|
|
|
|
mninfos_online = self.mninfo.copy()
|
|
|
|
mninfos_valid = self.mninfo.copy()
|
|
|
|
expected_contributors = len(mninfos_online)
|
2020-03-30 16:50:51 +02:00
|
|
|
for i in range(2):
|
2020-11-19 12:42:35 +01:00
|
|
|
mn = mninfos_valid.pop()
|
|
|
|
went_offline = invalidate_proc(mn)
|
|
|
|
if went_offline:
|
|
|
|
mninfos_online.remove(mn)
|
|
|
|
expected_contributors -= 1
|
2019-03-11 09:42:34 +01:00
|
|
|
|
2019-08-28 13:51:59 +02:00
|
|
|
t = time.time()
|
2020-11-19 12:42:35 +01:00
|
|
|
while (not self.check_banned(mn)) and (time.time() - t) < 120:
|
2021-03-29 20:04:35 +02:00
|
|
|
self.reset_probe_timeouts()
|
2020-11-19 12:42:35 +01:00
|
|
|
self.mine_quorum(expected_connections=expected_connections, expected_members=expected_contributors, expected_contributions=expected_contributors, expected_complaints=expected_contributors-1, expected_commitments=expected_contributors, mninfos_online=mninfos_online, mninfos_valid=mninfos_valid)
|
|
|
|
|
2021-08-27 21:03:02 +02:00
|
|
|
assert self.check_banned(mn)
|
2019-03-11 09:42:34 +01:00
|
|
|
|
2020-11-19 12:42:35 +01:00
|
|
|
if not went_offline:
|
|
|
|
# we do not include PoSe banned mns in quorums, so the next one should have 1 contributor less
|
|
|
|
expected_contributors -= 1
|
2019-03-11 09:42:34 +01:00
|
|
|
|
2020-03-30 16:52:30 +02:00
|
|
|
def repair_masternodes(self, restart):
|
|
|
|
# Repair all nodes
|
|
|
|
for mn in self.mninfo:
|
|
|
|
if self.check_banned(mn) or self.check_punished(mn):
|
|
|
|
addr = self.nodes[0].getnewaddress()
|
|
|
|
self.nodes[0].sendtoaddress(addr, 0.1)
|
|
|
|
self.nodes[0].protx('update_service', mn.proTxHash, '127.0.0.1:%d' % p2p_port(mn.node.index), mn.keyOperator, "", addr)
|
2020-12-12 07:57:39 +01:00
|
|
|
# Make sure this tx "safe" to mine even when InstantSend and ChainLocks are no longer functional
|
|
|
|
self.bump_mocktime(60 * 10 + 1)
|
2020-03-30 16:52:30 +02:00
|
|
|
self.nodes[0].generate(1)
|
2021-08-27 21:03:02 +02:00
|
|
|
assert not self.check_banned(mn)
|
2020-03-30 16:52:30 +02:00
|
|
|
|
|
|
|
if restart:
|
|
|
|
self.stop_node(mn.node.index)
|
2020-04-17 07:52:06 +02:00
|
|
|
self.start_masternode(mn)
|
2020-03-30 16:52:30 +02:00
|
|
|
else:
|
|
|
|
mn.node.setnetworkactive(True)
|
|
|
|
connect_nodes(mn.node, 0)
|
|
|
|
self.sync_all()
|
|
|
|
|
|
|
|
# Isolate and re-connect all MNs (otherwise there might be open connections with no MNAUTH for MNs which were banned before)
|
|
|
|
for mn in self.mninfo:
|
|
|
|
mn.node.setnetworkactive(False)
|
|
|
|
wait_until(lambda: mn.node.getconnectioncount() == 0)
|
|
|
|
mn.node.setnetworkactive(True)
|
2020-09-11 14:07:34 +02:00
|
|
|
force_finish_mnsync(mn.node)
|
2020-03-30 16:52:30 +02:00
|
|
|
connect_nodes(mn.node, 0)
|
|
|
|
|
|
|
|
def reset_probe_timeouts(self):
|
|
|
|
# Make sure all masternodes will reconnect/re-probe
|
2020-11-19 12:42:35 +01:00
|
|
|
self.bump_mocktime(50 * 60 + 1)
|
|
|
|
# Sleep a couple of seconds to let mn sync tick to happen
|
|
|
|
time.sleep(2)
|
2020-03-30 16:52:30 +02:00
|
|
|
|
2019-03-11 09:42:34 +01:00
|
|
|
def check_punished(self, mn):
|
|
|
|
info = self.nodes[0].protx('info', mn.proTxHash)
|
|
|
|
if info['state']['PoSePenalty'] > 0:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def check_banned(self, mn):
|
|
|
|
info = self.nodes[0].protx('info', mn.proTxHash)
|
|
|
|
if info['state']['PoSeBanHeight'] != -1:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
LLMQSimplePoSeTest().main()
|