diff --git a/src/Makefile.test.include b/src/Makefile.test.include index db14dade783a..c259e53fc689 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -98,6 +98,7 @@ BITCOIN_TESTS =\ test/evo_assetlocks_tests.cpp \ test/evo_deterministicmns_tests.cpp \ test/evo_instantsend_tests.cpp \ + test/evo_mnhf_tests.cpp \ test/evo_simplifiedmns_tests.cpp \ test/evo_trivialvalidation.cpp \ test/evo_utils_tests.cpp \ @@ -146,7 +147,6 @@ BITCOIN_TESTS =\ test/sigopcount_tests.cpp \ test/skiplist_tests.cpp \ test/sock_tests.cpp \ - test/specialtx_tests.cpp \ test/streams_tests.cpp \ test/subsidy_tests.cpp \ test/sync_tests.cpp \ diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp index 5c54c4e4242a..89ab2efb5d62 100644 --- a/src/consensus/tx_check.cpp +++ b/src/consensus/tx_check.cpp @@ -14,7 +14,7 @@ bool CheckTransaction(const CTransaction& tx, TxValidationState& state) { bool allowEmptyTxIn = false; bool allowEmptyTxOut = false; - if (tx.nType == TRANSACTION_QUORUM_COMMITMENT) { + if (tx.nType == TRANSACTION_QUORUM_COMMITMENT || tx.nType == TRANSACTION_MNHF_SIGNAL) { allowEmptyTxIn = true; allowEmptyTxOut = true; } diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 55394beaa4ab..665e600f734c 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -13,29 +13,39 @@ #include #include #include +#include #include -extern const std::string CBLSIG_REQUESTID_PREFIX = "clsig"; +extern const std::string MNEHF_REQUESTID_PREFIX = "mnhf"; -bool MNHFTx::Verify(const CBlockIndex* pQuorumIndex) const +bool MNHFTx::Verify(const CBlockIndex* pQuorumIndex, const uint256& msgHash, TxValidationState& state) const { - if (nVersion == 0 || nVersion > (llmq::utils::IsV19Active(pQuorumIndex) ? BASIC_BLS_VERSION : LEGACY_BLS_VERSION)) { - return false; + if (versionBit >= VERSIONBITS_NUM_BITS) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-nbit-out-of-bounds"); } Consensus::LLMQType llmqType = Params().GetConsensus().llmqTypeMnhf; const auto& llmq_params_opt = llmq::GetLLMQParams(llmqType); - assert(llmq_params_opt.has_value()); + if (!llmq_params_opt.has_value()) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-quorum-type"); + } int signOffset{llmq_params_opt->dkgInterval}; - const uint256 requestId = ::SerializeHash(std::make_pair(CBLSIG_REQUESTID_PREFIX, pQuorumIndex->nHeight)); - return llmq::CSigningManager::VerifyRecoveredSig(llmqType, *llmq::quorumManager, pQuorumIndex->nHeight, requestId, pQuorumIndex->GetBlockHash(), sig, 0) || - llmq::CSigningManager::VerifyRecoveredSig(llmqType, *llmq::quorumManager, pQuorumIndex->nHeight, requestId, pQuorumIndex->GetBlockHash(), sig, signOffset); + const uint256 requestId = ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{versionBit})); + + if (!llmq::CSigningManager::VerifyRecoveredSig(llmqType, *llmq::quorumManager, pQuorumIndex->nHeight + signOffset, requestId, msgHash, sig)) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-invalid"); + } + return true; } bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + if (tx.nVersion != 3 || tx.nType != TRANSACTION_MNHF_SIGNAL) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-type"); + } + MNHFTxPayload mnhfTx; if (!GetTxPayload(tx, mnhfTx)) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-payload"); @@ -55,12 +65,16 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-quorum-hash"); } - if (!llmq::GetLLMQParams(Params().GetConsensus().llmqTypeMnhf).has_value()) { - return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-type"); - } + // Copy transaction except `quorumSig` field to calculate hash + CMutableTransaction tx_copy(tx); + auto payload_copy = mnhfTx; + payload_copy.signal.sig = CBLSSignature(); + SetTxPayload(tx_copy, payload_copy); + uint256 msgHash = tx_copy.GetHash(); - if (!mnhfTx.signal.Verify(pindexQuorum)) { - return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-invalid"); + if (!mnhfTx.signal.Verify(pindexQuorum, msgHash, state)) { + // set up inside Verify + return false; } return true; @@ -68,7 +82,11 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida std::string MNHFTx::ToString() const { - return strprintf("MNHFTx(nVersion=%d, quorumHash=%s, sig=%s)", - nVersion, quorumHash.ToString(), sig.ToString()); + return strprintf("MNHFTx(versionBit=%d, quorumHash=%s, sig=%s)", + versionBit, quorumHash.ToString(), sig.ToString()); +} +std::string MNHFTxPayload::ToString() const +{ + return strprintf("MNHFTxPayload(nVersion=%d, signal=%s)", + nVersion, signal.ToString()); } - diff --git a/src/evo/mnhftx.h b/src/evo/mnhftx.h index d4f405e0f379..392fe5ff857c 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -19,20 +19,17 @@ extern RecursiveMutex cs_main; class MNHFTx { public: - static constexpr uint16_t LEGACY_BLS_VERSION = 1; - static constexpr uint16_t BASIC_BLS_VERSION = 2; - - uint16_t nVersion{LEGACY_BLS_VERSION}; + uint8_t versionBit{0}; uint256 quorumHash; CBLSSignature sig; MNHFTx() = default; - bool Verify(const CBlockIndex* pQuorumIndex) const; + bool Verify(const CBlockIndex* pQuorumIndex, const uint256& msgHash, TxValidationState& state) const; SERIALIZE_METHODS(MNHFTx, obj) { - READWRITE(obj.nVersion, obj.quorumHash); - READWRITE(CBLSSignatureVersionWrapper(const_cast(obj.sig), (obj.nVersion == LEGACY_BLS_VERSION))); + READWRITE(obj.versionBit, obj.quorumHash); + READWRITE(CBLSSignatureVersionWrapper(const_cast(obj.sig), /* fLegacy= */ false)); } std::string ToString() const; @@ -41,7 +38,7 @@ class MNHFTx { obj.clear(); obj.setObject(); - obj.pushKV("version", (int)nVersion); + obj.pushKV("versionBit", (int)versionBit); obj.pushKV("quorumHash", quorumHash.ToString()); obj.pushKV("sig", sig.ToString()); } @@ -53,7 +50,7 @@ class MNHFTxPayload static constexpr auto SPECIALTX_TYPE = TRANSACTION_MNHF_SIGNAL; static constexpr uint16_t CURRENT_VERSION = 1; - uint16_t nVersion{CURRENT_VERSION}; + uint8_t nVersion{CURRENT_VERSION}; MNHFTx signal; SERIALIZE_METHODS(MNHFTxPayload, obj) @@ -61,6 +58,8 @@ class MNHFTxPayload READWRITE(obj.nVersion, obj.signal); } + std::string ToString() const; + void ToJson(UniValue& obj) const { obj.setObject(); diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 943ec68870da..6b3bd5a10a3c 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -45,7 +45,10 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const case TRANSACTION_QUORUM_COMMITMENT: return llmq::CheckLLMQCommitment(tx, pindexPrev, state); case TRANSACTION_MNHF_SIGNAL: - return pindexPrev->nHeight + 1 >= Params().GetConsensus().DIP0024Height && CheckMNHFTx(tx, pindexPrev, state); + if (!llmq::utils::IsV20Active(pindexPrev)) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "mnhf-before-v20"); + } + return CheckMNHFTx(tx, pindexPrev, state); case TRANSACTION_ASSET_LOCK: case TRANSACTION_ASSET_UNLOCK: if (!llmq::utils::IsV20Active(pindexPrev)) { diff --git a/src/test/specialtx_tests.cpp b/src/test/evo_mnhf_tests.cpp similarity index 95% rename from src/test/specialtx_tests.cpp rename to src/test/evo_mnhf_tests.cpp index 04dc3329afe3..2e65fcb1c5c4 100644 --- a/src/test/specialtx_tests.cpp +++ b/src/test/evo_mnhf_tests.cpp @@ -36,7 +36,7 @@ static CMutableTransaction CreateMNHFTx(const uint256& mnhfTxHash, const CBLSSig { MNHFTxPayload extraPayload; extraPayload.nVersion = 1; - extraPayload.signal.nVersion = versionBit; + extraPayload.signal.versionBit = versionBit; extraPayload.signal.quorumHash = mnhfTxHash; extraPayload.signal.sig = cblSig; @@ -48,7 +48,7 @@ static CMutableTransaction CreateMNHFTx(const uint256& mnhfTxHash, const CBLSSig return tx; } -BOOST_FIXTURE_TEST_SUITE(specialtx_tests, BasicTestingSetup) +BOOST_FIXTURE_TEST_SUITE(evo_mnhf_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(verify_mnhf_specialtx_tests) { diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 9d42ec927bee..91c29d7e0bda 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -457,6 +457,8 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces bool ok = GetTxPayload(tx, assetUnlockTx); assert(ok); mapAssetUnlockExpiry.insert({tx.GetHash(), assetUnlockTx.getHeightToExpiry()}); + } else if (tx.nType == TRANSACTION_MNHF_SIGNAL) { + PrioritiseTransaction(tx.GetHash(), 0.1 * COIN); } } diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 075509284939..8a28d5c00826 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1176,6 +1176,45 @@ def __repr__(self): .format(self.version, self.index, self.fee, self.requestedHeight, self.quorumHash, self.quorumSig.hex()) +class CMnEhf: + __slots__ = ("version", "versionBit", "quorumHash", "quorumSig") + + def __init__(self, version=None, versionBit=None, quorumHash = 0, quorumSig = None): + self.set_null() + if version is not None: + self.version = version + if versionBit is not None: + self.versionBit = versionBit + if quorumHash is not None: + self.quorumHash = quorumHash + if quorumSig is not None: + self.quorumSig = quorumSig + + def set_null(self): + self.version = 0 + self.versionBit = 0 + self.quorumHash = 0 + self.quorumSig = b'\x00' * 96 + + def deserialize(self, f): + self.version = struct.unpack("