Skip to content

Commit

Permalink
Merge pull request #45 from Elenpay/develop
Browse files Browse the repository at this point in the history
Release 0.3.0
  • Loading branch information
Jossec101 authored Jan 10, 2023
2 parents bf6367d + 40aef54 commit 0783540
Show file tree
Hide file tree
Showing 25 changed files with 1,384 additions and 174 deletions.
39 changes: 2 additions & 37 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,45 +42,10 @@
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/bin/Debug/net6.0/FundsManager.dll",
"args": ["--urls","http://localhost:38080"],
"launchSettingsFilePath": "${workspaceFolder}/src/Properties/launchSettings.json",
"launchSettingsProfile": "FundsManager NOVS",
"cwd": "${workspaceFolder}/src",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(http?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"POSTGRES_CONNECTIONSTRING": "Host=127.0.0.1;Port=5432;Database=fundsmanager;Username=rw_dev;Password=rw_dev",
"BITCOIN_NETWORK": "REGTEST",
"MAX_BTC": "21000000",
"NBXPLORER_URI": "http://127.0.0.1:32838",
"NBXPLORER_BTCRPCUSER": "polaruser",
"NBXPLORER_BTCRPCPASSWORD": "polarpass",
"NBXPLORER_BTCRPCURL": "http://127.0.0.1:18443/",
"NBXPLORER_BTCNODEENDPOINT": "127.0.0.1:19444",
"PUSH_NOTIFICATIONS_ONESIGNAL_API_BASE_PATH": "https://onesignal.com/api/v1",
"PUSH_NOTIFICATIONS_ONESIGNAL_APP_ID": "88695835-1513-4f3d-8104-ce8089447d11",
"PUSH_NOTIFICATIONS_ONESIGNAL_API_TOKEN": "NDcxNDRjZTMtYTMxMy00MmU4LWFlYTgtZTNjNTMzNGFiNzE1",
"PUSH_NOTIFICATIONS_ONESIGNAL_ENABLED": "false",
"DEFAULT_DERIVATION_PATH": "m/48'/1'/1'",
"IS_DEV_ENVIRONMENT": "true",
"FUNDSMANAGER_ENDPOINT": "http://localhost:38080",
"Logging__LogLevel__Microsoft": "Warning",
"SWEEPNODEWALLETSJOB_CRON": "0 */1 * * * ?",
"ANCHOR_CLOSINGS_MINIMUM_SATS": "100000",
"ALICE_HOST": "localhost:10001",
"CAROL_HOST": "localhost:10003",
"ENABLE_HW_SUPPORT": "false",
"MINIMUM_WITHDRAWAL_BTC_AMOUNT": "0.001",
"MINIMUM_CHANNEL_CAPACITY_SATS": "20000",
"MEMPOOL_ENDPOINT": "https://mempool-staging.elenpay.tech",
"TRANSACTION_CONFIRMATION_MINIMUM_BLOCKS": "6",
"MONITOR_WITHDRAWALS_CRON": "0 */1 * * * ?",
"REDIS_CONNECTIONSTRING": "localhost:6379,abortConnect=false,connectTimeout=30000,responseTimeout=30000",
"COINGECKO_ENDPOINT": "https://pro-api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=bitcoin",
"COINGECKO_KEY": "TBD",
//"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317", //gRPC endpoint for OTel collector (optional)
//"OTEL_RESOURCE_ATTRIBUTES": "service.name=NodeGuard"

Expand Down
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ services:
IS_DEV_ENVIRONMENT: "true"
Logging__LogLevel__Microsoft: Warning
Logging__LogLevel__Microsoft__EntityFrameworkCore: Warning
MAX_BTC: 21000000
MAXIMUM_WITHDRAWAL_BTC_AMOUNT: 21000000
NBXPLORER_BTCNODEENDPOINT: host.docker.internal:19444
NBXPLORER_BTCRPCPASSWORD: "polarpass"
NBXPLORER_BTCRPCURL: http://host.docker.internal:18443/
Expand Down
6 changes: 3 additions & 3 deletions src/Data/DbInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public static void Initialize(IServiceProvider serviceProvider)
//Roles
SetRoles(roleManager);

if (webHostEnvironment.IsDevelopment())
if (webHostEnvironment.IsDevelopment() && Environment.GetEnvironmentVariable("ENABLE_REMOTE_SIGNER") == null)
{
//Miner setup
var rpcuser = Environment.GetEnvironmentVariable("NBXPLORER_BTCRPCUSER");
Expand Down Expand Up @@ -249,8 +249,8 @@ public static void Initialize(IServiceProvider serviceProvider)
new Key
{
Name = "FundsManager Co-signing Key",
XPUB = internalWallet.GetXPUB(nbXplorerNetwork),
IsFundsManagerPrivateKey = true,
XPUB = internalWallet.XPUB,
InternalWalletId = internalWallet.Id,
Path = internalWallet.DerivationPath,
MasterFingerprint = new Mnemonic(internalWallet.MnemonicString).DeriveExtKey().GetWif(Network.RegTest).GetPublicKey().GetHDFingerPrint().ToString()
};
Expand Down
31 changes: 17 additions & 14 deletions src/Data/Models/InternalWallet.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NBitcoin;
using FundsManager.Helpers;
using NBitcoin;

namespace FundsManager.Data.Models
{
Expand All @@ -8,6 +9,7 @@ namespace FundsManager.Data.Models
/// </summary>
public class InternalWallet : Entity
{

/// <summary>
/// Derivation path of the wallet
/// </summary>
Expand All @@ -17,11 +19,18 @@ public class InternalWallet : Entity
/// 24 Words mnemonic
/// </summary>
public string? MnemonicString { get; set; }

/// <summary>
/// XPUB in the case the Mnemonic is not set (Remote signer mode)
/// </summary>
public string? XPUB { get; set; }
public string? XPUB
{
get => GetXPUB();
set => _xpub = value;
}
private string? _xpub;

public string? MasterFingerprint { get; set; }

/// <summary>
/// Returns the master private key (xprv..tprv)
Expand All @@ -40,16 +49,12 @@ public class InternalWallet : Entity
/// <summary>
/// Returns the xpub/tpub as nbxplorer uses
/// </summary>
/// <param name="network"></param>
/// <returns></returns>
public string? GetXPUB(Network network)
private string? GetXPUB()
{
if (string.IsNullOrWhiteSpace(DerivationPath) || string.IsNullOrWhiteSpace(MnemonicString))
{
return null;
}

if (MnemonicString != null)
var network = CurrentNetworkHelper.GetCurrentNetwork();
if(!string.IsNullOrWhiteSpace(MnemonicString))
{
var masterKey = new Mnemonic(MnemonicString).DeriveExtKey().GetWif(network);
var keyPath = new KeyPath(DerivationPath); //https://github.com/dgarage/NBXplorer/blob/0595a87fc14aee6a6e4a0194f75aec4717819/NBXplorer/Controllers/MainController.cs#L1141
Expand All @@ -60,10 +65,8 @@ public class InternalWallet : Entity

return walletDerivationScheme;
}
else
{
return XPUB;
}

return _xpub;
}

public BitcoinExtKey GetAccountKey(Network network)
Expand Down
5 changes: 3 additions & 2 deletions src/Data/Models/Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ public class Key : Entity, IEquatable<Key>
public ICollection<Wallet> Wallets { get; set; }

/// <summary>
/// Indicates if the key comes from a internal wallet managed by the fundsmanager
/// The internal wallet where this key belongs (if it were a internal wallet key)
/// </summary>
public bool IsFundsManagerPrivateKey { get; set; }
public int? InternalWalletId { get; set; }
public InternalWallet? InternalWallet { get; set; }

#endregion Relationships

Expand Down
4 changes: 3 additions & 1 deletion src/Data/Repositories/ChannelRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ public async Task<List<Channel>> GetAll()
var map = new JobDataMap();
map.Put("closeRequestId", closeRequest.Id);
map.Put("forceClose", forceClose);
var job = RetriableJob.Create<ChannelCloseJob>(map, closeRequest.Id.ToString());

var retryList = RetriableJob.ParseRetryListFromEnvironmenVariable("JOB_RETRY_INTERVAL_LIST_IN_MINUTES");
var job = RetriableJob.Create<ChannelCloseJob>(map, closeRequest.Id.ToString(), retryList);
await scheduler.ScheduleJob(job.Job, job.Trigger);

// TODO: Check job id
Expand Down
31 changes: 28 additions & 3 deletions src/Data/Repositories/KeysRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ public class KeyRepository : IKeyRepository
private readonly IRepository<Key> _repository;
private readonly ILogger<KeyRepository> _logger;
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
private readonly IInternalWalletRepository _internalWalletRepository;

public KeyRepository(IRepository<Key> repository,
ILogger<KeyRepository> logger,
IDbContextFactory<ApplicationDbContext> dbContextFactory)
IDbContextFactory<ApplicationDbContext> dbContextFactory,
IInternalWalletRepository internalWalletRepository)
{
_repository = repository;
_logger = logger;
_dbContextFactory = dbContextFactory;
_internalWalletRepository = internalWalletRepository;
}

public async Task<Key?> GetById(int id)
Expand Down Expand Up @@ -107,8 +110,30 @@ public async Task<Key> GetCurrentInternalWalletKey()
{
await using var applicationDbContext = _dbContextFactory.CreateDbContext();

var result = await applicationDbContext.Keys.OrderByDescending(x => x.Id).Where(x => x.IsFundsManagerPrivateKey)
.FirstOrDefaultAsync();
var internalWallet = await _internalWalletRepository.GetCurrentInternalWallet();

if (internalWallet == null)
return null;

var result = await applicationDbContext.Keys.OrderByDescending(x => x.Id).SingleOrDefaultAsync(x=> x.InternalWalletId == internalWallet.Id);

//If they key does not exist we should create it
if (result == null)
{
result = new Key
{
CreationDatetime = DateTimeOffset.Now,
InternalWalletId = internalWallet.Id,
UpdateDatetime = DateTimeOffset.Now,
Name = "Internal wallet",
XPUB = internalWallet.XPUB,
MasterFingerprint = internalWallet.MasterFingerprint,
//Derivation path
Path = Environment.GetEnvironmentVariable("DEFAULT_DERIVATION_PATH") ?? throw new ArgumentException("DEFAULT_DERIVATION_PATH is not set"),

};

}

return result;
}
Expand Down
73 changes: 68 additions & 5 deletions src/Helpers/JobTypes.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
using FundsManager.Jobs;
using Quartz;
using Quartz.Impl.Triggers;

namespace FundsManager.Helpers;

public class SimpleJob
{
/// <summary>
/// Creates a job that starts immediately when scheduled
/// </summary>
/// <param name="data">The data you want to have access to inside the Job</param>
/// <param name="identitySuffix">A suffix to identify a specific job, triggered from the same class</param>
/// <returns>An object with a job and a trigger to pass to a scheduler</returns>
public static JobAndTrigger Create<T>(JobDataMap data, string identitySuffix) where T : IJob
{
var job = JobBuilder.Create<T>()
Expand All @@ -23,11 +29,23 @@ public static JobAndTrigger Create<T>(JobDataMap data, string identitySuffix) wh

public class RetriableJob
{
public static JobAndTrigger Create<T>(JobDataMap data, string identitySuffix, int intervalInSeconds = 10, int retryTimes = 20) where T : IJob
/// <summary>
/// Creates a job that can be retried if failed. Used in conjuntion with Execute method
/// </summary>
/// <param name="data">The data you want to have access to inside the Job</param>
/// <param name="identitySuffix">A suffix to identify a specific job, triggered from the same class</param>
/// <param name="intervalListInMinutes">An optional list of retry intervals in minutes, defaults to { 1, 5, 10, 20 }</param>
/// <returns>An object with a job and a trigger to pass to a scheduler</returns>
public static JobAndTrigger Create<T>(JobDataMap data, string identitySuffix, int[]? intervalListInMinutes = null) where T : IJob
{
intervalListInMinutes = intervalListInMinutes ?? new int[] { 1, 5, 10, 20 };

var map = data ?? new JobDataMap();
map.Put("intervalListInMinutes", intervalListInMinutes);

var job = JobBuilder.Create<T>()
.DisallowConcurrentExecution()
.SetJobData(data ?? new JobDataMap())
.SetJobData(map)
.WithIdentity($"{typeof(T).Name}-{identitySuffix}")
.Build();

Expand All @@ -36,12 +54,57 @@ public static JobAndTrigger Create<T>(JobDataMap data, string identitySuffix, in
.StartNow()
.WithSimpleSchedule(opts =>
opts
.WithIntervalInSeconds(intervalInSeconds)
.WithRepeatCount(retryTimes))
.WithIntervalInMinutes(intervalListInMinutes[0])
.WithRepeatCount(intervalListInMinutes.Length))
.Build();

return new JobAndTrigger(job, trigger);
}

public static int[]? ParseRetryListFromEnvironmenVariable(string variable)
{
var retryListAsString = Environment.GetEnvironmentVariable(variable);
return retryListAsString?
.Split(",")
.Select<string, int>(s => int.Parse(s))
.ToArray();
}

/// <summary>
/// Call this function inside your Job's Execute method to set up retries and execute your action.
/// </summary>
/// <param name="context">The execution context of the job</param>
/// <param name="f">The action you want to perform inside the job</param>
public static async Task Execute(IJobExecutionContext context, Func<Task> callback)
{
var token = context.CancellationToken;
token.ThrowIfCancellationRequested();

var data = context.JobDetail.JobDataMap;
var intervals = data.Get("intervalListInMinutes") as int[];
if (intervals == null)
{
throw new Exception("No interval list found, make sure you're using the RetriableJob class");
};

var trigger = context.Trigger as SimpleTriggerImpl;

if (trigger!.TimesTriggered >= intervals.Length)
{
return;
}

var repeatInterval = intervals[trigger!.TimesTriggered - 1];

var prevTriggerTime = trigger.GetPreviousFireTimeUtc();
trigger.SetNextFireTimeUtc(prevTriggerTime!.Value.AddMinutes(repeatInterval));

await context.Scheduler.RescheduleJob(context.Trigger.Key, trigger);

await callback();

var schedule = context.Scheduler.DeleteJob(context.JobDetail.Key, token);
}
}

public class JobAndTrigger
Expand Down
16 changes: 9 additions & 7 deletions src/Helpers/PriceConversionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Text.Json;
using NBitcoin;
using RestSharp;
using Serilog;

namespace FundsManager.Helpers;

Expand All @@ -25,18 +26,19 @@ public static decimal GetBtcToUsdPrice()

try
{
// If response fails, it returns an object with status error
JsonDocument document = JsonDocument.Parse(response.Content);
var valueType = document.RootElement.GetType();
if (valueType.IsArray) {
if (response.IsSuccessful)
{
JsonDocument document = JsonDocument.Parse(response.Content);
btcPrice = document.RootElement[0].GetProperty("current_price").GetDecimal();
} else {
btcPrice = 0;
}
else
{
throw new Exception("Bitcoin price could not be retrieved");
}
}
catch (Exception e)
{
Console.WriteLine(e);
Log.Logger.Error(e.Message);
btcPrice = 0;
}

Expand Down
Loading

0 comments on commit 0783540

Please sign in to comment.