Wallet object scripts

This commit is contained in:
Kukks 2022-11-18 07:25:35 +01:00
parent 0520b69c18
commit d3e2d5a095
No known key found for this signature in database
GPG key ID: 8E5530D9D1C93097
8 changed files with 140 additions and 33 deletions

View file

@ -21,6 +21,8 @@ namespace BTCPayServer.Data
public const string PayjoinExposed = "pj-exposed"; public const string PayjoinExposed = "pj-exposed";
public const string Payout = "payout"; public const string Payout = "payout";
public const string PullPayment = "pull-payment"; public const string PullPayment = "pull-payment";
public const string Script = "script";
public const string Utxo = "utxo";
} }
public string WalletId { get; set; } public string WalletId { get; set; }
public string Type { get; set; } public string Type { get; set; }

View file

@ -3194,7 +3194,7 @@ namespace BTCPayServer.Tests
// Only the node `test` `test` is connected to `test1` // Only the node `test` `test` is connected to `test1`
var wid = new WalletId(admin.StoreId, "BTC"); var wid = new WalletId(admin.StoreId, "BTC");
var repo = tester.PayTester.GetService<WalletRepository>(); var repo = tester.PayTester.GetService<WalletRepository>();
var allObjects = await repo.GetWalletObjects((new(wid, null) { UseInefficientPath = useInefficient })); var allObjects = await repo.GetWalletObjects(new(wid) { UseInefficientPath = useInefficient });
var allObjectsNoWallet = await repo.GetWalletObjects((new() { UseInefficientPath = useInefficient })); var allObjectsNoWallet = await repo.GetWalletObjects((new() { UseInefficientPath = useInefficient }));
var allObjectsNoWalletAndType = await repo.GetWalletObjects((new() { Type = "test", UseInefficientPath = useInefficient })); var allObjectsNoWalletAndType = await repo.GetWalletObjects((new() { Type = "test", UseInefficientPath = useInefficient }));
var allTests = await repo.GetWalletObjects((new(wid, "test") { UseInefficientPath = useInefficient })); var allTests = await repo.GetWalletObjects((new(wid, "test") { UseInefficientPath = useInefficient }));

View file

@ -189,7 +189,7 @@ namespace BTCPayServer.Controllers.Greenfield
var wallet = _btcPayWalletProvider.GetWallet(network); var wallet = _btcPayWalletProvider.GetWallet(network);
var walletId = new WalletId(storeId, cryptoCode); var walletId = new WalletId(storeId, cryptoCode);
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId); var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId, (string[] ) null);
var preFiltering = true; var preFiltering = true;
if (statusFilter?.Any() is true || !string.IsNullOrWhiteSpace(labelFilter)) if (statusFilter?.Any() is true || !string.IsNullOrWhiteSpace(labelFilter))

View file

@ -578,17 +578,24 @@ namespace BTCPayServer.Controllers
var utxos = await _walletProvider.GetWallet(network) var utxos = await _walletProvider.GetWallet(network)
.GetUnspentCoins(schemeSettings.AccountDerivation, false, cancellation); .GetUnspentCoins(schemeSettings.AccountDerivation, false, cancellation);
var walletTransactionsInfoAsync = await this.WalletRepository.GetWalletTransactionsInfo(walletId, utxos.Select(u => u.OutPoint.Hash.ToString()).Distinct().ToArray()); var walletTransactionsInfoAsync = await this.WalletRepository.GetWalletTransactionsInfo(walletId,
utxos.SelectMany(u => GetWalletObjectsQuery.Get(u)).Distinct().ToArray());
vm.InputsAvailable = utxos.Select(coin => vm.InputsAvailable = utxos.Select(coin =>
{ {
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info); walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info);
var labels = CreateTransactionTagModels(info).ToList(); walletTransactionsInfoAsync.TryGetValue(coin.ScriptPubKey.ToHex(), out var info2);
if (info is not null && info2 is not null)
{
info.Merge(info2);
}
info ??= info2;
return new WalletSendModel.InputSelectionOption() return new WalletSendModel.InputSelectionOption()
{ {
Outpoint = coin.OutPoint.ToString(), Outpoint = coin.OutPoint.ToString(),
Amount = coin.Value.GetValue(network), Amount = coin.Value.GetValue(network),
Comment = info?.Comment, Comment = info?.Comment,
Labels = labels, Labels = CreateTransactionTagModels(info),
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink,
coin.OutPoint.Hash.ToString()), coin.OutPoint.Hash.ToString()),
Confirmations = coin.Confirmations Confirmations = coin.Confirmations
@ -1291,7 +1298,7 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
var wallet = _walletProvider.GetWallet(paymentMethod.Network); var wallet = _walletProvider.GetWallet(paymentMethod.Network);
var walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId); var walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId, (string[] ) null);
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, null, null); var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, null, null);
var walletTransactionsInfo = await walletTransactionsInfoAsync; var walletTransactionsInfo = await walletTransactionsInfoAsync;
var export = new TransactionsExport(wallet, walletTransactionsInfo); var export = new TransactionsExport(wallet, walletTransactionsInfo);

