Skip to content

Commit

Permalink
Merge pull request #83 from multiversx/non-breaking-feat-next-10-07
Browse files Browse the repository at this point in the history
Merge feat/next without breaking changes into main
  • Loading branch information
popenta committed Jul 10, 2024
2 parents f35b2c4 + 4ece73e commit 154376e
Show file tree
Hide file tree
Showing 17 changed files with 304 additions and 103 deletions.
97 changes: 51 additions & 46 deletions examples/Cookbook.ipynb

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions multiversx_sdk/converters/transactions_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ def transaction_to_dictionary(self, transaction: ITransaction) -> Dict[str, Any]
"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)
"guardianSignature": self._value_to_hex_or_empty(transaction.guardian_signature),
"relayer": transaction.relayer,
"innerTransactions": [self.transaction_to_dictionary(inner_tx) for inner_tx in transaction.inner_transactions]
}

def dictionary_to_transaction(self, dictionary: Dict[str, Any]) -> Transaction:
Expand All @@ -54,7 +56,9 @@ def dictionary_to_transaction(self, dictionary: Dict[str, Any]) -> Transaction:
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", ""))
guardian_signature=self._bytes_from_hex(dictionary.get("guardianSignature", "")),
relayer=dictionary.get("relayer", None),
inner_transactions=[self.dictionary_to_transaction(inner_tx) for inner_tx in dictionary.get("innerTransactions", [])],
)

def transaction_on_network_to_outcome(self, transaction_on_network: TransactionOnNetwork) -> TransactionOutcome:
Expand Down
57 changes: 32 additions & 25 deletions multiversx_sdk/converters/transactions_converter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,6 @@
from multiversx_sdk.network_providers.transactions import TransactionOnNetwork


class TransactionMatcher:
def __init__(self, transaction: Transaction) -> None:
self.expected = transaction

def __eq__(self, actual: object) -> bool:
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()

Expand All @@ -64,7 +40,38 @@ def test_transaction_converter():
tx_as_dict = converter.transaction_to_dictionary(transaction)
restored_tx = converter.dictionary_to_transaction(tx_as_dict)

assert TransactionMatcher(transaction) == restored_tx
assert transaction == restored_tx


def test_transaction_from_dictionary_with_inner_transaction():
converter = TransactionsConverter()

inner_transaction = Transaction(
nonce=90,
value=123456789000000000000000000000,
sender="erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
receiver="erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
sender_username="alice",
receiver_username="bob",
gas_limit=80000,
data=b"hello",
chain_id="localnet",
relayer="erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"
)

relayed_transaction = Transaction(
nonce=77,
sender="erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8",
receiver="erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8",
gas_limit=180000,
chain_id="localnet",
inner_transactions=[inner_transaction]
)

tx_as_dict = converter.transaction_to_dictionary(relayed_transaction)
restored_tx = converter.dictionary_to_transaction(tx_as_dict)

assert relayed_transaction == restored_tx


def test_convert_tx_on_network_to_outcome():
Expand Down
7 changes: 6 additions & 1 deletion multiversx_sdk/core/interfaces.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, Protocol
from typing import Optional, Protocol, Sequence


class IAddress(Protocol):
Expand All @@ -25,6 +25,11 @@ class ITransaction(Protocol):
guardian: str
signature: bytes
guardian_signature: bytes
relayer: str

@property
def inner_transactions(self) -> Sequence["ITransaction"]:
...


class IMessage(Protocol):
Expand Down
2 changes: 2 additions & 0 deletions multiversx_sdk/core/proto/transaction.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ message Transaction {
uint32 Options = 13;
bytes GuardAddr = 14;
bytes GuardSignature = 15;
bytes Relayer = 16;
repeated Transaction InnerTransactions = 17;
}
4 changes: 2 additions & 2 deletions multiversx_sdk/core/proto/transaction_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions multiversx_sdk/core/proto/transaction_pb2.pyi
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from google.protobuf.internal import containers as _containers
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Optional as _Optional
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union

DESCRIPTOR: _descriptor.FileDescriptor

