-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transaction Converter & refactoring and reorganizing #17
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from multiversx_sdk.adapters.query_runner_adapter import QueryRunnerAdapter | ||
|
||
__all__ = ["QueryRunnerAdapter"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from multiversx_sdk.converters.transactions_converter import \ | ||
TransactionsConverter | ||
|
||
__all__ = ["TransactionsConverter"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
class MissingFieldError(Exception): | ||
def __init__(self, message: str) -> None: | ||
super().__init__(message) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import base64 | ||
from typing import Any, Dict | ||
|
||
from multiversx_sdk.converters.errors import MissingFieldError | ||
from multiversx_sdk.core.interfaces import ITransaction | ||
from multiversx_sdk.core.transaction import Transaction | ||
|
||
|
||
class TransactionsConverter: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the specs there is also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That has been added later. Will be implemented in a future PR. |
||
def __init__(self) -> None: | ||
pass | ||
|
||
def transaction_to_dictionary(self, transaction: ITransaction) -> Dict[str, Any]: | ||
return { | ||
"nonce": transaction.nonce, | ||
"value": str(transaction.value), | ||
"receiver": transaction.receiver, | ||
"sender": transaction.sender, | ||
"senderUsername": self._value_to_b64_or_empty(transaction.sender_username), | ||
"receiverUsername": self._value_to_b64_or_empty(transaction.receiver_username), | ||
"gasPrice": transaction.gas_price, | ||
"gasLimit": transaction.gas_limit, | ||
"data": self._value_to_b64_or_empty(transaction.data), | ||
"chainID": transaction.chain_id, | ||
"version": transaction.version, | ||
"options": transaction.options, | ||
"guardian": transaction.guardian, | ||
"signature": self._value_to_hex_or_empty(transaction.signature), | ||
"guardianSignature": self._value_to_hex_or_empty(transaction.guardian_signature) | ||
} | ||
|
||
def dictionary_to_transaction(self, dictionary: Dict[str, Any]) -> Transaction: | ||
self._ensure_mandatory_fields_for_transaction(dictionary) | ||
|
||
return Transaction( | ||
nonce=dictionary.get("nonce", None), | ||
value=int(dictionary.get("value", None)), | ||
receiver=dictionary["receiver"], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, sender, receiver, gas limit, chain ID are required - but if the input dictionary is missing them, the resulting exception will not be very intelligible (JS implementation would suffer from the same thing, but there we have the legacy Maybe validate the input dictionary around the required fields and throw exceptions with explicit messages? Maybe can also stay as it is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added a method to validate that the fields sender, receiver, chainID and gasLimit are not missing |
||
receiver_username=self._bytes_from_b64(dictionary.get("receiverUsername", "")).decode(), | ||
sender=dictionary["sender"], | ||
sender_username=self._bytes_from_b64(dictionary.get("senderUsername", "")).decode(), | ||
guardian=dictionary.get("guardian", None), | ||
gas_price=dictionary.get("gasPrice", None), | ||
gas_limit=dictionary["gasLimit"], | ||
data=self._bytes_from_b64(dictionary.get("data", "")), | ||
chain_id=dictionary["chainID"], | ||
version=dictionary.get("version", None), | ||
options=dictionary.get("options", None), | ||
signature=self._bytes_from_hex(dictionary.get("signature", "")), | ||
guardian_signature=self._bytes_from_hex(dictionary.get("guardianSignature", "")) | ||
) | ||
|
||
def _ensure_mandatory_fields_for_transaction(self, dictionary: Dict[str, Any]) -> None: | ||
sender = dictionary.get("sender", None) | ||
if sender is None: | ||
raise MissingFieldError("The 'sender' key is missing from the dictionary") | ||
|
||
receiver = dictionary.get("receiver", None) | ||
if receiver is None: | ||
raise MissingFieldError("The 'receiver' key is missing from the dictionary") | ||
|
||
chain_id = dictionary.get("chainID", None) | ||
if chain_id is None: | ||
raise MissingFieldError("The 'chainID' key is missing from the dictionary") | ||
|
||
gas_limit = dictionary.get("gasLimit", None) | ||
if gas_limit is None: | ||
raise MissingFieldError("The 'gasLimit' key is missing from the dictionary") | ||
|
||
def _value_to_b64_or_empty(self, value: str | bytes) -> str: | ||
value_as_bytes = value.encode() if isinstance(value, str) else value | ||
|
||
if len(value): | ||
return base64.b64encode(value_as_bytes).decode() | ||
return "" | ||
|
||
def _value_to_hex_or_empty(self, value: bytes) -> str: | ||
if len(value): | ||
return value.hex() | ||
return "" | ||
|
||
def _bytes_from_b64(self, value: str) -> bytes: | ||
if len(value): | ||
return base64.b64decode(value.encode()) | ||
return b"" | ||
|
||
def _bytes_from_hex(self, value: str) -> bytes: | ||
if len(value): | ||
return bytes.fromhex(value) | ||
return b"" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from multiversx_sdk.converters.transactions_converter import \ | ||
TransactionsConverter | ||
from multiversx_sdk.core.transaction import Transaction | ||
|
||
|
||
class TransactionMatcher: | ||
def __init__(self, transaction: Transaction) -> None: | ||
self.expected = transaction | ||
|
||
def __eq__(self, actual: object) -> bool: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting. Maybe we can also promote this as a core functionality (just a question)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I think this is more for testing purposes, but we can think about it. |
||
if isinstance(actual, Transaction): | ||
return self.expected.chain_id == actual.chain_id and \ | ||
self.expected.sender == actual.sender and \ | ||
self.expected.receiver == actual.receiver and \ | ||
self.expected.gas_limit == actual.gas_limit and \ | ||
self.expected.data == actual.data and \ | ||
self.expected.nonce == actual.nonce and \ | ||
self.expected.value == actual.value and \ | ||
self.expected.gas_price == actual.gas_price and \ | ||
self.expected.sender_username == actual.sender_username and \ | ||
self.expected.receiver_username == actual.receiver_username and \ | ||
self.expected.version == actual.version and \ | ||
self.expected.options == actual.options and \ | ||
self.expected.guardian == actual.guardian and \ | ||
self.expected.signature == actual.signature and \ | ||
self.expected.guardian_signature == actual.guardian_signature | ||
return False | ||
|
||
|
||
def test_transaction_converter(): | ||
converter = TransactionsConverter() | ||
|
||
transaction = Transaction( | ||
nonce=90, | ||
value=123456789000000000000000000000, | ||
sender="erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", | ||
receiver="erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", | ||
sender_username="alice", | ||
receiver_username="bob", | ||
gas_price=1000000000, | ||
gas_limit=80000, | ||
data=b"hello", | ||
chain_id="localnet" | ||
) | ||
|
||
tx_as_dict = converter.transaction_to_dictionary(transaction) | ||
restored_tx = converter.dictionary_to_transaction(tx_as_dict) | ||
|
||
assert TransactionMatcher(transaction) == restored_tx |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, easy to welcome breaking change.