Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/interfaces/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<CService> getPlatformHTTPSAddrs() const = 0;
virtual MnType getType() const = 0;
virtual UniValue toJson() const = 0;
virtual const CKeyID& getKeyIdOwner() const = 0;
Expand Down Expand Up @@ -213,6 +218,19 @@ class LLMQ
int32_t m_expiry_height{0};
};
virtual std::vector<QuorumInfo> getQuorumStats() = 0;
struct PlatformQuorum {
uint256 m_quorum_hash{};
std::vector<uint8_t> 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<PlatformQuorum> 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<uint8_t> getInstantSendLock(const uint256& txid) = 0;
virtual void setContext(node::NodeContext* context) {}
};

Expand Down
74 changes: 74 additions & 0 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <interfaces/handler.h>
#include <interfaces/wallet.h>
#include <instantsend/instantsend.h>
#include <llmq/blockprocessor.h>
#include <llmq/commitment.h>
#include <llmq/context.h>
#include <llmq/options.h>
Expand Down Expand Up @@ -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<CService> getPlatformHTTPSAddrs() const override
{
std::vector<CService> 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)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include legacy EvoNode HTTPS endpoints

For EvoNodes whose state version predates ProTxVersion::ExtAddr, the Platform endpoint is stored as platformHTTPPort alongside the primary Core address rather than as a PLATFORM_HTTPS netInfo entry (src/evo/dmnstate.h:112-115, with the conversion spelled out in src/evo/specialtxman.cpp:471-473). This loop therefore returns an empty vector for valid legacy EvoNodes that have not submitted an extended-address update, removing those DAPI gateways from the GUI; synthesize the service from the primary address and legacy HTTP port when netInfo cannot store Platform entries.

AGENTS.md reference: AGENTS.md:L15-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — fixed at the new head.

Confirmed against the code: CDeterministicMNState only serializes platformP2PPort/platformHTTPPort when nVersion < ProTxVersion::ExtAddr (src/evo/dmnstate.h:112-115), and specialtxman.cpp:471-478 zeroes those scalars from ExtAddr onward precisely because netInfo owns them then. So the netInfo-only loop returned an empty vector for every legacy EvoNode — which today is most of them, meaning the GUI would have seen gateways only from nodes that had already submitted an extended-address update.

getPlatformHTTPSAddrs() now branches on the state version: below ExtAddr it synthesizes the gateway from the primary address and the legacy platformHTTPPort (guarded on MnType::Evo and a non-zero port, using the same CService(addr, port) idiom as dmnstate.cpp:138 and rpc/evo_util.h:41); at ExtAddr and above it reads the PLATFORM_HTTPS netInfo entries as before.


🤖 Posted autonomously by Claude on behalf of pasta.

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; }
Expand Down Expand Up @@ -539,6 +561,58 @@ class LLMQImpl : public LLMQ
}
return stats;
}
std::vector<PlatformQuorum> getPlatformQuorums(uint8_t llmq_type) override
{
std::vector<PlatformQuorum> 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<Consensus::LLMQType>(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<size_t>(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<uint8_t> getInstantSendLock(const uint256& txid) override
{
if (!context().llmq_ctx || !context().llmq_ctx->isman) {
return {};
}
const auto islock{context().llmq_ctx->isman->GetInstantSendLockByTxid(txid)};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Read retained islocks even when InstantSend is disabled

When SPORK_2 is disabled, or the node is reindexing/importing, this call returns empty for every txid because CInstantSendManager::GetInstantSendLockByTxid() exits before querying its database (src/instantsend/instantsend.cpp:357-363, with the gate defined at lines 479-482). Consequently, a previously stored islock becomes unavailable to the Platform GUI precisely as it tries to build an asset-lock proof, despite this interface promising emptiness only when the transaction has no islock; use a retained-lock lookup that is independent of whether new InstantSend processing is enabled.

AGENTS.md reference: AGENTS.md:L169-L172

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fine; no action needed imo

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;
Expand Down
Loading