Skip to content

Commit 75ce116

Browse files
committed
fix: update TopicUpdateTransaction to use Key instead of PublicKey for key parameters
Signed-off-by: oGranny <ogranny.github.io@gmail.com>
1 parent 79b34a7 commit 75ce116

2 files changed

Lines changed: 173 additions & 24 deletions

File tree

src/hiero_sdk_python/consensus/topic_update_transaction.py

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from hiero_sdk_python.account.account_id import AccountId
1111
from hiero_sdk_python.channels import _Channel
1212
from hiero_sdk_python.consensus.topic_id import TopicId
13-
from hiero_sdk_python.crypto.public_key import PublicKey
13+
from hiero_sdk_python.crypto.key import Key
1414
from hiero_sdk_python.Duration import Duration
1515
from hiero_sdk_python.executable import _Method
1616
from hiero_sdk_python.hapi.services import consensus_update_topic_pb2, duration_pb2, timestamp_pb2, transaction_pb2
@@ -21,6 +21,7 @@
2121
from hiero_sdk_python.timestamp import Timestamp
2222
from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee
2323
from hiero_sdk_python.transaction.transaction import Transaction
24+
from hiero_sdk_python.utils.key_utils import key_to_proto
2425

2526

2627
class TopicUpdateTransaction(Transaction):
@@ -30,39 +31,39 @@ def __init__(
3031
self,
3132
topic_id: TopicId | None = None,
3233
memo: str | None = None,
33-
admin_key: PublicKey | None = None,
34-
submit_key: PublicKey | None = None,
34+
admin_key: Key | None = None,
35+
submit_key: Key | None = None,
3536
auto_renew_period: Duration | None = Duration(7890000),
3637
auto_renew_account: AccountId | None = None,
3738
expiration_time: Timestamp | None = None,
3839
custom_fees: list[CustomFixedFee] | None = None,
39-
fee_schedule_key: PublicKey | None = None,
40-
fee_exempt_keys: list[PublicKey] | None = None,
40+
fee_schedule_key: Key | None = None,
41+
fee_exempt_keys: list[Key] | None = None,
4142
) -> None:
4243
"""
4344
Initializes a new instance of the TopicUpdateTransaction class.
4445
4546
Args:
4647
topic_id (TopicId): The ID of the topic to update.
4748
memo (str): The memo associated with the topic.
48-
admin_key (PublicKey): The admin key for the topic.
49-
submit_key (PublicKey): The submit key for the topic.
49+
admin_key (Key): The admin key for the topic.
50+
submit_key (Key): The submit key for the topic.
5051
auto_renew_period (Duration): The auto-renew period for the topic.
5152
auto_renew_account (AccountId): The account ID for auto-renewal.
5253
expiration_time (Timestamp): The expiration time of the topic.
5354
"""
5455
super().__init__()
5556
self.topic_id: TopicId | None = topic_id
5657
self.memo: str = memo or ""
57-
self.admin_key: PublicKey | None = admin_key
58-
self.submit_key: PublicKey | None = submit_key
58+
self.admin_key: Key | None = admin_key
59+
self.submit_key: Key | None = submit_key
5960
self.auto_renew_period: Duration | None = auto_renew_period
6061
self.auto_renew_account: AccountId | None = auto_renew_account
6162
self.expiration_time: Timestamp | None = expiration_time
6263
self.transaction_fee: int = 10_000_000
6364
self.custom_fees: list[CustomFixedFee] | None = custom_fees
64-
self.fee_schedule_key: PublicKey | None = fee_schedule_key
65-
self.fee_exempt_keys: list[PublicKey] | None = fee_exempt_keys
65+
self.fee_schedule_key: Key | None = fee_schedule_key
66+
self.fee_exempt_keys: list[Key] | None = fee_exempt_keys
6667

