Skip to content

Commit

Permalink
relay previously saved transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
popenta committed Nov 25, 2024
1 parent a3e08ce commit ccfb060
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 29 deletions.
21 changes: 18 additions & 3 deletions multiversx_sdk_cli/cli_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,13 @@ def add_guardian_wallet_args(args: List[str], sub: Any):
sub.add_argument("--guardian-ledger-address-index", type=int, default=0, help="🔐 the index of the address when using Ledger")


# Required check not properly working, same for guardian. Will be refactored in the future.
def add_relayed_v3_wallet_args(args: List[str], sub: Any):
sub.add_argument("--relayer-pem", required=check_if_sign_method_required(args, "--relayer-pem"), help="🔑 the PEM file, if keyfile not provided")
sub.add_argument("--relayer-pem", help="🔑 the PEM file, if keyfile not provided")
sub.add_argument("--relayer-pem-index", type=int, default=0, help="🔑 the index in the PEM file (default: %(default)s)")
sub.add_argument("--relayer-keyfile", required=check_if_sign_method_required(args, "--relayer-keyfile"), help="🔑 a JSON keyfile, if PEM not provided")
sub.add_argument("--relayer-keyfile", help="🔑 a JSON keyfile, if PEM not provided")
sub.add_argument("--relayer-passfile", help="🔑 a file containing keyfile's password, if keyfile provided")
sub.add_argument("--relayer-ledger", action="store_true", required=check_if_sign_method_required(args, "--relayer-ledger"), default=False, help="🔐 bool flag for signing transaction using ledger")
sub.add_argument("--relayer-ledger", action="store_true", default=False, help="🔐 bool flag for signing transaction using ledger")
sub.add_argument("--relayer-ledger-account-index", type=int, default=0, help="🔐 the index of the account when using Ledger")
sub.add_argument("--relayer-ledger-address-index", type=int, default=0, help="🔐 the index of the address when using Ledger")

Expand Down Expand Up @@ -181,6 +182,20 @@ def prepare_account(args: Any):
return account


def prepare_relayer_account(args: Any) -> Account:
if args.relayer_ledger:
account = LedgerAccount(account_index=args.relayer_ledger_account_index, address_index=args.relayer_ledger_address_index)
if args.relayer_pem:
account = Account(pem_file=args.relayer_pem, pem_index=args.relayer_pem_index)
elif args.relayer_keyfile:
password = load_password(args)
account = Account(key_file=args.relayer_keyfile, password=password)
else:
raise errors.NoWalletProvided()

return account


def prepare_guardian_account(args: Any):
if args.guardian_pem:
account = Account(pem_file=args.guardian_pem, pem_index=args.guardian_pem_index)
Expand Down
33 changes: 32 additions & 1 deletion multiversx_sdk_cli/cli_transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from multiversx_sdk_cli.cli_output import CLIOutputBuilder
from multiversx_sdk_cli.config import get_config_for_network_providers
from multiversx_sdk_cli.cosign_transaction import cosign_transaction
from multiversx_sdk_cli.errors import NoWalletProvided
from multiversx_sdk_cli.errors import IncorrectWalletError, NoWalletProvided
from multiversx_sdk_cli.transactions import (compute_relayed_v1_data,
do_prepare_transaction,
load_transaction_from_file)
Expand Down Expand Up @@ -57,6 +57,14 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
cli_shared.add_guardian_wallet_args(args, sub)
sub.set_defaults(func=sign_transaction)

sub = cli_shared.add_command_subparser(subparsers, "tx", "relay", f"Relay a previously saved transaction.{CLIOutputBuilder.describe()}")
cli_shared.add_relayed_v3_wallet_args(args, sub)
cli_shared.add_infile_arg(sub, what="a previously saved transaction")
cli_shared.add_outfile_arg(sub, what="the signed transaction")
cli_shared.add_broadcast_args(sub)
cli_shared.add_proxy_arg(sub)
sub.set_defaults(func=relay_transaction)

parser.epilog = cli_shared.build_group_epilog(subparsers)
return subparsers

Expand Down Expand Up @@ -141,3 +149,26 @@ def sign_transaction(args: Any):
tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code)

cli_shared.send_or_simulate(tx, args)


def relay_transaction(args: Any):
args = utils.as_object(args)

if not _is_relayer_wallet_provided(args):
raise NoWalletProvided()

cli_shared.check_broadcast_args(args)

tx = load_transaction_from_file(args.infile)
relayer = cli_shared.prepare_relayer_account(args)

if tx.relayer != relayer.address.to_bech32():
raise IncorrectWalletError("Relayer wallet does not match the relayer's address set in the transaction.")

tx.relayer_signature = bytes.fromhex(relayer.sign_transaction(tx))

cli_shared.send_or_simulate(tx, args)


def _is_relayer_wallet_provided(args: Any):
return any([args.relayer_pem, args.relayer_keyfile, args.relayer_ledger])
5 changes: 5 additions & 0 deletions multiversx_sdk_cli/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,8 @@ def __init__(self, message: str, inner: Any = None):
class NativeAuthClientError(KnownError):
def __init__(self, message: str):
super().__init__(message)


