1use bincode::{Decode, Encode};
2use chrono::Utc;
3use num_bigint::BigUint;
4use std::{
5 collections::{HashMap, HashSet},
6 ops::Deref,
7};
8use thiserror::Error;
9
10use crate::{core::transaction::{self, Transaction, TransactionId, TransactionOutput}, crypto::keys::Public};
11
12#[derive(Error, Debug)]
13pub enum TransactionError {
14 #[error("Transaction timestamp is in the future: {0}")]
15 FutureTimestamp(u64),
16
17 #[error("Transaction missing ID")]
18 MissingId,
19
20 #[error("Transaction hash is invalid: {0}")]
21 InvalidHash(String),
22
23 #[error("Transaction hash does not meet required difficulty: {0}")]
24 InsufficientDifficulty(String),
25
26 #[error("Transaction has no inputs")]
27 NoInputs,
28
29 #[error("Transaction input not found in UTXOs: {0}")]
30 InputNotFound(String),
31
32 #[error("Transaction input index invalid for transaction {tx_id}: {input_tx_id}")]
33 InvalidInputIndex { tx_id: String, input_tx_id: String },
34
35 #[error("Referenced transaction input is already spent")]
36 SpentInputIndex,
37
38 #[error("Transaction input signature is invalid for transaction {0}")]
39 InvalidSignature(String),
40
41 #[error("Double spending detected in the same transaction {0}")]
42 DoubleSpend(String),
43
44 #[error("Transaction output amount cannot be zero for transaction {0}")]
45 ZeroOutput(String),
46
47 #[error("Transaction inputs and outputs don't sum up to same amount for transaction {0}")]
48 SumMismatch(String),
49
50 #[error("Transaction has too many inputs or outputs")]
51 TooManyIO,
52}
53
54#[derive(Encode, Decode, Debug, Clone)]
56pub struct UTXODiff {
57 spent: Vec<(TransactionId, usize, TransactionOutput)>,
58 created: Vec<(TransactionId, usize, TransactionOutput)>,
59}
60
61impl UTXODiff {
62 pub fn new_empty() -> Self {
63 Self {
64 spent: Vec::new(),
65 created: Vec::new(),
66 }
67 }
68
69 pub fn extend(&mut self, other: &mut Self) {
70 self.spent.append(&mut other.spent);
71 self.created.append(&mut other.created);
72 }
73}
74
75#[derive(Encode, Decode, Clone, Debug)]
76pub struct UTXOs {
77 pub utxos: HashMap<TransactionId, Vec<Option<TransactionOutput>>>,
79}
80
81impl UTXOs {
82 pub fn new() -> Self {
84 UTXOs {
85 utxos: HashMap::new(),
86 }
87 }
88
89 pub fn validate_transaction(
92 &self,
93 transaction: &Transaction,
94 tx_hashing_difficulty: &BigUint,
95 ) -> Result<(), TransactionError> {
96 let tx_id = transaction
97 .transaction_id
98 .ok_or(TransactionError::MissingId)?;
99 let input_signing_buf = transaction
100 .get_input_signing_buf()
101 .map_err(|_| TransactionError::InvalidHash(tx_id.dump_base36()))?;
102 let transaction_hashing_buf = transaction
103 .get_tx_hashing_buf()
104 .map_err(|_| TransactionError::InvalidHash(tx_id.dump_base36()))?;
105
106 if transaction.timestamp > Utc::now().timestamp() as u64 {
107 return Err(TransactionError::FutureTimestamp(transaction.timestamp));
108 }
109
110 if !tx_id.compare_with_data(&transaction_hashing_buf) {
111 return Err(TransactionError::InvalidHash(tx_id.dump_base36()));
112 }
113
114 if BigUint::from_bytes_be(tx_id.deref()) > *tx_hashing_difficulty {
115 return Err(TransactionError::InsufficientDifficulty(
116 tx_id.dump_base36(),
117 ));
118 }
119
120 if transaction.inputs.is_empty() {
121 return Err(TransactionError::NoInputs);
122 }
123
124 if transaction.inputs.len() > transaction::MAX_TRANSACTION_IO
125 || transaction.outputs.len() > transaction::MAX_TRANSACTION_IO
126 {
127 return Err(TransactionError::TooManyIO);
128 }
129
130 let mut used_utxos: HashSet<(TransactionId, usize)> = HashSet::new();
131 let mut input_sum: u64 = 0;
132 let mut output_sum: u64 = 0;
133
134 for input in &transaction.inputs {
135 let prev_outputs = self
136 .utxos
137 .get(&input.transaction_id)
138 .ok_or(TransactionError::InputNotFound(tx_id.dump_base36()))?;
139
140 let output = prev_outputs.get(input.output_index).ok_or(
141 TransactionError::InvalidInputIndex {
142 tx_id: tx_id.dump_base36(),
143 input_tx_id: input.transaction_id.dump_base36(),
144 },
145 )?;
146
147 if output.is_none() {
148 return Err(TransactionError::SpentInputIndex);
149 }
150
151 if input.signature.is_none() || input.signature.unwrap().validate_with_public(&output.unwrap().receiver, &input_signing_buf).map_or(true, |valid| !valid) {
152 return Err(TransactionError::InvalidSignature(tx_id.dump_base36()));
153 }
154
155 let utxo_key = (input.transaction_id, input.output_index);
156 if used_utxos.contains(&utxo_key) {
157 return Err(TransactionError::DoubleSpend(tx_id.dump_base36()));
158 }
159 used_utxos.insert(utxo_key);
160
161 input_sum += output.unwrap().amount;
162 }
163
164 for output in &transaction.outputs {
165 if output.amount == 0 {
166 return Err(TransactionError::ZeroOutput(tx_id.dump_base36()));
167 }
168 output_sum += output.amount;
169 }
170
171 if input_sum != output_sum {
172 return Err(TransactionError::SumMismatch(tx_id.dump_base36()));
173 }
174
175 Ok(())
176 }
177
178 pub fn execute_transaction(&mut self, transaction: &Transaction) -> UTXODiff {
182 let mut spent_utxos: Vec<(TransactionId, usize, TransactionOutput)> = Vec::new();
184 let mut created_utxos: Vec<(TransactionId, usize, TransactionOutput)> = Vec::new();
185 for input in &transaction.inputs {
186 if let Some(outputs) = self.utxos.get_mut(&input.transaction_id) {
187 let spent = outputs[input.output_index].clone().unwrap();
188 outputs[input.output_index] = None;
189
190 if outputs.iter().all(|o| o.is_none()) {
191 self.utxos.remove(&input.transaction_id);
192 }
193 spent_utxos.push((
194 input.transaction_id,
195 input.output_index,
196 spent,
197 ));
198 }
199 }
200
201 self.utxos.insert(
202 transaction.transaction_id.unwrap(),
203 transaction.outputs.iter().map(|o| Some(*o)).collect(),
204 );
205
206 for (output_index, output) in transaction.outputs.iter().enumerate() {
207 created_utxos.push((transaction.transaction_id.unwrap(), output_index, *output));
208 }
209
210 UTXODiff {
212 spent: spent_utxos,
213 created: created_utxos,
214 }
215 }
216
217 pub fn recall_block_utxos(&mut self, diffs: UTXODiff) {
219 for spent in diffs.spent {
220 if self.utxos.get(&spent.0).is_some() {
221 self.utxos.get_mut(&spent.0).unwrap()[spent.1] = Some(spent.2);
222 } else {
223 let mut outputs = vec![None; spent.1 + 1];
224 outputs[spent.1] = Some(spent.2);
225 self.utxos.insert(spent.0, outputs);
226 }
227 }
228
229 for created in diffs.created {
230 if let Some(vec) = self.utxos.get_mut(&created.0) {
231 vec[created.1] = None;
232 if vec.iter().all(|x| x.is_none()) {
233 self.utxos.remove(&created.0);
234 }
235 }
236 }
237
238 }
239
240 pub fn calculate_confirmed_balance(&self, address: Public) -> u64 {
241 let mut balance = 0u64;
242
243 for transaction_out in self.utxos.values().flat_map(|utxos| utxos) {
244 if let Some(transaction_out) = transaction_out {
245 if transaction_out.receiver == address {
246 balance += transaction_out.amount;
247 }
248 }
249 }
250
251 balance
252 }
253
254 pub fn get_utxos(&self, address: Public) -> Vec<TransactionId> {
255 let mut utxos: Vec<TransactionId> = vec![];
256
257 for (transaction_id, outputs) in &self.utxos {
258 if outputs.iter().any(|output| output.is_some_and(|output| output.receiver == address)) {
259 utxos.push(*transaction_id);
260 }
261 }
262
263 utxos
264 }
265}