6768
def set_topic_id(self, topic_id: TopicId) -> TopicUpdateTransaction:
6869
"""
@@ -92,12 +93,12 @@ def set_memo(self, memo: str) -> TopicUpdateTransaction:
9293
self.memo = memo
9394
return self
9495

95-
def set_admin_key(self, key: PublicKey) -> TopicUpdateTransaction:
96+
def set_admin_key(self, key: Key) -> TopicUpdateTransaction:
9697
"""
9798
Sets the public admin key for the topic.
9899
99100
Args:
100-
Publickey: The admin key to set.
101+
Key: The admin key to set.
101102
102103
Returns:
103104
TopicUpdateTransaction: Returns the instance for method chaining.
@@ -106,12 +107,12 @@ def set_admin_key(self, key: PublicKey) -> TopicUpdateTransaction:
106107
self.admin_key = key
107108
return self
108109

109-
def set_submit_key(self, key: PublicKey) -> TopicUpdateTransaction:
110+
def set_submit_key(self, key: Key) -> TopicUpdateTransaction:
110111
"""
111112
Sets the public submit key for the topic.
112113
113114
Args:
114-
Publickey: The submit key to set.
115+
Key: The submit key to set.
115116
116117
Returns:
117118
TopicUpdateTransaction: Returns the instance for method chaining.
@@ -181,12 +182,12 @@ def set_custom_fees(self, custom_fees: list[CustomFixedFee]) -> TopicUpdateTrans
181182
self.custom_fees = custom_fees
182183
return self
183184

184-
def set_fee_schedule_key(self, key: PublicKey) -> TopicUpdateTransaction:
185+
def set_fee_schedule_key(self, key: Key) -> TopicUpdateTransaction:
185186
"""
186187
Sets the fee schedule key for the topic update transaction.
187188
188189
Args:
189-
key (PublicKey): The fee schedule key to set for the topic.
190+
key (Key): The fee schedule key to set for the topic.
190191
191192
Returns:
192193
TopicUpdateTransaction: The current instance for method chaining.
@@ -195,12 +196,12 @@ def set_fee_schedule_key(self, key: PublicKey) -> TopicUpdateTransaction:
195196
self.fee_schedule_key = key
196197
return self
197198