class IncorrectWalletError(KnownError):
def __init__(self, message: str):
super().__init__(message)
93 changes: 71 additions & 22 deletions multiversx_sdk_cli/tests/test_cli_transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
from pathlib import Path
from typing import Any

import pytest

from multiversx_sdk_cli.cli import main

testdata_path = Path(__file__).parent / "testdata"
Expand Down Expand Up @@ -107,7 +105,46 @@ def test_create_multi_transfer_transaction_with_single_egld_transfer(capsys: Any
assert data == "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@01@45474c442d303030303030@@0de0b6b3a7640000"


def test_relayed_v3_without_relayer_wallet(capsys: Any):
return_code = main([
"tx", "new",
"--pem", str(testdata_path / "alice.pem"),
"--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
"--nonce", "7",
"--gas-limit", "1300000",
"--value", "1000000000000000000",
"--chain", "T",
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
])
assert return_code == 0
tx = _read_stdout(capsys)
tx_json = json.loads(tx)["emittedTransaction"]
assert tx_json["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"
assert tx_json["receiver"] == "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"
assert tx_json["relayer"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
assert tx_json["signature"]
assert not tx_json["relayerSignature"]


def test_relayed_v3_incorrect_relayer():
return_code = main([
"tx", "new",
"--pem", str(testdata_path / "alice.pem"),
"--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
"--nonce", "7",
"--gas-limit", "1300000",
"--value", "1000000000000000000",
"--chain", "T",
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5",
"--relayer-pem", str(testdata_path / "alice.pem")
])
assert return_code


def test_create_relayed_v3_transaction(capsys: Any):
# create relayed v3 tx and save signature and relayer signature
# create the same tx, save to file
# sign from file with relayer wallet and make sure signatures match
return_code = main([
"tx", "new",
"--pem", str(testdata_path / "alice.pem"),
Expand All @@ -129,7 +166,13 @@ def test_create_relayed_v3_transaction(capsys: Any):
assert tx_json["signature"]
assert tx_json["relayerSignature"]

# no relayer wallet provided
initial_sender_signature = tx_json["signature"]
initial_relayer_signature = tx_json["relayerSignature"]

# Clear the captured content
capsys.readouterr()

# save tx to file then load and sign tx by relayer
return_code = main([
"tx", "new",
"--pem", str(testdata_path / "alice.pem"),
Expand All @@ -138,30 +181,36 @@ def test_create_relayed_v3_transaction(capsys: Any):
"--gas-limit", "1300000",
"--value", "1000000000000000000",
"--chain", "T",
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5",
"--outfile", str(testdata_out / "relayed.json")
])
assert return_code == 0

# Clear the captured content
capsys.readouterr()

return_code = main([
"tx", "relay",
"--relayer-pem", str(testdata_path / "testUser.pem"),
"--infile", str(testdata_out / "relayed.json")
])
assert return_code == 0

tx = _read_stdout(capsys)
tx_json = json.loads(tx)["emittedTransaction"]
assert tx_json["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"
assert tx_json["receiver"] == "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"
assert tx_json["relayer"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
assert tx_json["signature"]
assert not tx_json["relayerSignature"]
assert tx_json["signature"] == initial_sender_signature
assert tx_json["relayerSignature"] == initial_relayer_signature

# Clear the captured content
capsys.readouterr()

# incorrect relayer wallet
with pytest.raises(Exception, match="Relayer address does not match the provided relayer wallet."):
main([
"tx", "new",
"--pem", str(testdata_path / "alice.pem"),
"--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
"--nonce", "7",
"--gas-limit", "1300000",
"--value", "1000000000000000000",
"--chain", "T",
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5",
"--relayer-pem", str(testdata_path / "alice.pem")
])

def test_check_relayer_wallet_is_provided():
return_code = main([
"tx", "relay",
"--infile", str(testdata_out / "relayed.json")
])
assert return_code


def _read_stdout(capsys: Any) -> str:
Expand Down
8 changes: 5 additions & 3 deletions multiversx_sdk_cli/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from multiversx_sdk_cli.cli_password import (load_guardian_password,
load_password)
from multiversx_sdk_cli.cosign_transaction import cosign_transaction
from multiversx_sdk_cli.errors import NoWalletProvided
from multiversx_sdk_cli.errors import IncorrectWalletError, NoWalletProvided
from multiversx_sdk_cli.interfaces import ITransaction
from multiversx_sdk_cli.ledger.ledger_functions import do_get_ledger_address

Expand Down Expand Up @@ -84,11 +84,13 @@ def do_prepare_transaction(args: Any) -> Transaction:
try:
relayer_account = load_relayer_account_from_args(args)
if relayer_account.address.to_bech32() != tx.relayer:
raise Exception("Relayer address does not match the provided relayer wallet.")
raise IncorrectWalletError("")

tx.relayer_signature = bytes.fromhex(relayer_account.sign_transaction(tx))
except errors.NoWalletProvided:
except NoWalletProvided:
logger.warning("Relayer wallet not provided. Transaction will not be signed by relayer.")
except IncorrectWalletError:
raise IncorrectWalletError("Relayer wallet does not match the relayer's address set in the transaction.")

tx.signature = bytes.fromhex(account.sign_transaction(tx))
tx = sign_tx_by_guardian(args, tx)
Expand Down

0 comments on commit ccfb060

Please sign in to comment.