2021-12-01 16:09:30 +13:00
// Copyright (c) 2011-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
# include <core_io.h>
# include <key_io.h>
# include <rpc/util.h>
# include <util/moneystr.h>
# include <wallet/coincontrol.h>
# include <wallet/receive.h>
# include <wallet/rpc/util.h>
# include <wallet/spend.h>
# include <wallet/wallet.h>
# include <univalue.h>
2021-11-12 11:13:29 -05:00
namespace wallet {
2021-12-01 16:09:30 +13:00
static CAmount GetReceived ( const CWallet & wallet , const UniValue & params , bool by_label ) EXCLUSIVE_LOCKS_REQUIRED ( wallet . cs_wallet )
{
std : : set < CTxDestination > address_set ;
if ( by_label ) {
// Get the set of addresses assigned to label
std : : string label = LabelFromValue ( params [ 0 ] ) ;
address_set = wallet . GetLabelAddresses ( label ) ;
} else {
// Get the address
CTxDestination dest = DecodeDestination ( params [ 0 ] . get_str ( ) ) ;
if ( ! IsValidDestination ( dest ) ) {
throw JSONRPCError ( RPC_INVALID_ADDRESS_OR_KEY , " Invalid Bitcoin address " ) ;
}
CScript script_pub_key = GetScriptForDestination ( dest ) ;
if ( ! wallet . IsMine ( script_pub_key ) ) {
throw JSONRPCError ( RPC_WALLET_ERROR , " Address not found in wallet " ) ;
}
address_set . insert ( dest ) ;
}
// Minimum confirmations
int min_depth = 1 ;
if ( ! params [ 1 ] . isNull ( ) )
min_depth = params [ 1 ] . get_int ( ) ;
const bool include_immature_coinbase { params [ 2 ] . isNull ( ) ? false : params [ 2 ] . get_bool ( ) } ;
// Excluding coinbase outputs is deprecated
// It can be enabled by setting deprecatedrpc=exclude_coinbase
const bool include_coinbase { ! wallet . chain ( ) . rpcEnableDeprecated ( " exclude_coinbase " ) } ;
if ( include_immature_coinbase & & ! include_coinbase ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " include_immature_coinbase is incompatible with deprecated exclude_coinbase " ) ;
}
// Tally
CAmount amount = 0 ;
for ( const std : : pair < const uint256 , CWalletTx > & wtx_pair : wallet . mapWallet ) {
const CWalletTx & wtx = wtx_pair . second ;
int depth { wallet . GetTxDepthInMainChain ( wtx ) } ;
if ( depth < min_depth
// Coinbase with less than 1 confirmation is no longer in the main chain
| | ( wtx . IsCoinBase ( ) & & ( depth < 1 | | ! include_coinbase ) )
| | ( wallet . IsTxImmatureCoinBase ( wtx ) & & ! include_immature_coinbase )
| | ! wallet . chain ( ) . checkFinalTx ( * wtx . tx ) ) {
continue ;
}
for ( const CTxOut & txout : wtx . tx - > vout ) {
CTxDestination address ;
if ( ExtractDestination ( txout . scriptPubKey , address ) & & wallet . IsMine ( address ) & & address_set . count ( address ) ) {
amount + = txout . nValue ;
}
}
}
return amount ;
}
RPCHelpMan getreceivedbyaddress ( )
{
return RPCHelpMan { " getreceivedbyaddress " ,
" \n Returns the total amount received by the given address in transactions with at least minconf confirmations. \n " ,
{
{ " address " , RPCArg : : Type : : STR , RPCArg : : Optional : : NO , " The bitcoin address for transactions. " } ,
{ " minconf " , RPCArg : : Type : : NUM , RPCArg : : Default { 1 } , " Only include transactions confirmed at least this many times. " } ,
{ " include_immature_coinbase " , RPCArg : : Type : : BOOL , RPCArg : : Default { false } , " Include immature coinbase transactions. " } ,
} ,
RPCResult {
RPCResult : : Type : : STR_AMOUNT , " amount " , " The total amount in " + CURRENCY_UNIT + " received at this address. "
} ,
RPCExamples {
" \n The amount from transactions with at least 1 confirmation \n "
+ HelpExampleCli ( " getreceivedbyaddress " , " \" " + EXAMPLE_ADDRESS [ 0 ] + " \" " ) +
" \n The amount including unconfirmed transactions, zero confirmations \n "
+ HelpExampleCli ( " getreceivedbyaddress " , " \" " + EXAMPLE_ADDRESS [ 0 ] + " \" 0 " ) +
" \n The amount with at least 6 confirmations \n "
+ HelpExampleCli ( " getreceivedbyaddress " , " \" " + EXAMPLE_ADDRESS [ 0 ] + " \" 6 " ) +
" \n The amount with at least 6 confirmations including immature coinbase outputs \n "
+ HelpExampleCli ( " getreceivedbyaddress " , " \" " + EXAMPLE_ADDRESS [ 0 ] + " \" 6 true " ) +
" \n As a JSON-RPC call \n "
+ HelpExampleRpc ( " getreceivedbyaddress " , " \" " + EXAMPLE_ADDRESS [ 0 ] + " \" , 6 " )
} ,
[ & ] ( const RPCHelpMan & self , const JSONRPCRequest & request ) - > UniValue
{
const std : : shared_ptr < const CWallet > pwallet = GetWalletForJSONRPCRequest ( request ) ;
if ( ! pwallet ) return NullUniValue ;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet - > BlockUntilSyncedToCurrentChain ( ) ;
LOCK ( pwallet - > cs_wallet ) ;
return ValueFromAmount ( GetReceived ( * pwallet , request . params , /* by_label */ false ) ) ;
} ,
} ;
}
RPCHelpMan getreceivedbylabel ( )
{
return RPCHelpMan { " getreceivedbylabel " ,
" \n Returns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations. \n " ,
{
{ " label " , RPCArg : : Type : : STR , RPCArg : : Optional : : NO , " The selected label, may be the default label using \" \" . " } ,
{ " minconf " , RPCArg : : Type : : NUM , RPCArg : : Default { 1 } , " Only include transactions confirmed at least this many times. " } ,
{ " include_immature_coinbase " , RPCArg : : Type : : BOOL , RPCArg : : Default { false } , " Include immature coinbase transactions. " } ,
} ,
RPCResult {
RPCResult : : Type : : STR_AMOUNT , " amount " , " The total amount in " + CURRENCY_UNIT + " received for this label. "
} ,
RPCExamples {
" \n Amount received by the default label with at least 1 confirmation \n "
+ HelpExampleCli ( " getreceivedbylabel " , " \" \" " ) +
" \n Amount received at the tabby label including unconfirmed amounts with zero confirmations \n "
+ HelpExampleCli ( " getreceivedbylabel " , " \" tabby \" 0 " ) +
" \n The amount with at least 6 confirmations \n "
+ HelpExampleCli ( " getreceivedbylabel " , " \" tabby \" 6 " ) +
" \n The amount with at least 6 confirmations including immature coinbase outputs \n "
+ HelpExampleCli ( " getreceivedbylabel " , " \" tabby \" 6 true " ) +
" \n As a JSON-RPC call \n "
+ HelpExampleRpc ( " getreceivedbylabel " , " \" tabby \" , 6, true " )
} ,
[ & ] ( const RPCHelpMan & self , const JSONRPCRequest & request ) - > UniValue
{
const std : : shared_ptr < const CWallet > pwallet = GetWalletForJSONRPCRequest ( request ) ;
if ( ! pwallet ) return NullUniValue ;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet - > BlockUntilSyncedToCurrentChain ( ) ;
LOCK ( pwallet - > cs_wallet ) ;
return ValueFromAmount ( GetReceived ( * pwallet , request . params , /* by_label */ true ) ) ;
} ,
} ;
}
RPCHelpMan getbalance ( )
{
return RPCHelpMan { " getbalance " ,
" \n Returns the total available balance. \n "
" The available balance is what the wallet considers currently spendable, and is \n "
" thus affected by options which limit spendability such as -spendzeroconfchange. \n " ,
{
{ " dummy " , RPCArg : : Type : : STR , RPCArg : : Optional : : OMITTED_NAMED_ARG , " Remains for backward compatibility. Must be excluded or set to \" * \" . " } ,
{ " minconf " , RPCArg : : Type : : NUM , RPCArg : : Default { 0 } , " Only include transactions confirmed at least this many times. " } ,
{ " include_watchonly " , RPCArg : : Type : : BOOL , RPCArg : : DefaultHint { " true for watch-only wallets, otherwise false " } , " Also include balance in watch-only addresses (see 'importaddress') " } ,
{ " avoid_reuse " , RPCArg : : Type : : BOOL , RPCArg : : Default { true } , " (only available if avoid_reuse wallet flag is set) Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction. " } ,
} ,
RPCResult {
RPCResult : : Type : : STR_AMOUNT , " amount " , " The total amount in " + CURRENCY_UNIT + " received for this wallet. "
} ,
RPCExamples {
" \n The total amount in the wallet with 0 or more confirmations \n "
+ HelpExampleCli ( " getbalance " , " " ) +
" \n The total amount in the wallet with at least 6 confirmations \n "
+ HelpExampleCli ( " getbalance " , " \" * \" 6 " ) +
" \n As a JSON-RPC call \n "
+ HelpExampleRpc ( " getbalance " , " \" * \" , 6 " )
} ,
[ & ] ( const RPCHelpMan & self , const JSONRPCRequest & request ) - > UniValue
{
const std : : shared_ptr < const CWallet > pwallet = GetWalletForJSONRPCRequest ( request ) ;
if ( ! pwallet ) return NullUniValue ;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet - > BlockUntilSyncedToCurrentChain ( ) ;
LOCK ( pwallet - > cs_wallet ) ;
const UniValue & dummy_value = request . params [ 0 ] ;
if ( ! dummy_value . isNull ( ) & & dummy_value . get_str ( ) ! = " * " ) {
throw JSONRPCError ( RPC_METHOD_DEPRECATED , " dummy first argument must be excluded or set to \" * \" . " ) ;
}
int min_depth = 0 ;
if ( ! request . params [ 1 ] . isNull ( ) ) {
min_depth = request . params [ 1 ] . get_int ( ) ;
}
bool include_watchonly = ParseIncludeWatchonly ( request . params [ 2 ] , * pwallet ) ;
bool avoid_reuse = GetAvoidReuseFlag ( * pwallet , request . params [ 3 ] ) ;
const auto bal = GetBalance ( * pwallet , min_depth , avoid_reuse ) ;
return ValueFromAmount ( bal . m_mine_trusted + ( include_watchonly ? bal . m_watchonly_trusted : 0 ) ) ;
} ,
} ;
}
RPCHelpMan getunconfirmedbalance ( )
{
return RPCHelpMan { " getunconfirmedbalance " ,
" DEPRECATED \n Identical to getbalances().mine.untrusted_pending \n " ,
{ } ,
RPCResult { RPCResult : : Type : : NUM , " " , " The balance " } ,
RPCExamples { " " } ,
[ & ] ( const RPCHelpMan & self , const JSONRPCRequest & request ) - > UniValue
{
const std : : shared_ptr < const CWallet > pwallet = GetWalletForJSONRPCRequest ( request ) ;
if ( ! pwallet ) return NullUniValue ;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet - > BlockUntilSyncedToCurrentChain ( ) ;
LOCK ( pwallet - > cs_wallet ) ;
return ValueFromAmount ( GetBalance ( * pwallet ) . m_mine_untrusted_pending ) ;
} ,
} ;
}
RPCHelpMan lockunspent ( )
{
return RPCHelpMan { " lockunspent " ,
" \n Updates list of temporarily unspendable outputs. \n "
" Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs. \n "
" If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked. \n "
" A locked transaction output will not be chosen by automatic coin selection, when spending bitcoins. \n "
" Manually selected coins are automatically unlocked. \n "
" Locks are stored in memory only, unless persistent=true, in which case they will be written to the \n "
" wallet database and loaded on node start. Unwritten (persistent=false) locks are always cleared \n "
" (by virtue of process exit) when a node stops or fails. Unlocking will clear both persistent and not. \n "
" Also see the listunspent call \n " ,
{
{ " unlock " , RPCArg : : Type : : BOOL , RPCArg : : Optional : : NO , " Whether to unlock (true) or lock (false) the specified transactions " } ,
{ " transactions " , RPCArg : : Type : : ARR , RPCArg : : Default { UniValue : : VARR } , " The transaction outputs and within each, the txid (string) vout (numeric). " ,
{
{ " " , RPCArg : : Type : : OBJ , RPCArg : : Optional : : OMITTED , " " ,
{
{ " txid " , RPCArg : : Type : : STR_HEX , RPCArg : : Optional : : NO , " The transaction id " } ,
{ " vout " , RPCArg : : Type : : NUM , RPCArg : : Optional : : NO , " The output number " } ,
} ,
} ,
} ,
} ,
{ " persistent " , RPCArg : : Type : : BOOL , RPCArg : : Default { false } , " Whether to write/erase this lock in the wallet database, or keep the change in memory only. Ignored for unlocking. " } ,
} ,
RPCResult {
RPCResult : : Type : : BOOL , " " , " Whether the command was successful or not "
} ,
RPCExamples {
" \n List the unspent transactions \n "
+ HelpExampleCli ( " listunspent " , " " ) +
" \n Lock an unspent transaction \n "
+ HelpExampleCli ( " lockunspent " , " false \" [{ \\ \" txid \\ \" : \\ \" a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0 \\ \" , \\ \" vout \\ \" :1}] \" " ) +
" \n List the locked transactions \n "
+ HelpExampleCli ( " listlockunspent " , " " ) +
" \n Unlock the transaction again \n "
+ HelpExampleCli ( " lockunspent " , " true \" [{ \\ \" txid \\ \" : \\ \" a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0 \\ \" , \\ \" vout \\ \" :1}] \" " ) +
" \n Lock the transaction persistently in the wallet database \n "
+ HelpExampleCli ( " lockunspent " , " false \" [{ \\ \" txid \\ \" : \\ \" a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0 \\ \" , \\ \" vout \\ \" :1}] \" true " ) +
" \n As a JSON-RPC call \n "
+ HelpExampleRpc ( " lockunspent " , " false, \" [{ \\ \" txid \\ \" : \\ \" a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0 \\ \" , \\ \" vout \\ \" :1}] \" " )
} ,
[ & ] ( const RPCHelpMan & self , const JSONRPCRequest & request ) - > UniValue
{
std : : shared_ptr < CWallet > const pwallet = GetWalletForJSONRPCRequest ( request ) ;
if ( ! pwallet ) return NullUniValue ;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet - > BlockUntilSyncedToCurrentChain ( ) ;
LOCK ( pwallet - > cs_wallet ) ;
RPCTypeCheckArgument ( request . params [ 0 ] , UniValue : : VBOOL ) ;
bool fUnlock = request . params [ 0 ] . get_bool ( ) ;
const bool persistent { request . params [ 2 ] . isNull ( ) ? false : request . params [ 2 ] . get_bool ( ) } ;
if ( request . params [ 1 ] . isNull ( ) ) {
if ( fUnlock ) {
if ( ! pwallet - > UnlockAllCoins ( ) )
throw JSONRPCError ( RPC_WALLET_ERROR , " Unlocking coins failed " ) ;
}
return true ;
}
RPCTypeCheckArgument ( request . params [ 1 ] , UniValue : : VARR ) ;
const UniValue & output_params = request . params [ 1 ] ;
// Create and validate the COutPoints first.
std : : vector < COutPoint > outputs ;
outputs . reserve ( output_params . size ( ) ) ;
for ( unsigned int idx = 0 ; idx < output_params . size ( ) ; idx + + ) {
const UniValue & o = output_params [ idx ] . get_obj ( ) ;
RPCTypeCheckObj ( o ,
{
{ " txid " , UniValueType ( UniValue : : VSTR ) } ,
{ " vout " , UniValueType ( UniValue : : VNUM ) } ,
} ) ;
const uint256 txid ( ParseHashO ( o , " txid " ) ) ;
const int nOutput = find_value ( o , " vout " ) . get_int ( ) ;
if ( nOutput < 0 ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, vout cannot be negative " ) ;
}
const COutPoint outpt ( txid , nOutput ) ;
const auto it = pwallet - > mapWallet . find ( outpt . hash ) ;
if ( it = = pwallet - > mapWallet . end ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, unknown transaction " ) ;
}
const CWalletTx & trans = it - > second ;
if ( outpt . n > = trans . tx - > vout . size ( ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, vout index out of bounds " ) ;
}
if ( pwallet - > IsSpent ( outpt . hash , outpt . n ) ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, expected unspent output " ) ;
}
const bool is_locked = pwallet - > IsLockedCoin ( outpt . hash , outpt . n ) ;
if ( fUnlock & & ! is_locked ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, expected locked output " ) ;
}
if ( ! fUnlock & & is_locked & & ! persistent ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , " Invalid parameter, output already locked " ) ;
}
outputs . push_back ( outpt ) ;
}
std : : unique_ptr < WalletBatch > batch = nullptr ;
// Unlock is always persistent
if ( fUnlock | | persistent ) batch = std : : make_unique < WalletBatch > ( pwallet - > GetDatabase ( ) ) ;
// Atomically set (un)locked status for the outputs.
for ( const COutPoint & outpt : outputs ) {
if ( fUnlock ) {
if ( ! pwallet - > UnlockCoin ( outpt , batch . get ( ) ) ) throw JSONRPCError ( RPC_WALLET_ERROR , " Unlocking coin failed " ) ;
} else {
if ( ! pwallet - > LockCoin ( outpt , batch . get ( ) ) ) throw JSONRPCError ( RPC_WALLET_ERROR , " Locking coin failed " ) ;
}
}
return true ;
} ,
} ;
}
RPCHelpMan listlockunspent ( )
{
return RPCHelpMan { " listlockunspent " ,
" \n Returns list of temporarily unspendable outputs. \n "
" See the lockunspent call to lock and unlock transactions for spending. \n " ,
{ } ,
RPCResult {
RPCResult : : Type : : ARR , " " , " " ,
{
{ RPCResult : : Type : : OBJ , " " , " " ,
{
{ RPCResult : : Type : : STR_HEX , " txid " , " The transaction id locked " } ,
{ RPCResult : : Type : : NUM , " vout " , " The vout value " } ,
} } ,
}
} ,
RPCExamples {
" \n List the unspent transactions \n "
+ HelpExampleCli ( " listunspent " , " " ) +
" \n Lock an unspent transaction \n "
+ HelpExampleCli ( " lockunspent " , " false \" [{ \\ \" txid \\ \" : \\ \" a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0 \\ \" , \\ \" vout \\ \" :1}] \" " ) +
" \n List the locked transactions \n "
+ HelpExampleCli ( " listlockunspent " , " " ) +
" \n Unlock the transaction again \n "
+ HelpExampleCli ( " lockunspent " , " true \" [{ \\ \" txid \\ \" : \\ \" a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0 \\ \" , \\ \" vout \\ \" :1}] \" " ) +
" \n As a JSON-RPC call \n "
+ HelpExampleRpc ( " listlockunspent " , " " )
} ,
[ & ] ( const RPCHelpMan & self , const JSONRPCRequest & request ) - > UniValue
{
const std : : shared_ptr < const CWallet > pwallet = GetWalletForJSONRPCRequest ( request ) ;
if ( ! pwallet ) return NullUniValue ;
LOCK ( pwallet - > cs_wallet ) ;
std : : vector < COutPoint > vOutpts ;
pwallet - > ListLockedCoins ( vOutpts ) ;
UniValue ret ( UniValue : : VARR ) ;
for ( const COutPoint & outpt : vOutpts ) {
UniValue o ( UniValue : : VOBJ ) ;
o . pushKV ( " txid " , outpt . hash . GetHex ( ) ) ;
o . pushKV ( " vout " , ( int ) outpt . n ) ;
ret . push_back ( o ) ;
}
return ret ;
} ,
} ;
}
RPCHelpMan getbalances ( )
{
return RPCHelpMan {
" getbalances " ,
" Returns an object with all balances in " + CURRENCY_UNIT + " . \n " ,
{ } ,
RPCResult {
RPCResult : : Type : : OBJ , " " , " " ,
{
{ RPCResult : : Type : : OBJ , " mine " , " balances from outputs that the wallet can sign " ,
{
{ RPCResult : : Type : : STR_AMOUNT , " trusted " , " trusted balance (outputs created by the wallet or confirmed outputs) " } ,
{ RPCResult : : Type : : STR_AMOUNT , " untrusted_pending " , " untrusted pending balance (outputs created by others that are in the mempool) " } ,
{ RPCResult : : Type : : STR_AMOUNT , " immature " , " balance from immature coinbase outputs " } ,
2021-12-08 11:55:46 +01:00
{ RPCResult : : Type : : STR_AMOUNT , " used " , /*optional=*/ true , " (only present if avoid_reuse is set) balance from coins sent to addresses that were previously spent from (potentially privacy violating) " } ,
2021-12-01 16:09:30 +13:00
} } ,
2021-12-08 11:55:46 +01:00
{ RPCResult : : Type : : OBJ , " watchonly " , /*optional=*/ true , " watchonly balances (not present if wallet does not watch anything) " ,
2021-12-01 16:09:30 +13:00
{
{ RPCResult : : Type : : STR_AMOUNT , " trusted " , " trusted balance (outputs created by the wallet or confirmed outputs) " } ,
{ RPCResult : : Type : : STR_AMOUNT , " untrusted_pending " , " untrusted pending balance (outputs created by others that are in the mempool) " } ,
{ RPCResult : : Type : : STR_AMOUNT , " immature " , " balance from immature coinbase outputs " } ,
} } ,
}
} ,
RPCExamples {
HelpExampleCli ( " getbalances " , " " ) +
HelpExampleRpc ( " getbalances " , " " ) } ,
[ & ] ( const RPCHelpMan & self , const JSONRPCRequest & request ) - > UniValue
{
const std : : shared_ptr < const CWallet > rpc_wallet = GetWalletForJSONRPCRequest ( request ) ;
if ( ! rpc_wallet ) return NullUniValue ;
const CWallet & wallet = * rpc_wallet ;
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
wallet . BlockUntilSyncedToCurrentChain ( ) ;
LOCK ( wallet . cs_wallet ) ;
const auto bal = GetBalance ( wallet ) ;
UniValue balances { UniValue : : VOBJ } ;
{
UniValue balances_mine { UniValue : : VOBJ } ;
balances_mine . pushKV ( " trusted " , ValueFromAmount ( bal . m_mine_trusted ) ) ;
balances_mine . pushKV ( " untrusted_pending " , ValueFromAmount ( bal . m_mine_untrusted_pending ) ) ;
balances_mine . pushKV ( " immature " , ValueFromAmount ( bal . m_mine_immature ) ) ;
if ( wallet . IsWalletFlagSet ( WALLET_FLAG_AVOID_REUSE ) ) {
// If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get
// the total balance, and then subtract bal to get the reused address balance.
const auto full_bal = GetBalance ( wallet , 0 , false ) ;
balances_mine . pushKV ( " used " , ValueFromAmount ( full_bal . m_mine_trusted + full_bal . m_mine_untrusted_pending - bal . m_mine_trusted - bal . m_mine_untrusted_pending ) ) ;
}
balances . pushKV ( " mine " , balances_mine ) ;
}
auto spk_man = wallet . GetLegacyScriptPubKeyMan ( ) ;
if ( spk_man & & spk_man - > HaveWatchOnly ( ) ) {
UniValue balances_watchonly { UniValue : : VOBJ } ;
balances_watchonly . pushKV ( " trusted " , ValueFromAmount ( bal . m_watchonly_trusted ) ) ;
balances_watchonly . pushKV ( " untrusted_pending " , ValueFromAmount ( bal . m_watchonly_untrusted_pending ) ) ;
balances_watchonly . pushKV ( " immature " , ValueFromAmount ( bal . m_watchonly_immature ) ) ;
balances . pushKV ( " watchonly " , balances_watchonly ) ;
}
return balances ;
} ,
} ;
}
RPCHelpMan listunspent ( )
{
return RPCHelpMan {
" listunspent " ,
" \n Returns array of unspent transaction outputs \n "
" with between minconf and maxconf (inclusive) confirmations. \n "
" Optionally filter to only include txouts paid to specified addresses. \n " ,
{
{ " minconf " , RPCArg : : Type : : NUM , RPCArg : : Default { 1 } , " The minimum confirmations to filter " } ,
{ " maxconf " , RPCArg : : Type : : NUM , RPCArg : : Default { 9999999 } , " The maximum confirmations to filter " } ,
{ " addresses " , RPCArg : : Type : : ARR , RPCArg : : Default { UniValue : : VARR } , " The bitcoin addresses to filter " ,
{
{ " address " , RPCArg : : Type : : STR , RPCArg : : Optional : : OMITTED , " bitcoin address " } ,
} ,
} ,
{ " include_unsafe " , RPCArg : : Type : : BOOL , RPCArg : : Default { true } , " Include outputs that are not safe to spend \n "
" See description of \" safe \" attribute below. " } ,
{ " query_options " , RPCArg : : Type : : OBJ , RPCArg : : Optional : : OMITTED_NAMED_ARG , " JSON with query options " ,
{
{ " minimumAmount " , RPCArg : : Type : : AMOUNT , RPCArg : : Default { FormatMoney ( 0 ) } , " Minimum value of each UTXO in " + CURRENCY_UNIT + " " } ,
{ " maximumAmount " , RPCArg : : Type : : AMOUNT , RPCArg : : DefaultHint { " unlimited " } , " Maximum value of each UTXO in " + CURRENCY_UNIT + " " } ,
{ " maximumCount " , RPCArg : : Type : : NUM , RPCArg : : DefaultHint { " unlimited " } , " Maximum number of UTXOs " } ,
{ " minimumSumAmount " , RPCArg : : Type : : AMOUNT , RPCArg : : DefaultHint { " unlimited " } , " Minimum sum value of all UTXOs in " + CURRENCY_UNIT + " " } ,
} ,
" query_options " } ,
} ,
RPCResult {
RPCResult : : Type : : ARR , " " , " " ,
{
{ RPCResult : : Type : : OBJ , " " , " " ,
{
{ RPCResult : : Type : : STR_HEX , " txid " , " the transaction id " } ,
{ RPCResult : : Type : : NUM , " vout " , " the vout value " } ,
2021-12-08 11:55:46 +01:00
{ RPCResult : : Type : : STR , " address " , /*optional=*/ true , " the bitcoin address " } ,
{ RPCResult : : Type : : STR , " label " , /*optional=*/ true , " The associated label, or \" \" for the default label " } ,
2021-12-01 16:09:30 +13:00
{ RPCResult : : Type : : STR , " scriptPubKey " , " the script key " } ,
{ RPCResult : : Type : : STR_AMOUNT , " amount " , " the transaction output amount in " + CURRENCY_UNIT } ,
{ RPCResult : : Type : : NUM , " confirmations " , " The number of confirmations " } ,
2021-12-08 11:55:46 +01:00
{ RPCResult : : Type : : NUM , " ancestorcount " , /*optional=*/ true , " The number of in-mempool ancestor transactions, including this one (if transaction is in the mempool) " } ,
{ RPCResult : : Type : : NUM , " ancestorsize " , /*optional=*/ true , " The virtual transaction size of in-mempool ancestors, including this one (if transaction is in the mempool) " } ,
{ RPCResult : : Type : : STR_AMOUNT , " ancestorfees " , /*optional=*/ true , " The total fees of in-mempool ancestors (including this one) with fee deltas used for mining priority in " + CURRENCY_ATOM + " (if transaction is in the mempool) " } ,
{ RPCResult : : Type : : STR_HEX , " redeemScript " , /*optional=*/ true , " The redeemScript if scriptPubKey is P2SH " } ,
{ RPCResult : : Type : : STR , " witnessScript " , /*optional=*/ true , " witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH " } ,
2021-12-01 16:09:30 +13:00
{ RPCResult : : Type : : BOOL , " spendable " , " Whether we have the private keys to spend this output " } ,
{ RPCResult : : Type : : BOOL , " solvable " , " Whether we know how to spend this output, ignoring the lack of keys " } ,
2021-12-08 11:55:46 +01:00
{ RPCResult : : Type : : BOOL , " reused " , /*optional=*/ true , " (only present if avoid_reuse is set) Whether this output is reused/dirty (sent to an address that was previously spent from) " } ,
{ RPCResult : : Type : : STR , " desc " , /*optional=*/ true , " (only when solvable) A descriptor for spending this output " } ,
2021-12-01 16:09:30 +13:00
{ RPCResult : : Type : : BOOL , " safe " , " Whether this output is considered safe to spend. Unconfirmed transactions \n "
" from outside keys and unconfirmed replacement transactions are considered unsafe \n "
" and are not eligible for spending by fundrawtransaction and sendtoaddress. " } ,
} } ,
}
} ,
RPCExamples {
HelpExampleCli ( " listunspent " , " " )
+ HelpExampleCli ( " listunspent " , " 6 9999999 \" [ \\ \" " + EXAMPLE_ADDRESS [ 0 ] + " \\ \" , \\ \" " + EXAMPLE_ADDRESS [ 1 ] + " \\ \" ] \" " )
+ HelpExampleRpc ( " listunspent " , " 6, 9999999 \" [ \\ \" " + EXAMPLE_ADDRESS [ 0 ] + " \\ \" , \\ \" " + EXAMPLE_ADDRESS [ 1 ] + " \\ \" ] \" " )
+ HelpExampleCli ( " listunspent " , " 6 9999999 '[]' true '{ \" minimumAmount \" : 0.005 }' " )
+ HelpExampleRpc ( " listunspent " , " 6, 9999999, [] , true, { \" minimumAmount \" : 0.005 } " )
} ,
[ & ] ( const RPCHelpMan & self , const JSONRPCRequest & request ) - > UniValue
{
const std : : shared_ptr < const CWallet > pwallet = GetWalletForJSONRPCRequest ( request ) ;
if ( ! pwallet ) return NullUniValue ;
int nMinDepth = 1 ;
if ( ! request . params [ 0 ] . isNull ( ) ) {
RPCTypeCheckArgument ( request . params [ 0 ] , UniValue : : VNUM ) ;
nMinDepth = request . params [ 0 ] . get_int ( ) ;
}
int nMaxDepth = 9999999 ;
if ( ! request . params [ 1 ] . isNull ( ) ) {
RPCTypeCheckArgument ( request . params [ 1 ] , UniValue : : VNUM ) ;
nMaxDepth = request . params [ 1 ] . get_int ( ) ;
}
std : : set < CTxDestination > destinations ;
if ( ! request . params [ 2 ] . isNull ( ) ) {
RPCTypeCheckArgument ( request . params [ 2 ] , UniValue : : VARR ) ;
UniValue inputs = request . params [ 2 ] . get_array ( ) ;
for ( unsigned int idx = 0 ; idx < inputs . size ( ) ; idx + + ) {
const UniValue & input = inputs [ idx ] ;
CTxDestination dest = DecodeDestination ( input . get_str ( ) ) ;
if ( ! IsValidDestination ( dest ) ) {
throw JSONRPCError ( RPC_INVALID_ADDRESS_OR_KEY , std : : string ( " Invalid Bitcoin address: " ) + input . get_str ( ) ) ;
}
if ( ! destinations . insert ( dest ) . second ) {
throw JSONRPCError ( RPC_INVALID_PARAMETER , std : : string ( " Invalid parameter, duplicated address: " ) + input . get_str ( ) ) ;
}
}
}
bool include_unsafe = true ;
if ( ! request . params [ 3 ] . isNull ( ) ) {
RPCTypeCheckArgument ( request . params [ 3 ] , UniValue : : VBOOL ) ;
include_unsafe = request . params [ 3 ] . get_bool ( ) ;
}
CAmount nMinimumAmount = 0 ;
CAmount nMaximumAmount = MAX_MONEY ;
CAmount nMinimumSumAmount = MAX_MONEY ;
uint64_t nMaximumCount = 0 ;
if ( ! request . params [ 4 ] . isNull ( ) ) {
const UniValue & options = request . params [ 4 ] . get_obj ( ) ;
RPCTypeCheckObj ( options ,
{
{ " minimumAmount " , UniValueType ( ) } ,
{ " maximumAmount " , UniValueType ( ) } ,
{ " minimumSumAmount " , UniValueType ( ) } ,
{ " maximumCount " , UniValueType ( UniValue : : VNUM ) } ,
} ,
true , true ) ;
if ( options . exists ( " minimumAmount " ) )
nMinimumAmount = AmountFromValue ( options [ " minimumAmount " ] ) ;
if ( options . exists ( " maximumAmount " ) )
nMaximumAmount = AmountFromValue ( options [ " maximumAmount " ] ) ;
if ( options . exists ( " minimumSumAmount " ) )
nMinimumSumAmount = AmountFromValue ( options [ " minimumSumAmount " ] ) ;
if ( options . exists ( " maximumCount " ) )
nMaximumCount = options [ " maximumCount " ] . get_int64 ( ) ;
}
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
pwallet - > BlockUntilSyncedToCurrentChain ( ) ;
UniValue results ( UniValue : : VARR ) ;
std : : vector < COutput > vecOutputs ;
{
CCoinControl cctl ;
cctl . m_avoid_address_reuse = false ;
cctl . m_min_depth = nMinDepth ;
cctl . m_max_depth = nMaxDepth ;
cctl . m_include_unsafe_inputs = include_unsafe ;
LOCK ( pwallet - > cs_wallet ) ;
AvailableCoins ( * pwallet , vecOutputs , & cctl , nMinimumAmount , nMaximumAmount , nMinimumSumAmount , nMaximumCount ) ;
}
LOCK ( pwallet - > cs_wallet ) ;
const bool avoid_reuse = pwallet - > IsWalletFlagSet ( WALLET_FLAG_AVOID_REUSE ) ;
for ( const COutput & out : vecOutputs ) {
CTxDestination address ;
const CScript & scriptPubKey = out . tx - > tx - > vout [ out . i ] . scriptPubKey ;
bool fValidAddress = ExtractDestination ( scriptPubKey , address ) ;
bool reused = avoid_reuse & & pwallet - > IsSpentKey ( out . tx - > GetHash ( ) , out . i ) ;
if ( destinations . size ( ) & & ( ! fValidAddress | | ! destinations . count ( address ) ) )
continue ;
UniValue entry ( UniValue : : VOBJ ) ;
entry . pushKV ( " txid " , out . tx - > GetHash ( ) . GetHex ( ) ) ;
entry . pushKV ( " vout " , out . i ) ;
if ( fValidAddress ) {
entry . pushKV ( " address " , EncodeDestination ( address ) ) ;
const auto * address_book_entry = pwallet - > FindAddressBookEntry ( address ) ;
if ( address_book_entry ) {
entry . pushKV ( " label " , address_book_entry - > GetLabel ( ) ) ;
}
std : : unique_ptr < SigningProvider > provider = pwallet - > GetSolvingProvider ( scriptPubKey ) ;
if ( provider ) {
if ( scriptPubKey . IsPayToScriptHash ( ) ) {
const CScriptID & hash = CScriptID ( std : : get < ScriptHash > ( address ) ) ;
CScript redeemScript ;
if ( provider - > GetCScript ( hash , redeemScript ) ) {
entry . pushKV ( " redeemScript " , HexStr ( redeemScript ) ) ;
// Now check if the redeemScript is actually a P2WSH script
CTxDestination witness_destination ;
if ( redeemScript . IsPayToWitnessScriptHash ( ) ) {
bool extracted = ExtractDestination ( redeemScript , witness_destination ) ;
CHECK_NONFATAL ( extracted ) ;
// Also return the witness script
const WitnessV0ScriptHash & whash = std : : get < WitnessV0ScriptHash > ( witness_destination ) ;
CScriptID id ;
CRIPEMD160 ( ) . Write ( whash . begin ( ) , whash . size ( ) ) . Finalize ( id . begin ( ) ) ;
CScript witnessScript ;
if ( provider - > GetCScript ( id , witnessScript ) ) {
entry . pushKV ( " witnessScript " , HexStr ( witnessScript ) ) ;
}
}
}
} else if ( scriptPubKey . IsPayToWitnessScriptHash ( ) ) {
const WitnessV0ScriptHash & whash = std : : get < WitnessV0ScriptHash > ( address ) ;
CScriptID id ;
CRIPEMD160 ( ) . Write ( whash . begin ( ) , whash . size ( ) ) . Finalize ( id . begin ( ) ) ;
CScript witnessScript ;
if ( provider - > GetCScript ( id , witnessScript ) ) {
entry . pushKV ( " witnessScript " , HexStr ( witnessScript ) ) ;
}
}
}
}
entry . pushKV ( " scriptPubKey " , HexStr ( scriptPubKey ) ) ;
entry . pushKV ( " amount " , ValueFromAmount ( out . tx - > tx - > vout [ out . i ] . nValue ) ) ;
entry . pushKV ( " confirmations " , out . nDepth ) ;
if ( ! out . nDepth ) {
size_t ancestor_count , descendant_count , ancestor_size ;
CAmount ancestor_fees ;
pwallet - > chain ( ) . getTransactionAncestry ( out . tx - > GetHash ( ) , ancestor_count , descendant_count , & ancestor_size , & ancestor_fees ) ;
if ( ancestor_count ) {
entry . pushKV ( " ancestorcount " , uint64_t ( ancestor_count ) ) ;
entry . pushKV ( " ancestorsize " , uint64_t ( ancestor_size ) ) ;
entry . pushKV ( " ancestorfees " , uint64_t ( ancestor_fees ) ) ;
}
}
entry . pushKV ( " spendable " , out . fSpendable ) ;
entry . pushKV ( " solvable " , out . fSolvable ) ;
if ( out . fSolvable ) {
std : : unique_ptr < SigningProvider > provider = pwallet - > GetSolvingProvider ( scriptPubKey ) ;
if ( provider ) {
auto descriptor = InferDescriptor ( scriptPubKey , * provider ) ;
entry . pushKV ( " desc " , descriptor - > ToString ( ) ) ;
}
}
if ( avoid_reuse ) entry . pushKV ( " reused " , reused ) ;
entry . pushKV ( " safe " , out . fSafe ) ;
results . push_back ( entry ) ;
}
return results ;
} ,
} ;
}
2021-11-12 11:13:29 -05:00
} // namespace wallet