use crate::consensus::{self, reward, REWARD};
use crate::core::committed::{self, Committed};
use crate::core::compact_block::CompactBlock;
use crate::core::hash::{DefaultHashable, Hash, Hashed, ZERO_HASH};
use crate::core::{
pmmr, transaction, Commitment, Inputs, KernelFeatures, Output, Transaction, TransactionBody,
TxKernel, Weighting,
};
use crate::global;
use crate::pow::{verify_size, Difficulty, Proof, ProofOfWork};
use crate::ser::{
self, deserialize_default, serialize_default, PMMRable, Readable, Reader, Writeable, Writer,
};
use chrono::prelude::{DateTime, NaiveDateTime, Utc};
use chrono::Duration;
use keychain::{self, BlindingFactor};
use std::convert::TryInto;
use std::fmt;
use util::from_hex;
use util::{secp, static_secp_instance};
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
pub enum Error {
KernelSumMismatch,
InvalidTotalKernelSum,
CoinbaseSumMismatch,
TooHeavy,
InvalidBlockVersion(HeaderVersion),
InvalidBlockTime,
InvalidPow,
KernelLockHeight(u64),
NRDKernelPreHF3,
NRDKernelNotEnabled,
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")
}
}
#[derive(Debug)]
pub struct HeaderEntry {
hash: Hash,
timestamp: u64,
total_difficulty: Difficulty,
secondary_scaling: u32,
is_secondary: bool,
}
impl Readable for HeaderEntry {
fn read<R: Reader>(reader: &mut R) -> 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 Hashed for HeaderEntry {
fn hash(&self) -> Hash {
self.hash
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Serialize)]
pub struct HeaderVersion(pub u16);
impl From<HeaderVersion> for u16 {
fn from(v: HeaderVersion) -> u16 {
v.0
}
}
impl Writeable for HeaderVersion {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u16(self.0)
}
}
impl Readable for HeaderVersion {
fn read<R: Reader>(reader: &mut R) -> Result<HeaderVersion, ser::Error> {
let version = reader.read_u16()?;
Ok(HeaderVersion(version))
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct BlockHeader {
pub version: HeaderVersion,
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 DefaultHashable for BlockHeader {}
impl Default for BlockHeader {
fn default() -> BlockHeader {
BlockHeader {
version: HeaderVersion(1),
height: 0,
timestamp: DateTime::from_naive_utc_and_offset(
NaiveDateTime::from_timestamp_opt(0, 0).unwrap(),
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(),
}
}
fn elmt_size() -> Option<u16> {
const LEN: usize = Hash::LEN + 8 + Difficulty::LEN + 4 + 1;
Some(LEN.try_into().unwrap())
}
}
impl Writeable for BlockHeader {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
if !writer.serialization_mode().is_hash_mode() {
self.write_pre_pow(writer)?;
}
self.pow.write(writer)?;
Ok(())
}
}
fn read_block_header<R: Reader>(reader: &mut R) -> Result<BlockHeader, ser::Error> {
let version = HeaderVersion::read(reader)?;
let (height, timestamp) = ser_multiread!(reader, 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(reader)?;
if timestamp
> chrono::NaiveDate::MAX
.and_hms_opt(0, 0, 0)
.unwrap()
.timestamp()
|| timestamp
< chrono::NaiveDate::MIN
.and_hms_opt(0, 0, 0)
.unwrap()
.timestamp()
{
return Err(ser::Error::CorruptedData);
}
let ts = NaiveDateTime::from_timestamp_opt(timestamp, 0);
if ts.is_none() {
return Err(ser::Error::CorruptedData);
}
Ok(BlockHeader {
version,
height,
timestamp: DateTime::from_naive_utc_and_offset(ts.unwrap(), Utc),
prev_hash,
prev_root,
output_root,
range_proof_root,
kernel_root,
total_kernel_offset,
output_mmr_size,
kernel_mmr_size,
pow,
})
}
impl Readable for BlockHeader {
fn read<R: Reader>(reader: &mut R) -> Result<BlockHeader, ser::Error> {
read_block_header(reader)
}
}
impl BlockHeader {
pub fn write_pre_pow<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.version.write(writer)?;
ser_multiwrite!(
writer,
[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::default(&mut header_buf);
self.write_pre_pow(&mut writer).unwrap();
self.pow.write_pre_pow(&mut writer).unwrap();
writer.write_u64(self.pow.nonce).unwrap();
}
header_buf
}
pub fn from_pre_pow_and_proof(
pre_pow: String,
nonce: u64,
proof: Proof,
) -> Result<Self, Error> {
let mut header_bytes =
from_hex(&pre_pow).map_err(|e| Error::Serialization(ser::Error::HexError(e)))?;
serialize_default(&mut header_bytes, &nonce)?;
serialize_default(&mut header_bytes, &proof)?;
Ok(deserialize_default(&mut &header_bytes[..])?)
}
pub fn output_mmr_count(&self) -> u64 {
pmmr::n_leaves(self.output_mmr_size)
}
pub fn kernel_mmr_count(&self) -> u64 {
pmmr::n_leaves(self.kernel_mmr_size)
}
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.clone()
}
}
impl From<UntrustedBlockHeader> for BlockHeader {
fn from(header: UntrustedBlockHeader) -> Self {
header.0
}
}
#[derive(Debug)]
pub struct UntrustedBlockHeader(BlockHeader);
impl Readable for UntrustedBlockHeader {
fn read<R: Reader>(reader: &mut R) -> Result<UntrustedBlockHeader, ser::Error> {
let header = read_block_header(reader)?;
let ftl = global::get_future_time_limit();
if header.timestamp > Utc::now() + Duration::seconds(ftl as i64) {
error!(
"block header {} validation error: block time is more than {} seconds in the future",
header.hash(), ftl
);
return Err(ser::Error::CorruptedData);
}
if !consensus::valid_header_version(header.height, header.version) {
return Err(ser::Error::InvalidBlockVersion);
}
if !header.pow.is_primary() && !header.pow.is_secondary() {
error!(
"block header {} validation error: invalid edge bits",
header.hash()
);
return Err(ser::Error::CorruptedData);
}
if let Err(e) = verify_size(&header) {
error!(
"block header {} validation error: invalid POW: {}",
header.hash(),
e
);
return Err(ser::Error::CorruptedData);
}
let global_weight =
TransactionBody::weight_by_iok(0, header.output_mmr_count(), header.kernel_mmr_count());
if global_weight > global::max_block_weight() * (header.height + 1) {
return Err(ser::Error::CorruptedData);
}
Ok(UntrustedBlockHeader(header))
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Block {
pub header: BlockHeader,
pub body: TransactionBody,
}
impl Hashed for Block {
fn hash(&self) -> Hash {
self.header.hash()
}
}
impl Writeable for Block {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.header.write(writer)?;
if !writer.serialization_mode().is_hash_mode() {
self.body.write(writer)?;
}
Ok(())
}
}
impl Readable for Block {
fn read<R: Reader>(reader: &mut R) -> Result<Block, ser::Error> {
let header = BlockHeader::read(reader)?;
let body = TransactionBody::read(reader)?;
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: &[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: &[Transaction]) -> Result<Block, Error> {
trace!("block: hydrate_from: {}, {} txs", cb.hash(), txs.len(),);
let header = cb.header.clone();
let mut inputs = vec![];
let mut outputs = vec![];
let mut kernels = vec![];
for tx in txs {
let tx_inputs: Vec<_> = tx.inputs().into();
inputs.extend_from_slice(tx_inputs.as_slice());
outputs.extend_from_slice(tx.outputs());
kernels.extend_from_slice(tx.kernels());
}
let (inputs, outputs, _, _) = transaction::cut_through(&mut inputs, &mut outputs)?;
let mut outputs = outputs.to_vec();
let mut kernels = kernels.to_vec();
outputs.extend_from_slice(cb.out_full());
kernels.extend_from_slice(cb.kern_full());
let body = TransactionBody::init(inputs.into(), &outputs, &kernels, false)?;
Ok(Block { header, body })
}
pub fn with_header(header: BlockHeader) -> Block {
Block {
header,
..Default::default()
}
}
pub fn from_reward(
prev: &BlockHeader,
txs: &[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.clone(), prev.total_kernel_offset.clone()],
vec![],
)?;
let height = prev.height + 1;
let version = consensus::header_version(height);
let now = Utc::now().timestamp();
let ts = NaiveDateTime::from_timestamp_opt(now, 0);
if ts.is_none() {
return Err(Error::Other("Converting Utc::now() into timestamp".into()));
}
let timestamp = DateTime::from_naive_utc_and_offset(ts.unwrap(), Utc);
let block = Block {
header: BlockHeader {
version,
height,
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(),
};
Ok(block)
}
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) -> Inputs {
self.body.inputs()
}
pub fn outputs(&self) -> &[Output] {
&self.body.outputs()
}
pub fn kernels(&self) -> &[TxKernel] {
&self.body.kernels()
}
pub fn total_fees(&self) -> u64 {
self.body.fee()
}
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) -> Result<(), Error> {
self.body.validate(Weighting::AsBlock)?;
self.verify_kernel_lock_heights()?;
self.verify_nrd_kernels_for_header_version()?;
self.verify_coinbase()?;
self.verify_kernel_sums(
self.header.overage(),
self.block_kernel_offset(prev_kernel_offset.clone())?,
)?;
Ok(())
}
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.kernels() {
if let KernelFeatures::HeightLocked { lock_height, .. } = k.features {
if lock_height > self.header.height {
return Err(Error::KernelLockHeight(lock_height));
}
}
}
Ok(())
}
fn verify_nrd_kernels_for_header_version(&self) -> Result<(), Error> {
if self.kernels().iter().any(|k| k.is_nrd()) {
if !global::is_nrd_enabled() {
return Err(Error::NRDKernelNotEnabled);
}
if self.header.version < HeaderVersion(4) {
return Err(Error::NRDKernelPreHF3);
}
}
Ok(())
}
}
impl From<UntrustedBlock> for Block {
fn from(block: UntrustedBlock) -> Self {
block.0
}
}
pub struct UntrustedBlock(Block);
impl Readable for UntrustedBlock {
fn read<R: Reader>(reader: &mut R) -> Result<UntrustedBlock, ser::Error> {
let header = UntrustedBlockHeader::read(reader)?;
let body = TransactionBody::read(reader)?;
body.validate_read(Weighting::AsBlock).map_err(|e| {
error!("read validation error: {}", e);
ser::Error::CorruptedData
})?;
let block = Block {
header: header.into(),
body,
};
Ok(UntrustedBlock(block))
}
}