waves_rust/model/transaction/
transaction_info.rs

1use crate::constants::HASH_LENGTH;
2use serde_json::Value;
3
4use crate::error::Error::{UnsupportedOperation, WrongTransactionType};
5use crate::error::{Error, Result};
6use crate::model::account::{PrivateKey, PublicKey};
7use crate::model::transaction::data_transaction::DataTransaction;
8use crate::model::transaction::TransactionData::Transfer;
9use crate::model::transaction::TransferTransaction;
10use crate::model::TransactionData::{
11    Burn, CreateAlias, Data, Ethereum, Exchange, Genesis, InvokeScript, Issue, Lease, LeaseCancel,
12    MassTransfer, Payment, Reissue, SetAssetScript, SetScript, SponsorFee, UpdateAssetInfo,
13};
14use crate::model::{
15    Address, AssetId, BurnTransaction, BurnTransactionInfo, ByteString, CreateAliasTransaction,
16    CreateAliasTransactionInfo, DataTransactionInfo, EthereumTransaction, EthereumTransactionInfo,
17    ExchangeTransaction, ExchangeTransactionInfo, GenesisTransaction, GenesisTransactionInfo, Id,
18    InvokeScriptTransaction, InvokeScriptTransactionInfo, IssueTransaction, IssueTransactionInfo,
19    LeaseCancelTransaction, LeaseCancelTransactionInfo, LeaseTransaction, LeaseTransactionInfo,
20    MassTransferTransaction, MassTransferTransactionInfo, PaymentTransaction,
21    PaymentTransactionInfo, Proof, ReissueTransaction, ReissueTransactionInfo,
22    SetAssetScriptTransaction, SetAssetScriptTransactionInfo, SetScriptTransaction,
23    SetScriptTransactionInfo, SponsorFeeTransaction, SponsorFeeTransactionInfo, TransactionBuilder,
24    TransferTransactionInfo, UpdateAssetInfoTransaction, UpdateAssetInfoTransactionInfo,
25};
26use crate::util::{sign_tx, Base58, BinarySerializer, Hash, JsonDeserializer, JsonSerializer};
27
28#[derive(Clone, Eq, PartialEq, Debug)]
29pub struct TransactionInfoResponse {
30    id: Id,
31    status: ApplicationStatus,
32    data: TransactionDataInfo,
33    fee: Amount,
34    timestamp: u64,
35    public_key: PublicKey,
36    tx_type: u8,
37    version: u8,
38    chain_id: u8,
39    height: u32,
40    proofs: Vec<Proof>,
41}
42
43#[allow(clippy::too_many_arguments)]
44impl TransactionInfoResponse {
45    pub fn new(
46        id: Id,
47        status: ApplicationStatus,
48        data: TransactionDataInfo,
49        fee: Amount,
50        timestamp: u64,
51        public_key: PublicKey,
52        tx_type: u8,
53        version: u8,
54        chain_id: u8,
55        height: u32,
56        proofs: Vec<Proof>,
57    ) -> TransactionInfoResponse {
58        TransactionInfoResponse {
59            id,
60            status,
61            data,
62            fee,
63            timestamp,
64            public_key,
65            tx_type,
66            version,
67            chain_id,
68            height,
69            proofs,
70        }
71    }
72
73    pub fn id(&self) -> Id {
74        self.id.clone()
75    }
76
77    pub fn status(&self) -> ApplicationStatus {
78        self.status
79    }
80
81    pub fn data(&self) -> TransactionDataInfo {
82        self.data.clone()
83    }
84
85    pub fn fee(&self) -> Amount {
86        self.fee.clone()
87    }
88
89    pub fn timestamp(&self) -> u64 {
90        self.timestamp
91    }
92
93    pub fn public_key(&self) -> PublicKey {
94        self.public_key.clone()
95    }
96
97    pub fn height(&self) -> u32 {
98        self.height
99    }
100
101    pub fn tx_type(&self) -> u8 {
102        self.tx_type
103    }
104
105    pub fn version(&self) -> u8 {
106        self.version
107    }
108
109    pub fn chain_id(&self) -> u8 {
110        self.chain_id
111    }
112
113    pub fn proofs(&self) -> Vec<Proof> {
114        self.proofs.clone()
115    }
116}
117
118#[derive(Debug, Clone, Copy, Eq, PartialEq)]
119pub enum ApplicationStatus {
120    Succeed,
121    ScriptExecutionFailed,
122    Unknown,
123}
124
125#[derive(Clone, Eq, PartialEq, Debug)]
126pub struct Transaction {
127    data: TransactionData,
128    fee: Amount,
129    timestamp: u64,
130    public_key: PublicKey,
131    tx_type: u8,
132    version: u8,
133    chain_id: u8,
134}
135
136#[derive(Clone, Eq, PartialEq, Debug)]
137pub struct Amount {
138    value: u64,
139    asset_id: Option<AssetId>,
140}
141
142impl Amount {
143    pub fn new(value: u64, asset_id: Option<AssetId>) -> Amount {
144        Amount { value, asset_id }
145    }
146
147    pub fn value(&self) -> u64 {
148        self.value
149    }
150
151    pub fn asset_id(&self) -> Option<AssetId> {
152        self.asset_id.clone()
153    }
154}
155
156impl Transaction {
157    pub fn new(
158        data: TransactionData,
159        fee: Amount,
160        timestamp: u64,
161        public_key: PublicKey,
162        version: u8,
163        chain_id: u8,
164    ) -> Transaction {
165        let tx_type = data.tx_type();
166        Transaction {
167            data,
168            fee,
169            timestamp,
170            public_key,
171            tx_type,
172            version,
173            chain_id,
174        }
175    }
176
177    pub fn with_defaults(
178        public_key: &PublicKey,
179        chain_id: u8,
180        tx_data: &TransactionData,
181    ) -> TransactionBuilder {
182        TransactionBuilder::new(public_key, chain_id, tx_data)
183    }
184
185    pub fn data(&self) -> &TransactionData {
186        &self.data
187    }
188
189    pub fn fee(&self) -> Amount {
190        self.fee.clone()
191    }
192
193    pub fn timestamp(&self) -> u64 {
194        self.timestamp
195    }
196
197    pub fn public_key(&self) -> PublicKey {
198        self.public_key.clone()
199    }
200
201    pub fn tx_type(&self) -> u8 {
202        self.tx_type
203    }
204
205    pub fn version(&self) -> u8 {
206        self.version
207    }
208
209    pub fn chain_id(&self) -> u8 {
210        self.chain_id
211    }
212
213    pub fn sign(&self, private_key: &PrivateKey) -> Result<SignedTransaction> {
214        sign_tx(self, private_key)
215    }
216
217    pub fn bytes(&self) -> Result<Vec<u8>> {
218        BinarySerializer::tx_body_bytes(self)
219    }
220
221    pub fn id(&self) -> Result<Id> {
222        match self.tx_type {
223            1 | 2 => Err(Error::UnsupportedOperation(
224                "id calculation from unsigned payment or genesis transaction".to_owned(),
225            )),
226            _ => Ok(Id::from_bytes(&Hash::blake(&self.bytes()?)?)),
227        }
228    }
229}
230
231#[derive(Clone, Eq, PartialEq, Debug)]
232//todo fix it
233#[allow(clippy::large_enum_variant)]
234pub enum TransactionDataInfo {
235    Genesis(GenesisTransactionInfo),
236    Payment(PaymentTransactionInfo),
237    Transfer(TransferTransactionInfo),
238    Data(DataTransactionInfo),
239    Issue(IssueTransactionInfo),
240    Reissue(ReissueTransactionInfo),
241    Lease(LeaseTransactionInfo),
242    LeaseCancel(LeaseCancelTransactionInfo),
243    CreateAlias(CreateAliasTransactionInfo),
244    MassTransfer(MassTransferTransactionInfo),
245    SetScript(SetScriptTransactionInfo),
246    SponsorFee(SponsorFeeTransactionInfo),
247    SetAssetScript(SetAssetScriptTransactionInfo),
248    Burn(BurnTransactionInfo),
249    Exchange(ExchangeTransactionInfo),
250    Invoke(InvokeScriptTransactionInfo),
251    UpdateAssetInfo(UpdateAssetInfoTransactionInfo),
252    Ethereum(EthereumTransactionInfo),
253}
254
255impl TransactionDataInfo {
256    pub fn transfer_tx(&self) -> Result<&TransferTransactionInfo> {
257        match self {
258            TransactionDataInfo::Transfer(tx) => Ok(tx),
259            tx => Err(WrongTransactionType {
260                expected_type: TransferTransaction::tx_type(),
261                actual_type: tx.tx_type(),
262            }),
263        }
264    }
265
266    pub fn data_tx(&self) -> Result<&DataTransactionInfo> {
267        match self {
268            TransactionDataInfo::Data(tx) => Ok(tx),
269            tx => Err(WrongTransactionType {
270                expected_type: DataTransaction::tx_type(),
271                actual_type: tx.tx_type(),
272            }),
273        }
274    }
275
276    pub fn tx_type(&self) -> u8 {
277        match self {
278            TransactionDataInfo::Genesis(_) => GenesisTransaction::tx_type(),
279            TransactionDataInfo::Payment(_) => PaymentTransaction::tx_type(),
280            TransactionDataInfo::Transfer(_) => TransferTransaction::tx_type(),
281            TransactionDataInfo::Data(_) => DataTransaction::tx_type(),
282            TransactionDataInfo::Issue(_) => IssueTransaction::tx_type(),
283            TransactionDataInfo::Exchange(_) => ExchangeTransaction::tx_type(),
284            TransactionDataInfo::Invoke(_) => InvokeScriptTransaction::tx_type(),
285            TransactionDataInfo::Reissue(_) => ReissueTransaction::tx_type(),
286            TransactionDataInfo::Burn(_) => BurnTransaction::tx_type(),
287            TransactionDataInfo::Lease(_) => LeaseTransaction::tx_type(),
288            TransactionDataInfo::LeaseCancel(_) => LeaseCancelTransaction::tx_type(),
289            TransactionDataInfo::CreateAlias(_) => CreateAliasTransaction::tx_type(),
290            TransactionDataInfo::MassTransfer(_) => MassTransferTransaction::tx_type(),
291            TransactionDataInfo::SetScript(_) => SetScriptTransaction::tx_type(),
292            TransactionDataInfo::SetAssetScript(_) => SetAssetScriptTransaction::tx_type(),
293            TransactionDataInfo::SponsorFee(_) => SponsorFeeTransaction::tx_type(),
294            TransactionDataInfo::UpdateAssetInfo(_) => UpdateAssetInfoTransaction::tx_type(),
295            TransactionDataInfo::Ethereum(_) => EthereumTransaction::tx_type(),
296        }
297    }
298}
299
300#[derive(Clone, Eq, PartialEq, Debug)]
301//todo fix it
302#[allow(clippy::large_enum_variant)]
303pub enum TransactionData {
304    Genesis(GenesisTransaction),
305    Payment(PaymentTransaction),
306    Transfer(TransferTransaction),
307    Reissue(ReissueTransaction),
308    Burn(BurnTransaction),
309    Lease(LeaseTransaction),
310    LeaseCancel(LeaseCancelTransaction),
311    CreateAlias(CreateAliasTransaction),
312    MassTransfer(MassTransferTransaction),
313    SetScript(SetScriptTransaction),
314    SponsorFee(SponsorFeeTransaction),
315    SetAssetScript(SetAssetScriptTransaction),
316    Data(DataTransaction),
317    Issue(IssueTransaction),
318    InvokeScript(InvokeScriptTransaction),
319    UpdateAssetInfo(UpdateAssetInfoTransaction),
320    Exchange(ExchangeTransaction),
321    Ethereum(EthereumTransaction),
322}
323
324impl TransactionData {
325    pub fn tx_type(&self) -> u8 {
326        match self {
327            Genesis(_) => GenesisTransaction::tx_type(),
328            Payment(_) => PaymentTransaction::tx_type(),
329            Transfer(_) => TransferTransaction::tx_type(),
330            Data(_) => DataTransaction::tx_type(),
331            Issue(_) => IssueTransaction::tx_type(),
332            InvokeScript(_) => InvokeScriptTransaction::tx_type(),
333            Exchange(_) => ExchangeTransaction::tx_type(),
334            Reissue(_) => ReissueTransaction::tx_type(),
335            Burn(_) => BurnTransaction::tx_type(),
336            Lease(_) => LeaseTransaction::tx_type(),
337            LeaseCancel(_) => LeaseCancelTransaction::tx_type(),
338            CreateAlias(_) => CreateAliasTransaction::tx_type(),
339            MassTransfer(_) => MassTransferTransaction::tx_type(),
340            SetScript(_) => SetScriptTransaction::tx_type(),
341            SetAssetScript(_) => SetAssetScriptTransaction::tx_type(),
342            SponsorFee(_) => SponsorFeeTransaction::tx_type(),
343            UpdateAssetInfo(_) => UpdateAssetInfoTransaction::tx_type(),
344            Ethereum(_) => EthereumTransaction::tx_type(),
345        }
346    }
347
348    pub fn get_min_supported_version(&self) -> u8 {
349        match self {
350            Genesis(_) => 2,
351            Payment(_) => 2,
352            Transfer(_) => 3,
353            Issue(_) => 3,
354            Reissue(_) => 3,
355            Burn(_) => 3,
356            Exchange(_) => 3,
357            Lease(_) => 3,
358            LeaseCancel(_) => 3,
359            CreateAlias(_) => 3,
360            MassTransfer(_) => 2,
361            Data(_) => 2,
362            SetScript(_) => 2,
363            SponsorFee(_) => 2,
364            SetAssetScript(_) => 2,
365            InvokeScript(_) => 2,
366            UpdateAssetInfo(_) => 1,
367            Ethereum(_) => 1,
368        }
369    }
370
371    pub fn get_min_fee(&self) -> Result<Amount> {
372        let value = match self {
373            Genesis(_) => 0,
374            Payment(_) => 1,
375            Transfer(_) => 100_000,
376            Issue(tx) => tx.min_fee().value,
377            Reissue(_) => 100_000,
378            Burn(_) => 100_000,
379            Exchange(_) => 300_000,
380            Lease(_) => 100_000,
381            LeaseCancel(_) => 100_000,
382            CreateAlias(_) => 100_000,
383            MassTransfer(_) => 100_000,
384            Data(_) => 100_000,
385            SetScript(_) => 1_000_000,
386            SponsorFee(_) => 100_000,
387            SetAssetScript(_) => 100_000_000,
388            InvokeScript(_) => 500_000,
389            UpdateAssetInfo(_) => 100_000,
390            Ethereum(_) => Err(UnsupportedOperation(
391                "Min fee for Ethereum transaction is undefined".into(),
392            ))?,
393        };
394        Ok(Amount::new(value, None))
395    }
396}
397
398#[derive(Clone, Eq, PartialEq, Debug)]
399pub struct SignedTransaction {
400    transaction: Transaction,
401    proofs: Vec<Proof>,
402}
403
404impl SignedTransaction {
405    pub fn new(transaction: Transaction, proofs: Vec<Proof>) -> SignedTransaction {
406        SignedTransaction {
407            transaction,
408            proofs,
409        }
410    }
411
412    pub fn tx(&self) -> &Transaction {
413        &self.transaction
414    }
415
416    pub fn id(&self) -> Result<Id> {
417        let tx = self.tx();
418        match tx.tx_type {
419            1 | 2 => Ok(Id::from_bytes(&self.proofs[0].bytes())),
420            _ => tx.id(),
421        }
422    }
423
424    pub fn proofs(&self) -> Vec<Proof> {
425        self.proofs.clone()
426    }
427
428    pub fn to_json(&self) -> Result<Value> {
429        JsonSerializer::serialize_signed_tx(self)
430    }
431
432    //todo sign
433}
434
435impl TryFrom<&Value> for TransactionInfoResponse {
436    type Error = Error;
437
438    fn try_from(value: &Value) -> Result<Self> {
439        let id = Id::from_string(&JsonDeserializer::safe_to_string_from_field(value, "id")?)?;
440
441        let signed_tx: SignedTransaction = value.try_into()?;
442
443        let tx_type = JsonDeserializer::safe_to_int_from_field(value, "type")? as u8;
444
445        let application_status = if tx_type == 1 || tx_type == 2 {
446            ApplicationStatus::Unknown
447        } else {
448            match value["applicationStatus"].as_str() {
449                Some(status) => match status {
450                    "succeeded" => ApplicationStatus::Succeed,
451                    "script_execution_failed" => ApplicationStatus::ScriptExecutionFailed,
452                    &_ => ApplicationStatus::Unknown,
453                },
454                None => ApplicationStatus::Unknown,
455            }
456        };
457        let height = JsonDeserializer::safe_to_int_from_field(value, "height")? as u32;
458        let transaction_data = match tx_type {
459            1 => TransactionDataInfo::Genesis(value.try_into()?),
460            2 => TransactionDataInfo::Payment(value.try_into()?),
461            3 => TransactionDataInfo::Issue(value.try_into()?),
462            4 => TransactionDataInfo::Transfer(value.try_into()?),
463            5 => TransactionDataInfo::Reissue(value.try_into()?),
464            6 => TransactionDataInfo::Burn(value.try_into()?),
465            7 => TransactionDataInfo::Exchange(value.try_into()?),
466            8 => TransactionDataInfo::Lease(value.try_into()?),
467            9 => TransactionDataInfo::LeaseCancel(value.try_into()?),
468            10 => TransactionDataInfo::CreateAlias(value.try_into()?),
469            11 => TransactionDataInfo::MassTransfer(value.try_into()?),
470            12 => TransactionDataInfo::Data(value.try_into()?),
471            13 => TransactionDataInfo::SetScript(value.try_into()?),
472            14 => TransactionDataInfo::SponsorFee(value.try_into()?),
473            15 => TransactionDataInfo::SetAssetScript(value.try_into()?),
474            16 => TransactionDataInfo::Invoke(value.try_into()?),
475            17 => TransactionDataInfo::UpdateAssetInfo(value.try_into()?),
476            18 => TransactionDataInfo::Ethereum(value.try_into()?),
477            _ => return Err(UnsupportedOperation("unknown tx type".to_owned())),
478        };
479        let timestamp = JsonDeserializer::safe_to_int_from_field(value, "timestamp")? as u64;
480
481        let tx = signed_tx.tx();
482        Ok(TransactionInfoResponse::new(
483            id,
484            application_status,
485            transaction_data,
486            tx.fee(),
487            timestamp,
488            tx.public_key(),
489            tx_type,
490            tx.version(),
491            tx.chain_id(),
492            height,
493            signed_tx.proofs(),
494        ))
495    }
496}
497
498impl TryFrom<&Value> for SignedTransaction {
499    type Error = Error;
500
501    fn try_from(value: &Value) -> Result<Self> {
502        let transaction: Transaction = value.try_into()?;
503
504        let proofs_array = match transaction.tx_type() {
505            1 => vec![Value::String(JsonDeserializer::safe_to_string_from_field(
506                value,
507                "signature",
508            )?)],
509            18 => vec![],
510            _ => JsonDeserializer::safe_to_array_from_field(value, "proofs")?,
511        };
512
513        let proofs = proofs_array
514            .iter()
515            .map(|v| {
516                Ok(Proof::new(Base58::decode(
517                    &JsonDeserializer::safe_to_string(v)?,
518                )?))
519            })
520            .collect::<Result<Vec<Proof>>>()?;
521        Ok(SignedTransaction::new(transaction, proofs))
522    }
523}
524
525impl TryFrom<&Value> for Transaction {
526    type Error = Error;
527
528    fn try_from(value: &Value) -> Result<Self> {
529        let tx_type = JsonDeserializer::safe_to_int_from_field(value, "type")? as u8;
530        let fee = JsonDeserializer::safe_to_int_from_field(value, "fee")? as u64;
531        let fee_asset_id = match value["feeAssetId"].as_str() {
532            Some(val) => Some(AssetId::from_string(val)?),
533            None => None,
534        };
535        let transaction_data = match tx_type {
536            1 => Genesis(value.try_into()?),
537            2 => Payment(value.try_into()?),
538            3 => Issue(value.try_into()?),
539            4 => Transfer(value.try_into()?),
540            5 => Reissue(value.try_into()?),
541            6 => Burn(value.try_into()?),
542            7 => Exchange(value.try_into()?),
543            8 => Lease(value.try_into()?),
544            9 => LeaseCancel(value.try_into()?),
545            10 => CreateAlias(value.try_into()?),
546            11 => MassTransfer(value.try_into()?),
547            12 => Data(value.try_into()?),
548            13 => SetScript(value.try_into()?),
549            14 => SponsorFee(value.try_into()?),
550            15 => SetAssetScript(value.try_into()?),
551            16 => InvokeScript(InvokeScriptTransaction::from_json(value)?),
552            17 => UpdateAssetInfo(value.try_into()?),
553            18 => Ethereum(value.try_into()?),
554            _ => return Err(UnsupportedOperation("unknown transaction type".to_owned())),
555        };
556        let timestamp = JsonDeserializer::safe_to_int_from_field(value, "timestamp")? as u64;
557        let public_key = match tx_type {
558            1 => PublicKey::from_bytes(&[0; HASH_LENGTH])?,
559            _ => {
560                JsonDeserializer::safe_to_string_from_field(value, "senderPublicKey")?.try_into()?
561            }
562        };
563
564        let chain_id = match tx_type {
565            1 => Address::from_string(&JsonDeserializer::safe_to_string_from_field(
566                value,
567                "recipient",
568            )?)?
569            .chain_id(),
570            _ => Address::from_string(&JsonDeserializer::safe_to_string_from_field(
571                value, "sender",
572            )?)?
573            .chain_id(),
574        };
575
576        let version = match tx_type {
577            1 | 2 => 1_u8,
578            _ => JsonDeserializer::safe_to_int_from_field(value, "version")? as u8,
579        };
580        Ok(Transaction::new(
581            transaction_data,
582            Amount::new(fee, fee_asset_id),
583            timestamp,
584            public_key,
585            version,
586            chain_id,
587        ))
588    }
589}