hedera/schedule/schedule_info.rs
1// SPDX-License-Identifier: Apache-2.0
2
3use hedera_proto::services;
4use time::OffsetDateTime;
5
6use super::schedulable_transaction_body::SchedulableTransactionBody;
7use crate::protobuf::ToProtobuf;
8use crate::transaction::TransactionBody;
9use crate::{
10 AccountId,
11 AnyTransaction,
12 FromProtobuf,
13 Key,
14 KeyList,
15 LedgerId,
16 ScheduleId,
17 Transaction,
18 TransactionId,
19};
20
21// TODO: scheduled_transaction
22/// Response from [`ScheduleInfoQuery`][crate::ScheduleInfoQuery].
23#[derive(Debug, Clone)]
24pub struct ScheduleInfo {
25 /// The ID of the schedule for which information is requested.
26 pub schedule_id: ScheduleId,
27
28 /// The account that created the scheduled transaction.
29 pub creator_account_id: AccountId,
30
31 /// The account paying for the execution of the scheduled transaction.
32 pub payer_account_id: Option<AccountId>,
33
34 /// The signatories that have provided signatures so far for the schedule
35 /// transaction.
36 pub signatories: KeyList,
37
38 /// The key which is able to delete the schedule transaction if set.
39 pub admin_key: Option<Key>,
40
41 /// The transaction id that will be used in the record of the scheduled transaction (if
42 /// it executes).
43 pub scheduled_transaction_id: TransactionId,
44
45 scheduled_transaction: SchedulableTransactionBody,
46
47 /// When set to true, the transaction will be evaluated for execution at `expiration_time`
48 /// instead of when all required signatures are received.
49 pub wait_for_expiry: bool,
50
51 /// Publicly visible information about the Schedule entity.
52 pub memo: String,
53
54 /// The date and time the schedule transaction will expire
55 pub expiration_time: Option<OffsetDateTime>,
56
57 /// The time the schedule transaction was executed.
58 pub executed_at: Option<OffsetDateTime>,
59
60 /// The time the schedule transaction was deleted.
61 pub deleted_at: Option<OffsetDateTime>,
62
63 /// The ledger ID the response was returned from
64 pub ledger_id: LedgerId,
65}
66
67impl ScheduleInfo {
68 /// Create a new `ScheduleInfo` from protobuf-encoded `bytes`.
69 ///
70 /// # Errors
71 /// - [`Error::FromProtobuf`](crate::Error::FromProtobuf) if decoding the bytes fails to produce a valid protobuf.
72 /// - [`Error::FromProtobuf`](crate::Error::FromProtobuf) if decoding the protobuf fails.
73 pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
74 FromProtobuf::<services::ScheduleInfo>::from_bytes(bytes)
75 }
76
77 /// Returns the scheduled transaction.
78 ///
79 /// This is *not* guaranteed to be a constant time operation.
80 pub fn scheduled_transaction(&self) -> crate::Result<AnyTransaction> {
81 // note: this can't error *right now* but the API *will* be faliable eventually, and as such, returns a result to make the change non-breaking.
82 Ok(Transaction::from_parts(
83 TransactionBody {
84 data: (*self.scheduled_transaction.data).clone().into(),
85 node_account_ids: None,
86 transaction_valid_duration: None,
87 max_transaction_fee: None,
88 transaction_memo: self.scheduled_transaction.transaction_memo.clone(),
89 transaction_id: Some(self.scheduled_transaction_id),
90 operator: None,
91 is_frozen: true,
92 regenerate_transaction_id: Some(false),
93 custom_fee_limits: Vec::new(),
94 batch_key: None,
95 },
96 Vec::new(),
97 ))
98 }
99
100 /// Convert `self` to a protobuf-encoded [`Vec<u8>`].
101 #[must_use]
102 pub fn to_bytes(&self) -> Vec<u8> {
103 ToProtobuf::to_bytes(self)
104 }
105}
106
107impl FromProtobuf<services::response::Response> for ScheduleInfo {
108 #[allow(deprecated)]
109 fn from_protobuf(pb: services::response::Response) -> crate::Result<Self>
110 where
111 Self: Sized,
112 {
113 let response = pb_getv!(pb, ScheduleGetInfo, services::response::Response);
114 let info = pb_getf!(response, schedule_info)?;
115 Self::from_protobuf(info)
116 }
117}
118
119impl FromProtobuf<services::ScheduleInfo> for ScheduleInfo {
120 fn from_protobuf(pb: services::ScheduleInfo) -> crate::Result<Self>
121 where
122 Self: Sized,
123 {
124 let schedule_id = pb_getf!(pb, schedule_id)?;
125 let creator_account_id = pb_getf!(pb, creator_account_id)?;
126 let payer_account_id = Option::from_protobuf(pb.payer_account_id)?;
127 let admin_key = Option::from_protobuf(pb.admin_key)?;
128 let ledger_id = LedgerId::from_bytes(pb.ledger_id);
129
130 let scheduled_transaction_id =
131 TransactionId::from_protobuf(pb_getf!(pb, scheduled_transaction_id)?)?;
132
133 let transaction_body =
134 SchedulableTransactionBody::from_protobuf(pb_getf!(pb, scheduled_transaction_body)?)?;
135
136 let signatories = pb.signers.map(KeyList::from_protobuf).transpose()?.unwrap_or_default();
137
138 let (executed_at, deleted_at) = match pb.data {
139 Some(services::schedule_info::Data::DeletionTime(deleted)) => {
140 (None, Some(deleted.into()))
141 }
142
143 Some(services::schedule_info::Data::ExecutionTime(executed)) => {
144 (Some(executed.into()), None)
145 }
146
147 None => (None, None),
148 };
149
150 Ok(Self {
151 schedule_id: ScheduleId::from_protobuf(schedule_id)?,
152 executed_at,
153 deleted_at,
154 memo: pb.memo,
155 creator_account_id: AccountId::from_protobuf(creator_account_id)?,
156 payer_account_id,
157 expiration_time: pb.expiration_time.map(Into::into),
158 admin_key,
159 scheduled_transaction_id,
160 signatories,
161 wait_for_expiry: pb.wait_for_expiry,
162 ledger_id,
163 scheduled_transaction: transaction_body,
164 })
165 }
166}
167
168impl ToProtobuf for ScheduleInfo {
169 type Protobuf = services::ScheduleInfo;
170
171 fn to_protobuf(&self) -> Self::Protobuf {
172 services::ScheduleInfo {
173 schedule_id: Some(self.schedule_id.to_protobuf()),
174 expiration_time: self.expiration_time.to_protobuf(),
175 memo: self.memo.clone(),
176 admin_key: self.admin_key.to_protobuf(),
177 signers: (!self.signatories.is_empty())
178 .then(|| services::KeyList { keys: self.signatories.keys.to_protobuf() }),
179 creator_account_id: Some(self.creator_account_id.to_protobuf()),
180 payer_account_id: self.payer_account_id.to_protobuf(),
181 scheduled_transaction_id: Some(self.scheduled_transaction_id.to_protobuf()),
182 ledger_id: self.ledger_id.to_bytes(),
183 wait_for_expiry: self.wait_for_expiry,
184
185 // unimplemented fields
186 scheduled_transaction_body: Some(
187 self.scheduled_transaction.to_scheduled_body_protobuf(),
188 ),
189 data: None,
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use expect_test::expect;
197
198 use crate::protobuf::{
199 FromProtobuf,
200 ToProtobuf,
201 };
202 use crate::schedule::schedulable_transaction_body::{
203 AnySchedulableTransactionData,
204 SchedulableTransactionBody,
205 };
206 use crate::transaction::test_helpers::{
207 unused_private_key,
208 VALID_START,
209 };
210 use crate::transaction::ToSchedulableTransactionDataProtobuf;
211 use crate::{
212 AccountDeleteTransaction,
213 LedgerId,
214 ScheduleInfo,
215 TransactionId,
216 };
217
218 fn make_info() -> ScheduleInfo {
219 let schedueld = AnySchedulableTransactionData::from_protobuf(
220 AccountDeleteTransaction::new()
221 .account_id("6.6.6".parse().unwrap())
222 .data()
223 .to_schedulable_transaction_data_protobuf(),
224 )
225 .unwrap();
226
227 ScheduleInfo {
228 schedule_id: "1.2.3".parse().unwrap(),
229 creator_account_id: "4.5.6".parse().unwrap(),
230 payer_account_id: Some("2.3.4".parse().unwrap()),
231 signatories: crate::KeyList::from([unused_private_key().public_key()]),
232 admin_key: Some(unused_private_key().public_key().into()),
233 scheduled_transaction_id: TransactionId {
234 account_id: "5006".parse().unwrap(),
235 valid_start: VALID_START,
236 nonce: None,
237 scheduled: false,
238 },
239 scheduled_transaction: SchedulableTransactionBody {
240 data: Box::new(schedueld),
241 max_transaction_fee: None,
242 transaction_memo: Default::default(),
243 },
244 wait_for_expiry: true,
245 memo: "memo".to_owned(),
246 expiration_time: Some(VALID_START),
247 executed_at: Some(VALID_START),
248 deleted_at: None,
249 ledger_id: LedgerId::testnet(),
250 }
251 }
252
253 fn make_deleted_info() -> ScheduleInfo {
254 ScheduleInfo { executed_at: None, deleted_at: Some(VALID_START), ..make_info() }
255 }
256
257 #[test]
258 fn serialize() {
259 expect![[r#"
260 ScheduleInfo {
261 schedule_id: Some(
262 ScheduleId {
263 shard_num: 1,
264 realm_num: 2,
265 schedule_num: 3,
266 },
267 ),
268 expiration_time: Some(
269 Timestamp {
270 seconds: 1554158542,
271 nanos: 0,
272 },
273 ),
274 scheduled_transaction_body: Some(
275 SchedulableTransactionBody {
276 transaction_fee: 200000000,
277 memo: "",
278 max_custom_fees: [],
279 data: Some(
280 CryptoDelete(
281 CryptoDeleteTransactionBody {
282 transfer_account_id: None,
283 delete_account_id: Some(
284 AccountId {
285 shard_num: 6,
286 realm_num: 6,
287 account: Some(
288 AccountNum(
289 6,
290 ),
291 ),
292 },
293 ),
294 },
295 ),
296 ),
297 },
298 ),
299 memo: "memo",
300 admin_key: Some(
301 Key {
302 key: Some(
303 Ed25519(
304 [
305 224,
306 200,
307 236,
308 39,
309 88,
310 165,
311 135,
312 159,
313 250,
314 194,
315 38,
316 161,
317 60,
318 12,
319 81,
320 107,
321 121,
322 158,
323 114,
324 227,
325 81,
326 65,
327 160,
328 221,
329 130,
330 143,
331 148,
332 211,
333 121,
334 136,
335 164,
336 183,
337 ],
338 ),
339 ),
340 },
341 ),
342 signers: Some(
343 KeyList {
344 keys: [
345 Key {
346 key: Some(
347 Ed25519(
348 [
349 224,
350 200,
351 236,
352 39,
353 88,
354 165,
355 135,
356 159,
357 250,
358 194,
359 38,
360 161,
361 60,
362 12,
363 81,
364 107,
365 121,
366 158,
367 114,
368 227,
369 81,
370 65,
371 160,
372 221,
373 130,
374 143,
375 148,
376 211,
377 121,
378 136,
379 164,
380 183,
381 ],
382 ),
383 ),
384 },
385 ],
386 },
387 ),
388 creator_account_id: Some(
389 AccountId {
390 shard_num: 4,
391 realm_num: 5,
392 account: Some(
393 AccountNum(
394 6,
395 ),
396 ),
397 },
398 ),
399 payer_account_id: Some(
400 AccountId {
401 shard_num: 2,
402 realm_num: 3,
403 account: Some(
404 AccountNum(
405 4,
406 ),
407 ),
408 },
409 ),
410 scheduled_transaction_id: Some(
411 TransactionId {
412 transaction_valid_start: Some(
413 Timestamp {
414 seconds: 1554158542,
415 nanos: 0,
416 },
417 ),
418 account_id: Some(
419 AccountId {
420 shard_num: 0,
421 realm_num: 0,
422 account: Some(
423 AccountNum(
424 5006,
425 ),
426 ),
427 },
428 ),
429 scheduled: false,
430 nonce: 0,
431 },
432 ),
433 ledger_id: [
434 1,
435 ],
436 wait_for_expiry: true,
437 data: None,
438 }
439 "#]]
440 .assert_debug_eq(&make_info().to_protobuf());
441 }
442
443 #[test]
444 fn serialize_deleted() {
445 expect![[r#"
446 ScheduleInfo {
447 schedule_id: Some(
448 ScheduleId {
449 shard_num: 1,
450 realm_num: 2,
451 schedule_num: 3,
452 },
453 ),
454 expiration_time: Some(
455 Timestamp {
456 seconds: 1554158542,
457 nanos: 0,
458 },
459 ),
460 scheduled_transaction_body: Some(
461 SchedulableTransactionBody {
462 transaction_fee: 200000000,
463 memo: "",
464 max_custom_fees: [],
465 data: Some(
466 CryptoDelete(
467 CryptoDeleteTransactionBody {
468 transfer_account_id: None,
469 delete_account_id: Some(
470 AccountId {
471 shard_num: 6,
472 realm_num: 6,
473 account: Some(
474 AccountNum(
475 6,
476 ),
477 ),
478 },
479 ),
480 },
481 ),
482 ),
483 },
484 ),
485 memo: "memo",
486 admin_key: Some(
487 Key {
488 key: Some(
489 Ed25519(
490 [
491 224,
492 200,
493 236,
494 39,
495 88,
496 165,
497 135,
498 159,
499 250,
500 194,
501 38,
502 161,
503 60,
504 12,
505 81,
506 107,
507 121,
508 158,
509 114,
510 227,
511 81,
512 65,
513 160,
514 221,
515 130,
516 143,
517 148,
518 211,
519 121,
520 136,
521 164,
522 183,
523 ],
524 ),
525 ),
526 },
527 ),
528 signers: Some(
529 KeyList {
530 keys: [
531 Key {
532 key: Some(
533 Ed25519(
534 [
535 224,
536 200,
537 236,
538 39,
539 88,
540 165,
541 135,
542 159,
543 250,
544 194,
545 38,
546 161,
547 60,
548 12,
549 81,
550 107,
551 121,
552 158,
553 114,
554 227,
555 81,
556 65,
557 160,
558 221,
559 130,
560 143,
561 148,
562 211,
563 121,
564 136,
565 164,
566 183,
567 ],
568 ),
569 ),
570 },
571 ],
572 },
573 ),
574 creator_account_id: Some(
575 AccountId {
576 shard_num: 4,
577 realm_num: 5,
578 account: Some(
579 AccountNum(
580 6,
581 ),
582 ),
583 },
584 ),
585 payer_account_id: Some(
586 AccountId {
587 shard_num: 2,
588 realm_num: 3,
589 account: Some(
590 AccountNum(
591 4,
592 ),
593 ),
594 },
595 ),
596 scheduled_transaction_id: Some(
597 TransactionId {
598 transaction_valid_start: Some(
599 Timestamp {
600 seconds: 1554158542,
601 nanos: 0,
602 },
603 ),
604 account_id: Some(
605 AccountId {
606 shard_num: 0,
607 realm_num: 0,
608 account: Some(
609 AccountNum(
610 5006,
611 ),
612 ),
613 },
614 ),
615 scheduled: false,
616 nonce: 0,
617 },
618 ),
619 ledger_id: [
620 1,
621 ],
622 wait_for_expiry: true,
623 data: None,
624 }
625 "#]]
626 .assert_debug_eq(&make_deleted_info().to_protobuf());
627 }
628}