Have address wallet objects rather than script objects (#4417)

This commit is contained in:
Nicolas Dorier 2022-12-13 09:09:25 +09:00 committed by GitHub
parent 3673230fdf
commit 0c3f819200
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 67 additions and 55 deletions

View File

@ -4,7 +4,7 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="4.2.1" />
<PackageReference Include="NBXplorer.Client" Version="4.2.2" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">

View File

@ -21,7 +21,7 @@ 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 Address = "address";
public const string Utxo = "utxo";
}
public string WalletId { get; set; }

View File

@ -678,7 +678,7 @@ namespace BTCPayServer.Tests
parser = new DerivationSchemeParser(networkProvider.BTC);
var od = "wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48";
(strategyBase, rootedKeyPath) = parser.ParseOutputDescriptor(od);
Assert.Equal(1, rootedKeyPath.Length);
Assert.Single(rootedKeyPath);
Assert.IsType<DirectDerivationStrategy>(strategyBase);
Assert.True(((DirectDerivationStrategy)strategyBase).Segwit);

View File

@ -1936,7 +1936,7 @@ namespace BTCPayServer.Tests
}
});
var invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", newInvoice.Id), false);
Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "script");
Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "address");
Assert.EndsWith($"/i/{newInvoice.Id}", newInvoice.CheckoutLink);
var controller = tester.PayTester.GetController<UIInvoiceController>(user.UserId, user.StoreId);
@ -1972,7 +1972,7 @@ namespace BTCPayServer.Tests
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = 1, Currency = "USD" });
invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
Assert.DoesNotContain(invoiceObject.Links.Select(l => l.Type), t => t == "script");
Assert.DoesNotContain(invoiceObject.Links.Select(l => l.Type), t => t == "address");
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
@ -1981,7 +1981,7 @@ namespace BTCPayServer.Tests
await client.ActivateInvoicePaymentMethod(user.StoreId, invoice.Id,
paymentMethods.First().PaymentMethod);
invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "script");
Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "address");
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
Assert.Single(paymentMethods);
@ -2046,10 +2046,10 @@ namespace BTCPayServer.Tests
var pm = Assert.Single(await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id));
Assert.Single(pm.Payments);
Assert.Equal(-0.0001m, pm.Due);
});
invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "tx");
invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false);
Assert.Contains(invoiceObject.Links.Select(l => l.Type), t => t == "tx");
});
}
[Fact(Timeout = 60 * 20 * 1000)]

View File

@ -90,7 +90,7 @@ services:
expose:
- "4444"
nbxplorer:
image: nicolasdorier/nbxplorer:2.3.40
image: nicolasdorier/nbxplorer:2.3.54
restart: unless-stopped
ports:
- "32838:32838"

View File

@ -87,7 +87,7 @@ services:
expose:
- "4444"
nbxplorer:
image: nicolasdorier/nbxplorer:2.3.40
image: nicolasdorier/nbxplorer:2.3.54
restart: unless-stopped
ports:
- "32838:32838"

View File

@ -391,8 +391,8 @@ namespace BTCPayServer.Controllers
await _walletRepository.EnsureWalletObjectLink(
new WalletObjectId(
walletId,
WalletObjectData.Types.Script,
address.ScriptPubKey.ToHex()),
WalletObjectData.Types.Address,
address.ToString()),
new WalletObjectId(
walletId,
WalletObjectData.Types.Invoice,

View File

@ -16,6 +16,7 @@ using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Labels;
using BTCPayServer.Services.PaymentRequests;
using NBitcoin;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -25,9 +26,12 @@ namespace BTCPayServer.HostedServices
{
private readonly WalletRepository _walletRepository;
public TransactionLabelMarkerHostedService(EventAggregator eventAggregator, WalletRepository walletRepository, Logs logs) :
public BTCPayNetworkProvider NetworkProvider { get; }
public TransactionLabelMarkerHostedService(BTCPayNetworkProvider networkProvider, EventAggregator eventAggregator, WalletRepository walletRepository, Logs logs) :
base(eventAggregator, logs)
{
NetworkProvider = networkProvider;
_walletRepository = walletRepository;
}
@ -44,36 +48,40 @@ namespace BTCPayServer.HostedServices
// any utxo or script object matching it.
// If we find, then we create a link between them and the tx object.
case NewOnChainTransactionEvent transactionEvent:
{
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();
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);
}
}
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(transactionEvent.CryptoCode);
var derivation = transactionEvent.NewTransactionEvent.DerivationStrategy;
if (network is null || derivation is null)
break;
var txHash = transactionEvent.NewTransactionEvent.TransactionData.TransactionHash.ToString();
break;
}
// 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, ObjectTypeId>(txIn => new ObjectTypeId(WalletObjectData.Types.Utxo, txIn.PrevOut.ToString()))
.Concat(transactionEvent.NewTransactionEvent.Outputs.SelectMany<NBXplorer.Models.MatchedOutput, ObjectTypeId>(txOut =>
new[]{
new ObjectTypeId(WalletObjectData.Types.Address, GetAddress(derivation, txOut, network).ToString()),
new ObjectTypeId(WalletObjectData.Types.Utxo, new OutPoint(transactionEvent.NewTransactionEvent.TransactionData.TransactionHash, (uint)txOut.Index).ToString())
})).Distinct().ToArray();
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:
@ -98,5 +106,11 @@ namespace BTCPayServer.HostedServices
}
}
}
private BitcoinAddress GetAddress(DerivationStrategyBase derivationStrategy, NBXplorer.Models.MatchedOutput txOut, BTCPayNetwork network)
{
// Old version of NBX doesn't give address in the event, so we need to guess
return (txOut.Address ?? network.NBXplorerNetwork.CreateAddress(derivationStrategy, txOut.KeyPath, txOut.ScriptPubKey));
}
}
}

