mirror of
https://github.com/logos-co/logos-verified-proxy-module.git
synced 2026-08-27 13:01:09 +00:00
Three fields — head.blockNumber, head.updatedAt and heartbeatFailures — were read by statusSnapshot() and never assigned, and State::Degraded appeared only in stateName(). The heartbeat was fire-and-forget with a comment pointing at a pollHeartbeat() that does not exist, so nothing ever observed its outcome: status().head stayed "" for the life of the process and a proxy whose sync had died still reported "running". CallSlot now carries a Kind, so the trampoline can tell a user call from a heartbeat or a head probe. Three consecutive heartbeat failures degrade the proxy and one success clears it; head is refreshed by a separate eth_blockNumber probe every fifth beat, since eth_syncing answers a hardcoded `false` and cannot report it. live() joins Running and Degraded for callers making lifecycle decisions, leaving running() strict for health. fetchFinalizedRoot() is new, and lives here rather than in the panel because Basecamp sandboxes ui_qml plugins away from the network: an XMLHttpRequest from a view is refused outright. It is a convenience, not a trust anchor, and says so. Adds libcurl, used for that one request and nothing else. Tests spin on the condition rather than sleeping a fixed interval — the first draft was green on an idle machine and red under a parallel build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
2.7 KiB
C++
74 lines
2.7 KiB
C++
// URL construction and response parsing for the finalized-root helper.
|
|
//
|
|
// Both halves are pure, so the whole shape of the request and the whole shape
|
|
// of what we accept back are testable without a beacon node or a socket.
|
|
|
|
#include <string>
|
|
|
|
#include <logos_test.h>
|
|
|
|
#include "beacon_client.h"
|
|
|
|
namespace bc = beacon_client;
|
|
|
|
LOGOS_TEST(beacon_url_appends_the_finalized_headers_path) {
|
|
LOGOS_ASSERT_EQ(bc::finalizedHeaderUrl("https://example.org"),
|
|
"https://example.org/eth/v1/beacon/headers/finalized");
|
|
}
|
|
|
|
LOGOS_TEST(beacon_url_tolerates_trailing_slashes) {
|
|
// A URL pasted from a browser very often carries one.
|
|
LOGOS_ASSERT_EQ(bc::finalizedHeaderUrl("https://example.org///"),
|
|
"https://example.org/eth/v1/beacon/headers/finalized");
|
|
}
|
|
|
|
LOGOS_TEST(beacon_url_keeps_a_path_prefix) {
|
|
LOGOS_ASSERT_EQ(bc::finalizedHeaderUrl("https://example.org/beacon"),
|
|
"https://example.org/beacon/eth/v1/beacon/headers/finalized");
|
|
}
|
|
|
|
LOGOS_TEST(beacon_accepts_only_http_schemes) {
|
|
// ws:// and wss:// are valid beacon transports for the library itself, but
|
|
// cannot serve the REST GET this helper makes.
|
|
LOGOS_ASSERT_TRUE(bc::isHttpUrl("http://example.org"));
|
|
LOGOS_ASSERT_TRUE(bc::isHttpUrl("https://example.org"));
|
|
LOGOS_ASSERT_FALSE(bc::isHttpUrl("wss://example.org"));
|
|
LOGOS_ASSERT_FALSE(bc::isHttpUrl("ws://example.org"));
|
|
LOGOS_ASSERT_FALSE(bc::isHttpUrl("file:///etc/passwd"));
|
|
LOGOS_ASSERT_FALSE(bc::isHttpUrl(""));
|
|
}
|
|
|
|
LOGOS_TEST(beacon_trims_surrounding_whitespace) {
|
|
LOGOS_ASSERT_EQ(bc::trim(" https://example.org \n"), "https://example.org");
|
|
LOGOS_ASSERT_EQ(bc::trim(" "), "");
|
|
}
|
|
|
|
LOGOS_TEST(beacon_parses_root_and_slot) {
|
|
const std::string body = R"({
|
|
"data": {
|
|
"root": "0xabc",
|
|
"header": { "message": { "slot": "12345" } }
|
|
}
|
|
})";
|
|
std::string slot;
|
|
LOGOS_ASSERT_EQ(bc::parseFinalizedRoot(body, slot), "0xabc");
|
|
LOGOS_ASSERT_EQ(slot, "12345");
|
|
}
|
|
|
|
LOGOS_TEST(beacon_parse_tolerates_a_missing_slot) {
|
|
// The slot is informational; only the root is load-bearing.
|
|
std::string slot = "stale";
|
|
LOGOS_ASSERT_EQ(bc::parseFinalizedRoot(R"({"data":{"root":"0xdef"}})", slot), "0xdef");
|
|
LOGOS_ASSERT_EQ(slot, "");
|
|
}
|
|
|
|
LOGOS_TEST(beacon_parse_rejects_malformed_payloads) {
|
|
std::string slot;
|
|
LOGOS_ASSERT_EQ(bc::parseFinalizedRoot("not json", slot), "");
|
|
LOGOS_ASSERT_EQ(bc::parseFinalizedRoot("[]", slot), "");
|
|
LOGOS_ASSERT_EQ(bc::parseFinalizedRoot("{}", slot), "");
|
|
LOGOS_ASSERT_EQ(bc::parseFinalizedRoot(R"({"data":{}})", slot), "");
|
|
// A non-string root is the shape an error page could plausibly take.
|
|
LOGOS_ASSERT_EQ(bc::parseFinalizedRoot(R"({"data":{"root":42}})", slot), "");
|
|
}
|