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/// Provides a standardized way to access blockchain data from many sources
12pub mod blockchain_data_provider;
13
14/// Core implementation of Snap Coin, contains all blockchain logic
15pub mod core;
16
17/// Contains all crypto primitives
18pub mod crypto;
19
20/// Handles the basic P2P protocol of a node
21pub mod node;
22
23/// Full node logic
24pub mod full_node;
25
26/// Light node logic
27pub mod light_node;
28
29/// A struct for interacting with a node instance through the Snap Coin API
30pub mod api;
31
32/// Tests
33mod tests;
34
35/// Current Snap Coin version
36pub mod version;
37
38/// Economics package. Mostly utils and CONSTS
39pub use core::economics;
40pub use core::economics::to_snap;
41pub use economics::to_nano;
42
43use crate::{
44    blockchain_data_provider::{BlockchainDataProvider, BlockchainDataProviderError},
45    core::{
46        block::MAX_TRANSACTIONS_PER_BLOCK,
47        transaction::{MAX_TRANSACTION_IO, TransactionError},
48    },
49    crypto::{
50        address_inclusion_filter::{AddressInclusionFilter, AddressInclusionFilterError},
51        keys::{Private, Public},
52        merkle_tree::MerkleTree,
53    },
54    economics::GENESIS_PREVIOUS_BLOCK_HASH,
55};
56
57#[derive(Error, Debug)]
58pub enum UtilError {
59    #[error("Blockchain error: {0}")]
60    BlockchainError(#[from] BlockchainError),
61
62    #[error("Transaction error: {0}")]
63    TransactionError(#[from] TransactionError),
64
65    #[error("Insufficient funds to complete operation")]
66    InsufficientFunds,
67
68    #[error("Encode error {0}")]
69    EncodeError(#[from] EncodeError),
70
71    #[error("Data provider error {0}")]
72    BlockchainDataProviderError(#[from] BlockchainDataProviderError),
73
74    #[error(
75        "Too many inputs and outputs for one transaction. Consider splitting transaction in to more than one (smaller SNAP amount) or less receivers."
76    )]
77    TooMuchIO,
78
79    #[error("Too many transactions for block")]
80    TooManyTransactions,
81
82    #[error("Address inclusion filter error: {0}")]
83    AddressInclusionFilter(#[from] AddressInclusionFilterError),
84}
85
86/// Build a new transactions, sending from sender to receiver where each receiver has a amount to receive attached. Takes biggest coins first.
87/// WARNING: this does not compute transaction pow!
88pub async fn build_transaction<B>(
89    blockchain_data_provider: &B,
90    sender: Private,
91    mut receivers: Vec<(Public, u64)>,
92    ignore_inputs: &Vec<TransactionInput>,
93) -> Result<Transaction, UtilError>
94where
95    B: BlockchainDataProvider,
96{
97    let target_balance = receivers
98        .iter()
99        .fold(0u64, |acc, receiver| acc + receiver.1);
100
101    let mut available_inputs = blockchain_data_provider
102        .get_available_transaction_outputs(sender.to_public())
103        .await?;
104
105    available_inputs.retain(|(transaction, _, index)| {
106        !ignore_inputs
107            .iter()
108            .any(|i_input| i_input.output_index == *index && i_input.transaction_id == *transaction)
109    });
110
111    let mut used_inputs = vec![];
112
113    let mut current_funds = 0u64;
114    for (transaction, input, index) in available_inputs {
115        current_funds += input.amount;
116        used_inputs.push((transaction, input, index));
117        if current_funds >= target_balance {
118            break;
119        }
120    }
121
122    if target_balance > current_funds {
123        return Err(UtilError::InsufficientFunds);
124    }
125
126    if target_balance < current_funds {
127        receivers.push((sender.to_public(), current_funds - target_balance));
128    }
129
130    if used_inputs.len() + receivers.len() > MAX_TRANSACTION_IO {
131        return Err(UtilError::TooMuchIO);
132    }
133
134    used_inputs.sort_by(|a, b| a.1.amount.cmp(&b.1.amount)); // From highest amount to lowest amount (breadcrumbs last)
135
136    let transaction = Transaction::new_transaction_now(
137        used_inputs
138            .iter()
139            .map(|input| TransactionInput {
140                transaction_id: input.0,
141                output_index: input.2,
142                signature: None,
143                output_owner: sender.to_public(),
144            })
145            .collect::<Vec<TransactionInput>>(),
146        receivers
147            .iter()
148            .map(|receiver| TransactionOutput {
149                amount: receiver.1,
150                receiver: receiver.0,
151            })
152            .collect(),
153        &mut vec![sender; used_inputs.len()],
154    )?;
155
156    Ok(transaction)
157}
158
159/// Build a new block, given a blockchain data provider reference and a transaction vector
160/// WARNING: This does not compute block pow nor hash!
161/// WARNING: It is assumed that all input transactions are fully valid (at current blockchain height)
162/// WARNING: This function adds reward transactions for you!
163pub async fn build_block<B>(
164    blockchain_data_provider: &B,
165    transactions: &Vec<Transaction>,
166    miner: Public,
167) -> Result<Block, UtilError>
168where
169    B: BlockchainDataProvider,
170{
171    let reward = get_block_reward(blockchain_data_provider.get_height().await?);
172
173    let mut transactions = transactions.clone();
174
175    transactions.push(Transaction::new_transaction_now(
176        vec![],
177        vec![
178            TransactionOutput {
179                amount: calculate_dev_fee(reward),
180                receiver: DEV_WALLET,
181            },
182            TransactionOutput {
183                amount: reward - calculate_dev_fee(reward),
184                receiver: miner,
185            },
186        ],
187        &mut vec![],
188    )?);
189
190    if transactions.len() > MAX_TRANSACTIONS_PER_BLOCK {
191        return Err(UtilError::TooManyTransactions);
192    }
193    let reward_tx_i = transactions.len() - 1;
194    transactions[reward_tx_i].compute_pow(
195        &blockchain_data_provider
196            .get_transaction_difficulty()
197            .await?,
198        None,
199    )?;
200
201    let mut ids = vec![];
202    for tx in &transactions {
203        tx.check_completeness()?;
204        ids.push(tx.transaction_id.unwrap());
205    }
206    let merkle_tree = MerkleTree::build(&ids);
207
208    let filter = AddressInclusionFilter::create_filter(&transactions)?;
209
210    let previous_block = blockchain_data_provider
211        .get_block_hash_by_height(
212            blockchain_data_provider
213                .get_height()
214                .await?
215                .saturating_sub(1),
216        )
217        .await?
218        .unwrap_or(GENESIS_PREVIOUS_BLOCK_HASH);
219
220    let block = Block::new_block_now(
221        transactions,
222        &blockchain_data_provider.get_block_difficulty().await?,
223        &blockchain_data_provider
224            .get_transaction_difficulty()
225            .await?,
226        previous_block,
227        &merkle_tree.root_hash(),
228        filter,
229    );
230
231    Ok(block)
232}