1use bincode::error::EncodeError;
2use thiserror::Error;
3
4use core::{
5 block::Block,
6 blockchain::BlockchainError,
7 economics::{DEV_WALLET, calculate_dev_fee, get_block_reward},
8 transaction::{Transaction, TransactionInput, TransactionOutput},
9};
10
11pub mod api;
13
14pub mod blockchain_data_provider;
16
17pub mod core;
19
20pub mod crypto;
22
23pub mod node;
25
26mod tests;
28
29pub mod version;
31
32pub use core::economics;
34pub use core::economics::to_snap;
35pub use economics::to_nano;
36
37use crate::{
38 blockchain_data_provider::{BlockchainDataProvider, BlockchainDataProviderError},
39 core::{block::MAX_TRANSACTIONS, transaction::MAX_TRANSACTION_IO},
40 crypto::keys::{Private, Public},
41 economics::GENESIS_PREVIOUS_BLOCK_HASH,
42};
43
44#[derive(Error, Debug)]
45pub enum UtilError {
46 #[error("Blockchain error: {0}")]
47 BlockchainError(#[from] BlockchainError),
48
49 #[error("Insufficient funds to complete operation")]
50 InsufficientFunds,
51
52 #[error("Encode error {0}")]
53 EncodeError(#[from] EncodeError),
54
55 #[error("Data provider error {0}")]
56 BlockchainDataProviderError(#[from] BlockchainDataProviderError),
57
58 #[error(
59 "Too many inputs and outputs for one transaction. Consider splitting transaction in to more than one (smaller SNAP amount) or less receivers."
60 )]
61 TooMuchIO,
62
63 #[error("Too many transactions for block")]
64 TooManyTransactions,
65}
66
67pub async fn build_transaction<B>(
70 blockchain_data_provider: &B,
71 sender: Private,
72 mut receivers: Vec<(Public, u64)>,
73 ignore_inputs: Vec<TransactionInput>
74
75) -> Result<Transaction, UtilError>
76where
77 B: BlockchainDataProvider,
78{
79 let target_balance = receivers
80 .iter()
81 .fold(0u64, |acc, receiver| acc + receiver.1);
82
83 let mut available_inputs = blockchain_data_provider
84 .get_available_transaction_outputs(sender.to_public())
85 .await?;
86
87 available_inputs.retain(|(transaction, _, index)| !ignore_inputs.iter().any(|i_input| i_input.output_index == *index && i_input.transaction_id == *transaction));
88
89 let mut used_inputs = vec![];
90
91 let mut current_funds = 0u64;
92 for (transaction, input, index) in available_inputs {
93 current_funds += input.amount;
94 used_inputs.push((transaction, input, index));
95 if current_funds >= target_balance {
96 break;
97 }
98 }
99
100 if target_balance > current_funds {
101 return Err(UtilError::InsufficientFunds);
102 }
103
104 if target_balance < current_funds {
105 receivers.push((sender.to_public(), current_funds - target_balance));
106 }
107
108 if used_inputs.len() + receivers.len() > MAX_TRANSACTION_IO {
109 return Err(UtilError::TooMuchIO);
110 }
111
112 used_inputs.sort_by(|a, b| a.1.amount.cmp(&b.1.amount)); let transaction = Transaction::new_transaction_now(
115 used_inputs
116 .iter()
117 .map(|input| TransactionInput {
118 transaction_id: input.0,
119 output_index: input.2,
120 signature: None,
121 })
122 .collect::<Vec<TransactionInput>>(),
123 receivers
124 .iter()
125 .map(|receiver| TransactionOutput {
126 amount: receiver.1,
127 receiver: receiver.0,
128 })
129 .collect(),
130 &mut vec![sender; used_inputs.len()],
131 )?;
132
133 Ok(transaction)
134}
135
136pub async fn build_block<B>(
141 blockchain_data_provider: &B,
142 transactions: &Vec<Transaction>,
143 miner: Public,
144) -> Result<Block, UtilError>
145where
146 B: BlockchainDataProvider,
147{
148 let reward = get_block_reward(blockchain_data_provider.get_height().await?);
149 let mut transactions = transactions.clone();
150 transactions.push(Transaction::new_transaction_now(
151 vec![],
152 vec![
153 TransactionOutput {
154 amount: calculate_dev_fee(reward),
155 receiver: DEV_WALLET,
156 },
157 TransactionOutput {
158 amount: reward - calculate_dev_fee(reward),
159 receiver: miner,
160 },
161 ],
162 &mut vec![],
163 )?);
164 if transactions.len() > MAX_TRANSACTIONS {
165 return Err(UtilError::TooManyTransactions);
166 }
167 let reward_tx_i = transactions.len() - 1;
168 transactions[reward_tx_i].compute_pow(
169 &blockchain_data_provider
170 .get_transaction_difficulty()
171 .await?,
172 None,
173 )?;
174 let block = Block::new_block_now(
175 transactions,
176 &blockchain_data_provider.get_block_difficulty().await?,
177 &blockchain_data_provider
178 .get_transaction_difficulty()
179 .await?,
180 blockchain_data_provider
181 .get_block_hash_by_height(
182 blockchain_data_provider
183 .get_height()
184 .await?
185 .saturating_sub(1),
186 )
187 .await?
188 .unwrap_or(GENESIS_PREVIOUS_BLOCK_HASH),
189 );
190
191 Ok(block)
192}