diff --git a/src/Networks/x42/x42/Networks/Consensus/x42Consensus.cs b/src/Networks/x42/x42/Networks/Consensus/x42Consensus.cs
index 969a6164a..8aecfed5d 100644
--- a/src/Networks/x42/x42/Networks/Consensus/x42Consensus.cs
+++ b/src/Networks/x42/x42/Networks/Consensus/x42Consensus.cs
@@ -154,6 +154,8 @@ public class x42Consensus : IConsensus
///
public Money LastProofOfStakeRewardHeight { get; }
+ public Money MinOpReturnFee { get; }
+
public x42Consensus(
ConsensusFactory consensusFactory,
ConsensusOptions consensusOptions,
@@ -188,6 +190,7 @@ public x42Consensus(
Money proofOfStakeRewardAfterSubsidyLimit,
long subsidyLimit,
Money lastProofOfStakeRewardHeight,
+ Money minOpReturnFee,
bool posEmptyCoinbase,
uint proofOfStakeTimestampMask
)
@@ -225,6 +228,7 @@ uint proofOfStakeTimestampMask
this.ProofOfStakeRewardAfterSubsidyLimit = proofOfStakeRewardAfterSubsidyLimit;
this.SubsidyLimit = subsidyLimit;
this.LastProofOfStakeRewardHeight = lastProofOfStakeRewardHeight;
+ this.MinOpReturnFee = minOpReturnFee;
this.ConsensusRules = new ConsensusRules();
this.MempoolRules = new List();
this.PosEmptyCoinbase = posEmptyCoinbase;
diff --git a/src/Networks/x42/x42/Networks/Consensus/x42ConsensusErrors.cs b/src/Networks/x42/x42/Networks/Consensus/x42ConsensusErrors.cs
new file mode 100644
index 000000000..721ac0dc4
--- /dev/null
+++ b/src/Networks/x42/x42/Networks/Consensus/x42ConsensusErrors.cs
@@ -0,0 +1,9 @@
+using Blockcore.Consensus;
+
+namespace x42.Networks.Consensus
+{
+ public static class x42ConsensusErrors
+ {
+ public static ConsensusError InsufficientOpReturnFee => new ConsensusError("op-return-fee-insufficient", "The OP_RETURN fee is insufficient.");
+ }
+}
\ No newline at end of file
diff --git a/src/Networks/x42/x42/Networks/Consensus/x42PosBlockDefinition.cs b/src/Networks/x42/x42/Networks/Consensus/x42PosBlockDefinition.cs
deleted file mode 100644
index 3e7e24ac5..000000000
--- a/src/Networks/x42/x42/Networks/Consensus/x42PosBlockDefinition.cs
+++ /dev/null
@@ -1,330 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Blockcore.Base.Deployments;
-using Blockcore.Consensus;
-using Blockcore.Features.MemoryPool;
-using Blockcore.Features.MemoryPool.Interfaces;
-using Blockcore.Features.Miner;
-using Blockcore.Features.Miner.Comparers;
-using Blockcore.Mining;
-using Blockcore.Utilities;
-using Microsoft.Extensions.Logging;
-using NBitcoin;
-
-namespace x42.Networks.Consensus
-{
- public class x42BlockDefinition : BlockDefinition
- {
- /// Instance logger.
- private readonly ILogger logger;
-
- protected x42BlockDefinition(
- IConsensusManager consensusManager,
- IDateTimeProvider dateTimeProvider,
- ILoggerFactory loggerFactory,
- ITxMempool mempool,
- MempoolSchedulerLock mempoolLock,
- MinerSettings minerSettings,
- Network network,
- NodeDeployments nodeDeployments) : base(consensusManager, dateTimeProvider, loggerFactory, mempool, mempoolLock, minerSettings, network, nodeDeployments)
- {
- this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
- }
-
- public override void AddToBlock(TxMempoolEntry mempoolEntry)
- {
- base.AddTransactionToBlock(mempoolEntry.Transaction);
- base.UpdateBlockStatistics(mempoolEntry);
- base.UpdateTotalFees(mempoolEntry.Fee);
- }
-
- public override BlockTemplate Build(ChainedHeader chainTip, Script scriptPubKey)
- {
- base.OnBuild(chainTip, scriptPubKey);
-
- base.coinbase.Outputs[0].ScriptPubKey = new Script();
- base.coinbase.Outputs[0].Value = Money.Zero;
-
- return base.BlockTemplate;
- }
-
- public override void UpdateHeaders()
- {
- return;
- }
-
- public static bool IsOpReturn(byte[] bytes)
- {
- return bytes.Length > 0 && bytes[0] == (byte)OpcodeType.OP_RETURN;
- }
-
- protected override void AddTransactions(out int nPackagesSelected, out int nDescendantsUpdated)
- {
- nPackagesSelected = 0;
- nDescendantsUpdated = 0;
-
- // mapModifiedTx will store sorted packages after they are modified
- // because some of their txs are already in the block.
- var mapModifiedTx = new Dictionary();
-
- //var mapModifiedTxRes = this.mempoolScheduler.ReadAsync(() => mempool.MapTx.Values).GetAwaiter().GetResult();
- // mapModifiedTxRes.Select(s => new TxMemPoolModifiedEntry(s)).OrderBy(o => o, new CompareModifiedEntry());
-
- // Keep track of entries that failed inclusion, to avoid duplicate work.
- var failedTx = new TxMempool.SetEntries();
-
- // Start by adding all descendants of previously added txs to mapModifiedTx
- // and modifying them for their already included ancestors.
- UpdatePackagesForAdded(this.inBlock, mapModifiedTx);
-
- List ancestorScoreList = this.MempoolLock.ReadAsync(() => this.Mempool.MapTx.AncestorScore).ConfigureAwait(false).GetAwaiter().GetResult().ToList();
-
- TxMempoolEntry iter;
-
- int nConsecutiveFailed = 0;
- while (ancestorScoreList.Any() || mapModifiedTx.Any())
- {
- TxMempoolEntry mi = ancestorScoreList.FirstOrDefault();
- if (mi != null)
- {
- // Skip entries in mapTx that are already in a block or are present
- // in mapModifiedTx (which implies that the mapTx ancestor state is
- // stale due to ancestor inclusion in the block).
- // Also skip transactions that we've already failed to add. This can happen if
- // we consider a transaction in mapModifiedTx and it fails: we can then
- // potentially consider it again while walking mapTx. It's currently
- // guaranteed to fail again, but as a belt-and-suspenders check we put it in
- // failedTx and avoid re-evaluation, since the re-evaluation would be using
- // cached size/sigops/fee values that are not actually correct.
-
- // First try to find a new transaction in mapTx to evaluate.
- if (mapModifiedTx.ContainsKey(mi.TransactionHash) || this.inBlock.Contains(mi) || failedTx.Contains(mi))
- {
- ancestorScoreList.Remove(mi);
- continue;
- }
- }
-
- // Now that mi is not stale, determine which transaction to evaluate:
- // the next entry from mapTx, or the best from mapModifiedTx?
- bool fUsingModified = false;
- TxMemPoolModifiedEntry modit;
- var compare = new CompareModifiedEntry();
- if (mi == null)
- {
- modit = mapModifiedTx.Values.OrderBy(o => o, compare).First();
- iter = modit.MempoolEntry;
- fUsingModified = true;
- }
- else
- {
- // Try to compare the mapTx entry to the mapModifiedTx entry
- iter = mi;
-
- modit = mapModifiedTx.Values.OrderBy(o => o, compare).FirstOrDefault();
- if ((modit != null) && (compare.Compare(modit, new TxMemPoolModifiedEntry(iter)) < 0))
- {
- // The best entry in mapModifiedTx has higher score
- // than the one from mapTx..
- // Switch which transaction (package) to consider.
-
- iter = modit.MempoolEntry;
- fUsingModified = true;
- }
- else
- {
- // Either no entry in mapModifiedTx, or it's worse than mapTx.
- // Increment mi for the next loop iteration.
- ancestorScoreList.Remove(iter);
- }
- }
-
- // We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't
- // contain anything that is inBlock.
- Guard.Assert(!this.inBlock.Contains(iter));
-
- long packageSize = iter.SizeWithAncestors;
- Money packageFees = iter.ModFeesWithAncestors;
- long packageSigOpsCost = iter.SigOpCostWithAncestors;
- if (fUsingModified)
- {
- packageSize = modit.SizeWithAncestors;
- packageFees = modit.ModFeesWithAncestors;
- packageSigOpsCost = modit.SigOpCostWithAncestors;
- }
-
- int opReturnCount = iter.Transaction.Outputs.Select(o => o.ScriptPubKey.ToBytes(true)).Count(b => IsOpReturn(b));
-
- // When there are zero fee's we want to still include the transaction.
- // If there is OP_RETURN data, we will want to make sure there is a fee.
- if (this.Network.MinTxFee > Money.Zero || opReturnCount > 0)
- {
- if (packageFees < this.BlockMinFeeRate.GetFee((int)packageSize))
- {
- // Everything else we might consider has a lower fee rate
- return;
- }
- }
-
- if (!this.TestPackage(iter, packageSize, packageSigOpsCost))
- {
- if (fUsingModified)
- {
- // Since we always look at the best entry in mapModifiedTx,
- // we must erase failed entries so that we can consider the
- // next best entry on the next loop iteration
- mapModifiedTx.Remove(modit.MempoolEntry.TransactionHash);
- failedTx.Add(iter);
- }
-
- nConsecutiveFailed++;
-
- if ((nConsecutiveFailed > MaxConsecutiveAddTransactionFailures) && (this.BlockWeight > this.Options.BlockMaxWeight - 4000))
- {
- // Give up if we're close to full and haven't succeeded in a while
- break;
- }
- continue;
- }
-
- var ancestors = new TxMempool.SetEntries();
- long nNoLimit = long.MaxValue;
- string dummy;
-
- this.MempoolLock.ReadAsync(() => this.Mempool.CalculateMemPoolAncestors(iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, out dummy, false)).ConfigureAwait(false).GetAwaiter().GetResult();
-
- this.OnlyUnconfirmed(ancestors);
- ancestors.Add(iter);
-
- // Test if all tx's are Final.
- if (!this.TestPackageTransactions(ancestors))
- {
- if (fUsingModified)
- {
- mapModifiedTx.Remove(modit.MempoolEntry.TransactionHash);
- failedTx.Add(iter);
- }
- continue;
- }
-
- // This transaction will make it in; reset the failed counter.
- nConsecutiveFailed = 0;
-
- // Package can be added. Sort the entries in a valid order.
- // Sort package by ancestor count
- // If a transaction A depends on transaction B, then A's ancestor count
- // must be greater than B's. So this is sufficient to validly order the
- // transactions for block inclusion.
- List sortedEntries = ancestors.ToList().OrderBy(o => o, new CompareTxIterByAncestorCount()).ToList();
- foreach (TxMempoolEntry sortedEntry in sortedEntries)
- {
- this.AddToBlock(sortedEntry);
- // Erase from the modified set, if present
- mapModifiedTx.Remove(sortedEntry.TransactionHash);
- }
-
- nPackagesSelected++;
-
- // Update transactions that depend on each of these
- nDescendantsUpdated += this.UpdatePackagesForAdded(ancestors, mapModifiedTx);
- }
- }
-
- ///
- /// Add descendants of given transactions to mapModifiedTx with ancestor
- /// state updated assuming given transactions are inBlock. Returns number
- /// of updated descendants.
- ///
- private int UpdatePackagesForAdded(TxMempool.SetEntries alreadyAdded, Dictionary mapModifiedTx)
- {
- int descendantsUpdated = 0;
-
- foreach (TxMempoolEntry addedEntry in alreadyAdded)
- {
- var setEntries = new TxMempool.SetEntries();
-
- this.MempoolLock.ReadAsync(() =>
- {
- if (!this.Mempool.MapTx.ContainsKey(addedEntry.TransactionHash))
- {
- this.logger.LogWarning("{0} is not present in {1} any longer, skipping.", addedEntry.TransactionHash, nameof(this.Mempool.MapTx));
- return;
- }
-
- this.Mempool.CalculateDescendants(addedEntry, setEntries);
-
- }).GetAwaiter().GetResult();
-
- foreach (TxMempoolEntry desc in setEntries)
- {
- if (alreadyAdded.Contains(desc))
- continue;
-
- descendantsUpdated++;
- TxMemPoolModifiedEntry modEntry;
- if (!mapModifiedTx.TryGetValue(desc.TransactionHash, out modEntry))
- {
- modEntry = new TxMemPoolModifiedEntry(desc);
- mapModifiedTx.Add(desc.TransactionHash, modEntry);
- this.logger.LogDebug("Added transaction '{0}' to the block template because it's a required ancestor for '{1}'.", desc.TransactionHash, addedEntry.TransactionHash);
- }
-
- modEntry.SizeWithAncestors -= addedEntry.GetTxSize();
- modEntry.ModFeesWithAncestors -= addedEntry.ModifiedFee;
- modEntry.SigOpCostWithAncestors -= addedEntry.SigOpCost;
- }
- }
-
- return descendantsUpdated;
- }
-
- ///
- /// Remove confirmed entries from given set.
- ///
- private void OnlyUnconfirmed(TxMempool.SetEntries testSet)
- {
- foreach (TxMempoolEntry setEntry in testSet.ToList())
- {
- // Only test txs not already in the block
- if (this.inBlock.Contains(setEntry))
- {
- testSet.Remove(setEntry);
- }
- }
- }
-
- ///
- /// Perform transaction-level checks before adding to block.
- ///
- ///
- /// - Transaction finality (locktime).
- /// - Premature witness (in case segwit transactions are added to mempool before segwit activation).
- /// - serialized size (in case -blockmaxsize is in use).
- ///
- ///
- ///
- private bool TestPackageTransactions(TxMempool.SetEntries package)
- {
- foreach (TxMempoolEntry it in package)
- {
- if (!it.Transaction.IsFinal(Utils.UnixTimeToDateTime(this.LockTimeCutoff), this.height))
- return false;
-
- if (!this.IncludeWitness && it.Transaction.HasWitness)
- return false;
-
- if (this.NeedSizeAccounting)
- {
- long nPotentialBlockSize = this.BlockSize; // only used with needSizeAccounting
- int nTxSize = it.Transaction.GetSerializedSize();
- if (nPotentialBlockSize + nTxSize >= this.Options.BlockMaxSize)
- return false;
-
- nPotentialBlockSize += nTxSize;
- }
- }
-
- return true;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Networks/x42/x42/Networks/Rules/x42OpReturnFeeMempoolRule.cs b/src/Networks/x42/x42/Networks/Rules/x42OpReturnFeeMempoolRule.cs
new file mode 100644
index 000000000..1d4f722fe
--- /dev/null
+++ b/src/Networks/x42/x42/Networks/Rules/x42OpReturnFeeMempoolRule.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.Linq;
+using Blockcore.Features.MemoryPool;
+using Blockcore.Features.MemoryPool.Interfaces;
+using Blockcore.Features.MemoryPool.Rules;
+using Microsoft.Extensions.Logging;
+using NBitcoin;
+
+namespace x42.Networks.Consensus.Rules
+{
+ ///
+ /// Verify the OP_RETURN fee.
+ ///
+ public class x42OpReturnFeeMempoolRule : CheckFeeMempoolRule
+ {
+ public x42OpReturnFeeMempoolRule(Network network,
+ ITxMempool mempool,
+ MempoolSettings mempoolSettings,
+ ChainIndexer chainIndexer,
+ ILoggerFactory loggerFactory) : base(network, mempool, mempoolSettings, chainIndexer, loggerFactory)
+ {
+ }
+
+ public override void CheckTransaction(MempoolValidationContext context)
+ {
+ if (context.Transaction.IsCoinBase)
+ return;
+
+ List opReturns = context.Transaction.Outputs.Select(o => o.ScriptPubKey.ToBytes(true)).Where(b => IsOpReturn(b)).ToList();
+ Money transactionFees = context.Fees;
+ FeeRate OpReturnFeeRate = new FeeRate(((x42Consensus)this.network.Consensus).MinOpReturnFee);
+
+ // If there is OP_RETURN data, we will want to make sure the fee is correct.
+ if (opReturns.Count() > 0)
+ {
+ var opReturnSize = opReturns.Sum(r => r.Length);
+ if (transactionFees < OpReturnFeeRate.GetFee(opReturnSize))
+ {
+ this.logger.LogTrace($"(-)[FAIL_{nameof(x42OpReturnFeeMempoolRule)}]".ToUpperInvariant());
+ x42ConsensusErrors.InsufficientOpReturnFee.Throw();
+ }
+ }
+
+ base.CheckTransaction(context);
+ }
+
+ public static bool IsOpReturn(byte[] bytes)
+ {
+ return bytes.Length > 0 && bytes[0] == (byte)OpcodeType.OP_RETURN;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Networks/x42/x42/Networks/x42Main.cs b/src/Networks/x42/x42/Networks/x42Main.cs
index b99446ea5..0fb703573 100644
--- a/src/Networks/x42/x42/Networks/x42Main.cs
+++ b/src/Networks/x42/x42/Networks/x42Main.cs
@@ -15,6 +15,7 @@
using x42.Networks.Setup;
using x42.Networks.Deployments;
using NBitcoin.Protocol;
+using x42.Networks.Consensus.Rules;
namespace x42.Networks
{
@@ -129,6 +130,7 @@ public x42Main()
subsidyLimit: setup.SubsidyLimit,
proofOfStakeRewardAfterSubsidyLimit: setup.ProofOfStakeRewardAfterSubsidyLimit,
lastProofOfStakeRewardHeight: setup.LastProofOfStakeRewardHeight,
+ minOpReturnFee: Money.Coins(0.02m).Satoshi,
proofOfStakeTimestampMask: setup.ProofOfStakeTimestampMask,
posEmptyCoinbase: x42Setup.Instance.IsPoSv3()
)
@@ -227,7 +229,7 @@ protected void RegisterMempoolRules(IConsensus consensus)
typeof(CheckCoinViewMempoolRule),
typeof(CreateMempoolEntryMempoolRule),
typeof(CheckSigOpsMempoolRule),
- typeof(CheckFeeMempoolRule),
+ typeof(x42OpReturnFeeMempoolRule),
typeof(CheckRateLimitMempoolRule),
typeof(CheckAncestorsMempoolRule),
typeof(CheckReplacementMempoolRule),
diff --git a/src/Networks/x42/x42/Networks/x42RegTest.cs b/src/Networks/x42/x42/Networks/x42RegTest.cs
index 350425995..9fa8c72ab 100644
--- a/src/Networks/x42/x42/Networks/x42RegTest.cs
+++ b/src/Networks/x42/x42/Networks/x42RegTest.cs
@@ -105,6 +105,7 @@ public x42RegTest()
subsidyLimit: setup.SubsidyLimit,
proofOfStakeRewardAfterSubsidyLimit: setup.ProofOfStakeRewardAfterSubsidyLimit,
lastProofOfStakeRewardHeight: setup.LastProofOfStakeRewardHeight,
+ minOpReturnFee: Money.Coins(0.02m).Satoshi,
proofOfStakeTimestampMask: setup.ProofOfStakeTimestampMask,
posEmptyCoinbase: x42Setup.Instance.IsPoSv3()
)
diff --git a/src/Networks/x42/x42/Networks/x42Test.cs b/src/Networks/x42/x42/Networks/x42Test.cs
index c5889c49f..6aa6e8b1f 100644
--- a/src/Networks/x42/x42/Networks/x42Test.cs
+++ b/src/Networks/x42/x42/Networks/x42Test.cs
@@ -127,6 +127,7 @@ public x42Test()
proofOfStakeRewardAfterSubsidyLimit: setup.ProofOfStakeRewardAfterSubsidyLimit,
lastProofOfStakeRewardHeight: setup.LastProofOfStakeRewardHeight,
proofOfStakeTimestampMask: setup.ProofOfStakeTimestampMask,
+ minOpReturnFee: Money.Coins(0.02m).Satoshi,
posEmptyCoinbase: x42Setup.Instance.IsPoSv3()
)
{