doublezero-telemetry 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- doublezero_telemetry-0.0.2.dist-info/METADATA +9 -0
- doublezero_telemetry-0.0.2.dist-info/RECORD +12 -0
- doublezero_telemetry-0.0.2.dist-info/WHEEL +4 -0
- telemetry/__init__.py +0 -0
- telemetry/client.py +92 -0
- telemetry/config.py +15 -0
- telemetry/pda.py +55 -0
- telemetry/rpc.py +48 -0
- telemetry/state.py +141 -0
- telemetry/tests/__init__.py +0 -0
- telemetry/tests/test_fixtures.py +77 -0
- telemetry/tests/test_pda.py +38 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
telemetry/client.py,sha256=gImIz7ozpXb87wfYMQrKVplakQ1CSLYvNKZXgAYYf9o,2728
|
|
3
|
+
telemetry/config.py,sha256=Vhn6wJjhEsODjGOGXll_ypZQstwHqYdb-9huG6pWs0Y,685
|
|
4
|
+
telemetry/pda.py,sha256=vlentmvAFyvRLYbS65pGxY7oqSCkbHjn8hrwjVaIK28,1416
|
|
5
|
+
telemetry/rpc.py,sha256=G7GPRl0DM3x3p6B3NTdIgjDJTljwCat9-C_XKL5wXwM,1548
|
|
6
|
+
telemetry/state.py,sha256=J0dHUKLG6-AZTnRrEjRRuGm3ACX9tPFoB8POrjsKQFM,4603
|
|
7
|
+
telemetry/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
telemetry/tests/test_fixtures.py,sha256=2cDoa5pGcKziDR-eNhrtJ-XCuRMexvnYiv402De-ClU,2985
|
|
9
|
+
telemetry/tests/test_pda.py,sha256=zFzEWbw_UVEppL4W75Lquf3t_4Sob3cbXo0l2Ow3hvM,1340
|
|
10
|
+
doublezero_telemetry-0.0.2.dist-info/METADATA,sha256=6kIU1xJN0nPwcN4oL0MbBuKZuHPFZUl-nntJSN2yCig,239
|
|
11
|
+
doublezero_telemetry-0.0.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
+
doublezero_telemetry-0.0.2.dist-info/RECORD,,
|
telemetry/__init__.py
ADDED
|
File without changes
|
telemetry/client.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""RPC client for fetching telemetry program accounts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
|
|
7
|
+
from solders.pubkey import Pubkey # type: ignore[import-untyped]
|
|
8
|
+
from solders.rpc.responses import GetAccountInfoResp # type: ignore[import-untyped]
|
|
9
|
+
|
|
10
|
+
from telemetry.config import PROGRAM_IDS, LEDGER_RPC_URLS
|
|
11
|
+
from telemetry.rpc import new_rpc_client
|
|
12
|
+
from telemetry.pda import (
|
|
13
|
+
derive_device_latency_samples_pda,
|
|
14
|
+
derive_internet_latency_samples_pda,
|
|
15
|
+
)
|
|
16
|
+
from telemetry.state import DeviceLatencySamples, InternetLatencySamples
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SolanaClient(Protocol):
|
|
20
|
+
def get_account_info(self, pubkey: Pubkey) -> GetAccountInfoResp: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Client:
|
|
24
|
+
"""Read-only client for telemetry program accounts."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
solana_rpc: SolanaClient,
|
|
29
|
+
program_id: Pubkey,
|
|
30
|
+
) -> None:
|
|
31
|
+
self._solana_rpc = solana_rpc
|
|
32
|
+
self._program_id = program_id
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_env(cls, env: str) -> Client:
|
|
36
|
+
"""Create a client configured for the given environment.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
env: Environment name ("mainnet-beta", "testnet", "devnet", "localnet")
|
|
40
|
+
"""
|
|
41
|
+
return cls(
|
|
42
|
+
new_rpc_client(LEDGER_RPC_URLS[env]),
|
|
43
|
+
Pubkey.from_string(PROGRAM_IDS[env]),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def mainnet_beta(cls) -> Client:
|
|
48
|
+
return cls.from_env("mainnet-beta")
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def testnet(cls) -> Client:
|
|
52
|
+
return cls.from_env("testnet")
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def devnet(cls) -> Client:
|
|
56
|
+
return cls.from_env("devnet")
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def localnet(cls) -> Client:
|
|
60
|
+
return cls.from_env("localnet")
|
|
61
|
+
|
|
62
|
+
def get_device_latency_samples(
|
|
63
|
+
self,
|
|
64
|
+
origin_device_pk: Pubkey,
|
|
65
|
+
target_device_pk: Pubkey,
|
|
66
|
+
link_pk: Pubkey,
|
|
67
|
+
epoch: int,
|
|
68
|
+
) -> DeviceLatencySamples:
|
|
69
|
+
addr, _ = derive_device_latency_samples_pda(
|
|
70
|
+
self._program_id, origin_device_pk, target_device_pk, link_pk, epoch
|
|
71
|
+
)
|
|
72
|
+
resp = self._solana_rpc.get_account_info(addr)
|
|
73
|
+
return DeviceLatencySamples.from_bytes(resp.value.data)
|
|
74
|
+
|
|
75
|
+
def get_internet_latency_samples(
|
|
76
|
+
self,
|
|
77
|
+
collector_oracle_pk: Pubkey,
|
|
78
|
+
data_provider_name: str,
|
|
79
|
+
origin_location_pk: Pubkey,
|
|
80
|
+
target_location_pk: Pubkey,
|
|
81
|
+
epoch: int,
|
|
82
|
+
) -> InternetLatencySamples:
|
|
83
|
+
addr, _ = derive_internet_latency_samples_pda(
|
|
84
|
+
self._program_id,
|
|
85
|
+
collector_oracle_pk,
|
|
86
|
+
data_provider_name,
|
|
87
|
+
origin_location_pk,
|
|
88
|
+
target_location_pk,
|
|
89
|
+
epoch,
|
|
90
|
+
)
|
|
91
|
+
resp = self._solana_rpc.get_account_info(addr)
|
|
92
|
+
return InternetLatencySamples.from_bytes(resp.value.data)
|
telemetry/config.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Network configuration for the telemetry program."""
|
|
2
|
+
|
|
3
|
+
PROGRAM_IDS = {
|
|
4
|
+
"mainnet-beta": "tE1exJ5VMyoC9ByZeSmgtNzJCFF74G9JAv338sJiqkC",
|
|
5
|
+
"testnet": "3KogTMmVxc5eUHtjZnwm136H5P8tvPwVu4ufbGPvM7p1",
|
|
6
|
+
"devnet": "C9xqH76NSm11pBS6maNnY163tWHT8Govww47uyEmSnoG",
|
|
7
|
+
"localnet": "C9xqH76NSm11pBS6maNnY163tWHT8Govww47uyEmSnoG",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
LEDGER_RPC_URLS = {
|
|
11
|
+
"mainnet-beta": "https://doublezero-mainnet-beta.rpcpool.com/db336024-e7a8-46b1-80e5-352dd77060ab",
|
|
12
|
+
"testnet": "https://doublezerolocalnet.rpcpool.com/8a4fd3f4-0977-449f-88c7-63d4b0f10f16",
|
|
13
|
+
"devnet": "https://doublezerolocalnet.rpcpool.com/8a4fd3f4-0977-449f-88c7-63d4b0f10f16",
|
|
14
|
+
"localnet": "http://localhost:8899",
|
|
15
|
+
}
|
telemetry/pda.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""PDA derivation for telemetry program accounts."""
|
|
2
|
+
|
|
3
|
+
import struct
|
|
4
|
+
|
|
5
|
+
from solders.pubkey import Pubkey # type: ignore[import-untyped]
|
|
6
|
+
|
|
7
|
+
from telemetry.state import (
|
|
8
|
+
TELEMETRY_SEED_PREFIX,
|
|
9
|
+
DEVICE_LATENCY_SAMPLES_SEED,
|
|
10
|
+
INTERNET_LATENCY_SAMPLES_SEED,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def derive_device_latency_samples_pda(
|
|
15
|
+
program_id: Pubkey,
|
|
16
|
+
origin_device_pk: Pubkey,
|
|
17
|
+
target_device_pk: Pubkey,
|
|
18
|
+
link_pk: Pubkey,
|
|
19
|
+
epoch: int,
|
|
20
|
+
) -> tuple[Pubkey, int]:
|
|
21
|
+
epoch_bytes = struct.pack("<Q", epoch)
|
|
22
|
+
return Pubkey.find_program_address(
|
|
23
|
+
[
|
|
24
|
+
TELEMETRY_SEED_PREFIX,
|
|
25
|
+
DEVICE_LATENCY_SAMPLES_SEED,
|
|
26
|
+
bytes(origin_device_pk),
|
|
27
|
+
bytes(target_device_pk),
|
|
28
|
+
bytes(link_pk),
|
|
29
|
+
epoch_bytes,
|
|
30
|
+
],
|
|
31
|
+
program_id,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def derive_internet_latency_samples_pda(
|
|
36
|
+
program_id: Pubkey,
|
|
37
|
+
collector_oracle_pk: Pubkey,
|
|
38
|
+
data_provider_name: str,
|
|
39
|
+
origin_location_pk: Pubkey,
|
|
40
|
+
target_location_pk: Pubkey,
|
|
41
|
+
epoch: int,
|
|
42
|
+
) -> tuple[Pubkey, int]:
|
|
43
|
+
epoch_bytes = struct.pack("<Q", epoch)
|
|
44
|
+
return Pubkey.find_program_address(
|
|
45
|
+
[
|
|
46
|
+
TELEMETRY_SEED_PREFIX,
|
|
47
|
+
INTERNET_LATENCY_SAMPLES_SEED,
|
|
48
|
+
bytes(collector_oracle_pk),
|
|
49
|
+
data_provider_name.encode("utf-8"),
|
|
50
|
+
bytes(origin_location_pk),
|
|
51
|
+
bytes(target_location_pk),
|
|
52
|
+
epoch_bytes,
|
|
53
|
+
],
|
|
54
|
+
program_id,
|
|
55
|
+
)
|
telemetry/rpc.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""RPC client helpers with retry on rate limiting."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from solana.rpc.api import Client as SolanaHTTPClient # type: ignore[import-untyped]
|
|
7
|
+
|
|
8
|
+
_DEFAULT_MAX_RETRIES = 5
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _RetryTransport(httpx.BaseTransport):
|
|
12
|
+
"""HTTP transport that retries on 429 Too Many Requests."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
wrapped: httpx.BaseTransport | None = None,
|
|
17
|
+
max_retries: int = _DEFAULT_MAX_RETRIES,
|
|
18
|
+
) -> None:
|
|
19
|
+
self._wrapped = wrapped or httpx.HTTPTransport()
|
|
20
|
+
self._max_retries = max_retries
|
|
21
|
+
|
|
22
|
+
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
|
23
|
+
for attempt in range(self._max_retries + 1):
|
|
24
|
+
response = self._wrapped.handle_request(request)
|
|
25
|
+
if response.status_code != 429 or attempt >= self._max_retries:
|
|
26
|
+
return response
|
|
27
|
+
response.close()
|
|
28
|
+
time.sleep((attempt + 1) * 2)
|
|
29
|
+
return response # unreachable, but satisfies type checker
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def new_rpc_client(
|
|
33
|
+
url: str,
|
|
34
|
+
timeout: float = 30,
|
|
35
|
+
max_retries: int = _DEFAULT_MAX_RETRIES,
|
|
36
|
+
) -> SolanaHTTPClient:
|
|
37
|
+
"""Create a Solana RPC client with automatic retry on 429 responses."""
|
|
38
|
+
client = SolanaHTTPClient(url, timeout=timeout)
|
|
39
|
+
# Replace the underlying httpx session with one using retry transport.
|
|
40
|
+
transport = _RetryTransport(
|
|
41
|
+
wrapped=httpx.HTTPTransport(),
|
|
42
|
+
max_retries=max_retries,
|
|
43
|
+
)
|
|
44
|
+
client._provider.session = httpx.Client(
|
|
45
|
+
timeout=timeout,
|
|
46
|
+
transport=transport,
|
|
47
|
+
)
|
|
48
|
+
return client
|
telemetry/state.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""On-chain account data structures for the telemetry program.
|
|
2
|
+
|
|
3
|
+
Binary layout: 1-byte AccountType discriminator followed by Borsh-serialized
|
|
4
|
+
header fields, then raw u32 LE sample values (not a Borsh Vec — count is
|
|
5
|
+
determined by next_sample_index).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
from borsh_incremental import IncrementalReader
|
|
13
|
+
from solders.pubkey import Pubkey # type: ignore[import-untyped]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
TELEMETRY_SEED_PREFIX = b"telemetry"
|
|
17
|
+
DEVICE_LATENCY_SAMPLES_SEED = b"dzlatency"
|
|
18
|
+
INTERNET_LATENCY_SAMPLES_SEED = b"inetlatency"
|
|
19
|
+
|
|
20
|
+
MAX_DEVICE_LATENCY_SAMPLES_PER_ACCOUNT = 35_000
|
|
21
|
+
MAX_INTERNET_LATENCY_SAMPLES_PER_ACCOUNT = 3_000
|
|
22
|
+
|
|
23
|
+
DEVICE_LATENCY_HEADER_SIZE = 1 + 8 + 32 * 6 + 8 + 8 + 4 + 128
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _read_pubkey(r: IncrementalReader) -> Pubkey:
|
|
27
|
+
return Pubkey.from_bytes(r.read_pubkey_raw())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class DeviceLatencySamples:
|
|
32
|
+
account_type: int
|
|
33
|
+
epoch: int
|
|
34
|
+
origin_device_agent_pk: Pubkey
|
|
35
|
+
origin_device_pk: Pubkey
|
|
36
|
+
target_device_pk: Pubkey
|
|
37
|
+
origin_device_location_pk: Pubkey
|
|
38
|
+
target_device_location_pk: Pubkey
|
|
39
|
+
link_pk: Pubkey
|
|
40
|
+
sampling_interval_microseconds: int
|
|
41
|
+
start_timestamp_microseconds: int
|
|
42
|
+
next_sample_index: int
|
|
43
|
+
samples: list[int] = field(default_factory=list)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_bytes(cls, data: bytes) -> DeviceLatencySamples:
|
|
47
|
+
if len(data) < DEVICE_LATENCY_HEADER_SIZE:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"data too short for device latency header: {len(data)} < {DEVICE_LATENCY_HEADER_SIZE}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
r = IncrementalReader(data)
|
|
53
|
+
|
|
54
|
+
account_type = r.read_u8()
|
|
55
|
+
epoch = r.read_u64()
|
|
56
|
+
origin_device_agent_pk = _read_pubkey(r)
|
|
57
|
+
origin_device_pk = _read_pubkey(r)
|
|
58
|
+
target_device_pk = _read_pubkey(r)
|
|
59
|
+
origin_device_location_pk = _read_pubkey(r)
|
|
60
|
+
target_device_location_pk = _read_pubkey(r)
|
|
61
|
+
link_pk = _read_pubkey(r)
|
|
62
|
+
sampling_interval = r.read_u64()
|
|
63
|
+
start_timestamp = r.read_u64()
|
|
64
|
+
next_sample_index = r.read_u32()
|
|
65
|
+
|
|
66
|
+
r.read_bytes(128) # reserved
|
|
67
|
+
|
|
68
|
+
count = min(next_sample_index, MAX_DEVICE_LATENCY_SAMPLES_PER_ACCOUNT)
|
|
69
|
+
samples: list[int] = []
|
|
70
|
+
for _ in range(count):
|
|
71
|
+
if r.remaining < 4:
|
|
72
|
+
break
|
|
73
|
+
samples.append(r.read_u32())
|
|
74
|
+
|
|
75
|
+
return cls(
|
|
76
|
+
account_type=account_type,
|
|
77
|
+
epoch=epoch,
|
|
78
|
+
origin_device_agent_pk=origin_device_agent_pk,
|
|
79
|
+
origin_device_pk=origin_device_pk,
|
|
80
|
+
target_device_pk=target_device_pk,
|
|
81
|
+
origin_device_location_pk=origin_device_location_pk,
|
|
82
|
+
target_device_location_pk=target_device_location_pk,
|
|
83
|
+
link_pk=link_pk,
|
|
84
|
+
sampling_interval_microseconds=sampling_interval,
|
|
85
|
+
start_timestamp_microseconds=start_timestamp,
|
|
86
|
+
next_sample_index=next_sample_index,
|
|
87
|
+
samples=samples,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class InternetLatencySamples:
|
|
93
|
+
account_type: int
|
|
94
|
+
epoch: int
|
|
95
|
+
data_provider_name: str
|
|
96
|
+
oracle_agent_pk: Pubkey
|
|
97
|
+
origin_exchange_pk: Pubkey
|
|
98
|
+
target_exchange_pk: Pubkey
|
|
99
|
+
sampling_interval_microseconds: int
|
|
100
|
+
start_timestamp_microseconds: int
|
|
101
|
+
next_sample_index: int
|
|
102
|
+
samples: list[int] = field(default_factory=list)
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def from_bytes(cls, data: bytes) -> InternetLatencySamples:
|
|
106
|
+
if len(data) < 10:
|
|
107
|
+
raise ValueError("data too short")
|
|
108
|
+
|
|
109
|
+
r = IncrementalReader(data)
|
|
110
|
+
|
|
111
|
+
account_type = r.read_u8()
|
|
112
|
+
epoch = r.read_u64()
|
|
113
|
+
data_provider_name = r.read_string()
|
|
114
|
+
oracle_agent_pk = _read_pubkey(r)
|
|
115
|
+
origin_exchange_pk = _read_pubkey(r)
|
|
116
|
+
target_exchange_pk = _read_pubkey(r)
|
|
117
|
+
sampling_interval = r.read_u64()
|
|
118
|
+
start_timestamp = r.read_u64()
|
|
119
|
+
next_sample_index = r.read_u32()
|
|
120
|
+
|
|
121
|
+
r.read_bytes(128) # reserved
|
|
122
|
+
|
|
123
|
+
count = min(next_sample_index, MAX_INTERNET_LATENCY_SAMPLES_PER_ACCOUNT)
|
|
124
|
+
samples: list[int] = []
|
|
125
|
+
for _ in range(count):
|
|
126
|
+
if r.remaining < 4:
|
|
127
|
+
break
|
|
128
|
+
samples.append(r.read_u32())
|
|
129
|
+
|
|
130
|
+
return cls(
|
|
131
|
+
account_type=account_type,
|
|
132
|
+
epoch=epoch,
|
|
133
|
+
data_provider_name=data_provider_name,
|
|
134
|
+
oracle_agent_pk=oracle_agent_pk,
|
|
135
|
+
origin_exchange_pk=origin_exchange_pk,
|
|
136
|
+
target_exchange_pk=target_exchange_pk,
|
|
137
|
+
sampling_interval_microseconds=sampling_interval,
|
|
138
|
+
start_timestamp_microseconds=start_timestamp,
|
|
139
|
+
next_sample_index=next_sample_index,
|
|
140
|
+
samples=samples,
|
|
141
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Fixture-based compatibility tests."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from solders.pubkey import Pubkey # type: ignore[import-untyped]
|
|
7
|
+
|
|
8
|
+
from telemetry.state import DeviceLatencySamples, InternetLatencySamples
|
|
9
|
+
|
|
10
|
+
FIXTURES_DIR = Path(__file__).resolve().parent.parent.parent.parent / "testdata" / "fixtures"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _load_fixture(name: str) -> tuple[bytes, dict]:
|
|
14
|
+
bin_data = (FIXTURES_DIR / f"{name}.bin").read_bytes()
|
|
15
|
+
meta = json.loads((FIXTURES_DIR / f"{name}.json").read_text())
|
|
16
|
+
return bin_data, meta
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _assert_fields(expected_fields: list[dict], got: dict) -> None:
|
|
20
|
+
for f in expected_fields:
|
|
21
|
+
name = f["name"]
|
|
22
|
+
if name not in got:
|
|
23
|
+
continue
|
|
24
|
+
typ = f["typ"]
|
|
25
|
+
raw = f["value"]
|
|
26
|
+
actual = got[name]
|
|
27
|
+
if typ in ("u8", "u16", "u32", "u64"):
|
|
28
|
+
assert actual == int(raw), f"{name}: expected {raw}, got {actual}"
|
|
29
|
+
elif typ == "pubkey":
|
|
30
|
+
expected = Pubkey.from_string(raw)
|
|
31
|
+
assert actual == expected, f"{name}: expected {expected}, got {actual}"
|
|
32
|
+
elif typ == "string":
|
|
33
|
+
assert actual == raw, f"{name}: expected {raw}, got {actual}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestFixtureDeviceLatencySamples:
|
|
37
|
+
def test_deserialize(self):
|
|
38
|
+
data, meta = _load_fixture("device_latency_samples")
|
|
39
|
+
d = DeviceLatencySamples.from_bytes(data)
|
|
40
|
+
_assert_fields(
|
|
41
|
+
meta["fields"],
|
|
42
|
+
{
|
|
43
|
+
"AccountType": d.account_type,
|
|
44
|
+
"Epoch": d.epoch,
|
|
45
|
+
"OriginDeviceAgentPK": d.origin_device_agent_pk,
|
|
46
|
+
"OriginDevicePK": d.origin_device_pk,
|
|
47
|
+
"TargetDevicePK": d.target_device_pk,
|
|
48
|
+
"OriginDeviceLocationPK": d.origin_device_location_pk,
|
|
49
|
+
"TargetDeviceLocationPK": d.target_device_location_pk,
|
|
50
|
+
"LinkPK": d.link_pk,
|
|
51
|
+
"SamplingIntervalMicroseconds": d.sampling_interval_microseconds,
|
|
52
|
+
"StartTimestampMicroseconds": d.start_timestamp_microseconds,
|
|
53
|
+
"NextSampleIndex": d.next_sample_index,
|
|
54
|
+
"SamplesCount": len(d.samples),
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestFixtureInternetLatencySamples:
|
|
60
|
+
def test_deserialize(self):
|
|
61
|
+
data, meta = _load_fixture("internet_latency_samples")
|
|
62
|
+
d = InternetLatencySamples.from_bytes(data)
|
|
63
|
+
_assert_fields(
|
|
64
|
+
meta["fields"],
|
|
65
|
+
{
|
|
66
|
+
"AccountType": d.account_type,
|
|
67
|
+
"Epoch": d.epoch,
|
|
68
|
+
"DataProviderName": d.data_provider_name,
|
|
69
|
+
"OracleAgentPK": d.oracle_agent_pk,
|
|
70
|
+
"OriginExchangePK": d.origin_exchange_pk,
|
|
71
|
+
"TargetExchangePK": d.target_exchange_pk,
|
|
72
|
+
"SamplingIntervalMicroseconds": d.sampling_interval_microseconds,
|
|
73
|
+
"StartTimestampMicroseconds": d.start_timestamp_microseconds,
|
|
74
|
+
"NextSampleIndex": d.next_sample_index,
|
|
75
|
+
"SamplesCount": len(d.samples),
|
|
76
|
+
},
|
|
77
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""PDA derivation tests."""
|
|
2
|
+
|
|
3
|
+
from solders.pubkey import Pubkey # type: ignore[import-untyped]
|
|
4
|
+
|
|
5
|
+
from telemetry.pda import (
|
|
6
|
+
derive_device_latency_samples_pda,
|
|
7
|
+
derive_internet_latency_samples_pda,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
PROGRAM_ID = Pubkey.from_string("tE1exJ5VMyoC9ByZeSmgtNzJCFF74G9JAv338sJiqkC")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestDeriveDeviceLatencySamplesPDA:
|
|
14
|
+
def test_deterministic(self):
|
|
15
|
+
origin = Pubkey.from_string("11111111111111111111111111111112")
|
|
16
|
+
target = Pubkey.from_string("11111111111111111111111111111113")
|
|
17
|
+
link = Pubkey.from_string("11111111111111111111111111111114")
|
|
18
|
+
|
|
19
|
+
addr1, bump1 = derive_device_latency_samples_pda(
|
|
20
|
+
PROGRAM_ID, origin, target, link, 42
|
|
21
|
+
)
|
|
22
|
+
addr2, bump2 = derive_device_latency_samples_pda(
|
|
23
|
+
PROGRAM_ID, origin, target, link, 42
|
|
24
|
+
)
|
|
25
|
+
assert addr1 == addr2
|
|
26
|
+
assert bump1 == bump2
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestDeriveInternetLatencySamplesPDA:
|
|
30
|
+
def test_deterministic(self):
|
|
31
|
+
oracle = Pubkey.from_string("11111111111111111111111111111112")
|
|
32
|
+
origin = Pubkey.from_string("11111111111111111111111111111113")
|
|
33
|
+
target = Pubkey.from_string("11111111111111111111111111111114")
|
|
34
|
+
|
|
35
|
+
addr1, _ = derive_internet_latency_samples_pda(
|
|
36
|
+
PROGRAM_ID, oracle, "RIPE Atlas", origin, target, 42
|
|
37
|
+
)
|
|
38
|
+
assert addr1 != Pubkey.default()
|