0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-03-05 14:06:27 -05:00

Merge bitcoin/bitcoin#26094: rpc: Return block hash & height in getbalances, gettransaction and getwalletinfo

710b83938a 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 710b83938a

Tree-SHA512: d4478d990be98b1642e9ffb2930987f4a224e8bd64e2e35a5dda927a54c509ec9d712cd0eac35dc2bb89f00a1678e530ce14d7445f1dd93aa3a4cce2bc9b130d
This commit is contained in:
Andrew Chow 2023-05-02 11:42:50 -04:00
commit da9f62f912
No known key found for this signature in database
GPG key ID: 17565732E08E5E41
9 changed files with 70 additions and 6 deletions

View file

@ -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.

View file

@ -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;
},
};

View file

@ -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;
},
};

View file

@ -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

View file

@ -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

View file

@ -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;
},
};

View file

@ -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()

View file

@ -680,7 +680,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}

View file

@ -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)