View file

@ -7,7 +7,6 @@ using BTCPayServer.Services;
using BTCPayServer.Services.Labels; using BTCPayServer.Services.Labels;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace BTCPayServer.Data namespace BTCPayServer.Data
{ {
@ -83,5 +82,23 @@ namespace BTCPayServer.Data
} }
} }
public string Type { get; set; }
public void Merge(WalletTransactionInfo? value)
{
if (value is null)
return;
foreach (var valueLabelColor in value.LabelColors)
{
LabelColors.TryAdd(valueLabelColor.Key, valueLabelColor.Value);
}
foreach (var valueAttachment in value.Attachments.Where(valueAttachment => !Attachments.Any(attachment =>
attachment.Id == valueAttachment.Id && attachment.Type == valueAttachment.Type)))
{
Attachments.Add(valueAttachment);
}
}
} }
} }

View file

@ -5,6 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Events; using BTCPayServer.Events;
using BTCPayServer.Logging; using BTCPayServer.Logging;
@ -22,42 +23,77 @@ namespace BTCPayServer.HostedServices
{ {
public class TransactionLabelMarkerHostedService : EventHostedServiceBase public class TransactionLabelMarkerHostedService : EventHostedServiceBase
{ {
private readonly EventAggregator _eventAggregator;
private readonly WalletRepository _walletRepository; private readonly WalletRepository _walletRepository;
public TransactionLabelMarkerHostedService(EventAggregator eventAggregator, WalletRepository walletRepository, Logs logs) : public TransactionLabelMarkerHostedService(EventAggregator eventAggregator, WalletRepository walletRepository, Logs logs) :
base(eventAggregator, logs) base(eventAggregator, logs)
{ {
_eventAggregator = eventAggregator;
_walletRepository = walletRepository; _walletRepository = walletRepository;
} }
protected override void SubscribeToEvents() protected override void SubscribeToEvents()
{ {
Subscribe<InvoiceEvent>(); Subscribe<InvoiceEvent>();
Subscribe<NewOnChainTransactionEvent>();
} }
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken) protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
{ {
if (evt is InvoiceEvent invoiceEvent && invoiceEvent.Name == InvoiceEvent.ReceivedPayment && switch (evt)
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance &&
invoiceEvent.Payment.GetCryptoPaymentData() is BitcoinLikePaymentData bitcoinLikePaymentData)
{ {
var walletId = new WalletId(invoiceEvent.Invoice.StoreId, invoiceEvent.Payment.GetCryptoCode()); case NewOnChainTransactionEvent transactionEvent:
var transactionId = bitcoinLikePaymentData.Outpoint.Hash;
var labels = new List<Attachment>
{ {
Attachment.Invoice(invoiceEvent.Invoice.Id) var txHash = transactionEvent.NewTransactionEvent.TransactionData.TransactionHash.ToString();
};
foreach (var paymentId in PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice))
{
labels.Add(Attachment.PaymentRequest(paymentId));
}
foreach (var appId in AppService.GetAppInternalTags(invoiceEvent.Invoice))
{
labels.Add(Attachment.App(appId));
}
await _walletRepository.AddWalletTransactionAttachment(walletId, transactionId, labels); // find all wallet objects that fit this transaction
// that means see if there are any utxo objects that match in/outs and scripts/addresses that match outs
var matchedObjects = transactionEvent.NewTransactionEvent.TransactionData.Transaction.Inputs
.Select(txIn => new ObjectTypeId(WalletObjectData.Types.Utxo, txIn.PrevOut.ToString()))
.Concat(transactionEvent.NewTransactionEvent.TransactionData.Transaction.Outputs.AsIndexedOutputs().SelectMany(txOut =>
new[]{
new ObjectTypeId(WalletObjectData.Types.Script,txOut.TxOut.ScriptPubKey.ToHex()),
new ObjectTypeId(WalletObjectData.Types.Utxo,txOut.ToCoin().Outpoint.ToString())
} )).Distinct().ToArray();
// we are intentionally excluding wallet id filter so that we reduce db trips
var objs = await _walletRepository.GetWalletObjects(new GetWalletObjectsQuery(){TypesIds = matchedObjects});
foreach (var walletObjectDatas in objs.GroupBy(data => data.Key.WalletId))
{
var txWalletObject = new WalletObjectId(walletObjectDatas.Key,
WalletObjectData.Types.Tx, txHash);
await _walletRepository.EnsureWalletObject(txWalletObject);
foreach (var walletObjectData in walletObjectDatas)
{
await _walletRepository.EnsureWalletObjectLink(txWalletObject, walletObjectData.Key);
}
}
break;
}
case InvoiceEvent {Name: InvoiceEvent.ReceivedPayment} invoiceEvent when
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<Attachment>
{
Attachment.Invoice(invoiceEvent.Invoice.Id)
};
foreach (var paymentId in PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice))
{
labels.Add(Attachment.PaymentRequest(paymentId));
}
foreach (var appId in AppService.GetAppInternalTags(invoiceEvent.Invoice))
{
labels.Add(Attachment.App(appId));
}
await _walletRepository.AddWalletTransactionAttachment(walletId, transactionId, labels);
break;
}
} }
} }
} }

