use crate::consensus::{self, calc_mwc_block_overage, calc_mwc_block_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, Utc};
use chrono::Duration;
use keychain::{self, BlindingFactor};
use std::convert::TryInto;
use util::from_hex;
use util::secp;
use util::secp::Secp256k1;
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum Error {
#[error("Block Input/output vs kernel sum mismatch")]
KernelSumMismatch,
#[error("Block Invalid total kernel sum")]
InvalidTotalKernelSum,
#[error("Block Invalid total kernel sum plus reward")]
CoinbaseSumMismatch,
#[error("Block total weight is too heavy")]
TooHeavy,
#[error("Block version {0:?} is invalid")]
InvalidBlockVersion(HeaderVersion),
#[error("Block time is invalid")]
InvalidBlockTime,
#[error("Invalid POW")]
InvalidPow,
#[error("Block lock_height {0} exceeding header height {1}")]
KernelLockHeight(u64, u64),
#[error("NRD kernels are not valid prior to HF3")]
NRDKernelPreHF3,
#[error("NRD kernels are not valid, disabled locally via 'feature flag'")]
NRDKernelNotEnabled,
#[error("Block Invalid Transaction, {0}")]
Transaction(transaction::Error),
#[error("Secp256k1 error, {0}")]
Secp(secp::Error),
#[error("keychain error, {0}")]
Keychain(keychain::Error),
#[error("Block Commits error, {0}")]
Committed(committed::Error),
#[error("Block cut-through error")]
CutThrough,
#[error("Block serialization error, {0}")]
Serialization(ser::Error),
#[error("Block Generic error, {0}")]
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)
}
}
#[derive(Debug)]
pub struct HeaderEntry {
pub 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_timestamp(0, 0).unwrap().to_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::DateTime::<Utc>::MAX_UTC.timestamp()
|| timestamp < chrono::DateTime::<Utc>::MIN_UTC.timestamp()
{
return Err(ser::Error::CorruptedData(format!(
"Incorrect timestamp {} at block header",
timestamp
)));
}
let ts = DateTime::from_timestamp(timestamp, 0);
if ts.is_none() {
return Err(ser::Error::CorruptedData("Timestamp is None".into()));
}
Ok(BlockHeader {
version,
height,
timestamp: ts.unwrap(),
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) -> Result<Vec<u8>, Error> {
let mut header_buf = vec![];
{
let mut writer = ser::BinWriter::default(&mut header_buf);
self.write_pre_pow(&mut writer)?;
self.pow.write_pre_pow(&mut writer)?;
writer.write_u64(self.pow.nonce)?;
}
Ok(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(format!(
"Unable to process {}, {}",
pre_pow, 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 {
(calc_mwc_block_reward(self.height) as i64)
.checked_neg()
.unwrap_or(0)
}
pub fn total_overage(&self, genesis_had_reward: bool) -> i64 {
let reward_count = self.height;
(calc_mwc_block_overage(reward_count, genesis_had_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)?;
if header.timestamp
> Utc::now() + Duration::seconds(12 * (consensus::BLOCK_TIME_SEC as i64))
{
let error_msg = format!(
"block header {} validation error: block time is more than 12 blocks in future",
header.hash()
);
error!("{}", error_msg);
return Err(ser::Error::CorruptedData(error_msg));
}
if !consensus::valid_header_version(header.height, header.version) {
return Err(ser::Error::InvalidBlockVersion(format!(
"Get header at height {} with version {}",
header.height, header.version.0
)));
}
if !header.pow.is_primary() && !header.pow.is_secondary() {
let error_msg = format!(
"block header {} validation error: invalid edge bits",
header.hash()
);
error!("{}", error_msg);
return Err(ser::Error::CorruptedData(error_msg));
}
if let Err(e) = verify_size(&header) {
let error_msg = format!(
"block header {} validation error: invalid POW: {}",
header.hash(),
e
);
error!("{}", error_msg);
return Err(ser::Error::CorruptedData(error_msg));
}
let global_weight =
Transaction::weight_for_size(0, header.output_mmr_count(), header.kernel_mmr_count());
if global_weight > global::max_block_weight() * (header.height + 1) {
return Err(ser::Error::CorruptedData(
"Tx global weight is exceed the limit".to_string(),
));
}
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),
secp: &Secp256k1,
) -> Result<Block, Error> {
let mut block = Block::from_reward(
prev,
txs,
reward_output.0,
reward_output.1,
difficulty,
secp,
)?;
{
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,
secp: &Secp256k1,
) -> Result<Block, Error> {
let agg_tx = transaction::aggregate(txs, secp)?
.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![],
secp,
)?;
let height = prev.height + 1;
let version = consensus::header_version(height);
let now = Utc::now().timestamp();
let ts = DateTime::from_timestamp(now, 0);
if ts.is_none() {
return Err(Error::Other("Converting Utc::now() into timestamp".into()));
}
let timestamp = ts.unwrap();
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(self.header.height)
}
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,
secp: &Secp256k1,
) -> 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],
secp,
)?
};
Ok(offset)
}
pub fn validate(
&self,
prev_kernel_offset: &BlindingFactor,
secp: &Secp256k1,
) -> Result<Commitment, Error> {
self.body.validate(Weighting::AsBlock, secp)?;
self.verify_kernel_lock_heights()?;
self.verify_nrd_kernels_for_header_version()?;
self.verify_coinbase(secp)?;
let (_utxo_sum, kernel_sum) = self.verify_kernel_sums(
self.header.overage(),
self.block_kernel_offset(prev_kernel_offset.clone(), secp)?,
secp,
)?;
Ok(kernel_sum)
}
pub fn verify_coinbase(&self, secp: &Secp256k1) -> 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 over_commit = secp.commit_value(reward(self.total_fees(), self.header.height))?;
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, self.header.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
}
}
#[derive(Debug)]
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(format!("Fail to validate Tx body, {}", e))
})?;
let block = Block {
header: header.into(),
body,
};
Ok(UntrustedBlock(block))
}
}