Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion common/src/chain/block/block_v1.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::chain::block::ConsensusData;
use crate::chain::transaction::Transaction;
use crate::primitives::consensus_data::ConsensusData;
use crate::primitives::{id, Id, Idable, H256};
use parity_scale_codec_derive::{Decode as DecodeDer, Encode as EncodeDer};

Expand Down Expand Up @@ -38,6 +38,10 @@ impl BlockV1 {
self.header.consensus_data = consensus_data;
}

pub fn consensus_data(&self) -> &ConsensusData {
&self.header.consensus_data
}

Comment thread
b-yap marked this conversation as resolved.
Outdated
pub fn get_block_time(&self) -> u32 {
self.header.time
}
Expand Down
44 changes: 44 additions & 0 deletions common/src/chain/block/consensus_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::chain::TxOutput;
use crate::primitives::Compact;
use parity_scale_codec::{Decode, Encode};

#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
pub enum ConsensusData {
#[codec(index = 0)]
None,
#[codec(index = 1)]
PoW(PoWData),
}

#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
pub struct PoWData {
bits: Compact,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this would deviate from Bitcoin but the difficulty is determined by the time difference of the last two blocks that are at a height that is a multiple of 2016, so this field is not needed. Even if the field is included, we need to check it has the correct value if it's relied upon for the difficulty check. Have you thought about getting rid of it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bits field is being used in the ff. ways:

  1. When no_retargeting field in the config is set to true. That only happens for Regtest.
    ChainType::Regtest => true,
  2. if it's not due for retargeting yet, and allow_min_dfficulty_blocks is set to false, which is the Mainnet and Signet:
    ChainType::Mainnet | ChainType::Signet => false,

Let's say the current block height is 8067, and it's not due for retarget (since retarget has happened at 8064).
I am assuming that the difficulty is retrieved from the previous block, at height 8066. No calculation needed.
If we remove the bits field, we'll have to keep calculating the time diff of the blocks of height 8064 and 6048.

As for checking whether its value is correct, Did you mean the check_proof_of_work()? Or do you mean checking whether the previous block's bits field is a correct value?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for checking whether its value is correct, Did you mean the check_proof_of_work()? Or do you mean checking whether the previous block's bits field is a correct value?

What I mean is that the value of the bits field cannot be trusted to contain the difficulty anyway. I see the get_work_required method here but I can't see it being used anywhere. I.e. at any point, the following must pass:

assert_eq!(calculate_difficulty_from_the_scratch(...).try_into(), Just(this_block.bits))

Blocks that do not satisfy that condition must be rejected, since the check_proof_of_work function relies on the bits field to contain accurate difficulty.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_work_required()(previously check_for_work_required()) will be used in the block production.
This was the earliest implementation : ed8db7d#diff-9a00856cffad13af6904b1cb9d4b5a4fd9e0591f10a35a67ddd805141890eeb6R91

But I removed it in this PR, because it's meant for the block production PR (which is currently closed, until Anton's or this is pushed).

Did you mean because the bits is a u32 representation, while the difficulty is 256, that's why the bits field can't be trusted? Or because the previous block can't be trusted, since the bits field can easily be just any number and not from a calculated value during the generation of the 2016th multiple block?

I haven't checked in bitcoin where that is, but we can actually add that kind of validation.

I somehow do agree with you; removing the bits field is not so bad, since we can always calculate for the difficulty. Your get_block_id_by_height function will be useful (Though I think I have to access it through the BlockIndex).

For now I've been following how Bitcoin does it. But also I don't have problems diverging from Bitcoin.
Although I don't know what Sam @TheQuantumPhysicist thinks, we can wait for his opinion.

nonce: u128,
/// contains the block reward
outputs: Vec<TxOutput>,
}

impl PoWData {
pub fn new(bits: Compact, nonce: u128, outputs: Vec<TxOutput>) -> Self {
PoWData {
bits,
nonce,
outputs,
}
}
pub fn bits(&self) -> Compact {
self.bits
}

pub fn nonce(&self) -> u128 {
self.nonce
}

pub fn outputs(&self) -> &[TxOutput] {
&self.outputs
}

pub fn update_nonce(&mut self, nonce: u128) {
self.nonce = nonce;
}
}
9 changes: 8 additions & 1 deletion common/src/chain/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ use crate::primitives::merkle::MerkleTreeFormError;
use crate::primitives::H256;
use crate::primitives::{Id, Idable};
mod block_v1;
pub mod consensus_data;

use crate::primitives::consensus_data::ConsensusData;
use block_v1::BlockHeader;
use block_v1::BlockV1;
pub use consensus_data::ConsensusData;
use parity_scale_codec::{Decode, Encode};

pub fn calculate_tx_merkle_root(
Expand Down Expand Up @@ -113,6 +114,12 @@ impl Block {
}
}

pub fn consensus_data(&self) -> &ConsensusData {
match self {
Block::V1(blk) => blk.consensus_data()
}
}

