diff --git a/src/chainparams/MainNet.cs b/src/chainparams/MainNet.cs
index e568f4d..45bd68f 100644
--- a/src/chainparams/MainNet.cs
+++ b/src/chainparams/MainNet.cs
@@ -7,6 +7,7 @@
using NBitcoin;
using NBitcoin.BouncyCastle.math;
using NBitcoin.DataEncoders;
+using UnnamedCoin.Bitcoin.Features.Consensus;
using UnnamedCoin.Bitcoin.Features.Consensus.Rules.CommonRules;
using UnnamedCoin.Bitcoin.Features.Consensus.Rules.ProvenHeaderRules;
using UnnamedCoin.Bitcoin.Features.MemoryPool.Rules;
@@ -100,6 +101,8 @@ public MainNet()
proofOfStakeReward: Money.Coins(50),
posEmptyCoinbase: false);
+ this.Consensus.LongPosPowPowDifficultyAdjustments = true;
+ this.Consensus.UsePosPowScaling = true;
this.StandardScriptsRegistry = new MainNetStandardScriptsRegistry();
diff --git a/src/components/Fullnode/UnnamedCoin.Bitcoin.Features.Consensus/PosPowScaling.cs b/src/components/Fullnode/UnnamedCoin.Bitcoin.Features.Consensus/PosPowScaling.cs
new file mode 100644
index 0000000..09111f2
--- /dev/null
+++ b/src/components/Fullnode/UnnamedCoin.Bitcoin.Features.Consensus/PosPowScaling.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Diagnostics;
+using NBitcoin;
+using NBitcoin.BouncyCastle.math;
+
+namespace UnnamedCoin.Bitcoin.Features.Consensus
+{
+ public class PosPowScaling
+ {
+ ///
+ /// Gets a factor to compare PoS and PoW difficulties based on past averages.
+ ///
+ /// Consensus
+ /// The current tip
+ /// StakeChain
+ /// The posPowAdjustmentFactor
+ public static double GetPosPowAdjustmentFactor(IConsensus consensus, ChainedHeader tip, IStakeChain stakeChain)
+ {
+ var posPoWScalingAdjustmentInterval = GetPoSPoWScalingAdjustmentInterval(consensus); // 2016 blocks
+
+ if (tip.Height <= posPoWScalingAdjustmentInterval)
+ {
+ return 1.0; // for the first 2016 blocks, do not scale (factor is 1.0)
+ }
+
+ // the first time we are here is block 2017
+ int posBlockCount = 0;
+ int powBlockCount = 0;
+
+ BigInteger sumPosTarget = BigInteger.Zero;
+ BigInteger sumPowTarget = BigInteger.Zero;
+
+ var height = tip.Height;
+
+ while (height % posPoWScalingAdjustmentInterval != 0) // go back till last scaling adjustment
+ {
+ height--;
+ }
+
+ int blocksToCount = posPoWScalingAdjustmentInterval;
+ var it = tip;
+
+ while (blocksToCount-- > 0)
+ {
+ BlockStake blockStake = stakeChain.Get(it.HashBlock);
+
+ if (blockStake.IsProofOfStake())
+ {
+ posBlockCount++;
+ sumPosTarget.Add(it.Header.Bits.ToBigInteger());
+ it.Header.Bits.ToBigInteger();
+ }
+ else
+ {
+ powBlockCount++;
+ sumPowTarget.Add(it.Header.Bits.ToBigInteger());
+ it.Header.Bits.ToBigInteger();
+ }
+
+ it = it.Previous;
+ }
+
+ Debug.Assert(posBlockCount + powBlockCount == posPoWScalingAdjustmentInterval, "invalid sum");
+ BigInteger avgPosTarget = sumPosTarget.Divide(BigInteger.ValueOf(posBlockCount));
+ BigInteger avgPowTarget = sumPowTarget.Divide(BigInteger.ValueOf(powBlockCount));
+
+ double posPowAdjustmentFactor = avgPosTarget.Multiply(BigInteger.ValueOf(1000)).Divide(avgPowTarget).LongValue / 1000.0;
+ return posPowAdjustmentFactor;
+ }
+
+ ///
+ /// Calculate the PoS / PoW scaling adjustment interval in blocks based on settings defined in .
+ /// Note that this comes down to the same points as the difficulty adjustment.
+ ///
+ /// The PoS / PoW scaling adjustment interval in blocks.
+ static int GetPoSPoWScalingAdjustmentInterval(IConsensus consensus)
+ {
+ // 1,209,600 / 600 = 2016
+ return (int)consensus.PowTargetTimespan.TotalSeconds / (int)consensus.PowTargetSpacing.TotalSeconds;
+ }
+ }
+}
diff --git a/src/components/Fullnode/UnnamedCoin.Bitcoin.Features.Miner/BlockProvider.cs b/src/components/Fullnode/UnnamedCoin.Bitcoin.Features.Miner/BlockProvider.cs
index 55f1cce..9ccfb1b 100644
--- a/src/components/Fullnode/UnnamedCoin.Bitcoin.Features.Miner/BlockProvider.cs
+++ b/src/components/Fullnode/UnnamedCoin.Bitcoin.Features.Miner/BlockProvider.cs
@@ -38,9 +38,11 @@ public BlockTemplate BuildPosBlock(ChainedHeader chainTip, Script script)
///
public BlockTemplate BuildPowBlock(ChainedHeader chainTip, Script script)
{
- if (this.network.Consensus.IsProofOfStake)
+ // when building a PoW block in a permanent PoS + PoW network (PosPowOptions is not null), use the Bitcoin two week difficulty retarget.
+ if (this.network.Consensus.IsProofOfStake && !this.network.Consensus.LongPosPowPowDifficultyAdjustments)
return this.posPowBlockDefinition.Build(chainTip, script);
+ // XDS will choose this execution path.
return this.powBlockDefinition.Build(chainTip, script);
}
diff --git a/src/components/NBitcoin/ChainIndexer.cs b/src/components/NBitcoin/ChainIndexer.cs
index 2c0bed3..f7d034a 100644
--- a/src/components/NBitcoin/ChainIndexer.cs
+++ b/src/components/NBitcoin/ChainIndexer.cs
@@ -27,7 +27,12 @@ public ChainIndexer(Network network) : this()
{
this.Network = network;
- Initialize(new ChainedHeader(network.GetGenesis().Header, network.GetGenesis().GetHash(), 0));
+ ChainedHeader chainedHeader;
+ if (network.Consensus.UsePosPowScaling)
+ chainedHeader = new PosPowChainedHeader(network.GetGenesis().Header, network.GetGenesis().GetHash(), 0, network, this);
+ else
+ chainedHeader = new ChainedHeader(network.GetGenesis().Header, network.GetGenesis().GetHash(), 0);
+ Initialize(chainedHeader);
}
public ChainIndexer(Network network, ChainedHeader chainedHeader) : this()
@@ -187,7 +192,7 @@ public virtual IEnumerable EnumerateAfter(ChainedHeader block)
}
}
-
+
public void Add(ChainedHeader addTip)
{
lock (this.lockObject)
@@ -202,7 +207,7 @@ public void Add(ChainedHeader addTip)
}
}
-
+
public void Remove(ChainedHeader removeTip)
{
lock (this.lockObject)
diff --git a/src/components/NBitcoin/ChainedHeader.cs b/src/components/NBitcoin/ChainedHeader.cs
index 9fab4bb..f5e78f7 100644
--- a/src/components/NBitcoin/ChainedHeader.cs
+++ b/src/components/NBitcoin/ChainedHeader.cs
@@ -64,7 +64,7 @@ public class ChainedHeader
static readonly BigInteger Pow256 = BigInteger.ValueOf(2).Pow(256);
/// Integer representation of the .
- BigInteger chainWork;
+ protected BigInteger chainWork;
///
/// Constructs a chained block.
@@ -150,6 +150,9 @@ public ChainedHeader(BlockHeader header, uint256 headerHash, int height) : this(
/// Total amount of work in the chain up to and including this block.
public uint256 ChainWork => Target.ToUInt256(this.chainWork);
+ /// Total amount of work in the chain up to and including this block.
+ public BigInteger ChainWorkBigInteger => this.chainWork;
+
///
public BlockDataAvailabilityState BlockDataAvailability { get; set; }
@@ -187,7 +190,7 @@ public bool IsReferenceConnected
///
/// Calculates the total amount of work in the chain up to and including this block.
///
- void CalculateChainWork()
+ protected virtual void CalculateChainWork()
{
this.chainWork = (this.Previous == null ? BigInteger.Zero : this.Previous.chainWork).Add(GetBlockProof());
}
diff --git a/src/components/NBitcoin/Consensus.cs b/src/components/NBitcoin/Consensus.cs
index e7ee1b8..57834f0 100644
--- a/src/components/NBitcoin/Consensus.cs
+++ b/src/components/NBitcoin/Consensus.cs
@@ -161,5 +161,12 @@ public Consensus(
///
public List MempoolRules { get; set; }
+
+ ///
+ public bool UsePosPowScaling { get; set; }
+
+ ///
+ public bool LongPosPowPowDifficultyAdjustments { get; set; }
+
}
}
\ No newline at end of file
diff --git a/src/components/NBitcoin/IConsensus.cs b/src/components/NBitcoin/IConsensus.cs
index b13a2d6..d4ccd67 100644
--- a/src/components/NBitcoin/IConsensus.cs
+++ b/src/components/NBitcoin/IConsensus.cs
@@ -133,5 +133,11 @@ public interface IConsensus
/// Group of mempool validation rules used by the given network.
List MempoolRules { get; set; }
+
+ /// PoS-to-PoW economy balancing.
+ bool UsePosPowScaling { get; set; }
+
+ /// Use long difficulty adjustment intervals in PoS + PoW networks.
+ bool LongPosPowPowDifficultyAdjustments { get; set; }
}
}
\ No newline at end of file
diff --git a/src/components/NBitcoin/PosPowChainedHeader.cs b/src/components/NBitcoin/PosPowChainedHeader.cs
new file mode 100644
index 0000000..00a94b8
--- /dev/null
+++ b/src/components/NBitcoin/PosPowChainedHeader.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using NBitcoin.BouncyCastle.math;
+
+namespace NBitcoin
+{
+ public class PosPowChainedHeader : ChainedHeader
+ {
+ readonly Network network;
+ readonly ChainIndexer chainIndexer;
+
+ public PosPowChainedHeader(BlockHeader header, uint256 headerHash, ChainedHeader previous, Network network) : base(header, headerHash, previous)
+ {
+ this.IsProofOfStake = ((ProvenBlockHeader)header).Coinstake.IsCoinStake;
+ this.network = network;
+ }
+
+ public PosPowChainedHeader(BlockHeader header, uint256 headerHash, int height, Network network, ChainIndexer chainIndexer) : base(header, headerHash, height)
+ {
+ this.IsProofOfStake = ((ProvenBlockHeader)header).Coinstake.IsCoinStake;
+ this.network = network;
+ this.chainIndexer = chainIndexer;
+ }
+
+ protected override void CalculateChainWork()
+ {
+ if (!this.IsProofOfStake)
+ this.chainWork = (this.Previous == null ? BigInteger.Zero : this.Previous.ChainWorkBigInteger).Add(GetBlockProof());
+ else
+ {
+ var factor = GetPosPowAdjustmentFactor(this.network.Consensus, this, this.chainIndexer);
+ var blockProof = GetBlockProof();
+ var adjusted =
+ this.chainWork = blockProof.Divide(factor);
+ }
+
+ }
+
+ public bool IsProofOfStake { get; }
+
+ ///
+ /// Gets a factor to compare PoS and PoW difficulties based on past averages.
+ ///
+ /// Consensus
+ /// The current tip
+ /// ChainIndexer
+ /// The posPowAdjustmentFactor
+ public static BigInteger GetPosPowAdjustmentFactor(IConsensus consensus, ChainedHeader tip, ChainIndexer chainIndexer)
+ {
+ var posPoWScalingAdjustmentInterval = GetPoSPoWScalingAdjustmentInterval(consensus); // 2016 blocks
+
+ if (tip.Height <= posPoWScalingAdjustmentInterval)
+ {
+ return BigInteger.One; // for the first 2016 blocks, do not scale (factor is 1.0)
+ }
+
+ // the first time we are here is block 2017
+ int posBlockCount = 0;
+ int powBlockCount = 0;
+
+ BigInteger sumPosTarget = BigInteger.Zero;
+ BigInteger sumPowTarget = BigInteger.Zero;
+
+ var height = tip.Height;
+
+ while (height % posPoWScalingAdjustmentInterval != 0) // go back till last scaling adjustment
+ {
+ height--;
+ }
+
+ int blocksToCount = posPoWScalingAdjustmentInterval;
+ var it = tip;
+
+ while (blocksToCount-- > 0)
+ {
+ var posPowChainedHeader = (PosPowChainedHeader) chainIndexer.GetHeader(it.HashBlock);
+
+ if (posPowChainedHeader.IsProofOfStake)
+ {
+ posBlockCount++;
+ sumPosTarget.Add(it.Header.Bits.ToBigInteger());
+ it.Header.Bits.ToBigInteger();
+ }
+ else
+ {
+ powBlockCount++;
+ sumPowTarget.Add(it.Header.Bits.ToBigInteger());
+ it.Header.Bits.ToBigInteger();
+ }
+
+ it = it.Previous;
+ }
+
+ Debug.Assert(posBlockCount + powBlockCount == posPoWScalingAdjustmentInterval, "invalid sum");
+ BigInteger avgPosTarget = sumPosTarget.Divide(BigInteger.ValueOf(posBlockCount));
+ BigInteger avgPowTarget = sumPowTarget.Divide(BigInteger.ValueOf(powBlockCount));
+
+ var posPowAdjustmentFactor = avgPosTarget.Divide(avgPowTarget);
+ return posPowAdjustmentFactor;
+ }
+
+ ///
+ /// Calculate the PoS / PoW scaling adjustment interval in blocks based on settings defined in .
+ /// Note that this comes down to the same points as the difficulty adjustment.
+ ///
+ /// The PoS / PoW scaling adjustment interval in blocks.
+ static int GetPoSPoWScalingAdjustmentInterval(IConsensus consensus)
+ {
+ // 1,209,600 / 600 = 2016
+ return (int)consensus.PowTargetTimespan.TotalSeconds / (int)consensus.PowTargetSpacing.TotalSeconds;
+ }
+ }
+}