From 5a0e4b7e1c812e76ac6e025f92fa3383d91653fd Mon Sep 17 00:00:00 2001 From: Kevin Loubser Date: Mon, 29 Mar 2021 07:15:05 +0200 Subject: [PATCH 01/13] Dynamic fee computation --- .../ApiClients/CoinGeckoClient.cs | 56 +++++++ .../ApiClients/EtherscanClient.cs | 110 +++++++++++++ .../ExternalApiFeature.cs | 48 ++++++ .../ExternalApiPoller.cs | 144 ++++++++++++++++++ .../ExternalApiSettings.cs | 31 ++++ .../Models/CoinGeckoResponse.cs | 14 ++ .../Models/EtherscanGasOracleResponse.cs | 22 +++ ...tratis.Bitcoin.Features.ExternalApi.csproj | 23 +++ src/Stratis.CirrusPegD/Program.cs | 3 + .../Stratis.CirrusPegD.csproj | 1 + .../SourceChain/DepositExtractor.cs | 12 +- .../SourceChain/MaturedBlocksProvider.cs | 12 +- .../Stratis.Features.FederatedPeg.csproj | 1 + src/Stratis.FullNode.sln | 7 + src/Stratis.StraxD/Program.cs | 4 +- src/Stratis.StraxD/Stratis.StraxD.csproj | 1 + 16 files changed, 483 insertions(+), 6 deletions(-) create mode 100644 src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/CoinGeckoClient.cs create mode 100644 src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/EtherscanClient.cs create mode 100644 src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiFeature.cs create mode 100644 src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs create mode 100644 src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiSettings.cs create mode 100644 src/Stratis.Bitcoin.Features.ExternalAPI/Models/CoinGeckoResponse.cs create mode 100644 src/Stratis.Bitcoin.Features.ExternalAPI/Models/EtherscanGasOracleResponse.cs create mode 100644 src/Stratis.Bitcoin.Features.ExternalAPI/Stratis.Bitcoin.Features.ExternalApi.csproj diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/CoinGeckoClient.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/CoinGeckoClient.cs new file mode 100644 index 0000000000..9a098eac53 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/CoinGeckoClient.cs @@ -0,0 +1,56 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Stratis.Bitcoin.Features.ExternalApi.Models; + +namespace Stratis.Bitcoin.Features.ExternalApi.ApiClients +{ + public class CoinGeckoClient : IDisposable + { + private readonly ExternalApiSettings externalApiSettings; + private readonly HttpClient client; + + private decimal stratisPrice = -1; + private decimal ethereumPrice = -1; + + public CoinGeckoClient(ExternalApiSettings externalApiSettings) + { + this.externalApiSettings = externalApiSettings; + + this.client = new HttpClient(); + } + + public decimal GetStratisPrice() + { + return this.stratisPrice; + } + + public decimal GetEthereumPrice() + { + return this.ethereumPrice; + } + + public async Task PriceDataRetrievalAsync() + { + string content = await this.client.GetStringAsync(this.externalApiSettings.EtherscanGasOracleUrl); + + CoinGeckoResponse response = JsonConvert.DeserializeObject(content); + + if (response?.stratis == null || response?.ethereum == null) + { + return null; + } + + this.stratisPrice = response.stratis.usd; + this.ethereumPrice = response.ethereum.usd; + + return response; + } + + public void Dispose() + { + this.client?.Dispose(); + } + } +} diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/EtherscanClient.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/EtherscanClient.cs new file mode 100644 index 0000000000..dcf815b0dc --- /dev/null +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/EtherscanClient.cs @@ -0,0 +1,110 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Stratis.Bitcoin.Features.ExternalApi.Models; + +namespace Stratis.Bitcoin.Features.ExternalApi.ApiClients +{ + public class EtherscanClient : IDisposable + { + private readonly ExternalApiSettings externalApiSettings; + private readonly HttpClient client; + + private int[] fastSamples = new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + private int[] proposeSamples = new [] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + private int[] safeSamples = new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + private bool sampled = false; + private int samplePointer = 0; + + public EtherscanClient(ExternalApiSettings externalApiSettings) + { + this.externalApiSettings = externalApiSettings; + + this.client = new HttpClient(); + } + + /// + /// Retrieves a recommended gas price based on historical measured samples. + /// + /// We use the average of the historic proposed price. + /// The recommended gas price in gwei. Returns -1 if price data is not yet available. + public int GetGasPrice() + { + if (!this.sampled) + { + return -1; + } + + // In future this could be made more responsive to sudden changes in the recent price, and possibly use the safe price if it can be reasonably anticipated that the transaction will still confirm timeously. + + // The weightings should add up to 1. + decimal fastWeighting = 0.0m; + decimal proposedWeighting = 1.0m; + decimal safeWeighting = 0.0m; + + decimal totalFast = 0m; + decimal totalProposed = 0m; + decimal totalSafe = 0m; + + for (int i = 0; i < this.fastSamples.Length; i++) + { + totalFast += this.fastSamples[i]; + totalProposed += this.proposeSamples[i]; + totalSafe += this.safeSamples[i]; + } + + return (int)Math.Ceiling(((totalFast * fastWeighting) + (totalProposed * proposedWeighting) + (totalSafe * safeWeighting) / this.fastSamples.Length)); + } + + public async Task GasOracle(bool recordSamples) + { + string content = await this.client.GetStringAsync(this.externalApiSettings.EtherscanGasOracleUrl); + + EtherscanGasOracleResponse response = JsonConvert.DeserializeObject(content); + + if (response?.result == null) + { + return null; + } + + // We do not know how long the node was shut down for, so the very first sample must populate every array element (regardless of whether the caller requested sample recording). + // There would be little point storing the historic data in the key value store, for instance. + if (!this.sampled) + { + for (int i = 0; i < this.fastSamples.Length; i++) + { + this.fastSamples[i] = response.result.FastGasPrice; + this.proposeSamples[i] = response.result.ProposeGasPrice; + this.safeSamples[i] = response.result.SafeGasPrice; + } + + this.sampled = true; + + return response; + } + + if (recordSamples) + { + this.fastSamples[this.samplePointer] = response.result.FastGasPrice; + this.proposeSamples[this.samplePointer] = response.result.ProposeGasPrice; + this.safeSamples[this.samplePointer] = response.result.SafeGasPrice; + + this.samplePointer++; + + if (this.samplePointer > this.fastSamples.Length) + { + this.samplePointer = 0; + } + } + + return response; + } + + public void Dispose() + { + this.client?.Dispose(); + } + } +} diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiFeature.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiFeature.cs new file mode 100644 index 0000000000..4c35b74c58 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiFeature.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Stratis.Bitcoin.Builder; +using Stratis.Bitcoin.Builder.Feature; +using Stratis.Bitcoin.Configuration.Logging; + +namespace Stratis.Bitcoin.Features.ExternalApi +{ + public sealed class ExternalApiFeature : FullNodeFeature + { + private readonly ExternalApiPoller externalApiPoller; + + public ExternalApiFeature(ExternalApiPoller externalApiPoller) + { + this.externalApiPoller = externalApiPoller; + } + + public override Task InitializeAsync() + { + this.externalApiPoller?.Initialize(); + + return Task.CompletedTask; + } + + public override void Dispose() + { + this.externalApiPoller?.Dispose(); + } + } + + public static partial class IFullNodeBuilderExtensions + { + public static IFullNodeBuilder AddExternalApi(this IFullNodeBuilder fullNodeBuilder) + { + LoggingConfiguration.RegisterFeatureNamespace("externalapi"); + + fullNodeBuilder.ConfigureFeature(features => + features + .AddFeature() + .FeatureServices(services => services + .AddSingleton() + .AddSingleton() + )); + + return fullNodeBuilder; + } + } +} diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs new file mode 100644 index 0000000000..fcb070ffa2 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs @@ -0,0 +1,144 @@ +using System; +using System.Numerics; +using Microsoft.Extensions.Logging; +using Stratis.Bitcoin.AsyncWork; +using Stratis.Bitcoin.Configuration; +using Stratis.Bitcoin.Features.ExternalApi.ApiClients; +using Stratis.Bitcoin.Utilities; + +namespace Stratis.Bitcoin.Features.ExternalApi +{ + public class ExternalApiPoller : IDisposable + { + private readonly IAsyncProvider asyncProvider; + private readonly INodeLifetime nodeLifetime; + private readonly ILogger logger; + private readonly ExternalApiSettings externalApiSettings; + private readonly EtherscanClient etherscanClient; + private readonly CoinGeckoClient coinGeckoClient; + + private IAsyncLoop gasPriceLoop; + private IAsyncLoop priceLoop; + + public ExternalApiPoller(NodeSettings nodeSettings, + IAsyncProvider asyncProvider, + INodeLifetime nodeLifetime, + ExternalApiSettings externalApiSettings) + { + this.asyncProvider = asyncProvider; + this.nodeLifetime = nodeLifetime; + this.logger = nodeSettings.LoggerFactory.CreateLogger(this.GetType().FullName); + this.externalApiSettings = externalApiSettings; + this.etherscanClient = new EtherscanClient(this.externalApiSettings); + this.coinGeckoClient = new CoinGeckoClient(this.externalApiSettings); + } + + public void Initialize() + { + this.logger.LogInformation($"External API feature enabled, initializing periodic loops."); + + if (this.externalApiSettings.EthereumGasPriceTracking) + { + this.logger.LogInformation($"Ethereum gas price tracking enabled."); + + this.gasPriceLoop = this.asyncProvider.CreateAndRunAsyncLoop("PeriodicCheckGasPrice", async (cancellation) => + { + this.logger.LogTrace("Beginning gas price check loop."); + + try + { + await this.etherscanClient.GasOracle(true).ConfigureAwait(false); + } + catch (Exception e) + { + this.logger.LogWarning("Exception raised when checking current gas price. {0}", e); + } + + this.logger.LogTrace("Finishing gas price check loop."); + }, + this.nodeLifetime.ApplicationStopping, + repeatEvery: TimeSpans.Minute, + startAfter: TimeSpans.TenSeconds); + } + + if (this.externalApiSettings.PriceTracking) + { + this.logger.LogInformation($"Price tracking for STRAX and ETH enabled."); + + this.priceLoop = this.asyncProvider.CreateAndRunAsyncLoop("PeriodicCheckPrice", async (cancellation) => + { + this.logger.LogTrace("Beginning price check loop."); + + try + { + await this.coinGeckoClient.PriceDataRetrievalAsync().ConfigureAwait(false); + } + catch (Exception e) + { + this.logger.LogWarning("Exception raised when checking current prices. {0}", e); + } + + this.logger.LogTrace("Finishing price check loop."); + }, + this.nodeLifetime.ApplicationStopping, + repeatEvery: TimeSpans.Minute, + startAfter: TimeSpans.TenSeconds); + } + } + + public decimal GetStratisPrice() + { + return this.coinGeckoClient.GetStratisPrice(); + } + + public decimal GetEthereumPrice() + { + return this.coinGeckoClient.GetEthereumPrice(); + } + + public int GetGasPrice() + { + return this.etherscanClient.GetGasPrice(); + } + + public BigInteger EstimateConversionTransactionGas() + { + // The cost of submitting a multisig ERC20 transfer to the multisig contract. + const decimal SubmissionGasCost = 230_000; + + // The cost of submitting a confirmation transaction to the multisig contract. + const decimal ConfirmGasCost = 100_000; + + // The final confirmation that meets the contract threshold; this incurs slightly higher gas due to the transaction execution occurring as well. + const decimal ExecuteGasCost = 160_000; + + var totalGas = new BigInteger(SubmissionGasCost + (7 * ConfirmGasCost) + ExecuteGasCost)); + + int gasPrice = this.GetGasPrice(); + + return totalGas * gasPrice; + } + + public decimal EstimateConversionTransactionFee() + { + // The approximate USD fee that will be applied to conversion transactions, over and above the computed gas cost. + const decimal ConversionTransactionFee = 100; + + var OneEther = new BigInteger(1_000_000_000_000_000_000); + + BigInteger overallGas = this.EstimateConversionTransactionGas(); + + // WIP + + decimal ethereumUsdPrice = this.GetEthereumPrice(); + + decimal stratisPrice = this.GetStratisPrice(); + } + + public void Dispose() + { + this.gasPriceLoop?.Dispose(); + this.priceLoop?.Dispose(); + } + } +} diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiSettings.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiSettings.cs new file mode 100644 index 0000000000..d93bb2c320 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiSettings.cs @@ -0,0 +1,31 @@ +using Stratis.Bitcoin.Configuration; + +namespace Stratis.Bitcoin.Features.ExternalApi +{ + public class ExternalApiSettings + { + public const string EtherscanGasOracleUrlKey = "etherscangasoracle"; + + public const string EthereumGasPriceTrackingKey = "ethereumgaspricetracking"; + + public const string PriceUrlKey = "ethereumpriceurl"; + + public const string PriceTrackingKey = "ethereumpricetracking"; + + public string EtherscanGasOracleUrl { get; set; } + + public bool EthereumGasPriceTracking { get; set; } + + public string PriceUrl { get; set; } + + public bool PriceTracking { get; set; } + + public ExternalApiSettings(NodeSettings nodeSettings) + { + this.EtherscanGasOracleUrl = nodeSettings.ConfigReader.GetOrDefault(EtherscanGasOracleUrlKey, "https://api.etherscan.io/api?module=gastracker&action=gasoracle"); + this.EthereumGasPriceTracking = nodeSettings.ConfigReader.GetOrDefault(EthereumGasPriceTrackingKey, false); + this.PriceUrl = nodeSettings.ConfigReader.GetOrDefault(PriceUrlKey, "https://api.coingecko.com/api/v3/simple/price?ids=stratis,ethereum&vs_currencies=usd"); + this.PriceTracking = nodeSettings.ConfigReader.GetOrDefault(PriceTrackingKey, false); + } + } +} diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/Models/CoinGeckoResponse.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/Models/CoinGeckoResponse.cs new file mode 100644 index 0000000000..b6863cc173 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/Models/CoinGeckoResponse.cs @@ -0,0 +1,14 @@ +namespace Stratis.Bitcoin.Features.ExternalApi.Models +{ + public class CoinGeckoPriceData + { + public decimal usd { get; set; } + } + + public class CoinGeckoResponse + { + public CoinGeckoPriceData stratis { get; set; } + + public CoinGeckoPriceData ethereum { get; set; } + } +} diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/Models/EtherscanGasOracleResponse.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/Models/EtherscanGasOracleResponse.cs new file mode 100644 index 0000000000..1bc915796e --- /dev/null +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/Models/EtherscanGasOracleResponse.cs @@ -0,0 +1,22 @@ +namespace Stratis.Bitcoin.Features.ExternalApi.Models +{ + public class EtherscanGasOracleResponse + { + public string status { get; set; } + + public string message { get; set; } + + public EtherscanGasOracleResponseResult result { get; set; } + } + + public class EtherscanGasOracleResponseResult + { + public int LastBlock { get; set; } + + public int SafeGasPrice { get; set; } + + public int ProposeGasPrice { get; set; } + + public int FastGasPrice { get; set; } + } +} diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/Stratis.Bitcoin.Features.ExternalApi.csproj b/src/Stratis.Bitcoin.Features.ExternalAPI/Stratis.Bitcoin.Features.ExternalApi.csproj new file mode 100644 index 0000000000..85991d58d6 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/Stratis.Bitcoin.Features.ExternalApi.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.1 + 1.0.8.0 + Stratis Group Ltd. + Stratis.Features.ExternalAPI + Stratis.Features.ExternalAPI + + + + bin\Debug\netcoreapp3.1\Stratis.Bitcoin.Features.ExternalAPI.xml + + + + + + + + + + + diff --git a/src/Stratis.CirrusPegD/Program.cs b/src/Stratis.CirrusPegD/Program.cs index cedd47b2b0..7ca7de9c3d 100644 --- a/src/Stratis.CirrusPegD/Program.cs +++ b/src/Stratis.CirrusPegD/Program.cs @@ -10,6 +10,7 @@ using Stratis.Bitcoin.Features.Api; using Stratis.Bitcoin.Features.BlockStore; using Stratis.Bitcoin.Features.Consensus; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Features.Interop; using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Bitcoin.Features.Miner; @@ -97,6 +98,7 @@ private static IFullNode GetMainchainFullNode(string[] args) .UseWallet() .AddSQLiteWalletRepository() .AddPowPosMining(true) + .AddExternalApi() .Build(); return node; @@ -132,6 +134,7 @@ private static IFullNode GetSidechainFullNode(string[] args) .AddInteroperability() .UseSmartContractWallet() .AddSQLiteWalletRepository() + .AddExternalApi() .Build(); return node; diff --git a/src/Stratis.CirrusPegD/Stratis.CirrusPegD.csproj b/src/Stratis.CirrusPegD/Stratis.CirrusPegD.csproj index c22f630cf0..48c7f3fa84 100644 --- a/src/Stratis.CirrusPegD/Stratis.CirrusPegD.csproj +++ b/src/Stratis.CirrusPegD/Stratis.CirrusPegD.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs b/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs index f3190ec9eb..988e80839e 100644 --- a/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs +++ b/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs @@ -1,15 +1,13 @@ using System.Collections.Generic; using System.Linq; using NBitcoin; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Features.FederatedPeg.Interfaces; namespace Stratis.Features.FederatedPeg.SourceChain { public sealed class DepositExtractor : IDepositExtractor { - // Conversion transaction deposits smaller than this threshold will be ignored. Denominated in STRAX. - private const decimal ConversionTransactionMinimum = 90_000; - /// /// This deposit extractor implementation only looks for a very specific deposit format. /// Deposits will have 2 outputs when there is no change. @@ -23,13 +21,15 @@ public sealed class DepositExtractor : IDepositExtractor private readonly IFederatedPegSettings federatedPegSettings; private readonly Network network; private readonly IOpReturnDataReader opReturnDataReader; + private readonly ExternalApiPoller externalApiPoller; - public DepositExtractor(IFederatedPegSettings federatedPegSettings, Network network, IOpReturnDataReader opReturnDataReader) + public DepositExtractor(IFederatedPegSettings federatedPegSettings, Network network, IOpReturnDataReader opReturnDataReader, ExternalApiPoller externalApiPoller) { this.depositScript = federatedPegSettings.MultiSigRedeemScript.PaymentScript; this.federatedPegSettings = federatedPegSettings; this.network = network; this.opReturnDataReader = opReturnDataReader; + this.externalApiPoller = externalApiPoller; } /// @@ -93,6 +93,10 @@ public IDeposit ExtractDepositFromTransaction(Transaction transaction, int block if (conversionTransaction) { + // Instead of a fixed minimum, check that the deposit size at least covers the fee. + int gasPrice = this.externalApiPoller.GetGasPrice(); + decimal stratisPrice = this.externalApiPoller.GetStratisPrice(); + if (amount < Money.Coins(ConversionTransactionMinimum)) return null; diff --git a/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs b/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs index b487c2b07c..1b8acae3e9 100644 --- a/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs +++ b/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs @@ -7,6 +7,7 @@ using NLog; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Controllers; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Primitives; using Stratis.Bitcoin.Utilities; using Stratis.Features.FederatedPeg.Interfaces; @@ -50,8 +51,9 @@ public sealed class MaturedBlocksProvider : IMaturedBlocksProvider private readonly IFederatedPegSettings federatedPegSettings; private readonly ILogger logger; private readonly Dictionary retrievalTypeConfirmations; + private readonly ExternalApiPoller externalApiPoller; - public MaturedBlocksProvider(IConsensusManager consensusManager, IDepositExtractor depositExtractor, IFederatedPegSettings federatedPegSettings) + public MaturedBlocksProvider(IConsensusManager consensusManager, IDepositExtractor depositExtractor, IFederatedPegSettings federatedPegSettings, ExternalApiPoller externalApiPoller) { this.consensusManager = consensusManager; this.depositExtractor = depositExtractor; @@ -74,6 +76,8 @@ public MaturedBlocksProvider(IConsensusManager consensusManager, IDepositExtract this.retrievalTypeConfirmations[DepositRetrievalType.ConversionNormal] = this.federatedPegSettings.MinimumConfirmationsNormalDeposits; this.retrievalTypeConfirmations[DepositRetrievalType.ConversionLarge] = this.federatedPegSettings.MinimumConfirmationsLargeDeposits; } + + this.externalApiPoller = externalApiPoller; } /// @@ -82,6 +86,12 @@ public SerializableResult> RetrieveDeposits(int if (this.consensusManager.Tip == null) return SerializableResult>.Fail("Consensus is not ready to provide blocks (it is un-initialized or still starting up)."); + int gasPrice = this.externalApiPoller.GetGasPrice(); + decimal stratisPrice = this.externalApiPoller.GetStratisPrice(); + + if (gasPrice == -1 || stratisPrice == -1) + return SerializableResult>.Fail("Pricing data not yet available from external API pollers."); + var result = new SerializableResult> { Value = new List(), diff --git a/src/Stratis.Features.FederatedPeg/Stratis.Features.FederatedPeg.csproj b/src/Stratis.Features.FederatedPeg/Stratis.Features.FederatedPeg.csproj index 0533d830c7..7cdb200ab9 100644 --- a/src/Stratis.Features.FederatedPeg/Stratis.Features.FederatedPeg.csproj +++ b/src/Stratis.Features.FederatedPeg/Stratis.Features.FederatedPeg.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Stratis.FullNode.sln b/src/Stratis.FullNode.sln index 591c5c3545..cc7435c15a 100644 --- a/src/Stratis.FullNode.sln +++ b/src/Stratis.FullNode.sln @@ -183,6 +183,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Patricia.Tests", "S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Bitcoin.Features.Interop", "Stratis.Bitcoin.Features.Interop\Stratis.Bitcoin.Features.Interop.csproj", "{3DCC6195-1271-4A12-8B94-E821925D98DC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Bitcoin.Features.ExternalApi", "Stratis.Bitcoin.Features.ExternalAPI\Stratis.Bitcoin.Features.ExternalApi.csproj", "{74F3F581-03FB-4C18-BED8-D2A526B5138E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -481,6 +483,10 @@ Global {3DCC6195-1271-4A12-8B94-E821925D98DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DCC6195-1271-4A12-8B94-E821925D98DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DCC6195-1271-4A12-8B94-E821925D98DC}.Release|Any CPU.Build.0 = Release|Any CPU + {74F3F581-03FB-4C18-BED8-D2A526B5138E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74F3F581-03FB-4C18-BED8-D2A526B5138E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74F3F581-03FB-4C18-BED8-D2A526B5138E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74F3F581-03FB-4C18-BED8-D2A526B5138E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -549,6 +555,7 @@ Global {57457525-FD6E-401D-BF3F-862F7A2D2AC9} = {1B9A916F-DDAC-4675-B424-EDEDC1A58231} {EE71EBA7-5515-4F1E-B0E0-6C32BAAD6B35} = {1B9A916F-DDAC-4675-B424-EDEDC1A58231} {3DCC6195-1271-4A12-8B94-E821925D98DC} = {15D29FFD-6142-4DC5-AFFD-10BA0CA55C45} + {74F3F581-03FB-4C18-BED8-D2A526B5138E} = {15D29FFD-6142-4DC5-AFFD-10BA0CA55C45} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6C780ABA-5872-4B83-AD3F-A5BD423AD907} diff --git a/src/Stratis.StraxD/Program.cs b/src/Stratis.StraxD/Program.cs index c7bcfd3f31..a6f004cd83 100644 --- a/src/Stratis.StraxD/Program.cs +++ b/src/Stratis.StraxD/Program.cs @@ -9,6 +9,7 @@ using Stratis.Bitcoin.Features.BlockStore; using Stratis.Bitcoin.Features.ColdStaking; using Stratis.Bitcoin.Features.Consensus; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Bitcoin.Features.Miner; using Stratis.Bitcoin.Features.RPC; @@ -69,7 +70,8 @@ public static async Task Main(string[] args) }) }; }) - .UseDiagnosticFeature(); + .UseDiagnosticFeature() + .AddExternalApi(); IFullNode node = nodeBuilder.Build(); diff --git a/src/Stratis.StraxD/Stratis.StraxD.csproj b/src/Stratis.StraxD/Stratis.StraxD.csproj index f61eb9e137..bded13d552 100644 --- a/src/Stratis.StraxD/Stratis.StraxD.csproj +++ b/src/Stratis.StraxD/Stratis.StraxD.csproj @@ -26,6 +26,7 @@ + From 18a57c921e7b64deade89455af434b0edf02e45d Mon Sep 17 00:00:00 2001 From: Kevin Loubser Date: Wed, 31 Mar 2021 07:06:16 +0200 Subject: [PATCH 02/13] Dynamic fee --- .../Controllers/ExternalApiController.cs | 114 ++++++++++++++++++ .../ExternalApiPoller.cs | 26 ++-- .../InteropPoller.cs | 6 +- .../InteropSettings.cs | 4 + .../SourceChain/DepositExtractor.cs | 14 ++- .../TargetChain/MaturedBlocksSyncManager.cs | 2 + 6 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 src/Stratis.Bitcoin.Features.ExternalAPI/Controllers/ExternalApiController.cs diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/Controllers/ExternalApiController.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/Controllers/ExternalApiController.cs new file mode 100644 index 0000000000..3c30d2c3e7 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/Controllers/ExternalApiController.cs @@ -0,0 +1,114 @@ +using System; +using System.Net; +using Microsoft.AspNetCore.Mvc; +using NLog; +using Stratis.Bitcoin.Features.ExternalApi; +using Stratis.Bitcoin.Utilities.JsonErrors; + +namespace Stratis.Features.ExternalApi.Controllers +{ + [ApiVersion("1")] + [Route("api/[controller]")] + public class ExternalApiController : Controller + { + private readonly ExternalApiPoller externalApiPoller; + + private readonly ILogger logger; + + public ExternalApiController(ExternalApiPoller externalApiPoller) + { + this.externalApiPoller = externalApiPoller; + this.logger = LogManager.GetCurrentClassLogger(); + } + + [Route("estimateconversiongas")] + [HttpGet] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.InternalServerError)] + public IActionResult EstimateConversionGas() + { + try + { + return this.Json(this.externalApiPoller.EstimateConversionTransactionGas()); + } + catch (Exception e) + { + this.logger.Error("Exception occurred: {0}", e.ToString()); + return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()); + } + } + + [Route("estimateconversionfee")] + [HttpGet] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.InternalServerError)] + public IActionResult EstimateConversionFee() + { + try + { + return this.Json(this.externalApiPoller.EstimateConversionTransactionFee()); + } + catch (Exception e) + { + this.logger.Error("Exception occurred: {0}", e.ToString()); + return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()); + } + } + + [Route("gasprice")] + [HttpGet] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.InternalServerError)] + public IActionResult GasPrice() + { + try + { + return this.Json(this.externalApiPoller.GetGasPrice()); + } + catch (Exception e) + { + this.logger.Error("Exception occurred: {0}", e.ToString()); + return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()); + } + } + + [Route("stratisprice")] + [HttpGet] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.InternalServerError)] + public IActionResult StratisPrice() + { + try + { + return this.Json(this.externalApiPoller.GetStratisPrice()); + } + catch (Exception e) + { + this.logger.Error("Exception occurred: {0}", e.ToString()); + return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()); + } + } + + [Route("ethereumprice")] + [HttpGet] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.InternalServerError)] + public IActionResult EthereumPrice() + { + try + { + return this.Json(this.externalApiPoller.GetEthereumPrice()); + } + catch (Exception e) + { + this.logger.Error("Exception occurred: {0}", e.ToString()); + return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()); + } + } + } +} diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs index fcb070ffa2..31c297b1c7 100644 --- a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs @@ -1,5 +1,4 @@ using System; -using System.Numerics; using Microsoft.Extensions.Logging; using Stratis.Bitcoin.AsyncWork; using Stratis.Bitcoin.Configuration; @@ -10,6 +9,9 @@ namespace Stratis.Bitcoin.Features.ExternalApi { public class ExternalApiPoller : IDisposable { + // TODO: This should be linked to the setting in the interop feature + public const int QuorumSize = 6; + private readonly IAsyncProvider asyncProvider; private readonly INodeLifetime nodeLifetime; private readonly ILogger logger; @@ -101,7 +103,9 @@ public int GetGasPrice() return this.etherscanClient.GetGasPrice(); } - public BigInteger EstimateConversionTransactionGas() + /// The decimal type is acceptable here because it supports sufficiently large numbers for most conceivable gas calculations. + /// The estimated total amount of gas a conversion transaction will require. + public decimal EstimateConversionTransactionGas() { // The cost of submitting a multisig ERC20 transfer to the multisig contract. const decimal SubmissionGasCost = 230_000; @@ -112,27 +116,33 @@ public BigInteger EstimateConversionTransactionGas() // The final confirmation that meets the contract threshold; this incurs slightly higher gas due to the transaction execution occurring as well. const decimal ExecuteGasCost = 160_000; - var totalGas = new BigInteger(SubmissionGasCost + (7 * ConfirmGasCost) + ExecuteGasCost)); + // Of the required number of confirmations, one confirmation comes from the initial submission, and the final confirmation is more expensive. + decimal totalGas = SubmissionGasCost + ((QuorumSize - 2) * ConfirmGasCost) + ExecuteGasCost; int gasPrice = this.GetGasPrice(); return totalGas * gasPrice; } + /// The estimated conversion transaction fee, converted from the USD total to the equivalent STRAX amount. public decimal EstimateConversionTransactionFee() { // The approximate USD fee that will be applied to conversion transactions, over and above the computed gas cost. const decimal ConversionTransactionFee = 100; - var OneEther = new BigInteger(1_000_000_000_000_000_000); + decimal ethereumUsdPrice = this.GetEthereumPrice(); + + if (ethereumUsdPrice == -1) + return -1; - BigInteger overallGas = this.EstimateConversionTransactionGas(); + decimal overallGasUsd = this.EstimateConversionTransactionGas() * ethereumUsdPrice; - // WIP + decimal stratisPriceUsd = this.GetStratisPrice(); - decimal ethereumUsdPrice = this.GetEthereumPrice(); + if (stratisPriceUsd == -1) + return -1; - decimal stratisPrice = this.GetStratisPrice(); + return (overallGasUsd / stratisPriceUsd) + ConversionTransactionFee; } public void Dispose() diff --git a/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs b/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs index 314c13f727..0906ab9ad1 100644 --- a/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs +++ b/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs @@ -413,7 +413,7 @@ private async Task ProcessConversionRequestsAsync() { await this.BroadcastCoordinationAsync(request.RequestId, transactionId2).ConfigureAwait(false); - BigInteger agreedTransactionId = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, 6); + BigInteger agreedTransactionId = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, this.interopSettings.MultisigWalletQuorum); if (agreedTransactionId != BigInteger.MinusOne) { @@ -425,7 +425,7 @@ private async Task ProcessConversionRequestsAsync() break; case ((int)ConversionRequestStatus.VoteFinalised): - BigInteger transactionId3 = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, 6); + BigInteger transactionId3 = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, this.interopSettings.MultisigWalletQuorum); if (transactionId3 != BigInteger.MinusOne) { @@ -464,7 +464,7 @@ private async Task ProcessConversionRequestsAsync() // So each node needs to satisfy itself that the transactionId sent by the originator exists in the multisig wallet. // This is done within the InteropBehavior automatically, we just check each poll loop if a transaction has enough votes yet. // Each node must only ever confirm a single transactionId for a given conversion transaction. - BigInteger agreedUponId = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, 6); + BigInteger agreedUponId = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, this.interopSettings.MultisigWalletQuorum); if (agreedUponId != BigInteger.MinusOne) { diff --git a/src/Stratis.Bitcoin.Features.Interop/InteropSettings.cs b/src/Stratis.Bitcoin.Features.Interop/InteropSettings.cs index 2cd5433d96..d7ead9db6f 100644 --- a/src/Stratis.Bitcoin.Features.Interop/InteropSettings.cs +++ b/src/Stratis.Bitcoin.Features.Interop/InteropSettings.cs @@ -9,6 +9,7 @@ public class InteropSettings public const string InteropContractCirrusAddressKey = "interopcontractcirrusaddress"; public const string InteropContractEthereumAddressKey = "interopcontractethereumaddress"; public const string MultisigWalletContractAddressKey = "multisigwalletcontractaddress"; + public const string MultisigWalletContractQuorumKey = "multisigwalletcontractquorum"; public const string WrappedStraxContractAddressKey = "wrappedstraxcontractaddress"; public const string EthereumClientUrlKey = "ethereumclienturl"; public const string EthereumAccountKey = "ethereumaccount"; @@ -24,6 +25,8 @@ public class InteropSettings public string MultisigWalletAddress { get; set; } + public int MultisigWalletQuorum { get; set; } + public string WrappedStraxAddress { get; set; } public string EthereumClientUrl { get; set; } @@ -50,6 +53,7 @@ public InteropSettings(NodeSettings nodeSettings) this.InteropContractEthereumAddress = nodeSettings.ConfigReader.GetOrDefault(InteropContractEthereumAddressKey, ""); this.MultisigWalletAddress = nodeSettings.ConfigReader.GetOrDefault(MultisigWalletContractAddressKey, ""); + this.MultisigWalletQuorum = nodeSettings.ConfigReader.GetOrDefault(MultisigWalletContractQuorumKey, 6); this.WrappedStraxAddress = nodeSettings.ConfigReader.GetOrDefault(WrappedStraxContractAddressKey, ""); this.EthereumClientUrl = nodeSettings.ConfigReader.GetOrDefault(EthereumClientUrlKey, "http://localhost:8545"); this.EthereumAccount = nodeSettings.ConfigReader.GetOrDefault(EthereumAccountKey, ""); diff --git a/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs b/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs index 988e80839e..2f4eb6b7b9 100644 --- a/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs +++ b/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using NBitcoin; +using NLog; using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Features.FederatedPeg.Interfaces; @@ -22,6 +23,7 @@ public sealed class DepositExtractor : IDepositExtractor private readonly Network network; private readonly IOpReturnDataReader opReturnDataReader; private readonly ExternalApiPoller externalApiPoller; + private readonly ILogger logger; public DepositExtractor(IFederatedPegSettings federatedPegSettings, Network network, IOpReturnDataReader opReturnDataReader, ExternalApiPoller externalApiPoller) { @@ -30,6 +32,7 @@ public DepositExtractor(IFederatedPegSettings federatedPegSettings, Network netw this.network = network; this.opReturnDataReader = opReturnDataReader; this.externalApiPoller = externalApiPoller; + this.logger = LogManager.GetCurrentClassLogger(); } /// @@ -94,11 +97,16 @@ public IDeposit ExtractDepositFromTransaction(Transaction transaction, int block if (conversionTransaction) { // Instead of a fixed minimum, check that the deposit size at least covers the fee. - int gasPrice = this.externalApiPoller.GetGasPrice(); - decimal stratisPrice = this.externalApiPoller.GetStratisPrice(); + decimal minimumDeposit = this.externalApiPoller.EstimateConversionTransactionFee(); + + if (amount < Money.Coins(minimumDeposit)) + { + this.logger.Warn("Received deposit of {0}, but computed minimum deposit fee is {1}. Ignoring deposit.", amount, minimumDeposit); - if (amount < Money.Coins(ConversionTransactionMinimum)) return null; + } + + this.logger.Info("Received conversion transaction deposit of {0}, subtracting estimated fee of {1}.", amount, minimumDeposit); if (amount > this.federatedPegSettings.NormalDepositThresholdAmount) depositRetrievalType = DepositRetrievalType.ConversionLarge; diff --git a/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs b/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs index e7bf0c3d03..b4b0762fda 100644 --- a/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs +++ b/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs @@ -159,6 +159,8 @@ private async Task ProcessMatureBlockDepositsAsync(SerializableResult Date: Wed, 31 Mar 2021 09:33:11 +0300 Subject: [PATCH 03/13] Interflux (#481) * Update NBitcoin.csproj (#391) * Interflux (#397) * Initial InterFlux version * Refactor * Modify DI sequence (#398) * Modify DI sequence * Update NEthereum and Stratis.Patricia (#412) * Initial InterFlux version * Update NBitcoin.csproj (#391) * Pass along height information for OP_FEDERATION (#379) * Pass along height information for OP_FEDERATION * Add defaults * Add defaults * Filter Transaction History by Transaction Id (#393) * Update WalletService.cs * Add Filter * Fix Test * Perform check for cold staking txs * Refactor * Modify DI sequence * Add mainnet check points (#400) * Add MainNet check points * Fix Tests * Change gas limit upper bound to 250,000 (#399) * Add UInt256, serialization methods and CLR updates (#365) * Add UInt256 serialization methods * Refactor * Use UInt256 in InternalExecutor * Undo unrelated change * Update TestMessage * Revert type updates to InternalExecutor * Update Serializer and PersistentState * Update package versions * Update SC packages to version 1.4.0-alpha * Add serializers and enums * Add primitive serializer * Update StandardToken.cs * Revert change * Update integration tests * Use switch/case in Serialize/Deserialize * Update package versions and remove workaround * Ensure the right Stratis.SmartContracts.Standards is used by contracts * Changes based on feedback * Fix primitive deserialize * Update standards dll name * Fix legacy standards dll path * Don't upgrade standards package of node * Don't upgrade StandardToken of node * Don't upgrade standards package of node * Add Standards forwards compatibility * Add test case for futuristic contracts * Fix Token_Standards_Test * Update RPC_GetReceipt_Returns_Value test * Cleanup non-required changes * Validate standards assembly * Disable automatic downloads * Add LegacyStandardsDLLs * Update test * Bump package versions for Core and Networks * Bump observer version * Update package versions * Refactor * Revert gitignore * Revert unnecessary changes * Revert unnecessary changes * Update Stratis.SmartContracts.xxx to 2.0.0.0 * Update package version for Stratis.SmartContracts.Tests.Common * Add TestNet checkpoints (#402) * Update StraxTest.cs * Add testnet checkpoints * Fix Tests * Update Nuget Scripts and Collateral/FederatedPeg versions * Bump Version (#403) * Update some test projects PackageIds and versions * Update PushNuget.ps1 * Update MaxStandardSigOpsCost and CCTS Max Partials Txs (#404) * Update Stratis.SmartContracts.CLR.Validation's allowed primitive types (#405) * Update validator's primitives * Bump package version * Bump Stratis.SmartContracts.Core and Stratis.SmartContracts.CLR versions (#406) * Update validator's primitives * Bump package version * Bump Stratis.SmartContracts.Core and Stratis.SmartContracts.CLR versions * Bump Stratis.Bicoin.Features.SmartContracts as well * Bump Stratis.SmartContracts.Networks * Update PushSCNuget.ps1 * Fix Finality Bug (#407) * Fix Finalized Bug * Fix Build * Trigger CI * Fix Tests * Comments * Console Cleanup (#408) * Suppress bench logs * Done * Update NodeStats.cs * Fix Test * Bump version prior to fixing NuGet dependencies for Stratis.SmartContracts.Tests.Common (#409) * Rocks DB (#401) * WIP * Fix DB connections * Update CirrusD * Add RocksDbNative * Update Stratis.CirrusD.csproj * Add Snappy * Fix BlockStoreQueue * Add switch to program.cs * Fix Logger * Cleanup Console Logs * Add RocksDbChainStore * Update BlockPuller.cs * Implement RocksDb KeyValueStore * Add dbtype switch to StraxD * NodeStats changes * KeyValue fix * Fix Build * Voting / Wallet Log * Update ConnectionManager.cs * Hide bench stats * Update VersionProvider.cs * Self Review * Fix Test * Update ConnectionManager.cs * Fix Locks in StakeDB * Create RocksDbProvenBlockHeaderRepository * Update RocksDbProvenBlockHeaderRepository.cs * Add network type to console * Fix Test * Update NodeStats.cs * Add datafolder to nodestats * Fix Merge * Revert * Revert launchsettings * Add ColdStaking to PushNuGet script (#410) * Add ColdStaking to PushNuGet script * Bump Stratis.Bitcoin.IntegrationTests.Common to 1.0.7 * Bump Stratis.Core.Tests.Common to 1.0.7 * Update NEthereum and Stratis.Patricia * SLN changes * Clean up commits * Remove launchSettings change Co-authored-by: Francois de la Rouviere Co-authored-by: quantumagi * Add references (#413) * Initial InterFlux version * Update NBitcoin.csproj (#391) * Pass along height information for OP_FEDERATION (#379) * Pass along height information for OP_FEDERATION * Add defaults * Add defaults * Filter Transaction History by Transaction Id (#393) * Update WalletService.cs * Add Filter * Fix Test * Perform check for cold staking txs * Refactor * Modify DI sequence * Add mainnet check points (#400) * Add MainNet check points * Fix Tests * Change gas limit upper bound to 250,000 (#399) * Add UInt256, serialization methods and CLR updates (#365) * Add UInt256 serialization methods * Refactor * Use UInt256 in InternalExecutor * Undo unrelated change * Update TestMessage * Revert type updates to InternalExecutor * Update Serializer and PersistentState * Update package versions * Update SC packages to version 1.4.0-alpha * Add serializers and enums * Add primitive serializer * Update StandardToken.cs * Revert change * Update integration tests * Use switch/case in Serialize/Deserialize * Update package versions and remove workaround * Ensure the right Stratis.SmartContracts.Standards is used by contracts * Changes based on feedback * Fix primitive deserialize * Update standards dll name * Fix legacy standards dll path * Don't upgrade standards package of node * Don't upgrade StandardToken of node * Don't upgrade standards package of node * Add Standards forwards compatibility * Add test case for futuristic contracts * Fix Token_Standards_Test * Update RPC_GetReceipt_Returns_Value test * Cleanup non-required changes * Validate standards assembly * Disable automatic downloads * Add LegacyStandardsDLLs * Update test * Bump package versions for Core and Networks * Bump observer version * Update package versions * Refactor * Revert gitignore * Revert unnecessary changes * Revert unnecessary changes * Update Stratis.SmartContracts.xxx to 2.0.0.0 * Update package version for Stratis.SmartContracts.Tests.Common * Add TestNet checkpoints (#402) * Update StraxTest.cs * Add testnet checkpoints * Fix Tests * Update Nuget Scripts and Collateral/FederatedPeg versions * Bump Version (#403) * Update some test projects PackageIds and versions * Update PushNuget.ps1 * Update MaxStandardSigOpsCost and CCTS Max Partials Txs (#404) * Update Stratis.SmartContracts.CLR.Validation's allowed primitive types (#405) * Update validator's primitives * Bump package version * Bump Stratis.SmartContracts.Core and Stratis.SmartContracts.CLR versions (#406) * Update validator's primitives * Bump package version * Bump Stratis.SmartContracts.Core and Stratis.SmartContracts.CLR versions * Bump Stratis.Bicoin.Features.SmartContracts as well * Bump Stratis.SmartContracts.Networks * Update PushSCNuget.ps1 * Fix Finality Bug (#407) * Fix Finalized Bug * Fix Build * Trigger CI * Fix Tests * Comments * Console Cleanup (#408) * Suppress bench logs * Done * Update NodeStats.cs * Fix Test * Bump version prior to fixing NuGet dependencies for Stratis.SmartContracts.Tests.Common (#409) * Rocks DB (#401) * WIP * Fix DB connections * Update CirrusD * Add RocksDbNative * Update Stratis.CirrusD.csproj * Add Snappy * Fix BlockStoreQueue * Add switch to program.cs * Fix Logger * Cleanup Console Logs * Add RocksDbChainStore * Update BlockPuller.cs * Implement RocksDb KeyValueStore * Add dbtype switch to StraxD * NodeStats changes * KeyValue fix * Fix Build * Voting / Wallet Log * Update ConnectionManager.cs * Hide bench stats * Update VersionProvider.cs * Self Review * Fix Test * Update ConnectionManager.cs * Fix Locks in StakeDB * Create RocksDbProvenBlockHeaderRepository * Update RocksDbProvenBlockHeaderRepository.cs * Add network type to console * Fix Test * Update NodeStats.cs * Add datafolder to nodestats * Fix Merge * Revert * Revert launchsettings * Add ColdStaking to PushNuGet script (#410) * Add ColdStaking to PushNuGet script * Bump Stratis.Bitcoin.IntegrationTests.Common to 1.0.7 * Bump Stratis.Core.Tests.Common to 1.0.7 * Update NEthereum and Stratis.Patricia * SLN changes * Clean up commits * Remove launchSettings change * Add references Co-authored-by: Francois de la Rouviere Co-authored-by: quantumagi * Conditional invocation (#419) * Update LaunchSidechainMasternode.ps1 (#420) * Update LaunchSidechainMasternode.ps1 * Update LaunchSidechainMasternode.ps1 * Update LaunchSidechainMasternode.ps1 * Update LaunchSidechainMasternode.ps1 * Update LaunchSidechainMasternode.ps1 * Update LaunchSidechainMasternode.ps1 * Update LaunchSidechainMasternode.ps1 * Update LaunchSidechainMasternode.ps1 * Move interflux namespace (#428) * Fix null reference (#429) * Update to 1.0.7.2 (#430) * Update NBitcoin.csproj (#391) * Pass along height information for OP_FEDERATION (#379) * Pass along height information for OP_FEDERATION * Add defaults * Add defaults * Filter Transaction History by Transaction Id (#393) * Update WalletService.cs * Add Filter * Fix Test * Perform check for cold staking txs * Add mainnet check points (#400) * Add MainNet check points * Fix Tests * Change gas limit upper bound to 250,000 (#399) * Add UInt256, serialization methods and CLR updates (#365) * Add UInt256 serialization methods * Refactor * Use UInt256 in InternalExecutor * Undo unrelated change * Update TestMessage * Revert type updates to InternalExecutor * Update Serializer and PersistentState * Update package versions * Update SC packages to version 1.4.0-alpha * Add serializers and enums * Add primitive serializer * Update StandardToken.cs * Revert change * Update integration tests * Use switch/case in Serialize/Deserialize * Update package versions and remove workaround * Ensure the right Stratis.SmartContracts.Standards is used by contracts * Changes based on feedback * Fix primitive deserialize * Update standards dll name * Fix legacy standards dll path * Don't upgrade standards package of node * Don't upgrade StandardToken of node * Don't upgrade standards package of node * Add Standards forwards compatibility * Add test case for futuristic contracts * Fix Token_Standards_Test * Update RPC_GetReceipt_Returns_Value test * Cleanup non-required changes * Validate standards assembly * Disable automatic downloads * Add LegacyStandardsDLLs * Update test * Bump package versions for Core and Networks * Bump observer version * Update package versions * Refactor * Revert gitignore * Revert unnecessary changes * Revert unnecessary changes * Update Stratis.SmartContracts.xxx to 2.0.0.0 * Update package version for Stratis.SmartContracts.Tests.Common * Add TestNet checkpoints (#402) * Update StraxTest.cs * Add testnet checkpoints * Fix Tests * Update Nuget Scripts and Collateral/FederatedPeg versions * Bump Version (#403) * Update some test projects PackageIds and versions * Update PushNuget.ps1 * Update MaxStandardSigOpsCost and CCTS Max Partials Txs (#404) * Update Stratis.SmartContracts.CLR.Validation's allowed primitive types (#405) * Update validator's primitives * Bump package version * Bump Stratis.SmartContracts.Core and Stratis.SmartContracts.CLR versions (#406) * Update validator's primitives * Bump package version * Bump Stratis.SmartContracts.Core and Stratis.SmartContracts.CLR versions * Bump Stratis.Bicoin.Features.SmartContracts as well * Bump Stratis.SmartContracts.Networks * Update PushSCNuget.ps1 * Fix Finality Bug (#407) * Fix Finalized Bug * Fix Build * Trigger CI * Fix Tests * Comments * Console Cleanup (#408) * Suppress bench logs * Done * Update NodeStats.cs * Fix Test * Bump version prior to fixing NuGet dependencies for Stratis.SmartContracts.Tests.Common (#409) * Rocks DB (#401) * WIP * Fix DB connections * Update CirrusD * Add RocksDbNative * Update Stratis.CirrusD.csproj * Add Snappy * Fix BlockStoreQueue * Add switch to program.cs * Fix Logger * Cleanup Console Logs * Add RocksDbChainStore * Update BlockPuller.cs * Implement RocksDb KeyValueStore * Add dbtype switch to StraxD * NodeStats changes * KeyValue fix * Fix Build * Voting / Wallet Log * Update ConnectionManager.cs * Hide bench stats * Update VersionProvider.cs * Self Review * Fix Test * Update ConnectionManager.cs * Fix Locks in StakeDB * Create RocksDbProvenBlockHeaderRepository * Update RocksDbProvenBlockHeaderRepository.cs * Add network type to console * Fix Test * Update NodeStats.cs * Add datafolder to nodestats * Fix Merge * Revert * Revert launchsettings * Add ColdStaking to PushNuGet script (#410) * Add ColdStaking to PushNuGet script * Bump Stratis.Bitcoin.IntegrationTests.Common to 1.0.7 * Bump Stratis.Core.Tests.Common to 1.0.7 * Update to Stratis.RocksDb (#415) * Prevent changing published packages w/o bumping local version (#411) * Prevent changing package code of published version - must bump version * Update comments * Small refactor * Ensure Release folder exists * Check version changes related to project references * Bump versions * Refactor * Refactor * Fix wallet feature vesion * Update RocksDb dylibs (#417) * Update to Stratis.RocksDb * Update to version 0.0.9.1 * Update RocksDb Nugets * Update Rocksdb (#418) * Revert "Dont fetch/process deposits until the CCTS has cleared its suspended transfers (#367)" This reverts commit 334b4f0d687904a834628c77b79b03dd4272dd96. * Update Nuget * Update CirrusMain.cs (#422) * Consolidate outstanding console changes for the MS/Miner (#423) * update RocksDb Nuget (#424) * Update Stratis.Bitcoin Assembly info to version 1.0.7.2 * Fix Seeder (#425) * Fix AddressIndexer Console (#426) * Update ConnectionManager.cs (#427) * Fix merge conflicts Co-authored-by: Francois de la Rouviere Co-authored-by: quantumagi * Add signature to coordination payload and improve logging (#432) * Fix constructor (#433) * Configurable gas limit and price (#434) * Recreate event filter on RPC exception * Clean up * Fixes & updated deposit confirmation intervals * Asyncify * Remove unused * Add minimum threshold * Update LaunchSidechainMasternode.ps1 * Add documentation and refactor transaction manager * Register message type * comment ETH Height check * return loaded eth account * Refactor transaction originator logic * Default gas price * Add status endpoint * Fix data type * Pass the calculated fee to the builder (#455) * Add initial Gnosis Safe interface (#466) * Add reserve balance logic * Initial Gnosis Safe interface classes * Filter (#467) * Update LaunchSidechainMasternode.ps1 * Update LaunchSidechainMasternode.ps1 * Update ethGasLimit * Refactor conversion transaction state machine (#473) * Make existing state numbers the same as previous version * Fix transfer destination * Increment agent version * Fix quorum * Trap invalid transactionId * Adjust threshold and agent string * Fix withdrawalAddresses return type * Comments, creating logger the new way * clean usings * htmldoc for IConversionRequestRepository * more htmldocs * Fix tests * Bump CI * remove unused InteropRequestRepository * Update CirrusTest.cs (#491) * Removed InteropRequestKeyValueStore and InteropRequest bc unused * fix namespaces * Move IConversionRequestKeyValueStore and IConversionRequestRepository to the files with implementation * Move interface and fix namespace --- Scripts/LaunchSidechainMasternode.ps1 | 125 ++- src/FederationSetup/Program.cs | 1 - src/NBitcoin/Consensus.cs | 1 - src/NBitcoin/NBitcoin.csproj | 4 +- src/NBitcoin/Network.cs | 12 +- src/NBitcoin/Script.cs | 1 - .../BlockStoreTests.cs | 8 +- .../Models/ColdStakingModels.cs | 3 +- .../CoinViews/CoinView.cs | 1 - .../CoinViews/InMemoryCoinView.cs | 1 - .../CoinviewPrefetcher.cs | 1 - .../PosConsensusFeature.cs | 1 - .../PowConsensusFeature.cs | 1 - .../ProvenHeaderCoinstakeRule.cs | 3 +- .../Controllers/InteropController.cs | 105 +++ .../ContractSource/GnosisSafeProxy.cs | 185 +++++ .../ContractSource/GnosisSafeProxyFactory.cs | 52 ++ .../ContractSource/MultisigWallet.sol | 392 ++++++++++ .../EthereumClient/ContractSource/README.md | 5 + .../ContractSource/WrappedStrax.sol | 60 ++ .../EthereumClient/EthereumClientBase.cs | 138 ++++ .../EthereumClient/IEthereumClientBase.cs | 91 +++ .../EthereumClient/MultisigWallet.cs | 713 ++++++++++++++++++ .../EthereumClient/WrappedStrax.cs | 665 ++++++++++++++++ .../FodyWeavers.xml | 3 + .../InteropBehavior.cs | 124 +++ .../InteropFeature.cs | 85 +++ .../InteropPoller.cs | 533 +++++++++++++ .../InteropSettings.cs | 74 ++ .../InteropTransactionManager.cs | 173 +++++ .../Models/ConversionRequestModel.cs | 28 + .../Models/InteropStatusResponseModel.cs | 17 + .../Payloads/InteropCoordinationPayload.cs | 43 ++ .../Stratis.Bitcoin.Features.Interop.csproj | 31 + .../LightWalletFeature.cs | 1 - .../LightWalletSyncManager.cs | 2 - .../MempoolValidatorTest.cs | 1 - .../MemPoolCoinView.cs | 2 - .../BlockDefinition.cs | 12 +- .../Stratis.Bitcoin.Features.Miner.csproj | 4 +- .../BlockNotificationFeature.cs | 1 - .../TransactionNotificationFeature.cs | 1 - .../SlotsManagerTests.cs | 1 - .../PoAHeaderSignatureRule.cs | 3 +- .../RPCFeature.cs | 2 - .../ApiLogDeserializerTests.cs | 3 +- .../Rules/AllowedScriptTypeRuleTests.cs | 1 - .../Consensus/Rules/CanGetSenderRuleTest.cs | 1 - .../Consensus/Rules/OpSpendRuleTest.cs | 1 - .../Rules/P2PKHNotContractRuleTests.cs | 1 - .../Rules/TxOutSmartContractExecRuleTest.cs | 1 - .../ApiLogDeserializer.cs | 2 +- .../MempoolRules/CanGetSenderMempoolRule.cs | 1 - .../MempoolRules/OpSpendMempoolRule.cs | 1 - .../BuildCallContractTransactionRequest.cs | 1 - .../PoA/SmartContractPoABlockHeader.cs | 1 - .../Rules/AllowedScriptTypeRule.cs | 1 - .../Rules/CanGetSenderRule.cs | 1 - .../ContractTransactionFullValidationRule.cs | 2 - .../Rules/P2PKHNotContractRule.cs | 1 - .../Rules/TxOutSmartContractExecRule.cs | 1 - .../SmartContractFeature.cs | 6 +- .../SmartContractScriptAddressReader.cs | 1 - .../SmartContractTransactionPolicy.cs | 1 - ...tis.Bitcoin.Features.SmartContracts.csproj | 2 + .../Wallet/SmartContractWalletController.cs | 1 - .../SmartContractWalletTransactionHandler.cs | 4 +- .../WalletTransactionHandlerTest.cs | 1 - .../Interfaces/IWalletSyncManager.cs | 1 - .../UnspentOutputReference.cs | 5 +- .../WatchOnlyWalletFeature.cs | 1 - .../EnvironmentMockUpHelpers/NodeBuilder.cs | 7 +- .../Program.cs | 1 - .../RPC/RPCTestsMutable.cs | 1 - .../Policies/StraxStandardScriptsRegistry.cs | 1 - .../WalletTestsHelpers.cs | 2 - .../Consensus/TestInMemoryCoinView.cs | 1 - .../Models/TransactionModelsTest.cs | 2 - .../EnforcePeerVersionCheckBehaviorTests.cs | 6 - .../Utilities/DBreezeTest.cs | 1 - .../Configuration/DataFolder.cs | 6 + .../Converters/BtcDecimalJsonConverter.cs | 1 - .../Controllers/Models/BannedPeerModel.cs | 4 - .../P2P/PeerConnectorAddNode.cs | 1 - .../Properties/AssemblyInfo.cs | 4 +- src/Stratis.Bitcoin/Stratis.Bitcoin.csproj | 2 +- src/Stratis.Bitcoin/Utilities/PrefixLogger.cs | 1 - .../Properties/launchSettings.json | 10 +- .../Properties/launchSettings.json | 16 + src/Stratis.CirrusPegD/Program.cs | 2 + .../Stratis.CirrusPegD.csproj | 1 + ...tures.FederatedPeg.IntegrationTests.csproj | 1 + .../Utils/FederatedPegTestHelper.cs | 1 - .../Utils/SidechainFederationNodeRunner.cs | 2 + .../Utils/SidechainTestContext.cs | 1 - .../CrossChainTestBase.cs | 5 +- .../DepositExtractorTests.cs | 58 +- .../MaturedBlocksSyncManagerTests.cs | 8 +- .../RestApiClientBaseTests.cs | 2 +- .../WithdrawalExtractorTests.cs | 6 +- .../Conversion/ConversionRequest.cs | 98 +++ .../ConversionRequestKeyValueStore.cs | 126 ++++ .../Conversion/ConversionRequestRepository.cs | 67 ++ .../Events/WalletNeedsConsolidation.cs | 3 +- .../FederatedPegFeature.cs | 5 + .../FederatedPegSettings.cs | 3 + .../ConsolidationTransaction.cs | 1 - .../Interfaces/IDepositExtractor.cs | 2 - .../Interfaces/IFederatedPegSettings.cs | 2 + .../OpReturnDataReader.cs | 39 +- .../SourceChain/DepositExtractor.cs | 44 +- .../SourceChain/MaturedBlocksProvider.cs | 13 +- .../TargetChain/CrossChainTransferStore.cs | 1 - .../TargetChain/MaturedBlocksSyncManager.cs | 76 +- .../TargetChain/WithdrawalExtractor.cs | 52 +- .../TargetChain/WithdrawalHistoryProvider.cs | 5 +- .../Wallet/ConsolidationCoinSelector.cs | 4 +- .../DBLockTests.cs | 3 +- .../WalletAddressLookup.cs | 1 - src/Stratis.FullNode.sln | 21 + .../ByteArrayComparerTests.cs | 38 + src/Stratis.Patricia.Tests/KeyTests.cs | 16 + .../Stratis.Patricia.Tests.csproj | 16 + src/Stratis.Patricia.Tests/TrieTests.cs | 178 +++++ src/Stratis.Patricia/AssemblyInfo.cs | 3 + src/Stratis.Patricia/ByteArrayComparer.cs | 43 ++ src/Stratis.Patricia/IHasher.cs | 10 + src/Stratis.Patricia/IPatriciaTrie.cs | 16 + src/Stratis.Patricia/ISource.cs | 29 + src/Stratis.Patricia/Keccak256Hasher.cs | 13 + src/Stratis.Patricia/Key.cs | 180 +++++ .../MemoryDictionarySource.cs | 47 ++ src/Stratis.Patricia/Node.cs | 329 ++++++++ src/Stratis.Patricia/NodeType.cs | 9 + .../PatriciaTreeResolutionException.cs | 9 + src/Stratis.Patricia/PatriciaTrie.cs | 319 ++++++++ src/Stratis.Patricia/Stratis.Patricia.csproj | 18 + .../CallDataSerializerTests.cs | 1 - .../ContractAssemblyLoaderTests.cs | 5 +- .../ContractLogHolderTests.cs | 1 - .../ReflectionVirtualMachineTests.cs | 1 - .../CallDataSerializer.cs | 5 +- .../ContractTxData.cs | 1 - .../IVirtualMachine.cs | 1 - .../ReflectionVirtualMachine.cs | 1 - .../ContractPrimitiveSerializer.cs | 4 +- .../MethodParameterByteSerializer.cs | 4 +- .../Serialization/Serializer.cs | 2 +- .../ChainIndexerRangeQueryTests.cs | 2 - .../Receipts/ReceiptMatcherTests.cs | 1 - .../Receipts/ReceiptSerializationTest.cs | 17 + .../SenderRetrieverTests.cs | 1 - .../StateRepositoryTests.cs | 1 + .../ChainIndexerRangeQuery.cs | 1 - .../Receipts/Log.cs | 7 +- .../Receipts/Receipt.cs | 13 +- .../ContractUnspentOutput.cs | 3 +- .../State/AccountState.cs | 4 +- .../Stratis.SmartContracts.Core.csproj | 4 +- .../ContractExecutionFailureTests.cs | 1 - .../ContractInternalTransferTests.cs | 2 - .../ContractParameterSerializationTests.cs | 1 - .../Interop/ConversionRequestTests.cs | 87 +++ .../PoW/ContractCreationTests.cs | 2 - .../PoW/SmartContractMemoryPoolTests.cs | 1 - .../WhitelistedContractTests.cs | 3 - .../SmartContractsRegTest.cs | 1 - 167 files changed, 5718 insertions(+), 206 deletions(-) create mode 100644 src/Stratis.Bitcoin.Features.Interop/Controllers/InteropController.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/GnosisSafeProxy.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/GnosisSafeProxyFactory.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/MultisigWallet.sol create mode 100644 src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/README.md create mode 100644 src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/WrappedStrax.sol create mode 100644 src/Stratis.Bitcoin.Features.Interop/EthereumClient/EthereumClientBase.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/EthereumClient/IEthereumClientBase.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/EthereumClient/MultisigWallet.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/EthereumClient/WrappedStrax.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/FodyWeavers.xml create mode 100644 src/Stratis.Bitcoin.Features.Interop/InteropBehavior.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/InteropFeature.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/InteropSettings.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/InteropTransactionManager.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/Models/ConversionRequestModel.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/Models/InteropStatusResponseModel.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/Payloads/InteropCoordinationPayload.cs create mode 100644 src/Stratis.Bitcoin.Features.Interop/Stratis.Bitcoin.Features.Interop.csproj create mode 100644 src/Stratis.CirrusMinerD/Properties/launchSettings.json create mode 100644 src/Stratis.Features.FederatedPeg/Conversion/ConversionRequest.cs create mode 100644 src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestKeyValueStore.cs create mode 100644 src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestRepository.cs create mode 100644 src/Stratis.Patricia.Tests/ByteArrayComparerTests.cs create mode 100644 src/Stratis.Patricia.Tests/KeyTests.cs create mode 100644 src/Stratis.Patricia.Tests/Stratis.Patricia.Tests.csproj create mode 100644 src/Stratis.Patricia.Tests/TrieTests.cs create mode 100644 src/Stratis.Patricia/AssemblyInfo.cs create mode 100644 src/Stratis.Patricia/ByteArrayComparer.cs create mode 100644 src/Stratis.Patricia/IHasher.cs create mode 100644 src/Stratis.Patricia/IPatriciaTrie.cs create mode 100644 src/Stratis.Patricia/ISource.cs create mode 100644 src/Stratis.Patricia/Keccak256Hasher.cs create mode 100644 src/Stratis.Patricia/Key.cs create mode 100644 src/Stratis.Patricia/MemoryDictionarySource.cs create mode 100644 src/Stratis.Patricia/Node.cs create mode 100644 src/Stratis.Patricia/NodeType.cs create mode 100644 src/Stratis.Patricia/PatriciaTreeResolutionException.cs create mode 100644 src/Stratis.Patricia/PatriciaTrie.cs create mode 100644 src/Stratis.Patricia/Stratis.Patricia.csproj create mode 100644 src/Stratis.SmartContracts.IntegrationTests/Interop/ConversionRequestTests.cs diff --git a/Scripts/LaunchSidechainMasternode.ps1 b/Scripts/LaunchSidechainMasternode.ps1 index a205027c61..958d3a47cf 100644 --- a/Scripts/LaunchSidechainMasternode.ps1 +++ b/Scripts/LaunchSidechainMasternode.ps1 @@ -300,6 +300,31 @@ if ( Test-Connection -TargetName 127.0.0.1 -TCPPort $sideChainAPIPort ) Shutdown-SidechainNode } +Write-Host (Get-TimeStamp) "Checking for running GETH Node" -ForegroundColor Cyan +if ( Test-Connection -TargetName 127.0.0.1 -TCPPort $gethAPIPort ) +{ + Write-Host (Get-TimeStamp) "WARNING: A node is already running, please gracefully close GETH with CTRL+C to avoid forceful shutdown" -ForegroundColor DarkYellow + "" + While ( $shutdownCounter -le "30" ) + { + if ( Get-Process -Name geth -ErrorAction SilentlyContinue ) + { + Start-Sleep 3 + Write-Host (Get-TimeStamp) "Waiting for graceful shutdown ( CTRL+C )..." + $shutdownCounter++ + } + Else + { + $shutdownCounter = "31" + } + } + if ( Get-Process -Name geth -ErrorAction SilentlyContinue ) + { + Write-Host (Get-TimeStamp) "WARNING: A node is still running, performing a forced shutdown" -ForegroundColor DarkYellow + Stop-Process -ProcessName geth -Force -ErrorAction SilentlyContinue + } +} + #Check for running dashboard Write-Host (Get-TimeStamp) "Checking for the Stratis Masternode Dashboard" -ForegroundColor Cyan if ( Test-Connection -TargetName 127.0.0.1 -TCPPort 37000 -ErrorAction SilentlyContinue ) @@ -316,6 +341,76 @@ Check-TimeDifference if ( $NodeType -eq "50K" ) { + + #Getting ETH Account + $gethProcess = Start-Process geth -ArgumentList "account list --datadir=$ethDataDir" -NoNewWindow -PassThru -Wait -RedirectStandardOutput $env:TEMP\accountlist.txt + $gethAccountsOuput = Get-Content $env:TEMP\accountlist.txt + $ethAddress = ($gethAccountsOuput.Split('{').Split('}') | Select-Object -Index 1).Insert('0','0x') + Write-Host (Get-TimeStamp) "Loaded $ethAddress..." -ForegroundColor Green + "" + Start-Sleep 10 + + #Launching GETH + $API = $gethAPIPort + Write-Host (Get-TimeStamp) "Starting GETH Masternode" -ForegroundColor Cyan + $StartNode = Start-Process 'geth.exe' -ArgumentList "--syncmode fast --rpc --rpccorsdomain=* --rpcapi web3,eth,debug,personal,net --datadir=$ethDataDir" -PassThru + + While ( -not ( Test-Connection -TargetName 127.0.0.1 -TCPPort $API ) ) + { + Write-Host (Get-TimeStamp) "Waiting for API..." -ForegroundColor Yellow + Start-Sleep 3 + if ( $StartNode.HasExited -eq $true ) + { + Write-Host (Get-TimeStamp) "ERROR: Something went wrong. Please contact support in Discord" -ForegroundColor Red + Start-Sleep 30 + Exit + } + } + <# + $gethPeerCountBody = ConvertTo-Json -Compress @{ + jsonrpc = "2.0" + method = "net_peerCount" + id = "1" + } + + [uint32]$gethPeerCount = Invoke-RestMethod -Uri "http://127.0.0.1:$API" -Method Post -Body $gethPeerCountBody -ContentType application/json | Select-Object -ExpandProperty result + While ( $gethPeerCount -lt 1 ) + { + Write-Host (Get-TimeStamp) "Waiting for Peers..." -ForegroundColor Yellow + Start-Sleep 2 + [uint32]$gethPeerCount = Invoke-RestMethod -Uri "http://127.0.0.1:$API" -Method Post -Body $gethPeerCountBody -ContentType application/json | Select-Object -ExpandProperty result + } + + $gethSyncStateBody = ConvertTo-Json -Compress @{ + jsonrpc = "2.0" + method = "eth_syncing" + id = "1" + } + + $syncStatus = Invoke-RestMethod -Uri "http://127.0.0.1:$API" -Method Post -Body $gethSyncStateBody -ContentType application/json -ErrorAction SilentlyContinue | Select-Object -ExpandProperty result + While ( $syncStatus -eq $false -or $syncStatus.currentBlock -eq $null ) + { + Write-Host (Get-TimeStamp) "Waiting for Blockchain Synchronization to begin" -ForegroundColor Yellow + $syncStatus = Invoke-RestMethod -Uri "http://127.0.0.1:$API" -Method Post -Body $gethSyncStateBody -ContentType application/json -ErrorAction SilentlyContinue | Select-Object -ExpandProperty result + Start-Sleep 2 + } + + [uint32]$currentBlock = Invoke-RestMethod -Uri "http://127.0.0.1:$API" -Method Post -Body $gethSyncStateBody -ContentType application/json | Select-Object -ExpandProperty result | Select-Object -ExpandProperty currentBlock + [uint32]$highestBlock = Invoke-RestMethod -Uri "http://127.0.0.1:$API" -Method Post -Body $gethSyncStateBody -ContentType application/json | Select-Object -ExpandProperty result | Select-Object -ExpandProperty highestBlock + + While ( ( $highestBlock ) -gt ( $currentBlock ) ) + { + [uint32]$currentBlock = Invoke-RestMethod -Uri "http://127.0.0.1:$API" -Method Post -Body $gethSyncStateBody -ContentType application/json | Select-Object -ExpandProperty result | Select-Object -ExpandProperty currentBlock + [uint32]$highestBlock = Invoke-RestMethod -Uri "http://127.0.0.1:$API" -Method Post -Body $gethSyncStateBody -ContentType application/json | Select-Object -ExpandProperty result | Select-Object -ExpandProperty highestBlock + $syncProgress = $highestBlock - $currentBlock + "" + Write-Host (Get-TimeStamp) "The Local Height is $currentBlock" -ForegroundColor Yellow + Write-Host (Get-TimeStamp) "The Current Tip is $highestBlock" -ForegroundColor Yellow + Write-Host (Get-TimeStamp) "$syncProgress Blocks Require Indexing..." -ForegroundColor Yellow + Start-Sleep 10 + } + #> + #Move to CirrusPegD Set-Location -Path $cloneDir/src/Stratis.CirrusPegD } @@ -391,9 +486,16 @@ if ( Get-Variable c -ErrorAction SilentlyContinue ) { Clear-Variable c } #Start Sidechain Node $API = $sideChainAPIPort Write-Host (Get-TimeStamp) "Starting Sidechain Masternode" -ForegroundColor Cyan -if ( $NodeType -eq "50K" ) +if ( $NodeType -eq "50K" ) { - $StartNode = Start-Process dotnet -ArgumentList "run -c Release -- -sidechain -apiport=$sideChainAPIPort -counterchainapiport=$mainChainAPIPort -redeemscript=""$redeemscript"" -publickey=$multiSigPublicKey -federationips=$federationIPs" -PassThru + if ( $ethGasPrice ) + { + $StartNode = Start-Process dotnet -ArgumentList "run -c Release -- -sidechain -apiport=$sideChainAPIPort -counterchainapiport=$mainChainAPIPort -redeemscript=""$redeemscript"" -publickey=$multiSigPublicKey -federationips=$federationIPs -interop=1 -ethereumaccount=$ethAddress -ethereumpassphrase=$ethPassword -multisigwalletcontractaddress=$ethMultiSigContract -wrappedstraxcontractaddress=$ethWrappedStraxContract -ethereumgasprice=$ethGasPrice -ethereumgas=$ethGasLimit" -PassThru + } + Else + { + $StartNode = Start-Process dotnet -ArgumentList "run -c Release -- -sidechain -apiport=$sideChainAPIPort -counterchainapiport=$mainChainAPIPort -redeemscript=""$redeemscript"" -publickey=$multiSigPublicKey -federationips=$federationIPs -interop=1 -ethereumaccount=$ethAddress -ethereumpassphrase=$ethPassword -multisigwalletcontractaddress=$ethMultiSigContract -wrappedstraxcontractaddress=$ethWrappedStraxContract" -PassThru + } } Else { @@ -593,11 +695,12 @@ Write-Host (Get-TimeStamp) "SUCCESS: Stratis Masternode Dashboard launched" -For Exit + # SIG # Begin signature block # MIIO+wYJKoZIhvcNAQcCoIIO7DCCDugCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR -# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUNjDKWkl5DNclOcReU/bbOt2t -# 1j2gggxDMIIFfzCCBGegAwIBAgIQB+RAO8y2U5CYymWFgvSvNDANBgkqhkiG9w0B +# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUygfSbIIh67aa4zS8Liyk87Z1 +# MvqgggxDMIIFfzCCBGegAwIBAgIQB+RAO8y2U5CYymWFgvSvNDANBgkqhkiG9w0B # AQsFADBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBFViBDb2Rl # IFNpZ25pbmcgQ0EgKFNIQTIpMB4XDTE4MDcxNzAwMDAwMFoXDTIxMDcyMTEyMDAw @@ -667,11 +770,11 @@ Exit # Y2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEVWIENvZGUgU2lnbmluZyBDQSAo # U0hBMikCEAfkQDvMtlOQmMplhYL0rzQwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcC # AQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYB -# BAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFLVSB4FSW+DI -# nvxrRxJ5M8+ETM94MA0GCSqGSIb3DQEBAQUABIIBAGX++n4uElYFjo2HkbFBQllW -# q6c7Hti09KnDyw75pWPM4CiQ8XE+4W44k3t4OKIe5GcBQW/3BniDoHzZQP6AF31i -# kmoAazG5IraZuQhj9xiqudD/dzQNfg/gBf60M7RFydpGc/vhIecQR3ZQ8o3JgF5U -# 5XfXbscPdWgFkaEgFGs+LfAoJopLLjmb1lJlrUUrVHl8wnMry3XrZvDsLB4whwHp -# QBKMbeYrLbM19Ln8rsHzLuiKwY8H5aGksClJQ2Ip/u3OkYEIBGy1Vp8gkPsq8nIM -# boWpdBBgeQUsOoWyOSpO2zdSfe9s08n2zEYcuiHarUM2WAc3O70hzwM8NJs8iSA= +# BAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFApMBalGh7Bq +# 6Lzaql1r51+Tu81XMA0GCSqGSIb3DQEBAQUABIIBAK0Xb6xT8DDw47Pzl/WFLyft +# O22nOg6uaF9i5M/Iz23GBQ9dqdTza++l6IInX0ivQBpSdb4/MlO0Gn0878pVQZKY +# NZIc911dAedpJxVOs4NzaaxxRDnYyQtNscCuTc0cEx0OkY63JPsVyYBKC9WXn5OU +# 71kOvZ8E/7kGE/LwJPHCrcZcgiYI6QXelGOVWjlWF9fu9/ULrkPemZ8QtGXxAH31 +# NqZGgouKR2gOBzuMDvyJ8B09RoNUVgmzOlXS5P6DBpubUQxA+P8RQ5XoBxT+f5tR +# iiMIgklcXa0j3Oi4ZyWNCkjrHWj101uf2MHYFXNzWsQMTIz2ML5X4HZRDA354sE= # SIG # End signature block diff --git a/src/FederationSetup/Program.cs b/src/FederationSetup/Program.cs index 6dc301b706..c5b6386006 100644 --- a/src/FederationSetup/Program.cs +++ b/src/FederationSetup/Program.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data; using System.IO; using System.Linq; using NBitcoin; diff --git a/src/NBitcoin/Consensus.cs b/src/NBitcoin/Consensus.cs index 8c4067b69b..414ed9fa0a 100644 --- a/src/NBitcoin/Consensus.cs +++ b/src/NBitcoin/Consensus.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using NBitcoin.BouncyCastle.Math; -using NBitcoin.Rules; namespace NBitcoin { diff --git a/src/NBitcoin/NBitcoin.csproj b/src/NBitcoin/NBitcoin.csproj index 174ed04de4..360631cae1 100644 --- a/src/NBitcoin/NBitcoin.csproj +++ b/src/NBitcoin/NBitcoin.csproj @@ -1,4 +1,4 @@ - + NStratis @@ -56,4 +56,4 @@ - + \ No newline at end of file diff --git a/src/NBitcoin/Network.cs b/src/NBitcoin/Network.cs index 28986cef1d..1825f854da 100644 --- a/src/NBitcoin/Network.cs +++ b/src/NBitcoin/Network.cs @@ -451,13 +451,19 @@ public byte[] MagicBytes public IFederations Federations { get; protected set; } - /// This is used for reward distribution transactions. + /// + /// This is used for reward distribution transactions. + /// public string CirrusRewardDummyAddress { get; protected set; } - /// The height at which reward batching will be activated. + /// + /// The height at which reward batching will be activated. + /// public int RewardClaimerBatchActivationHeight { get; set; } - /// Rewards will be claimed every N blocks, as defined here. + /// + /// Rewards will be claimed every N blocks, as defined here. + /// public int RewardClaimerBlockInterval { get; set; } /// diff --git a/src/NBitcoin/Script.cs b/src/NBitcoin/Script.cs index 8395a08aec..e655abe7de 100644 --- a/src/NBitcoin/Script.cs +++ b/src/NBitcoin/Script.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using NBitcoin.Crypto; diff --git a/src/Stratis.Bitcoin.Features.BlockStore.Tests/BlockStoreTests.cs b/src/Stratis.Bitcoin.Features.BlockStore.Tests/BlockStoreTests.cs index 1bc637737c..e34b8ff765 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore.Tests/BlockStoreTests.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore.Tests/BlockStoreTests.cs @@ -46,7 +46,10 @@ public BlockStoreTests() { this.network = new StraxMain(); this.repositoryTipHashAndHeight = new HashHeightPair(this.network.GenesisHash, 0); - this.storeSettings = new StoreSettings(NodeSettings.Default(this.network)); + + var nodeSettings = new NodeSettings(this.network, args: new [] { $"-datadir={TestBase.GetTestDirectoryPath(this)}" }); + + this.storeSettings = new StoreSettings(nodeSettings); this.random = new Random(); @@ -60,6 +63,9 @@ public BlockStoreTests() this.nodeLifetime = new NodeLifetime(); this.blockRepositoryMock = new Mock(); + + this.blockRepositoryMock.Setup(x => x.TxIndex).Returns(this.storeSettings.TxIndex); + this.blockRepositoryMock.Setup(x => x.PutBlocks(It.IsAny(), It.IsAny>())) .Callback((HashHeightPair newTip, List blocks) => { diff --git a/src/Stratis.Bitcoin.Features.ColdStaking/Models/ColdStakingModels.cs b/src/Stratis.Bitcoin.Features.ColdStaking/Models/ColdStakingModels.cs index f289594fd0..40606b7d4f 100644 --- a/src/Stratis.Bitcoin.Features.ColdStaking/Models/ColdStakingModels.cs +++ b/src/Stratis.Bitcoin.Features.ColdStaking/Models/ColdStakingModels.cs @@ -1,5 +1,4 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; using Stratis.Bitcoin.Utilities.ValidationAttributes; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs index 935eecb597..b356a60b5d 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Threading; using NBitcoin; using Stratis.Bitcoin.Utilities; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs index c96e552c40..e35770925a 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using NBitcoin; using Stratis.Bitcoin.Utilities; using ReaderWriterLock = NBitcoin.ReaderWriterLock; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinviewPrefetcher.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinviewPrefetcher.cs index 101516886d..273c044640 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinviewPrefetcher.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinviewPrefetcher.cs @@ -8,7 +8,6 @@ using Stratis.Bitcoin.Base.Deployments; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.Consensus.CoinViews; -using Stratis.Bitcoin.Utilities; namespace Stratis.Bitcoin.Features.Consensus { diff --git a/src/Stratis.Bitcoin.Features.Consensus/PosConsensusFeature.cs b/src/Stratis.Bitcoin.Features.Consensus/PosConsensusFeature.cs index 1c05720993..2b8e061e54 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/PosConsensusFeature.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/PosConsensusFeature.cs @@ -13,7 +13,6 @@ using Stratis.Bitcoin.Features.Consensus.Behaviors; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.P2P.Peer; -using Stratis.Bitcoin.P2P.Protocol.Payloads; [assembly: InternalsVisibleTo("Stratis.Bitcoin.Features.Miner.Tests")] [assembly: InternalsVisibleTo("Stratis.Bitcoin.Features.Consensus.Tests")] diff --git a/src/Stratis.Bitcoin.Features.Consensus/PowConsensusFeature.cs b/src/Stratis.Bitcoin.Features.Consensus/PowConsensusFeature.cs index 2dc85589ca..a0fd72c5b5 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/PowConsensusFeature.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/PowConsensusFeature.cs @@ -8,7 +8,6 @@ using Stratis.Bitcoin.Connection; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Interfaces; -using Stratis.Bitcoin.P2P.Protocol.Payloads; [assembly: InternalsVisibleTo("Stratis.Bitcoin.Features.Miner.Tests")] [assembly: InternalsVisibleTo("Stratis.Bitcoin.Features.Consensus.Tests")] diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/ProvenHeaderRules/ProvenHeaderCoinstakeRule.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/ProvenHeaderRules/ProvenHeaderCoinstakeRule.cs index 7bc342cad4..25ef9ede83 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/ProvenHeaderRules/ProvenHeaderCoinstakeRule.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/ProvenHeaderRules/ProvenHeaderCoinstakeRule.cs @@ -1,5 +1,4 @@ -using System.Linq; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using NBitcoin; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.Consensus.CoinViews; diff --git a/src/Stratis.Bitcoin.Features.Interop/Controllers/InteropController.cs b/src/Stratis.Bitcoin.Features.Interop/Controllers/InteropController.cs new file mode 100644 index 0000000000..4958be87ba --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/Controllers/InteropController.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.AspNetCore.Mvc; +using NBitcoin; +using NLog; +using Stratis.Bitcoin.Features.Interop.Models; +using Stratis.Bitcoin.Utilities.JsonErrors; +using Stratis.Features.FederatedPeg.Conversion; + +namespace Stratis.Bitcoin.Features.Interop.Controllers +{ + [ApiVersion("1")] + [Route("api/[controller]")] + public class InteropController : Controller + { + private readonly Network network; + + private readonly IConversionRequestRepository conversionRequestRepository; + + private readonly IInteropTransactionManager interopTransactionManager; + + private readonly ILogger logger; + + public InteropController(Network network, IConversionRequestRepository conversionRequestRepository, IInteropTransactionManager interopTransactionManager) + { + this.network = network; + this.conversionRequestRepository = conversionRequestRepository; + this.interopTransactionManager = interopTransactionManager; + this.logger = LogManager.GetCurrentClassLogger(); + } + + [Route("status")] + [HttpGet] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.InternalServerError)] + public IActionResult InteropStatus() + { + try + { + var response = new InteropStatusResponseModel(); + + var mintRequests = new List(); + + foreach (ConversionRequest request in this.conversionRequestRepository.GetAllMint(false)) + { + mintRequests.Add(new ConversionRequestModel() + { + RequestId = request.RequestId, + RequestType = request.RequestType, + RequestStatus = request.RequestStatus, + BlockHeight = request.BlockHeight, + DestinationAddress = request.DestinationAddress, + Amount = request.Amount, + Processed = request.Processed + }); + } + + response.MintRequests = mintRequests; + + var burnRequests = new List(); + + foreach (ConversionRequest request in this.conversionRequestRepository.GetAllBurn(false)) + { + burnRequests.Add(new ConversionRequestModel() + { + RequestId = request.RequestId, + RequestType = request.RequestType, + RequestStatus = request.RequestStatus, + BlockHeight = request.BlockHeight, + DestinationAddress = request.DestinationAddress, + Amount = request.Amount, + Processed = request.Processed + }); + } + + response.MintRequests = burnRequests; + + var receivedVotes = new Dictionary>(); + + foreach ((string requestId, HashSet pubKeys) in this.interopTransactionManager.GetStatus()) + { + var pubKeyList = new List(); + + foreach (PubKey pubKey in pubKeys) + { + pubKeyList.Add(pubKey.ToHex()); + } + + receivedVotes.Add(requestId, pubKeyList); + } + + response.ReceivedVotes = receivedVotes; + + return this.Json(response); + } + catch (Exception e) + { + this.logger.Error("Exception occurred: {0}", e.ToString()); + return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()); + } + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/GnosisSafeProxy.cs b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/GnosisSafeProxy.cs new file mode 100644 index 0000000000..cacf1dbd3a --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/GnosisSafeProxy.cs @@ -0,0 +1,185 @@ +using System.Numerics; +using System.Threading.Tasks; +using NBitcoin.DataEncoders; +using Nethereum.ABI; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Contracts; +using Nethereum.Contracts.ContractHandlers; +using Nethereum.Hex.HexConvertors.Extensions; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Util; +using Nethereum.Web3; + +namespace Stratis.Bitcoin.Features.Interop.EthereumClient.ContractSource +{ + public class GnosisSafeProxyDeployment : ContractDeploymentMessage + { + public static string BYTECODE = + "0x608060405234801561001057600080fd5b5060405161014d38038061014d8339818101604052602081101561003357600080fd5b50516001600160a01b03811661007a5760405162461bcd60e51b81526004018080602001828103825260248152602001806101296024913960400191505060405180910390fd5b600080546001600160a01b039092166001600160a01b03199092169190911790556080806100a96000396000f3fe60806040526001600160a01b036000541663530ca43760e11b6000351415602a578060005260206000f35b3660008037600080366000845af43d6000803e806046573d6000fd5b3d6000f3fea265627a7a7231582046a8dbddaad1ac182d6cbeb8ec6e0517cdba24140b7b56fee470166efea336c664736f6c63430005110032496e76616c6964206d617374657220636f707920616464726573732070726f7669646564"; + + public GnosisSafeProxyDeployment() : base(BYTECODE) + { + } + + // We do not actually need to deploy this contract as it gets created for us by the proxy factory. + // ... + } + + // All the functions defined against this contract are actually delegated to be executed by the singleton master copy of the Gnosis Safe contract that is already deployed on-chain. + + [Function("setup", "uint256")] + public class SetupFunction : FunctionMessage + { + [Parameter("address[]", "_owners", 1)] + public string Owners { get; set; } + + [Parameter("uint256", "threshold", 2)] + public BigInteger Threshold { get; set; } + + [Parameter("address", "to", 3)] + public string To { get; set; } + + [Parameter("bytes", "data", 4)] + public byte[] Data { get; set; } + + [Parameter("address", "fallbackHandler", 5)] + public string FallbackHandler { get; set; } + + [Parameter("address", "paymentToken", 6)] + public string PaymentToken { get; set; } + + [Parameter("uint256", "payment", 7)] + public BigInteger Payment { get; set; } + + [Parameter("address", "paymentReceiver", 8)] + public string PaymentReceiver { get; set; } + } + + [Function("execTransaction", "bool")] + public class ExecTransactionFunction : FunctionMessage + { + [Parameter("address", "to", 1)] + public string To { get; set; } + + [Parameter("uint256", "value", 2)] + public BigInteger Value { get; set; } + + [Parameter("bytes", "data", 3)] + public byte[] Data { get; set; } + + [Parameter("uint8", "operation", 4)] + public uint Operation { get; set; } + + [Parameter("uint256", "safeTxGas", 5)] + public BigInteger SafeTxGas { get; set; } + + [Parameter("uint256", "baseGas", 6)] + public BigInteger BaseGas { get; set; } + + [Parameter("uint256", "gasPrice", 7)] + public BigInteger GasPrice { get; set; } + + [Parameter("address", "gasToken", 8)] + public string GasToken { get; set; } + + [Parameter("address", "refundReceiver", 9)] + public string RefundReceiver { get; set; } + + [Parameter("bytes", "signatures", 10)] + public byte[] Signatures { get; set; } + } + + public class GnosisSafeProxy + { + public static string EncodeSetup(string[] owners, BigInteger threshold, string to = "0x0000000000000000000000000000000000000000", byte[] data = null, string fallbackHandler = "0x0000000000000000000000000000000000000000", string paymentToken = "0x0000000000000000000000000000000000000000", BigInteger payment = new BigInteger(), string paymentReceiver = "0x0000000000000000000000000000000000000000") + { + if (data == null) + data = new byte[] { }; + + var abiEncode = new ABIEncode(); + + // @dev Setup function sets initial storage of contract. + // @param _owners List of Safe owners. + // @param _threshold Number of required confirmations for a Safe transaction. + // @param to Contract address for optional delegate call. + // @param data Data payload for optional delegate call. + // @param fallbackHandler Handler for fallback calls to this contract + // @param paymentToken Token that should be used for the payment (0 is ETH) + // @param payment Value that should be paid + // @param paymentReceiver Address that should receive the payment (or 0 if tx.origin) + return abiEncode.GetABIEncoded( + new ABIValue("address[]", owners), + new ABIValue("uint256", threshold), + new ABIValue("address", to), + new ABIValue("bytes", data), + new ABIValue("address", fallbackHandler), + new ABIValue("address", paymentToken), + new ABIValue("uint256", payment), + new ABIValue("address", paymentReceiver) + ).ToHex(); + } + + /// + /// Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction. + /// + /// Note: The fees are always transferred, even if the user transaction fails. + /// Instance of the web3 client to execute the function against. + /// The address of the Gnosis Safe proxy deployment that owns the wrapped STRAX contract. + /// The address of the wrapped STRAX ERC20 contract. + /// The Ether value of the transaction, if applicable. For ERC20 transfers this is 0. + /// The ABI-encoded data of the transaction, e.g. if a contract method is being called. For ERC20 transfers this will be set. + /// Gas that should be used for the Safe transaction. + /// Gas costs that are independent of the transaction execution (e.g. base transaction fee, signature check, payment of the refund). + /// Gas price that should be used for the payment calculation. + /// The number of packed signatures included. + /// Packed signature data ({bytes32 r}{bytes32 s}{uint8 v}). + /// The transaction hash of the execution transaction. + public static async Task ExecTransactionAsync(Web3 web3, string proxyContract, string wrappedStraxContract, BigInteger value, string data, BigInteger safeTxGas, BigInteger baseGas, BigInteger gasPrice, int signatureCount, byte[] signatures) + { + // These parameters are supplied to the function hardcoded: + // @param operation Operation type of Safe transaction. The Safe supports CALL (uint8 = 0), DELEGATECALL (uint8 = 1) and CREATE (uint8 = 2). + // @param gasToken Token address (or 0 if ETH) that is used for the payment. + // @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). + + IContractTransactionHandler execHandler = web3.Eth.GetContractTransactionHandler(); + + var execTransactionFunctionMessage = new ExecTransactionFunction() + { + To = proxyContract, + Value = value, + Data = Encoders.Hex.DecodeData(data), + Operation = 0, // CALL + SafeTxGas = safeTxGas, + BaseGas = baseGas, + GasPrice = Web3.Convert.ToWei(gasPrice, UnitConversion.EthUnit.Gwei), + GasToken = EthereumClientBase.ZeroAddress, + RefundReceiver = EthereumClientBase.ZeroAddress, + Signatures = signatures + }; + + TransactionReceipt execTransactionReceipt = await execHandler.SendRequestAndWaitForReceiptAsync(proxyContract, execTransactionFunctionMessage).ConfigureAwait(false); + + return execTransactionReceipt.TransactionHash; + } + + public static string ABI = @"[ + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""_masterCopy"", + ""type"": ""address"" + } + ], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""constructor"" + }, + { + ""payable"": true, + ""stateMutability"": ""payable"", + ""type"": ""fallback"" + } + ]"; + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/GnosisSafeProxyFactory.cs b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/GnosisSafeProxyFactory.cs new file mode 100644 index 0000000000..6d57f7101d --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/GnosisSafeProxyFactory.cs @@ -0,0 +1,52 @@ +using System.Threading.Tasks; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Contracts; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Web3; + +namespace Stratis.Bitcoin.Features.Interop.EthereumClient.ContractSource +{ + public class GnosisSafeProxyFactoryDeployment : ContractDeploymentMessage + { + public static string BYTECODE = + "0x608060405234801561001057600080fd5b50610f73806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80631688f0b9146100675780632500510e1461018c57806353e5d9351461026f57806361b69abd146102f2578063addacc0f1461040d578063d18af54d14610490575b600080fd5b61014a6004803603606081101561007d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156100ba57600080fd5b8201836020820111156100cc57600080fd5b803590602001918460018302840111640100000000831117156100ee57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001909291905050506105d5565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61022d600480360360608110156101a257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156101df57600080fd5b8201836020820111156101f157600080fd5b8035906020019184600183028401116401000000008311171561021357600080fd5b909192939192939080359060200190929190505050610674565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6102776107b7565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102b757808201518184015260208101905061029c565b50505050905090810190601f1680156102e45780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103cb6004803603604081101561030857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019064010000000081111561034557600080fd5b82018360208201111561035757600080fd5b8035906020019184600183028401116401000000008311171561037957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506107e2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6104156108d5565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561045557808201518184015260208101905061043a565b50505050905090810190601f1680156104825780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610593600480360360808110156104a657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156104e357600080fd5b8201836020820111156104f557600080fd5b8035906020019184600183028401116401000000008311171561051757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610900565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006105e2848484610af1565b905060008351111561060a5760008060008551602087016000865af1141561060957600080fd5b5b7fa38789425dbeee0239e16ff2d2567e31720127fbc6430758c1a4efc6aef29f8081604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a19392505050565b60006106c58585858080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505084610af1565b905080604051602001808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014019150506040516020818303038152906040526040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561077c578082015181840152602081019050610761565b50505050905090810190601f1680156107a95780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b6060604051806020016107c990610c94565b6020820181038252601f19601f82011660405250905090565b6000826040516107f190610c94565b808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050604051809103906000f080158015610843573d6000803e3d6000fd5b50905060008251111561086c5760008060008451602086016000865af1141561086b57600080fd5b5b7fa38789425dbeee0239e16ff2d2567e31720127fbc6430758c1a4efc6aef29f8081604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a192915050565b6060604051806020016108e790610ca1565b6020820181038252601f19601f82011660405250905090565b6000808383604051602001808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401925050506040516020818303038152906040528051906020012060001c90506109718686836105d5565b9150600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614610ae8578273ffffffffffffffffffffffffffffffffffffffff16631e52b518838888886040518563ffffffff1660e01b8152600401808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001838152602001828103825284818151815260200191508051906020019080838360005b83811015610a80578082015181840152602081019050610a65565b50505050905090810190601f168015610aad5780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b158015610acf57600080fd5b505af1158015610ae3573d6000803e3d6000fd5b505050505b50949350505050565b6000808380519060200120836040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209050606060405180602001610b3e90610c94565b6020820181038252601f19601f820116604052508673ffffffffffffffffffffffffffffffffffffffff166040516020018083805190602001908083835b60208310610b9f5780518252602082019150602081019050602083039250610b7c565b6001836020036101000a038019825116818451168082178552505050505050905001828152602001925050506040516020818303038152906040529050818151826020016000f59250600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610c8b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f437265617465322063616c6c206661696c65640000000000000000000000000081525060200191505060405180910390fd5b50509392505050565b6101e780610cae83390190565b60aa80610e958339019056fe608060405234801561001057600080fd5b506040516101e73803806101e78339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806101c36024913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060aa806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea265627a7a72315820d8a00dc4fe6bf675a9d7416fc2d00bb3433362aa8186b750f76c4027269667ff64736f6c634300050e0032496e76616c6964206d617374657220636f707920616464726573732070726f7669646564608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea265627a7a72315820d8a00dc4fe6bf675a9d7416fc2d00bb3433362aa8186b750f76c4027269667ff64736f6c634300050e0032a265627a7a723158201ce3789010194971b13acfa9eebc44ead6ec5ccc8c78026a0dc52d22c3c4b2bd64736f6c634300050e0032"; + + public GnosisSafeProxyFactoryDeployment() : base(BYTECODE) + { + } + + // We do not actually need to deploy this contract as it exists on-chain already. + // ... + } + + [Function("createProxy")] + public class CreateProxyFunction : FunctionMessage + { + [Parameter("address", "masterCopy", 1)] + public string MasterCopy { get; set; } + + [Parameter("bytes", "data", 2)] + public byte[] Data { get; set; } + } + + public class GnosisSafeProxyFactory + { + public static async Task CreateProxyAsync(Web3 web3, string masterCopy, string proxyFactory, byte[] data) + { + var createProxyHandler = web3.Eth.GetContractTransactionHandler(); + + var transfer = new CreateProxyFunction() + { + MasterCopy = masterCopy, + Data = data + }; + + TransactionReceipt createProxyReceipt = await createProxyHandler.SendRequestAndWaitForReceiptAsync(proxyFactory, transfer).ConfigureAwait(false); + + // TODO: Extract the proxy contract's address from the logs for convenience + return createProxyReceipt.TransactionHash; + } + + public static string ABI = @"[{""anonymous"":false,""inputs"":[{""indexed"":false,""internalType"":""contract Proxy"",""name"":""proxy"",""type"":""address""}],""name"":""ProxyCreation"",""type"":""event""},{""constant"":false,""inputs"":[{""internalType"":""address"",""name"":""_mastercopy"",""type"":""address""},{""internalType"":""bytes"",""name"":""initializer"",""type"":""bytes""},{""internalType"":""uint256"",""name"":""saltNonce"",""type"":""uint256""}],""name"":""calculateCreateProxyWithNonceAddress"",""outputs"":[{""internalType"":""contract Proxy"",""name"":""proxy"",""type"":""address""}],""payable"":false,""stateMutability"":""nonpayable"",""type"":""function""},{""constant"":false,""inputs"":[{""internalType"":""address"",""name"":""masterCopy"",""type"":""address""},{""internalType"":""bytes"",""name"":""data"",""type"":""bytes""}],""name"":""createProxy"",""outputs"":[{""internalType"":""contract Proxy"",""name"":""proxy"",""type"":""address""}],""payable"":false,""stateMutability"":""nonpayable"",""type"":""function""},{""constant"":false,""inputs"":[{""internalType"":""address"",""name"":""_mastercopy"",""type"":""address""},{""internalType"":""bytes"",""name"":""initializer"",""type"":""bytes""},{""internalType"":""uint256"",""name"":""saltNonce"",""type"":""uint256""},{""internalType"":""contract IProxyCreationCallback"",""name"":""callback"",""type"":""address""}],""name"":""createProxyWithCallback"",""outputs"":[{""internalType"":""contract Proxy"",""name"":""proxy"",""type"":""address""}],""payable"":false,""stateMutability"":""nonpayable"",""type"":""function""},{""constant"":false,""inputs"":[{""internalType"":""address"",""name"":""_mastercopy"",""type"":""address""},{""internalType"":""bytes"",""name"":""initializer"",""type"":""bytes""},{""internalType"":""uint256"",""name"":""saltNonce"",""type"":""uint256""}],""name"":""createProxyWithNonce"",""outputs"":[{""internalType"":""contract Proxy"",""name"":""proxy"",""type"":""address""}],""payable"":false,""stateMutability"":""nonpayable"",""type"":""function""},{""constant"":true,""inputs"":[],""name"":""proxyCreationCode"",""outputs"":[{""internalType"":""bytes"",""name"":"""",""type"":""bytes""}],""payable"":false,""stateMutability"":""pure"",""type"":""function""},{""constant"":true,""inputs"":[],""name"":""proxyRuntimeCode"",""outputs"":[{""internalType"":""bytes"",""name"":"""",""type"":""bytes""}],""payable"":false,""stateMutability"":""pure"",""type"":""function""}]"; + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/MultisigWallet.sol b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/MultisigWallet.sol new file mode 100644 index 0000000000..6a777f2b02 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/ContractSource/MultisigWallet.sol @@ -0,0 +1,392 @@ +pragma solidity ^0.4.15; + + +/// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. +/// @author Stefan George - +contract MultiSigWallet { + + /* + * Events + */ + event Confirmation(address indexed sender, uint indexed transactionId); + event Revocation(address indexed sender, uint indexed transactionId); + event Submission(uint indexed transactionId); + event Execution(uint indexed transactionId); + event ExecutionFailure(uint indexed transactionId); + event Deposit(address indexed sender, uint value); + event OwnerAddition(address indexed owner); + event OwnerRemoval(address indexed owner); + event RequirementChange(uint required); + + /* + * Constants + */ + uint constant public MAX_OWNER_COUNT = 50; + + /* + * Storage + */ + mapping (uint => Transaction) public transactions; + mapping (uint => mapping (address => bool)) public confirmations; + mapping (address => bool) public isOwner; + address[] public owners; + uint public required; + uint public transactionCount; + + struct Transaction { + address destination; + uint value; + bytes data; + bool executed; + } + + /* + * Modifiers + */ + modifier onlyWallet() { + require(msg.sender == address(this)); + _; + } + + modifier ownerDoesNotExist(address owner) { + require(!isOwner[owner]); + _; + } + + modifier ownerExists(address owner) { + require(isOwner[owner]); + _; + } + + modifier transactionExists(uint transactionId) { + require(transactions[transactionId].destination != 0); + _; + } + + modifier confirmed(uint transactionId, address owner) { + require(confirmations[transactionId][owner]); + _; + } + + modifier notConfirmed(uint transactionId, address owner) { + require(!confirmations[transactionId][owner]); + _; + } + + modifier notExecuted(uint transactionId) { + require(!transactions[transactionId].executed); + _; + } + + modifier notNull(address _address) { + require(_address != 0); + _; + } + + modifier validRequirement(uint ownerCount, uint _required) { + require(ownerCount <= MAX_OWNER_COUNT + && _required <= ownerCount + && _required != 0 + && ownerCount != 0); + _; + } + + /// @dev Fallback function allows to deposit ether. + function() + payable + { + if (msg.value > 0) + Deposit(msg.sender, msg.value); + } + + /* + * Public functions + */ + /// @dev Contract constructor sets initial owners and required number of confirmations. + /// @param _owners List of initial owners. + /// @param _required Number of required confirmations. + function MultiSigWallet(address[] _owners, uint _required) + public + validRequirement(_owners.length, _required) + { + for (uint i=0; i<_owners.length; i++) { + require(!isOwner[_owners[i]] && _owners[i] != 0); + isOwner[_owners[i]] = true; + } + owners = _owners; + required = _required; + } + + /// @dev Allows to add a new owner. Transaction has to be sent by wallet. + /// @param owner Address of new owner. + function addOwner(address owner) + public + onlyWallet + ownerDoesNotExist(owner) + notNull(owner) + validRequirement(owners.length + 1, required) + { + isOwner[owner] = true; + owners.push(owner); + OwnerAddition(owner); + } + + /// @dev Allows to remove an owner. Transaction has to be sent by wallet. + /// @param owner Address of owner. + function removeOwner(address owner) + public + onlyWallet + ownerExists(owner) + { + isOwner[owner] = false; + for (uint i=0; i owners.length) + changeRequirement(owners.length); + OwnerRemoval(owner); + } + + /// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet. + /// @param owner Address of owner to be replaced. + /// @param newOwner Address of new owner. + function replaceOwner(address owner, address newOwner) + public + onlyWallet + ownerExists(owner) + ownerDoesNotExist(newOwner) + { + for (uint i=0; i string) public withdrawalAddresses; + + constructor(uint256 initialSupply) public ERC20("WrappedStrax", "WSTRAX") { + _mint(msg.sender, initialSupply); + } + + /** + * @dev Creates `amount` new tokens and assigns them to `account`. + * + * See {ERC20-_mint}. + */ + function mint(address account, uint256 amount) public onlyOwner { + _mint(account, amount); + } + + /** + * @dev Destroys `amount` tokens from the caller. + * + * See {ERC20-_burn}. + */ + function burn(uint256 amount, string memory straxAddress) public { + _burn(_msgSender(), amount); + + // When the tokens are burnt we need to know where to credit the equivalent value on the Stratis chain. + // Currently it is only possible to assign a single address here, so if multiple recipients are required + // the burner will have to wait until each burn is processed before proceeding with the next. + withdrawalAddresses[msg.sender] = straxAddress; + } + + /** + * @dev Destroys `amount` tokens from `account`, deducting from the caller's + * allowance. + * + * See {ERC20-_burn} and {ERC20-allowance}. + * + * Requirements: + * + * - the caller must have allowance for ``accounts``'s tokens of at least + * `amount`. + */ + function burnFrom(address account, uint256 amount, string memory straxAddress) public { + uint256 decreasedAllowance = allowance(account, _msgSender()).sub(amount, "ERC20: burn amount exceeds allowance"); + + _approve(account, _msgSender(), decreasedAllowance); + _burn(account, amount); + + // When the tokens are burnt we need to know where to credit the equivalent value on the Stratis chain. + // Currently it is only possible to assign a single address here, so if multiple recipients are required + // the burner will have to wait until each burn is processed before proceeding with the next. + withdrawalAddresses[msg.sender] = straxAddress; + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/EthereumClient/EthereumClientBase.cs b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/EthereumClientBase.cs new file mode 100644 index 0000000000..cc91cc87ef --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/EthereumClientBase.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using System.Numerics; +using System.Threading.Tasks; +using Nethereum.ABI; +using Nethereum.Contracts; +using Nethereum.Hex.HexConvertors.Extensions; +using Nethereum.Hex.HexTypes; +using Nethereum.JsonRpc.Client; +using Nethereum.RPC.Eth.Blocks; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Web3; +using Nethereum.Web3.Accounts.Managed; + +namespace Stratis.Bitcoin.Features.Interop.EthereumClient +{ + public class EthereumClientBase : IEthereumClientBase + { + private readonly InteropSettings interopSettings; + private readonly Web3 web3; + private Event transferEventHandler; + private NewFilterInput filterAllTransferEventsForContract; + private HexBigInteger filterId; + + public const string ZeroAddress = "0x0000000000000000000000000000000000000000"; + + public EthereumClientBase(InteropSettings interopSettings) + { + this.interopSettings = interopSettings; + + if (!this.interopSettings.Enabled) + return; + + var account = new ManagedAccount(interopSettings.EthereumAccount, interopSettings.EthereumPassphrase); + + // TODO: Support loading offline accounts from keystore JSON directly? + this.web3 = !string.IsNullOrWhiteSpace(interopSettings.EthereumClientUrl) ? new Web3(account, interopSettings.EthereumClientUrl) : new Web3(account); + } + + /// + public async Task CreateTransferEventFilterAsync() + { + this.transferEventHandler = this.web3.Eth.GetEvent(this.interopSettings.WrappedStraxAddress); + this.filterAllTransferEventsForContract = this.transferEventHandler.CreateFilterInput(); + this.filterId = await this.transferEventHandler.CreateFilterAsync(this.filterAllTransferEventsForContract).ConfigureAwait(false); + } + + /// + public async Task>> GetTransferEventsForWrappedStraxAsync() + { + try + { + // Note: this will only return events from after the filter is created. + return await this.transferEventHandler.GetFilterChanges(this.filterId).ConfigureAwait(false); + } + catch (RpcResponseException) + { + // If the filter is no longer available it may need to be re-created. + await this.CreateTransferEventFilterAsync().ConfigureAwait(false); + } + + return await this.transferEventHandler.GetFilterChanges(this.filterId).ConfigureAwait(false); + } + + /// + public async Task GetDestinationAddressAsync(string address) + { + return await WrappedStrax.GetDestinationAddressAsync(this.web3, this.interopSettings.WrappedStraxAddress, address).ConfigureAwait(false); + } + + /// + public async Task GetBlockHeightAsync() + { + var blockNumberHandler = new EthBlockNumber(this.web3.Client); + HexBigInteger block = await blockNumberHandler.SendRequestAsync().ConfigureAwait(false); + + return block.Value; + } + + /// + public async Task SubmitTransactionAsync(string destination, BigInteger value, string data) + { + return await MultisigWallet.SubmitTransactionAsync(this.web3, this.interopSettings.MultisigWalletAddress, destination, value, data, this.interopSettings.EthereumGas, this.interopSettings.EthereumGasPrice).ConfigureAwait(false); + } + + /// + public async Task ConfirmTransactionAsync(BigInteger transactionId) + { + return await MultisigWallet.ConfirmTransactionAsync(this.web3, this.interopSettings.MultisigWalletAddress, transactionId, this.interopSettings.EthereumGas, this.interopSettings.EthereumGasPrice).ConfigureAwait(false); + } + + /// + public async Task GetConfirmationCountAsync(BigInteger transactionId) + { + return await MultisigWallet.GetConfirmationCountAsync(this.web3, this.interopSettings.MultisigWalletAddress, transactionId).ConfigureAwait(false); + } + + public async Task GetErc20BalanceAsync(string addressToQuery) + { + return await WrappedStrax.GetErc20BalanceAsync(this.web3, this.interopSettings.WrappedStraxAddress, addressToQuery).ConfigureAwait(false); + } + + /// + public string EncodeTransferParams(string address, BigInteger amount) + { + // TODO: Extract this directly from the ABI + const string TransferMethod = "a9059cbb"; + + var abiEncode = new ABIEncode(); + string result = TransferMethod + abiEncode.GetABIEncoded(new ABIValue("address", address), new ABIValue("uint256", amount)).ToHex(); + + return result; + } + + /// + public string EncodeMintParams(string address, BigInteger amount) + { + // TODO: Extract this directly from the ABI + const string MintMethod = "40c10f19"; + + var abiEncode = new ABIEncode(); + string result = MintMethod + abiEncode.GetABIEncoded(new ABIValue("address", address), new ABIValue("uint256", amount)).ToHex(); + + return result; + } + + /// + public string EncodeBurnParams(BigInteger amount, string straxAddress) + { + // TODO: Extract this directly from the ABI + const string BurnMethod = "7641e6f3"; + + var abiEncode = new ABIEncode(); + string result = BurnMethod + abiEncode.GetABIEncoded(new ABIValue("uint256", amount), new ABIValue("string", straxAddress)).ToHex(); + + return result; + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/EthereumClient/IEthereumClientBase.cs b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/IEthereumClientBase.cs new file mode 100644 index 0000000000..b7f4c70053 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/IEthereumClientBase.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Numerics; +using System.Threading.Tasks; +using Nethereum.Contracts; + +namespace Stratis.Bitcoin.Features.Interop.EthereumClient +{ + public interface IEthereumClientBase + { + /// + /// Creates the filter that the RPC interface uses to listen for events against the desired contract. + /// In this case the filter is specifically listening for Transfer events emitted by the wrapped strax + /// contract. + /// + Task CreateTransferEventFilterAsync(); + + /// + /// Queries the previously created event filter for any new events matching the filter criteria. + /// + /// A list of event logs. + Task>> GetTransferEventsForWrappedStraxAsync(); + + /// + /// Retrieves the STRAX address that was recorded in the wrapped STRAX contract when a given account + /// burnt funds. The destination address must have been provided as a parameter to the burn() method + /// invocation and only one address at a time can be associated with each account. + /// + /// The Ethereum account to retrieve the destination STRAX address for. + /// The STRAX address associated with the provided Ethereum account. + Task GetDestinationAddressAsync(string address); + + /// + /// Retrieves the current block height of the Ethereum node. + /// + Task GetBlockHeightAsync(); + + /// + /// Submits a transaction to the multisig wallet contract, to enable it to be separately confirmed by a quorum of the multisig wallet owners. + /// + /// The account that the transaction is being sent to after confirmation. For wSTRAX operations this will typically be the wSTRAX ERC20 contract address. + /// The amount that is being sent. For wSTRAX operations this is typically zero, as the balance changes are encoded within the additional data. + /// Additional transaction data. This is encoded in accordance with the applicable contract's ABI. + /// Returns the transactionId of the transaction + Task SubmitTransactionAsync(string destination, BigInteger value, string data); + + /// + /// Confirms a multisig wallet transaction. + /// + /// Once a sufficient threshold of confirmations is reached, the contract will automatically execute the saved transaction. + /// The transactionId of an existing transaction stored in the multisig wallet contract. + /// The hash of the confirmation transaction. + Task ConfirmTransactionAsync(BigInteger transactionId); + + /// + /// Retrieve the number of confirmations a given transaction currently has in the multisig wallet contract. + /// + /// The identifier of the transaction. + /// The number of confirmations. + Task GetConfirmationCountAsync(BigInteger transactionId); + + Task GetErc20BalanceAsync(string addressToQuery); + + /// + /// Returns the encoded form of transaction data that calls the transfer(address, uint256) method on the WrappedStrax contract. + /// This is exactly the contents of the 'data' field in a normal transaction. + /// This encoded data is required for submitting a transaction to the multisig contract. + /// + /// The address to transfer a quantity of the wSTRAX token to. + /// The amount (in wei) of tokens to be transferred. + /// The hex data of the encoded parameters. + string EncodeTransferParams(string address, BigInteger amount); + + /// + /// Constructs the data field for a transaction invoking the mint() method of an ERC20 contract that implements it. + /// The actual transaction will be sent to the multisig wallet contract as it is the contract that needs to execute the transaction. + /// + /// The account that needs tokens to be minted into it (not the address of the multisig contract or the wrapped STRAX contract) + /// The number of tokens to be minted. This is denominated in wei. + /// The hex data of the encoded parameters. + string EncodeMintParams(string address, BigInteger amount); + + /// + /// Constructs the data field for a transaction invoking the burn() method of an ERC20 contract that implements it. + /// The actual transaction will be sent to the multisig wallet contract as it is the contract that needs to execute the transaction. + /// + /// The number of tokens to be minted. This is denominated in wei. + /// The destination address on the STRAX chain that the equivalent value of the burnt funds will be sent to. + /// The hex data of the encoded parameters. + string EncodeBurnParams(BigInteger amount, string straxAddress); + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/EthereumClient/MultisigWallet.cs b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/MultisigWallet.cs new file mode 100644 index 0000000000..8ec60616c3 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/MultisigWallet.cs @@ -0,0 +1,713 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using NBitcoin.DataEncoders; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Contracts; +using Nethereum.Contracts.ContractHandlers; +using Nethereum.Contracts.CQS; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Util; +using Nethereum.Web3; + +namespace Stratis.Bitcoin.Features.Interop.EthereumClient +{ + [Function("getOwners", "address[]")] + public class GetOwnersFunction : FunctionMessage + { + } + + public class MultisigWalletDeployment : ContractDeploymentMessage + { + public static string BYTECODE = + "0x60806040523480156200001157600080fd5b50604051620024c8380380620024c883398101806040528101908080518201929190602001805190602001909291905050506000825182603282111580156200005a5750818111155b801562000068575060008114155b801562000076575060008214155b15156200008257600080fd5b600092505b8451831015620001bd57600260008685815181101515620000a457fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16158015620001335750600085848151811015156200011057fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1614155b15156200013f57600080fd5b60016002600087868151811015156200015457fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550828060010193505062000087565b8460039080519060200190620001d5929190620001e8565b50836004819055505050505050620002bd565b82805482825590600052602060002090810192821562000264579160200282015b82811115620002635782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509160200191906001019062000209565b5b50905062000273919062000277565b5090565b620002ba91905b80821115620002b657600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016200027e565b5090565b90565b6121fb80620002cd6000396000f30060806040526004361061011d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063025e7c2714610177578063173825d9146101e457806320ea8d86146102275780632f54bf6e146102545780633411c81c146102af57806354741525146103145780637065cb4814610363578063784547a7146103a65780638b51d13f146103eb5780639ace38c21461042c578063a0e67e2b14610517578063a8abe69a14610583578063b5dc40c314610627578063b77bf600146106a9578063ba51a6df146106d4578063c01a8c8414610701578063c64274741461072e578063d74f8edd146107d5578063dc8452cd14610800578063e20056e61461082b578063ee22610b1461088e575b6000341115610175573373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a25b005b34801561018357600080fd5b506101a2600480360381019080803590602001909291905050506108bb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156101f057600080fd5b50610225600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506108f9565b005b34801561023357600080fd5b5061025260048036038101908080359060200190929190505050610b92565b005b34801561026057600080fd5b50610295600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610d3a565b604051808215151515815260200191505060405180910390f35b3480156102bb57600080fd5b506102fa60048036038101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610d5a565b604051808215151515815260200191505060405180910390f35b34801561032057600080fd5b5061034d600480360381019080803515159060200190929190803515159060200190929190505050610d89565b6040518082815260200191505060405180910390f35b34801561036f57600080fd5b506103a4600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610e1b565b005b3480156103b257600080fd5b506103d160048036038101908080359060200190929190505050611020565b604051808215151515815260200191505060405180910390f35b3480156103f757600080fd5b5061041660048036038101908080359060200190929190505050611105565b6040518082815260200191505060405180910390f35b34801561043857600080fd5b50610457600480360381019080803590602001909291905050506111d0565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018060200183151515158152602001828103825284818151815260200191508051906020019080838360005b838110156104d95780820151818401526020810190506104be565b50505050905090810190601f1680156105065780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b34801561052357600080fd5b5061052c6112c5565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561056f578082015181840152602081019050610554565b505050509050019250505060405180910390f35b34801561058f57600080fd5b506105d06004803603810190808035906020019092919080359060200190929190803515159060200190929190803515159060200190929190505050611353565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156106135780820151818401526020810190506105f8565b505050509050019250505060405180910390f35b34801561063357600080fd5b50610652600480360381019080803590602001909291905050506114c4565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561069557808201518184015260208101905061067a565b505050509050019250505060405180910390f35b3480156106b557600080fd5b506106be611701565b6040518082815260200191505060405180910390f35b3480156106e057600080fd5b506106ff60048036038101908080359060200190929190505050611707565b005b34801561070d57600080fd5b5061072c600480360381019080803590602001909291905050506117c1565b005b34801561073a57600080fd5b506107bf600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919291929050505061199e565b6040518082815260200191505060405180910390f35b3480156107e157600080fd5b506107ea6119bd565b6040518082815260200191505060405180910390f35b34801561080c57600080fd5b506108156119c2565b6040518082815260200191505060405180910390f35b34801561083757600080fd5b5061088c600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506119c8565b005b34801561089a57600080fd5b506108b960048036038101908080359060200190929190505050611cdd565b005b6003818154811015156108ca57fe5b906000526020600020016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561093557600080fd5b81600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151561098e57600080fd5b6000600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550600091505b600160038054905003821015610b13578273ffffffffffffffffffffffffffffffffffffffff16600383815481101515610a2157fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415610b06576003600160038054905003815481101515610a7f57fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600383815481101515610ab957fe5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610b13565b81806001019250506109eb565b6001600381818054905003915081610b2b91906120fe565b506003805490506004541115610b4a57610b49600380549050611707565b5b8273ffffffffffffffffffffffffffffffffffffffff167f8001553a916ef2f495d26a907cc54d96ed840d7bda71e73194bf5a9df7a76b9060405160405180910390a2505050565b33600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161515610beb57600080fd5b81336001600083815260200190815260200160002060008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161515610c5657600080fd5b8360008082815260200190815260200160002060030160009054906101000a900460ff16151515610c8657600080fd5b60006001600087815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550843373ffffffffffffffffffffffffffffffffffffffff167ff6a317157440607f36269043eb55f1287a5a19ba2216afeab88cd46cbcfb88e960405160405180910390a35050505050565b60026020528060005260406000206000915054906101000a900460ff1681565b60016020528160005260406000206020528060005260406000206000915091509054906101000a900460ff1681565b600080600090505b600554811015610e1457838015610dc8575060008082815260200190815260200160002060030160009054906101000a900460ff16155b80610dfb5750828015610dfa575060008082815260200190815260200160002060030160009054906101000a900460ff165b5b15610e07576001820191505b8080600101915050610d91565b5092915050565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610e5557600080fd5b80600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151515610eaf57600080fd5b8160008173ffffffffffffffffffffffffffffffffffffffff1614151515610ed657600080fd5b60016003805490500160045460328211158015610ef35750818111155b8015610f00575060008114155b8015610f0d575060008214155b1515610f1857600080fd5b6001600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555060038590806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550508473ffffffffffffffffffffffffffffffffffffffff167ff39e6e1eb0edcf53c221607b54b00cd28f3196fed0a24994dc308b8f611b682d60405160405180910390a25050505050565b6000806000809150600090505b6003805490508110156110fd5760016000858152602001908152602001600020600060038381548110151561105e57fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16156110dd576001820191505b6004548214156110f057600192506110fe565b808060010191505061102d565b5b5050919050565b600080600090505b6003805490508110156111ca5760016000848152602001908152602001600020600060038381548110151561113e57fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16156111bd576001820191505b808060010191505061110d565b50919050565b60006020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806001015490806002018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156112a85780601f1061127d576101008083540402835291602001916112a8565b820191906000526020600020905b81548152906001019060200180831161128b57829003601f168201915b5050505050908060030160009054906101000a900460ff16905084565b6060600380548060200260200160405190810160405280929190818152602001828054801561134957602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116112ff575b5050505050905090565b60608060008060055460405190808252806020026020018201604052801561138a5781602001602082028038833980820191505090505b50925060009150600090505b600554811015611436578580156113cd575060008082815260200190815260200160002060030160009054906101000a900460ff16155b8061140057508480156113ff575060008082815260200190815260200160002060030160009054906101000a900460ff165b5b156114295780838381518110151561141457fe5b90602001906020020181815250506001820191505b8080600101915050611396565b8787036040519080825280602002602001820160405280156114675781602001602082028038833980820191505090505b5093508790505b868110156114b957828181518110151561148457fe5b906020019060200201518489830381518110151561149e57fe5b9060200190602002018181525050808060010191505061146e565b505050949350505050565b6060806000806003805490506040519080825280602002602001820160405280156114fe5781602001602082028038833980820191505090505b50925060009150600090505b60038054905081101561164b5760016000868152602001908152602001600020600060038381548110151561153b57fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161561163e576003818154811015156115c257fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1683838151811015156115fb57fe5b9060200190602002019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250506001820191505b808060010191505061150a565b8160405190808252806020026020018201604052801561167a5781602001602082028038833980820191505090505b509350600090505b818110156116f957828181518110151561169857fe5b9060200190602002015184828151811015156116b057fe5b9060200190602002019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508080600101915050611682565b505050919050565b60055481565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561174157600080fd5b60038054905081603282111580156117595750818111155b8015611766575060008114155b8015611773575060008214155b151561177e57600080fd5b826004819055507fa3f1ee9126a074d9326c682f561767f710e927faa811f7a99829d49dc421797a836040518082815260200191505060405180910390a1505050565b33600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151561181a57600080fd5b81600080600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415151561187657600080fd5b82336001600083815260200190815260200160002060008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161515156118e257600080fd5b600180600087815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550843373ffffffffffffffffffffffffffffffffffffffff167f4a504a94899432a9846e1aa406dceb1bcfd538bb839071d49d1e5e23f5be30ef60405160405180910390a361199785611cdd565b5050505050565b60006119ab848484611f85565b90506119b6816117c1565b9392505050565b603281565b60045481565b60003073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515611a0457600080fd5b82600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161515611a5d57600080fd5b82600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151515611ab757600080fd5b600092505b600380549050831015611ba0578473ffffffffffffffffffffffffffffffffffffffff16600384815481101515611aef57fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415611b935783600384815481101515611b4657fe5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550611ba0565b8280600101935050611abc565b6000600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055506001600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508473ffffffffffffffffffffffffffffffffffffffff167f8001553a916ef2f495d26a907cc54d96ed840d7bda71e73194bf5a9df7a76b9060405160405180910390a28373ffffffffffffffffffffffffffffffffffffffff167ff39e6e1eb0edcf53c221607b54b00cd28f3196fed0a24994dc308b8f611b682d60405160405180910390a25050505050565b600033600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161515611d3857600080fd5b82336001600083815260200190815260200160002060008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161515611da357600080fd5b8460008082815260200190815260200160002060030160009054906101000a900460ff16151515611dd357600080fd5b611ddc86611020565b15611f7d57600080878152602001908152602001600020945060018560030160006101000a81548160ff021916908315150217905550611efa8560000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16866001015487600201805460018160011615610100020316600290049050886002018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611ef05780601f10611ec557610100808354040283529160200191611ef0565b820191906000526020600020905b815481529060010190602001808311611ed357829003601f168201915b50505050506120d7565b15611f3157857f33e13ecb54c3076d8e8bb8c2881800a4d972b792045ffae98fdf46df365fed7560405160405180910390a2611f7c565b857f526441bb6c1aba3c9a4a6ca1d6545da9c2333c8c48343ef398eb858d72b7923660405160405180910390a260008560030160006101000a81548160ff0219169083151502179055505b5b505050505050565b60008360008173ffffffffffffffffffffffffffffffffffffffff1614151515611fae57600080fd5b60055491506080604051908101604052808673ffffffffffffffffffffffffffffffffffffffff1681526020018581526020018481526020016000151581525060008084815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160010155604082015181600201908051906020019061206d92919061212a565b5060608201518160030160006101000a81548160ff0219169083151502179055509050506001600560008282540192505081905550817fc0ba8fe4b176c1714197d43b9cc6bcf797a4a7461c5fe8d0ef6e184ae7601e5160405160405180910390a2509392505050565b6000806040516020840160008287838a8c6187965a03f19250505080915050949350505050565b8154818355818111156121255781836000526020600020918201910161212491906121aa565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061216b57805160ff1916838001178555612199565b82800160010185558215612199579182015b8281111561219857825182559160200191906001019061217d565b5b5090506121a691906121aa565b5090565b6121cc91905b808211156121c85760008160009055506001016121b0565b5090565b905600a165627a7a72305820d75e9eb36c60c222630447163984f892585447a8fa4f29e28a13557ecf25c51b0029"; + + public MultisigWalletDeployment() : base(BYTECODE) + { + } + + [Parameter("address[]", "_owners", 1)] + public string[] Owners { get; set; } + + [Parameter("uint", "_required", 2)] + public uint Required { get; set; } + } + + [Function("submitTransaction", "uint256")] + public class SubmitTransactionFunction : FunctionMessage + { + [Parameter("address", "destination", 1)] + public string Destination { get; set; } + + [Parameter("uint", "value", 2)] + public BigInteger Value { get; set; } + + [Parameter("bytes", "data", 3)] + public byte[] Data { get; set; } + } + + [Event("Submission")] + public class SubmissionEventDTO : IEventDTO + { + [Parameter("uint256", "transactionId", 1, true)] + public BigInteger TransactionId { get; set; } + } + + [Function("confirmTransaction")] + public class ConfirmTransactionFunction : FunctionMessage + { + [Parameter("uint256", "transactionId", 1)] + public BigInteger TransactionId { get; set; } + } + + [Function("executeTransaction")] + public class ExecuteTransactionFunction : FunctionMessage + { + [Parameter("uint256", "transactionId", 1)] + public BigInteger TransactionId { get; set; } + } + + [Function("getConfirmationCount", "uint256")] + public class GetConfirmationCountFunction : FunctionMessage + { + [Parameter("uint256", "transactionId", 1)] + public BigInteger TransactionId { get; set; } + } + + public class MultisigWallet + { + public static async Task DeployContractAsync(Web3 web3, string[] owners, uint required) + { + var deploymentMessage = new MultisigWalletDeployment() + { + Owners = owners, + Required = required + }; + + IContractDeploymentTransactionHandler deploymentHandler = web3.Eth.GetContractDeploymentHandler(); + TransactionReceipt transactionReceiptDeployment = await deploymentHandler.SendRequestAndWaitForReceiptAsync(deploymentMessage).ConfigureAwait(false); + string contractAddress = transactionReceiptDeployment.ContractAddress; + + return contractAddress; + } + + public static async Task> GetOwnersAsync(Web3 web3, string contractAddress) + { + var getOwnersFunctionMessage = new GetOwnersFunction() + { + }; + + IContractQueryHandler ownerHandler = web3.Eth.GetContractQueryHandler(); + List owners = await ownerHandler.QueryAsync>(contractAddress, getOwnersFunctionMessage).ConfigureAwait(false); + + return owners; + } + + public static async Task SubmitTransactionAsync(Web3 web3, string contractAddress, string destination, BigInteger value, string data, BigInteger gas, BigInteger gasPrice) + { + IContractTransactionHandler submitHandler = web3.Eth.GetContractTransactionHandler(); + + var submitTransactionFunctionMessage = new SubmitTransactionFunction() + { + Destination = destination, + Value = value, + Data = Encoders.Hex.DecodeData(data), + Gas = gas, + GasPrice = Web3.Convert.ToWei(gasPrice, UnitConversion.EthUnit.Gwei) + }; + + TransactionReceipt submitTransactionReceipt = await submitHandler.SendRequestAndWaitForReceiptAsync(contractAddress, submitTransactionFunctionMessage).ConfigureAwait(false); + EventLog submission = submitTransactionReceipt.DecodeAllEvents().FirstOrDefault(); + + // Use -1 as an error indicator. + if (submission == null) + return BigInteger.MinusOne; + + return submission.Event.TransactionId; + } + + public static async Task ConfirmTransactionAsync(Web3 web3, string contractAddress, BigInteger transactionId, BigInteger gas, BigInteger gasPrice) + { + IContractTransactionHandler confirmationHandler = web3.Eth.GetContractTransactionHandler(); + + var confirmTransactionFunctionMessage = new ConfirmTransactionFunction() + { + TransactionId = transactionId, + Gas = gas, + GasPrice = Web3.Convert.ToWei(gasPrice, UnitConversion.EthUnit.Gwei) + }; + + TransactionReceipt confirmTransactionReceipt = await confirmationHandler.SendRequestAndWaitForReceiptAsync(contractAddress, confirmTransactionFunctionMessage).ConfigureAwait(false); + + return confirmTransactionReceipt.TransactionHash; + } + + /// + /// Normally the final mandatory confirmation will automatically call the execute. + /// This is provided in case it has to be called again due to an error condition. + /// + public static async Task ExecuteTransactionAsync(Web3 web3, string contractAddress, BigInteger transactionId, BigInteger gas, BigInteger gasPrice) + { + IContractTransactionHandler executionHandler = web3.Eth.GetContractTransactionHandler(); + + var executeTransactionFunctionMessage = new ExecuteTransactionFunction() + { + TransactionId = transactionId, + Gas = gas, + GasPrice = Web3.Convert.ToWei(gasPrice, UnitConversion.EthUnit.Gwei) + }; + + TransactionReceipt executeTransactionReceipt = await executionHandler.SendRequestAndWaitForReceiptAsync(contractAddress, executeTransactionFunctionMessage).ConfigureAwait(false); + + return executeTransactionReceipt.TransactionHash; + } + + public static async Task GetConfirmationCountAsync(Web3 web3, string contractAddress, BigInteger transactionId) + { + var getConfirmationCountFunctionMessage = new GetConfirmationCountFunction() + { + TransactionId = transactionId + }; + + IContractQueryHandler confirmationHandler = web3.Eth.GetContractQueryHandler(); + BigInteger confirmations = await confirmationHandler.QueryAsync(contractAddress, getConfirmationCountFunctionMessage).ConfigureAwait(false); + + return confirmations; + } + + public static string ABI = @"[ + { + ""constant"": false, + ""inputs"": [ + { + ""name"": ""owner"", + ""type"": ""address"" + } + ], + ""name"": ""addOwner"", + ""outputs"": [], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""constant"": false, + ""inputs"": [ + { + ""name"": ""_required"", + ""type"": ""uint256"" + } + ], + ""name"": ""changeRequirement"", + ""outputs"": [], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""constant"": false, + ""inputs"": [ + { + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""confirmTransaction"", + ""outputs"": [], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""constant"": false, + ""inputs"": [ + { + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""executeTransaction"", + ""outputs"": [], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""constant"": false, + ""inputs"": [ + { + ""name"": ""owner"", + ""type"": ""address"" + } + ], + ""name"": ""removeOwner"", + ""outputs"": [], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""constant"": false, + ""inputs"": [ + { + ""name"": ""owner"", + ""type"": ""address"" + }, + { + ""name"": ""newOwner"", + ""type"": ""address"" + } + ], + ""name"": ""replaceOwner"", + ""outputs"": [], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""constant"": false, + ""inputs"": [ + { + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""revokeConfirmation"", + ""outputs"": [], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""constant"": false, + ""inputs"": [ + { + ""name"": ""destination"", + ""type"": ""address"" + }, + { + ""name"": ""value"", + ""type"": ""uint256"" + }, + { + ""name"": ""data"", + ""type"": ""bytes"" + } + ], + ""name"": ""submitTransaction"", + ""outputs"": [ + { + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""name"": ""_owners"", + ""type"": ""address[]"" + }, + { + ""name"": ""_required"", + ""type"": ""uint256"" + } + ], + ""payable"": false, + ""stateMutability"": ""nonpayable"", + ""type"": ""constructor"" + }, + { + ""payable"": true, + ""stateMutability"": ""payable"", + ""type"": ""fallback"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""name"": ""sender"", + ""type"": ""address"" + }, + { + ""indexed"": true, + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""Confirmation"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""name"": ""sender"", + ""type"": ""address"" + }, + { + ""indexed"": true, + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""Revocation"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""Submission"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""Execution"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""ExecutionFailure"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""name"": ""sender"", + ""type"": ""address"" + }, + { + ""indexed"": false, + ""name"": ""value"", + ""type"": ""uint256"" + } + ], + ""name"": ""Deposit"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""name"": ""owner"", + ""type"": ""address"" + } + ], + ""name"": ""OwnerAddition"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""name"": ""owner"", + ""type"": ""address"" + } + ], + ""name"": ""OwnerRemoval"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": false, + ""name"": ""required"", + ""type"": ""uint256"" + } + ], + ""name"": ""RequirementChange"", + ""type"": ""event"" + }, + { + ""constant"": true, + ""inputs"": [ + { + ""name"": """", + ""type"": ""uint256"" + }, + { + ""name"": """", + ""type"": ""address"" + } + ], + ""name"": ""confirmations"", + ""outputs"": [ + { + ""name"": """", + ""type"": ""bool"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [ + { + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""getConfirmationCount"", + ""outputs"": [ + { + ""name"": ""count"", + ""type"": ""uint256"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [ + { + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""getConfirmations"", + ""outputs"": [ + { + ""name"": ""_confirmations"", + ""type"": ""address[]"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [], + ""name"": ""getOwners"", + ""outputs"": [ + { + ""name"": """", + ""type"": ""address[]"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [ + { + ""name"": ""pending"", + ""type"": ""bool"" + }, + { + ""name"": ""executed"", + ""type"": ""bool"" + } + ], + ""name"": ""getTransactionCount"", + ""outputs"": [ + { + ""name"": ""count"", + ""type"": ""uint256"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [ + { + ""name"": ""from"", + ""type"": ""uint256"" + }, + { + ""name"": ""to"", + ""type"": ""uint256"" + }, + { + ""name"": ""pending"", + ""type"": ""bool"" + }, + { + ""name"": ""executed"", + ""type"": ""bool"" + } + ], + ""name"": ""getTransactionIds"", + ""outputs"": [ + { + ""name"": ""_transactionIds"", + ""type"": ""uint256[]"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [ + { + ""name"": ""transactionId"", + ""type"": ""uint256"" + } + ], + ""name"": ""isConfirmed"", + ""outputs"": [ + { + ""name"": """", + ""type"": ""bool"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [ + { + ""name"": """", + ""type"": ""address"" + } + ], + ""name"": ""isOwner"", + ""outputs"": [ + { + ""name"": """", + ""type"": ""bool"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [], + ""name"": ""MAX_OWNER_COUNT"", + ""outputs"": [ + { + ""name"": """", + ""type"": ""uint256"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [ + { + ""name"": """", + ""type"": ""uint256"" + } + ], + ""name"": ""owners"", + ""outputs"": [ + { + ""name"": """", + ""type"": ""address"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [], + ""name"": ""required"", + ""outputs"": [ + { + ""name"": """", + ""type"": ""uint256"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [], + ""name"": ""transactionCount"", + ""outputs"": [ + { + ""name"": """", + ""type"": ""uint256"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""constant"": true, + ""inputs"": [ + { + ""name"": """", + ""type"": ""uint256"" + } + ], + ""name"": ""transactions"", + ""outputs"": [ + { + ""name"": ""destination"", + ""type"": ""address"" + }, + { + ""name"": ""value"", + ""type"": ""uint256"" + }, + { + ""name"": ""data"", + ""type"": ""bytes"" + }, + { + ""name"": ""executed"", + ""type"": ""bool"" + } + ], + ""payable"": false, + ""stateMutability"": ""view"", + ""type"": ""function"" + } + ]"; + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/EthereumClient/WrappedStrax.cs b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/WrappedStrax.cs new file mode 100644 index 0000000000..4b7c8f406b --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/EthereumClient/WrappedStrax.cs @@ -0,0 +1,665 @@ +using System.Numerics; +using System.Threading.Tasks; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Contracts; +using Nethereum.Contracts.ContractHandlers; +using Nethereum.Contracts.CQS; +using Nethereum.Hex.HexTypes; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Util; +using Nethereum.Web3; + +namespace Stratis.Bitcoin.Features.Interop.EthereumClient +{ + public class WrappedStraxTokenDeployment : ContractDeploymentMessage + { + public static string BYTECODE = + "0x60806040523480156200001157600080fd5b50604051620016ba380380620016ba833981810160405260208110156200003757600080fd5b5051604080518082018252600c81526b0aee4c2e0e0cac8a6e8e4c2f60a31b6020828101918252835180850190945260068452650aea6a8a482b60d31b9084015281519192916200008b91600391620002a0565b508051620000a1906004906020840190620002a0565b50506005805460ff19166012179055506000620000bd62000126565b60058054610100600160a81b0319166101006001600160a01b03841690810291909117909155604051919250906000907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a3506200011f33826200012a565b506200033c565b3390565b6001600160a01b03821662000186576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b620001946000838362000239565b620001b0816002546200023e60201b62000ba61790919060201c565b6002556001600160a01b03821660009081526020818152604090912054620001e391839062000ba66200023e821b17901c565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b505050565b60008282018381101562000299576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620002e357805160ff191683800117855562000313565b8280016001018555821562000313579182015b8281111562000313578251825591602001919060010190620002f6565b506200032192915062000325565b5090565b5b8082111562000321576000815560010162000326565b61136e806200034c6000396000f3fe608060405234801561001057600080fd5b50600436106101165760003560e01c80637641e6f3116100a2578063979430d211610071578063979430d2146103cd578063a457c2d714610488578063a9059cbb146104b4578063dd62ed3e146104e0578063f2fde38b1461050e57610116565b80637641e6f3146102ce5780637e0518671461037b5780638da5cb5b146103a157806395d89b41146103c557610116565b8063313ce567116100e9578063313ce56714610228578063395093511461024657806340c10f191461027257806370a08231146102a0578063715018a6146102c657610116565b806306fdde031461011b578063095ea7b31461019857806318160ddd146101d857806323b872dd146101f2575b600080fd5b610123610534565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561015d578181015183820152602001610145565b50505050905090810190601f16801561018a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101c4600480360360408110156101ae57600080fd5b506001600160a01b0381351690602001356105ca565b604080519115158252519081900360200190f35b6101e06105e7565b60408051918252519081900360200190f35b6101c46004803603606081101561020857600080fd5b506001600160a01b038135811691602081013590911690604001356105ed565b610230610674565b6040805160ff9092168252519081900360200190f35b6101c46004803603604081101561025c57600080fd5b506001600160a01b03813516906020013561067d565b61029e6004803603604081101561028857600080fd5b506001600160a01b0381351690602001356106cb565b005b6101e0600480360360208110156102b657600080fd5b50356001600160a01b0316610748565b61029e610763565b61029e600480360360408110156102e457600080fd5b8135919081019060408101602082013564010000000081111561030657600080fd5b82018360208201111561031857600080fd5b8035906020019184600183028401116401000000008311171561033a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610822945050505050565b6101236004803603602081101561039157600080fd5b50356001600160a01b0316610858565b6103a96108f3565b604080516001600160a01b039092168252519081900360200190f35b610123610907565b61029e600480360360608110156103e357600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561041357600080fd5b82018360208201111561042557600080fd5b8035906020019184600183028401116401000000008311171561044757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610968945050505050565b6101c46004803603604081101561049e57600080fd5b506001600160a01b0381351690602001356109e4565b6101c4600480360360408110156104ca57600080fd5b506001600160a01b038135169060200135610a4c565b6101e0600480360360408110156104f657600080fd5b506001600160a01b0381358116916020013516610a60565b61029e6004803603602081101561052457600080fd5b50356001600160a01b0316610a8b565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156105c05780601f10610595576101008083540402835291602001916105c0565b820191906000526020600020905b8154815290600101906020018083116105a357829003601f168201915b5050505050905090565b60006105de6105d7610c07565b8484610c0b565b50600192915050565b60025490565b60006105fa848484610cf7565b61066a84610606610c07565b6106658560405180606001604052806028815260200161125e602891396001600160a01b038a16600090815260016020526040812090610644610c07565b6001600160a01b031681526020810191909152604001600020549190610e52565b610c0b565b5060019392505050565b60055460ff1690565b60006105de61068a610c07565b84610665856001600061069b610c07565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610ba6565b6106d3610c07565b60055461010090046001600160a01b0390811691161461073a576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6107448282610ee9565b5050565b6001600160a01b031660009081526020819052604090205490565b61076b610c07565b60055461010090046001600160a01b039081169116146107d2576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b60055460405160009161010090046001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a360058054610100600160a81b0319169055565b61083361082d610c07565b83610fd9565b336000908152600660209081526040909120825161085392840190611117565b505050565b60066020908152600091825260409182902080548351601f6002600019610100600186161502019093169290920491820184900484028101840190945280845290918301828280156108eb5780601f106108c0576101008083540402835291602001916108eb565b820191906000526020600020905b8154815290600101906020018083116108ce57829003601f168201915b505050505081565b60055461010090046001600160a01b031690565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156105c05780601f10610595576101008083540402835291602001916105c0565b600061099f836040518060600160405280602481526020016112866024913961099887610993610c07565b610a60565b9190610e52565b90506109b3846109ad610c07565b83610c0b565b6109bd8484610fd9565b33600090815260066020908152604090912083516109dd92850190611117565b5050505050565b60006105de6109f1610c07565b84610665856040518060600160405280602581526020016113146025913960016000610a1b610c07565b6001600160a01b03908116825260208083019390935260409182016000908120918d16815292529020549190610e52565b60006105de610a59610c07565b8484610cf7565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b610a93610c07565b60055461010090046001600160a01b03908116911614610afa576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b038116610b3f5760405162461bcd60e51b81526004018080602001828103825260268152602001806111f06026913960400191505060405180910390fd5b6005546040516001600160a01b0380841692610100900416907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600580546001600160a01b0390921661010002610100600160a81b0319909216919091179055565b600082820183811015610c00576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b038316610c505760405162461bcd60e51b81526004018080602001828103825260248152602001806112f06024913960400191505060405180910390fd5b6001600160a01b038216610c955760405162461bcd60e51b81526004018080602001828103825260228152602001806112166022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b038316610d3c5760405162461bcd60e51b81526004018080602001828103825260258152602001806112cb6025913960400191505060405180910390fd5b6001600160a01b038216610d815760405162461bcd60e51b81526004018080602001828103825260238152602001806111ab6023913960400191505060405180910390fd5b610d8c838383610853565b610dc981604051806060016040528060268152602001611238602691396001600160a01b0386166000908152602081905260409020549190610e52565b6001600160a01b038085166000908152602081905260408082209390935590841681522054610df89082610ba6565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b60008184841115610ee15760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610ea6578181015183820152602001610e8e565b50505050905090810190601f168015610ed35780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6001600160a01b038216610f44576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b610f5060008383610853565b600254610f5d9082610ba6565b6002556001600160a01b038216600090815260208190526040902054610f839082610ba6565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6001600160a01b03821661101e5760405162461bcd60e51b81526004018080602001828103825260218152602001806112aa6021913960400191505060405180910390fd5b61102a82600083610853565b611067816040518060600160405280602281526020016111ce602291396001600160a01b0385166000908152602081905260409020549190610e52565b6001600160a01b03831660009081526020819052604090205560025461108d90826110d5565b6002556040805182815290516000916001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35050565b6000610c0083836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250610e52565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061115857805160ff1916838001178555611185565b82800160010185558215611185579182015b8281111561118557825182559160200191906001019061116a565b50611191929150611195565b5090565b5b80821115611191576000815560010161119656fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a206275726e20616d6f756e7420657863656564732062616c616e63654f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a206275726e20616d6f756e74206578636565647320616c6c6f77616e636545524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220cad3aac9a515bf8c31e395c1cb60e9ec8ba2edb3e0e92408c5f7db093befc84664736f6c634300060c0033"; + + public WrappedStraxTokenDeployment() : base(BYTECODE) + { + } + + [Parameter("uint256", "totalSupply")] + public BigInteger TotalSupply { get; set; } + } + + [Function("balanceOf", "uint256")] + public class BalanceOfFunction : FunctionMessage + { + [Parameter("address", "_owner", 1)] + public string Owner { get; set; } + } + + [FunctionOutput] + public class BalanceOfOutputDTO : IFunctionOutputDTO + { + [Parameter("uint256", "balance", 1)] + public BigInteger Balance { get; set; } + } + + [Function("transfer", "bool")] + public class TransferFunction : FunctionMessage + { + [Parameter("address", "_to", 1)] + public string To { get; set; } + + [Parameter("uint256", "_value", 2)] + public BigInteger TokenAmount { get; set; } + } + + [Event("Transfer")] + public class TransferEventDTO : IEventDTO + { + [Parameter("address", "_from", 1, true)] + public string From { get; set; } + + [Parameter("address", "_to", 2, true)] + public string To { get; set; } + + [Parameter("uint256", "_value", 3, false)] + public BigInteger Value { get; set; } + } + + [Function("owner", "address")] + public class OwnerFunction : FunctionMessage + { + } + + [Function("transferOwnership")] + public class TransferOwnershipFunction : FunctionMessage + { + [Parameter("address", "newOwner", 1)] + public string NewOwner { get; set; } + } + + [Function("withdrawalAddresses", "string")] + public class WithdrawalAddressesFunction : FunctionMessage + { + [Parameter("address", "", 1)] + public string Address { get; set; } + } + + [Function("mint")] + public class MintFunction : FunctionMessage + { + [Parameter("address", "account", 1)] + public string Account { get; set; } + + [Parameter("uint256", "amount", 2)] + public BigInteger Amount { get; set; } + } + + [Function("burn")] + public class BurnFunction : FunctionMessage + { + [Parameter("uint256", "amount", 1)] + public BigInteger Amount { get; set; } + + [Parameter("string", "straxAddress", 2)] + public string StraxAddress { get; set; } + } + + public class TransferParamsInput + { + [Parameter("address", 1)] public string To { get; set; } + + [Parameter("uint256", 2)] public BigInteger Value { get; set; } + } + + public class MintParamsInput + { + [Parameter("address", 1)] public string Account { get; set; } + + [Parameter("uint256", 2)] public BigInteger Amount { get; set; } + } + + public class BurnParamsInput + { + [Parameter("uint256", 1)] public BigInteger Amount { get; set; } + } + + public class WrappedStrax + { + public static async Task DeployContractAsync(Web3 web3, BigInteger totalSupply) + { + var deploymentMessage = new WrappedStraxTokenDeployment() + { + TotalSupply = totalSupply + }; + + IContractDeploymentTransactionHandler deploymentHandler = web3.Eth.GetContractDeploymentHandler(); + TransactionReceipt transactionReceiptDeployment = await deploymentHandler.SendRequestAndWaitForReceiptAsync(deploymentMessage).ConfigureAwait(false); + string contractAddress = transactionReceiptDeployment.ContractAddress; + + return contractAddress; + } + + public static async Task GetErc20BalanceAsync(Web3 web3, string contractAddress, string addressToQuery) + { + var balanceOfFunctionMessage = new BalanceOfFunction() + { + Owner = addressToQuery + }; + + IContractQueryHandler balanceHandler = web3.Eth.GetContractQueryHandler(); + BigInteger balance = await balanceHandler.QueryAsync(contractAddress, balanceOfFunctionMessage).ConfigureAwait(false); + + return balance; + } + + public static async Task TransferAsync(Web3 web3, string contractAddress, string recipient, BigInteger amount, BigInteger gas, BigInteger gasPrice) + { + IContractTransactionHandler transferHandler = web3.Eth.GetContractTransactionHandler(); + + var transfer = new TransferFunction() + { + To = recipient, + TokenAmount = amount, + Gas = gas, + GasPrice = Web3.Convert.ToWei(gasPrice, UnitConversion.EthUnit.Gwei) + }; + + TransactionReceipt transactionTransferReceipt = await transferHandler.SendRequestAndWaitForReceiptAsync(contractAddress, transfer).ConfigureAwait(false); + + return transactionTransferReceipt.TransactionHash; + } + + public static async Task TransferOfflineAsync(Web3 web3, string contractAddress, string recipient, BigInteger amount, HexBigInteger nonce, BigInteger gas, BigInteger gasPrice, string fromAddress = null) + { + IContractTransactionHandler transferHandler = web3.Eth.GetContractTransactionHandler(); + + var transfer = new TransferFunction + { + To = recipient, + TokenAmount = amount, + // Nethereum internally calls its Ethereum client by default to set the GasPrice, Nonce and estimate the Gas, + // so if we want to sign the transaction for the contract completely offline we will need to set those values ourselves. + Nonce = nonce.Value, + Gas = gas, + GasPrice = Web3.Convert.ToWei(gasPrice, UnitConversion.EthUnit.Gwei) + }; + + if (fromAddress != null) + transfer.FromAddress = fromAddress; + + string result = await transferHandler.SignTransactionAsync(contractAddress, transfer).ConfigureAwait(false); + + return result; + } + + public static async Task TransferOwnershipAsync(Web3 web3, string contractAddress, string newOwner) + { + IContractTransactionHandler transferHandler = web3.Eth.GetContractTransactionHandler(); + + var transfer = new TransferOwnershipFunction() + { + NewOwner = newOwner + }; + + TransactionReceipt transactionTransferReceipt = await transferHandler.SendRequestAndWaitForReceiptAsync(contractAddress, transfer).ConfigureAwait(false); + + return transactionTransferReceipt.TransactionHash; + } + + public static async Task GetOwnerAsync(Web3 web3, string contractAddress) + { + var ownerFunctionMessage = new OwnerFunction() + { + }; + + IContractQueryHandler ownerHandler = web3.Eth.GetContractQueryHandler(); + string owner = await ownerHandler.QueryAsync(contractAddress, ownerFunctionMessage).ConfigureAwait(false); + + return owner; + } + + public static async Task GetDestinationAddressAsync(Web3 web3, string contractAddress, string addressToQuery) + { + var withdrawalAddressesFunctionMessage = new WithdrawalAddressesFunction() + { + Address = addressToQuery + }; + + IContractQueryHandler queryHandler = web3.Eth.GetContractQueryHandler(); + string destinationAddress = await queryHandler.QueryAsync(contractAddress, withdrawalAddressesFunctionMessage).ConfigureAwait(false); + + return destinationAddress; + } + + /* + "39509351": "increaseAllowance(address,uint256)", + "dd62ed3e": "allowance(address,address)", + "095ea7b3": "approve(address,uint256)", + "70a08231": "balanceOf(address)", + "7641e6f3": "burn(uint256,string)", + "979430d2": "burnFrom(address,uint256,string)", + "313ce567": "decimals()", + "a457c2d7": "decreaseAllowance(address,uint256)", + "40c10f19": "mint(address,uint256)", + "06fdde03": "name()", + "8da5cb5b": "owner()", + "715018a6": "renounceOwnership()", + "95d89b41": "symbol()", + "18160ddd": "totalSupply()", + "a9059cbb": "transfer(address,uint256)", + "23b872dd": "transferFrom(address,address,uint256)", + "f2fde38b": "transferOwnership(address)", + "7e051867": "withdrawalAddresses(address)" + */ + + public static string ABI = @"[ + { + ""inputs"": [ + { + ""internalType"": ""uint256"", + ""name"": ""initialSupply"", + ""type"": ""uint256"" + } + ], + ""stateMutability"": ""nonpayable"", + ""type"": ""constructor"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""internalType"": ""address"", + ""name"": ""owner"", + ""type"": ""address"" + }, + { + ""indexed"": true, + ""internalType"": ""address"", + ""name"": ""spender"", + ""type"": ""address"" + }, + { + ""indexed"": false, + ""internalType"": ""uint256"", + ""name"": ""value"", + ""type"": ""uint256"" + } + ], + ""name"": ""Approval"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""internalType"": ""address"", + ""name"": ""previousOwner"", + ""type"": ""address"" + }, + { + ""indexed"": true, + ""internalType"": ""address"", + ""name"": ""newOwner"", + ""type"": ""address"" + } + ], + ""name"": ""OwnershipTransferred"", + ""type"": ""event"" + }, + { + ""anonymous"": false, + ""inputs"": [ + { + ""indexed"": true, + ""internalType"": ""address"", + ""name"": ""from"", + ""type"": ""address"" + }, + { + ""indexed"": true, + ""internalType"": ""address"", + ""name"": ""to"", + ""type"": ""address"" + }, + { + ""indexed"": false, + ""internalType"": ""uint256"", + ""name"": ""value"", + ""type"": ""uint256"" + } + ], + ""name"": ""Transfer"", + ""type"": ""event"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""owner"", + ""type"": ""address"" + }, + { + ""internalType"": ""address"", + ""name"": ""spender"", + ""type"": ""address"" + } + ], + ""name"": ""allowance"", + ""outputs"": [ + { + ""internalType"": ""uint256"", + ""name"": """", + ""type"": ""uint256"" + } + ], + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""spender"", + ""type"": ""address"" + }, + { + ""internalType"": ""uint256"", + ""name"": ""amount"", + ""type"": ""uint256"" + } + ], + ""name"": ""approve"", + ""outputs"": [ + { + ""internalType"": ""bool"", + ""name"": """", + ""type"": ""bool"" + } + ], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""account"", + ""type"": ""address"" + } + ], + ""name"": ""balanceOf"", + ""outputs"": [ + { + ""internalType"": ""uint256"", + ""name"": """", + ""type"": ""uint256"" + } + ], + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""uint256"", + ""name"": ""amount"", + ""type"": ""uint256"" + }, + { + ""internalType"": ""string"", + ""name"": ""straxAddress"", + ""type"": ""string"" + } + ], + ""name"": ""burn"", + ""outputs"": [], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""account"", + ""type"": ""address"" + }, + { + ""internalType"": ""uint256"", + ""name"": ""amount"", + ""type"": ""uint256"" + }, + { + ""internalType"": ""string"", + ""name"": ""straxAddress"", + ""type"": ""string"" + } + ], + ""name"": ""burnFrom"", + ""outputs"": [], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [], + ""name"": ""decimals"", + ""outputs"": [ + { + ""internalType"": ""uint8"", + ""name"": """", + ""type"": ""uint8"" + } + ], + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""spender"", + ""type"": ""address"" + }, + { + ""internalType"": ""uint256"", + ""name"": ""subtractedValue"", + ""type"": ""uint256"" + } + ], + ""name"": ""decreaseAllowance"", + ""outputs"": [ + { + ""internalType"": ""bool"", + ""name"": """", + ""type"": ""bool"" + } + ], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""spender"", + ""type"": ""address"" + }, + { + ""internalType"": ""uint256"", + ""name"": ""addedValue"", + ""type"": ""uint256"" + } + ], + ""name"": ""increaseAllowance"", + ""outputs"": [ + { + ""internalType"": ""bool"", + ""name"": """", + ""type"": ""bool"" + } + ], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""account"", + ""type"": ""address"" + }, + { + ""internalType"": ""uint256"", + ""name"": ""amount"", + ""type"": ""uint256"" + } + ], + ""name"": ""mint"", + ""outputs"": [], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [], + ""name"": ""name"", + ""outputs"": [ + { + ""internalType"": ""string"", + ""name"": """", + ""type"": ""string"" + } + ], + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""inputs"": [], + ""name"": ""owner"", + ""outputs"": [ + { + ""internalType"": ""address"", + ""name"": """", + ""type"": ""address"" + } + ], + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""inputs"": [], + ""name"": ""renounceOwnership"", + ""outputs"": [], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [], + ""name"": ""symbol"", + ""outputs"": [ + { + ""internalType"": ""string"", + ""name"": """", + ""type"": ""string"" + } + ], + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""inputs"": [], + ""name"": ""totalSupply"", + ""outputs"": [ + { + ""internalType"": ""uint256"", + ""name"": """", + ""type"": ""uint256"" + } + ], + ""stateMutability"": ""view"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""recipient"", + ""type"": ""address"" + }, + { + ""internalType"": ""uint256"", + ""name"": ""amount"", + ""type"": ""uint256"" + } + ], + ""name"": ""transfer"", + ""outputs"": [ + { + ""internalType"": ""bool"", + ""name"": """", + ""type"": ""bool"" + } + ], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""sender"", + ""type"": ""address"" + }, + { + ""internalType"": ""address"", + ""name"": ""recipient"", + ""type"": ""address"" + }, + { + ""internalType"": ""uint256"", + ""name"": ""amount"", + ""type"": ""uint256"" + } + ], + ""name"": ""transferFrom"", + ""outputs"": [ + { + ""internalType"": ""bool"", + ""name"": """", + ""type"": ""bool"" + } + ], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": ""newOwner"", + ""type"": ""address"" + } + ], + ""name"": ""transferOwnership"", + ""outputs"": [], + ""stateMutability"": ""nonpayable"", + ""type"": ""function"" + }, + { + ""inputs"": [ + { + ""internalType"": ""address"", + ""name"": """", + ""type"": ""address"" + } + ], + ""name"": ""withdrawalAddresses"", + ""outputs"": [ + { + ""internalType"": ""string"", + ""name"": """", + ""type"": ""string"" + } + ], + ""stateMutability"": ""view"", + ""type"": ""function"" + } + ]"; + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/FodyWeavers.xml b/src/Stratis.Bitcoin.Features.Interop/FodyWeavers.xml new file mode 100644 index 0000000000..9291f49ca7 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Stratis.Bitcoin.Features.Interop/InteropBehavior.cs b/src/Stratis.Bitcoin.Features.Interop/InteropBehavior.cs new file mode 100644 index 0000000000..3b64f09145 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/InteropBehavior.cs @@ -0,0 +1,124 @@ +using System; +using System.Numerics; +using System.Threading.Tasks; +using NBitcoin; +using NLog; +using Stratis.Bitcoin.Features.Interop.EthereumClient; +using Stratis.Bitcoin.Features.Interop.Payloads; +using Stratis.Bitcoin.Features.PoA; +using Stratis.Bitcoin.P2P.Peer; +using Stratis.Bitcoin.P2P.Protocol; +using Stratis.Bitcoin.P2P.Protocol.Behaviors; +using Stratis.Bitcoin.Utilities; +using TracerAttributes; + +namespace Stratis.Bitcoin.Features.Interop +{ + public class InteropBehavior : NetworkPeerBehavior + { + private readonly NLog.ILogger logger; + + private readonly Network network; + + private readonly IFederationManager federationManager; + + private readonly IInteropTransactionManager interopTransactionManager; + + private readonly IEthereumClientBase ethereumClientBase; + + public InteropBehavior( + Network network, + IFederationManager federationManager, + IInteropTransactionManager interopTransactionManager, + IEthereumClientBase ethereumClientBase) + { + Guard.NotNull(network, nameof(network)); + Guard.NotNull(federationManager, nameof(federationManager)); + Guard.NotNull(interopTransactionManager, nameof(interopTransactionManager)); + Guard.NotNull(ethereumClientBase, nameof(ethereumClientBase)); + + this.logger = LogManager.GetCurrentClassLogger(); + this.network = network; + this.federationManager = federationManager; + this.interopTransactionManager = interopTransactionManager; + this.ethereumClientBase = ethereumClientBase; + } + + [NoTrace] + public override object Clone() + { + return new InteropBehavior(this.network, this.federationManager, this.interopTransactionManager, this.ethereumClientBase); + } + + protected override void AttachCore() + { + this.logger.Debug("Attaching behaviour for {0}", this.AttachedPeer.PeerEndPoint.Address); + this.AttachedPeer.MessageReceived.Register(this.OnMessageReceivedAsync, true); + } + + protected override void DetachCore() + { + this.logger.Debug("Detaching behaviour for {0}", this.AttachedPeer.PeerEndPoint.Address); + this.AttachedPeer.MessageReceived.Unregister(this.OnMessageReceivedAsync); + } + + private async Task OnMessageReceivedAsync(INetworkPeer peer, IncomingMessage message) + { + if (!(message.Message.Payload is InteropCoordinationPayload payload)) + return; + + if (!this.federationManager.IsFederationMember) + return; + + this.logger.Info("{0} received from '{1}':'{2}'. Request {3} proposing transaction ID {4}.", nameof(InteropCoordinationPayload), peer.PeerEndPoint.Address, peer.RemoteSocketEndpoint.Address, payload.RequestId, payload.TransactionId); + + if (payload.TransactionId == BigInteger.MinusOne) + return; + + // Check that the payload is signed by a federation member. + PubKey pubKey; + + try + { + pubKey = PubKey.RecoverFromMessage(payload.RequestId + payload.TransactionId, payload.Signature); + + if (!this.federationManager.IsMultisigMember(pubKey)) + { + this.logger.Warn("Received unverified coordination payload for {0}. Computed pubkey {1}.", payload.RequestId, pubKey?.ToHex()); + + return; + } + } + catch (Exception) + { + this.logger.Warn("Received malformed coordination payload for {0}.", payload.RequestId); + + return; + } + + BigInteger confirmationCount; + + try + { + // Check that the transaction ID in the payload actually exists, and is unconfirmed. + confirmationCount = await this.ethereumClientBase.GetConfirmationCountAsync(payload.TransactionId).ConfigureAwait(false); + } + catch (Exception) + { + return; + } + + // We presume that the initial submitter of the transaction must have at least confirmed it. Otherwise just ignore this coordination attempt. + if (confirmationCount < 1) + { + this.logger.Info("Multisig wallet transaction {0} has no confirmations.", payload.TransactionId); + + return; + } + + this.logger.Info("Multisig wallet transaction {0} has {1} confirmations (request ID: {2}).", payload.TransactionId, confirmationCount, payload.RequestId); + + this.interopTransactionManager.AddVote(payload.RequestId, payload.TransactionId, pubKey); + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/InteropFeature.cs b/src/Stratis.Bitcoin.Features.Interop/InteropFeature.cs new file mode 100644 index 0000000000..22f40bab99 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/InteropFeature.cs @@ -0,0 +1,85 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NBitcoin; +using Stratis.Bitcoin.Builder; +using Stratis.Bitcoin.Builder.Feature; +using Stratis.Bitcoin.Configuration.Logging; +using Stratis.Bitcoin.Connection; +using Stratis.Bitcoin.Features.Interop.EthereumClient; +using Stratis.Bitcoin.Features.Interop.Payloads; +using Stratis.Bitcoin.Features.PoA; +using Stratis.Bitcoin.P2P.Peer; +using Stratis.Bitcoin.P2P.Protocol.Payloads; + +namespace Stratis.Bitcoin.Features.Interop +{ + public sealed class InteropFeature : FullNodeFeature + { + private readonly Network network; + + private readonly IFederationManager federationManager; + + private readonly IConnectionManager connectionManager; + + private readonly InteropPoller interopPoller; + + private readonly IInteropTransactionManager interopTransactionManager; + + private readonly IEthereumClientBase ethereumClientBase; + + public InteropFeature( + Network network, + IFederationManager federationManager, + IConnectionManager connectionManager, + InteropPoller interopPoller, + IInteropTransactionManager interopTransactionManager, + IEthereumClientBase ethereumClientBase, + IFullNode fullNode) + { + this.network = network; + this.federationManager = federationManager; + this.connectionManager = connectionManager; + this.interopPoller = interopPoller; + this.interopTransactionManager = interopTransactionManager; + this.ethereumClientBase = ethereumClientBase; + + var payloadProvider = (PayloadProvider)fullNode.Services.ServiceProvider.GetService(typeof(PayloadProvider)); + payloadProvider.AddPayload(typeof(InteropCoordinationPayload)); + } + + public override Task InitializeAsync() + { + this.interopPoller?.Initialize(); + + NetworkPeerConnectionParameters networkPeerConnectionParameters = this.connectionManager.Parameters; + networkPeerConnectionParameters.TemplateBehaviors.Add(new InteropBehavior(this.network, this.federationManager, this.interopTransactionManager, this.ethereumClientBase)); + + return Task.CompletedTask; + } + + public override void Dispose() + { + this.interopPoller?.Dispose(); + } + } + + public static partial class IFullNodeBuilderExtensions + { + public static IFullNodeBuilder AddInteroperability(this IFullNodeBuilder fullNodeBuilder) + { + LoggingConfiguration.RegisterFeatureNamespace("interop"); + + fullNodeBuilder.ConfigureFeature(features => + features + .AddFeature() + .FeatureServices(services => services + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + )); + + return fullNodeBuilder; + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs b/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs new file mode 100644 index 0000000000..b2118ec212 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs @@ -0,0 +1,533 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using NBitcoin; +using Nethereum.Contracts; +using Nethereum.Util; +using Nethereum.Web3; +using Stratis.Bitcoin.AsyncWork; +using Stratis.Bitcoin.Configuration; +using Stratis.Bitcoin.Features.PoA; +using Stratis.Bitcoin.Features.Interop.EthereumClient; +using Stratis.Bitcoin.Features.Interop.Payloads; +using Stratis.Bitcoin.Interfaces; +using Stratis.Bitcoin.Utilities; +using Stratis.Features.Collateral.CounterChain; +using Stratis.Features.FederatedPeg.Conversion; +using Stratis.Features.FederatedPeg.Interfaces; + +namespace Stratis.Bitcoin.Features.Interop +{ + public class InteropPoller : IDisposable + { + // 1x10^24 wei = 1 000 000 tokens + public BigInteger ReserveBalanceTarget = BigInteger.Parse("1000000000000000000000000"); + + public const string MintPlaceHolderRequestId = "00000000000000000000000000000000"; + + private readonly InteropSettings interopSettings; + private readonly IEthereumClientBase ethereumClientBase; + private readonly Network network; + private readonly IAsyncProvider asyncProvider; + private readonly INodeLifetime nodeLifetime; + private readonly ChainIndexer chainIndexer; + private readonly ILogger logger; + private readonly IInitialBlockDownloadState initialBlockDownloadState; + private readonly IFederationManager federationManager; + private readonly IFederationHistory federationHistory; + private readonly IFederatedPegBroadcaster federatedPegBroadcaster; + private readonly IConversionRequestRepository conversionRequestRepository; + private readonly IInteropTransactionManager interopTransactionManager; + private readonly Network counterChainNetwork; + + private IAsyncLoop interopLoop; + private IAsyncLoop conversionLoop; + + private bool firstPoll; + private bool minting; + + public InteropPoller(NodeSettings nodeSettings, + InteropSettings interopSettings, + IEthereumClientBase ethereumClientBase, + IAsyncProvider asyncProvider, + INodeLifetime nodeLifetime, + ChainIndexer chainIndexer, + IInitialBlockDownloadState initialBlockDownloadState, + IFederationManager federationManager, + IFederationHistory federationHistory, + IFederatedPegBroadcaster federatedPegBroadcaster, + IConversionRequestRepository conversionRequestRepository, + IInteropTransactionManager interopTransactionManager, + CounterChainNetworkWrapper counterChainNetworkWrapper) + { + this.interopSettings = interopSettings; + this.ethereumClientBase = ethereumClientBase; + this.network = nodeSettings.Network; + this.asyncProvider = asyncProvider; + this.nodeLifetime = nodeLifetime; + this.chainIndexer = chainIndexer; + this.initialBlockDownloadState = initialBlockDownloadState; + this.federationManager = federationManager; + this.federationHistory = federationHistory; + this.federatedPegBroadcaster = federatedPegBroadcaster; + this.conversionRequestRepository = conversionRequestRepository; + this.interopTransactionManager = interopTransactionManager; + this.counterChainNetwork = counterChainNetworkWrapper.CounterChainNetwork; + this.logger = nodeSettings.LoggerFactory.CreateLogger(this.GetType().FullName); + + this.firstPoll = true; + } + + public void Initialize() + { + if (!this.interopSettings.Enabled) + return; + + if (!this.federationManager.IsFederationMember) + return; + + this.logger.LogInformation($"Interoperability enabled, initializing periodic loop."); + + // Initialize the interop polling loop, to check for interop contract requests. + this.interopLoop = this.asyncProvider.CreateAndRunAsyncLoop("PeriodicCheckInterop", async (cancellation) => + { + if (this.initialBlockDownloadState.IsInitialBlockDownload()) + return; + + this.logger.LogTrace("Beginning interop loop."); + + try + { + await this.CheckEthereumNodeAsync().ConfigureAwait(false); + } + catch (Exception e) + { + this.logger.LogWarning("Exception raised when checking interop requests. {0}", 0); + } + + this.logger.LogTrace("Finishing interop loop."); + }, + this.nodeLifetime.ApplicationStopping, + repeatEvery: TimeSpans.TenSeconds, + startAfter: TimeSpans.Second); + + // Initialize the conversion polling loop, to check for conversion requests. + this.conversionLoop = this.asyncProvider.CreateAndRunAsyncLoop("PeriodicCheckConversionStore", async (cancellation) => + { + if (this.initialBlockDownloadState.IsInitialBlockDownload()) + return; + + this.logger.LogTrace("Beginning conversion processing loop."); + + try + { + await this.CheckForContractEventsAsync().ConfigureAwait(false); + await this.ProcessConversionRequestsAsync().ConfigureAwait(false); + } + catch (Exception e) + { + this.logger.LogWarning($"Exception raised when checking conversion requests. {e}"); + } + + this.logger.LogTrace("Finishing conversion processing loop."); + }, + this.nodeLifetime.ApplicationStopping, + repeatEvery: TimeSpans.TenSeconds, + startAfter: TimeSpans.Second); + } + + /// + /// Retrieves the current Ethereum chain height via the RPC interface to geth. + /// + private async Task CheckEthereumNodeAsync() + { + try + { + BigInteger blockHeight = await this.ethereumClientBase.GetBlockHeightAsync().ConfigureAwait(false); + + this.logger.LogInformation("Current Ethereum node block height is {0}.", blockHeight); + } + catch (Exception e) + { + this.logger.LogError("Error checking Ethereum node status: {0}", e); + } + } + + /// + /// Retrieves any Transfer events from the logs of the Wrapped Strax contract. + /// Transfers with the zero (0x0000...) address as their destination can be considered to be burn transactions and are saved for processing as withdrawals on the mainchain. + /// + private async Task CheckForContractEventsAsync() + { + if (this.firstPoll) + { + // The filter should only be set up once IBD completes. + await this.ethereumClientBase.CreateTransferEventFilterAsync().ConfigureAwait(false); + + this.firstPoll = false; + } + + // Check for all Transfer events against the WrappedStrax contract since the last time we checked. + // In future this could also poll for other events as the need arises. + List> transferEvents = await this.ethereumClientBase.GetTransferEventsForWrappedStraxAsync().ConfigureAwait(false); + + foreach (EventLog transferEvent in transferEvents) + { + // Will probably never be the case, but check anyway. + if (string.IsNullOrWhiteSpace(transferEvent.Log.TransactionHash)) + continue; + + // These could be mints or something else, either way ignore them. + if (transferEvent.Event.From == EthereumClientBase.ZeroAddress) + continue; + + // Transfers can only be burns if they are made with the zero address as the destination. + if (transferEvent.Event.To != EthereumClientBase.ZeroAddress) + continue; + + this.logger.LogInformation("Conversion burn transaction {0} received from contract events, sender {1}.", transferEvent.Log.TransactionHash, transferEvent.Event.From); + + if (this.conversionRequestRepository.Get(transferEvent.Log.TransactionHash) != null) + { + this.logger.LogInformation("Conversion burn transaction {0} already exists, ignoring.", transferEvent.Log.TransactionHash); + + continue; + } + + if (transferEvent.Event.Value == BigInteger.Zero) + { + this.logger.LogInformation("Ignoring zero-valued burn transaction {0}.", transferEvent.Log.TransactionHash); + + continue; + } + + if (transferEvent.Event.Value < BigInteger.Zero) + { + this.logger.LogInformation("Ignoring negative-valued burn transaction {0}.", transferEvent.Log.TransactionHash); + + continue; + } + + this.logger.LogInformation("Conversion burn transaction {0} has value {1}.", transferEvent.Log.TransactionHash, transferEvent.Event.Value); + + // Look up the desired destination address for this account. + string destinationAddress = await this.ethereumClientBase.GetDestinationAddressAsync(transferEvent.Event.From).ConfigureAwait(false); + + this.logger.LogInformation("Conversion burn transaction {0} has destination address {1}.", transferEvent.Log.TransactionHash, destinationAddress); + + // Validate that it is a mainchain address here before bothering to add it to the repository. + BitcoinAddress parsedAddress; + try + { + parsedAddress = BitcoinAddress.Create(destinationAddress, this.counterChainNetwork); + } + catch (Exception) + { + this.logger.LogWarning("Error validating destination address {0} for transaction {1}.", destinationAddress, transferEvent.Log.TransactionHash); + + continue; + } + + // Schedule this transaction to be processed at the next block height that is divisible by 10. If the current block height is divisible by 10, add a further 10 to it. + // In this way, burns will always be scheduled at a predictable future time across the multisig. + // This is because we cannot predict exactly when each node is polling the Ethereum chain for events. + ulong blockHeight = (ulong)this.chainIndexer.Tip.Height - ((ulong)this.chainIndexer.Tip.Height % 10) + 10; + + if (blockHeight <= 0) + blockHeight = 10; + + this.conversionRequestRepository.Save(new ConversionRequest() + { + RequestId = transferEvent.Log.TransactionHash, + RequestType = (int)ConversionRequestType.Burn, + Processed = false, + RequestStatus = (int)ConversionRequestStatus.Unprocessed, + Amount = this.ConvertWeiToSatoshi(transferEvent.Event.Value), + BlockHeight = (int)blockHeight, + DestinationAddress = destinationAddress + }); + } + } + + /// + /// Converting from wei to satoshi will result in a loss of precision past the 8th decimal place. + /// + /// The number of wei to convert. + /// The equivalent number of satoshi corresponding to the number of wei. + private ulong ConvertWeiToSatoshi(BigInteger wei) + { + decimal baseCurrencyUnits = Web3.Convert.FromWei(wei, UnitConversion.EthUnit.Ether); + + return Convert.ToUInt64(Money.Coins(baseCurrencyUnits).Satoshi); + } + + /// + /// Iterates through all unprocessed mint requests in the repository. + /// If this node is regarded as the designated originator of the multisig transaction, it will submit the transfer transaction data to + /// the multisig wallet contract on the Ethereum chain. This data consists of a method call to the transfer() method on the wrapped STRAX contract, + /// as well as the intended recipient address and amount of tokens to be transferred. + /// + private async Task ProcessConversionRequestsAsync() + { + List mintRequests = this.conversionRequestRepository.GetAllMint(true); + + if (mintRequests == null) + return; + + this.logger.LogInformation("There are {0} unprocessed conversion mint requests.", mintRequests.Count); + + foreach (ConversionRequest request in mintRequests) + { + this.logger.LogInformation("Processing conversion mint request {0}.", request.RequestId); + + // We are not able to simply use the entire federation member list, as only multisig nodes can be transaction originators. + List federation = this.federationHistory.GetFederationForBlock(this.chainIndexer.GetHeader(request.BlockHeight)); + + var multisig = new List(); + + var filtered = new List() + { + new PubKey("03a37019d2e010b046ef9d0459e4844a015758007602ddfbdc9702534924a23695"), + new PubKey("027e793fbf4f6d07de15b0aa8355f88759b8bdf92a9ffb8a65a87fa8ee03baeccd"), + new PubKey("03e8809be396745434ee8c875089e518a3eef40e31ade81869ce9cbef63484996d"), + new PubKey("03535a285d0919a9bd71df3b274cecb46e16b78bf50d3bf8b0a3b41028cf8a842d"), + new PubKey("0317abe6a28cc7af44a46de97e7c6120c1ccec78afb83efe18030f5c36e3016b32"), + new PubKey("03eb5db0b1703ea7418f0ad20582bf8de0b4105887d232c7724f43f19f14862488"), + new PubKey("038e1a76f0e33474144b61e0796404821a5150c00b05aad8a1cd502c865d8b5b92"), + new PubKey("0323033679aa439a0388f09f2883bf1ca6f50283b41bfeb6be6ddcc4e420144c16"), + new PubKey("028e1d9fd64b84a2ec85fac7185deb2c87cc0dd97270cf2d8adc3aa766dde975a7"), + new PubKey("036437789fac0ab74cda93d98b519c28608a48ef86c3bd5e8227af606c1e025f61"), + new PubKey("03f5de5176e29e1e7d518ae76c1e020b1da18b57a3713ac81b16015026e232748e") + }; + + foreach (IFederationMember member in federation) + { + if (!(member is CollateralFederationMember collateralMember)) + continue; + + if (!collateralMember.IsMultisigMember) + continue; + + if (this.network.NetworkType == NetworkType.Mainnet && !filtered.Contains(collateralMember.PubKey)) + continue; + + multisig.Add(collateralMember); + } + + // This should be impossible. + if (multisig.Count == 0) + return; + + IFederationMember designatedMember = multisig[request.BlockHeight % multisig.Count]; + + bool originator = designatedMember.Equals(this.federationManager.GetCurrentFederationMember()); + + // Regardless of whether we are the originator, this is a good time to check the multisig's remaining reserve + // token balance. It is necessary to maintain a reserve as mint transactions are many times more expensive than + // transfers. As we don't know precisely what value transactions are expected, the sole determining factor is + // whether the reserve has a large enough balance to service the current conversion request. If not, trigger a + // mint for a predetermined amount. + //BigInteger reserveBalanace = await this.ethereumClientBase.GetErc20BalanceAsync(this.interopSettings.MultisigWalletAddress).ConfigureAwait(false); + + // The request is denominated in satoshi and needs to be converted to wei. + BigInteger amountInWei = this.CoinsToWei(Money.Satoshis(request.Amount)); + + /* Temporarily disabled mint logic + if (amountInWei > reserveBalanace) + { + if (this.minting) + { + this.logger.LogInformation("Minting transaction has not yet confirmed. Waiting."); + + return; + } + + this.minting = true; + + this.logger.LogInformation("Insufficient reserve balance remaining, initiating mint transaction to replenish reserve."); + + string mintData = this.ethereumClientBase.EncodeMintParams(this.interopSettings.MultisigWalletAddress, ReserveBalanceTarget); + + BigInteger mintTransactionId = await this.ethereumClientBase.SubmitTransactionAsync(request.DestinationAddress, 0, mintData).ConfigureAwait(false); + + // Now we need to broadcast the mint transactionId to the other multisig nodes so that they can sign it off. + string mintSignature = this.federationManager.CurrentFederationKey.SignMessage(MintPlaceHolderRequestId + ((int)mintTransactionId)); + // TODO: The other multisig nodes must be careful not to blindly trust that any given transactionId relates to a mint transaction. Need to validate the recipient + await this.federatedPegBroadcaster.BroadcastAsync(new InteropCoordinationPayload(MintPlaceHolderRequestId, (int)mintTransactionId, mintSignature)).ConfigureAwait(false); + + return; + } + */ + + // TODO: Perhaps the transactionId coordination should actually be done within the multisig contract. This will however increase gas costs for each mint. Maybe a Cirrus contract instead? + switch (request.RequestStatus) + { + case ((int)ConversionRequestStatus.Unprocessed): + if (originator) + { + // If this node is the designated transaction originator, it must create and submit the transaction to the multisig. + this.logger.LogInformation("This node selected as originator for transaction {0}.", request.RequestId); + + request.RequestStatus = (int)ConversionRequestStatus.OriginatorNotSubmitted; + } + else + { + this.logger.LogInformation("This node was not selected as the originator for transaction {0}. The originator is: {1}.", request.RequestId, designatedMember.PubKey.ToHex()); + + request.RequestStatus = (int)ConversionRequestStatus.NotOriginator; + } + + break; + case ((int)ConversionRequestStatus.OriginatorNotSubmitted): + // First construct the necessary transfer() transaction data, utilising the ABI of the wrapped STRAX ERC20 contract. + + // When this constructed transaction is actually executed, the transfer's source account will be the account executing the transaction i.e. the multisig contract address. + string abiData = this.ethereumClientBase.EncodeTransferParams(request.DestinationAddress, amountInWei); + + // Submit the unconfirmed transaction data to the multisig contract, returning a transactionId used to refer to it. + // Once sufficient multisig owners have confirmed the transaction the multisig contract will execute it. + // Note that by submitting the transaction to the multisig wallet contract, the originator is implicitly granting it one confirmation. + BigInteger transactionId = await this.ethereumClientBase.SubmitTransactionAsync(this.interopSettings.WrappedStraxAddress, 0, abiData).ConfigureAwait(false); + + this.logger.LogInformation("Originator submitted transaction to multisig and was allocated transactionId {0}.", transactionId); + + // TODO: Need to persist vote storage across node shutdowns + this.interopTransactionManager.AddVote(request.RequestId, transactionId, this.federationManager.CurrentFederationKey.PubKey); + + request.RequestStatus = (int)ConversionRequestStatus.OriginatorSubmitted; + + break; + case ((int)ConversionRequestStatus.OriginatorSubmitted): + // It must then propagate the transactionId to the other nodes so that they know they should confirm it. + // The reason why each node doesn't simply maintain its own transaction counter, is that it can't be guaranteed + // that a transaction won't be submitted out-of-turn by a rogue or malfunctioning federation multisig node. + // The coordination mechanism safeguards against this, as any such spurious transaction will not receive acceptance votes. + // TODO: The transactionId should be accompanied by the hash of the submission transaction on the Ethereum chain so that it can be verified + + BigInteger transactionId2 = this.interopTransactionManager.GetCandidateTransactionId(request.RequestId); + + if (transactionId2 != BigInteger.MinusOne) + { + await this.BroadcastCoordinationAsync(request.RequestId, transactionId2).ConfigureAwait(false); + + BigInteger agreedTransactionId = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, 6); + + if (agreedTransactionId != BigInteger.MinusOne) + { + this.logger.LogInformation("Transaction {0} has received sufficient votes, it should now start getting confirmed by each peer.", agreedTransactionId); + + request.RequestStatus = (int)ConversionRequestStatus.VoteFinalised; + } + } + + break; + case ((int)ConversionRequestStatus.VoteFinalised): + BigInteger transactionId3 = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, 6); + + if (transactionId3 != BigInteger.MinusOne) + { + // The originator isn't responsible for anything further at this point, except for periodically checking the confirmation count. + // The non-originators also need to monitor the confirmation count so that they know when to mark the transaction as processed locally. + BigInteger confirmationCount = await this.ethereumClientBase.GetConfirmationCountAsync(transactionId3).ConfigureAwait(false); + + if (confirmationCount >= 6) + { + this.logger.LogInformation("Transaction {0} has received at least 6 confirmations, it will be automatically executed by the multisig contract.", transactionId3); + + request.RequestStatus = (int)ConversionRequestStatus.Processed; + request.Processed = true; + + // We no longer need to track votes for this transaction. + this.interopTransactionManager.RemoveTransaction(request.RequestId); + } + else + { + this.logger.LogInformation("Transaction {0} has finished voting but does not yet have 8 confirmations, re-broadcasting votes to peers.", transactionId3); + + // There are not enough confirmations yet. + // Even though the vote is finalised, other nodes may come and go. So we re-broadcast the finalised votes to all federation peers. + // Nodes will simply ignore the messages if they are not relevant. + + await this.BroadcastCoordinationAsync(request.RequestId, transactionId3).ConfigureAwait(false); + + // No state transition here, we are waiting for sufficient confirmations. + } + } + + break; + case ((int)ConversionRequestStatus.NotOriginator): + // If not the originator, this node needs to determine what multisig wallet transactionId it should confirm. + // Initially there will not be a quorum of nodes that agree on the transactionId. + // So each node needs to satisfy itself that the transactionId sent by the originator exists in the multisig wallet. + // This is done within the InteropBehavior automatically, we just check each poll loop if a transaction has enough votes yet. + // Each node must only ever confirm a single transactionId for a given conversion transaction. + BigInteger agreedUponId = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, 6); + + if (agreedUponId != BigInteger.MinusOne) + { + this.logger.LogInformation("Quorum reached for conversion transaction {0} with transactionId {1}, submitting confirmation to contract.", request.RequestId, agreedUponId); + + // Once a quorum is reached, each node confirms the agreed transactionId. + // If the originator or some other nodes renege on their vote, the current node will not re-confirm a different transactionId. + string confirmationHash = await this.ethereumClientBase.ConfirmTransactionAsync(agreedUponId).ConfigureAwait(false); + + this.logger.LogInformation("The hash of the confirmation transaction for conversion transaction {0} was {1}.", request.RequestId, confirmationHash); + + request.RequestStatus = (int)ConversionRequestStatus.VoteFinalised; + } + else + { + BigInteger transactionId4 = this.interopTransactionManager.GetCandidateTransactionId(request.RequestId); + + if (transactionId4 != BigInteger.MinusOne) + { + this.logger.LogInformation("Broadcasting vote (transactionId {0}) for conversion transaction {1}.", transactionId4, request.RequestId); + + this.interopTransactionManager.AddVote(request.RequestId, transactionId4, this.federationManager.CurrentFederationKey.PubKey); + + await this.BroadcastCoordinationAsync(request.RequestId, transactionId4).ConfigureAwait(false); + } + + // No state transition here, as we are waiting for the candidate transactionId to progress to an agreed upon transactionId via a quorum. + } + + break; + } + + // Make sure that any state transitions are persisted to storage. + this.conversionRequestRepository.Save(request); + + // Unlike the mint requests, burns are not initiated by the multisig wallet. + // Instead they are initiated by the user, via a contract call to the burn() method on the WrappedStrax contract. + // They need to provide a destination STRAX address when calling the burn method. + + // Properly processing burn transactions requires emulating a withdrawal on the main chain from the multisig wallet. + // It will be easier when conversion can be done directly to and from a Cirrus contract instead. + + // Currently the processing is done in the WithdrawalExtractor. + } + } + + private async Task BroadcastCoordinationAsync(string requestId, BigInteger transactionId) + { + string signature = this.federationManager.CurrentFederationKey.SignMessage(requestId + ((int)transactionId)); + + await this.federatedPegBroadcaster.BroadcastAsync(new InteropCoordinationPayload(requestId, (int)transactionId, signature)).ConfigureAwait(false); + } + + private BigInteger CoinsToWei(Money coins) + { + BigInteger baseCurrencyUnits = Web3.Convert.ToWei(coins.ToUnit(MoneyUnit.BTC), UnitConversion.EthUnit.Ether); + + return baseCurrencyUnits; + } + + public void Dispose() + { + this.interopLoop?.Dispose(); + this.conversionLoop?.Dispose(); + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/InteropSettings.cs b/src/Stratis.Bitcoin.Features.Interop/InteropSettings.cs new file mode 100644 index 0000000000..40428e101b --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/InteropSettings.cs @@ -0,0 +1,74 @@ +using System; +using Stratis.Bitcoin.Configuration; + +namespace Stratis.Bitcoin.Features.Interop +{ + public class InteropSettings + { + public const string InteropKey = "interop"; + public const string InteropContractCirrusAddressKey = "interopcontractcirrusaddress"; + public const string InteropContractEthereumAddressKey = "interopcontractethereumaddress"; + public const string MultisigWalletContractAddressKey = "multisigwalletcontractaddress"; + public const string WrappedStraxContractAddressKey = "wrappedstraxcontractaddress"; + public const string EthereumClientUrlKey = "ethereumclienturl"; + public const string EthereumAccountKey = "ethereumaccount"; + public const string EthereumPassphraseKey = "ethereumpassphrase"; + public const string EthereumGasKey = "ethereumgas"; + public const string EthereumGasPriceKey = "ethereumgasprice"; + + public bool Enabled { get; set; } + + public string InteropContractCirrusAddress { get; set; } + + public string InteropContractEthereumAddress { get; set; } + + public string MultisigWalletAddress { get; set; } + + public string WrappedStraxAddress { get; set; } + + public string EthereumClientUrl { get; set; } + + public string EthereumAccount { get; set; } + + public string EthereumPassphrase { get; set; } + + /// + /// The gas limit for Ethereum interoperability transactions. + /// + public int EthereumGas { get; set; } + + /// + /// The gas price for Ethereum interoperability transactions (denominated in gwei). + /// + public int EthereumGasPrice { get; set; } + + public InteropSettings(NodeSettings nodeSettings) + { + this.Enabled = nodeSettings.ConfigReader.GetOrDefault(InteropKey, false); + + this.InteropContractCirrusAddress = nodeSettings.ConfigReader.GetOrDefault(InteropContractCirrusAddressKey, ""); + this.InteropContractEthereumAddress = nodeSettings.ConfigReader.GetOrDefault(InteropContractEthereumAddressKey, ""); + + this.MultisigWalletAddress = nodeSettings.ConfigReader.GetOrDefault(MultisigWalletContractAddressKey, ""); + this.WrappedStraxAddress = nodeSettings.ConfigReader.GetOrDefault(WrappedStraxContractAddressKey, ""); + this.EthereumClientUrl = nodeSettings.ConfigReader.GetOrDefault(EthereumClientUrlKey, "http://localhost:8545"); + this.EthereumAccount = nodeSettings.ConfigReader.GetOrDefault(EthereumAccountKey, ""); + this.EthereumPassphrase = nodeSettings.ConfigReader.GetOrDefault(EthereumPassphraseKey, ""); + + this.EthereumGas = nodeSettings.ConfigReader.GetOrDefault(EthereumGasKey, 3_000_000); + this.EthereumGasPrice = nodeSettings.ConfigReader.GetOrDefault(EthereumGasPriceKey, 100); + + if (!this.Enabled) + return; + + if (string.IsNullOrWhiteSpace(this.MultisigWalletAddress)) + throw new Exception($"Cannot initialize interoperability feature without -{MultisigWalletContractAddressKey} specified."); + + if (string.IsNullOrWhiteSpace(this.WrappedStraxAddress)) + throw new Exception($"Cannot initialize interoperability feature without -{WrappedStraxContractAddressKey} specified."); + + if (string.IsNullOrWhiteSpace(this.EthereumClientUrl)) + throw new Exception($"Cannot initialize interoperability feature without -{EthereumClientUrlKey} specified."); + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/InteropTransactionManager.cs b/src/Stratis.Bitcoin.Features.Interop/InteropTransactionManager.cs new file mode 100644 index 0000000000..8882f8e816 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/InteropTransactionManager.cs @@ -0,0 +1,173 @@ +using System.Collections.Generic; +using System.Numerics; +using NBitcoin; +using NLog; +using Stratis.Features.FederatedPeg.Conversion; + +namespace Stratis.Bitcoin.Features.Interop +{ + public interface IInteropTransactionManager + { + /// + /// Records a vote for a particular transactionId to be associated with the request. + /// The vote is recorded against the pubkey of the federation member that cast it. + /// + /// The identifier of the request. + /// The voted-for transactionId. + /// The pubkey of the federation member that signed the incoming message. + void AddVote(string requestId, BigInteger transactionId, PubKey pubKey); + + /// + /// If one of the transaction Ids being voted on has reached a quroum, this will return that transactionId. + /// + /// The identifier of the request. + /// The number of votes required for a majority. + /// The transactionId of the request that has reached a quorum of votes. + BigInteger GetAgreedTransactionId(string requestId, int quorum); + + /// + /// Returns the currently highest-voted transactionId. + /// If there is a tie, one is picked randomly. + /// + /// The identifier of the request. + /// The transactionId of the highest-voted request. + BigInteger GetCandidateTransactionId(string requestId); + + /// Checks if vote was received from the pubkey specified for a particular . + bool CheckIfVoted(string requestId, PubKey pubKey); + + /// Removes all votes associated with provided request Id. + void RemoveTransaction(string requestId); + + /// Provides mapping of all request ids to pubkeys that have voted for them. + Dictionary> GetStatus(); + } + + public class InteropTransactionManager : IInteropTransactionManager + { + private readonly ILogger logger; + + private Dictionary> activeVotes; + private Dictionary> receivedVotes; + + private readonly object lockObject = new object(); + + public InteropTransactionManager() + { + this.activeVotes = new Dictionary>(); + this.receivedVotes = new Dictionary>(); + this.logger = LogManager.GetCurrentClassLogger(); + } + + /// + public void AddVote(string requestId, BigInteger transactionId, PubKey pubKey) + { + lock (this.lockObject) + { + if (!this.receivedVotes.TryGetValue(requestId, out HashSet voted)) + { + voted = new HashSet(); + } + + // Ignore the vote if the pubkey has already submitted a vote. + if (voted.Contains(pubKey)) + return; + + this.logger.Info("Pubkey {0} adding vote for request {1}, transactionId {2}.", pubKey.ToHex(), requestId, transactionId); + + voted.Add(pubKey); + + if (!this.activeVotes.TryGetValue(requestId, out Dictionary transactionIdVotes)) + transactionIdVotes = new Dictionary(); + + if (!transactionIdVotes.ContainsKey(transactionId)) + transactionIdVotes[transactionId] = 1; + else + transactionIdVotes[transactionId]++; + + this.activeVotes[requestId] = transactionIdVotes; + this.receivedVotes[requestId] = voted; + } + } + + /// + public BigInteger GetAgreedTransactionId(string requestId, int quorum) + { + lock (this.lockObject) + { + if (!this.activeVotes.ContainsKey(requestId)) + return BigInteger.MinusOne; + + BigInteger highestVoted = BigInteger.MinusOne; + int voteCount = 0; + foreach (KeyValuePair vote in this.activeVotes[requestId]) + { + if (vote.Value > voteCount && vote.Value >= quorum) + { + highestVoted = vote.Key; + voteCount = vote.Value; + } + } + + return highestVoted; + } + } + + /// + public BigInteger GetCandidateTransactionId(string requestId) + { + lock (this.lockObject) + { + if (!this.activeVotes.ContainsKey(requestId)) + return BigInteger.MinusOne; + + BigInteger highestVoted = BigInteger.MinusOne; + int voteCount = 0; + foreach (KeyValuePair vote in this.activeVotes[requestId]) + { + if (vote.Value > voteCount) + { + highestVoted = vote.Key; + voteCount = vote.Value; + } + } + + return highestVoted; + } + } + + /// + public bool CheckIfVoted(string requestId, PubKey pubKey) + { + lock (this.lockObject) + { + if (!this.receivedVotes.ContainsKey(requestId)) + return false; + + if (!this.receivedVotes[requestId].Contains(pubKey)) + return false; + + return true; + } + } + + /// + public void RemoveTransaction(string requestId) + { + lock (this.lockObject) + { + this.activeVotes.Remove(requestId); + this.receivedVotes.Remove(requestId); + } + } + + /// + public Dictionary> GetStatus() + { + lock (this.lockObject) + { + return this.receivedVotes; + } + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/Models/ConversionRequestModel.cs b/src/Stratis.Bitcoin.Features.Interop/Models/ConversionRequestModel.cs new file mode 100644 index 0000000000..451257bb58 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/Models/ConversionRequestModel.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace Stratis.Bitcoin.Features.Interop.Models +{ + public class ConversionRequestModel + { + [JsonProperty(PropertyName = "requestId")] + public string RequestId { get; set; } + + [JsonProperty(PropertyName = "requestType")] + public int RequestType { get; set; } + + [JsonProperty(PropertyName = "requestStatus")] + public int RequestStatus { get; set; } + + [JsonProperty(PropertyName = "blockHeight")] + public int BlockHeight { get; set; } + + [JsonProperty(PropertyName = "destinationAddress")] + public string DestinationAddress { get; set; } + + [JsonProperty(PropertyName = "amount")] + public ulong Amount { get; set; } + + [JsonProperty(PropertyName = "processed")] + public bool Processed { get; set; } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/Models/InteropStatusResponseModel.cs b/src/Stratis.Bitcoin.Features.Interop/Models/InteropStatusResponseModel.cs new file mode 100644 index 0000000000..a7f1eec341 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/Models/InteropStatusResponseModel.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Stratis.Bitcoin.Features.Interop.Models +{ + public class InteropStatusResponseModel + { + [JsonProperty(PropertyName="mintRequests")] + public List MintRequests { get; set; } + + [JsonProperty(PropertyName = "burnRequests")] + public List BurnRequests { get; set; } + + [JsonProperty(PropertyName = "receivedVotes")] + public Dictionary> ReceivedVotes { get; set; } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/Payloads/InteropCoordinationPayload.cs b/src/Stratis.Bitcoin.Features.Interop/Payloads/InteropCoordinationPayload.cs new file mode 100644 index 0000000000..4806a36426 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/Payloads/InteropCoordinationPayload.cs @@ -0,0 +1,43 @@ +using NBitcoin; +using Stratis.Bitcoin.P2P.Protocol.Payloads; + +namespace Stratis.Bitcoin.Features.Interop.Payloads +{ + [Payload("coord")] + public class InteropCoordinationPayload : Payload + { + private string requestId; + private int transactionId; + private string signature; + + public string RequestId => this.requestId; + + public int TransactionId => this.transactionId; + + public string Signature => this.signature; + + /// Needed for deserialization. + public InteropCoordinationPayload() + { + } + + public InteropCoordinationPayload(string requestId, int transactionId, string signature) + { + this.requestId = requestId; + this.transactionId = transactionId; + this.signature = signature; + } + + public override void ReadWriteCore(BitcoinStream stream) + { + stream.ReadWrite(ref this.requestId); + stream.ReadWrite(ref this.transactionId); + stream.ReadWrite(ref this.signature); + } + + public override string ToString() + { + return $"{nameof(this.Command)}:'{this.Command}',{nameof(this.RequestId)}:'{this.RequestId}',{nameof(this.TransactionId)}:'{this.TransactionId}'"; + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Interop/Stratis.Bitcoin.Features.Interop.csproj b/src/Stratis.Bitcoin.Features.Interop/Stratis.Bitcoin.Features.Interop.csproj new file mode 100644 index 0000000000..a4f5176845 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Interop/Stratis.Bitcoin.Features.Interop.csproj @@ -0,0 +1,31 @@ + + + + netcoreapp3.1 + 1.0.8.0 + Stratis Group Ltd. + Stratis.Features.Interop + Stratis.Features.Interop + + + + bin\Debug\netcoreapp3.1\Stratis.Bitcoin.Features.Interop.xml + + + + + + + + + + + + + + + + + + + diff --git a/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs b/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs index 2031ff9bb1..d17e1cf2bf 100644 --- a/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs +++ b/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs @@ -20,7 +20,6 @@ using Stratis.Bitcoin.Features.Notifications; using Stratis.Bitcoin.Features.Wallet; using Stratis.Bitcoin.Features.Wallet.Broadcasting; -using Stratis.Bitcoin.Features.Wallet.Controllers; using Stratis.Bitcoin.Features.Wallet.Interfaces; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.P2P.Protocol.Payloads; diff --git a/src/Stratis.Bitcoin.Features.LightWallet/LightWalletSyncManager.cs b/src/Stratis.Bitcoin.Features.LightWallet/LightWalletSyncManager.cs index b3b361f702..abd6d86884 100644 --- a/src/Stratis.Bitcoin.Features.LightWallet/LightWalletSyncManager.cs +++ b/src/Stratis.Bitcoin.Features.LightWallet/LightWalletSyncManager.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using Microsoft.Extensions.Logging; using NBitcoin; diff --git a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/MempoolValidatorTest.cs b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/MempoolValidatorTest.cs index 8a9241d414..f26c6ed35c 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/MempoolValidatorTest.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/MempoolValidatorTest.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using NBitcoin; diff --git a/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs b/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs index af1df6c062..38c8a7fd44 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NBitcoin; using Stratis.Bitcoin.Features.Consensus; using Stratis.Bitcoin.Features.Consensus.CoinViews; diff --git a/src/Stratis.Bitcoin.Features.Miner/BlockDefinition.cs b/src/Stratis.Bitcoin.Features.Miner/BlockDefinition.cs index 79bc9d3ac4..04bb3cf238 100644 --- a/src/Stratis.Bitcoin.Features.Miner/BlockDefinition.cs +++ b/src/Stratis.Bitcoin.Features.Miner/BlockDefinition.cs @@ -176,19 +176,25 @@ protected virtual void CreateCoinbase() /// private void Configure() { - this.BlockSize = 1000; this.BlockTemplate = new BlockTemplate(this.Network); - this.BlockTx = 0; + + // Reserve space for the coinbase transaction, bitcoind miner.cpp void BlockAssembler::resetBlock() + this.BlockSize = 1000; this.BlockWeight = 1000 * this.Network.Consensus.Options.WitnessScaleFactor; this.BlockSigOpsCost = 400; + this.IncludeWitness = false; + + // These counters do not include the coinbase transaction + this.BlockTx = 0; this.fees = 0; + this.inBlock = new TxMempool.SetEntries(); - this.IncludeWitness = false; } /// /// Constructs a block template which will be passed to consensus. /// + /// bitcoind miner.cpp BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn) /// Tip of the chain that this instance will work with without touching any shared chain resources. /// Script that explains what conditions must be met to claim ownership of a coin. protected void OnBuild(ChainedHeader chainTip, Script scriptPubKey) diff --git a/src/Stratis.Bitcoin.Features.Miner/Stratis.Bitcoin.Features.Miner.csproj b/src/Stratis.Bitcoin.Features.Miner/Stratis.Bitcoin.Features.Miner.csproj index 1455a8766b..e465b91032 100644 --- a/src/Stratis.Bitcoin.Features.Miner/Stratis.Bitcoin.Features.Miner.csproj +++ b/src/Stratis.Bitcoin.Features.Miner/Stratis.Bitcoin.Features.Miner.csproj @@ -1,4 +1,4 @@ - + Stratis Bitcoin Features Miner @@ -47,4 +47,4 @@ - + \ No newline at end of file diff --git a/src/Stratis.Bitcoin.Features.Notifications/BlockNotificationFeature.cs b/src/Stratis.Bitcoin.Features.Notifications/BlockNotificationFeature.cs index 20f34fe98a..b11283c745 100644 --- a/src/Stratis.Bitcoin.Features.Notifications/BlockNotificationFeature.cs +++ b/src/Stratis.Bitcoin.Features.Notifications/BlockNotificationFeature.cs @@ -8,7 +8,6 @@ using Stratis.Bitcoin.Builder.Feature; using Stratis.Bitcoin.Connection; using Stratis.Bitcoin.Consensus; -using Stratis.Bitcoin.Features.Notifications.Controllers; using Stratis.Bitcoin.Features.Notifications.Interfaces; using Stratis.Bitcoin.P2P.Peer; diff --git a/src/Stratis.Bitcoin.Features.Notifications/TransactionNotificationFeature.cs b/src/Stratis.Bitcoin.Features.Notifications/TransactionNotificationFeature.cs index 50edb3eec9..ae7a209097 100644 --- a/src/Stratis.Bitcoin.Features.Notifications/TransactionNotificationFeature.cs +++ b/src/Stratis.Bitcoin.Features.Notifications/TransactionNotificationFeature.cs @@ -3,7 +3,6 @@ using Stratis.Bitcoin.Builder; using Stratis.Bitcoin.Builder.Feature; using Stratis.Bitcoin.Connection; -using Stratis.Bitcoin.Features.Notifications.Controllers; namespace Stratis.Bitcoin.Features.Notifications { diff --git a/src/Stratis.Bitcoin.Features.PoA.Tests/SlotsManagerTests.cs b/src/Stratis.Bitcoin.Features.PoA.Tests/SlotsManagerTests.cs index 5533b0b0bb..fcf198562b 100644 --- a/src/Stratis.Bitcoin.Features.PoA.Tests/SlotsManagerTests.cs +++ b/src/Stratis.Bitcoin.Features.PoA.Tests/SlotsManagerTests.cs @@ -4,7 +4,6 @@ using NBitcoin; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Logging; -using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Tests.Common; using Xunit; diff --git a/src/Stratis.Bitcoin.Features.PoA/BasePoAFeatureConsensusRules/PoAHeaderSignatureRule.cs b/src/Stratis.Bitcoin.Features.PoA/BasePoAFeatureConsensusRules/PoAHeaderSignatureRule.cs index b1c56399cf..463120c42d 100644 --- a/src/Stratis.Bitcoin.Features.PoA/BasePoAFeatureConsensusRules/PoAHeaderSignatureRule.cs +++ b/src/Stratis.Bitcoin.Features.PoA/BasePoAFeatureConsensusRules/PoAHeaderSignatureRule.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NBitcoin; diff --git a/src/Stratis.Bitcoin.Features.RPC/RPCFeature.cs b/src/Stratis.Bitcoin.Features.RPC/RPCFeature.cs index d4da415feb..589db83efa 100644 --- a/src/Stratis.Bitcoin.Features.RPC/RPCFeature.cs +++ b/src/Stratis.Bitcoin.Features.RPC/RPCFeature.cs @@ -9,9 +9,7 @@ using Stratis.Bitcoin.Builder.Feature; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Logging; -using Stratis.Bitcoin.Connection; using Stratis.Bitcoin.Features.Consensus; -using Stratis.Bitcoin.Features.RPC.Controllers; namespace Stratis.Bitcoin.Features.RPC { diff --git a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/ApiLogDeserializerTests.cs b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/ApiLogDeserializerTests.cs index 33a1aaa86d..6f04ca03b8 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/ApiLogDeserializerTests.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/ApiLogDeserializerTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Stratis.SmartContracts; using Stratis.SmartContracts.CLR; using Stratis.SmartContracts.CLR.Serialization; diff --git a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/AllowedScriptTypeRuleTests.cs b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/AllowedScriptTypeRuleTests.cs index b1c3cc82c0..23790e2fec 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/AllowedScriptTypeRuleTests.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/AllowedScriptTypeRuleTests.cs @@ -1,7 +1,6 @@ using NBitcoin; using NBitcoin.DataEncoders; using Stratis.Bitcoin.Features.SmartContracts.Rules; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Networks; using Xunit; diff --git a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/CanGetSenderRuleTest.cs b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/CanGetSenderRuleTest.cs index 9fa6f9fe03..4176f8e5b9 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/CanGetSenderRuleTest.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/CanGetSenderRuleTest.cs @@ -20,7 +20,6 @@ using Stratis.Bitcoin.Signals; using Stratis.Bitcoin.Tests.Common; using Stratis.Bitcoin.Utilities; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Core.Util; using Stratis.SmartContracts.Networks; using Xunit; diff --git a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/OpSpendRuleTest.cs b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/OpSpendRuleTest.cs index ace99ee7af..45b1d15859 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/OpSpendRuleTest.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/OpSpendRuleTest.cs @@ -4,7 +4,6 @@ using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.SmartContracts.Rules; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Networks; using Xunit; diff --git a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/P2PKHNotContractRuleTests.cs b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/P2PKHNotContractRuleTests.cs index 42a8e03269..e86b83bff2 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/P2PKHNotContractRuleTests.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/P2PKHNotContractRuleTests.cs @@ -1,7 +1,6 @@ using Moq; using NBitcoin; using Stratis.Bitcoin.Consensus; -using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Bitcoin.Features.SmartContracts.Rules; using Stratis.SmartContracts.Core.State; using Stratis.SmartContracts.Networks; diff --git a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/TxOutSmartContractExecRuleTest.cs b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/TxOutSmartContractExecRuleTest.cs index f5e706b476..85a1cef3d9 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/TxOutSmartContractExecRuleTest.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts.Tests/Consensus/Rules/TxOutSmartContractExecRuleTest.cs @@ -4,7 +4,6 @@ using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.SmartContracts.Rules; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Networks; using Xunit; diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/ApiLogDeserializer.cs b/src/Stratis.Bitcoin.Features.SmartContracts/ApiLogDeserializer.cs index cd3d829bf2..5ef8e3c04a 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/ApiLogDeserializer.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/ApiLogDeserializer.cs @@ -33,7 +33,7 @@ public ApiLogDeserializer(IContractPrimitiveSerializer primitiveSerializer, Netw /// An containing the fields of the Type and its deserialized values. public dynamic DeserializeLogData(byte[] bytes, Type type) { - RLPCollection collection = (RLPCollection)RLP.Decode(bytes)[0]; + RLPCollection collection = (RLPCollection)RLP.Decode(bytes); var instance = new ExpandoObject() as IDictionary; diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/MempoolRules/CanGetSenderMempoolRule.cs b/src/Stratis.Bitcoin.Features.SmartContracts/MempoolRules/CanGetSenderMempoolRule.cs index a9f0a41b74..008f2071e2 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/MempoolRules/CanGetSenderMempoolRule.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/MempoolRules/CanGetSenderMempoolRule.cs @@ -4,7 +4,6 @@ using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Bitcoin.Features.MemoryPool.Interfaces; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Core.Util; namespace Stratis.Bitcoin.Features.SmartContracts.MempoolRules diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/MempoolRules/OpSpendMempoolRule.cs b/src/Stratis.Bitcoin.Features.SmartContracts/MempoolRules/OpSpendMempoolRule.cs index 3b15f73f23..e1cdbbe17f 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/MempoolRules/OpSpendMempoolRule.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/MempoolRules/OpSpendMempoolRule.cs @@ -4,7 +4,6 @@ using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Bitcoin.Features.MemoryPool.Interfaces; -using Stratis.SmartContracts.Core; namespace Stratis.Bitcoin.Features.SmartContracts.MempoolRules { diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Models/BuildCallContractTransactionRequest.cs b/src/Stratis.Bitcoin.Features.SmartContracts/Models/BuildCallContractTransactionRequest.cs index 1e016e76fe..b3fa86da0a 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/Models/BuildCallContractTransactionRequest.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/Models/BuildCallContractTransactionRequest.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text; -using Newtonsoft.Json; using Stratis.Bitcoin.Features.SmartContracts.ReflectionExecutor.Consensus.Rules; using Stratis.Bitcoin.Features.Wallet.Models; using Stratis.Bitcoin.Features.Wallet.Validations; diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/PoA/SmartContractPoABlockHeader.cs b/src/Stratis.Bitcoin.Features.SmartContracts/PoA/SmartContractPoABlockHeader.cs index fc8d4d5ccd..fde1d4ee71 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/PoA/SmartContractPoABlockHeader.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/PoA/SmartContractPoABlockHeader.cs @@ -1,6 +1,5 @@ using NBitcoin; using Stratis.Bitcoin.Features.PoA; -using Stratis.SmartContracts.Core; using TracerAttributes; using uint256 = NBitcoin.uint256; diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/AllowedScriptTypeRule.cs b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/AllowedScriptTypeRule.cs index d52f85bfb8..cc96b8aa7d 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/AllowedScriptTypeRule.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/AllowedScriptTypeRule.cs @@ -2,7 +2,6 @@ using NBitcoin; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; -using Stratis.SmartContracts.Core; namespace Stratis.Bitcoin.Features.SmartContracts.Rules { diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/CanGetSenderRule.cs b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/CanGetSenderRule.cs index f14eee33fb..98962707ba 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/CanGetSenderRule.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/CanGetSenderRule.cs @@ -6,7 +6,6 @@ using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Features.Consensus.Rules; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Core.Util; namespace Stratis.Bitcoin.Features.SmartContracts.Rules diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/ContractTransactionFullValidationRule.cs b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/ContractTransactionFullValidationRule.cs index 8bee23bfeb..7530355c94 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/ContractTransactionFullValidationRule.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/ContractTransactionFullValidationRule.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Stratis.Bitcoin.Consensus.Rules; -using Stratis.Bitcoin.Features.MemoryPool; -using Stratis.Bitcoin.Features.MemoryPool.Interfaces; using Stratis.SmartContracts.CLR; namespace Stratis.Bitcoin.Features.SmartContracts.Rules diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/P2PKHNotContractRule.cs b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/P2PKHNotContractRule.cs index d4c8cffb62..c23c9190cc 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/P2PKHNotContractRule.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/P2PKHNotContractRule.cs @@ -2,7 +2,6 @@ using NBitcoin; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; -using Stratis.Bitcoin.Features.MemoryPool; using Stratis.SmartContracts.Core.State; namespace Stratis.Bitcoin.Features.SmartContracts.Rules diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/TxOutSmartContractExecRule.cs b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/TxOutSmartContractExecRule.cs index a0ad2b9534..44dad3f61f 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/TxOutSmartContractExecRule.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/TxOutSmartContractExecRule.cs @@ -3,7 +3,6 @@ using NBitcoin; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; -using Stratis.SmartContracts.Core; namespace Stratis.Bitcoin.Features.SmartContracts.Rules { diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractFeature.cs b/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractFeature.cs index 548ea228b7..917471ab08 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractFeature.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractFeature.cs @@ -62,6 +62,10 @@ public override Task InitializeAsync() this.logger.LogInformation("Smart Contract Feature Injected."); return Task.CompletedTask; } + + public override void Dispose() + { + } } public class SmartContractOptions @@ -187,4 +191,4 @@ public static SmartContractOptions UseReflectionExecutor(this SmartContractOptio return options; } } -} \ No newline at end of file +} diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractScriptAddressReader.cs b/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractScriptAddressReader.cs index 1cef195ede..dfbf91b221 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractScriptAddressReader.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractScriptAddressReader.cs @@ -3,7 +3,6 @@ using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Interfaces; using Stratis.SmartContracts.CLR; -using Stratis.SmartContracts.Core; namespace Stratis.Bitcoin.Features.SmartContracts { diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractTransactionPolicy.cs b/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractTransactionPolicy.cs index f74b8cb034..89fa6ec55a 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractTransactionPolicy.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/SmartContractTransactionPolicy.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using NBitcoin; using NBitcoin.Policy; -using Stratis.SmartContracts.Core; namespace Stratis.Bitcoin.Features.SmartContracts { diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Stratis.Bitcoin.Features.SmartContracts.csproj b/src/Stratis.Bitcoin.Features.SmartContracts/Stratis.Bitcoin.Features.SmartContracts.csproj index d52c1d8357..29b239ca17 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/Stratis.Bitcoin.Features.SmartContracts.csproj +++ b/src/Stratis.Bitcoin.Features.SmartContracts/Stratis.Bitcoin.Features.SmartContracts.csproj @@ -13,7 +13,9 @@ + + diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Wallet/SmartContractWalletController.cs b/src/Stratis.Bitcoin.Features.SmartContracts/Wallet/SmartContractWalletController.cs index a104f8a1bf..cf48bd5ad3 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/Wallet/SmartContractWalletController.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/Wallet/SmartContractWalletController.cs @@ -17,7 +17,6 @@ using Stratis.Bitcoin.Utilities.JsonErrors; using Stratis.Bitcoin.Utilities.ModelStateErrors; using Stratis.SmartContracts.CLR; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Core.Receipts; namespace Stratis.Bitcoin.Features.SmartContracts.Wallet diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Wallet/SmartContractWalletTransactionHandler.cs b/src/Stratis.Bitcoin.Features.SmartContracts/Wallet/SmartContractWalletTransactionHandler.cs index ac92c3f6de..764b1a82c8 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/Wallet/SmartContractWalletTransactionHandler.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/Wallet/SmartContractWalletTransactionHandler.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Microsoft.Extensions.Logging; using NBitcoin; using NBitcoin.Policy; @@ -7,7 +6,6 @@ using Stratis.Bitcoin.Features.Wallet.Interfaces; using Stratis.Bitcoin.Features.Wallet.Services; using Stratis.Bitcoin.Utilities; -using Stratis.SmartContracts.Core; namespace Stratis.Bitcoin.Features.SmartContracts.Wallet { diff --git a/src/Stratis.Bitcoin.Features.Wallet.Tests/WalletTransactionHandlerTest.cs b/src/Stratis.Bitcoin.Features.Wallet.Tests/WalletTransactionHandlerTest.cs index 5e36fc08b4..a5b43557fa 100644 --- a/src/Stratis.Bitcoin.Features.Wallet.Tests/WalletTransactionHandlerTest.cs +++ b/src/Stratis.Bitcoin.Features.Wallet.Tests/WalletTransactionHandlerTest.cs @@ -8,7 +8,6 @@ using Moq; using NBitcoin; using NBitcoin.Policy; -using Stratis.Bitcoin.AsyncWork; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Logging; using Stratis.Bitcoin.Consensus; diff --git a/src/Stratis.Bitcoin.Features.Wallet/Interfaces/IWalletSyncManager.cs b/src/Stratis.Bitcoin.Features.Wallet/Interfaces/IWalletSyncManager.cs index b4bca77fe5..a51037fd01 100644 --- a/src/Stratis.Bitcoin.Features.Wallet/Interfaces/IWalletSyncManager.cs +++ b/src/Stratis.Bitcoin.Features.Wallet/Interfaces/IWalletSyncManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using NBitcoin; namespace Stratis.Bitcoin.Features.Wallet.Interfaces diff --git a/src/Stratis.Bitcoin.Features.Wallet/UnspentOutputReference.cs b/src/Stratis.Bitcoin.Features.Wallet/UnspentOutputReference.cs index 9eb33897fc..8d0429bf07 100644 --- a/src/Stratis.Bitcoin.Features.Wallet/UnspentOutputReference.cs +++ b/src/Stratis.Bitcoin.Features.Wallet/UnspentOutputReference.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using NBitcoin; +using NBitcoin; namespace Stratis.Bitcoin.Features.Wallet { diff --git a/src/Stratis.Bitcoin.Features.WatchOnlyWallet/WatchOnlyWalletFeature.cs b/src/Stratis.Bitcoin.Features.WatchOnlyWallet/WatchOnlyWalletFeature.cs index 78bdd74796..7f7166739a 100644 --- a/src/Stratis.Bitcoin.Features.WatchOnlyWallet/WatchOnlyWalletFeature.cs +++ b/src/Stratis.Bitcoin.Features.WatchOnlyWallet/WatchOnlyWalletFeature.cs @@ -3,7 +3,6 @@ using Stratis.Bitcoin.Builder; using Stratis.Bitcoin.Builder.Feature; using Stratis.Bitcoin.Features.Notifications; -using Stratis.Bitcoin.Features.WatchOnlyWallet.Controllers; namespace Stratis.Bitcoin.Features.WatchOnlyWallet { diff --git a/src/Stratis.Bitcoin.IntegrationTests.Common/EnvironmentMockUpHelpers/NodeBuilder.cs b/src/Stratis.Bitcoin.IntegrationTests.Common/EnvironmentMockUpHelpers/NodeBuilder.cs index 9761bbc3f0..8dc2c0d338 100644 --- a/src/Stratis.Bitcoin.IntegrationTests.Common/EnvironmentMockUpHelpers/NodeBuilder.cs +++ b/src/Stratis.Bitcoin.IntegrationTests.Common/EnvironmentMockUpHelpers/NodeBuilder.cs @@ -199,8 +199,11 @@ public void Dispose() node.Kill(); // Logs are static so clear them after every run. - LogManager.Configuration.LoggingRules.Clear(); - LogManager.ReconfigExistingLoggers(); + if (LogManager.Configuration != null) + { + LogManager.Configuration.LoggingRules.Clear(); + LogManager.ReconfigExistingLoggers(); + } } /// diff --git a/src/Stratis.Bitcoin.IntegrationTests/Program.cs b/src/Stratis.Bitcoin.IntegrationTests/Program.cs index 737a4d6d27..ac21295713 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/Program.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/Program.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Stratis.Bitcoin.IntegrationTests.Miners; using Stratis.Bitcoin.IntegrationTests.Wallet; using Xunit; diff --git a/src/Stratis.Bitcoin.IntegrationTests/RPC/RPCTestsMutable.cs b/src/Stratis.Bitcoin.IntegrationTests/RPC/RPCTestsMutable.cs index 21967c16cc..46ece73aa7 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/RPC/RPCTestsMutable.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/RPC/RPCTestsMutable.cs @@ -8,7 +8,6 @@ using NBitcoin.DataEncoders; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Stratis.Bitcoin.Base; using Stratis.Bitcoin.Features.RPC; using Stratis.Bitcoin.Features.Wallet; using Stratis.Bitcoin.IntegrationTests.Common; diff --git a/src/Stratis.Bitcoin.Networks/Policies/StraxStandardScriptsRegistry.cs b/src/Stratis.Bitcoin.Networks/Policies/StraxStandardScriptsRegistry.cs index 97c842b4f9..379249c724 100644 --- a/src/Stratis.Bitcoin.Networks/Policies/StraxStandardScriptsRegistry.cs +++ b/src/Stratis.Bitcoin.Networks/Policies/StraxStandardScriptsRegistry.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using NBitcoin; namespace Stratis.Bitcoin.Networks.Policies diff --git a/src/Stratis.Bitcoin.Tests.Wallet.Common/WalletTestsHelpers.cs b/src/Stratis.Bitcoin.Tests.Wallet.Common/WalletTestsHelpers.cs index 543879b2b4..56b9b26c7b 100644 --- a/src/Stratis.Bitcoin.Tests.Wallet.Common/WalletTestsHelpers.cs +++ b/src/Stratis.Bitcoin.Tests.Wallet.Common/WalletTestsHelpers.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Linq; using NBitcoin; -using NBitcoin.Policy; -using Newtonsoft.Json; using Stratis.Bitcoin.Features.Wallet; using Stratis.Bitcoin.Features.Wallet.Interfaces; using Stratis.Bitcoin.Tests.Common; diff --git a/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs b/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs index a03457f5d0..b57c488767 100644 --- a/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs +++ b/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using NBitcoin; using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Utilities; diff --git a/src/Stratis.Bitcoin.Tests/Models/TransactionModelsTest.cs b/src/Stratis.Bitcoin.Tests/Models/TransactionModelsTest.cs index d641339f8b..379eacea80 100644 --- a/src/Stratis.Bitcoin.Tests/Models/TransactionModelsTest.cs +++ b/src/Stratis.Bitcoin.Tests/Models/TransactionModelsTest.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Stratis.Bitcoin.Controllers.Models; -using Stratis.Bitcoin.Features.RPC; using Stratis.Bitcoin.Tests.Common; using Xunit; diff --git a/src/Stratis.Bitcoin.Tests/P2P/EnforcePeerVersionCheckBehaviorTests.cs b/src/Stratis.Bitcoin.Tests/P2P/EnforcePeerVersionCheckBehaviorTests.cs index 5bb29afaaa..525410ccb0 100644 --- a/src/Stratis.Bitcoin.Tests/P2P/EnforcePeerVersionCheckBehaviorTests.cs +++ b/src/Stratis.Bitcoin.Tests/P2P/EnforcePeerVersionCheckBehaviorTests.cs @@ -1,23 +1,17 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Moq; using NBitcoin; using NBitcoin.Protocol; -using Stratis.Bitcoin.BlockPulling; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Logging; -using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.P2P.Peer; using Stratis.Bitcoin.P2P.Protocol; using Stratis.Bitcoin.P2P.Protocol.Behaviors; using Stratis.Bitcoin.P2P.Protocol.Payloads; using Stratis.Bitcoin.Tests.Common; using Stratis.Bitcoin.Tests.Common.Logging; -using Stratis.Bitcoin.Utilities; -using Stratis.Sidechains.Networks; using Xunit; namespace Stratis.Bitcoin.Tests.BlockPulling diff --git a/src/Stratis.Bitcoin.Tests/Utilities/DBreezeTest.cs b/src/Stratis.Bitcoin.Tests/Utilities/DBreezeTest.cs index 8b6c65aada..4b8aef2333 100644 --- a/src/Stratis.Bitcoin.Tests/Utilities/DBreezeTest.cs +++ b/src/Stratis.Bitcoin.Tests/Utilities/DBreezeTest.cs @@ -6,7 +6,6 @@ using DBreeze.DataTypes; using FluentAssertions; using NBitcoin; -using NBitcoin.BitcoinCore; using Stratis.Bitcoin.Tests.Common; using Stratis.Bitcoin.Utilities; using Xunit; diff --git a/src/Stratis.Bitcoin/Configuration/DataFolder.cs b/src/Stratis.Bitcoin/Configuration/DataFolder.cs index 72a8822981..eb931470fa 100644 --- a/src/Stratis.Bitcoin/Configuration/DataFolder.cs +++ b/src/Stratis.Bitcoin/Configuration/DataFolder.cs @@ -34,6 +34,8 @@ public DataFolder(string rootPath, DbType dbType = DbType.Leveldb) this.AddressManagerFilePath = rootPath; this.ChainPath = Path.Combine(databasePath, "chain"); this.KeyValueRepositoryPath = Path.Combine(databasePath, "common"); + this.InteropRepositoryPath = Path.Combine(rootPath, "interop"); + this.ConversionRepositoryPath = Path.Combine(rootPath, "conversion"); this.BlockPath = Path.Combine(databasePath, "blocks"); this.PollsPath = Path.Combine(rootPath, "polls"); this.IndexPath = Path.Combine(rootPath, "index"); @@ -65,6 +67,10 @@ public DataFolder(string rootPath, DbType dbType = DbType.Leveldb) /// Path to the folder with separated key-value items managed by . public string KeyValueRepositoryPath { get; internal set; } + public string InteropRepositoryPath { get; internal set; } + + public string ConversionRepositoryPath { get; internal set; } + /// Path to the folder with block repository database files. /// public string BlockPath { get; internal set; } diff --git a/src/Stratis.Bitcoin/Controllers/Converters/BtcDecimalJsonConverter.cs b/src/Stratis.Bitcoin/Controllers/Converters/BtcDecimalJsonConverter.cs index b965efe1d9..76e6e7e524 100644 --- a/src/Stratis.Bitcoin/Controllers/Converters/BtcDecimalJsonConverter.cs +++ b/src/Stratis.Bitcoin/Controllers/Converters/BtcDecimalJsonConverter.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using System.Linq; using Newtonsoft.Json; namespace Stratis.Bitcoin.Controllers.Converters diff --git a/src/Stratis.Bitcoin/Controllers/Models/BannedPeerModel.cs b/src/Stratis.Bitcoin/Controllers/Models/BannedPeerModel.cs index 129a559c98..aac36283d4 100644 --- a/src/Stratis.Bitcoin/Controllers/Models/BannedPeerModel.cs +++ b/src/Stratis.Bitcoin/Controllers/Models/BannedPeerModel.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Net; -using Newtonsoft.Json; -using Stratis.Bitcoin.Controllers.Converters; namespace Stratis.Bitcoin.Controllers.Models { diff --git a/src/Stratis.Bitcoin/P2P/PeerConnectorAddNode.cs b/src/Stratis.Bitcoin/P2P/PeerConnectorAddNode.cs index f8a9e72c97..cca3301a19 100644 --- a/src/Stratis.Bitcoin/P2P/PeerConnectorAddNode.cs +++ b/src/Stratis.Bitcoin/P2P/PeerConnectorAddNode.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Threading.Tasks; using Microsoft.Extensions.Logging; diff --git a/src/Stratis.Bitcoin/Properties/AssemblyInfo.cs b/src/Stratis.Bitcoin/Properties/AssemblyInfo.cs index 8a724750b9..eec45629a3 100644 --- a/src/Stratis.Bitcoin/Properties/AssemblyInfo.cs +++ b/src/Stratis.Bitcoin/Properties/AssemblyInfo.cs @@ -32,6 +32,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.8.0")] -[assembly: AssemblyFileVersion("1.0.8.0")] +[assembly: AssemblyVersion("1.0.8.6")] +[assembly: AssemblyFileVersion("1.0.8.6")] [assembly: InternalsVisibleTo("Stratis.Bitcoin.Tests")] \ No newline at end of file diff --git a/src/Stratis.Bitcoin/Stratis.Bitcoin.csproj b/src/Stratis.Bitcoin/Stratis.Bitcoin.csproj index 14322c5034..7b8c5de7cb 100644 --- a/src/Stratis.Bitcoin/Stratis.Bitcoin.csproj +++ b/src/Stratis.Bitcoin/Stratis.Bitcoin.csproj @@ -14,7 +14,7 @@ false false false - 1.0.8.0 + 1.0.8.6 False ..\Stratis.ruleset Stratis Group Ltd. diff --git a/src/Stratis.Bitcoin/Utilities/PrefixLogger.cs b/src/Stratis.Bitcoin/Utilities/PrefixLogger.cs index 4c930d00f4..22623a4363 100644 --- a/src/Stratis.Bitcoin/Utilities/PrefixLogger.cs +++ b/src/Stratis.Bitcoin/Utilities/PrefixLogger.cs @@ -1,6 +1,5 @@ using System; using Microsoft.Extensions.Logging; -using Stratis.Bitcoin.Configuration.Logging; using TracerAttributes; namespace Stratis.Bitcoin.Utilities diff --git a/src/Stratis.CirrusD/Properties/launchSettings.json b/src/Stratis.CirrusD/Properties/launchSettings.json index 397506d9f8..055d83c539 100644 --- a/src/Stratis.CirrusD/Properties/launchSettings.json +++ b/src/Stratis.CirrusD/Properties/launchSettings.json @@ -1,7 +1,15 @@ -{ +{ "profiles": { "Stratis.CirrusD": { "commandName": "Project" + }, + "Stratis.CirrusD Test": { + "commandName": "Project", + "commandLineArgs": "-testnet -addressindex=0" + }, + "Stratis.CirrusD RegTest": { + "commandName": "Project", + "commandLineArgs": "-regtest -addressindex=0" } } } \ No newline at end of file diff --git a/src/Stratis.CirrusMinerD/Properties/launchSettings.json b/src/Stratis.CirrusMinerD/Properties/launchSettings.json new file mode 100644 index 0000000000..2ee2c67913 --- /dev/null +++ b/src/Stratis.CirrusMinerD/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "Stratis.CirrusMinerD": { + "commandName": "Project", + "commandLineArgs": "-txindex=1" + }, + "Stratis.CirrusMinerD TestNet": { + "commandName": "Project", + "commandLineArgs": "-testnet -txindex=1" + }, + "Stratis.CirrusMinerD RegTest": { + "commandName": "Project", + "commandLineArgs": "-regtest -txindex=1" + } + } +} \ No newline at end of file diff --git a/src/Stratis.CirrusPegD/Program.cs b/src/Stratis.CirrusPegD/Program.cs index db4c686284..cedd47b2b0 100644 --- a/src/Stratis.CirrusPegD/Program.cs +++ b/src/Stratis.CirrusPegD/Program.cs @@ -10,6 +10,7 @@ using Stratis.Bitcoin.Features.Api; using Stratis.Bitcoin.Features.BlockStore; using Stratis.Bitcoin.Features.Consensus; +using Stratis.Bitcoin.Features.Interop; using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Bitcoin.Features.Miner; using Stratis.Bitcoin.Features.Notifications; @@ -128,6 +129,7 @@ private static IFullNode GetSidechainFullNode(string[] args) options.UseReflectionExecutor(); options.UsePoAWhitelistedContracts(); }) + .AddInteroperability() .UseSmartContractWallet() .AddSQLiteWalletRepository() .Build(); diff --git a/src/Stratis.CirrusPegD/Stratis.CirrusPegD.csproj b/src/Stratis.CirrusPegD/Stratis.CirrusPegD.csproj index 0b80e0e8c4..c22f630cf0 100644 --- a/src/Stratis.CirrusPegD/Stratis.CirrusPegD.csproj +++ b/src/Stratis.CirrusPegD/Stratis.CirrusPegD.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Stratis.Features.FederatedPeg.IntegrationTests/Stratis.Features.FederatedPeg.IntegrationTests.csproj b/src/Stratis.Features.FederatedPeg.IntegrationTests/Stratis.Features.FederatedPeg.IntegrationTests.csproj index 90c1c52a6e..71bbf8cf78 100644 --- a/src/Stratis.Features.FederatedPeg.IntegrationTests/Stratis.Features.FederatedPeg.IntegrationTests.csproj +++ b/src/Stratis.Features.FederatedPeg.IntegrationTests/Stratis.Features.FederatedPeg.IntegrationTests.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/FederatedPegTestHelper.cs b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/FederatedPegTestHelper.cs index b2c86dad94..474832f006 100644 --- a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/FederatedPegTestHelper.cs +++ b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/FederatedPegTestHelper.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using NBitcoin; namespace Stratis.Features.FederatedPeg.IntegrationTests.Utils diff --git a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainFederationNodeRunner.cs b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainFederationNodeRunner.cs index 93708ada97..a597dd2e3a 100644 --- a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainFederationNodeRunner.cs +++ b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainFederationNodeRunner.cs @@ -4,6 +4,7 @@ using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Features.Api; using Stratis.Bitcoin.Features.BlockStore; +using Stratis.Bitcoin.Features.Interop; using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Bitcoin.Features.Notifications; using Stratis.Bitcoin.Features.PoA.IntegrationTests.Common; @@ -63,6 +64,7 @@ public override void BuildNode() options.UseReflectionExecutor(); options.UsePoAWhitelistedContracts(); }) + .AddInteroperability() .UseSmartContractWallet() .AddSQLiteWalletRepository() .MockIBD() diff --git a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainTestContext.cs b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainTestContext.cs index 45d4d9930d..efea522d7a 100644 --- a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainTestContext.cs +++ b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainTestContext.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; using Flurl; diff --git a/src/Stratis.Features.FederatedPeg.Tests/CrossChainTestBase.cs b/src/Stratis.Features.FederatedPeg.Tests/CrossChainTestBase.cs index a260dbbfb6..1499229f4d 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/CrossChainTestBase.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/CrossChainTestBase.cs @@ -20,6 +20,7 @@ using Stratis.Bitcoin.Tests.Common; using Stratis.Bitcoin.Utilities; using Stratis.Features.Collateral.CounterChain; +using Stratis.Features.FederatedPeg.Conversion; using Stratis.Features.FederatedPeg.Interfaces; using Stratis.Features.FederatedPeg.TargetChain; using Stratis.Features.FederatedPeg.Wallet; @@ -46,6 +47,7 @@ public class CrossChainTestBase protected IFullNode fullNode; protected IFederationWalletManager federationWalletManager; protected IFederatedPegSettings federatedPegSettings; + protected IConversionRequestRepository repository; protected IFederationWalletSyncManager federationWalletSyncManager; protected IFederationWalletTransactionHandler FederationWalletTransactionHandler; protected IWithdrawalTransactionBuilder withdrawalTransactionBuilder; @@ -106,6 +108,7 @@ public CrossChainTestBase(Network network = null, Network counterChainNetwork = this.ibdState = Substitute.For(); this.wallet = null; this.federatedPegSettings = Substitute.For(); + this.repository = Substitute.For(); this.ChainIndexer = new ChainIndexer(this.network); // Generate the keys used by the federation members for our tests. @@ -162,7 +165,7 @@ protected void SetExtendedKey(int keyNum) this.federatedPegSettings.MultiSigAddress.Returns(this.redeemScript.Hash.GetAddress(this.network)); this.federatedPegSettings.PublicKey.Returns(this.extendedKey.PrivateKey.PubKey.ToHex()); this.federatedPegSettings.MaximumPartialTransactionThreshold.Returns(CrossChainTransferStore.MaximumPartialTransactions); - this.withdrawalExtractor = new WithdrawalExtractor(this.federatedPegSettings, this.opReturnDataReader, this.network); + this.withdrawalExtractor = new WithdrawalExtractor(this.federatedPegSettings, this.repository, this.opReturnDataReader, this.network); } protected (Transaction, ChainedHeader) AddFundingTransaction(Money[] amounts) diff --git a/src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs b/src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs index 95dd5fe912..6eaf36adfb 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs @@ -15,6 +15,8 @@ namespace Stratis.Features.FederatedPeg.Tests { public class DepositExtractorTests { + public const string TargetEthereumAddress = "0x4F26FfBe5F04ED43630fdC30A87638d53D0b0876"; + private readonly IFederatedPegSettings federationSettings; private readonly IOpReturnDataReader opReturnDataReader; private readonly DepositExtractor depositExtractor; @@ -240,7 +242,7 @@ public void ExtractLargeDeposits_ReturnDeposits_AboveNormalThreshold() // Set amount to be exactly the normal threshold amount. CreateDepositTransaction(targetAddress, block, this.federationSettings.NormalDepositThresholdAmount, opReturnBytes); - // Set amount to be equal to thee normal threshold amount. + // Set amount to be equal to the normal threshold amount. CreateDepositTransaction(targetAddress, block, this.federationSettings.NormalDepositThresholdAmount, opReturnBytes); // Set amount to be greater than the normal threshold amount. @@ -257,6 +259,60 @@ public void ExtractLargeDeposits_ReturnDeposits_AboveNormalThreshold() } } + // Conversion deposits + [Fact] + public void ExtractLargeConversionDeposits_ReturnDeposits_AboveNormalThreshold() + { + Block block = this.network.Consensus.ConsensusFactory.CreateBlock(); + + // Create the target address. + BitcoinPubKeyAddress targetAddress = this.addressHelper.GetNewTargetChainPubKeyAddress(); + byte[] opReturnBytes = Encoding.UTF8.GetBytes(targetAddress.ToString()); + + // Set amount to be less than deposit minimum + CreateDepositTransaction(targetAddress, block, FederatedPegSettings.CrossChainTransferMinimum - 1, opReturnBytes); + + // Set amount to be less than the small threshold amount. + CreateDepositTransaction(targetAddress, block, this.federationSettings.SmallDepositThresholdAmount - 1, opReturnBytes); + + // Set amount to be exactly the normal threshold amount. + CreateDepositTransaction(targetAddress, block, this.federationSettings.NormalDepositThresholdAmount, opReturnBytes); + + // Set amount to be equal to the normal threshold amount. + CreateConversionTransaction(TargetEthereumAddress, block, this.federationSettings.NormalDepositThresholdAmount, opReturnBytes); + + // Set amount to be greater than the conversion deposit minimum amount. + CreateConversionTransaction(TargetEthereumAddress, block, Money.Coins(DepositExtractor.ConversionTransactionMinimum + 1), opReturnBytes); + + int blockHeight = 12345; + IReadOnlyList extractedDeposits = this.depositExtractor.ExtractDepositsFromBlock(block, blockHeight, new[] { DepositRetrievalType.ConversionLarge }); + + // Should only be 1, with the value just over the withdrawal fee. + extractedDeposits.Count.Should().Be(1); + foreach (IDeposit extractedDeposit in extractedDeposits) + { + Assert.True(extractedDeposit.Amount > this.federationSettings.NormalDepositThresholdAmount); + Assert.Equal(TargetEthereumAddress, extractedDeposit.TargetAddress); + } + } + + private Transaction CreateConversionTransaction(string targetEthereumAddress, Block block, Money depositAmount, byte[] opReturnBytes) + { + // Create the conversion transaction. + Transaction conversionTransaction = this.transactionBuilder.BuildOpReturnTransaction(this.addressHelper.SourceChainMultisigAddress, opReturnBytes, depositAmount); + + // Add the conversion transaction to the block. + block.AddTransaction(conversionTransaction); + + this.opReturnDataReader.TryGetTargetEthereumAddress(conversionTransaction, out string _).Returns(callInfo => + { + callInfo[1] = targetEthereumAddress; + return true; + }); + + return conversionTransaction; + } + private Transaction CreateDepositTransaction(BitcoinPubKeyAddress targetAddress, Block block, Money depositAmount, byte[] opReturnBytes) { // Create the deposit transaction. diff --git a/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksSyncManagerTests.cs b/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksSyncManagerTests.cs index c91a1a0795..d531efb42e 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksSyncManagerTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksSyncManagerTests.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; using NSubstitute; using Stratis.Bitcoin.AsyncWork; using Stratis.Bitcoin.Utilities; @@ -24,9 +23,8 @@ public MaturedBlocksSyncManagerTests() this.asyncProvider = Substitute.For(); this.crossChainTransferStore = Substitute.For(); this.federationGatewayClient = Substitute.For(); - ILoggerFactory loggerFactory = Substitute.For(); - this.syncManager = new TestOnlyMaturedBlocksSyncManager(this.asyncProvider, this.crossChainTransferStore, this.federationGatewayClient, loggerFactory, new NodeLifetime()); + this.syncManager = new TestOnlyMaturedBlocksSyncManager(this.asyncProvider, this.crossChainTransferStore, this.federationGatewayClient, new NodeLifetime()); } [Fact] @@ -62,8 +60,8 @@ public async Task BlocksAreRequestedIfThereIsSomethingToRequestAsync() private class TestOnlyMaturedBlocksSyncManager : MaturedBlocksSyncManager { - public TestOnlyMaturedBlocksSyncManager(IAsyncProvider asyncProvider, ICrossChainTransferStore crossChainTransferStore, IFederationGatewayClient federationGatewayClient, ILoggerFactory loggerFactory, INodeLifetime nodeLifetime) - : base(asyncProvider, crossChainTransferStore, federationGatewayClient, nodeLifetime) + public TestOnlyMaturedBlocksSyncManager(IAsyncProvider asyncProvider, ICrossChainTransferStore crossChainTransferStore, IFederationGatewayClient federationGatewayClient, INodeLifetime nodeLifetime) + : base(asyncProvider, crossChainTransferStore, federationGatewayClient, nodeLifetime, null, null) { } diff --git a/src/Stratis.Features.FederatedPeg.Tests/RestClientsTests/RestApiClientBaseTests.cs b/src/Stratis.Features.FederatedPeg.Tests/RestClientsTests/RestApiClientBaseTests.cs index 7f90acc51c..e153b6331b 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/RestClientsTests/RestApiClientBaseTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/RestClientsTests/RestApiClientBaseTests.cs @@ -24,7 +24,7 @@ public RestApiClientBaseTests() this.loggerFactory = Substitute.For(); this.logger = Substitute.For(); this.loggerFactory.CreateLogger(null).ReturnsForAnyArgs(this.logger); - this.httpClientFactory = new HttpClientFactory(); + this.httpClientFactory = new Bitcoin.Controllers.HttpClientFactory(); } [Fact] diff --git a/src/Stratis.Features.FederatedPeg.Tests/WithdrawalExtractorTests.cs b/src/Stratis.Features.FederatedPeg.Tests/WithdrawalExtractorTests.cs index 02d7f04046..4e109ed2c5 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/WithdrawalExtractorTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/WithdrawalExtractorTests.cs @@ -4,6 +4,7 @@ using NBitcoin; using NSubstitute; using Stratis.Bitcoin.Networks; +using Stratis.Features.FederatedPeg.Conversion; using Stratis.Features.FederatedPeg.Interfaces; using Stratis.Features.FederatedPeg.TargetChain; using Stratis.Features.FederatedPeg.Tests.Utils; @@ -15,6 +16,8 @@ public class WithdrawalExtractorTests { private readonly IFederatedPegSettings settings; + private readonly IConversionRequestRepository repository; + private readonly IOpReturnDataReader opReturnDataReader; private readonly ILoggerFactory loggerFactory; @@ -36,6 +39,7 @@ public WithdrawalExtractorTests() this.loggerFactory = Substitute.For(); this.settings = Substitute.For(); + this.repository = Substitute.For(); this.opReturnDataReader = Substitute.For(); this.addressHelper = new MultisigAddressHelper(this.network, this.counterChainNetwork); @@ -49,7 +53,7 @@ public WithdrawalExtractorTests() this.transactionBuilder = new TestMultisigTransactionBuilder(this.addressHelper); this.withdrawalExtractor = new WithdrawalExtractor( - this.settings, this.opReturnDataReader, this.network); + this.settings, this.repository, this.opReturnDataReader, this.network); } // TODO: Will depend on decision made on backlog issue https://github.com/stratisproject/FederatedSidechains/issues/124 diff --git a/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequest.cs b/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequest.cs new file mode 100644 index 0000000000..5bf53267e9 --- /dev/null +++ b/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequest.cs @@ -0,0 +1,98 @@ +using NBitcoin; + +namespace Stratis.Features.FederatedPeg.Conversion +{ + public enum ConversionRequestType + { + Mint, + Burn + } + + public enum ConversionRequestStatus + { + Unprocessed, + Submitted, // Unused, need to keep it due to previous state ordering in database + Processed, + + // States particular to Mint transactions + OriginatorNotSubmitted, + OriginatorSubmitted, + VoteFinalised, + NotOriginator + } + + /// Request to mint or burn wSTRAX on ETH chain. + /// + /// When wSTRAX coins are minted and sent to on ETH chain same amount of STRAX coins should be received by the multisig address. + /// When wSTRAX coins are burned on ETH chain same amount of STRAX coins should be sent to . + /// + public class ConversionRequest : IBitcoinSerializable + { + /// + /// The unique identifier for this particular conversion request. + /// It gets selected by the request creator. + /// The request ID is typically the initiating transaction ID. + /// + public string RequestId { get { return this.requestId; } set { this.requestId = value; } } + + /// + /// The type of the conversion request, mint or burn. + /// + /// + public int RequestType { get { return this.requestType; } set { this.requestType = value; } } + + /// + /// The status of the request, from unprocessed to processed. + /// + public int RequestStatus { get { return this.requestStatus; } set { this.requestStatus = value; } } + + /// + /// For a mint request this is needed to coordinate which multisig member is considered the transaction originator on the wallet contract. + /// A burn request needs to be scheduled for a future block on the main chain so that the conversion can be cleanly inserted into the sequence + /// of transfers. + /// + public int BlockHeight { get { return this.blockHeight; } set { this.blockHeight = value; } } + + /// + /// Either the Ethereum address to send the minted funds to, or the STRAX address to send unwrapped wSTRAX funds to. + /// + public string DestinationAddress { get { return this.destinationAddress; } set { this.destinationAddress = value; } } + + /// + /// Amount of the conversion, this is always denominated in satoshi. This needs to be converted to wei for submitting mint transactions. + /// Burn transactions are already denominated in wei on the Ethereum chain and thus need to be converted back into satoshi when the + /// conversion request is created. Conversions are currently processed 1 ether : 1 STRAX. + /// + public ulong Amount { get { return this.amount; } set { this.amount = value; } } + + /// + /// Indicates whether or not this request has been processed by the interop poller. + /// + public bool Processed { get { return this.processed; } set { this.processed = value; } } + + private string requestId; + + private int requestType; + + private int requestStatus; + + private int blockHeight; + + private string destinationAddress; + + private ulong amount; + + private bool processed; + + public void ReadWrite(BitcoinStream s) + { + s.ReadWrite(ref this.requestId); + s.ReadWrite(ref this.requestType); + s.ReadWrite(ref this.requestStatus); + s.ReadWrite(ref this.blockHeight); + s.ReadWrite(ref this.destinationAddress); + s.ReadWrite(ref this.amount); + s.ReadWrite(ref this.processed); + } + } +} diff --git a/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestKeyValueStore.cs b/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestKeyValueStore.cs new file mode 100644 index 0000000000..68f2639685 --- /dev/null +++ b/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestKeyValueStore.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using LevelDB; +using Stratis.Bitcoin.Configuration; +using Stratis.Bitcoin.Persistence; +using Stratis.Bitcoin.Utilities; +using Stratis.Bitcoin.Utilities.JsonConverters; + +namespace Stratis.Features.FederatedPeg.Conversion +{ + public interface IConversionRequestKeyValueStore : IKeyValueRepository + { + List GetAll(int type, bool onlyUnprocessed); + } + + public class ConversionRequestKeyValueStore : IConversionRequestKeyValueStore + { + /// Access to database. + private readonly DB leveldb; + + private readonly DBreezeSerializer dBreezeSerializer; + + public ConversionRequestKeyValueStore(DataFolder dataFolder, DBreezeSerializer dBreezeSerializer) : this(dataFolder.InteropRepositoryPath, dBreezeSerializer) + { + } + + public ConversionRequestKeyValueStore(string folder, DBreezeSerializer dBreezeSerializer) + { + Directory.CreateDirectory(folder); + this.dBreezeSerializer = dBreezeSerializer; + + // Open a connection to a new DB and create if not found. + var options = new Options { CreateIfMissing = true }; + this.leveldb = new DB(options, folder); + } + + public List GetAll(int type, bool onlyUnprocessed) + { + var values = new List(); + IEnumerator> enumerator = this.leveldb.GetEnumerator(); + + while (enumerator.MoveNext()) + { + (byte[] key, byte[] value) = enumerator.Current; + + if (value == null) + continue; + + ConversionRequest deserialized = this.dBreezeSerializer.Deserialize(value); + + if (deserialized.RequestType != type) + continue; + + if (deserialized.Processed && onlyUnprocessed) + continue; + + values.Add(deserialized); + } + + return values; + } + + public void SaveBytes(string key, byte[] bytes) + { + byte[] keyBytes = Encoding.ASCII.GetBytes(key); + + this.leveldb.Put(keyBytes, bytes); + } + + public void SaveValue(string key, T value) + { + this.SaveBytes(key, this.dBreezeSerializer.Serialize(value)); + } + + public void SaveValueJson(string key, T value) + { + string json = Serializer.ToString(value); + byte[] jsonBytes = Encoding.ASCII.GetBytes(json); + + this.SaveBytes(key, jsonBytes); + } + + public byte[] LoadBytes(string key) + { + byte[] keyBytes = Encoding.ASCII.GetBytes(key); + + byte[] row = this.leveldb.Get(keyBytes); + + if (row == null) + return null; + + return row; + } + + public T LoadValue(string key) + { + byte[] bytes = this.LoadBytes(key); + + if (bytes == null) + return default(T); + + T value = this.dBreezeSerializer.Deserialize(bytes); + return value; + } + + public T LoadValueJson(string key) + { + byte[] bytes = this.LoadBytes(key); + + if (bytes == null) + return default(T); + + string json = Encoding.ASCII.GetString(bytes); + + T value = Serializer.ToObject(json); + + return value; + } + + public void Dispose() + { + this.leveldb.Dispose(); + } + } +} diff --git a/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestRepository.cs b/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestRepository.cs new file mode 100644 index 0000000000..00c6b829a1 --- /dev/null +++ b/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestRepository.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using NLog; + +namespace Stratis.Features.FederatedPeg.Conversion +{ + /// Provides saving and retrieving functionality for objects of type. + public interface IConversionRequestRepository + { + /// Saves to the repository. + void Save(ConversionRequest request); + + /// Retrieves with specified id. + ConversionRequest Get(string requestId); + + /// Retrieves all mint requests. + List GetAllMint(bool onlyUnprocessed); + + /// Retrieves all burn requests. + List GetAllBurn(bool onlyUnprocessed); + } + + public class ConversionRequestRepository : IConversionRequestRepository + { + private IConversionRequestKeyValueStore KeyValueStore { get; } + + private readonly NLog.ILogger logger; + + public ConversionRequestRepository(IConversionRequestKeyValueStore conversionRequestKeyValueStore) + { + this.KeyValueStore = conversionRequestKeyValueStore; + + this.logger = LogManager.GetCurrentClassLogger(); + } + + /// + public void Save(ConversionRequest request) + { + this.logger.Debug($"Saving conversion request {request.RequestId} to store."); + + this.KeyValueStore.SaveValue(request.RequestId, request); + } + + /// + public ConversionRequest Get(string requestId) + { + this.logger.Debug($"Retrieving conversion request {requestId} from store."); + + return this.KeyValueStore.LoadValue(requestId); + } + + /// + public List GetAllMint(bool onlyUnprocessed) + { + this.logger.Debug($"Retrieving all mint requests from store, {nameof(onlyUnprocessed)}={onlyUnprocessed}"); + + return this.KeyValueStore.GetAll((int)ConversionRequestType.Mint, onlyUnprocessed); + } + + /// + public List GetAllBurn(bool onlyUnprocessed) + { + this.logger.Debug($"Retrieving all burn requests from store, {nameof(onlyUnprocessed)}={onlyUnprocessed}"); + + return this.KeyValueStore.GetAll((int)ConversionRequestType.Burn, onlyUnprocessed); + } + } +} diff --git a/src/Stratis.Features.FederatedPeg/Events/WalletNeedsConsolidation.cs b/src/Stratis.Features.FederatedPeg/Events/WalletNeedsConsolidation.cs index 759de7b5ce..23d104a501 100644 --- a/src/Stratis.Features.FederatedPeg/Events/WalletNeedsConsolidation.cs +++ b/src/Stratis.Features.FederatedPeg/Events/WalletNeedsConsolidation.cs @@ -1,5 +1,4 @@ -using System; -using NBitcoin; +using NBitcoin; using Stratis.Bitcoin.EventBus; namespace Stratis.Features.FederatedPeg.Events diff --git a/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs b/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs index a523ca2076..232f15b413 100644 --- a/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs +++ b/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs @@ -22,6 +22,7 @@ using Stratis.Features.Collateral; using Stratis.Features.Collateral.CounterChain; using Stratis.Features.FederatedPeg.Controllers; +using Stratis.Features.FederatedPeg.Conversion; using Stratis.Features.FederatedPeg.Distribution; using Stratis.Features.FederatedPeg.InputConsolidation; using Stratis.Features.FederatedPeg.Interfaces; @@ -272,6 +273,10 @@ public static IFullNodeBuilder AddFederatedPeg(this IFullNodeBuilder fullNodeBui services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Stratis.Features.FederatedPeg/FederatedPegSettings.cs b/src/Stratis.Features.FederatedPeg/FederatedPegSettings.cs index 0985527c67..07dbe615a7 100644 --- a/src/Stratis.Features.FederatedPeg/FederatedPegSettings.cs +++ b/src/Stratis.Features.FederatedPeg/FederatedPegSettings.cs @@ -137,6 +137,7 @@ public FederatedPegSettings(NodeSettings nodeSettings, CounterChainNetworkWrappe this.MinimumConfirmationsNormalDeposits = configReader.GetOrDefault(MinimumConfirmationsNormalDepositsParam, 80); this.MinimumConfirmationsLargeDeposits = (int)nodeSettings.Network.Consensus.MaxReorgLength + 1; this.MinimumConfirmationsDistributionDeposits = (int)nodeSettings.Network.Consensus.MaxReorgLength + 1; + this.MinimumConfirmationsConversionDeposits = (int)nodeSettings.Network.Consensus.MaxReorgLength + 1; this.MaximumPartialTransactionThreshold = configReader.GetOrDefault(MaximumPartialTransactionsParam, CrossChainTransferStore.MaximumPartialTransactions); this.WalletSyncFromHeight = configReader.GetOrDefault(WalletSyncFromHeightParam, 0); @@ -159,6 +160,8 @@ public FederatedPegSettings(NodeSettings nodeSettings, CounterChainNetworkWrappe public int MinimumConfirmationsDistributionDeposits { get; } + public int MinimumConfirmationsConversionDeposits { get; } + /// public Money SmallDepositThresholdAmount { get; } diff --git a/src/Stratis.Features.FederatedPeg/InputConsolidation/ConsolidationTransaction.cs b/src/Stratis.Features.FederatedPeg/InputConsolidation/ConsolidationTransaction.cs index 8f8703ff52..93e3dca68d 100644 --- a/src/Stratis.Features.FederatedPeg/InputConsolidation/ConsolidationTransaction.cs +++ b/src/Stratis.Features.FederatedPeg/InputConsolidation/ConsolidationTransaction.cs @@ -1,5 +1,4 @@ using NBitcoin; -using Stratis.Features.FederatedPeg.Interfaces; namespace Stratis.Features.FederatedPeg.InputConsolidation { diff --git a/src/Stratis.Features.FederatedPeg/Interfaces/IDepositExtractor.cs b/src/Stratis.Features.FederatedPeg/Interfaces/IDepositExtractor.cs index 98619d4cfb..3b05d85f05 100644 --- a/src/Stratis.Features.FederatedPeg/Interfaces/IDepositExtractor.cs +++ b/src/Stratis.Features.FederatedPeg/Interfaces/IDepositExtractor.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; using NBitcoin; -using Stratis.Bitcoin.Primitives; -using Stratis.Features.FederatedPeg.Models; using Stratis.Features.FederatedPeg.SourceChain; namespace Stratis.Features.FederatedPeg.Interfaces diff --git a/src/Stratis.Features.FederatedPeg/Interfaces/IFederatedPegSettings.cs b/src/Stratis.Features.FederatedPeg/Interfaces/IFederatedPegSettings.cs index 31dc7b48fa..5fea7d4ab3 100644 --- a/src/Stratis.Features.FederatedPeg/Interfaces/IFederatedPegSettings.cs +++ b/src/Stratis.Features.FederatedPeg/Interfaces/IFederatedPegSettings.cs @@ -86,6 +86,8 @@ public interface IFederatedPegSettings int MinimumConfirmationsDistributionDeposits { get; } + int MinimumConfirmationsConversionDeposits { get; } + /// /// Deposits under or equal to this value will be processed after blocks on the counter-chain. /// diff --git a/src/Stratis.Features.FederatedPeg/OpReturnDataReader.cs b/src/Stratis.Features.FederatedPeg/OpReturnDataReader.cs index fea13af282..38fe6ba7e2 100644 --- a/src/Stratis.Features.FederatedPeg/OpReturnDataReader.cs +++ b/src/Stratis.Features.FederatedPeg/OpReturnDataReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using NBitcoin; using NLog; using Stratis.Features.Collateral.CounterChain; @@ -26,6 +27,8 @@ public interface IOpReturnDataReader /// true if address was extracted; false otherwise. bool TryGetTargetAddress(Transaction transaction, out string address); + bool TryGetTargetEthereumAddress(Transaction transaction, out string address); + /// /// Tries to find a single OP_RETURN output that can be interpreted as a transaction id. /// @@ -66,6 +69,24 @@ public bool TryGetTargetAddress(Transaction transaction, out string address) return true; } + public bool TryGetTargetEthereumAddress(Transaction transaction, out string address) + { + var opReturnAddresses = SelectBytesContentFromOpReturn(transaction) + .Select(this.TryConvertValidOpReturnDataToEthereumAddress) + .Where(s => s != null) + .Distinct(StringComparer.InvariantCultureIgnoreCase).ToList(); + + // A standard OP_RETURN is not long enough to fit more than 1 Ethereum address, but a non-standard transaction could have multiple. + if (opReturnAddresses.Count != 1) + { + address = null; + return false; + } + + address = opReturnAddresses[0]; + return true; + } + /// public bool TryGetTransactionId(Transaction transaction, out string txId) { @@ -113,6 +134,22 @@ private string TryConvertValidOpReturnDataToAddress(byte[] data) } } + private string TryConvertValidOpReturnDataToEthereumAddress(byte[] data) + { + // After removing the RETURN operator, convert the remaining bytes to our candidate address. + string destination = Encoding.UTF8.GetString(data); + + // Attempt to parse the string. An Ethereum address is 42 characters: + // 0x - initial prefix + // <20 bytes> - rightmost 20 bytes of the Keccak hash of a public key, encoded as hex + Match match = Regex.Match(destination, @"^0x([A-Fa-f0-9]{40})$", RegexOptions.IgnoreCase); + + if (!match.Success) + return null; + + return destination; + } + private string TryConvertValidOpReturnDataToHash(byte[] data) { // Attempt to parse the hash. Validates the uint256 string. @@ -133,4 +170,4 @@ private static byte[] RemoveOpReturnOperator(byte[] rawBytes) return rawBytes.Skip(2).ToArray(); } } -} \ No newline at end of file +} diff --git a/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs b/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs index 449da75fd3..d702e478bd 100644 --- a/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs +++ b/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs @@ -7,6 +7,9 @@ namespace Stratis.Features.FederatedPeg.SourceChain { public sealed class DepositExtractor : IDepositExtractor { + // Conversion transaction deposits smaller than this threshold will be ignored. Denominated in STRAX. + public const decimal ConversionTransactionMinimum = 90_000; + /// /// This deposit extractor implementation only looks for a very specific deposit format. /// Deposits will have 2 outputs when there is no change. @@ -72,20 +75,45 @@ public IDeposit ExtractDepositFromTransaction(Transaction transaction, int block if (!depositsToMultisig.Any()) return null; + // Check the common case first. + bool conversionTransaction = false; if (!this.opReturnDataReader.TryGetTargetAddress(transaction, out string targetAddress)) - return null; + { + if (!this.opReturnDataReader.TryGetTargetEthereumAddress(transaction, out targetAddress)) + { + return null; + } + + conversionTransaction = true; + } Money amount = depositsToMultisig.Sum(o => o.Value); DepositRetrievalType depositRetrievalType; - if (targetAddress == this.network.CirrusRewardDummyAddress) - depositRetrievalType = DepositRetrievalType.Distribution; - else if (amount > this.federatedPegSettings.NormalDepositThresholdAmount) - depositRetrievalType = DepositRetrievalType.Large; - else if (amount > this.federatedPegSettings.SmallDepositThresholdAmount) - depositRetrievalType = DepositRetrievalType.Normal; + + if (conversionTransaction) + { + if (amount < Money.Coins(ConversionTransactionMinimum)) + return null; + + if (amount > this.federatedPegSettings.NormalDepositThresholdAmount) + depositRetrievalType = DepositRetrievalType.ConversionLarge; + else if (amount > this.federatedPegSettings.SmallDepositThresholdAmount) + depositRetrievalType = DepositRetrievalType.ConversionNormal; + else + depositRetrievalType = DepositRetrievalType.ConversionSmall; + } else - depositRetrievalType = DepositRetrievalType.Small; + { + if (targetAddress == this.network.CirrusRewardDummyAddress) + depositRetrievalType = DepositRetrievalType.Distribution; + else if (amount > this.federatedPegSettings.NormalDepositThresholdAmount) + depositRetrievalType = DepositRetrievalType.Large; + else if (amount > this.federatedPegSettings.SmallDepositThresholdAmount) + depositRetrievalType = DepositRetrievalType.Normal; + else + depositRetrievalType = DepositRetrievalType.Small; + } return new Deposit(transaction.GetHash(), depositRetrievalType, amount, targetAddress, blockHeight, blockHash); } diff --git a/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs b/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs index 5ec6ee85ef..b487c2b07c 100644 --- a/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs +++ b/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs @@ -68,7 +68,12 @@ public MaturedBlocksProvider(IConsensusManager consensusManager, IDepositExtract }; if (this.federatedPegSettings.IsMainChain) + { this.retrievalTypeConfirmations[DepositRetrievalType.Distribution] = this.federatedPegSettings.MinimumConfirmationsDistributionDeposits; + this.retrievalTypeConfirmations[DepositRetrievalType.ConversionSmall] = this.federatedPegSettings.MinimumConfirmationsSmallDeposits; + this.retrievalTypeConfirmations[DepositRetrievalType.ConversionNormal] = this.federatedPegSettings.MinimumConfirmationsNormalDeposits; + this.retrievalTypeConfirmations[DepositRetrievalType.ConversionLarge] = this.federatedPegSettings.MinimumConfirmationsLargeDeposits; + } } /// @@ -196,13 +201,17 @@ private IEnumerable RecallBlockDeposits(int blockHeight, DepositRetrie /// Small deposits are processed after confirmations (blocks). /// Normal deposits are processed after () confirmations (blocks). /// Large deposits are only processed after the height has increased past max re-org () confirmations (blocks). - /// Similarly, reward distribution deposits are only processed after the height has increased past max re-org () confirmations (blocks). + /// Conversion deposits are processed after similar intervals to the above, according to their size. + /// Reward distribution deposits are only processed after the height has increased past max re-org () confirmations (blocks). /// public enum DepositRetrievalType { Small, Normal, Large, - Distribution + Distribution, + ConversionSmall, + ConversionNormal, + ConversionLarge } } diff --git a/src/Stratis.Features.FederatedPeg/TargetChain/CrossChainTransferStore.cs b/src/Stratis.Features.FederatedPeg/TargetChain/CrossChainTransferStore.cs index 0b3de1c787..86d6aabc13 100644 --- a/src/Stratis.Features.FederatedPeg/TargetChain/CrossChainTransferStore.cs +++ b/src/Stratis.Features.FederatedPeg/TargetChain/CrossChainTransferStore.cs @@ -776,7 +776,6 @@ private void Put(List blocks, Dictionary chainedH for (int i = 0; i < uniqueDepositIds.Length; i++) transferLookup[uniqueDepositIds[i]] = uniqueTransfers[i]; - // Only create a transaction if there is important work to do. using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) { diff --git a/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs b/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs index 0c49d9a3d1..c4f168df14 100644 --- a/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs +++ b/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs @@ -7,8 +7,10 @@ using Stratis.Bitcoin.AsyncWork; using Stratis.Bitcoin.Utilities; using Stratis.Features.FederatedPeg.Controllers; +using Stratis.Features.FederatedPeg.Conversion; using Stratis.Features.FederatedPeg.Interfaces; using Stratis.Features.FederatedPeg.Models; +using Stratis.Features.FederatedPeg.SourceChain; using Stratis.Features.FederatedPeg.Wallet; namespace Stratis.Features.FederatedPeg.TargetChain @@ -31,6 +33,8 @@ public class MaturedBlocksSyncManager : IMaturedBlocksSyncManager private readonly IFederationGatewayClient federationGatewayClient; private readonly ILogger logger; private readonly INodeLifetime nodeLifetime; + private readonly IConversionRequestRepository conversionRequestRepository; + private readonly ChainIndexer chainIndexer; private IAsyncLoop requestDepositsTask; @@ -41,12 +45,15 @@ public class MaturedBlocksSyncManager : IMaturedBlocksSyncManager /// Needed to give other node some time to start before bombing it with requests. private const int InitializationDelaySeconds = 10; - public MaturedBlocksSyncManager(IAsyncProvider asyncProvider, ICrossChainTransferStore crossChainTransferStore, IFederationGatewayClient federationGatewayClient, INodeLifetime nodeLifetime) + public MaturedBlocksSyncManager(IAsyncProvider asyncProvider, ICrossChainTransferStore crossChainTransferStore, IFederationGatewayClient federationGatewayClient, + INodeLifetime nodeLifetime, IConversionRequestRepository conversionRequestRepository, ChainIndexer chainIndexer) { this.asyncProvider = asyncProvider; this.crossChainTransferStore = crossChainTransferStore; this.federationGatewayClient = federationGatewayClient; this.nodeLifetime = nodeLifetime; + this.conversionRequestRepository = conversionRequestRepository; + this.chainIndexer = chainIndexer; this.logger = LogManager.GetCurrentClassLogger(); } @@ -107,11 +114,68 @@ private async Task ProcessMatureBlockDepositsAsync(SerializableResult x.Id, Comparer.Create(DeterministicCoinOrdering.CompareUint256)).ToList(); + foreach (IDeposit conversionTransaction in maturedBlockDeposit.Deposits.Where(d => + d.RetrievalType == DepositRetrievalType.ConversionSmall || + d.RetrievalType == DepositRetrievalType.ConversionNormal || + d.RetrievalType == DepositRetrievalType.ConversionLarge)) + { + this.logger.Info("Conversion mint transaction " + conversionTransaction + " received in matured blocks."); + + if (this.conversionRequestRepository.Get(conversionTransaction.Id.ToString()) != null) + { + this.logger.Info("Conversion mint transaction " + conversionTransaction + " already exists, ignoring."); + + continue; + } + + // Get the first block on this chain that has a timestamp after the deposit's block time on the counterchain. + // This is so that we can assign a block height that the deposit 'arrived' on the sidechain. + // TODO: This can probably be made more efficient than looping every time. + ChainedHeader header = this.chainIndexer.Tip; + bool found = false; + + while (true) + { + if (header == this.chainIndexer.Genesis) + { + break; + } + + if (header.Previous.Header.Time <= maturedBlockDeposit.BlockInfo.BlockTime) + { + found = true; + + break; + } + + header = header.Previous; + } + + if (!found) + { + continue; + } + + this.conversionRequestRepository.Save(new ConversionRequest() { + RequestId = conversionTransaction.Id.ToString(), + RequestType = (int)ConversionRequestType.Mint, + Processed = false, + RequestStatus = (int)ConversionRequestStatus.Unprocessed, + // We do NOT convert to wei here yet. That is done when the minting transaction is submitted on the Ethereum network. + Amount = (ulong)conversionTransaction.Amount.Satoshi, + BlockHeight = header.Height, + DestinationAddress = conversionTransaction.TargetAddress + }); + } + + // Order all other transactions in the block deterministically. + maturedBlockDeposit.Deposits = maturedBlockDeposit.Deposits.Where(d => + d.RetrievalType != DepositRetrievalType.ConversionSmall && + d.RetrievalType != DepositRetrievalType.ConversionNormal && + d.RetrievalType != DepositRetrievalType.ConversionLarge).OrderBy(x => x.Id, Comparer.Create(DeterministicCoinOrdering.CompareUint256)).ToList(); foreach (IDeposit deposit in maturedBlockDeposit.Deposits) { @@ -119,7 +183,7 @@ private async Task ProcessMatureBlockDepositsAsync(SerializableResult private const int ExpectedNumberOfOutputsChange = 3; + private readonly IConversionRequestRepository conversionRequestRepository; + private readonly IOpReturnDataReader opReturnDataReader; private readonly Network network; @@ -40,10 +44,12 @@ public class WithdrawalExtractor : IWithdrawalExtractor public WithdrawalExtractor( IFederatedPegSettings federatedPegSettings, + IConversionRequestRepository conversionRequestRepository, IOpReturnDataReader opReturnDataReader, Network network) { this.multisigAddress = federatedPegSettings.MultiSigAddress; + this.conversionRequestRepository = conversionRequestRepository; this.opReturnDataReader = opReturnDataReader; this.network = network; } @@ -53,6 +59,48 @@ public IReadOnlyList ExtractWithdrawalsFromBlock(Block block, int b { var withdrawals = new List(); + // Check if this is the target height for a conversion transaction from wSTRAX back to STRAX. + // These get returned before any other withdrawal transactions in the block to ensure consistent ordering. + List burnRequests = this.conversionRequestRepository.GetAllBurn(true); + + if (burnRequests != null) + { + foreach (ConversionRequest burnRequest in burnRequests) + { + // So that we don't get stuck if we miss one inadvertently, don't break out of the loop if the height is less. + if (burnRequest.BlockHeight < blockHeight) + { + continue; + } + + // We expect them to be ordered, so as soon as they exceed the current height, ignore the rest. + if (burnRequest.BlockHeight > blockHeight) + { + break; + } + + // We use the transaction ID from the Ethereum chain as the request ID for the withdrawal. + // To parse it into a uint256 we need to trim the leading hex marker from the string. + uint256 requestId; + try + { + requestId = new uint256(burnRequest.RequestId.Replace("0x", "")); + } + catch (Exception) + { + continue; + } + + withdrawals.Add(new Withdrawal(requestId, null, Money.Satoshis(burnRequest.Amount), burnRequest.DestinationAddress, burnRequest.BlockHeight, block.GetHash())); + + // Immediately flag it as processed & persist so that it can't be added again. + burnRequest.Processed = true; + burnRequest.RequestStatus = (int)ConversionRequestStatus.Processed; + + this.conversionRequestRepository.Save(burnRequest); + } + } + if (block.Transactions.Count <= 1) return withdrawals; @@ -85,7 +133,7 @@ public IWithdrawal ExtractWithdrawalFromTransaction(Transaction transaction, uin Money withdrawalAmount = null; string targetAddress = null; - // Cross chain transfers either has 2 or 3 outputs. + // Cross chain transfers either have 2 or 3 outputs. if (transaction.Outputs.Count == ExpectedNumberOfOutputsNoChange || transaction.Outputs.Count == ExpectedNumberOfOutputsChange) { TxOut targetAddressOutput = transaction.Outputs.SingleOrDefault(this.IsTargetAddressCandidate); diff --git a/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalHistoryProvider.cs b/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalHistoryProvider.cs index 9629273e8d..2558706461 100644 --- a/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalHistoryProvider.cs +++ b/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalHistoryProvider.cs @@ -3,6 +3,7 @@ using NBitcoin; using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Features.Collateral.CounterChain; +using Stratis.Features.FederatedPeg.Conversion; using Stratis.Features.FederatedPeg.Interfaces; using Stratis.Features.FederatedPeg.Models; @@ -26,17 +27,19 @@ public class WithdrawalHistoryProvider : IWithdrawalHistoryProvider /// /// Network we are running on. /// Federation settings providing access to number of signatures required. + /// /// Repository containing all cross-network mint and burn transactions. /// Mempool which provides information about transactions in the mempool. /// Counter chain network. public WithdrawalHistoryProvider( Network network, IFederatedPegSettings federatedPegSettings, + IConversionRequestRepository conversionRequestRepository, MempoolManager mempoolManager, CounterChainNetworkWrapper counterChainNetworkWrapper) { this.network = network; this.federatedPegSettings = federatedPegSettings; - this.withdrawalExtractor = new WithdrawalExtractor(federatedPegSettings, new OpReturnDataReader(counterChainNetworkWrapper), network); + this.withdrawalExtractor = new WithdrawalExtractor(federatedPegSettings, conversionRequestRepository, new OpReturnDataReader(counterChainNetworkWrapper), network); this.mempoolManager = mempoolManager; } diff --git a/src/Stratis.Features.FederatedPeg/Wallet/ConsolidationCoinSelector.cs b/src/Stratis.Features.FederatedPeg/Wallet/ConsolidationCoinSelector.cs index b0510bde8d..abddb88857 100644 --- a/src/Stratis.Features.FederatedPeg/Wallet/ConsolidationCoinSelector.cs +++ b/src/Stratis.Features.FederatedPeg/Wallet/ConsolidationCoinSelector.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using NBitcoin; namespace Stratis.Features.FederatedPeg.Wallet diff --git a/src/Stratis.Features.SQLiteWalletRepository.Tests/DBLockTests.cs b/src/Stratis.Features.SQLiteWalletRepository.Tests/DBLockTests.cs index f8f46b351c..25eb23f716 100644 --- a/src/Stratis.Features.SQLiteWalletRepository.Tests/DBLockTests.cs +++ b/src/Stratis.Features.SQLiteWalletRepository.Tests/DBLockTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit; diff --git a/src/Stratis.Features.SQLiteWalletRepository/WalletAddressLookup.cs b/src/Stratis.Features.SQLiteWalletRepository/WalletAddressLookup.cs index 20fedeb5ba..e1827f3cb0 100644 --- a/src/Stratis.Features.SQLiteWalletRepository/WalletAddressLookup.cs +++ b/src/Stratis.Features.SQLiteWalletRepository/WalletAddressLookup.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using NBitcoin; -using NBitcoin.DataEncoders; using Stratis.Bitcoin.Features.Wallet.Interfaces; using Stratis.Bitcoin.Utilities; using Stratis.Features.SQLiteWalletRepository.External; diff --git a/src/Stratis.FullNode.sln b/src/Stratis.FullNode.sln index 0b46f99aec..591c5c3545 100644 --- a/src/Stratis.FullNode.sln +++ b/src/Stratis.FullNode.sln @@ -177,6 +177,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.StraxD", "Stratis.S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwapExtractionTool", "SwapExtractionTool\SwapExtractionTool.csproj", "{F2999E86-9949-4962-A6C6-38D1F0409D73}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Patricia", "Stratis.Patricia\Stratis.Patricia.csproj", "{57457525-FD6E-401D-BF3F-862F7A2D2AC9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Patricia.Tests", "Stratis.Patricia.Tests\Stratis.Patricia.Tests.csproj", "{EE71EBA7-5515-4F1E-B0E0-6C32BAAD6B35}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Bitcoin.Features.Interop", "Stratis.Bitcoin.Features.Interop\Stratis.Bitcoin.Features.Interop.csproj", "{3DCC6195-1271-4A12-8B94-E821925D98DC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -463,6 +469,18 @@ Global {F2999E86-9949-4962-A6C6-38D1F0409D73}.Debug|Any CPU.Build.0 = Debug|Any CPU {F2999E86-9949-4962-A6C6-38D1F0409D73}.Release|Any CPU.ActiveCfg = Release|Any CPU {F2999E86-9949-4962-A6C6-38D1F0409D73}.Release|Any CPU.Build.0 = Release|Any CPU + {57457525-FD6E-401D-BF3F-862F7A2D2AC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57457525-FD6E-401D-BF3F-862F7A2D2AC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57457525-FD6E-401D-BF3F-862F7A2D2AC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57457525-FD6E-401D-BF3F-862F7A2D2AC9}.Release|Any CPU.Build.0 = Release|Any CPU + {EE71EBA7-5515-4F1E-B0E0-6C32BAAD6B35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE71EBA7-5515-4F1E-B0E0-6C32BAAD6B35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE71EBA7-5515-4F1E-B0E0-6C32BAAD6B35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE71EBA7-5515-4F1E-B0E0-6C32BAAD6B35}.Release|Any CPU.Build.0 = Release|Any CPU + {3DCC6195-1271-4A12-8B94-E821925D98DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DCC6195-1271-4A12-8B94-E821925D98DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DCC6195-1271-4A12-8B94-E821925D98DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DCC6195-1271-4A12-8B94-E821925D98DC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -528,6 +546,9 @@ Global {AA085181-BBB4-495E-BFD8-7AE4BB52A0A0} = {15D29FFD-6142-4DC5-AFFD-10BA0CA55C45} {789E4584-5616-4F0C-A3E8-88669B6AA91F} = {EAE139C2-B19C-4905-9117-8A4068ABD3D2} {F2999E86-9949-4962-A6C6-38D1F0409D73} = {1B9A916F-DDAC-4675-B424-EDEDC1A58231} + {57457525-FD6E-401D-BF3F-862F7A2D2AC9} = {1B9A916F-DDAC-4675-B424-EDEDC1A58231} + {EE71EBA7-5515-4F1E-B0E0-6C32BAAD6B35} = {1B9A916F-DDAC-4675-B424-EDEDC1A58231} + {3DCC6195-1271-4A12-8B94-E821925D98DC} = {15D29FFD-6142-4DC5-AFFD-10BA0CA55C45} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6C780ABA-5872-4B83-AD3F-A5BD423AD907} diff --git a/src/Stratis.Patricia.Tests/ByteArrayComparerTests.cs b/src/Stratis.Patricia.Tests/ByteArrayComparerTests.cs new file mode 100644 index 0000000000..0eb3ca3204 --- /dev/null +++ b/src/Stratis.Patricia.Tests/ByteArrayComparerTests.cs @@ -0,0 +1,38 @@ +using Xunit; + +namespace Stratis.Patricia.Tests +{ + public class ByteArrayComparerTests + { + [Fact] + public void Test() + { + var bytes = new byte[] + { + 1, + 2, + 5, + 234 + }; + + var bytes2 = new byte[] + { + 1, + 2, + 5, + 234 + }; + + var bytes3 = new byte[] + { + 1, + 2, + 5, + 235 + }; + + Assert.True(new ByteArrayComparer().Equals(bytes, bytes2)); + Assert.False(new ByteArrayComparer().Equals(bytes, bytes3)); + } + } +} diff --git a/src/Stratis.Patricia.Tests/KeyTests.cs b/src/Stratis.Patricia.Tests/KeyTests.cs new file mode 100644 index 0000000000..b35f1cfdb7 --- /dev/null +++ b/src/Stratis.Patricia.Tests/KeyTests.cs @@ -0,0 +1,16 @@ +using Xunit; + +namespace Stratis.Patricia.Tests +{ + public class KeyTests + { + [Fact] + public void TestEmptyKey() + { + var testKey = Key.Empty(true); + Assert.Equal(0, testKey.Length); + Assert.True(testKey.IsTerminal); + Assert.True(testKey.IsEmpty); + } + } +} diff --git a/src/Stratis.Patricia.Tests/Stratis.Patricia.Tests.csproj b/src/Stratis.Patricia.Tests/Stratis.Patricia.Tests.csproj new file mode 100644 index 0000000000..7fef4f046d --- /dev/null +++ b/src/Stratis.Patricia.Tests/Stratis.Patricia.Tests.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + diff --git a/src/Stratis.Patricia.Tests/TrieTests.cs b/src/Stratis.Patricia.Tests/TrieTests.cs new file mode 100644 index 0000000000..18a5ac4fb4 --- /dev/null +++ b/src/Stratis.Patricia.Tests/TrieTests.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Stratis.Patricia.Tests +{ + public class TrieTests + { + private static readonly byte[] empty = new byte[0]; + private static readonly byte[] dog = Encoding.UTF8.GetBytes("dog"); + private static readonly byte[] dodecahedron = Encoding.UTF8.GetBytes("dodecahedron"); + private static readonly byte[] cat = Encoding.UTF8.GetBytes("cat"); + private static readonly byte[] fish = Encoding.UTF8.GetBytes("fish"); + private static readonly byte[] bird = Encoding.UTF8.GetBytes("bird"); + + [Fact] + public void TestTrieDeterminism() + { + // No matter the order that things are put in, if the contents are the same then root hash is the same + var trie1 = new PatriciaTrie(); + var trie2 = new PatriciaTrie(); + + trie1.Put(dog, cat); + trie1.Put(fish, bird); + + trie2.Put(fish, bird); + trie2.Put(dog, cat); + + Assert.Equal(trie1.GetRootHash(), trie2.GetRootHash()); + + trie1.Put(dog, bird); + trie1.Put(dog, fish); + trie1.Put(dodecahedron, dog); + trie1.Put(dodecahedron, cat); + trie1.Put(fish, bird); + trie1.Put(fish, cat); + + trie2.Put(dog, fish); + trie2.Put(fish, cat); + trie2.Put(dodecahedron, cat); + + Assert.Equal(trie1.GetRootHash(), trie2.GetRootHash()); + } + + [Fact] + public void TestTrieGetPut() + { + // We can retrieve the values we put in + var trie = new PatriciaTrie(); + + trie.Put(dog, cat); + trie.Put(fish, bird); + + Assert.Equal(cat, trie.Get(dog)); + Assert.Equal(bird, trie.Get(fish)); + + trie.Put(dog, fish); + trie.Put(dog, bird); + + Assert.Equal(bird, trie.Get(dog)); + } + + [Fact] + public void TestTrieFlush() + { + var memDb = new MemoryDictionarySource(); + var trie = new PatriciaTrie(memDb); + + trie.Put(dog, cat); + trie.Put(fish, bird); + Assert.Equal(cat, trie.Get(dog)); + + Assert.Empty(memDb.Db.Keys); + trie.Flush(); + Assert.NotEmpty(memDb.Db.Keys); // This should be more specific in future. How many nodes are we expecting? + } + + [Fact] + public void TestTrieGetAfterFlush() + { + var memDb = new MemoryDictionarySource(); + var trie = new PatriciaTrie(memDb); + + trie.Put(dog, cat); + Assert.Equal(cat, trie.Get(dog)); + trie.Flush(); + Assert.Equal(cat, trie.Get(dog)); + } + + [Fact] + public void TestDelete() + { + var memDb = new MemoryDictionarySource(); + var trie = new PatriciaTrie(memDb); + + trie.Put(dog, cat); + + byte[] dogCatOnlyHash = trie.GetRootHash(); + + trie.Put(fish, bird); + trie.Delete(fish); + + Assert.Equal(dogCatOnlyHash, trie.GetRootHash()); + + trie.Put(fish, bird); + trie.Put(fish, empty); + + Assert.Equal(dogCatOnlyHash, trie.GetRootHash()); + } + + [Fact] + public void TestTrieLoad() + { + var memDb = new MemoryDictionarySource(); + var trie = new PatriciaTrie(memDb); + + trie.Put(dog, cat); + trie.Put(fish, bird); + trie.Put(dodecahedron, fish); + trie.Flush(); + byte[] savedHash = trie.GetRootHash(); + + var trie2 = new PatriciaTrie(memDb); + trie2.SetRootHash(savedHash); + + Assert.Equal(cat, trie.Get(dog)); + Assert.Equal(cat, trie2.Get(dog)); + Assert.Equal(bird, trie2.Get(fish)); + Assert.Equal(fish, trie2.Get(dodecahedron)); + } + + + [Fact] + public void TestTrieBulkData() + { + var memDb = new MemoryDictionarySource(); + var trie = new PatriciaTrie(memDb); + + Dictionary toInput = new Dictionary(); + for (int i = 0; i < 1000; i++) + { + toInput.Add( + new Random().Next().ToString(), + new Random().Next().ToString() + ); + } + + foreach (var kvp in toInput) + { + trie.Put(Encoding.UTF8.GetBytes(kvp.Key), Encoding.UTF8.GetBytes(kvp.Value)); + } + + foreach (var kvp in toInput) + { + Assert.Equal(kvp.Value, Encoding.UTF8.GetString(trie.Get(Encoding.UTF8.GetBytes(kvp.Key)))); + } + + trie.Put(dog, cat); + trie.Put(fish, bird); + trie.Put(dodecahedron, fish); + trie.Flush(); + byte[] savedHash = trie.GetRootHash(); + + var trie2 = new PatriciaTrie(memDb); + trie2.SetRootHash(savedHash); + + Assert.Equal(cat, trie.Get(dog)); + Assert.Equal(cat, trie2.Get(dog)); + Assert.Equal(bird, trie2.Get(fish)); + Assert.Equal(fish, trie2.Get(dodecahedron)); + foreach (var kvp in toInput) + { + Assert.Equal(kvp.Value, Encoding.UTF8.GetString(trie2.Get(Encoding.UTF8.GetBytes(kvp.Key)))); + } + } + } +} diff --git a/src/Stratis.Patricia/AssemblyInfo.cs b/src/Stratis.Patricia/AssemblyInfo.cs new file mode 100644 index 0000000000..756347fb89 --- /dev/null +++ b/src/Stratis.Patricia/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Stratis.Patricia.Tests")] \ No newline at end of file diff --git a/src/Stratis.Patricia/ByteArrayComparer.cs b/src/Stratis.Patricia/ByteArrayComparer.cs new file mode 100644 index 0000000000..69f8ee7127 --- /dev/null +++ b/src/Stratis.Patricia/ByteArrayComparer.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; + +namespace Stratis.Patricia +{ + /// + /// Compares byte arrays by value rather than reference. + /// + public class ByteArrayComparer : IEqualityComparer + { + public bool Equals(byte[] left, byte[] right) + { + if (left == null || right == null) + { + return left == right; + } + if (left.Length != right.Length) + { + return false; + } + for (int i = 0; i < left.Length; i++) + { + if (left[i] != right[i]) + { + return false; + } + } + return true; + } + + public int GetHashCode(byte[] key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + int sum = 0; + foreach (byte cur in key) + { + sum += cur; + } + return sum; + } + } +} diff --git a/src/Stratis.Patricia/IHasher.cs b/src/Stratis.Patricia/IHasher.cs new file mode 100644 index 0000000000..ca984fd466 --- /dev/null +++ b/src/Stratis.Patricia/IHasher.cs @@ -0,0 +1,10 @@ +namespace Stratis.Patricia +{ + public interface IHasher + { + /// + /// Returns a hash of the given bytes. + /// + byte[] Hash(byte[] input); + } +} diff --git a/src/Stratis.Patricia/IPatriciaTrie.cs b/src/Stratis.Patricia/IPatriciaTrie.cs new file mode 100644 index 0000000000..308860465d --- /dev/null +++ b/src/Stratis.Patricia/IPatriciaTrie.cs @@ -0,0 +1,16 @@ +namespace Stratis.Patricia +{ + public interface IPatriciaTrie : ISource + { + /// + /// Get the 32-byte hash of the current root node. + /// + byte[] GetRootHash(); + + /// + /// Set the 32-byte hash of the current root node. + /// + /// + void SetRootHash(byte[] root); + } +} diff --git a/src/Stratis.Patricia/ISource.cs b/src/Stratis.Patricia/ISource.cs new file mode 100644 index 0000000000..7b6dc3321d --- /dev/null +++ b/src/Stratis.Patricia/ISource.cs @@ -0,0 +1,29 @@ +namespace Stratis.Patricia +{ + /// + /// A data store. + /// + public interface ISource + { + /// + /// Store a value by the given key. + /// + void Put(K key, V val); + + /// + /// Retrieve a value by key. + /// + V Get(K key); + + /// + /// Remove a key and value from the data store. + /// + void Delete(K key); + + /// + /// Persist any changes to an underlying source. + /// + /// + bool Flush(); + } +} diff --git a/src/Stratis.Patricia/Keccak256Hasher.cs b/src/Stratis.Patricia/Keccak256Hasher.cs new file mode 100644 index 0000000000..8f3d619bd0 --- /dev/null +++ b/src/Stratis.Patricia/Keccak256Hasher.cs @@ -0,0 +1,13 @@ +using HashLib; + +namespace Stratis.Patricia +{ + public class Keccak256Hasher : IHasher + { + /// + public byte[] Hash(byte[] input) + { + return HashFactory.Crypto.SHA3.CreateKeccak256().ComputeBytes(input).GetBytes(); + } + } +} diff --git a/src/Stratis.Patricia/Key.cs b/src/Stratis.Patricia/Key.cs new file mode 100644 index 0000000000..00e3588000 --- /dev/null +++ b/src/Stratis.Patricia/Key.cs @@ -0,0 +1,180 @@ +using System; + +namespace Stratis.Patricia +{ + internal sealed class Key + { + public const int ODD_OFFSET_FLAG = 0x1; + public const int TERMINATOR_FLAG = 0x2; + private readonly byte[] key; + private readonly int off; + + public bool IsTerminal { get; set; } + + public int Length + { + get + { + return (this.key.Length << 1) - this.off; + } + } + + public bool IsEmpty + { + get + { + return this.Length == 0; + } + } + + public static Key FromNormal(byte[] key) + { + return new Key(key); + } + + public static Key FromPacked(byte[] key) + { + return new Key(key, ((key[0] >> 4) & ODD_OFFSET_FLAG) != 0 ? 1 : 2, ((key[0] >> 4) & TERMINATOR_FLAG) != 0); + } + + public static Key Empty(bool terminal) + { + return new Key(new byte[0], 0, terminal); + } + + public static Key SingleHex(int hex) + { + Key ret = new Key(new byte[1], 1, false); + ret.SetHex(0, hex); + return ret; + } + + public Key(byte[] key, int off = 0, bool terminal = true) + { + this.IsTerminal = terminal; + this.off = off; + this.key = key; + } + + public byte[] ToPacked() + { + int flags = ((this.off & 1) != 0 ? ODD_OFFSET_FLAG : 0) | (this.IsTerminal ? TERMINATOR_FLAG : 0); + byte[] ret = new byte[this.Length / 2 + 1]; + int toCopy = (flags & ODD_OFFSET_FLAG) != 0 ? ret.Length : ret.Length - 1; + Array.Copy(this.key, this.key.Length - toCopy, ret, ret.Length - toCopy, toCopy); + ret[0] &= 0x0F; + ret[0] |= (byte) (flags << 4); + return ret; + } + + public int GetHex(int idx) + { + int adjustedIndex = (this.off + idx) >> 1; + byte b = this.key[(this.off + idx) >> 1]; + return (((this.off + idx) & 1) == 0 ? (b >> 4) : b) & 0xF; + } + + public Key Shift(int hexCnt) + { + return new Key(this.key, this.off + hexCnt, this.IsTerminal); + } + + private void SetHex(int idx, int hex) + { + int byteIdx = (this.off + idx) >> 1; + if (((this.off + idx) & 1) == 0) + { + this.key[byteIdx] &= 0x0F; + this.key[byteIdx] |= (byte) (hex << 4); + } + else + { + this.key[byteIdx] &= 0xF0; + this.key[byteIdx] |= (byte) hex; + } + } + + public Key MatchAndShift(Key k) + { + int len = this.Length; + int kLen = k.Length; + if (len < kLen) return null; + + if ((this.off & 1) == (k.off & 1)) + { + // optimization to compare whole keys bytes + if ((this.off & 1) == 1) + { + if (this.GetHex(0) != k.GetHex(0)) return null; + } + int idx1 = (this.off + 1) >> 1; + int idx2 = (k.off + 1) >> 1; + int l = kLen >> 1; + for (int i = 0; i < l; i++, idx1++, idx2++) + { + if (this.key[idx1] != k.key[idx2]) return null; + } + } + else + { + for (int i = 0; i < kLen; i++) + { + if (this.GetHex(i) != k.GetHex(i)) return null; + } + } + return this.Shift(kLen); + } + + public Key Concat(Key k) + { + if (this.IsTerminal) throw new PatriciaTreeResolutionException("Can't append to terminal key: " + this + " + " + k); + int len = this.Length; + int kLen = k.Length; + int newLen = len + kLen; + byte[] newKeyBytes = new byte[(newLen + 1) >> 1]; + Key ret = new Key(newKeyBytes, newLen & 1, k.IsTerminal); + for (int i = 0; i < len; i++) + { + ret.SetHex(i, this.GetHex(i)); + } + for (int i = 0; i < kLen; i++) + { + ret.SetHex(len + i, k.GetHex(i)); + } + return ret; + } + + public Key GetCommonPrefix(Key k) + { + // TODO can be optimized + int prefixLen = 0; + int thisLenght = this.Length; + int kLength = k.Length; + while (prefixLen < thisLenght && prefixLen < kLength && this.GetHex(prefixLen) == k.GetHex(prefixLen)) + prefixLen++; + byte[] prefixKey = new byte[(prefixLen + 1) >> 1]; + Key ret = new Key(prefixKey, (prefixLen & 1) == 0 ? 0 : 1, + prefixLen == this.Length && prefixLen == k.Length && this.IsTerminal && k.IsTerminal); + for (int i = 0; i < prefixLen; i++) + { + ret.SetHex(i, k.GetHex(i)); + } + return ret; + } + + public override bool Equals(object obj) + { + Key k = (Key)obj; + int len = this.Length; + + if (len != k.Length) return false; + // TODO can be optimized + for (int i = 0; i < len; i++) + { + if (this.GetHex(i) != k.GetHex(i)) return false; + } + return this.IsTerminal == k.IsTerminal; + } + + } +} diff --git a/src/Stratis.Patricia/MemoryDictionarySource.cs b/src/Stratis.Patricia/MemoryDictionarySource.cs new file mode 100644 index 0000000000..b308b05528 --- /dev/null +++ b/src/Stratis.Patricia/MemoryDictionarySource.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace Stratis.Patricia +{ + /// + /// A basic in-memory database. + /// + public class MemoryDictionarySource : ISource + { + public Dictionary Db { get; } + + public MemoryDictionarySource() + { + this.Db = new Dictionary(new ByteArrayComparer()); + } + + /// + public void Delete(byte[] key) + { + this.Db.Remove(key); + } + + /// + /// Not implemented on MemoryDictionarySource. + /// + /// + public bool Flush() + { + throw new NotImplementedException(); + } + + /// + public byte[] Get(byte[] key) + { + if (this.Db.ContainsKey(key)) + return this.Db[key]; + return null; + } + + /// + public void Put(byte[] key, byte[] val) + { + this.Db[key] = val; + } + } +} diff --git a/src/Stratis.Patricia/Node.cs b/src/Stratis.Patricia/Node.cs new file mode 100644 index 0000000000..acaf891ace --- /dev/null +++ b/src/Stratis.Patricia/Node.cs @@ -0,0 +1,329 @@ +using Nethereum.RLP; + +namespace Stratis.Patricia +{ + internal sealed class Node + { + private static readonly object NullNode = new object(); + + /// + /// The key/value store used to store nodes and data by their hashes. + /// + private readonly PatriciaTrie trie; + + private byte[] rlp; + private object[] children; + private RLPCollection parsedRlp; + + public byte[] Hash { get; private set; } + public bool Dirty { get; private set; } + + public NodeType NodeType + { + get + { + this.Parse(); + return this.children.Length == 17 ? NodeType.BranchNode : (this.children[1] is Node ? NodeType.KeyValueNodeNode : NodeType.KeyValueNodeValue); + } + } + + /// + /// Create a new empty branch node. + /// + public Node(PatriciaTrie trie) + { + this.children = new object[17]; + this.Dirty = true; + this.trie = trie; + } + + /// + /// Create a new key/value node. + /// + public Node(Key key, object valueOrNode, PatriciaTrie trie) : this(new object[] { key, valueOrNode }, trie) + { + this.Dirty = true; + } + + /// + /// Get a node from a hash or raw RLP-encoded data. + /// + public Node(byte[] hashOrRlp, PatriciaTrie trie) + { + // If length is 32, we know it's a hash as the RLP-encoded data will always have a length greater than 32. (TODO: Verify) + if (hashOrRlp.Length == 32) + { + this.Hash = hashOrRlp; + } + else + { + this.rlp = hashOrRlp; + } + this.trie = trie; + } + + /// + /// Get a node from a parsed RLP object + /// + public Node(RLPCollection parsedRlp, PatriciaTrie trie) + { + this.parsedRlp = parsedRlp; + this.rlp = parsedRlp.RLPData; + this.trie = trie; + } + + private Node(object[] children, PatriciaTrie trie) + { + this.children = children; + this.trie = trie; + } + + public byte[] Encode() + { + return this.Encode(1, true); + } + + public bool ResolveCheck() + { + if (this.rlp != null || this.parsedRlp != null || this.Hash == null) return true; + this.rlp = this.trie.TrieKvStore.Get(this.Hash); + return this.rlp != null; + } + + private void Resolve() + { + if (!this.ResolveCheck()) + throw new PatriciaTreeResolutionException("Invalid trie state. Can't resolve value for hash."); + } + + + private byte[] Encode(int depth, bool forceHash) + { + if (!this.Dirty) + { + return this.Hash != null ? RLP.EncodeElement(this.Hash) : this.rlp; + } + else + { + NodeType type = this.NodeType; + byte[] ret; + if (type == NodeType.BranchNode) + { + byte[][] encoded = new byte[17][]; + for (int i = 0; i < 16; i++) + { + Node child = this.BranchNodeGetChild(i); + encoded[i] = child == null ? PatriciaTrie.EmptyElementRlp : child.Encode(depth + 1, false); + } + byte[] value = this.BranchNodeGetValue(); + encoded[16] = RLP.EncodeElement(value); + ret = RLP.EncodeList(encoded); + } + else if (type == NodeType.KeyValueNodeNode) + { + ret = RLP.EncodeList(RLP.EncodeElement(this.KvNodeGetKey().ToPacked()), this.KvNodeGetChildNode().Encode(depth + 1, false)); + } + else + { + byte[] value = this.KvNodeGetValue(); + ret = RLP.EncodeList(RLP.EncodeElement(this.KvNodeGetKey().ToPacked()), + RLP.EncodeElement(value ?? PatriciaTrie.EmptyByteArray)); + } + if (this.Hash != null) + { + this.trie.TrieKvStore.Delete(this.Hash); + } + this.Dirty = false; + if (ret.Length < 32 && !forceHash) + { + this.rlp = ret; + return ret; + } + else + { + this.Hash = this.trie.Hasher.Hash(ret); + this.trie.TrieKvStore.Put(this.Hash, ret); + return RLP.EncodeElement(this.Hash); + } + } + } + + private void Parse() + { + if (this.children != null) return; + this.Resolve(); + + //RLPCollection list = this.parsedRlp ?? RLP.Decode(this.rlp)[0] as RLPCollection; + RLPCollection list = this.parsedRlp ?? RLP.Decode(this.rlp) as RLPCollection; + + if (list.Count == 2) + { + this.children = new object[2]; + Key key = Key.FromPacked(list[0].RLPData); + this.children[0] = key; + if (key.IsTerminal) + { + this.children[1] = list[1].RLPData; + } + else + { + this.children[1] = (list[1] is RLPCollection collection) + ? new Node(collection, this.trie) + : new Node(list[1].RLPData, this.trie); + } + } + else + { + this.children = new object[17]; + this.parsedRlp = list; + } + } + + public Node BranchNodeGetChild(int hex) + { + this.Parse(); + object n = this.children[hex]; + if (n == null && this.parsedRlp != null) + { + if (this.parsedRlp[hex] is RLPCollection) + { + n = new Node((RLPCollection)this.parsedRlp[hex], this.trie); + } + else + { + byte[] bytes = this.parsedRlp[hex].RLPData; + if (bytes == null || bytes.Length == 0) + { + n = NullNode; + } + else + { + n = new Node(bytes, this.trie); + } + } + this.children[hex] = n; + } + return n == NullNode ? null : (Node)n; + } + + public Node BranchNodeSetChild(int hex, Node node) + { + this.Parse(); + this.children[hex] = node ?? NullNode; + this.Dirty = true; + return this; + } + + public byte[] BranchNodeGetValue() + { + this.Parse(); + object n = this.children[16]; + if (n == null && this.parsedRlp != null) + { + byte[] bytes = this.parsedRlp[16].RLPData; + if (bytes == null || bytes.Length == 0) + { + n = NullNode; + } + else + { + n = bytes; + } + this.children[16] = n; + } + return n == NullNode ? null : (byte[])n; + } + + public Node BranchNodeSetValue(byte[] val) + { + this.Parse(); + this.children[16] = val == null ? NullNode : val; + this.Dirty = true; + return this; + } + + public int BranchNodeCompactIdx() + { + this.Parse(); + int cnt = 0; + int idx = -1; + for (int i = 0; i < 16; i++) + { + if (this.BranchNodeGetChild(i) != null) + { + cnt++; + idx = i; + if (cnt > 1) return -1; + } + } + return cnt > 0 ? idx : (this.BranchNodeGetValue() == null ? -1 : 16); + } + + public bool BranchNodeCanCompact() + { + this.Parse(); + int cnt = 0; + for (int i = 0; i < 16; i++) + { + cnt += this.BranchNodeGetChild(i) == null ? 0 : 1; + if (cnt > 1) return false; + } + return cnt == 0 || this.BranchNodeGetValue() == null; + } + + public Key KvNodeGetKey() + { + this.Parse(); + return (Key)this.children[0]; + } + + public Node KvNodeGetChildNode() + { + this.Parse(); + return (Node)this.children[1]; + } + + public byte[] KvNodeGetValue() + { + this.Parse(); + return (byte[])this.children[1]; + } + + public Node KvNodeSetValue(byte[] value) + { + this.Parse(); + this.children[1] = value; + this.Dirty = true; + return this; + } + + public object KvNodeGetValueOrNode() + { + this.Parse(); + return this.children[1]; + } + + public Node KvNodeSetValueOrNode(object valueOrNode) + { + this.Parse(); + this.children[1] = valueOrNode; + this.Dirty = true; + return this; + } + + public void Dispose() + { + if (this.Hash != null) + { + this.trie.TrieKvStore.Delete(this.Hash); + } + } + + public Node Invalidate() + { + this.Dirty = true; + return this; + } + } + +} diff --git a/src/Stratis.Patricia/NodeType.cs b/src/Stratis.Patricia/NodeType.cs new file mode 100644 index 0000000000..5ca9a0f41f --- /dev/null +++ b/src/Stratis.Patricia/NodeType.cs @@ -0,0 +1,9 @@ +namespace Stratis.Patricia +{ + internal enum NodeType + { + BranchNode, + KeyValueNodeValue, + KeyValueNodeNode + } +} diff --git a/src/Stratis.Patricia/PatriciaTreeResolutionException.cs b/src/Stratis.Patricia/PatriciaTreeResolutionException.cs new file mode 100644 index 0000000000..90a7d2d3ed --- /dev/null +++ b/src/Stratis.Patricia/PatriciaTreeResolutionException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Stratis.Patricia +{ + public class PatriciaTreeResolutionException : Exception + { + public PatriciaTreeResolutionException(string message) : base(message) { } + } +} diff --git a/src/Stratis.Patricia/PatriciaTrie.cs b/src/Stratis.Patricia/PatriciaTrie.cs new file mode 100644 index 0000000000..dbfb1f05e5 --- /dev/null +++ b/src/Stratis.Patricia/PatriciaTrie.cs @@ -0,0 +1,319 @@ +using Nethereum.RLP; + +namespace Stratis.Patricia +{ + /// + /// A merkle patricia trie implementation. Stores data in a trie and key/mapping structure + /// in such a way that all of the data can be represented by a 32-bit hash. + /// Full definition at: https://github.com/ethereum/wiki/wiki/Patricia-Tree + /// + public class PatriciaTrie : IPatriciaTrie + { + internal static readonly byte[] EmptyByteArray = new byte[0]; + internal static readonly byte[] EmptyElementRlp = RLP.EncodeElement(EmptyByteArray); + private readonly byte[] emptyDataHash; + private readonly byte[] emptyTrieHash; + + /// + /// The key/value store used to store nodes and data by their hashes. + /// + internal ISource TrieKvStore { get; } + + /// + /// Used to hash nodes and values. + /// + internal IHasher Hasher { get; } + + + /// + /// The root of the trie. + /// + private Node root; + + + public PatriciaTrie() : this(null, new MemoryDictionarySource(), new Keccak256Hasher()) { } + + public PatriciaTrie(byte[] root) : this(root, new MemoryDictionarySource(), new Keccak256Hasher()) { } + + public PatriciaTrie(ISource trieKvStore) : this(null, trieKvStore, new Keccak256Hasher()) { } + + public PatriciaTrie(byte[] root, ISource trieKvStore) : this(root, trieKvStore, new Keccak256Hasher()) { } + + public PatriciaTrie(byte[] root, ISource trieKvStore, IHasher hasher) + { + // Set these first because SetRootHash does check to see if root given is an empty value! + this.emptyDataHash = hasher.Hash(EmptyByteArray); + this.emptyTrieHash = hasher.Hash(EmptyElementRlp); + + this.TrieKvStore = trieKvStore; + this.Hasher = hasher; + this.SetRootHash(root); + } + + /// + public void SetRootHash(byte[] hash) + { + if (hash != null && !new ByteArrayComparer().Equals(hash, this.emptyTrieHash)) + { + this.root = new Node(hash, this); + } + else + { + this.root = null; + } + } + + /// + public byte[] GetRootHash() + { + this.Encode(); + return this.root != null ? this.root.Hash : this.emptyTrieHash; + } + + /// + public byte[] Get(byte[] key) + { + if (!this.HasRoot()) + return null; + Key k = Key.FromNormal(key); + return this.Get(this.root, k); + } + + /// + public void Put(byte[] key, byte[] value) + { + Key k = Key.FromNormal(key); + if (this.root == null) + { + if (value != null && value.Length > 0) + { + this.root = new Node(k, value, this); + } + } + else + { + if (value == null || value.Length == 0) + { + this.root = this.Delete(this.root, k); + } + else + { + this.root = this.Insert(this.root, k, value); + } + } + } + + /// + public void Delete(byte[] key) + { + Key k = Key.FromNormal(key); + if (this.root != null) + { + this.root = this.Delete(this.root, k); + } + } + + /// + public bool Flush() + { + if (this.root != null && this.root.Dirty) + { + // persist all dirty Nodes to underlying Source + this.Encode(); + // release all Trie Node instances for GC + this.root = new Node(this.root.Hash, this); + return true; + } + else + { + return false; + } + } + + private bool HasRoot() + { + return this.root != null && this.root.ResolveCheck(); + } + + private byte[] Get(Node n, Key k) + { + if (n == null) + return null; + + NodeType type = n.NodeType; + + if (type == NodeType.BranchNode) + { + if (k.IsEmpty) + return n.BranchNodeGetValue(); + + Node childNode = n.BranchNodeGetChild(k.GetHex(0)); + return this.Get(childNode, k.Shift(1)); + } + else + { + Key k1 = k.MatchAndShift(n.KvNodeGetKey()); + if (k1 == null) return null; + if (type == NodeType.KeyValueNodeValue) + { + return k1.IsEmpty ? n.KvNodeGetValue() : null; + } + else + { + return this.Get(n.KvNodeGetChildNode(), k1); + } + } + } + + private Node Insert(Node n, Key k, object NodeOrValue) + { + NodeType type = n.NodeType; + if (type == NodeType.BranchNode) + { + if (k.IsEmpty) return n.BranchNodeSetValue((byte[])NodeOrValue); + Node childNode = n.BranchNodeGetChild(k.GetHex(0)); + if (childNode != null) + { + return n.BranchNodeSetChild(k.GetHex(0), this.Insert(childNode, k.Shift(1), NodeOrValue)); + } + else + { + Key childKey = k.Shift(1); + Node newChildNode; + if (!childKey.IsEmpty) + { + newChildNode = new Node(childKey, NodeOrValue, this); + } + else + { + newChildNode = NodeOrValue is Node node + ? node + : new Node(childKey, NodeOrValue, this); + } + return n.BranchNodeSetChild(k.GetHex(0), newChildNode); + } + } + else + { + Key commonPrefix = k.GetCommonPrefix(n.KvNodeGetKey()); + if (commonPrefix.IsEmpty) + { + Node newBranchNode = new Node(this); + this.Insert(newBranchNode, n.KvNodeGetKey(), n.KvNodeGetValueOrNode()); + this.Insert(newBranchNode, k, NodeOrValue); + n.Dispose(); + return newBranchNode; + } + else if (commonPrefix.Equals(k)) + { + return n.KvNodeSetValueOrNode(NodeOrValue); + } + else if (commonPrefix.Equals(n.KvNodeGetKey())) + { + this.Insert(n.KvNodeGetChildNode(), k.Shift(commonPrefix.Length), NodeOrValue); + return n.Invalidate(); + } + else + { + Node newBranchNode = new Node(this); + Node newKvNode = new Node(commonPrefix, newBranchNode, this); + // TODO can be optimized + this.Insert(newKvNode, n.KvNodeGetKey(), n.KvNodeGetValueOrNode()); + this.Insert(newKvNode, k, NodeOrValue); + n.Dispose(); + return newKvNode; + } + } + } + + private Node Delete(Node n, Key k) + { + NodeType type = n.NodeType; + Node newKvNode; + if (type == NodeType.BranchNode) + { + if (k.IsEmpty) + { + n.BranchNodeSetValue(null); + } + else + { + int idx = k.GetHex(0); + Node child = n.BranchNodeGetChild(idx); + if (child == null) return n; // no key found + + Node newNode = this.Delete(child, k.Shift(1)); + n.BranchNodeSetChild(idx, newNode); + if (newNode != null) return n; // newNode != null thus number of children didn't decrease + } + + // child Node or value was deleted and the branch Node may need to be compacted + int compactIdx = n.BranchNodeCompactIdx(); + if (compactIdx < 0) return n; // no compaction is required + + // only value or a single child left - compact branch Node to kvNode + n.Dispose(); + if (compactIdx == 16) + { // only value left + return new Node(Key.Empty(true), n.BranchNodeGetValue(), this); + } + else + { // only single child left + newKvNode = new Node(Key.SingleHex(compactIdx), n.BranchNodeGetChild(compactIdx), this); + } + } + else + { // n - kvNode + Key k1 = k.MatchAndShift(n.KvNodeGetKey()); + if (k1 == null) + { + // no key found + return n; + } + else if (type == NodeType.KeyValueNodeValue) + { + if (k1.IsEmpty) + { + // delete this kvNode + n.Dispose(); + return null; + } + else + { + // else no key found + return n; + } + } + else + { + Node newChild = this.Delete(n.KvNodeGetChildNode(), k1); + if (newChild == null) + throw new PatriciaTreeResolutionException("New node failed instantiation after deletion."); + newKvNode = n.KvNodeSetValueOrNode(newChild); + } + } + + // if we get here a new kvNode was created, now need to check + // if it should be compacted with child kvNode + Node nChild = newKvNode.KvNodeGetChildNode(); + if (nChild.NodeType != NodeType.BranchNode) + { + // two kvNodes should be compacted into a single one + Key newKey = newKvNode.KvNodeGetKey().Concat(nChild.KvNodeGetKey()); + Node newNode = new Node(newKey, nChild.KvNodeGetValueOrNode(), this); + nChild.Dispose(); + return newNode; + } + else + { + // no compaction needed + return newKvNode; + } + } + + private void Encode() + { + this.root?.Encode(); + } + } +} diff --git a/src/Stratis.Patricia/Stratis.Patricia.csproj b/src/Stratis.Patricia/Stratis.Patricia.csproj new file mode 100644 index 0000000000..004d922b0c --- /dev/null +++ b/src/Stratis.Patricia/Stratis.Patricia.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + Stratis Patricia Trie + Stratis Group Ltd. + A C# implementation of a merkle patricia trie. + 0.1.0.8 + 0.1.0.8 + 1.0.8 + + + + + + + + diff --git a/src/Stratis.SmartContracts.CLR.Tests/CallDataSerializerTests.cs b/src/Stratis.SmartContracts.CLR.Tests/CallDataSerializerTests.cs index 322f0fa3e7..1c1ad523be 100644 --- a/src/Stratis.SmartContracts.CLR.Tests/CallDataSerializerTests.cs +++ b/src/Stratis.SmartContracts.CLR.Tests/CallDataSerializerTests.cs @@ -3,7 +3,6 @@ using System.Text; using NBitcoin; using Stratis.SmartContracts.CLR.Serialization; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Networks; using Xunit; diff --git a/src/Stratis.SmartContracts.CLR.Tests/ContractAssemblyLoaderTests.cs b/src/Stratis.SmartContracts.CLR.Tests/ContractAssemblyLoaderTests.cs index 1a7fad887f..036709efe2 100644 --- a/src/Stratis.SmartContracts.CLR.Tests/ContractAssemblyLoaderTests.cs +++ b/src/Stratis.SmartContracts.CLR.Tests/ContractAssemblyLoaderTests.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.Loader; +using System.Linq; using Stratis.SmartContracts.CLR.Compilation; using Stratis.SmartContracts.CLR.Loader; using Xunit; diff --git a/src/Stratis.SmartContracts.CLR.Tests/ContractLogHolderTests.cs b/src/Stratis.SmartContracts.CLR.Tests/ContractLogHolderTests.cs index 597bdc43fe..a76d10c283 100644 --- a/src/Stratis.SmartContracts.CLR.Tests/ContractLogHolderTests.cs +++ b/src/Stratis.SmartContracts.CLR.Tests/ContractLogHolderTests.cs @@ -4,7 +4,6 @@ using NBitcoin; using Stratis.SmartContracts.CLR.ContractLogging; using Stratis.SmartContracts.CLR.Serialization; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Core.Receipts; using Stratis.SmartContracts.Networks; using Xunit; diff --git a/src/Stratis.SmartContracts.CLR.Tests/ReflectionVirtualMachineTests.cs b/src/Stratis.SmartContracts.CLR.Tests/ReflectionVirtualMachineTests.cs index dbd44c02b2..5920e692e8 100644 --- a/src/Stratis.SmartContracts.CLR.Tests/ReflectionVirtualMachineTests.cs +++ b/src/Stratis.SmartContracts.CLR.Tests/ReflectionVirtualMachineTests.cs @@ -1,7 +1,6 @@ using System; using System.Reflection; using System.Text; -using CSharpFunctionalExtensions; using Moq; using NBitcoin; using Stratis.SmartContracts.CLR.Caching; diff --git a/src/Stratis.SmartContracts.CLR/CallDataSerializer.cs b/src/Stratis.SmartContracts.CLR/CallDataSerializer.cs index ae123b271d..af48f16f11 100644 --- a/src/Stratis.SmartContracts.CLR/CallDataSerializer.cs +++ b/src/Stratis.SmartContracts.CLR/CallDataSerializer.cs @@ -6,7 +6,6 @@ using Nethereum.RLP; using Stratis.Bitcoin.Utilities; using Stratis.SmartContracts.CLR.Serialization; -using Stratis.SmartContracts.Core; using TracerAttributes; namespace Stratis.SmartContracts.CLR @@ -90,9 +89,7 @@ public Result SerializeCallContract(byte[] smartContractBytes, i protected static IList RLPDecode(byte[] remaining) { - RLPCollection list = RLP.Decode(remaining); - - RLPCollection innerList = (RLPCollection) list[0]; + RLPCollection innerList = (RLPCollection)RLP.Decode(remaining); return innerList.Select(x => x.RLPData).ToList(); } diff --git a/src/Stratis.SmartContracts.CLR/ContractTxData.cs b/src/Stratis.SmartContracts.CLR/ContractTxData.cs index 564ea64dd2..0b4813bb3a 100644 --- a/src/Stratis.SmartContracts.CLR/ContractTxData.cs +++ b/src/Stratis.SmartContracts.CLR/ContractTxData.cs @@ -1,5 +1,4 @@ using NBitcoin; -using Stratis.SmartContracts.Core; namespace Stratis.SmartContracts.CLR { diff --git a/src/Stratis.SmartContracts.CLR/IVirtualMachine.cs b/src/Stratis.SmartContracts.CLR/IVirtualMachine.cs index 7883c7e27e..4a7871bf0d 100644 --- a/src/Stratis.SmartContracts.CLR/IVirtualMachine.cs +++ b/src/Stratis.SmartContracts.CLR/IVirtualMachine.cs @@ -1,5 +1,4 @@ using Stratis.SmartContracts.Core.State; -using Stratis.SmartContracts.RuntimeObserver; namespace Stratis.SmartContracts.CLR { diff --git a/src/Stratis.SmartContracts.CLR/ReflectionVirtualMachine.cs b/src/Stratis.SmartContracts.CLR/ReflectionVirtualMachine.cs index f4314ce1dc..2a62da0d0e 100644 --- a/src/Stratis.SmartContracts.CLR/ReflectionVirtualMachine.cs +++ b/src/Stratis.SmartContracts.CLR/ReflectionVirtualMachine.cs @@ -8,7 +8,6 @@ using Stratis.SmartContracts.CLR.Exceptions; using Stratis.SmartContracts.CLR.ILRewrite; using Stratis.SmartContracts.CLR.Loader; -using Stratis.SmartContracts.CLR.Metering; using Stratis.SmartContracts.CLR.Validation; using Stratis.SmartContracts.Core.Hashing; using Stratis.SmartContracts.Core.State; diff --git a/src/Stratis.SmartContracts.CLR/Serialization/ContractPrimitiveSerializer.cs b/src/Stratis.SmartContracts.CLR/Serialization/ContractPrimitiveSerializer.cs index 213f79bfab..260df49d2c 100644 --- a/src/Stratis.SmartContracts.CLR/Serialization/ContractPrimitiveSerializer.cs +++ b/src/Stratis.SmartContracts.CLR/Serialization/ContractPrimitiveSerializer.cs @@ -225,7 +225,7 @@ private string ToString(byte[] val) private object DeserializeStruct(Type type, byte[] bytes) { - RLPCollection collection = (RLPCollection) RLP.Decode(bytes)[0]; + RLPCollection collection = (RLPCollection) RLP.Decode(bytes); object ret = Activator.CreateInstance(type); @@ -246,7 +246,7 @@ private object DeserializeArray(Type elementType, byte[] bytes) if (elementType == typeof(byte)) return bytes; - RLPCollection collection = (RLPCollection)RLP.Decode(bytes)[0]; + RLPCollection collection = (RLPCollection)RLP.Decode(bytes); Array ret = Array.CreateInstance(elementType, collection.Count); diff --git a/src/Stratis.SmartContracts.CLR/Serialization/MethodParameterByteSerializer.cs b/src/Stratis.SmartContracts.CLR/Serialization/MethodParameterByteSerializer.cs index 19dc83a7cf..cdb9708f31 100644 --- a/src/Stratis.SmartContracts.CLR/Serialization/MethodParameterByteSerializer.cs +++ b/src/Stratis.SmartContracts.CLR/Serialization/MethodParameterByteSerializer.cs @@ -36,9 +36,7 @@ public byte[] Serialize(object[] methodParameters) public object[] Deserialize(byte[] bytes) { - RLPCollection list = RLP.Decode(bytes); - - RLPCollection innerList = (RLPCollection)list[0]; + RLPCollection innerList = (RLPCollection)RLP.Decode(bytes); IList encodedParamBytes = innerList.Select(x => x.RLPData).ToList(); diff --git a/src/Stratis.SmartContracts.CLR/Serialization/Serializer.cs b/src/Stratis.SmartContracts.CLR/Serialization/Serializer.cs index a4cc973d6f..8fb0e94c33 100644 --- a/src/Stratis.SmartContracts.CLR/Serialization/Serializer.cs +++ b/src/Stratis.SmartContracts.CLR/Serialization/Serializer.cs @@ -262,7 +262,7 @@ public T ToStruct(byte[] val) where T: struct private T DeserializeStruct(byte[] bytes) where T : struct { - RLPCollection collection = (RLPCollection)RLP.Decode(bytes)[0]; + RLPCollection collection = (RLPCollection)RLP.Decode(bytes); Type type = typeof(T); diff --git a/src/Stratis.SmartContracts.Core.Tests/ChainIndexerRangeQueryTests.cs b/src/Stratis.SmartContracts.Core.Tests/ChainIndexerRangeQueryTests.cs index 157072bb97..a3bfac41ab 100644 --- a/src/Stratis.SmartContracts.Core.Tests/ChainIndexerRangeQueryTests.cs +++ b/src/Stratis.SmartContracts.Core.Tests/ChainIndexerRangeQueryTests.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; using System.Linq; using NBitcoin; -using Stratis.Bitcoin.Features.SmartContracts; using Stratis.Sidechains.Networks; -using Stratis.SmartContracts.Networks; using Xunit; namespace Stratis.SmartContracts.Core.Tests diff --git a/src/Stratis.SmartContracts.Core.Tests/Receipts/ReceiptMatcherTests.cs b/src/Stratis.SmartContracts.Core.Tests/Receipts/ReceiptMatcherTests.cs index a3ab38141d..d8649f08b5 100644 --- a/src/Stratis.SmartContracts.Core.Tests/Receipts/ReceiptMatcherTests.cs +++ b/src/Stratis.SmartContracts.Core.Tests/Receipts/ReceiptMatcherTests.cs @@ -3,7 +3,6 @@ using NBitcoin; using Stratis.Sidechains.Networks; using Stratis.SmartContracts.Core.Receipts; -using Stratis.SmartContracts.Networks; using Xunit; namespace Stratis.SmartContracts.Core.Tests.Receipts diff --git a/src/Stratis.SmartContracts.Core.Tests/Receipts/ReceiptSerializationTest.cs b/src/Stratis.SmartContracts.Core.Tests/Receipts/ReceiptSerializationTest.cs index eb86e265ed..f8f12f7bc2 100644 --- a/src/Stratis.SmartContracts.Core.Tests/Receipts/ReceiptSerializationTest.cs +++ b/src/Stratis.SmartContracts.Core.Tests/Receipts/ReceiptSerializationTest.cs @@ -58,6 +58,23 @@ public void Receipt_Serializes_And_Deserializes() TestStorageSerialize(receipt); } + [Fact] + public void SanityTest() + { + // https://github.com/Nethereum/Nethereum/issues/510 + + var item1 = new byte[] { 0x01 }; + var item2 = new byte[] { 0x01, 0x02 }; + + byte[] encoded = RLP.EncodeList(RLP.EncodeElement(item1), RLP.EncodeElement(item2)); + + RLPCollection decoded = (RLPCollection)RLP.Decode(encoded); + + // The actual list used to be at decoded[0]. Previously, these asserts would fail. + Assert.Equal(item1, decoded[0].RLPData); + Assert.Equal(item2, decoded[1].RLPData); + } + [Fact] public void Receipt_With_No_MethodName_Or_BlockNumber_Deserializes_Correctly() { diff --git a/src/Stratis.SmartContracts.Core.Tests/SenderRetrieverTests.cs b/src/Stratis.SmartContracts.Core.Tests/SenderRetrieverTests.cs index c18e2ad045..b4b43b974a 100644 --- a/src/Stratis.SmartContracts.Core.Tests/SenderRetrieverTests.cs +++ b/src/Stratis.SmartContracts.Core.Tests/SenderRetrieverTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Threading; using Moq; using NBitcoin; using Stratis.Bitcoin.Features.Consensus.CoinViews; diff --git a/src/Stratis.SmartContracts.Core.Tests/StateRepositoryTests.cs b/src/Stratis.SmartContracts.Core.Tests/StateRepositoryTests.cs index 87c6a264ac..748c8a5bb3 100644 --- a/src/Stratis.SmartContracts.Core.Tests/StateRepositoryTests.cs +++ b/src/Stratis.SmartContracts.Core.Tests/StateRepositoryTests.cs @@ -34,6 +34,7 @@ public class StateRepositoryTests private static readonly byte[] horseVal0 = "c0a0".HexToByteArray(); private static readonly uint160 testAddress = 111111; + // TODO: Create a proper test directory for this private const string DbreezeTestLocation = "C:/temp"; private const string DbreezeTestDb = "test"; diff --git a/src/Stratis.SmartContracts.Core/ChainIndexerRangeQuery.cs b/src/Stratis.SmartContracts.Core/ChainIndexerRangeQuery.cs index 76fd3f4246..d14e4f13f3 100644 --- a/src/Stratis.SmartContracts.Core/ChainIndexerRangeQuery.cs +++ b/src/Stratis.SmartContracts.Core/ChainIndexerRangeQuery.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using NBitcoin; namespace Stratis.SmartContracts.Core diff --git a/src/Stratis.SmartContracts.Core/Receipts/Log.cs b/src/Stratis.SmartContracts.Core/Receipts/Log.cs index 2e1b37ac88..03105df929 100644 --- a/src/Stratis.SmartContracts.Core/Receipts/Log.cs +++ b/src/Stratis.SmartContracts.Core/Receipts/Log.cs @@ -48,11 +48,8 @@ public byte[] ToBytesRlp() public static Log FromBytesRlp(byte[] bytes) { - RLPCollection list = RLP.Decode(bytes); - RLPCollection innerList = (RLPCollection)list[0]; - - RLPCollection topicList = RLP.Decode(innerList[1].RLPData); - RLPCollection innerTopicList = (RLPCollection)topicList[0]; + RLPCollection innerList = (RLPCollection)RLP.Decode(bytes); + RLPCollection innerTopicList = (RLPCollection)RLP.Decode(innerList[1].RLPData); IList topics = innerTopicList.Select(x => x.RLPData).ToList(); return new Log( diff --git a/src/Stratis.SmartContracts.Core/Receipts/Receipt.cs b/src/Stratis.SmartContracts.Core/Receipts/Receipt.cs index a7d202c5c4..65d209c790 100644 --- a/src/Stratis.SmartContracts.Core/Receipts/Receipt.cs +++ b/src/Stratis.SmartContracts.Core/Receipts/Receipt.cs @@ -212,11 +212,9 @@ public byte[] ToConsensusBytesRlp() /// public static Receipt FromConsensusBytesRlp(byte[] bytes) { - RLPCollection list = RLP.Decode(bytes); - RLPCollection innerList = (RLPCollection) list[0]; + RLPCollection innerList = (RLPCollection)RLP.Decode(bytes); - RLPCollection logList = RLP.Decode(innerList[3].RLPData); - RLPCollection innerLogList = (RLPCollection)logList[0]; + RLPCollection innerLogList = (RLPCollection)RLP.Decode(innerList[3].RLPData); Log[] logs = innerLogList.Select(x => Log.FromBytesRlp(x.RLPData)).ToArray(); return new Receipt( @@ -244,11 +242,8 @@ public uint256 GetHash() /// public static Receipt FromStorageBytesRlp(byte[] bytes) { - RLPCollection list = RLP.Decode(bytes); - RLPCollection innerList = (RLPCollection)list[0]; - - RLPCollection logList = RLP.Decode(innerList[3].RLPData); - RLPCollection innerLogList = (RLPCollection)logList[0]; + RLPCollection innerList = (RLPCollection)RLP.Decode(bytes); + RLPCollection innerLogList = (RLPCollection)RLP.Decode(innerList[3].RLPData); Log[] logs = innerLogList.Select(x => Log.FromBytesRlp(x.RLPData)).ToArray(); // Method name is the 15th item to be added. Existing receipts without this data will throw exceptions without this check. diff --git a/src/Stratis.SmartContracts.Core/State/AccountAbstractionLayer/ContractUnspentOutput.cs b/src/Stratis.SmartContracts.Core/State/AccountAbstractionLayer/ContractUnspentOutput.cs index 040390184e..6d210cba4b 100644 --- a/src/Stratis.SmartContracts.Core/State/AccountAbstractionLayer/ContractUnspentOutput.cs +++ b/src/Stratis.SmartContracts.Core/State/AccountAbstractionLayer/ContractUnspentOutput.cs @@ -29,8 +29,7 @@ public ContractUnspentOutput() { } public ContractUnspentOutput(byte[] bytes) { - RLPCollection list = RLP.Decode(bytes); - RLPCollection innerList = (RLPCollection)list[0]; + RLPCollection innerList = (RLPCollection)RLP.Decode(bytes); this.Hash = new uint256(innerList[0].RLPData); this.Nvout = BitConverter.ToUInt32(innerList[1].RLPData, 0); this.Value = BitConverter.ToUInt64(innerList[2].RLPData, 0); diff --git a/src/Stratis.SmartContracts.Core/State/AccountState.cs b/src/Stratis.SmartContracts.Core/State/AccountState.cs index 0551b54882..126a99578a 100644 --- a/src/Stratis.SmartContracts.Core/State/AccountState.cs +++ b/src/Stratis.SmartContracts.Core/State/AccountState.cs @@ -35,8 +35,8 @@ public AccountState() { } public AccountState(byte[] bytes) : this() { - RLPCollection list = RLP.Decode(bytes); - RLPCollection innerList = (RLPCollection)list[0]; + RLPCollection innerList = (RLPCollection)RLP.Decode(bytes); + this.CodeHash = innerList[0].RLPData; this.StateRoot = innerList[1].RLPData; this.UnspentHash = innerList[2].RLPData; diff --git a/src/Stratis.SmartContracts.Core/Stratis.SmartContracts.Core.csproj b/src/Stratis.SmartContracts.Core/Stratis.SmartContracts.Core.csproj index 3d6cd65ed5..c29d7cef28 100644 --- a/src/Stratis.SmartContracts.Core/Stratis.SmartContracts.Core.csproj +++ b/src/Stratis.SmartContracts.Core/Stratis.SmartContracts.Core.csproj @@ -12,11 +12,10 @@ - + - @@ -24,6 +23,7 @@ + diff --git a/src/Stratis.SmartContracts.IntegrationTests/ContractExecutionFailureTests.cs b/src/Stratis.SmartContracts.IntegrationTests/ContractExecutionFailureTests.cs index eb659875f5..d6dad303e9 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/ContractExecutionFailureTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/ContractExecutionFailureTests.cs @@ -12,7 +12,6 @@ using Stratis.SmartContracts.CLR; using Stratis.SmartContracts.CLR.Compilation; using Stratis.SmartContracts.CLR.Serialization; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Core.Util; using Stratis.SmartContracts.RuntimeObserver; using Stratis.SmartContracts.Tests.Common.MockChain; diff --git a/src/Stratis.SmartContracts.IntegrationTests/ContractInternalTransferTests.cs b/src/Stratis.SmartContracts.IntegrationTests/ContractInternalTransferTests.cs index 5278555159..75ab50c5cc 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/ContractInternalTransferTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/ContractInternalTransferTests.cs @@ -1,13 +1,11 @@ using System; using System.Linq; using NBitcoin; -using Stratis.Bitcoin.Features.SmartContracts; using Stratis.Bitcoin.Features.SmartContracts.Models; using Stratis.SmartContracts.CLR; using Stratis.SmartContracts.CLR.Compilation; using Stratis.SmartContracts.CLR.Local; using Stratis.SmartContracts.CLR.Serialization; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Core.Util; using Stratis.SmartContracts.Tests.Common.MockChain; using Xunit; diff --git a/src/Stratis.SmartContracts.IntegrationTests/ContractParameterSerializationTests.cs b/src/Stratis.SmartContracts.IntegrationTests/ContractParameterSerializationTests.cs index 8d1c467d38..6ec3b8ac06 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/ContractParameterSerializationTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/ContractParameterSerializationTests.cs @@ -1,7 +1,6 @@ using System; using System.Text; using NBitcoin; -using Stratis.Bitcoin.Features.SmartContracts; using Stratis.Bitcoin.Features.SmartContracts.Models; using Stratis.SmartContracts.CLR; using Stratis.SmartContracts.CLR.Compilation; diff --git a/src/Stratis.SmartContracts.IntegrationTests/Interop/ConversionRequestTests.cs b/src/Stratis.SmartContracts.IntegrationTests/Interop/ConversionRequestTests.cs new file mode 100644 index 0000000000..249e35f8ba --- /dev/null +++ b/src/Stratis.SmartContracts.IntegrationTests/Interop/ConversionRequestTests.cs @@ -0,0 +1,87 @@ +using Stratis.Bitcoin.Tests.Common; +using Stratis.Bitcoin.Utilities; +using Stratis.Features.FederatedPeg.Conversion; +using Stratis.SmartContracts.Networks; +using Xunit; + +namespace Stratis.SmartContracts.IntegrationTests +{ + public class ConversionRequestTests + { + [Fact] + public void CanSaveConversionRequest() + { + var network = new SmartContractsPoARegTest(); + var dataFolder = TestBase.CreateDataFolder(this, network: network); + + var serializer = new DBreezeSerializer(network.Consensus.ConsensusFactory); + var kvs = new ConversionRequestKeyValueStore(dataFolder, serializer); + + var repo = new ConversionRequestRepository(kvs); + + var request = new ConversionRequest() + { + RequestId = "requestId", + RequestType = (int)ConversionRequestType.Mint, + Processed = false, + RequestStatus = (int)ConversionRequestStatus.Unprocessed, + Amount = 100000000, + BlockHeight = 123, + DestinationAddress = "" + }; + + repo.Save(request); + } + + [Fact] + public void CanRetrieveSavedConversionRequest() + { + var network = new SmartContractsPoARegTest(); + var dataFolder = TestBase.CreateDataFolder(this, network: network); + + var serializer = new DBreezeSerializer(network.Consensus.ConsensusFactory); + var kvs = new ConversionRequestKeyValueStore(dataFolder, serializer); + + var repo = new ConversionRequestRepository(kvs); + + var request = new ConversionRequest() + { + RequestId = "requestId", + RequestType = (int)ConversionRequestType.Mint, + Processed = false, + RequestStatus = (int)ConversionRequestStatus.Unprocessed, + Amount = 100000000, + BlockHeight = 123, + DestinationAddress = "" + }; + + repo.Save(request); + + var request2 = repo.Get(request.RequestId); + + Assert.Equal(request.RequestId, request2.RequestId); + Assert.Equal(request.RequestType, request2.RequestType); + Assert.Equal(request.Processed, request2.Processed); + Assert.Equal(request.RequestStatus, request2.RequestStatus); + Assert.Equal(request.Amount, request2.Amount); + Assert.Equal(request.BlockHeight, request2.BlockHeight); + Assert.Equal(request.DestinationAddress, request2.DestinationAddress); + } + + [Fact] + public void RetrievingInvalidRequestIdFails() + { + var network = new SmartContractsPoARegTest(); + var dataFolder = TestBase.CreateDataFolder(this, network: network); + + var serializer = new DBreezeSerializer(network.Consensus.ConsensusFactory); + var kvs = new ConversionRequestKeyValueStore(dataFolder, serializer); + + var repo = new ConversionRequestRepository(kvs); + + var request = repo.Get("nonexistent"); + + Assert.Null(request); + } + } +} diff --git a/src/Stratis.SmartContracts.IntegrationTests/PoW/ContractCreationTests.cs b/src/Stratis.SmartContracts.IntegrationTests/PoW/ContractCreationTests.cs index 90e609000f..529b65b1ae 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/PoW/ContractCreationTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/PoW/ContractCreationTests.cs @@ -1,11 +1,9 @@ using System; using System.Text; using NBitcoin; -using Stratis.Bitcoin.Features.SmartContracts; using Stratis.Bitcoin.Features.SmartContracts.Models; using Stratis.SmartContracts.CLR; using Stratis.SmartContracts.CLR.Compilation; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Tests.Common.MockChain; using Xunit; diff --git a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMemoryPoolTests.cs b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMemoryPoolTests.cs index 237d28ef05..69f8935f8e 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMemoryPoolTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMemoryPoolTests.cs @@ -7,7 +7,6 @@ using Stratis.Bitcoin.Tests.Common; using Stratis.SmartContracts.CLR; using Stratis.SmartContracts.CLR.Serialization; -using Stratis.SmartContracts.Core; using Stratis.SmartContracts.Core.State; using Stratis.SmartContracts.Tests.Common; using Xunit; diff --git a/src/Stratis.SmartContracts.IntegrationTests/WhitelistedContractTests.cs b/src/Stratis.SmartContracts.IntegrationTests/WhitelistedContractTests.cs index 91bb2c3b5c..a60dc18b5d 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/WhitelistedContractTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/WhitelistedContractTests.cs @@ -1,9 +1,6 @@ using System; -using System.Threading; -using System.Threading.Tasks; using NBitcoin; using Stratis.Bitcoin.Features.SmartContracts.Models; -using Stratis.Bitcoin.Features.Wallet.Interfaces; using Stratis.Bitcoin.IntegrationTests.Common.EnvironmentMockUpHelpers; using Stratis.SmartContracts.CLR.Compilation; using Stratis.SmartContracts.Networks; diff --git a/src/Stratis.SmartContracts.Networks/SmartContractsRegTest.cs b/src/Stratis.SmartContracts.Networks/SmartContractsRegTest.cs index aaedecd938..1e692b8e46 100644 --- a/src/Stratis.SmartContracts.Networks/SmartContractsRegTest.cs +++ b/src/Stratis.SmartContracts.Networks/SmartContractsRegTest.cs @@ -6,7 +6,6 @@ using Stratis.Bitcoin.Features.Consensus.Rules.CommonRules; using Stratis.Bitcoin.Features.MemoryPool.Rules; using Stratis.Bitcoin.Features.PoA.Policies; -using Stratis.Bitcoin.Features.SmartContracts; using Stratis.Bitcoin.Features.SmartContracts.MempoolRules; using Stratis.Bitcoin.Features.SmartContracts.PoW; using Stratis.Bitcoin.Features.SmartContracts.PoW.Rules; From e564910a7f0b7ac53f39ce993466b026498bea78 Mon Sep 17 00:00:00 2001 From: Kevin Loubser Date: Wed, 31 Mar 2021 22:36:34 +0200 Subject: [PATCH 04/13] Post-merge fixes --- .../Controllers/ExternalApiController.cs | 4 +- .../ExternalApiFeature.cs | 6 +-- .../ExternalApiPoller.cs | 17 +++++- .../Utils/MainChainFederationNodeRunner.cs | 1 + .../FederationGatewayControllerTests.cs | 5 +- .../CrossChainTransferStoreTests.cs | 4 +- .../DepositExtractorTests.cs | 4 +- .../Distribution/RewardClaimerTests.cs | 7 ++- .../MaturedBlocksProviderTests.cs | 53 ++++++++++--------- .../SourceChain/DepositExtractor.cs | 4 +- .../SourceChain/MaturedBlocksProvider.cs | 4 +- 11 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/Controllers/ExternalApiController.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/Controllers/ExternalApiController.cs index 3c30d2c3e7..7758f6f0d1 100644 --- a/src/Stratis.Bitcoin.Features.ExternalAPI/Controllers/ExternalApiController.cs +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/Controllers/ExternalApiController.cs @@ -11,11 +11,11 @@ namespace Stratis.Features.ExternalApi.Controllers [Route("api/[controller]")] public class ExternalApiController : Controller { - private readonly ExternalApiPoller externalApiPoller; + private readonly IExternalApiPoller externalApiPoller; private readonly ILogger logger; - public ExternalApiController(ExternalApiPoller externalApiPoller) + public ExternalApiController(IExternalApiPoller externalApiPoller) { this.externalApiPoller = externalApiPoller; this.logger = LogManager.GetCurrentClassLogger(); diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiFeature.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiFeature.cs index 4c35b74c58..d976b50157 100644 --- a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiFeature.cs +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiFeature.cs @@ -8,9 +8,9 @@ namespace Stratis.Bitcoin.Features.ExternalApi { public sealed class ExternalApiFeature : FullNodeFeature { - private readonly ExternalApiPoller externalApiPoller; + private readonly IExternalApiPoller externalApiPoller; - public ExternalApiFeature(ExternalApiPoller externalApiPoller) + public ExternalApiFeature(IExternalApiPoller externalApiPoller) { this.externalApiPoller = externalApiPoller; } @@ -39,7 +39,7 @@ public static IFullNodeBuilder AddExternalApi(this IFullNodeBuilder fullNodeBuil .AddFeature() .FeatureServices(services => services .AddSingleton() - .AddSingleton() + .AddSingleton() )); return fullNodeBuilder; diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs index 31c297b1c7..e3ef90a42f 100644 --- a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiPoller.cs @@ -7,7 +7,22 @@ namespace Stratis.Bitcoin.Features.ExternalApi { - public class ExternalApiPoller : IDisposable + public interface IExternalApiPoller : IDisposable + { + void Initialize(); + + decimal GetStratisPrice(); + + decimal GetEthereumPrice(); + + int GetGasPrice(); + + decimal EstimateConversionTransactionGas(); + + decimal EstimateConversionTransactionFee(); + } + + public class ExternalApiPoller : IExternalApiPoller { // TODO: This should be linked to the setting in the interop feature public const int QuorumSize = 6; diff --git a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/MainChainFederationNodeRunner.cs b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/MainChainFederationNodeRunner.cs index 3dd697d071..2ab0869414 100644 --- a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/MainChainFederationNodeRunner.cs +++ b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/MainChainFederationNodeRunner.cs @@ -49,6 +49,7 @@ public override void BuildNode() .UsePosConsensus() .UseWallet() .AddSQLiteWalletRepository() + // TODO: Change to true and fix tests .AddPowPosMining(false) .MockIBD() .Build(); diff --git a/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationGatewayControllerTests.cs b/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationGatewayControllerTests.cs index 6d4084b394..d074a678ae 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationGatewayControllerTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationGatewayControllerTests.cs @@ -15,6 +15,7 @@ using Stratis.Bitcoin.Connection; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.BlockStore; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Features.PoA; using Stratis.Bitcoin.Features.PoA.Voting; using Stratis.Bitcoin.Networks; @@ -110,7 +111,9 @@ private MaturedBlocksProvider GetMaturedBlocksProvider(IFederatedPegSettings fed return blocks; }); - return new MaturedBlocksProvider(this.consensusManager, this.depositExtractor, federatedPegSettings); + IExternalApiPoller externalApiPoller = Substitute.For(); + + return new MaturedBlocksProvider(this.consensusManager, this.depositExtractor, federatedPegSettings, externalApiPoller); } [Fact] diff --git a/src/Stratis.Features.FederatedPeg.Tests/CrossChainTransferStoreTests.cs b/src/Stratis.Features.FederatedPeg.Tests/CrossChainTransferStoreTests.cs index 601983acd7..33de90b742 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/CrossChainTransferStoreTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/CrossChainTransferStoreTests.cs @@ -10,6 +10,7 @@ using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Controllers; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Bitcoin.Features.Wallet.Models; using Stratis.Bitcoin.Tests.Common; @@ -604,7 +605,8 @@ public void DoTest() var transaction = new PosTransaction(model.Hex); var reader = new OpReturnDataReader(new CounterChainNetworkWrapper(CirrusNetwork.NetworksSelector.Testnet())); - var extractor = new DepositExtractor(this.federatedPegSettings, this.network, reader); + IExternalApiPoller externalApiPoller = Substitute.For(); + var extractor = new DepositExtractor(this.federatedPegSettings, this.network, reader, externalApiPoller); IDeposit deposit = extractor.ExtractDepositFromTransaction(transaction, 2, 1); Assert.NotNull(deposit); diff --git a/src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs b/src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs index 6c6503883e..1d21cefcfa 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs @@ -4,6 +4,7 @@ using FluentAssertions; using NBitcoin; using NSubstitute; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Networks; using Stratis.Features.FederatedPeg.Interfaces; using Stratis.Features.FederatedPeg.SourceChain; @@ -38,7 +39,8 @@ public DepositExtractorTests() this.opReturnDataReader = Substitute.For(); this.opReturnDataReader.TryGetTargetAddress(null, out string address).Returns(callInfo => { callInfo[1] = null; return false; }); - this.depositExtractor = new DepositExtractor(this.federationSettings, this.network, this.opReturnDataReader); + IExternalApiPoller externalApiPoller = Substitute.For(); + this.depositExtractor = new DepositExtractor(this.federationSettings, this.network, this.opReturnDataReader, externalApiPoller); this.transactionBuilder = new TestTransactionBuilder(); } diff --git a/src/Stratis.Features.FederatedPeg.Tests/Distribution/RewardClaimerTests.cs b/src/Stratis.Features.FederatedPeg.Tests/Distribution/RewardClaimerTests.cs index 4d6bcaf4f6..39f6176d49 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/Distribution/RewardClaimerTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/Distribution/RewardClaimerTests.cs @@ -4,6 +4,7 @@ using NSubstitute; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Consensus; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Features.Wallet.Interfaces; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Networks; @@ -86,7 +87,8 @@ public void RewardClaimer_RetrieveSingleDeposits() this.blocks = ChainedHeadersHelper.CreateConsecutiveHeadersAndBlocks(30, true, network: this.network, chainIndexer: this.chainIndexer, withCoinbaseAndCoinStake: true, createCirrusReward: true); using (var rewardClaimer = new RewardClaimer(this.broadCasterManager, this.chainIndexer, this.consensusManager, this.initialBlockDownloadState, keyValueRepository, this.network, this.signals)) { - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); // Add 5 distribution deposits from block 11 through to 15. for (int i = 11; i <= 15; i++) @@ -122,7 +124,8 @@ public void RewardClaimer_RetrieveBatchedDeposits() Assert.Equal(2, rewardTransaction.Outputs.Count); Assert.Equal(Money.Coins(90), rewardTransaction.TotalOut); - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); IDeposit deposit = depositExtractor.ExtractDepositFromTransaction(rewardTransaction, 30, this.blocks[30].Block.GetHash()); Assert.Equal(Money.Coins(90), deposit.Amount); } diff --git a/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksProviderTests.cs b/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksProviderTests.cs index 010f54a840..6a5f0c9d05 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksProviderTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksProviderTests.cs @@ -7,6 +7,7 @@ using NSubstitute; using NSubstitute.Core; using Stratis.Bitcoin.Consensus; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Networks; using Stratis.Bitcoin.Primitives; using Stratis.Bitcoin.Tests.Common; @@ -91,7 +92,8 @@ public void GetMaturedBlocksReturnsDeposits() this.consensusManager.Tip.Returns(tip); // Makes every block a matured block. - var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, federatedPegSettings); + IExternalApiPoller externalApiPoller = Substitute.For(); + var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, federatedPegSettings, externalApiPoller); SerializableResult> depositsResult = maturedBlocksProvider.RetrieveDeposits(0); @@ -137,9 +139,9 @@ public void RetrieveDeposits_ReturnsDataToAdvanceNextMaturedBlockHeight() return hashes.Select((hash) => this.blocks.Single(x => x.ChainedHeader.HashBlock == hash && x.ChainedHeader.Height <= this.consensusManager.Tip.Height)).ToArray(); }); - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); - - var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); + var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings, externalApiPoller); int nextMaturedBlockHeight = 1; for (int i = 1; i < this.blocks.Count; i++) @@ -198,9 +200,9 @@ public void RetrieveDeposits_ReturnsSmallAndNormalDeposits_Scenario2() }); this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader); - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); - - var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); + var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings, externalApiPoller); SerializableResult> depositsResult = maturedBlocksProvider.RetrieveDeposits(5); @@ -251,10 +253,10 @@ public void RetrieveDeposits_ReturnsSmallAndNormalDeposits_Scenario3() } this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); - - var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings); + var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings, externalApiPoller); SerializableResult> depositsResult = maturedBlocksProvider.RetrieveDeposits(5); @@ -308,9 +310,9 @@ public void RetrieveDeposits_ReturnsSmallAndNormalDeposits_Scenario4() }); this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader); - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); - - var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); + var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings, externalApiPoller); SerializableResult> depositsResult = maturedBlocksProvider.RetrieveDeposits(10); @@ -365,9 +367,9 @@ public void RetrieveDeposits_ReturnsSmallAndNormalDeposits_Scenario5() }); this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader); - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); - - var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); + var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings, externalApiPoller); SerializableResult> depositsResult = maturedBlocksProvider.RetrieveDeposits(10); @@ -421,10 +423,9 @@ public void RetrieveDeposits_ReturnsLargeDeposits_Scenario6() } this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader); - - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); - - var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); + var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings, externalApiPoller); SerializableResult> depositsResult = maturedBlocksProvider.RetrieveDeposits(10); @@ -484,9 +485,9 @@ public void RetrieveDeposits_ReturnsLargeDeposits_Scenario7() }); this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader); - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); - - var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); + var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings, externalApiPoller); SerializableResult> depositsResult = maturedBlocksProvider.RetrieveDeposits(10); @@ -547,9 +548,9 @@ public void RetrieveDeposits_ReturnsLargeDeposits_Scenario8() }); this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader); - var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader); - - var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings); + IExternalApiPoller externalApiPoller = Substitute.For(); + var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader, externalApiPoller); + var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings, externalApiPoller); SerializableResult> depositsResult = maturedBlocksProvider.RetrieveDeposits(10); diff --git a/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs b/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs index 2f4eb6b7b9..b5a5b8a2e1 100644 --- a/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs +++ b/src/Stratis.Features.FederatedPeg/SourceChain/DepositExtractor.cs @@ -22,10 +22,10 @@ public sealed class DepositExtractor : IDepositExtractor private readonly IFederatedPegSettings federatedPegSettings; private readonly Network network; private readonly IOpReturnDataReader opReturnDataReader; - private readonly ExternalApiPoller externalApiPoller; + private readonly IExternalApiPoller externalApiPoller; private readonly ILogger logger; - public DepositExtractor(IFederatedPegSettings federatedPegSettings, Network network, IOpReturnDataReader opReturnDataReader, ExternalApiPoller externalApiPoller) + public DepositExtractor(IFederatedPegSettings federatedPegSettings, Network network, IOpReturnDataReader opReturnDataReader, IExternalApiPoller externalApiPoller) { this.depositScript = federatedPegSettings.MultiSigRedeemScript.PaymentScript; this.federatedPegSettings = federatedPegSettings; diff --git a/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs b/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs index 1b8acae3e9..3ead49238d 100644 --- a/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs +++ b/src/Stratis.Features.FederatedPeg/SourceChain/MaturedBlocksProvider.cs @@ -51,9 +51,9 @@ public sealed class MaturedBlocksProvider : IMaturedBlocksProvider private readonly IFederatedPegSettings federatedPegSettings; private readonly ILogger logger; private readonly Dictionary retrievalTypeConfirmations; - private readonly ExternalApiPoller externalApiPoller; + private readonly IExternalApiPoller externalApiPoller; - public MaturedBlocksProvider(IConsensusManager consensusManager, IDepositExtractor depositExtractor, IFederatedPegSettings federatedPegSettings, ExternalApiPoller externalApiPoller) + public MaturedBlocksProvider(IConsensusManager consensusManager, IDepositExtractor depositExtractor, IFederatedPegSettings federatedPegSettings, IExternalApiPoller externalApiPoller) { this.consensusManager = consensusManager; this.depositExtractor = depositExtractor; From 622ff02aea733de1fa2ae40f002c10e8468a81bc Mon Sep 17 00:00:00 2001 From: Francois de la Rouviere Date: Thu, 1 Apr 2021 09:51:56 +0100 Subject: [PATCH 05/13] [Masternode] Migrate Powershell Script to .NET Console app (#488) * Add project * Start and initialize main chain node * Add main net sync check * Update NodeController.cs * Add main and side chain start up checks * Add collateral wallet checks * Add wallet fee checks for both chains * Testing * Tested up to both nodes syncing and wallet gen * Add federation key check/generation * Add monitoring * Self Review * Self Review * Changes based on review * Fix Tests --- .../NodeController.cs | 7 +- .../Models/AddressIndexerTipModel.cs | 4 + .../InitialBlockDownloadTest.cs | 8 +- .../Rules/CommonRules/PosCoinViewRuleTest.cs | 2 +- .../CommonRules/StraxCoinViewRuleTests.cs | 2 +- .../TestChainFactory.cs | 2 +- .../Models/FederationMemberModel.cs | 3 +- .../Voting/FederationController.cs | 12 +- .../WalletControllerTest.cs | 2 +- .../Models/RequestModels.cs | 1 - .../Models/WalletGeneralInfoModel.cs | 4 +- .../Services/WalletService.cs | 2 +- .../API/ApiSteps.cs | 2 +- .../Wallet/WalletOperationsTests.cs | 2 +- .../Controllers/NodeControllerTest.cs | 43 +- .../Base/InitialBlockDownloadState.cs | 10 +- .../Controllers/Models/StatusModel.cs | 3 + src/Stratis.CirrusMinerD/Program.cs | 2 +- src/Stratis.CirrusPegD/Program.cs | 2 +- src/Stratis.External.Masternodes/Program.cs | 29 + .../RegistrationService.cs | 524 ++++++++++++++++++ .../Stratis.External.Masternodes.csproj | 20 + .../CollateralFeature.cs | 5 +- .../Utils/SidechainFederationNodeRunner.cs | 2 +- .../Utils/SidechainMinerNodeRunner.cs | 2 +- .../FederationWalletControllerTests.cs | 2 +- .../Controllers/FederationWalletController.cs | 2 +- .../FederatedPegFeature.cs | 2 - src/Stratis.FullNode.sln | 7 + 29 files changed, 645 insertions(+), 63 deletions(-) create mode 100644 src/Stratis.External.Masternodes/Program.cs create mode 100644 src/Stratis.External.Masternodes/RegistrationService.cs create mode 100644 src/Stratis.External.Masternodes/Stratis.External.Masternodes.csproj diff --git a/src/Stratis.Bitcoin.Features.Api/NodeController.cs b/src/Stratis.Bitcoin.Features.Api/NodeController.cs index b9ed0bd069..934a2b4c5b 100644 --- a/src/Stratis.Bitcoin.Features.Api/NodeController.cs +++ b/src/Stratis.Bitcoin.Features.Api/NodeController.cs @@ -71,6 +71,8 @@ public class NodeController : Controller /// An interface implementation used to retrieve unspent transactions. private readonly IGetUnspentTransaction getUnspentTransaction; + private readonly IInitialBlockDownloadState initialBlockDownloadState; + /// Specification of the network the node runs on. private Network network; // Not readonly because of ValidateAddress @@ -97,6 +99,7 @@ public NodeController( ISelfEndpointTracker selfEndpointTracker, IConsensusManager consensusManager, IBlockStore blockStore, + IInitialBlockDownloadState initialBlockDownloadState, IGetUnspentTransaction getUnspentTransaction = null, INetworkDifficulty networkDifficulty = null, IPooledGetUnspentTransaction pooledGetUnspentTransaction = null, @@ -127,6 +130,7 @@ public NodeController( this.consensusManager = consensusManager; this.blockStore = blockStore; this.getUnspentTransaction = getUnspentTransaction; + this.initialBlockDownloadState = initialBlockDownloadState; this.networkDifficulty = networkDifficulty; this.pooledGetUnspentTransaction = pooledGetUnspentTransaction; this.pooledTransaction = pooledTransaction; @@ -158,7 +162,8 @@ public IActionResult Status() RunningTime = this.dateTimeProvider.GetUtcNow() - this.fullNode.StartTime, CoinTicker = this.network.CoinTicker, State = this.fullNode.State.ToString(), - BestPeerHeight = this.chainState.BestPeerTip?.Height + BestPeerHeight = this.chainState.BestPeerTip?.Height, + InIbd = this.initialBlockDownloadState.IsInitialBlockDownload() }; // Add the list of features that are enabled. diff --git a/src/Stratis.Bitcoin.Features.BlockStore/Models/AddressIndexerTipModel.cs b/src/Stratis.Bitcoin.Features.BlockStore/Models/AddressIndexerTipModel.cs index 9cc98a30b7..20f91805a2 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/Models/AddressIndexerTipModel.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/Models/AddressIndexerTipModel.cs @@ -1,10 +1,14 @@ using NBitcoin; +using Newtonsoft.Json; +using Stratis.Bitcoin.Utilities.JsonConverters; namespace Stratis.Bitcoin.Features.BlockStore.Models { public sealed class AddressIndexerTipModel { + [JsonConverter(typeof(UInt256JsonConverter))] public uint256 TipHash { get; set; } + public int? TipHeight { get; set; } } } \ No newline at end of file diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/InitialBlockDownloadTest.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/InitialBlockDownloadTest.cs index f486a8bab3..d7897826a9 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/InitialBlockDownloadTest.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/InitialBlockDownloadTest.cs @@ -34,7 +34,7 @@ public InitialBlockDownloadTest() public void InIBDIfChainTipIsNull() { this.chainState.ConsensusTip = null; - var blockDownloadState = new InitialBlockDownloadState(this.chainState, this.network, this.consensusSettings, this.checkpoints, this.loggerFactory.Object, DateTimeProvider.Default); + var blockDownloadState = new InitialBlockDownloadState(this.chainState, this.network, this.consensusSettings, this.checkpoints, DateTimeProvider.Default); Assert.True(blockDownloadState.IsInitialBlockDownload()); } @@ -43,7 +43,7 @@ public void InIBDIfBehindCheckpoint() { BlockHeader blockHeader = this.network.Consensus.ConsensusFactory.CreateBlockHeader(); this.chainState.ConsensusTip = new ChainedHeader(blockHeader, blockHeader.GetHash(), 1000); - var blockDownloadState = new InitialBlockDownloadState(this.chainState, this.network, this.consensusSettings, this.checkpoints, this.loggerFactory.Object, DateTimeProvider.Default); + var blockDownloadState = new InitialBlockDownloadState(this.chainState, this.network, this.consensusSettings, this.checkpoints, DateTimeProvider.Default); Assert.True(blockDownloadState.IsInitialBlockDownload()); } @@ -52,7 +52,7 @@ public void InIBDIfChainWorkIsLessThanMinimum() { BlockHeader blockHeader = this.network.Consensus.ConsensusFactory.CreateBlockHeader(); this.chainState.ConsensusTip = new ChainedHeader(blockHeader, blockHeader.GetHash(), this.checkpoints.GetLastCheckpointHeight() + 1); - var blockDownloadState = new InitialBlockDownloadState(this.chainState, this.network, this.consensusSettings, this.checkpoints, this.loggerFactory.Object, DateTimeProvider.Default); + var blockDownloadState = new InitialBlockDownloadState(this.chainState, this.network, this.consensusSettings, this.checkpoints, DateTimeProvider.Default); Assert.True(blockDownloadState.IsInitialBlockDownload()); } @@ -68,7 +68,7 @@ public void InIBDIfTipIsOlderThanMaxAge() blockHeader.Time = ((uint) DateTimeOffset.Now.ToUnixTimeSeconds()) - (uint) this.network.MaxTipAge - 1; this.chainState.ConsensusTip = new ChainedHeader(blockHeader, blockHeader.GetHash(), this.checkpoints.GetLastCheckpointHeight() + 1); - var blockDownloadState = new InitialBlockDownloadState(this.chainState, this.network, this.consensusSettings, this.checkpoints, this.loggerFactory.Object, DateTimeProvider.Default); + var blockDownloadState = new InitialBlockDownloadState(this.chainState, this.network, this.consensusSettings, this.checkpoints, DateTimeProvider.Default); Assert.True(blockDownloadState.IsInitialBlockDownload()); } } diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs index 8722b8fda4..4948d6612b 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs @@ -35,7 +35,7 @@ public class PosCoinViewRuleTests : TestPosConsensusRulesUnitTestBase private async Task CreateConsensusManagerAsync(Dictionary unspentOutputs) { this.consensusSettings = new ConsensusSettings(Configuration.NodeSettings.Default(this.network)); - var initialBlockDownloadState = new InitialBlockDownloadState(this.chainState.Object, this.network, this.consensusSettings, new Checkpoints(), this.loggerFactory.Object, DateTimeProvider.Default); + var initialBlockDownloadState = new InitialBlockDownloadState(this.chainState.Object, this.network, this.consensusSettings, new Checkpoints(), DateTimeProvider.Default); var signals = new Signals.Signals(this.loggerFactory.Object, null); var asyncProvider = new AsyncProvider(this.loggerFactory.Object, signals); diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/StraxCoinViewRuleTests.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/StraxCoinViewRuleTests.cs index 240b284609..669d820221 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/StraxCoinViewRuleTests.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/StraxCoinViewRuleTests.cs @@ -35,7 +35,7 @@ public class StraxCoinViewRuleTests : TestPosConsensusRulesUnitTestBase private async Task CreateConsensusManagerAsync(Dictionary unspentOutputs) { this.consensusSettings = new ConsensusSettings(Configuration.NodeSettings.Default(this.network)); - var initialBlockDownloadState = new InitialBlockDownloadState(this.chainState.Object, this.network, this.consensusSettings, new Checkpoints(), this.loggerFactory.Object, DateTimeProvider.Default); + var initialBlockDownloadState = new InitialBlockDownloadState(this.chainState.Object, this.network, this.consensusSettings, new Checkpoints(), DateTimeProvider.Default); var signals = new Signals.Signals(this.loggerFactory.Object, null); var asyncProvider = new AsyncProvider(this.loggerFactory.Object, signals); diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs index 62dfe27d35..59d4265931 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs @@ -116,7 +116,7 @@ public static async Task CreateAsync(Network network, string d testChainContext.Checkpoints = new Checkpoints(); testChainContext.ChainIndexer = new ChainIndexer(network); testChainContext.ChainState = new ChainState(); - testChainContext.InitialBlockDownloadState = new InitialBlockDownloadState(testChainContext.ChainState, testChainContext.Network, consensusSettings, new Checkpoints(), testChainContext.NodeSettings.LoggerFactory, testChainContext.DateTimeProvider); + testChainContext.InitialBlockDownloadState = new InitialBlockDownloadState(testChainContext.ChainState, testChainContext.Network, consensusSettings, new Checkpoints(), testChainContext.DateTimeProvider); var inMemoryCoinView = new InMemoryCoinView(new HashHeightPair(testChainContext.ChainIndexer.Tip)); var cachedCoinView = new CachedCoinView(network, new Checkpoints(), inMemoryCoinView, DateTimeProvider.Default, testChainContext.LoggerFactory, new NodeStats(testChainContext.DateTimeProvider, testChainContext.NodeSettings, new Mock().Object), new ConsensusSettings(testChainContext.NodeSettings)); diff --git a/src/Stratis.Bitcoin.Features.PoA/Models/FederationMemberModel.cs b/src/Stratis.Bitcoin.Features.PoA/Models/FederationMemberModel.cs index 1e7c0855fc..547bfc4fce 100644 --- a/src/Stratis.Bitcoin.Features.PoA/Models/FederationMemberModel.cs +++ b/src/Stratis.Bitcoin.Features.PoA/Models/FederationMemberModel.cs @@ -1,5 +1,4 @@ using System; -using NBitcoin; using Newtonsoft.Json; namespace Stratis.Bitcoin.Features.PoA.Models @@ -7,7 +6,7 @@ namespace Stratis.Bitcoin.Features.PoA.Models public class FederationMemberModel { [JsonProperty("pubkey")] - public PubKey PubKey { get; set; } + public string PubKey { get; set; } [JsonProperty("collateralAmount")] public decimal CollateralAmount { get; set; } diff --git a/src/Stratis.Bitcoin.Features.PoA/Voting/FederationController.cs b/src/Stratis.Bitcoin.Features.PoA/Voting/FederationController.cs index e07bb5731d..bc861e5634 100644 --- a/src/Stratis.Bitcoin.Features.PoA/Voting/FederationController.cs +++ b/src/Stratis.Bitcoin.Features.PoA/Voting/FederationController.cs @@ -83,10 +83,10 @@ public IActionResult GetCurrentMemberInfo() var federationMemberModel = new FederationMemberDetailedModel { - PubKey = this.federationManager.CurrentFederationKey.PubKey + PubKey = this.federationManager.CurrentFederationKey.PubKey.ToHex() }; - KeyValuePair lastActive = this.idleFederationMembersKicker.GetFederationMembersByLastActiveTime().FirstOrDefault(x => x.Key == federationMemberModel.PubKey); + KeyValuePair lastActive = this.idleFederationMembersKicker.GetFederationMembersByLastActiveTime().FirstOrDefault(x => x.Key == this.federationManager.CurrentFederationKey.PubKey); if (lastActive.Key != null) { federationMemberModel.LastActiveTime = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(lastActive.Value); @@ -94,7 +94,7 @@ public IActionResult GetCurrentMemberInfo() } // Is this member part of a pending poll - Poll poll = this.votingManager.GetPendingPolls().MemberPolls().OrderByDescending(p => p.PollStartBlockData.Height).FirstOrDefault(p => this.votingManager.GetMemberVotedOn(p.VotingData).PubKey == federationMemberModel.PubKey); + Poll poll = this.votingManager.GetPendingPolls().MemberPolls().OrderByDescending(p => p.PollStartBlockData.Height).FirstOrDefault(p => this.votingManager.GetMemberVotedOn(p.VotingData).PubKey == this.federationManager.CurrentFederationKey.PubKey); if (poll != null) { federationMemberModel.PollType = poll.VotingData.Key.ToString(); @@ -103,7 +103,7 @@ public IActionResult GetCurrentMemberInfo() } // Has the poll finished? - poll = this.votingManager.GetApprovedPolls().MemberPolls().OrderByDescending(p => p.PollVotedInFavorBlockData.Height).FirstOrDefault(p => this.votingManager.GetMemberVotedOn(p.VotingData).PubKey == federationMemberModel.PubKey); + poll = this.votingManager.GetApprovedPolls().MemberPolls().OrderByDescending(p => p.PollVotedInFavorBlockData.Height).FirstOrDefault(p => this.votingManager.GetMemberVotedOn(p.VotingData).PubKey == this.federationManager.CurrentFederationKey.PubKey); if (poll != null) { federationMemberModel.PollType = poll.VotingData.Key.ToString(); @@ -120,7 +120,7 @@ public IActionResult GetCurrentMemberInfo() } // Has the poll executed? - poll = this.votingManager.GetExecutedPolls().MemberPolls().OrderByDescending(p => p.PollExecutedBlockData.Height).FirstOrDefault(p => this.votingManager.GetMemberVotedOn(p.VotingData).PubKey == federationMemberModel.PubKey); + poll = this.votingManager.GetExecutedPolls().MemberPolls().OrderByDescending(p => p.PollExecutedBlockData.Height).FirstOrDefault(p => this.votingManager.GetMemberVotedOn(p.VotingData).PubKey == this.federationManager.CurrentFederationKey.PubKey); if (poll != null) federationMemberModel.PollExecutedBlockHeight = poll.PollExecutedBlockData.Height; @@ -159,7 +159,7 @@ public IActionResult GetMembers() { federationMemberModels.Add(new FederationMemberModel() { - PubKey = federationMember.PubKey, + PubKey = federationMember.PubKey.ToHex(), CollateralAmount = (federationMember as CollateralFederationMember).CollateralAmount.ToUnit(MoneyUnit.BTC), LastActiveTime = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(activeTimes.FirstOrDefault(a => a.Key == federationMember.PubKey).Value), PeriodOfInActivity = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(activeTimes.FirstOrDefault(a => a.Key == federationMember.PubKey).Value) diff --git a/src/Stratis.Bitcoin.Features.Wallet.Tests/WalletControllerTest.cs b/src/Stratis.Bitcoin.Features.Wallet.Tests/WalletControllerTest.cs index af2de8b787..b230503ba1 100644 --- a/src/Stratis.Bitcoin.Features.Wallet.Tests/WalletControllerTest.cs +++ b/src/Stratis.Bitcoin.Features.Wallet.Tests/WalletControllerTest.cs @@ -704,7 +704,7 @@ public async Task GetGeneralInfoSuccessfullyReturnsWalletGeneralInfoModel() var viewResult = Assert.IsType(result); var resultValue = Assert.IsType(viewResult.Value); - Assert.Equal(wallet.Network, resultValue.Network); + Assert.Equal(wallet.Network.Name, resultValue.Network); Assert.Equal(wallet.CreationTime, resultValue.CreationTime); Assert.Equal(15, resultValue.LastBlockSyncedHeight); Assert.Equal(0, resultValue.ConnectedNodes); diff --git a/src/Stratis.Bitcoin.Features.Wallet/Models/RequestModels.cs b/src/Stratis.Bitcoin.Features.Wallet/Models/RequestModels.cs index 6aa3e88eaa..b8f0362684 100644 --- a/src/Stratis.Bitcoin.Features.Wallet/Models/RequestModels.cs +++ b/src/Stratis.Bitcoin.Features.Wallet/Models/RequestModels.cs @@ -89,7 +89,6 @@ public class WalletLoadRequest : RequestModel /// public class WalletRecoveryRequest : RequestModel { - /// /// The mnemonic that was used to create the wallet. /// diff --git a/src/Stratis.Bitcoin.Features.Wallet/Models/WalletGeneralInfoModel.cs b/src/Stratis.Bitcoin.Features.Wallet/Models/WalletGeneralInfoModel.cs index 972c2ab689..e88d4621d9 100644 --- a/src/Stratis.Bitcoin.Features.Wallet/Models/WalletGeneralInfoModel.cs +++ b/src/Stratis.Bitcoin.Features.Wallet/Models/WalletGeneralInfoModel.cs @@ -1,5 +1,4 @@ using System; -using NBitcoin; using Newtonsoft.Json; using Stratis.Bitcoin.Utilities.JsonConverters; @@ -14,8 +13,7 @@ public class WalletGeneralInfoModel public string WalletName { get; set; } [JsonProperty(PropertyName = "network")] - [JsonConverter(typeof(NetworkConverter))] - public Network Network { get; set; } + public string Network { get; set; } /// /// The time this wallet was created. diff --git a/src/Stratis.Bitcoin.Features.Wallet/Services/WalletService.cs b/src/Stratis.Bitcoin.Features.Wallet/Services/WalletService.cs index 8fbf18cfed..768a3495a0 100644 --- a/src/Stratis.Bitcoin.Features.Wallet/Services/WalletService.cs +++ b/src/Stratis.Bitcoin.Features.Wallet/Services/WalletService.cs @@ -134,7 +134,7 @@ public async Task GetWalletGeneralInfo(string walletName return new WalletGeneralInfoModel { WalletName = wallet.Name, - Network = wallet.Network, + Network = wallet.Network.Name, CreationTime = wallet.CreationTime, LastBlockSyncedHeight = wallet.AccountsRoot.Single().LastBlockSyncedHeight, ConnectedNodes = this.connectionManager.ConnectedPeers.Count(), diff --git a/src/Stratis.Bitcoin.IntegrationTests/API/ApiSteps.cs b/src/Stratis.Bitcoin.IntegrationTests/API/ApiSteps.cs index 401e6a1656..04943f0f28 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/API/ApiSteps.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/API/ApiSteps.cs @@ -452,7 +452,7 @@ private void general_information_about_the_wallet_and_node_is_returned() { var generalInfoResponse = JsonDataSerializer.Instance.Deserialize(this.responseText); generalInfoResponse.WalletName.Should().Be(WalletName); - generalInfoResponse.Network.Name.Should().Be("StraxRegTest"); + generalInfoResponse.Network.Should().Be("StraxRegTest"); generalInfoResponse.ChainTip.Should().Be(0); generalInfoResponse.IsChainSynced.Should().BeFalse(); generalInfoResponse.ConnectedNodes.Should().Be(0); diff --git a/src/Stratis.Bitcoin.IntegrationTests/Wallet/WalletOperationsTests.cs b/src/Stratis.Bitcoin.IntegrationTests/Wallet/WalletOperationsTests.cs index 5fe34f2930..72acd671e4 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/Wallet/WalletOperationsTests.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/Wallet/WalletOperationsTests.cs @@ -1223,7 +1223,7 @@ public async Task GetWalletGeneralInfoAsync() generalInfoModel.ConnectedNodes.Should().Be(0); generalInfoModel.CreationTime.ToUnixTimeSeconds().Should().Be(1470467001); generalInfoModel.IsDecrypted.Should().BeTrue(); - generalInfoModel.Network.Name.Should().Be(new StraxRegTest().Name); + generalInfoModel.Network.Should().Be(new StraxRegTest().Name); //generalInfoModel.WalletFilePath.Should().Be(this.fixture.WalletWithFundsFilePath); } diff --git a/src/Stratis.Bitcoin.Tests/Controllers/NodeControllerTest.cs b/src/Stratis.Bitcoin.Tests/Controllers/NodeControllerTest.cs index e728186da9..791e140685 100644 --- a/src/Stratis.Bitcoin.Tests/Controllers/NodeControllerTest.cs +++ b/src/Stratis.Bitcoin.Tests/Controllers/NodeControllerTest.cs @@ -34,6 +34,7 @@ public class NodeControllerTest : LogsTestBase private readonly Mock connectionManager; private readonly Mock dateTimeProvider; private readonly Mock fullNode; + private readonly Mock initialBlockDownloadState; private readonly Mock peerBanning; private readonly Network network; private readonly NodeSettings nodeSettings; @@ -59,6 +60,7 @@ public NodeControllerTest() this.connectionManager.Setup(c => c.Network).Returns(this.network); this.dateTimeProvider = new Mock(); this.fullNode = new Mock(); + this.initialBlockDownloadState = new Mock(this.chainState.Object, this.network, null, new Checkpoints(), DateTimeProvider.Default); this.nodeSettings = new NodeSettings(networksSelector: Networks.Networks.Bitcoin); this.peerBanning = new Mock(); @@ -84,6 +86,7 @@ public NodeControllerTest() this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, + this.initialBlockDownloadState.Object, this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, @@ -131,8 +134,8 @@ public async Task GetRawTransactionAsync_TransactionCannotBeFound_ReturnsNullAsy .Verifiable(); this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); string txid = txId.ToString(); bool verbose = false; @@ -155,8 +158,8 @@ public async Task GetRawTransactionAsync_TransactionNotInPooledTransaction_Retur .Returns(transaction); this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); string txid = txId.ToString(); bool verbose = false; @@ -194,8 +197,8 @@ public async Task GetRawTransactionAsync_PooledTransactionServiceNotAvailable_Re .Returns(transaction); this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); string txid = txId.ToString(); bool verbose = false; @@ -215,8 +218,8 @@ public async Task GetRawTransactionAsync_PooledTransactionAndBlockStoreServiceNo .Verifiable(); this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); string txid = txId.ToString(); bool verbose = false; @@ -236,8 +239,8 @@ public void DecodeRawTransaction_ReturnsTransaction() this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); var json = (JsonResult)this.controller.DecodeRawTransaction(new DecodeRawTransactionModel() { RawHex = transaction.ToHex() }); var resultModel = (TransactionVerboseModel)json.Value; @@ -405,8 +408,8 @@ public async Task GetTxOutAsync_NotIncludeInMempool_GetUnspentTransactionNotAvai var txId = new uint256(1243124); this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); string txid = txId.ToString(); uint vout = 0; bool includeMemPool = false; @@ -425,8 +428,8 @@ public async Task GetTxOutAsync_IncludeMempool_UnspentTransactionNotFound_Return .Verifiable(); this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); string txid = txId.ToString(); uint vout = 0; bool includeMemPool = true; @@ -443,8 +446,8 @@ public async Task GetTxOutAsync_IncludeMempool_PooledGetUnspentTransactionNotAva var txId = new uint256(1243124); this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); string txid = txId.ToString(); uint vout = 0; bool includeMemPool = true; @@ -465,8 +468,8 @@ public async Task GetTxOutAsync_NotIncludeInMempool_UnspentTransactionFound_Retu .Verifiable(); this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); string txid = txId.ToString(); uint vout = 0; bool includeMemPool = false; @@ -493,8 +496,8 @@ public async Task GetTxOutAsync_IncludeInMempool_UnspentTransactionFound_Returns .Verifiable(); this.controller = new NodeController(this.chainIndexer, this.chainState.Object, this.connectionManager.Object, this.dateTimeProvider.Object, this.fullNode.Object, - this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.getUnspentTransaction.Object, - this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); + this.LoggerFactory.Object, this.nodeSettings, this.network, this.asyncProvider.Object, this.selfEndpointTracker.Object, this.consensusManager.Object, this.blockStore.Object, this.initialBlockDownloadState.Object, + this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.pooledGetUnspentTransaction.Object, this.pooledTransaction.Object); string txid = txId.ToString(); uint vout = 0; bool includeMemPool = true; diff --git a/src/Stratis.Bitcoin/Base/InitialBlockDownloadState.cs b/src/Stratis.Bitcoin/Base/InitialBlockDownloadState.cs index 04e28dd6dc..836ddff24a 100644 --- a/src/Stratis.Bitcoin/Base/InitialBlockDownloadState.cs +++ b/src/Stratis.Bitcoin/Base/InitialBlockDownloadState.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using NBitcoin; +using NBitcoin; using Stratis.Bitcoin.Configuration.Settings; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Interfaces; @@ -22,9 +21,6 @@ public class InitialBlockDownloadState : IInitialBlockDownloadState /// Information about node's chain. private readonly IChainState chainState; - /// Instance logger. - private readonly ILogger logger; - /// Specification of the network the node runs on - regtest/testnet/mainnet. private readonly Network network; @@ -34,7 +30,7 @@ public class InitialBlockDownloadState : IInitialBlockDownloadState private int lastCheckpointHeight; private uint256 minimumChainWork; - public InitialBlockDownloadState(IChainState chainState, Network network, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ILoggerFactory loggerFactory, IDateTimeProvider dateTimeProvider) + public InitialBlockDownloadState(IChainState chainState, Network network, ConsensusSettings consensusSettings, ICheckpoints checkpoints, IDateTimeProvider dateTimeProvider) { Guard.NotNull(chainState, nameof(chainState)); @@ -46,8 +42,6 @@ public InitialBlockDownloadState(IChainState chainState, Network network, Consen this.lastCheckpointHeight = this.checkpoints.GetLastCheckpointHeight(); this.minimumChainWork = this.network.Consensus.MinimumChainWork ?? uint256.Zero; - - this.logger = loggerFactory.CreateLogger(this.GetType().FullName); } /// diff --git a/src/Stratis.Bitcoin/Controllers/Models/StatusModel.cs b/src/Stratis.Bitcoin/Controllers/Models/StatusModel.cs index 4a8259a934..effaf15628 100644 --- a/src/Stratis.Bitcoin/Controllers/Models/StatusModel.cs +++ b/src/Stratis.Bitcoin/Controllers/Models/StatusModel.cs @@ -78,6 +78,9 @@ public StatusModel() /// Returns the status of the node. public string State { get; set; } + + /// Returns whether or not the node is in Initial Block Download (syncing). + public bool? InIbd { get; set; } } /// diff --git a/src/Stratis.CirrusMinerD/Program.cs b/src/Stratis.CirrusMinerD/Program.cs index 3bf2b95a46..208ec3ea2d 100644 --- a/src/Stratis.CirrusMinerD/Program.cs +++ b/src/Stratis.CirrusMinerD/Program.cs @@ -82,7 +82,7 @@ private static IFullNode BuildCirrusMiningNode(string[] args) .UseBlockStore() .AddPoAFeature() .UsePoAConsensus() - .AddPoACollateralMiningCapability() + .AddPoACollateralMiningCapability() .CheckCollateralCommitment() .AddDynamicMemberhip() .SetCounterChainNetwork(StraxNetwork.MainChainNetworks[nodeSettings.Network.NetworkType]()) diff --git a/src/Stratis.CirrusPegD/Program.cs b/src/Stratis.CirrusPegD/Program.cs index cedd47b2b0..00eb1f70e8 100644 --- a/src/Stratis.CirrusPegD/Program.cs +++ b/src/Stratis.CirrusPegD/Program.cs @@ -116,7 +116,7 @@ private static IFullNode GetSidechainFullNode(string[] args) .AddPoAFeature() .UsePoAConsensus() .AddFederatedPeg() - .AddPoACollateralMiningCapability() + .AddPoACollateralMiningCapability() .CheckCollateralCommitment() .AddDynamicMemberhip() .UseTransactionNotification() diff --git a/src/Stratis.External.Masternodes/Program.cs b/src/Stratis.External.Masternodes/Program.cs new file mode 100644 index 0000000000..304678fec8 --- /dev/null +++ b/src/Stratis.External.Masternodes/Program.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using NBitcoin; + +namespace Stratis.External.Masternodes +{ + class Program + { + static async Task Main(string[] args) + { + Console.WriteLine("Welcome to the Stratis Masternode Registration application."); + Console.WriteLine("Please press any key to start."); + Console.ReadKey(); + + var service = new RegistrationService(); + + NetworkType networkType = NetworkType.Mainnet; + + if (args.Contains("-testnet")) + networkType = NetworkType.Testnet; + + if (args.Contains("-regtest")) + networkType = NetworkType.Regtest; + + await service.StartAsync(networkType); + } + } +} diff --git a/src/Stratis.External.Masternodes/RegistrationService.cs b/src/Stratis.External.Masternodes/RegistrationService.cs new file mode 100644 index 0000000000..ce7d5d874c --- /dev/null +++ b/src/Stratis.External.Masternodes/RegistrationService.cs @@ -0,0 +1,524 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Flurl; +using Flurl.Http; +using NBitcoin; +using NBitcoin.DataEncoders; +using Stratis.Bitcoin; +using Stratis.Bitcoin.Controllers.Models; +using Stratis.Bitcoin.Features.BlockStore.Models; +using Stratis.Bitcoin.Features.PoA; +using Stratis.Bitcoin.Features.PoA.Models; +using Stratis.Bitcoin.Features.Wallet.Models; +using Stratis.Bitcoin.Networks; +using Stratis.Features.PoA.Voting; +using Stratis.Sidechains.Networks; + +namespace Stratis.External.Masternodes +{ + public sealed class RegistrationService + { + /// The folder where the CirrusMinerD.exe is stored. + private string nodeExecutablesPath; + private Network mainchainNetwork; + private Network sidechainNetwork; + private const string nodeExecutable = "Stratis.CirrusMinerD.exe"; + + private const int CollateralRequirement = 100_000; + private const int FeeRequirement = 500; + + private string rootDataDir; + + public async Task StartAsync(NetworkType networkType) + { + this.rootDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StratisNode"); + this.nodeExecutablesPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "StraxMinerD"); + + if (networkType == NetworkType.Mainnet) + { + this.mainchainNetwork = new StraxMain(); + this.sidechainNetwork = new CirrusMain(); + } + + if (networkType == NetworkType.Testnet) + { + this.mainchainNetwork = new StraxTest(); + this.sidechainNetwork = new CirrusTest(); + } + + if (networkType == NetworkType.Regtest) + { + this.mainchainNetwork = new StraxRegTest(); + this.sidechainNetwork = new CirrusRegTest(); + } + + // Start main chain node + if (!await StartNodeAsync(networkType, NodeType.MainChain)) + return; + + // Wait for main chain node to be initialized + if (!await EnsureNodeIsInitializedAsync(NodeType.MainChain, this.mainchainNetwork.DefaultAPIPort)) + return; + + // Wait for main chain node to be synced (out of IBD) + if (!await EnsureNodeIsSyncedAsync(NodeType.MainChain, this.mainchainNetwork.DefaultAPIPort)) + return; + + // Wait for main chain node's address indexer to be synced. + if (!await EnsureMainChainNodeAddressIndexerIsSyncedAsync()) + return; + + // Create the masternode public key. + if (!CreateFederationKey()) + return; + + // Start side chain node + if (!await StartNodeAsync(networkType, NodeType.SideChain)) + return; + + // Wait for side chain node to be initialized + if (!await EnsureNodeIsInitializedAsync(NodeType.SideChain, this.sidechainNetwork.DefaultAPIPort)) + return; + + // Wait for side chain node to be synced (out of IBD) + if (!await EnsureNodeIsSyncedAsync(NodeType.SideChain, this.sidechainNetwork.DefaultAPIPort)) + return; + + Console.Clear(); + Console.WriteLine("SUCCESS: STRAX Blockchain and Cirrus Blockchain are now fully synchronised."); + Console.WriteLine("Assessing Masternode Requirements..."); + + // Check main chain collateral wallet and balace + if (!await CheckWalletRequirementsAsync(NodeType.MainChain, this.mainchainNetwork.DefaultAPIPort)) + return; + + // Check side chain fee wallet + if (!await CheckWalletRequirementsAsync(NodeType.SideChain, this.sidechainNetwork.DefaultAPIPort)) + return; + + // Call the join federation API call. + if (!await CallJoinFederationRequestAsync()) + return; + + // Call the join federation API call. + await MonitorJoinFederationRequestAsync(); + } + + private async Task StartNodeAsync(NetworkType networkType, NodeType nodeType) + { + var argumentBuilder = new StringBuilder(); + + argumentBuilder.Append($"-{nodeType.ToString().ToLowerInvariant()} "); + + if (nodeType == NodeType.MainChain) + argumentBuilder.Append("-addressindex=1 "); + + if (nodeType == NodeType.SideChain) + argumentBuilder.Append($"-counterchainapiport={this.mainchainNetwork.DefaultAPIPort} "); + + if (networkType == NetworkType.Testnet) + argumentBuilder.Append("-testnet"); + + if (networkType == NetworkType.Regtest) + argumentBuilder.Append("-regtest"); + + Console.WriteLine($"Starting the {nodeType} node on {networkType}."); + Console.WriteLine($"Start up arguments: {argumentBuilder}"); + + var startInfo = new ProcessStartInfo + { + Arguments = argumentBuilder.ToString(), + FileName = Path.Combine(this.nodeExecutablesPath, nodeExecutable), + UseShellExecute = true, + }; + + var process = Process.Start(startInfo); + await Task.Delay(TimeSpan.FromSeconds(5)); + + if (process.HasExited) + { + Console.WriteLine($"{nodeType} node process failed to start, exiting..."); + return false; + } + + Console.WriteLine($"{nodeType} node started."); + + return true; + } + + private async Task EnsureNodeIsInitializedAsync(NodeType nodeType, int apiPort) + { + Console.WriteLine($"Waiting for the {nodeType} node to initialize..."); + + bool initialized = false; + + // Call the node status API until the node initialization state is Initialized. + CancellationToken cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token; + do + { + if (cancellationTokenSource.IsCancellationRequested) + { + Console.WriteLine($"{nodeType} node failed to initialized in 60 seconds..."); + break; + } + + StatusModel blockModel = await $"http://localhost:{apiPort}/api".AppendPathSegment("node/status").GetJsonAsync(); + if (blockModel.State == FullNodeState.Started.ToString()) + { + initialized = true; + Console.WriteLine($"{nodeType} node initialized."); + break; + } + + } while (true); + + return initialized; + } + + private async Task EnsureNodeIsSyncedAsync(NodeType nodeType, int apiPort) + { + Console.WriteLine($"Waiting for the {nodeType} node to sync with the network..."); + + bool result; + + // Call the node status API until the node initialization state is Initialized. + do + { + StatusModel blockModel = await $"http://localhost:{apiPort}/api".AppendPathSegment("node/status").GetJsonAsync(); + if (blockModel.InIbd.HasValue && !blockModel.InIbd.Value) + { + Console.WriteLine($"{nodeType} node is synced at height {blockModel.ConsensusHeight}."); + result = true; + break; + } + + Console.WriteLine($"{nodeType} node syncing, current height {blockModel.ConsensusHeight}..."); + await Task.Delay(TimeSpan.FromSeconds(3)); + } while (true); + + return result; + } + + private async Task EnsureMainChainNodeAddressIndexerIsSyncedAsync() + { + Console.WriteLine("Waiting for the main chain node to sync it's address indexer..."); + + bool result; + + do + { + StatusModel blockModel = await $"http://localhost:{this.mainchainNetwork.DefaultAPIPort}/api".AppendPathSegment("node/status").GetJsonAsync(); + AddressIndexerTipModel addressIndexerModel = await $"http://localhost:{this.mainchainNetwork.DefaultAPIPort}/api".AppendPathSegment("blockstore/addressindexertip").GetJsonAsync(); + if (addressIndexerModel.TipHeight > (blockModel.ConsensusHeight - 50)) + { + Console.WriteLine($"Main chain address indexer synced."); + result = true; + break; + } + + Console.WriteLine($"Main chain node address indexer is syncing, current height {addressIndexerModel.TipHeight}..."); + await Task.Delay(TimeSpan.FromSeconds(3)); + } while (true); + + return result; + } + + private async Task CheckWalletRequirementsAsync(NodeType nodeType, int apiPort) + { + var chainName = nodeType == NodeType.MainChain ? "STRAX" : "CIRRUS"; + var amountToCheck = nodeType == NodeType.MainChain ? CollateralRequirement : FeeRequirement; + var chainTicker = nodeType == NodeType.MainChain ? this.mainchainNetwork.CoinTicker : this.sidechainNetwork.CoinTicker; + + Console.WriteLine($"Please enter the name of the {chainName} wallet that contains the required collateral of {amountToCheck} {chainTicker}:"); + + var walletName = Console.ReadLine(); + + WalletInfoModel walletInfoModel = await $"http://localhost:{apiPort}/api".AppendPathSegment("Wallet/list-wallets").GetJsonAsync(); + + if (walletInfoModel.WalletNames.Contains(walletName)) + { + Console.WriteLine($"SUCCESS: Wallet with name '{chainName}' found."); + } + else + { + Console.WriteLine($"{chainName} wallet with name '{walletName}' does not exist."); + + ConsoleKeyInfo key; + do + { + Console.WriteLine($"Would you like to restore you {chainName} wallet that holds the required amount of {amountToCheck} {chainTicker} now? Enter (Y) to continue or (N) to exit."); + key = Console.ReadKey(); + if (key.Key == ConsoleKey.Y || key.Key == ConsoleKey.N) + break; + } while (true); + + if (key.Key == ConsoleKey.N) + { + Console.WriteLine($"You have chosen to exit the registration script."); + return false; + } + + if (!await RestoreWalletAsync(apiPort, chainName, walletName)) + return false; + } + + // Check wallet height (sync) status. + do + { + var walletNameRequest = new WalletName() { Name = walletName }; + WalletGeneralInfoModel walletInfo = await $"http://localhost:{apiPort}/api".AppendPathSegment("wallet/general-info").SetQueryParams(walletNameRequest).GetJsonAsync(); + StatusModel blockModel = await $"http://localhost:{apiPort}/api".AppendPathSegment("node/status").GetJsonAsync(); + + if (walletInfo.LastBlockSyncedHeight > (blockModel.ConsensusHeight - 50)) + { + Console.WriteLine($"{chainName} wallet is synced."); + break; + } + + Console.WriteLine($"Syncing {chainName} wallet, current height {walletInfo.LastBlockSyncedHeight}..."); + await Task.Delay(TimeSpan.FromSeconds(3)); + } while (true); + + // Check wallet balance. + try + { + var walletBalanceRequest = new WalletBalanceRequest() { WalletName = walletName }; + WalletBalanceModel walletBalanceModel = await $"http://localhost:{apiPort}/api" + .AppendPathSegment("wallet/balance") + .SetQueryParams(walletBalanceRequest) + .GetJsonAsync(); + + if (walletBalanceModel.AccountsBalances[0].SpendableAmount / 100000000 > amountToCheck) + { + Console.WriteLine($"SUCCESS: The {chainName} wallet contains the required amount of {amountToCheck} {chainTicker}."); + return true; + } + + Console.WriteLine($"ERROR: The {chainName} wallet does not contain the required amount of {amountToCheck} {chainTicker}."); + + } + catch (Exception ex) + { + Console.WriteLine($"ERROR: An exception occurred trying to check the wallet balance: {ex}"); + } + + return false; + } + + private async Task RestoreWalletAsync(int apiPort, string chainName, string walletName) + { + Console.WriteLine($"You have chosen to restore your {chainName} wallet."); + + string mnemonic; + string passphrase; + string password; + + do + { + Console.WriteLine($"Please enter your 12-Words used to recover your wallet:"); + mnemonic = Console.ReadLine(); + Console.WriteLine("Please enter your wallet passphrase:"); + passphrase = Console.ReadLine(); + Console.WriteLine("Please enter the wallet password used to encrypt the wallet:"); + password = Console.ReadLine(); + + if (!string.IsNullOrEmpty(mnemonic) && !string.IsNullOrEmpty(passphrase) && !string.IsNullOrEmpty(password)) + break; + + Console.WriteLine("ERROR: Please ensure that you enter all the wallet details."); + + } while (true); + + var walletRecoveryRequest = new WalletRecoveryRequest() + { + CreationDate = new DateTime(2020, 11, 1), + Mnemonic = mnemonic, + Name = walletName, + Passphrase = passphrase, + Password = password + }; + + try + { + await $"http://localhost:{apiPort}/api".AppendPathSegment("wallet/recover").PostJsonAsync(walletRecoveryRequest); + } + catch (Exception ex) + { + Console.WriteLine($"ERROR: An exception occurred trying to recover your {chainName} wallet: {ex}"); + return false; + } + + WalletInfoModel walletInfoModel = await $"http://localhost:{apiPort}/api".AppendPathSegment("Wallet/list-wallets").GetJsonAsync(); + if (walletInfoModel.WalletNames.Contains(walletName)) + { + Console.WriteLine($"SUCCESS: {chainName} wallet has been restored."); + } + else + { + Console.WriteLine($"ERROR: {chainName} wallet failed to be restored, exiting the registration process."); + return false; + } + + try + { + Console.WriteLine($"Your {chainName} wallet will now be resynced, please be patient..."); + var walletSyncRequest = new WalletSyncRequest() + { + All = true, + WalletName = walletName + }; + + await $"http://localhost:{apiPort}/api".AppendPathSegment("wallet/sync-from-date").PostJsonAsync(walletSyncRequest); + } + catch (Exception ex) + { + Console.WriteLine($"ERROR: An exception occurred trying to resync your {chainName} wallet: {ex}"); + return false; + } + + return true; + } + + private bool CreateFederationKey() + { + var keyFilePath = Path.Combine(this.rootDataDir, this.sidechainNetwork.RootFolderName, this.sidechainNetwork.Name, KeyTool.KeyFileDefaultName); + + if (File.Exists(keyFilePath)) + { + Console.WriteLine($"Your masternode public key file already exists."); + return true; + } + + Console.Clear(); + Console.WriteLine($"Your masternode public key will now be generated."); + + string publicKeyPassphrase; + + do + { + Console.WriteLine($"Please enter a passphrase (this can be anything, but please write it down):"); + publicKeyPassphrase = Console.ReadLine(); + + if (!string.IsNullOrEmpty(publicKeyPassphrase)) + break; + + Console.WriteLine("ERROR: Please ensure that you enter a valid passphrase."); + + } while (true); + + + + // Generate keys for mining. + var tool = new KeyTool(keyFilePath); + + Key key = tool.GeneratePrivateKey(); + + string savePath = tool.GetPrivateKeySavePath(); + tool.SavePrivateKey(key); + PubKey miningPubKey = key.PubKey; + + Console.WriteLine($"Your Masternode Public Key (PubKey) is: {Encoders.Hex.EncodeData(miningPubKey.ToBytes(false))}"); + + if (publicKeyPassphrase != null) + { + Console.WriteLine(Environment.NewLine); + Console.WriteLine($"Your passphrase: {publicKeyPassphrase}"); + } + + Console.WriteLine(Environment.NewLine); + Console.WriteLine($"It has been saved in the root Cirrus data folder: {savePath}"); + Console.WriteLine($"Please ensure that you take a backup of this file."); + return true; + } + + private async Task CallJoinFederationRequestAsync() + { + Console.Clear(); + Console.WriteLine($"The relevant masternode registration wallets has now been setup and verified."); + Console.WriteLine($"Press any key to continue (this will deduct the registation fee from your Cirrus wallet)"); + Console.ReadKey(); + + string collateralWallet; + string collateralPassword; + string collateralAddress; + string cirrusWalletName; + string cirrusWalletPassword; + + do + { + Console.WriteLine($"[Strax] Please enter the collateral wallet name:"); + collateralWallet = Console.ReadLine(); + Console.WriteLine($"[Strax] Please enter the collateral wallet password:"); + collateralPassword = Console.ReadLine(); + Console.WriteLine($"[Strax] Please enter the collateral address in which the collateral amount of {CollateralRequirement} {this.mainchainNetwork.CoinTicker} is held:"); + collateralAddress = Console.ReadLine(); + + Console.WriteLine($"[Cirrus] Please enter the wallet name which holds the registration fee of {FeeRequirement} {this.sidechainNetwork.CoinTicker}:"); + cirrusWalletName = Console.ReadLine(); + Console.WriteLine($"[Cirrus] Please enter the above wallet's password:"); + cirrusWalletPassword = Console.ReadLine(); + + if (!string.IsNullOrEmpty(collateralWallet) && !string.IsNullOrEmpty(collateralPassword) && !string.IsNullOrEmpty(collateralAddress) && + !string.IsNullOrEmpty(cirrusWalletName) && !string.IsNullOrEmpty(cirrusWalletPassword)) + break; + + Console.WriteLine("ERROR: Please ensure that you enter the relevant details correctly."); + + } while (true); + + var request = new JoinFederationRequestModel() + { + CollateralAddress = collateralAddress, + CollateralWalletName = collateralWallet, + CollateralWalletPassword = collateralPassword, + WalletAccount = "account 0", + WalletName = cirrusWalletName, + WalletPassword = cirrusWalletPassword + }; + + try + { + await $"http://localhost:{this.sidechainNetwork.DefaultAPIPort}/api".AppendPathSegment("collateral/joinfederation").PostJsonAsync(request); + Console.WriteLine($"SUCCESS: The masternode request has now been submitted to the network,please press any key to view its progress."); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"ERROR: An exception occurred trying to registre your masternode: {ex}"); + return false; + } + } + + private async Task MonitorJoinFederationRequestAsync() + { + // Check wallet height (sync) status. + do + { + FederationMemberDetailedModel memberInfo = await $"http://localhost:{this.sidechainNetwork.DefaultAPIPort}/api".AppendPathSegment("federation/members/current").GetJsonAsync(); + StatusModel blockModel = await $"http://localhost:{this.sidechainNetwork.DefaultAPIPort}/api".AppendPathSegment("node/status").GetJsonAsync(); + + Console.Clear(); + Console.WriteLine($">> Registration Progress"); + Console.WriteLine($"PubKey".PadRight(30) + $": {memberInfo.PubKey}"); + Console.WriteLine($"Current Height".PadRight(30) + $": {blockModel.ConsensusHeight}"); + Console.WriteLine($"Mining will start at height".PadRight(30) + $": {memberInfo.MemberWillStartMiningAtBlockHeight}"); + Console.WriteLine($"Rewards will start at height".PadRight(30) + $": {memberInfo.MemberWillStartEarningRewardsEstimateHeight}"); + Console.WriteLine(); + Console.WriteLine($"Press CRTL-C to exit..."); + await Task.Delay(TimeSpan.FromSeconds(5)); + } while (true); + } + } + + public enum NodeType + { + MainChain, + SideChain + } +} diff --git a/src/Stratis.External.Masternodes/Stratis.External.Masternodes.csproj b/src/Stratis.External.Masternodes/Stratis.External.Masternodes.csproj new file mode 100644 index 0000000000..47791c4d2a --- /dev/null +++ b/src/Stratis.External.Masternodes/Stratis.External.Masternodes.csproj @@ -0,0 +1,20 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + + + + + diff --git a/src/Stratis.Features.Collateral/CollateralFeature.cs b/src/Stratis.Features.Collateral/CollateralFeature.cs index 408349a5b1..fe819a92ab 100644 --- a/src/Stratis.Features.Collateral/CollateralFeature.cs +++ b/src/Stratis.Features.Collateral/CollateralFeature.cs @@ -5,7 +5,6 @@ using Stratis.Bitcoin.Features.Miner; using Stratis.Bitcoin.Features.PoA; using Stratis.Bitcoin.Features.SmartContracts; -using Stratis.Bitcoin.Features.SmartContracts.PoA; using Stratis.Features.Collateral.CounterChain; namespace Stratis.Features.Collateral @@ -49,7 +48,7 @@ public static IFullNodeBuilder CheckCollateralCommitment(this IFullNodeBuilder f /// /// Adds mining to the side chain node when on a proof-of-authority network with collateral enabled. /// - public static IFullNodeBuilder AddPoACollateralMiningCapability(this IFullNodeBuilder fullNodeBuilder) + public static IFullNodeBuilder AddPoACollateralMiningCapability(this IFullNodeBuilder fullNodeBuilder) where T : BlockDefinition { // Inject the CheckCollateralFullValidationRule as the first Full Validation Rule. // This is still a bit hacky and we need to properly review the dependencies again between the different side chain nodes. @@ -65,7 +64,7 @@ public static IFullNodeBuilder AddPoACollateralMiningCapability(this IFullNodeBu { services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainFederationNodeRunner.cs b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainFederationNodeRunner.cs index a597dd2e3a..6839c82e63 100644 --- a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainFederationNodeRunner.cs +++ b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainFederationNodeRunner.cs @@ -52,7 +52,7 @@ public override void BuildNode() .AddPoAFeature() .UsePoAConsensus() .AddFederatedPeg() - .AddPoACollateralMiningCapability() + .AddPoACollateralMiningCapability() .CheckCollateralCommitment() .UseTransactionNotification() .UseBlockNotification() diff --git a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainMinerNodeRunner.cs b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainMinerNodeRunner.cs index d4cfb40353..d5ad37d5b3 100644 --- a/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainMinerNodeRunner.cs +++ b/src/Stratis.Features.FederatedPeg.IntegrationTests/Utils/SidechainMinerNodeRunner.cs @@ -46,7 +46,7 @@ public override void BuildNode() .SetCounterChainNetwork(this.counterChainNetwork) .AddPoAFeature() .UsePoAConsensus() - .AddPoACollateralMiningCapability() + .AddPoACollateralMiningCapability() .AddDynamicMemberhip() .UseTransactionNotification() .UseBlockNotification() diff --git a/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationWalletControllerTests.cs b/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationWalletControllerTests.cs index 0c3d4a9480..33ab1c45e2 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationWalletControllerTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationWalletControllerTests.cs @@ -81,7 +81,7 @@ public void GetGeneralInfo() Assert.Equal(this.fedWallet.CreationTime, model.CreationTime); Assert.Equal(this.fedWallet.LastBlockSyncedHeight, model.LastBlockSyncedHeight); - Assert.Equal(this.fedWallet.Network, model.Network); + Assert.Equal(this.fedWallet.Network.Name, model.Network); } [Fact] diff --git a/src/Stratis.Features.FederatedPeg/Controllers/FederationWalletController.cs b/src/Stratis.Features.FederatedPeg/Controllers/FederationWalletController.cs index ec0c7ee36c..7b3eae1754 100644 --- a/src/Stratis.Features.FederatedPeg/Controllers/FederationWalletController.cs +++ b/src/Stratis.Features.FederatedPeg/Controllers/FederationWalletController.cs @@ -88,7 +88,7 @@ public IActionResult GetGeneralInfo() var model = new WalletGeneralInfoModel { - Network = wallet.Network, + Network = wallet.Network.Name, CreationTime = wallet.CreationTime, LastBlockSyncedHeight = wallet.LastBlockSyncedHeight, ConnectedNodes = this.connectionManager.ConnectedPeers.Count(), diff --git a/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs b/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs index 232f15b413..f0ee2fc461 100644 --- a/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs +++ b/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs @@ -13,7 +13,6 @@ using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Logging; using Stratis.Bitcoin.Connection; -using Stratis.Bitcoin.Features.Miner; using Stratis.Bitcoin.Features.Notifications; using Stratis.Bitcoin.Features.SmartContracts; using Stratis.Bitcoin.P2P.Peer; @@ -289,7 +288,6 @@ public static IFullNodeBuilder AddFederatedPeg(this IFullNodeBuilder fullNodeBui { services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); } diff --git a/src/Stratis.FullNode.sln b/src/Stratis.FullNode.sln index 591c5c3545..290c2ab3db 100644 --- a/src/Stratis.FullNode.sln +++ b/src/Stratis.FullNode.sln @@ -183,6 +183,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Patricia.Tests", "S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stratis.Bitcoin.Features.Interop", "Stratis.Bitcoin.Features.Interop\Stratis.Bitcoin.Features.Interop.csproj", "{3DCC6195-1271-4A12-8B94-E821925D98DC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stratis.External.Masternodes", "Stratis.External.Masternodes\Stratis.External.Masternodes.csproj", "{F04464B5-9D56-4C9A-9778-27B9D314A296}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -481,6 +483,10 @@ Global {3DCC6195-1271-4A12-8B94-E821925D98DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DCC6195-1271-4A12-8B94-E821925D98DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DCC6195-1271-4A12-8B94-E821925D98DC}.Release|Any CPU.Build.0 = Release|Any CPU + {F04464B5-9D56-4C9A-9778-27B9D314A296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F04464B5-9D56-4C9A-9778-27B9D314A296}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F04464B5-9D56-4C9A-9778-27B9D314A296}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F04464B5-9D56-4C9A-9778-27B9D314A296}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -549,6 +555,7 @@ Global {57457525-FD6E-401D-BF3F-862F7A2D2AC9} = {1B9A916F-DDAC-4675-B424-EDEDC1A58231} {EE71EBA7-5515-4F1E-B0E0-6C32BAAD6B35} = {1B9A916F-DDAC-4675-B424-EDEDC1A58231} {3DCC6195-1271-4A12-8B94-E821925D98DC} = {15D29FFD-6142-4DC5-AFFD-10BA0CA55C45} + {F04464B5-9D56-4C9A-9778-27B9D314A296} = {1B9A916F-DDAC-4675-B424-EDEDC1A58231} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6C780ABA-5872-4B83-AD3F-A5BD423AD907} From 21310c01e2d77d5368886258c8e4da09e88f3f9e Mon Sep 17 00:00:00 2001 From: Francois de la Rouviere Date: Fri, 2 Apr 2021 09:30:40 +0100 Subject: [PATCH 06/13] Fixes for PJ (#498) --- src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs | 6 +++--- src/Stratis.Bitcoin/IFullNode.cs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs b/src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs index e891df35aa..108f81d830 100644 --- a/src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs +++ b/src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs @@ -57,11 +57,11 @@ public void Initialize() this.Execute(feature => { - this.signals.Publish(new FullNodeEvent() { Message = $"Initializing feature '{feature.GetType().Name}' ... " }); + this.signals.Publish(new FullNodeEvent() { Message = $"Initializing feature '{feature.GetType().Name}'" }); feature.State = FeatureInitializationState.Initializing; feature.InitializeAsync().GetAwaiter().GetResult(); feature.State = FeatureInitializationState.Initialized; - this.signals.Publish(new FullNodeEvent() { Message = $"Feature '{feature.GetType().Name}' initialized ... " }); + this.signals.Publish(new FullNodeEvent() { Message = $"Feature '{feature.GetType().Name}' initialized" }); }); } catch @@ -164,7 +164,7 @@ private void LogAndAddException(IFullNodeFeature feature, bool disposing, ListAssigned when finished executing. - Disposed + Disposed, + + /// Assigned when one of the full node features failed to start. + Failed, } } From e1d25320da1bb793715cd9c23e9203631c767316 Mon Sep 17 00:00:00 2001 From: Francois de la Rouviere Date: Fri, 2 Apr 2021 09:31:00 +0100 Subject: [PATCH 07/13] [Cirrus] Add ability to have a non-miner join the devmode network (#497) * Add ability to have a non-miner join devmode network * Review --- .../FederationManager.cs | 2 +- .../PoAFeature.cs | 5 +++- src/Stratis.Bitcoin.Features.PoA/PoAMiner.cs | 13 +++++---- .../Controllers/SmartContractsController.cs | 4 +-- .../Services/WalletService.cs | 2 +- .../Configuration/NodeSettings.cs | 29 +++++++++++++++++-- src/Stratis.CirrusMinerD/Program.cs | 2 +- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.PoA/FederationManager.cs b/src/Stratis.Bitcoin.Features.PoA/FederationManager.cs index 8d74ff6eea..43776ffa04 100644 --- a/src/Stratis.Bitcoin.Features.PoA/FederationManager.cs +++ b/src/Stratis.Bitcoin.Features.PoA/FederationManager.cs @@ -139,7 +139,7 @@ public void Initialize() private bool InitializeFederationMemberKey() { - if (!this.nodeSettings.DevMode) + if (this.nodeSettings.DevMode == null) { // Load key. Key key = new KeyTool(this.nodeSettings.DataFolder).LoadPrivateKey(); diff --git a/src/Stratis.Bitcoin.Features.PoA/PoAFeature.cs b/src/Stratis.Bitcoin.Features.PoA/PoAFeature.cs index ad3c3adbe5..502ab49da7 100644 --- a/src/Stratis.Bitcoin.Features.PoA/PoAFeature.cs +++ b/src/Stratis.Bitcoin.Features.PoA/PoAFeature.cs @@ -136,7 +136,10 @@ public override Task InitializeAsync() if (rebuildFederationHeight) this.reconstructFederationService.Reconstruct(); - this.miner?.InitializeMining(); + // If the node is started in devmode, its role must be of miner in order to mine. + // If devmode is not specified, initialize mining as per normal. + if (this.nodeSettings.DevMode == null || this.nodeSettings.DevMode == DevModeNodeRole.Miner) + this.miner?.InitializeMining(); return Task.CompletedTask; } diff --git a/src/Stratis.Bitcoin.Features.PoA/PoAMiner.cs b/src/Stratis.Bitcoin.Features.PoA/PoAMiner.cs index 1a977086ab..328c047028 100644 --- a/src/Stratis.Bitcoin.Features.PoA/PoAMiner.cs +++ b/src/Stratis.Bitcoin.Features.PoA/PoAMiner.cs @@ -190,15 +190,18 @@ private async Task CreateBlocksAsync() var builder = new StringBuilder(); builder.AppendLine("<<==============================================================>>"); builder.AppendLine($"Block mined hash : '{chainedHeader}'"); - builder.AppendLine($"Block miner pubkey : '{this.federationManager.CurrentFederationKey.PubKey.ToString()}'"); + builder.AppendLine($"Block miner pubkey : '{this.federationManager.CurrentFederationKey.PubKey}'"); builder.AppendLine("<<==============================================================>>"); this.logger.LogInformation(builder.ToString()); + // If DevMode is enabled the miner will continue it's bootstrapped mining, i.e. without any connections. + if ((this.nodeSettings.DevMode != null && this.nodeSettings.DevMode == DevModeNodeRole.Miner)) + continue; + // The purpose of bootstrap mode is to kickstart the network when the last mined block is very old, which would normally put the node in IBD and inhibit mining. // There is therefore no point keeping this mode enabled once this node has mined successfully. // Additionally, keeping it enabled may result in network splits if this node becomes disconnected from its peers for a prolonged period. - // If DevMode is enabled the miner will conitnue it's bootstrapped mining, i.e. without any connections. - if (this.poaSettings.BootstrappingMode && !this.nodeSettings.DevMode) + if (this.poaSettings.BootstrappingMode) { this.logger.LogInformation("Disabling bootstrap mode as a block has been successfully mined."); this.poaSettings.DisableBootstrap(); @@ -424,7 +427,7 @@ private void AddComponentStats(StringBuilder log) // If the node is in DevMode just use the genesis members via the federation manager. List modifiedFederation; - if (this.nodeSettings.DevMode) + if (this.nodeSettings.DevMode != null) modifiedFederation = this.federationManager.GetFederationMembers(); else modifiedFederation = this.votingManager?.GetModifiedFederation(currentHeader) ?? this.federationManager.GetFederationMembers(); @@ -456,7 +459,7 @@ private void AddComponentStats(StringBuilder log) currentHeader = currentHeader.Previous; hitCount++; - if (this.nodeSettings.DevMode) + if (this.nodeSettings.DevMode != null) modifiedFederation = this.federationManager.GetFederationMembers(); else modifiedFederation = this.votingManager?.GetModifiedFederation(currentHeader) ?? this.federationManager.GetFederationMembers(); diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/ReflectionExecutor/Controllers/SmartContractsController.cs b/src/Stratis.Bitcoin.Features.SmartContracts/ReflectionExecutor/Controllers/SmartContractsController.cs index c2a74a856e..ad10848c53 100644 --- a/src/Stratis.Bitcoin.Features.SmartContracts/ReflectionExecutor/Controllers/SmartContractsController.cs +++ b/src/Stratis.Bitcoin.Features.SmartContracts/ReflectionExecutor/Controllers/SmartContractsController.cs @@ -517,7 +517,7 @@ public async Task BuildAndSendCreateSmartContractTransactionAsync return ModelStateErrors.BuildErrorResponse(this.ModelState); // Ignore this check if the node is running dev mode. - if (!this.nodeSettings.DevMode && !this.connectionManager.ConnectedPeers.Any()) + if (this.nodeSettings.DevMode == null && !this.connectionManager.ConnectedPeers.Any()) { this.logger.LogTrace("(-)[NO_CONNECTED_PEERS]"); return ErrorHelpers.BuildErrorResponse(HttpStatusCode.Forbidden, "Can't send transaction as the node requires at least one connection.", string.Empty); @@ -570,7 +570,7 @@ public async Task BuildAndSendCallSmartContractTransactionAsync([ return ModelStateErrors.BuildErrorResponse(this.ModelState); // Ignore this check if the node is running dev mode. - if (!this.nodeSettings.DevMode && !this.connectionManager.ConnectedPeers.Any()) + if (this.nodeSettings.DevMode == null && !this.connectionManager.ConnectedPeers.Any()) { this.logger.LogTrace("(-)[NO_CONNECTED_PEERS]"); return ErrorHelpers.BuildErrorResponse(HttpStatusCode.Forbidden, "Can't send transaction as the node requires at least one connection.", string.Empty); diff --git a/src/Stratis.Bitcoin.Features.Wallet/Services/WalletService.cs b/src/Stratis.Bitcoin.Features.Wallet/Services/WalletService.cs index 768a3495a0..7b9b945100 100644 --- a/src/Stratis.Bitcoin.Features.Wallet/Services/WalletService.cs +++ b/src/Stratis.Bitcoin.Features.Wallet/Services/WalletService.cs @@ -491,7 +491,7 @@ public async Task SendTransaction(SendTransactionReq { return await Task.Run(() => { - if (!this.nodeSettings.DevMode && !this.connectionManager.ConnectedPeers.Any()) + if (this.nodeSettings.DevMode == null && !this.connectionManager.ConnectedPeers.Any()) { this.logger.LogTrace("(-)[NO_CONNECTED_PEERS]"); diff --git a/src/Stratis.Bitcoin/Configuration/NodeSettings.cs b/src/Stratis.Bitcoin/Configuration/NodeSettings.cs index f479606506..cb6f409add 100644 --- a/src/Stratis.Bitcoin/Configuration/NodeSettings.cs +++ b/src/Stratis.Bitcoin/Configuration/NodeSettings.cs @@ -121,7 +121,7 @@ public class NodeSettings : IDisposable /// whilst in a closed (no connections) environment. /// /// - public bool DevMode { get; private set; } + public DevModeNodeRole? DevMode { get; private set; } /// /// Initializes a new instance of the object. @@ -250,7 +250,16 @@ public NodeSettings(Network network = null, ProtocolVersion protocolVersion = Su this.LoadConfiguration(); // Set the devmode flag. - this.DevMode = this.ConfigReader.GetOrDefault(DevModeParam, false); + var devmode = this.ConfigReader.GetOrDefault(DevModeParam, null); + if (devmode != null) + { + if (devmode == DevModeNodeRole.Default.ToString().ToLowerInvariant()) + this.DevMode = DevModeNodeRole.Default; + else if (devmode == DevModeNodeRole.Miner.ToString().ToLowerInvariant()) + this.DevMode = DevModeNodeRole.Miner; + else + throw new ConfigurationException("Invalid devmode option specified (either 'default' or 'miner' permitted."); + } } /// Determines whether to print help and exit. @@ -441,4 +450,20 @@ public void Dispose() this.LoggerFactory.Dispose(); } } + + /// + /// Determines the type of node that will be started in developer (dev) mode. + /// + public enum DevModeNodeRole + { + /// + /// This role allows the node to join the dev mode network as a miner (currently only one miner is allowed). + /// + Miner, + + /// + /// This role specifies that the node will join the network as a non-miner (normal node). + /// + Default + } } \ No newline at end of file diff --git a/src/Stratis.CirrusMinerD/Program.cs b/src/Stratis.CirrusMinerD/Program.cs index 208ec3ea2d..60f2dd4705 100644 --- a/src/Stratis.CirrusMinerD/Program.cs +++ b/src/Stratis.CirrusMinerD/Program.cs @@ -42,7 +42,7 @@ public static async Task MainAsync(string[] args) { bool isMainchainNode = args.FirstOrDefault(a => a.ToLower() == MainchainArgument) != null; bool isSidechainNode = args.FirstOrDefault(a => a.ToLower() == SidechainArgument) != null; - bool startInDevMode = args.FirstOrDefault(a => a.ToLower() == $"-{NodeSettings.DevModeParam}") != null; + bool startInDevMode = args.Any(a => a.ToLower().Contains($"-{NodeSettings.DevModeParam}")); IFullNode fullNode = null; From 55c4986ab8d02619bdf32cc81869d24c13ce0232 Mon Sep 17 00:00:00 2001 From: Francois de la Rouviere Date: Fri, 2 Apr 2021 15:26:00 +0100 Subject: [PATCH 08/13] Fix full node event publishing (#499) * Fix event publishing * Update FullNode.cs --- src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs | 4 ++-- src/Stratis.Bitcoin/FullNode.cs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs b/src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs index 108f81d830..dcbe634669 100644 --- a/src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs +++ b/src/Stratis.Bitcoin/Builder/FullNodeFeatureExecutor.cs @@ -57,11 +57,11 @@ public void Initialize() this.Execute(feature => { - this.signals.Publish(new FullNodeEvent() { Message = $"Initializing feature '{feature.GetType().Name}'" }); + this.signals.Publish(new FullNodeEvent() { Message = $"Initializing feature '{feature.GetType().Name}'.", State = FullNodeState.Initializing.ToString() }); feature.State = FeatureInitializationState.Initializing; feature.InitializeAsync().GetAwaiter().GetResult(); feature.State = FeatureInitializationState.Initialized; - this.signals.Publish(new FullNodeEvent() { Message = $"Feature '{feature.GetType().Name}' initialized" }); + this.signals.Publish(new FullNodeEvent() { Message = $"Feature '{feature.GetType().Name}' initialized.", State = FullNodeState.Initializing.ToString() }); }); } catch diff --git a/src/Stratis.Bitcoin/FullNode.cs b/src/Stratis.Bitcoin/FullNode.cs index 25246de66d..11f3b32cee 100644 --- a/src/Stratis.Bitcoin/FullNode.cs +++ b/src/Stratis.Bitcoin/FullNode.cs @@ -190,6 +190,7 @@ public IFullNode Initialize(IFullNodeServiceProvider serviceProvider) this.Signals.Publish(new FullNodeEvent() { Message = $"Full node initialized on {this.Network.Name}.", State = this.State.ToString() }); this.StartTime = this.DateTimeProvider.GetUtcNow(); + return this; } @@ -201,6 +202,8 @@ public void Start() if (this.State == FullNodeState.Disposing || this.State == FullNodeState.Disposed) throw new ObjectDisposedException(nameof(FullNode)); + this.Signals.Publish(new FullNodeEvent() { Message = $"Full node starting on {this.Network.Name}.", State = this.State.ToString() }); + this.nodeRunningLock = new NodeRunningLock(this.DataFolder); if (!this.nodeRunningLock.TryLockNodeFolder()) @@ -233,6 +236,8 @@ public void Start() this.StartPeriodicLog(); this.State = FullNodeState.Started; + + this.Signals.Publish(new FullNodeEvent() { Message = $"Full node started on {this.Network.Name}.", State = this.State.ToString() }); } /// From 85e1f345acbd92cdd8dd9654222b760e0e1eba19 Mon Sep 17 00:00:00 2001 From: Kevin Loubser Date: Mon, 5 Apr 2021 23:44:52 +0200 Subject: [PATCH 09/13] Wire in fee distribution --- src/NBitcoin/Network.cs | 5 +++ src/Stratis.Bitcoin.Networks/StraxMain.cs | 2 ++ src/Stratis.Bitcoin.Networks/StraxRegTest.cs | 2 ++ src/Stratis.Bitcoin.Networks/StraxTest.cs | 2 ++ .../MaturedBlocksSyncManagerTests.cs | 2 +- .../IRewardDistributionManager.cs | 2 ++ .../Distribution/RewardDistributionManager.cs | 36 ++++++++++++++++++- .../FederatedPegFeature.cs | 3 ++ .../TargetChain/MaturedBlocksSyncManager.cs | 25 +++++++++++-- .../WithdrawalTransactionBuilder.cs | 17 +++++++++ 10 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/NBitcoin/Network.cs b/src/NBitcoin/Network.cs index 1825f854da..eb69a200d1 100644 --- a/src/NBitcoin/Network.cs +++ b/src/NBitcoin/Network.cs @@ -456,6 +456,11 @@ public byte[] MagicBytes /// public string CirrusRewardDummyAddress { get; protected set; } + /// + /// This is used for conversion transaction fee distribution transactions. + /// + public string ConversionTransactionFeeDistributionDummyAddress { get; protected set; } + /// /// The height at which reward batching will be activated. /// diff --git a/src/Stratis.Bitcoin.Networks/StraxMain.cs b/src/Stratis.Bitcoin.Networks/StraxMain.cs index e9e9b8d71d..b2832e3414 100644 --- a/src/Stratis.Bitcoin.Networks/StraxMain.cs +++ b/src/Stratis.Bitcoin.Networks/StraxMain.cs @@ -37,6 +37,8 @@ public StraxMain() this.RewardClaimerBatchActivationHeight = 119_200; // Tuesday, 12 January 2021 9:00:00 AM (Estimated) this.RewardClaimerBlockInterval = 100; + this.ConversionTransactionFeeDistributionDummyAddress = "CXK1AhmK8XhmBWHUrCKRt5WMhz1CcYeguF"; + // To successfully process the OP_FEDERATION opcode the federations should be known. this.Federations = new Federations(); this.Federations.RegisterFederation(new Federation(new[] diff --git a/src/Stratis.Bitcoin.Networks/StraxRegTest.cs b/src/Stratis.Bitcoin.Networks/StraxRegTest.cs index fd016b482d..bd3b50ad3b 100644 --- a/src/Stratis.Bitcoin.Networks/StraxRegTest.cs +++ b/src/Stratis.Bitcoin.Networks/StraxRegTest.cs @@ -36,6 +36,8 @@ public StraxRegTest() this.CirrusRewardDummyAddress = "PDpvfcpPm9cjQEoxWzQUL699N8dPaf8qML"; // Cirrus test address + this.ConversionTransactionFeeDistributionDummyAddress = "PTCPsLQoF3WNoH1qXMy5PouquiXQKp7WBV"; + var powLimit = new Target(new uint256("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); var consensusFactory = new PosConsensusFactory(); diff --git a/src/Stratis.Bitcoin.Networks/StraxTest.cs b/src/Stratis.Bitcoin.Networks/StraxTest.cs index d4d6e8340f..97fd0dac0d 100644 --- a/src/Stratis.Bitcoin.Networks/StraxTest.cs +++ b/src/Stratis.Bitcoin.Networks/StraxTest.cs @@ -38,6 +38,8 @@ public StraxTest() this.RewardClaimerBatchActivationHeight = 166200; this.RewardClaimerBlockInterval = 100; + this.ConversionTransactionFeeDistributionDummyAddress = "tUAzRBe1CaKaZnrxWPLVv7F4owHHKXAtbj"; + var consensusFactory = new PosConsensusFactory(); // Create the genesis block. diff --git a/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksSyncManagerTests.cs b/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksSyncManagerTests.cs index d531efb42e..6ff4eeb6cd 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksSyncManagerTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/MaturedBlocksSyncManagerTests.cs @@ -61,7 +61,7 @@ public async Task BlocksAreRequestedIfThereIsSomethingToRequestAsync() private class TestOnlyMaturedBlocksSyncManager : MaturedBlocksSyncManager { public TestOnlyMaturedBlocksSyncManager(IAsyncProvider asyncProvider, ICrossChainTransferStore crossChainTransferStore, IFederationGatewayClient federationGatewayClient, INodeLifetime nodeLifetime) - : base(asyncProvider, crossChainTransferStore, federationGatewayClient, nodeLifetime, null, null) + : base(asyncProvider, crossChainTransferStore, federationGatewayClient, nodeLifetime, null, null, null, null) { } diff --git a/src/Stratis.Features.FederatedPeg/Distribution/IRewardDistributionManager.cs b/src/Stratis.Features.FederatedPeg/Distribution/IRewardDistributionManager.cs index f70a71a33d..ed37380b09 100644 --- a/src/Stratis.Features.FederatedPeg/Distribution/IRewardDistributionManager.cs +++ b/src/Stratis.Features.FederatedPeg/Distribution/IRewardDistributionManager.cs @@ -6,6 +6,8 @@ namespace Stratis.Features.FederatedPeg.Distribution { public interface IRewardDistributionManager { + List DistributeToMultisigNodes(int blockHeight, Money totalReward); + /// /// Finds the proportion of blocks mined by each miner. /// Creates a corresponding list of recipient scriptPubKeys and reward amounts. diff --git a/src/Stratis.Features.FederatedPeg/Distribution/RewardDistributionManager.cs b/src/Stratis.Features.FederatedPeg/Distribution/RewardDistributionManager.cs index 0feec9d10a..ab1fa4d03f 100644 --- a/src/Stratis.Features.FederatedPeg/Distribution/RewardDistributionManager.cs +++ b/src/Stratis.Features.FederatedPeg/Distribution/RewardDistributionManager.cs @@ -4,6 +4,7 @@ using NBitcoin; using NLog; using Stratis.Bitcoin.Consensus; +using Stratis.Bitcoin.Features.PoA; using Stratis.Features.FederatedPeg.Wallet; using Stratis.Features.PoA.Collateral; @@ -24,6 +25,7 @@ public sealed class RewardDistributionManager : IRewardDistributionManager private readonly int epochWindow; private readonly ILogger logger; private readonly Network network; + private readonly IFederationHistory federationHistory; private readonly Dictionary blocksMinedEach = new Dictionary(); private readonly Dictionary commitmentTransactionByHashDictionary = new Dictionary(); @@ -35,12 +37,13 @@ public sealed class RewardDistributionManager : IRewardDistributionManager // We pay no attention to whether a miner has been kicked since the last distribution or not. // If they produced an accepted block, they get their reward. - public RewardDistributionManager(Network network, ChainIndexer chainIndexer, IConsensusManager consensusManager) + public RewardDistributionManager(Network network, ChainIndexer chainIndexer, IConsensusManager consensusManager, IFederationHistory federationHistory) { this.network = network; this.chainIndexer = chainIndexer; this.consensusManager = consensusManager; this.logger = LogManager.GetCurrentClassLogger(); + this.federationHistory = federationHistory; this.encoder = new CollateralHeightCommitmentEncoder(); this.epoch = this.network.Consensus.MaxReorgLength == 0 ? DefaultEpoch : (int)this.network.Consensus.MaxReorgLength; @@ -57,6 +60,37 @@ public RewardDistributionManager(Network network, ChainIndexer chainIndexer, ICo } } + public List DistributeToMultisigNodes(int blockHeight, Money totalReward) + { + List federation = this.federationHistory.GetFederationForBlock(this.chainIndexer.GetHeader(blockHeight)); + + var multisigs = new List(); + + foreach (IFederationMember member in federation) + { + if (!(member is CollateralFederationMember collateralMember)) + continue; + + if (!collateralMember.IsMultisigMember) + continue; + + multisigs.Add(collateralMember); + } + + Money reward = totalReward / multisigs.Count; + + var recipients = new List(); + + foreach (CollateralFederationMember multisig in multisigs) + { + Script p2pkh = PayToPubkeyHashTemplate.Instance.GenerateScriptPubKey(multisig.PubKey); + + recipients.Add(new Recipient() { Amount = reward, ScriptPubKey = p2pkh }); + } + + return recipients; + } + /// public List Distribute(int heightOfRecordedDistributionDeposit, Money totalReward) { diff --git a/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs b/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs index 232f15b413..d431024366 100644 --- a/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs +++ b/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs @@ -227,6 +227,9 @@ private string CollectStats() var target = d.deposit.TargetAddress; if (target == this.network.CirrusRewardDummyAddress) target = "Reward Distribution"; + if (target == this.network.ConversionTransactionFeeDistributionDummyAddress) + target = "Conversion Fee"; + return $"{d.deposit.Amount} ({d.blocksBeforeMature}) => {target} ({d.deposit.RetrievalType})"; }).Take(10))); diff --git a/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs b/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs index 73b3bb4383..62eab18cf9 100644 --- a/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs +++ b/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs @@ -5,6 +5,7 @@ using NBitcoin; using NLog; using Stratis.Bitcoin.AsyncWork; +using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Utilities; using Stratis.Features.FederatedPeg.Controllers; using Stratis.Features.FederatedPeg.Conversion; @@ -35,6 +36,8 @@ public class MaturedBlocksSyncManager : IMaturedBlocksSyncManager private readonly INodeLifetime nodeLifetime; private readonly IConversionRequestRepository conversionRequestRepository; private readonly ChainIndexer chainIndexer; + private readonly IExternalApiPoller externalApiPoller; + private readonly Network network; private IAsyncLoop requestDepositsTask; @@ -46,7 +49,7 @@ public class MaturedBlocksSyncManager : IMaturedBlocksSyncManager private const int InitializationDelaySeconds = 10; public MaturedBlocksSyncManager(IAsyncProvider asyncProvider, ICrossChainTransferStore crossChainTransferStore, IFederationGatewayClient federationGatewayClient, - INodeLifetime nodeLifetime, IConversionRequestRepository conversionRequestRepository, ChainIndexer chainIndexer) + INodeLifetime nodeLifetime, IConversionRequestRepository conversionRequestRepository, ChainIndexer chainIndexer, IExternalApiPoller externalApiPoller, Network network) { this.asyncProvider = asyncProvider; this.crossChainTransferStore = crossChainTransferStore; @@ -54,6 +57,8 @@ public MaturedBlocksSyncManager(IAsyncProvider asyncProvider, ICrossChainTransfe this.nodeLifetime = nodeLifetime; this.conversionRequestRepository = conversionRequestRepository; this.chainIndexer = chainIndexer; + this.externalApiPoller = externalApiPoller; + this.network = network; this.logger = LogManager.GetCurrentClassLogger(); } @@ -122,11 +127,11 @@ private async Task ProcessMatureBlockDepositsAsync(SerializableResult ProcessMatureBlockDepositsAsync(SerializableResult + { + new Deposit(conversionTransaction.Id, conversionTransaction.RetrievalType, Money.Coins(conversionFeeAmount), this.network.ConversionTransactionFeeDistributionDummyAddress, conversionTransaction.BlockNumber, conversionTransaction.BlockHash) + }; + + tempList.AddRange(maturedBlockDeposit.Deposits); + + maturedBlockDeposit.Deposits = tempList.AsReadOnly(); this.conversionRequestRepository.Save(new ConversionRequest() { RequestId = conversionTransaction.Id.ToString(), diff --git a/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalTransactionBuilder.cs b/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalTransactionBuilder.cs index 904c5f2258..2dcb5d5882 100644 --- a/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalTransactionBuilder.cs +++ b/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalTransactionBuilder.cs @@ -26,12 +26,14 @@ public class WithdrawalTransactionBuilder : IWithdrawalTransactionBuilder private readonly Network network; private readonly Script cirrusRewardDummyAddressScriptPubKey; + private readonly Script conversionTransactionFeeDistributionScriptPubKey; private readonly IFederationWalletManager federationWalletManager; private readonly IFederationWalletTransactionHandler federationWalletTransactionHandler; private readonly IFederatedPegSettings federatedPegSettings; private readonly ISignals signals; private readonly IRewardDistributionManager distributionManager; private int previousDistributionHeight; + private int previousConversionFeeDistributionHeight; public WithdrawalTransactionBuilder( Network network, @@ -52,7 +54,11 @@ public WithdrawalTransactionBuilder( if (!this.federatedPegSettings.IsMainChain) this.cirrusRewardDummyAddressScriptPubKey = BitcoinAddress.Create(this.network.CirrusRewardDummyAddress).ScriptPubKey; + if (!this.federatedPegSettings.IsMainChain) + this.conversionTransactionFeeDistributionScriptPubKey = BitcoinAddress.Create(this.network.ConversionTransactionFeeDistributionDummyAddress).ScriptPubKey; + this.previousDistributionHeight = 0; + this.previousConversionFeeDistributionHeight = 0; } /// @@ -94,6 +100,17 @@ public Transaction BuildWithdrawalTransaction(int blockHeight, uint256 depositId } } + if (!this.federatedPegSettings.IsMainChain && recipient.ScriptPubKey.Length > 0 && recipient.ScriptPubKey == this.conversionTransactionFeeDistributionScriptPubKey) + { + if (this.previousConversionFeeDistributionHeight != blockHeight) + { + multiSigContext.Recipients = this.distributionManager.DistributeToMultisigNodes(blockHeight, recipient.WithPaymentReducedByFee(FederatedPegSettings.CrossChainTransferFee).Amount); + + // Similarly to the regular distributions, this prevents distribution occurring multiple times in a given block. + this.previousConversionFeeDistributionHeight = blockHeight; + } + } + // TODO: Amend this so we're not picking coins twice. (List coins, List unspentOutputs) = FederationWalletTransactionHandler.DetermineCoins(this.federationWalletManager, this.network, multiSigContext, this.federatedPegSettings); From a45758ea0ad923a35475e92920754c142c5ab4b7 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 6 Apr 2021 14:55:28 +1000 Subject: [PATCH 10/13] Fix GetSigOpCount and revert earlier workaround (#493) * Fix GetSigOpCount and revert earlier workaround * Add network argument * Add network argument --- src/NBitcoin.Tests/sigopcount_tests.cs | 20 +++++++++++++++++++ src/NBitcoin/Script.cs | 10 ++++++---- .../Rules/CommonRules/CoinviewRule.cs | 2 +- .../Rules/CreateMempoolEntryMempoolRule.cs | 9 +++------ 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/NBitcoin.Tests/sigopcount_tests.cs b/src/NBitcoin.Tests/sigopcount_tests.cs index 28d7c1a754..347f039933 100644 --- a/src/NBitcoin.Tests/sigopcount_tests.cs +++ b/src/NBitcoin.Tests/sigopcount_tests.cs @@ -1,4 +1,5 @@ using System.Linq; +using Moq; using Stratis.Bitcoin.Tests.Common; using Xunit; @@ -39,5 +40,24 @@ public void GetSigOpCount() scriptSig2 = scriptSig2 + OpcodeType.OP_1 + dummy.ToBytes() + dummy.ToBytes() + s2.ToBytes(); Assert.Equal(3U, p2sh.GetSigOpCount(KnownNetworks.Main, scriptSig2)); } + + [Fact] + [Trait("Core", "Core")] + public void GetSigOpCountForFederation() + { + PubKey[] keys = Enumerable.Range(0, 3).Select(_ => new Key(true).PubKey).ToArray(); + var federations = new Federations(); + federations.RegisterFederation(new Federation(keys.Take(2), 1)); + var network = KnownNetworks.StraxRegTest; + network.SetPrivatePropertyValue("Federations", federations); + + // Test CScript::GetSigOpCount() + var s1 = new Script(); + s1 = s1 + OpcodeType.OP_1 + OpcodeType.OP_FEDERATION + OpcodeType.OP_CHECKMULTISIG; + Assert.Equal(2U, s1.GetSigOpCount(true, network)); + s1 = s1 + OpcodeType.OP_IF + OpcodeType.OP_CHECKSIG + OpcodeType.OP_ENDIF; + Assert.Equal(3U, s1.GetSigOpCount(true, network)); + Assert.Equal(21U, s1.GetSigOpCount(false, network)); + } } } diff --git a/src/NBitcoin/Script.cs b/src/NBitcoin/Script.cs index e655abe7de..fa31eca5a2 100644 --- a/src/NBitcoin/Script.cs +++ b/src/NBitcoin/Script.cs @@ -854,7 +854,7 @@ public IList ToOps() } } - public uint GetSigOpCount(bool fAccurate) + public uint GetSigOpCount(bool fAccurate, Network network = null) { uint n = 0; Op lastOpcode = null; @@ -864,7 +864,9 @@ public uint GetSigOpCount(bool fAccurate) n++; else if (op.Code == OpcodeType.OP_CHECKMULTISIG || op.Code == OpcodeType.OP_CHECKMULTISIGVERIFY) { - if (fAccurate && lastOpcode != null && lastOpcode.Code >= OpcodeType.OP_1 && lastOpcode.Code <= OpcodeType.OP_16) + if (fAccurate && network?.Federations != null && lastOpcode.Code == OpcodeType.OP_FEDERATION) + n += (uint)network.Federations.GetOnlyFederation().GetFederationDetails().transactionSigningKeys.Length; + else if (fAccurate && lastOpcode != null && lastOpcode.Code >= OpcodeType.OP_1 && lastOpcode.Code <= OpcodeType.OP_16) n += (lastOpcode.PushData == null || lastOpcode.PushData.Length == 0) ? 0U : (uint)lastOpcode.PushData[0]; else n += 20; @@ -913,12 +915,12 @@ public uint GetSigOpCount(Network network, Script scriptSig) { // TODO: Is the network needed? if (!IsPayToScriptHash(network)) - return GetSigOpCount(true); + return GetSigOpCount(true, network); // This is a pay-to-script-hash scriptPubKey; // get the last item that the scriptSig // pushes onto the stack: bool validSig = new PayToScriptHashTemplate().CheckScriptSig(network, scriptSig, this); - return !validSig ? 0 : new Script(scriptSig.ToOps().Last().PushData).GetSigOpCount(true); + return !validSig ? 0 : new Script(scriptSig.ToOps().Last().PushData).GetSigOpCount(true, network); // ... and return its opcount: } diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/CoinviewRule.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/CoinviewRule.cs index 75ff69c23a..9bbd88f2ba 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/CoinviewRule.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/CoinviewRule.cs @@ -365,7 +365,7 @@ private long CountWitnessSignatureOperation(Script scriptPubKey, WitScript witne if (witParams.Program.Length == 32 && witness.PushCount > 0) { Script subscript = Script.FromBytesUnsafe(witness.GetUnsafePush(witness.PushCount - 1)); - return subscript.GetSigOpCount(true); + return subscript.GetSigOpCount(true, this.Parent.Network); } } diff --git a/src/Stratis.Bitcoin.Features.MemoryPool/Rules/CreateMempoolEntryMempoolRule.cs b/src/Stratis.Bitcoin.Features.MemoryPool/Rules/CreateMempoolEntryMempoolRule.cs index d301d4e62c..2acf1e0952 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool/Rules/CreateMempoolEntryMempoolRule.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool/Rules/CreateMempoolEntryMempoolRule.cs @@ -234,13 +234,10 @@ private bool AreInputsStandard(Network network, Transaction tx, MempoolCoinView var redeemScript = new Script(ctx.Stack.Top(-1)); // TODO: Move this into a network-specific rule so that it only applies to Strax (the Cirrus validator already allows non-standard transactions) - if (!redeemScript.ToOps().Select(o => o.Code).Contains(OpcodeType.OP_FEDERATION)) + if (redeemScript.GetSigOpCount(true, this.network) > MaxP2SHSigOps) { - if (redeemScript.GetSigOpCount(true) > MaxP2SHSigOps) - { - this.logger.LogTrace("(-)[SIG_OP_MAX]:false"); - return false; - } + this.logger.LogTrace("(-)[SIG_OP_MAX]:false"); + return false; } } } From dc9aeb33eff3a01bd72d7db0160a12c891ac44cc Mon Sep 17 00:00:00 2001 From: Francois de la Rouviere Date: Tue, 6 Apr 2021 14:46:44 +0100 Subject: [PATCH 11/13] Update FederationWalletManager.cs (#500) --- .../Wallet/FederationWalletManager.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Stratis.Features.FederatedPeg/Wallet/FederationWalletManager.cs b/src/Stratis.Features.FederatedPeg/Wallet/FederationWalletManager.cs index f2445b9ac4..71e056b975 100644 --- a/src/Stratis.Features.FederatedPeg/Wallet/FederationWalletManager.cs +++ b/src/Stratis.Features.FederatedPeg/Wallet/FederationWalletManager.cs @@ -1066,13 +1066,21 @@ public ValidateTransactionResult ValidateTransaction(Transaction transaction, bo { TransactionBuilder builder = new TransactionBuilder(this.Wallet.Network).AddCoins(coins); - if (builder.Verify(transaction, transaction.GetFee(coins.ToArray()), out TransactionPolicyError[] errors)) + var verifyResult = builder.Verify(transaction, transaction.GetFee(coins.ToArray()), out TransactionPolicyError[] errors); + if (verifyResult) + return ValidateTransactionResult.Valid(); + + // Ignore any fee related errors here as the BuildTransaction method in FederationWalletTransactionHandler would have already + // verified the transaction's fee. Fee errors could occur here as the signatures (secrets) aren't added to the builder when calling + // the verify method. + IEnumerable filteredErrors = errors.Where(a => a.GetType() != typeof(FeeTooLowPolicyError)); + if (!filteredErrors.Any()) return ValidateTransactionResult.Valid(); var errorList = new List(); // Trace the reason validation failed. Note that failure here doesn't mean an error necessarily. Just that the transaction is not fully signed. - foreach (TransactionPolicyError transactionPolicyError in errors) + foreach (TransactionPolicyError transactionPolicyError in filteredErrors) { this.logger.Debug("{0} FAILED - {1}", nameof(TransactionBuilder.Verify), transactionPolicyError.ToString()); errorList.Add(transactionPolicyError.ToString()); From 3135a24ba66ba4e4e35c7cb68cf7bedb6dcdd8e4 Mon Sep 17 00:00:00 2001 From: Kevin Loubser Date: Tue, 6 Apr 2021 22:23:51 +0200 Subject: [PATCH 12/13] Bump version --- .../ApiClients/CoinGeckoClient.cs | 9 ++++++++- .../ExternalApiSettings.cs | 2 +- src/Stratis.Bitcoin/Properties/AssemblyInfo.cs | 4 ++-- src/Stratis.Bitcoin/Stratis.Bitcoin.csproj | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/CoinGeckoClient.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/CoinGeckoClient.cs index 9a098eac53..9f1ff07c08 100644 --- a/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/CoinGeckoClient.cs +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ApiClients/CoinGeckoClient.cs @@ -8,6 +8,8 @@ namespace Stratis.Bitcoin.Features.ExternalApi.ApiClients { public class CoinGeckoClient : IDisposable { + public const string DummyUserAgent = "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36"; + private readonly ExternalApiSettings externalApiSettings; private readonly HttpClient client; @@ -33,7 +35,12 @@ public decimal GetEthereumPrice() public async Task PriceDataRetrievalAsync() { - string content = await this.client.GetStringAsync(this.externalApiSettings.EtherscanGasOracleUrl); + var targetUri = new Uri(this.externalApiSettings.PriceUrl); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, targetUri); + requestMessage.Headers.TryAddWithoutValidation("User-Agent", DummyUserAgent); + + HttpResponseMessage resp = await this.client.SendAsync(requestMessage).ConfigureAwait(false); + string content = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); CoinGeckoResponse response = JsonConvert.DeserializeObject(content); diff --git a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiSettings.cs b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiSettings.cs index d93bb2c320..2aa194c907 100644 --- a/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiSettings.cs +++ b/src/Stratis.Bitcoin.Features.ExternalAPI/ExternalApiSettings.cs @@ -10,7 +10,7 @@ public class ExternalApiSettings public const string PriceUrlKey = "ethereumpriceurl"; - public const string PriceTrackingKey = "ethereumpricetracking"; + public const string PriceTrackingKey = "pricetracking"; public string EtherscanGasOracleUrl { get; set; } diff --git a/src/Stratis.Bitcoin/Properties/AssemblyInfo.cs b/src/Stratis.Bitcoin/Properties/AssemblyInfo.cs index eec45629a3..45d57ae75b 100644 --- a/src/Stratis.Bitcoin/Properties/AssemblyInfo.cs +++ b/src/Stratis.Bitcoin/Properties/AssemblyInfo.cs @@ -32,6 +32,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.8.6")] -[assembly: AssemblyFileVersion("1.0.8.6")] +[assembly: AssemblyVersion("1.0.8.7")] +[assembly: AssemblyFileVersion("1.0.8.7")] [assembly: InternalsVisibleTo("Stratis.Bitcoin.Tests")] \ No newline at end of file diff --git a/src/Stratis.Bitcoin/Stratis.Bitcoin.csproj b/src/Stratis.Bitcoin/Stratis.Bitcoin.csproj index 7b8c5de7cb..bc2dbe164c 100644 --- a/src/Stratis.Bitcoin/Stratis.Bitcoin.csproj +++ b/src/Stratis.Bitcoin/Stratis.Bitcoin.csproj @@ -14,7 +14,7 @@ false false false - 1.0.8.6 + 1.0.8.7 False ..\Stratis.ruleset Stratis Group Ltd. From 8641d7afa41bda52d386227851a120ec467fd365 Mon Sep 17 00:00:00 2001 From: Kevin Loubser Date: Tue, 6 Apr 2021 22:48:46 +0200 Subject: [PATCH 13/13] Fix --- .../TargetChain/MaturedBlocksSyncManager.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs b/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs index 62eab18cf9..4bbff17a1a 100644 --- a/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs +++ b/src/Stratis.Features.FederatedPeg/TargetChain/MaturedBlocksSyncManager.cs @@ -169,6 +169,13 @@ private async Task ProcessMatureBlockDepositsAsync(SerializableResult= conversionTransaction.Amount) + { + this.logger.Warn("Conversion transaction {0} is no longer large enough to cover the fee.", conversionTransaction.Id); + + continue; + } + // We insert the fee distribution as a deposit to be processed, albeit with a special address. // Deposits with this address as their destination will be distributed between the multisig members. var tempList = new List @@ -186,7 +193,7 @@ private async Task ProcessMatureBlockDepositsAsync(SerializableResult