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#[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 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 pub fn with_proofs(self, proofs: Vec<Vec<u8>>) -> ProvenTransaction<'a> {
466 ProvenTransaction { tx: self, proofs }
467 }
468}
469
470pub 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}