pub fn get_merkle_root(&self) -> H256 {
match &self {
Block::V1(blk) => blk.get_tx_merkle_root(),
Expand Down
2 changes: 1 addition & 1 deletion common/src/chain/config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::address::Address;
use crate::chain::block::Block;
use crate::chain::block::ConsensusData;
use crate::chain::transaction::Transaction;
use crate::chain::upgrades::NetUpgrades;
use crate::chain::{PoWChainConfig, UpgradeVersion};
use crate::primitives::consensus_data::ConsensusData;
use crate::primitives::id::{Id, H256};
use crate::primitives::{version::SemVer, BlockHeight};
use std::collections::BTreeMap;
Expand Down
23 changes: 23 additions & 0 deletions common/src/chain/pow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ const fn limit(chain_type: ChainType) -> Uint256 {

#[cfg(test)]
mod tests {
use crate::chain::block::ConsensusData;
use crate::chain::config::{create_mainnet, ChainType};
use crate::chain::pow::{allow_min_difficulty_blocks, limit, no_retargeting};
use crate::Uint256;

#[test]
fn check_mainnet_powconfig() {
Expand All @@ -113,5 +115,26 @@ mod tests {

assert!(!mainnet_cfg.no_retargeting());
assert!(!mainnet_cfg.allow_min_difficulty_blocks());

assert_eq!(
mainnet_cfg.target_timespan().as_secs() % mainnet_cfg.target_spacing().as_secs(),
0
);

assert_eq!(&ConsensusData::None, cfg.genesis_block().consensus_data());

if !mainnet_cfg.no_retargeting() {
let target_max = Uint256([
0xFFFFFFFFFFFFFFFF,
0xFFFFFFFFFFFFFFFF,
0xFFFFFFFFFFFFFFFF,
0xFFFFFFFFFFFFFFFF,
]);

let target_max = target_max
/ Uint256::from_u64(mainnet_cfg.target_timespan().as_secs() * 4)
.expect("should be okay");
assert!(mainnet_cfg.limit() < target_max);
}
}
}
1 change: 1 addition & 0 deletions common/src/primitives/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,6 @@ mod tests {

err_conversion(0x04923456);
err_conversion(0x01fedcba);
err_conversion(!0x00800000); // overflow
}
}
16 changes: 0 additions & 16 deletions common/src/primitives/consensus_data.rs

This file was deleted.

1 change: 0 additions & 1 deletion common/src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ pub mod id;
pub mod merkle;
pub mod time;

pub mod consensus_data;
pub mod version;

pub use amount::Amount;
Expand Down
1 change: 1 addition & 0 deletions consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ license = "MIT"
[dependencies]
blockchain-storage = { path = '../blockchain_storage'}
common = { path = '../common'}
num = "0.4.0"
rand = "0.8.4"
logging = { path = '../logging'}
4 changes: 4 additions & 0 deletions consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
// use common::chain::block::Block;

mod orphan_blocks;
mod pow;

pub use pow::{work::check_proof_of_work, Error as PoWError};

// use orphan_blocks::OrphanBlocks;

// struct Consensus<S: BlockchainStorage> {
Expand Down
2 changes: 1 addition & 1 deletion consensus/src/orphan_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ mod tests {

mod helpers {
use super::*;
use common::chain::block::ConsensusData;
use common::chain::transaction::Transaction;
use common::primitives::consensus_data::ConsensusData;
use rand::Rng;

pub fn gen_random_blocks(count: u32) -> Vec<Block> {
Expand Down
129 changes: 129 additions & 0 deletions consensus/src/pow/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use crate::pow::temp::BlockIndex;
use crate::pow::Error;
use common::primitives::{BlockHeight, Compact};
use common::Uint256;

/// checks if retargeting is due for the provided block_height
pub fn due_for_retarget(difficulty_adjustment_interval: u64, block_height: BlockHeight) -> bool {
block_height.inner() % difficulty_adjustment_interval == 0
}

/// The block time of the first block, based on the difficulty adjustment interval,
/// where first block = height of given block - difficulty adjustment interval - 1 (off by one)
pub fn get_starting_block_time(
difficulty_adjustment_interval: u64,
block_index: &BlockIndex,
) -> u32 {
let retarget_height = {
// Go back by what we want to be 14 days worth of blocks (the last 2015 blocks)
let old_block_height = block_index.height.inner() - (difficulty_adjustment_interval - 1);
BlockHeight::new(old_block_height)
};

let retarget_block_index = block_index.get_ancestor(retarget_height);

retarget_block_index.get_block_time()
}

/// Returns a calculated new target as Compact datatype.
/// See Bitcoin's Protocol rules of [Difficulty change](https://en.bitcoin.it/wiki/Protocol_rules)
/// # Arguments
/// `actual_timespan_of_last_interval` - the actual timespan or the difference between the current block
/// and the 2016th block before it. This should be in seconds.
/// `target_timespan` - found in the `PoWChainConfig`. This should be in seconds.
/// `old_target` - Coming from the last block, this is the `bits` of the PoWData.
/// `difficulty_limit` - found in the PoWChainConfig, as `limit`
pub fn calculate_new_target(
actual_timespan_of_last_interval: u64,
target_timespan: u64,
old_target: Compact,
difficulty_limit: Uint256,
) -> Result<Compact, Error> {
let actual_timespan = Uint256::from_u64(actual_timespan_of_last_interval).ok_or_else(|| {
Error::ConversionError(format!(
"conversion of actual timespan {:?} to Uint256 type failed.",
actual_timespan_of_last_interval
))
})?;

let target_timespan = Uint256::from_u64(target_timespan).ok_or_else(|| {
Error::ConversionError(format!(
"conversion of target timespan {:?} to Uint256 type failed.",
target_timespan
))
})?;

let old_target = Uint256::try_from(old_target).map_err(|e| {
Error::ConversionError(format!(
"conversion of bits {:?} to Uint256 type: {:?}",
old_target, e
))
})?;

// new target is computed by multiplying the old target by ratio of the actual timespan / target timespan.
// see Bitcoin's Protocol rules of Difficulty change: https://en.bitcoin.it/wiki/Protocol_rules
let mut new_target = old_target * actual_timespan;
new_target = new_target / target_timespan;

new_target = if new_target > difficulty_limit {
difficulty_limit
} else {
new_target
};

Ok(Compact::from(new_target))
}

pub mod special_rules {
use super::*;

/// Checks if it took > 20 minutes to find a block
pub fn block_production_stalled(
target_spacing_in_secs: u64,
new_block_time: u32,
prev_block_time: u32,
) -> bool {
new_block_time as u64 > (prev_block_time as u64 + (target_spacing_in_secs * 2))
}

pub fn last_non_special_min_difficulty(_block_index: &BlockIndex) -> Compact {
// Return the last non-special-min-difficulty-rules-block
// let mut ctr_index = block_index.clone();
// loop {
// let block_bits = ctr_index.data.bits();
// if ctr_index.height == BlockHeight::zero() {
// return block_bits;
// }
//
// if due_for_retarget(pow_cfg, ctr_index.height) && block_bits == pow_cfg.limit() {
// match ctr_index.prev() {
// None => { return block_bits; }
// Some(id) => { }
// }
// }
// }
todo!()
}
}

#[cfg(test)]
mod tests {
use crate::pow::helpers::due_for_retarget;
use common::primitives::BlockHeight;

#[test]
fn due_for_retarget_test() {
let interval = 2016;
let test = |h: BlockHeight| due_for_retarget(interval, h);

assert!(test(BlockHeight::zero()));
assert!(!test(BlockHeight::one()));
assert!(test(BlockHeight::new(interval)));
assert!(!test(BlockHeight::new(interval + 1)));
assert!(test(BlockHeight::new(interval * 2)));
assert!(test(BlockHeight::new(interval * 5)));
assert!(test(BlockHeight::new(interval * 10)));
assert!(!test(BlockHeight::new((interval * 10) + 1)));
assert!(!test(BlockHeight::new((interval * 10) - 1)));
}
}
59 changes: 59 additions & 0 deletions consensus/src/pow/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use common::chain::PoWChainConfig;
use common::Uint256;
use std::time::Duration;

mod helpers;
mod temp;
pub mod work;

#[derive(Debug)]
pub enum Error {
BlockToMineError(String),
ConversionError(String),
OutofBounds(String),
}

pub struct PoW(PoWChainConfig);

impl PoW {
pub fn difficulty_limit(&self) -> Uint256 {
self.0.limit()
}

pub fn no_retargeting(&self) -> bool {
self.0.no_retargeting()
}

pub fn allow_min_difficulty_blocks(&self) -> bool {
self.0.allow_min_difficulty_blocks()
}

pub fn target_spacing(&self) -> Duration {
self.0.target_spacing()
}

pub fn max_retarget_factor(&self) -> u64 {
self.0.max_retarget_factor()
}

pub fn target_timespan_in_secs(&self) -> u64 {
self.0.target_timespan().as_secs()
}

/// Follows the upper bound of the target timespan (2 weeks * 4) of Bitcoin.
/// See Bitcoin's Protocol rules on [Difficulty change](https://en.bitcoin.it/wiki/Protocol_rules)
pub fn max_target_timespan_in_secs(&self) -> u64 {
self.target_timespan_in_secs() * self.max_retarget_factor()
}

/// Follows the lower bound of the target timespan (2 weeks / 4) of Bitcoin.
/// See Bitcoin's Protocol rules on [Difficulty change](https://en.bitcoin.it/wiki/Protocol_rules)
pub fn min_target_timespan_in_secs(&self) -> u64 {
self.target_timespan_in_secs() / self.max_retarget_factor()
}

pub fn difficulty_adjustment_interval(&self) -> u64 {
// or a total of 2016 blocks
self.target_timespan_in_secs() / self.target_spacing().as_secs()
}
}
Loading