class Transaction(_message.Message):
__slots__ = ["ChainID", "Data", "GasLimit", "GasPrice", "GuardAddr", "GuardSignature", "Nonce", "Options", "RcvAddr", "RcvUserName", "Signature", "SndAddr", "SndUserName", "Value", "Version"]
__slots__ = ["ChainID", "Data", "GasLimit", "GasPrice", "GuardAddr", "GuardSignature", "InnerTransactions", "Nonce", "Options", "RcvAddr", "RcvUserName", "Relayer", "Signature", "SndAddr", "SndUserName", "Value", "Version"]
CHAINID_FIELD_NUMBER: _ClassVar[int]
ChainID: bytes
DATA_FIELD_NUMBER: _ClassVar[int]
Expand All @@ -18,14 +19,18 @@ class Transaction(_message.Message):
GasPrice: int
GuardAddr: bytes
GuardSignature: bytes
INNERTRANSACTIONS_FIELD_NUMBER: _ClassVar[int]
InnerTransactions: _containers.RepeatedCompositeFieldContainer[Transaction]
NONCE_FIELD_NUMBER: _ClassVar[int]
Nonce: int
OPTIONS_FIELD_NUMBER: _ClassVar[int]
Options: int
RCVADDR_FIELD_NUMBER: _ClassVar[int]
RCVUSERNAME_FIELD_NUMBER: _ClassVar[int]
RELAYER_FIELD_NUMBER: _ClassVar[int]
RcvAddr: bytes
RcvUserName: bytes
Relayer: bytes
SIGNATURE_FIELD_NUMBER: _ClassVar[int]
SNDADDR_FIELD_NUMBER: _ClassVar[int]
SNDUSERNAME_FIELD_NUMBER: _ClassVar[int]
Expand All @@ -36,4 +41,4 @@ class Transaction(_message.Message):
VERSION_FIELD_NUMBER: _ClassVar[int]
Value: bytes
Version: int
def __init__(self, Nonce: _Optional[int] = ..., Value: _Optional[bytes] = ..., RcvAddr: _Optional[bytes] = ..., RcvUserName: _Optional[bytes] = ..., SndAddr: _Optional[bytes] = ..., SndUserName: _Optional[bytes] = ..., GasPrice: _Optional[int] = ..., GasLimit: _Optional[int] = ..., Data: _Optional[bytes] = ..., ChainID: _Optional[bytes] = ..., Version: _Optional[int] = ..., Signature: _Optional[bytes] = ..., Options: _Optional[int] = ..., GuardAddr: _Optional[bytes] = ..., GuardSignature: _Optional[bytes] = ...) -> None: ...
def __init__(self, Nonce: _Optional[int] = ..., Value: _Optional[bytes] = ..., RcvAddr: _Optional[bytes] = ..., RcvUserName: _Optional[bytes] = ..., SndAddr: _Optional[bytes] = ..., SndUserName: _Optional[bytes] = ..., GasPrice: _Optional[int] = ..., GasLimit: _Optional[int] = ..., Data: _Optional[bytes] = ..., ChainID: _Optional[bytes] = ..., Version: _Optional[int] = ..., Signature: _Optional[bytes] = ..., Options: _Optional[int] = ..., GuardAddr: _Optional[bytes] = ..., GuardSignature: _Optional[bytes] = ..., Relayer: _Optional[bytes] = ..., InnerTransactions: _Optional[_Iterable[_Union[Transaction, _Mapping]]] = ...) -> None: ... # pyright: ignore
45 changes: 30 additions & 15 deletions multiversx_sdk/core/proto/transaction_serializer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Protocol
from typing import Protocol, Sequence

import multiversx_sdk.core.proto.transaction_pb2 as ProtoTransaction
from multiversx_sdk.core.address import Address
Expand All @@ -10,24 +10,44 @@ class ITransaction(Protocol):
receiver: str
gas_limit: int
chain_id: str
gas_price: int
sender_username: str
receiver_username: str
nonce: int
value: int
sender_username: str
receiver_username: str
gas_price: int
data: bytes
version: int
signature: bytes
options: int
guardian: str
signature: bytes
guardian_signature: bytes
relayer: str

