Skip to content
Open
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 src/Blockcore/Consensus/BlockInfo/BlockHeader.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using Blockcore.Consensus.Chain;
using Blockcore.Networks;
using Blockcore.Utilities.Extensions;
using NBitcoin;
using NBitcoin.BouncyCastle.Math;
using NBitcoin.Crypto;
Expand Down
193 changes: 0 additions & 193 deletions src/Blockcore/Consensus/Chain/ChainedHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -408,141 +408,6 @@ public ChainedHeader FindAncestorOrSelf(uint256 blockHash, int height = 0)
return (currentBlock?.HashBlock == blockHash) ? currentBlock : null;
}

/// <summary>
/// Gets the proof of work target for a potential new block after this entry on the chain.
/// </summary>
/// <param name="network">The network to get target for.</param>
/// <returns>The target proof of work.</returns>
public Target GetNextWorkRequired(Network network)
{
return this.GetNextWorkRequired(network.Consensus);
}

/// <summary>
/// Gets the proof of work target for a potential new block after this entry on the chain.
/// </summary>
/// <param name="consensus">Consensus rules to use for this computation.</param>
/// <returns>The target proof of work.</returns>
public Target GetNextWorkRequired(IConsensus consensus)
{
BlockHeader dummy = consensus.ConsensusFactory.CreateBlockHeader();
dummy.HashPrevBlock = this.HashBlock;
dummy.BlockTime = DateTimeOffset.UtcNow;
return this.GetNextWorkRequired(dummy, consensus);
}

/// <summary>
/// Gets the proof of work target for the new block specified.
/// </summary>
/// <param name="block">The new block to get proof of work for.</param>
/// <param name="network">The network to get target for.</param>
/// <returns>The target proof of work.</returns>
public Target GetNextWorkRequired(BlockHeader block, Network network)
{
return this.GetNextWorkRequired(block, network.Consensus);
}

/// <summary>
/// Gets the proof of work target for the new block specified.
/// </summary>
/// <param name="block">The new block to get proof of work for.</param>
/// <param name="consensus">Consensus rules to use for this computation.</param>
/// <returns>The target proof of work.</returns>
public Target GetNextWorkRequired(BlockHeader block, IConsensus consensus)
{
return new ChainedHeader(block, block.GetHash(), this).GetWorkRequired(consensus);
}

/// <summary>
/// Gets the proof of work target for this entry in the chain.
/// </summary>
/// <param name="network">The network to get target for.</param>
/// <returns>The target proof of work.</returns>
public Target GetWorkRequired(Network network)
{
return this.GetWorkRequired(network.Consensus);
}

/// <summary>
/// Gets the proof of work target for this entry in the chain.
/// </summary>
/// <param name="consensus">Consensus rules to use for this computation.</param>
/// <returns>The target proof of work.</returns>
public Target GetWorkRequired(IConsensus consensus)
{
// Genesis block.
if (this.Height == 0)
return consensus.PowLimit;

Target proofOfWorkLimit = consensus.PowLimit;
ChainedHeader lastBlock = this.Previous;
int height = this.Height;

if (lastBlock == null)
return proofOfWorkLimit;

long difficultyAdjustmentInterval = this.GetDifficultyAdjustmentInterval(consensus);

// Only change once per interval.
if ((height) % difficultyAdjustmentInterval != 0)
{
if (consensus.PowAllowMinDifficultyBlocks)
{
// Special difficulty rule for testnet:
// If the new block's timestamp is more than 2* 10 minutes
// then allow mining of a min-difficulty block.
if (this.Header.BlockTime > (lastBlock.Header.BlockTime + TimeSpan.FromTicks(consensus.TargetSpacing.Ticks * 2)))
return proofOfWorkLimit;

// Return the last non-special-min-difficulty-rules-block.
ChainedHeader chainedHeader = lastBlock;
while ((chainedHeader.Previous != null) && ((chainedHeader.Height % difficultyAdjustmentInterval) != 0) && (chainedHeader.Header.Bits == proofOfWorkLimit))
chainedHeader = chainedHeader.Previous;

return chainedHeader.Header.Bits;
}

return lastBlock.Header.Bits;
}

// Go back by what we want to be 14 days worth of blocks.
long pastHeight = lastBlock.Height - (difficultyAdjustmentInterval - 1);

ChainedHeader firstChainedHeader = this.GetAncestor((int)pastHeight);
if (firstChainedHeader == null)
throw new NotSupportedException("Can only calculate work of a full chain");

if (consensus.PowNoRetargeting)
return lastBlock.Header.Bits;

// Limit adjustment step.
TimeSpan actualTimespan = lastBlock.Header.BlockTime - firstChainedHeader.Header.BlockTime;
if (actualTimespan < TimeSpan.FromTicks(consensus.TargetTimespan.Ticks / 4))
actualTimespan = TimeSpan.FromTicks(consensus.TargetTimespan.Ticks / 4);
if (actualTimespan > TimeSpan.FromTicks(consensus.TargetTimespan.Ticks * 4))
actualTimespan = TimeSpan.FromTicks(consensus.TargetTimespan.Ticks * 4);

// Retarget.
BigInteger newTarget = lastBlock.Header.Bits.ToBigInteger();
newTarget = newTarget.Multiply(BigInteger.ValueOf((long)actualTimespan.TotalSeconds));
newTarget = newTarget.Divide(BigInteger.ValueOf((long)consensus.TargetTimespan.TotalSeconds));

var finalTarget = new Target(newTarget);
if (finalTarget > proofOfWorkLimit)
finalTarget = proofOfWorkLimit;

return finalTarget;
}

