Skip to content

Commit e7e3d85

Browse files
CopilotSteake
andcommitted
Merge rc1 into feature/economic-system-and-rewards: resolve conflicts in favor of production code
Co-authored-by: Steake <530040+Steake@users.noreply.github.com>
1 parent d5c9fcc commit e7e3d85

7 files changed

Lines changed: 133 additions & 544 deletions

File tree

crates/bitcell-node/src/blockchain.rs

Lines changed: 32 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,17 @@
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
82
93
use crate::{Result, MetricsRegistry};
104
use bitcell_consensus::{Block, BlockHeader, Transaction, BattleProof};
115
use bitcell_crypto::{Hash256, PublicKey, SecretKey};
12-
use bitcell_economics::{COIN, INITIAL_BLOCK_REWARD, HALVING_INTERVAL, MAX_HALVINGS};
6+
use bitcell_economics::{COIN, INITIAL_BLOCK_REWARD, HALVING_INTERVAL};
137
use bitcell_state::StateManager;
148
use std::sync::{Arc, RwLock};
159
use std::collections::HashMap;
1610

1711
/// Genesis block height
1812
pub const GENESIS_HEIGHT: u64 = 0;
1913

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-
2714
/// Blockchain manager
28-
///
29-
/// Maintains the blockchain state including blocks, transactions, and state root.
30-
/// Provides O(1) transaction lookup via hash index.
3115
#[derive(Clone)]
3216
pub struct Blockchain {
3317
/// Current chain height
@@ -39,9 +23,6 @@ pub struct Blockchain {
3923
/// Block storage (height -> block)
4024
blocks: Arc<RwLock<HashMap<u64, Block>>>,
4125

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

@@ -65,7 +46,6 @@ impl Blockchain {
6546
height: Arc::new(RwLock::new(GENESIS_HEIGHT)),
6647
latest_hash: Arc::new(RwLock::new(genesis_hash)),
6748
blocks: Arc::new(RwLock::new(blocks)),
68-
tx_index: Arc::new(RwLock::new(HashMap::new())),
6949
state: Arc::new(RwLock::new(StateManager::new())),
7050
metrics,
7151
secret_key,
@@ -101,64 +81,29 @@ impl Blockchain {
10181
}
10282

10383
/// 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.
10784
pub fn height(&self) -> u64 {
10885
*self.height.read().unwrap_or_else(|e| {
109-
tracing::error!("Lock poisoned in height() - prior panic detected: {}", e);
86+
eprintln!("Lock poisoned in height(): {}", e);
11087
e.into_inner()
11188
})
11289
}
11390

11491
/// 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.
11892
pub fn latest_hash(&self) -> Hash256 {
11993
*self.latest_hash.read().unwrap_or_else(|e| {
120-
tracing::error!("Lock poisoned in latest_hash() - prior panic detected: {}", e);
94+
eprintln!("Lock poisoned in latest_hash(): {}", e);
12195
e.into_inner()
12296
})
12397
}
12498

12599
/// 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.
129100
pub fn get_block(&self, height: u64) -> Option<Block> {
130101
self.blocks.read().unwrap_or_else(|e| {
131-
tracing::error!("Lock poisoned in get_block() - prior panic detected: {}", e);
102+
eprintln!("Lock poisoned in get_block(): {}", e);
132103
e.into_inner()
133104
}).get(&height).cloned()
134105
}
135106

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-
162107
/// Get state manager (read-only access)
163108
pub fn state(&self) -> Arc<RwLock<StateManager>> {
164109
Arc::clone(&self.state)
@@ -167,8 +112,8 @@ impl Blockchain {
167112
/// Calculate block reward based on height (halves every HALVING_INTERVAL blocks)
168113
pub fn calculate_block_reward(height: u64) -> u64 {
169114
let halvings = height / HALVING_INTERVAL;
170-
if halvings >= MAX_HALVINGS {
171-
// After MAX_HALVINGS halvings, reward is effectively 0
115+
if halvings >= 64 {
116+
// After 64 halvings, reward is effectively 0
172117
return 0;
173118
}
174119
INITIAL_BLOCK_REWARD >> halvings
@@ -190,45 +135,24 @@ impl Blockchain {
190135

191136
// Get current state root
192137
let state_root = {
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-
});
138+
let state = self.state.read().unwrap();
197139
state.state_root
198140
};
199141

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())
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()
211146
} else {
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())
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
231152
};
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();
232156

233157
// Create block header
234158
let header = BlockHeader {
@@ -283,27 +207,16 @@ impl Blockchain {
283207
return Err(crate::Error::Node("Invalid block signature".to_string()));
284208
}
285209

286-
// Verify VRF proof using proper VRF chaining
210+
// Verify VRF
287211
let vrf_proof: bitcell_crypto::VrfProof = bincode::deserialize(&block.header.vrf_proof)
288212
.map_err(|_| crate::Error::Node("Invalid VRF proof format".to_string()))?;
289213

290-
// Reconstruct VRF input using the same chaining logic as produce_block
291214
let vrf_input = if block.header.height == 1 {
292-
// First block after genesis uses genesis hash as VRF input
293215
block.header.prev_hash.as_bytes().to_vec()
294216
} else {
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-
}
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
307220
};
308221

309222
let vrf_output = vrf_proof.verify(&block.header.proposer, &vrf_input)
@@ -337,23 +250,13 @@ impl Blockchain {
337250

338251
// Apply transactions to state
339252
{
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-
});
253+
let mut state = self.state.write().unwrap();
344254

345255
// Apply block reward to proposer
346256
let reward = Self::calculate_block_reward(block_height);
347257
if reward > 0 {
348-
match state.credit_account(*block.header.proposer.as_bytes(), reward) {
349-
Ok(_) => {
350-
tracing::info!("Block reward credited: {} units to proposer", reward);
351-
}
352-
Err(e) => {
353-
tracing::error!("Failed to credit block reward: {:?}", e);
354-
return Err(crate::Error::Node("Failed to credit block reward".to_string()));
355-
}
356-
}
258+
state.credit_account(*block.header.proposer.as_bytes(), reward);
259+
println!("Block reward credited: {} units to proposer", reward);
357260
}
358261

359262
for tx in &block.transactions {
@@ -366,54 +269,30 @@ impl Blockchain {
366269
) {
367270
Ok(new_state_root) => {
368271
// State updated successfully
369-
tracing::debug!("Transaction applied, new state root: {:?}", new_state_root);
272+
println!("Transaction applied, new state root: {:?}", new_state_root);
370273
}
371274
Err(e) => {
372-
tracing::warn!("Failed to apply transaction: {:?}", e);
275+
println!("Failed to apply transaction: {:?}", e);
373276
// In production, this should rollback the entire block
374277
// For now, we just skip the transaction
375278
}
376279
}
377280
}
378281
}
379282

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-
395283
// Store block
396284
{
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-
});
285+
let mut blocks = self.blocks.write().unwrap();
401286
blocks.insert(block_height, block);
402287
}
403288

