Skip to content

Commit 943deb2

Browse files
committed
add support for TON collectors
1 parent 1eacd87 commit 943deb2

File tree

4 files changed

+156
-1
lines changed

4 files changed

+156
-1
lines changed

src/collectors.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,3 +548,58 @@ def client_version(self):
548548
def latency(self):
549549
"""Returns connection latency."""
550550
return self.interface.latest_query_latency
551+
552+
class TonCollector():
553+
"""A collector to fetch information about Ton endpoints."""
554+
555+
def __init__(self, url, labels, chain_id, **client_parameters):
556+
557+
self.labels = labels
558+
self.chain_id = chain_id
559+
self.interface = HttpsInterface(url.rstrip("/") + "/jsonRPC", client_parameters.get('open_timeout'),
560+
client_parameters.get('ping_timeout'))
561+
self._logger_metadata = {
562+
'component': 'TonCollector',
563+
'url': strip_url(url)
564+
}
565+
self.block_height_payload = {
566+
'jsonrpc': '2.0',
567+
'method': "getMasterchainInfo",
568+
'id': 1
569+
}
570+
self.consensus_block_height_payload = {
571+
'jsonrpc': '2.0',
572+
'method': "getConsensusBlock",
573+
'id': 1
574+
}
575+
576+
def alive(self):
577+
"""Returns true if endpoint is alive, false if not."""
578+
# Run cached query because we can also fetch block height from this
579+
# later on. This will save us an RPC call per run.
580+
return self.interface.cached_json_rpc_post(
581+
self.block_height_payload) is not None
582+
583+
def block_height(self):
584+
"""Returns latest block height."""
585+
result = self.interface.cached_json_rpc_post(self.block_height_payload)
586+
if result is None:
587+
raise ValueError("No response received from TON endpoint")
588+
block_height = result.get('last', {}).get('seqno', None)
589+
if block_height is not None:
590+
return block_height
591+
raise ValueError(f"Invalid block height result: {result}")
592+
593+
def finalized_block_height(self):
594+
"""Runs a query to return consensus block height"""
595+
result = self.interface.json_rpc_post(self.consensus_block_height_payload)
596+
if result is None:
597+
raise ValueError("No response received from TON endpoint")
598+
consensus_block = result.get('consensus_block', None)
599+
if consensus_block is not None:
600+
return consensus_block
601+
raise ValueError(f"Invalid consensus block height result: {result}")
602+
603+
def latency(self):
604+
"""Returns connection latency."""
605+
return self.interface.latest_query_latency

src/configuration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def endpoints(self):
4545
def _load_configuration(self):
4646
supported_collectors = ('evm', 'evmhttp', 'cardano', 'conflux', 'solana',
4747
'bitcoin', 'doge', 'filecoin', 'starknet', 'aptos',
48-
'tron', 'xrpl')
48+
'tron', 'xrpl', 'ton')
4949

