Skip to content

Commit 118f236

Browse files
author
Xiao Li
committed
add some util functions to jsonrpc client, testnet and identifier.Intent
1 parent cf8329c commit 118f236

File tree

10 files changed

+163
-12
lines changed

10 files changed

+163
-12
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/build
44
/dist
55
__pycache__
6-
/src/diem_client_sdk.egg-info
6+
/src/*.egg-info
77

88
# JetBrains IDEs configuration
99
.idea

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ install:
2323
./venv/bin/python setup.py develop
2424

2525
test: format install
26-
./venv/bin/pytest tests/test_* examples/* -k "$(TEST)"
26+
./venv/bin/pytest tests/test_* examples/* -k "$(TEST)" -vv
2727

2828
cover: install
2929
./venv/bin/pytest --cov-report html --cov=src tests

src/diem/identifier/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,22 @@ def __init__(
5757
sub_address: typing.Optional[bytes],
5858
currency_code: str,
5959
amount: int,
60+
hrp: str,
6061
) -> None:
6162
self.account_address = account_address
6263
self.sub_address = sub_address
6364
self.currency_code = currency_code
6465
self.amount = amount
66+
self.hrp = hrp
6567

6668
@property
6769
def account_address_bytes(self) -> bytes:
6870
return self.account_address.to_bytes()
6971

72+
@property
73+
def account_id(self) -> str:
74+
return encode_account(self.account_address, self.sub_address, self.hrp)
75+
7076

7177
def encode_intent(encoded_account_identifier: str, currency_code: str, amount: int) -> str:
7278
"""
@@ -109,6 +115,7 @@ def decode_intent(encoded_intent_identifier: str, hrp: str) -> Intent:
109115
sub_address=sub_address,
110116
currency_code=currency_code,
111117
amount=amount,
118+
hrp=hrp,
112119
)
113120

114121

src/diem/jsonrpc/client.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import typing
1212
import random, queue
1313
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
14+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
1415

1516
from .. import diem_types, utils
1617
from . import jsonrpc_pb2 as rpc
@@ -55,7 +56,7 @@ class WaitForTransactionTimeout(Exception):
5556
pass
5657

5758

58-
class AccountNotFoundError(Exception):
59+
class AccountNotFoundError(ValueError):
5960
pass
6061

6162

@@ -205,10 +206,7 @@ def get_parent_vasp_account(
205206
could not find the account by the parent_vasp_address found in ChildVASP account.
206207
"""
207208

208-
account = self.get_account(vasp_account_address)
209-
if account is None:
210-
hex = utils.account_address_hex(vasp_account_address)
211-
raise AccountNotFoundError(f"account not found by address: {hex}")
209+
account = self.must_get_account(vasp_account_address)
212210

213211
if account.role.type == constants.ACCOUNT_ROLE_PARENT_VASP:
214212
return account
@@ -218,6 +216,33 @@ def get_parent_vasp_account(
218216
hex = utils.account_address_hex(vasp_account_address)
219217
raise ValueError(f"given account address({hex}) is not a VASP account: {account}")
220218

219+
def get_base_url_and_compliance_key(
220+
self, account_address: typing.Union[diem_types.AccountAddress, str]
221+
) -> typing.Tuple[str, Ed25519PublicKey]:
222+
"""get base_url and compliance key
223+
224+
ParentVASP or Designated Dealer account role has base_url and compliance key setup, which
225+
are used for offchain API communication.
226+
"""
227+
account = self.must_get_account(account_address)
228+
229+
if account.role.compliance_key and account.role.base_url:
230+
key = Ed25519PublicKey.from_public_bytes(bytes.fromhex(account.role.compliance_key))
231+
return (account.role.base_url, key)
232+
if account.role.parent_vasp_address:
233+
return self.get_base_url_and_compliance_key(account.role.parent_vasp_address)
234+
235+
raise ValueError(f"could not find base_url and compliance_key from account: {account}")
236+
237+
def must_get_account(self, account_address: typing.Union[diem_types.AccountAddress, str]) -> rpc.Account:
238+
"""must_get_account raises AccountNotFoundError if account could not be found by given address"""
239+
240+
account = self.get_account(account_address)
241+
if account is None:
242+
hex = utils.account_address_hex(account_address)
243+
raise AccountNotFoundError(f"account not found by address: {hex}")
244+
return account
245+
221246
def get_account_sequence(self, account_address: typing.Union[diem_types.AccountAddress, str]) -> int:
222247
"""get on-chain account sequence number
223248

src/diem/local_account.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ def generate() -> "LocalAccount":
3535
return LocalAccount(private_key)
3636

3737
private_key: Ed25519PrivateKey
38+
compliance_key: Ed25519PrivateKey
3839

3940
def __init__(self, private_key: Ed25519PrivateKey) -> None:
4041
self.private_key = private_key
42+
self.compliance_key = Ed25519PrivateKey.generate()
4143

4244
@property
4345
def auth_key(self) -> AuthKey:
@@ -55,6 +57,10 @@ def public_key_bytes(self) -> bytes:
5557
def public_key(self) -> Ed25519PublicKey:
5658
return self.private_key.public_key()
5759

60+
@property
61+
def compliance_public_key_bytes(self) -> bytes:
62+
return utils.public_key_bytes(self.compliance_key.public_key())
63+
5864
def sign(self, txn: diem_types.RawTransaction) -> diem_types.SignedTransaction:
5965
"""Create signed transaction for given raw transaction"""
6066

src/diem/testnet.py

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
"""
2222

2323
import requests
24-
import typing
24+
import typing, time
2525

26-
from . import diem_types, jsonrpc, utils, local_account, serde_types, auth_key, chain_ids, lcs
26+
from . import diem_types, jsonrpc, utils, local_account, serde_types, auth_key, chain_ids, lcs, stdlib, LocalAccount
2727

2828

2929
JSON_RPC_URL: str = "https://testnet.diem.com/v1"
@@ -40,6 +40,62 @@ def create_client() -> jsonrpc.Client:
4040
return jsonrpc.Client(JSON_RPC_URL)
4141

4242

43+
def gen_vasp_account(base_url: str) -> LocalAccount:
44+
account = gen_account()
45+
exec_txn(
46+
account,
47+
stdlib.encode_rotate_dual_attestation_info_script(
48+
new_url=base_url.encode("utf-8"), new_key=account.compliance_public_key_bytes
49+
),
50+
)
51+
return account
52+
53+
54+
def gen_account() -> LocalAccount:
55+
"""generates a Testnet onchain account"""
56+
57+
return Faucet(create_client()).gen_account()
58+
59+
60+
def gen_child_vasp(
61+
parent_vasp: LocalAccount, initial_balance: int = 10_000_000_000, currency: str = TEST_CURRENCY_CODE
62+
) -> LocalAccount:
63+
child_vasp = LocalAccount.generate()
64+
exec_txn(
65+
parent_vasp,
66+
stdlib.encode_create_child_vasp_account_script(
67+
coin_type=utils.currency_code(currency),
68+
child_address=child_vasp.account_address,
69+
auth_key_prefix=child_vasp.auth_key.prefix(),
70+
add_all_currencies=False,
71+
child_initial_balance=initial_balance,
72+
),
73+
)
74+
return child_vasp
75+
76+
77+
def exec_txn(sender: LocalAccount, script: diem_types.Script) -> jsonrpc.Transaction:
78+
"""create, submit and wait for the transaction"""
79+
80+
client = create_client()
81+
sender_account_sequence = client.get_account_sequence(sender.account_address)
82+
expire = 30
83+
txn = sender.sign(
84+
diem_types.RawTransaction( # pyre-ignore
85+
sender=sender.account_address,
86+
sequence_number=diem_types.st.uint64(sender_account_sequence),
87+
payload=diem_types.TransactionPayload__Script(value=script),
88+
max_gas_amount=1_000_000,
89+
gas_unit_price=0,
90+
gas_currency_code=TEST_CURRENCY_CODE,
91+
expiration_timestamp_secs=int(time.time()) + expire,
92+
chain_id=CHAIN_ID,
93+
)
94+
)
95+
client.submit(txn)
96+
return client.wait_for_transaction(txn, timeout_secs=expire)
97+
98+
4399
class Faucet:
44100
"""Faucet service is a proxy server to mint coins for your test account on Testnet
45101
@@ -57,9 +113,9 @@ def __init__(
57113
self._retry: jsonrpc.Retry = retry or jsonrpc.Retry(5, 0.2, Exception)
58114
self._session: requests.Session = requests.Session()
59115

60-
def gen_account(self, currency_code: str = TEST_CURRENCY_CODE) -> local_account.LocalAccount:
61-
account = local_account.LocalAccount.generate()
62-
self.mint(account.auth_key.hex(), 5_000_000_000, currency_code)
116+
def gen_account(self, currency_code: str = TEST_CURRENCY_CODE) -> LocalAccount:
117+
account = LocalAccount.generate()
118+
self.mint(account.auth_key.hex(), 100_000_000_000, currency_code)
63119
return account
64120

65121
def mint(self, authkey: str, amount: int, currency_code: str) -> None:

src/diem/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,10 @@ def decode_transaction_script(
155155
return decode_transaction_script(txn.script_bytes)
156156

157157
raise TypeError(f"unknown transaction type: {txn}")
158+
159+
160+
def balance(account: jsonrpc.Account, currency: str) -> int:
161+
for b in account.balances:
162+
if b.currency == currency:
163+
return b.amount
164+
return 0

tests/test_identifier.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ def test_intent_identifier():
129129
assert intent.currency_code == "Coin1"
130130
assert intent.amount == 123
131131

132+
assert account_id == intent.account_id
133+
132134

133135
def test_intent_identifier_with_sub_address():
134136
account_id = identifier.encode_account(test_onchain_address, test_sub_address, "lbr")

tests/test_testnet.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,42 @@ def test_get_parent_vasp_account_with_non_vasp_account_address():
346346
client.get_parent_vasp_account(utils.TREASURY_ADDRESS)
347347

348348

349+
def test_gen_vasp_account():
350+
account = testnet.gen_vasp_account("http://hello.com")
351+
child_vasp = testnet.gen_child_vasp(account)
352+
353+
client = testnet.create_client()
354+
assert client.get_account(account.account_address).role.type == "parent_vasp"
355+
assert client.get_account(child_vasp.account_address).role.type == "child_vasp"
356+
357+
358+
def test_get_base_url_and_compliance_key():
359+
client = testnet.create_client()
360+
361+
parent_vasp = testnet.gen_vasp_account("http://hello.com")
362+
child_vasp = testnet.gen_child_vasp(parent_vasp)
363+
364+
base_url, key = client.get_base_url_and_compliance_key(child_vasp.account_address)
365+
assert base_url == "http://hello.com"
366+
assert utils.public_key_bytes(key) == parent_vasp.compliance_public_key_bytes
367+
base_url, key = client.get_base_url_and_compliance_key(parent_vasp.account_address)
368+
assert base_url == "http://hello.com"
369+
assert utils.public_key_bytes(key) == parent_vasp.compliance_public_key_bytes
370+
371+
372+
def test_account_not_found_error_when_get_base_url_and_compliance_key_for_invalid_account():
373+
client = testnet.create_client()
374+
account = LocalAccount.generate()
375+
with pytest.raises(jsonrpc.AccountNotFoundError):
376+
client.get_base_url_and_compliance_key(account.account_address)
377+
378+
379+
def test_value_error_when_get_base_url_and_compliance_key_for_account_has_no_base_url():
380+
client = testnet.create_client()
381+
with pytest.raises(ValueError):
382+
client.get_base_url_and_compliance_key(utils.TREASURY_ADDRESS)
383+
384+
349385
def create_child_vasp_txn(
350386
parent_vasp: LocalAccount, child_vasp: LocalAccount, seq: int = 0
351387
) -> diem_types.RawTransaction:

tests/test_utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,15 @@ def test_decode_transaction_script():
7171

7272
with pytest.raises(TypeError):
7373
utils.decode_transaction_script(False)
74+
75+
76+
def test_balance():
77+
account = jsonrpc.Account(
78+
balances=[
79+
jsonrpc.Amount(amount=32, currency="Coin1"),
80+
jsonrpc.Amount(amount=33, currency="Coin2"),
81+
]
82+
)
83+
assert utils.balance(account, "Coin1") == 32
84+
assert utils.balance(account, "Coin2") == 33
85+
assert utils.balance(account, "unknown") == 0

0 commit comments

Comments
 (0)