Skip to content

Commit 4b5891f

Browse files
PastaPastaPastaknst
authored andcommitted
Merge dashpay#6724: test: functional tests for invalid QuorumCommitment
c9ef70a tests: add is_mature for quorum generation logs (Konstantin Akimov) 59060b5 fmt: order imports and fix gap in feature_llmq_dkgerrors.py (Konstantin Akimov) 58377f8 test: added functional tests for invalid CQuorumCommitment (Konstantin Akimov) bb0b8b0 test: add serialization/deserialization of CFinalCommitmentPayload (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented As I noticed implementing dashpay#6692 if BlsChecker works incorrectly it won't be caught by unit or functional tests. See also dashpay#6692 (comment) how 6692 has been tested without this PR. ## What was done? This PR introduces new functional tests to validated that `llmqType`, `membersSig`, `quorumSig` and `quorumPublicKey` are indeed validated by Dash Core as part of consensus. ## How Has This Been Tested? See changes in `feature_llmq_dkgerrors.py` ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone ACKs for top commit: kwvg: utACK c9ef70a UdjinM6: utACK c9ef70a Tree-SHA512: ad61f8c845f6681765105224b2a84e0b206791e2c9a786433b9aa91018ab44c1fa764528196fd079f42f08a55794756ba8c9249c6eb10af6fe97c33fa4757f44
1 parent 8e80b3e commit 4b5891f

File tree

3 files changed

+99
-7
lines changed

3 files changed

+99
-7
lines changed

test/functional/feature_llmq_dkgerrors.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,17 @@
33
# Distributed under the MIT software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

6-
from test_framework.test_framework import DashTestFramework
6+
import copy
7+
from io import BytesIO
8+
9+
from test_framework.messages import (
10+
CBlock,
11+
CFinalCommitmentPayload,
12+
from_hex,
13+
)
714

15+
from test_framework.test_framework import DashTestFramework
16+
from test_framework.util import assert_equal
817
'''
918
feature_llmq_dkgerrors.py
1019
@@ -20,8 +29,9 @@ def run_test(self):
2029
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
2130
self.wait_for_sporks_same()
2231

23-
self.log.info("Mine one quorum without simulating any errors")
24-
qh = self.mine_quorum()
32+
qh = self.test_qc()
33+
34+
self.log.info("Mine one regular quorum with no invalid members is mined at this point")
2535
self.assert_member_valid(qh, self.mninfo[0].proTxHash, True)
2636

2737
self.log.info("Lets omit the contribution")
@@ -68,6 +78,59 @@ def run_test(self):
6878
qh = self.mine_quorum(expected_contributions=3, expected_complaints=0, expected_justifications=0, expected_commitments=2)
6979
self.assert_member_valid(qh, self.mninfo[0].proTxHash, True)
7080

81+
def test_qc(self):
82+
quorumHash = self.mine_quorum(skip_maturity=True)
83+
best = self.nodes[0].getbestblockhash()
84+
block_hex = self.nodes[0].getblock(best, 0)
85+
86+
block = from_hex(CBlock(), block_hex)
87+
88+
self.nodes[0].invalidateblock(best)
89+
90+
self.test_invalid(block, 'bad-qc-commitment-type', lambda qc : (setattr(qc, 'llmqType', 77), qc)[1])
91+
self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'llmqType', 106), qc)[1])
92+
# TODO: test quorumIndex for rotation quorums
93+
# self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'quorumIndex', 2), qc)[1])
94+
self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'quorumSig', getattr(qc, 'membersSig')), qc)[1])
95+
self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'membersSig', getattr(qc, 'quorumSig')), qc)[1])
96+
self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'quorumPublicKey', b'\x00' * 48), qc)[1])
97+
# TODO: test quorumVvecHash
98+
# TODO: test signers
99+
# TODO: test validMembers
100+
101+
self.nodes[0].reconsiderblock(best)
102+
# Mine 8 (SIGN_HEIGHT_OFFSET) more blocks to make sure that the new quorum gets eligible for signing sessions
103+
self.generate(self.nodes[0], 8, sync_fun=lambda: self.sync_blocks(self.nodes))
104+
105+
return quorumHash
106+
107+
def test_invalid(self, original_block, error, transform):
108+
node = self.nodes[0]
109+
110+
block = copy.deepcopy(original_block)
111+
112+
for tx in block.vtx:
113+
if tx.nType == 6:
114+
qc_payload = CFinalCommitmentPayload()
115+
qc_payload.deserialize(BytesIO(tx.vExtraPayload))
116+
# test not only quorum 100, rotation quorums also and single-node quorum
117+
if qc_payload.commitment.llmqType == 100:
118+
qc_payload.commitment = transform(qc_payload.commitment)
119+
120+
tx.vExtraPayload = qc_payload.serialize()
121+
tx.rehash()
122+
123+
def assert_submitblock(block, result_str_1, result_str_2=None):
124+
block.hashMerkleRoot = block.calc_merkle_root()
125+
block.solve()
126+
result_str_2 = result_str_2 or 'duplicate-invalid'
127+
assert_equal(result_str_1, node.submitblock(hexdata=block.serialize().hex()))
128+
assert_equal(result_str_2, node.submitblock(hexdata=block.serialize().hex()))
129+
130+
# TODO: implement similar validation not only for submitblock but for p2p message
131+
assert_submitblock(block, error)
132+
133+
71134
def assert_member_valid(self, quorumHash, proTxHash, expectedValid):
72135
q = self.nodes[0].quorum('info', 100, quorumHash, True)
73136
for m in q['members']:

test/functional/test_framework/messages.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,34 @@ def __repr__(self):
14071407
.format(self.nVersion, self.llmqType, self.quorumHash, self.quorumIndex, repr(self.signers),
14081408
repr(self.validMembers), self.quorumPublicKey.hex(), self.quorumVvecHash, self.quorumSig.hex(), self.membersSig.hex())
14091409

1410+
1411+
class CFinalCommitmentPayload:
1412+
__slots__ = ("nVersion", "nHeight", "commitment")
1413+
1414+
def __init__(self):
1415+
self.set_null()
1416+
1417+
def set_null(self):
1418+
self.nVersion = 0
1419+
self.nHeight = 0
1420+
self.commitment = CFinalCommitment()
1421+
1422+
def deserialize(self, f):
1423+
self.nVersion = struct.unpack("<H", f.read(2))[0]
1424+
self.nHeight = struct.unpack("<I", f.read(4))[0]
1425+
self.commitment = CFinalCommitment()
1426+
self.commitment.deserialize(f)
1427+
1428+
def serialize(self):
1429+
r = b""
1430+
r += struct.pack("<H", self.nVersion)
1431+
r += struct.pack("<I", self.nHeight)
1432+
r += self.commitment.serialize()
1433+
return r
1434+
1435+
def __repr__(self):
1436+
return f"CFinalCommitmentPayload(nVersion={self.nVersion} nHeight={self.nHeight} commitment={self.commitment})"
1437+
14101438
class CGovernanceObject:
14111439
__slots__ = ("nHashParent", "nRevision", "nTime", "nCollateralHash", "vchData", "nObjectType",
14121440
"masternodeOutpoint", "vchSig")

test/functional/test_framework/test_framework.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,7 +1815,7 @@ def move_blocks(self, nodes, num_blocks):
18151815
self.bump_mocktime(1, nodes=nodes)
18161816
self.generate(self.nodes[0], num_blocks, sync_fun=lambda: self.sync_blocks(nodes))
18171817

1818-
def mine_quorum(self, llmq_type_name="llmq_test", llmq_type=100, expected_connections=None, expected_members=None, expected_contributions=None, expected_complaints=0, expected_justifications=0, expected_commitments=None, mninfos_online=None, mninfos_valid=None):
1818+
def mine_quorum(self, llmq_type_name="llmq_test", llmq_type=100, expected_connections=None, expected_members=None, expected_contributions=None, expected_complaints=0, expected_justifications=0, expected_commitments=None, mninfos_online=None, mninfos_valid=None, skip_maturity=False):
18191819
spork21_active = self.nodes[0].spork('show')['SPORK_21_QUORUM_ALL_CONNECTED'] <= 1
18201820
spork23_active = self.nodes[0].spork('show')['SPORK_23_QUORUM_POSE'] <= 1
18211821

@@ -1893,10 +1893,11 @@ def mine_quorum(self, llmq_type_name="llmq_test", llmq_type=100, expected_connec
18931893
assert_equal(q, new_quorum)
18941894
quorum_info = self.nodes[0].quorum("info", llmq_type, new_quorum)
18951895

1896-
# Mine 8 (SIGN_HEIGHT_OFFSET) more blocks to make sure that the new quorum gets eligible for signing sessions
1897-
self.generate(self.nodes[0], 8, sync_fun=lambda: self.sync_blocks(nodes))
1896+
if not skip_maturity:
1897+
# Mine 8 (SIGN_HEIGHT_OFFSET) more blocks to make sure that the new quorum gets eligible for signing sessions
1898+
self.generate(self.nodes[0], 8, sync_fun=lambda: self.sync_blocks(nodes))
18981899

1899-
self.log.info("New quorum: height=%d, quorumHash=%s, quorumIndex=%d, minedBlock=%s" % (quorum_info["height"], new_quorum, quorum_info["quorumIndex"], quorum_info["minedBlock"]))
1900+
self.log.info(f"New quorum: height={quorum_info['height']}, quorumHash={new_quorum}, is_mature={not skip_maturity} quorumIndex={quorum_info['quorumIndex']}, minedBlock={quorum_info['minedBlock']}")
19001901

19011902
return new_quorum
19021903

0 commit comments

Comments
 (0)