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; + } + } +}