btcpayserver/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs
Andrew Camilleri b31fb1a269
Auto label utxos based on invoice and payjoin (#1499)
* Auto label utxos based on invoice and payjoin

This PR introduces automatic labelling to transactions.
* If it is an incoming tx to an invoice, it will tag it as such.
* If it was a payjoin tx , it will tag it as such.
* If a transaction's inputs were exposed to a payjoin sender, we tag it as such.

* wip

* wip

* support in coinselection

* remove ugly hack

* support wallet transactions page

* remove messy loop

* better label template helpers

* add tests and susbcribe to event

* simplify data

* fix test

* fix label  + color

* fix remove label

* renove useless call

* add toString

* fix potential crash by txid

* trim json keyword in manual label

* format file
2020-04-28 15:06:28 +09:00

129 lines
5.2 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Services;
using NBitcoin;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.HostedServices
{
public class TransactionLabelMarkerHostedService : EventHostedServiceBase
{
private readonly EventAggregator _eventAggregator;
private readonly WalletRepository _walletRepository;
public TransactionLabelMarkerHostedService(EventAggregator eventAggregator, WalletRepository walletRepository) :
base(eventAggregator)
{
_eventAggregator = eventAggregator;
_walletRepository = walletRepository;
}
protected override void SubscribeToEvents()
{
Subscribe<InvoiceEvent>();
Subscribe<UpdateTransactionLabel>();
}
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
{
if (evt is InvoiceEvent invoiceEvent && invoiceEvent.Name == InvoiceEvent.ReceivedPayment &&
invoiceEvent.Payment.GetPaymentMethodId().PaymentType == BitcoinPaymentType.Instance &&
invoiceEvent.Payment.GetCryptoPaymentData() is BitcoinLikePaymentData bitcoinLikePaymentData)
{
var walletId = new WalletId(invoiceEvent.Invoice.StoreId, invoiceEvent.Payment.GetCryptoCode());
var transactionId = bitcoinLikePaymentData.Outpoint.Hash;
var labels = new List<(string color, string label)>
{
UpdateTransactionLabel.InvoiceLabelTemplate(invoiceEvent.Invoice.Id)
};
if (invoiceEvent.Invoice.GetPayments(invoiceEvent.Payment.GetCryptoCode()).Any(entity =>
entity.GetCryptoPaymentData() is BitcoinLikePaymentData pData &&
pData.PayjoinInformation?.CoinjoinTransactionHash == transactionId))
{
labels.Add(UpdateTransactionLabel.PayjoinLabelTemplate());
}
_eventAggregator.Publish(new UpdateTransactionLabel()
{
WalletId = walletId,
TransactionLabels =
new Dictionary<uint256, List<(string color, string label)>>() {{transactionId, labels}}
});
}
else if (evt is UpdateTransactionLabel updateTransactionLabel)
{
var walletTransactionsInfo =
await _walletRepository.GetWalletTransactionsInfo(updateTransactionLabel.WalletId);
var walletBlobInfo = await _walletRepository.GetWalletInfo(updateTransactionLabel.WalletId);
await Task.WhenAll(updateTransactionLabel.TransactionLabels.Select(async pair =>
{
if (!walletTransactionsInfo.TryGetValue(pair.Key.ToString(), out var walletTransactionInfo))
{
walletTransactionInfo = new WalletTransactionInfo();
}
foreach (var label in pair.Value)
{
walletBlobInfo.LabelColors.TryAdd(label.label, label.color);
}
await _walletRepository.SetWalletInfo(updateTransactionLabel.WalletId, walletBlobInfo);
var update = false;
foreach (var label in pair.Value)
{
if (walletTransactionInfo.Labels.Add(label.label))
{
update = true;
}
}
if (update)
{
await _walletRepository.SetWalletTransactionInfo(updateTransactionLabel.WalletId,
pair.Key.ToString(), walletTransactionInfo);
}
}));
}
}
}
public class UpdateTransactionLabel
{
public static (string color, string label) PayjoinLabelTemplate()
{
return ("#51b13e", "payjoin");
}
public static (string color, string label) InvoiceLabelTemplate(string invoice)
{
return ("#cedc21", JObject.FromObject(new {value = "invoice", id = invoice}).ToString());
}
public static (string color, string label) PayjoinExposedLabelTemplate(string invoice)
{
return ("#51b13e", JObject.FromObject(new {value = "pj-exposed", id = invoice}).ToString());
}
public WalletId WalletId { get; set; }
public Dictionary<uint256, List<(string color, string label)>> TransactionLabels { get; set; }
public override string ToString()
{
var result = new StringBuilder();
foreach (var transactionLabel in TransactionLabels)
{
result.AppendLine(
$"Adding {transactionLabel.Value.Count} labels to {transactionLabel.Key} in wallet {WalletId}");
}
return result.ToString();
}
}
}