public NbxChainWatchInterface(ExplorerClient nbxplorerClient, ILogger <NbxChainWatchInterface> logger, NRustLightningNetwork network) { _logger = logger; _network = network; NbxplorerClient = nbxplorerClient; util = new ChainWatchInterfaceUtil(network.NBitcoinNetwork); }
public Channel <FeeRateSet> GetFeeRateChannel(NRustLightningNetwork n) { if (!_feeRateChannels.TryGetValue(n.CryptoCode, out var feeRateChannel)) { throw new Exception($"Channel not found for {n.CryptoCode}! this should never happen"); } return(feeRateChannel); }
public Task <BitcoinAddress> GetNewAddressAsync(NRustLightningNetwork network, CancellationToken ct = default) { var p = this.GetBaseXPriv(network); var t = Task.FromResult(p.Derive(_derivationCount).Neuter().ExtPubKey.PubKey.WitHash.GetAddress(network.NBitcoinNetwork)); _derivationCount++; return(t); }
public async Task <BitcoinAddress> GetNewAddressAsync(NRustLightningNetwork network, CancellationToken ct = default) { var cli = _nbXplorerClientProvider.GetClient(network); var deriv = await GetOurDerivationStrategyAsync(network, ct); var a = await cli.GetUnusedAsync(deriv, DerivationFeature.Deposit, 0, false, ct); return(a.Address); }
private PSBT SignPSBT(PSBT psbt, NRustLightningNetwork network) { if (BaseXPrivs.TryGetValue(network, out var xpriv)) { psbt.SignAll(ScriptPubKeyType.Segwit, xpriv); } return(psbt); }
private BitcoinExtKey GetBaseXPriv(NRustLightningNetwork network) { if (BaseXPrivs.TryGetValue(network, out var baseKey)) { return(baseKey); } baseKey = new BitcoinExtKey(new ExtKey(_keysRepository.GetNodeSecret().ToBytes()).Derive(network.BaseKeyPath), network.NBitcoinNetwork); BaseXPrivs.TryAdd(network, baseKey); return(baseKey); }
public async Task TrackSpendableOutput(NRustLightningNetwork network, SpendableOutputDescriptor desc, CancellationToken ct = default) { var client = _nbXplorerClientProvider.GetClient(network); switch (desc) { case SpendableOutputDescriptor.StaticOutput staticOutput: { var addr = staticOutput.Item.Output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork); var s = new AddressTrackedSource(addr); await client.TrackAsync(s, ct); await client.RPCClient.ImportAddressAsync(addr); break; } case SpendableOutputDescriptor.DynamicOutputP2WSH p2wshOutput: { var(param1, param2) = p2wshOutput.Item.KeyDerivationParams.ToValueTuple(); var amountSat = (ulong)p2wshOutput.Item.Output.Value.Satoshi; var channelKeys = _keysRepository.DeriveChannelKeys(amountSat, param1, param2); var delayedPaymentKey = Generators.derivePrivKey(_secp256k1Ctx, channelKeys.DelayedPaymentBaseKey, p2wshOutput.Item.PerCommitmentPoint.PubKey); var toSelfDelay = p2wshOutput.Item.ToSelfDelay; var revokeableRedeemScript = _keysRepository.GetRevokeableRedeemScript(p2wshOutput.Item.RemoteRevocationPubKey, toSelfDelay, delayedPaymentKey.PubKey); Debug.Assert(p2wshOutput.Item.Output.ScriptPubKey.Equals(revokeableRedeemScript.WitHash.ScriptPubKey)); var addr = revokeableRedeemScript.WitHash.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork); var s = new AddressTrackedSource(addr); await client.TrackAsync(s, ct); await client.RPCClient.ImportAddressAsync(addr); break; } case SpendableOutputDescriptor.StaticOutputRemotePayment remoteOutput: { var(p1, p2) = remoteOutput.Item.KeyDerivationParams.ToValueTuple(); var amountSat = (ulong)remoteOutput.Item.Output.Value; var keys = _keysRepository.DeriveChannelKeys(amountSat, p1, p2); Debug.Assert( keys.PaymentKey.PubKey.WitHash.ScriptPubKey.Equals(remoteOutput.Item.Output.ScriptPubKey)); var addr = remoteOutput.Item.Output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork); await client.TrackAsync(new AddressTrackedSource(addr), ct); await client.RPCClient.ImportAddressAsync(addr); break; } } }
/// <summary> /// Get total UTXO originated as <see cref="SpendableOutputDescriptor"/> which we currently have. /// TODO: Stop using Bitcoin-core RPC feature (TrackAsync and ListUnspentAsync) and batch query to nbx when https://github.com/dgarage/NBXplorer/issues/291 is ready. /// </summary> /// <returns></returns> private async Task <Money> GetAmountForLNOutput(NRustLightningNetwork network) { var repo = _repositoryProvider.GetRepository(network); var cli = _nbXplorerClientProvider.GetClient(network); var ourAddreses = repo.GetAllSpendableOutputDescriptors().ToEnumerable().Select(desc => desc.Output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)).ToArray(); var currentHeight = await cli.RPCClient.GetBlockCountAsync(); var coins = await cli.RPCClient.ListUnspentAsync(0, currentHeight, ourAddreses); return(coins.Where(coin => ourAddreses.Contains(coin.Address)).Sum(x => x.Amount)); }
internal RPCClient ConfigureRPCClient(NRustLightningNetwork nRustLightningNetwork, ILogger logger) { var n = nRustLightningNetwork.NBitcoinNetwork; RPCClient rpcClient = null; if (Url != null && User != null && Password != null) { rpcClient = new RPCClient(new NetworkCredential(User, Password), Url, n); } if (rpcClient is null) { if (CookieFile != null) { try { rpcClient = new RPCClient(new RPCCredentialString { CookieFile = CookieFile }, Url, n); } catch (IOException) { logger.LogWarning($"{nRustLightningNetwork.CryptoCode}: RPC Cookie file not found at " + (CookieFile ?? RPCClient.GetDefaultCookieFilePath(n))); } } if (AuthenticationString != null) { rpcClient = new RPCClient(RPCCredentialString.Parse(AuthenticationString), Url, n); } if (rpcClient is null) { try { rpcClient = new RPCClient(null as NetworkCredential, Url, n); } catch { // ignored } if (rpcClient == null) { logger.LogError($"{nRustLightningNetwork.CryptoCode}: RPC connection settings not configured"); throw new ConfigException(); } } } return(rpcClient); }
public async Task <Money> GetBalanceAsync(NRustLightningNetwork network, CancellationToken ct = default) { var cli = _nbXplorerClientProvider.GetClient(network); var derivationScheme = await GetOurDerivationStrategyAsync(network, ct); var b = await cli.GetBalanceAsync(derivationScheme, ct); if (b.Total is Money m) { return(m); } throw new NRustLightningException($"Unexpected money type {b.Total.GetType().Name}"); }
public WorkQueueProcessor(ChannelProvider channelProvider, P2PConnectionHandler p2PConnectionHandler, ILogger <WorkQueueProcessor> logger, PeerManagerProvider peerManagerProvider, NRustLightningNetwork network, IWalletService walletService, IRepository repo) { _channelProvider = channelProvider; _p2PConnectionHandler = p2PConnectionHandler; _logger = logger; _peerManagerProvider = peerManagerProvider; _network = network; _walletService = walletService; _repo = repo; _tasks = new [] { HandleOutboundConnectQueue(), HandleSpendableOutputEventQueue() }; }
public NBXplorerListener( ExplorerClient explorerClient, PeerManagerProvider peerManagerProvider, ILogger <NBXplorerListener> logger, ChannelWriter <FeeRateSet> feeRateWriter, NRustLightningNetwork network) { _explorerClient = explorerClient; _peerManagerProvider = peerManagerProvider; _logger = logger; _feeRateWriter = feeRateWriter; _network = network; }
public async Task <PaymentRequest> GetNewInvoice(NRustLightningNetwork network, InvoiceCreationOption option) { if (network == null) { throw new ArgumentNullException(nameof(network)); } Primitives.PaymentPreimage paymentPreimage = Primitives.PaymentPreimage.Create(RandomUtils.GetBytes(32)); var nodeId = Primitives.NodeId.NewNodeId(_keysRepository.GetNodeId()); var paymentHash = paymentPreimage.Hash; var taggedFields = new List <TaggedField> { TaggedField.NewPaymentHashTaggedField(paymentHash), TaggedField.NewNodeIdTaggedField(nodeId), (option.EncodeDescriptionWithHash.HasValue && option.EncodeDescriptionWithHash.Value) ? TaggedField.NewDescriptionHashTaggedField(new uint256(Hashes.SHA256(Encoding.UTF8.GetBytes(option.Description)), false)) : TaggedField.NewDescriptionTaggedField(option.Description), }; if (option.PaymentSecret != null) { taggedFields.Add(TaggedField.NewPaymentSecretTaggedField(option.PaymentSecret)); } var t = new TaggedFields(taggedFields.ToFSharpList()); var r = PaymentRequest.TryCreate(network.BOLT11InvoicePrefix, option.Amount.ToFSharpOption(), _systemClock.UtcNow, nodeId, t, _keysRepository.AsMessageSigner()); if (r.IsError) { throw new InvalidDataException($"Error when creating our payment request: {r.ErrorValue}"); } _logger.LogDebug($"Publish new invoice with hash {paymentHash}"); using var tx = await _engine.OpenTransaction(); var table = tx.GetTable(DBKeys.HashToPreimage); await table.Insert(paymentHash.ToBytes(false), paymentPreimage.ToByteArray()); var table2 = tx.GetTable(DBKeys.HashToInvoice); await table2.Insert(paymentHash.ToBytes(false).ToHexString(), r.ResultValue.ToString()); await tx.Commit(); return(r.ResultValue); }
public RustLightningEventReactor( P2PConnectionHandler connectionHandler, IPeerManagerProvider peerManagerProvider, IWalletService walletService, NRustLightningNetwork network, EventAggregator eventAggregator, ILogger logger, IInvoiceRepository invoiceRepository) { _pool = MemoryPool <byte> .Shared; _connectionHandler = connectionHandler; _peerManagerProvider = peerManagerProvider; _walletService = walletService; _network = network; _eventAggregator = eventAggregator; _logger = logger; _invoiceRepository = invoiceRepository; _peerManager = peerManagerProvider.TryGetPeerManager(network.CryptoCode); }
public NRustLightningClient(string baseUrl, NRustLightningNetwork network, X509Certificate2?certificate = null) { var handler = new HttpClientHandler() { ClientCertificateOptions = ClientCertificateOption.Automatic, }; if (certificate != null) { handler.ClientCertificates.Add(certificate); } cryptoCode = network.CryptoCode; var ser = new RepositorySerializer(network); ser.ConfigureSerializer(jsonSerializerOptions); HttpClient = new HttpClient(handler); _baseUri = new Uri(baseUrl); }
public async Task <PaymentRequest> GetNewInvoice(NRustLightningNetwork network, InvoiceCreationOption option, CancellationToken ct = default) { if (network == null) { throw new ArgumentNullException(nameof(network)); } Primitives.PaymentPreimage paymentPreimage = Primitives.PaymentPreimage.Create(RandomUtils.GetBytes(32)); var nodeId = Primitives.NodeId.NewNodeId(_keysRepository.GetNodeId()); var paymentHash = paymentPreimage.Hash; var taggedFields = new List <TaggedField> { TaggedField.NewPaymentHashTaggedField(paymentHash), TaggedField.NewNodeIdTaggedField(nodeId), (option.EncodeDescriptionWithHash.HasValue && option.EncodeDescriptionWithHash.Value) ? TaggedField.NewDescriptionHashTaggedField(new uint256(Hashes.SHA256(Encoding.UTF8.GetBytes(option.Description)), false)) : TaggedField.NewDescriptionTaggedField(option.Description), }; if (option.PaymentSecret != null) { taggedFields.Add(TaggedField.NewPaymentSecretTaggedField(option.PaymentSecret)); } var t = new TaggedFields(taggedFields.ToFSharpList()); var r = PaymentRequest.TryCreate(network.BOLT11InvoicePrefix, option.Amount.ToFSharpOption(), DateTimeOffset.UtcNow, nodeId, t, _keysRepository.AsMessageSigner()); if (r.IsError) { throw new InvalidDataException($"Error when creating our payment request: {r.ErrorValue}"); } _logger.LogDebug($"Publish new invoice with hash {paymentHash}"); await _repository.SetInvoice(r.ResultValue, ct); await _repository.SetPreimage(paymentPreimage, ct); return(r.ResultValue); }
public Task <UTXOChangesWithMetadata> ListUnspent(NRustLightningNetwork network) { var res = new UTXOChangesWithMetadata(); var confirmed = new UTXOChangeWithSpentOutput(); var unconfirmed = new UTXOChangeWithSpentOutput(); confirmed.SpentOutPoint = new List <OutPoint>() { OutPoint.Zero }; var coinBaseTx = Transaction.Parse( "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401750101ffffffff0200f2052a0100000017a914d4bb8bf5f987cd463a2f5e6e4f04618c7aaed1b5870000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", network.NBitcoinNetwork); confirmed.UTXO = new List <UTXOChangeWithMetadata>() { new UTXOChangeWithMetadata(new UTXO(new NBXplorer.Models.UTXO(coinBaseTx.Outputs.AsCoins().First())), UTXOKind.UserDeposit, new AddressTrackedSource(coinBaseTx.Outputs[0].ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork))) }; res.Confirmed = confirmed; res.UnConfirmed = unconfirmed; return(Task.FromResult(res)); }
public RustLightningEventReactor( P2PConnectionHandler connectionHandler, PeerManagerProvider peerManagerProvider, IWalletService walletService, NRustLightningNetwork network, EventAggregator eventAggregator, ILogger logger, InvoiceService invoiceService, ChannelProvider channelProvider, IRepository repository) { _pool = MemoryPool <byte> .Shared; _connectionHandler = connectionHandler; _peerManagerProvider = peerManagerProvider; _walletService = walletService; _network = network; _eventAggregator = eventAggregator; _logger = logger; _invoiceService = invoiceService; _channelProvider = channelProvider; _repository = repository; }
public async ValueTask <DerivationStrategyBase> GetOurDerivationStrategyAsync(NRustLightningNetwork network, CancellationToken ct = default) { DerivationStrategyBase strategy; if (DerivationStrategyBaseCache.TryGetValue(network, out strategy)) { return(strategy); } var baseKey = GetBaseXPriv(network); strategy = network.NbXplorerNetwork.DerivationStrategyFactory.CreateDirectDerivationStrategy(baseKey.Neuter(), new DerivationStrategyOptions { ScriptPubKeyType = ScriptPubKeyType.Segwit, }); _logger.LogInformation($"Tracking new xpub ({strategy}) for {network.CryptoCode} with nbxplorer"); var cli = _nbXplorerClientProvider.GetClient(network); await cli.TrackAsync(strategy, ct); DerivationStrategyBaseCache[network] = strategy; return(strategy); }
public Task <Transaction> GetSendingTxAsync(BitcoinAddress destination, Money amount, NRustLightningNetwork network, CancellationToken cancellationToken = default) { return(Task.FromResult(network.NBitcoinNetwork.CreateTransaction())); }
private async Task <PSBT> SignPSBT(PSBT psbt, NRustLightningNetwork network) { var repo = _repositoryProvider.GetRepository(network); var outpoints = psbt.Inputs.Select(txIn => txIn.PrevOut); var signingKeys = new List <Key>(); await foreach (var(desc, i) in repo.GetSpendableOutputDescriptors(outpoints).Select((x, i) => (x, i))) { if (desc is null) { // We don't have any information in repo. This probably means the UTXO is not LN-origin // (i.e. those are the funds user provided by on-chain) continue; } switch (desc) { case SpendableOutputDescriptor.StaticOutput _: // signature for this input will be generated by Destination key (see below). // so no need for any operation. break; case SpendableOutputDescriptor.DynamicOutputP2WSH p2wshOutput: { var(param1, param2) = p2wshOutput.Item.KeyDerivationParams.ToValueTuple(); var amountSat = (ulong)p2wshOutput.Item.Output.Value.Satoshi; var channelKeys = _keysRepository.DeriveChannelKeys(amountSat, param1, param2); var delayedPaymentKey = Generators.derivePrivKey(_secp256k1Ctx, channelKeys.DelayedPaymentBaseKey, p2wshOutput.Item.PerCommitmentPoint.PubKey); var toSelfDelay = p2wshOutput.Item.ToSelfDelay; var revokeableRedeemScript = _keysRepository.GetRevokeableRedeemScript(p2wshOutput.Item.RemoteRevocationPubKey, toSelfDelay, delayedPaymentKey.PubKey); Debug.Assert( p2wshOutput.Item.Output.ScriptPubKey.Equals(revokeableRedeemScript.WitHash.ScriptPubKey)); psbt.AddScripts(revokeableRedeemScript); psbt.Inputs[i].SetSequence(toSelfDelay); signingKeys.Add(delayedPaymentKey); break; } case SpendableOutputDescriptor.StaticOutputRemotePayment remoteOutput: { var(p1, p2) = remoteOutput.Item.KeyDerivationParams.ToValueTuple(); var amountSat = (ulong)remoteOutput.Item.Output.Value; var keys = _keysRepository.DeriveChannelKeys(amountSat, p1, p2); Debug.Assert( keys.PaymentKey.PubKey.WitHash.ScriptPubKey.Equals(remoteOutput.Item.Output.ScriptPubKey)); signingKeys.Add(keys.PaymentKey); break; } default: throw new Exception($"Unreachable! Unknown output descriptor type {desc}"); } } // sign for user provided on-chain funds utxos. if (BaseXPrivs.TryGetValue(network, out var xpriv)) { psbt.SignAll(ScriptPubKeyType.Segwit, xpriv); } // sign for static-outputs. Which RL gave us as a result of off-chain balance handling. var destinationKey = _keysRepository.GetDestinationKey(); psbt.SignWithKeys(destinationKey); // sign with other keys which we have saved in repo. foreach (var sk in signingKeys) { psbt.SignWithKeys(sk); } return(psbt); }
public async Task <UTXOChangesWithMetadata> ListUnspent(NRustLightningNetwork n) { var _cli = _nbXplorerClientProvider.GetClient(n); var deriv = await GetOurDerivationStrategyAsync(n); var confirmedUtxo = new List <UTXOChangeWithMetadata>(); var unconfirmedUtxo = new List <UTXOChangeWithMetadata>(); var confirmedSpentOutpoints = new List <OutPoint>(); var unconfirmedSpentOutpoints = new List <OutPoint>(); var nativeFundsResponse = await _cli.GetUTXOsAsync(deriv); foreach (var utxo in nativeFundsResponse.Confirmed.UTXOs) { var item = new UTXOChangeWithMetadata(new UTXO(utxo), UTXOKind.UserDeposit, new DerivationSchemeTrackedSource(deriv)); confirmedUtxo.Add(item); } foreach (var utxo in nativeFundsResponse.Unconfirmed.UTXOs) { unconfirmedUtxo.Add(new UTXOChangeWithMetadata(new UTXO(utxo), UTXOKind.UserDeposit, new DerivationSchemeTrackedSource(deriv))); } confirmedSpentOutpoints.AddRange(nativeFundsResponse.Confirmed.SpentOutpoints); unconfirmedSpentOutpoints.AddRange(nativeFundsResponse.Unconfirmed.SpentOutpoints); var repo = _repositoryProvider.GetRepository(n); // TODO: Refactor when https://github.com/dgarage/NBXplorer/issues/291 is ready await foreach (var o in repo.GetAllSpendableOutputDescriptors()) { var addr = o.Output.ScriptPubKey.GetDestinationAddress(n.NBitcoinNetwork); var ts = new AddressTrackedSource(addr); var lnUTXOResposne = await _cli.GetUTXOsAsync(ts); foreach (var utxo in lnUTXOResposne.Confirmed.UTXOs) { confirmedUtxo.Add(new UTXOChangeWithMetadata(new UTXO(utxo), o.GetKind(), ts)); } foreach (var utxo in lnUTXOResposne.Unconfirmed.UTXOs) { unconfirmedUtxo.Add(new UTXOChangeWithMetadata(new UTXO(utxo), o.GetKind(), ts)); } confirmedSpentOutpoints.AddRange(lnUTXOResposne.Confirmed.SpentOutpoints); unconfirmedSpentOutpoints.AddRange(lnUTXOResposne.Unconfirmed.SpentOutpoints); } ; var confirmed = new UTXOChangeWithSpentOutput() { UTXO = confirmedUtxo, SpentOutPoint = confirmedSpentOutpoints }; var unconfirmed = new UTXOChangeWithSpentOutput() { UTXO = unconfirmedUtxo, SpentOutPoint = unconfirmedSpentOutpoints }; return(new UTXOChangesWithMetadata() { Confirmed = confirmed, UnConfirmed = unconfirmed, CurrentHeight = nativeFundsResponse.CurrentHeight }); }
public Task <Money> GetBalanceAsync(NRustLightningNetwork network, CancellationToken ct = default) { return(Task.FromResult(Money.Zero)); }
public Channel <PeerConnectionString> GetOutboundConnectionRequestQueue(NRustLightningNetwork n) => GetOutboundConnectionRequestQueue(n.CryptoCode);
public Task BroadcastAsync(Transaction tx, NRustLightningNetwork network) { return(Task.CompletedTask); }
public async Task <Transaction> GetSendingTxAsync(BitcoinAddress destination, Money amount, NRustLightningNetwork network, CancellationToken cancellationToken = default) { var deriv = await GetOurDerivationStrategyAsync(network, cancellationToken).ConfigureAwait(false); var nbXplorerClient = _nbXplorerClientProvider.GetClient(network); await _outpointAssumedAsSpentLock.WaitAsync(cancellationToken); try { var req = new CreatePSBTRequest() { Destinations = new[] { amount == Money.Zero ? new CreatePSBTDestination { SweepAll = true, Destination = destination } : new CreatePSBTDestination { Amount = amount, Destination = destination } , }.ToList(), ExcludeOutpoints = _outpointAssumedAsSpent.ToList() }; var psbtResponse = await nbXplorerClient.CreatePSBTAsync(deriv, req, cancellationToken) .ConfigureAwait(false); var psbt = await SignPSBT(psbtResponse.PSBT, network); if (!psbt.IsAllFinalized()) { psbt.Finalize(); } var tx = psbt.ExtractTransaction(); foreach (var prevOut in tx.Inputs.Select(txIn => txIn.PrevOut)) { _outpointAssumedAsSpent.Enqueue(prevOut); } return(tx); } finally { _outpointAssumedAsSpentLock.Release(); } }
public Task TrackSpendableOutput(NRustLightningNetwork network, SpendableOutputDescriptor desc, CancellationToken ct = default) { return(Task.CompletedTask); }
public RepositorySerializer(NRustLightningNetwork network) { _network = network ?? throw new ArgumentNullException(nameof(network)); ConfigureSerializer(Options); }
public PeerManager?TryGetPeerManager(NRustLightningNetwork network) { Debug.Assert(network.NBitcoinNetwork.NetworkType == NetworkType.Regtest); return(TryGetPeerManager(network.CryptoCode)); }
public async Task BroadcastAsync(Transaction tx, NRustLightningNetwork n) { await _nbXplorerClientProvider.GetClient(n).BroadcastAsync(tx); }