RPC: Also serve txo from gettxout (not just utxo and mempool) #10822

Open
wants to merge 1 commit into
from
Jump to file or symbol
Failed to load files and symbols.
+82 −14
Split
View
@@ -46,6 +46,33 @@ static CUpdatedBlock latestblock;
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
+static bool ReadTxoFromOutpoint(Coin& coin, bool& is_spent, const CBlockIndex* pindex, const COutPoint& out, bool include_spent)
+{
+ is_spent = false;
+ if (pcoinsTip->GetCoin(out, coin)) {
+ return true;
+ } else if (!include_spent) {
+ return false;
+ }
+ is_spent = true;
+
+ uint256 hashblock;
+ CTransactionRef tx_ref;
+ if (!GetBlockchainTx(out.hash, tx_ref, Params().GetConsensus(), hashblock, true)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string(fTxIndex ? "No such blockchain transaction"
+ : "No such utxo blockchain transaction. Use -txindex to enable spent output queries"));
+ }
+ if (out.n >= tx_ref->vout.size()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string(strprintf("No output %d for tx %s", out.n, out.hash.GetHex())));
+ }
+ const CTxOut& prevoutput = tx_ref->vout[out.n];
+ coin.out = prevoutput;
+ coin.fCoinBase = tx_ref->IsCoinBase();
+ coin.nHeight = mapBlockIndex[hashblock]->nHeight;
+
+ return true;
+}
+
double GetDifficulty(const CBlockIndex* blockindex)
{
if (blockindex == NULL)
@@ -939,14 +966,15 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request)
UniValue gettxout(const JSONRPCRequest& request)
{
- if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
+ if (request.fHelp || request.params.size() < 2 || request.params.size() > 4)
throw std::runtime_error(
"gettxout \"txid\" n ( include_mempool )\n"
- "\nReturns details about an unspent transaction output.\n"
+ "\nReturns details about a transaction output.\n"
"\nArguments:\n"
- "1. \"txid\" (string, required) The transaction id\n"
- "2. n (numeric, required) vout number\n"
- "3. include_mempool (boolean, optional) Whether to include the mempool\n"
+ "1. \"txid\" (string, required) The transaction id\n"
+ "2. \"n\" (numeric, required) vout number\n"
+ "3. \"include_mempool\" (boolean, optional) Only search in the mempool. Default: true\n"
+ "4. \"include_spent\" (boolean, optional) Whether to include spent outputs. Default: false\n"
"\nResult:\n"
"{\n"
" \"bestblock\" : \"hash\", (string) the block hash\n"
@@ -964,6 +992,7 @@ UniValue gettxout(const JSONRPCRequest& request)
" },\n"
" \"version\" : n, (numeric) The version\n"
" \"coinbase\" : true|false (boolean) Coinbase or not\n"
+ " \"spent\" : true|false (boolean) Is the output spent with the current tip or not\n"
"}\n"
"\nExamples:\n"
@@ -987,6 +1016,12 @@ UniValue gettxout(const JSONRPCRequest& request)
if (request.params.size() > 2)
fMempool = request.params[2].get_bool();
+ bool include_spent = false;
+ if (request.params.size() > 3) {
+ include_spent = request.params[3].get_bool();
+ }
+
+ bool is_spent = false;
Coin coin;
if (fMempool) {
LOCK(mempool.cs);
@@ -995,7 +1030,7 @@ UniValue gettxout(const JSONRPCRequest& request)
return NullUniValue;
}
} else {
- if (!pcoinsTip->GetCoin(out, coin)) {
+ if (!ReadTxoFromOutpoint(coin, is_spent, chainActive.Tip(), out, include_spent)) {
return NullUniValue;
}
}
@@ -1013,6 +1048,7 @@ UniValue gettxout(const JSONRPCRequest& request)
ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
ret.push_back(Pair("scriptPubKey", o));
ret.push_back(Pair("coinbase", (bool)coin.fCoinBase));
+ ret.push_back(Pair("spent", (bool)is_spent));
return ret;
}
View
@@ -93,6 +93,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "fundrawtransaction", 1, "options" },
{ "gettxout", 1, "n" },
{ "gettxout", 2, "include_mempool" },
+ { "gettxout", 3, "include_spent" },
{ "gettxoutproof", 0, "txids" },
{ "lockunspent", 0, "unlock" },
{ "lockunspent", 1, "transactions" },
View
@@ -900,19 +900,12 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
}
/** Return transaction in txOut, and if it was found inside a block, its hash is placed in hashBlock */
-bool GetTransaction(const uint256 &hash, CTransactionRef &txOut, const Consensus::Params& consensusParams, uint256 &hashBlock, bool fAllowSlow)
+bool GetBlockchainTx(const uint256 &hash, CTransactionRef &txOut, const Consensus::Params& consensusParams, uint256 &hashBlock, bool fAllowSlow)
{
CBlockIndex *pindexSlow = NULL;
LOCK(cs_main);
- CTransactionRef ptx = mempool.get(hash);
- if (ptx)
- {
- txOut = ptx;
- return true;
- }
-
if (fTxIndex) {
CDiskTxPos postx;
if (pblocktree->ReadTxIndex(hash, postx)) {
@@ -955,6 +948,23 @@ bool GetTransaction(const uint256 &hash, CTransactionRef &txOut, const Consensus
return false;
}
+/**
+ * Return transaction in txOut, and if it was found inside a block,
+ * its hash is placed in hashBlock. Look in the mempool first.
+*/
+bool GetTransaction(const uint256 &hash, CTransactionRef &txOut, const Consensus::Params& consensusParams, uint256 &hashBlock, bool fAllowSlow)
+{
+ LOCK(cs_main);
+
+ CTransactionRef ptx = mempool.get(hash);
+ if (ptx)
+ {
+ txOut = ptx;
+ return true;
+ }
+ return GetBlockchainTx(hash, txOut, consensusParams, hashBlock, fAllowSlow);
+}
+
View
@@ -276,6 +276,8 @@ bool IsInitialBlockDownload();
* This function only returns the highest priority warning of the set selected by strFor.
*/
std::string GetWarnings(const std::string& strFor);
+/** Retrieve a transaction (from disk (not mempool), if possible) */
+bool GetBlockchainTx(const uint256 &hash, CTransactionRef &txOut, const Consensus::Params& consensusParams, uint256 &hashBlock, bool fAllowSlow);
/** Retrieve a transaction (from memory pool, or from disk, if possible) */
bool GetTransaction(const uint256 &hash, CTransactionRef &tx, const Consensus::Params& params, uint256 &hashBlock, bool fAllowSlow = false);
/** Find the best known block, and make it the tip of the block chain */
View
@@ -19,6 +19,7 @@ def __init__(self):
self.setup_clean_chain = True
self.num_nodes = 4
self.extra_args = [['-usehd={:d}'.format(i%2==0)] for i in range(4)]
+ self.extra_args[0].append('-txindex')
def setup_network(self):
self.nodes = self.start_nodes(3, self.options.tmpdir, self.extra_args[:3])
@@ -71,6 +72,8 @@ def run_test(self):
confirmed_txid, confirmed_index = utxos[0]["txid"], utxos[0]["vout"]
txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False)
assert_equal(txout['value'], 50)
+ assert_equal(txout['confirmations'], 102)
+ assert_equal(txout['spent'], False)
txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, True)
assert txout is None
# new utxo from mempool should be invisible if you exclude mempool
@@ -83,13 +86,29 @@ def run_test(self):
# but 10 will go to node2 and the rest will go to node0
balance = self.nodes[0].getbalance()
assert_equal(set([txout1['value'], txout2['value']]), set([10, balance]))
+ assert_equal(txout1['spent'], False)
walletinfo = self.nodes[0].getwalletinfo()
assert_equal(walletinfo['immature_balance'], 0)
+ assert_raises_jsonrpc(-5, 'No such blockchain transaction', self.nodes[0].gettxout, mempool_txid, 0, False, True)
# Have node0 mine a block, thus it will collect its own fee.
self.nodes[0].generate(1)
self.sync_all()
+ # Now the txo should be visible only if you include spent outputs
+ txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False, False)
+ assert txout is None
+ txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, True, False)
+ assert txout is None
+ txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, True, True)
+ assert txout is None
+ txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False, True)
+ assert_equal(txout['value'], 50)
+ assert_equal(txout['confirmations'], 103)
+ assert_equal(txout['spent'], True)
+ assert_raises_jsonrpc(-5, 'No output %d for tx %s' % (100, confirmed_txid), self.nodes[0].gettxout, confirmed_txid, 100, False, True)
+ assert_raises_jsonrpc(-5, 'No such utxo blockchain transaction. Use -txindex to enable spent output queries', self.nodes[1].gettxout, confirmed_txid, confirmed_index, False, True)
+
# Exercise locking of unspent outputs
unspent_0 = self.nodes[2].listunspent()[0]
unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]}