1use std::borrow::Cow;
4use std::collections::HashMap;
5
6use hiero_sdk_proto::services;
7use prost::Message;
8use tonic::transport::Channel;
9
10use super::chunked::ChunkInfo;
11use super::source::SourceChunk;
12use super::{
13 ChunkData,
14 TransactionSources,
15};
16use crate::execute::Execute;
17use crate::ledger_id::RefLedgerId;
18use crate::transaction::any::AnyTransactionData;
19use crate::transaction::protobuf::ToTransactionDataProtobuf;
20use crate::transaction::DEFAULT_TRANSACTION_VALID_DURATION;
21use crate::{
22 AccountId,
23 BoxGrpcFuture,
24 Client,
25 Error,
26 Hbar,
27 PublicKey,
28 ToProtobuf,
29 Transaction,
30 TransactionHash,
31 TransactionId,
32 TransactionResponse,
33 ValidateChecksums,
34};
35
36#[derive(Debug)]
37pub(super) struct SignaturePair {
38 signature: Vec<u8>,
39 public: PublicKey,
40}
41
42impl SignaturePair {
43 pub fn into_protobuf(self) -> services::SignaturePair {
44 let signature = match self.public.kind() {
45 crate::key::KeyKind::Ed25519 => {
46 services::signature_pair::Signature::Ed25519(self.signature)
47 }
48 crate::key::KeyKind::Ecdsa => {
49 services::signature_pair::Signature::EcdsaSecp256k1(self.signature)
50 }
51 };
52 services::SignaturePair {
53 signature: Some(signature),
54 pub_key_prefix: self.public.to_bytes_raw(),
56 }
57 }
58}
59
60impl From<(PublicKey, Vec<u8>)> for SignaturePair {
61 fn from((public, signature): (PublicKey, Vec<u8>)) -> Self {
62 Self { signature, public }
63 }
64}
65
66impl<D> Transaction<D>
67where
68 D: TransactionData + ToTransactionDataProtobuf,
69{
70 pub(crate) fn make_request_inner(
71 &self,
72 chunk_info: &ChunkInfo,
73 ) -> (services::Transaction, TransactionHash) {
74 let transaction_body = self.to_transaction_body_protobuf(chunk_info);
75
76 let body_bytes = transaction_body.encode_to_vec();
77
78 let mut signatures = Vec::with_capacity(1 + self.signers.len());
79
80 if let Some(operator) = &self.body.operator {
81 let operator_signature = operator.sign(&body_bytes);
82
83 signatures.push(SignaturePair::from(operator_signature).into_protobuf());
84 }
85
86 for signer in &self.signers {
87 let public_key = signer.public_key().to_bytes();
88 if !signatures.iter().any(|it| public_key.starts_with(&it.pub_key_prefix)) {
89 let signature = signer.sign(&body_bytes);
90 signatures.push(SignaturePair::from(signature).into_protobuf());
91 }
92 }
93
94 let signed_transaction = services::SignedTransaction {
95 body_bytes,
96 sig_map: Some(services::SignatureMap { sig_pair: signatures }),
97 use_serialized_tx_message_hash_algorithm: false,
98 };
99
100 let signed_transaction_bytes = signed_transaction.encode_to_vec();
101
102 let transaction_hash = TransactionHash::new(&signed_transaction_bytes);
103
104 let transaction =
105 services::Transaction { signed_transaction_bytes, ..services::Transaction::default() };
106
107 (transaction, transaction_hash)
108 }
109}
110
111pub trait TransactionData: Clone + Into<AnyTransactionData> {
113 #[doc(hidden)]
115 fn for_cost_estimate(&self) -> bool {
116 false
117 }
118
119 fn default_max_transaction_fee(&self) -> Hbar {
125 Hbar::new(2)
126 }
127
128 fn maybe_chunk_data(&self) -> Option<&ChunkData> {
130 None
131 }
132
133 fn wait_for_receipt(&self) -> bool {
135 false
136 }
137}
138
139pub trait TransactionExecute:
140 ToTransactionDataProtobuf + TransactionData + ValidateChecksums
141{
142 fn execute(
143 &self,
144 channel: Channel,
145 request: services::Transaction,
146 ) -> BoxGrpcFuture<'_, services::TransactionResponse>;
147}
148
149impl<D> Execute for Transaction<D>
150where
151 D: TransactionExecute,
152{
153 type GrpcRequest = services::Transaction;
154
155 type GrpcResponse = services::TransactionResponse;
156
157 type Context = TransactionHash;
158
159 type Response = TransactionResponse;
160
161 fn node_account_ids(&self) -> Option<&[AccountId]> {
162 self.body.node_account_ids.as_deref()
163 }
164
165 fn transaction_id(&self) -> Option<TransactionId> {
166 self.body.transaction_id
167 }
168
169 fn requires_transaction_id(&self) -> bool {
170 true
171 }
172
173 fn operator_account_id(&self) -> Option<&AccountId> {
174 self.body.operator.as_deref().map(|it| &it.account_id)
175 }
176
177 fn regenerate_transaction_id(&self) -> Option<bool> {
178 self.body.regenerate_transaction_id
179 }
180
181 fn grpc_deadline(&self) -> Option<std::time::Duration> {
182 self.grpc_deadline
183 }
184
185 fn request_timeout(&self) -> Option<std::time::Duration> {
186 self.request_timeout
187 }
188
189 fn make_request(
190 &self,
191 transaction_id: Option<&TransactionId>,
192 node_account_id: AccountId,
193 ) -> crate::Result<(Self::GrpcRequest, Self::Context)> {
194 assert!(self.is_frozen());
195
196 Ok(self.make_request_inner(&ChunkInfo::single(
197 *transaction_id.ok_or(Error::NoPayerAccountOrTransactionId)?,
198 node_account_id,
199 )))
200 }
201
202 fn execute(
203 &self,
204 channel: Channel,
205 request: Self::GrpcRequest,
206 ) -> BoxGrpcFuture<'_, Self::GrpcResponse> {
207 self.body.data.execute(channel, request)
208 }
209
210 fn make_response(
211 &self,
212 _response: Self::GrpcResponse,
213 transaction_hash: Self::Context,
214 node_account_id: AccountId,
215 transaction_id: Option<&TransactionId>,
216 ) -> crate::Result<Self::Response> {
217 Ok(TransactionResponse {
218 node_account_id,
219 transaction_id: *transaction_id.unwrap(),
220 transaction_hash,
221 validate_status: true,
222 })
223 }
224
225 fn make_error_pre_check(
226 &self,
227 status: crate::Status,
228 transaction_id: Option<&TransactionId>,
229 response: Self::GrpcResponse,
230 ) -> crate::Error {
231 crate::Error::TransactionPreCheckStatus {
232 status,
233 cost: (response.cost != 0).then(|| Hbar::from_tinybars(response.cost as i64)),
234 transaction_id: Box::new(
235 *transaction_id.expect("transactions must have transaction IDs"),
236 ),
237 }
238 }
239
240 fn response_pre_check_status(response: &Self::GrpcResponse) -> crate::Result<i32> {
241 Ok(response.node_transaction_precheck_code)
242 }
243}
244
245pub trait TransactionExecuteChunked: TransactionExecute {}
247
248impl<D: ValidateChecksums> ValidateChecksums for Transaction<D> {
249 fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
250 if let Some(node_account_ids) = &self.body.node_account_ids {
251 for node_account_id in node_account_ids {
252 node_account_id.validate_checksums(ledger_id)?;
253 }
254 }
255 self.body.transaction_id.validate_checksums(ledger_id)?;
256 self.body.data.validate_checksums(ledger_id)
257 }
258}
259
260impl<D> Transaction<D>
261where
262 D: TransactionData + ToTransactionDataProtobuf,
263{
264 #[allow(deprecated)]
265 fn to_transaction_body_protobuf(&self, chunk_info: &ChunkInfo) -> services::TransactionBody {
266 let data = self.body.data.to_transaction_data_protobuf(chunk_info);
267
268 let transaction_fee = if self.body.data.for_cost_estimate() {
269 0
270 } else {
271 self.body
272 .max_transaction_fee
273 .unwrap_or_else(|| self.body.data.default_max_transaction_fee())
274 .to_tinybars() as u64
275 };
276
277 services::TransactionBody {
278 data: Some(data),
279 transaction_id: Some(chunk_info.current_transaction_id.to_protobuf()),
280 transaction_valid_duration: Some(
281 self.body
282 .transaction_valid_duration
283 .unwrap_or(DEFAULT_TRANSACTION_VALID_DURATION)
284 .into(),
285 ),
286 memo: self.body.transaction_memo.clone(),
287 node_account_id: chunk_info.node_account_id.to_protobuf(),
288 generate_record: false,
289 transaction_fee,
290 max_custom_fees: { self.body.custom_fee_limits.to_protobuf() },
291 batch_key: None,
292 high_volume: self.body.high_volume,
293 }
294 }
295}
296
297pub(crate) struct SourceTransaction<'a, D> {
299 inner: &'a Transaction<D>,
300 sources: Cow<'a, TransactionSources>,
301}
302
303impl<'a, D> SourceTransaction<'a, D> {
304 pub(crate) fn new(transaction: &'a Transaction<D>, sources: &'a TransactionSources) -> Self {
305 let sources = sources.sign_with(&transaction.signers);
307
308 Self { inner: transaction, sources }
309 }
310
311 pub(crate) async fn execute(
312 &self,
313 client: &Client,
314 timeout: Option<std::time::Duration>,
315 ) -> crate::Result<TransactionResponse>
316 where
317 D: TransactionExecute,
318 {
319 Ok(self.execute_all(client, timeout).await?.swap_remove(0))
320 }
321
322 pub(crate) async fn execute_all(
323 &self,
324 client: &Client,
325 timeout_per_chunk: Option<std::time::Duration>,
326 ) -> crate::Result<Vec<TransactionResponse>>
327 where
328 D: TransactionExecute,
329 {
330 let mut responses = Vec::with_capacity(self.sources.chunks_len());
331 for chunk in self.sources.chunks() {
332 let response = crate::execute::execute(
333 client,
334 &SourceTransactionExecuteView::new(self.inner, chunk),
335 timeout_per_chunk,
336 )
337 .await?;
338
339 if self.inner.data().wait_for_receipt() {
340 response.get_receipt(client).await?;
341 }
342
343 responses.push(response);
344 }
345
346 Ok(responses)
347 }
348}
349
350struct SourceTransactionExecuteView<'a, D> {
352 transaction: &'a Transaction<D>,
353 chunk: SourceChunk<'a>,
354 indecies_by_node_id: HashMap<AccountId, usize>,
355}
356
357impl<'a, D> SourceTransactionExecuteView<'a, D> {
358 fn new(transaction: &'a Transaction<D>, chunk: SourceChunk<'a>) -> Self {
359 let indecies_by_node_id =
360 chunk.node_ids().iter().copied().enumerate().map(|it| (it.1, it.0)).collect();
361 Self { transaction, chunk, indecies_by_node_id }
362 }
363}
364
365impl<'a, D: ValidateChecksums> ValidateChecksums for SourceTransactionExecuteView<'a, D> {
366 fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
367 self.transaction.validate_checksums(ledger_id)
368 }
369}
370
371impl<'a, D: TransactionExecute> Execute for SourceTransactionExecuteView<'a, D> {
372 type GrpcRequest = <Transaction<D> as Execute>::GrpcRequest;
373
374 type GrpcResponse = <Transaction<D> as Execute>::GrpcResponse;
375
376 type Context = <Transaction<D> as Execute>::Context;
377
378 type Response = <Transaction<D> as Execute>::Response;
379
380 fn node_account_ids(&self) -> Option<&[AccountId]> {
381 let node_ids = self.chunk.node_ids();
382 if node_ids.is_empty() {
383 None } else {
385 Some(node_ids)
386 }
387 }
388
389 fn transaction_id(&self) -> Option<TransactionId> {
390 self.chunk.transaction_id()
391 }
392
393 fn requires_transaction_id(&self) -> bool {
394 true
395 }
396
397 fn operator_account_id(&self) -> Option<&AccountId> {
398 None
399 }
400
401 fn regenerate_transaction_id(&self) -> Option<bool> {
402 Some(self.chunk.transaction_id().is_none())
403 }
404
405 fn make_request(
406 &self,
407 transaction_id: Option<&TransactionId>,
408 node_account_id: AccountId,
409 ) -> crate::Result<(Self::GrpcRequest, Self::Context)> {
410 debug_assert_eq!(transaction_id, self.transaction_id().as_ref());
411
412 let index = *self.indecies_by_node_id.get(&node_account_id).unwrap();
413 Ok((self.chunk.transactions()[index].clone(), self.chunk.transaction_hashes()[index]))
414 }
415
416 fn execute(
417 &self,
418 channel: Channel,
419 request: Self::GrpcRequest,
420 ) -> BoxGrpcFuture<Self::GrpcResponse> {
421 self.transaction.execute(channel, request)
422 }
423
424 fn make_response(
425 &self,
426 response: Self::GrpcResponse,
427 context: Self::Context,
428 node_account_id: AccountId,
429 transaction_id: Option<&TransactionId>,
430 ) -> crate::Result<Self::Response> {
431 self.transaction.make_response(response, context, node_account_id, transaction_id)
432 }
433
434 fn make_error_pre_check(
435 &self,
436 status: crate::Status,
437 transaction_id: Option<&TransactionId>,
438 response: Self::GrpcResponse,
439 ) -> crate::Error {
440 self.transaction.make_error_pre_check(status, transaction_id, response)
441 }
442
443 fn response_pre_check_status(response: &Self::GrpcResponse) -> crate::Result<i32> {
444 Transaction::<D>::response_pre_check_status(response)
445 }
446}