1use std::net::Ipv4Addr;
4
5use hiero_sdk_proto::services;
6use hiero_sdk_proto::services::address_book_service_client::AddressBookServiceClient;
7use tonic::transport::Channel;
8
9use crate::ledger_id::RefLedgerId;
10use crate::protobuf::FromProtobuf;
11use crate::service_endpoint::ServiceEndpoint;
12use crate::transaction::{
13 AnyTransactionData,
14 ChunkInfo,
15 ToSchedulableTransactionDataProtobuf,
16 ToTransactionDataProtobuf,
17 TransactionData,
18 TransactionExecute,
19};
20use crate::{
21 AccountId,
22 BoxGrpcFuture,
23 Error,
24 Key,
25 ToProtobuf,
26 Transaction,
27 ValidateChecksums,
28};
29
30pub type NodeUpdateTransaction = Transaction<NodeUpdateTransactionData>;
49
50#[derive(Debug, Clone, Default)]
52pub struct NodeUpdateTransactionData {
53 node_id: u64,
55
56 account_id: Option<AccountId>,
58
59 description: Option<String>,
61
62 gossip_endpoints: Vec<ServiceEndpoint>,
64
65 service_endpoints: Vec<ServiceEndpoint>,
67
68 gossip_ca_certificate: Option<Vec<u8>>,
70
71 grpc_certificate_hash: Option<Vec<u8>>,
73
74 admin_key: Option<Key>,
76
77 decline_reward: Option<bool>,
79
80 grpc_proxy_endpoint: Option<ServiceEndpoint>,
82}
83
84impl NodeUpdateTransaction {
85 #[must_use]
87 pub fn get_node_id(&self) -> u64 {
88 self.data().node_id
89 }
90
91 pub fn node_id(&mut self, node_id: u64) -> &mut Self {
93 self.data_mut().node_id = node_id;
94 self
95 }
96
97 #[must_use]
99 pub fn get_account_id(&self) -> Option<AccountId> {
100 self.data().account_id
101 }
102
103 pub fn account_id(&mut self, account_id: AccountId) -> &mut Self {
105 self.data_mut().account_id = Some(account_id);
106 self
107 }
108
109 #[must_use]
111 pub fn get_description(&self) -> Option<&str> {
112 self.data().description.as_deref()
113 }
114
115 pub fn description(&mut self, description: impl Into<String>) -> &mut Self {
117 self.data_mut().description = Some(description.into());
118 self
119 }
120
121 #[must_use]
123 pub fn get_gossip_endpoints(&self) -> Vec<ServiceEndpoint> {
124 self.data().gossip_endpoints.clone()
125 }
126
127 pub fn gossip_endpoints(
129 &mut self,
130 gossip_endpoint: impl IntoIterator<Item = ServiceEndpoint>,
131 ) -> &mut Self {
132 self.data_mut().gossip_endpoints = gossip_endpoint.into_iter().collect();
133 self
134 }
135
136 pub fn add_gossip_endpoint(&mut self, gossip_endpoint: ServiceEndpoint) -> &mut Self {
138 self.data_mut().gossip_endpoints.push(gossip_endpoint);
139 self
140 }
141
142 #[must_use]
144 pub fn get_service_endpoints(&self) -> Vec<ServiceEndpoint> {
145 self.data().service_endpoints.clone()
146 }
147
148 pub fn service_endpoints(
150 &mut self,
151 service_endpoint: impl IntoIterator<Item = ServiceEndpoint>,
152 ) -> &mut Self {
153 self.data_mut().service_endpoints = service_endpoint.into_iter().collect();
154 self
155 }
156
157 pub fn add_service_endpoint(&mut self, service_endpoint: ServiceEndpoint) -> &mut Self {
159 self.data_mut().service_endpoints.push(service_endpoint);
160 self
161 }
162
163 #[must_use]
165 pub fn get_gossip_ca_certificate(&self) -> Option<Vec<u8>> {
166 self.data().gossip_ca_certificate.clone()
167 }
168
169 pub fn gossip_ca_certificate(
171 &mut self,
172 gossip_ca_certificate: impl Into<Vec<u8>>,
173 ) -> &mut Self {
174 self.data_mut().gossip_ca_certificate = Some(gossip_ca_certificate.into());
175 self
176 }
177
178 #[must_use]
180 pub fn get_grpc_certificate_hash(&self) -> Option<Vec<u8>> {
181 self.data().grpc_certificate_hash.clone()
182 }
183
184 pub fn grpc_certificate_hash(
186 &mut self,
187 grpc_certificate_hash: impl Into<Vec<u8>>,
188 ) -> &mut Self {
189 self.data_mut().grpc_certificate_hash = Some(grpc_certificate_hash.into());
190 self
191 }
192
193 #[must_use]
195 pub fn get_admin_key(&self) -> Option<&Key> {
196 self.data().admin_key.as_ref()
197 }
198
199 pub fn admin_key(&mut self, key: impl Into<Key>) -> &mut Self {
201 self.data_mut().admin_key = Some(key.into());
202 self
203 }
204
205 #[must_use]
207 pub fn get_decline_reward(&self) -> Option<bool> {
208 self.data().decline_reward
209 }
210
211 pub fn decline_reward(&mut self, decline_reward: bool) -> &mut Self {
213 self.data_mut().decline_reward = Some(decline_reward);
214 self
215 }
216
217 #[must_use]
219 pub fn get_grpc_proxy_endpoint(&self) -> Option<&ServiceEndpoint> {
220 self.data().grpc_proxy_endpoint.as_ref()
221 }
222
223 pub fn grpc_proxy_endpoint(&mut self, grpc_proxy_endpoint: ServiceEndpoint) -> &mut Self {
225 self.data_mut().grpc_proxy_endpoint = Some(grpc_proxy_endpoint);
226 self
227 }
228
229 pub fn delete_grpc_proxy_endpoint(&mut self) -> &mut Self {
233 self.data_mut().grpc_proxy_endpoint = None;
234 self
235 }
236}
237
238impl TransactionData for NodeUpdateTransactionData {}
239
240impl TransactionExecute for NodeUpdateTransactionData {
241 fn execute(
242 &self,
243 channel: Channel,
244 request: services::Transaction,
245 ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
246 Box::pin(async { AddressBookServiceClient::new(channel).update_node(request).await })
247 }
248}
249
250impl ValidateChecksums for NodeUpdateTransactionData {
251 fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
252 self.account_id.validate_checksums(ledger_id)?;
253 Ok(())
254 }
255}
256
257impl ToTransactionDataProtobuf for NodeUpdateTransactionData {
258 fn to_transaction_data_protobuf(
259 &self,
260 chunk_info: &ChunkInfo,
261 ) -> services::transaction_body::Data {
262 let _ = chunk_info.assert_single_transaction();
263
264 services::transaction_body::Data::NodeUpdate(self.to_protobuf())
265 }
266}
267
268impl ToSchedulableTransactionDataProtobuf for NodeUpdateTransactionData {
269 fn to_schedulable_transaction_data_protobuf(
270 &self,
271 ) -> services::schedulable_transaction_body::Data {
272 services::schedulable_transaction_body::Data::NodeUpdate(self.to_protobuf())
273 }
274}
275
276impl From<NodeUpdateTransactionData> for AnyTransactionData {
277 fn from(transaction: NodeUpdateTransactionData) -> Self {
278 Self::NodeUpdate(transaction)
279 }
280}
281
282impl FromProtobuf<services::NodeUpdateTransactionBody> for NodeUpdateTransactionData {
283 fn from_protobuf(pb: services::NodeUpdateTransactionBody) -> crate::Result<Self> {
284 let gossip_endpoints = pb
285 .gossip_endpoint
286 .iter()
287 .map(|it| {
288 let ip_addr_v4 = &it.ip_address_v4[..];
289 let ip = Ipv4Addr::new(ip_addr_v4[0], ip_addr_v4[1], ip_addr_v4[2], ip_addr_v4[3]);
290 ServiceEndpoint {
291 ip_address_v4: Some(ip),
292 port: it.port,
293 domain_name: it.domain_name.clone(),
294 }
295 })
296 .collect();
297 let service_endpoints = pb
298 .service_endpoint
299 .iter()
300 .map(|it| {
301 let ip_addr_v4 = &it.ip_address_v4[..];
302 let ip = Ipv4Addr::new(ip_addr_v4[0], ip_addr_v4[1], ip_addr_v4[2], ip_addr_v4[3]);
303 ServiceEndpoint {
304 ip_address_v4: Some(ip),
305 port: it.port,
306 domain_name: it.domain_name.clone(),
307 }
308 })
309 .collect();
310
311 Ok(Self {
312 node_id: pb.node_id,
313 account_id: FromProtobuf::from_protobuf(pb.account_id)?,
314 description: pb.description,
315 gossip_endpoints: gossip_endpoints,
316 service_endpoints: service_endpoints,
317 gossip_ca_certificate: pb.gossip_ca_certificate,
318 grpc_certificate_hash: pb.grpc_certificate_hash,
319 admin_key: Option::from_protobuf(pb.admin_key)?,
320 decline_reward: pb.decline_reward,
321 grpc_proxy_endpoint: pb.grpc_proxy_endpoint.map(|it| ServiceEndpoint {
322 ip_address_v4: Some(Ipv4Addr::new(
323 it.ip_address_v4[0],
324 it.ip_address_v4[1],
325 it.ip_address_v4[2],
326 it.ip_address_v4[3],
327 )),
328 port: it.port,
329 domain_name: it.domain_name.clone(),
330 }),
331 })
332 }
333}
334
335impl ToProtobuf for NodeUpdateTransactionData {
336 type Protobuf = services::NodeUpdateTransactionBody;
337
338 fn to_protobuf(&self) -> Self::Protobuf {
339 let gossip_endpoints =
340 self.gossip_endpoints.iter().map(|it| it.to_protobuf()).collect::<Vec<_>>();
341 let service_endpoints =
342 self.service_endpoints.iter().map(|it| it.to_protobuf()).collect::<Vec<_>>();
343
344 services::NodeUpdateTransactionBody {
345 node_id: self.node_id,
346 account_id: self.account_id.to_protobuf(),
347 description: self.description.clone(),
348 gossip_endpoint: gossip_endpoints,
349 service_endpoint: service_endpoints,
350 gossip_ca_certificate: self.gossip_ca_certificate.clone(),
351 grpc_certificate_hash: self.grpc_certificate_hash.clone(),
352 admin_key: self.admin_key.to_protobuf(),
353 decline_reward: self.decline_reward,
354 grpc_proxy_endpoint: self.grpc_proxy_endpoint.as_ref().map(|it| it.to_protobuf()),
355 associated_registered_node_list: None,
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use std::net::Ipv4Addr;
363
364 use expect_test::expect_file;
365 use hiero_sdk_proto::services;
366
367 use super::NodeUpdateTransaction;
368 use crate::address_book::NodeUpdateTransactionData;
369 use crate::protobuf::{
370 FromProtobuf,
371 ToProtobuf,
372 };
373 use crate::service_endpoint::ServiceEndpoint;
374 use crate::transaction::test_helpers::{
375 check_body,
376 transaction_body,
377 unused_private_key,
378 TEST_ACCOUNT_ID,
379 };
380 use crate::{
381 AnyTransaction,
382 Key,
383 };
384
385 const TEST_DESCRIPTION: &str = "test description";
386 const TEST_GOSSIP_CA_CERTIFICATE: &[u8] = &[1, 2, 3, 4];
387 const TEST_GRPC_CERTIFICATE_HASH: &[u8] = &[5, 6, 7, 8];
388
389 fn make_ip_address_list() -> Vec<ServiceEndpoint> {
390 vec![
391 ServiceEndpoint {
392 ip_address_v4: Some(Ipv4Addr::new(127, 0, 0, 1)),
393 port: 1234,
394 domain_name: "".to_owned(),
395 },
396 ServiceEndpoint {
397 ip_address_v4: Some(Ipv4Addr::new(127, 0, 0, 1)),
398 port: 8008,
399 domain_name: "".to_owned(),
400 },
401 ]
402 }
403
404 fn make_transaction() -> NodeUpdateTransaction {
405 let mut tx = NodeUpdateTransaction::new_for_tests();
406
407 tx.account_id(TEST_ACCOUNT_ID)
408 .description(TEST_DESCRIPTION)
409 .gossip_endpoints(make_ip_address_list())
410 .service_endpoints(make_ip_address_list())
411 .gossip_ca_certificate(TEST_GOSSIP_CA_CERTIFICATE)
412 .grpc_certificate_hash(TEST_GRPC_CERTIFICATE_HASH)
413 .admin_key(unused_private_key().public_key())
414 .freeze()
415 .unwrap();
416
417 tx
418 }
419
420 #[test]
421 fn serialize() {
422 let tx = make_transaction();
423
424 let tx = transaction_body(tx);
425
426 let tx = check_body(tx);
427
428 expect_file!["./snapshots/node_update_transaction/serialize.txt"].assert_debug_eq(&tx);
429 }
430
431 #[test]
432 fn to_from_bytes() {
433 let tx = make_transaction();
434
435 let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
436
437 let tx = transaction_body(tx);
438 let tx2 = transaction_body(tx2);
439
440 assert_eq!(tx, tx2)
441 }
442
443 #[test]
444 fn from_proto_body() {
445 let grpc_proxy_endpoint = make_ip_address_list().into_iter().next().unwrap();
446 let tx = services::NodeUpdateTransactionBody {
447 node_id: 1,
448 account_id: Some(TEST_ACCOUNT_ID.to_protobuf()),
449 description: Some(TEST_DESCRIPTION.to_owned()),
450 gossip_endpoint: make_ip_address_list()
451 .into_iter()
452 .map(|it| it.to_protobuf())
453 .collect(),
454 service_endpoint: make_ip_address_list()
455 .into_iter()
456 .map(|it| it.to_protobuf())
457 .collect(),
458 gossip_ca_certificate: Some(TEST_GOSSIP_CA_CERTIFICATE.to_vec()),
459 grpc_certificate_hash: Some(TEST_GRPC_CERTIFICATE_HASH.to_vec()),
460 admin_key: Some(unused_private_key().public_key().to_protobuf()),
461 decline_reward: Some(false),
462 grpc_proxy_endpoint: Some(grpc_proxy_endpoint.to_protobuf()),
463 associated_registered_node_list: None,
464 };
465
466 let data = NodeUpdateTransactionData::from_protobuf(tx).unwrap();
467
468 assert_eq!(data.account_id, Some(TEST_ACCOUNT_ID));
469 assert_eq!(data.description, Some(TEST_DESCRIPTION.to_string()));
470 assert_eq!(data.gossip_endpoints, make_ip_address_list());
471 assert_eq!(data.service_endpoints, make_ip_address_list());
472 assert_eq!(data.gossip_ca_certificate, Some(TEST_GOSSIP_CA_CERTIFICATE.to_vec()));
473 assert_eq!(data.grpc_certificate_hash, Some(TEST_GRPC_CERTIFICATE_HASH.to_vec()));
474 assert_eq!(data.admin_key, Some(Key::from(unused_private_key().public_key())));
475 assert_eq!(data.decline_reward, Some(false));
476 assert_eq!(data.grpc_proxy_endpoint, Some(grpc_proxy_endpoint));
477 }
478
479 #[test]
480 fn get_set_node_id() {
481 let mut tx = NodeUpdateTransaction::new();
482 tx.node_id(1);
483
484 assert_eq!(tx.get_node_id(), 1);
485 }
486
487 #[test]
488 #[should_panic]
489 fn get_set_node_id_frozen_panic() {
490 make_transaction().node_id(1);
491 }
492
493 #[test]
494 fn get_set_account_id() {
495 let account_id = TEST_ACCOUNT_ID;
496 let mut tx = NodeUpdateTransaction::new();
497 tx.account_id(account_id.to_owned());
498
499 assert_eq!(tx.get_account_id(), Some(account_id));
500 }
501
502 #[test]
503 #[should_panic]
504 fn get_set_account_id_frozen_panic() {
505 make_transaction().account_id(TEST_ACCOUNT_ID);
506 }
507
508 #[test]
509 fn get_set_description() {
510 let description = TEST_DESCRIPTION.to_owned();
511 let mut tx = NodeUpdateTransaction::new();
512 tx.description(description.to_owned());
513
514 assert_eq!(tx.get_description(), Some(TEST_DESCRIPTION));
515 }
516
517 #[test]
518 #[should_panic]
519 fn get_set_description_frozen_panic() {
520 make_transaction().description(TEST_DESCRIPTION);
521 }
522
523 #[test]
524 fn get_set_gossip_endpoints() {
525 let gossip_endpoints = make_ip_address_list();
526 let mut tx = NodeUpdateTransaction::new();
527 tx.gossip_endpoints(gossip_endpoints.to_owned());
528
529 assert_eq!(tx.get_gossip_endpoints(), gossip_endpoints);
530 }
531
532 #[test]
533 #[should_panic]
534 fn get_set_gossip_endpoint_frozen_panic() {
535 make_transaction().gossip_endpoints(make_ip_address_list());
536 }
537
538 #[test]
539 fn get_set_decline_reward() {
540 let mut tx = NodeUpdateTransaction::new();
541 tx.decline_reward(true);
542
543 assert_eq!(tx.get_decline_reward(), Some(true));
544 }
545
546 #[test]
547 fn get_set_grpc_proxy_endpoint() {
548 let grpc_proxy_endpoint = make_ip_address_list().into_iter().next().unwrap();
549 let mut tx = NodeUpdateTransaction::new();
550 tx.grpc_proxy_endpoint(grpc_proxy_endpoint.clone());
551
552 assert_eq!(tx.get_grpc_proxy_endpoint(), Some(&grpc_proxy_endpoint));
553 }
554
555 #[test]
556 fn delete_grpc_proxy_endpoint() {
557 let grpc_proxy_endpoint = make_ip_address_list().into_iter().next().unwrap();
558 let mut tx = NodeUpdateTransaction::new();
559
560 tx.grpc_proxy_endpoint(grpc_proxy_endpoint.clone());
562 assert_eq!(tx.get_grpc_proxy_endpoint(), Some(&grpc_proxy_endpoint));
563
564 tx.delete_grpc_proxy_endpoint();
566 assert_eq!(tx.get_grpc_proxy_endpoint(), None);
567 }
568
569 #[test]
570 #[should_panic]
571 fn delete_grpc_proxy_endpoint_frozen_panic() {
572 make_transaction().delete_grpc_proxy_endpoint();
573 }
574
575 #[test]
576 fn get_set_service_endpoints() {
577 let service_endpoints = make_ip_address_list();
578 let mut tx = NodeUpdateTransaction::new();
579 tx.service_endpoints(service_endpoints.to_owned());
580
581 assert_eq!(tx.get_service_endpoints(), service_endpoints);
582 }
583
584 #[test]
585 #[should_panic]
586 fn get_set_service_endpoints_frozen_panic() {
587 make_transaction().service_endpoints(make_ip_address_list());
588 }
589
590 #[test]
591 fn get_set_grpc_certificate_hash() {
592 let mut tx = NodeUpdateTransaction::new();
593 tx.grpc_certificate_hash(TEST_GOSSIP_CA_CERTIFICATE);
594
595 assert_eq!(tx.get_grpc_certificate_hash(), Some(TEST_GOSSIP_CA_CERTIFICATE.to_vec()));
596 }
597
598 #[test]
599 #[should_panic]
600 fn get_set_grpc_certificate_hash_frozen_panic() {
601 make_transaction().grpc_certificate_hash(TEST_GOSSIP_CA_CERTIFICATE);
602 }
603
604 #[test]
605 fn get_set_admin_key() {
606 let mut tx = NodeUpdateTransaction::new();
607 tx.admin_key(unused_private_key().public_key());
608
609 assert_eq!(tx.get_admin_key(), Some(&Key::from(unused_private_key().public_key())));
610 }
611
612 #[test]
613 #[should_panic]
614 fn get_set_admin_key_frozen_panic() {
615 make_transaction().admin_key(Key::from(unused_private_key().public_key()));
616 }
617}