18
18
using System . Text . RegularExpressions ;
19
19
using Npgsql ;
20
20
using static NBXplorer . Backend . DbConnectionHelper ;
21
+ using Microsoft . AspNetCore . DataProtection . KeyManagement ;
21
22
22
23
23
24
namespace NBXplorer . Backend
@@ -368,32 +369,94 @@ public async Task<MultiValueDictionary<Script, KeyPathInformation>> GetKeyInform
368
369
await using var connection = await connectionFactory . CreateConnection ( ) ;
369
370
return await GetKeyInformations ( connection , scripts ) ;
370
371
}
371
- async Task < MultiValueDictionary < Script , KeyPathInformation > > GetKeyInformations ( DbConnection connection , IList < Script > scripts )
372
+ abstract class GetKeyInformationsQuery
372
373
{
373
- scripts = scripts . Distinct ( ) . ToArray ( ) ;
374
- MultiValueDictionary < Script , KeyPathInformation > result = new MultiValueDictionary < Script , KeyPathInformation > ( ) ;
375
- foreach ( var s in scripts )
376
- result . AddRange ( s , Array . Empty < KeyPathInformation > ( ) ) ;
377
- var command = connection . CreateCommand ( ) ;
378
- if ( scripts . Count == 0 )
374
+ public static GetKeyInformationsQuery ByScripts ( IList < Script > scripts ) => new ByScriptsQuery ( scripts ) ;
375
+ public static GetKeyInformationsQuery ByUnused ( DerivationStrategyBase strategy , DerivationFeature derivationFeature , int skip , NBXplorerNetwork network ) => new ByUnusedQuery ( strategy , derivationFeature , skip , network ) ;
376
+ public abstract string GetScriptsQuery ( ) ;
377
+ public virtual string GetKeyPathInfoPredicate ( ) => string . Empty ;
378
+ public abstract void AddParameters ( DynamicParameters parameters ) ;
379
+ public virtual bool IsEmpty => false ;
380
+ public virtual MultiValueDictionary < Script , KeyPathInformation > CreateResult ( ) => new ( ) ;
381
+ class ByUnusedQuery : GetKeyInformationsQuery
382
+ {
383
+ private int n ;
384
+ private readonly string descriptorId ;
385
+
386
+ public ByUnusedQuery ( DerivationStrategyBase strategy , DerivationFeature derivationFeature , int n , NBXplorerNetwork network )
387
+ {
388
+ this . n = n ;
389
+ descriptorId = DBUtils . nbxv1_get_descriptor_id ( network . CryptoCode , strategy . ToString ( ) , derivationFeature . ToString ( ) ) ;
390
+ }
391
+
392
+ public override void AddParameters ( DynamicParameters parameters )
393
+ {
394
+ parameters . Add ( "skip" , n ) ;
395
+ parameters . Add ( "descriptor" , descriptorId ) ;
396
+ }
397
+ public override string GetScriptsQuery ( )
398
+ {
399
+ return $@ "
400
+ (SELECT script FROM descriptors_scripts_unused
401
+ WHERE code=@code AND descriptor=@descriptor
402
+ ORDER BY idx
403
+ LIMIT 1 OFFSET @skip) AS r (script)
404
+ " ;
405
+ }
406
+ public override string GetKeyPathInfoPredicate ( ) => "AND ki.descriptor=@descriptor" ;
407
+ }
408
+ class ByScriptsQuery : GetKeyInformationsQuery
409
+ {
410
+ private readonly Script [ ] scripts ;
411
+
412
+ public override bool IsEmpty => scripts . Length is 0 ;
413
+
414
+ public ByScriptsQuery ( IList < Script > scripts )
415
+ {
416
+ this . scripts = scripts . Distinct ( ) . ToArray ( ) ;
417
+ }
418
+ public override string GetScriptsQuery ( ) => "unnest(@records) AS r (script)" ;
419
+ public override void AddParameters ( DynamicParameters parameters )
420
+ {
421
+ parameters . Add ( "records" , scripts . Select ( s => s . ToHex ( ) ) . ToArray ( ) ) ;
422
+ }
423
+ public override MultiValueDictionary < Script , KeyPathInformation > CreateResult ( )
424
+ {
425
+ MultiValueDictionary < Script , KeyPathInformation > result = new MultiValueDictionary < Script , KeyPathInformation > ( ) ;
426
+ foreach ( var s in scripts )
427
+ result . AddRange ( s , Array . Empty < KeyPathInformation > ( ) ) ;
428
+ return result ;
429
+ }
430
+ }
431
+ }
432
+ Task < MultiValueDictionary < Script , KeyPathInformation > > GetKeyInformations ( DbConnection connection , IList < Script > scripts )
433
+ => GetKeyInformations ( connection , GetKeyInformationsQuery . ByScripts ( scripts ) ) ;
434
+ async Task < MultiValueDictionary < Script , KeyPathInformation > > GetKeyInformations ( DbConnection connection , GetKeyInformationsQuery query )
435
+ {
436
+ var result = query . CreateResult ( ) ;
437
+ if ( query . IsEmpty )
379
438
return result ;
439
+
440
+ DynamicParameters parameters = new DynamicParameters ( ) ;
441
+ parameters . Add ( "code" , Network . CryptoCode ) ;
442
+ query . AddParameters ( parameters ) ;
380
443
string additionalColumn = Network . IsElement ? ", ts.blinded_addr" : "" ;
381
444
var rows = await connection . QueryAsync ( $@ "
382
- SELECT ts.code, ts.script, ts.addr, ts.derivation, ts.keypath, ts.redeem{ additionalColumn } ,
445
+ SELECT ts.code, ts.script, ts.addr, ts.derivation, ts.keypath, ts.idx, ts.feature, ts. redeem{ additionalColumn } ,
383
446
ts.wallet_id,
384
447
w.metadata AS wallet_metadata
385
- FROM unnest(@records) AS r (script) ,
448
+ FROM { query . GetScriptsQuery ( ) } ,
386
449
LATERAL (
387
450
SELECT code, script, wallet_id, addr, descriptor_metadata->>'derivation' derivation,
388
- keypath, descriptors_scripts_metadata->>'redeem' redeem,
451
+ keypath, ki.idx, descriptors_scripts_metadata->>'redeem' redeem,
389
452
descriptors_scripts_metadata->>'blindedAddress' blinded_addr,
390
453
descriptors_scripts_metadata->>'blindingKey' blindingKey,
391
- descriptor_metadata->>'descriptor' descriptor
392
- FROM nbxv1_keypath_info ki
393
- WHERE ki.code=@code AND ki.script=r.script
454
+ descriptor_metadata->>'descriptor' descriptor,
455
+ descriptor_metadata->>'feature' feature
456
+ FROM nbxv1_keypath_info ki
457
+ WHERE ki.code=@code AND ki.script=r.script { query . GetKeyPathInfoPredicate ( ) }
394
458
) ts
395
- JOIN wallets w USING(wallet_id)" ,
396
- new { code = Network . CryptoCode , records = scripts . Select ( s => s . ToHex ( ) ) . ToArray ( ) } ) ;
459
+ JOIN wallets w USING(wallet_id)" , parameters ) ;
397
460
foreach ( var r in rows )
398
461
{
399
462
// This might be the case for a derivation added by a different indexer
@@ -425,7 +488,12 @@ JOIN wallets w USING(wallet_id)",
425
488
ki . KeyPath = keypath ;
426
489
ki . ScriptPubKey = script ;
427
490
ki . TrackedSource = trackedSource ;
428
- ki . Feature = keypath is null ? DerivationFeature . Deposit : KeyPathTemplates . GetDerivationFeature ( keypath ) ;
491
+ ki . Feature = DerivationFeature . Deposit ;
492
+ if ( keypath is not null )
493
+ {
494
+ ki . Feature = Enum . Parse < DerivationFeature > ( r . feature , true ) ;
495
+ ki . Index = ( int ) r . idx ;
496
+ }
429
497
ki . Redeem = redeem is null ? null : Script . FromHex ( redeem ) ;
430
498
result . Add ( script , ki ) ;
431
499
}
@@ -783,14 +851,8 @@ public async Task<KeyPathInformation> GetUnused(DerivationStrategyBase strategy,
783
851
{
784
852
await using var helper = await connectionFactory . CreateConnectionHelper ( Network ) ;
785
853
var connection = helper . Connection ;
786
- var key = GetDescriptorKey ( strategy , derivationFeature ) ;
787
- string additionalColumn = Network . IsElement ? ", ds_metadata->>'blindedAddress' blinded_addr" : string . Empty ;
788
854
retry :
789
- var unused = await connection . QueryFirstOrDefaultAsync (
790
- $ "SELECT script, addr, nbxv1_get_keypath(d_metadata, idx) keypath, ds_metadata->>'redeem' redeem { additionalColumn } FROM descriptors_scripts_unused " +
791
- "WHERE code=@code AND descriptor=@descriptor " +
792
- "ORDER BY idx " +
793
- "LIMIT 1 OFFSET @skip" , new { key . code , key . descriptor , skip = n } ) ;
855
+ var unused = ( await GetKeyInformations ( connection , GetKeyInformationsQuery . ByUnused ( strategy , derivationFeature , n , Network ) ) ) . FirstOrDefault ( ) . Value ? . FirstOrDefault ( ) ;
794
856
if ( unused is null )
795
857
{
796
858
// If we don't find unused address, then either:
@@ -809,23 +871,12 @@ public async Task<KeyPathInformation> GetUnused(DerivationStrategyBase strategy,
809
871
}
810
872
if ( reserve )
811
873
{
812
- var updated = await connection . ExecuteAsync ( "UPDATE descriptors_scripts SET used='t' WHERE code=@code AND script=@script AND descriptor=@descriptor AND used='f'" , new { key . code , unused . script , key . descriptor } ) ;
874
+ var updated = await connection . ExecuteAsync ( "UPDATE descriptors_scripts SET used='t' WHERE code=@code AND script=@script AND descriptor=@descriptor AND used='f'" , new { code = Network . CryptoCode , script = unused . ScriptPubKey . ToHex ( ) , descriptor = GetDescriptorKey ( strategy , derivationFeature ) . descriptor } ) ;
813
875
if ( updated == 0 )
814
876
goto retry ;
815
877
}
816
- var keypath = KeyPath . Parse ( unused . keypath ) ;
817
- var keyInfo = new KeyPathInformation ( )
818
- {
819
- Address = GetAddress ( unused ) ,
820
- DerivationStrategy = strategy ,
821
- KeyPath = keypath ,
822
- ScriptPubKey = Script . FromHex ( unused . script ) ,
823
- TrackedSource = new DerivationSchemeTrackedSource ( strategy ) ,
824
- Feature = KeyPathTemplates . GetDerivationFeature ( keypath ) ,
825
- Redeem = unused . redeem is string s ? Script . FromHex ( s ) : null
826
- } ;
827
- await ImportAddressToRPC ( helper , keyInfo . TrackedSource , keyInfo . Address , keyInfo . KeyPath ) ;
828
- return keyInfo ;
878
+ await ImportAddressToRPC ( helper , unused . TrackedSource , unused . Address , unused . KeyPath ) ;
879
+ return unused ;
829
880
}
830
881
831
882
record SingleAddressInsert ( string code , string script , string address , string walletid ) ;
@@ -863,7 +914,7 @@ internal async Task SaveKeyInformations(DbConnection connection, KeyPathInformat
863
914
{
864
915
descriptorInsert . Add ( new DescriptorScriptInsert (
865
916
descriptorKey . descriptor ,
866
- ki . GetIndex ( KeyPathTemplates ) ,
917
+ ki . Index . Value ,
867
918
ki . ScriptPubKey . ToHex ( ) ,
868
919
metadata ? . ToString ( Formatting . None ) ,
869
920
addr . ToString ( ) ,
0 commit comments