Skip to content

Commit

Permalink
Remove GetGroupAddresses from the API
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasDorier committed Jan 10, 2024
1 parent 6d4c51f commit d45f7eb
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 210 deletions.
4 changes: 0 additions & 4 deletions NBXplorer.Client/ExplorerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -568,10 +568,6 @@ public Task AddGroupAddressAsync(string cryptoCode, string groupId, string[] add
{
return SendAsync<GroupInformation>(HttpMethod.Post, addresses, $"v1/cryptos/{cryptoCode}/groups/{groupId}/addresses", cancellationToken);
}
public Task<string[]> GetGroupAddressesAsync(string cryptoCode, string groupId, CancellationToken cancellationToken = default)
{
return SendAsync<string[]>(HttpMethod.Get, null, $"v1/cryptos/{cryptoCode}/groups/{groupId}/addresses", cancellationToken);
}

private static readonly HttpClient SharedClient = new HttpClient();
internal HttpClient Client = SharedClient;
Expand Down
36 changes: 32 additions & 4 deletions NBXplorer.Tests/UnitTest1.Groups.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;
using Dapper;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;
using NBitcoin;
using NBXplorer.Backends.Postgres;
using NBXplorer.Controllers;
using NBXplorer.Models;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -56,7 +60,7 @@ void AssertG1Empty()

async Task AssertAddresses(GroupInformation g)
{
var groupAddresses = await tester.Client.GetGroupAddressesAsync("BTC", g.GroupId);
var groupAddresses = await GetGroupAddressesAsync(tester, "BTC", g.GroupId);
Assert.Equal(groupAddresses.Length, addresses.Length);
foreach (var a in addresses)
{
Expand All @@ -65,14 +69,27 @@ async Task AssertAddresses(GroupInformation g)
}
await AssertAddresses(g1);
await AssertAddresses(g2);
var g3Addrs = await tester.Client.GetGroupAddressesAsync("BTC", g3.GroupId);
var g3Addrs = await GetGroupAddressesAsync(tester, "BTC", g3.GroupId);
Assert.Empty(g3Addrs);

// Removing g2 should remove all its addresses
g1 = await tester.Client.RemoveGroupChildrenAsync(g1.GroupId, [g2.AsGroupChild()]);
await AssertAddresses(g2);
var g1Addrs = await tester.Client.GetGroupAddressesAsync("BTC", g1.GroupId);
var g1Addrs = await GetGroupAddressesAsync(tester, "BTC", g1.GroupId);
Assert.Empty(g1Addrs);

await AssertNBXplorerException(400, tester.Client.AddGroupChildrenAsync(g2.GroupId, [new GroupChild() { TrackedSource= "DERIVATIONSCHEME:tpubDC45vUDsFAAqwYKz5hSLi5yJLNduJzpmTw6QTMRPrwdXURoyL81H8oZAaL8EiwEgg92qgMa9h1bB4Y1BZpy9CTNPfjfxvFcWxeiKBHCqSdc" }]));
await AssertNBXplorerException(400, tester.Client.AddGroupChildrenAsync(g2.GroupId, [new GroupChild() { CryptoCode="BTC", TrackedSource = "DERIVATIONSCHEME:lol" }]));
}

private async Task<string[]> GetGroupAddressesAsync(ServerTester tester, string code, string groupId)
{
await using var conn = await tester.GetService<DbConnectionFactory>().CreateConnection();
return (await conn.QueryAsync<string>("SELECT s.addr FROM wallets_scripts JOIN scripts s USING (code, script) WHERE code=@code AND wallet_id=@wid", new
{
code = code,
wid = PostgresRepository.GetWalletKey(new GroupTrackedSource(groupId)).wid
})).ToArray();
}

[Fact]
Expand All @@ -96,6 +113,17 @@ public async Task CanAliceAndBobShareWallet()
var txs = await tester.Client.GetTransactionsAsync(gts);
var tx = Assert.Single(txs.UnconfirmedTransactions.Transactions);
Assert.Equal(txid, tx.TransactionId);
Assert.NotNull(tx.Outputs[0].Address);

// Can we track manually added address?
await tester.Client.AddGroupAddressAsync("BTC", shared.GroupId, ["n3XyBWEKWLxm5EzrrvLCJyCQrRhVWQ8YGa"]);
txid = tester.SendToAddress(BitcoinAddress.Create("n3XyBWEKWLxm5EzrrvLCJyCQrRhVWQ8YGa", tester.Network), Money.Coins(1.2m));
var txEvt = tester.Notifications.WaitForTransaction(gts, txid);
Assert.Single(txEvt.Outputs);
Assert.NotNull(tx.Outputs[0].Address);

balance = await tester.Client.GetBalanceAsync(gts);
Assert.Equal(Money.Coins(1.0m + 1.2m), balance.Unconfirmed);
}