404289
// Update chain tip
405290
{
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-
});
291+
let mut height = self.height.write().unwrap();
410292
*height = block_height;
411293
}
412294
{
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-
});
295+
let mut latest_hash = self.latest_hash.write().unwrap();
417296
*latest_hash = block_hash;
418297
}
419298

@@ -446,10 +325,7 @@ impl Blockchain {
446325
}
447326

448327
// Check nonce and balance
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-
});
328+
let state = self.state.read().unwrap();
453329
if let Some(account) = state.get_account(tx.from.as_bytes()) {
454330
if tx.nonce != account.nonce {
455331
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-
tracing::info!("Local Peer ID: {}", local_peer_id);
53+
println!("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-
tracing::info!("Received block via Gossipsub from {}", peer_id);
139+
println!("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-
tracing::info!("Received tx via Gossipsub from {}", peer_id);
144+
println!("Received tx via Gossipsub from {}", peer_id);
145145
let _ = tx_tx.send(tx).await;
146146
}
147147
}
148148
}
149149
SwarmEvent::NewListenAddr { address, .. } => {
150-
tracing::info!("DHT listening on {:?}", address);
150+
println!("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-
tracing::error!("Failed to publish block via Gossipsub: {:?}", e);
160+
eprintln!("Failed to publish block: {:?}", e);
161161
}
162162
}
163163
Some(DhtCommand::BroadcastTransaction(data)) => {
164164
if let Err(e) = swarm.behaviour_mut().gossipsub.publish(tx_topic.clone(), data) {
165-
tracing::error!("Failed to publish transaction via Gossipsub: {:?}", e);
165+
eprintln!("Failed to publish tx: {:?}", e);
166166
}
167167
}
168168
None => break,

0 commit comments

Comments
 (0)