diff --git a/src/interfaces/node.h b/src/interfaces/node.h index ba85809416be..8a67c6915b1d 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -74,6 +74,11 @@ class MnEntry virtual bool isBanned() const = 0; virtual CService getNetInfoPrimary() const = 0; + //! Platform HTTPS (DAPI gateway) endpoints from the extended address + //! list; empty for non-evo masternodes. Domain-based entries are + //! skipped rather than resolved here — the first entry is always a + //! CService, so an evonode never contributes an empty set. + virtual std::vector getPlatformHTTPSAddrs() const = 0; virtual MnType getType() const = 0; virtual UniValue toJson() const = 0; virtual const CKeyID& getKeyIdOwner() const = 0; @@ -213,6 +218,19 @@ class LLMQ int32_t m_expiry_height{0}; }; virtual std::vector getQuorumStats() = 0; + struct PlatformQuorum { + uint256 m_quorum_hash{}; + std::vector m_pubkey{}; //!< serialized BLS public key (basic scheme) + int32_t m_height{0}; + }; + //! Locally retained quorums of the given LLMQ type with their public + //! keys. Used by the GUI Platform client to verify Platform state-root + //! quorum signatures against locally synced quorum data. This includes + //! retained signing quorums that are no longer in the active signing set. + virtual std::vector getPlatformQuorums(uint8_t llmq_type) = 0; + //! Serialized InstantSend lock for the given txid, or empty if the + //! transaction has no islock (used to build asset lock proofs). + virtual std::vector getInstantSendLock(const uint256& txid) = 0; virtual void setContext(node::NodeContext* context) {} }; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 80dd1e018d46..1c02779e93e1 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -134,6 +135,27 @@ class MnEntryImpl : public MnEntry bool isBanned() const override { return m_dmn->pdmnState->IsBanned(); } CService getNetInfoPrimary() const override { return m_dmn->pdmnState->netInfo->GetPrimary(); } + std::vector getPlatformHTTPSAddrs() const override + { + std::vector ret; + if (m_dmn->pdmnState->nVersion < ProTxVersion::ExtAddr) { + // Before ExtAddr the Platform ports are scalar fields paired with + // the primary address instead of netInfo entries, so an evonode + // that has not submitted an extended-address update would + // otherwise contribute no gateway at all. + if (m_dmn->nType == MnType::Evo && m_dmn->pdmnState->platformHTTPPort != 0) { + ret.emplace_back(m_dmn->pdmnState->netInfo->GetPrimary(), + m_dmn->pdmnState->platformHTTPPort); + } + return ret; + } + for (const auto& entry : m_dmn->pdmnState->netInfo->GetEntries(NetInfoPurpose::PLATFORM_HTTPS)) { + if (const auto service_opt{entry.GetAddrPort()}) { + ret.push_back(*service_opt); + } + } + return ret; + } MnType getType() const override { return m_dmn->nType; } UniValue toJson() const override { return m_dmn->ToJson(); } const CKeyID& getKeyIdOwner() const override { return m_dmn->pdmnState->keyIDOwner; } @@ -539,6 +561,58 @@ class LLMQImpl : public LLMQ } return stats; } + std::vector getPlatformQuorums(uint8_t llmq_type) override + { + std::vector ret; + if (!context().llmq_ctx || !context().llmq_ctx->quorum_block_processor || !context().chainman) { + return ret; + } + const auto* pindex{WITH_LOCK(::cs_main, return context().chainman->ActiveChain().Tip())}; + if (!pindex) { + return ret; + } + const auto type{static_cast(llmq_type)}; + const auto llmq_params{Params().GetLLMQ(type)}; + if (!llmq_params.has_value()) { + return ret; + } + // Drive proofs may be signed by an older Platform quorum while they + // are still consensus-valid and retained locally. Export the full + // retained-key window, not only the current signing-active set. + const auto quorum_count{static_cast(std::max(llmq_params->signingActiveQuorumCount, + llmq_params->keepOldKeys))}; + // Read mined final commitments directly: they already carry the quorum + // hash and public key, so there is no need to materialize full CQuorum + // objects (member lists, vvec/contribution reads, quorum cache inserts) + // via ScanQuorums. Newest-first, matching ScanQuorums' ordering. + const auto& qbp{*context().llmq_ctx->quorum_block_processor}; + const auto quorum_base_block_indexes{llmq_params->useRotation + ? qbp.GetMinedCommitmentsIndexedUntilBlock(type, pindex, quorum_count) + : qbp.GetMinedCommitmentsUntilBlock(type, pindex, quorum_count)}; + for (const auto* pQuorumBaseBlockIndex : quorum_base_block_indexes) { + const auto qc{qbp.GetMinedCommitment(type, pQuorumBaseBlockIndex->GetBlockHash()).first}; + if (!qc.quorumPublicKey.IsValid()) continue; + ret.emplace_back(PlatformQuorum{ + .m_quorum_hash = qc.quorumHash, + .m_pubkey = qc.quorumPublicKey.ToByteVector(/*specificLegacyScheme=*/false), + .m_height = pQuorumBaseBlockIndex->nHeight, + }); + } + return ret; + } + std::vector getInstantSendLock(const uint256& txid) override + { + if (!context().llmq_ctx || !context().llmq_ctx->isman) { + return {}; + } + const auto islock{context().llmq_ctx->isman->GetInstantSendLockByTxid(txid)}; + if (!islock) { + return {}; + } + CDataStream ds(SER_NETWORK, PROTOCOL_VERSION); + ds << *islock; + return {UCharCast(ds.data()), UCharCast(ds.data()) + ds.size()}; + } void setContext(NodeContext* context) override { m_context = context;