Skip to content

Commit da9f62f

Browse files
committed
Merge bitcoin/bitcoin#26094: rpc: Return block hash & height in getbalances, gettransaction and getwalletinfo
710b839 rpc: return block hash & height in getbalances, gettransaction & getwalletinfo JSONs (Harris) Pull request description: Reopens #18570 and closes #18567. I have rebased the original PR. Not sure why the original got closed as it was about to get merged. ACKs for top commit: achow101: ACK 710b839 Tree-SHA512: d4478d990be98b1642e9ffb2930987f4a224e8bd64e2e35a5dda927a54c509ec9d712cd0eac35dc2bb89f00a1678e530ce14d7445f1dd93aa3a4cce2bc9b130d
2 parents 7b45d17 + 710b839 commit da9f62f

File tree

9 files changed

+70
-6
lines changed

9 files changed

+70
-6
lines changed

doc/release-notes-26094.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- The `getbalances` RPC now returns a `lastprocessedblock` JSON object which contains the wallet's last processed block
2+
hash and height at the time the balances were calculated. This result shouldn't be cached because importing new keys could invalidate it.
3+
- The `gettransaction` RPC now returns a `lastprocessedblock` JSON object which contains the wallet's last processed block
4+
hash and height at the time the transaction information was generated.
5+
- The `getwalletinfo` RPC now returns a `lastprocessedblock` JSON object which contains the wallet's last processed block
6+
hash and height at the time the wallet information was generated.

src/wallet/rpc/coins.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ RPCHelpMan getbalances()
448448
{RPCResult::Type::STR_AMOUNT, "untrusted_pending", "untrusted pending balance (outputs created by others that are in the mempool)"},
449449
{RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"},
450450
}},
451+
RESULT_LAST_PROCESSED_BLOCK,
451452
}
452453
},
453454
RPCExamples{
@@ -488,6 +489,8 @@ RPCHelpMan getbalances()
488489
balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature));
489490
balances.pushKV("watchonly", balances_watchonly);
490491
}
492+
493+
AppendLastProcessedBlock(balances, wallet);
491494
return balances;
492495
},
493496
};

src/wallet/rpc/transactions.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ RPCHelpMan gettransaction()
731731
{
732732
{RPCResult::Type::ELISION, "", "Equivalent to the RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed."},
733733
}},
734+
RESULT_LAST_PROCESSED_BLOCK,
734735
})
735736
},
736737
RPCExamples{
@@ -791,6 +792,7 @@ RPCHelpMan gettransaction()
791792
entry.pushKV("decoded", decoded);
792793
}
793794

795+
AppendLastProcessedBlock(entry, *pwallet);
794796
return entry;
795797
},
796798
};

src/wallet/rpc/util.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,14 @@ void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& st
177177
throw JSONRPCError(code, error.original);
178178
}
179179
}
180+
181+
void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
182+
{
183+
AssertLockHeld(wallet.cs_wallet);
184+
UniValue lastprocessedblock{UniValue::VOBJ};
185+
lastprocessedblock.pushKV("hash", wallet.GetLastBlockHash().GetHex());
186+
lastprocessedblock.pushKV("height", wallet.GetLastBlockHeight());
187+
entry.pushKV("lastprocessedblock", lastprocessedblock);
188+
}
189+
180190
} // namespace wallet

src/wallet/rpc/util.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
#ifndef BITCOIN_WALLET_RPC_UTIL_H
66
#define BITCOIN_WALLET_RPC_UTIL_H
77

8+
#include <rpc/util.h>
89
#include <script/script.h>
10+
#include <wallet/wallet.h>
911

1012
#include <any>
1113
#include <memory>
@@ -17,13 +19,17 @@ class UniValue;
1719
struct bilingual_str;
1820

1921
namespace wallet {
20-
class CWallet;
2122
class LegacyScriptPubKeyMan;
2223
enum class DatabaseStatus;
2324
struct WalletContext;
2425

2526
extern const std::string HELP_REQUIRING_PASSPHRASE;
2627

28+
static const RPCResult RESULT_LAST_PROCESSED_BLOCK { RPCResult::Type::OBJ, "lastprocessedblock", "hash and height of the block this information was generated on",{
29+
{RPCResult::Type::STR_HEX, "hash", "hash of the block this information was generated on"},
30+
{RPCResult::Type::NUM, "height", "height of the block this information was generated on"}}
31+
};
32+
2733
/**
2834
* Figures out what wallet, if any, to use for a JSONRPCRequest.
2935
*
@@ -45,8 +51,8 @@ std::string LabelFromValue(const UniValue& value);
4551
void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry);
4652

4753
void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error);
48-
4954
int64_t ParseISO8601DateTime(const std::string& str);
55+
void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
5056
} // namespace wallet
5157

5258
#endif // BITCOIN_WALLET_RPC_UTIL_H

src/wallet/rpc/wallet.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ static RPCHelpMan getwalletinfo()
6868
}, /*skip_type_check=*/true},
6969
{RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
7070
{RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
71+
RESULT_LAST_PROCESSED_BLOCK,
7172
}},
7273
},
7374
RPCExamples{
@@ -129,6 +130,8 @@ static RPCHelpMan getwalletinfo()
129130
}
130131
obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
131132
obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
133+
134+
AppendLastProcessedBlock(obj, *pwallet);
132135
return obj;
133136
},
134137
};

