Skip to content

Commit fe977af

Browse files
authored
Add functions to automatically add required signers and validity range (#179)
* Add functions to automatically add required signers and validity range * Filter out signatures of script hashes * Fix implementation of last slot no in fixed chain context * Adjust test cases * Formatting * Fix types * prevent negative validity start * Update txbuilder to only add signatures and validity when building smart transactions * Use all input_addresses instead of actually selected inputs * Require auto_required_signers to be set to True or None * Correctly transform to Address * Use _input_vkey_hashes * Use _input_vkey_hashes * Remove input_addresses from tx_builder required signers * Move signer key hash addition to later
1 parent cf41ba1 commit fe977af

File tree

3 files changed

+89
-11
lines changed

3 files changed

+89
-11
lines changed

pycardano/txbuilder.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,9 @@ def build(
876876
change_address: Optional[Address] = None,
877877
merge_change: Optional[bool] = False,
878878
collateral_change_address: Optional[Address] = None,
879+
auto_validity_start_offset: Optional[int] = None,
880+
auto_ttl_offset: Optional[int] = None,
881+
auto_required_signers: Optional[bool] = None,
879882
) -> TransactionBody:
880883
"""Build a transaction body from all constraints set through the builder.
881884
@@ -885,11 +888,41 @@ def build(
885888
merge_change (Optional[bool]): If the change address match one of the transaction output, the change amount
886889
will be directly added to that transaction output, instead of being added as a separate output.
887890
collateral_change_address (Optional[Address]): Address to which collateral changes will be returned.
891+
auto_validity_start_offset (Optional[int]): Automatically set the validity start interval of the transaction
892+
to the current slot number + the given offset (default -1000).
893+
A manually set validity start will always take precedence.
894+
auto_ttl_offset (Optional[int]): Automatically set the validity end interval (ttl) of the transaction
895+
to the current slot number + the given offset (default 10_000).
896+
A manually set ttl will always take precedence.
897+
auto_required_signers (Optional[bool]): Automatically add all pubkeyhashes of transaction inputs
898+
to required signatories (default only for Smart Contract transactions).
899+
Manually set required signers will always take precedence.
888900
889901
Returns:
890902
TransactionBody: A transaction body.
891903
"""
892904
self._ensure_no_input_exclusion_conflict()
905+
906+
# only automatically set the validity interval and required signers if scripts are involved
907+
is_smart = bool(self.scripts)
908+
909+
# Automatically set the validity range to a tight value around transaction creation
910+
if (
911+
is_smart or auto_validity_start_offset is not None
912+
) and self.validity_start is None:
913+
last_slot = self.context.last_block_slot
914+
# If None is provided, the default value is -1000
915+
if auto_validity_start_offset is None:
916+
auto_validity_start_offset = -1000
917+
self.validity_start = max(0, last_slot + auto_validity_start_offset)
918+
919+
if (is_smart or auto_ttl_offset is not None) and self.ttl is None:
920+
last_slot = self.context.last_block_slot
921+
# If None is provided, the default value is 10_000
922+
if auto_ttl_offset is None:
923+
auto_ttl_offset = 10_000
924+
self.ttl = max(0, last_slot + auto_ttl_offset)
925+
893926
selected_utxos = []
894927
selected_amount = Value()
895928
for i in self.inputs:
@@ -1021,6 +1054,14 @@ def build(
10211054

10221055
self.inputs[:] = selected_utxos[:]
10231056

1057+
# Automatically set the required signers for smart transactions
1058+
if (
1059+
is_smart and auto_required_signers is not False
1060+
) and self.required_signers is None:
1061+
# Collect all signatories from explicitly defined
1062+
# transaction inputs and collateral inputs, and input addresses
1063+
self.required_signers = list(self._input_vkey_hashes())
1064+
10241065
self._set_redeemer_index()
10251066

10261067
self._set_collateral_return(collateral_change_address or change_address)
@@ -1175,6 +1216,9 @@ def build_and_sign(
11751216
change_address: Optional[Address] = None,
11761217
merge_change: Optional[bool] = False,
11771218
collateral_change_address: Optional[Address] = None,
1219+
auto_validity_start_offset: Optional[int] = None,
1220+
auto_ttl_offset: Optional[int] = None,
1221+
auto_required_signers: Optional[bool] = None,
11781222
) -> Transaction:
11791223
"""Build a transaction body from all constraints set through the builder and sign the transaction with
11801224
provided signing keys.
@@ -1187,6 +1231,15 @@ def build_and_sign(
11871231
merge_change (Optional[bool]): If the change address match one of the transaction output, the change amount
11881232
will be directly added to that transaction output, instead of being added as a separate output.
11891233
collateral_change_address (Optional[Address]): Address to which collateral changes will be returned.
1234+
auto_validity_start_offset (Optional[int]): Automatically set the validity start interval of the transaction
1235+
to the current slot number + the given offset (default -1000).
1236+
A manually set validity start will always take precedence.
1237+
auto_ttl_offset (Optional[int]): Automatically set the validity end interval (ttl) of the transaction
1238+
to the current slot number + the given offset (default 10_000).
1239+
A manually set ttl will always take precedence.
1240+
auto_required_signers (Optional[bool]): Automatically add all pubkeyhashes of transaction inputs
1241+
to required signatories (default only for Smart Contract transactions).
1242+
Manually set required signers will always take precedence.
11901243
11911244
Returns:
11921245
Transaction: A signed transaction.
@@ -1196,6 +1249,9 @@ def build_and_sign(
11961249
change_address=change_address,
11971250
merge_change=merge_change,
11981251
collateral_change_address=collateral_change_address,
1252+
auto_validity_start_offset=auto_validity_start_offset,
1253+
auto_ttl_offset=auto_ttl_offset,
1254+
auto_required_signers=auto_required_signers,
11991255
)
12001256
witness_set = self.build_witness_set()
12011257
witness_set.vkey_witnesses = []

test/pycardano/test_txbuilder.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@ def test_tx_builder_raises_utxo_selection(chain_context):
178178
)
179179

180180
with pytest.raises(UTxOSelectionException) as e:
181-
tx_body = tx_builder.build(change_address=sender_address)
181+
tx_body = tx_builder.build(
182+
change_address=sender_address,
183+
)
182184

183185
# The unfulfilled amount includes requested (991000000) and estimated fees (161277)
184186
assert "Unfulfilled amount:\n {\n 'coin': 991161277" in e.value.args[0]
@@ -301,7 +303,7 @@ def test_tx_builder_mint_multi_asset(chain_context):
301303

302304
tx_builder = TransactionBuilder(chain_context)
303305
sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x"
304-
sender_address = Address.from_primitive(sender)
306+
sender_address: Address = Address.from_primitive(sender)
305307

306308
# Add sender address as input
307309
mint = {policy_id.payload: {b"Token1": 1}}
@@ -328,14 +330,16 @@ def test_tx_builder_mint_multi_asset(chain_context):
328330
[
329331
sender_address.to_primitive(),
330332
[
331-
5811267,
333+
5809683,
332334
{b"1111111111111111111111111111": {b"Token1": 1, b"Token2": 2}},
333335
],
334336
],
335337
],
336-
2: 188733,
338+
2: 190317,
337339
3: 123456789,
340+
8: 1000,
338341
9: mint,
342+
14: [sender_address.payment_part.to_primitive()],
339343
}
340344

341345
assert expected == tx_body.to_primitive()
@@ -806,7 +810,10 @@ def test_build_and_sign(chain_context):
806810
tx_builder2.add_input_address(sender).add_output(
807811
TransactionOutput.from_primitive([sender, 500000])
808812
)
809-
tx = tx_builder2.build_and_sign([SK], change_address=sender_address)
813+
tx = tx_builder2.build_and_sign(
814+
[SK],
815+
change_address=sender_address,
816+
)
810817

811818
assert tx.transaction_witness_set.vkey_witnesses == [
812819
VerificationKeyWitness(SK.to_verification_key(), SK.sign(tx_body.hash()))
@@ -981,7 +988,10 @@ def test_tx_builder_no_output(chain_context):
981988

982989
tx_builder.add_input(utxo1)
983990

984-
tx_body = tx_builder.build(change_address=sender_address, merge_change=True)
991+
tx_body = tx_builder.build(
992+
change_address=sender_address,
993+
merge_change=True,
994+
)
985995

986996
expected = {
987997
0: [[b"11111111111111111111111111111111", 3]],
@@ -1008,7 +1018,10 @@ def test_tx_builder_merge_change_to_output(chain_context):
10081018
tx_builder.add_input(utxo1)
10091019
tx_builder.add_output(TransactionOutput.from_primitive([sender, 10000]))
10101020

1011-
tx_body = tx_builder.build(change_address=sender_address, merge_change=True)
1021+
tx_body = tx_builder.build(
1022+
change_address=sender_address,
1023+
merge_change=True,
1024+
)
10121025

10131026
expected = {
10141027
0: [[b"11111111111111111111111111111111", 3]],
@@ -1039,7 +1052,10 @@ def test_tx_builder_merge_change_to_output_2(chain_context):
10391052
tx_builder.add_output(TransactionOutput.from_primitive([receiver, 10000]))
10401053
tx_builder.add_output(TransactionOutput.from_primitive([sender, 0]))
10411054

1042-
tx_body = tx_builder.build(change_address=sender_address, merge_change=True)
1055+
tx_body = tx_builder.build(
1056+
change_address=sender_address,
1057+
merge_change=True,
1058+
)
10431059

10441060
expected = {
10451061
0: [[b"11111111111111111111111111111111", 3]],
@@ -1068,7 +1084,10 @@ def test_tx_builder_merge_change_to_zero_amount_output(chain_context):
10681084
tx_builder.add_input(utxo1)
10691085
tx_builder.add_output(TransactionOutput.from_primitive([sender, 0]))
10701086

1071-
tx_body = tx_builder.build(change_address=sender_address, merge_change=True)
1087+
tx_body = tx_builder.build(
1088+
change_address=sender_address,
1089+
merge_change=True,
1090+
)
10721091

10731092
expected = {
10741093
0: [[b"11111111111111111111111111111111", 3]],
@@ -1095,7 +1114,10 @@ def test_tx_builder_merge_change_smaller_than_min_utxo(chain_context):
10951114
tx_builder.add_input(utxo1)
10961115
tx_builder.add_output(TransactionOutput.from_primitive([sender, 9800000]))
10971116

1098-
tx_body = tx_builder.build(change_address=sender_address, merge_change=True)
1117+
tx_body = tx_builder.build(
1118+
change_address=sender_address,
1119+
merge_change=True,
1120+
)
10991121

11001122
expected = {
11011123
0: [[b"11111111111111111111111111111111", 3]],

test/pycardano/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def epoch(self) -> int:
9090
return 300
9191

9292
@property
93-
def slot(self) -> int:
93+
def last_block_slot(self) -> int:
9494
"""Current slot number"""
9595
return 2000
9696

0 commit comments

Comments
 (0)