btcpayserver/BTCPayServer/Services/DelayedTransactionBroadcaster.cs

145 lines
5 KiB
C#
Raw Normal View History

2020-06-28 21:44:35 -05:00
using System;
2020-03-30 00:28:22 +09:00
using System.Collections.Generic;
using System.Linq;
using System.Threading;
2020-06-28 17:55:27 +09:00
using System.Threading.Tasks;
2020-04-13 15:17:28 +09:00
using BTCPayServer.Data;
2020-03-30 00:28:22 +09:00
using BTCPayServer.Logging;
2020-04-13 15:17:28 +09:00
using Microsoft.EntityFrameworkCore;
2020-06-28 17:55:27 +09:00
using Microsoft.Extensions.Logging;
using NBitcoin;
2020-03-30 00:28:22 +09:00
namespace BTCPayServer.Services
{
public class DelayedTransactionBroadcaster
{
class Record
{
2020-04-13 15:17:28 +09:00
public string Id;
2020-03-30 00:28:22 +09:00
public DateTimeOffset BroadcastTime;
public Transaction Transaction;
public BTCPayNetwork Network;
}
2020-04-13 15:17:28 +09:00
private readonly BTCPayNetworkProvider _networkProvider;
2020-03-30 00:28:22 +09:00
private readonly ExplorerClientProvider _explorerClientProvider;
2020-04-13 15:17:28 +09:00
private readonly ApplicationDbContextFactory _dbContextFactory;
2020-03-30 00:28:22 +09:00
2021-11-22 17:16:08 +09:00
public Logs Logs { get; }
2020-04-13 15:17:28 +09:00
public DelayedTransactionBroadcaster(
BTCPayNetworkProvider networkProvider,
2020-06-28 17:55:27 +09:00
ExplorerClientProvider explorerClientProvider,
2021-11-22 17:16:08 +09:00
Data.ApplicationDbContextFactory dbContextFactory,
Logs logs)
2020-03-30 00:28:22 +09:00
{
ArgumentNullException.ThrowIfNull(explorerClientProvider);
2020-04-13 15:17:28 +09:00
_networkProvider = networkProvider;
2020-03-30 00:28:22 +09:00
_explorerClientProvider = explorerClientProvider;
2020-04-13 15:17:28 +09:00
_dbContextFactory = dbContextFactory;
2021-11-22 17:16:08 +09:00
this.Logs = logs;
2020-03-30 00:28:22 +09:00
}
2020-04-13 15:17:28 +09:00
public async Task Schedule(DateTimeOffset broadcastTime, Transaction transaction, BTCPayNetwork network)
2020-03-30 00:28:22 +09:00
{
ArgumentNullException.ThrowIfNull(transaction);
ArgumentNullException.ThrowIfNull(network);
2022-01-14 17:50:29 +09:00
using var db = _dbContextFactory.CreateContext();
db.PlannedTransactions.Add(new PlannedTransaction()
{
Id = $"{network.CryptoCode}-{transaction.GetHash()}",
BroadcastAt = broadcastTime,
Blob = transaction.ToBytes()
});
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
2020-03-30 00:28:22 +09:00
{
2020-04-13 15:17:28 +09:00
}
2020-03-30 00:28:22 +09:00
}
2020-04-13 15:17:28 +09:00
public async Task<int> ProcessAll(CancellationToken cancellationToken = default)
2020-03-30 00:28:22 +09:00
{
if (disabled)
2020-04-13 15:17:28 +09:00
return 0;
2020-03-30 00:28:22 +09:00
List<Record> scheduled = new List<Record>();
2020-04-13 15:17:28 +09:00
using (var db = _dbContextFactory.CreateContext())
2020-03-30 00:28:22 +09:00
{
2020-04-13 15:17:28 +09:00
scheduled = (await db.PlannedTransactions
.ToListAsync()).Select(ToRecord)
.Where(r => r != null)
// Client side filtering because entity framework is retarded.
.Where(r => r.BroadcastTime < DateTimeOffset.UtcNow).ToList();
2020-03-30 00:28:22 +09:00
}
2020-06-28 17:55:27 +09:00
2020-04-13 15:17:28 +09:00
List<Record> rescheduled = new List<Record>();
List<Record> broadcasted = new List<Record>();
2020-03-30 00:28:22 +09:00
var broadcasts = scheduled.Select(async (record) =>
{
var explorer = _explorerClientProvider.GetExplorerClient(record.Network);
if (explorer is null)
return false;
try
{
// We don't look the result, this is a best effort basis.
var result = await explorer.BroadcastAsync(record.Transaction, cancellationToken);
if (result.Success)
{
Logs.PayServer.LogInformation($"{record.Network.CryptoCode}: {record.Transaction.GetHash()} has been successfully broadcasted");
}
return false;
}
catch
{
// If this goes here, maybe RPC is down or NBX is down, we should reschedule
return true;
}
}).ToArray();
2020-06-28 17:55:27 +09:00
2020-03-30 00:28:22 +09:00
for (int i = 0; i < scheduled.Count; i++)
{
var needReschedule = await broadcasts[i];
(needReschedule ? rescheduled : broadcasted).Add(scheduled[i]);
}
2020-04-13 15:17:28 +09:00
using (var db = _dbContextFactory.CreateContext())
2020-03-30 00:28:22 +09:00
{
2020-04-13 15:17:28 +09:00
foreach (Record record in broadcasted)
{
2020-06-28 17:55:27 +09:00
db.PlannedTransactions.Remove(new PlannedTransaction() { Id = record.Id });
2020-04-13 15:17:28 +09:00
}
return await db.SaveChangesAsync();
2020-03-30 00:28:22 +09:00
}
2020-04-13 15:17:28 +09:00
}
private Record ToRecord(PlannedTransaction plannedTransaction)
{
var s = plannedTransaction.Id.Split('-');
var network = _networkProvider.GetNetwork(s[0]) as BTCPayNetwork;
if (network is null)
return null;
return new Record()
{
Id = plannedTransaction.Id,
Network = network,
Transaction = Transaction.Load(plannedTransaction.Blob, network.NBitcoinNetwork),
BroadcastTime = plannedTransaction.BroadcastAt
};
2020-03-30 00:28:22 +09:00
}
private bool disabled = false;
public void Disable()
{
disabled = true;
}
2020-04-13 15:17:28 +09:00
public void Enable()
{
disabled = false;
}
2020-03-30 00:28:22 +09:00
}
}