From 2273fd4b7c827c40bab15210905d09ba807e190e Mon Sep 17 00:00:00 2001 From: Dan Gershony Date: Wed, 15 Apr 2020 01:55:49 +0100 Subject: [PATCH] Add XDS network (#83) * First commit copy of stratis * Add XDS network * Get network to sync. and fix a consensus bug * Fix create genesis bug * Fix dust error --- .../ColdStakingControllerTest.cs | 2 +- .../ColdStakingManager.cs | 38 +-- .../ColdStakingWalletRPCController.cs | 1 + .../Rules/CommonRules/PosCoinViewRuleTest.cs | 9 +- .../Rules/CommonRules/PosTimeMaskRule.cs | 2 +- .../ProvenHeaderCoinstakeRule.cs | 2 +- .../Rules/UtxosetRules/CheckPosUtxosetRule.cs | 6 +- .../StakeValidator.cs | 20 +- .../MempoolPersistence.cs | 4 +- .../MempoolValidator.cs | 2 +- .../Staking/PosMinting.cs | 20 +- src/Blockcore.Features.PoA/PoANetwork.cs | 3 +- .../Policies/PoAStandardScriptsRegistry.cs | 4 +- .../Controller/WalletRPCControllerTests.cs | 17 +- .../RPCParametersValueProviderTest.cs | 3 +- .../{ => Controllers}/WalletRPCController.cs | 2 +- .../RPC/AddNodeActionTest.cs | 15 +- src/Blockcore.Networks.Bitcoin/BitcoinMain.cs | 3 +- .../BitcoinRegTest.cs | 5 +- src/Blockcore.Networks.Bitcoin/BitcoinTest.cs | 3 +- .../Blockcore.Networks.Bitcoin.csproj | 2 +- .../BitcoinStandardScriptsRegistry.cs | 2 + .../Blockcore.Networks.Stratis.csproj | 2 +- .../StratisStandardScriptsRegistry.cs | 4 +- src/Blockcore.Networks.Stratis/StratisMain.cs | 4 +- .../StratisRegTest.cs | 3 +- src/Blockcore.Networks.Stratis/StratisTest.cs | 3 +- .../Blockcore.Networks.Xds.csproj | 29 ++ .../Configuration/MemoryPoolConfiguration.cs | 53 ++++ .../Configuration/XdsMempoolValidator.cs | 228 ++++++++++++++++ .../XdsPreMempoolChecksMempoolRule.cs | 157 +++++++++++ .../Consensus/XdsBlockHeader.cs | 22 ++ .../Consensus/XdsConsensusFactory.cs | 112 ++++++++ .../Consensus/XdsConsensusOptions.cs | 50 ++++ .../Consensus/XdsProvenBlockHeader.cs | 30 ++ .../Consensus/XdsTransaction.cs | 14 + .../Deployments/XdsBIP9Deployments.cs | 54 ++++ .../Directory.Build.props | 5 + src/Blockcore.Networks.Xds/Networks.cs | 15 + .../Policies/XdsStandardScriptsRegistry.cs | 63 +++++ .../Rules/XdsCheckFeeMempoolRule.cs | 39 +++ .../Rules/XdsConsensusErrors.cs | 15 + .../Rules/XdsEmptyScriptSigMempoolRule.cs | 41 +++ .../Rules/XdsEmptyScriptSigRule.cs | 38 +++ .../Rules/XdsHeaderVersionRule.cs | 32 +++ .../XdsOutputNotWhitelistedMempoolRule.cs | 38 +++ .../Rules/XdsOutputNotWhitelistedRule.cs | 64 +++++ .../Rules/XdsPosCoinviewRule.cs | 86 ++++++ .../Rules/XdsRequireWitnessMempoolRule.cs | 32 +++ .../Rules/XdsRequireWitnessRule.cs | 28 ++ src/Blockcore.Networks.Xds/XdsMain.cs | 203 ++++++++++++++ src/Blockcore.Networks.Xds/XdsRegTest.cs | 10 + src/Blockcore.Networks.Xds/XdsTest.cs | 10 + .../ConnectionManagerControllerTest.cs | 9 +- .../Connection/ConnectionManagerTest.cs | 6 +- src/Blockcore.sln | 17 ++ .../Deployments/ThresholdConditionCache.cs | 2 +- .../ConnectionManagerController.cs | 83 ++++++ .../ConnectionManagerRPCController.cs} | 90 ++---- src/NBitcoin/Consensus.cs | 12 +- src/NBitcoin/ConsensusOptions.cs | 6 +- src/NBitcoin/Crypto/Sha512t.cs | 27 ++ src/NBitcoin/IConsensus.cs | 14 + src/NBitcoin/IStandardScriptsRegistry.cs | 7 +- src/NBitcoin/StandardScriptTemplate.cs | 256 +++++++++++------- src/NBitcoin/StandardScripts.cs | 2 +- src/NBitcoin/StandardScriptsRegistry.cs | 10 +- src/Xds/Program.cs | 52 ++++ src/Xds/Properties/AssemblyInfo.cs | 18 ++ src/Xds/Properties/launchSettings.json | 8 + src/Xds/XdsD.csproj | 37 +++ 71 files changed, 2025 insertions(+), 280 deletions(-) rename src/Blockcore.Features.Wallet/{ => Controllers}/WalletRPCController.cs (99%) create mode 100644 src/Blockcore.Networks.Xds/Blockcore.Networks.Xds.csproj create mode 100644 src/Blockcore.Networks.Xds/Configuration/MemoryPoolConfiguration.cs create mode 100644 src/Blockcore.Networks.Xds/Configuration/XdsMempoolValidator.cs create mode 100644 src/Blockcore.Networks.Xds/Configuration/XdsPreMempoolChecksMempoolRule.cs create mode 100644 src/Blockcore.Networks.Xds/Consensus/XdsBlockHeader.cs create mode 100644 src/Blockcore.Networks.Xds/Consensus/XdsConsensusFactory.cs create mode 100644 src/Blockcore.Networks.Xds/Consensus/XdsConsensusOptions.cs create mode 100644 src/Blockcore.Networks.Xds/Consensus/XdsProvenBlockHeader.cs create mode 100644 src/Blockcore.Networks.Xds/Consensus/XdsTransaction.cs create mode 100644 src/Blockcore.Networks.Xds/Deployments/XdsBIP9Deployments.cs create mode 100644 src/Blockcore.Networks.Xds/Directory.Build.props create mode 100644 src/Blockcore.Networks.Xds/Networks.cs create mode 100644 src/Blockcore.Networks.Xds/Policies/XdsStandardScriptsRegistry.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsCheckFeeMempoolRule.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsConsensusErrors.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsEmptyScriptSigMempoolRule.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsEmptyScriptSigRule.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsHeaderVersionRule.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsOutputNotWhitelistedMempoolRule.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsOutputNotWhitelistedRule.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsPosCoinviewRule.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsRequireWitnessMempoolRule.cs create mode 100644 src/Blockcore.Networks.Xds/Rules/XdsRequireWitnessRule.cs create mode 100644 src/Blockcore.Networks.Xds/XdsMain.cs create mode 100644 src/Blockcore.Networks.Xds/XdsRegTest.cs create mode 100644 src/Blockcore.Networks.Xds/XdsTest.cs create mode 100644 src/Blockcore/Controllers/ConnectionManagerController.cs rename src/Blockcore/{Connection/ConnectionManagerController.cs => Controllers/ConnectionManagerRPCController.cs} (53%) create mode 100644 src/NBitcoin/Crypto/Sha512t.cs create mode 100644 src/Xds/Program.cs create mode 100644 src/Xds/Properties/AssemblyInfo.cs create mode 100644 src/Xds/Properties/launchSettings.json create mode 100644 src/Xds/XdsD.csproj diff --git a/src/Blockcore.Features.ColdStaking.Tests/ColdStakingControllerTest.cs b/src/Blockcore.Features.ColdStaking.Tests/ColdStakingControllerTest.cs index 19ae21f5f..a8bf64c0a 100644 --- a/src/Blockcore.Features.ColdStaking.Tests/ColdStakingControllerTest.cs +++ b/src/Blockcore.Features.ColdStaking.Tests/ColdStakingControllerTest.cs @@ -108,7 +108,7 @@ private void MockStakeChain() { Flags = BlockFlag.BLOCK_PROOF_OF_STAKE, StakeModifierV2 = 0, - StakeTime = (this.chainIndexer.Tip.Header.Time + 60) & ~PosConsensusOptions.StakeTimestampMask + StakeTime = (this.chainIndexer.Tip.Header.Time + 60) & ~this.Network.Consensus.ProofOfStakeTimestampMask }); } diff --git a/src/Blockcore.Features.ColdStaking/ColdStakingManager.cs b/src/Blockcore.Features.ColdStaking/ColdStakingManager.cs index 85d086647..988ccbc4f 100644 --- a/src/Blockcore.Features.ColdStaking/ColdStakingManager.cs +++ b/src/Blockcore.Features.ColdStaking/ColdStakingManager.cs @@ -360,7 +360,7 @@ internal Transaction GetColdStakingSetupTransaction(IWalletTransactionHandler wa coldPubKeyHash = new BitcoinWitPubKeyAddress(coldWalletAddress, wallet.Network).Hash.AsKeyId(); destination = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKeyHash, coldPubKeyHash); - if(payToScript) + if (payToScript) { HdAddress address = coldAddress ?? hotAddress; address.RedeemScript = destination; @@ -382,7 +382,7 @@ internal Transaction GetColdStakingSetupTransaction(IWalletTransactionHandler wa } // Only normal accounts should be allowed. - if (!this.GetAccounts(walletName).Any(a => a.Name == walletAccount)) + if (this.GetAccounts(walletName).All(a => a.Name != walletAccount)) { this.logger.LogTrace("(-)[COLDSTAKE_ACCOUNT_NOT_FOUND]"); throw new WalletException($"Can't find wallet account '{walletAccount}'."); @@ -403,20 +403,22 @@ internal Transaction GetColdStakingSetupTransaction(IWalletTransactionHandler wa { // In the case of P2SH and P2WSH, to avoid the possibility of lose of funds // we add an opreturn with the hot and cold key hashes to the setup transaction - // this will allow a user to recreate the redeem script of the output in case they lose - // access to one of the keys. - // The special marker will help a wallet that is tracking cold staking accounts to monitor - // the hot and cold keys, if a special marker is found then the keys are in the opreturn are checked + // this will allow a user to recreate the redeem script of the output in case they lose + // access to one of the keys. + // The special marker will help a wallet that is tracking cold staking accounts to monitor + // the hot and cold keys, if a special marker is found then the keys are in the opreturn are checked // against the current wallet, if found and validated the wallet will track that ScriptPubKey var opreturnKeys = new List(); opreturnKeys.AddRange(hotPubKeyHash.ToBytes()); opreturnKeys.AddRange(coldPubKeyHash.ToBytes()); - + context.OpReturnRawData = opreturnKeys.ToArray(); - //context.OpReturnAmount = Money.Satoshis(1); // mandatory fee must be paid. - - // The P2SH and P2WSH hide the cold stake keys in the script hash so the wallet cannot track + var template = this.network.StandardScriptsRegistry.GetScriptTemplates.OfType().First(); + if (template.MinRequiredSatoshiFee > 0) + context.OpReturnAmount = Money.Satoshis(template.MinRequiredSatoshiFee); // mandatory fee must be paid. + + // The P2SH and P2WSH hide the cold stake keys in the script hash so the wallet cannot track // the ouputs based on the derived keys when the trx is subbmited to the network. // So we add the output script manually. } @@ -611,7 +613,7 @@ public override void TransactionFoundInternal(Script script, Func /// The purpose of this method is to try to identify the P2SH and P2WSH that are coldstake outputs for this wallet /// We look for an opreturn script that is created when seting up a P2SH and P2WSH cold stake trx - /// if we find any then try to find the keys and track the script before calling in to the main wallet. + /// if we find any then try to find the keys and track the script before calling in to the main wallet. /// /// public override bool ProcessTransaction(Transaction transaction, int? blockHeight = null, Block block = null, bool isPropagated = true) @@ -628,7 +630,7 @@ public override bool ProcessTransaction(Transaction transaction, int? blockHeigh if (data[0].Length == 40) { HdAddress address = null; - + Span span = data[0].AsSpan(); var hotPubKey = new KeyId(span.Slice(0, 20).ToArray()); var coldPubKey = new KeyId(span.Slice(20, 20).ToArray()); @@ -642,11 +644,11 @@ public override bool ProcessTransaction(Transaction transaction, int? blockHeigh // Find the type of script for the opreturn (P2SH or P2WSH) foreach (TxOut utxoInner in transaction.Outputs) { - if(utxoInner.ScriptPubKey == destination.Hash.ScriptPubKey) + if (utxoInner.ScriptPubKey == destination.Hash.ScriptPubKey) { - if (!this.scriptToAddressLookup.TryGetValue(destination.Hash.ScriptPubKey,out HdAddress _)) + if (!this.scriptToAddressLookup.TryGetValue(destination.Hash.ScriptPubKey, out HdAddress _)) this.scriptToAddressLookup[destination.Hash.ScriptPubKey] = address; - + break; } @@ -673,9 +675,9 @@ protected override void AddAddressToIndex(HdAddress address) { base.AddAddressToIndex(address); - if(address.RedeemScript != null) + if (address.RedeemScript != null) { - // The redeem script has no indication on the script type (P2SH or P2WSH), + // The redeem script has no indication on the script type (P2SH or P2WSH), // so we track both, add both to the indexer then. if (!this.scriptToAddressLookup.TryGetValue(address.RedeemScript.Hash.ScriptPubKey, out HdAddress _)) @@ -686,4 +688,4 @@ protected override void AddAddressToIndex(HdAddress address) } } } -} +} \ No newline at end of file diff --git a/src/Blockcore.Features.ColdStaking/Controllers/ColdStakingWalletRPCController.cs b/src/Blockcore.Features.ColdStaking/Controllers/ColdStakingWalletRPCController.cs index f6080f90d..83841bfd7 100644 --- a/src/Blockcore.Features.ColdStaking/Controllers/ColdStakingWalletRPCController.cs +++ b/src/Blockcore.Features.ColdStaking/Controllers/ColdStakingWalletRPCController.cs @@ -1,6 +1,7 @@ using Blockcore.Consensus; using Blockcore.Features.BlockStore; using Blockcore.Features.Wallet; +using Blockcore.Features.Wallet.Controllers; using Blockcore.Features.Wallet.Interfaces; using Blockcore.Interfaces; using Microsoft.AspNetCore.Mvc; diff --git a/src/Blockcore.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs b/src/Blockcore.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs index 47f805822..9ab22328f 100644 --- a/src/Blockcore.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs +++ b/src/Blockcore.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs @@ -68,7 +68,7 @@ private async Task CreateConsensusManagerAsync(Dictionary().Object, initialBlockDownloadState, this.ChainIndexer, new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, this.consensusSettings, this.dateTimeProvider.Object); - // Mock the coinviews "FetchCoinsAsync" method. We will use the "unspentOutputs" dictionary to track spendable outputs. + // Mock the coinviews "FetchCoinsAsync" method. We will use the "unspentOutputs" dictionary to track spendable outputs. this.coinView.Setup(d => d.FetchCoins(It.IsAny())) .Returns((OutPoint[] txIds) => { @@ -106,7 +106,7 @@ private async Task CreateConsensusManagerAsync(Dictionarytrue if block timestamp is equal to transaction timestamp, false otherwise. private bool CheckCoinStakeTimestamp(long blockTime) { - return (blockTime & PosConsensusOptions.StakeTimestampMask) == 0; + return (blockTime & this.Parent.Network.Consensus.ProofOfStakeTimestampMask) == 0; } } } \ No newline at end of file diff --git a/src/Blockcore.Features.Consensus/Rules/ProvenHeaderRules/ProvenHeaderCoinstakeRule.cs b/src/Blockcore.Features.Consensus/Rules/ProvenHeaderRules/ProvenHeaderCoinstakeRule.cs index afeee029a..da11a2a47 100644 --- a/src/Blockcore.Features.Consensus/Rules/ProvenHeaderRules/ProvenHeaderCoinstakeRule.cs +++ b/src/Blockcore.Features.Consensus/Rules/ProvenHeaderRules/ProvenHeaderCoinstakeRule.cs @@ -178,7 +178,7 @@ private void CheckHeaderAndCoinstakeTimes(ProvenBlockHeader header) } } - if ((header.Time & PosConsensusOptions.StakeTimestampMask) != 0) + if ((header.Time & this.Parent.Network.Consensus.ProofOfStakeTimestampMask) != 0) { this.Logger.LogTrace("(-)[BAD_TIME]"); ConsensusErrors.StakeTimeViolation.Throw(); diff --git a/src/Blockcore.Features.Consensus/Rules/UtxosetRules/CheckPosUtxosetRule.cs b/src/Blockcore.Features.Consensus/Rules/UtxosetRules/CheckPosUtxosetRule.cs index c807cb0f9..2a079c594 100644 --- a/src/Blockcore.Features.Consensus/Rules/UtxosetRules/CheckPosUtxosetRule.cs +++ b/src/Blockcore.Features.Consensus/Rules/UtxosetRules/CheckPosUtxosetRule.cs @@ -12,7 +12,7 @@ namespace Blockcore.Features.Consensus.Rules.UtxosetRules /// /// Proof of stake override for the coinview rules - BIP68, MaxSigOps and BlockReward checks. /// - public sealed class CheckPosUtxosetRule : CheckUtxosetRule + public class CheckPosUtxosetRule : CheckUtxosetRule { /// Provides functionality for checking validity of PoS blocks. private IStakeValidator stakeValidator; @@ -21,7 +21,7 @@ public sealed class CheckPosUtxosetRule : CheckUtxosetRule private IStakeChain stakeChain; /// The consensus of the parent Network. - private IConsensus consensus; + protected IConsensus consensus; /// public override void Initialize() @@ -203,7 +203,7 @@ public override Money GetProofOfWorkReward(int height) /// /// Target block height. /// Miner's coin stake reward. - public Money GetProofOfStakeReward(int height) + public virtual Money GetProofOfStakeReward(int height) { if (this.IsPremine(height)) return this.consensus.PremineReward; diff --git a/src/Blockcore.Features.Consensus/StakeValidator.cs b/src/Blockcore.Features.Consensus/StakeValidator.cs index ec14b0c54..94d9d81bc 100644 --- a/src/Blockcore.Features.Consensus/StakeValidator.cs +++ b/src/Blockcore.Features.Consensus/StakeValidator.cs @@ -167,7 +167,7 @@ public Target GetNextTargetRequired(IStakeChain stakeChain, ChainedHeader chaine } // This is used in tests to allow quickly mining blocks. - if (!proofOfStake && consensus.PowNoRetargeting) + if (!proofOfStake && consensus.PowNoRetargeting) { this.logger.LogTrace("(-)[NO_POW_RETARGET]:'{0}'", lastPowPosBlock.Header.Bits); return lastPowPosBlock.Header.Bits; @@ -308,11 +308,6 @@ public bool CheckStakeKernelHash(PosRuleContext context, uint headerBits, uint25 // Base target. BigInteger target = new Target(headerBits).ToBigInteger(); - // TODO: Investigate: - // The POS protocol should probably put a limit on the max amount that can be staked - // not a hard limit but a limit that allow any amount to be staked with a max weight value. - // the max weight should not exceed the max uint256 array size (array size = 32). - // Weighted target. long valueIn = stakingCoins.Coins.TxOut.Value.Satoshi; BigInteger weight = BigInteger.ValueOf(valueIn); @@ -326,7 +321,8 @@ public bool CheckStakeKernelHash(PosRuleContext context, uint headerBits, uint25 { var serializer = new BitcoinStream(ms, true); serializer.ReadWrite(prevStakeModifier); - serializer.ReadWrite(stakingCoins.Coins.Time); + if (this.network.Consensus.PosUseTimeFieldInKernalHash) // old posv3 time field + serializer.ReadWrite(stakingCoins.Coins.Time); serializer.ReadWrite(prevout.Hash); serializer.ReadWrite(prevout.N); serializer.ReadWrite(transactionTime); @@ -358,13 +354,6 @@ public bool VerifySignature(UnspentOutput coin, Transaction txTo, int txToInN, S TxIn input = txTo.Inputs[txToInN]; - //if (input.PrevOut.N >= coin.Outputs.Length) - //{ - // this.logger.LogTrace("(-)[OUTPUT_INCORRECT_LENGTH]"); - // return false; - //} - - //if (input.PrevOut.Hash != coin.TransactionId) if (input.PrevOut.Hash != coin.OutPoint.Hash) { this.logger.LogTrace("(-)[INCORRECT_TX]"); @@ -486,5 +475,4 @@ public bool CheckStakeSignature(BlockSignature signature, uint256 blockHash, Tra return verifyRes; } } -} - +} \ No newline at end of file diff --git a/src/Blockcore.Features.MemoryPool/MempoolPersistence.cs b/src/Blockcore.Features.MemoryPool/MempoolPersistence.cs index 5df4fd85b..21ffb433d 100644 --- a/src/Blockcore.Features.MemoryPool/MempoolPersistence.cs +++ b/src/Blockcore.Features.MemoryPool/MempoolPersistence.cs @@ -158,7 +158,7 @@ public override int GetHashCode() /// /// Object used for persisting memory pool transactions. /// - internal class MempoolPersistence : IMempoolPersistence + public class MempoolPersistence : IMempoolPersistence { /// Current memory pool version number for persistence. public const ulong MempoolDumpVersion = 0; @@ -328,4 +328,4 @@ internal IEnumerable LoadFromStream(Network network, St return toReturn; } } -} +} \ No newline at end of file diff --git a/src/Blockcore.Features.MemoryPool/MempoolValidator.cs b/src/Blockcore.Features.MemoryPool/MempoolValidator.cs index cd66355de..5a99990aa 100644 --- a/src/Blockcore.Features.MemoryPool/MempoolValidator.cs +++ b/src/Blockcore.Features.MemoryPool/MempoolValidator.cs @@ -76,7 +76,7 @@ public class MempoolValidator : IMempoolValidator public const bool DefaultPermitBareMultisig = true; /// Maximum age of our tip in seconds for us to be considered current for fee estimation. - private const int MaxFeeEstimationTipAge = 3 * 60 * 60; + public const int MaxFeeEstimationTipAge = 3 * 60 * 60; /// A lock for managing asynchronous access to memory pool. private readonly MempoolSchedulerLock mempoolLock; diff --git a/src/Blockcore.Features.Miner/Staking/PosMinting.cs b/src/Blockcore.Features.Miner/Staking/PosMinting.cs index 7c3221774..eea26dba4 100644 --- a/src/Blockcore.Features.Miner/Staking/PosMinting.cs +++ b/src/Blockcore.Features.Miner/Staking/PosMinting.cs @@ -66,7 +66,6 @@ namespace Blockcore.Features.Miner.Staking /// public class PosMinting : IPosMinting { - /// /// Indicates the current state: idle, staking requested, staking in progress and stop staking requested. /// @@ -133,6 +132,7 @@ public enum CurrentState /// Factory for creating loggers. private readonly ILoggerFactory loggerFactory; + private readonly MinerSettings minerSettings; /// Instance logger. @@ -401,7 +401,7 @@ public async Task GenerateBlocksAsync(WalletSecret walletSecret, CancellationTok blockTemplate = null; } - uint coinstakeTimestamp = (uint)this.dateTimeProvider.GetAdjustedTimeAsUnixTimestamp() & ~PosConsensusOptions.StakeTimestampMask; + uint coinstakeTimestamp = (uint)this.dateTimeProvider.GetAdjustedTimeAsUnixTimestamp() & ~this.network.Consensus.ProofOfStakeTimestampMask; if (coinstakeTimestamp <= this.lastCoinStakeSearchTime) { this.logger.LogDebug("Current coinstake time {0} is not greater than last search timestamp {1}.", coinstakeTimestamp, this.lastCoinStakeSearchTime); @@ -653,7 +653,7 @@ public async Task CreateCoinstakeAsync(List utxoStak // If the time after applying the mask is lower than minimal allowed time, // it is simply too early for us to mine, there can't be any valid solution. - if ((coinstakeContext.StakeTimeSlot & ~PosConsensusOptions.StakeTimestampMask) < minimalAllowedTime) + if ((coinstakeContext.StakeTimeSlot & ~this.network.Consensus.ProofOfStakeTimestampMask) < minimalAllowedTime) { this.logger.LogTrace("(-)[TOO_EARLY_TIME_AFTER_LAST_BLOCK]:false"); return false; @@ -735,7 +735,7 @@ await Task.Run(() => Parallel.ForEach(workerContexts, cwc => int eventuallyStakableUtxosCount = utxoStakeDescriptions.Count; Transaction coinstakeTx = this.PrepareCoinStakeTransactions(chainTip.Height, coinstakeContext, coinstakeOutputValue, eventuallyStakableUtxosCount, ourWeight); - if(coinstakeTx is IPosTransactionWithTime posTrxn) + if (coinstakeTx is IPosTransactionWithTime posTrxn) { posTrxn.Time = coinstakeContext.StakeTimeSlot; } @@ -853,7 +853,7 @@ private void CoinstakeWorker(CoinstakeWorkerContext context, ChainedHeader chain if (txTime < minimalAllowedTime) break; - if ((txTime & PosConsensusOptions.StakeTimestampMask) != 0) + if ((txTime & this.network.Consensus.ProofOfStakeTimestampMask) != 0) continue; context.Logger.LogDebug("Trying with transaction time {0}.", txTime); @@ -902,7 +902,7 @@ private void CoinstakeWorker(CoinstakeWorkerContext context, ChainedHeader chain context.CoinstakeContext.CoinstakeTx.Outputs.Add(new TxOut(0, scriptPubKeyOut)); context.Result.KernelCoin = utxoStakeInfo; - + context.Logger.LogDebug("Kernel accepted, coinstake input is '{0}', stopping work.", prevoutStake); } else context.Logger.LogDebug("Kernel found, but worker #{0} announced its kernel earlier, stopping work.", context.Result.KernelFoundIndex); @@ -942,9 +942,9 @@ private bool SignTransactionInput(UtxoStakeDescription input, Transaction transa { if (input.Address.RedeemScript == null) throw new MinerException("Redeem script does not match output"); - + var scriptCoin = ScriptCoin.Create(this.network, input.OutPoint, input.TxOut, input.Address.RedeemScript); - + transactionBuilder.AddCoins(scriptCoin); } else @@ -1177,7 +1177,7 @@ public double GetNetworkWeight() if (stakesTime != 0) res = stakeKernelsAvg / stakesTime; - res *= PosConsensusOptions.StakeTimestampMask + 1; + res *= this.network.Consensus.ProofOfStakeTimestampMask + 1; return res; } @@ -1227,4 +1227,4 @@ internal bool ShouldSplitStake(int stakedUtxosCount, long amountStaked, long coi return shouldSplitCoin; } } -} +} \ No newline at end of file diff --git a/src/Blockcore.Features.PoA/PoANetwork.cs b/src/Blockcore.Features.PoA/PoANetwork.cs index 56776c116..608df8fff 100644 --- a/src/Blockcore.Features.PoA/PoANetwork.cs +++ b/src/Blockcore.Features.PoA/PoANetwork.cs @@ -135,7 +135,8 @@ public PoANetwork() lastPowBlock: 0, proofOfStakeLimit: null, proofOfStakeLimitV2: null, - proofOfStakeReward: Money.Zero + proofOfStakeReward: Money.Zero, + proofOfStakeTimestampMask: 0 ); // https://en.bitcoin.it/wiki/List_of_address_prefixes diff --git a/src/Blockcore.Features.PoA/Policies/PoAStandardScriptsRegistry.cs b/src/Blockcore.Features.PoA/Policies/PoAStandardScriptsRegistry.cs index 037cd11db..c68be5f33 100644 --- a/src/Blockcore.Features.PoA/Policies/PoAStandardScriptsRegistry.cs +++ b/src/Blockcore.Features.PoA/Policies/PoAStandardScriptsRegistry.cs @@ -23,6 +23,8 @@ public class PoAStandardScriptsRegistry : StandardScriptsRegistry PayToWitTemplate.Instance }; + public override List GetScriptTemplates => this.standardTemplates; + public override void RegisterStandardScriptTemplate(ScriptTemplate scriptTemplate) { if (!this.standardTemplates.Any(template => (template.Type == scriptTemplate.Type))) @@ -56,4 +58,4 @@ public override bool AreInputsStandard(Network network, Transaction tx, CoinsVie return base.AreInputsStandard(network, tx, coinsView); } } -} +} \ No newline at end of file diff --git a/src/Blockcore.Features.RPC.Tests/Controller/WalletRPCControllerTests.cs b/src/Blockcore.Features.RPC.Tests/Controller/WalletRPCControllerTests.cs index 58f9926e5..6bf4cdfc7 100644 --- a/src/Blockcore.Features.RPC.Tests/Controller/WalletRPCControllerTests.cs +++ b/src/Blockcore.Features.RPC.Tests/Controller/WalletRPCControllerTests.cs @@ -4,6 +4,7 @@ using Blockcore.Features.BlockStore; using Blockcore.Features.RPC.Exceptions; using Blockcore.Features.Wallet; +using Blockcore.Features.Wallet.Controllers; using Blockcore.Features.Wallet.Interfaces; using Blockcore.Features.Wallet.Models; using Blockcore.Interfaces; @@ -49,15 +50,15 @@ public WalletRPCControllerTests() this.walletSettings = new WalletSettings(this.nodeSettings); this.walletTransactionHandler = new Mock(); - this.controller = + this.controller = new WalletRPCController( - this.blockStore.Object, - this.broadCastManager.Object, - this.chain, - this.consensusManager.Object, - this.fullNode.Object, + this.blockStore.Object, + this.broadCastManager.Object, + this.chain, + this.consensusManager.Object, + this.fullNode.Object, this.LoggerFactory.Object, - this.network, + this.network, this.scriptAddressReader.Object, this.storeSettings, this.walletManager.Object, @@ -114,4 +115,4 @@ public void GetUnusedAddress_WithIncompatibleAddressType_ThrowsException() Assert.Equal("Only address type 'legacy' is currently supported.", exception.Message); } } -} +} \ No newline at end of file diff --git a/src/Blockcore.Features.RPC.Tests/RPCParametersValueProviderTest.cs b/src/Blockcore.Features.RPC.Tests/RPCParametersValueProviderTest.cs index 618402980..99a4d1474 100644 --- a/src/Blockcore.Features.RPC.Tests/RPCParametersValueProviderTest.cs +++ b/src/Blockcore.Features.RPC.Tests/RPCParametersValueProviderTest.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Threading.Tasks; using Blockcore.Features.Wallet; +using Blockcore.Features.Wallet.Controllers; using FluentAssertions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -205,4 +206,4 @@ public void GetValueReturnsNullIfNotExists() Assert.Null(result.FirstValue); } } -} +} \ No newline at end of file diff --git a/src/Blockcore.Features.Wallet/WalletRPCController.cs b/src/Blockcore.Features.Wallet/Controllers/WalletRPCController.cs similarity index 99% rename from src/Blockcore.Features.Wallet/WalletRPCController.cs rename to src/Blockcore.Features.Wallet/Controllers/WalletRPCController.cs index 0ae37f61b..36a6abf91 100644 --- a/src/Blockcore.Features.Wallet/WalletRPCController.cs +++ b/src/Blockcore.Features.Wallet/Controllers/WalletRPCController.cs @@ -18,7 +18,7 @@ using NBitcoin; using Newtonsoft.Json; -namespace Blockcore.Features.Wallet +namespace Blockcore.Features.Wallet.Controllers { public class WalletRPCController : FeatureController { diff --git a/src/Blockcore.IntegrationTests/RPC/AddNodeActionTest.cs b/src/Blockcore.IntegrationTests/RPC/AddNodeActionTest.cs index 5e5bd55b8..1d396a15e 100644 --- a/src/Blockcore.IntegrationTests/RPC/AddNodeActionTest.cs +++ b/src/Blockcore.IntegrationTests/RPC/AddNodeActionTest.cs @@ -1,6 +1,7 @@ using System; using System.Net.Sockets; using Blockcore.Connection; +using Blockcore.Controllers; using Blockcore.IntegrationTests.Common.Extensions; using Xunit; @@ -17,12 +18,12 @@ public void CanCall_AddNode() IFullNode fullNode = this.BuildServicedNode(testDirectory); fullNode.Start(); - var controller = fullNode.NodeController(); + var controller = fullNode.NodeController(); - Assert.True(controller.AddNodeRPC("0.0.0.0", "add")); - Assert.Throws(() => { controller.AddNodeRPC("0.0.0.0", "notarealcommand"); }); - Assert.ThrowsAny(() => { controller.AddNodeRPC("a.b.c.d", "onetry"); }); - Assert.True(controller.AddNodeRPC("0.0.0.0", "remove")); + Assert.True(controller.AddNode("0.0.0.0", "add")); + Assert.Throws(() => { controller.AddNode("0.0.0.0", "notarealcommand"); }); + Assert.ThrowsAny(() => { controller.AddNode("a.b.c.d", "onetry"); }); + Assert.True(controller.AddNode("0.0.0.0", "remove")); } [Fact] @@ -32,10 +33,10 @@ public void CanCall_AddNode_AddsNodeToCollection() IFullNode fullNode = this.BuildServicedNode(testDirectory); - var controller = fullNode.NodeController(); + var controller = fullNode.NodeController(); var connectionManager = fullNode.NodeService(); - controller.AddNodeRPC("0.0.0.0", "add"); + controller.AddNode("0.0.0.0", "add"); Assert.Single(connectionManager.ConnectionSettings.RetrieveAddNodes()); } } diff --git a/src/Blockcore.Networks.Bitcoin/BitcoinMain.cs b/src/Blockcore.Networks.Bitcoin/BitcoinMain.cs index e71f78d61..31bfbd5e9 100644 --- a/src/Blockcore.Networks.Bitcoin/BitcoinMain.cs +++ b/src/Blockcore.Networks.Bitcoin/BitcoinMain.cs @@ -97,7 +97,8 @@ public BitcoinMain() lastPowBlock: default(int), proofOfStakeLimit: null, proofOfStakeLimitV2: null, - proofOfStakeReward: Money.Zero + proofOfStakeReward: Money.Zero, + proofOfStakeTimestampMask: 0 ); this.Base58Prefixes = new byte[12][]; diff --git a/src/Blockcore.Networks.Bitcoin/BitcoinRegTest.cs b/src/Blockcore.Networks.Bitcoin/BitcoinRegTest.cs index fae7981cf..faaf8fded 100644 --- a/src/Blockcore.Networks.Bitcoin/BitcoinRegTest.cs +++ b/src/Blockcore.Networks.Bitcoin/BitcoinRegTest.cs @@ -46,7 +46,7 @@ public BitcoinRegTest() var bip9Deployments = new BitcoinBIP9Deployments { [BitcoinBIP9Deployments.TestDummy] = new BIP9DeploymentsParameters("TestDummy", 28, 0, 999999999, BIP9DeploymentsParameters.DefaultRegTestThreshold), - [BitcoinBIP9Deployments.CSV] = new BIP9DeploymentsParameters("CSV",0, 0, 999999999, BIP9DeploymentsParameters.DefaultRegTestThreshold), + [BitcoinBIP9Deployments.CSV] = new BIP9DeploymentsParameters("CSV", 0, 0, 999999999, BIP9DeploymentsParameters.DefaultRegTestThreshold), [BitcoinBIP9Deployments.Segwit] = new BIP9DeploymentsParameters("Segwit", 1, BIP9DeploymentsParameters.AlwaysActive, 999999999, BIP9DeploymentsParameters.AlwaysActive) }; @@ -81,7 +81,8 @@ public BitcoinRegTest() lastPowBlock: default(int), proofOfStakeLimit: null, proofOfStakeLimitV2: null, - proofOfStakeReward: Money.Zero + proofOfStakeReward: Money.Zero, + proofOfStakeTimestampMask: 0 ); this.Base58Prefixes[(int)Base58Type.PUBKEY_ADDRESS] = new byte[] { (111) }; diff --git a/src/Blockcore.Networks.Bitcoin/BitcoinTest.cs b/src/Blockcore.Networks.Bitcoin/BitcoinTest.cs index 98ea7bc62..a8b2a077d 100644 --- a/src/Blockcore.Networks.Bitcoin/BitcoinTest.cs +++ b/src/Blockcore.Networks.Bitcoin/BitcoinTest.cs @@ -82,7 +82,8 @@ public BitcoinTest() lastPowBlock: default(int), proofOfStakeLimit: null, proofOfStakeLimitV2: null, - proofOfStakeReward: Money.Zero + proofOfStakeReward: Money.Zero, + proofOfStakeTimestampMask: 0 ); this.Base58Prefixes[(int)Base58Type.PUBKEY_ADDRESS] = new byte[] { (111) }; diff --git a/src/Blockcore.Networks.Bitcoin/Blockcore.Networks.Bitcoin.csproj b/src/Blockcore.Networks.Bitcoin/Blockcore.Networks.Bitcoin.csproj index c14253b08..e35b1cfee 100644 --- a/src/Blockcore.Networks.Bitcoin/Blockcore.Networks.Bitcoin.csproj +++ b/src/Blockcore.Networks.Bitcoin/Blockcore.Networks.Bitcoin.csproj @@ -2,7 +2,7 @@ Bitcoin FullNode - Blockcore.Networks + Blockcore.Networks.Bitcoin netcoreapp3.1 3.1.0 Blockcore.Networks.Bitcoin diff --git a/src/Blockcore.Networks.Bitcoin/Policies/BitcoinStandardScriptsRegistry.cs b/src/Blockcore.Networks.Bitcoin/Policies/BitcoinStandardScriptsRegistry.cs index 0417aa930..28cdda451 100644 --- a/src/Blockcore.Networks.Bitcoin/Policies/BitcoinStandardScriptsRegistry.cs +++ b/src/Blockcore.Networks.Bitcoin/Policies/BitcoinStandardScriptsRegistry.cs @@ -25,6 +25,8 @@ public class BitcoinStandardScriptsRegistry : StandardScriptsRegistry PayToWitTemplate.Instance }; + public override List GetScriptTemplates => this.standardTemplates; + public override void RegisterStandardScriptTemplate(ScriptTemplate scriptTemplate) { if (!this.standardTemplates.Any(template => (template.Type == scriptTemplate.Type))) diff --git a/src/Blockcore.Networks.Stratis/Blockcore.Networks.Stratis.csproj b/src/Blockcore.Networks.Stratis/Blockcore.Networks.Stratis.csproj index e33b27006..a754f2111 100644 --- a/src/Blockcore.Networks.Stratis/Blockcore.Networks.Stratis.csproj +++ b/src/Blockcore.Networks.Stratis/Blockcore.Networks.Stratis.csproj @@ -2,7 +2,7 @@ Bitcoin FullNode - Blockcore.Networks + Blockcore.Networks.Stratis netcoreapp3.1 3.1.0 Blockcore.Networks.Stratis diff --git a/src/Blockcore.Networks.Stratis/Policies/StratisStandardScriptsRegistry.cs b/src/Blockcore.Networks.Stratis/Policies/StratisStandardScriptsRegistry.cs index c94e7ba7e..0baee6018 100644 --- a/src/Blockcore.Networks.Stratis/Policies/StratisStandardScriptsRegistry.cs +++ b/src/Blockcore.Networks.Stratis/Policies/StratisStandardScriptsRegistry.cs @@ -24,7 +24,7 @@ public class StratisStandardScriptsRegistry : StandardScriptsRegistry PayToWitTemplate.Instance }; - public List GetScriptTemplates => this.standardTemplates; + public override List GetScriptTemplates => this.standardTemplates; public override void RegisterStandardScriptTemplate(ScriptTemplate scriptTemplate) { @@ -59,4 +59,4 @@ public override bool AreInputsStandard(Network network, Transaction tx, CoinsVie return base.AreInputsStandard(network, tx, coinsView); } } -} +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Stratis/StratisMain.cs b/src/Blockcore.Networks.Stratis/StratisMain.cs index 50d7b60da..ca8fe424c 100644 --- a/src/Blockcore.Networks.Stratis/StratisMain.cs +++ b/src/Blockcore.Networks.Stratis/StratisMain.cs @@ -144,10 +144,12 @@ public StratisMain() lastPowBlock: 12500, proofOfStakeLimit: new BigInteger(uint256.Parse("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), proofOfStakeLimitV2: new BigInteger(uint256.Parse("000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), - proofOfStakeReward: Money.COIN + proofOfStakeReward: Money.COIN, + proofOfStakeTimestampMask: 0x0000000F // 16 sec ); this.Consensus.PosEmptyCoinbase = true; + this.Consensus.PosUseTimeFieldInKernalHash = true; this.Base58Prefixes = new byte[12][]; this.Base58Prefixes[(int)Base58Type.PUBKEY_ADDRESS] = new byte[] { (63) }; diff --git a/src/Blockcore.Networks.Stratis/StratisRegTest.cs b/src/Blockcore.Networks.Stratis/StratisRegTest.cs index eceeb6eee..55a32d1f9 100644 --- a/src/Blockcore.Networks.Stratis/StratisRegTest.cs +++ b/src/Blockcore.Networks.Stratis/StratisRegTest.cs @@ -107,7 +107,8 @@ public StratisRegTest() lastPowBlock: 12500, proofOfStakeLimit: new BigInteger(uint256.Parse("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), proofOfStakeLimitV2: new BigInteger(uint256.Parse("000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), - proofOfStakeReward: Money.COIN + proofOfStakeReward: Money.COIN, + proofOfStakeTimestampMask: 0x0000000F ); this.Consensus.PosEmptyCoinbase = true; diff --git a/src/Blockcore.Networks.Stratis/StratisTest.cs b/src/Blockcore.Networks.Stratis/StratisTest.cs index 4d4e3a6f2..6d4868137 100644 --- a/src/Blockcore.Networks.Stratis/StratisTest.cs +++ b/src/Blockcore.Networks.Stratis/StratisTest.cs @@ -126,7 +126,8 @@ public StratisTest() lastPowBlock: 12500, proofOfStakeLimit: new BigInteger(uint256.Parse("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), proofOfStakeLimitV2: new BigInteger(uint256.Parse("000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffff").ToBytes(false)), - proofOfStakeReward: Money.COIN + proofOfStakeReward: Money.COIN, + proofOfStakeTimestampMask: 0x0000000F ); this.Consensus.PosEmptyCoinbase = true; diff --git a/src/Blockcore.Networks.Xds/Blockcore.Networks.Xds.csproj b/src/Blockcore.Networks.Xds/Blockcore.Networks.Xds.csproj new file mode 100644 index 000000000..9045ae972 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Blockcore.Networks.Xds.csproj @@ -0,0 +1,29 @@ + + + + Bitcoin FullNode + Blockcore.Networks.Xds + netcoreapp3.1 + 3.1.0 + Blockcore.Networks.Xds + Blockcore.Networks.Xds + false + false + false + false + false + false + false + false + 1.0.0 + False + Stratis Group Ltd. and Blockcore Developers + MIT + + + + + + + + \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Configuration/MemoryPoolConfiguration.cs b/src/Blockcore.Networks.Xds/Configuration/MemoryPoolConfiguration.cs new file mode 100644 index 000000000..537c88cb8 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Configuration/MemoryPoolConfiguration.cs @@ -0,0 +1,53 @@ +using Blockcore.Builder; +using Blockcore.Configuration.Logging; +using Blockcore.Features.Consensus; +using Blockcore.Features.MemoryPool; +using Blockcore.Features.MemoryPool.Fee; +using Blockcore.Features.MemoryPool.Interfaces; +using Blockcore.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace Blockcore.Networks.Xds.Configuration +{ + public static class MemoryPoolConfiguration + { + /// + /// Include the memory pool feature and related services in the full node. + /// + /// Full node builder. + /// Full node builder. + public static IFullNodeBuilder UseObsidianXMempool(this IFullNodeBuilder fullNodeBuilder) + { + LoggingConfiguration.RegisterFeatureNamespace("mempool"); + LoggingConfiguration.RegisterFeatureNamespace("estimatefee"); + + fullNodeBuilder.ConfigureFeature(features => + { + features + .AddFeature() + .DependOn() + .FeatureServices(services => + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton() + .AddSingleton(provider => provider.GetService()) + .AddSingleton(provider => provider.GetService()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + foreach (var ruleType in fullNodeBuilder.Network.Consensus.MempoolRules) + services.AddSingleton(typeof(IMempoolRule), ruleType); + }); + }); + + return fullNodeBuilder; + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Configuration/XdsMempoolValidator.cs b/src/Blockcore.Networks.Xds/Configuration/XdsMempoolValidator.cs new file mode 100644 index 000000000..524f6eee3 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Configuration/XdsMempoolValidator.cs @@ -0,0 +1,228 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blockcore.Configuration; +using Blockcore.Consensus; +using Blockcore.Features.Consensus.CoinViews; +using Blockcore.Features.MemoryPool; +using Blockcore.Features.MemoryPool.Interfaces; +using Blockcore.Utilities; +using Microsoft.Extensions.Logging; +using NBitcoin; + +namespace Blockcore.Networks.Xds.Configuration +{ + public class XdsMempoolValidator : IMempoolValidator + { + private readonly Network network; + private readonly ITxMempool txMemPool; + private readonly MempoolSchedulerLock mempoolLock; + private readonly IDateTimeProvider dateTimeProvider; + private readonly MempoolSettings mempoolSettings; + private readonly ChainIndexer chainIndexer; + private readonly ICoinView coinView; + private readonly IEnumerable mempoolRules; + private readonly ILogger logger; + private readonly FeeRate minRelayTxFee; + + public XdsMempoolValidator( + Network network, + ITxMempool txMemPool, + MempoolSchedulerLock mempoolLock, + IDateTimeProvider dateTimeProvider, + MempoolSettings mempoolSettings, + ChainIndexer chainIndexer, + ICoinView coinView, + ILoggerFactory loggerFactory, + NodeSettings nodeSettings, + IEnumerable mempoolRules) + { + this.network = network; + this.txMemPool = txMemPool; + this.mempoolLock = mempoolLock; + this.dateTimeProvider = dateTimeProvider; + this.mempoolSettings = mempoolSettings; + this.chainIndexer = chainIndexer; + this.network = chainIndexer.Network; + this.coinView = coinView; + this.logger = loggerFactory.CreateLogger(GetType().FullName); + this.PerformanceCounter = new MempoolPerformanceCounter(this.dateTimeProvider); + this.minRelayTxFee = nodeSettings.MinRelayTxFeeRate; + this.mempoolRules = mempoolRules.ToList(); + } + + #region IMempoolValidator + + /// + public ConsensusOptions ConsensusOptions => this.network.Consensus.Options; + + /// + public MempoolPerformanceCounter PerformanceCounter { get; } + + /// + public Task AcceptToMemoryPool(MempoolValidationState state, Transaction tx) + { + state.AcceptTime = this.dateTimeProvider.GetTime(); + return AcceptToMemoryPoolWithTime(state, tx); + } + + /// + public async Task AcceptToMemoryPoolWithTime(MempoolValidationState state, Transaction tx) + { + try + { + await AcceptToMemoryPoolWorkerAsync(state, tx); + //if (!res) { + // BOOST_FOREACH(const uint256& hashTx, vHashTxToUncache) + // pcoinsTip->Uncache(hashTx); + //} + + if (state.IsInvalid) + { + this.logger.LogTrace("(-):false"); + return false; + } + + this.logger.LogTrace("(-):true"); + return true; + } + catch (MempoolErrorException mempoolError) + { + this.logger.LogDebug("{0}:'{1}' ErrorCode:'{2}',ErrorMessage:'{3}'", nameof(MempoolErrorException), mempoolError.Message, mempoolError.ValidationState?.Error?.Code, mempoolError.ValidationState?.ErrorMessage); + this.logger.LogTrace("(-)[MEMPOOL_EXCEPTION]:false"); + return false; + } + catch (ConsensusErrorException consensusError) + { + this.logger.LogDebug("{0}:'{1}' ErrorCode:'{2}',ErrorMessage:'{3}'", nameof(ConsensusErrorException), consensusError.Message, consensusError.ConsensusError?.Code, consensusError.ConsensusError?.Message); + state.Error = new MempoolError(consensusError.ConsensusError); + this.logger.LogTrace("(-)[CONSENSUS_EXCEPTION]:false"); + return false; + } + } + + /// + public Task SanityCheck() + { + return Task.CompletedTask; // let's not pretend this is doing something! + } + + #endregion IMempoolValidator + + /// + /// Validates and then adds a transaction to memory pool. + /// + /// Validation state for creating the validation context. + /// The transaction to validate. + private async Task AcceptToMemoryPoolWorkerAsync(MempoolValidationState state, Transaction tx) + { + var context = new MempoolValidationContext(tx, state); + + context.MinRelayTxFee = this.minRelayTxFee; + + // TODO: Convert these into rules too + // this.PreMempoolChecks(context); - done! + + // Create the MemPoolCoinView and load relevant utxoset + context.View = new MempoolCoinView(this.network, this.coinView, this.txMemPool, this.mempoolLock, this); + + // adding to the mem pool can only be done sequentially + // use the sequential scheduler for that. + await this.mempoolLock.WriteAsync(() => + { + context.View.LoadViewLocked(context.Transaction); + + // If the transaction already exists in the mempool, + // we only record the state but do not throw an exception. + // This is because the caller will check if the state is invalid + // and if so return false, meaning that the transaction should not be relayed. + if (this.txMemPool.Exists(context.TransactionHash)) + { + state.Invalid(MempoolErrors.InPool); + this.logger.LogTrace("(-)[INVALID_TX_ALREADY_EXISTS]"); + return; + } + + foreach (IMempoolRule rule in this.mempoolRules) + { + rule.CheckTransaction(context); + } + + // Remove conflicting transactions from the mempool + foreach (TxMempoolEntry it in context.AllConflicting) + this.logger.LogInformation($"Replacing tx {it.TransactionHash} with {context.TransactionHash} for {context.ModifiedFees - context.ConflictingFees} BTC additional fees, {context.EntrySize - context.ConflictingSize} delta bytes"); + + this.txMemPool.RemoveStaged(context.AllConflicting, false); + + // This transaction should only count for fee estimation if + // the node is not behind and it is not dependent on any other + // transactions in the mempool + bool validForFeeEstimation = IsCurrentForFeeEstimation() && this.txMemPool.HasNoInputsOf(tx); + + // Store transaction in memory + this.txMemPool.AddUnchecked(context.TransactionHash, context.Entry, context.SetAncestors, validForFeeEstimation); + + // trim mempool and check if tx was trimmed + if (!state.OverrideMempoolLimit) + { + LimitMempoolSize(this.mempoolSettings.MaxMempool * 1000000, this.mempoolSettings.MempoolExpiry * 60 * 60); + + if (!this.txMemPool.Exists(context.TransactionHash)) + { + this.logger.LogTrace("(-)[FAIL_MEMPOOL_FULL]"); + state.Fail(MempoolErrors.Full).Throw(); + } + } + + // do this here inside the exclusive scheduler for better accuracy + // and to avoid springing more concurrent tasks later + state.MempoolSize = this.txMemPool.Size; + state.MempoolDynamicSize = this.txMemPool.DynamicMemoryUsage(); + + this.PerformanceCounter.SetMempoolSize(state.MempoolSize); + this.PerformanceCounter.SetMempoolDynamicSize(state.MempoolDynamicSize); + this.PerformanceCounter.AddHitCount(1); + }); + } + + /// + /// Whether chain is currently valid for fee estimation. + /// It should only count for fee estimation if the node is not behind. + /// + /// Whether current for fee estimation. + private bool IsCurrentForFeeEstimation() + { + // TODO: implement method (find a way to know if in IBD) + + //if (IsInitialBlockDownload()) + // return false; + + if (this.chainIndexer.Tip.Header.BlockTime.ToUnixTimeMilliseconds() < (this.dateTimeProvider.GetTime() - MempoolValidator.MaxFeeEstimationTipAge)) + { + return false; + } + + //if (chainActive.Height() < pindexBestHeader->nHeight - 1) + // return false; + + return true; + } + + /// + /// Trims memory pool to a new size. + /// First expires transactions older than age. + /// Then trims memory pool to limit if necessary. + /// + /// New size. + /// AAge to use for calculating expired transactions. + private void LimitMempoolSize(long limit, long age) + { + int expired = this.txMemPool.Expire(this.dateTimeProvider.GetTime() - age); + if (expired != 0) + this.logger.LogInformation($"Expired {expired} transactions from the memory pool"); + + var vNoSpendsRemaining = new List(); + this.txMemPool.TrimToSize(limit, vNoSpendsRemaining); + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Configuration/XdsPreMempoolChecksMempoolRule.cs b/src/Blockcore.Networks.Xds/Configuration/XdsPreMempoolChecksMempoolRule.cs new file mode 100644 index 000000000..24c9bf410 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Configuration/XdsPreMempoolChecksMempoolRule.cs @@ -0,0 +1,157 @@ +using System; +using Blockcore.Configuration; +using Blockcore.Consensus; +using Blockcore.Features.Consensus.Rules.CommonRules; +using Blockcore.Features.MemoryPool; +using Blockcore.Features.MemoryPool.Interfaces; +using Blockcore.Utilities; +using Microsoft.Extensions.Logging; +using NBitcoin; + +namespace Blockcore.Networks.Xds.Configuration +{ + /// + /// Checks that are done before touching the memory pool. + /// These checks don't need to run under the memory pool lock, + /// but since we run them now in the rules engine, they do. + /// + public class XdsPreMempoolChecksMempoolRule : MempoolRule + { + private readonly IDateTimeProvider dateTimeProvider; + private readonly NodeSettings nodeSettings; + private readonly CheckPowTransactionRule checkPowTransactionRule; + + public XdsPreMempoolChecksMempoolRule(Network network, + ITxMempool txMempool, + MempoolSettings mempoolSettings, + ChainIndexer chainIndexer, + IConsensusRuleEngine consensusRules, + IDateTimeProvider dateTimeProvider, + NodeSettings nodeSettings, + ILoggerFactory loggerFactory) : base(network, txMempool, mempoolSettings, chainIndexer, loggerFactory) + { + this.dateTimeProvider = dateTimeProvider; + this.nodeSettings = nodeSettings; + + this.checkPowTransactionRule = consensusRules.GetRule(); + } + + public override void CheckTransaction(MempoolValidationContext context) + { + Transaction transaction = context.Transaction; + + // Coinbase is only valid in a block, not as a loose transaction + if (context.Transaction.IsCoinBase) + { + this.logger.LogTrace("(-)[FAIL_INVALID_COINBASE]"); + context.State.Fail(MempoolErrors.Coinbase).Throw(); + } + + // Coinstake is only valid in a block, not as a loose transaction + if (transaction.IsCoinStake) + { + this.logger.LogTrace("(-)[FAIL_INVALID_COINSTAKE]"); + context.State.Fail(MempoolErrors.Coinstake).Throw(); + } + + this.checkPowTransactionRule.CheckTransaction(this.network, this.network.Consensus.Options, transaction); + + // we do not need the CheckPosTransactionRule, the checks for empty outputs are already in the XdsOutputNotWhitelistedMempoolRule. + + CheckStandardTransaction(context); + + // ObsidianX behaves according do BIP 113. Since the adoption of BIP 113, the time-based nLockTime is compared to the 11 - block median time past + // (the median timestamp of the 11 blocks preceding the block in which the transaction is mined), and not the block time itself. + // The median time past tends to lag the current unix time by about one hour(give or take), but unlike block time it increases monotonically. + // For transaction relay, nLockTime must be <= the current block's height (block-based) or <= the current median time past (if time based). + // This ensures that the transaction can be included in the next block. + if (!CheckFinalTransaction(this.chainIndexer, this.dateTimeProvider, context.Transaction)) + { + this.logger.LogTrace("(-)[FAIL_NONSTANDARD]"); + context.State.Fail(MempoolErrors.NonFinal).Throw(); + } + } + + /// + /// Validate the transaction is a standard transaction. Checks the version number, transaction size, input signature size, + /// output script template, single output, & dust outputs. + /// + /// https://github.com/bitcoin/bitcoin/blob/aa624b61c928295c27ffbb4d27be582f5aa31b56/src/policy/policy.cpp##L82-L144 + /// + /// + /// Note that, unfortunately, this does not constitute everything that can be non-standard about a transaction. + /// For example, there may be script verification flags that are not consensus-mandatory but are part of the standardness checks. + /// These verify flags are checked elsewhere. + /// Current validation context. + private void CheckStandardTransaction(MempoolValidationContext context) + { + Transaction tx = context.Transaction; + if (tx.Version > this.network.Consensus.Options.MaxStandardVersion || tx.Version < 1) + { + this.logger.LogTrace("(-)[FAIL_TX_VERSION]"); + context.State.Fail(MempoolErrors.Version).Throw(); + } + + int dataOut = 0; + foreach (TxOut txOut in tx.Outputs) + { + // this rule runs very early in the validation pipeline, check basics as well. + if (txOut?.ScriptPubKey == null || txOut.ScriptPubKey.Length < 1) + { + this.logger.LogTrace("(-)[FAIL_EMPTY_SCRIPTPUBKEY]"); + context.State.Fail(MempoolErrors.Scriptpubkey).Throw(); + } + + // Output checking is already implemented in the XdsOutputNotWhitelistedRule + // and XdsOutputNotWhitelistedMempoolRule. + + // OP_RETURN + byte[] raw = txOut.ScriptPubKey.ToBytes(); + if (raw[0] == (byte)OpcodeType.OP_RETURN) + dataOut++; + + if (txOut.IsDust(this.nodeSettings.MinRelayTxFeeRate)) + { + this.logger.LogTrace("(-)[FAIL_DUST]"); + context.State.Fail(MempoolErrors.Dust).Throw(); + } + } + + // Only one OP_RETURN txOut is permitted + if (dataOut > 1) + { + this.logger.LogTrace("(-)[FAIL_MULTI_OPRETURN]"); + context.State.Fail(MempoolErrors.MultiOpReturn).Throw(); + } + } + + /// + /// Validates that the transaction is the final transaction with the time rules + /// according to BIP-113 rules. + /// + /// Block chain used for computing time-locking on the transaction. + /// Provides the current date and time. + /// The transaction to validate. + /// Whether the final transaction was valid. + /// + private static bool CheckFinalTransaction(ChainIndexer chainIndexer, IDateTimeProvider dateTimeProvider, Transaction tx) + { + // CheckFinalTx() uses chainActive.Height()+1 to evaluate + // nLockTime because when IsFinalTx() is called within + // CBlock::AcceptBlock(), the height of the block *being* + // evaluated is what is used. Thus if we want to know if a + // transaction can be part of the *next* block, we need to call + // IsFinalTx() with one more than chainActive.Height(). + int blockHeight = chainIndexer.Height + 1; + + // BIP113 requires that time-locked transactions have nLockTime set to + // less than the median time of the previous block they're contained in. + // When the next block is created its previous block will be the current + // chain tip, so we use that to calculate the median time passed to + // IsFinalTx(). + DateTimeOffset blockTime = DateTimeOffset.FromUnixTimeMilliseconds(dateTimeProvider.GetTime()); + + return tx.IsFinal(blockTime, blockHeight); + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Consensus/XdsBlockHeader.cs b/src/Blockcore.Networks.Xds/Consensus/XdsBlockHeader.cs new file mode 100644 index 000000000..3f0affa45 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Consensus/XdsBlockHeader.cs @@ -0,0 +1,22 @@ +using System.IO; +using NBitcoin; +using NBitcoin.Crypto; + +namespace Blockcore.Networks.Xds.Consensus +{ + public class XdsBlockHeader : PosBlockHeader + { + public override uint256 GetPoWHash() + { + byte[] serialized; + + using (var ms = new MemoryStream()) + { + this.ReadWriteHashingStream(new BitcoinStream(ms, true)); + serialized = ms.ToArray(); + } + + return Sha512T.GetHash(serialized); + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Consensus/XdsConsensusFactory.cs b/src/Blockcore.Networks.Xds/Consensus/XdsConsensusFactory.cs new file mode 100644 index 000000000..7efef2345 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Consensus/XdsConsensusFactory.cs @@ -0,0 +1,112 @@ +using System; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; +using NBitcoin; +using NBitcoin.DataEncoders; + +namespace Blockcore.Networks.Xds.Consensus +{ + public class XdsConsensusFactory : PosConsensusFactory + { + public override BlockHeader CreateBlockHeader() + { + return new XdsBlockHeader(); + } + + public override ProvenBlockHeader CreateProvenBlockHeader() + { + return new XdsProvenBlockHeader(); + } + + public override ProvenBlockHeader CreateProvenBlockHeader(PosBlock block) + { + var provenBlockHeader = new XdsProvenBlockHeader(block); + + // Serialize the size. + provenBlockHeader.ToBytes(this); + + return provenBlockHeader; + } + + public override Transaction CreateTransaction() + { + return new XdsTransaction(); + } + + public override Transaction CreateTransaction(byte[] bytes) + { + if (bytes == null) + throw new ArgumentNullException(nameof(bytes)); + + var transaction = new XdsTransaction(); + transaction.ReadWrite(bytes, this); + return transaction; + } + + public override Transaction CreateTransaction(string hex) + { + if (hex == null) + throw new ArgumentNullException(nameof(hex)); + + return CreateTransaction(Encoders.Hex.DecodeData(hex)); + } + + public Block ComputeGenesisBlock(uint genesisTime, uint genesisNonce, uint genesisBits, int genesisVersion, Money genesisReward, bool? mine = false) + { + if (mine == true) + MineGenesisBlock(genesisTime, genesisBits, genesisVersion, genesisReward); + + string pszTimestamp = "https://www.blockchain.com/btc/block/611000"; + + Transaction txNew = CreateTransaction(); + Debug.Assert(txNew.GetType() == typeof(XdsTransaction)); + + txNew.Version = 1; + + txNew.AddInput(new TxIn() + { + ScriptSig = new Script(Op.GetPushOp(0), new Op() + { + Code = (OpcodeType)0x1, + PushData = new[] { (byte)42 } + }, Op.GetPushOp(Encoding.UTF8.GetBytes(pszTimestamp))) + }); + txNew.AddOutput(new TxOut() + { + Value = genesisReward, + }); + Block genesis = CreateBlock(); + genesis.Header.BlockTime = Utils.UnixTimeToDateTime(genesisTime); + genesis.Header.Bits = genesisBits; + genesis.Header.Nonce = genesisNonce; + genesis.Header.Version = genesisVersion; + genesis.Transactions.Add(txNew); + genesis.Header.HashPrevBlock = uint256.Zero; + genesis.UpdateMerkleRoot(); + + if (mine == false) + if (genesis.GetHash() != uint256.Parse("0000000e13c5bf36c155c7cb1681053d607c191fc44b863d0c5aef6d27b8eb8f") || + genesis.Header.HashMerkleRoot != uint256.Parse("e3c549956232f0878414d765e83c3f9b1b084b0fa35643ddee62857220ea02b0")) + throw new InvalidOperationException("Invalid network"); + return genesis; + } + + private void MineGenesisBlock(uint genesisTime, uint genesisBits, int genesisVersion, Money genesisReward) + { + Parallel.ForEach(new long[] { 0, 1, 2, 3, 4, 5, 6, 7 }, l => + { + if (Utils.UnixTimeToDateTime(genesisTime) > DateTime.UtcNow) + throw new Exception("Time must not be in the future"); + uint nonce = 0; + while (!ComputeGenesisBlock(genesisTime, nonce, genesisBits, genesisVersion, genesisReward, null).GetHash().ToString().StartsWith("00000000")) + { + nonce += 8; + } + + var genesisBlock = ComputeGenesisBlock(genesisTime, nonce, genesisBits, genesisVersion, genesisReward, null); + throw new Exception($"Found: Nonce:{nonce}, Hash: {genesisBlock.GetHash()}, Hash Merkle Root: {genesisBlock.Header.HashMerkleRoot}"); + }); + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Consensus/XdsConsensusOptions.cs b/src/Blockcore.Networks.Xds/Consensus/XdsConsensusOptions.cs new file mode 100644 index 000000000..62375c1df --- /dev/null +++ b/src/Blockcore.Networks.Xds/Consensus/XdsConsensusOptions.cs @@ -0,0 +1,50 @@ +using NBitcoin; + +namespace Blockcore.Networks.Xds.Consensus +{ + /// + public class XdsConsensusOptions : PosConsensusOptions + { + /// + /// Initializes all values. Used by networks that use block weight rules. + /// + public XdsConsensusOptions( + uint maxBlockBaseSize, + uint maxBlockWeight, + uint maxBlockSerializedSize, + int witnessScaleFactor, + int maxStandardVersion, + int maxStandardTxWeight, + int maxBlockSigopsCost, + int maxStandardTxSigopsCost) : base(maxBlockBaseSize, maxBlockWeight, maxBlockSerializedSize, witnessScaleFactor, maxStandardVersion, maxStandardTxWeight, maxBlockSigopsCost, maxStandardTxSigopsCost) + { + } + + /// + /// Initializes values for networks that use block size rules. + /// + public XdsConsensusOptions( + uint maxBlockBaseSize, + int maxStandardVersion, + int maxStandardTxWeight, + int maxBlockSigopsCost, + int maxStandardTxSigopsCost, + int witnessScaleFactor + ) : base(maxBlockBaseSize, maxStandardVersion, maxStandardTxWeight, maxBlockSigopsCost, maxStandardTxSigopsCost, witnessScaleFactor) + { + } + + /// + /// Uses base class c'tor with Bitcoin rules. + /// + public XdsConsensusOptions() + { } + + /// + public override int GetStakeMinConfirmations(int height, Network network) + { + // StakeMinConfirmations must equal MaxReorgLength so that nobody can stake in isolation and then force a reorg + return (int)network.Consensus.MaxReorgLength; + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Consensus/XdsProvenBlockHeader.cs b/src/Blockcore.Networks.Xds/Consensus/XdsProvenBlockHeader.cs new file mode 100644 index 000000000..f50c416f1 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Consensus/XdsProvenBlockHeader.cs @@ -0,0 +1,30 @@ +using System.IO; +using NBitcoin; +using NBitcoin.Crypto; + +namespace Blockcore.Networks.Xds.Consensus +{ + public class XdsProvenBlockHeader : ProvenBlockHeader + { + public XdsProvenBlockHeader() + { + } + + public XdsProvenBlockHeader(PosBlock block) : base(block) + { + } + + public override uint256 GetPoWHash() + { + byte[] serialized; + + using (var ms = new MemoryStream()) + { + this.ReadWriteHashingStream(new BitcoinStream(ms, true)); + serialized = ms.ToArray(); + } + + return Sha512T.GetHash(serialized); + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Consensus/XdsTransaction.cs b/src/Blockcore.Networks.Xds/Consensus/XdsTransaction.cs new file mode 100644 index 000000000..21b6f9b4a --- /dev/null +++ b/src/Blockcore.Networks.Xds/Consensus/XdsTransaction.cs @@ -0,0 +1,14 @@ +using System.IO; +using NBitcoin; +using NBitcoin.Crypto; + +namespace Blockcore.Networks.Xds.Consensus +{ + public class XdsTransaction : Transaction + { + public override bool IsProtocolTransaction() + { + return this.IsCoinBase || this.IsCoinStake; + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Deployments/XdsBIP9Deployments.cs b/src/Blockcore.Networks.Xds/Deployments/XdsBIP9Deployments.cs new file mode 100644 index 000000000..2944e6220 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Deployments/XdsBIP9Deployments.cs @@ -0,0 +1,54 @@ +using NBitcoin; + +namespace Blockcore.Networks.Xds.Deployments +{ + public class XdsBIP9Deployments : BIP9DeploymentsArray + { + // The position of each deployment in the deployments array. + public const int TestDummy = 0; + + public const int ColdStaking = 1; + public const int CSV = 2; + public const int Segwit = 3; + + // The number of deployments. + public const int NumberOfDeployments = 4; + + /// + /// Constructs the BIP9 deployments array. + /// + public XdsBIP9Deployments() : base(NumberOfDeployments) + { + } + + /// + /// Gets the deployment flags to set when the deployment activates. + /// + /// The deployment number. + /// The deployment flags. + public override BIP9DeploymentFlags GetFlags(int deployment) + { + BIP9DeploymentFlags flags = new BIP9DeploymentFlags(); + + switch (deployment) + { + case ColdStaking: + flags.ScriptFlags |= ScriptVerify.CheckColdStakeVerify; + break; + + case CSV: + // Start enforcing BIP68 (sequence locks), BIP112 (CHECKSEQUENCEVERIFY) and BIP113 (Median Time Past) using versionbits logic. + flags.ScriptFlags = ScriptVerify.CheckSequenceVerify; + flags.LockTimeFlags = Transaction.LockTimeFlags.VerifySequence | Transaction.LockTimeFlags.MedianTimePast; + break; + + case Segwit: + // Start enforcing WITNESS rules using versionbits logic. + flags.ScriptFlags = ScriptVerify.Witness; + break; + } + + return flags; + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Directory.Build.props b/src/Blockcore.Networks.Xds/Directory.Build.props new file mode 100644 index 000000000..3735bb2f2 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Directory.Build.props @@ -0,0 +1,5 @@ + + + true + + \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Networks.cs b/src/Blockcore.Networks.Xds/Networks.cs new file mode 100644 index 000000000..0616a70b4 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Networks.cs @@ -0,0 +1,15 @@ +using NBitcoin; + +namespace Blockcore.Networks.Xds +{ + public static class Networks + { + public static NetworksSelector Xds + { + get + { + return new NetworksSelector(() => new XdsMain(), () => null, () => null); + } + } + } +} \ No newline at end of file diff --git a/src/Blockcore.Networks.Xds/Policies/XdsStandardScriptsRegistry.cs b/src/Blockcore.Networks.Xds/Policies/XdsStandardScriptsRegistry.cs new file mode 100644 index 000000000..9cd6ad977 --- /dev/null +++ b/src/Blockcore.Networks.Xds/Policies/XdsStandardScriptsRegistry.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Linq; +using NBitcoin; +using NBitcoin.BitcoinCore; + +namespace Blockcore.Networks.Xds.Policies +{ + /// + /// ObsidianX-specific standard transaction definitions. + /// + public class XdsStandardScriptsRegistry : StandardScriptsRegistry + { + // See MAX_OP_RETURN_RELAY in Bitcoin Core,