Skip to content

Commit 9fcb846

Browse files
CopilotSteake
andcommitted
Restore rc1 production code for all conflicting files
Co-authored-by: Steake <530040+Steake@users.noreply.github.com>
1 parent 67235b0 commit 9fcb846

7 files changed

Lines changed: 527 additions & 136 deletions

File tree

crates/bitcell-node/src/blockchain.rs

Lines changed: 146 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
///! Blockchain manager for block production and validation
2+
///!
3+
///! Provides functionality for:
4+
///! - Block production with VRF-based proposer selection
5+
///! - Block validation including signature, VRF, and transaction verification
6+
///! - Transaction indexing for efficient lookups
7+
///! - State management with Merkle tree root computation
28
39
use crate::{Result, MetricsRegistry};
410
use bitcell_consensus::{Block, BlockHeader, Transaction, BattleProof};
@@ -11,7 +17,17 @@ use std::collections::HashMap;
1117
/// Genesis block height
1218
pub const GENESIS_HEIGHT: u64 = 0;
1319

20+
/// Transaction location in blockchain (block height and index within block)
21+
#[derive(Clone, Debug)]
22+
pub struct TxLocation {
23+
pub block_height: u64,
24+
pub tx_index: usize,
25+
}
26+
1427
/// Blockchain manager
28+
///
29+
/// Maintains the blockchain state including blocks, transactions, and state root.
30+
/// Provides O(1) transaction lookup via hash index.
1531
#[derive(Clone)]
1632
pub struct Blockchain {
1733
/// Current chain height
@@ -23,6 +39,9 @@ pub struct Blockchain {
2339
/// Block storage (height -> block)
2440
blocks: Arc<RwLock<HashMap<u64, Block>>>,
2541

42+
/// Transaction hash index for O(1) lookups (tx_hash -> location)
43+
tx_index: Arc<RwLock<HashMap<Hash256, TxLocation>>>,
44+
2645
/// State manager
2746
state: Arc<RwLock<StateManager>>,
2847

@@ -46,6 +65,7 @@ impl Blockchain {
4665
height: Arc::new(RwLock::new(GENESIS_HEIGHT)),
4766
latest_hash: Arc::new(RwLock::new(genesis_hash)),
4867
blocks: Arc::new(RwLock::new(blocks)),
68+
tx_index: Arc::new(RwLock::new(HashMap::new())),
4969
state: Arc::new(RwLock::new(StateManager::new())),
5070
metrics,
5171
secret_key,
@@ -81,29 +101,64 @@ impl Blockchain {
81101
}
82102

83103
/// Get current chain height
104+
///
105+
/// Returns the current blockchain height. If the lock is poisoned (indicating
106+
/// a prior panic while holding the lock), logs an error and recovers the guard.
84107
pub fn height(&self) -> u64 {
85108
*self.height.read().unwrap_or_else(|e| {
86-
eprintln!("Lock poisoned in height(): {}", e);
109+
tracing::error!("Lock poisoned in height() - prior panic detected: {}", e);
87110
e.into_inner()
88111
})
89112
}
90113

91114
/// Get latest block hash
115+
///
116+
/// Returns the hash of the latest block. If the lock is poisoned (indicating
117+
/// a prior panic while holding the lock), logs an error and recovers the guard.
92118
pub fn latest_hash(&self) -> Hash256 {
93119
*self.latest_hash.read().unwrap_or_else(|e| {
94-
eprintln!("Lock poisoned in latest_hash(): {}", e);
120+
tracing::error!("Lock poisoned in latest_hash() - prior panic detected: {}", e);
95121
e.into_inner()
96122
})
97123
}
98124

99125
/// Get block by height
126+
///
127+
/// Returns the block at the specified height, or None if not found.
128+
/// If the lock is poisoned, logs an error and recovers the guard.
100129
pub fn get_block(&self, height: u64) -> Option<Block> {
101130
self.blocks.read().unwrap_or_else(|e| {
102-
eprintln!("Lock poisoned in get_block(): {}", e);
131+
tracing::error!("Lock poisoned in get_block() - prior panic detected: {}", e);
103132
e.into_inner()
104133
}).get(&height).cloned()
105134
}
106-
135+
136+
/// Get transaction by hash using the O(1) hash index
137+
///
138+
/// Returns the transaction and its location (block height, index) if found.
139+
/// This is significantly more efficient than linear scan for large blockchains.
140+
pub fn get_transaction_by_hash(&self, tx_hash: &Hash256) -> Option<(Transaction, TxLocation)> {
141+
// First, look up the location in the index
142+
let location = {
143+
let index = self.tx_index.read().unwrap_or_else(|e| {
144+
tracing::error!("Lock poisoned in get_transaction_by_hash() - prior panic detected: {}", e);
145+
e.into_inner()
146+
});
147+
index.get(tx_hash).cloned()
148+
};
149+
150+
// Then retrieve the actual transaction from the block
151+
if let Some(loc) = location {
152+
if let Some(block) = self.get_block(loc.block_height) {
153+
if loc.tx_index < block.transactions.len() {
154+
return Some((block.transactions[loc.tx_index].clone(), loc));
155+
}
156+
}
157+
}
158+
159+
None
160+
}
161+
107162
/// Get state manager (read-only access)
108163
pub fn state(&self) -> Arc<RwLock<StateManager>> {
109164
Arc::clone(&self.state)
@@ -135,24 +190,45 @@ impl Blockchain {
135190

136191
// Get current state root
137192
let state_root = {
138-
let state = self.state.read().unwrap();
193+
let state = self.state.read().unwrap_or_else(|e| {
194+
tracing::error!("Lock poisoned in produce_block() while reading state - prior panic detected: {}", e);
195+
e.into_inner()
196+
});
139197
state.state_root
140198
};
141-
142-
// Generate VRF output and proof
143-
// Input is previous block's VRF output (or hash if genesis)
144-
let vrf_input = if new_height == 1 {
145-
prev_hash.as_bytes().to_vec()
199+
200+
// Generate VRF output and proof using proper VRF chaining
201+
// For genesis block (height 1), use previous hash as input
202+
// For all other blocks, use the previous block's VRF output for chaining
203+
//
204+
// NOTE: We generate VRF proof while holding the blocks lock to prevent race conditions
205+
// where the blockchain state could change between reading the VRF input and using it.
206+
let (vrf_output, vrf_proof_bytes) = if new_height == 1 {
207+
// First block after genesis uses genesis hash as VRF input
208+
let vrf_input = prev_hash.as_bytes().to_vec();
209+
let (vrf_output, vrf_proof) = self.secret_key.vrf_prove(&vrf_input);
210+
(vrf_output, bincode::serialize(&vrf_proof).unwrap_or_default())
146211
} else {
147-
// In a real implementation, we'd get the previous block's VRF output
148-
// For now, we mix the prev_hash with the height to ensure uniqueness
149-
let mut input = prev_hash.as_bytes().to_vec();
150-
input.extend_from_slice(&new_height.to_le_bytes());
151-
input
212+
// Use previous block's VRF output for proper VRF chaining
213+
// This ensures verifiable randomness chain where each output
214+
// deterministically derives from the previous output
215+
let blocks = self.blocks.read().unwrap_or_else(|e| {
216+
tracing::error!("Lock poisoned in produce_block() - prior panic detected: {}", e);
217+
e.into_inner()
218+
});
219+
220+
let vrf_input = if let Some(prev_block) = blocks.get(&current_height) {
221+
prev_block.header.vrf_output.to_vec()
222+
} else {
223+
// Fallback if previous block not found (shouldn't happen in normal operation)
224+
tracing::warn!("Previous block {} not found for VRF chaining, using hash fallback", current_height);
225+
prev_hash.as_bytes().to_vec()
226+
};
227+
228+
// Generate VRF proof while still holding the read lock to prevent race conditions
229+
let (vrf_output, vrf_proof) = self.secret_key.vrf_prove(&vrf_input);
230+
(vrf_output, bincode::serialize(&vrf_proof).unwrap_or_default())
152231
};
153-
154-
let (vrf_output, vrf_proof) = self.secret_key.vrf_prove(&vrf_input);
155-
let vrf_proof_bytes = bincode::serialize(&vrf_proof).unwrap_or_default();
156232

157233
// Create block header
158234
let header = BlockHeader {
@@ -206,17 +282,28 @@ impl Blockchain {
206282
if block.signature.verify(&block.header.proposer, header_hash.as_bytes()).is_err() {
207283
return Err(crate::Error::Node("Invalid block signature".to_string()));
208284
}
209-
210-
// Verify VRF
285+
286+
// Verify VRF proof using proper VRF chaining
211287
let vrf_proof: bitcell_crypto::VrfProof = bincode::deserialize(&block.header.vrf_proof)
212288
.map_err(|_| crate::Error::Node("Invalid VRF proof format".to_string()))?;
213-
289+
290+
// Reconstruct VRF input using the same chaining logic as produce_block
214291
let vrf_input = if block.header.height == 1 {
292+
// First block after genesis uses genesis hash as VRF input
215293
block.header.prev_hash.as_bytes().to_vec()
216294
} else {
217-
let mut input = block.header.prev_hash.as_bytes().to_vec();
218-
input.extend_from_slice(&block.header.height.to_le_bytes());
219-
input
295+
// Use previous block's VRF output for proper VRF chaining
296+
let blocks = self.blocks.read().unwrap_or_else(|e| {
297+
tracing::error!("Lock poisoned in validate_block() - prior panic detected: {}", e);
298+
e.into_inner()
299+
});
300+
if let Some(prev_block) = blocks.get(&(block.header.height - 1)) {
301+
prev_block.header.vrf_output.to_vec()
302+
} else {
303+
return Err(crate::Error::Node(
304+
format!("Previous block {} not found for VRF verification", block.header.height - 1)
305+
));
306+
}
220307
};
221308

222309
let vrf_output = vrf_proof.verify(&block.header.proposer, &vrf_input)
@@ -250,7 +337,10 @@ impl Blockchain {
250337

251338
// Apply transactions to state
252339
{
253-
let mut state = self.state.write().unwrap();
340+
let mut state = self.state.write().unwrap_or_else(|e| {
341+
tracing::error!("Lock poisoned in add_block() while writing state - prior panic detected: {}", e);
342+
e.into_inner()
343+
});
254344

255345
// Apply block reward to proposer
256346
let reward = Self::calculate_block_reward(block_height);
@@ -287,19 +377,43 @@ impl Blockchain {
287377
}
288378
}
289379

380+
// Index transactions for O(1) lookup
381+
{
382+
let mut tx_index = self.tx_index.write().unwrap_or_else(|e| {
383+
tracing::error!("Lock poisoned in add_block() while indexing transactions - prior panic detected: {}", e);
384+
e.into_inner()
385+
});
386+
for (idx, tx) in block.transactions.iter().enumerate() {
387+
tx_index.insert(tx.hash(), TxLocation {
388+
block_height,
389+
tx_index: idx,
390+
});
391+
}
392+
tracing::debug!("Indexed {} transactions in block {}", block.transactions.len(), block_height);
393+
}
394+
290395
// Store block
291396
{
292-
let mut blocks = self.blocks.write().unwrap();
397+
let mut blocks = self.blocks.write().unwrap_or_else(|e| {
398+
tracing::error!("Lock poisoned in add_block() while storing block - prior panic detected: {}", e);
399+
e.into_inner()
400+
});
293401
blocks.insert(block_height, block);
294402
}
295403

296404
// Update chain tip
297405
{
298-
let mut height = self.height.write().unwrap();
406+
let mut height = self.height.write().unwrap_or_else(|e| {
407+
tracing::error!("Lock poisoned in add_block() while updating height - prior panic detected: {}", e);
408+
e.into_inner()
409+
});
299410
*height = block_height;
300411
}
301412
{
302-
let mut latest_hash = self.latest_hash.write().unwrap();
413+
let mut latest_hash = self.latest_hash.write().unwrap_or_else(|e| {
414+
tracing::error!("Lock poisoned in add_block() while updating latest hash - prior panic detected: {}", e);
415+
e.into_inner()
416+
});
303417
*latest_hash = block_hash;
304418
}
305419

@@ -332,7 +446,10 @@ impl Blockchain {
332446
}
333447

334448
// Check nonce and balance
335-
let state = self.state.read().unwrap();
449+
let state = self.state.read().unwrap_or_else(|e| {
450+
tracing::error!("Lock poisoned in validate_transaction() - prior panic detected: {}", e);
451+
e.into_inner()
452+
});
336453
if let Some(account) = state.get_account(tx.from.as_bytes()) {
337454
if tx.nonce != account.nonce {
338455
return Err(crate::Error::Node(format!(

crates/bitcell-node/src/dht.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl DhtManager {
5050
// 1. Create libp2p keypair
5151
let keypair = Self::bitcell_to_libp2p_keypair(secret_key)?;
5252
let local_peer_id = PeerId::from(keypair.public());
53-
println!("Local Peer ID: {}", local_peer_id);
53+
tracing::info!("Local Peer ID: {}", local_peer_id);
5454

5555
// 2. Create transport
5656
let mut swarm = SwarmBuilder::with_existing_identity(keypair.clone())
@@ -136,18 +136,18 @@ impl DhtManager {
136136
})) => {
137137
if message.topic == block_topic.hash() {
138138
if let Ok(block) = bincode::deserialize::<Block>(&message.data) {
139-
println!("Received block via Gossipsub from {}", peer_id);
139+
tracing::info!("Received block via Gossipsub from {}", peer_id);
140140
let _ = block_tx.send(block).await;
141141
}
142142
} else if message.topic == tx_topic.hash() {
143143
if let Ok(tx) = bincode::deserialize::<Transaction>(&message.data) {
144-
println!("Received tx via Gossipsub from {}", peer_id);
144+
tracing::info!("Received tx via Gossipsub from {}", peer_id);
145145
let _ = tx_tx.send(tx).await;
146146
}
147147
}
148148
}
149149
SwarmEvent::NewListenAddr { address, .. } => {
150-
println!("DHT listening on {:?}", address);
150+
tracing::info!("DHT listening on {:?}", address);
151151
}
152152
_ => {}
153153
},
@@ -157,12 +157,12 @@ impl DhtManager {
157157
}
158158
Some(DhtCommand::BroadcastBlock(data)) => {
159159
if let Err(e) = swarm.behaviour_mut().gossipsub.publish(block_topic.clone(), data) {
160-
eprintln!("Failed to publish block: {:?}", e);
160+
tracing::error!("Failed to publish block via Gossipsub: {:?}", e);
161161
}
162162
}
163163
Some(DhtCommand::BroadcastTransaction(data)) => {
164164
if let Err(e) = swarm.behaviour_mut().gossipsub.publish(tx_topic.clone(), data) {
165-
eprintln!("Failed to publish tx: {:?}", e);
165+
tracing::error!("Failed to publish transaction via Gossipsub: {:?}", e);
166166
}
167167
}
168168
None => break,

0 commit comments

Comments
 (0)