/// <summary>
/// Calculate the difficulty adjustment interval in blocks based on settings defined in <see cref="IConsensus"/>.
/// </summary>
/// <returns>The difficulty adjustment interval in blocks.</returns>
private long GetDifficultyAdjustmentInterval(IConsensus consensus)
{
return (long)consensus.TargetTimespan.TotalSeconds / (long)consensus.TargetSpacing.TotalSeconds;
}

/// <summary>
/// Calculate the median block time over <see cref="MedianTimeSpan"/> window from this entry in the chain.
/// </summary>
Expand All @@ -561,64 +426,6 @@ public DateTimeOffset GetMedianTimePast()
return median[begin + ((end - begin) / 2)];
}

/// <summary>
/// Check that the header is a valid block header including the work done for PoW blocks.
/// </summary>
/// <param name="network">The network to verify against.</param>
/// <returns><c>true</c> if the header is a valid block header, <c>false</c> otherwise.</returns>
public bool Validate(Network network)
{
if (network == null)
throw new ArgumentNullException("network");

if (network.Consensus.IsProofOfStake)
return BlockStake.Validate(network, this);

bool genesisCorrect = (this.Height != 0) || this.HashBlock == network.GetGenesis().GetHash();
return genesisCorrect && this.Validate(network.Consensus);
}

/// <summary>
/// Check PoW against consensus and that the blocks connect correctly.
/// </summary>
/// <param name="consensus">The consensus rules being used.</param>
/// <returns><c>true</c> if the header is a valid block header, <c>false</c> otherwise.</returns>
public bool Validate(IConsensus consensus)
{
if (consensus == null)
throw new ArgumentNullException("consensus");

if ((this.Height != 0) && (this.Previous == null))
return false;

bool heightCorrect = (this.Height == 0) || (this.Height == this.Previous.Height + 1);
bool hashPrevCorrect = (this.Height == 0) || (this.Header.HashPrevBlock == this.Previous.HashBlock);
bool hashCorrect = this.HashBlock == this.Header.GetHash();
bool workCorrect = this.CheckProofOfWorkAndTarget(consensus);

return heightCorrect && hashPrevCorrect && hashCorrect && workCorrect;
}

/// <summary>
/// Verify proof of work of the header of this chain using consensus.
/// </summary>
/// <param name="network">The network to verify proof of work on.</param>
/// <returns>Whether proof of work is valid.</returns>
public bool CheckProofOfWorkAndTarget(Network network)
{
return this.CheckProofOfWorkAndTarget(network.Consensus);
}

/// <summary>
/// Verify proof of work of the header of this chain using consensus.
/// </summary>
/// <param name="consensus">Consensus rules to use for this validation.</param>
/// <returns>Whether proof of work is valid.</returns>
public bool CheckProofOfWorkAndTarget(IConsensus consensus)
{
return (this.Height == 0) || (this.Header.CheckProofOfWork() && (this.Header.Bits == this.GetWorkRequired(consensus)));
}

/// <summary>
/// Find first common block between two chains.
/// </summary>
Expand Down
73 changes: 73 additions & 0 deletions src/Blockcore/Consensus/Consensus.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Blockcore.Base.Deployments;
using Blockcore.Consensus.Chain;
using NBitcoin;
using NBitcoin.BouncyCastle.Math;

Expand Down Expand Up @@ -169,5 +170,77 @@ public Consensus(
this.MempoolRules = new List<Type>();
this.ProofOfStakeTimestampMask = proofOfStakeTimestampMask;
}

