Skip to content

Commit

Permalink
feat: read transfer data from destination blockchain
Browse files Browse the repository at this point in the history
  • Loading branch information
danut13 committed Jul 10, 2024
1 parent e663492 commit 65beb7b
Show file tree
Hide file tree
Showing 7 changed files with 437 additions and 9 deletions.
8 changes: 8 additions & 0 deletions client-library.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ blockchains:
fallback_providers:
- !ENV ${AVALANCHE_FALLBACK_PROVIDER:https://rpc.ankr.com/avalanche_fuji}
average_block_time: !ENV tag:yaml.org,2002:int ${AVALANCHE_AVERAGE_BLOCK_TIME:3}
blocks_per_query: !ENV tag:yaml.org,2002:int ${AVALANCHE_BLOCKS_PER_QUERY:2000}
chain_id: !ENV tag:yaml.org,2002:int ${AVALANCHE_CHAIN_ID:43113}
confirmations: !ENV tag:yaml.org,2002:int ${AVALANCHE_CONFIRMATIONS:20}
hub: !ENV ${AVALANCHE_HUB:0xbafFb84601BeC1FCb4B842f8917E3eA850781BE7}
Expand All @@ -32,6 +33,7 @@ blockchains:
fallback_providers:
- !ENV ${BNB_FALLBACK_PROVIDER:https://data-seed-prebsc-1-s1.binance.org:8545/}
average_block_time: !ENV tag:yaml.org,2002:int ${BNB_AVERAGE_BLOCK_TIME:3}
blocks_per_query: !ENV tag:yaml.org,2002:int ${BNB_BLOCKS_PER_QUERY:2000}
chain_id: !ENV tag:yaml.org,2002:int ${BNB_CHAIN_ID:97}
confirmations: !ENV tag:yaml.org,2002:int ${BNB_CONFIRMATIONS:3}
hub: !ENV ${BNB_HUB:0xFB37499DC5401Dc39a0734df1fC7924d769721d5}
Expand All @@ -53,6 +55,7 @@ blockchains:
fallback_providers:
- !ENV ${CELO_FALLBACK_PROVIDER:https://alfajores-forno.celo-testnet.org}
average_block_time: !ENV tag:yaml.org,2002:int ${CELO_AVERAGE_BLOCK_TIME:5}
blocks_per_query: !ENV tag:yaml.org,2002:int ${CELO_BLOCKS_PER_QUERY:2000}
chain_id: !ENV tag:yaml.org,2002:int ${CELO_CHAIN_ID:44787}
confirmations: !ENV tag:yaml.org,2002:int ${CELO_CONFIRMATIONS:3}
hub: !ENV ${CELO_HUB:0x8389B9A7608dbf52a699b998f309883257923C0E}
Expand All @@ -74,6 +77,7 @@ blockchains:
fallback_providers:
- !ENV ${CRONOS_FALLBACK_PROVIDER:https://evm-t3.cronos.org}
average_block_time: !ENV tag:yaml.org,2002:int ${CRONOS_AVERAGE_BLOCK_TIME:5}
blocks_per_query: !ENV tag:yaml.org,2002:int ${CRONOS_BLOCKS_PER_QUERY:2000}
chain_id: !ENV tag:yaml.org,2002:int ${CRONOS_CHAIN_ID:338}
confirmations: !ENV tag:yaml.org,2002:int ${CRONOS_CONFIRMATIONS:3}
hub: !ENV ${CRONOS_HUB:0x0Cfb3c7C11A33BEf124A9D86073e73932b9AbF90}
Expand All @@ -95,6 +99,7 @@ blockchains:
fallback_providers:
- !ENV ${ETHEREUM_FALLBACK_PROVIDER:https://ethereum-holesky.publicnode.com}
average_block_time: !ENV tag:yaml.org,2002:int ${ETHEREUM_AVERAGE_BLOCK_TIME:14}
blocks_per_query: !ENV tag:yaml.org,2002:int ${ETHEREUM_BLOCKS_PER_QUERY:2000}
chain_id: !ENV tag:yaml.org,2002:int ${ETHEREUM_CHAIN_ID:17000}
confirmations: !ENV tag:yaml.org,2002:int ${ETHEREUM_CONFIRMATIONS:20}
hub: !ENV ${ETHEREUM_HUB:0x5e447968d4a177fE7bFB8877cA12aE20Bd60dD85}
Expand All @@ -116,6 +121,7 @@ blockchains:
fallback_providers:
- !ENV ${FANTOM_FALLBACK_PROVIDER:https://rpc.ankr.com/fantom_testnet}
average_block_time: !ENV tag:yaml.org,2002:int ${FANTOM_AVERAGE_BLOCK_TIME:1}
blocks_per_query: !ENV tag:yaml.org,2002:int ${FANTOM_BLOCKS_PER_QUERY:2000}
chain_id: !ENV tag:yaml.org,2002:int ${FANTOM_CHAIN_ID:4002}
confirmations: !ENV tag:yaml.org,2002:int ${FANTOM_CONFIRMATIONS:3}
hub: !ENV ${FANTOM_HUB:0x4BC6A71D4C3D6170d0Db849fE19b8DbA18f1a7F5}
Expand All @@ -137,6 +143,7 @@ blockchains:
fallback_providers:
- !ENV ${POLYGON_FALLBACK_PROVIDER:https://rpc.ankr.com/polygon_mumbai}
average_block_time: !ENV tag:yaml.org,2002:int ${POLYGON_AVERAGE_BLOCK_TIME:3}
blocks_per_query: !ENV tag:yaml.org,2002:int ${POLYGON_BLOCKS_PER_QUERY:2000}
chain_id: !ENV tag:yaml.org,2002:int ${POLYGON_CHAIN_ID:80001}
confirmations: !ENV tag:yaml.org,2002:int ${POLYGON_CONFIRMATIONS:20}
hub: !ENV ${POLYGON_HUB:0x5C4B92cd0A956dedc14AF31fD474931540D8277B}
Expand All @@ -158,6 +165,7 @@ blockchains:
fallback_providers:
- !ENV ${SOLANA_FALLBACK_PROVIDER}
average_block_time: !ENV tag:yaml.org,2002:int ${SOLANA_AVERAGE_BLOCK_TIME:1}
blocks_per_query: !ENV tag:yaml.org,2002:int ${SOLANA_BLOCKS_PER_QUERY:2000}
chain_id: !ENV tag:yaml.org,2002:int ${SOLANA_CHAIN_ID:-1}
confirmations: !ENV tag:yaml.org,2002:int ${SOLANA_CONFIRMATIONS:1}
hub: !ENV ${SOLANA_HUB}
Expand Down
126 changes: 121 additions & 5 deletions pantos/client/library/blockchains/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ class BlockchainClientError(ClientLibraryError):
pass


class UnknownTransferError(BlockchainClientError):
"""Exception raised for unknown token transfers.
"""
def __init__(self, **kwargs: typing.Any):
# Docstring inherited
super().__init__('unknown transfer', **kwargs)


class BlockchainClient(BlockchainHandler, ErrorCreator[BlockchainClientError]):
"""Base class for all blockchain clients.
Expand Down Expand Up @@ -125,6 +134,82 @@ class ComputeTransferSignatureResponse:
sender_nonce: int
signature: str

@dataclasses.dataclass
class DestinationTransferRequest:
"""Request data for a token transfer on the destination
blockchain.
Attributes
----------
source_blockchain : Blockchain
The token transfer's source blockchain.
source_transaction_id : str
The transaction ID of the token transfer on the
source blockchain.
blocks_to_search : int | None
The blocks to search for the token transfer on the
destination blockchain.
"""
source_blockchain: Blockchain
source_transaction_id: str
blocks_to_search: int | None = None

@dataclasses.dataclass
class DestinationTransferResponse:
"""Response data for a token transfer on the destination
blockchain.
Attributes
----------
latest_block_number : int
The latest block number on the destination blockchain.
transaction_block_number : int
The block number of the token transfer transaction.
destination_transaction_id : str
The transaction ID of the token transfer.
source_transfer_id : int
The unique identifier of the token transfer on the source
blockchain.
destination_transfer_id : int
The unique identifier of the token transfer on the
destination blockchain.
sender_address : BlockchainAddress
The address of the sender's account.
recipient_address : BlockchainAddress
The address of the recipient's account.
source_token_address : BlockchainAddress
The transferred token's address on the source blockchain.
destination_token_address : BlockchainAddress
The transferred token's address on the destination
blockchain.
amount : int
The transferred token amount.
validator_nonce : int
The unique nonce of the validator for the token transfer on
the destination blockchain.
signer_addresses : list of BlockchainAddress
The addresses of the validators which signed the token
transfer on the destination blockchain.
signatures : list of str
The signatures of the validators which signed the token
transfer on the destination blockchain.
"""
latest_block_number: int
transaction_block_number: int
destination_transaction_id: str
source_transfer_id: int
destination_transfer_id: int
sender_address: BlockchainAddress
recipient_address: BlockchainAddress
source_token_address: BlockchainAddress
destination_token_address: BlockchainAddress
amount: int
validator_nonce: int
signer_addresses: list[BlockchainAddress]
signatures: list[str]

@abc.abstractmethod
def compute_transfer_signature(
self, request: ComputeTransferSignatureRequest) \
Expand Down Expand Up @@ -289,9 +374,9 @@ def decrypt_private_key(self, keystore: str, password: str) -> PrivateKey:

@abc.abstractmethod
def read_external_token_address(
self, token_address: BlockchainAddress,
destination_blockchain: Blockchain
) -> BlockchainAddress: # pragma: no cover
self, token_address: BlockchainAddress,
destination_blockchain: Blockchain) \
-> BlockchainAddress: # pragma: no cover
"""Read an external token address that is registered at the
Pantos Hub on the blockchain.
Expand Down Expand Up @@ -338,8 +423,8 @@ def read_service_node_addresses(

@abc.abstractmethod
def read_service_node_url(
self, service_node_address: BlockchainAddress
) -> str: # pragma: no cover
self, service_node_address: BlockchainAddress) \
-> str: # pragma: no cover
"""Read a service node's URL that is registered at the Pantos
Hub on the blockchain.
Expand All @@ -362,6 +447,32 @@ def read_service_node_url(
"""
pass

@abc.abstractmethod
def read_destination_transfer(
self, request: DestinationTransferRequest) \
-> DestinationTransferResponse: # pragma: no cover
"""Read a token transfer on the destination blockchain.
Parameters
----------
request : DestinationTransferRequest
The request data for reading the token transfer.
Returns
-------
DestinationTransferResponse
The response data with the token transfer information.
Raises
------
UnknownTransferError
If the token transfer is unkown.
BlockchainClientError
If the token transfer cannot be read.
"""
pass

def read_token_balance(self, token_address: BlockchainAddress,
account_id: AccountId) -> int:
"""Read a blockchain account's balance of a Pantos-compatible
Expand Down Expand Up @@ -418,6 +529,11 @@ def read_token_decimals(
"""
pass

def _create_unknown_transfer_error(
self, **kwargs: typing.Any) -> BlockchainClientError:
return self._create_error(specialized_error_class=UnknownTransferError,
**kwargs)

def _account_id_to_account_address(
self, account_id: AccountId) -> BlockchainAddress:
if isinstance(account_id, BlockchainAddress):
Expand Down
66 changes: 66 additions & 0 deletions pantos/client/library/blockchains/ethereum.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import hexbytes
import web3
import web3.contract
import web3.types
from pantos.common.blockchains.base import Blockchain
from pantos.common.blockchains.base import NodeConnections
from pantos.common.blockchains.enums import ContractAbi
Expand All @@ -18,6 +19,7 @@
from pantos.client.library.blockchains.base import VERSIONED_CONTRACT_ABIS
from pantos.client.library.blockchains.base import BlockchainClient
from pantos.client.library.blockchains.base import BlockchainClientError
from pantos.client.library.blockchains.base import UnknownTransferError
from pantos.client.library.constants import TOKEN_SYMBOL_PAN

Web3Contract: typing.TypeAlias = NodeConnections.Wrapper[
Expand Down Expand Up @@ -192,6 +194,40 @@ def read_service_node_url(self,
raise self._create_error('unable to read a service node URL',
service_node_address=service_node_address)

def read_destination_transfer(
self, request: BlockchainClient.DestinationTransferRequest) \
-> BlockchainClient.DestinationTransferResponse:
# Docstring inherited
try:
node_connections = self._get_utilities().create_node_connections()
to_block_number = \
node_connections.eth.get_block_number().get_minimum_result()
from_block_number = (to_block_number - request.blocks_to_search +
1 if request.blocks_to_search else 0)
blocks_per_query = self._get_config()['blocks_per_query']
hub_contract = self._create_hub_contract(node_connections)
transfer_event = typing.cast(
NodeConnections.Wrapper[web3.contract.contract.ContractEvent],
hub_contract.events.TransferTo())
for to_block_number_ in range(to_block_number + 1,
from_block_number,
-blocks_per_query):
from_block_number_ = max(to_block_number_ - blocks_per_query,
from_block_number)
transfer_event_logs = self._get_utilities().get_logs(
transfer_event, from_block_number_, to_block_number_ - 1)
transfer_response = self.__find_destination_transfer(
transfer_event_logs, request.source_transaction_id,
request.source_blockchain, to_block_number)
if transfer_response:
return transfer_response
raise self._create_unknown_transfer_error(request=request)
except UnknownTransferError:
raise
except Exception:
raise self._create_error('unable to read a destination transfer',
request=request)

def read_token_decimals(self, token_address: BlockchainAddress) -> int:
# Docstring inherited
try:
Expand Down Expand Up @@ -246,3 +282,33 @@ def __sign_message(self, private_key: PrivateKey,
signed_message = web3.Account.sign_message(message,
private_key=private_key)
return signed_message.signature.to_0x_hex()

def __find_destination_transfer(
self, transfer_event_logs: list[web3.types.EventData],
source_transaction_id: str, source_blockchain_id: int,
to_block_number: int) \
-> BlockchainClient.DestinationTransferResponse | None:
for transfer_event_log in transfer_event_logs:
transfer_event_args = transfer_event_log['args']
if (transfer_event_args['sourceTransactionId']
== source_transaction_id
and transfer_event_args['sourceBlockchainId']
== source_blockchain_id):
return BlockchainClient.DestinationTransferResponse(
to_block_number, transfer_event_log['blockNumber'],
transfer_event_log['transactionHash'].hex(),
transfer_event_args['sourceTransferId'],
transfer_event_args['destinationTransferId'],
BlockchainAddress(transfer_event_args['sender']),
BlockchainAddress(transfer_event_args['recipient']),
BlockchainAddress(transfer_event_args['sourceToken']),
BlockchainAddress(transfer_event_args['destinationToken']),
transfer_event_args['amount'],
transfer_event_args['nonce'], [
BlockchainAddress(signer_address) for signer_address in
transfer_event_args['signerAddresses']
], [
signature.hex()
for signature in transfer_event_args['signatures']
])
return None
6 changes: 6 additions & 0 deletions pantos/client/library/blockchains/solana.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ def read_service_node_url(self,
# Docstring inherited
raise NotImplementedError

def read_destination_transfer(
self, request: BlockchainClient.DestinationTransferRequest) \
-> BlockchainClient.DestinationTransferResponse:
# Docstring inherited
raise NotImplementedError

def read_token_decimals(self, token_address: BlockchainAddress) -> int:
# Docstring inherited
raise NotImplementedError
5 changes: 5 additions & 0 deletions pantos/client/library/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
'type': 'integer',
'required': True
},
'blocks_per_query': {
'type': 'integer',
'required': True,
'min': 1
},
'chain_id': {
'type': 'integer',
'required': True
Expand Down
Loading

0 comments on commit 65beb7b

Please sign in to comment.