snap_coin/
lib.rs

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
11/// Snap Coin API for accessing blockchain data from different programs
12pub mod api;
13
14/// Provides a standardized way to access blockchain data from many sources
15pub mod blockchain_data_provider;
16
17/// Core implementation of Snap Coin, contains all blockchain logic
18pub mod core;
19
20/// Contains all crypto primitives
21pub mod crypto;
22
23/// Node struct for hosting a P2P Snap Coin node
24pub mod node;
25
26/// Tests
27mod tests;
28
29/// Current Snap Coin version
30pub mod version;
31
32/// Economics package. Mostly utils and CONSTS
33pub 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
67/// Build a new transactions, sending from sender to receiver where each receiver has a amount to receive attached. Takes biggest coins first.
68/// WARNING: this does not compute transaction pow!
69pub 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)); // From highest amount to lowest amount (breadcrumbs last)
113
114    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
136/// Build a new block, given a blockchain data provider reference and a transaction vector
137/// WARNING: This does not compute block pow nor hash!
138/// WARNING: It is assumed that all input transactions are fully valid (at current blockchain height)
139/// WARNING: This function adds reward transactions for you!
140pub 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}