5050
configuration_schema = Schema({
5151
'blockchain':

src/registries.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ def get_collector_registry(self) -> list:
9090
collector = collectors.AptosCollector
9191
case "xrpl", "xrpl":
9292
collector = collectors.XRPLCollector
93+
case "ton", "ton":
94+
collector = collectors.TonCollector
9395
case "evmhttp", other: # pylint: disable=unused-variable
9496
collector = collectors.EvmHttpCollector
9597
case "evm", other: # pylint: disable=unused-variable

src/test_collectors.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,3 +961,101 @@ def test_latency(self):
961961
"""Tests that the latency is obtained from the interface based on latest_query_latency"""
962962
self.mocked_connection.return_value.latest_query_latency = 0.123
963963
self.assertEqual(0.123, self.xrpl_collector.latency())
964+
965+
966+
class TestTonCollector(TestCase):
967+
"""Tests the TON collector class"""
968+
969+
def setUp(self):
970+
self.url = "https://test.com"
971+
self.labels = ["dummy", "labels"]
972+
self.chain_id = 123
973+
self.open_timeout = 8
974+
self.ping_timeout = 9
975+
self.client_params = {
976+
"open_timeout": self.open_timeout, "ping_timeout": self.ping_timeout}
977+
self.block_height_payload = {
978+
'jsonrpc': '2.0',
979+
'method': "getMasterchainInfo",
980+
'id': 1
981+
}
982+
self.consensus_block_height_payload = {
983+
'jsonrpc': '2.0',
984+
'method': "getConsensusBlock",
985+
'id': 1
986+
}
987+
with mock.patch('collectors.HttpsInterface') as mocked_connection:
988+
self.ton_collector = collectors.TonCollector(
989+
self.url, self.labels, self.chain_id, **self.client_params)
990+
self.mocked_connection = mocked_connection
991+
992+
def test_logger_metadata(self):
993+
"""Validate logger metadata. Makes sure url is stripped by helpers.strip_url function."""
994+
expected_metadata = {
995+
'component': 'TonCollector', 'url': 'test.com'}
996+
self.assertEqual(expected_metadata,
997+
self.ton_collector._logger_metadata)
998+
999+
def test_connection_created(self):
1000+
"""Tests that the ton collector calls the https interface with the correct args"""
1001+
self.mocked_connection.assert_called_once_with(
1002+
self.url + "/jsonRPC", self.open_timeout, self.ping_timeout)
1003+
1004+
def test_interface_attribute_exists(self):
1005+
"""Tests that the interface attribute exists.
1006+
May be used by external calls to access objects such as the interface cache"""
1007+
self.assertTrue(hasattr(self.ton_collector, 'interface'))
1008+
1009+
def test_alive_true(self):
1010+
"""Tests the alive function returns true when json_rpc_post returns valid result"""
1011+
self.mocked_connection.return_value.cached_json_rpc_post.return_value = {"last": {"seqno": 123}}
1012+
self.assertTrue(self.ton_collector.alive())
1013+
1014+
def test_alive_false(self):
1015+
"""Tests the alive function returns false when json_rpc_post returns None"""
1016+
self.mocked_connection.return_value.cached_json_rpc_post.return_value = None
1017+
self.assertFalse(self.ton_collector.alive())
1018+
1019+
def test_block_height_success(self):
1020+
"""Tests block_height method returns correct value"""
1021+
expected_height = 12345
1022+
self.mocked_connection.return_value.cached_json_rpc_post.return_value = {
1023+
"last": {"seqno": expected_height}}
1024+
result = self.ton_collector.block_height()
1025+
self.assertEqual(expected_height, result)
1026+
1027+
def test_block_height_none_response(self):
1028+
"""Tests block_height method raises ValueError when response is None"""
1029+
self.mocked_connection.return_value.cached_json_rpc_post.return_value = None
1030+
with self.assertRaises(ValueError) as context:
1031+
self.ton_collector.block_height()
1032+
self.assertIn("No response received from TON endpoint", str(context.exception))
1033+
1034+
def test_block_height_invalid_response(self):
1035+
"""Tests block_height method raises ValueError when response is invalid"""
1036+
self.mocked_connection.return_value.cached_json_rpc_post.return_value = {"invalid": "data"}
1037+
with self.assertRaises(ValueError) as context:
1038+
self.ton_collector.block_height()
1039+
self.assertIn("Invalid block height result", str(context.exception))
1040+
1041+
def test_finalized_block_height_success(self):
1042+
"""Tests finalized_block_height method returns correct value"""
1043+
expected_height = 12345
1044+
self.mocked_connection.return_value.cached_json_rpc_post.return_value = {
1045+
"consensus_block": expected_height}
1046+
result = self.ton_collector.finalized_block_height()
1047+
self.assertEqual(expected_height, result)
1048+
1049+
def test_finalized_block_height_none_response(self):
1050+
"""Tests finalized_block_height method raises ValueError when response is None"""
1051+
self.mocked_connection.return_value.cached_json_rpc_post.return_value = None
1052+
with self.assertRaises(ValueError) as context:
1053+
self.ton_collector.finalized_block_height()
1054+
self.assertIn("No response received from TON endpoint", str(context.exception))
1055+
1056+
def test_latency(self):
1057+
"""Tests latency method returns interface latency"""
1058+
expected_latency = 0.123
1059+
self.mocked_connection.return_value.latest_query_latency = expected_latency
1060+
result = self.ton_collector.latency()
1061+
self.assertEqual(expected_latency, result)

0 commit comments

Comments
 (0)