diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index b3cb1d4b677b..34537ffb64c8 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -44,7 +44,13 @@ Uint256HashSet GetIdsFromLockable(const std::vector& vec) if (vec.empty()) return ret; ret.reserve(vec.size()); for (const auto& in : vec) { - ret.emplace(instantsend::GenInputLockRequestId(in)); + if constexpr (std::is_same_v) { + ret.emplace(instantsend::GenInputLockRequestId(in)); + } else if constexpr (std::is_same_v) { + ret.emplace(instantsend::GenInputLockRequestId(in.prevout)); + } else { + assert(false); + } } return ret; } diff --git a/src/instantsend/lock.cpp b/src/instantsend/lock.cpp index 10ef0afc3c2d..f0f2c1d1508d 100644 --- a/src/instantsend/lock.cpp +++ b/src/instantsend/lock.cpp @@ -38,11 +38,8 @@ bool InstantSendLock::TriviallyValid() const return inputs_set.size() == inputs.size(); } -template -uint256 GenInputLockRequestId(const T& val) +uint256 GenInputLockRequestId(const COutPoint& outpoint) { - return ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, val)); + return ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, outpoint)); } -template uint256 GenInputLockRequestId(const COutPoint& val); -template uint256 GenInputLockRequestId(const CTxIn& val); } // namespace instantsend diff --git a/src/instantsend/lock.h b/src/instantsend/lock.h index 2786ba1ff7cf..b0536e8421bc 100644 --- a/src/instantsend/lock.h +++ b/src/instantsend/lock.h @@ -40,8 +40,7 @@ struct InstantSendLock { bool TriviallyValid() const; }; -template -uint256 GenInputLockRequestId(const T& val); +uint256 GenInputLockRequestId(const COutPoint& outpoint); using InstantSendLockPtr = std::shared_ptr; } // namespace instantsend diff --git a/src/instantsend/signing.cpp b/src/instantsend/signing.cpp index 93d2320f7fa3..fa6d40f4623e 100644 --- a/src/instantsend/signing.cpp +++ b/src/instantsend/signing.cpp @@ -295,7 +295,7 @@ bool InstantSendSigner::TrySignInputLocks(const CTransaction& tx, bool fRetroact size_t alreadyVotedCount = 0; for (const auto& in : tx.vin) { - auto id = GenInputLockRequestId(in); + auto id = GenInputLockRequestId(in.prevout); ids.emplace_back(id); uint256 otherTxHash; @@ -344,7 +344,7 @@ void InstantSendSigner::TrySignInstantSendLock(const CTransaction& tx) const auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend; for (const auto& in : tx.vin) { - auto id = GenInputLockRequestId(in); + auto id = GenInputLockRequestId(in.prevout); if (!m_sigman.HasRecoveredSig(llmqType, id, tx.GetHash())) { return; } diff --git a/src/test/evo_islock_tests.cpp b/src/test/evo_islock_tests.cpp index eb6767b6216d..7dc76475941d 100644 --- a/src/test/evo_islock_tests.cpp +++ b/src/test/evo_islock_tests.cpp @@ -107,4 +107,106 @@ BOOST_AUTO_TEST_CASE(deserialize_instantlock_from_realdata2) BOOST_CHECK_EQUAL(islock.sig.Get().ToString(), expectedSignatureStr); } +BOOST_AUTO_TEST_CASE(geninputlockrequestid_basic) +{ + // Test that GenInputLockRequestId generates consistent hashes for the same outpoint + const uint256 txHash = uint256S("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); + const uint32_t outputIndex = 5; + + COutPoint outpoint1(txHash, outputIndex); + COutPoint outpoint2(txHash, outputIndex); + + // Same outpoints should produce identical request IDs + const uint256 requestId1 = instantsend::GenInputLockRequestId(outpoint1); + const uint256 requestId2 = instantsend::GenInputLockRequestId(outpoint2); + + BOOST_CHECK(requestId1 == requestId2); + BOOST_CHECK(!requestId1.IsNull()); +} + +BOOST_AUTO_TEST_CASE(geninputlockrequestid_different_outpoints) +{ + // Test that different outpoints produce different request IDs + const uint256 txHash1 = uint256S("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); + const uint256 txHash2 = uint256S("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"); + + COutPoint outpoint1(txHash1, 0); + COutPoint outpoint2(txHash2, 0); + COutPoint outpoint3(txHash1, 1); // Same hash, different index + + const uint256 requestId1 = instantsend::GenInputLockRequestId(outpoint1); + const uint256 requestId2 = instantsend::GenInputLockRequestId(outpoint2); + const uint256 requestId3 = instantsend::GenInputLockRequestId(outpoint3); + + // All should be different + BOOST_CHECK(requestId1 != requestId2); + BOOST_CHECK(requestId1 != requestId3); + BOOST_CHECK(requestId2 != requestId3); +} + +BOOST_AUTO_TEST_CASE(geninputlockrequestid_only_outpoint_matters) +{ + // Critical test: Verify that only the COutPoint is hashed, not scriptSig or nSequence + // This validates the fix where CTxIn was incorrectly used before + const uint256 txHash = uint256S("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"); + const uint32_t outputIndex = 3; + + COutPoint outpoint(txHash, outputIndex); + + // Create two CTxIn objects with the same prevout but different scriptSig and nSequence + CTxIn txin1; + txin1.prevout = outpoint; + txin1.scriptSig = CScript() << OP_1 << OP_2; + txin1.nSequence = 0xFFFFFFFF; + + CTxIn txin2; + txin2.prevout = outpoint; + txin2.scriptSig = CScript() << OP_3 << OP_4 << OP_5; // Different scriptSig + txin2.nSequence = 0x12345678; // Different nSequence + + // The request IDs should be identical because they share the same prevout (COutPoint) + const uint256 requestId1 = instantsend::GenInputLockRequestId(txin1.prevout); + const uint256 requestId2 = instantsend::GenInputLockRequestId(txin2.prevout); + + BOOST_CHECK(requestId1 == requestId2); + + // Also verify against the direct outpoint + const uint256 requestId3 = instantsend::GenInputLockRequestId(outpoint); + BOOST_CHECK(requestId1 == requestId3); +} + +BOOST_AUTO_TEST_CASE(geninputlockrequestid_serialization_format) +{ + // Test that the serialization format is: SerializeHash(pair("inlock", outpoint)) + const uint256 txHash = uint256S("0x0000000000000000000000000000000000000000000000000000000000000001"); + const uint32_t outputIndex = 0; + + COutPoint outpoint(txHash, outputIndex); + + // Calculate the expected hash manually + const uint256 expectedHash = ::SerializeHash(std::make_pair(std::string_view("inlock"), outpoint)); + + // Get the actual hash from the function + const uint256 actualHash = instantsend::GenInputLockRequestId(outpoint); + + BOOST_CHECK(actualHash == expectedHash); +} + +BOOST_AUTO_TEST_CASE(geninputlockrequestid_edge_cases) +{ + // Test edge cases: null hash, max index + COutPoint nullOutpoint(uint256(), 0); + COutPoint maxIndexOutpoint(uint256::ONE, COutPoint::NULL_INDEX); + + const uint256 nullRequestId = instantsend::GenInputLockRequestId(nullOutpoint); + const uint256 maxIndexRequestId = instantsend::GenInputLockRequestId(maxIndexOutpoint); + + // Both should produce valid (non-null) request IDs + BOOST_CHECK(!nullRequestId.IsNull()); + BOOST_CHECK(!maxIndexRequestId.IsNull()); + + // And they should be different from each other + BOOST_CHECK(nullRequestId != maxIndexRequestId); +} + BOOST_AUTO_TEST_SUITE_END()