use crate::bitcoin::consensus::{encode, Decodable, Encodable};
use crate::bitcoin::{Block, BlockHash, OutPoint, Transaction, TxOut};
use crate::FsBlock;
use bitcoin::consensus::serialize;
use bitcoin::Txid;
use log::debug;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::io::{Read, Seek, SeekFrom};
use std::ops::DerefMut;
use std::sync::OnceLock;
#[derive(Debug, Eq, PartialEq)]
pub struct BlockExtra {
pub(crate) version: u8,
block_bytes: Vec<u8>,
block: OnceLock<Block>,
pub(crate) block_hash: BlockHash,
pub(crate) size: u32,
pub(crate) next: Vec<BlockHash>,
pub(crate) height: u32,
pub(crate) outpoint_values: OnceLock<HashMap<OutPoint, TxOut>>,
pub(crate) outpoint_values_vec: Vec<(OutPoint, TxOut)>,
pub(crate) block_total_inputs: u32,
pub(crate) block_total_outputs: u32,
pub(crate) txids: Vec<Txid>,
pub(crate) block_total_txs: usize,
}
impl TryFrom<FsBlock> for BlockExtra {
type Error = String;
fn try_from(fs_block: FsBlock) -> Result<Self, Self::Error> {
let err = |e: String, f: &FsBlock| -> String { format!("{:?} {:?}", e, f) };
let mut guard = fs_block
.file
.lock()
.map_err(|e| err(e.to_string(), &fs_block))?;
let file = guard.deref_mut();
file.seek(SeekFrom::Start(fs_block.start as u64))
.map_err(|e| err(e.to_string(), &fs_block))?;
debug!("going to read: {:?}", file);
let mut block_bytes = vec![0u8; fs_block.end - fs_block.start];
file.read_exact(&mut block_bytes)
.map_err(|e| err(e.to_string(), &fs_block))?;
Ok(BlockExtra {
version: fs_block.serialization_version,
block_bytes,
block: OnceLock::new(),
block_hash: fs_block.hash,
size: (fs_block.end - fs_block.start) as u32,
next: fs_block.next,
height: 0,
outpoint_values: OnceLock::new(),
outpoint_values_vec: Vec::with_capacity(fs_block.block_total_inputs as usize),
block_total_inputs: fs_block.block_total_inputs,
block_total_outputs: fs_block.block_total_outputs,
txids: vec![],
block_total_txs: fs_block.block_total_txs as usize,
})
}
}
impl BlockExtra {
pub fn version(&self) -> u8 {
self.version
}
pub fn block(&self) -> &Block {
self.block
.get_or_init(|| Block::consensus_decode(&mut &self.block_bytes[..]).unwrap())
}
pub fn block_bytes(&self) -> &[u8] {
&self.block_bytes
}
pub fn block_hash(&self) -> BlockHash {
self.block_hash
}
pub fn size(&self) -> u32 {
self.size
}
pub fn next(&self) -> &Vec<BlockHash> {
&self.next
}
pub fn height(&self) -> u32 {
self.height
}
pub fn outpoint_values(&self) -> &HashMap<OutPoint, TxOut> {
self.outpoint_values.get_or_init(|| {
self.outpoint_values_vec
.iter()
.cloned()
.collect::<HashMap<_, _>>()
})
}
pub fn block_total_inputs(&self) -> usize {
self.block_total_inputs as usize
}
pub fn block_total_outputs(&self) -> usize {
self.block_total_outputs as usize
}
pub fn txids(&self) -> &Vec<Txid> {
&self.txids
}
pub fn average_fee(&self) -> Option<f64> {
Some(self.fee()? as f64 / self.block_total_txs as f64)
}
pub fn fee(&self) -> Option<u64> {
let mut total = 0u64;
for tx in self.block().txdata.iter() {
total += self.tx_fee(tx)?;
}
Some(total)
}
pub fn tx_fee(&self, tx: &Transaction) -> Option<u64> {
let output_total: u64 = tx.output.iter().map(|el| el.value.to_sat()).sum();
let mut input_total = 0u64;
for input in tx.input.iter() {
input_total += self
.outpoint_values()
.get(&input.previous_output)?
.value
.to_sat();
}
Some(input_total - output_total)
}
pub fn base_reward(&self) -> u64 {
let initial = 50 * 100_000_000u64;
let division = self.height as u64 / 210_000u64;
initial >> division
}
pub fn iter_tx(&self) -> impl Iterator<Item = (&Txid, &Transaction)> {
self.txids.iter().zip(self.block().txdata.iter())
}
}
impl Encodable for BlockExtra {
fn consensus_encode<W: bitcoin::io::Write + ?Sized>(
&self,
writer: &mut W,
) -> Result<usize, bitcoin::io::Error> {
let mut written = 0;
written += self.version.consensus_encode(writer)?;
if self.version == 1 {
written += self.size.consensus_encode(writer)?;
}
writer.write_all(&self.block_bytes)?;
written += self.block_bytes.len();
written += self.block_hash.consensus_encode(writer)?;
if self.version == 0 {
written += self.size.consensus_encode(writer)?;
}
written += self.next.consensus_encode(writer)?;
written += self.height.consensus_encode(writer)?;
written += (self.outpoint_values_vec.len() as u32).consensus_encode(writer)?;
for (out_point, tx_out) in self.outpoint_values_vec.iter() {
written += out_point.consensus_encode(writer)?;
written += tx_out.consensus_encode(writer)?;
}
written += self.block_total_inputs.consensus_encode(writer)?;
written += self.block_total_outputs.consensus_encode(writer)?;
written += (self.txids.len() as u32).consensus_encode(writer)?;
for txid in self.txids.iter() {
written += txid.consensus_encode(writer)?;
}
Ok(written)
}
}
impl Decodable for BlockExtra {
fn consensus_decode<D: bitcoin::io::Read + ?Sized>(d: &mut D) -> Result<Self, encode::Error> {
let version = Decodable::consensus_decode(d)?;
let (size, block_bytes, block_hash) = match version {
0 => {
let block = Block::consensus_decode(d)?;
let block_bytes = serialize(&block);
let block_hash = Decodable::consensus_decode(d)?;
let size = Decodable::consensus_decode(d)?;
(size, block_bytes, block_hash)
}
1 => {
let size = Decodable::consensus_decode(d)?;
let mut block_bytes = vec![0u8; size as usize];
d.read_exact(&mut block_bytes)?;
let block_hash = Decodable::consensus_decode(d)?;
(size, block_bytes, block_hash)
}
_ => {
return Err(encode::Error::ParseFailed(
"Only version 0 and 1 are supported",
));
}
};
let mut b = BlockExtra {
version,
block_bytes,
block: OnceLock::new(),
block_hash,
size,
next: Decodable::consensus_decode(d)?,
height: Decodable::consensus_decode(d)?,
outpoint_values: OnceLock::new(),
outpoint_values_vec: {
let len = u32::consensus_decode(d)?;
let mut m = Vec::with_capacity(len as usize);
for _ in 0..len {
m.push((
Decodable::consensus_decode(d)?,
Decodable::consensus_decode(d)?,
));
}
m
},
block_total_inputs: Decodable::consensus_decode(d)?,
block_total_outputs: Decodable::consensus_decode(d)?,
txids: {
let len = u32::consensus_decode(d)?;
let mut v = Vec::with_capacity(len as usize);
for _ in 0..len {
v.push(Decodable::consensus_decode(d)?);
}
v
},
block_total_txs: 0, };
b.block_total_txs = b.txids.len();
Ok(b)
}
}
#[cfg(test)]
pub mod test {
use crate::bitcoin::consensus::serialize;
use crate::bitcoin::{Block, OutPoint, TxOut};
use crate::BlockExtra;
use bitcoin::block::{Header, Version};
use bitcoin::consensus::encode::serialize_hex;
use bitcoin::consensus::{deserialize, Decodable};
use bitcoin::hash_types::TxMerkleNode;
use bitcoin::hashes::Hash;
use bitcoin::{BlockHash, CompactTarget};
use std::sync::OnceLock;
#[test]
fn block_extra_round_trip() {
let be = block_extra();
let ser = serialize(&be);
let deser = deserialize(&ser).unwrap();
assert_eq!(be, deser);
let mut be1 = be;
be1.version = 1;
let ser = serialize(&be1);
let deser = deserialize(&ser).unwrap();
assert_eq!(be1, deser);
}
pub fn block_extra() -> BlockExtra {
let block = Block {
header: Header {
version: Version::from_consensus(0),
prev_blockhash: BlockHash::all_zeros(),
merkle_root: TxMerkleNode::all_zeros(),
time: 0,
bits: CompactTarget::from_consensus(0),
nonce: 0,
},
txdata: vec![],
};
let block_bytes = serialize(&block);
let size = block_bytes.len() as u32;
BlockExtra {
version: 0,
block_bytes,
block: OnceLock::new(),
block_hash: BlockHash::from_slice(&[
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
])
.unwrap(),
size,
next: vec![BlockHash::all_zeros()],
height: 0,
outpoint_values_vec: vec![(OutPoint::default(), TxOut::NULL)],
outpoint_values: OnceLock::new(),
block_total_inputs: 0,
block_total_outputs: 0,
block_total_txs: 0,
txids: vec![],
}
}
#[test]
fn test_block_reward() {
let mut be = block_extra();
assert_eq!(be.base_reward(), 50 * 100_000_000);
be.height = 209_999;
assert_eq!(be.base_reward(), 50 * 100_000_000);
be.height = 210_000;
assert_eq!(be.base_reward(), 25 * 100_000_000);
be.height = 420_000;
assert_eq!(be.base_reward(), 1_250_000_000);
be.height = 630_000;
assert_eq!(be.base_reward(), 625_000_000);
}
#[test]
fn test_hex() {
let be = block_extra();
let hex0 = serialize_hex(&be);
assert_eq!(hex0, "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000005100000001000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000");
let mut be1 = be;
be1.version = 1;
let hex1 = serialize_hex(&be1);
assert_eq!(hex1, "0151000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000");
assert_ne!(hex0, hex1);
}
#[test]
fn block_extra_unsupported_version() {
assert_eq!(
"parse failed: Only version 0 and 1 are supported",
BlockExtra::consensus_decode(&mut &[2u8][..])
.unwrap_err()
.to_string()
);
}
}