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    crypto::keys::{Private, Public}, economics::GENESIS_PREVIOUS_BLOCK_HASH,
40};
41
42#[derive(Error, Debug)]
43pub enum UtilError {
44    #[error("Blockchain error: {0}")]
45    BlockchainError(#[from] BlockchainError),
46
47    #[error("Insufficient funds to complete operation")]
48    InsufficientFunds,
49
50    #[error("Encode error {0}")]
51    EncodeError(#[from] EncodeError),
52
53    #[error("Data provider error {0}")]
54    BlockchainDataProviderError(#[from] BlockchainDataProviderError)
55}
56
57/// Build a new transactions, sending from sender to receiver where each receiver has a amount to receive attached. Takes biggest coins first.
58/// WARNING: this does not compute transaction pow!
59pub async fn build_transaction<B>(
60    blockchain_data_provider: &B,
61    sender: Private,
62    mut receivers: Vec<(Public, u64)>,
63) -> Result<Transaction, UtilError> where B: BlockchainDataProvider {
64    let target_balance = receivers
65        .iter()
66        .fold(0u64, |acc, receiver| acc + receiver.1);
67
68    let available_inputs = blockchain_data_provider.get_available_transaction_outputs(sender.to_public()).await?;
69    
70    let mut used_inputs = vec![];
71
72    let mut current_funds = 0u64;
73    for (transaction, input, index) in available_inputs {
74        current_funds += input.amount;
75        used_inputs.push((transaction, input, index));
76        if current_funds >= target_balance {
77            break;
78        }
79    }
80
81    if target_balance > current_funds {
82        return Err(UtilError::InsufficientFunds);
83    }
84
85    if target_balance < current_funds {
86        receivers.push((sender.to_public(), current_funds - target_balance));
87    }
88
89    used_inputs.sort_by(|a, b| a.1.amount.cmp(&b.1.amount)); // From highest amount to lowest amount (breadcrumbs last)
90
91    let transaction = Transaction::new_transaction_now(
92        used_inputs
93            .iter()
94            .map(|input| TransactionInput {
95                transaction_id: input.0,
96                output_index: input.2,
97                signature: None,
98            })
99            .collect::<Vec<TransactionInput>>(),
100        receivers
101            .iter()
102            .map(|receiver| TransactionOutput {
103                amount: receiver.1,
104                receiver: receiver.0,
105            })
106            .collect(),
107        &mut vec![sender; used_inputs.len()],
108    )?;
109
110    Ok(transaction)
111}
112
113/// Build a new block, given a blockchain data provider reference and a transaction vector
114/// WARNING: This does not compute block pow nor hash!
115/// WARNING: It is assumed that all input transactions are fully valid (at current blockchain height)
116/// WARNING: This function adds reward transactions for you!
117pub async fn build_block<B>(
118    blockchain_data_provider: &B,
119    transactions: &Vec<Transaction>,
120    miner: Public,
121) -> Result<Block, UtilError> where B: BlockchainDataProvider {
122    let reward = get_block_reward(blockchain_data_provider.get_height().await?);
123    let mut transactions = transactions.clone();
124    transactions.push(Transaction::new_transaction_now(
125        vec![],
126        vec![
127            TransactionOutput {
128                amount: calculate_dev_fee(reward),
129                receiver: DEV_WALLET,
130            },
131            TransactionOutput {
132                amount: reward - calculate_dev_fee(reward),
133                receiver: miner,
134            },
135        ],
136        &mut vec![],
137    )?);
138    let reward_tx_i = transactions.len() - 1;
139    transactions[reward_tx_i]
140        .compute_pow(&blockchain_data_provider.get_transaction_difficulty().await?, None)?;
141    let block = Block::new_block_now(
142        transactions,
143        &blockchain_data_provider.get_block_difficulty().await?,
144        &blockchain_data_provider.get_transaction_difficulty().await?,
145        blockchain_data_provider.get_block_hash_by_height(blockchain_data_provider.get_height().await?.saturating_sub(1)).await?.unwrap_or(GENESIS_PREVIOUS_BLOCK_HASH)
146    );
147
148    Ok(block)
149}