transactions.cpp
1 // Copyright (c) 2011-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <core_io.h> 6 #include <key_io.h> 7 #include <policy/rbf.h> 8 #include <primitives/transaction_identifier.h> 9 #include <rpc/util.h> 10 #include <rpc/rawtransaction_util.h> 11 #include <rpc/blockchain.h> 12 #include <util/vector.h> 13 #include <wallet/receive.h> 14 #include <wallet/rpc/util.h> 15 #include <wallet/wallet.h> 16 17 using interfaces::FoundBlock; 18 19 namespace wallet { 20 static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry) 21 EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) 22 { 23 interfaces::Chain& chain = wallet.chain(); 24 int confirms = wallet.GetTxDepthInMainChain(wtx); 25 entry.pushKV("confirmations", confirms); 26 if (wtx.IsCoinBase()) 27 entry.pushKV("generated", true); 28 if (auto* conf = wtx.state<TxStateConfirmed>()) 29 { 30 entry.pushKV("blockhash", conf->confirmed_block_hash.GetHex()); 31 entry.pushKV("blockheight", conf->confirmed_block_height); 32 entry.pushKV("blockindex", conf->position_in_block); 33 int64_t block_time; 34 CHECK_NONFATAL(chain.findBlock(conf->confirmed_block_hash, FoundBlock().time(block_time))); 35 entry.pushKV("blocktime", block_time); 36 } else { 37 entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx)); 38 } 39 entry.pushKV("txid", wtx.GetHash().GetHex()); 40 entry.pushKV("wtxid", wtx.GetWitnessHash().GetHex()); 41 UniValue conflicts(UniValue::VARR); 42 for (const Txid& conflict : wallet.GetTxConflicts(wtx)) 43 conflicts.push_back(conflict.GetHex()); 44 entry.pushKV("walletconflicts", std::move(conflicts)); 45 UniValue mempool_conflicts(UniValue::VARR); 46 for (const Txid& mempool_conflict : wtx.mempool_conflicts) 47 mempool_conflicts.push_back(mempool_conflict.GetHex()); 48 entry.pushKV("mempoolconflicts", std::move(mempool_conflicts)); 49 entry.pushKV("time", wtx.GetTxTime()); 50 entry.pushKV("timereceived", int64_t{wtx.nTimeReceived}); 51 52 // Add opt-in RBF status 53 std::string rbfStatus = "no"; 54 if (confirms <= 0) { 55 RBFTransactionState rbfState = chain.isRBFOptIn(*wtx.tx); 56 if (rbfState == RBFTransactionState::UNKNOWN) 57 rbfStatus = "unknown"; 58 else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) 59 rbfStatus = "yes"; 60 } 61 entry.pushKV("bip125-replaceable", rbfStatus); 62 63 for (const std::pair<const std::string, std::string>& item : wtx.mapValue) 64 entry.pushKV(item.first, item.second); 65 } 66 67 struct tallyitem 68 { 69 CAmount nAmount{0}; 70 int nConf{std::numeric_limits<int>::max()}; 71 std::vector<Txid> txids; 72 tallyitem() = default; 73 }; 74 75 static UniValue ListReceived(const CWallet& wallet, const UniValue& params, const bool by_label, const bool include_immature_coinbase) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) 76 { 77 // Minimum confirmations 78 int nMinDepth = 1; 79 if (!params[0].isNull()) 80 nMinDepth = params[0].getInt<int>(); 81 82 // Whether to include empty labels 83 bool fIncludeEmpty = false; 84 if (!params[1].isNull()) 85 fIncludeEmpty = params[1].get_bool(); 86 87 std::optional<CTxDestination> filtered_address{std::nullopt}; 88 if (!by_label && !params[3].isNull() && !params[3].get_str().empty()) { 89 if (!IsValidDestinationString(params[3].get_str())) { 90 throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid"); 91 } 92 filtered_address = DecodeDestination(params[3].get_str()); 93 } 94 95 // Tally 96 std::map<CTxDestination, tallyitem> mapTally; 97 for (const auto& [_, wtx] : wallet.mapWallet) { 98 99 int nDepth = wallet.GetTxDepthInMainChain(wtx); 100 if (nDepth < nMinDepth) 101 continue; 102 103 // Coinbase with less than 1 confirmation is no longer in the main chain 104 if ((wtx.IsCoinBase() && (nDepth < 1)) 105 || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) { 106 continue; 107 } 108 109 for (const CTxOut& txout : wtx.tx->vout) { 110 CTxDestination address; 111 if (!ExtractDestination(txout.scriptPubKey, address)) 112 continue; 113 114 if (filtered_address && !(filtered_address == address)) { 115 continue; 116 } 117 118 if (!wallet.IsMine(address)) 119 continue; 120 121 tallyitem& item = mapTally[address]; 122 item.nAmount += txout.nValue; 123 item.nConf = std::min(item.nConf, nDepth); 124 item.txids.push_back(wtx.GetHash()); 125 } 126 } 127 128 // Reply 129 UniValue ret(UniValue::VARR); 130 std::map<std::string, tallyitem> label_tally; 131 132 const auto& func = [&](const CTxDestination& address, const std::string& label, bool is_change, const std::optional<AddressPurpose>& purpose) { 133 if (is_change) return; // no change addresses 134 135 auto it = mapTally.find(address); 136 if (it == mapTally.end() && !fIncludeEmpty) 137 return; 138 139 CAmount nAmount = 0; 140 int nConf = std::numeric_limits<int>::max(); 141 if (it != mapTally.end()) { 142 nAmount = (*it).second.nAmount; 143 nConf = (*it).second.nConf; 144 } 145 146 if (by_label) { 147 tallyitem& _item = label_tally[label]; 148 _item.nAmount += nAmount; 149 _item.nConf = std::min(_item.nConf, nConf); 150 } else { 151 UniValue obj(UniValue::VOBJ); 152 obj.pushKV("address", EncodeDestination(address)); 153 obj.pushKV("amount", ValueFromAmount(nAmount)); 154 obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); 155 obj.pushKV("label", label); 156 UniValue transactions(UniValue::VARR); 157 if (it != mapTally.end()) { 158 for (const Txid& _item : (*it).second.txids) { 159 transactions.push_back(_item.GetHex()); 160 } 161 } 162 obj.pushKV("txids", std::move(transactions)); 163 ret.push_back(std::move(obj)); 164 } 165 }; 166 167 if (filtered_address) { 168 const auto& entry = wallet.FindAddressBookEntry(*filtered_address, /*allow_change=*/false); 169 if (entry) func(*filtered_address, entry->GetLabel(), entry->IsChange(), entry->purpose); 170 } else { 171 // No filtered addr, walk-through the addressbook entry 172 wallet.ForEachAddrBookEntry(func); 173 } 174 175 if (by_label) { 176 for (const auto& entry : label_tally) { 177 CAmount nAmount = entry.second.nAmount; 178 int nConf = entry.second.nConf; 179 UniValue obj(UniValue::VOBJ); 180 obj.pushKV("amount", ValueFromAmount(nAmount)); 181 obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); 182 obj.pushKV("label", entry.first); 183 ret.push_back(std::move(obj)); 184 } 185 } 186 187 return ret; 188 } 189 190 RPCHelpMan listreceivedbyaddress() 191 { 192 return RPCHelpMan{ 193 "listreceivedbyaddress", 194 "List balances by receiving address.\n", 195 { 196 {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."}, 197 {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include addresses that haven't received any payments."}, 198 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"}, 199 {"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If present and non-empty, only return information on this address."}, 200 {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."}, 201 }, 202 RPCResult{ 203 RPCResult::Type::ARR, "", "", 204 { 205 {RPCResult::Type::OBJ, "", "", 206 { 207 {RPCResult::Type::STR, "address", "The receiving address"}, 208 {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received by the address"}, 209 {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"}, 210 {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""}, 211 {RPCResult::Type::ARR, "txids", "", 212 { 213 {RPCResult::Type::STR_HEX, "txid", "The ids of transactions received with the address"}, 214 }}, 215 }}, 216 } 217 }, 218 RPCExamples{ 219 HelpExampleCli("listreceivedbyaddress", "") 220 + HelpExampleCli("listreceivedbyaddress", "6 true") 221 + HelpExampleCli("listreceivedbyaddress", "6 true true \"\" true") 222 + HelpExampleRpc("listreceivedbyaddress", "6, true, true") 223 + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"" + EXAMPLE_ADDRESS[0] + "\", true") 224 }, 225 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 226 { 227 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 228 if (!pwallet) return UniValue::VNULL; 229 230 // Make sure the results are valid at least up to the most recent block 231 // the user could have gotten from another RPC command prior to now 232 pwallet->BlockUntilSyncedToCurrentChain(); 233 234 const bool include_immature_coinbase{request.params[4].isNull() ? false : request.params[4].get_bool()}; 235 236 LOCK(pwallet->cs_wallet); 237 238 return ListReceived(*pwallet, request.params, false, include_immature_coinbase); 239 }, 240 }; 241 } 242 243 RPCHelpMan listreceivedbylabel() 244 { 245 return RPCHelpMan{ 246 "listreceivedbylabel", 247 "List received transactions by label.\n", 248 { 249 {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."}, 250 {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include labels that haven't received any payments."}, 251 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"}, 252 {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."}, 253 }, 254 RPCResult{ 255 RPCResult::Type::ARR, "", "", 256 { 257 {RPCResult::Type::OBJ, "", "", 258 { 259 {RPCResult::Type::STR_AMOUNT, "amount", "The total amount received by addresses with this label"}, 260 {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"}, 261 {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""}, 262 }}, 263 } 264 }, 265 RPCExamples{ 266 HelpExampleCli("listreceivedbylabel", "") 267 + HelpExampleCli("listreceivedbylabel", "6 true") 268 + HelpExampleRpc("listreceivedbylabel", "6, true, true, true") 269 }, 270 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 271 { 272 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 273 if (!pwallet) return UniValue::VNULL; 274 275 // Make sure the results are valid at least up to the most recent block 276 // the user could have gotten from another RPC command prior to now 277 pwallet->BlockUntilSyncedToCurrentChain(); 278 279 const bool include_immature_coinbase{request.params[3].isNull() ? false : request.params[3].get_bool()}; 280 281 LOCK(pwallet->cs_wallet); 282 283 return ListReceived(*pwallet, request.params, true, include_immature_coinbase); 284 }, 285 }; 286 } 287 288 static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) 289 { 290 if (IsValidDestination(dest)) { 291 entry.pushKV("address", EncodeDestination(dest)); 292 } 293 } 294 295 /** 296 * List transactions based on the given criteria. 297 * 298 * @param wallet The wallet. 299 * @param wtx The wallet transaction. 300 * @param nMinDepth The minimum confirmation depth. 301 * @param fLong Whether to include the JSON version of the transaction. 302 * @param ret The vector into which the result is stored. 303 * @param filter_label Optional label string to filter incoming transactions. 304 */ 305 template <class Vec> 306 static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, 307 Vec& ret, const std::optional<std::string>& filter_label, 308 bool include_change = false) 309 EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) 310 { 311 CAmount nFee; 312 std::list<COutputEntry> listReceived; 313 std::list<COutputEntry> listSent; 314 315 CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, include_change); 316 317 // Sent 318 if (!filter_label.has_value()) 319 { 320 for (const COutputEntry& s : listSent) 321 { 322 UniValue entry(UniValue::VOBJ); 323 MaybePushAddress(entry, s.destination); 324 entry.pushKV("category", "send"); 325 entry.pushKV("amount", ValueFromAmount(-s.amount)); 326 const auto* address_book_entry = wallet.FindAddressBookEntry(s.destination); 327 if (address_book_entry) { 328 entry.pushKV("label", address_book_entry->GetLabel()); 329 } 330 entry.pushKV("vout", s.vout); 331 entry.pushKV("fee", ValueFromAmount(-nFee)); 332 if (fLong) 333 WalletTxToJSON(wallet, wtx, entry); 334 entry.pushKV("abandoned", wtx.isAbandoned()); 335 ret.push_back(std::move(entry)); 336 } 337 } 338 339 // Received 340 if (listReceived.size() > 0 && wallet.GetTxDepthInMainChain(wtx) >= nMinDepth) { 341 for (const COutputEntry& r : listReceived) 342 { 343 std::string label; 344 const auto* address_book_entry = wallet.FindAddressBookEntry(r.destination); 345 if (address_book_entry) { 346 label = address_book_entry->GetLabel(); 347 } 348 if (filter_label.has_value() && label != filter_label.value()) { 349 continue; 350 } 351 UniValue entry(UniValue::VOBJ); 352 MaybePushAddress(entry, r.destination); 353 PushParentDescriptors(wallet, wtx.tx->vout.at(r.vout).scriptPubKey, entry); 354 if (wtx.IsCoinBase()) 355 { 356 if (wallet.GetTxDepthInMainChain(wtx) < 1) 357 entry.pushKV("category", "orphan"); 358 else if (wallet.IsTxImmatureCoinBase(wtx)) 359 entry.pushKV("category", "immature"); 360 else 361 entry.pushKV("category", "generate"); 362 } 363 else 364 { 365 entry.pushKV("category", "receive"); 366 } 367 entry.pushKV("amount", ValueFromAmount(r.amount)); 368 if (address_book_entry) { 369 entry.pushKV("label", label); 370 } 371 entry.pushKV("vout", r.vout); 372 entry.pushKV("abandoned", wtx.isAbandoned()); 373 if (fLong) 374 WalletTxToJSON(wallet, wtx, entry); 375 ret.push_back(std::move(entry)); 376 } 377 } 378 } 379 380 381 static std::vector<RPCResult> TransactionDescriptionString() 382 { 383 return{{RPCResult::Type::NUM, "confirmations", "The number of confirmations for the transaction. Negative confirmations means the\n" 384 "transaction conflicted that many blocks ago."}, 385 {RPCResult::Type::BOOL, "generated", /*optional=*/true, "Only present if the transaction's only input is a coinbase one."}, 386 {RPCResult::Type::BOOL, "trusted", /*optional=*/true, "Whether we consider the transaction to be trusted and safe to spend from.\n" 387 "Only present when the transaction has 0 confirmations (or negative confirmations, if conflicted)."}, 388 {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash containing the transaction."}, 389 {RPCResult::Type::NUM, "blockheight", /*optional=*/true, "The block height containing the transaction."}, 390 {RPCResult::Type::NUM, "blockindex", /*optional=*/true, "The index of the transaction in the block that includes it."}, 391 {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME + "."}, 392 {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, 393 {RPCResult::Type::STR_HEX, "wtxid", "The hash of serialized transaction, including witness data."}, 394 {RPCResult::Type::ARR, "walletconflicts", "Confirmed transactions that have been detected by the wallet to conflict with this transaction.", 395 { 396 {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, 397 }}, 398 {RPCResult::Type::STR_HEX, "replaced_by_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx was replaced."}, 399 {RPCResult::Type::STR_HEX, "replaces_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx replaces another."}, 400 {RPCResult::Type::ARR, "mempoolconflicts", "Transactions in the mempool that directly conflict with either this transaction or an ancestor transaction", 401 { 402 {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, 403 }}, 404 {RPCResult::Type::STR, "to", /*optional=*/true, "If a comment to is associated with the transaction."}, 405 {RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."}, 406 {RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."}, 407 {RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."}, 408 {RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction signals BIP125 replaceability or has an unconfirmed ancestor signaling BIP125 replaceability.\n" 409 "May be unknown for unconfirmed transactions not in the mempool because their unconfirmed ancestors are unknown."}, 410 {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the output script of this coin.", { 411 {RPCResult::Type::STR, "desc", "The descriptor string."}, 412 }}, 413 }; 414 } 415 416 RPCHelpMan listtransactions() 417 { 418 return RPCHelpMan{ 419 "listtransactions", 420 "If a label name is provided, this will return only incoming transactions paying to addresses with the specified label.\n" 421 "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions.\n", 422 { 423 {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, should be a valid label name to return only incoming transactions\n" 424 "with the specified label, or \"*\" to disable filtering and return all transactions."}, 425 {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "The number of transactions to return"}, 426 {"skip", RPCArg::Type::NUM, RPCArg::Default{0}, "The number of transactions to skip"}, 427 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"}, 428 }, 429 RPCResult{ 430 RPCResult::Type::ARR, "", "", 431 { 432 {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>( 433 { 434 {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."}, 435 {RPCResult::Type::STR, "category", "The transaction category.\n" 436 "\"send\" Transactions sent.\n" 437 "\"receive\" Non-coinbase transactions received.\n" 438 "\"generate\" Coinbase transactions received with more than 100 confirmations.\n" 439 "\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" 440 "\"orphan\" Orphaned coinbase transactions received."}, 441 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n" 442 "for all other categories"}, 443 {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"}, 444 {RPCResult::Type::NUM, "vout", "the vout value"}, 445 {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n" 446 "'send' category of transactions."}, 447 }, 448 TransactionDescriptionString()), 449 { 450 {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."}, 451 })}, 452 } 453 }, 454 RPCExamples{ 455 "\nList the most recent 10 transactions in the systems\n" 456 + HelpExampleCli("listtransactions", "") + 457 "\nList transactions 100 to 120\n" 458 + HelpExampleCli("listtransactions", "\"*\" 20 100") + 459 "\nAs a JSON-RPC call\n" 460 + HelpExampleRpc("listtransactions", "\"*\", 20, 100") 461 }, 462 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 463 { 464 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 465 if (!pwallet) return UniValue::VNULL; 466 467 // Make sure the results are valid at least up to the most recent block 468 // the user could have gotten from another RPC command prior to now 469 pwallet->BlockUntilSyncedToCurrentChain(); 470 471 std::optional<std::string> filter_label; 472 if (!request.params[0].isNull() && request.params[0].get_str() != "*") { 473 filter_label.emplace(LabelFromValue(request.params[0])); 474 if (filter_label.value().empty()) { 475 throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\"."); 476 } 477 } 478 int nCount = 10; 479 if (!request.params[1].isNull()) 480 nCount = request.params[1].getInt<int>(); 481 int nFrom = 0; 482 if (!request.params[2].isNull()) 483 nFrom = request.params[2].getInt<int>(); 484 485 if (nCount < 0) 486 throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count"); 487 if (nFrom < 0) 488 throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from"); 489 490 std::vector<UniValue> ret; 491 { 492 LOCK(pwallet->cs_wallet); 493 494 const CWallet::TxItems & txOrdered = pwallet->wtxOrdered; 495 496 // iterate backwards until we have nCount items to return: 497 for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) 498 { 499 CWalletTx *const pwtx = (*it).second; 500 ListTransactions(*pwallet, *pwtx, 0, true, ret, filter_label); 501 if ((int)ret.size() >= (nCount+nFrom)) break; 502 } 503 } 504 505 // ret is newest to oldest 506 507 if (nFrom > (int)ret.size()) 508 nFrom = ret.size(); 509 if ((nFrom + nCount) > (int)ret.size()) 510 nCount = ret.size() - nFrom; 511 512 auto txs_rev_it{std::make_move_iterator(ret.rend())}; 513 UniValue result{UniValue::VARR}; 514 result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom); // Return oldest to newest 515 return result; 516 }, 517 }; 518 } 519 520 RPCHelpMan listsinceblock() 521 { 522 return RPCHelpMan{ 523 "listsinceblock", 524 "Get all transactions in blocks since block [blockhash], or all transactions if omitted.\n" 525 "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n" 526 "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n", 527 { 528 {"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, the block hash to list transactions since, otherwise list all transactions."}, 529 {"target_confirmations", RPCArg::Type::NUM, RPCArg::Default{1}, "Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value"}, 530 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"}, 531 {"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n" 532 "(not guaranteed to work on pruned nodes)"}, 533 {"include_change", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also add entries for change outputs.\n"}, 534 {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Return only incoming transactions paying to addresses with the specified label.\n"}, 535 }, 536 RPCResult{ 537 RPCResult::Type::OBJ, "", "", 538 { 539 {RPCResult::Type::ARR, "transactions", "", 540 { 541 {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>( 542 { 543 {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."}, 544 {RPCResult::Type::STR, "category", "The transaction category.\n" 545 "\"send\" Transactions sent.\n" 546 "\"receive\" Non-coinbase transactions received.\n" 547 "\"generate\" Coinbase transactions received with more than 100 confirmations.\n" 548 "\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" 549 "\"orphan\" Orphaned coinbase transactions received."}, 550 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n" 551 "for all other categories"}, 552 {RPCResult::Type::NUM, "vout", "the vout value"}, 553 {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n" 554 "'send' category of transactions."}, 555 }, 556 TransactionDescriptionString()), 557 { 558 {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."}, 559 {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"}, 560 })}, 561 }}, 562 {RPCResult::Type::ARR, "removed", /*optional=*/true, "<structure is the same as \"transactions\" above, only present if include_removed=true>\n" 563 "Note: transactions that were re-added in the active chain will appear as-is in this array, and may thus have a positive confirmation count." 564 , {{RPCResult::Type::ELISION, "", ""},}}, 565 {RPCResult::Type::STR_HEX, "lastblock", "The hash of the block (target_confirmations-1) from the best block on the main chain, or the genesis hash if the referenced block does not exist yet. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones"}, 566 } 567 }, 568 RPCExamples{ 569 HelpExampleCli("listsinceblock", "") 570 + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6") 571 + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6") 572 }, 573 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 574 { 575 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 576 if (!pwallet) return UniValue::VNULL; 577 578 const CWallet& wallet = *pwallet; 579 // Make sure the results are valid at least up to the most recent block 580 // the user could have gotten from another RPC command prior to now 581 wallet.BlockUntilSyncedToCurrentChain(); 582 583 LOCK(wallet.cs_wallet); 584 585 std::optional<int> height; // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain. 586 std::optional<int> altheight; // Height of the specified block, even if it's in a deactivated chain. 587 int target_confirms = 1; 588 589 uint256 blockId; 590 if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { 591 blockId = ParseHashV(request.params[0], "blockhash"); 592 height = int{}; 593 altheight = int{}; 594 if (!wallet.chain().findCommonAncestor(blockId, wallet.GetLastBlockHash(), /*ancestor_out=*/FoundBlock().height(*height), /*block1_out=*/FoundBlock().height(*altheight))) { 595 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); 596 } 597 } 598 599 if (!request.params[1].isNull()) { 600 target_confirms = request.params[1].getInt<int>(); 601 602 if (target_confirms < 1) { 603 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); 604 } 605 } 606 607 bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); 608 bool include_change = (!request.params[4].isNull() && request.params[4].get_bool()); 609 610 // Only set it if 'label' was provided. 611 std::optional<std::string> filter_label; 612 if (!request.params[5].isNull()) filter_label.emplace(LabelFromValue(request.params[5])); 613 614 int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1; 615 616 UniValue transactions(UniValue::VARR); 617 618 for (const auto& [_, tx] : wallet.mapWallet) { 619 620 if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) { 621 ListTransactions(wallet, tx, 0, true, transactions, filter_label, include_change); 622 } 623 } 624 625 // when a reorg'd block is requested, we also list any relevant transactions 626 // in the blocks of the chain that was detached 627 UniValue removed(UniValue::VARR); 628 while (include_removed && altheight && *altheight > *height) { 629 CBlock block; 630 if (!wallet.chain().findBlock(blockId, FoundBlock().data(block)) || block.IsNull()) { 631 throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); 632 } 633 for (const CTransactionRef& tx : block.vtx) { 634 auto it = wallet.mapWallet.find(tx->GetHash()); 635 if (it != wallet.mapWallet.end()) { 636 // We want all transactions regardless of confirmation count to appear here, 637 // even negative confirmation ones, hence the big negative. 638 ListTransactions(wallet, it->second, -100000000, true, removed, filter_label, include_change); 639 } 640 } 641 blockId = block.hashPrevBlock; 642 --*altheight; 643 } 644 645 uint256 lastblock; 646 target_confirms = std::min(target_confirms, wallet.GetLastBlockHeight() + 1); 647 CHECK_NONFATAL(wallet.chain().findAncestorByHeight(wallet.GetLastBlockHash(), wallet.GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock))); 648 649 UniValue ret(UniValue::VOBJ); 650 ret.pushKV("transactions", std::move(transactions)); 651 if (include_removed) ret.pushKV("removed", std::move(removed)); 652 ret.pushKV("lastblock", lastblock.GetHex()); 653 654 return ret; 655 }, 656 }; 657 } 658 659 RPCHelpMan gettransaction() 660 { 661 return RPCHelpMan{ 662 "gettransaction", 663 "Get detailed information about in-wallet transaction <txid>\n", 664 { 665 {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"}, 666 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"}, 667 {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, 668 "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"}, 669 }, 670 RPCResult{ 671 RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>( 672 { 673 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT}, 674 {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n" 675 "'send' category of transactions."}, 676 }, 677 TransactionDescriptionString()), 678 { 679 {RPCResult::Type::ARR, "details", "", 680 { 681 {RPCResult::Type::OBJ, "", "", 682 { 683 {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address involved in the transaction."}, 684 {RPCResult::Type::STR, "category", "The transaction category.\n" 685 "\"send\" Transactions sent.\n" 686 "\"receive\" Non-coinbase transactions received.\n" 687 "\"generate\" Coinbase transactions received with more than 100 confirmations.\n" 688 "\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" 689 "\"orphan\" Orphaned coinbase transactions received."}, 690 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT}, 691 {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"}, 692 {RPCResult::Type::NUM, "vout", "the vout value"}, 693 {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" 694 "'send' category of transactions."}, 695 {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."}, 696 {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the output script of this coin.", { 697 {RPCResult::Type::STR, "desc", "The descriptor string."}, 698 }}, 699 }}, 700 }}, 701 {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"}, 702 {RPCResult::Type::OBJ, "decoded", /*optional=*/true, "The decoded transaction (only present when `verbose` is passed)", 703 { 704 DecodeTxDoc(/*txid_field_doc=*/"The transaction id", /*wallet=*/true), 705 }}, 706 RESULT_LAST_PROCESSED_BLOCK, 707 }) 708 }, 709 RPCExamples{ 710 HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") 711 + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true") 712 + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true") 713 + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") 714 }, 715 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 716 { 717 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 718 if (!pwallet) return UniValue::VNULL; 719 720 // Make sure the results are valid at least up to the most recent block 721 // the user could have gotten from another RPC command prior to now 722 pwallet->BlockUntilSyncedToCurrentChain(); 723 724 LOCK(pwallet->cs_wallet); 725 726 Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))}; 727 728 bool verbose = request.params[2].isNull() ? false : request.params[2].get_bool(); 729 730 UniValue entry(UniValue::VOBJ); 731 auto it = pwallet->mapWallet.find(hash); 732 if (it == pwallet->mapWallet.end()) { 733 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); 734 } 735 const CWalletTx& wtx = it->second; 736 737 CAmount nCredit = CachedTxGetCredit(*pwallet, wtx, /*avoid_reuse=*/false); 738 CAmount nDebit = CachedTxGetDebit(*pwallet, wtx, /*avoid_reuse=*/false); 739 CAmount nNet = nCredit - nDebit; 740 CAmount nFee = (CachedTxIsFromMe(*pwallet, wtx) ? wtx.tx->GetValueOut() - nDebit : 0); 741 742 entry.pushKV("amount", ValueFromAmount(nNet - nFee)); 743 if (CachedTxIsFromMe(*pwallet, wtx)) 744 entry.pushKV("fee", ValueFromAmount(nFee)); 745 746 WalletTxToJSON(*pwallet, wtx, entry); 747 748 UniValue details(UniValue::VARR); 749 ListTransactions(*pwallet, wtx, 0, false, details, /*filter_label=*/std::nullopt); 750 entry.pushKV("details", std::move(details)); 751 752 entry.pushKV("hex", EncodeHexTx(*wtx.tx)); 753 754 if (verbose) { 755 UniValue decoded(UniValue::VOBJ); 756 TxToUniv(*wtx.tx, 757 /*block_hash=*/uint256(), 758 /*entry=*/decoded, 759 /*include_hex=*/false, 760 /*txundo=*/nullptr, 761 /*verbosity=*/TxVerbosity::SHOW_DETAILS, 762 /*is_change_func=*/[&pwallet](const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { 763 AssertLockHeld(pwallet->cs_wallet); 764 return OutputIsChange(*pwallet, txout); 765 }); 766 entry.pushKV("decoded", std::move(decoded)); 767 } 768 769 AppendLastProcessedBlock(entry, *pwallet); 770 return entry; 771 }, 772 }; 773 } 774 775 RPCHelpMan abandontransaction() 776 { 777 return RPCHelpMan{ 778 "abandontransaction", 779 "Mark in-wallet transaction <txid> as abandoned\n" 780 "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n" 781 "for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n" 782 "It only works on transactions which are not included in a block and are not currently in the mempool.\n" 783 "It has no effect on transactions which are already abandoned.\n", 784 { 785 {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, 786 }, 787 RPCResult{RPCResult::Type::NONE, "", ""}, 788 RPCExamples{ 789 HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") 790 + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") 791 }, 792 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 793 { 794 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 795 if (!pwallet) return UniValue::VNULL; 796 797 // Make sure the results are valid at least up to the most recent block 798 // the user could have gotten from another RPC command prior to now 799 pwallet->BlockUntilSyncedToCurrentChain(); 800 801 LOCK(pwallet->cs_wallet); 802 803 Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))}; 804 805 if (!pwallet->mapWallet.count(hash)) { 806 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); 807 } 808 if (!pwallet->AbandonTransaction(hash)) { 809 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); 810 } 811 812 return UniValue::VNULL; 813 }, 814 }; 815 } 816 817 RPCHelpMan rescanblockchain() 818 { 819 return RPCHelpMan{ 820 "rescanblockchain", 821 "Rescan the local blockchain for wallet related transactions.\n" 822 "Note: Use \"getwalletinfo\" to query the scanning progress.\n" 823 "The rescan is significantly faster if block filters are available\n" 824 "(using startup option \"-blockfilterindex=1\").\n", 825 { 826 {"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"}, 827 {"stop_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "the last block height that should be scanned. If none is provided it will rescan up to the tip at return time of this call."}, 828 }, 829 RPCResult{ 830 RPCResult::Type::OBJ, "", "", 831 { 832 {RPCResult::Type::NUM, "start_height", "The block height where the rescan started (the requested height or 0)"}, 833 {RPCResult::Type::NUM, "stop_height", "The height of the last rescanned block. May be null in rare cases if there was a reorg and the call didn't scan any blocks because they were already scanned in the background."}, 834 } 835 }, 836 RPCExamples{ 837 HelpExampleCli("rescanblockchain", "100000 120000") 838 + HelpExampleRpc("rescanblockchain", "100000, 120000") 839 }, 840 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 841 { 842 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 843 if (!pwallet) return UniValue::VNULL; 844 CWallet& wallet{*pwallet}; 845 846 // Make sure the results are valid at least up to the most recent block 847 // the user could have gotten from another RPC command prior to now 848 wallet.BlockUntilSyncedToCurrentChain(); 849 850 WalletRescanReserver reserver(*pwallet); 851 if (!reserver.reserve(/*with_passphrase=*/true)) { 852 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); 853 } 854 855 int start_height = 0; 856 std::optional<int> stop_height; 857 uint256 start_block; 858 859 LOCK(pwallet->m_relock_mutex); 860 { 861 LOCK(pwallet->cs_wallet); 862 EnsureWalletIsUnlocked(*pwallet); 863 int tip_height = pwallet->GetLastBlockHeight(); 864 865 if (!request.params[0].isNull()) { 866 start_height = request.params[0].getInt<int>(); 867 if (start_height < 0 || start_height > tip_height) { 868 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); 869 } 870 } 871 872 if (!request.params[1].isNull()) { 873 stop_height = request.params[1].getInt<int>(); 874 if (*stop_height < 0 || *stop_height > tip_height) { 875 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); 876 } else if (*stop_height < start_height) { 877 throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height"); 878 } 879 } 880 881 // We can't rescan unavailable blocks, stop and throw an error 882 if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(), start_height, stop_height)) { 883 if (pwallet->chain().havePruned() && pwallet->chain().getPruneHeight() >= start_height) { 884 throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height."); 885 } 886 if (pwallet->chain().hasAssumedValidChain()) { 887 throw JSONRPCError(RPC_MISC_ERROR, "Failed to rescan unavailable blocks likely due to an in-progress assumeutxo background sync. Check logs or getchainstates RPC for assumeutxo background sync progress and try again later."); 888 } 889 throw JSONRPCError(RPC_MISC_ERROR, "Failed to rescan unavailable blocks, potentially caused by data corruption. If the issue persists you may want to reindex (see -reindex option)."); 890 } 891 892 CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), start_height, FoundBlock().hash(start_block))); 893 } 894 895 CWallet::ScanResult result = 896 pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, /*fUpdate=*/true, /*save_progress=*/false); 897 switch (result.status) { 898 case CWallet::ScanResult::SUCCESS: 899 break; 900 case CWallet::ScanResult::FAILURE: 901 throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); 902 case CWallet::ScanResult::USER_ABORT: 903 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted."); 904 // no default case, so the compiler can warn about missing cases 905 } 906 UniValue response(UniValue::VOBJ); 907 response.pushKV("start_height", start_height); 908 response.pushKV("stop_height", result.last_scanned_height ? *result.last_scanned_height : UniValue()); 909 return response; 910 }, 911 }; 912 } 913 914 RPCHelpMan abortrescan() 915 { 916 return RPCHelpMan{"abortrescan", 917 "Stops current wallet rescan triggered by an RPC call, e.g. by a rescanblockchain call.\n" 918 "Note: Use \"getwalletinfo\" to query the scanning progress.\n", 919 {}, 920 RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"}, 921 RPCExamples{ 922 "\nImport a private key\n" 923 + HelpExampleCli("rescanblockchain", "") + 924 "\nAbort the running wallet rescan\n" 925 + HelpExampleCli("abortrescan", "") + 926 "\nAs a JSON-RPC call\n" 927 + HelpExampleRpc("abortrescan", "") 928 }, 929 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 930 { 931 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 932 if (!pwallet) return UniValue::VNULL; 933 934 if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; 935 pwallet->AbortRescan(); 936 return true; 937 }, 938 }; 939 } 940 } // namespace wallet