@@ -1458,3 +1458,297 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
1458
1458
1459
1459
return response;
1460
1460
}
1461
+
1462
+ static UniValue ProcessDescriptorImport (CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
1463
+ {
1464
+ UniValue warnings (UniValue::VARR);
1465
+ UniValue result (UniValue::VOBJ);
1466
+
1467
+ try {
1468
+ if (!data.exists (" desc" )) {
1469
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Descriptor not found." );
1470
+ }
1471
+
1472
+ const std::string& descriptor = data[" desc" ].get_str ();
1473
+ const bool active = data.exists (" active" ) ? data[" active" ].get_bool () : false ;
1474
+ const bool internal = data.exists (" internal" ) ? data[" internal" ].get_bool () : false ;
1475
+ const std::string& label = data.exists (" label" ) ? data[" label" ].get_str () : " " ;
1476
+
1477
+ // Parse descriptor string
1478
+ FlatSigningProvider keys;
1479
+ std::string error;
1480
+ auto parsed_desc = Parse (descriptor, keys, error, /* require_checksum = */ true );
1481
+ if (!parsed_desc) {
1482
+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, error);
1483
+ }
1484
+
1485
+ // Range check
1486
+ int64_t range_start = 0 , range_end = 1 , next_index = 0 ;
1487
+ if (!parsed_desc->IsRange () && data.exists (" range" )) {
1488
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Range should not be specified for an un-ranged descriptor" );
1489
+ } else if (parsed_desc->IsRange ()) {
1490
+ if (data.exists (" range" )) {
1491
+ auto range = ParseDescriptorRange (data[" range" ]);
1492
+ range_start = range.first ;
1493
+ range_end = range.second + 1 ; // Specified range end is inclusive, but we need range end as exclusive
1494
+ } else {
1495
+ warnings.push_back (" Range not given, using default keypool range" );
1496
+ range_start = 0 ;
1497
+ range_end = gArgs .GetArg (" -keypool" , DEFAULT_KEYPOOL_SIZE);
1498
+ }
1499
+ next_index = range_start;
1500
+
1501
+ if (data.exists (" next_index" )) {
1502
+ next_index = data[" next_index" ].get_int64 ();
1503
+ // bound checks
1504
+ if (next_index < range_start || next_index >= range_end) {
1505
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " next_index is out of range" );
1506
+ }
1507
+ }
1508
+ }
1509
+
1510
+ // Active descriptors must be ranged
1511
+ if (active && !parsed_desc->IsRange ()) {
1512
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Active descriptors must be ranged" );
1513
+ }
1514
+
1515
+ // Ranged descriptors should not have a label
1516
+ if (data.exists (" range" ) && data.exists (" label" )) {
1517
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Ranged descriptors should not have a label" );
1518
+ }
1519
+
1520
+ // Internal addresses should not have a label either
1521
+ if (internal && data.exists (" label" )) {
1522
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Internal addresses should not have a label" );
1523
+ }
1524
+
1525
+ // Combo descriptor check
1526
+ if (active && !parsed_desc->IsSingleType ()) {
1527
+ throw JSONRPCError (RPC_WALLET_ERROR, " Combo descriptors cannot be set to active" );
1528
+ }
1529
+
1530
+ // If the wallet disabled private keys, abort if private keys exist
1531
+ if (pwallet->IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys .empty ()) {
1532
+ throw JSONRPCError (RPC_WALLET_ERROR, " Cannot import private keys to a wallet with private keys disabled" );
1533
+ }
1534
+
1535
+ // Need to ExpandPrivate to check if private keys are available for all pubkeys
1536
+ FlatSigningProvider expand_keys;
1537
+ std::vector<CScript> scripts;
1538
+ parsed_desc->Expand (0 , keys, scripts, expand_keys);
1539
+ parsed_desc->ExpandPrivate (0 , keys, expand_keys);
1540
+
1541
+ // Check if all private keys are provided
1542
+ bool have_all_privkeys = !expand_keys.keys .empty ();
1543
+ for (const auto & entry : expand_keys.origins ) {
1544
+ const CKeyID& key_id = entry.first ;
1545
+ CKey key;
1546
+ if (!expand_keys.GetKey (key_id, key)) {
1547
+ have_all_privkeys = false ;
1548
+ break ;
1549
+ }
1550
+ }
1551
+
1552
+ // If private keys are enabled, check some things.
1553
+ if (!pwallet->IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1554
+ if (keys.keys .empty ()) {
1555
+ throw JSONRPCError (RPC_WALLET_ERROR, " Cannot import descriptor without private keys to a wallet with private keys enabled" );
1556
+ }
1557
+ if (!have_all_privkeys) {
1558
+ warnings.push_back (" Not all private keys provided. Some wallet functionality may return unexpected errors" );
1559
+ }
1560
+ }
1561
+
1562
+ WalletDescriptor w_desc (std::move (parsed_desc), timestamp, range_start, range_end, next_index);
1563
+
1564
+ // Check if the wallet already contains the descriptor
1565
+ auto existing_spk_manager = pwallet->GetDescriptorScriptPubKeyMan (w_desc);
1566
+ if (existing_spk_manager) {
1567
+ LOCK (existing_spk_manager->cs_desc_man );
1568
+ if (range_start > existing_spk_manager->GetWalletDescriptor ().range_start ) {
1569
+ throw JSONRPCError (RPC_INVALID_PARAMS, strprintf (" range_start can only decrease; current range = [%d,%d]" , existing_spk_manager->GetWalletDescriptor ().range_start , existing_spk_manager->GetWalletDescriptor ().range_end ));
1570
+ }
1571
+ }
1572
+
1573
+ // Add descriptor to the wallet
1574
+ auto spk_manager = pwallet->AddWalletDescriptor (w_desc, keys, label);
1575
+ if (spk_manager == nullptr ) {
1576
+ throw JSONRPCError (RPC_WALLET_ERROR, strprintf (" Could not add descriptor '%s'" , descriptor));
1577
+ }
1578
+
1579
+ // Set descriptor as active if necessary
1580
+ if (active) {
1581
+ if (!w_desc.descriptor ->GetOutputType ()) {
1582
+ warnings.push_back (" Unknown output type, cannot set descriptor to active." );
1583
+ } else {
1584
+ pwallet->SetActiveScriptPubKeyMan (spk_manager->GetID (), *w_desc.descriptor ->GetOutputType (), internal);
1585
+ }
1586
+ }
1587
+
1588
+ result.pushKV (" success" , UniValue (true ));
1589
+ } catch (const UniValue& e) {
1590
+ result.pushKV (" success" , UniValue (false ));
1591
+ result.pushKV (" error" , e);
1592
+ } catch (...) {
1593
+ result.pushKV (" success" , UniValue (false ));
1594
+
1595
+ result.pushKV (" error" , JSONRPCError (RPC_MISC_ERROR, " Missing required fields" ));
1596
+ }
1597
+ if (warnings.size ()) result.pushKV (" warnings" , warnings);
1598
+ return result;
1599
+ }
1600
+
1601
+ UniValue importdescriptors (const JSONRPCRequest& main_request) {
1602
+ // Acquire the wallet
1603
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest (main_request);
1604
+ CWallet* const pwallet = wallet.get ();
1605
+ if (!EnsureWalletIsAvailable (pwallet, main_request.fHelp )) {
1606
+ return NullUniValue;
1607
+ }
1608
+
1609
+ RPCHelpMan{" importdescriptors" ,
1610
+ " \n Import descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n "
1611
+ " \n Note: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n "
1612
+ " may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n " ,
1613
+ {
1614
+ {" requests" , RPCArg::Type::ARR, RPCArg::Optional::NO, " Data to be imported" ,
1615
+ {
1616
+ {" " , RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, " " ,
1617
+ {
1618
+ {" desc" , RPCArg::Type::STR, RPCArg::Optional::NO, " Descriptor to import." },
1619
+ {" active" , RPCArg::Type::BOOL, /* default */ " false" , " Set this descriptor to be the active descriptor for the corresponding output type/externality" },
1620
+ {" range" , RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, " If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import" },
1621
+ {" next_index" , RPCArg::Type::NUM, RPCArg::Optional::OMITTED, " If a ranged descriptor is set to active, this specifies the next index to generate addresses from" },
1622
+ {" timestamp" , RPCArg::Type::NUM, RPCArg::Optional::NO, " Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + " \n "
1623
+ " Use the string \" now\" to substitute the current synced blockchain time.\n "
1624
+ " \" now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n "
1625
+ " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n "
1626
+ " of all descriptors being imported will be scanned." ,
1627
+ /* oneline_description */ " " , {" timestamp | \" now\" " , " integer / string" }
1628
+ },
1629
+ {" internal" , RPCArg::Type::BOOL, /* default */ " false" , " Whether matching outputs should be treated as not incoming payments (e.g. change)" },
1630
+ {" label" , RPCArg::Type::STR, /* default */ " ''" , " Label to assign to the address, only allowed with internal=false" },
1631
+ },
1632
+ },
1633
+ },
1634
+ " \" requests\" " },
1635
+ },
1636
+ RPCResult{
1637
+ RPCResult::Type::ARR, " " , " Response is an array with the same size as the input that has the execution result" ,
1638
+ {
1639
+ {RPCResult::Type::OBJ, " " , " " ,
1640
+ {
1641
+ {RPCResult::Type::BOOL, " success" , " " },
1642
+ {RPCResult::Type::ARR, " warnings" , /* optional */ true , " " ,
1643
+ {
1644
+ {RPCResult::Type::STR, " " , " " },
1645
+ }},
1646
+ {RPCResult::Type::OBJ, " error" , /* optional */ true , " " ,
1647
+ {
1648
+ {RPCResult::Type::ELISION, " " , " JSONRPC error" },
1649
+ }},
1650
+ }},
1651
+ }
1652
+ },
1653
+ RPCExamples{
1654
+ HelpExampleCli (" importdescriptors" , " '[{ \" desc\" : \" <my descriptor>\" , \" timestamp\" :1455191478, \" internal\" : true }, "
1655
+ " { \" desc\" : \" <my desccriptor 2>\" , \" label\" : \" example 2\" , \" timestamp\" : 1455191480 }]'" ) +
1656
+ HelpExampleCli (" importdescriptors" , " '[{ \" desc\" : \" <my descriptor>\" , \" timestamp\" :1455191478, \" active\" : true, \" range\" : [0,100], \" label\" : \" <my bech32 wallet>\" }]'" )
1657
+ },
1658
+ }.Check (main_request);
1659
+
1660
+ // Make sure wallet is a descriptor wallet
1661
+ if (!pwallet->IsWalletFlagSet (WALLET_FLAG_DESCRIPTORS)) {
1662
+ throw JSONRPCError (RPC_WALLET_ERROR, " importdescriptors is not available for non-descriptor wallets" );
1663
+ }
1664
+
1665
+ RPCTypeCheck (main_request.params , {UniValue::VARR, UniValue::VOBJ});
1666
+
1667
+ WalletRescanReserver reserver (*pwallet);
1668
+ if (!reserver.reserve ()) {
1669
+ throw JSONRPCError (RPC_WALLET_ERROR, " Wallet is currently rescanning. Abort existing rescan or wait." );
1670
+ }
1671
+
1672
+ const UniValue& requests = main_request.params [0 ];
1673
+ const int64_t minimum_timestamp = 1 ;
1674
+ int64_t now = 0 ;
1675
+ int64_t lowest_timestamp = 0 ;
1676
+ bool rescan = false ;
1677
+ UniValue response (UniValue::VARR);
1678
+ {
1679
+ auto locked_chain = pwallet->chain ().lock ();
1680
+ LOCK (pwallet->cs_wallet );
1681
+ EnsureWalletIsUnlocked (pwallet);
1682
+
1683
+ CHECK_NONFATAL (pwallet->chain ().findBlock (pwallet->GetLastBlockHash (), FoundBlock ().time (lowest_timestamp).mtpTime (now)));
1684
+
1685
+ // Get all timestamps and extract the lowest timestamp
1686
+ for (const UniValue& request : requests.getValues ()) {
1687
+ // This throws an error if "timestamp" doesn't exist
1688
+ const int64_t timestamp = std::max (GetImportTimestamp (request, now), minimum_timestamp);
1689
+ const UniValue result = ProcessDescriptorImport (pwallet, request, timestamp);
1690
+ response.push_back (result);
1691
+
1692
+ if (lowest_timestamp > timestamp ) {
1693
+ lowest_timestamp = timestamp;
1694
+ }
1695
+
1696
+ // If we know the chain tip, and at least one request was successful then allow rescan
1697
+ if (!rescan && result[" success" ].get_bool ()) {
1698
+ rescan = true ;
1699
+ }
1700
+ }
1701
+ pwallet->ConnectScriptPubKeyManNotifiers ();
1702
+ }
1703
+
1704
+ // Rescan the blockchain using the lowest timestamp
1705
+ if (rescan) {
1706
+ int64_t scanned_time = pwallet->RescanFromTime (lowest_timestamp, reserver, true /* update */ );
1707
+ {
1708
+ auto locked_chain = pwallet->chain ().lock ();
1709
+ LOCK (pwallet->cs_wallet );
1710
+ pwallet->ReacceptWalletTransactions ();
1711
+ }
1712
+
1713
+ if (pwallet->IsAbortingRescan ()) {
1714
+ throw JSONRPCError (RPC_MISC_ERROR, " Rescan aborted by user." );
1715
+ }
1716
+
1717
+ if (scanned_time > lowest_timestamp) {
1718
+ std::vector<UniValue> results = response.getValues ();
1719
+ response.clear ();
1720
+ response.setArray ();
1721
+
1722
+ // Compose the response
1723
+ for (unsigned int i = 0 ; i < requests.size (); ++i) {
1724
+ const UniValue& request = requests.getValues ().at (i);
1725
+
1726
+ // If the descriptor timestamp is within the successfully scanned
1727
+ // range, or if the import result already has an error set, let
1728
+ // the result stand unmodified. Otherwise replace the result
1729
+ // with an error message.
1730
+ if (scanned_time <= GetImportTimestamp (request, now) || results.at (i).exists (" error" )) {
1731
+ response.push_back (results.at (i));
1732
+ } else {
1733
+ UniValue result = UniValue (UniValue::VOBJ);
1734
+ result.pushKV (" success" , UniValue (false ));
1735
+ result.pushKV (
1736
+ " error" ,
1737
+ JSONRPCError (
1738
+ RPC_MISC_ERROR,
1739
+ strprintf (" Rescan failed for descriptor with timestamp %d. There was an error reading a "
1740
+ " block from time %d, which is after or within %d seconds of key creation, and "
1741
+ " could contain transactions pertaining to the desc. As a result, transactions "
1742
+ " and coins using this desc may not appear in the wallet. This error could be "
1743
+ " caused by pruning or data corruption (see bitcoind log for details) and could "
1744
+ " be dealt with by downloading and rescanning the relevant blocks (see -reindex "
1745
+ " and -rescan options)." ,
1746
+ GetImportTimestamp (request, now), scanned_time - TIMESTAMP_WINDOW - 1 , TIMESTAMP_WINDOW)));
1747
+ response.push_back (std::move (result));
1748
+ }
1749
+ }
1750
+ }
1751
+ }
1752
+
1753
+ return response;
1754
+ }
0 commit comments