@property
def inner_transactions(self) -> Sequence["ITransaction"]:
...


class ProtoSerializer:
def __init__(self) -> None:
pass

def serialize_transaction(self, transaction: ITransaction) -> bytes:
proto_transaction = self.convert_to_proto_message(transaction)

encoded_tx: bytes = proto_transaction.SerializeToString()
return encoded_tx

def serialize_transaction_value(self, tx_value: int):
if tx_value == 0:
return bytes([0, 0])

buffer = encode_unsigned_number(tx_value)
buffer = bytes([0x00]) + buffer

return buffer

def convert_to_proto_message(self, transaction: ITransaction) -> ProtoTransaction.Transaction:
receiver_pubkey = Address.new_from_bech32(transaction.receiver).get_public_key()
sender_pubkey = Address.new_from_bech32(transaction.sender).get_public_key()

Expand All @@ -51,15 +71,10 @@ def serialize_transaction(self, transaction: ITransaction) -> bytes:
proto_transaction.GuardAddr = Address.new_from_bech32(guardian_address).get_public_key()
proto_transaction.GuardSignature = transaction.guardian_signature

encoded_tx: bytes = proto_transaction.SerializeToString()

return encoded_tx

def serialize_transaction_value(self, tx_value: int):
if tx_value == 0:
return bytes([0, 0])
if transaction.relayer != "":
proto_transaction.Relayer = Address.new_from_bech32(transaction.relayer).get_public_key()

buffer = encode_unsigned_number(tx_value)
buffer = bytes([0x00]) + buffer
proto_transaction.InnerTransactions.extend(
[self.convert_to_proto_message(inner_tx) for inner_tx in transaction.inner_transactions])

return buffer
return proto_transaction
31 changes: 31 additions & 0 deletions multiversx_sdk/core/proto/transaction_serializer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,34 @@ def test_serialized_tx_with_usernames(self):

serialized_transaction = self.proto_serializer.serialize_transaction(transaction)
assert serialized_transaction.hex() == "08cc011209000de0b6b3a76400001a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e12205616c6963652a20b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba32056361726f6c388094ebdc0340d086035201545802624051e6cd78fb3ab4b53ff7ad6864df27cb4a56d70603332869d47a5cf6ea977c30e696103e41e8dddf2582996ad335229fdf4acb726564dbc1a0bc9e705b511f06"

def test_serialized_tx_with_inner_txs(self):
inner_transaction = Transaction(
sender=self.carol.label,
receiver=self.alice.label,
gas_limit=50000,
chain_id="T",
nonce=204,
value=1000000000000000000,
sender_username="carol",
receiver_username="alice"
)
inner_transaction.signature = self.carol.secret_key.sign(self.transaction_computer.compute_bytes_for_signing(inner_transaction))

relayed_transaction = Transaction(
sender=self.carol.label,
receiver=self.alice.label,
gas_limit=50000,
chain_id="T",
nonce=204,
value=1000000000000000000,
sender_username="carol",
receiver_username="alice",
relayer=self.carol.label,
inner_transactions=[inner_transaction]
)

relayed_transaction.signature = self.carol.secret_key.sign(self.transaction_computer.compute_bytes_for_signing(
relayed_transaction))
serialized_transaction = self.proto_serializer.serialize_transaction(relayed_transaction)
assert serialized_transaction.hex() == "08cc011209000de0b6b3a76400001a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e12205616c6963652a20b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba32056361726f6c388094ebdc0340d0860352015458026240901a6a974d6ab36546e7881c6e0364ec4c61a891aa70e5eb60f818d6c92a39cfa0beac6fab73f503853cfe8fe6149b4be207ddb93788f8450d75a07fa8759d06820120b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba8a01b10108cc011209000de0b6b3a76400001a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e12205616c6963652a20b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba32056361726f6c388094ebdc0340d086035201545802624051e6cd78fb3ab4b53ff7ad6864df27cb4a56d70603332869d47a5cf6ea977c30e696103e41e8dddf2582996ad335229fdf4acb726564dbc1a0bc9e705b511f06"
9 changes: 7 additions & 2 deletions multiversx_sdk/core/transaction.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import Optional
from typing import Optional, Sequence

