diff --git a/test/functional/feature_llmq_dkgerrors.py b/test/functional/feature_llmq_dkgerrors.py index 803294c48a3f..187bea3b8bc8 100755 --- a/test/functional/feature_llmq_dkgerrors.py +++ b/test/functional/feature_llmq_dkgerrors.py @@ -3,8 +3,17 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -from test_framework.test_framework import DashTestFramework +import copy +from io import BytesIO + +from test_framework.messages import ( + CBlock, + CFinalCommitmentPayload, + from_hex, +) +from test_framework.test_framework import DashTestFramework +from test_framework.util import assert_equal ''' feature_llmq_dkgerrors.py @@ -20,8 +29,9 @@ def run_test(self): self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0) self.wait_for_sporks_same() - self.log.info("Mine one quorum without simulating any errors") - qh = self.mine_quorum() + qh = self.test_qc() + + self.log.info("Mine one regular quorum with no invalid members is mined at this point") self.assert_member_valid(qh, self.mninfo[0].proTxHash, True) mninfos_valid = self.mninfo.copy()[1:] @@ -70,6 +80,59 @@ def run_test(self): qh = self.mine_quorum(expected_contributions=3, expected_complaints=0, expected_justifications=0, expected_commitments=2, mninfos_valid=mninfos_valid) self.assert_member_valid(qh, self.mninfo[0].proTxHash, True) + def test_qc(self): + quorumHash = self.mine_quorum(skip_maturity=True) + best = self.nodes[0].getbestblockhash() + block_hex = self.nodes[0].getblock(best, 0) + + block = from_hex(CBlock(), block_hex) + + self.nodes[0].invalidateblock(best) + + self.test_invalid(block, 'bad-qc-commitment-type', lambda qc : (setattr(qc, 'llmqType', 77), qc)[1]) + self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'llmqType', 106), qc)[1]) + # TODO: test quorumIndex for rotation quorums + # self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'quorumIndex', 2), qc)[1]) + self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'quorumSig', getattr(qc, 'membersSig')), qc)[1]) + self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'membersSig', getattr(qc, 'quorumSig')), qc)[1]) + self.test_invalid(block, 'bad-qc-invalid', lambda qc : (setattr(qc, 'quorumPublicKey', b'\x00' * 48), qc)[1]) + # TODO: test quorumVvecHash + # TODO: test signers + # TODO: test validMembers + + self.nodes[0].reconsiderblock(best) + # Mine 8 (SIGN_HEIGHT_OFFSET) more blocks to make sure that the new quorum gets eligible for signing sessions + self.generate(self.nodes[0], 8, sync_fun=lambda: self.sync_blocks(self.nodes)) + + return quorumHash + + def test_invalid(self, original_block, error, transform): + node = self.nodes[0] + + block = copy.deepcopy(original_block) + + for tx in block.vtx: + if tx.nType == 6: + qc_payload = CFinalCommitmentPayload() + qc_payload.deserialize(BytesIO(tx.vExtraPayload)) + # test not only quorum 100, rotation quorums also and single-node quorum + if qc_payload.commitment.llmqType == 100: + qc_payload.commitment = transform(qc_payload.commitment) + + tx.vExtraPayload = qc_payload.serialize() + tx.rehash() + + def assert_submitblock(block, result_str_1, result_str_2=None): + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + result_str_2 = result_str_2 or 'duplicate-invalid' + assert_equal(result_str_1, node.submitblock(hexdata=block.serialize().hex())) + assert_equal(result_str_2, node.submitblock(hexdata=block.serialize().hex())) + + # TODO: implement similar validation not only for submitblock but for p2p message + assert_submitblock(block, error) + + def assert_member_valid(self, quorumHash, proTxHash, expectedValid): q = self.nodes[0].quorum('info', 100, quorumHash, True) for m in q['members']: diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 95023f840c5b..5892c7c0d1ac 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1407,6 +1407,34 @@ def __repr__(self): .format(self.nVersion, self.llmqType, self.quorumHash, self.quorumIndex, repr(self.signers), repr(self.validMembers), self.quorumPublicKey.hex(), self.quorumVvecHash, self.quorumSig.hex(), self.membersSig.hex()) + +class CFinalCommitmentPayload: + __slots__ = ("nVersion", "nHeight", "commitment") + + def __init__(self): + self.set_null() + + def set_null(self): + self.nVersion = 0 + self.nHeight = 0 + self.commitment = CFinalCommitment() + + def deserialize(self, f): + self.nVersion = struct.unpack("