198-
def set_fee_exempt_keys(self, keys: list[PublicKey]) -> TopicUpdateTransaction:
199+
def set_fee_exempt_keys(self, keys: list[Key]) -> TopicUpdateTransaction:
199200
"""
200201
Sets the fee exempt keys for the topic update transaction.
201202
202203
Args:
203-
keys (list[PublicKey]): The fee exempt keys to set for the topic.
204+
keys (list[Key]): The fee exempt keys to set for the topic.
204205
205206
Returns:
206207
TopicUpdateTransaction: The current instance for method chaining.
@@ -253,23 +254,23 @@ def _build_proto_body(self) -> consensus_update_topic_pb2.ConsensusUpdateTopicTr
253254
)
254255

255256
fee_exempt_key_list = (
256-
FeeExemptKeyList(keys=[key._to_proto() for key in self.fee_exempt_keys])
257+
FeeExemptKeyList(keys=[key_to_proto(key) for key in self.fee_exempt_keys])
257258
if self.fee_exempt_keys is not None
258259
else None
259260
)
260261

261262
return consensus_update_topic_pb2.ConsensusUpdateTopicTransactionBody(
262263
topicID=self.topic_id._to_proto(),
263-
adminKey=self.admin_key._to_proto() if self.admin_key else None,
264-
submitKey=self.submit_key._to_proto() if self.submit_key else None,
264+
adminKey=key_to_proto(self.admin_key) if self.admin_key else None,
265+
submitKey=key_to_proto(self.submit_key) if self.submit_key else None,
265266
autoRenewPeriod=(
266267
duration_pb2.Duration(seconds=self.auto_renew_period.seconds) if self.auto_renew_period else None
267268
),
268269
autoRenewAccount=(self.auto_renew_account._to_proto() if self.auto_renew_account else None),
269270
expirationTime=self.expiration_time._to_protobuf() if self.expiration_time else None,
270271
memo=_wrappers_pb2.StringValue(value=self.memo) if self.memo is not None else None,
271272
custom_fees=custom_fees,
272-
fee_schedule_key=self.fee_schedule_key._to_proto() if self.fee_schedule_key else None,
273+
fee_schedule_key=key_to_proto(self.fee_schedule_key) if self.fee_schedule_key else None,
273274
fee_exempt_key_list=fee_exempt_key_list,
274275
)
275276

tests/unit/topic_update_transaction_test.py

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
from hiero_sdk_python.account.account_id import AccountId
88
from hiero_sdk_python.consensus.topic_update_transaction import TopicUpdateTransaction
9+
from hiero_sdk_python.crypto.key import Key
910
from hiero_sdk_python.crypto.private_key import PrivateKey
11+
from hiero_sdk_python.crypto.public_key import PublicKey
1012
from hiero_sdk_python.Duration import Duration
1113
from hiero_sdk_python.hapi.services import (
1214
response_header_pb2,
@@ -26,7 +28,153 @@
2628
pytestmark = pytest.mark.unit
2729

2830

29-
# This test uses fixtures (mock_account_ids, topic_id) as parameters
31+
def create_key(key_type: str, use_private: bool) -> PrivateKey | PublicKey:
32+
"""
33+
Create a key based on type and whether to use private or public.
34+
35+
Args:
36+
key_type: "ed25519" or "ecdsa"
37+
use_private: True for PrivateKey, False for PublicKey
38+
39+
Returns:
40+
The created key (PrivateKey or PublicKey)
41+
"""
42+
private_key = PrivateKey.generate_ed25519() if key_type == "ed25519" else PrivateKey.generate("ecdsa")
43+
return private_key if use_private else private_key.public_key()
44+
45+
46+
def get_expected_public_key(key: Key) -> PublicKey:
47+
"""
48+
Get the public key from either PrivateKey or PublicKey.
49+
50+
Args:
51+
key: PrivateKey or PublicKey
52+
53+
Returns:
54+
PublicKey
55+
"""
56+
return key if isinstance(key, PublicKey) else key.public_key()
57+
58+
59+
def verify_key_in_proto(proto_key, expected_public_key: PublicKey, key_type: str) -> None:
60+
"""Verify the proto key matches expected public key."""
61+
if key_type == "ed25519":
62+
assert proto_key.ed25519 == expected_public_key.to_bytes_raw()
63+
else: # ecdsa
64+
assert proto_key.HasField("ECDSA_secp256k1")
65+
assert proto_key.ECDSA_secp256k1 == expected_public_key.to_bytes_raw()
66+
67+
68+
@pytest.mark.parametrize(
69+
"key_type,use_private",
70+
[
71+
("ed25519", True),
72+
("ed25519", False),
73+
("ecdsa", True),
74+
("ecdsa", False),
75+
],
76+
)
77+
def test_build_topic_update_transaction_body_with_all_key_types(mock_account_ids, topic_id, key_type, use_private):
78+
"""Test building a TopicUpdateTransaction body with different key types."""
79+
_, _, node_account_id, _, _ = mock_account_ids
80+
81+
admin_key = create_key(key_type, use_private)
82+
submit_key = create_key(key_type, use_private)
83+
fee_schedule_key = create_key(key_type, use_private)
84+
fee_exempt_keys = [
85+
create_key(key_type, use_private),
86+
create_key(key_type, use_private),
87+
]
88+
89+
expected_admin_public = get_expected_public_key(admin_key)
90+
expected_submit_public = get_expected_public_key(submit_key)
91+
expected_fee_schedule_public = get_expected_public_key(fee_schedule_key)
92+
expected_fee_exempt_publics = [get_expected_public_key(key) for key in fee_exempt_keys]
93+
94+
tx = TopicUpdateTransaction(
95+
topic_id=topic_id,
96+
memo="Updated Memo",
97+
admin_key=admin_key,
98+
submit_key=submit_key,
99+
custom_fees=[CustomFixedFee(1000, fee_collector_account_id=AccountId(0, 0, 9876))],
100+
fee_schedule_key=fee_schedule_key,
101+
fee_exempt_keys=fee_exempt_keys,
102+
)
103+
104+
tx.operator_account_id = AccountId(0, 0, 2)
105+
tx.node_account_id = node_account_id
106+
107+
transaction_body = tx.build_transaction_body()
108+
109+
assert transaction_body.consensusUpdateTopic.topicID.topicNum == 1234
110+
assert transaction_body.consensusUpdateTopic.memo.value == "Updated Memo"
111+
verify_key_in_proto(transaction_body.consensusUpdateTopic.adminKey, expected_admin_public, key_type)
112+
verify_key_in_proto(transaction_body.consensusUpdateTopic.submitKey, expected_submit_public, key_type)
113+
verify_key_in_proto(transaction_body.consensusUpdateTopic.fee_schedule_key, expected_fee_schedule_public, key_type)
114+
assert len(transaction_body.consensusUpdateTopic.fee_exempt_key_list.keys) == 2
115+
verify_key_in_proto(
116+
transaction_body.consensusUpdateTopic.fee_exempt_key_list.keys[0], expected_fee_exempt_publics[0], key_type
117+
)
118+
verify_key_in_proto(
119+
transaction_body.consensusUpdateTopic.fee_exempt_key_list.keys[1], expected_fee_exempt_publics[1], key_type
120+
)
121+
assert len(transaction_body.consensusUpdateTopic.custom_fees.fees) == 1
122+
123+
124+
@pytest.mark.parametrize(
125+
"key_type,use_private",
126+
[
127+
("ed25519", True),
128+
("ed25519", False),
129+
("ecdsa", True),
130+
("ecdsa", False),
131+
],
132+
)
133+
def test_build_scheduled_topic_update_body_with_all_key_types(topic_id, key_type, use_private):
134+
"""Test building scheduled body for TopicUpdateTransaction with different key types."""
135+
admin_key = create_key(key_type, use_private)
136+
submit_key = create_key(key_type, use_private)
137+
fee_schedule_key = create_key(key_type, use_private)
138+
fee_exempt_keys = [
139+
create_key(key_type, use_private),
140+
create_key(key_type, use_private),
141+
]
142+
143+
expected_admin_public = get_expected_public_key(admin_key)
144+
expected_submit_public = get_expected_public_key(submit_key)
145+
expected_fee_schedule_public = get_expected_public_key(fee_schedule_key)
146+
expected_fee_exempt_publics = [get_expected_public_key(key) for key in fee_exempt_keys]
147+
148+
tx = TopicUpdateTransaction()
149+
tx.set_topic_id(topic_id)
150+
tx.set_memo("Scheduled Topic Update")
151+
tx.set_admin_key(admin_key)
152+
tx.set_submit_key(submit_key)
153+
tx.set_auto_renew_period(Duration(8000000))
154+
tx.set_auto_renew_account(AccountId(0, 0, 9876))
155+
tx.set_custom_fees([CustomFixedFee(1000, fee_collector_account_id=AccountId(0, 0, 9876))])
156+
tx.set_fee_schedule_key(fee_schedule_key)
157+
tx.set_fee_exempt_keys(fee_exempt_keys)
158+
159+
schedulable_body = tx.build_scheduled_body()
160+
161+
assert isinstance(schedulable_body, SchedulableTransactionBody)
162+
assert schedulable_body.HasField("consensusUpdateTopic")
163+
assert schedulable_body.consensusUpdateTopic.topicID.topicNum == 1234
164+
assert schedulable_body.consensusUpdateTopic.memo.value == "Scheduled Topic Update"
165+
verify_key_in_proto(schedulable_body.consensusUpdateTopic.adminKey, expected_admin_public, key_type)
166+
verify_key_in_proto(schedulable_body.consensusUpdateTopic.submitKey, expected_submit_public, key_type)
167+
verify_key_in_proto(schedulable_body.consensusUpdateTopic.fee_schedule_key, expected_fee_schedule_public, key_type)
168+
assert len(schedulable_body.consensusUpdateTopic.fee_exempt_key_list.keys) == 2
169+
verify_key_in_proto(
170+
schedulable_body.consensusUpdateTopic.fee_exempt_key_list.keys[0], expected_fee_exempt_publics[0], key_type
171+
)
172+
verify_key_in_proto(
173+
schedulable_body.consensusUpdateTopic.fee_exempt_key_list.keys[1], expected_fee_exempt_publics[1], key_type
174+
)
175+
assert len(schedulable_body.consensusUpdateTopic.custom_fees.fees) == 1
176+
177+
30178
def test_build_topic_update_transaction_body(mock_account_ids, topic_id):
31179
"""Test building a TopicUpdateTransaction body with valid topic ID and memo."""
32180
_, _, node_account_id, _, _ = mock_account_ids

0 commit comments

Comments
 (0)