Skip to content

Commit

Permalink
RPC: Add get node status information (#2776)
Browse files Browse the repository at this point in the history
* Dev on getnodestatusinfo rpc

* Add health status check

* Fix typo

* Add health check endpoints API

* Better description

* Default turn on health check APIs

* Clean up comments
  • Loading branch information
sieniven authored Jan 12, 2024
1 parent b84fc58 commit 76eb7e7
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/httprpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,16 @@ void InterruptREST();
*/
void StopREST();

/** Start HTTP Health endpoints subsystem.
* Precondition; HTTP and RPC has been started.
*/
void StartHealthEndpoints();
/** Interrupt HTTP Health endpoints subsystem.
*/
void InterruptHealthEndpoints();
/** Stop HTTP Health endpoints subsystem.
* Precondition; HTTP and RPC has been stopped.
*/
void StopHealthEndpoints();

#endif
6 changes: 6 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
static bool fFeeEstimatesInitialized = false;
static const bool DEFAULT_PROXYRANDOMIZE = true;
static const bool DEFAULT_REST_ENABLE = false;
static const bool DEFAULT_HEALTH_ENDPOINTS_ENABLE = true;
static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;

// Dump addresses to banlist.dat every 15 minutes (900s)
Expand Down Expand Up @@ -185,6 +186,7 @@ void Interrupt()
InterruptHTTPRPC();
InterruptRPC();
InterruptREST();
InterruptHealthEndpoints();
InterruptTorControl();
InterruptMapPort();
if (g_connman)
Expand Down Expand Up @@ -218,6 +220,7 @@ void Shutdown(InitInterfaces& interfaces)

StopHTTPRPC();
StopREST();
StopHealthEndpoints();
StopRPC();
StopHTTPServer();
for (const auto& client : interfaces.chain_clients) {
Expand Down Expand Up @@ -630,6 +633,7 @@ void SetupServerArgs()
gArgs.AddArg("-blockversion=<n>", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION);

gArgs.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-healthendpoints", strprintf("Provide health check endpoints to check for the current status of the node.(default: %u)", DEFAULT_HEALTH_ENDPOINTS_ENABLE), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC);
Expand Down Expand Up @@ -912,6 +916,8 @@ static bool AppInitServers()
if (!StartHTTPRPC())
return false;
if (gArgs.GetBoolArg("-rest", DEFAULT_REST_ENABLE)) StartREST();
if (gArgs.GetBoolArg("-healthendpoints", DEFAULT_HEALTH_ENDPOINTS_ENABLE)) StartHealthEndpoints();

StartHTTPServer();
return true;
}
Expand Down
77 changes: 77 additions & 0 deletions src/rest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <primitives/transaction.h>
#include <rpc/blockchain.h>
#include <rpc/protocol.h>
#include <rpc/request.h>
#include <rpc/server.h>
#include <streams.h>
#include <sync.h>
Expand Down Expand Up @@ -622,6 +623,58 @@ static bool rest_blockhash_by_height(HTTPRequest* req,
}
}

static bool rest_blockchain_liveness(HTTPRequest* req, const std::string&) {
if (!CheckWarmup(req))
return false;

req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, "");
return true;
}

// Hack dependency on function defined in rpc/net.cpp
UniValue getnodestatusinfo(const JSONRPCRequest& request);

static bool rest_blockchain_readiness(HTTPRequest* req, const std::string&) {
if (!CheckWarmup(req))
return false;

try {
JSONRPCRequest jsonRequest{};
UniValue status = getnodestatusinfo(jsonRequest);

if (status["health_status"].get_bool()) {
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, "Health status: Ready - sync-to-tip: true, active-peers: true.");
return true;
} else {
std::string syncToTip = "false";
std::string activePeerNodes = "false";
if (status["sync_to_tip"].get_bool()) {
syncToTip = "true";
}
if (status["active_peer_nodes"].get_bool()) {
activePeerNodes = "true";
}
RESTERR(req, HTTP_SERVICE_UNAVAILABLE, strprintf("Health status: Not ready - sync-to-tip: %s, active-peers: %s.", syncToTip, activePeerNodes));
return false;
}
} catch (const UniValue& objError) {
HTTPStatusCode nStatus = HTTP_INTERNAL_SERVER_ERROR;
std::string msg = "";
int code = find_value(objError, "code").get_int();

if (code == RPC_CLIENT_P2P_DISABLED)
nStatus = HTTP_SERVICE_UNAVAILABLE;
msg = "Error: Peer-to-peer functionality missing or disabled";
RESTERR(req, nStatus, msg);
return false;
} catch (const std::exception& e) {
RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, e.what());
return false;
}
}

