Client.ping() / Client.pingAll() - Test specification
Description:
This test specification covers the node-health probe issued by Client.ping() and Client.pingAll(). Consensus node release 0.77 removes CryptoService/cryptoGetBalance, which the SDKs historically used as the ping probe. Per Stage 1 of the AccountBalanceQuery deprecation proposal, every SDK replaces the probe with CryptoService/getAccountInfo for account 0.0.2 using ResponseType = COST_ANSWER. These tests verify the probe switched and that existing ping behaviour (success reporting, node backoff bookkeeping) is preserved.
These tests must pass before consensus node release 0.77 reaches testnet. The Stage 2 deprecation tests for AccountBalanceQuery itself must not run until these pass.
Out of scope: Solo readiness checks (test 5 of the proposal's Stage 1 test plan) are a Solo-level integration concern, not a TCK test.
Design:
ping and pingAll are exposed over the TCK's JSON-RPC interface as two new methods that map directly onto the SDK's Client.ping(nodeAccountId) and Client.pingAll(). Their JSON-RPC responses only report success or failure of the probe.
Response contract
A probe that completes returns a result object with status: "SUCCESS" and an informational message. SUCCESS is the only value status ever carries: a failed probe is never reported as a result with a failure status.
A probe that fails returns no result at all. The SDK server responds with a JSON-RPC error object, following the existing TCK convention in Error Codes:
- The probe never reached the network (node not in the client's network map, node unreachable, transport error):
code = -32603(INTERNAL_ERROR),message = "Internal error". - The probe reached the node and was rejected at precheck:
code = -32001(Hiero error), with the response code indata.status.
The test driver asserts on the error object, never on a status field, for every failure case below.
pingAll is all-or-nothing, matching the SDKs' sequential ping-per-node implementation: it probes each node of the current network map in turn and returns SUCCESS only if every probe succeeded. The first failing node aborts the call with the error object described above — there is no partial result, no per-node breakdown, and no guarantee that nodes after the failing one were probed. Per-node evidence comes from the proxy capture, not from the JSON-RPC result.
Proxy contract
The critical assertion — that the probe issues CryptoService/getAccountInfo and no longer CryptoService/cryptoGetBalance — cannot be made from the JSON-RPC response alone. Rather than requiring every SDK server to expose introspection of its outbound queries, the test driver runs a gRPC-aware proxy in front of the consensus node and points the SDK server at it during setup (via the existing nodeIp parameter). The proxy records the gRPC method path (the HTTP/2 :path pseudo-header, e.g. /proto.CryptoService/getAccountInfo) of every forwarded call, and the driver decodes the captured request frames with the Hedera protobufs to assert the probe targets account 0.0.2 with ResponseType = COST_ANSWER. This keeps the per-SDK surface at exactly the two JSON-RPC methods below.
The tests depend on the proxy in three concrete ways:
- Routing and node identity. The driver starts one listener per consensus node in the client's network map, each with a fixed upstream node, and passes the listener's address as
nodeIpinsetup.nodeAccountIdkeeps the real node's account ID, so the SDK's network map is unchanged apart from the endpoint. A capture is attributed to a node identity by the listener that received it, so the driver never has to infer the target node from the frame. The currentsetupcontract carries a singlenodeIp/nodeAccountIdpair, so today the network map holds one node and the proxy one listener; per-listener attribution is what letspingAllcorrelate correctly ifsetuplater accepts several. - Capture scoping. Each capture records the receiving listener, the gRPC method path, and the raw request frame. The driver clears the capture buffer immediately before the JSON-RPC call under test and after
setuphas returned, so setup traffic and any earlier test's in-flight retries cannot contaminate the assertions. Assertions run only after the JSON-RPC response has returned, at which point every probe the call made has already been forwarded. - Fault injection. "Blocked at the proxy" means that node's listener refuses connections instead of forwarding them, which is how test 4 makes a reachable node unreachable without touching the consensus node.
For pingAll, the assertion is one probe capture per node identity in the network map: every listener must show a probe, and no probe may be attributed to a node outside the map. Retries of the same probe on one listener count once.
Deprecation proposal (Stage 1 test plan):
CryptoGetInfo protobufs:
https://github.com/hashgraph/hedera-protobufs/blob/main/services/crypto_get_info.proto
Response codes:
https://github.com/hashgraph/hedera-protobufs/blob/main/services/response_code.proto
JSON-RPC API Endpoint Documentation
Method Name
ping
Input Parameters
| Parameter Name | Type | Required/Optional | Description/Notes |
|---|---|---|---|
| nodeAccountId | string | required | The account ID of the node to ping. |
Output Parameters
| Parameter Name | Type | Description/Notes |
|---|---|---|
| message | string | Informational message about the execution of the method |
| status | string | Always SUCCESS. A failed probe returns a JSON-RPC error object instead of a result (see Response contract) |
JSON Request/Response Examples
A probe that succeeds
{
"jsonrpc": "2.0",
"id": 8451,
"method": "ping",
"params": {
"nodeAccountId": "0.0.3"
}
}
{
"jsonrpc": "2.0",
"id": 8451,
"result": {
"message": "Successfully pinged node 0.0.3.",
"status": "SUCCESS"
}
}
A probe that never reached the network
{
"jsonrpc": "2.0",
"id": 8452,
"error": {
"code": -32603,
"message": "Internal error",
"data": {
"message": "Node account ID 1000000.0.0 is not in the client's network map"
}
}
}
Method Name
pingAll
Input Parameters
None. Pings every node in the client's current network map.
Output Parameters
| Parameter Name | Type | Description/Notes |
|---|---|---|
| message | string | Informational message about the execution of the method |
| status | string | Always SUCCESS, and only when every node's probe succeeded. The first failing node returns a JSON-RPC error object with no partial result |
JSON Request/Response Examples
Every node in the network map probed successfully
{
"jsonrpc": "2.0",
"id": 8453,
"method": "pingAll"
}
{
"jsonrpc": "2.0",
"id": 8453,
"result": {
"message": "Successfully pinged all nodes.",
"status": "SUCCESS"
}
}
Function Tests
Ping:
- Probes a single node's health
| Test no | Name | Input | Expected response | Implemented (Y/N) |
|---|---|---|---|---|
| 1 | Ping a reachable node | nodeAccountId=<VALID_NODE_ACCOUNT_ID> | The ping succeeds and returns status SUCCESS | N |
| 2 | Ping sends the COST_ANSWER getAccountInfo probe | nodeAccountId=<VALID_NODE_ACCOUNT_ID> | The proxy capture shows a CryptoService/getAccountInfo query for account 0.0.2 with ResponseType = COST_ANSWER, and no CryptoService/cryptoGetBalance query | N |
| 3 | Ping a node that isn't in the network map | nodeAccountId=1000000.0.0 | The ping fails with a JSON-RPC error response of code -32603 (INTERNAL_ERROR) and returns no result | N |
| 4 | Ping an unreachable node | nodeAccountId=<VALID_NODE_ACCOUNT_ID> with the node's endpoint blocked at the proxy | The ping fails with a JSON-RPC error response of code -32603 (INTERNAL_ERROR) and returns no result | N |
| 5 | Successful ping resets the node's backoff | nodeAccountId=<VALID_NODE_ACCOUNT_ID> from test 4, endpoint unblocked, then an unpinned getAccountInfo query | The ping succeeds, and the proxy capture shows the follow-up query forwarded to the recovered node without waiting out the backoff window test 4 opened | N |
Test 5's follow-up query must not be pinned to the recovered node. A pinned query bypasses the SDK's node selector entirely, so it would execute against that node — and the test would pass — even if Client.ping() had left the backoff untouched. Submitting a plain getAccountInfo (the JSON-RPC method exposes no node pinning) forces the selector to make the choice, and the proxy capture shows which node it picked and when. The reset is what makes the recovered node eligible immediately instead of after its remaining backoff; on a multi-node network map the same capture also proves the selector picked the recovered node rather than a healthy sibling.
PingAll:
- Probes every node in the client's network map
| Test no | Name | Input | Expected response | Implemented (Y/N) |
|---|---|---|---|---|
| 1 | Ping all nodes | The call returns status SUCCESS, and the proxy capture shows exactly one CryptoService/getAccountInfo COST_ANSWER probe of account 0.0.2 per node identity in the network map, and no CryptoService/cryptoGetBalance | N |