View file

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Services.Wallets;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NBitcoin; using NBitcoin;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -37,8 +38,9 @@ namespace BTCPayServer.Services
Type = type; Type = type;
Ids = ids; Ids = ids;
} }
public GetWalletObjectsQuery(ObjectTypeId[]? typesIds) public GetWalletObjectsQuery(WalletId? walletId,ObjectTypeId[]? typesIds)
{ {
WalletId = walletId;
TypesIds = typesIds; TypesIds = typesIds;
} }
@ -50,6 +52,18 @@ namespace BTCPayServer.Services
public string[]? Ids { get; set; } public string[]? Ids { get; set; }
public bool IncludeNeighbours { get; set; } = true; public bool IncludeNeighbours { get; set; } = true;
public bool UseInefficientPath { get; set; } public bool UseInefficientPath { get; set; }
public static ObjectTypeId Get(Script script)
{
return new ObjectTypeId(WalletObjectData.Types.Script, script.ToHex());
}
public static IEnumerable<ObjectTypeId> Get(ReceivedCoin coin)
{
yield return new ObjectTypeId(WalletObjectData.Types.Tx, coin.OutPoint.Hash.ToString());
yield return Get(coin.ScriptPubKey);
yield return new ObjectTypeId(WalletObjectData.Types.Utxo, coin.OutPoint.ToString());
}
} }
#nullable restore #nullable restore
@ -230,9 +244,28 @@ namespace BTCPayServer.Services
} }
} }
#nullable restore #nullable restore
public async Task<Dictionary<string, WalletTransactionInfo>> GetWalletTransactionsInfo(WalletId walletId, string[] transactionIds = null)
public async Task<Dictionary<string, WalletTransactionInfo>> GetWalletTransactionsInfo(WalletId walletId,
string[] transactionIds = null)
{ {
var wos = await GetWalletObjects((GetWalletObjectsQuery)(new(walletId, WalletObjectData.Types.Tx, transactionIds))); var wos = await GetWalletObjects(
new GetWalletObjectsQuery(walletId, WalletObjectData.Types.Tx, transactionIds));
return await GetWalletTransactionsInfoCore(walletId, wos);
}
public async Task<Dictionary<string, WalletTransactionInfo>> GetWalletTransactionsInfo(WalletId walletId,
ObjectTypeId[] transactionIds = null)
{
var wos = await GetWalletObjects(
new GetWalletObjectsQuery(walletId, transactionIds));
return await GetWalletTransactionsInfoCore(walletId, wos);
}
private async Task<Dictionary<string, WalletTransactionInfo>> GetWalletTransactionsInfoCore(WalletId walletId,
Dictionary<WalletObjectId, WalletObjectData> wos)
{
var result = new Dictionary<string, WalletTransactionInfo>(wos.Count); var result = new Dictionary<string, WalletTransactionInfo>(wos.Count);
foreach (var obj in wos.Values) foreach (var obj in wos.Values)
{ {
@ -421,13 +454,20 @@ namespace BTCPayServer.Services
} }
public Task AddWalletTransactionAttachment(WalletId walletId, uint256 txId, Attachment attachment) public Task AddWalletTransactionAttachment(WalletId walletId, uint256 txId, Attachment attachment)
{ {
return AddWalletTransactionAttachment(walletId, txId, new[] { attachment }); return AddWalletTransactionAttachment(walletId, txId.ToString(), new []{attachment}, WalletObjectData.Types.Tx);
} }
public async Task AddWalletTransactionAttachment(WalletId walletId, uint256 txId, IEnumerable<Attachment> attachments)
public Task AddWalletTransactionAttachment(WalletId walletId, uint256 txId,
IEnumerable<Attachment> attachments)
{
return AddWalletTransactionAttachment(walletId, txId.ToString(), attachments, WalletObjectData.Types.Tx);
}
public async Task AddWalletTransactionAttachment(WalletId walletId, string txId, IEnumerable<Attachment> attachments, string type)
{ {
ArgumentNullException.ThrowIfNull(walletId); ArgumentNullException.ThrowIfNull(walletId);
ArgumentNullException.ThrowIfNull(txId); ArgumentNullException.ThrowIfNull(txId);
var txObjId = new WalletObjectId(walletId, WalletObjectData.Types.Tx, txId.ToString()); var txObjId = new WalletObjectId(walletId, type, txId.ToString());
await EnsureWalletObject(txObjId); await EnsureWalletObject(txObjId);
foreach (var attachment in attachments) foreach (var attachment in attachments)
{ {

View file

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Events; using BTCPayServer.Events;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@ -22,19 +23,21 @@ namespace BTCPayServer.Services.Wallets
private readonly BTCPayWalletProvider _btcPayWalletProvider; private readonly BTCPayWalletProvider _btcPayWalletProvider;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly WalletRepository _walletRepository;
private readonly ConcurrentDictionary<WalletId, KeyPathInformation> _walletReceiveState = private readonly ConcurrentDictionary<WalletId, KeyPathInformation> _walletReceiveState =
new ConcurrentDictionary<WalletId, KeyPathInformation>(); new ConcurrentDictionary<WalletId, KeyPathInformation>();
public WalletReceiveService(EventAggregator eventAggregator, ExplorerClientProvider explorerClientProvider, public WalletReceiveService(EventAggregator eventAggregator, ExplorerClientProvider explorerClientProvider,
BTCPayWalletProvider btcPayWalletProvider, BTCPayNetworkProvider btcPayNetworkProvider, BTCPayWalletProvider btcPayWalletProvider, BTCPayNetworkProvider btcPayNetworkProvider,
StoreRepository storeRepository) StoreRepository storeRepository, WalletRepository walletRepository )
{ {
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_explorerClientProvider = explorerClientProvider; _explorerClientProvider = explorerClientProvider;
_btcPayWalletProvider = btcPayWalletProvider; _btcPayWalletProvider = btcPayWalletProvider;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_walletRepository = walletRepository;
} }
public async Task<string> UnReserveAddress(WalletId walletId) public async Task<string> UnReserveAddress(WalletId walletId)
@ -73,6 +76,8 @@ namespace BTCPayServer.Services.Wallets
} }
var reserve = (await wallet.ReserveAddressAsync(derivationScheme.AccountDerivation)); var reserve = (await wallet.ReserveAddressAsync(derivationScheme.AccountDerivation));
await _walletRepository.AddWalletTransactionAttachment(walletId, reserve.ScriptPubKey.ToString(), new []{new Attachment("receive")},
WalletObjectData.Types.Script);
Set(walletId, reserve); Set(walletId, reserve);
return reserve; return reserve;
} }