Skip to content

Commit fdc3432

Browse files
authored
support pythtest-conformance and pythtest-crosschain (#50)
* support pythtest-conformance and pythtest-crosschain * fix test * address comments * fix test
1 parent a7fb15e commit fdc3432

File tree

4 files changed

+54
-106
lines changed

4 files changed

+54
-106
lines changed

pythclient/solana.py

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
TESTNET_ENDPOINT = "api.testnet.solana.com"
2121
MAINNET_ENDPOINT = "api.mainnet-beta.solana.com"
2222
PYTHNET_ENDPOINT = "pythnet.rpcpool.com"
23+
PYTHTEST_CROSSCHAIN_ENDPOINT = "api.pythtest.pyth.network"
24+
PYTHTEST_CONFORMANCE_ENDPOINT = "api.pythtest.pyth.network"
2325

2426
SOLANA_DEVNET_WS_ENDPOINT = WS_PREFIX + "://" + DEVNET_ENDPOINT
2527
SOLANA_DEVNET_HTTP_ENDPOINT = HTTP_PREFIX + "://" + DEVNET_ENDPOINT
@@ -33,6 +35,12 @@
3335
PYTHNET_WS_ENDPOINT = WS_PREFIX + "://" + PYTHNET_ENDPOINT
3436
PYTHNET_HTTP_ENDPOINT = HTTP_PREFIX + "://" + PYTHNET_ENDPOINT
3537

38+
PYTHTEST_CROSSCHAIN_WS_ENDPOINT = WS_PREFIX + "://" + PYTHTEST_CROSSCHAIN_ENDPOINT
39+
PYTHTEST_CROSSCHAIN_HTTP_ENDPOINT = HTTP_PREFIX + "://" + PYTHTEST_CROSSCHAIN_ENDPOINT
40+
41+
PYTHTEST_CONFORMANCE_WS_ENDPOINT = WS_PREFIX + "://" + PYTHTEST_CONFORMANCE_ENDPOINT
42+
PYTHTEST_CONFORMANCE_HTTP_ENDPOINT = HTTP_PREFIX + "://" + PYTHTEST_CONFORMANCE_ENDPOINT
43+
3644
class SolanaPublicKey:
3745
"""
3846
Represents a Solana public key. This class is meant to be immutable.

pythclient/utils.py

+34-30
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,40 @@
1-
import ast
2-
import dns.resolver
3-
from loguru import logger
41
from typing import Optional
52

6-
DEFAULT_VERSION = "v2"
73

8-
9-
# Retrieving keys via DNS TXT records should not be considered secure and is provided as a convenience only.
10-
# Accounts should be stored locally and verified before being used for production.
11-
def get_key(network: str, type: str, version: str = DEFAULT_VERSION) -> Optional[str]:
4+
def get_key(network: str, type: str) -> Optional[str]:
125
"""
13-
Get the program or mapping keys from dns TXT records.
14-
15-
Example dns records:
16-
17-
devnet-program-v2.pyth.network
18-
mainnet-program-v2.pyth.network
19-
testnet-mapping-v2.pyth.network
20-
pythnet-mapping-v2.pyth.network
6+
Get the program or mapping key.
7+
:param network: The network to get the key for. Either "mainnet", "devnet", "testnet", "pythnet", "pythtest-conformance", or "pythtest-crosschain".
8+
:param type: The type of key to get. Either "program" or "mapping".
219
"""
22-
url = f"{network}-{type}-{version}.pyth.network"
10+
keys = {
11+
"pythnet": {
12+
"program": "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH",
13+
"mapping": "AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J",
14+
},
15+
"mainnet": {
16+
"program": "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH",
17+
"mapping": "AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J",
18+
},
19+
"devnet": {
20+
"program": "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s",
21+
"mapping": "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2",
22+
},
23+
"testnet": {
24+
"program": "8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz",
25+
"mapping": "AFmdnt9ng1uVxqCmqwQJDAYC5cKTkw8gJKSM5PnzuF6z",
26+
},
27+
"pythtest-conformance": {
28+
"program": "8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz",
29+
"mapping": "AFmdnt9ng1uVxqCmqwQJDAYC5cKTkw8gJKSM5PnzuF6z",
30+
},
31+
"pythtest-crosschain": {
32+
"program": "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s",
33+
"mapping": "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2",
34+
},
35+
}
36+
2337
try:
24-
answer = dns.resolver.resolve(url, "TXT")
25-
except dns.resolver.NXDOMAIN:
26-
logger.error("TXT record for {} not found", url)
27-
return ""
28-
if len(answer) != 1:
29-
logger.error("Invalid number of records returned for {}!", url)
30-
return ""
31-
# Example of the raw_key:
32-
# "program=FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH"
33-
raw_key = ast.literal_eval(list(answer)[0].to_text())
34-
# program=FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH"
35-
_, key = raw_key.split("=", 1)
36-
return key
38+
return keys[network][type]
39+
except KeyError:
40+
raise Exception(f"Unknown network or type: {network}, {type}")

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from setuptools import setup
22

3-
requirements = ['aiodns', 'aiohttp>=3.7.4', 'backoff', 'base58', 'dnspython', 'flake8', 'loguru', 'typing-extensions', 'pytz', 'pycryptodome']
3+
requirements = ['aiodns', 'aiohttp>=3.7.4', 'backoff', 'base58', 'flake8', 'loguru', 'typing-extensions', 'pytz', 'pycryptodome']
44

55
with open('README.md', 'r', encoding='utf-8') as fh:
66
long_description = fh.read()
77

88
setup(
99
name='pythclient',
10-
version='0.1.19',
10+
version='0.1.20',
1111
packages=['pythclient'],
1212
author='Pyth Developers',
1313
author_email='[email protected]',

tests/test_utils.py

+10-74
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,25 @@
1-
from _pytest.logging import LogCaptureFixture
21
import pytest
32

4-
from pytest_mock import MockerFixture
5-
6-
from mock import Mock
7-
8-
import dns.resolver
9-
import dns.rdatatype
10-
import dns.rdataclass
11-
import dns.message
12-
import dns.rrset
13-
import dns.flags
14-
153
from pythclient.utils import get_key
164

175

18-
@pytest.fixture()
19-
def answer_program() -> dns.resolver.Answer:
20-
qname = dns.name.Name(labels=(b'devnet-program-v2', b'pyth', b'network', b''))
21-
rdtype = dns.rdatatype.TXT
22-
rdclass = dns.rdataclass.IN
23-
response = dns.message.QueryMessage(id=0)
24-
response.flags = dns.flags.QR
25-
rrset_qn = dns.rrset.from_text(qname, 100, rdclass, rdtype)
26-
rrset_ans = dns.rrset.from_text(qname, 100, rdclass, rdtype, '"program=gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"')
27-
response.question = [rrset_qn]
28-
response.answer = [rrset_ans]
29-
answer = dns.resolver.Answer(
30-
qname=qname, rdtype=rdtype, rdclass=rdclass, response=response)
31-
answer.rrset = rrset_ans
32-
return answer
33-
34-
35-
@pytest.fixture()
36-
def answer_mapping() -> dns.resolver.Answer:
37-
qname = dns.name.Name(labels=(b'devnet-mapping-v2', b'pyth', b'network', b''))
38-
rdtype = dns.rdatatype.TXT
39-
rdclass = dns.rdataclass.IN
40-
response = dns.message.QueryMessage(id=0)
41-
response.flags = dns.flags.QR
42-
rrset_qn = dns.rrset.from_text(qname, 100, rdclass, rdtype)
43-
rrset_ans = dns.rrset.from_text(qname, 100, rdclass, rdtype, '"mapping=BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2"')
44-
response.question = [rrset_qn]
45-
response.answer = [rrset_ans]
46-
answer = dns.resolver.Answer(
47-
qname=qname, rdtype=rdtype, rdclass=rdclass, response=response)
48-
answer.rrset = rrset_ans
49-
return answer
50-
51-
52-
@pytest.fixture()
53-
def mock_dns_resolver_resolve(mocker: MockerFixture) -> Mock:
54-
mock = Mock()
55-
mocker.patch('dns.resolver.resolve', side_effect=mock)
56-
return mock
57-
58-
59-
def test_utils_get_program_key(mock_dns_resolver_resolve: Mock, answer_program: dns.resolver.Answer) -> None:
60-
mock_dns_resolver_resolve.return_value = answer_program
6+
def test_utils_get_program_key() -> None:
617
program_key = get_key("devnet", "program")
628
assert program_key == "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"
639

6410

65-
def test_utils_get_mapping_key(mock_dns_resolver_resolve: Mock, answer_mapping: dns.resolver.Answer) -> None:
66-
mock_dns_resolver_resolve.return_value = answer_mapping
11+
def test_utils_get_mapping_key() -> None:
6712
mapping_key = get_key("devnet", "mapping")
6813
assert mapping_key == "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2"
6914

7015

71-
def test_utils_get_mapping_key_not_found(mock_dns_resolver_resolve: Mock,
72-
answer_mapping: dns.resolver.Answer,
73-
caplog: LogCaptureFixture) -> None:
74-
mock_dns_resolver_resolve.side_effect = dns.resolver.NXDOMAIN
75-
exc_message = f'TXT record for {str(answer_mapping.response.canonical_name())[:-1]} not found'
76-
key = get_key("devnet", "mapping")
77-
assert exc_message in caplog.text
78-
assert key == ""
16+
def test_utils_invalid_network() -> None:
17+
with pytest.raises(Exception) as e:
18+
get_key("testdevnet", "mapping")
19+
assert str(e.value) == "Unknown network or type: testdevnet, mapping"
7920

8021

81-
def test_utils_get_mapping_key_invalid_number(mock_dns_resolver_resolve: Mock,
82-
answer_mapping: dns.resolver.Answer,
83-
caplog: LogCaptureFixture) -> None:
84-
answer_mapping.rrset = None
85-
mock_dns_resolver_resolve.return_value = answer_mapping
86-
exc_message = f'Invalid number of records returned for {str(answer_mapping.response.canonical_name())[:-1]}!'
87-
key = get_key("devnet", "mapping")
88-
assert exc_message in caplog.text
89-
assert key == ""
22+
def test_utils_get_invalid_type() -> None:
23+
with pytest.raises(Exception) as e:
24+
get_key("devnet", "mappingprogram")
25+
assert str(e.value) == "Unknown network or type: devnet, mappingprogram"

0 commit comments

Comments
 (0)