1use ahash::{AHashMap, AHashSet};
2use log::{debug, info, trace, warn};
3use std::fmt::Display;
4use std::io::{Error, ErrorKind};
5
6use crate::core::consensus::block::Block;
7use crate::core::consensus::golden_ticket::GoldenTicket;
8use crate::core::consensus::slip::{Slip, SlipType};
9use crate::core::consensus::transaction::{Transaction, TransactionType};
10use crate::core::defs::{
11 BlockId, Currency, PrintForLog, SaitoHash, SaitoPrivateKey, SaitoPublicKey, SaitoSignature,
12 SaitoUTXOSetKey, UTXO_KEY_LENGTH,
13};
14use crate::core::io::interface_io::{InterfaceEvent, InterfaceIO};
15use crate::core::io::network::Network;
16use crate::core::io::storage::Storage;
17use crate::core::process::version::{read_pkg_version, Version};
18use crate::core::util::balance_snapshot::BalanceSnapshot;
19use crate::core::util::crypto::{generate_keys, hash, sign};
20
21pub const WALLET_SIZE: usize = 65;
22
23pub type WalletUpdateStatus = bool;
24pub const WALLET_UPDATED: WalletUpdateStatus = true;
25pub const WALLET_NOT_UPDATED: WalletUpdateStatus = false;
26
27#[derive(Clone, Debug, PartialEq)]
39pub struct WalletSlip {
40 pub utxokey: SaitoUTXOSetKey,
41 pub amount: Currency,
42 pub block_id: u64,
43 pub tx_ordinal: u64,
44 pub lc: bool,
45 pub slip_index: u8,
46 pub spent: bool,
47 pub slip_type: SlipType,
48}
49
50#[derive(Clone, Debug, PartialEq)]
51pub struct NFT {
52 pub slip1: SaitoUTXOSetKey,
53 pub slip2: SaitoUTXOSetKey,
54 pub slip3: SaitoUTXOSetKey,
55 pub id: Vec<u8>,
56 pub tx_sig: SaitoSignature,
57}
58
59impl Default for NFT {
60 fn default() -> Self {
61 Self {
62 slip1: [0; UTXO_KEY_LENGTH], slip2: [0; UTXO_KEY_LENGTH], slip3: [0; UTXO_KEY_LENGTH], id: vec![],
66 tx_sig: [0; 64],
67 }
68 }
69}
70
71#[derive(Clone, Debug, PartialEq)]
74pub struct Wallet {
75 pub public_key: SaitoPublicKey,
76 pub private_key: SaitoPrivateKey,
77 pub slips: AHashMap<SaitoUTXOSetKey, WalletSlip>,
78 pub unspent_slips: AHashSet<SaitoUTXOSetKey>,
79 pub staking_slips: AHashSet<SaitoUTXOSetKey>,
80 pub filename: String,
81 pub filepass: String,
82 available_balance: Currency,
83 pub pending_txs: AHashMap<SaitoHash, Transaction>,
84 pub wallet_version: Version,
86 pub core_version: Version,
87 pub key_list: Vec<SaitoPublicKey>,
88 pub nfts: Vec<NFT>,
89}
90
91impl Wallet {
92 pub fn new(private_key: SaitoPrivateKey, public_key: SaitoPublicKey) -> Wallet {
93 info!("generating new wallet...");
94 Wallet {
97 public_key,
98 private_key,
99 slips: AHashMap::new(),
100 unspent_slips: AHashSet::new(),
101 staking_slips: Default::default(),
102 filename: "default".to_string(),
103 filepass: "password".to_string(),
104 available_balance: 0,
105 pending_txs: Default::default(),
106 wallet_version: Default::default(),
107 core_version: read_pkg_version(),
108 key_list: vec![],
109 nfts: Vec::new(),
110 }
111 }
112
113 pub async fn load(wallet: &mut Wallet, io: &(dyn InterfaceIO + Send + Sync)) {
114 info!("loading wallet...");
115 let result = io.load_wallet(wallet).await;
116 if result.is_err() {
117 warn!("loading wallet failed. saving new wallet");
118 io.save_wallet(wallet).await.unwrap();
120 } else {
121 info!("wallet loaded");
122 io.send_interface_event(InterfaceEvent::WalletUpdate());
123 }
124 }
125 pub async fn save(wallet: &mut Wallet, io: &(dyn InterfaceIO + Send + Sync)) {
126 trace!("saving wallet");
127 io.save_wallet(wallet).await.unwrap();
128 trace!("wallet saved");
129 }
130
131 pub async fn reset(
132 &mut self,
133 _storage: &mut Storage,
134 network: Option<&Network>,
135 keep_keys: bool,
136 ) {
137 info!("resetting wallet");
138 if !keep_keys {
139 let keys = generate_keys();
140 self.public_key = keys.0;
141 self.private_key = keys.1;
142 }
143
144 self.pending_txs.clear();
145 self.available_balance = 0;
146 self.slips.clear();
147 self.unspent_slips.clear();
148 self.staking_slips.clear();
149 if let Some(network) = network {
150 network
151 .io_interface
152 .send_interface_event(InterfaceEvent::WalletUpdate());
153 }
154 }
155
156 pub fn serialize_for_disk(&self) -> Vec<u8> {
159 let mut vbytes: Vec<u8> = vec![];
160
161 vbytes.extend(&self.private_key);
162 vbytes.extend(&self.public_key);
163
164 vbytes
167 }
168
169 pub fn deserialize_from_disk(&mut self, bytes: &[u8]) {
172 self.private_key = bytes[0..32].try_into().unwrap();
173 self.public_key = bytes[32..65].try_into().unwrap();
174 }
175
176 pub fn on_chain_reorganization(
177 &mut self,
178 block: &Block,
179 lc: bool,
180 genesis_period: BlockId,
181 ) -> WalletUpdateStatus {
182 let mut wallet_changed = WALLET_NOT_UPDATED;
183 debug!("tx count : {}", block.transactions.len());
184 let mut tx_index = 0;
185 if lc {
186 for tx in block.transactions.iter() {
187 let mut i = 0;
191 while i < tx.to.len() {
192 let output = &tx.to[i];
193 let is_this_an_nft = tx.is_nft(&tx.to, i);
209
210 if is_this_an_nft {
215 let slip1 = &tx.to[i];
216 let slip2 = &tx.to[i + 1];
217 let slip3 = &tx.to[i + 2];
218
219 let nft = NFT {
229 slip1: slip1.utxoset_key, slip2: slip2.utxoset_key, slip3: slip3.utxoset_key, id: slip2.utxoset_key.to_vec(), tx_sig: tx.signature,
234 };
235 self.nfts.push(nft);
236
237 i += 3;
238 } else {
239 if output.amount > 0 && output.public_key == self.public_key {
243 wallet_changed |= WALLET_UPDATED;
244 self.add_slip(block.id, tx_index, output, true, None);
245 }
246
247 i += 1;
248 }
249 }
250
251 let mut i = 0;
255 while i < tx.from.len() {
256 let input = &tx.from[i];
257
258 let is_this_an_nft = tx.is_nft(&tx.from, i);
264
265 if is_this_an_nft == true {
271 if i == 0 && input.slip_type == SlipType::Bound && tx.from.len() >= 3 {
272 let nft_id = &tx.from[2].utxoset_key;
273
274 if let Some(pos) =
275 self.nfts.iter().position(|nft| nft.id == nft_id.to_vec())
276 {
277 self.nfts.remove(pos);
278 debug!(
279 "Send-bound NFT input group detected. Removed NFT with id: {:?}",
280 nft_id.to_hex()
281 );
282 }
283 }
284
285 i += 3;
286 } else {
287 if input.public_key == self.public_key {
295 if input.amount > 0 {
299 wallet_changed |= WALLET_UPDATED;
300 self.delete_slip(input, None);
301 }
302
303 if self.delete_pending_transaction(tx) {
307 wallet_changed |= WALLET_UPDATED;
308 }
309 }
310
311 i += 1;
312 }
313 }
314
315 if let TransactionType::SPV = tx.transaction_type {
316 tx_index += tx.txs_replacements as u64;
317 } else {
318 tx_index += 1;
319 }
320
321 if block.id > genesis_period {
322 self.remove_old_slips(block.id - genesis_period);
323 }
324 }
325 } else {
326 for tx in block.transactions.iter() {
331 let mut i = 0;
335 while i < tx.to.len() {
336 let output = &tx.to[i];
337
338 let is_this_an_nft = tx.is_nft(&tx.to, i);
339
340 if is_this_an_nft {
341 let slip2 = &tx.to[i + 1];
345 let nft_id = slip2.utxoset_key.to_vec();
346 if let Some(pos) = self.nfts.iter().position(|n| n.id == nft_id) {
347 self.nfts.remove(pos);
348 debug!(
349 "Unwound NFT output group, removed id: {:?}",
350 slip2.utxoset_key.to_hex()
351 );
352 }
353 i += 3;
354 } else {
355 if output.amount > 0 && output.public_key == self.public_key {
359 wallet_changed |= WALLET_UPDATED;
360 self.delete_slip(output, None);
361 }
362 i += 1;
363 }
364 }
365
366 let mut i = 0;
370 while i < tx.from.len() {
371 let input = &tx.from[i];
372
373 let is_this_an_nft = tx.is_nft(&tx.from, i);
377
378 if is_this_an_nft {
379 let slip1 = &tx.from[i];
383 let slip2 = &tx.from[i + 1];
384 let slip3 = &tx.from[i + 2];
385
386 let nft = NFT {
387 slip1: slip1.utxoset_key,
388 slip2: slip2.utxoset_key,
389 slip3: slip3.utxoset_key,
390 id: slip2.utxoset_key.to_vec(),
391 tx_sig: tx.signature,
392 };
393
394 self.nfts.push(nft);
395
396 debug!(
397 "Unwound NFT input group, re‑added id: {:?}",
398 slip2.utxoset_key.to_hex()
399 );
400
401 i += 3;
402 } else {
403 if input.amount > 0 && input.public_key == self.public_key {
407 wallet_changed |= WALLET_UPDATED;
408 self.add_slip(block.id, tx_index, input, true, None);
409 }
410 i += 1;
411 }
412 }
413
414 if let TransactionType::SPV = tx.transaction_type {
418 tx_index += tx.txs_replacements as u64;
419 } else {
420 tx_index += 1;
421 }
422 }
423 }
424
425 debug!("wallet changed ? {:?}", wallet_changed);
426
427 wallet_changed
428 }
429
430 pub fn delete_block(&mut self, block: &Block) -> WalletUpdateStatus {
432 let mut wallet_changed = WALLET_NOT_UPDATED;
433 for tx in block.transactions.iter() {
434 for input in tx.from.iter() {
435 if input.public_key == self.public_key {
436 wallet_changed = WALLET_UPDATED;
437 }
438 self.delete_slip(input, None);
439 }
440 for output in tx.to.iter() {
441 if output.amount > 0 {
442 self.delete_slip(output, None);
443 }
444 }
445 }
446
447 wallet_changed
448 }
449
450 pub fn remove_old_slips(&mut self, block_id: BlockId) {
451 let mut keys_to_remove = vec![];
452 for (key, slip) in self.slips.iter() {
453 if slip.block_id < block_id {
454 keys_to_remove.push(*key);
455 }
456 }
457
458 for key in keys_to_remove {
459 let slip = Slip::parse_slip_from_utxokey(&key).unwrap();
460 debug!("removing old slip : {}", slip);
461 self.delete_slip(&slip, None);
462 }
463 }
464
465 pub fn add_slip(
466 &mut self,
467 block_id: u64,
468 tx_index: u64,
469 slip: &Slip,
470 lc: bool,
471 network: Option<&Network>,
472 ) {
473 if self.slips.contains_key(&slip.get_utxoset_key()) {
474 debug!("wallet already has slip : {}", slip);
475 return;
476 }
477 let mut wallet_slip = WalletSlip::new();
478 assert_ne!(block_id, 0);
479 wallet_slip.utxokey = slip.get_utxoset_key();
480 wallet_slip.amount = slip.amount;
481 wallet_slip.slip_index = slip.slip_index;
482 wallet_slip.block_id = block_id;
483 wallet_slip.tx_ordinal = tx_index;
484 wallet_slip.lc = lc;
485 wallet_slip.slip_type = slip.slip_type;
486
487 if let SlipType::BlockStake = slip.slip_type {
488 self.staking_slips.insert(wallet_slip.utxokey);
489 } else if let SlipType::Bound = slip.slip_type {
490 } else {
491 self.available_balance += slip.amount;
492 self.unspent_slips.insert(wallet_slip.utxokey);
493 }
494
495 trace!(
496 "adding slip of type : {:?} with value : {:?} to wallet : {:?} \nslip : {}",
497 wallet_slip.slip_type,
498 wallet_slip.amount,
499 wallet_slip.utxokey.to_hex(),
500 Slip::parse_slip_from_utxokey(&wallet_slip.utxokey).unwrap()
501 );
502 self.slips.insert(wallet_slip.utxokey, wallet_slip);
503 if let Some(network) = network {
504 network
505 .io_interface
506 .send_interface_event(InterfaceEvent::WalletUpdate());
507 }
508 }
509
510 pub fn delete_slip(&mut self, slip: &Slip, network: Option<&Network>) {
511 trace!(
512 "deleting slip : {:?} with value : {:?} from wallet",
513 slip.utxoset_key.to_hex(),
514 slip.amount
515 );
516 if let Some(removed_slip) = self.slips.remove(&slip.utxoset_key) {
517 let in_unspent_list = self.unspent_slips.remove(&slip.utxoset_key);
518 if in_unspent_list {
519 self.available_balance -= removed_slip.amount;
520 } else {
521 self.staking_slips.remove(&slip.utxoset_key);
522 }
523 if let Some(network) = network {
524 network
525 .io_interface
526 .send_interface_event(InterfaceEvent::WalletUpdate());
527 }
528 }
529 }
530
531 pub fn get_available_balance(&self) -> Currency {
532 self.available_balance
533 }
534
535 pub fn get_unspent_slip_count(&self) -> u64 {
536 self.unspent_slips.len() as u64
537 }
538
539 pub fn generate_slips(
543 &mut self,
544 nolan_requested: Currency,
545 network: Option<&Network>,
546 latest_block_id: u64,
547 genesis_period: u64,
548 ) -> (Vec<Slip>, Vec<Slip>) {
549 let mut inputs: Vec<Slip> = Vec::new();
550 let mut nolan_in: Currency = 0;
551 let mut nolan_out: Currency = 0;
552 let my_public_key = self.public_key;
553
554 let mut keys_to_remove = Vec::new();
556 let mut unspent_slips;
557 #[cfg(test)]
558 {
559 unspent_slips = self.unspent_slips.iter().collect::<Vec<&SaitoUTXOSetKey>>();
561 unspent_slips.sort_by(|slip, slip2| {
562 let slip = Slip::parse_slip_from_utxokey(slip).unwrap();
563 let slip2 = Slip::parse_slip_from_utxokey(slip2).unwrap();
564 slip.amount.cmp(&slip2.amount)
565 });
566 }
567 #[cfg(not(test))]
568 {
569 unspent_slips = &self.unspent_slips;
570 }
571
572 for key in unspent_slips {
573 let slip = self.slips.get_mut(key).expect("slip should be here");
574
575 if slip.block_id <= latest_block_id.saturating_sub(genesis_period - 1) {
577 debug!("Balance in process of rebroadcasting. Please wait 2 blocks and retry...");
578 continue;
579 }
580
581 if nolan_in >= nolan_requested {
582 break;
583 }
584
585 nolan_in += slip.amount;
586
587 let mut input = Slip::default();
588 input.public_key = my_public_key;
589 input.amount = slip.amount;
590 input.block_id = slip.block_id;
591 input.tx_ordinal = slip.tx_ordinal;
592 input.slip_index = slip.slip_index;
593 input.slip_type = slip.slip_type;
594 inputs.push(input);
595
596 slip.spent = true;
597 self.available_balance -= slip.amount;
598
599 trace!(
600 "marking slip : {:?} with value : {:?} as spent",
601 slip.utxokey.to_hex(),
602 slip.amount
603 );
604 keys_to_remove.push(slip.utxokey);
605 }
606
607 for key in keys_to_remove {
608 self.unspent_slips.remove(&key);
609 }
610
611 if nolan_in > nolan_requested {
613 nolan_out = nolan_in - nolan_requested;
614 }
615
616 if nolan_in < nolan_requested {
617 warn!(
618 "Trying to spend more than available. requested : {:?}, available : {:?}",
619 nolan_requested, nolan_in
620 );
621 }
622
623 let mut outputs: Vec<Slip> = Vec::new();
624 let output = Slip {
626 public_key: my_public_key,
627 amount: nolan_out,
628 ..Default::default()
629 };
630 outputs.push(output);
631
632 if inputs.is_empty() {
634 let input = Slip {
635 public_key: my_public_key,
636 amount: 0,
637 block_id: 0,
638 tx_ordinal: 0,
639 ..Default::default()
640 };
641 inputs.push(input);
642 }
643 if outputs.is_empty() {
644 let output = Slip {
645 public_key: my_public_key,
646 amount: 0,
647 block_id: 0,
648 tx_ordinal: 0,
649 ..Default::default()
650 };
651 outputs.push(output);
652 }
653 if let Some(network) = network {
654 network
655 .io_interface
656 .send_interface_event(InterfaceEvent::WalletUpdate());
657 }
658
659 (inputs, outputs)
660 }
661
662 pub fn sign(&self, message_bytes: &[u8]) -> SaitoSignature {
663 sign(message_bytes, &self.private_key)
664 }
665
666 pub async fn create_bound_transaction(
667 &mut self,
668 nft_input_amount: Currency, nft_uuid_block_id: u64, nft_uuid_transaction_id: u64, nft_uuid_slip_id: u64, nft_create_deposit_amt: Currency, nft_data: Vec<u32>, recipient: &SaitoPublicKey, network: Option<&Network>,
676 latest_block_id: u64,
677 genesis_period: u64,
678 nft_type: String,
679 ) -> Result<Transaction, Error> {
680 let mut transaction = Transaction::default();
681 transaction.transaction_type = TransactionType::Bound;
682
683 let input_slip = Slip {
695 public_key: self.public_key, amount: nft_input_amount, block_id: nft_uuid_block_id, slip_index: nft_uuid_slip_id as u8, tx_ordinal: nft_uuid_transaction_id, ..Default::default()
701 };
702
703 let utxo_key = input_slip.get_utxoset_key(); if !self.unspent_slips.contains(&utxo_key) {
720 info!("UTXO Key not found: {:?}", utxo_key);
721 return Err(Error::new(
722 ErrorKind::NotFound,
723 format!("UTXO not found: {:?}", utxo_key),
724 ));
725 }
726
727 let output_slip1 = Slip {
776 public_key: self.public_key,
777 amount: 1, slip_type: SlipType::Bound,
779 ..Default::default()
780 };
781
782 let output_slip2 = Slip {
793 public_key: *recipient,
794 amount: nft_create_deposit_amt,
795 ..Default::default()
796 };
797
798 let uuid_pubkey = Wallet::create_nft_uuid(&input_slip, &nft_type);
822
823 let output_slip3 = Slip {
827 public_key: uuid_pubkey,
828 amount: 0,
829 slip_type: SlipType::Bound,
830 ..Default::default()
831 };
832
833 let mut additional_input_slips: Vec<Slip> = Vec::new();
842 let mut change_slip_opt: Option<Slip> = None;
843
844 if nft_input_amount > nft_create_deposit_amt {
848 let change_slip_amt = nft_input_amount - nft_create_deposit_amt;
849 change_slip_opt = Some(Slip {
850 public_key: self.public_key, amount: change_slip_amt,
852 slip_type: SlipType::Normal,
853 ..Default::default()
854 });
855
856 } else if nft_input_amount < nft_create_deposit_amt {
860 let additional_needed = nft_create_deposit_amt - nft_input_amount;
861 let (generated_inputs, generated_outputs) =
862 self.generate_slips(additional_needed, network, latest_block_id, genesis_period);
863 additional_input_slips = generated_inputs;
864 if let Some(first_generated_output) = generated_outputs.into_iter().next() {
865 change_slip_opt = Some(first_generated_output);
866 } else {
867 return Err(Error::new(
868 ErrorKind::Other,
869 "Failed to generate change slip via generate_slips",
870 ));
871 }
872 }
873
874 transaction.add_from_slip(input_slip);
880 for slip in additional_input_slips {
881 transaction.add_from_slip(slip);
882 }
883
884 transaction.add_to_slip(output_slip1);
888 transaction.add_to_slip(output_slip2);
889 transaction.add_to_slip(output_slip3);
890
891 if let Some(change) = change_slip_opt {
895 transaction.add_to_slip(change);
896 }
897
898 let hash_for_signature: SaitoHash = hash(&transaction.serialize_for_signature());
902 transaction.hash_for_signature = Some(hash_for_signature);
903 transaction.sign(&self.private_key);
904
905 Ok(transaction)
909 }
910
911 pub async fn create_send_bound_transaction(
912 &mut self,
913 nft_amount: Currency,
914 nft_id: Vec<u8>,
915 nft_data: Vec<u32>,
916 recipient_public_key: &SaitoPublicKey,
917 ) -> Result<Transaction, Error> {
918 let mut transaction = Transaction::default();
922 transaction.transaction_type = TransactionType::Bound;
923
924 let pos = self
928 .nfts
929 .iter()
930 .position(|nft| nft.id == nft_id)
931 .ok_or(Error::new(ErrorKind::NotFound, "NFT not found"))?;
932 let old_nft = self.nfts.remove(pos);
933
934 let input_slip1 = Slip::parse_slip_from_utxokey(&old_nft.slip1)?;
947 let input_slip2 = Slip::parse_slip_from_utxokey(&old_nft.slip2)?;
948 let input_slip3 = Slip::parse_slip_from_utxokey(&old_nft.slip3)?;
949
950 let output_slip1 = input_slip1.clone();
954 let mut output_slip2 = input_slip2.clone();
955 let output_slip3 = input_slip3.clone();
956
957 output_slip2.public_key = recipient_public_key.clone();
961
962 transaction.add_from_slip(input_slip1.clone());
966 transaction.add_from_slip(input_slip2.clone());
967 transaction.add_from_slip(input_slip3.clone());
968 transaction.add_to_slip(output_slip1);
969 transaction.add_to_slip(output_slip2);
970 transaction.add_to_slip(output_slip3);
971
972 let hash_for_signature: SaitoHash = hash(&transaction.serialize_for_signature());
976 transaction.hash_for_signature = Some(hash_for_signature);
977 transaction.sign(&self.private_key);
978 let tx_sig = transaction.signature.clone();
979
980 Ok(transaction)
981 }
982
983 pub fn create_nft_uuid(input_slip: &Slip, nft_type: &str) -> [u8; 33] {
1001 let mut uuid = Vec::with_capacity(33);
1012 uuid.extend(&input_slip.block_id.to_be_bytes()); uuid.extend(&input_slip.tx_ordinal.to_be_bytes()); uuid.push(input_slip.slip_index); let mut tbytes = nft_type.as_bytes().to_vec();
1026 if tbytes.len() < 16 {
1027 tbytes.resize(16, 0);
1029 } else if tbytes.len() > 16 {
1030 tbytes.truncate(16);
1032 }
1033 uuid.extend(&tbytes); uuid.try_into().expect("NFT UUID must be exactly 33 bytes")
1040 }
1041
1042 pub async fn create_golden_ticket_transaction(
1043 golden_ticket: GoldenTicket,
1044 public_key: &SaitoPublicKey,
1045 private_key: &SaitoPrivateKey,
1046 ) -> Transaction {
1047 let mut transaction = Transaction::default();
1048
1049 transaction.transaction_type = TransactionType::GoldenTicket;
1051 transaction.data = golden_ticket.serialize_for_net();
1052
1053 let mut input1 = Slip::default();
1054 input1.public_key = *public_key;
1055 input1.amount = 0;
1056 input1.block_id = 0;
1057 input1.tx_ordinal = 0;
1058
1059 let mut output1 = Slip::default();
1060 output1.public_key = *public_key;
1061 output1.amount = 0;
1062 output1.block_id = 0;
1063 output1.tx_ordinal = 0;
1064
1065 transaction.add_from_slip(input1);
1066 transaction.add_to_slip(output1);
1067
1068 let hash_for_signature: SaitoHash = hash(&transaction.serialize_for_signature());
1069 transaction.hash_for_signature = Some(hash_for_signature);
1070
1071 transaction.sign(private_key);
1072
1073 transaction
1074 }
1075 pub fn add_to_pending(&mut self, tx: Transaction) {
1076 assert_eq!(tx.from.first().unwrap().public_key, self.public_key);
1077 assert_ne!(tx.transaction_type, TransactionType::GoldenTicket);
1078 assert!(tx.hash_for_signature.is_some());
1079 self.pending_txs.insert(tx.hash_for_signature.unwrap(), tx);
1080 }
1081
1082 pub fn delete_pending_transaction(&mut self, tx: &Transaction) -> bool {
1083 let hash = tx.hash_for_signature.unwrap();
1084 if self.pending_txs.remove(&hash).is_some() {
1085 true
1086 } else {
1087 false
1089 }
1090 }
1091
1092 pub fn update_from_balance_snapshot(
1093 &mut self,
1094 snapshot: BalanceSnapshot,
1095 network: Option<&Network>,
1096 ) {
1097 self.unspent_slips.clear();
1099 self.slips.clear();
1100 self.available_balance = 0;
1101
1102 snapshot.slips.iter().for_each(|slip| {
1103 assert_ne!(slip.utxoset_key, [0; UTXO_KEY_LENGTH]);
1104 let wallet_slip = WalletSlip {
1105 utxokey: slip.utxoset_key,
1106 amount: slip.amount,
1107 block_id: slip.block_id,
1108 tx_ordinal: slip.tx_ordinal,
1109 lc: true,
1110 slip_index: slip.slip_index,
1111 spent: false,
1112 slip_type: slip.slip_type,
1113 };
1114 let result = self.slips.insert(slip.utxoset_key, wallet_slip);
1115 if result.is_none() {
1116 self.unspent_slips.insert(slip.utxoset_key);
1117 self.available_balance += slip.amount;
1118 info!("slip key : {:?} with value : {:?} added to wallet from snapshot for address : {:?}",
1119 slip.utxoset_key.to_hex(),
1120 slip.amount,
1121 slip.public_key.to_base58());
1122 } else {
1123 info!(
1124 "slip with utxo key : {:?} was already available",
1125 slip.utxoset_key.to_hex()
1126 );
1127 }
1128 });
1129
1130 if let Some(network) = network {
1131 network
1132 .io_interface
1133 .send_interface_event(InterfaceEvent::WalletUpdate());
1134 }
1135 }
1136 pub fn set_key_list(&mut self, key_list: Vec<SaitoPublicKey>) {
1137 self.key_list = key_list;
1138 }
1139
1140 pub fn create_staking_transaction(
1141 &mut self,
1142 staking_amount: Currency,
1143 latest_unlocked_block_id: BlockId,
1144 last_valid_slips_in_block_id: BlockId,
1145 ) -> Result<Transaction, Error> {
1146 debug!(
1147 "creating staking transaction with amount : {:?}",
1148 staking_amount
1149 );
1150
1151 let mut transaction: Transaction = Transaction {
1152 transaction_type: TransactionType::BlockStake,
1153 ..Default::default()
1154 };
1155
1156 let (inputs, outputs) = self.find_slips_for_staking(
1157 staking_amount,
1158 latest_unlocked_block_id,
1159 last_valid_slips_in_block_id,
1160 )?;
1161
1162 for input in inputs {
1163 transaction.add_from_slip(input);
1164 }
1165 for output in outputs {
1166 transaction.add_to_slip(output);
1167 }
1168
1169 let hash_for_signature: SaitoHash = hash(&transaction.serialize_for_signature());
1170 transaction.hash_for_signature = Some(hash_for_signature);
1171
1172 transaction.sign(&self.private_key);
1173
1174 Ok(transaction)
1175 }
1176
1177 fn find_slips_for_staking(
1178 &mut self,
1179 staking_amount: Currency,
1180 latest_unlocked_block_id: BlockId,
1181 last_valid_slips_in_block_id: BlockId,
1182 ) -> Result<(Vec<Slip>, Vec<Slip>), std::io::Error> {
1183 debug!(
1184 "finding slips for staking : {:?} latest_unblocked_block_id: {:?} staking_slip_count: {:?}",
1185 staking_amount, latest_unlocked_block_id, self.staking_slips.len()
1186 );
1187
1188 let mut selected_staking_inputs: Vec<Slip> = vec![];
1189 let mut collected_amount: Currency = 0;
1190 let mut unlocked_slips_to_remove = vec![];
1191
1192 for key in self.staking_slips.iter() {
1193 let slip = self.slips.get(key).unwrap();
1194 if !slip.is_staking_slip_unlocked(latest_unlocked_block_id) {
1195 continue;
1197 }
1198 if slip.block_id < last_valid_slips_in_block_id {
1199 continue;
1201 }
1202
1203 collected_amount += slip.amount;
1204
1205 unlocked_slips_to_remove.push(*key);
1206 selected_staking_inputs.push(slip.to_slip());
1207
1208 if collected_amount >= staking_amount {
1209 break;
1211 }
1212 }
1213
1214 let mut should_break_slips = false;
1215 if collected_amount < staking_amount {
1216 debug!("not enough funds in staking slips. searching in normal slips. current_balance : {:?}", self.available_balance);
1217 let required_from_unspent_slips = staking_amount - collected_amount;
1218 let mut collected_from_unspent_slips: Currency = 0;
1219 let mut unspent_slips_to_remove = vec![];
1220
1221 let mut unspent_slips = self.unspent_slips.iter().collect::<Vec<&SaitoUTXOSetKey>>();
1222 unspent_slips.sort_by(|slip, slip2| {
1223 let slip = Slip::parse_slip_from_utxokey(slip).unwrap();
1224 let slip2 = Slip::parse_slip_from_utxokey(slip2).unwrap();
1225 slip2.amount.cmp(&slip.amount)
1226 });
1227 for key in unspent_slips {
1228 let slip = self.slips.get(key).unwrap();
1229
1230 collected_from_unspent_slips += slip.amount;
1231
1232 selected_staking_inputs.push(slip.to_slip());
1233 unspent_slips_to_remove.push(*key);
1234
1235 if collected_from_unspent_slips >= required_from_unspent_slips {
1236 should_break_slips = self.unspent_slips.len() == 1;
1238 break;
1239 }
1240 }
1241
1242 if collected_from_unspent_slips < required_from_unspent_slips {
1243 warn!("couldn't collect enough funds upto requested staking amount. requested: {:?}, collected: {:?} required_from_unspent: {:?}",
1244 staking_amount,collected_amount,required_from_unspent_slips);
1245 warn!("wallet balance : {:?}", self.available_balance);
1246 return Err(Error::from(ErrorKind::NotFound));
1247 }
1248
1249 for key in unspent_slips_to_remove {
1250 self.unspent_slips.remove(&key);
1251 }
1252 collected_amount += collected_from_unspent_slips;
1253 self.available_balance -= collected_from_unspent_slips;
1254 }
1255
1256 for key in unlocked_slips_to_remove {
1257 self.staking_slips.remove(&key);
1258 }
1259
1260 let mut outputs = vec![];
1261
1262 let mut output: Slip = Default::default();
1263 output.amount = staking_amount;
1264 output.slip_type = SlipType::BlockStake;
1265 output.public_key = self.public_key;
1266 outputs.push(output);
1267
1268 if collected_amount > staking_amount {
1269 let amount = collected_amount - staking_amount;
1270 let mut remainder = amount;
1271 let mut slip_count = 1;
1272 if should_break_slips {
1273 slip_count = 2;
1274 }
1275 {
1276 let mut output: Slip = Default::default();
1277 output.amount = amount / slip_count;
1278 remainder -= output.amount;
1279 output.slip_type = SlipType::Normal;
1280 output.public_key = self.public_key;
1281 outputs.push(output);
1282 }
1283 if remainder > 0 {
1284 let mut output: Slip = Default::default();
1285 output.amount = remainder;
1286 output.slip_type = SlipType::Normal;
1287 output.public_key = self.public_key;
1288 outputs.push(output);
1289 }
1290 }
1291
1292 Ok((selected_staking_inputs, outputs))
1293 }
1294
1295 pub fn get_nft_list(&self) -> &[NFT] {
1296 &self.nfts
1297 }
1298}
1299
1300impl WalletSlip {
1301 #[allow(clippy::new_without_default)]
1302 pub fn new() -> Self {
1303 WalletSlip {
1304 utxokey: [0; UTXO_KEY_LENGTH],
1305 amount: 0,
1306 block_id: 0,
1307 tx_ordinal: 0,
1308 lc: true,
1309 slip_index: 0,
1310 spent: false,
1311 slip_type: SlipType::Normal,
1312 }
1313 }
1314
1315 pub fn is_staking_slip_unlocked(&self, latest_unlocked_block_id: BlockId) -> bool {
1329 matches!(self.slip_type, SlipType::BlockStake) && self.block_id <= latest_unlocked_block_id
1330 }
1331
1332 fn to_slip(&self) -> Slip {
1333 Slip::parse_slip_from_utxokey(&self.utxokey)
1334 .expect("since we already have a wallet slip, utxo key should be valid")
1335 }
1336}
1337
1338impl Display for WalletSlip {
1339 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1340 write!(f, "WalletSlip : utxokey : {:?}, amount : {:?}, block_id : {:?}, tx_ordinal : {:?}, lc : {:?}, slip_index : {:?}, spent : {:?}, slip_type : {:?}", self.utxokey.to_hex(), self.amount, self.block_id, self.tx_ordinal, self.lc, self.slip_index, self.spent, self.slip_type)
1341 }
1342}
1343
1344#[cfg(test)]
1345mod tests {
1346 use crate::core::consensus::wallet::Wallet;
1347 use crate::core::defs::SaitoPublicKey;
1348 use crate::core::io::storage::Storage;
1349 use crate::core::util::crypto::generate_keys;
1350 use crate::core::util::test::test_manager::test::TestManager;
1351
1352 use super::*;
1353
1354 #[test]
1355 fn wallet_new_test() {
1356 let keys = generate_keys();
1357 let wallet = Wallet::new(keys.1, keys.0);
1358 assert_ne!(wallet.public_key, [0; 33]);
1359 assert_ne!(wallet.private_key, [0; 32]);
1360 assert_eq!(wallet.serialize_for_disk().len(), WALLET_SIZE);
1361 }
1362
1363 #[tokio::test]
1365 #[serial_test::serial]
1366 async fn wallet_transfer_to_address_test() {
1367 let mut t = TestManager::default();
1368 t.initialize(100, 100000).await;
1369
1370 let mut last_param = 120000;
1371
1372 let public_keys = [
1373 "s8oFPjBX97NC2vbm9E5Kd2oHWUShuSTUuZwSB1U4wsPR",
1374 "s9adoFPjBX972vbm9E5Kd2oHWUShuSTUuZwSB1U4wsPR",
1375 "s223oFPjBX97NC2bmE5Kd2oHWUShuSTUuZwSB1U4wsPR",
1376 ];
1377
1378 for &public_key_string in &public_keys {
1379 let public_key = Storage::decode_str(public_key_string).unwrap();
1380 let mut to_public_key: SaitoPublicKey = [0u8; 33];
1381 to_public_key.copy_from_slice(&public_key);
1382 t.transfer_value_to_public_key(to_public_key, 500, last_param)
1383 .await
1384 .unwrap();
1385 let balance_map = t.balance_map().await;
1386 let their_balance = *balance_map.get(&to_public_key).unwrap();
1387 assert_eq!(500, their_balance);
1388
1389 last_param += 120000;
1390 }
1391
1392 let my_balance = t.get_balance().await;
1393
1394 let expected_balance = 10000000 - 500 * public_keys.len() as u64; assert_eq!(expected_balance, my_balance);
1396 }
1397
1398 #[tokio::test]
1400 #[serial_test::serial]
1401 async fn transfer_with_insufficient_funds_failure_test() {
1402 let mut t = TestManager::default();
1404 t.initialize(100, 200_000_000_000_000).await;
1405 let public_key_string = "s8oFPjBX97NC2vbm9E5Kd2oHWUShuSTUuZwSB1U4wsPR";
1406 let public_key = Storage::decode_str(public_key_string).unwrap();
1407 let mut to_public_key: SaitoPublicKey = [0u8; 33];
1408 to_public_key.copy_from_slice(&public_key);
1409
1410 let result = t
1412 .transfer_value_to_public_key(to_public_key, 200_000_000_000_000_000, 120000)
1413 .await;
1414 assert!(result.is_err());
1415 }
1416
1417 #[tokio::test]
1419 #[serial_test::serial]
1420 async fn test_transfer_with_exact_funds() {
1421 let mut t = TestManager::default();
1423 {
1424 let mut blockchain = t.blockchain_lock.write().await;
1425 blockchain.social_stake_requirement = 0;
1426 }
1427 t.initialize(1, 500).await;
1428
1429 let public_key_string = "s8oFPjBX97NC2vbm9E5Kd2oHWUShuSTUuZwSB1U4wsPR";
1430 let public_key = Storage::decode_str(public_key_string).unwrap();
1431 let mut to_public_key: SaitoPublicKey = [0u8; 33];
1432 to_public_key.copy_from_slice(&public_key);
1433
1434 t.transfer_value_to_public_key(to_public_key, 500, 120000)
1435 .await
1436 .unwrap();
1437
1438 let balance_map = t.balance_map().await;
1439
1440 let their_balance = *balance_map.get(&to_public_key).unwrap();
1441 assert_eq!(500, their_balance);
1442 let my_balance = t.get_balance().await;
1443 assert_eq!(0, my_balance);
1444 }
1445
1446 #[test]
1447 fn wallet_serialize_and_deserialize_test() {
1448 let keys = generate_keys();
1449 let wallet1 = Wallet::new(keys.1, keys.0);
1450 let keys = generate_keys();
1451 let mut wallet2 = Wallet::new(keys.1, keys.0);
1452 let serialized = wallet1.serialize_for_disk();
1453 wallet2.deserialize_from_disk(&serialized);
1454 assert_eq!(wallet1, wallet2);
1455 }
1456
1457 #[tokio::test]
1458 #[serial_test::serial]
1459 async fn find_staking_slips_with_normal_slips() {
1460 let t = TestManager::default();
1461
1462 let mut wallet = t.wallet_lock.write().await;
1463
1464 let mut slip = Slip {
1465 public_key: wallet.public_key,
1466 amount: 1_000_000,
1467 slip_type: SlipType::Normal,
1468 ..Slip::default()
1469 };
1470 slip.generate_utxoset_key();
1471 wallet.add_slip(1, 1, &slip, true, Some(&t.network));
1472 assert_eq!(wallet.available_balance, 1_000_000);
1473
1474 let result = wallet.find_slips_for_staking(1_000_000, 1, 0);
1475 assert!(result.is_ok());
1476 let (inputs, outputs) = result.unwrap();
1477 assert_eq!(inputs.len(), 1);
1478 assert_eq!(outputs.len(), 1);
1479 assert_eq!(outputs[0].amount, 1_000_000);
1480 assert_eq!(outputs[0].slip_type, SlipType::BlockStake);
1481
1482 assert_eq!(wallet.staking_slips.len(), 0);
1483 assert_eq!(wallet.unspent_slips.len(), 0);
1484 assert_eq!(wallet.available_balance, 0);
1485
1486 let result = wallet.find_slips_for_staking(1_000, 2, 0);
1487 assert!(result.is_err());
1488
1489 assert_eq!(wallet.staking_slips.len(), 0);
1490 assert_eq!(wallet.unspent_slips.len(), 0);
1491
1492 let mut slip = Slip {
1493 public_key: wallet.public_key,
1494 amount: 1_000,
1495 ..Slip::default()
1496 };
1497 slip.generate_utxoset_key();
1498 wallet.add_slip(1, 2, &slip, true, Some(&t.network));
1499
1500 let result = wallet.find_slips_for_staking(1_000_000, 2, 0);
1501 assert!(result.is_err());
1502 assert_eq!(wallet.staking_slips.len(), 0);
1503 assert_eq!(wallet.unspent_slips.len(), 1);
1504 }
1505 #[tokio::test]
1506 #[serial_test::serial]
1507 async fn find_staking_slips_with_normal_slips_with_extra_funds() {
1508 let t = TestManager::default();
1509
1510 let mut wallet = t.wallet_lock.write().await;
1511
1512 let mut slip = Slip {
1513 public_key: wallet.public_key,
1514 amount: 2_500_000,
1515 slip_type: SlipType::Normal,
1516 ..Slip::default()
1517 };
1518 slip.generate_utxoset_key();
1519 wallet.add_slip(1, 1, &slip, true, Some(&t.network));
1520 assert_eq!(wallet.available_balance, 2_500_000);
1521
1522 let result = wallet.find_slips_for_staking(1_000_000, 1, 0);
1523 assert!(result.is_ok());
1524 let (inputs, outputs) = result.unwrap();
1525 assert_eq!(inputs.len(), 1);
1526 assert_eq!(outputs.len(), 3);
1527 assert_eq!(outputs[0].amount, 1_000_000);
1528 assert_eq!(outputs[0].slip_type, SlipType::BlockStake);
1529
1530 assert_eq!(outputs[1].amount, 750_000);
1531 assert_eq!(outputs[1].slip_type, SlipType::Normal);
1532
1533 assert_eq!(outputs[2].amount, 750_000);
1534 assert_eq!(outputs[2].slip_type, SlipType::Normal);
1535
1536 assert_eq!(wallet.staking_slips.len(), 0);
1537 assert_eq!(wallet.unspent_slips.len(), 0);
1538 assert_eq!(wallet.available_balance, 0);
1539
1540 let result = wallet.find_slips_for_staking(1_000, 2, 0);
1541 assert!(result.is_err());
1542
1543 assert_eq!(wallet.staking_slips.len(), 0);
1544 assert_eq!(wallet.unspent_slips.len(), 0);
1545
1546 let mut slip = Slip {
1547 public_key: wallet.public_key,
1548 amount: 1_000,
1549 ..Slip::default()
1550 };
1551 slip.generate_utxoset_key();
1552 wallet.add_slip(1, 2, &slip, true, Some(&t.network));
1553
1554 let result = wallet.find_slips_for_staking(1_000_000, 2, 0);
1555 assert!(result.is_err());
1556 assert_eq!(wallet.staking_slips.len(), 0);
1557 assert_eq!(wallet.unspent_slips.len(), 1);
1558 }
1559
1560 #[tokio::test]
1561 #[serial_test::serial]
1562 async fn find_staking_slips_with_staking_slips() {
1563 let t = TestManager::default();
1564
1565 let mut wallet = t.wallet_lock.write().await;
1566
1567 let mut slip = Slip {
1568 public_key: wallet.public_key,
1569 amount: 1_000_000,
1570 slip_type: SlipType::BlockStake,
1571 ..Slip::default()
1572 };
1573 slip.generate_utxoset_key();
1574 wallet.add_slip(1, 1, &slip, true, Some(&t.network));
1575 assert_eq!(wallet.available_balance, 0);
1576
1577 let result = wallet.find_slips_for_staking(1_000_000, 1, 0);
1578 assert!(result.is_ok());
1579 let (inputs, outputs) = result.unwrap();
1580 assert_eq!(inputs.len(), 1);
1581 assert_eq!(outputs[0].amount, 1_000_000);
1582 assert_eq!(outputs[0].slip_type, SlipType::BlockStake);
1583
1584 assert_eq!(wallet.staking_slips.len(), 0);
1585 assert_eq!(wallet.unspent_slips.len(), 0);
1586 assert_eq!(wallet.available_balance, 0);
1587
1588 let result = wallet.find_slips_for_staking(1_000, 2, 0);
1589 assert!(result.is_err());
1590
1591 assert_eq!(wallet.staking_slips.len(), 0);
1592 assert_eq!(wallet.unspent_slips.len(), 0);
1593 }
1594
1595 }