Skip to content

Commit 4e43e6b

Browse files
partial bitcoin#26094: rpc: Return block hash & height in getbalances, gettransaction and getwalletinfo (#1182)
Co-authored-by: Andrew Chow <github@achow101.com>
1 parent 9af554c commit 4e43e6b

File tree

8 files changed

+66
-5
lines changed

8 files changed

+66
-5
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
@@ -452,6 +452,7 @@ RPCHelpMan getbalances()
452452
{RPCResult::Type::STR_AMOUNT, "untrusted_pending", " untrusted pending balance (outputs created by others that are in the mempool)"},
453453
{RPCResult::Type::STR_AMOUNT, "immature", " balance from immature coinbase outputs"},
454454
}},
455+
RESULT_LAST_PROCESSED_BLOCK,
455456
},
456457
},
457458
RPCExamples{
@@ -494,6 +495,8 @@ RPCHelpMan getbalances()
494495
balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature));
495496
balances.pushKV("watchonly", balances_watchonly);
496497
}
498+
499+
AppendLastProcessedBlock(balances, wallet);
497500
return balances;
498501
},
499502
};

src/wallet/rpc/transactions.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,7 @@ RPCHelpMan gettransaction()
736736
{
737737
{RPCResult::Type::ELISION, "", "RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed."},
738738
}},
739+
RESULT_LAST_PROCESSED_BLOCK,
739740
}),
740741
},
741742
RPCExamples{
@@ -795,6 +796,7 @@ RPCHelpMan gettransaction()
795796
entry.pushKV("decoded", decoded);
796797
}
797798

799+
AppendLastProcessedBlock(entry, *pwallet);
798800
return entry;
799801
},
800802
};

src/wallet/rpc/util.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,14 @@ void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& st
164164
throw JSONRPCError(code, error.original);
165165
}
166166
}
167+
168+
void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
169+
{
170+
AssertLockHeld(wallet.cs_wallet);
171+
UniValue lastprocessedblock{UniValue::VOBJ};
172+
lastprocessedblock.pushKV("hash", wallet.GetLastBlockHash().GetHex());
173+
lastprocessedblock.pushKV("height", wallet.GetLastBlockHeight());
174+
entry.pushKV("lastprocessedblock", lastprocessedblock);
175+
}
176+
167177
} // namespace wallet

src/wallet/rpc/util.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#define BITCOIN_WALLET_RPC_UTIL_H
77

88
#include <context.h>
9+
#include <rpc/util.h>
10+
#include <wallet/wallet.h>
911

1012
#include <memory>
1113
#include <string>
@@ -16,13 +18,17 @@ class JSONRPCRequest;
1618
class UniValue;
1719

1820
namespace wallet {
19-
class CWallet;
2021
class LegacyScriptPubKeyMan;
2122
enum class DatabaseStatus;
2223
struct WalletContext;
2324

2425
extern const std::string HELP_REQUIRING_PASSPHRASE;
2526

27+
static const RPCResult RESULT_LAST_PROCESSED_BLOCK { RPCResult::Type::OBJ, "lastprocessedblock", "hash and height of the block this information was generated on",{
28+
{RPCResult::Type::STR_HEX, "hash", "hash of the block this information was generated on"},
29+
{RPCResult::Type::NUM, "height", "height of the block this information was generated on"}}
30+
};
31+
2632
/**
2733
* Figures out what wallet, if any, to use for a JSONRPCRequest.
2834
*
@@ -43,6 +49,7 @@ std::string LabelFromValue(const UniValue& value);
4349

4450
void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error);
4551
int64_t ParseISO8601DateTime(const std::string& str);
52+
void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
4653
} // namespace wallet
4754

4855
#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
@@ -192,6 +192,7 @@ static RPCHelpMan getwalletinfo()
192192
}},
193193
{RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
194194
{RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
195+
RESULT_LAST_PROCESSED_BLOCK,
195196
},
196197
},
197198
RPCExamples{
@@ -270,6 +271,8 @@ static RPCHelpMan getwalletinfo()
270271
}
271272
obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
272273
obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
274+
275+
AppendLastProcessedBlock(obj, *pwallet);
273276
return obj;
274277
},
275278
};

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

@@ -174,8 +175,13 @@ def test_balances(*, fee_node_1=0):
174175
'untrusted_pending': Decimal('30.0') - fee_node_1}} # Doesn't include output of node 0's send since it was spent
175176
if self.options.descriptors:
176177
del expected_balances_0["watchonly"]
177-
assert_equal(self.nodes[0].getbalances(), expected_balances_0)
178-
assert_equal(self.nodes[1].getbalances(), expected_balances_1)
178+
balances_0 = self.nodes[0].getbalances()
179+
balances_1 = self.nodes[1].getbalances()
180+
# remove lastprocessedblock keys (they will be tested later)
181+
del balances_0['lastprocessedblock']
182+
del balances_1['lastprocessedblock']
183+
assert_equal(balances_0, expected_balances_0)
184+
assert_equal(balances_1, expected_balances_1)
179185
# getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
180186
assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send
181187
assert_equal(self.nodes[1].getbalance(), Decimal('0')) # node 1's send had an unsafe input
@@ -304,5 +310,30 @@ def test_balances(*, fee_node_1=0):
304310
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1'))
305311

306312

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

test/functional/wallet_basic.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,8 +710,7 @@ def run_test(self):
710710
"vout": baz["vout"]}
711711
expected_fields = frozenset({'amount', 'chainlock', 'confirmations', 'details', 'fee',
712712
'instantlock', 'instantlock_internal',
713-
'hex', 'timereceived', 'time', 'trusted', 'txid', 'walletconflicts'})
714-
713+
'hex', 'lastprocessedblock', 'timereceived', 'time', 'trusted', 'txid', 'walletconflicts'})
715714
verbose_field = "decoded"
716715
expected_verbose_fields = expected_fields | {verbose_field}
717716

0 commit comments

Comments
 (0)