aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarris <brakmic@gmail.com>2020-04-27 10:45:03 +0200
committerAurèle Oulès <aurele@oules.com>2023-04-26 16:07:47 +0200
commit710b83938ab5bbc4bd324d8b2e69461a2a1d2eec (patch)
treee78465394ff8286c4969d565209f18bd8252c5a3
parent91ccb62faab21b2b52b089cc04f3a5c1bf6989cc (diff)
rpc: return block hash & height in getbalances, gettransaction & getwalletinfo JSONs
Co-authored-by: Aurèle Oulès <aurele@oules.com>
-rw-r--r--doc/release-notes-26094.md6
-rw-r--r--src/wallet/rpc/coins.cpp3
-rw-r--r--src/wallet/rpc/transactions.cpp2
-rw-r--r--src/wallet/rpc/util.cpp10
-rw-r--r--src/wallet/rpc/util.h10
-rw-r--r--src/wallet/rpc/wallet.cpp3
-rwxr-xr-xtest/functional/wallet_balance.py35
-rwxr-xr-xtest/functional/wallet_basic.py2
-rwxr-xr-xtest/functional/wallet_orphanedreward.py5
9 files changed, 70 insertions, 6 deletions
diff --git a/doc/release-notes-26094.md b/doc/release-notes-26094.md
new file mode 100644
index 0000000000..ba73f2707e
--- /dev/null
+++ b/doc/release-notes-26094.md
@@ -0,0 +1,6 @@
+- The `getbalances` RPC now returns a `lastprocessedblock` JSON object which contains the wallet's last processed block
+ hash and height at the time the balances were calculated. This result shouldn't be cached because importing new keys could invalidate it.
+- The `gettransaction` RPC now returns a `lastprocessedblock` JSON object which contains the wallet's last processed block
+ hash and height at the time the transaction information was generated.
+- The `getwalletinfo` RPC now returns a `lastprocessedblock` JSON object which contains the wallet's last processed block
+ hash and height at the time the wallet information was generated. \ No newline at end of file
diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp
index 4c386789f1..750ef69f6e 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -448,6 +448,7 @@ RPCHelpMan getbalances()
{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"},
}},
+ RESULT_LAST_PROCESSED_BLOCK,
}
},
RPCExamples{
@@ -488,6 +489,8 @@ RPCHelpMan getbalances()
balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature));
balances.pushKV("watchonly", balances_watchonly);
}
+
+ AppendLastProcessedBlock(balances, wallet);
return balances;
},
};
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index eb4f4c87ae..c34391e6e8 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -731,6 +731,7 @@ RPCHelpMan gettransaction()
{
{RPCResult::Type::ELISION, "", "Equivalent to the RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed."},
}},
+ RESULT_LAST_PROCESSED_BLOCK,
})
},
RPCExamples{
@@ -791,6 +792,7 @@ RPCHelpMan gettransaction()
entry.pushKV("decoded", decoded);
}
+ AppendLastProcessedBlock(entry, *pwallet);
return entry;
},
};
diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp
index 4d82e0a41f..4ff44b84b0 100644
--- a/src/wallet/rpc/util.cpp
+++ b/src/wallet/rpc/util.cpp
@@ -177,4 +177,14 @@ void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& st
throw JSONRPCError(code, error.original);
}
}
+
+void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+{
+ AssertLockHeld(wallet.cs_wallet);
+ UniValue lastprocessedblock{UniValue::VOBJ};
+ lastprocessedblock.pushKV("hash", wallet.GetLastBlockHash().GetHex());
+ lastprocessedblock.pushKV("height", wallet.GetLastBlockHeight());
+ entry.pushKV("lastprocessedblock", lastprocessedblock);
+}
+
} // namespace wallet
diff --git a/src/wallet/rpc/util.h b/src/wallet/rpc/util.h
index d5d6ac0dfa..2fdba04352 100644
--- a/src/wallet/rpc/util.h
+++ b/src/wallet/rpc/util.h
@@ -5,7 +5,9 @@
#ifndef BITCOIN_WALLET_RPC_UTIL_H
#define BITCOIN_WALLET_RPC_UTIL_H
+#include <rpc/util.h>
#include <script/script.h>
+#include <wallet/wallet.h>
#include <any>
#include <memory>
@@ -17,13 +19,17 @@ class UniValue;
struct bilingual_str;
namespace wallet {
-class CWallet;
class LegacyScriptPubKeyMan;
enum class DatabaseStatus;
struct WalletContext;
extern const std::string HELP_REQUIRING_PASSPHRASE;
+static const RPCResult RESULT_LAST_PROCESSED_BLOCK { RPCResult::Type::OBJ, "lastprocessedblock", "hash and height of the block this information was generated on",{
+ {RPCResult::Type::STR_HEX, "hash", "hash of the block this information was generated on"},
+ {RPCResult::Type::NUM, "height", "height of the block this information was generated on"}}
+};
+
/**
* Figures out what wallet, if any, to use for a JSONRPCRequest.
*
@@ -45,8 +51,8 @@ std::string LabelFromValue(const UniValue& value);
void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry);
void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error);
-
int64_t ParseISO8601DateTime(const std::string& str);
+void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
} // namespace wallet
#endif // BITCOIN_WALLET_RPC_UTIL_H
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index ea3507bc75..a3a2a7b89c 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -68,6 +68,7 @@ static RPCHelpMan getwalletinfo()
}, /*skip_type_check=*/true},
{RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
{RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
+ RESULT_LAST_PROCESSED_BLOCK,
}},
},
RPCExamples{
@@ -129,6 +130,8 @@ static RPCHelpMan getwalletinfo()
}
obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
+
+ AppendLastProcessedBlock(obj, *pwallet);
return obj;
},
};
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index 9ed2caefb7..af9270a321 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -11,6 +11,7 @@ from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ assert_is_hash_string,
assert_raises_rpc_error,
)
@@ -183,8 +184,13 @@ class WalletTest(BitcoinTestFramework):
'untrusted_pending': Decimal('30.0') - fee_node_1}} # Doesn't include output of node 0's send since it was spent
if self.options.descriptors:
del expected_balances_0["watchonly"]
- assert_equal(self.nodes[0].getbalances(), expected_balances_0)
- assert_equal(self.nodes[1].getbalances(), expected_balances_1)
+ balances_0 = self.nodes[0].getbalances()
+ balances_1 = self.nodes[1].getbalances()
+ # remove lastprocessedblock keys (they will be tested later)
+ del balances_0['lastprocessedblock']
+ del balances_1['lastprocessedblock']
+ assert_equal(balances_0, expected_balances_0)
+ assert_equal(balances_1, expected_balances_1)
# getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send
assert_equal(self.nodes[1].getbalance(), Decimal('0')) # node 1's send had an unsafe input
@@ -309,5 +315,30 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1'))
+ # Tests the lastprocessedblock JSON object in getbalances, getwalletinfo
+ # and gettransaction by checking for valid hex strings and by comparing
+ # the hashes & heights between generated blocks.
+ self.log.info("Test getbalances returns expected lastprocessedblock json object")
+ prev_hash = self.nodes[0].getbestblockhash()
+ prev_height = self.nodes[0].getblock(prev_hash)['height']
+ self.generatetoaddress(self.nodes[0], 5, self.nodes[0].get_deterministic_priv_key().address)
+ lastblock = self.nodes[0].getbalances()['lastprocessedblock']
+ assert_is_hash_string(lastblock['hash'])
+ assert_equal((prev_hash == lastblock['hash']), False)
+ assert_equal(lastblock['height'], prev_height + 5)
+
+ prev_hash = self.nodes[0].getbestblockhash()
+ prev_height = self.nodes[0].getblock(prev_hash)['height']
+ self.log.info("Test getwalletinfo returns expected lastprocessedblock json object")
+ walletinfo = self.nodes[0].getwalletinfo()
+ assert_equal(walletinfo['lastprocessedblock']['height'], prev_height)
+ assert_equal(walletinfo['lastprocessedblock']['hash'], prev_hash)
+
+ self.log.info("Test gettransaction returns expected lastprocessedblock json object")
+ txid = self.nodes[1].sendtoaddress(self.nodes[1].getnewaddress(), 0.01)
+ tx_info = self.nodes[1].gettransaction(txid)
+ assert_equal(tx_info['lastprocessedblock']['height'], prev_height)
+ assert_equal(tx_info['lastprocessedblock']['hash'], prev_hash)
+
if __name__ == '__main__':
WalletTest().main()
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 53ac01686a..6f33e8159d 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -666,7 +666,7 @@ class WalletTest(BitcoinTestFramework):
"category": baz["category"],
"vout": baz["vout"]}
expected_fields = frozenset({'amount', 'bip125-replaceable', 'confirmations', 'details', 'fee',
- 'hex', 'time', 'timereceived', 'trusted', 'txid', 'wtxid', 'walletconflicts'})
+ 'hex', 'lastprocessedblock', 'time', 'timereceived', 'trusted', 'txid', 'wtxid', 'walletconflicts'})
verbose_field = "decoded"
expected_verbose_fields = expected_fields | {verbose_field}
diff --git a/test/functional/wallet_orphanedreward.py b/test/functional/wallet_orphanedreward.py
index d8931fa620..451f8abb0a 100755
--- a/test/functional/wallet_orphanedreward.py
+++ b/test/functional/wallet_orphanedreward.py
@@ -65,7 +65,10 @@ class OrphanedBlockRewardTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getbestblockhash(), orig_chain_tip)
self.generate(self.nodes[0], 3)
- assert_equal(self.nodes[1].getbalances(), pre_reorg_conf_bals)
+ balances = self.nodes[1].getbalances()
+ del balances["lastprocessedblock"]
+ del pre_reorg_conf_bals["lastprocessedblock"]
+ assert_equal(balances, pre_reorg_conf_bals)
assert_equal(self.nodes[1].gettransaction(txid)["details"][0]["abandoned"], True)