2020-06-29 04:44:35 +02:00
using System ;
2018-02-25 16:48:12 +01:00
using System.Linq ;
2020-06-28 10:55:27 +02:00
using System.Threading ;
2018-02-25 16:48:12 +01:00
using System.Threading.Tasks ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Data ;
using BTCPayServer.Lightning ;
2021-10-25 08:18:02 +02:00
using BTCPayServer.Logging ;
2018-02-25 16:48:12 +01:00
using BTCPayServer.Models.StoreViewModels ;
using BTCPayServer.Payments ;
using BTCPayServer.Payments.Lightning ;
2020-06-28 10:55:27 +02:00
using Microsoft.AspNetCore.Mvc ;
using Microsoft.Extensions.DependencyInjection ;
2018-02-25 16:48:12 +01:00
namespace BTCPayServer.Controllers
{
public partial class StoresController
{
2021-03-31 13:23:36 +02:00
[HttpGet("{storeId}/lightning/{cryptoCode}")]
2021-07-27 14:08:54 +02:00
public async Task < IActionResult > SetupLightningNode ( string storeId , string cryptoCode )
2018-02-25 16:48:12 +01:00
{
2018-04-29 19:33:42 +02:00
var store = HttpContext . GetStoreData ( ) ;
2018-02-25 16:48:12 +01:00
if ( store = = null )
return NotFound ( ) ;
2021-03-31 13:23:36 +02:00
var vm = new LightningNodeViewModel
2018-07-27 13:37:16 +02:00
{
CryptoCode = cryptoCode ,
2019-01-07 09:52:27 +01:00
StoreId = storeId
2018-07-27 13:37:16 +02:00
} ;
2021-07-27 14:08:54 +02:00
await SetExistingValues ( store , vm ) ;
2018-02-25 16:48:12 +01:00
return View ( vm ) ;
}
2021-03-31 13:23:36 +02:00
[HttpPost("{storeId}/lightning/{cryptoCode}")]
2021-04-19 16:21:50 +02:00
public async Task < IActionResult > SetupLightningNode ( string storeId , LightningNodeViewModel vm , string command , string cryptoCode )
2018-02-25 16:48:12 +01:00
{
2018-03-20 18:48:11 +01:00
vm . CryptoCode = cryptoCode ;
2018-04-29 19:33:42 +02:00
var store = HttpContext . GetStoreData ( ) ;
2018-02-25 16:48:12 +01:00
if ( store = = null )
return NotFound ( ) ;
2018-03-20 18:09:25 +01:00
2021-07-27 14:08:54 +02:00
vm . CanUseInternalNode = await CanUseInternalLightning ( ) ;
2021-03-31 13:23:36 +02:00
var network = vm . CryptoCode = = null ? null : _ExplorerProvider . GetNetwork ( vm . CryptoCode ) ;
2018-03-20 03:59:43 +01:00
if ( network = = null )
2018-02-25 16:48:12 +01:00
{
2018-03-20 18:48:11 +01:00
ModelState . AddModelError ( nameof ( vm . CryptoCode ) , "Invalid network" ) ;
2018-02-25 16:48:12 +01:00
return View ( vm ) ;
}
2021-03-31 13:23:36 +02:00
var paymentMethodId = new PaymentMethodId ( network . CryptoCode , PaymentTypes . LightningLike ) ;
2021-04-16 15:31:09 +02:00
2021-03-31 13:23:36 +02:00
LightningSupportedPaymentMethod paymentMethod = null ;
if ( vm . LightningNodeType = = LightningNodeType . Internal )
2021-03-02 03:11:58 +01:00
{
2021-07-27 14:08:54 +02:00
if ( ! await CanUseInternalLightning ( ) )
2021-03-02 03:11:58 +01:00
{
2021-03-31 13:23:36 +02:00
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , "You are not authorized to use the internal lightning node" ) ;
2021-03-02 03:11:58 +01:00
return View ( vm ) ;
}
2021-03-31 13:23:36 +02:00
paymentMethod = new LightningSupportedPaymentMethod
2021-03-02 03:11:58 +01:00
{
2021-10-29 08:25:43 +02:00
CryptoCode = paymentMethodId . CryptoCode
2021-03-02 03:11:58 +01:00
} ;
paymentMethod . SetInternalNode ( ) ;
}
2021-03-31 13:23:36 +02:00
else
2018-02-25 16:48:12 +01:00
{
2021-03-31 13:23:36 +02:00
if ( string . IsNullOrEmpty ( vm . ConnectionString ) )
{
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , "Please provide a connection string" ) ;
return View ( vm ) ;
}
2018-07-01 08:45:08 +02:00
if ( ! LightningConnectionString . TryParse ( vm . ConnectionString , false , out var connectionString , out var error ) )
2018-02-25 16:48:12 +01:00
{
2018-07-01 08:45:08 +02:00
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , $"Invalid URL ({error})" ) ;
2018-02-25 16:48:12 +01:00
return View ( vm ) ;
}
2020-06-28 10:55:27 +02:00
if ( connectionString . ConnectionType = = LightningConnectionType . LndGRPC )
2018-07-23 04:11:00 +02:00
{
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , $"BTCPay does not support gRPC connections" ) ;
return View ( vm ) ;
}
2021-03-02 03:11:58 +01:00
if ( ! User . IsInRole ( Roles . ServerAdmin ) & & ! connectionString . IsSafe ( ) )
2018-07-10 05:46:04 +02:00
{
2021-03-31 13:23:36 +02:00
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , "You are not a server admin, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'." ) ;
2018-02-26 10:58:02 +01:00
return View ( vm ) ;
}
2021-03-31 13:23:36 +02:00
paymentMethod = new LightningSupportedPaymentMethod
2018-02-25 16:48:12 +01:00
{
2021-10-29 08:25:43 +02:00
CryptoCode = paymentMethodId . CryptoCode
2018-02-25 16:48:12 +01:00
} ;
2018-03-20 16:31:19 +01:00
paymentMethod . SetLightningUrl ( connectionString ) ;
2018-02-25 16:48:12 +01:00
}
2018-06-24 05:03:51 +02:00
2018-07-27 13:37:16 +02:00
switch ( command )
2018-02-25 16:48:12 +01:00
{
2018-07-27 13:37:16 +02:00
case "save" :
2018-08-08 10:32:16 +02:00
var storeBlob = store . GetStoreBlob ( ) ;
2020-10-17 22:52:41 +02:00
storeBlob . Hints . Lightning = false ;
2021-10-25 08:18:02 +02:00
var lnurl = new PaymentMethodId ( vm . CryptoCode , PaymentTypes . LNURLPay ) ;
2018-08-08 10:32:16 +02:00
store . SetStoreBlob ( storeBlob ) ;
2019-05-09 09:11:09 +02:00
store . SetSupportedPaymentMethod ( paymentMethodId , paymentMethod ) ;
2021-10-25 08:18:02 +02:00
2018-07-27 13:37:16 +02:00
await _Repo . UpdateStore ( store ) ;
2021-04-20 09:09:32 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = $"{network.CryptoCode} Lightning node updated." ;
2021-10-29 08:25:43 +02:00
return RedirectToAction ( nameof ( PaymentMethods ) , new { storeId } ) ;
2021-03-31 13:23:36 +02:00
2018-07-27 13:37:16 +02:00
case "test" :
2019-05-29 11:43:50 +02:00
var handler = _ServiceProvider . GetRequiredService < LightningLikePaymentHandler > ( ) ;
2018-07-27 13:37:16 +02:00
try
2018-04-09 09:25:31 +02:00
{
2021-11-04 15:26:29 +01:00
var info = await handler . GetNodeInfo ( paymentMethod , network , new InvoiceLogs ( ) , Request . IsOnion ( ) , true ) ;
2018-07-27 13:37:16 +02:00
if ( ! vm . SkipPortTest )
2018-04-09 09:25:31 +02:00
{
2021-03-31 13:23:36 +02:00
using var cts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 20 ) ) ;
2021-09-23 13:36:42 +02:00
await handler . TestConnection ( info . First ( ) , cts . Token ) ;
2018-04-09 09:25:31 +02:00
}
2021-09-23 13:36:42 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = $"Connection to the Lightning node successful. Your node address: {info.First()}" ;
2018-07-27 13:37:16 +02:00
}
catch ( Exception ex )
{
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . ErrorMessage ] = ex . Message ;
2018-07-27 13:37:16 +02:00
return View ( vm ) ;
2018-04-09 09:25:31 +02:00
}
2018-02-25 16:48:12 +01:00
return View ( vm ) ;
2021-03-31 13:23:36 +02:00
2018-07-27 13:37:16 +02:00
default :
return View ( vm ) ;
2018-02-25 16:48:12 +01:00
}
}
2021-10-29 08:25:43 +02:00
[HttpGet("{storeId}/lightning/{cryptoCode}/settings")]
public async Task < IActionResult > LightningSettings ( string storeId , string cryptoCode )
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
return NotFound ( ) ;
var storeBlob = store . GetStoreBlob ( ) ;
var vm = new LightningSettingsViewModel
{
CryptoCode = cryptoCode ,
StoreId = storeId ,
LightningDescriptionTemplate = storeBlob . LightningDescriptionTemplate ,
LightningAmountInSatoshi = storeBlob . LightningAmountInSatoshi ,
LightningPrivateRouteHints = storeBlob . LightningPrivateRouteHints ,
OnChainWithLnInvoiceFallback = storeBlob . OnChainWithLnInvoiceFallback
} ;
await SetExistingValues ( store , vm ) ;
var lightning = GetExistingLightningSupportedPaymentMethod ( vm . CryptoCode , store ) ;
var lnSet = lightning ! = null ;
if ( lnSet )
{
vm . DisableBolt11PaymentMethod = lightning . DisableBOLT11PaymentOption ;
}
var lnurl = GetExistingLNURLSupportedPaymentMethod ( vm . CryptoCode , store ) ;
if ( lnurl ! = null )
{
vm . LNURLEnabled = ! store . GetStoreBlob ( ) . GetExcludedPaymentMethods ( ) . Match ( lnurl . PaymentId ) ;
vm . LNURLBech32Mode = lnurl . UseBech32Scheme ;
vm . LNURLStandardInvoiceEnabled = lnurl . EnableForStandardInvoices ;
vm . LUD12Enabled = lnurl . LUD12Enabled ;
vm . DisableBolt11PaymentMethod =
vm . LNURLEnabled & & vm . LNURLStandardInvoiceEnabled & & vm . DisableBolt11PaymentMethod ;
}
else
{
//disable by default for now
//vm.LNURLEnabled = !lnSet;
vm . DisableBolt11PaymentMethod = false ;
}
return View ( vm ) ;
}
[HttpPost("{storeId}/lightning/{cryptoCode}/settings")]
public async Task < IActionResult > LightningSettings ( LightningSettingsViewModel vm )
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
return NotFound ( ) ;
var network = vm . CryptoCode = = null ? null : _ExplorerProvider . GetNetwork ( vm . CryptoCode ) ;
if ( network = = null )
{
ModelState . AddModelError ( nameof ( vm . CryptoCode ) , "Invalid network" ) ;
return View ( vm ) ;
}
var needUpdate = false ;
var blob = store . GetStoreBlob ( ) ;
blob . LightningDescriptionTemplate = vm . LightningDescriptionTemplate ? ? string . Empty ;
blob . LightningAmountInSatoshi = vm . LightningAmountInSatoshi ;
blob . LightningPrivateRouteHints = vm . LightningPrivateRouteHints ;
blob . OnChainWithLnInvoiceFallback = vm . OnChainWithLnInvoiceFallback ;
var disableBolt11PaymentMethod =
vm . LNURLEnabled & & vm . LNURLStandardInvoiceEnabled & & vm . DisableBolt11PaymentMethod ;
var lnurlId = new PaymentMethodId ( vm . CryptoCode , PaymentTypes . LNURLPay ) ;
blob . SetExcluded ( lnurlId , ! vm . LNURLEnabled ) ;
var lightning = GetExistingLightningSupportedPaymentMethod ( vm . CryptoCode , store ) ;
if ( lightning . DisableBOLT11PaymentOption ! = disableBolt11PaymentMethod )
{
needUpdate = true ;
lightning . DisableBOLT11PaymentOption = disableBolt11PaymentMethod ;
store . SetSupportedPaymentMethod ( lightning ) ;
}
var lnurl = GetExistingLNURLSupportedPaymentMethod ( vm . CryptoCode , store ) ;
2021-10-29 11:27:36 +02:00
if ( lnurl is null | | (
2021-10-29 08:25:43 +02:00
lnurl . EnableForStandardInvoices ! = vm . LNURLStandardInvoiceEnabled | |
lnurl . UseBech32Scheme ! = vm . LNURLBech32Mode | |
lnurl . LUD12Enabled ! = vm . LUD12Enabled ) )
{
needUpdate = true ;
}
store . SetSupportedPaymentMethod ( new LNURLPaySupportedPaymentMethod
{
CryptoCode = vm . CryptoCode ,
EnableForStandardInvoices = vm . LNURLStandardInvoiceEnabled ,
UseBech32Scheme = vm . LNURLBech32Mode ,
LUD12Enabled = vm . LUD12Enabled
} ) ;
if ( store . SetStoreBlob ( blob ) )
{
needUpdate = true ;
}
if ( needUpdate )
{
await _Repo . UpdateStore ( store ) ;
TempData [ WellKnownTempData . SuccessMessage ] = $"{network.CryptoCode} Lightning settings successfully updated" ;
}
return RedirectToAction ( nameof ( PaymentMethods ) , new { vm . StoreId } ) ;
}
2021-03-31 13:23:36 +02:00
2021-04-16 15:31:09 +02:00
[HttpPost("{storeId}/lightning/{cryptoCode}/status")]
public async Task < IActionResult > SetLightningNodeEnabled ( string storeId , string cryptoCode , bool enabled )
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
return NotFound ( ) ;
var network = cryptoCode = = null ? null : _ExplorerProvider . GetNetwork ( cryptoCode ) ;
if ( network = = null )
return NotFound ( ) ;
var lightning = GetExistingLightningSupportedPaymentMethod ( cryptoCode , store ) ;
if ( lightning = = null )
return NotFound ( ) ;
2021-04-20 09:10:23 +02:00
var paymentMethodId = new PaymentMethodId ( network . CryptoCode , PaymentTypes . LightningLike ) ;
var storeBlob = store . GetStoreBlob ( ) ;
storeBlob . SetExcluded ( paymentMethodId , ! enabled ) ;
store . SetStoreBlob ( storeBlob ) ;
await _Repo . UpdateStore ( store ) ;
TempData [ WellKnownTempData . SuccessMessage ] = $"{network.CryptoCode} Lightning payments are now {(enabled ? " enabled " : " disabled ")} for this store." ;
2021-04-16 15:31:09 +02:00
2021-10-29 08:25:43 +02:00
return RedirectToAction ( nameof ( PaymentMethods ) , new { storeId } ) ;
2021-04-16 15:31:09 +02:00
}
2021-07-27 14:08:54 +02:00
private async Task < bool > CanUseInternalLightning ( )
2018-02-26 10:58:02 +01:00
{
2021-07-27 14:08:54 +02:00
return User . IsInRole ( Roles . ServerAdmin ) | | ( await _settingsRepository . GetPolicies ( ) ) . AllowLightningInternalNodeForAll ;
2018-02-26 10:58:02 +01:00
}
2021-03-31 13:23:36 +02:00
2021-07-27 14:08:54 +02:00
private async Task SetExistingValues ( StoreData store , LightningNodeViewModel vm )
2021-03-31 13:23:36 +02:00
{
2021-07-27 14:08:54 +02:00
vm . CanUseInternalNode = await CanUseInternalLightning ( ) ;
2021-03-31 13:23:36 +02:00
var lightning = GetExistingLightningSupportedPaymentMethod ( vm . CryptoCode , store ) ;
2021-10-25 08:18:02 +02:00
var lnSet = lightning ! = null ;
if ( lnSet )
2021-03-31 13:23:36 +02:00
{
vm . LightningNodeType = lightning . IsInternalNode ? LightningNodeType . Internal : LightningNodeType . Custom ;
vm . ConnectionString = lightning . GetDisplayableConnectionString ( ) ;
}
2021-04-19 16:21:50 +02:00
else
{
vm . LightningNodeType = vm . CanUseInternalNode ? LightningNodeType . Internal : LightningNodeType . Custom ;
}
2021-03-31 13:23:36 +02:00
}
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod ( string cryptoCode , StoreData store )
{
var id = new PaymentMethodId ( cryptoCode , PaymentTypes . LightningLike ) ;
var existing = store . GetSupportedPaymentMethods ( _NetworkProvider )
. OfType < LightningSupportedPaymentMethod > ( )
. FirstOrDefault ( d = > d . PaymentId = = id ) ;
return existing ;
}
2021-10-25 08:18:02 +02:00
private LNURLPaySupportedPaymentMethod GetExistingLNURLSupportedPaymentMethod ( string cryptoCode , StoreData store )
{
var id = new PaymentMethodId ( cryptoCode , PaymentTypes . LNURLPay ) ;
var existing = store . GetSupportedPaymentMethods ( _NetworkProvider )
. OfType < LNURLPaySupportedPaymentMethod > ( )
. FirstOrDefault ( d = > d . PaymentId = = id ) ;
return existing ;
}
2018-02-25 16:48:12 +01:00
}
}