Skip to main content

snap_coin/light_node/
mod.rs

1/// Handles Light Node State
2pub mod light_node_state;
3
4/// Handles storing downloaded block meta
5pub mod block_meta_store;
6
7/// Handles storing blocks that the node might be interested in
8pub mod interesting_blocks;
9
10/// Starts initial block download
11pub mod initial_block_download;
12
13/// Handles what the light node does when it gets a p2p message
14mod behavior;
15
16use flexi_logger::{Duplicate, FileSpec, Logger};
17use log::info;
18use num_bigint::BigUint;
19use std::{
20    net::SocketAddr,
21    path::PathBuf,
22    sync::{Arc, Once},
23};
24use tokio::net::TcpStream;
25
26use crate::{
27    core::{
28        block::{Block, MAX_TRANSACTIONS_PER_BLOCK},
29        blockchain::{self, BlockchainError},
30        difficulty::calculate_block_difficulty,
31        transaction::{MAX_TRANSACTION_IO, Transaction, TransactionError},
32    },
33    light_node::{
34        behavior::LightNodePeerBehavior,
35        light_node_state::{LightChainEvent, LightNodeState},
36    },
37    node::peer::{PeerError, PeerHandle, create_peer},
38};
39
40pub type SharedLightNodeState = Arc<LightNodeState>;
41
42static LOGGER_INIT: Once = Once::new();
43
44/// Creates a full node (SharedBlockchain and SharedNodeState), connecting to peers, accepting blocks and transactions
45pub fn create_light_node(node_path: &str, disable_stdout: bool) -> SharedLightNodeState {
46    let node_path = PathBuf::from(node_path);
47
48    LOGGER_INIT.call_once(|| {
49        let log_path = node_path.join("logs");
50        std::fs::create_dir_all(&log_path).expect("Failed to create log directory");
51
52        let mut logger = Logger::try_with_str("info")
53            .unwrap()
54            .log_to_file(FileSpec::default().directory(&log_path));
55
56        if !disable_stdout {
57            logger = logger.duplicate_to_stderr(Duplicate::Info);
58        }
59
60        logger.start().ok(); // Ignore errors if logger is already set
61
62        info!("Logger initialized for node at {:?}", node_path);
63    });
64
65    let node_state = LightNodeState::new_empty(node_path);
66
67    Arc::new(node_state)
68}
69
70/// Connect to a peer
71pub async fn connect_peer(
72    address: SocketAddr,
73    light_node_state: &SharedLightNodeState,
74) -> Result<PeerHandle, PeerError> {
75    let stream = TcpStream::connect(address)
76        .await
77        .map_err(|e| PeerError::Io(format!("IO error: {e}")))?;
78
79    let handle = create_peer(
80        stream,
81        LightNodePeerBehavior::new(light_node_state.clone()),
82        false,
83    )?;
84    light_node_state
85        .connected_peers
86        .write()
87        .await
88        .insert(address, handle.clone());
89
90    Ok(handle)
91}
92
93/// Accept a new block to the local blockchain, and forward it to all peers
94pub async fn accept_block(
95    light_node_state: &SharedLightNodeState,
96    new_block: Block,
97) -> Result<(), PeerError> {
98    // Make sure merkle tree, filter, and hash
99    if let Err(e) = new_block.check_meta() {
100        return Err(BlockchainError::from(e).into());
101    }
102    let block_hash = new_block.meta.hash.unwrap(); // Unwrap is okay, we checked that block is complete
103
104    // Validation
105    blockchain::validate_block_timestamp(&new_block)?;
106    for tx in &new_block.transactions {
107        blockchain::validate_transaction_timestamp_in_block(tx, &new_block)?;
108    }
109
110    if light_node_state.meta_store().get_last_block_hash() != new_block.meta.previous_block {
111        return Err(BlockchainError::InvalidPreviousBlockHash.into());
112    }
113
114    if new_block.transactions.len() > MAX_TRANSACTIONS_PER_BLOCK {
115        return Err(BlockchainError::TooManyTransactions.into());
116    }
117
118    if BigUint::from_bytes_be(&*block_hash)
119        > BigUint::from_bytes_be(&calculate_block_difficulty(
120            &light_node_state
121                .meta_store()
122                .difficulty_state
123                .get_block_difficulty(),
124            new_block.transactions.len(),
125        ))
126    {
127        return Err(
128            BlockchainError::from(TransactionError::InsufficientDifficulty(
129                block_hash.dump_base36(),
130            ))
131            .into(),
132        );
133    }
134
135    if let Err(e) = new_block.validate_difficulties(
136        &light_node_state
137            .meta_store()
138            .difficulty_state
139            .get_block_difficulty(),
140        &light_node_state
141            .meta_store()
142            .difficulty_state
143            .get_transaction_difficulty(),
144    ) {
145        return Err(BlockchainError::from(e).into());
146    }
147
148    light_node_state
149        .meta_store()
150        .difficulty_state
151        .update_difficulty(&new_block);
152
153    light_node_state
154        .meta_store()
155        .save_block_meta(new_block.meta.clone())?;
156
157    info!("New block accepted: {}", block_hash.dump_base36());
158
159    // Broadcast new block
160    let _ = light_node_state.chain_events.send(LightChainEvent::Block {
161        block: new_block.clone(),
162    });
163    Ok(())
164}
165
166/// Accept a new block to the local blockchain, and forward it to all peers
167pub async fn accept_transaction(
168    light_node_state: &SharedLightNodeState,
169    new_transaction: Transaction,
170) -> Result<(), BlockchainError> {
171    new_transaction.check_completeness()?;
172    let transaction_id = new_transaction.transaction_id.unwrap(); // Unwrap is okay, we checked that tx is complete
173    if light_node_state
174        .seen_transactions
175        .read()
176        .await
177        .contains(&transaction_id)
178    {
179        return Ok(());
180    }
181    light_node_state
182        .seen_transactions
183        .write()
184        .await
185        .insert(transaction_id);
186
187    let transaction_hashing_buf = new_transaction
188        .get_tx_hashing_buf()
189        .map_err(|e| BlockchainError::BincodeEncode(e.to_string()))?;
190
191    // Validation
192    blockchain::validate_transaction_timestamp(&new_transaction)?;
193    new_transaction.check_completeness()?;
194
195    if !transaction_id.compare_with_data(&transaction_hashing_buf) {
196        return Err(TransactionError::InvalidHash(transaction_id.dump_base36()).into());
197    }
198
199    if BigUint::from_bytes_be(&*transaction_id)
200        > BigUint::from_bytes_be(
201            &light_node_state
202                .meta_store()
203                .difficulty_state
204                .get_transaction_difficulty(),
205        )
206    {
207        return Err(TransactionError::InsufficientDifficulty(transaction_id.dump_base36()).into());
208    }
209
210    if new_transaction.inputs.len() + new_transaction.outputs.len() > MAX_TRANSACTION_IO {
211        return Err(TransactionError::TooMuchIO.into());
212    }
213
214    if new_transaction.inputs.is_empty() {
215        return Err(TransactionError::NoInputs.into());
216    }
217
218    for input in &new_transaction.inputs {
219        if input.signature.is_none()
220            || input
221                .signature
222                .unwrap()
223                .validate_with_public(
224                    &input.output_owner,
225                    &new_transaction
226                        .get_input_signing_buf()
227                        .map_err(|e| BlockchainError::BincodeEncode(e.to_string()))?,
228                )
229                .map_or(true, |valid| !valid)
230        {
231            return Err(TransactionError::InvalidSignature(transaction_id.dump_base36()).into());
232        }
233    }
234
235    // Broadcast new transaction
236    let _ = light_node_state
237        .chain_events
238        .send(LightChainEvent::Transaction {
239            transaction: new_transaction.clone(),
240        });
241
242    Ok(())
243}