use crate::util::RwLock;
use chrono::naive::{MAX_DATE, MIN_DATE};
use chrono::prelude::{DateTime, NaiveDateTime, Utc};
use std::collections::HashSet;
use std::fmt;
use std::iter::FromIterator;
use std::sync::Arc;
use crate::consensus::{reward, REWARD};
use crate::core::committed::{self, Committed};
use crate::core::compact_block::{CompactBlock, CompactBlockBody};
use crate::core::hash::{Hash, Hashed, ZERO_HASH};
use crate::core::verifier_cache::VerifierCache;
use crate::core::{
transaction, Commitment, Input, Output, Transaction, TransactionBody, TxKernel, Weighting,
};
use crate::global;
use crate::keychain::{self, BlindingFactor};
use crate::pow::{Difficulty, Proof, ProofOfWork};
use crate::ser::{self, FixedLength, PMMRable, Readable, Reader, Writeable, Writer};
use crate::util::{secp, static_secp_instance};
#[derive(Debug, Clone, Eq, PartialEq, Fail)]
pub enum Error {
KernelSumMismatch,
InvalidTotalKernelSum,
CoinbaseSumMismatch,
TooHeavy,
WeightExceeded,
KernelLockHeight(u64),
Transaction(transaction::Error),
Secp(secp::Error),
Keychain(keychain::Error),
MerkleProof,
Committed(committed::Error),
CutThrough,
Serialization(ser::Error),
Other(String),
}
impl From<committed::Error> for Error {
fn from(e: committed::Error) -> Error {
Error::Committed(e)
}
}
impl From<transaction::Error> for Error {
fn from(e: transaction::Error) -> Error {
Error::Transaction(e)
}
}
impl From<ser::Error> for Error {
fn from(e: ser::Error) -> Error {
Error::Serialization(e)
}
}
impl From<secp::Error> for Error {
fn from(e: secp::Error) -> Error {
Error::Secp(e)
}
}
impl From<keychain::Error> for Error {
fn from(e: keychain::Error) -> Error {
Error::Keychain(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Block Error (display needs implementation")
}
}
pub struct HeaderEntry {
hash: Hash,
timestamp: u64,
total_difficulty: Difficulty,
secondary_scaling: u32,
is_secondary: bool,
}
impl Readable for HeaderEntry {
fn read(reader: &mut Reader) -> Result<HeaderEntry, ser::Error> {
let hash = Hash::read(reader)?;
let timestamp = reader.read_u64()?;
let total_difficulty = Difficulty::read(reader)?;
let secondary_scaling = reader.read_u32()?;
let is_secondary = reader.read_u8()? != 0;
Ok(HeaderEntry {
hash,
timestamp,
total_difficulty,
secondary_scaling,
is_secondary,
})
}
}
impl Writeable for HeaderEntry {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.hash.write(writer)?;
writer.write_u64(self.timestamp)?;
self.total_difficulty.write(writer)?;
writer.write_u32(self.secondary_scaling)?;
if self.is_secondary {
writer.write_u8(1)?;
} else {
writer.write_u8(0)?;
}
Ok(())
}
}
impl FixedLength for HeaderEntry {
const LEN: usize = Hash::LEN + 8 + Difficulty::LEN + 4 + 1;
}
impl HeaderEntry {
pub fn hash(&self) -> Hash {
self.hash
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BlockHeader {
pub version: u16,
pub height: u64,
pub prev_hash: Hash,
pub prev_root: Hash,
pub timestamp: DateTime<Utc>,
pub output_root: Hash,
pub range_proof_root: Hash,
pub kernel_root: Hash,
pub total_kernel_offset: BlindingFactor,
pub output_mmr_size: u64,
pub kernel_mmr_size: u64,
pub pow: ProofOfWork,
}
impl Default for BlockHeader {
fn default() -> BlockHeader {
BlockHeader {
version: 1,
height: 0,
timestamp: DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc),
prev_hash: ZERO_HASH,
prev_root: ZERO_HASH,
output_root: ZERO_HASH,
range_proof_root: ZERO_HASH,
kernel_root: ZERO_HASH,
total_kernel_offset: BlindingFactor::zero(),
output_mmr_size: 0,
kernel_mmr_size: 0,
pow: ProofOfWork::default(),
}
}
}
impl PMMRable for BlockHeader {
type E = HeaderEntry;
fn as_elmt(&self) -> Self::E {
HeaderEntry {
hash: self.hash(),
timestamp: self.timestamp.timestamp() as u64,
total_difficulty: self.total_difficulty(),
secondary_scaling: self.pow.secondary_scaling,
is_secondary: self.pow.is_secondary(),
}
}
}
impl Writeable for BlockHeader {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
if writer.serialization_mode() != ser::SerializationMode::Hash {
self.write_pre_pow(writer)?;
}
self.pow.write(self.version, writer)?;
Ok(())
}
}
impl Readable for BlockHeader {
fn read(reader: &mut dyn Reader) -> Result<BlockHeader, ser::Error> {
let (version, height, timestamp) = ser_multiread!(reader, read_u16, read_u64, read_i64);
let prev_hash = Hash::read(reader)?;
let prev_root = Hash::read(reader)?;
let output_root = Hash::read(reader)?;
let range_proof_root = Hash::read(reader)?;
let kernel_root = Hash::read(reader)?;
let total_kernel_offset = BlindingFactor::read(reader)?;
let (output_mmr_size, kernel_mmr_size) = ser_multiread!(reader, read_u64, read_u64);
let pow = ProofOfWork::read(version, reader)?;
if timestamp > MAX_DATE.and_hms(0, 0, 0).timestamp()
|| timestamp < MIN_DATE.and_hms(0, 0, 0).timestamp()
{
return Err(ser::Error::CorruptedData);
}
Ok(BlockHeader {
version,
height,
timestamp: DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc),
prev_hash,
prev_root,
output_root,
range_proof_root,
kernel_root,
total_kernel_offset,
output_mmr_size,
kernel_mmr_size,
pow,
})
}
}
impl BlockHeader {
pub fn write_pre_pow<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
ser_multiwrite!(
writer,
[write_u16, self.version],
[write_u64, self.height],
[write_i64, self.timestamp.timestamp()],
[write_fixed_bytes, &self.prev_hash],
[write_fixed_bytes, &self.prev_root],
[write_fixed_bytes, &self.output_root],
[write_fixed_bytes, &self.range_proof_root],
[write_fixed_bytes, &self.kernel_root],
[write_fixed_bytes, &self.total_kernel_offset],
[write_u64, self.output_mmr_size],
[write_u64, self.kernel_mmr_size]
);
Ok(())
}
pub fn pre_pow(&self) -> Vec<u8> {
let mut header_buf = vec![];
{
let mut writer = ser::BinWriter::new(&mut header_buf);
self.write_pre_pow(&mut writer).unwrap();
self.pow.write_pre_pow(self.version, &mut writer).unwrap();
writer.write_u64(self.pow.nonce).unwrap();
}
header_buf
}
pub fn total_difficulty(&self) -> Difficulty {
self.pow.total_difficulty
}
pub fn overage(&self) -> i64 {
(REWARD as i64).checked_neg().unwrap_or(0)
}
pub fn total_overage(&self, genesis_had_reward: bool) -> i64 {
let mut reward_count = self.height;
if genesis_had_reward {
reward_count += 1;
}
((reward_count * REWARD) as i64).checked_neg().unwrap_or(0)
}
pub fn total_kernel_offset(&self) -> BlindingFactor {
self.total_kernel_offset
}
}
#[derive(Debug, Clone)]
pub struct Block {
pub header: BlockHeader,
body: TransactionBody,
}
impl Writeable for Block {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.header.write(writer)?;
if writer.serialization_mode() != ser::SerializationMode::Hash {
self.body.write(writer)?;
}
Ok(())
}
}
impl Readable for Block {
fn read(reader: &mut dyn Reader) -> Result<Block, ser::Error> {
let header = BlockHeader::read(reader)?;
let body = TransactionBody::read(reader)?;
body.validate_read(Weighting::AsBlock)
.map_err(|_| ser::Error::CorruptedData)?;
Ok(Block { header, body })
}
}
impl Committed for Block {
fn inputs_committed(&self) -> Vec<Commitment> {
self.body.inputs_committed()
}
fn outputs_committed(&self) -> Vec<Commitment> {
self.body.outputs_committed()
}
fn kernels_committed(&self) -> Vec<Commitment> {
self.body.kernels_committed()
}
}
impl Default for Block {
fn default() -> Block {
Block {
header: Default::default(),
body: Default::default(),
}
}
}
impl Block {
#[warn(clippy::new_ret_no_self)]
pub fn new(
prev: &BlockHeader,
txs: Vec<Transaction>,
difficulty: Difficulty,
reward_output: (Output, TxKernel),
) -> Result<Block, Error> {
let mut block =
Block::from_reward(prev, txs, reward_output.0, reward_output.1, difficulty)?;
{
let proof_size = global::proofsize();
block.header.pow.proof = Proof::random(proof_size);
}
Ok(block)
}
pub fn hydrate_from(cb: CompactBlock, txs: Vec<Transaction>) -> Result<Block, Error> {
trace!("block: hydrate_from: {}, {} txs", cb.hash(), txs.len(),);
let header = cb.header.clone();
let mut all_inputs = HashSet::new();
let mut all_outputs = HashSet::new();
let mut all_kernels = HashSet::new();
for tx in txs {
let tb: TransactionBody = tx.into();
all_inputs.extend(tb.inputs);
all_outputs.extend(tb.outputs);
all_kernels.extend(tb.kernels);
}
{
let body: CompactBlockBody = cb.into();
all_outputs.extend(body.out_full);
all_kernels.extend(body.kern_full);
}
let all_inputs = Vec::from_iter(all_inputs);
let all_outputs = Vec::from_iter(all_outputs);
let all_kernels = Vec::from_iter(all_kernels);
let body = TransactionBody::init(all_inputs, all_outputs, all_kernels, false)?;
Block { header, body }.cut_through()
}
pub fn with_header(header: BlockHeader) -> Block {
Block {
header,
..Default::default()
}
}
pub fn from_reward(
prev: &BlockHeader,
txs: Vec<Transaction>,
reward_out: Output,
reward_kern: TxKernel,
difficulty: Difficulty,
) -> Result<Block, Error> {
let agg_tx = transaction::aggregate(txs)?
.with_output(reward_out)
.with_kernel(reward_kern);
let total_kernel_offset =
committed::sum_kernel_offsets(vec![agg_tx.offset, prev.total_kernel_offset], vec![])?;
let now = Utc::now().timestamp();
let timestamp = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(now, 0), Utc);
Block {
header: BlockHeader {
height: prev.height + 1,
timestamp,
prev_hash: prev.hash(),
total_kernel_offset,
pow: ProofOfWork {
total_difficulty: difficulty + prev.pow.total_difficulty,
..Default::default()
},
..Default::default()
},
body: agg_tx.into(),
}
.cut_through()
}
pub fn with_reward(mut self, reward_out: Output, reward_kern: TxKernel) -> Block {
self.body.outputs = vec![reward_out];
self.body.kernels = vec![reward_kern];
self
}
pub fn inputs(&self) -> &Vec<Input> {
&self.body.inputs
}
pub fn inputs_mut(&mut self) -> &mut Vec<Input> {
&mut self.body.inputs
}
pub fn outputs(&self) -> &Vec<Output> {
&self.body.outputs
}
pub fn outputs_mut(&mut self) -> &mut Vec<Output> {
&mut self.body.outputs
}
pub fn kernels(&self) -> &Vec<TxKernel> {
&self.body.kernels
}
pub fn kernels_mut(&mut self) -> &mut Vec<TxKernel> {
&mut self.body.kernels
}
pub fn hash(&self) -> Hash {
self.header.hash()
}
pub fn total_fees(&self) -> u64 {
self.body
.kernels
.iter()
.fold(0, |acc, ref x| acc.saturating_add(x.fee))
}
pub fn cut_through(self) -> Result<Block, Error> {
let mut inputs = self.inputs().clone();
let mut outputs = self.outputs().clone();
transaction::cut_through(&mut inputs, &mut outputs)?;
let kernels = self.kernels().clone();
let body = TransactionBody::init(inputs, outputs, kernels, false)?;
Ok(Block {
header: self.header,
body,
})
}
pub fn validate_read(&self) -> Result<(), Error> {
self.body.validate_read(Weighting::AsBlock)?;
self.verify_kernel_lock_heights()?;
Ok(())
}
fn block_kernel_offset(
&self,
prev_kernel_offset: BlindingFactor,
) -> Result<BlindingFactor, Error> {
let offset = if self.header.total_kernel_offset() == prev_kernel_offset {
BlindingFactor::zero()
} else {
committed::sum_kernel_offsets(
vec![self.header.total_kernel_offset()],
vec![prev_kernel_offset],
)?
};
Ok(offset)
}
pub fn validate(
&self,
prev_kernel_offset: &BlindingFactor,
verifier: Arc<RwLock<dyn VerifierCache>>,
) -> Result<Commitment, Error> {
self.body.validate(Weighting::AsBlock, verifier)?;
self.verify_kernel_lock_heights()?;
self.verify_coinbase()?;
let (_utxo_sum, kernel_sum) = self.verify_kernel_sums(
self.header.overage(),
self.block_kernel_offset(*prev_kernel_offset)?,
)?;
Ok(kernel_sum)
}
pub fn verify_coinbase(&self) -> Result<(), Error> {
let cb_outs = self
.body
.outputs
.iter()
.filter(|out| out.is_coinbase())
.collect::<Vec<&Output>>();
let cb_kerns = self
.body
.kernels
.iter()
.filter(|kernel| kernel.is_coinbase())
.collect::<Vec<&TxKernel>>();
{
let secp = static_secp_instance();
let secp = secp.lock();
let over_commit = secp.commit_value(reward(self.total_fees()))?;
let out_adjust_sum =
secp.commit_sum(map_vec!(cb_outs, |x| x.commitment()), vec![over_commit])?;
let kerns_sum = secp.commit_sum(cb_kerns.iter().map(|x| x.excess).collect(), vec![])?;
if kerns_sum != out_adjust_sum {
return Err(Error::CoinbaseSumMismatch);
}
}
Ok(())
}
fn verify_kernel_lock_heights(&self) -> Result<(), Error> {
for k in &self.body.kernels {
if k.lock_height > self.header.height {
return Err(Error::KernelLockHeight(k.lock_height));
}
}
Ok(())
}
}