mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Wallet object scripts (#4301)
* Wallet object scripts * Adjust comment Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
0520b69c18
commit
0af3faf6ff
8 changed files with 142 additions and 33 deletions
|
@ -21,6 +21,8 @@ namespace BTCPayServer.Data
|
|||
public const string PayjoinExposed = "pj-exposed";
|
||||
public const string Payout = "payout";
|
||||
public const string PullPayment = "pull-payment";
|
||||
public const string Script = "script";
|
||||
public const string Utxo = "utxo";
|
||||
}
|
||||
public string WalletId { get; set; }
|
||||
public string Type { get; set; }
|
||||
|
|
|
@ -3194,7 +3194,7 @@ namespace BTCPayServer.Tests
|
|||
// Only the node `test` `test` is connected to `test1`
|
||||
var wid = new WalletId(admin.StoreId, "BTC");
|
||||
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 allObjectsNoWalletAndType = await repo.GetWalletObjects((new() { Type = "test", UseInefficientPath = useInefficient }));
|
||||
var allTests = await repo.GetWalletObjects((new(wid, "test") { UseInefficientPath = useInefficient }));
|
||||
|
|
|
@ -189,7 +189,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||
|
||||
var wallet = _btcPayWalletProvider.GetWallet(network);
|
||||
var walletId = new WalletId(storeId, cryptoCode);
|
||||
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId);
|
||||
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId, (string[] ) null);
|
||||
|
||||
var preFiltering = true;
|
||||
if (statusFilter?.Any() is true || !string.IsNullOrWhiteSpace(labelFilter))
|
||||
|
|
|
@ -578,17 +578,24 @@ namespace BTCPayServer.Controllers
|
|||
var utxos = await _walletProvider.GetWallet(network)
|
||||
.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 =>
|
||||
{
|
||||
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()
|
||||
{
|
||||
Outpoint = coin.OutPoint.ToString(),
|
||||
Amount = coin.Value.GetValue(network),
|
||||
Comment = info?.Comment,
|
||||
Labels = labels,
|
||||
Labels = CreateTransactionTagModels(info),
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink,
|
||||
coin.OutPoint.Hash.ToString()),
|
||||
Confirmations = coin.Confirmations
|
||||
|
@ -1291,7 +1298,7 @@ namespace BTCPayServer.Controllers
|
|||
return NotFound();
|
||||
|
||||
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 walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
var export = new TransactionsExport(wallet, walletTransactionsInfo);
|
||||
|
|
|
@ -7,7 +7,6 @@ using BTCPayServer.Services;
|
|||
using BTCPayServer.Services.Labels;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Logging;
|
||||
|
@ -22,42 +23,79 @@ namespace BTCPayServer.HostedServices
|
|||
{
|
||||
public class TransactionLabelMarkerHostedService : EventHostedServiceBase
|
||||
{
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly WalletRepository _walletRepository;
|
||||
|
||||
public TransactionLabelMarkerHostedService(EventAggregator eventAggregator, WalletRepository walletRepository, Logs logs) :
|
||||
base(eventAggregator, logs)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_walletRepository = walletRepository;
|
||||
}
|
||||
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
Subscribe<InvoiceEvent>();
|
||||
Subscribe<NewOnChainTransactionEvent>();
|
||||
}
|
||||
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)
|
||||
switch (evt)
|
||||
{
|
||||
var walletId = new WalletId(invoiceEvent.Invoice.StoreId, invoiceEvent.Payment.GetCryptoCode());
|
||||
var transactionId = bitcoinLikePaymentData.Outpoint.Hash;
|
||||
var labels = new List<Attachment>
|
||||
// For each new transaction that we detect, we check if we can find
|
||||
// any utxo or script object matching it.
|
||||
// If we find, then we create a link between them and the tx object.
|
||||
case NewOnChainTransactionEvent transactionEvent:
|
||||
{
|
||||
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));
|
||||
}
|
||||
var txHash = transactionEvent.NewTransactionEvent.TransactionData.TransactionHash.ToString();
|
||||
|
||||
// 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();
|
||||
|
||||
await _walletRepository.AddWalletTransactionAttachment(walletId, transactionId, labels);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
|||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -37,8 +38,9 @@ namespace BTCPayServer.Services
|
|||
Type = type;
|
||||
Ids = ids;
|
||||
}
|
||||
public GetWalletObjectsQuery(ObjectTypeId[]? typesIds)
|
||||
public GetWalletObjectsQuery(WalletId? walletId,ObjectTypeId[]? typesIds)
|
||||
{
|
||||
WalletId = walletId;
|
||||
TypesIds = typesIds;
|
||||
}
|
||||
|
||||
|
@ -50,6 +52,18 @@ namespace BTCPayServer.Services
|
|||
public string[]? Ids { get; set; }
|
||||
public bool IncludeNeighbours { get; set; } = true;
|
||||
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
|
||||
|
@ -230,9 +244,28 @@ namespace BTCPayServer.Services
|
|||
}
|
||||
}
|
||||
#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);
|
||||
foreach (var obj in wos.Values)
|
||||
{
|
||||
|
@ -421,13 +454,20 @@ namespace BTCPayServer.Services
|
|||
}
|
||||
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(txId);
|
||||
var txObjId = new WalletObjectId(walletId, WalletObjectData.Types.Tx, txId.ToString());
|
||||
var txObjId = new WalletObjectId(walletId, type, txId.ToString());
|
||||
await EnsureWalletObject(txObjId);
|
||||
foreach (var attachment in attachments)
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
@ -22,19 +23,21 @@ namespace BTCPayServer.Services.Wallets
|
|||
private readonly BTCPayWalletProvider _btcPayWalletProvider;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly WalletRepository _walletRepository;
|
||||
|
||||
private readonly ConcurrentDictionary<WalletId, KeyPathInformation> _walletReceiveState =
|
||||
new ConcurrentDictionary<WalletId, KeyPathInformation>();
|
||||
|
||||
public WalletReceiveService(EventAggregator eventAggregator, ExplorerClientProvider explorerClientProvider,
|
||||
BTCPayWalletProvider btcPayWalletProvider, BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
StoreRepository storeRepository)
|
||||
StoreRepository storeRepository, WalletRepository walletRepository )
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_btcPayWalletProvider = btcPayWalletProvider;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_storeRepository = storeRepository;
|
||||
_walletRepository = walletRepository;
|
||||
}
|
||||
|
||||
public async Task<string> UnReserveAddress(WalletId walletId)
|
||||
|
@ -73,6 +76,8 @@ namespace BTCPayServer.Services.Wallets
|
|||
}
|
||||
|
||||
var reserve = (await wallet.ReserveAddressAsync(derivationScheme.AccountDerivation));
|
||||
await _walletRepository.AddWalletTransactionAttachment(walletId, reserve.ScriptPubKey.ToString(), new []{new Attachment("receive")},
|
||||
WalletObjectData.Types.Script);
|
||||
Set(walletId, reserve);
|
||||
return reserve;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue