Skip to content

Commit

Permalink
Tron collector for Exporter (#63)
Browse files Browse the repository at this point in the history
* Initial Changes to Enable Tron collector for Exporter

* Update text

* Update comments

* Initial tests

* Lint changes
  • Loading branch information
rodgerjohnson authored Sep 5, 2024
1 parent 62b68f9 commit a272dd6
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 1 deletion.
53 changes: 53 additions & 0 deletions src/collectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,56 @@ def client_version(self):
def latency(self):
"""Returns connection latency."""
return self.interface.latest_query_latency

class TronCollector():
"""A collector to fetch information from Tron endpoints."""

def __init__(self, url, labels, chain_id, **client_parameters):

self.labels = labels
self.chain_id = chain_id
self.interface = HttpsInterface(url, client_parameters.get('open_timeout'),
client_parameters.get('ping_timeout'))

self._logger_metadata = {
'component': 'TronCollector',
'url': strip_url(url)
}
self.client_version_payload = {
'jsonrpc': '2.0',
'method': "web3_clientVersion",
'id': 1
}
self.block_height_payload = {
'jsonrpc': '2.0',
'method': "eth_blockNumber",
'id': 1
}

def alive(self):
"""Returns true if endpoint is alive, false if not."""
# Run cached query because we can also fetch client version from this
# later on. This will save us an RPC call per run.
return self.interface.cached_json_rpc_post(
self.client_version_payload) is not None

def block_height(self):
"""Cached query and returns blockheight after converting hex string value to an int"""
result = self.interface.cached_json_rpc_post(self.block_height_payload)

if result and isinstance(result, str) and result.startswith('0x'):
return int(result, 16)
raise ValueError(f"Invalid block height result: {result}")

def client_version(self):
"""Runs a cached query to return client version."""
version = self.interface.cached_json_rpc_post(
self.client_version_payload)
if version is None:
return None
client_version = {"client_version": version}
return client_version

def latency(self):
"""Returns connection latency."""
return self.interface.latest_query_latency
3 changes: 2 additions & 1 deletion src/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def endpoints(self):
def _load_configuration(self):
allowed_providers = self._load_validation_file()
supported_collectors = ('evm', 'cardano', 'conflux', 'solana',
'bitcoin', 'doge', 'filecoin', 'starknet', 'aptos')
'bitcoin', 'doge', 'filecoin', 'starknet', 'aptos',
'tron')

configuration_schema = Schema({
'blockchain':
Expand Down
2 changes: 2 additions & 0 deletions src/registries.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def get_collector_registry(self) -> list:
collector = collectors.StarknetCollector
case "aptos", "aptos":
collector = collectors.AptosCollector
case "tron", "tron":
collector = collectors.TronCollector
case "evm", other: # pylint: disable=unused-variable
collector = collectors.EvmCollector
if collector is None:
Expand Down
77 changes: 77 additions & 0 deletions src/test_collectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,3 +734,80 @@ def test_latency(self):
"""Tests that the latency is obtained from the interface based on latest_query_latency"""
self.mocked_connection.return_value.latest_query_latency = 0.123
self.assertEqual(0.123, self.aptos_collector.latency())

class TestTronCollector(TestCase):
"""Tests the Tron collector class"""

def setUp(self):
self.url = "https://test.com"
self.labels = ["dummy", "labels"]
self.chain_id = 123
self.open_timeout = 8
self.ping_timeout = 9
self.client_params = {
"open_timeout": self.open_timeout, "ping_timeout": self.ping_timeout}
with mock.patch('collectors.HttpsInterface') as mocked_connection:
self.tron_collector = collectors.TronCollector(
self.url, self.labels, self.chain_id, **self.client_params)
self.mocked_connection = mocked_connection

def test_logger_metadata(self):
"""Validate logger metadata. Makes sure url is stripped by helpers.strip_url function."""
expected_metadata = {
'component': 'TronCollector', 'url': 'test.com'}
self.assertEqual(expected_metadata,
self.tron_collector._logger_metadata)

def test_https_interface_created(self):
"""Tests that the Tron collector calls the https interface with the correct args"""
self.mocked_connection.assert_called_once_with(
self.url, self.open_timeout, self.ping_timeout)

def test_interface_attribute_exists(self):
"""Tests that the interface attribute exists."""
self.assertTrue(hasattr(self.tron_collector, 'interface'))

def test_alive_call(self):
"""Tests the alive function uses the correct call"""
self.tron_collector.alive()
self.mocked_connection.return_value.cached_json_rpc_post.assert_called_once_with(
self.tron_collector.client_version_payload)

def test_alive_false(self):
"""Tests the alive function returns false when post returns None"""
self.mocked_connection.return_value.cached_json_rpc_post.return_value = None
result = self.tron_collector.alive()
self.assertFalse(result)

def test_block_height(self):
"""Tests the block_height function uses the correct call to get block height"""
self.mocked_connection.return_value.cached_json_rpc_post.return_value = "0x1a2b3c"
result = self.tron_collector.block_height()
self.mocked_connection.return_value.cached_json_rpc_post.assert_called_once_with(
self.tron_collector.block_height_payload)
self.assertEqual(result, 1715004)

def test_block_height_raises_value_error(self):
"""Tests that the block height raises ValueError if result is invalid"""
self.mocked_connection.return_value.cached_json_rpc_post.return_value = "invalid"
with self.assertRaises(ValueError):
self.tron_collector.block_height()

def test_client_version(self):
"""Tests the client_version function uses the correct call to get client version"""
self.mocked_connection.return_value.cached_json_rpc_post.return_value = "Tron/v1.0.0"
result = self.tron_collector.client_version()
self.mocked_connection.return_value.cached_json_rpc_post.assert_called_once_with(
self.tron_collector.client_version_payload)
self.assertEqual(result, {"client_version": "Tron/v1.0.0"})

def test_client_version_returns_none(self):
"""Tests that the client_version returns None if cached_json_rpc_post returns None"""
self.mocked_connection.return_value.cached_json_rpc_post.return_value = None
result = self.tron_collector.client_version()
self.assertIsNone(result)

def test_latency(self):
"""Tests that the latency is obtained from the interface based on latest_query_latency"""
self.mocked_connection.return_value.latest_query_latency = 0.123
self.assertEqual(0.123, self.tron_collector.latency())
11 changes: 11 additions & 0 deletions src/test_registries.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ def test_get_collector_registry_for_aptos(self):
with mock.patch('collectors.AptosCollector', new=mock.Mock()) as collector:
helper_test_collector_registry(self, collector)

@mock.patch.dict(os.environ, {
"CONFIG_FILE_PATH": "tests/fixtures/configuration_tron.yaml",
"VALIDATION_FILE_PATH": "tests/fixtures/validation.yaml"
})
def test_get_collector_registry_for_tron(self):
"""Tests that the Tron collector is called with the correct args"""
self.collector_registry = CollectorRegistry()
with mock.patch('collectors.TronCollector', new=mock.Mock()) as collector:
helper_test_collector_registry(self, collector)


@mock.patch.dict(os.environ, {
"CONFIG_FILE_PATH": "tests/fixtures/configuration_evm.yaml",
"VALIDATION_FILE_PATH": "tests/fixtures/validation.yaml"
Expand Down
12 changes: 12 additions & 0 deletions src/tests/fixtures/configuration_tron.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
blockchain: "Tron"
chain_id: 1234
network_name: "Testnet"
network_type: "Testnet"
collector: "tron"
endpoints:
- url: https://test1.com
provider: TestProvider1
- url: https://test2.com
provider: TestProvider2
- url: https://test3.com
provider: TestProvider3

0 comments on commit a272dd6

Please sign in to comment.