static const struct {
const char* prefix;
bool (*handler)(HTTPRequest* req, const std::string& strReq);
Expand All @@ -637,6 +690,14 @@ static const struct {
{"/rest/blockhashbyheight/", rest_blockhash_by_height},
};

static const struct {
const char* prefix;
bool (*handler)(HTTPRequest* req, const std::string& strReq);
} health_uri_prefixes[] = {
{"/livez/", rest_blockchain_liveness},
{"/readyz/", rest_blockchain_readiness},
};

void StartREST()
{
for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++)
Expand All @@ -652,3 +713,19 @@ void StopREST()
for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++)
UnregisterHTTPHandler(uri_prefixes[i].prefix, false);
}

void StartHealthEndpoints()
{
for (unsigned int i = 0; i < ARRAYLEN(health_uri_prefixes); i++)
RegisterHTTPHandler(health_uri_prefixes[i].prefix, false, health_uri_prefixes[i].handler);
}

void InterruptHealthEndpoints()
{
}

void StopHealthEndpoints()
{
for (unsigned int i = 0; i < ARRAYLEN(health_uri_prefixes); i++)
UnregisterHTTPHandler(health_uri_prefixes[i].prefix, false);
}
53 changes: 53 additions & 0 deletions src/rpc/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

#include <univalue.h>

static const int64_t DEFAULT_MINIMUM_ACTIVE_NODE_PEERS = 5;
static const int64_t DEFAULT_ACTIVE_PEER_CONNECTION_TIMEOUT = 300;

static UniValue getconnectioncount(const JSONRPCRequest& request)
{
RPCHelpMan{"getconnectioncount",
Expand Down Expand Up @@ -211,6 +214,54 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
return ret;
}

UniValue getnodestatusinfo(const JSONRPCRequest& request)
{
RPCHelpMan{"getnodestatusinfo",
"\nReturns data about the node status information as a json array of objects.\n",
{},
RPCResult{
"{\n"
" \"health_status\": true|false, (boolean) Health status flag (sync_to_tip && connected_nodes) \n"
" \"sync_to_tip\": true|false, (boolean) Whether node is sync-ed to tip\n"
" \"active_peer_nodes\": true|false, (boolean) Whether node is connected to minimum number of active peer nodes. Minimum of " + std::to_string(DEFAULT_MINIMUM_ACTIVE_NODE_PEERS) + "peer nodes with a lastrecv of less than " + std::to_string(DEFAULT_ACTIVE_PEER_CONNECTION_TIMEOUT) + " seconds\n"
"}\n"
},
RPCExamples{
HelpExampleCli("getnodestatusinfo", "")
+ HelpExampleRpc("getnodestatusinfo", "")
},
}.Check(request);

if(!g_connman)
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");

std::vector<CNodeStats> vstats;
g_connman->GetNodeStats(vstats);

// Check node if it is connected to minimum number of active peer nodes
int active_peers = 0;
int64_t currTime = GetSystemTimeInSeconds();
for (const CNodeStats& stats : vstats) {
if (currTime - stats.nLastRecv <= DEFAULT_ACTIVE_PEER_CONNECTION_TIMEOUT) {
active_peers++;
if (active_peers == DEFAULT_MINIMUM_ACTIVE_NODE_PEERS)
break;
}
}
bool activePeerNodes = active_peers >= DEFAULT_MINIMUM_ACTIVE_NODE_PEERS;

// Check chain tip is at block headers tip
const auto chainHeight = (int)::ChainActive().Height();
const auto headerHeight = pindexBestHeader ? pindexBestHeader->nHeight : -1;
bool syncToTip = chainHeight == headerHeight;

UniValue obj(UniValue::VOBJ);
obj.pushKV("health_status", syncToTip && activePeerNodes);
obj.pushKV("sync_to_tip", syncToTip);
obj.pushKV("active_peer_nodes", activePeerNodes);
return obj;
}

static UniValue addnode(const JSONRPCRequest& request)
{
std::string strCommand;
Expand Down Expand Up @@ -573,6 +624,7 @@ static UniValue getversioninfo(const JSONRPCRequest& request){
nodeInfoObj.pushKV("spv",spvInfoObj);
return nodeInfoObj;
}

static UniValue setban(const JSONRPCRequest& request)
{
const RPCHelpMan help{"setban",
Expand Down Expand Up @@ -786,6 +838,7 @@ static const CRPCCommand commands[] =
{ "network", "getconnectioncount", &getconnectioncount, {} },
{ "network", "ping", &ping, {} },
{ "network", "getpeerinfo", &getpeerinfo, {} },
{ "network", "getnodestatusinfo", &getnodestatusinfo, {} },
{ "network", "addnode", &addnode, {"node","command"} },
{ "network", "disconnectnode", &disconnectnode, {"address", "nodeid"} },
{ "network", "getaddednodeinfo", &getaddednodeinfo, {"node"} },
Expand Down

0 comments on commit 76eb7e7

Please sign in to comment.