Skip to main content

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