wavesplatform/
transaction.rs

1mod data_entry;
2mod hash;
3mod transaction_data;
4mod type_id;
5mod version;
6
7use crate::account::{blake_hash, Address, PublicKeyAccount};
8use crate::bytebuffer::Buffer;
9
10pub use data_entry::*;
11pub use hash::*;
12pub use transaction_data::*;
13pub use type_id::*;
14pub use version::*;
15
16/// Transaction data. Data specific to a particular transaction type are stored in the `data` field.
17/// # Usage
18/// ```
19/// use wavesplatform::account::{PrivateKeyAccount, TESTNET};
20/// use wavesplatform::transaction::*;
21/// let account = PrivateKeyAccount::from_seed("seed");
22/// let tx = Transaction::new_alias(
23///     &account.public_key(),
24///     "rhino",
25///     TESTNET,
26///     100000,
27///     1536000000000,
28/// );
29/// let signed_tx = account.sign_transaction(tx);
30/// ```
31#[derive(Debug)]
32pub struct Transaction<'a> {
33    data: TransactionData<'a>,
34    fee: u64,
35    timestamp: u64,
36    sender_public_key: &'a PublicKeyAccount,
37    type_id: u8,
38    version: u8,
39}
40
41use transaction_data::TransactionData::*;
42
43impl<'a> Transaction<'a> {
44    #[allow(clippy::too_many_arguments)]
45    pub fn new_issue(
46        sender_public_key: &'a PublicKeyAccount,
47        name: &'a str,
48        description: &'a str,
49        quantity: u64,
50        decimals: u8,
51        reissuable: bool,
52        chain_id: u8,
53        fee: u64,
54        timestamp: u64,
55        script: Option<&'a [u8]>,
56    ) -> Transaction<'a> {
57        Transaction {
58            data: Issue {
59                name,
60                description,
61                quantity,
62                decimals,
63                reissuable,
64                chain_id,
65                script,
66            },
67            fee,
68            timestamp,
69            sender_public_key,
70            type_id: Type::Issue as u8,
71            version: Version::V2 as u8,
72        }
73    }
74
75    #[allow(clippy::too_many_arguments)]
76    pub fn new_transfer(
77        sender_public_key: &'a PublicKeyAccount,
78        recipient: &'a Address,
79        asset: Option<&'a Asset>,
80        amount: u64,
81        fee_asset: Option<&'a Asset>,
82        fee: u64,
83        attachment: Option<&'a str>,
84        timestamp: u64,
85    ) -> Transaction<'a> {
86        Transaction {
87            data: Transfer {
88                recipient,
89                asset,
90                amount,
91                fee_asset,
92                attachment,
93            },
94            fee,
95            timestamp,
96            sender_public_key,
97            type_id: Type::Transfer as u8,
98            version: Version::V2 as u8,
99        }
100    }
101
102    pub fn new_reissue(
103        sender_public_key: &'a PublicKeyAccount,
104        asset: &'a Asset,
105        quantity: u64,
106        reissuable: bool,
107        chain_id: u8,
108        fee: u64,
109        timestamp: u64,
110    ) -> Transaction<'a> {
111        Transaction {
112            data: Reissue {
113                asset,
114                quantity,
115                reissuable,
116                chain_id,
117            },
118            fee,
119            timestamp,
120            sender_public_key,
121            type_id: Type::Reissue as u8,
122            version: Version::V2 as u8,
123        }
124    }
125
126    pub fn new_burn(
127        sender_public_key: &'a PublicKeyAccount,
128        asset: &'a Asset,
129        quantity: u64,
130        chain_id: u8,
131        fee: u64,
132        timestamp: u64,
133    ) -> Transaction<'a> {
134        Transaction {
135            data: Burn {
136                asset,
137                quantity,
138                chain_id,
139            },
140            fee,
141            timestamp,
142            sender_public_key,
143            type_id: Type::Burn as u8,
144            version: Version::V2 as u8,
145        }
146    }
147
148    pub fn new_lease(
149        sender_public_key: &'a PublicKeyAccount,
150        recipient: &'a Address,
151        amount: u64,
152        chain_id: u8,
153        fee: u64,
154        timestamp: u64,
155    ) -> Transaction<'a> {
156        Transaction {
157            data: Lease {
158                recipient,
159                amount,
160                chain_id,
161            },
162            fee,
163            timestamp,
164            sender_public_key,
165            type_id: Type::Lease as u8,
166            version: Version::V2 as u8,
167        }
168    }
169
170    pub fn new_lease_cancel(
171        sender_public_key: &'a PublicKeyAccount,
172        lease_id: &'a TransactionId,
173        chain_id: u8,
174        fee: u64,
175        timestamp: u64,
176    ) -> Transaction<'a> {
177        Transaction {
178            data: CancelLease { lease_id, chain_id },
179            fee,
180            timestamp,
181            sender_public_key,
182            type_id: Type::LeaseCancel as u8,
183            version: Version::V2 as u8,
184        }
185    }
186
187    pub fn new_alias(
188        sender_public_key: &'a PublicKeyAccount,
189        alias: &'a str,
190        chain_id: u8,
191        fee: u64,
192        timestamp: u64,
193    ) -> Transaction<'a> {
194        Transaction {
195            data: Alias { alias, chain_id },
196            fee,
197            timestamp,
198            sender_public_key,
199            type_id: Type::Alias as u8,
200            version: Version::V2 as u8,
201        }
202    }
203
204    pub fn new_mass_transfer(
205        sender_public_key: &'a PublicKeyAccount,
206        asset: Option<&'a Asset>,
207        transfers: Vec<(&'a Address, u64)>,
208        attachment: Option<&'a str>,
209        fee: u64,
210        timestamp: u64,
211    ) -> Transaction<'a> {
212        Transaction {
213            data: MassTransfer {
214                asset,
215                transfers,
216                attachment,
217            },
218            fee,
219            timestamp,
220            sender_public_key,
221            type_id: Type::MassTransfer as u8,
222            version: Version::V1 as u8,
223        }
224    }
225
226    pub fn new_data(
227        sender_public_key: &'a PublicKeyAccount,
228        data: Vec<&'a DataEntry<'a>>,
229        fee: u64,
230        timestamp: u64,
231    ) -> Transaction<'a> {
232        Transaction {
233            data: Data { data },
234            fee,
235            timestamp,
236            sender_public_key,
237            type_id: Type::Data as u8,
238            version: Version::V1 as u8,
239        }
240    }
241
242    pub fn new_script(
243        sender_public_key: &'a PublicKeyAccount,
244        script: Option<&'a [u8]>,
245        chain_id: u8,
246        fee: u64,
247        timestamp: u64,
248    ) -> Transaction<'a> {
249        Transaction {
250            data: SetScript { script, chain_id },
251            fee,
252            timestamp,
253            sender_public_key,
254            type_id: Type::SetScript as u8,
255            version: Version::V1 as u8,
256        }
257    }
258
259    pub fn new_sponsor(
260        sender_public_key: &'a PublicKeyAccount,
261        asset: &'a Asset,
262        rate: Option<u64>,
263        fee: u64,
264        timestamp: u64,
265    ) -> Transaction<'a> {
266        Transaction {
267            data: Sponsor { asset, rate },
268            fee,
269            timestamp,
270            sender_public_key,
271            type_id: Type::Sponsor as u8,
272            version: Version::V1 as u8,
273        }
274    }
275
276    pub fn new_set_asset_script(
277        sender_public_key: &'a PublicKeyAccount,
278        asset: &'a Asset,
279        script: Option<&'a [u8]>,
280        chain_id: u8,
281        fee: u64,
282        timestamp: u64,
283    ) -> Transaction<'a> {
284        Transaction {
285            data: SetAssetScript {
286                asset,
287                script,
288                chain_id,
289            },
290            fee,
291            timestamp,
292            sender_public_key,
293            type_id: Type::SetAssetScript as u8,
294            version: Version::V1 as u8,
295        }
296    }
297
298    pub fn to_bytes(&self) -> Vec<u8> {
299        let mut buf = Buffer::new();
300        buf.byte(self.type_id).byte(self.version);
301        match self.data {
302            Issue {
303                name,
304                description,
305                quantity,
306                decimals,
307                reissuable,
308                chain_id,
309                script,
310            } => {
311                buf.byte(chain_id)
312                    .bytes(self.sender_public_key.to_bytes())
313                    .array(name.as_bytes())
314                    .array(description.as_bytes())
315                    .long(quantity)
316                    .byte(decimals)
317                    .boolean(reissuable)
318                    .long(self.fee)
319                    .long(self.timestamp);
320                match script {
321                    Some(bytes) => buf.byte(1).array(bytes),
322                    None => buf.byte(0),
323                }
324            }
325            Transfer {
326                recipient,
327                ref asset,
328                amount,
329                ref fee_asset,
330                attachment,
331            } => buf
332                .bytes(self.sender_public_key.to_bytes())
333                .asset_opt(asset)
334                .asset_opt(fee_asset)
335                .long(self.timestamp)
336                .long(amount)
337                .long(self.fee)
338                .recipient(recipient.chain_id(), &recipient.to_string())
339                .array_opt(attachment.map(|s| s.as_bytes())),
340            Reissue {
341                asset,
342                quantity,
343                reissuable,
344                chain_id,
345            } => buf
346                .byte(chain_id)
347                .bytes(self.sender_public_key.to_bytes())
348                .asset(asset)
349                .long(quantity)
350                .boolean(reissuable)
351                .long(self.fee)
352                .long(self.timestamp),
353            Burn {
354                asset,
355                quantity,
356                chain_id,
357            } => buf
358                .byte(chain_id)
359                .bytes(self.sender_public_key.to_bytes())
360                .asset(asset)
361                .long(quantity)
362                .long(self.fee)
363                .long(self.timestamp),
364            Lease {
365                recipient,
366                amount,
367                chain_id,
368            } => buf
369                .byte(0)
370                .bytes(self.sender_public_key.to_bytes())
371                .recipient(chain_id, &recipient.to_string())
372                .long(amount)
373                .long(self.fee)
374                .long(self.timestamp),
375            CancelLease { lease_id, chain_id } => buf
376                .byte(chain_id)
377                .bytes(self.sender_public_key.to_bytes())
378                .long(self.fee)
379                .long(self.timestamp)
380                .bytes(&lease_id.to_bytes()),
381            Alias { alias, chain_id } => buf
382                .bytes(self.sender_public_key.to_bytes())
383                .size(alias.len() + 4)
384                .byte(2)
385                .byte(chain_id)
386                .array(alias.as_bytes())
387                .long(self.fee)
388                .long(self.timestamp),
389            MassTransfer {
390                ref asset,
391                ref transfers,
392                attachment,
393            } => {
394                buf.bytes(self.sender_public_key.to_bytes())
395                    .asset_opt(asset)
396                    .size(transfers.len());
397                for (addr, amt) in transfers {
398                    buf.bytes(addr.to_bytes()).long(*amt);
399                }
400                buf.long(self.timestamp)
401                    .long(self.fee)
402                    .array_opt(attachment.map(|s| s.as_bytes()))
403            }
404            Data { ref data } => {
405                buf.bytes(self.sender_public_key.to_bytes())
406                    .size(data.len());
407                for e in data {
408                    buf.data_entry(e);
409                }
410                buf.long(self.timestamp).long(self.fee)
411            }
412            SetScript { script, chain_id } => {
413                buf.byte(chain_id).bytes(self.sender_public_key.to_bytes());
414                match script {
415                    Some(bytes) => buf.byte(1).array(bytes),
416                    None => buf.byte(0),
417                };
418                buf.long(self.fee).long(self.timestamp)
419            }
420            Sponsor { asset, rate } => buf
421                .bytes(self.sender_public_key.to_bytes())
422                .asset(asset)
423                .long(rate.unwrap_or(0))
424                .long(self.fee)
425                .long(self.timestamp),
426            SetAssetScript {
427                asset,
428                script,
429                chain_id,
430            } => {
431                buf.byte(chain_id)
432                    .bytes(self.sender_public_key.to_bytes())
433                    .asset(asset);
434                match script {
435                    Some(bytes) => buf.byte(1).array(bytes),
436                    None => buf.byte(0),
437                };
438                buf.long(self.fee).long(self.timestamp)
439            }
440        };
441        Vec::from(buf.as_slice())
442    }
443
444    /// Returns transaction ID
445    pub fn id(&self) -> TransactionId {
446        let bytes = match self.data {
447            Alias { alias, chain_id } => {
448                let mut buf = Buffer::new();
449                Vec::from(
450                    buf.byte(self.type_id)
451                        .byte(2)
452                        .byte(chain_id)
453                        .array(alias.as_bytes())
454                        .as_slice(),
455                )
456            }
457            _ => self.to_bytes(),
458        };
459        let mut id = [0u8; HASH_LENGTH];
460        id.copy_from_slice(&blake_hash(&bytes));
461        TransactionId::new(id)
462    }
463
464    /// Returns a ProvenTransaction with the given proofs
465    pub fn with_proofs(self, proofs: Vec<Vec<u8>>) -> ProvenTransaction<'a> {
466        ProvenTransaction { tx: self, proofs }
467    }
468}
469
470/// Transaction with proofs. Proofs are byte vectors at most 64 bytes long, and maximum number of
471/// proofs is 8.
472pub struct ProvenTransaction<'a> {
473    pub tx: Transaction<'a>,
474    pub proofs: Vec<Vec<u8>>,
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480
481    use crate::account::{Address, PrivateKeyAccount, TESTNET};
482
483    use base58::FromBase58;
484    use ed25519_dalek::*;
485
486    #[test]
487    fn test_tx_ids() {
488        let pk = PublicKeyAccount([1u8; 32]);
489        let asset = Asset::new([2u8; 32]);
490        let lease = TransactionId::new([3u8; 32]);
491        let recipient = Address::from_string("3MzGEv9wnaqrYFYujAXSH5RQfHaVKNQvx3D");
492        let fee = 100000;
493        let ts: u64 = 1536000000000;
494
495        fn check_hash(tx: &Transaction, hash: &str) -> () {
496            assert_eq!(tx.id().to_bytes(), hash.from_base58().unwrap().as_slice());
497        }
498
499        check_hash(
500            &Transaction::new_issue(
501                &pk, "coin", "coin", 100000000, 8, false, TESTNET, 100000, ts, None,
502            ),
503            "GHqHz8xot1Yo7fivjPBYiqgtJNW3jR6bvpNh2WH66tEM",
504        );
505        check_hash(
506            &Transaction::new_transfer(
507                &pk,
508                &recipient,
509                Some(&asset),
510                10,
511                None,
512                fee,
513                Some("atta ch me"),
514                ts,
515            ),
516            "E4Jc1vMh4TqryNzajU7onTHLLFkDmjNzo7aSedX4Rpad",
517        );
518        check_hash(
519            &Transaction::new_reissue(&pk, &asset, 100000000, false, TESTNET, fee, ts),
520            "83WaG6AAzxF3NFormpqrJr9Bi8eSdwyp3DEB67N7avvM",
521        );
522        check_hash(
523            &Transaction::new_burn(&pk, &asset, 100000000, TESTNET, fee, ts),
524            "CfsAEtEAwe4NFKjezeCssaUPevTX56rBsuMeMKRERd6Y",
525        );
526        check_hash(
527            &Transaction::new_lease(&pk, &recipient, 10, TESTNET, fee, ts),
528            "HHs5qfpDN88WTGszpfjVedhMPHeHynDtWPobm2rkpfH4",
529        );
530        check_hash(
531            &Transaction::new_lease_cancel(&pk, &lease, TESTNET, fee, ts),
532            "9BQLzTCHi9H9jqKeC5rvN7x9m8xfHQh1iApqmAPFTFEU",
533        );
534        let alias = Transaction::new_alias(&pk, "lilias", TESTNET, fee, ts);
535        check_hash(&alias, "GPyHWQSCT6znfZmjfZfsS6TXPV3zueVZKFUWG7duku1Z");
536
537        let transfers = vec![(&recipient, 10), (&recipient, 10)];
538        check_hash(
539            &Transaction::new_mass_transfer(
540                &pk,
541                Some(&asset),
542                transfers,
543                Some("mass trans"),
544                fee,
545                ts,
546            ),
547            "HwWmpBbbYPShKsFAgVA3eH86LkrZgX1xYSoH5YarnwPE",
548        );
549
550        let arr = vec![4u8; 32];
551        let bin_entry = DataEntry::Binary("bin", &arr);
552        let data = vec![
553            &DataEntry::Integer("int", 1),
554            &DataEntry::Boolean("bool", true),
555            &bin_entry,
556            &DataEntry::String("str", "str"),
557        ];
558        check_hash(
559            &Transaction::new_data(&pk, data, fee, ts),
560            "6fGLB7yxzkWPBb4fv32Fs7d5si6xifenj69Da9yHvwgx",
561        );
562
563        let script = vec![1, 6, 183, 111, 203, 71];
564        check_hash(
565            &Transaction::new_script(&pk, Some(script.as_slice()), TESTNET, fee, ts),
566            "HASXvcgoikizpWnCLd2cXNeCN5DxdKojCfcn8f7T3KjK",
567        );
568        check_hash(
569            &Transaction::new_script(&pk, None, TESTNET, fee, ts),
570            "1gwS1qkKKShwk5scB7M7N9t6L3eX2Hpkp9hF5RG8HJD",
571        );
572        check_hash(
573            &Transaction::new_sponsor(&pk, &asset, Some(100), fee, ts),
574            "9zmHx3fyXz7pW6bRazPP28PGjnM8XjoHuyjzXCMHE2PY",
575        );
576        check_hash(
577            &Transaction::new_set_asset_script(&pk, &asset, None, TESTNET, fee, ts),
578            "FiZJwFqVbYiYeRN1oADFuqCmKHQJpDJonD5a9ekqXFuY",
579        );
580    }
581
582    #[test]
583    fn test_sign() {
584        let sender = PrivateKeyAccount::from_seed("test");
585        let recipient = Address::from_string("3MzGEv9wnaqrYFYujAXSH5RQfHaVKNQvx3D");
586        let tx = Transaction::new_lease(&sender.1, &recipient, 100000, 84, 100000, 1500000000000);
587
588        let ProvenTransaction { tx, proofs } = sender.sign_transaction(tx);
589        assert_eq!(proofs.len(), 1);
590        let sig = proofs.get(0).unwrap();
591        assert_eq!(sig.len(), SIGNATURE_LENGTH);
592
593        let ProvenTransaction { tx: _, proofs } = tx.with_proofs(vec![vec![1, 2, 3]]);
594        assert_eq!(proofs.len(), 1);
595        let sig = proofs.get(0).unwrap();
596        assert_eq!(*sig, vec![1, 2, 3]);
597    }
598}