use super::{BlockData, ChainWrapper, ChainWrapperExt, CoreChainSpec, Epoch, Position};
use crate::blocks::{generate_genesis_block, Block, BlockHeader, BlockHeaderSpec, CoreBlockSpec};
use scsys::prelude::{
rand::{self, Rng},
Hashable, Timestamp, H160, H256,
};
use std::collections::HashMap;
#[derive(Debug)]
pub struct Blockchain {
pub chain: HashMap<H256, BlockData>,
pub epoch: Epoch,
pub lead: u128,
pub length: u128,
pub map: HashMap<H256, HashMap<H256, H160>>,
pub position: Position,
pub timestamp: i64, pub tip: H256,
}
impl Blockchain {
pub fn new(timestamp: i64) -> Self {
let genesis = generate_genesis_block(timestamp);
log::info!(
"Created the genesis block with the timestamp: {}",
genesis.header.timestamp
);
let data = BlockData::new(genesis.clone(), 0);
let hash: H256 = genesis.hash();
let map = HashMap::new();
Self {
chain: HashMap::from([(hash, data)]),
epoch: Epoch::default(),
lead: 0,
length: 0,
map,
position: Position::default(),
timestamp: genesis.header.timestamp,
tip: hash,
}
}
pub fn insert_selfish_pos(&mut self, block: &Block) -> bool {
if self.is_block(&block.hash()) {
false
} else {
let header: BlockHeader = block.header().clone();
let parenthash: H256 = header.parent();
let parentdata: BlockData = match self.chain.get(&parenthash) {
Some(data) => data.clone(),
None => return false,
};
let parentheight = parentdata.height;
let newheight = parentheight + 1;
let newdata = BlockData::new(block.clone(), newheight);
let newhash = block.hash();
self.chain.insert(newhash, newdata);
self.position.pos += 1;
if newheight > self.position.depth && block.selfish_block {
self.lead += 1;
self.position.depth = newheight;
self.tip = newhash;
return true;
} else if !block.selfish_block && newheight > self.length {
if self.lead > 0 {
self.lead -= 1;
self.length += 1;
return false;
} else {
self.position.depth = newheight;
self.tip = newhash;
self.length = newheight;
return true;
}
}
false
}
}
pub fn insert_unselfish_pos(&mut self, block: &Block) -> bool {
if self.chain.contains_key(&block.hash()) {
false
} else {
let pdata: BlockData = match self.find_one_payload(&block.header.parent()) {
Some(v) => v,
None => return false,
};
let height = pdata.height + 1;
let data = BlockData::new(block.clone(), height);
let newhash = block.hash();
self.chain.insert(newhash, data);
self.position.pos += 1;
let mut rng = rand::thread_rng();
let p: f64 = rng.gen::<f64>();
if height > self.position.depth
|| (height == self.position.depth && block.selfish_block && p < 1.0)
{
self.position.depth = height;
self.tip = newhash;
return true;
}
false
}
}
pub fn insert_pos(&mut self, block: &Block, selfish: bool) -> bool {
if !selfish {
self.insert_unselfish_pos(block)
} else {
self.insert_selfish_pos(block)
}
}
pub fn insert_pow(&mut self, block: &Block) -> bool {
if self.is_block(&block.hash()) {
false
} else {
let prev: BlockData = match self.find_one_payload(&block.header().parent()) {
None => return false,
Some(v) => v,
};
let data = BlockData::new(block.clone(), prev.height + 1);
let hash = block.hash();
self.chain.insert(hash, data);
self.position.pow += 1;
true
}
}
}
impl CoreChainSpec for Blockchain {
fn chain(&self) -> &HashMap<H256, BlockData> {
&self.chain
}
fn epoch(&self) -> &Epoch {
&self.epoch
}
fn tip(&self) -> H256 {
self.tip
}
fn map(&self) -> &HashMap<H256, HashMap<H256, H160>> {
&self.map
}
fn position(&self) -> &Position {
&self.position
}
fn timestamp(&self) -> i64 {
self.timestamp
}
fn lead(&self) -> u128 {
self.lead
}
fn length(&self) -> u128 {
self.length
}
}
impl ChainWrapper for Blockchain {}
impl ChainWrapperExt for Blockchain {
fn genesis(blockgen: fn(i64) -> Block, timestamp: i64) -> Self
where
Self: Sized,
{
let genesis = blockgen(timestamp);
log::info!(
"Created the genesis block with the timestamp: {}",
genesis.header.timestamp
);
let data = BlockData::new(genesis.clone(), 0);
let hash: H256 = genesis.hash();
let map = HashMap::new();
Self {
chain: HashMap::from([(hash, data)]),
epoch: Epoch::default(),
lead: 0,
length: 0,
map,
position: Position::default(),
timestamp: genesis.header.timestamp,
tip: hash,
}
}
}
impl Default for Blockchain {
fn default() -> Self {
Self::new(Timestamp::timestamp())
}
}
impl std::fmt::Display for Blockchain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", serde_json::to_string(&self.chain).unwrap())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_blockchain_genesis() {
let a = Blockchain::default();
assert!(a.contains_hash(a.chain.keys().last().unwrap()));
}
}