@@ -1458,3 +1458,297 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
14581458
14591459 return response;
14601460}
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