Skip to content

Commit ccfb060

Browse files
committed
relay previously saved transaction
1 parent a3e08ce commit ccfb060

5 files changed

Lines changed: 131 additions & 29 deletions

File tree

multiversx_sdk_cli/cli_shared.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,13 @@ def add_guardian_wallet_args(args: List[str], sub: Any):
127127
sub.add_argument("--guardian-ledger-address-index", type=int, default=0, help="🔐 the index of the address when using Ledger")
128128

129129

130+
# Required check not properly working, same for guardian. Will be refactored in the future.
130131
def add_relayed_v3_wallet_args(args: List[str], sub: Any):
131-
sub.add_argument("--relayer-pem", required=check_if_sign_method_required(args, "--relayer-pem"), help="🔑 the PEM file, if keyfile not provided")
132+
sub.add_argument("--relayer-pem", help="🔑 the PEM file, if keyfile not provided")
132133
sub.add_argument("--relayer-pem-index", type=int, default=0, help="🔑 the index in the PEM file (default: %(default)s)")
133-
sub.add_argument("--relayer-keyfile", required=check_if_sign_method_required(args, "--relayer-keyfile"), help="🔑 a JSON keyfile, if PEM not provided")
134+
sub.add_argument("--relayer-keyfile", help="🔑 a JSON keyfile, if PEM not provided")
134135
sub.add_argument("--relayer-passfile", help="🔑 a file containing keyfile's password, if keyfile provided")
135-
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")
136+
sub.add_argument("--relayer-ledger", action="store_true", default=False, help="🔐 bool flag for signing transaction using ledger")
136137
sub.add_argument("--relayer-ledger-account-index", type=int, default=0, help="🔐 the index of the account when using Ledger")
137138
sub.add_argument("--relayer-ledger-address-index", type=int, default=0, help="🔐 the index of the address when using Ledger")
138139

@@ -181,6 +182,20 @@ def prepare_account(args: Any):
181182
return account
182183

183184

185+
def prepare_relayer_account(args: Any) -> Account:
186+
if args.relayer_ledger:
187+
account = LedgerAccount(account_index=args.relayer_ledger_account_index, address_index=args.relayer_ledger_address_index)
188+
if args.relayer_pem:
189+
account = Account(pem_file=args.relayer_pem, pem_index=args.relayer_pem_index)
190+
elif args.relayer_keyfile:
191+
password = load_password(args)
192+
account = Account(key_file=args.relayer_keyfile, password=password)
193+
else:
194+
raise errors.NoWalletProvided()
195+
196+
return account
197+
198+
184199
def prepare_guardian_account(args: Any):
185200
if args.guardian_pem:
186201
account = Account(pem_file=args.guardian_pem, pem_index=args.guardian_pem_index)

multiversx_sdk_cli/cli_transactions.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from multiversx_sdk_cli.cli_output import CLIOutputBuilder
99
from multiversx_sdk_cli.config import get_config_for_network_providers
1010
from multiversx_sdk_cli.cosign_transaction import cosign_transaction
11-
from multiversx_sdk_cli.errors import NoWalletProvided
11+
from multiversx_sdk_cli.errors import IncorrectWalletError, NoWalletProvided
1212
from multiversx_sdk_cli.transactions import (compute_relayed_v1_data,
1313
do_prepare_transaction,
1414
load_transaction_from_file)
@@ -57,6 +57,14 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
5757
cli_shared.add_guardian_wallet_args(args, sub)
5858
sub.set_defaults(func=sign_transaction)
5959

60+
sub = cli_shared.add_command_subparser(subparsers, "tx", "relay", f"Relay a previously saved transaction.{CLIOutputBuilder.describe()}")
61+
cli_shared.add_relayed_v3_wallet_args(args, sub)
62+
cli_shared.add_infile_arg(sub, what="a previously saved transaction")
63+
cli_shared.add_outfile_arg(sub, what="the signed transaction")
64+
cli_shared.add_broadcast_args(sub)
65+
cli_shared.add_proxy_arg(sub)
66+
sub.set_defaults(func=relay_transaction)
67+
6068
parser.epilog = cli_shared.build_group_epilog(subparsers)
6169
return subparsers
6270

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

143151
cli_shared.send_or_simulate(tx, args)
152+
153+
154+
def relay_transaction(args: Any):
155+
args = utils.as_object(args)
156+
157+
if not _is_relayer_wallet_provided(args):
158+
raise NoWalletProvided()
159+
160+
cli_shared.check_broadcast_args(args)
161+
162+
tx = load_transaction_from_file(args.infile)
163+
relayer = cli_shared.prepare_relayer_account(args)
164+
165+
if tx.relayer != relayer.address.to_bech32():
166+
raise IncorrectWalletError("Relayer wallet does not match the relayer's address set in the transaction.")
167+
168+
tx.relayer_signature = bytes.fromhex(relayer.sign_transaction(tx))
169+
170+
cli_shared.send_or_simulate(tx, args)
171+
172+
173+
def _is_relayer_wallet_provided(args: Any):
174+
return any([args.relayer_pem, args.relayer_keyfile, args.relayer_ledger])

multiversx_sdk_cli/errors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,8 @@ def __init__(self, message: str, inner: Any = None):
182182
class NativeAuthClientError(KnownError):
183183
def __init__(self, message: str):
184184
super().__init__(message)
185+
186+
187+
class IncorrectWalletError(KnownError):
188+
def __init__(self, message: str):
189+
super().__init__(message)