View File

@ -66,8 +66,8 @@ namespace BTCPayServer.Services
await _walletRepository.EnsureWalletObjectLink(
new WalletObjectId(
walletId,
WalletObjectData.Types.Script,
address.ScriptPubKey.ToHex()),
WalletObjectData.Types.Address,
address.ToString()),
new WalletObjectId(
walletId,
WalletObjectData.Types.Invoice,

View File

@ -52,16 +52,11 @@ 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.Address, coin.Address.ToString());
yield return new ObjectTypeId(WalletObjectData.Types.Utxo, coin.OutPoint.ToString());
}
}
@ -250,7 +245,7 @@ namespace BTCPayServer.Services
{
var wos = await GetWalletObjects(
new GetWalletObjectsQuery(walletId, WalletObjectData.Types.Tx, transactionIds));
return await GetWalletTransactionsInfoCore(walletId, wos);
return GetWalletTransactionsInfoCore(walletId, wos);
}
public async Task<Dictionary<string, WalletTransactionInfo>> GetWalletTransactionsInfo(WalletId walletId,
@ -259,10 +254,10 @@ namespace BTCPayServer.Services
var wos = await GetWalletObjects(
new GetWalletObjectsQuery(walletId, transactionIds));
return await GetWalletTransactionsInfoCore(walletId, wos);
return GetWalletTransactionsInfoCore(walletId, wos);
}
private async Task<Dictionary<string, WalletTransactionInfo>> GetWalletTransactionsInfoCore(WalletId walletId,
private Dictionary<string, WalletTransactionInfo> GetWalletTransactionsInfoCore(WalletId walletId,
Dictionary<WalletObjectId, WalletObjectData> wos)
{

View File

@ -27,6 +27,7 @@ namespace BTCPayServer.Services.Wallets
public IMoney Value { get; set; }
public Coin Coin { get; set; }
public long Confirmations { get; set; }
public BitcoinAddress Address { get; set; }
}
public class NetworkCoins
{
@ -91,7 +92,7 @@ namespace BTCPayServer.Services.Wallets
if (storeId != null)
{
await WalletRepository.EnsureWalletObject(
new WalletObjectId(new WalletId(storeId, Network.CryptoCode), WalletObjectData.Types.Script, pathInfo.ScriptPubKey.ToHex()),
new WalletObjectId(new WalletId(storeId, Network.CryptoCode), WalletObjectData.Types.Address, pathInfo.Address.ToString()),
new JObject() { ["generatedBy"] = generatedBy });
}
return pathInfo;
@ -360,7 +361,9 @@ namespace BTCPayServer.Services.Wallets
OutPoint = c.Outpoint,
ScriptPubKey = c.ScriptPubKey,
Coin = c.AsCoin(derivationStrategy),
Confirmations = c.Confirmations
Confirmations = c.Confirmations,
// Some old version of NBX doesn't have Address in this call
Address = c.Address ?? c.ScriptPubKey.GetDestinationAddress(Network.NBitcoinNetwork)
}).ToArray();
}