private T ParseJObject <T>(JObject requestObj, NBXplorerNetwork network) { if (requestObj == null) { return(default);
public async Task <IActionResult> CreatePSBT( [ModelBinder(BinderType = typeof(NetworkModelBinder))] NBXplorerNetwork network, [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))] DerivationStrategyBase strategy, [FromBody] JObject body) { if (body == null) { throw new ArgumentNullException(nameof(body)); } CreatePSBTRequest request = ParseJObject <CreatePSBTRequest>(body, network); if (strategy == null) { throw new ArgumentNullException(nameof(strategy)); } var repo = RepositoryProvider.GetRepository(network); var txBuilder = request.Seed is int s?network.NBitcoinNetwork.CreateTransactionBuilder(s) : network.NBitcoinNetwork.CreateTransactionBuilder(); CreatePSBTSuggestions suggestions = null; if (!(request.DisableFingerprintRandomization is true) && fingerprintService.GetDistribution(network) is FingerprintDistribution distribution) { suggestions ??= new CreatePSBTSuggestions(); var known = new List <(Fingerprint feature, bool value)>(); if (request.RBF is bool rbf) { known.Add((Fingerprint.RBF, rbf)); } if (request.DiscourageFeeSniping is bool feeSnipping) { known.Add((Fingerprint.FeeSniping, feeSnipping)); } if (request.LockTime is LockTime l) { if (l == LockTime.Zero) { known.Add((Fingerprint.TimelockZero, true)); } } if (request.Version is uint version) { if (version == 1) { known.Add((Fingerprint.V1, true)); } if (version == 2) { known.Add((Fingerprint.V2, true)); } } known.Add((Fingerprint.SpendFromMixed, false)); known.Add((Fingerprint.SequenceMixed, false)); if (strategy is DirectDerivationStrategy direct) { if (direct.Segwit) { known.Add((Fingerprint.SpendFromP2WPKH, true)); } else { known.Add((Fingerprint.SpendFromP2PKH, true)); } } else { // TODO: What if multisig? For now we consider it p2wpkh known.Add((Fingerprint.SpendFromP2SHP2WPKH, true)); } Fingerprint fingerprint = distribution.PickFingerprint(txBuilder.ShuffleRandom); try { fingerprint = distribution.KnowingThat(known.ToArray()) .PickFingerprint(txBuilder.ShuffleRandom); } catch (InvalidOperationException) { } request.RBF ??= fingerprint.HasFlag(Fingerprint.RBF); request.DiscourageFeeSniping ??= fingerprint.HasFlag(Fingerprint.FeeSniping); if (request.LockTime is null && fingerprint.HasFlag(Fingerprint.TimelockZero)) { request.LockTime = new LockTime(0); } if (request.Version is null && fingerprint.HasFlag(Fingerprint.V1)) { request.Version = 1; } if (request.Version is null && fingerprint.HasFlag(Fingerprint.V2)) { request.Version = 2; } suggestions.ShouldEnforceLowR = fingerprint.HasFlag(Fingerprint.LowR); } var waiter = Waiters.GetWaiter(network); if (waiter.NetworkInfo?.GetRelayFee() is FeeRate feeRate) { txBuilder.StandardTransactionPolicy.MinRelayTxFee = feeRate; } txBuilder.OptInRBF = !(request.RBF is false); if (request.LockTime is LockTime lockTime) { txBuilder.SetLockTime(lockTime); } // Discourage fee sniping. // // For a large miner the value of the transactions in the best block and // the mempool can exceed the cost of deliberately attempting to mine two // blocks to orphan the current best block. By setting nLockTime such that // only the next block can include the transaction, we discourage this // practice as the height restricted and limited blocksize gives miners // considering fee sniping fewer options for pulling off this attack. // // A simple way to think about this is from the wallet's point of view we // always want the blockchain to move forward. By setting nLockTime this // way we're basically making the statement that we only want this // transaction to appear in the next block; we don't want to potentially // encourage reorgs by allowing transactions to appear at lower heights // than the next block in forks of the best chain. // // Of course, the subsidy is high enough, and transaction volume low // enough, that fee sniping isn't a problem yet, but by implementing a fix // now we ensure code won't be written that makes assumptions about // nLockTime that preclude a fix later. else if (!(request.DiscourageFeeSniping is false)) { if (waiter.State is BitcoinDWaiterState.Ready) { int blockHeight = ChainProvider.GetChain(network).Height; // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, // e.g. high-latency mix networks and some CoinJoin implementations, have // better privacy. if (txBuilder.ShuffleRandom.Next(0, 10) == 0) { blockHeight = Math.Max(0, blockHeight - txBuilder.ShuffleRandom.Next(0, 100)); } txBuilder.SetLockTime(new LockTime(blockHeight)); } else { txBuilder.SetLockTime(new LockTime(0)); } } var utxos = (await GetUTXOs(network.CryptoCode, strategy, null)).As <UTXOChanges>().GetUnspentUTXOs(request.MinConfirmations); var availableCoinsByOutpoint = utxos.ToDictionary(o => o.Outpoint); if (request.IncludeOnlyOutpoints != null) { var includeOnlyOutpoints = request.IncludeOnlyOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => includeOnlyOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } if (request.ExcludeOutpoints?.Any() is true) { var excludedOutpoints = request.ExcludeOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => !excludedOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } if (request.MinValue != null) { availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => request.MinValue >= (Money)c.Value.Value).ToDictionary(o => o.Key, o => o.Value); } ICoin[] coins = null; if (strategy.GetDerivation().Redeem != null) { // We need to add the redeem script to the coins var hdKeys = strategy.AsHDRedeemScriptPubKey().AsHDKeyCache(); var arr = availableCoinsByOutpoint.Values.ToArray(); coins = new ICoin[arr.Length]; // Can be very intense CPU wise Parallel.For(0, coins.Length, i => { coins[i] = ((Coin)arr[i].AsCoin()).ToScriptCoin(hdKeys.Derive(arr[i].KeyPath).ScriptPubKey); }); } else { coins = availableCoinsByOutpoint.Values.Select(v => v.AsCoin()).ToArray(); } txBuilder.AddCoins(coins); foreach (var dest in request.Destinations) { if (dest.SweepAll) { try { txBuilder.SendAll(dest.Destination); } catch { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't sweep funds, because you don't have any.")); } } else { txBuilder.Send(dest.Destination, dest.Amount); if (dest.SubstractFees) { try { txBuilder.SubtractFees(); } catch { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't substract fee on this destination, because not enough money was sent to it")); } } } } (Script ScriptPubKey, KeyPath KeyPath)change = (null, null); bool hasChange = false; if (request.ExplicitChangeAddress == null) { var keyInfo = await repo.GetUnused(strategy, DerivationFeature.Change, 0, false); change = (keyInfo.ScriptPubKey, keyInfo.KeyPath); } else { // The provided explicit change might have a known keyPath, let's change for it KeyPath keyPath = null; var keyInfos = await repo.GetKeyInformations(new[] { request.ExplicitChangeAddress.ScriptPubKey }); if (keyInfos.TryGetValue(request.ExplicitChangeAddress.ScriptPubKey, out var kis)) { keyPath = kis.FirstOrDefault(k => k.DerivationStrategy == strategy)?.KeyPath; } change = (request.ExplicitChangeAddress.ScriptPubKey, keyPath); } txBuilder.SetChange(change.ScriptPubKey); PSBT psbt = null; try { if (request.FeePreference?.ExplicitFeeRate is FeeRate explicitFeeRate) { txBuilder.SendEstimatedFees(explicitFeeRate); } else if (request.FeePreference?.BlockTarget is int blockTarget) { try { var rate = await GetFeeRate(blockTarget, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } else if (request.FeePreference?.ExplicitFee is Money explicitFee) { txBuilder.SendFees(explicitFee); } else { try { var rate = await GetFeeRate(1, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } psbt = txBuilder.BuildPSBT(false); hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey); } catch (NotEnoughFundsException) { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "Not enough funds for doing this transaction")); } // We made sure we can build the PSBT, so now we can reserve the change address if we need to if (hasChange && request.ExplicitChangeAddress == null && request.ReserveChangeAddress) { var derivation = await repo.GetUnused(strategy, DerivationFeature.Change, 0, true); // In most of the time, this is the same as previously, so no need to rebuild PSBT if (derivation.ScriptPubKey != change.ScriptPubKey) { change = (derivation.ScriptPubKey, derivation.KeyPath); txBuilder.SetChange(change.ScriptPubKey); psbt = txBuilder.BuildPSBT(false); } } var tx = psbt.GetOriginalTransaction(); if (request.Version is uint v) { tx.Version = v; } psbt = txBuilder.CreatePSBTFrom(tx, false, SigHash.All); var update = new UpdatePSBTRequest() { DerivationScheme = strategy, PSBT = psbt, RebaseKeyPaths = request.RebaseKeyPaths, AlwaysIncludeNonWitnessUTXO = request.AlwaysIncludeNonWitnessUTXO, IncludeGlobalXPub = request.IncludeGlobalXPub }; await UpdatePSBTCore(update, network); var resp = new CreatePSBTResponse() { PSBT = update.PSBT, ChangeAddress = hasChange ? change.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork) : null, Suggestions = suggestions }; return(Json(resp, network.JsonSerializerSettings)); }
public async Task <IActionResult> CreatePSBT( [ModelBinder(BinderType = typeof(NetworkModelBinder))] NBXplorerNetwork network, [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))] DerivationStrategyBase strategy, [FromBody] JObject body) { if (body == null) { throw new ArgumentNullException(nameof(body)); } CreatePSBTRequest request = ParseJObject <CreatePSBTRequest>(body, network); if (strategy == null) { throw new ArgumentNullException(nameof(strategy)); } var repo = RepositoryProvider.GetRepository(network); var txBuilder = request.Seed is int s?network.NBitcoinNetwork.CreateTransactionBuilder(s) : network.NBitcoinNetwork.CreateTransactionBuilder(); if (Waiters.GetWaiter(network).NetworkInfo?.GetRelayFee() is FeeRate feeRate) { txBuilder.StandardTransactionPolicy.MinRelayTxFee = feeRate; } txBuilder.OptInRBF = request.RBF; if (request.LockTime is LockTime lockTime) { txBuilder.SetLockTime(lockTime); txBuilder.OptInRBF = true; } var utxos = (await GetUTXOs(network.CryptoCode, strategy, null)).As <UTXOChanges>().GetUnspentCoins(request.MinConfirmations); var availableCoinsByOutpoint = utxos.ToDictionary(o => o.Outpoint); if (request.IncludeOnlyOutpoints != null) { var includeOnlyOutpoints = request.IncludeOnlyOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => includeOnlyOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } if (request.ExcludeOutpoints?.Any() is true) { var excludedOutpoints = request.ExcludeOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => !excludedOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } if (request.MinValue != null) { availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => request.MinValue >= c.Value.Amount).ToDictionary(o => o.Key, o => o.Value); } txBuilder.AddCoins(availableCoinsByOutpoint.Values); foreach (var dest in request.Destinations) { if (dest.SweepAll) { try { txBuilder.SendAll(dest.Destination); } catch { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't sweep funds, because you don't have any.")); } } else { txBuilder.Send(dest.Destination, dest.Amount); if (dest.SubstractFees) { try { txBuilder.SubtractFees(); } catch { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't substract fee on this destination, because not enough money was sent to it")); } } } } (Script ScriptPubKey, KeyPath KeyPath)change = (null, null); bool hasChange = false; if (request.ExplicitChangeAddress == null) { var keyInfo = await repo.GetUnused(strategy, DerivationFeature.Change, 0, false); change = (keyInfo.ScriptPubKey, keyInfo.KeyPath); } else { // The provided explicit change might have a known keyPath, let's change for it KeyPath keyPath = null; var keyInfos = await repo.GetKeyInformations(new[] { request.ExplicitChangeAddress.ScriptPubKey }); if (keyInfos.TryGetValue(request.ExplicitChangeAddress.ScriptPubKey, out var kis)) { keyPath = kis.FirstOrDefault(k => k.DerivationStrategy == strategy)?.KeyPath; } change = (request.ExplicitChangeAddress.ScriptPubKey, keyPath); } txBuilder.SetChange(change.ScriptPubKey); PSBT psbt = null; try { if (request.FeePreference?.ExplicitFeeRate is FeeRate explicitFeeRate) { txBuilder.SendEstimatedFees(explicitFeeRate); } else if (request.FeePreference?.BlockTarget is int blockTarget) { try { var rate = await GetFeeRate(blockTarget, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } else if (request.FeePreference?.ExplicitFee is Money explicitFee) { txBuilder.SendFees(explicitFee); } else { try { var rate = await GetFeeRate(1, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } psbt = txBuilder.BuildPSBT(false); hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey); } catch (NotEnoughFundsException) { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "Not enough funds for doing this transaction")); } // We made sure we can build the PSBT, so now we can reserve the change address if we need to if (hasChange && request.ExplicitChangeAddress == null && request.ReserveChangeAddress) { var derivation = await repo.GetUnused(strategy, DerivationFeature.Change, 0, true); // In most of the time, this is the same as previously, so no need to rebuild PSBT if (derivation.ScriptPubKey != change.ScriptPubKey) { change = (derivation.ScriptPubKey, derivation.KeyPath); txBuilder.SetChange(change.ScriptPubKey); psbt = txBuilder.BuildPSBT(false); } } var tx = psbt.GetOriginalTransaction(); if (request.Version is uint v) { tx.Version = v; } psbt = txBuilder.CreatePSBTFrom(tx, false, SigHash.All); await UpdatePSBTCore(new UpdatePSBTRequest() { DerivationScheme = strategy, PSBT = psbt, RebaseKeyPaths = request.RebaseKeyPaths }, network); var resp = new CreatePSBTResponse() { PSBT = psbt, ChangeAddress = hasChange ? change.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork) : null }; return(Json(resp, network.JsonSerializerSettings)); }
public async Task <IActionResult> CreatePSBT( [ModelBinder(BinderType = typeof(NetworkModelBinder))] NBXplorerNetwork network, [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))] DerivationStrategyBase strategy, [FromBody] JObject body) { if (body == null) { throw new ArgumentNullException(nameof(body)); } CreatePSBTRequest request = ParseJObject <CreatePSBTRequest>(body, network); if (strategy == null) { throw new ArgumentNullException(nameof(strategy)); } var repo = RepositoryProvider.GetRepository(network); var txBuilder = request.Seed is int s?network.NBitcoinNetwork.CreateTransactionBuilder(s) : network.NBitcoinNetwork.CreateTransactionBuilder(); if (Waiters.GetWaiter(network).NetworkInfo?.GetRelayFee() is FeeRate feeRate) { txBuilder.StandardTransactionPolicy.MinRelayTxFee = feeRate; } txBuilder.OptInRBF = request.RBF; if (request.LockTime is LockTime lockTime) { txBuilder.SetLockTime(lockTime); txBuilder.OptInRBF = true; } var utxos = (await GetUTXOs(network.CryptoCode, strategy, null)).GetUnspentCoins(request.MinConfirmations); var availableCoinsByOutpoint = utxos.ToDictionary(o => o.Outpoint); if (request.IncludeOnlyOutpoints != null) { var includeOnlyOutpoints = request.IncludeOnlyOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => includeOnlyOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } if (request.ExcludeOutpoints?.Any() is true) { var excludedOutpoints = request.ExcludeOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => !excludedOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } txBuilder.AddCoins(availableCoinsByOutpoint.Values); foreach (var dest in request.Destinations) { if (dest.SweepAll) { txBuilder.SendAll(dest.Destination); } else { txBuilder.Send(dest.Destination, dest.Amount); if (dest.SubstractFees) { txBuilder.SubtractFees(); } } } (Script ScriptPubKey, KeyPath KeyPath)change = (null, null); bool hasChange = false; // We first build the transaction with a change which keep the length of the expected change scriptPubKey // This allow us to detect if there is a change later in the constructed transaction. // This defend against bug which can happen if one of the destination is the same as the expected change // This assume that a script with only 0 can't be created from a strategy, nor by passing any data to explicitChangeAddress if (request.ExplicitChangeAddress == null) { // The dummyScriptPubKey is necessary to know the size of the change var dummyScriptPubKey = utxos.FirstOrDefault()?.ScriptPubKey ?? strategy.GetDerivation(0).ScriptPubKey; change = (Script.FromBytesUnsafe(new byte[dummyScriptPubKey.Length]), null); } else { change = (Script.FromBytesUnsafe(new byte[request.ExplicitChangeAddress.ScriptPubKey.Length]), null); } txBuilder.SetChange(change.ScriptPubKey); PSBT psbt = null; try { if (request.FeePreference?.ExplicitFeeRate is FeeRate explicitFeeRate) { txBuilder.SendEstimatedFees(explicitFeeRate); } else if (request.FeePreference?.BlockTarget is int blockTarget) { try { var rate = await GetFeeRate(blockTarget, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } else if (request.FeePreference?.ExplicitFee is Money explicitFee) { txBuilder.SendFees(explicitFee); } else { try { var rate = await GetFeeRate(1, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } psbt = txBuilder.BuildPSBT(false); hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey); } catch (NotEnoughFundsException) { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "Not enough funds for doing this transaction")); } if (hasChange) // We need to reserve an address, so we need to build again the psbt { if (request.ExplicitChangeAddress == null) { var derivation = await repo.GetUnused(strategy, DerivationFeature.Change, 0, request.ReserveChangeAddress); change = (derivation.ScriptPubKey, derivation.KeyPath); } else { change = (request.ExplicitChangeAddress.ScriptPubKey, null); } txBuilder.SetChange(change.ScriptPubKey); psbt = txBuilder.BuildPSBT(false); } var tx = psbt.GetOriginalTransaction(); if (request.Version is uint v) { tx.Version = v; } psbt = txBuilder.CreatePSBTFrom(tx, false, SigHash.All); // Maybe it is a change that we know about, let's search in the DB if (hasChange && change.KeyPath == null) { var keyInfos = await repo.GetKeyInformations(new[] { request.ExplicitChangeAddress.ScriptPubKey }); if (keyInfos.TryGetValue(request.ExplicitChangeAddress.ScriptPubKey, out var kis)) { var ki = kis.FirstOrDefault(k => k.DerivationStrategy == strategy); if (ki != null) { change = (change.ScriptPubKey, kis.First().KeyPath); } } } await UpdatePSBTCore(new UpdatePSBTRequest() { DerivationScheme = strategy, PSBT = psbt, RebaseKeyPaths = request.RebaseKeyPaths }, network); var resp = new CreatePSBTResponse() { PSBT = psbt, ChangeAddress = hasChange ? change.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork) : null }; return(Json(resp, network.JsonSerializerSettings)); }
public bool Supports(NBXplorerNetwork network) { return(ChainConfigurations.Any(c => network.CryptoCode == c.CryptoCode)); }
internal NRustLightningNetwork(INetworkSet networkSet, NetworkType networkType, NBXplorerNetwork nbXplorerNetwork, KeyPath baseKeyPath, string bolt11InvoicePrefix) { NbXplorerNetwork = nbXplorerNetwork; BaseKeyPath = baseKeyPath; BOLT11InvoicePrefix = bolt11InvoicePrefix; NBitcoinNetwork = networkSet.GetNetwork(networkType); CryptoCode = networkSet.CryptoCode.ToLowerInvariant(); }
private void StartNBXplorer() { var port = CustomServer.FreeTcpPort(); List <(string key, string value)> keyValues = new List <(string key, string value)>(); keyValues.Add(("conf", Path.Combine(datadir, "settings.config"))); keyValues.Add(("datadir", datadir)); keyValues.Add(("port", port.ToString())); keyValues.Add(("network", "regtest")); keyValues.Add(("instancename", Caller)); keyValues.Add(("chains", CryptoCode.ToLowerInvariant())); keyValues.Add(("verbose", "1")); keyValues.Add(($"{CryptoCode.ToLowerInvariant()}rpcauth", Explorer.GetRPCAuth())); keyValues.Add(($"{CryptoCode.ToLowerInvariant()}rpcurl", Explorer.CreateRPCClient().Address.AbsoluteUri)); keyValues.Add(("cachechain", "0")); keyValues.Add(("exposerpc", "1")); keyValues.Add(("rpcnotest", "1")); keyValues.Add(("trimevents", TrimEvents.ToString())); keyValues.Add(("mingapsize", "3")); keyValues.Add(("maxgapsize", "8")); keyValues.Add(($"{CryptoCode.ToLowerInvariant()}startheight", Explorer.CreateRPCClient().GetBlockCount().ToString())); keyValues.Add(($"{CryptoCode.ToLowerInvariant()}nodeendpoint", $"{Explorer.Endpoint.Address}:{Explorer.Endpoint.Port}")); keyValues.Add(("asbcnstr", AzureServiceBusTestConfig.ConnectionString)); keyValues.Add(("asbblockq", AzureServiceBusTestConfig.NewBlockQueue)); keyValues.Add(("asbtranq", AzureServiceBusTestConfig.NewTransactionQueue)); keyValues.Add(("asbblockt", AzureServiceBusTestConfig.NewBlockTopic)); keyValues.Add(("asbtrant", AzureServiceBusTestConfig.NewTransactionTopic)); if (UseRabbitMQ) { keyValues.Add(("rmqhost", RabbitMqTestConfig.RabbitMqHostName)); keyValues.Add(("rmqvirtual", RabbitMqTestConfig.RabbitMqVirtualHost)); keyValues.Add(("rmquser", RabbitMqTestConfig.RabbitMqUsername)); keyValues.Add(("rmqpass", RabbitMqTestConfig.RabbitMqPassword)); keyValues.Add(("rmqtranex", RabbitMqTestConfig.RabbitMqTransactionExchange)); keyValues.Add(("rmqblockex", RabbitMqTestConfig.RabbitMqBlockExchange)); } var args = keyValues.SelectMany(kv => new[] { $"--{kv.key}", kv.value } .Concat(new[] { $"--{CryptoCode.ToLowerInvariant()}hastxindex" })).ToArray(); Host = new WebHostBuilder() .UseConfiguration(new DefaultConfiguration().CreateConfiguration(args)) .UseKestrel() .ConfigureLogging(l => { l.SetMinimumLevel(LogLevel.Information) .AddFilter("System.Net.Http.HttpClient", LogLevel.Error) .AddFilter("Microsoft", LogLevel.Error) .AddFilter("Hangfire", LogLevel.Error) .AddFilter("NBXplorer.Authentication.BasicAuthenticationHandler", LogLevel.Critical) .AddProvider(Logs.LogProvider); }) .UseStartup <Startup>() .Build(); RPC = ((RPCClientProvider)Host.Services.GetService(typeof(RPCClientProvider))).GetRPCClient(CryptoCode); NBXplorerNetwork = ((NBXplorerNetworkProvider)Host.Services.GetService(typeof(NBXplorerNetworkProvider))).GetFromCryptoCode(CryptoCode); var conf = (ExplorerConfiguration)Host.Services.GetService(typeof(ExplorerConfiguration)); Host.Start(); Configuration = conf; _Client = NBXplorerNetwork.CreateExplorerClient(Address); HttpClient = ((IHttpClientFactory)Host.Services.GetService(typeof(IHttpClientFactory))).CreateClient(); HttpClient.BaseAddress = Address; _Client.SetCookieAuth(Path.Combine(conf.DataDir, ".cookie")); Notifications = _Client.CreateLongPollingNotificationSession(); }
public static bool TryParse(ReadOnlySpan <char> strSpan, out DerivationSchemeTrackedSource derivationSchemeTrackedSource, NBXplorerNetwork network) { if (strSpan == null) { throw new ArgumentNullException(nameof(strSpan)); } if (network == null) { throw new ArgumentNullException(nameof(network)); } derivationSchemeTrackedSource = null; if (!strSpan.StartsWith("DERIVATIONSCHEME:".AsSpan(), StringComparison.Ordinal)) { return(false); } try { var factory = network.DerivationStrategyFactory; var derivationScheme = factory.Parse(strSpan.Slice("DERIVATIONSCHEME:".Length).ToString()); derivationSchemeTrackedSource = new DerivationSchemeTrackedSource(derivationScheme); return(true); } catch { return(false); } }
public FingerprintDistribution GetDistribution(NBXplorerNetwork network) { return(data[network].Distribution ?? data[network].DefaultDistribution); }
public KeyPathInformation(KeyPathTemplates keyPathTemplates, KeyPath keyPath, DerivationStrategyBase derivationStrategy, NBXplorerNetwork network) { var derivation = derivationStrategy.GetDerivation(keyPath); ScriptPubKey = derivation.ScriptPubKey; Redeem = derivation.Redeem; TrackedSource = new DerivationSchemeTrackedSource(derivationStrategy); DerivationStrategy = derivationStrategy; Feature = keyPathTemplates.GetDerivationFeature(keyPath); KeyPath = keyPath; Address = network.CreateAddress(derivationStrategy, keyPath, ScriptPubKey); }
private void SetEnvironment() { //CryptoCode = "LTC"; //nodeDownloadData = NodeDownloadData.Litecoin.v0_16_3; //Network = NBitcoin.Altcoins.Litecoin.Instance.Regtest; //CryptoCode = "BCH"; //nodeDownloadData = NodeDownloadData.BCash.v0_16_2; //Network = NBitcoin.Altcoins.BCash.Instance.Regtest; //Tests of DOGE are broken because it outpoint locking seems to work differently //CryptoCode = "DOGE"; //nodeDownloadData = NodeDownloadData.Dogecoin.v1_10_0; //Network = NBitcoin.Altcoins.Dogecoin.Instance.Regtest; //RPCStringAmount = false; //CryptoCode = "DASH"; //nodeDownloadData = NodeDownloadData.Dash.v0_12_2; //Network = NBitcoin.Altcoins.Dash.Instance.Regtest; //CryptoCode = "POLIS"; //nodeDownloadData = NodeDownloadData.Polis.v1_3_1; //Network = NBitcoin.Altcoins.Polis.Instance.Regtest; //CryptoCode = "BTG"; //nodeDownloadData = NodeDownloadData.BGold.v0_15_0; //Network = NBitcoin.Altcoins.BGold.Instance.Regtest; //CryptoCode = "MONA"; //nodeDownloadData = NodeDownloadData.Monacoin.v0_15_1; //Network = NBitcoin.Altcoins.Monacoin.Instance.Regtest; //CryptoCode = "FTC"; //nodeDownloadData = NodeDownloadData.Feathercoin.v0_16_0; //Network = NBitcoin.Altcoins.Feathercoin.Instance.Regtest; //CryptoCode = "UFO"; //nodeDownloadData = NodeDownloadData.Ufo.v0_16_0; //Network = NBitcoin.Altcoins.Ufo.Instance.Regtest; //CryptoCode = "VIA"; //nodeDownloadData = NodeDownloadData.Viacoin.v0_15_1; //Network = NBitcoin.Altcoins.Viacoin.Instance.Regtest; //CryptoCode = "GRS"; //nodeDownloadData = NodeDownloadData.Groestlcoin.v2_16_0; //Network = NBitcoin.Altcoins.Groestlcoin.Instance.Regtest; //CryptoCode = "BTX"; //nodeDownloadData = NodeDownloadData.Bitcore.v0_15_2; //Network = NBitcoin.Altcoins.Bitcore.Instance.Regtest; //CryptoCode = "XMCC"; //nodeDownloadData = NodeDownloadData.Monoeci.v0_12_2_3; //Network = NBitcoin.Altcoins.Monoeci.Instance.Regtest; //RPCSupportSegwit = false; //CryptoCode = "GBX"; //nodeDownloadData = NodeDownloadData.Gobyte.v0_12_2_4; //Network = NBitcoin.Altcoins.Gobyte.Instance.Regtest; //RPCSupportSegwit = false; //CryptoCode = "COLX"; //nodeDownloadData = NodeDownloadData.Colossus.v1_1_1; //Network = NBitcoin.Altcoins.Colossus.Instance.Regtest; //RPCSupportSegwit = false; //CryptoCode = "LBTC"; //nodeDownloadData = NodeDownloadData.Elements.v0_18_1_1; //NBXplorerNetwork = new NBXplorerNetwork(NBitcoin.Altcoins.Liquid.Instance, NetworkType.Regtest); // CryptoCode = "BTC"; nodeDownloadData = NodeDownloadData.Bitcoin.v0_18_0; NBXplorerNetwork = new NBXplorerNetwork(Network.RegTest.NetworkSet, NetworkType.Regtest); }
public static async Task <RPCCapabilities> TestRPCAsync(NBXplorerNetwork networkInfo, RPCClient rpcClient, CancellationToken cancellation) { var network = networkInfo.NBitcoinNetwork; Logs.Configuration.LogInformation($"{networkInfo.CryptoCode}: Testing RPC connection to " + rpcClient.Address.AbsoluteUri); RPCResponse blockchainInfo = null; try { int time = 0; retry: try { blockchainInfo = await rpcClient.SendCommandAsync("getblockchaininfo"); blockchainInfo.ThrowIfError(); } catch (RPCException ex) when(IsTransient(ex)) { Logs.Configuration.LogInformation($"{networkInfo.CryptoCode}: Transient error '{ex.Message}', retrying soon..."); time++; await Task.Delay(Math.Min(1000 * time, 10000), cancellation); goto retry; } } catch (ConfigException) { throw; } catch (RPCException ex) { Logs.Configuration.LogError($"{networkInfo.CryptoCode}: Invalid response from RPC server " + ex.Message); throw new ConfigException(); } catch (Exception ex) { Logs.Configuration.LogError($"{networkInfo.CryptoCode}: Error connecting to RPC server " + ex.Message); throw new ConfigException(); } Logs.Configuration.LogInformation($"{networkInfo.CryptoCode}: RPC connection successful"); if (blockchainInfo?.Result["chain"]?.Value <string>() is string rpcChain) { List <string> expectedNames = new List <string>(); expectedNames.Add(network.ChainName.ToString()); expectedNames.Add(network.ChainName.ToString().Replace("net", string.Empty, StringComparison.OrdinalIgnoreCase)); if (!expectedNames.Any(e => rpcChain.Equals(e, StringComparison.OrdinalIgnoreCase))) { Logs.Configuration.LogError($"{networkInfo.CryptoCode}: NBXplorer expected chain is '{network.ChainName}', but the RPC node is running '{rpcChain}'"); throw new ConfigException(); } } var capabilities = await rpcClient.ScanRPCCapabilitiesAsync(); if (capabilities.Version < networkInfo.MinRPCVersion) { Logs.Configuration.LogError($"{networkInfo.CryptoCode}: The minimum node version required is {networkInfo.MinRPCVersion} (detected: {capabilities.Version})"); throw new ConfigException(); } Logs.Configuration.LogInformation($"{networkInfo.CryptoCode}: Full node version detected: {capabilities.Version}"); return(capabilities); }
public KeyPathInformation(Derivation derivation, DerivationSchemeTrackedSource derivationStrategy, DerivationFeature feature, KeyPath keyPath, NBXplorerNetwork network) { ScriptPubKey = derivation.ScriptPubKey; Redeem = derivation.Redeem; TrackedSource = derivationStrategy; DerivationStrategy = derivationStrategy.DerivationStrategy; Feature = feature; KeyPath = keyPath; Address = network.CreateAddress(derivationStrategy.DerivationStrategy, keyPath, ScriptPubKey); }
public TrackedSourceJsonConverter(NBXplorerNetwork network) { Network = network; }
public async Task <IActionResult> CreatePSBT( [ModelBinder(BinderType = typeof(NetworkModelBinder))] NBXplorerNetwork network, [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))] DerivationStrategyBase strategy, [FromBody] JObject body) { if (body == null) { throw new ArgumentNullException(nameof(body)); } CreatePSBTRequest request = ParseJObject <CreatePSBTRequest>(body, network); if (strategy == null) { throw new ArgumentNullException(nameof(strategy)); } var repo = RepositoryProvider.GetRepository(network); var utxos = await GetUTXOs(network.CryptoCode, strategy, null); var txBuilder = request.Seed is int s?network.NBitcoinNetwork.CreateTransactionBuilder(s) : network.NBitcoinNetwork.CreateTransactionBuilder(); if (Waiters.GetWaiter(network).NetworkInfo?.GetRelayFee() is FeeRate feeRate) { txBuilder.StandardTransactionPolicy.MinRelayTxFee = feeRate; } txBuilder.OptInRBF = request.RBF; if (request.LockTime is LockTime lockTime) { txBuilder.SetLockTime(lockTime); txBuilder.OptInRBF = true; } var availableCoinsByOutpoint = utxos.GetUnspentCoins(request.MinConfirmations).ToDictionary(o => o.Outpoint); if (request.IncludeOnlyOutpoints != null) { var includeOnlyOutpoints = request.IncludeOnlyOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => includeOnlyOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } if (request.ExcludeOutpoints?.Any() is true) { var excludedOutpoints = request.ExcludeOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => !excludedOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } txBuilder.AddCoins(availableCoinsByOutpoint.Values); foreach (var dest in request.Destinations) { if (dest.SweepAll) { txBuilder.SendAll(dest.Destination); } else { txBuilder.Send(dest.Destination, dest.Amount); if (dest.SubstractFees) { txBuilder.SubtractFees(); } } } (Script ScriptPubKey, KeyPath KeyPath)change = (null, null); bool hasChange = false; // We first build the transaction with a change which keep the length of the expected change scriptPubKey // This allow us to detect if there is a change later in the constructed transaction. // This defend against bug which can happen if one of the destination is the same as the expected change // This assume that a script with only 0 can't be created from a strategy, nor by passing any data to explicitChangeAddress if (request.ExplicitChangeAddress == null) { // The dummyScriptPubKey is necessary to know the size of the change var dummyScriptPubKey = utxos.Unconfirmed.UTXOs.FirstOrDefault()?.ScriptPubKey ?? utxos.Confirmed.UTXOs.FirstOrDefault()?.ScriptPubKey ?? strategy.Derive(0).ScriptPubKey; change = (Script.FromBytesUnsafe(new byte[dummyScriptPubKey.Length]), null); } else { change = (Script.FromBytesUnsafe(new byte[request.ExplicitChangeAddress.ScriptPubKey.Length]), null); } txBuilder.SetChange(change.ScriptPubKey); PSBT psbt = null; try { if (request.FeePreference?.ExplicitFeeRate is FeeRate explicitFeeRate) { txBuilder.SendEstimatedFees(explicitFeeRate); } else if (request.FeePreference?.BlockTarget is int blockTarget) { try { var rate = await GetFeeRate(blockTarget, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } else if (request.FeePreference?.ExplicitFee is Money explicitFee) { txBuilder.SendFees(explicitFee); } else { try { var rate = await GetFeeRate(1, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } psbt = txBuilder.BuildPSBT(false); hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey); } catch (NotEnoughFundsException) { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "Not enough funds for doing this transaction")); } if (hasChange) // We need to reserve an address, so we need to build again the psbt { if (request.ExplicitChangeAddress == null) { var derivation = await repo.GetUnused(strategy, DerivationFeature.Change, 0, request.ReserveChangeAddress); change = (derivation.ScriptPubKey, derivation.KeyPath); } else { change = (request.ExplicitChangeAddress.ScriptPubKey, null); } txBuilder.SetChange(change.ScriptPubKey); psbt = txBuilder.BuildPSBT(false); } var tx = psbt.GetOriginalTransaction(); if (request.Version is uint v) { tx.Version = v; } psbt = txBuilder.CreatePSBTFrom(tx, false, SigHash.All); var outputsKeyInformations = repo.GetKeyInformations(psbt.Outputs.Where(o => !o.HDKeyPaths.Any()).Select(o => o.ScriptPubKey).ToArray()); var utxosByOutpoint = utxos.GetUnspentUTXOs().ToDictionary(u => u.Outpoint); // Maybe it is a change that we know about, let's search in the DB if (hasChange && change.KeyPath == null) { var keyInfos = await repo.GetKeyInformations(new[] { request.ExplicitChangeAddress.ScriptPubKey }); if (keyInfos.TryGetValue(request.ExplicitChangeAddress.ScriptPubKey, out var kis)) { var ki = kis.FirstOrDefault(k => k.DerivationStrategy == strategy); if (ki != null) { change = (change.ScriptPubKey, kis.First().KeyPath); } } } var pubkeys = strategy.GetExtPubKeys().Select(p => p.AsHDKeyCache()).ToArray(); var keyPaths = psbt.Inputs.Select(i => utxosByOutpoint[i.PrevOut].KeyPath).ToList(); if (hasChange && change.KeyPath != null) { keyPaths.Add(change.KeyPath); } var fps = new Dictionary <PubKey, HDFingerprint>(); foreach (var pubkey in pubkeys) { // We derive everything the fastest way possible on multiple cores pubkey.Derive(keyPaths.ToArray()); fps.TryAdd(pubkey.GetPublicKey(), pubkey.GetPublicKey().GetHDFingerPrint()); } foreach (var input in psbt.Inputs) { var utxo = utxosByOutpoint[input.PrevOut]; foreach (var pubkey in pubkeys) { var childPubKey = pubkey.Derive(utxo.KeyPath); NBitcoin.Extensions.TryAdd(input.HDKeyPaths, childPubKey.GetPublicKey(), Tuple.Create(fps[pubkey.GetPublicKey()], utxo.KeyPath)); } } await Task.WhenAll(psbt.Inputs .Select(async(input) => { if (input.WitnessUtxo == null) // We need previous tx { var prev = await repo.GetSavedTransactions(input.PrevOut.Hash); if (prev?.Any() is true) { input.NonWitnessUtxo = prev[0].Transaction; } } }).ToArray()); var outputsKeyInformationsResult = await outputsKeyInformations; foreach (var output in psbt.Outputs) { foreach (var keyInfo in outputsKeyInformationsResult[output.ScriptPubKey].Where(o => o.DerivationStrategy == strategy)) { foreach (var pubkey in pubkeys) { var childPubKey = pubkey.Derive(keyInfo.KeyPath); NBitcoin.Extensions.TryAdd(output.HDKeyPaths, childPubKey.GetPublicKey(), Tuple.Create(fps[pubkey.GetPublicKey()], keyInfo.KeyPath)); } } } if (request.RebaseKeyPaths != null) { foreach (var rebase in request.RebaseKeyPaths) { if (rebase.AccountKeyPath == null) { throw new NBXplorerException(new NBXplorerError(400, "missing-parameter", "rebaseKeyPaths[].accountKeyPath is missing")); } if (rebase.AccountKey == null) { throw new NBXplorerException(new NBXplorerError(400, "missing-parameter", "rebaseKeyPaths[].accountKey is missing")); } psbt.RebaseKeyPaths(rebase.AccountKey, rebase.AccountKeyPath, rebase.MasterFingerprint); } } var resp = new CreatePSBTResponse() { PSBT = psbt, ChangeAddress = hasChange ? change.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork) : null }; return(Json(resp, network.JsonSerializerSettings)); }
public BitcoinDStateChangedEvent(NBXplorerNetwork network, BitcoinDWaiterState oldState, BitcoinDWaiterState newState) { OldState = oldState; NewState = newState; Network = network; }