1use hiero_sdk_proto::services;
4use hiero_sdk_proto::services::freeze_service_client::FreezeServiceClient;
5use time::OffsetDateTime;
6use tonic::transport::Channel;
7
8use crate::protobuf::FromProtobuf;
9use crate::transaction::{
10 AnyTransactionData,
11 ChunkInfo,
12 ToSchedulableTransactionDataProtobuf,
13 ToTransactionDataProtobuf,
14 TransactionData,
15 TransactionExecute,
16};
17use crate::{
18 BoxGrpcFuture,
19 Error,
20 FileId,
21 FreezeType,
22 ToProtobuf,
23 Transaction,
24 ValidateChecksums,
25};
26
27pub type FreezeTransaction = Transaction<FreezeTransactionData>;
33
34#[derive(Debug, Clone, Default)]
35pub struct FreezeTransactionData {
36 start_time: Option<OffsetDateTime>,
37 file_id: Option<FileId>,
38 file_hash: Option<Vec<u8>>,
39 freeze_type: FreezeType,
40}
41
42impl FreezeTransaction {
43 #[must_use]
45 pub fn get_start_time(&self) -> Option<OffsetDateTime> {
46 self.data().start_time
47 }
48
49 pub fn start_time(&mut self, time: OffsetDateTime) -> &mut Self {
51 self.data_mut().start_time = Some(time);
52 self
53 }
54
55 #[must_use]
57 pub fn get_freeze_type(&self) -> FreezeType {
58 self.data().freeze_type
59 }
60
61 pub fn freeze_type(&mut self, ty: FreezeType) -> &mut Self {
63 self.data_mut().freeze_type = ty;
64 self
65 }
66
67 #[must_use]
69 pub fn get_file_id(&self) -> Option<FileId> {
70 self.data().file_id
71 }
72
73 pub fn file_id(&mut self, id: FileId) -> &mut Self {
75 self.data_mut().file_id = Some(id);
76 self
77 }
78
79 #[must_use]
81 pub fn get_file_hash(&self) -> Option<&[u8]> {
82 self.data().file_hash.as_deref()
83 }
84
85 pub fn file_hash(&mut self, hash: Vec<u8>) -> &mut Self {
87 self.data_mut().file_hash = Some(hash);
88 self
89 }
90}
91
92impl TransactionData for FreezeTransactionData {}
93
94impl TransactionExecute for FreezeTransactionData {
95 fn execute(
96 &self,
97 channel: Channel,
98 request: services::Transaction,
99 ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
100 Box::pin(async { FreezeServiceClient::new(channel).freeze(request).await })
101 }
102}
103
104impl ValidateChecksums for FreezeTransactionData {
105 fn validate_checksums(&self, ledger_id: &crate::ledger_id::RefLedgerId) -> Result<(), Error> {
106 self.file_id.validate_checksums(ledger_id)
107 }
108}
109
110impl ToTransactionDataProtobuf for FreezeTransactionData {
111 fn to_transaction_data_protobuf(
112 &self,
113 chunk_info: &ChunkInfo,
114 ) -> services::transaction_body::Data {
115 let _ = chunk_info.assert_single_transaction();
116
117 services::transaction_body::Data::Freeze(self.to_protobuf())
118 }
119}
120
121impl ToSchedulableTransactionDataProtobuf for FreezeTransactionData {
122 fn to_schedulable_transaction_data_protobuf(
123 &self,
124 ) -> services::schedulable_transaction_body::Data {
125 services::schedulable_transaction_body::Data::Freeze(self.to_protobuf())
126 }
127}
128
129impl From<FreezeTransactionData> for AnyTransactionData {
130 fn from(transaction: FreezeTransactionData) -> Self {
131 Self::Freeze(transaction)
132 }
133}
134
135impl FromProtobuf<services::FreezeTransactionBody> for FreezeTransactionData {
136 fn from_protobuf(pb: services::FreezeTransactionBody) -> crate::Result<Self> {
137 Ok(Self {
138 start_time: pb.start_time.map(Into::into),
139 file_id: Option::from_protobuf(pb.update_file)?,
140 file_hash: Some(pb.file_hash),
141 freeze_type: FreezeType::from(pb.freeze_type),
142 })
143 }
144}
145
146impl ToProtobuf for FreezeTransactionData {
147 type Protobuf = services::FreezeTransactionBody;
148
149 fn to_protobuf(&self) -> Self::Protobuf {
150 services::FreezeTransactionBody {
151 update_file: self.file_id.to_protobuf(),
152 file_hash: self.file_hash.clone().unwrap_or_default(),
153 start_time: self.start_time.map(Into::into),
154 freeze_type: self.freeze_type as _,
155 ..Default::default()
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use expect_test::expect;
163 use hex_literal::hex;
164 use hiero_sdk_proto::services;
165 use time::OffsetDateTime;
166
167 use crate::protobuf::{
168 FromProtobuf,
169 ToProtobuf,
170 };
171 use crate::system::FreezeTransactionData;
172 use crate::transaction::test_helpers::{
173 check_body,
174 transaction_body,
175 VALID_START,
176 };
177 use crate::{
178 AnyTransaction,
179 FileId,
180 FreezeTransaction,
181 FreezeType,
182 };
183
184 const FILE_ID: FileId = FileId::new(4, 5, 6);
185 const FILE_HASH: [u8; 14] = hex!("1723904587120938954702349857");
186 const START_TIME: OffsetDateTime = VALID_START;
187 const FREEZE_TYPE: FreezeType = FreezeType::FreezeAbort;
188
189 fn make_transaction() -> FreezeTransaction {
190 let mut tx = FreezeTransaction::new_for_tests();
191
192 tx.file_id(FILE_ID)
193 .file_hash(FILE_HASH.to_vec())
194 .start_time(START_TIME)
195 .freeze_type(FREEZE_TYPE)
196 .freeze()
197 .unwrap();
198
199 tx
200 }
201
202 #[test]
203 fn serialize() {
204 let tx = make_transaction();
205
206 let tx = transaction_body(tx);
207
208 let tx = check_body(tx);
209
210 expect![[r#"
211 Freeze(
212 FreezeTransactionBody {
213 start_hour: 0,
214 start_min: 0,
215 end_hour: 0,
216 end_min: 0,
217 update_file: Some(
218 FileId {
219 shard_num: 4,
220 realm_num: 5,
221 file_num: 6,
222 },
223 ),
224 file_hash: [
225 23,
226 35,
227 144,
228 69,
229 135,
230 18,
231 9,
232 56,
233 149,
234 71,
235 2,
236 52,
237 152,
238 87,
239 ],
240 start_time: Some(
241 Timestamp {
242 seconds: 1554158542,
243 nanos: 0,
244 },
245 ),
246 freeze_type: FreezeAbort,
247 },
248 )
249 "#]]
250 .assert_debug_eq(&tx)
251 }
252
253 #[test]
254 fn to_from_bytes() {
255 let tx = make_transaction();
256
257 let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
258
259 let tx = transaction_body(tx);
260
261 let tx2 = transaction_body(tx2);
262
263 assert_eq!(tx, tx2);
264 }
265
266 #[test]
267 fn from_proto_body() {
268 let tx = services::FreezeTransactionBody {
269 update_file: Some(FILE_ID.to_protobuf()),
270 file_hash: FILE_HASH.to_vec(),
271 start_time: Some(START_TIME.to_protobuf()),
272 freeze_type: FREEZE_TYPE as i32,
273 ..Default::default()
274 };
275
276 let tx = FreezeTransactionData::from_protobuf(tx).unwrap();
277
278 assert_eq!(tx.file_id, Some(FILE_ID));
279 assert_eq!(tx.file_hash.as_deref(), Some(FILE_HASH.as_slice()));
280 assert_eq!(tx.start_time, Some(START_TIME));
281 assert_eq!(tx.freeze_type, FREEZE_TYPE);
282 }
283
284 mod get_set {
285 use super::*;
286
287 #[test]
288 fn file_id() {
289 let mut tx = FreezeTransaction::new();
290 tx.file_id(FILE_ID);
291
292 assert_eq!(tx.get_file_id(), Some(FILE_ID));
293 }
294
295 #[test]
296 #[should_panic]
297 fn file_id_frozen_panics() {
298 make_transaction().file_id(FILE_ID);
299 }
300
301 #[test]
302 fn file_hash() {
303 let mut tx = FreezeTransaction::new();
304 tx.file_hash(FILE_HASH.to_vec());
305
306 assert_eq!(tx.get_file_hash(), Some(FILE_HASH.as_slice()));
307 }
308
309 #[test]
310 #[should_panic]
311 fn file_hash_frozen_panics() {
312 make_transaction().file_hash(FILE_HASH.to_vec());
313 }
314
315 #[test]
316 fn start_time() {
317 let mut tx = FreezeTransaction::new();
318 tx.start_time(START_TIME);
319
320 assert_eq!(tx.get_start_time(), Some(START_TIME));
321 }
322
323 #[test]
324 #[should_panic]
325 fn start_time_frozen_panics() {
326 make_transaction().start_time(START_TIME);
327 }
328
329 #[test]
330 fn freeze_type() {
331 let mut tx = FreezeTransaction::new();
332 tx.freeze_type(FREEZE_TYPE);
333
334 assert_eq!(tx.get_freeze_type(), FREEZE_TYPE);
335 }
336
337 #[test]
338 #[should_panic]
339 fn freeze_type_frozen_panics() {
340 make_transaction().freeze_type(FREEZE_TYPE);
341 }
342 }
343}