1use std::borrow::Cow;
4use std::collections::HashMap;
5
6use hedera_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: vec![],
291 batch_key: None,
292 }
293 }
294}
295
296pub(crate) struct SourceTransaction<'a, D> {
298 inner: &'a Transaction<D>,
299 sources: Cow<'a, TransactionSources>,
300}
301
302impl<'a, D> SourceTransaction<'a, D> {
303 pub(crate) fn new(transaction: &'a Transaction<D>, sources: &'a TransactionSources) -> Self {
304 let sources = sources.sign_with(&transaction.signers);
306
307 Self { inner: transaction, sources }
308 }
309
310 pub(crate) async fn execute(
311 &self,
312 client: &Client,
313 timeout: Option<std::time::Duration>,
314 ) -> crate::Result<TransactionResponse>
315 where
316 D: TransactionExecute,
317 {
318 Ok(self.execute_all(client, timeout).await?.swap_remove(0))
319 }
320
321 pub(crate) async fn execute_all(
322 &self,
323 client: &Client,
324 timeout_per_chunk: Option<std::time::Duration>,
325 ) -> crate::Result<Vec<TransactionResponse>>
326 where
327 D: TransactionExecute,
328 {
329 let mut responses = Vec::with_capacity(self.sources.chunks_len());
330 for chunk in self.sources.chunks() {
331 let response = crate::execute::execute(
332 client,
333 &SourceTransactionExecuteView::new(self.inner, chunk),
334 timeout_per_chunk,
335 )
336 .await?;
337
338 if self.inner.data().wait_for_receipt() {
339 response.get_receipt(client).await?;
340 }
341
342 responses.push(response);
343 }
344
345 Ok(responses)
346 }
347}
348
349struct SourceTransactionExecuteView<'a, D> {
351 transaction: &'a Transaction<D>,
352 chunk: SourceChunk<'a>,
353 indecies_by_node_id: HashMap<AccountId, usize>,
354}
355
356impl<'a, D> SourceTransactionExecuteView<'a, D> {
357 fn new(transaction: &'a Transaction<D>, chunk: SourceChunk<'a>) -> Self {
358 let indecies_by_node_id =
359 chunk.node_ids().iter().copied().enumerate().map(|it| (it.1, it.0)).collect();
360 Self { transaction, chunk, indecies_by_node_id }
361 }
362}
363
364impl<'a, D: ValidateChecksums> ValidateChecksums for SourceTransactionExecuteView<'a, D> {
365 fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
366 self.transaction.validate_checksums(ledger_id)
367 }
368}
369
370impl<'a, D: TransactionExecute> Execute for SourceTransactionExecuteView<'a, D> {
371 type GrpcRequest = <Transaction<D> as Execute>::GrpcRequest;
372
373 type GrpcResponse = <Transaction<D> as Execute>::GrpcResponse;
374
375 type Context = <Transaction<D> as Execute>::Context;
376
377 type Response = <Transaction<D> as Execute>::Response;
378
379 fn node_account_ids(&self) -> Option<&[AccountId]> {
380 let node_ids = self.chunk.node_ids();
381 if node_ids.is_empty() {
382 None } else {
384 Some(node_ids)
385 }
386 }
387
388 fn transaction_id(&self) -> Option<TransactionId> {
389 self.chunk.transaction_id()
390 }
391
392 fn requires_transaction_id(&self) -> bool {
393 true
394 }
395
396 fn operator_account_id(&self) -> Option<&AccountId> {
397 None
398 }
399
400 fn regenerate_transaction_id(&self) -> Option<bool> {
401 Some(self.chunk.transaction_id().is_none())
402 }
403
404 fn make_request(
405 &self,
406 transaction_id: Option<&TransactionId>,
407 node_account_id: AccountId,
408 ) -> crate::Result<(Self::GrpcRequest, Self::Context)> {
409 debug_assert_eq!(transaction_id, self.transaction_id().as_ref());
410
411 let index = *self.indecies_by_node_id.get(&node_account_id).unwrap();
412 Ok((self.chunk.transactions()[index].clone(), self.chunk.transaction_hashes()[index]))
413 }
414
415 fn execute(
416 &self,
417 channel: Channel,
418 request: Self::GrpcRequest,
419 ) -> BoxGrpcFuture<Self::GrpcResponse> {
420 self.transaction.execute(channel, request)
421 }
422
423 fn make_response(
424 &self,
425 response: Self::GrpcResponse,
426 context: Self::Context,
427 node_account_id: AccountId,
428 transaction_id: Option<&TransactionId>,
429 ) -> crate::Result<Self::Response> {
430 self.transaction.make_response(response, context, node_account_id, transaction_id)
431 }
432
433 fn make_error_pre_check(
434 &self,
435 status: crate::Status,
436 transaction_id: Option<&TransactionId>,
437 response: Self::GrpcResponse,
438 ) -> crate::Error {
439 self.transaction.make_error_pre_check(status, transaction_id, response)
440 }
441
442 fn response_pre_check_status(response: &Self::GrpcResponse) -> crate::Result<i32> {
443 Transaction::<D>::response_pre_check_status(response)
444 }
445}