xpx_chain_sdk/models/transaction/
transaction.rs

1/*
2 * Copyright 2018 ProximaX Limited. All rights reserved.
3 * Use of this source code is governed by the Apache 2.0
4 * license that can be found in the LICENSE file.
5 */
6
7use sha3::{Digest, Sha3_256};
8
9use ::std::fmt;
10use std::any::Any;
11use serde_json::Value;
12use crate::account::{Account, PublicAccount};
13
14use crate::anyhow::Result;
15use crate::{AsUint64, GenerationHash};
16use crate::helpers::{hex_decode, hex_encode, TransactionHash};
17use crate::models::consts::{HALF_OF_SIGNATURE, SIGNATURE_SIZE, SIGNER_SIZE, SIZE_SIZE};
18use crate::transaction::{DEFAULT_FEE_CALCULATION_STRATEGY, SignedTransaction, TransactionType};
19
20use super::{
21    CommonTransaction, deadline::Deadline, TransactionStatus,
22};
23
24pub type Amount = u64;
25
26pub type Height = u64;
27
28pub type TransactionsStatus = Vec<TransactionStatus>;
29
30pub type Transactions = Vec<Box<dyn Transaction>>;
31
32pub(crate) struct AbsVector<'b> {
33    pub signature_vec: fb::WIPOffset<fb::Vector<'b, u8>>,
34    pub signer_vec: fb::WIPOffset<fb::Vector<'b, u8>>,
35    pub version_vec: fb::UOffsetT,
36    pub type_vec: u16,
37    pub max_fee_vec: fb::WIPOffset<fb::Vector<'b, u32>>,
38    pub deadline_vec: fb::WIPOffset<fb::Vector<'b, u32>>,
39}
40
41impl<'b> AbsVector<'b> {
42    pub fn build_vector(
43        common: &CommonTransaction,
44        size: usize,
45        builder: &mut fb::FlatBufferBuilder<'b>,
46    ) -> Self {
47        let max_fee = match common.max_fee {
48            Some(item) => item,
49            _ => DEFAULT_FEE_CALCULATION_STRATEGY * (size as u64),
50        };
51
52        let deadline = match common.deadline {
53            Some(item) => item,
54            _ => Deadline::default(),
55        };
56
57        let network_type: fb::UOffsetT = common.network_type.value() as u32;
58
59        let version_vec = (network_type << 24) + *common.version as fb::UOffsetT;
60        let signature_vec = builder.create_vector(&[0u8; SIGNATURE_SIZE]);
61        let signer_vec = builder.create_vector(&[0u8; SIGNER_SIZE]);
62        let deadline_vec = builder.create_vector(&deadline.to_dto());
63
64        let max_fee_vec = builder.create_vector(&max_fee.to_dto());
65
66        AbsVector {
67            signature_vec,
68            signer_vec,
69            version_vec,
70            type_vec: common.transaction_type.value(),
71            max_fee_vec,
72            deadline_vec,
73        }
74    }
75}
76
77/// An abstract transaction trait that serves as the base of all transaction types.
78///
79#[typetag::serde]
80pub trait Transaction
81where
82    Self: fmt::Debug + 'static + Sync + Send,
83{
84    fn size(&self) -> usize;
85
86    /// Serialize this transaction object.
87    fn as_value(&self) -> Value;
88
89    /// Get the `CommonTransaction`.
90    fn get_common_transaction(&self) -> CommonTransaction;
91
92    /// Get the `TransactionType`.
93    fn get_transaction_type(&self) -> TransactionType {
94        self.get_common_transaction().transaction_type
95    }
96
97    /// Get the `TransactionHash`.
98    fn get_transaction_hash(&self) -> TransactionHash {
99        self.get_common_transaction().get_hash()
100    }
101
102    /// Generate signing bytes.
103    ///
104    /// # Inputs
105    ///
106    /// * `payload_bytes`: Payload buffer.
107    /// * `generation_hash_bytes`: GenerationHash buffer.
108    ///
109    /// # Returns
110    ///
111    /// A `Vec<u8>`.
112    fn get_signing_bytes(&self, payload_bytes: &[u8], generation_hash_bytes: &[u8]) -> Vec<u8> {
113        let byte_buffer_without_header = &payload_bytes[SIZE_SIZE + SIGNATURE_SIZE + SIGNER_SIZE..];
114        if self.get_transaction_type() == TransactionType::AggregateBonded
115            || self.get_transaction_type() == TransactionType::AggregateComplete
116        {
117            [generation_hash_bytes[..32].as_ref(), byte_buffer_without_header].concat()
118        } else {
119            [generation_hash_bytes, byte_buffer_without_header].concat()
120        }
121    }
122
123    /// Serialize and sign transaction creating a SignedTransaction.
124    ///
125    /// # Inputs
126    ///
127    /// * `account`: The account to sign the transaction.
128    /// * `generation_hash`: Network generation hash hex.
129    ///
130    /// # Returns
131    ///
132    /// A Symbol `SignedTransaction`.
133    fn sign_with(
134        &self,
135        account: Account,
136        generation_hash: GenerationHash,
137    ) -> Result<SignedTransaction> {
138        let generation_hash_bytes = generation_hash.to_fixed_bytes();
139        let byte_buffer = self.to_serializer();
140
141        let signing_bytes =
142            self.get_signing_bytes(byte_buffer.as_ref(), generation_hash_bytes.as_ref());
143
144        let signature = account.key_pair.sign(signing_bytes.as_ref());
145
146        let mut signed_transaction_buffer = Vec::with_capacity(byte_buffer.len());
147        signed_transaction_buffer.extend_from_slice(byte_buffer[..4].as_ref());
148        signed_transaction_buffer.extend_from_slice(&signature.to_bytes());
149        signed_transaction_buffer.extend_from_slice(account.public_account.public_key.as_ref());
150        signed_transaction_buffer.extend_from_slice(
151            byte_buffer[SIZE_SIZE + SIGNATURE_SIZE + SIGNER_SIZE..byte_buffer.len()]
152                .as_ref(),
153        );
154
155        let payload = hex_encode(signed_transaction_buffer.as_ref());
156
157
158        let transaction_hash =
159            <dyn Transaction>::create_transaction_hash(&payload, &generation_hash_bytes);
160
161        Ok(SignedTransaction {
162            payload: payload.to_uppercase(),
163            hash: transaction_hash,
164            signer: account.public_key_to_hex(),
165            entity_type: self.get_transaction_type(),
166            network_type: self.get_common_transaction().network_type.value(),
167        })
168    }
169
170    /// An abstract method to generate the embedded transaction bytes.
171    fn to_serializer(&self) -> Vec<u8>;
172
173    fn set_aggregate(&mut self, signer: PublicAccount) {
174        self.get_common_transaction().set_aggregate(signer)
175    }
176
177    /// Transaction pending to be included in a block.
178    fn is_unconfirmed(&self) -> bool {
179        self.get_common_transaction().is_unconfirmed()
180    }
181
182    /// Transaction included in a block.
183    fn is_confirmed(&self) -> bool {
184        self.get_common_transaction().is_confirmed()
185    }
186
187    /// if a transaction has missing signatures.
188    fn has_missing_signatures(&self) -> bool {
189        self.get_common_transaction().has_missing_signatures()
190    }
191
192    /// Transaction is not known by the network
193    fn is_unannounced(&self) -> bool {
194        self.get_common_transaction().is_unannounced()
195    }
196
197    fn as_any(&self) -> &dyn Any;
198
199    fn into_any(self: Box<Self>) -> Box<dyn Any>;
200
201    fn box_clone(&self) -> Box<dyn Transaction>;
202}
203
204impl dyn Transaction {
205    /// Transaction header size
206    ///
207    /// Included fields are `size`, `signer_public_key` and `signature`.
208    ///
209    const HEADER_SIZE: usize = 4 + 32 + 64;
210
211    /// Index of the transaction *type*
212    ///
213    /// Included fields are the transaction header, `version` and `network`
214    const TYPE_INDEX: usize = Self::HEADER_SIZE + 2;
215
216    /// Index of the transaction *body*
217    ///
218    /// Included fields are the transaction header, `version`, `type`, `maxFee` and `deadline`
219    const BODY_INDEX: usize = Self::HEADER_SIZE + 4 + 2 + 8 + 8;
220
221    /// Generate transaction hash hex.
222    ///
223    /// # Inputs
224    ///
225    /// * `transaction_payload`: HexString Payload.
226    /// * `generation_hash`: Network generation hash byte.
227    ///
228    /// # Returns
229    ///
230    /// A Transaction Payload hash.
231    fn create_transaction_hash(
232        transaction_payload: &str,
233        generation_hash: &[u8],
234    ) -> TransactionHash {
235        let mut transaction_hash = TransactionHash::zero();
236
237        let transaction_bytes = hex_decode(transaction_payload);
238
239        // read transaction type
240        static TYPE_IDX: usize = <dyn Transaction>::TYPE_INDEX;
241
242        let mut sb = vec![];
243
244        sb.extend_from_slice(&transaction_bytes[SIZE_SIZE..SIZE_SIZE + HALF_OF_SIGNATURE]);
245
246        sb.extend_from_slice(
247            &transaction_bytes
248                [SIZE_SIZE + SIGNATURE_SIZE..SIZE_SIZE + SIGNATURE_SIZE + SIGNER_SIZE],
249        );
250
251        sb.extend_from_slice(generation_hash);
252
253        sb.extend_from_slice(&transaction_bytes[100..]);
254
255        let sha3_hash = Sha3_256::digest(sb.as_slice());
256
257        transaction_hash.assign_from_slice(&sha3_hash.as_slice()[0..32]);
258        transaction_hash
259    }
260
261    /// Downcast a reference to this generic [`Transaction`] to a specific transaction type.
262    ///
263    /// # Panics
264    ///
265    /// Panics if the transaction type is not `T`. In normal usage, you should know the
266    /// specific Transaction type. In other cases, use `try_downcast_ref`.
267    ///
268    pub fn downcast_ref<T: Transaction>(&self) -> &T {
269        self.try_downcast_ref::<T>().unwrap_or_else(|| {
270            panic!(
271                "downcast to wrong Transaction type; original transaction type: {}",
272                self.get_transaction_type()
273            )
274        })
275    }
276
277    /// Downcast a reference to this generic [`Transaction`] to a specific transaction type.
278    #[inline]
279    pub fn try_downcast_ref<T: Transaction>(&self) -> Option<&T> {
280        self.as_any().downcast_ref::<T>()
281    }
282
283    /// Downcast this generic [`Transaction`] to a specific transaction type.
284    ///
285    /// # Panics
286    ///
287    /// Panics if the transaction type is not `T`. In normal usage, you should know the
288    /// specific transaction type. In other cases, use `try_downcast`.
289    ///
290    pub fn downcast<T: Transaction>(self: Box<Self>) -> Box<T> {
291        self.try_downcast().unwrap_or_else(|err| panic!("{}", err))
292    }
293
294    /// Downcast this generic [`Transaction`] to a specific transaction type.
295    #[inline]
296    pub fn try_downcast<T: Transaction>(self: Box<Self>) -> Result<Box<T>> {
297        if self.as_ref().as_any().is::<T>() {
298            Ok(self.into_any().downcast().unwrap())
299        } else {
300            Err(anyhow!(
301				"downcast to wrong Transaction type; original transaction type: {}",
302				self.get_transaction_type()
303			))
304        }
305    }
306}
307
308// implement Clone manually by forwarding to clone_box.
309impl Clone for Box<dyn Transaction + 'static> {
310    fn clone(&self) -> Box<dyn Transaction + 'static> {
311        self.box_clone()
312    }
313}
314
315impl<'a> PartialEq for &'a dyn Transaction {
316    fn eq(&self, other: &Self) -> bool {
317        self.to_serializer() == other.to_serializer()
318    }
319}
320
321impl fmt::Display for dyn Transaction {
322    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323        write!(f, "{}", serde_json::to_string_pretty(&self).unwrap_or_default())
324    }
325}