test/functional/wallet_balance.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from test_framework.test_framework import BitcoinTestFramework
1212
from test_framework.util import (
1313
assert_equal,
14+
assert_is_hash_string,
1415
assert_raises_rpc_error,
1516
)
1617

@@ -183,8 +184,13 @@ def test_balances(*, fee_node_1=0):
183184
'untrusted_pending': Decimal('30.0') - fee_node_1}} # Doesn't include output of node 0's send since it was spent
184185
if self.options.descriptors:
185186
del expected_balances_0["watchonly"]
186-
assert_equal(self.nodes[0].getbalances(), expected_balances_0)
187-
assert_equal(self.nodes[1].getbalances(), expected_balances_1)
187+
balances_0 = self.nodes[0].getbalances()
188+
balances_1 = self.nodes[1].getbalances()
189+
# remove lastprocessedblock keys (they will be tested later)
190+
del balances_0['lastprocessedblock']
191+
del balances_1['lastprocessedblock']
192+
assert_equal(balances_0, expected_balances_0)
193+
assert_equal(balances_1, expected_balances_1)
188194
# getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
189195
assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send
190196
assert_equal(self.nodes[1].getbalance(), Decimal('0')) # node 1's send had an unsafe input
@@ -309,5 +315,30 @@ def test_balances(*, fee_node_1=0):
309315
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1'))
310316

311317

318+
# Tests the lastprocessedblock JSON object in getbalances, getwalletinfo
319+
# and gettransaction by checking for valid hex strings and by comparing
320+
# the hashes & heights between generated blocks.
321+
self.log.info("Test getbalances returns expected lastprocessedblock json object")
322+
prev_hash = self.nodes[0].getbestblockhash()
323+
prev_height = self.nodes[0].getblock(prev_hash)['height']
324+
self.generatetoaddress(self.nodes[0], 5, self.nodes[0].get_deterministic_priv_key().address)
325+
lastblock = self.nodes[0].getbalances()['lastprocessedblock']
326+
assert_is_hash_string(lastblock['hash'])
327+
assert_equal((prev_hash == lastblock['hash']), False)
328+
assert_equal(lastblock['height'], prev_height + 5)
329+
330+
prev_hash = self.nodes[0].getbestblockhash()
331+
prev_height = self.nodes[0].getblock(prev_hash)['height']
332+
self.log.info("Test getwalletinfo returns expected lastprocessedblock json object")
333+
walletinfo = self.nodes[0].getwalletinfo()
334+
assert_equal(walletinfo['lastprocessedblock']['height'], prev_height)
335+
assert_equal(walletinfo['lastprocessedblock']['hash'], prev_hash)
336+
337+
self.log.info("Test gettransaction returns expected lastprocessedblock json object")
338+
txid = self.nodes[1].sendtoaddress(self.nodes[1].getnewaddress(), 0.01)
339+
tx_info = self.nodes[1].gettransaction(txid)
340+
assert_equal(tx_info['lastprocessedblock']['height'], prev_height)
341+
assert_equal(tx_info['lastprocessedblock']['hash'], prev_hash)
342+
312343
if __name__ == '__main__':
313344
WalletTest().main()

test/functional/wallet_basic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ def run_test(self):
680680
"category": baz["category"],
681681
"vout": baz["vout"]}
682682
expected_fields = frozenset({'amount', 'bip125-replaceable', 'confirmations', 'details', 'fee',
683-
'hex', 'time', 'timereceived', 'trusted', 'txid', 'wtxid', 'walletconflicts'})
683+
'hex', 'lastprocessedblock', 'time', 'timereceived', 'trusted', 'txid', 'wtxid', 'walletconflicts'})
684684
verbose_field = "decoded"
685685
expected_verbose_fields = expected_fields | {verbose_field}
686686

test/functional/wallet_orphanedreward.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ def run_test(self):
6565
assert_equal(self.nodes[0].getbestblockhash(), orig_chain_tip)
6666
self.generate(self.nodes[0], 3)
6767

68-
assert_equal(self.nodes[1].getbalances(), pre_reorg_conf_bals)
68+
balances = self.nodes[1].getbalances()
69+
del balances["lastprocessedblock"]
70+
del pre_reorg_conf_bals["lastprocessedblock"]
71+
assert_equal(balances, pre_reorg_conf_bals)
6972
assert_equal(self.nodes[1].gettransaction(txid)["details"][0]["abandoned"], True)
7073

7174

0 commit comments

Comments
 (0)