Ruff
Some checks failed
Run Python tests (through Pytest) / Test (push) Failing after 23s
Verify Python project can be installed, loaded and have version checked / Test (push) Failing after 22s

This commit is contained in:
Jon Michael Aanes 2025-04-13 01:19:47 +02:00
parent 5afd7e596f
commit dae2168d8f
3 changed files with 86 additions and 50 deletions

View File

@ -1,31 +1,37 @@
""" # Partisia Blockchain Client
"""# Partisia Blockchain Client
Unofficial library for reading contract states from Partisia Blockchain.
This library is not officially associated with Partisia Blockchain nor Partisia
Group ApS.
"""
import base64
import dataclasses
import datetime
import base64
import email.utils
import hashlib
import json
import logging
import ecdsa
import hashlib
import pbcabi
from collections.abc import Mapping
import time
from collections.abc import Mapping
from decimal import Decimal
from typing import Any
import ecdsa
import pbcabi
import requests
from frozendict import frozendict
from .pbc_types import (Address, Signature, HashSha256, Transaction,
SignedTransaction, SignedTransactionInnerPart)
from ._version import __version__ #noqa: F401
from ._version import __version__ # noqa: F401
from .pbc_types import (
Address,
HashSha256,
Signature,
SignedTransaction,
SignedTransactionInnerPart,
Transaction,
)
logger = logging.getLogger(__name__)
@ -62,12 +68,15 @@ class Balances:
mpc: Decimal
byoc: Mapping[str, Decimal]
@dataclasses.dataclass(frozen=True)
class SenderAuthentication:
secret_key: str
def sender_address(self) -> Address:
verifying_key_repr = self._signing_key().get_verifying_key().to_string("uncompressed")
verifying_key_repr = (
self._signing_key().get_verifying_key().to_string('uncompressed')
)
hashed = HashSha256.of_bytes(verifying_key_repr)
return Address(b'\0' + hashed._bytes[-20:])
@ -76,10 +85,15 @@ class SenderAuthentication:
def _signing_key(self):
secret_exponent = int(self.secret_key, 16)
return ecdsa.SigningKey.from_secret_exponent(secret_exponent , curve=ecdsa.SECP256k1)
return ecdsa.SigningKey.from_secret_exponent(
secret_exponent,
curve=ecdsa.SECP256k1,
)
TRANSACTION_VALIDITY_DURATION = 60
def sign_transaction(
sender_authentication: SenderAuthentication,
nonce: int,
@ -91,16 +105,17 @@ def sign_transaction(
sender = sender_authentication.sender_address
valid_to_time: int = int(time.time() + TRANSACTION_VALIDITY_DURATION) * 1000
inner: SignedTransactionInnerPart = SignedTransactionInnerPart(nonce,
valid_to_time,
gas_cost,
Transaction(Address.from_string(contract_address),
transaction_rpc))
inner: SignedTransactionInnerPart = SignedTransactionInnerPart(
nonce,
valid_to_time,
gas_cost,
Transaction(Address.from_string(contract_address), transaction_rpc),
)
transaction_hash_bytes = inner.rpc_serialize() + chain_id.encode('utf8')
transaction_hash: HashSha256 = HashSha256.of_bytes(transaction_hash_bytes)
signature : Signature = sender_authentication.sign_hash(transaction_hash)
signature: Signature = sender_authentication.sign_hash(transaction_hash)
return SignedTransaction(inner, signature, transaction_hash)
@ -117,29 +132,38 @@ class PbcClient:
if self.sender_authentication is not None:
assert isinstance(self.sender_authentication, SenderAuthentication)
def send_transaction(self, contract_address: str, rpc: bytes, gas_cost: int):
if self.sender_authentication is None:
msg = "PbcClient.sender_authentication required for send_transaction"
msg = 'PbcClient.sender_authentication required for send_transaction'
raise Exception(msg)
signed_transaction = sign_transaction(self.sender_authentication,
self.get_sender_authentication_nonce(),
gas_cost,
self.get_chain_id(),
contract_address, rpc)
signed_transaction = sign_transaction(
self.sender_authentication,
self.get_sender_authentication_nonce(),
gas_cost,
self.get_chain_id(),
contract_address,
rpc,
)
return self.send_signed_transaction(signed_transaction)
def send_signed_transaction(self, signed_transaction: SignedTransaction):
url = URL_TRANSACTION.format(
hostname=self.hostname,
shard=shard_id_for_address(signed_transaction.inner.transaction.contract_address),
shard=shard_id_for_address(
signed_transaction.inner.transaction.contract_address,
),
)
transaction_payload: str = base64.b64encode(signed_transaction.rpc_serialize()).decode('utf8')
self._get_json(url, data = {'transactionPayload':transaction_payload }, method = 'PUT')
transaction_payload: str = base64.b64encode(
signed_transaction.rpc_serialize(),
).decode('utf8')
self._get_json(
url,
data={'transactionPayload': transaction_payload},
method='PUT',
)
def _get_json(
self,
@ -226,7 +250,6 @@ class PbcClient:
)
return self._get_json(url, method='GET')[0]['nonce']
def get_contract_state(self, address: str) -> tuple[dict, datetime.datetime]:
# TODO: Rename to get_contract_state_json
url = URL_CONTRACT_STATE.format(
@ -244,11 +267,15 @@ class PbcClient:
file_abi = self.get_contract_abi(address)
state, server_time = self.get_contract_state(address)
state_bytes = base64.b64decode(state['state']['data'])
state_deserialized = file_abi.contract.read_state(state_bytes)
state_deserialized = file_abi.contract.read_state(state_bytes)
return state_deserialized, server_time
def get_typed_contract_avl_tree(self, address: str, avl_tree_id: pbcabi.model.AvlTreeId) -> tuple[dict, datetime.datetime]:
def get_typed_contract_avl_tree(
self,
address: str,
avl_tree_id: pbcabi.model.AvlTreeId,
) -> tuple[dict, datetime.datetime]:
file_abi = self.get_contract_abi(address)
state, server_time = self.get_contract_state(address)
for avl_tree in state['avlTrees']:
@ -260,8 +287,14 @@ class PbcClient:
key_bytes = base64.b64decode(key_and_value['key']['data']['data'])
value_bytes = base64.b64decode(key_and_value['value']['data'])
key = file_abi.contract.read_state(key_bytes, avl_tree_id.type_spec.type_key)
value = file_abi.contract.read_state(value_bytes, avl_tree_id.type_spec.type_value)
key = file_abi.contract.read_state(
key_bytes,
avl_tree_id.type_spec.type_key,
)
value = file_abi.contract.read_state(
value_bytes,
avl_tree_id.type_spec.type_value,
)
data[key] = value
@ -269,7 +302,6 @@ class PbcClient:
return data, server_time
def get_contract_abi(self, address: str) -> pbcabi.model.FileAbi:
url = URL_CONTRACT_STATE.format(
hostname=self.hostname,

View File

@ -1,22 +1,11 @@
import dataclasses
import datetime
import base64
import email.utils
import json
import logging
import hashlib
import pbcabi
from collections.abc import Mapping
import time
from decimal import Decimal
from typing import Any
def size_prefixed(_bytes: bytes) -> bytes:
return len(_bytes).to_bytes(4, 'big') + _bytes
@dataclasses.dataclass(frozen=True)
class Address:
_bytes: bytes
@ -50,6 +39,7 @@ class Signature:
def __str__(self) -> str:
return self._bytes.hex()
@dataclasses.dataclass(frozen=True)
class HashSha256:
_bytes: bytes
@ -67,6 +57,7 @@ class HashSha256:
def __str__(self) -> str:
return self._bytes.hex()
@dataclasses.dataclass(frozen=True)
class Transaction:
contract_address: Address
@ -76,7 +67,10 @@ class Transaction:
assert isinstance(self.contract_address, Address), self.contract_address
def rpc_serialize(self) -> bytes:
return self.contract_address.rpc_serialize() + size_prefixed(self.transaction_rpc)
return self.contract_address.rpc_serialize() + size_prefixed(
self.transaction_rpc,
)
@dataclasses.dataclass(frozen=True)
class SignedTransactionInnerPart:
@ -86,7 +80,13 @@ class SignedTransactionInnerPart:
transaction: Transaction
def rpc_serialize(self) -> bytes:
return self.nonce.to_bytes(4, 'big') + self.valid_to_time.to_bytes(8, 'big') + self.gas_cost.to_bytes(8, 'big') + self.transaction.rpc_serialize()
return (
self.nonce.to_bytes(4, 'big')
+ self.valid_to_time.to_bytes(8, 'big')
+ self.gas_cost.to_bytes(8, 'big')
+ self.transaction.rpc_serialize()
)
@dataclasses.dataclass(frozen=True)
class SignedTransaction:
@ -95,4 +95,8 @@ class SignedTransaction:
hash: HashSha256
def rpc_serialize(self) -> bytes:
return self.inner.rpc_serialize() + self.signature.rpc_serialize() + self.hash.rpc_serialize()
return (
self.inner.rpc_serialize()
+ self.signature.rpc_serialize()
+ self.hash.rpc_serialize()
)

View File

@ -1,2 +1,2 @@
def test_init():
import pbc_client #noqa: F401
import pbc_client # noqa: F401