private async Task<NBXplorerException> AssertNBXplorerException(int httpCode, Task<GroupInformation> task)
Expand Down
2 changes: 1 addition & 1 deletion NBXplorer/Backends/DBTrie/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1411,7 +1411,7 @@ public async Task<TrackedTransaction[]> GetMatches(IList<NBitcoin.Transaction> t
}
foreach (var m in matches.Values)
{
m.KnownKeyPathMappingUpdated();
m.OwnedScriptsUpdated();
await AfterMatch(m, keyPathInformationsByTrackedTransaction[m]);
}

Expand Down
13 changes: 8 additions & 5 deletions NBXplorer/Backends/Postgres/PostgresRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ SaveTransactionRecord CreateTransactionRecord(Transaction tx) => SaveTransaction
}
foreach (var m in matches.Values)
{
m.KnownKeyPathMappingUpdated();
m.OwnedScriptsUpdated();
if (elementContext is not null)
await elementContext.Unblind(rpc, m);
}
Expand Down Expand Up @@ -841,9 +841,9 @@ public async Task<TrackedTransaction[]> GetTransactions(TrackedSource trackedSou
var tip = await connection.GetTip();
var txIdCond = txId is null ? string.Empty : " AND tx_id=@tx_id";
var utxos = await
connection.Connection.QueryAsync<(string tx_id, long idx, string blk_id, long? blk_height, int? blk_idx, bool is_out, string spent_tx_id, long spent_idx, string script, long value, string asset_id, bool immature, string keypath, DateTime seen_at)>(
"SELECT tx_id, idx, blk_id, blk_height, blk_idx, is_out, spent_tx_id, spent_idx, script, value, asset_id, immature, keypath, seen_at " +
"FROM nbxv1_tracked_txs " +
connection.Connection.QueryAsync<(string tx_id, long idx, string blk_id, long? blk_height, int? blk_idx, bool is_out, string spent_tx_id, long spent_idx, string script, string addr, long value, string asset_id, bool immature, string keypath, DateTime seen_at)>(
"SELECT tx_id, idx, blk_id, blk_height, blk_idx, is_out, spent_tx_id, spent_idx, script, s.addr, value, asset_id, immature, keypath, seen_at " +
"FROM nbxv1_tracked_txs LEFT JOIN scripts s USING (code, script) " +
$"WHERE code=@code AND wallet_id=@walletId{txIdCond}", new { code = Network.CryptoCode, walletId = GetWalletKey(trackedSource).wid, tx_id = txId?.ToString() });
utxos.TryGetNonEnumeratedCount(out int c);
var trackedById = new Dictionary<string, TrackedTransaction>(c);
Expand All @@ -854,6 +854,7 @@ public async Task<TrackedTransaction[]> GetTransactions(TrackedSource trackedSou
{
var txout = Network.NBitcoinNetwork.Consensus.ConsensusFactory.CreateTxOut();
txout.ScriptPubKey = Script.FromHex(utxo.script);
tracked.KnownAddresses.TryAdd(txout.ScriptPubKey, BitcoinAddress.Create(utxo.addr, this.Network.NBitcoinNetwork));
txout.Value = Money.Satoshis(utxo.value);
var coin = new Coin(new OutPoint(tracked.Key.TxId, (uint)utxo.idx), txout);
if (Network.IsElement)
Expand All @@ -864,11 +865,13 @@ public async Task<TrackedTransaction[]> GetTransactions(TrackedSource trackedSou
{
tracked.ReceivedCoins.Add(coin);
}

// TODO: IsCoinBase is actually not used anywhere.
tracked.IsCoinBase = utxo.immature;
tracked.Immature = utxo.immature;
if (utxo.keypath is string)
tracked.KnownKeyPathMapping.TryAdd(txout.ScriptPubKey, KeyPath.Parse(utxo.keypath));
tracked.OwnedScripts.Add(txout.ScriptPubKey);
}
else
{
Expand Down Expand Up @@ -896,7 +899,7 @@ public async Task<TrackedTransaction[]> GetTransactions(TrackedSource trackedSou

return trackedById.Values.Select(c =>
{
c.KnownKeyPathMappingUpdated();
c.OwnedScriptsUpdated();
return c;
}).ToArray();
}
Expand Down
16 changes: 3 additions & 13 deletions NBXplorer/Controllers/GroupsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,25 +152,15 @@ await conn.ExecuteAsync(PostgresRepository.InsertScriptsScript +
return Ok();
}

[HttpGet($"{CommonRoutes.BaseCryptoEndpoint}/{CommonRoutes.GroupEndpoint}/addresses")]
public async Task<IActionResult> GetGroupAddresses(TrackedSourceContext trackedSourceContext)
{
var group = (GroupTrackedSource)trackedSourceContext.TrackedSource;
await using var conn = await ConnectionFactory.CreateConnection();
return Ok((await conn.QueryAsync<string>("SELECT s.addr FROM wallets_scripts JOIN scripts s USING (code, script) WHERE code=@code AND wallet_id=@wid", new
{
code = trackedSourceContext.Network.CryptoCode,
wid = PostgresRepository.GetWalletKey(group).wid
})).ToArray());
}

private string GetWid(GroupChild c)
{
if (c?.TrackedSource is null)
return null;
var net = c.CryptoCode is null ? null : NetworkProvider.GetFromCryptoCode(c.CryptoCode);
if (c.TrackedSource.StartsWith("ADDRESS:") || c.TrackedSource.StartsWith("DERIVATIONSCHEME:") && c.CryptoCode is null)
throw new NBXplorerException(new NBXplorerError(400, "invalid-group-child", "ADDRESS: and DERIVATIONSCHEME: tracked sources must also include a cryptoCode parameter"));
if (!TrackedSource.TryParse(c.TrackedSource, out var ts, net))
return null;
throw new NBXplorerException(new NBXplorerError(400, "invalid-group-child", "Invalid tracked source format"));
return PostgresRepository.GetWalletKey(ts, net)?.wid;
}

Expand Down
12 changes: 7 additions & 5 deletions NBXplorer/Controllers/MainController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
using NBXplorer.Backends;
using NBitcoin.Scripting;
using System.Globalization;
using NBXplorer.Backends.Postgres;

namespace NBXplorer.Controllers
{
Expand Down Expand Up @@ -757,15 +756,18 @@ public async Task<IActionResult> Rescan(TrackedSourceContext trackedSourceContex
}
}

[HttpPost]
[Route($"{CommonRoutes.DerivationEndpoint}/metadata/{{key}}")]
[HttpPost($"{CommonRoutes.DerivationEndpoint}/metadata/{{key}}")]
[HttpPost($"{CommonRoutes.AddressEndpoint}/metadata/{{key}}")]
[HttpPost($"{CommonRoutes.GroupEndpoint}/metadata/{{key}}")]
public async Task<IActionResult> SetMetadata(TrackedSourceContext trackedSourceContext, string key, [FromBody] JToken value = null)
{
await trackedSourceContext.Repository.SaveMetadata(trackedSourceContext.TrackedSource, key, value);
return Ok();
}
[HttpGet]
[Route($"{CommonRoutes.DerivationEndpoint}/metadata/{{key}}")]

[HttpGet($"{CommonRoutes.DerivationEndpoint}/metadata/{{key}}")]
[HttpGet($"{CommonRoutes.AddressEndpoint}/metadata/{{key}}")]
[HttpGet($"{CommonRoutes.GroupEndpoint}/metadata/{{key}}")]
public async Task<IActionResult> GetMetadata(TrackedSourceContext trackedSourceContext, string key)
{
var result = await trackedSourceContext.Repository.GetMetadata<JToken>(trackedSourceContext.TrackedSource, key);
Expand Down
23 changes: 15 additions & 8 deletions NBXplorer/TrackedTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,33 @@ public TrackedTransaction(TrackedTransactionKey key, TrackedSource trackedSource
Transaction = transaction;
transaction.PrecomputeHash(false, true);
KnownKeyPathMapping = knownScriptMapping;

KnownKeyPathMappingUpdated();
if ((TrackedSource as IDestination)?.ScriptPubKey is Script s)
OwnedScripts.Add(s);
foreach (var ss in knownScriptMapping.Keys)
OwnedScripts.Add(ss);
OwnedScriptsUpdated();
}

internal void KnownKeyPathMappingUpdated()
internal void OwnedScriptsUpdated()
{
if (Transaction == null)
return;
var scriptPubKey = (TrackedSource as IDestination)?.ScriptPubKey;
ReceivedCoins.Clear();
SpentOutpoints.Clear();
for (int i = 0; i < Transaction.Outputs.Count; i++)
{
var output = Transaction.Outputs[i];
if (KnownKeyPathMapping.ContainsKey(output.ScriptPubKey) || scriptPubKey == output.ScriptPubKey)
if (OwnedScripts.Contains(output.ScriptPubKey))
ReceivedCoins.Add(new Coin(new OutPoint(Key.TxId, i), output));
}

if (!Transaction.IsCoinBase)
SpentOutpoints.AddInputs(Transaction);
}

public HashSet<Script> OwnedScripts { get; } = new HashSet<Script>();
public Dictionary<Script, KeyPath> KnownKeyPathMapping { get; } = new Dictionary<Script, KeyPath>();
public Dictionary<Script, KeyPathInformation> KnownKeyPathInformation { get; } = new Dictionary<Script, KeyPathInformation>();
public Dictionary<Script, BitcoinAddress> KnownAddresses { get; } = new Dictionary<Script, BitcoinAddress>();
public HashSet<ICoin> ReceivedCoins { get; protected set; } = new HashSet<ICoin>(CoinOutpointEqualityComparer.Instance);

public class SpentOutpointsSet : HashSet<(OutPoint Outpoint, int InputIndex)>
Expand Down Expand Up @@ -150,7 +155,7 @@ public IEnumerable<MatchedOutput> GetReceivedOutputs()
.Select(o => (Index: (int)o.Outpoint.N,
Output: o,
KeyPath: KnownKeyPathMapping.TryGet(o.TxOut.ScriptPubKey),
Address: KnownKeyPathInformation.TryGet(o.TxOut.ScriptPubKey)?.Address))
Address: KnownAddresses.TryGet(o.TxOut.ScriptPubKey)))
.Where(o => o.KeyPath != null || o.Output.TxOut.ScriptPubKey == (TrackedSource as IDestination)?.ScriptPubKey || TrackedSource is GroupTrackedSource)
.Select(o => new MatchedOutput()
{
Expand All @@ -171,7 +176,9 @@ internal void AddKnownKeyPathInformation(KeyPathInformation keyInfo)
{
if (keyInfo.KeyPath != null)
this.KnownKeyPathMapping.TryAdd(keyInfo.ScriptPubKey, keyInfo.KeyPath);
this.KnownKeyPathInformation.TryAdd(keyInfo.ScriptPubKey, keyInfo);
if (keyInfo.Address != null)
this.KnownAddresses.TryAdd(keyInfo.ScriptPubKey, keyInfo.Address);
this.OwnedScripts.Add(keyInfo.ScriptPubKey);
}
}

Expand Down
Loading

0 comments on commit d45f7eb

Please sign in to comment.