from multiversx_sdk.core.constants import (TRANSACTION_MIN_GAS_PRICE,
TRANSACTION_OPTIONS_DEFAULT,
TRANSACTION_VERSION_DEFAULT)
from multiversx_sdk.core.interfaces import ITransaction


class Transaction:
Expand All @@ -21,7 +22,9 @@ def __init__(self,
options: Optional[int] = None,
guardian: Optional[str] = None,
signature: Optional[bytes] = None,
guardian_signature: Optional[bytes] = None) -> None:
guardian_signature: Optional[bytes] = None,
relayer: Optional[str] = None,
inner_transactions: Optional[Sequence[ITransaction]] = None) -> None:
self.chain_id = chain_id
self.sender = sender
self.receiver = receiver
Expand All @@ -41,6 +44,8 @@ def __init__(self,

self.guardian = guardian or ""
self.guardian_signature = guardian_signature or bytes()
self.relayer = relayer or ""
self.inner_transactions = inner_transactions or []

def __eq__(self, other: object) -> bool:
if not isinstance(other, Transaction):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,4 @@ def _prepare_inner_transaction(self) -> str:
tx[f"rcvUserName"] = base64.b64encode(self.inner_transaction.receiver_username.encode()).decode()

return json.dumps(tx, separators=(",", ":"))

13 changes: 12 additions & 1 deletion multiversx_sdk/core/transaction_computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def _ensure_fields(self, transaction: ITransaction) -> None:
if self.has_options_set_for_guarded_transaction(transaction) or self.has_options_set_for_hash_signing(transaction):
raise BadUsageError(f"Non-empty transaction options requires transaction version >= {MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS}")

def _to_dictionary(self, transaction: ITransaction) -> Dict[str, Any]:
def _to_dictionary(self, transaction: ITransaction, with_signature: bool = False) -> Dict[str, Any]:
dictionary: Dict[str, Any] = OrderedDict()
dictionary["nonce"] = transaction.nonce
dictionary["value"] = str(transaction.value)
Expand All @@ -111,6 +111,10 @@ def _to_dictionary(self, transaction: ITransaction) -> Dict[str, Any]:
if transaction.data:
dictionary["data"] = b64encode(transaction.data).decode()

if with_signature:
if transaction.signature:
dictionary["signature"] = transaction.signature.hex()

dictionary["chainID"] = transaction.chain_id

if transaction.version:
Expand All @@ -122,6 +126,13 @@ def _to_dictionary(self, transaction: ITransaction) -> Dict[str, Any]:
if transaction.guardian:
dictionary["guardian"] = transaction.guardian

if transaction.relayer:
dictionary["relayer"] = transaction.relayer

if len(transaction.inner_transactions):
dictionary["innerTransactions"] = \
[self._to_dictionary(transaction=tx, with_signature=True) for tx in transaction.inner_transactions]

return dictionary

def _dict_to_json(self, dictionary: Dict[str, Any]) -> bytes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ def create_transaction_for_unstaking_nodes(self,
def create_transaction_for_unjailing_nodes(self,
sender: IAddress,
delegation_contract: IAddress,
public_keys: Sequence[IValidatorPublicKey]) -> Transaction:
public_keys: Sequence[IValidatorPublicKey],
amount: int) -> Transaction:
num_nodes = len(public_keys)

parts = ["unJailNodes"]
Expand All @@ -179,7 +180,8 @@ def create_transaction_for_unjailing_nodes(self,
receiver=delegation_contract,
data_parts=parts,
gas_limit=self._compute_execution_gas_limit_for_nodes_management(num_nodes),
add_data_movement_gas=True
add_data_movement_gas=True,
amount=amount
).build()

return transaction
Expand Down
Loading

0 comments on commit 154376e

Please sign in to comment.