public async Task <OpenChannelResponse> OpenChannel(OpenChannelRequest openChannelRequest, CancellationToken cancellation = default(CancellationToken)) { try { var result = await _eclairClient.Open(openChannelRequest.NodeInfo.NodeId, openChannelRequest.ChannelAmount.Satoshi , null, Convert.ToInt64(openChannelRequest.FeeRate.SatoshiPerByte), null, cancellation); if (result.Contains("created channel", StringComparison.OrdinalIgnoreCase)) { var channelId = result.Replace("created channel", "").Trim(); var channel = await _eclairClient.Channel(channelId, cancellation); switch (channel.State) { case "WAIT_FOR_OPEN_CHANNEL": case "WAIT_FOR_ACCEPT_CHANNEL": case "WAIT_FOR_FUNDING_CREATED": case "WAIT_FOR_FUNDING_SIGNED": case "WAIT_FOR_FUNDING_LOCKED": case "WAIT_FOR_FUNDING_CONFIRMED": return(new OpenChannelResponse(OpenChannelResult.NeedMoreConf)); } } if (result.Contains("couldn't publish funding tx", StringComparison.OrdinalIgnoreCase)) { return(new OpenChannelResponse(OpenChannelResult.CannotAffordFunding)); } return(new OpenChannelResponse(OpenChannelResult.Ok)); } catch (Exception e) when(e.Message.Contains("not connected", StringComparison.OrdinalIgnoreCase) || e.Message.Contains("no connection to peer", StringComparison.OrdinalIgnoreCase) || e.Message.Contains("not found", StringComparison.OrdinalIgnoreCase)) { return(new OpenChannelResponse(OpenChannelResult.PeerNotConnected)); } catch (Exception e) when(e.Message.Contains("insufficient funds", StringComparison.OrdinalIgnoreCase)) { return(new OpenChannelResponse(OpenChannelResult.CannotAffordFunding)); } }
public async Task <OpenChannelResponse> OpenChannel(OpenChannelRequest openChannelRequest, CancellationToken cancellation) { var result = await _ptarmiganClient.OpenChannel(openChannelRequest.NodeInfo.NodeId, openChannelRequest.ChannelAmount.Satoshi, 0, Convert.ToInt64(openChannelRequest.FeeRate.FeePerK)); if (result.Error != null) { if (result.Error.Message.Contains("not connected")) { return(new OpenChannelResponse(OpenChannelResult.PeerNotConnected)); } if (result.Error.Message.Contains("channel already opened")) { return(new OpenChannelResponse(OpenChannelResult.NeedMoreConf)); } return(new OpenChannelResponse(OpenChannelResult.CannotAffordFunding)); } var info = await _ptarmiganClient.GetInfo(cancellation); var node = info.Result.Peers.Find(peer => peer.NodeId == openChannelRequest.NodeInfo.NodeId.ToString()); if (node == null) { return(new OpenChannelResponse(OpenChannelResult.CannotAffordFunding)); } if (node.Status == "none") { return(new OpenChannelResponse(OpenChannelResult.AlreadyExists)); } if (node.Status == "establishing") { return(new OpenChannelResponse(OpenChannelResult.NeedMoreConf)); } return(new OpenChannelResponse(OpenChannelResult.Ok)); }
async Task <OpenChannelResponse> ILightningClient.OpenChannel(OpenChannelRequest openChannelRequest, CancellationToken cancellation) { retry: try { await FundChannelAsync(openChannelRequest, cancellation); } catch (LightningRPCException ex) when(ex.Code == CLightningErrorCode.STILL_SYNCING_BITCOIN) { await Task.Delay(1000, cancellation); goto retry; } catch (LightningRPCException ex) when(ex.Code == CLightningErrorCode.CANNOT_AFFORD) { return(new OpenChannelResponse(OpenChannelResult.CannotAffordFunding)); } catch (LightningRPCException ex) when(ex.Code == CLightningErrorCode.CONNECT_ALL_ADDRESSES_FAILED || ex.Code == CLightningErrorCode.CONNECT_NO_KNOWN_ADDRESS || ex.Message == "Peer not connected" || ex.Message == "Unknown peer" || ex.Message == "Unable to connect, no address known for peer") { return(new OpenChannelResponse(OpenChannelResult.PeerNotConnected)); } catch (LightningRPCException ex) when(ex.Message.Contains("CHANNELD_AWAITING_LOCKIN")) { return(new OpenChannelResponse(OpenChannelResult.NeedMoreConf)); } catch (LightningRPCException ex) when( ex.Message.Contains("CHANNELD_NORMAL") || ex.Message.Contains("CHANNELD_SHUTTING_DOWN") || ex.Message.Contains("CLOSINGD_SIGEXCHANGE") || ex.Message.Contains("CLOSINGD_COMPLETE") || ex.Message.Contains("AWAITING_UNILATERAL") || ex.Message.Contains("FUNDING_SPEND_SEEN") || ex.Message.Contains("ONCHAIN")) { return(new OpenChannelResponse(OpenChannelResult.AlreadyExists)); } return(new OpenChannelResponse(OpenChannelResult.Ok)); }
public IActionResult OpenChannel([FromBody] OpenChannelRequest request) { var height = Services.BlockExplorerService.GetCurrentHeight(); BobServerChannelNegotiation session = CreateBobServerChannelNegotiation(request.CycleStart); var cycle = session.GetCycle(); if (!cycle.IsInPhase(CyclePhase.TumblerChannelEstablishment, height)) { return(BadRequest("incorrect-phase")); } var fee = Services.FeeService.GetFeeRate(); try { session.ReceiveBobEscrowInformation(request); if (!Repository.MarkUsedNonce(request.CycleStart, request.Nonce)) { return(BadRequest("nonce-already-used")); } var txOut = session.BuildEscrowTxOut(); var tx = Services.WalletService.FundTransaction(txOut, fee); if (tx == null) { return(BadRequest("tumbler-insufficient-funds")); } var escrowTumblerLabel = $"Cycle {session.GetCycle().Start} Tumbler Escrow"; Services.BlockExplorerService.Track(escrowTumblerLabel, txOut.ScriptPubKey); Services.BroadcastService.Broadcast(escrowTumblerLabel, tx); var promiseServerSession = session.SetSignedTransaction(tx); Repository.Save(cycle.Start, promiseServerSession); var redeem = Services.WalletService.GenerateAddress($"Cycle {cycle.Start} Tumbler Redeem"); var redeemTx = promiseServerSession.CreateRedeemTransaction(fee, redeem.ScriptPubKey); Services.TrustedBroadcastService.Broadcast($"Cycle {session.GetCycle().Start} Tumbler Redeem (locked until: {redeemTx.Transaction.LockTime})", redeemTx); return(Json(promiseServerSession.EscrowedCoin)); } catch (PuzzleException) { return(BadRequest("incorrect-voucher")); } }
public async Task <OpenChannelResponse> OpenChannel(OpenChannelRequest req, CancellationToken cancellation = default) { OpenChannelResult result; try { await _client.OpenChannel(req.NodeInfo, req.ChannelAmount, req.FeeRate, cancellation); result = OpenChannelResult.Ok; } catch (LNbankClient.LNbankApiException ex) { switch (ex.ErrorCode) { case "channel-already-exists": result = OpenChannelResult.AlreadyExists; break; case "cannot-afford-funding": result = OpenChannelResult.CannotAffordFunding; break; case "need-more-confirmations": result = OpenChannelResult.NeedMoreConf; break; case "peer-not-connected": result = OpenChannelResult.PeerNotConnected; break; default: throw new NotSupportedException("Unknown OpenChannelResult"); } } return(new OpenChannelResponse(result)); }
public ActionResult <ulong> OpenChannel(string cryptoCode, [FromBody] OpenChannelRequest o) { var n = _networkProvider.GetByCryptoCode(cryptoCode.ToLowerInvariant()); var peerMan = _peerManagerProvider.TryGetPeerManager(n); var ps = peerMan.GetPeerNodeIds(_pool); if (ps.All(p => p != o.TheirNetworkKey)) { return(BadRequest($"Unknown peer {o.TheirNetworkKey}. Make sure you are already connected to the peer.")); } var chanMan = peerMan.ChannelManager; var maybeConfig = o.OverrideConfig; var userId = RandomUtils.GetUInt64(); try { if (maybeConfig is null) { chanMan.CreateChannel(o.TheirNetworkKey, o.ChannelValueSatoshis, o.PushMSat, userId); } else { var v = maybeConfig.Value; chanMan.CreateChannel(o.TheirNetworkKey, o.ChannelValueSatoshis, o.PushMSat, userId, in v); } peerMan.ProcessEvents(); } catch (FFIException ex) { return(BadRequest(ex.Message)); } return(Ok(userId)); }
/// <summary> /// Returns Id for the opened channel /// </summary> /// <param name="request"></param> /// <returns></returns> public Task <ulong> OpenChannelAsync(OpenChannelRequest request) { return(RequestAsync <ulong>($"/v1/channel/{cryptoCode}", HttpMethod.Post, request)); }
/// <inheritdoc /> public async Task <ScriptCoin> OpenChannelAsync(OpenChannelRequest request) { ScriptCoin result = await this.serverAddress.AppendPathSegment("api/v1/tumblers/0/channels/").PostJsonAsync(request).ReceiveJson <ScriptCoin>(); return(result); }
Task <OpenChannelResponse> ILightningClient.OpenChannel(OpenChannelRequest openChannelRequest, CancellationToken cancellation) { throw new NotSupportedException(); }
public async Task <uint160.MutableUint160> BeginOpenChannel( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] OpenChannelRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } var height = Services.BlockExplorerService.GetCurrentHeight(); if (Repository.IsUsed(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("duplicate-query")); } var cycle = GetCycle(request.CycleStart); if (!cycle.IsInPhase(CyclePhase.TumblerChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } var fee = await Services.FeeService.GetFeeRateAsync(); try { if (!Parameters.VoucherKey.PublicKey.Verify(request.Signature, NBitcoin.Utils.ToBytes((uint)request.CycleStart, true), request.Nonce)) { throw new ActionResultException(BadRequest("incorrect-voucher")); } if (!Repository.MarkUsedNonce(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("duplicate-query")); } var escrowKey = new Key(); var escrow = new EscrowScriptPubKeyParameters(); escrow.LockTime = cycle.GetTumblerLockTime(); escrow.Receiver = request.EscrowKey; escrow.Initiator = escrowKey.PubKey; var channelId = new uint160(RandomUtils.GetBytes(20)); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Asked to open channel"); var txOut = new TxOut(Parameters.Denomination, escrow.ToScript().WitHash.ScriptPubKey.Hash); var unused = Services.WalletService.FundTransactionAsync(txOut, fee) .ContinueWith(async(Task <Transaction> task) => { try { var tx = await task.ConfigureAwait(false); var correlation = new CorrelationId(channelId); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, tx.GetHash(), correlation); //Logging/Broadcast per funding TX one time if (Repository.MarkUsedNonce(cycle.Start, new uint160(tx.GetHash().ToBytes().Take(20).ToArray()))) { var bobCount = Parameters.CountEscrows(tx, Client.Identity.Bob); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} channel created {tx.GetHash()} with {bobCount} users"); await Services.BroadcastService.BroadcastAsync(tx).ConfigureAwait(false); } await Services.BlockExplorerService.TrackAsync(txOut.ScriptPubKey).ConfigureAwait(false); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, txOut.ScriptPubKey, correlation); var coin = tx.Outputs.AsCoins().First(o => o.ScriptPubKey == txOut.ScriptPubKey && o.TxOut.Value == txOut.Value); var session = new PromiseServerSession(Parameters.CreatePromiseParamaters()); var redeem = await Services.WalletService.GenerateAddressAsync().ConfigureAwait(false); session.ConfigureEscrowedCoin(channelId, coin.ToScriptCoin(escrow.ToScript()), escrowKey, redeem.ScriptPubKey); var redeemTx = session.CreateRedeemTransaction(fee); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerRedeem, correlation, redeemTx); Repository.Save(cycle.Start, session); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerRedeem, redeem.ScriptPubKey, correlation); } catch (Exception ex) { Logs.Tumbler.LogCritical(new EventId(), ex, "Error during escrow transaction callback"); } }); return(channelId.AsBitcoinSerializable()); } catch (NotEnoughFundsException ex) { Logs.Tumbler.LogInformation(ex.Message); throw new ActionResultException(BadRequest("tumbler-insufficient-funds")); } }
public ScriptCoin OpenChannel(OpenChannelRequest request) { return(OpenChannelAsync(request).GetAwaiter().GetResult()); }
private void SubmitButtonOnClick(object sender, EventArgs e) { ClearErrorData(); if (typeof(TRequestMessage) == typeof(ConnectPeerRequest)) { var fullAddress = (string)Ws.Cells[_fieldToRow["addr"], EndColumn].Value2; if (fullAddress == null) { return; } var addressParts = fullAddress.Split('@'); string pubkey; string host; switch (addressParts.Length) { case 0: return; case 2: pubkey = addressParts[0]; host = addressParts[1]; break; default: Utilities.DisplayError(ErrorData, "Error", "Invalid address, must be pubkey@ip:host"); return; } var permanent = Ws.Cells[_fieldToRow["perm"], EndColumn].Value2; bool perm = permanent == null || (bool)permanent; var address = new LightningAddress { Host = host, Pubkey = pubkey }; var request = new ConnectPeerRequest { Addr = address, Perm = perm }; try { _lApp.LndClient.ConnectPeer(request); _lApp.Refresh(SheetNames.Peers); ClearForm(); } catch (RpcException rpcException) { DisplayError(rpcException); } } else if (typeof(TRequestMessage) == typeof(SendCoinsRequest)) { var request = new SendCoinsRequest { Addr = Ws.Cells[_fieldToRow["addr"], EndColumn].Value2, Amount = (long)Ws.Cells[_fieldToRow["amount"], EndColumn].Value2 }; var satPerByte = Ws.Cells[_fieldToRow["sat_per_byte"], EndColumn].Value2; if (satPerByte == null) { satPerByte = 0; } if (satPerByte > 0) { request.SatPerByte = satPerByte; } var targetConf = Ws.Cells[_fieldToRow["target_conf"], EndColumn].Value2; if (targetConf == null) { targetConf = 0; } if (targetConf > 0) { request.TargetConf = targetConf; } try { _lApp.LndClient.SendCoins(request); _lApp.Refresh(SheetNames.Transactions); ClearForm(); } catch (RpcException rpcException) { DisplayError(rpcException); } } else if (typeof(TRequestMessage) == typeof(OpenChannelRequest)) { var localFundingAmount = long.Parse(GetValue("local_funding_amount")); var minConfs = int.Parse(GetValue("min_confs")); var minHtlcMsat = long.Parse(GetValue("min_htlc_msat")); var nodePubKeyString = GetValue("node_pubkey"); var isPrivate = true; if (bool.TryParse(GetValue("private"), out var result)) { isPrivate = result; } var pushSat = long.Parse(GetValue("push_sat")); var remoteCsvDelay = uint.Parse(GetValue("remote_csv_delay")); var satPerByte = long.Parse(GetValue("sat_per_byte")); var targetConf = int.Parse(GetValue("target_conf")); var request = new OpenChannelRequest { LocalFundingAmount = localFundingAmount, MinConfs = minConfs, MinHtlcMsat = minHtlcMsat, NodePubkeyString = nodePubKeyString, Private = isPrivate, PushSat = pushSat }; if (remoteCsvDelay > 0) { request.RemoteCsvDelay = remoteCsvDelay; } if (satPerByte > 0) { request.SatPerByte = satPerByte; } if (targetConf > 0) { request.TargetConf = targetConf; } try { _lApp.LndClient.OpenChannel(request); _lApp.Refresh(SheetNames.Channels); ClearForm(); } catch (RpcException rpcException) { DisplayError(rpcException); } } else { var request = new TRequestMessage(); var rowNumber = _dataStartRow; foreach (var field in Fields) { Range dataCell = Ws.Cells[rowNumber, EndColumn]; var value = dataCell.Value2; if (!string.IsNullOrWhiteSpace(value?.ToString())) { field.Accessor.SetValue(request, dataCell.Value2); } } _query(request); } }
public ScriptCoinModel OpenChannel( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] OpenChannelRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } var height = Services.BlockExplorerService.GetCurrentHeight(); var cycle = GetCycle(request.CycleStart); if (!cycle.IsInPhase(CyclePhase.TumblerChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } var fee = Services.FeeService.GetFeeRate(); try { if (!Parameters.VoucherKey.Verify(request.Signature, NBitcoin.Utils.ToBytes((uint)request.CycleStart, true), request.Nonce)) { throw new ActionResultException(BadRequest("incorrect-voucher")); } if (!Repository.MarkUsedNonce(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("nonce-already-used")); } var escrowKey = new Key(); var escrow = new EscrowScriptPubKeyParameters(); escrow.LockTime = cycle.GetTumblerLockTime(); escrow.Receiver = request.EscrowKey; escrow.Initiator = escrowKey.PubKey; Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Asked to open channel"); var txOut = new TxOut(Parameters.Denomination, escrow.ToScript().Hash); var tx = Services.WalletService.FundTransaction(txOut, fee); var correlation = escrow.GetCorrelation(); var escrowTumblerLabel = $"Cycle {cycle.Start} Tumbler Escrow"; Services.BlockExplorerService.Track(txOut.ScriptPubKey); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, txOut.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, tx.GetHash(), correlation); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Channel created " + tx.GetHash()); var coin = tx.Outputs.AsCoins().First(o => o.ScriptPubKey == txOut.ScriptPubKey && o.TxOut.Value == txOut.Value); var session = new PromiseServerSession(Parameters.CreatePromiseParamaters()); var redeem = Services.WalletService.GenerateAddress(); session.ConfigureEscrowedCoin(coin.ToScriptCoin(escrow.ToScript()), escrowKey, redeem.ScriptPubKey); Repository.Save(cycle.Start, session); Services.BroadcastService.Broadcast(tx); var redeemTx = session.CreateRedeemTransaction(fee); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerRedeem, redeem.ScriptPubKey, correlation); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerRedeem, correlation, redeemTx); return(new ScriptCoinModel(session.EscrowedCoin)); } catch (PuzzleException) { throw new ActionResultException(BadRequest("incorrect-voucher")); } catch (NotEnoughFundsException ex) { Logs.Tumbler.LogInformation(ex.Message); throw new ActionResultException(BadRequest("tumbler-insufficient-funds")); } }
public uint160 BeginOpenChannel(OpenChannelRequest request) { return(BeginOpenChannelAsync(request).GetAwaiter().GetResult()); }
public void CanConvertJsonTypes() { var invoice = "lnbc1p0vhtzvpp5akajlfqdj6ek7eeh4kae6gc05fz9j99n8jadatqt4fmlwwxwx4zsnp4q2uqg2j52gxtxg5d0v928h5pll95ynsaek2csgfg26tvuzydgjrwgdqhdehjqer9wd3hy6tsw35k7msna3vtx"; var paymentRequest = PaymentRequest.Parse(invoice); var resp = new InvoiceResponse() { Invoice = paymentRequest.ResultValue }; var j = JsonSerializer.Serialize(resp); JsonSerializer.Deserialize <InvoiceResponse>(j); var invoiceResponseRaw = "{\"invoice\":\"lnbc1p0vma42pp5t2v5ehyay3x9g8769gqkrhmdlqjq0kc8ksqfxu3xjw7s2y96jegqnp4q2uqg2j52gxtxg5d0v928h5pll95ynsaek2csgfg26tvuzydgjrwgdqhdehjqer9wd3hy6tsw35k7ms3xhenl\"}"; JsonSerializer.Deserialize <InvoiceResponse>(invoiceResponseRaw); var conf = UserConfig.GetDefault(); j = JsonSerializer.Serialize(conf); var v = JsonSerializer.Deserialize <UserConfig>(j); Assert.Equal(conf.ChannelOptions.AnnouncedChannel, v.ChannelOptions.AnnouncedChannel); var openChannelRequest = new OpenChannelRequest(); j = JsonSerializer.Serialize(openChannelRequest); var conv = JsonSerializer.Deserialize <OpenChannelRequest>(j); Assert.Equal(openChannelRequest.OverrideConfig, conv.OverrideConfig); // with custom config openChannelRequest.OverrideConfig = UserConfig.GetDefault(); j = JsonSerializer.Serialize(openChannelRequest); // Don't know why but we must specify option here. var opt = new JsonSerializerOptions(); opt.Converters.Add(new NullableStructConverterFactory()); conv = JsonSerializer.Deserialize <OpenChannelRequest>(j, opt); Assert.True(conv.OverrideConfig.HasValue); Assert.Equal(openChannelRequest.OverrideConfig.Value.ChannelOptions.AnnouncedChannel, conv.OverrideConfig.Value.ChannelOptions.AnnouncedChannel); j = "{\"TheirNetworkKey\":\"024a8b7fc86957537bb365cc0242255582d3d40a5532489f67e700a89bcac2f010\",\"ChannelValueSatoshis\":100000,\"PushMSat\":1000,\"OverrideConfig\":null}"; openChannelRequest = JsonSerializer.Deserialize <OpenChannelRequest>(j, new JsonSerializerOptions() { Converters = { new HexPubKeyConverter() } }); Assert.Equal(100000UL, openChannelRequest.ChannelValueSatoshis); Assert.Equal(1000UL, openChannelRequest.PushMSat); Assert.NotNull(openChannelRequest.TheirNetworkKey); // wallet info j = "{\"DerivationStrategy\":\"tpubDBte1PdX36pt167AFbKpHwFJqZAVVRuJSadZ49LdkX5JJbJCNDc8JQ7w5GdaDZcUXm2SutgwjRuufwq4q4soePD4fPKSZCUhqDDarKRCUen\",\"OnChainBalanceSatoshis\":0}"; var networkProvider = new NRustLightningNetworkProvider(NetworkType.Regtest); var btcNetwork = networkProvider.GetByCryptoCode("BTC"); var walletInfo = JsonSerializer.Deserialize <WalletInfo>(j, new JsonSerializerOptions { Converters = { new DerivationStrategyJsonConverter(btcNetwork.NbXplorerNetwork.DerivationStrategyFactory) } }); // FeatureBit var featureBit = FeatureBit.TryParse("0b000000100100000100000000").ResultValue; var opts = new JsonSerializerOptions() { Converters = { new FeatureBitJsonConverter() } }; j = JsonSerializer.Serialize(featureBit, opts); Assert.Contains("prettyPrint", j); var featureBit2 = JsonSerializer.Deserialize <FeatureBit>(j, opts); Assert.Equal(featureBit, featureBit2); }