diff --git a/src/test/evo_mnhf_tests.cpp b/src/test/evo_mnhf_tests.cpp index 22cb33919240..a5ccf01d6fb5 100644 --- a/src/test/evo_mnhf_tests.cpp +++ b/src/test/evo_mnhf_tests.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -19,10 +20,10 @@ #include -static CMutableTransaction CreateMNHFTx(const uint256& quorumHash, const CBLSSignature& cblSig, const uint16_t& versionBit) +static CMutableTransaction CreateMNHFTx(const uint256& quorumHash, const CBLSSignature& cblSig, const uint16_t& versionBit, const uint16_t payload_version = MNHFTxPayload::CURRENT_VERSION) { MNHFTxPayload extraPayload; - extraPayload.nVersion = 1; + extraPayload.nVersion = payload_version; extraPayload.signal.versionBit = versionBit; extraPayload.signal.quorumHash = quorumHash; extraPayload.signal.sig = cblSig; @@ -68,19 +69,64 @@ BOOST_AUTO_TEST_CASE(verify_mnhf_specialtx_tests) auto& qman = *Assert(m_node.llmq_ctx)->qman; const CBlockIndex* pindex = chainman->ActiveChain().Tip(); uint256 hash = GetRandHash(); - TxValidationState state; + { // bad type + CMutableTransaction tx = CreateMNHFTx(hash, sig, bit); + tx.nVersion = 2; + TxValidationState state; + CheckMNHFTx(*chainman, qman, CTransaction(tx), pindex, state); + BOOST_CHECK_EQUAL(state.ToString(), "bad-mnhf-type"); + } + + { // bad payload + CMutableTransaction tx; + tx.nVersion = 3; + tx.nType = TRANSACTION_MNHF_SIGNAL; + tx.vExtraPayload = {0x01}; + TxValidationState state; + CheckMNHFTx(*chainman, qman, CTransaction(tx), pindex, state); + BOOST_CHECK_EQUAL(state.ToString(), "bad-mnhf-payload"); + } + + { // bad version + const CTransaction tx{CTransaction(CreateMNHFTx(hash, sig, bit, 0))}; + TxValidationState state; + CheckMNHFTx(*chainman, qman, tx, pindex, state); + BOOST_CHECK_EQUAL(state.ToString(), "bad-mnhf-version"); + } { // wrong quorum (we don't have any indeed) const CTransaction tx{CTransaction(CreateMNHFTx(hash, sig, bit))}; - CheckMNHFTx(*chainman, qman, CTransaction(tx), pindex, state); + TxValidationState state; + CheckMNHFTx(*chainman, qman, tx, pindex, state); BOOST_CHECK_EQUAL(state.ToString(), "bad-mnhf-quorum-hash"); } { // non EHF fork const CTransaction tx{CTransaction(CreateMNHFTx(hash, sig, 28))}; - CheckMNHFTx(*chainman, qman, CTransaction(tx), pindex, state); + TxValidationState state; + CheckMNHFTx(*chainman, qman, tx, pindex, state); BOOST_CHECK_EQUAL(state.ToString(), "bad-mnhf-non-ehf"); } + + { // out-of-bounds bit is rejected before quorum lookup + MNHFTx mnhfTx; + mnhfTx.versionBit = VERSIONBITS_NUM_BITS; + mnhfTx.quorumHash = hash; + mnhfTx.sig = sig; + TxValidationState state; + BOOST_CHECK(!mnhfTx.Verify(qman, hash, GetRandHash(), GetRandHash(), state)); + BOOST_CHECK_EQUAL(state.ToString(), "bad-mnhf-nbit-out-of-bounds"); + } + + { // well-formed bit still requires a known quorum + MNHFTx mnhfTx; + mnhfTx.versionBit = bit; + mnhfTx.quorumHash = hash; + mnhfTx.sig = sig; + TxValidationState state; + BOOST_CHECK(!mnhfTx.Verify(qman, hash, GetRandHash(), GetRandHash(), state)); + BOOST_CHECK_EQUAL(state.ToString(), "bad-mnhf-missing-quorum"); + } } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 40b9631f07de..5df7b6742a6c 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -8,12 +8,17 @@ from io import BytesIO from test_framework.authproxy import JSONRPCException +from test_framework.blocktools import ( + create_block, + create_coinbase, +) from test_framework.key import ECKey from test_framework.messages import ( CMnEhf, CTransaction, hash256, ser_string, + tx_from_hex, ) from test_framework.test_framework import ( @@ -79,6 +84,7 @@ def create_mnehf(self, versionBit, pubkey=None): mnehf_payload.quorumSig = bytearray.fromhex(recsig["sig"]) mnehf_tx.vExtraPayload = mnehf_payload.serialize() + mnehf_tx.rehash() return mnehf_tx @@ -121,6 +127,34 @@ def send_tx(self, tx, expected_error = None, reason = None): self.log.info(f"Send tx triggered an error: {e.error}") assert expected_error in e.error['message'] + def assert_mempool_reject(self, tx, reject_reason): + result = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0] + assert_equal(result["allowed"], False) + assert reject_reason in result["reject-reason"], result + + def assert_submitblock(self, txs, expected_error): + node = self.nodes[0] + best_block_hash = node.getbestblockhash() + best_block = node.getblock(best_block_hash) + height = best_block["height"] + 1 + block_time = best_block["time"] + 1 + + coinbase = create_coinbase(height, dip4_activated=True, v20_activated=True) + gbt = node.getblocktemplate() + coinbase.vExtraPayload = bytes.fromhex(gbt["coinbase_payload"]) + coinbase.rehash() + + block = create_block(int(best_block_hash, 16), coinbase, block_time, version=4) + for tx_obj in gbt["transactions"]: + tx = tx_from_hex(tx_obj["data"]) + if tx.nType == 6: + block.vtx.append(tx) + block.vtx.extend(txs) + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + + assert_equal(node.submitblock(block.serialize().hex()), expected_error) + def run_test(self): node = self.nodes[0] @@ -148,6 +182,18 @@ def run_test(self): self.log.info(f"ehf tx: {ehf_tx_sent}") ehf_unknown_tx_sent = self.send_tx(ehf_unknown_tx) self.log.info(f"unknown ehf tx: {ehf_unknown_tx_sent}") + + self.log.info("Testing MNHF rejection for invalid signature") + invalid_sig_tx = self.create_mnehf(27, pubkey) + invalid_sig_payload = CMnEhf() + invalid_sig_payload.deserialize(BytesIO(invalid_sig_tx.vExtraPayload)) + wrong_sig_payload = CMnEhf() + wrong_sig_payload.deserialize(BytesIO(ehf_tx.vExtraPayload)) + invalid_sig_payload.quorumSig = wrong_sig_payload.quorumSig + invalid_sig_tx.vExtraPayload = invalid_sig_payload.serialize() + invalid_sig_tx.rehash() + self.assert_mempool_reject(invalid_sig_tx, "bad-mnhf-invalid") + self.sync_all() ehf_blockhash = self.generate(self.nodes[1], 1)[0] @@ -212,7 +258,8 @@ def run_test(self): self.check_fork('active') self.log.info("Testing duplicate EHF signal with same bit") - ehf_tx_duplicate = self.send_tx(self.create_mnehf(28, pubkey)) + self.assert_submitblock([ehf_tx], "bad-mnhf-duplicate") + ehf_tx_duplicate = self.send_tx(ehf_tx) tip_blockhash = self.generate(node, 1, sync_fun=lambda: self.sync_blocks())[0] block = node.getblock(tip_blockhash) assert ehf_tx_duplicate in node.getrawmempool() and ehf_tx_duplicate not in block['tx']