multiversx_sdk_cli/tests/test_cli_transactions.py

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
from pathlib import Path
33
from typing import Any
44

5-
import pytest
6-
75
from multiversx_sdk_cli.cli import main
86

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

109107

108+
def test_relayed_v3_without_relayer_wallet(capsys: Any):
109+
return_code = main([
110+
"tx", "new",
111+
"--pem", str(testdata_path / "alice.pem"),
112+
"--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
113+
"--nonce", "7",
114+
"--gas-limit", "1300000",
115+
"--value", "1000000000000000000",
116+
"--chain", "T",
117+
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
118+
])
119+
assert return_code == 0
120+
tx = _read_stdout(capsys)
121+
tx_json = json.loads(tx)["emittedTransaction"]
122+
assert tx_json["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"
123+
assert tx_json["receiver"] == "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"
124+
assert tx_json["relayer"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
125+
assert tx_json["signature"]
126+
assert not tx_json["relayerSignature"]
127+
128+
129+
def test_relayed_v3_incorrect_relayer():
130+
return_code = main([
131+
"tx", "new",
132+
"--pem", str(testdata_path / "alice.pem"),
133+
"--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
134+
"--nonce", "7",
135+
"--gas-limit", "1300000",
136+
"--value", "1000000000000000000",
137+
"--chain", "T",
138+
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5",
139+
"--relayer-pem", str(testdata_path / "alice.pem")
140+
])
141+
assert return_code
142+
143+
110144
def test_create_relayed_v3_transaction(capsys: Any):
145+
# create relayed v3 tx and save signature and relayer signature
146+
# create the same tx, save to file
147+
# sign from file with relayer wallet and make sure signatures match
111148
return_code = main([
112149
"tx", "new",
113150
"--pem", str(testdata_path / "alice.pem"),
@@ -129,7 +166,13 @@ def test_create_relayed_v3_transaction(capsys: Any):
129166
assert tx_json["signature"]
130167
assert tx_json["relayerSignature"]
131168

132-
# no relayer wallet provided
169+
initial_sender_signature = tx_json["signature"]
170+
initial_relayer_signature = tx_json["relayerSignature"]
171+
172+
# Clear the captured content
173+
capsys.readouterr()
174+
175+
# save tx to file then load and sign tx by relayer
133176
return_code = main([
134177
"tx", "new",
135178
"--pem", str(testdata_path / "alice.pem"),
@@ -138,30 +181,36 @@ def test_create_relayed_v3_transaction(capsys: Any):
138181
"--gas-limit", "1300000",
139182
"--value", "1000000000000000000",
140183
"--chain", "T",
141-
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
184+
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5",
185+
"--outfile", str(testdata_out / "relayed.json")
186+
])
187+
assert return_code == 0
188+
189+
# Clear the captured content
190+
capsys.readouterr()
191+
192+
return_code = main([
193+
"tx", "relay",
194+
"--relayer-pem", str(testdata_path / "testUser.pem"),
195+
"--infile", str(testdata_out / "relayed.json")
142196
])
143197
assert return_code == 0
198+
144199
tx = _read_stdout(capsys)
145200
tx_json = json.loads(tx)["emittedTransaction"]
146-
assert tx_json["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"
147-
assert tx_json["receiver"] == "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"
148-
assert tx_json["relayer"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
149-
assert tx_json["signature"]
150-
assert not tx_json["relayerSignature"]
201+
assert tx_json["signature"] == initial_sender_signature
202+
assert tx_json["relayerSignature"] == initial_relayer_signature
203+
204+
# Clear the captured content
205+
capsys.readouterr()
151206

152-
# incorrect relayer wallet
153-
with pytest.raises(Exception, match="Relayer address does not match the provided relayer wallet."):
154-
main([
155-
"tx", "new",
156-
"--pem", str(testdata_path / "alice.pem"),
157-
"--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
158-
"--nonce", "7",
159-
"--gas-limit", "1300000",
160-
"--value", "1000000000000000000",
161-
"--chain", "T",
162-
"--relayer", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5",
163-
"--relayer-pem", str(testdata_path / "alice.pem")
164-
])
207+
208+
def test_check_relayer_wallet_is_provided():
209+
return_code = main([
210+
"tx", "relay",
211+
"--infile", str(testdata_out / "relayed.json")
212+
])
213+
assert return_code
165214

166215

167216
def _read_stdout(capsys: Any) -> str:

multiversx_sdk_cli/transactions.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from multiversx_sdk_cli.cli_password import (load_guardian_password,
1515
load_password)
1616
from multiversx_sdk_cli.cosign_transaction import cosign_transaction
17-
from multiversx_sdk_cli.errors import NoWalletProvided
17+
from multiversx_sdk_cli.errors import IncorrectWalletError, NoWalletProvided
1818
from multiversx_sdk_cli.interfaces import ITransaction
1919
from multiversx_sdk_cli.ledger.ledger_functions import do_get_ledger_address
2020

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

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

9395
tx.signature = bytes.fromhex(account.sign_transaction(tx))
9496
tx = sign_tx_by_guardian(args, tx)

0 commit comments

Comments
 (0)