/// <summary>
/// Gets the proof of work target for a given entry in the chain.
/// </summary>
/// <param name="chainedHeader">The header for which to calculate the required work.</param>
/// <returns>The target proof of work.</returns>
public Target GetWorkRequired(ChainedHeader chainedHeader)
{
// Genesis block.
if (chainedHeader.Height == 0)
return this.PowLimit;

Target proofOfWorkLimit = this.PowLimit;
ChainedHeader lastBlock = chainedHeader.Previous;
int height = chainedHeader.Height;

if (lastBlock == null)
return proofOfWorkLimit;

// Calculate the difficulty adjustment interval in blocks
long difficultyAdjustmentInterval = (long)this.TargetTimespan.TotalSeconds / (long)this.TargetSpacing.TotalSeconds;

// Only change once per interval.
if ((height) % difficultyAdjustmentInterval != 0)
{
if (this.PowAllowMinDifficultyBlocks)
{
// Special difficulty rule for testnet:
// If the new block's timestamp is more than 2* 10 minutes
// then allow mining of a min-difficulty block.
if (chainedHeader.Header.BlockTime > (lastBlock.Header.BlockTime + TimeSpan.FromTicks(this.TargetSpacing.Ticks * 2)))
return proofOfWorkLimit;

// Return the last non-special-min-difficulty-rules-block.
ChainedHeader lastChainedHeader = lastBlock;
while ((lastChainedHeader.Previous != null) && ((lastChainedHeader.Height % difficultyAdjustmentInterval) != 0) && (lastChainedHeader.Header.Bits == proofOfWorkLimit))
lastChainedHeader = lastChainedHeader.Previous;

return lastChainedHeader.Header.Bits;
}

return lastBlock.Header.Bits;
}

// Go back by what we want to be 14 days worth of blocks.
long pastHeight = lastBlock.Height - (difficultyAdjustmentInterval - 1);

ChainedHeader firstChainedHeader = chainedHeader.GetAncestor((int)pastHeight);
if (firstChainedHeader == null)
throw new NotSupportedException("Can only calculate work of a full chain");

if (this.PowNoRetargeting)
return lastBlock.Header.Bits;

// Limit adjustment step.
TimeSpan actualTimespan = lastBlock.Header.BlockTime - firstChainedHeader.Header.BlockTime;
if (actualTimespan < TimeSpan.FromTicks(this.TargetTimespan.Ticks / 4))
actualTimespan = TimeSpan.FromTicks(this.TargetTimespan.Ticks / 4);
if (actualTimespan > TimeSpan.FromTicks(this.TargetTimespan.Ticks * 4))
actualTimespan = TimeSpan.FromTicks(this.TargetTimespan.Ticks * 4);

// Retarget.
BigInteger newTarget = lastBlock.Header.Bits.ToBigInteger();
newTarget = newTarget.Multiply(BigInteger.ValueOf((long)actualTimespan.TotalSeconds));
newTarget = newTarget.Divide(BigInteger.ValueOf((long)this.TargetTimespan.TotalSeconds));

var finalTarget = new Target(newTarget);
if (finalTarget > proofOfWorkLimit)
finalTarget = proofOfWorkLimit;

return finalTarget;
}
}
}
8 changes: 8 additions & 0 deletions src/Blockcore/Consensus/IConsensus.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Blockcore.Base.Deployments;
using Blockcore.Consensus.Chain;
using NBitcoin;
using NBitcoin.BouncyCastle.Math;

Expand Down Expand Up @@ -147,5 +148,12 @@ public interface IConsensus

/// <summary>Group of mempool validation rules used by the given network.</summary>
List<Type> MempoolRules { get; set; }

/// <summary>
/// Gets the proof of work target for a given entry in the chain.
/// </summary>
/// <param name="chainedHeader">The header for which to calculate the required work.</param>
/// <returns>The target proof of work.</returns>
Target GetWorkRequired(ChainedHeader chainedHeader);
}
}
11 changes: 11 additions & 0 deletions src/Blockcore/Utilities/Extensions/BlockExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.IO;
using Blockcore.Consensus;
using Blockcore.Consensus.BlockInfo;
using Blockcore.Consensus.Chain;
using Blockcore.Consensus.TransactionInfo;
using NBitcoin;

Expand Down Expand Up @@ -42,5 +43,15 @@ public static int GetSize(this IBitcoinSerializable data, TransactionOptions opt

return (int)bms.Counter.WrittenBytes;
}

/// <summary>
/// Gets the proof of work target for this entry in the chain.
/// </summary>
/// <param name="consensus">Consensus rules to use for this computation.</param>
/// <returns>The target proof of work.</returns>
public static Target GetWorkRequired(this ChainedHeader chainedHeader, IConsensus consensus)
{
return consensus.GetWorkRequired(chainedHeader);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Blockcore.Interfaces;
using Blockcore.Networks;
using Blockcore.Utilities;
using Blockcore.Utilities.Extensions;
using Microsoft.Extensions.Logging;
using NBitcoin;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ public override void Run(RuleContext context)
return;
}

// TODO: In the future once we migrated to fully C# network it might be good to consider signaling in the block header the network type.

ChainedHeader chainedHeader = context.ValidationContext.ChainedHeaderToValidate;

// In order to calculate difficulty we need to know the if the block type is POW/POS.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Blockcore.Consensus;
using Blockcore.Consensus.BlockInfo;
using Blockcore.Consensus.Rules;
using Blockcore.Utilities.Extensions;
using Microsoft.Extensions.Logging;
using NBitcoin;

Expand Down
Loading