Browse Source

listsinceblock: optionally find and list any transactions that were undone due to reorg when requesting a non-main chain block in a new 'removed' array.

0.15
Karl-Johan Alm 8 years ago
parent
commit
f999c46cae
No known key found for this signature in database
GPG Key ID: 57AF762DB3353322
  1. 1
      src/rpc/client.cpp
  2. 78
      src/wallet/rpcwallet.cpp

1
src/rpc/client.cpp

@ -68,6 +68,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getblocktemplate", 0, "template_request" }, { "getblocktemplate", 0, "template_request" },
{ "listsinceblock", 1, "target_confirmations" }, { "listsinceblock", 1, "target_confirmations" },
{ "listsinceblock", 2, "include_watchonly" }, { "listsinceblock", 2, "include_watchonly" },
{ "listsinceblock", 3, "include_removed" },
{ "sendmany", 1, "amounts" }, { "sendmany", 1, "amounts" },
{ "sendmany", 2, "minconf" }, { "sendmany", 2, "minconf" },
{ "sendmany", 4, "subtractfeefrom" }, { "sendmany", 4, "subtractfeefrom" },

78
src/wallet/rpcwallet.cpp

@ -1426,6 +1426,17 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
entry.push_back(Pair("address", addr.ToString())); entry.push_back(Pair("address", addr.ToString()));
} }
/**
* List transactions based on the given criteria.
*
* @param pwallet The wallet.
* @param wtx The wallet transaction.
* @param strAccount The account, if any, or "*" for all.
* @param nMinDepth The minimum confirmation depth.
* @param fLong Whether to include the JSON version of the transaction.
* @param ret The UniValue into which the result is stored.
* @param filter The "is mine" filter bool.
*/
void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter)
{ {
CAmount nFee; CAmount nFee;
@ -1742,14 +1753,18 @@ UniValue listsinceblock(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || request.params.size() > 3) if (request.fHelp || request.params.size() > 4)
throw std::runtime_error( throw std::runtime_error(
"listsinceblock ( \"blockhash\" target_confirmations include_watchonly)\n" "listsinceblock ( \"blockhash\" target_confirmations include_watchonly include_removed )\n"
"\nGet all transactions in blocks since block [blockhash], or all transactions if omitted\n" "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted.\n"
"If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n"
"Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n"
"\nArguments:\n" "\nArguments:\n"
"1. \"blockhash\" (string, optional) The block hash to list transactions since\n" "1. \"blockhash\" (string, optional) The block hash to list transactions since\n"
"2. target_confirmations: (numeric, optional) The confirmations required, must be 1 or more\n" "2. target_confirmations: (numeric, optional, default=1) The confirmations required, must be 1 or more\n"
"3. include_watchonly: (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')" "3. include_watchonly: (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n"
"4. include_removed: (bool, optional, default=true) Show transactions that were removed due to a reorg in the \"removed\" array\n"
" (not guaranteed to work on pruned nodes)\n"
"\nResult:\n" "\nResult:\n"
"{\n" "{\n"
" \"transactions\": [\n" " \"transactions\": [\n"
@ -1775,6 +1790,10 @@ UniValue listsinceblock(const JSONRPCRequest& request)
" \"label\" : \"label\" (string) A comment for the address/transaction, if any\n" " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
" \"to\": \"...\", (string) If a comment to is associated with the transaction.\n" " \"to\": \"...\", (string) If a comment to is associated with the transaction.\n"
" ],\n" " ],\n"
" \"removed\": [\n"
" <structure is the same as \"transactions\" above, only present if include_removed=true>\n"
" Note: transactions that were readded in the active chain will appear as-is in this array, and may thus have a positive confirmation count.\n"
" ],\n"
" \"lastblock\": \"lastblockhash\" (string) The hash of the last block\n" " \"lastblock\": \"lastblockhash\" (string) The hash of the last block\n"
"}\n" "}\n"
"\nExamples:\n" "\nExamples:\n"
@ -1785,21 +1804,19 @@ UniValue listsinceblock(const JSONRPCRequest& request)
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
const CBlockIndex *pindex = NULL; const CBlockIndex* pindex = NULL; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain.
const CBlockIndex* paltindex = NULL; // Block index of the specified block, even if it's in a deactivated chain.
int target_confirms = 1; int target_confirms = 1;
isminefilter filter = ISMINE_SPENDABLE; isminefilter filter = ISMINE_SPENDABLE;
if (!request.params[0].isNull()) if (!request.params[0].isNull()) {
{
uint256 blockId; uint256 blockId;
blockId.SetHex(request.params[0].get_str()); blockId.SetHex(request.params[0].get_str());
BlockMap::iterator it = mapBlockIndex.find(blockId); BlockMap::iterator it = mapBlockIndex.find(blockId);
if (it != mapBlockIndex.end()) if (it != mapBlockIndex.end()) {
{ paltindex = pindex = it->second;
pindex = it->second; if (chainActive[pindex->nHeight] != pindex) {
if (chainActive[pindex->nHeight] != pindex)
{
// the block being asked for is a part of a deactivated chain; // the block being asked for is a part of a deactivated chain;
// we don't want to depend on its perceived height in the block // we don't want to depend on its perceived height in the block
// chain, we want to instead use the last common ancestor // chain, we want to instead use the last common ancestor
@ -1808,19 +1825,20 @@ UniValue listsinceblock(const JSONRPCRequest& request)
} }
} }
if (!request.params[1].isNull()) if (!request.params[1].isNull()) {
{
target_confirms = request.params[1].get_int(); target_confirms = request.params[1].get_int();
if (target_confirms < 1) if (target_confirms < 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
} }
}
if (request.params.size() > 2 && request.params[2].get_bool()) if (!request.params[2].isNull() && request.params[2].get_bool()) {
{
filter = filter | ISMINE_WATCH_ONLY; filter = filter | ISMINE_WATCH_ONLY;
} }
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1; int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1;
UniValue transactions(UniValue::VARR); UniValue transactions(UniValue::VARR);
@ -1828,15 +1846,35 @@ UniValue listsinceblock(const JSONRPCRequest& request)
for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
CWalletTx tx = pairWtx.second; CWalletTx tx = pairWtx.second;
if (depth == -1 || tx.GetDepthInMainChain() < depth) if (depth == -1 || tx.GetDepthInMainChain() < depth) {
ListTransactions(pwallet, tx, "*", 0, true, transactions, filter); ListTransactions(pwallet, tx, "*", 0, true, transactions, filter);
} }
}
// when a reorg'd block is requested, we also list any relevant transactions
// in the blocks of the chain that was detached
UniValue removed(UniValue::VARR);
while (include_removed && paltindex && paltindex != pindex) {
CBlock block;
if (!ReadBlockFromDisk(block, paltindex, Params().GetConsensus())) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
}
for (const CTransactionRef& tx : block.vtx) {
if (pwallet->mapWallet.count(tx->GetHash()) > 0) {
// We want all transactions regardless of confirmation count to appear here,
// even negative confirmation ones, hence the big negative.
ListTransactions(pwallet, pwallet->mapWallet[tx->GetHash()], "*", -100000000, true, removed, filter);
}
}
paltindex = paltindex->pprev;
}
CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms]; CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms];
uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256(); uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256();
UniValue ret(UniValue::VOBJ); UniValue ret(UniValue::VOBJ);
ret.push_back(Pair("transactions", transactions)); ret.push_back(Pair("transactions", transactions));
if (include_removed) ret.push_back(Pair("removed", removed));
ret.push_back(Pair("lastblock", lastblock.GetHex())); ret.push_back(Pair("lastblock", lastblock.GetHex()));
return ret; return ret;
@ -3082,7 +3120,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listlockunspent", &listlockunspent, false, {} }, { "wallet", "listlockunspent", &listlockunspent, false, {} },
{ "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false, {"minconf","include_empty","include_watchonly"} },
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} },
{ "wallet", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly"} }, { "wallet", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
{ "wallet", "listtransactions", &listtransactions, false, {"account","count","skip","include_watchonly"} }, { "wallet", "listtransactions", &listtransactions, false, {"account","count","skip","include_watchonly"} },
{ "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} }, { "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },

Loading…
Cancel
Save