snap_coin/light_node/
mod.rs1pub mod light_node_state;
3
4pub mod block_meta_store;
6
7pub mod interesting_blocks;
9
10pub mod initial_block_download;
12
13mod 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
44pub 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(); 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
70pub 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
93pub async fn accept_block(
95 light_node_state: &SharedLightNodeState,
96 new_block: Block,
97) -> Result<(), PeerError> {
98 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(); 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 let _ = light_node_state.chain_events.send(LightChainEvent::Block {
161 block: new_block.clone(),
162 });
163 Ok(())
164}
165
166pub 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(); 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 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 let _ = light_node_state
237 .chain_events
238 .send(LightChainEvent::Transaction {
239 transaction: new_transaction.clone(),
240 });
241
242 Ok(())
243}