Auto merge of #4574 - oxarbitrage:issue4475, r=daira

Add blockhash parameter to getrawtransaction

Code ported manually from https://github.com/bitcoin/bitcoin/pull/10275 in attempt to fix https://github.com/zcash/zcash/issues/4475
This commit is contained in:
Homu 2020-11-10 15:52:11 +00:00
commit 60331b9e83
4 changed files with 114 additions and 53 deletions

View File

@ -11,7 +11,7 @@
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException from test_framework.authproxy import JSONRPCException
from test_framework.util import assert_equal, initialize_chain_clean, \ from test_framework.util import assert_equal, initialize_chain_clean, \
start_nodes, connect_nodes_bi start_nodes, connect_nodes_bi, assert_raises
from decimal import Decimal from decimal import Decimal
@ -68,6 +68,30 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal("Missing inputs" in errorString, True); assert_equal("Missing inputs" in errorString, True);
#####################################
# getrawtransaction with block hash #
#####################################
# make a tx by sending then generate 2 blocks; block1 has the tx in it
tx = self.nodes[2].sendtoaddress(self.nodes[1].getnewaddress(), 1)
block1, block2 = self.nodes[2].generate(2)
self.sync_all()
# We should be able to get the raw transaction by providing the correct block
gottx = self.nodes[0].getrawtransaction(tx, 1, block1)
assert_equal(gottx['txid'], tx)
assert_equal(gottx['in_active_chain'], True)
# We should not have the 'in_active_chain' flag when we don't provide a block
gottx = self.nodes[0].getrawtransaction(tx, 1)
assert_equal(gottx['txid'], tx)
assert 'in_active_chain' not in gottx
# We should not get the tx if we provide an unrelated block
assert_raises(JSONRPCException, self.nodes[0].getrawtransaction, tx, 1, block2)
# An invalid block hash should raise errors
assert_raises(JSONRPCException, self.nodes[0].getrawtransaction, tx, 1, True)
assert_raises(JSONRPCException, self.nodes[0].getrawtransaction, tx, 1, "foobar")
assert_raises(JSONRPCException, self.nodes[0].getrawtransaction, tx, 1, "abcd1234")
assert_raises(JSONRPCException, self.nodes[0].getrawtransaction, tx, 1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
######################### #########################
# RAW TX MULTISIG TESTS # # RAW TX MULTISIG TESTS #
######################### #########################

View File

@ -1768,13 +1768,17 @@ bool GetAddressUnspent(const uint160& addressHash, int type,
return true; return true;
} }
/** Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock */ /**
bool GetTransaction(const uint256 &hash, CTransaction &txOut, const Consensus::Params& consensusParams, uint256 &hashBlock, bool fAllowSlow) * Return transaction in txOut, and if it was found inside a block, its hash is placed in hashBlock.
* If blockIndex is provided, the transaction is fetched from the corresponding block.
*/
bool GetTransaction(const uint256& hash, CTransaction& txOut, const Consensus::Params& consensusParams, uint256& hashBlock, bool fAllowSlow, CBlockIndex* blockIndex)
{ {
CBlockIndex *pindexSlow = NULL; CBlockIndex* pindexSlow = blockIndex;
LOCK(cs_main); LOCK(cs_main);
if (!blockIndex) {
if (mempool.lookup(hash, txOut)) if (mempool.lookup(hash, txOut))
{ {
return true; return true;
@ -1799,6 +1803,9 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, const Consensus::P
return error("%s: txid mismatch", __func__); return error("%s: txid mismatch", __func__);
return true; return true;
} }
// transaction not found in index, nothing more can be done
return false;
} }
if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it
@ -1812,6 +1819,7 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, const Consensus::P
if (nHeight > 0) if (nHeight > 0)
pindexSlow = chainActive[nHeight]; pindexSlow = chainActive[nHeight];
} }
}
if (pindexSlow) { if (pindexSlow) {
CBlock block; CBlock block;
@ -1829,11 +1837,6 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, const Consensus::P
return false; return false;
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// //
// CBlock and CBlockIndex // CBlock and CBlockIndex

View File

@ -260,7 +260,7 @@ bool IsInitialBlockDownload(const CChainParams& chainParams);
/** Pair of timestamp and formatted string that describes several potential problems detected by the core */ /** Pair of timestamp and formatted string that describes several potential problems detected by the core */
std::pair<std::string, int64_t> GetWarnings(const std::string& strFor); std::pair<std::string, int64_t> GetWarnings(const std::string& strFor);
/** Retrieve a transaction (from memory pool, or from disk, if possible) */ /** Retrieve a transaction (from memory pool, or from disk, if possible) */
bool GetTransaction(const uint256 &hash, CTransaction &tx, const Consensus::Params& params, uint256 &hashBlock, bool fAllowSlow = false); bool GetTransaction(const uint256& hash, CTransaction& tx, const Consensus::Params& params, uint256& hashBlock, bool fAllowSlow = false, CBlockIndex* blockIndex = nullptr);
/** Find the best known block, and make it the tip of the block chain */ /** Find the best known block, and make it the tip of the block chain */
bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, const CBlock* pblock = NULL); bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, const CBlock* pblock = NULL);
CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams); CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams);

View File

@ -268,25 +268,30 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
UniValue getrawtransaction(const UniValue& params, bool fHelp) UniValue getrawtransaction(const UniValue& params, bool fHelp)
{ {
if (fHelp || params.size() < 1 || params.size() > 2) if (fHelp || params.size() < 1 || params.size() > 3)
throw runtime_error( throw runtime_error(
"getrawtransaction \"txid\" ( verbose )\n" "getrawtransaction \"txid\" ( verbose \"blockhash\" )\n"
"\nNOTE: By default this function only works sometimes. This is when the tx is in the mempool\n" "\nNOTE: If \"blockhash\" is not provided and the -txindex option is not enabled, then this call only\n"
"or there is an unspent output in the utxo for this transaction. To make it always work,\n" "works for mempool transactions. If either \"blockhash\" is provided or the -txindex option is\n"
"you need to maintain a transaction index, using the -txindex command line option.\n" "enabled, it also works for blockchain transactions. If the block which contains the transaction\n"
"is known, its hash can be provided even for nodes without -txindex. Note that if a blockhash is\n"
"provided, only that block will be searched and if the transaction is in the mempool or other\n"
"blocks, or if this node does not have the given block available, the transaction will not be found.\n"
"\nReturn the raw transaction data.\n" "\nReturn the raw transaction data.\n"
"\nIf verbose=0, returns a string that is serialized, hex-encoded data for 'txid'.\n" "\nIf verbose=0, returns a string that is serialized, hex-encoded data for 'txid'.\n"
"If verbose is non-zero, returns an Object with information about 'txid'.\n" "If verbose is non-zero, returns an Object with information about 'txid'.\n"
"\nArguments:\n" "\nArguments:\n"
"1. \"txid\" (string, required) The transaction id\n" "1. \"txid\" (string, required) The transaction id\n"
"2. verbose (numeric, optional, default=0) If 0, return a string, other return a json object\n" "2. verbose (numeric, optional, default=0) If 0, return a string of hex-encoded data, otherwise return a JSON object\n"
"3. \"blockhash\" (string, optional) The block in which to look for the transaction\n"
"\nResult (if verbose is not set or set to 0):\n" "\nResult (if verbose is not set or set to 0):\n"
"\"data\" (string) The serialized, hex-encoded data for 'txid'\n" "\"data\" (string) The serialized, hex-encoded data for 'txid'\n"
"\nResult (if verbose > 0):\n" "\nResult (if verbose > 0):\n"
"{\n" "{\n"
" \"in_active_chain\": b, (bool) Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)\n"
" \"hex\" : \"data\", (string) The serialized, hex-encoded data for 'txid'\n" " \"hex\" : \"data\", (string) The serialized, hex-encoded data for 'txid'\n"
" \"txid\" : \"id\", (string) The transaction id (same as provided)\n" " \"txid\" : \"id\", (string) The transaction id (same as provided)\n"
" \"size\" : n, (numeric) The transaction size\n" " \"size\" : n, (numeric) The transaction size\n"
@ -359,20 +364,48 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp)
+ HelpExampleCli("getrawtransaction", "\"mytxid\"") + HelpExampleCli("getrawtransaction", "\"mytxid\"")
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 1") + HelpExampleCli("getrawtransaction", "\"mytxid\" 1")
+ HelpExampleRpc("getrawtransaction", "\"mytxid\", 1") + HelpExampleRpc("getrawtransaction", "\"mytxid\", 1")
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 0 \"myblockhash\"")
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 1 \"myblockhash\"")
); );
LOCK(cs_main);
bool in_active_chain = true;
uint256 hash = ParseHashV(params[0], "parameter 1"); uint256 hash = ParseHashV(params[0], "parameter 1");
CBlockIndex* blockindex = nullptr;
bool fVerbose = false; bool fVerbose = false;
if (params.size() > 1) if (params.size() > 1)
fVerbose = (params[1].get_int() != 0); fVerbose = (params[1].get_int() != 0);
LOCK(cs_main); if (params.size() > 2) {
uint256 blockhash = ParseHashV(params[2], "parameter 3");
if (!blockhash.IsNull()) {
BlockMap::iterator it = mapBlockIndex.find(blockhash);
if (it == mapBlockIndex.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block hash not found");
}
blockindex = it->second;
in_active_chain = chainActive.Contains(blockindex);
}
}
CTransaction tx; CTransaction tx;
uint256 hashBlock; uint256 hash_block;
if (!GetTransaction(hash, tx, Params().GetConsensus(), hashBlock, true)) if (!GetTransaction(hash, tx, Params().GetConsensus(), hash_block, true, blockindex)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); std::string errmsg;
if (blockindex) {
if (!(blockindex->nStatus & BLOCK_HAVE_DATA)) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available");
}
errmsg = "No such transaction found in the provided block";
} else {
errmsg = fTxIndex
? "No such mempool or blockchain transaction"
: "No such mempool transaction. Use -txindex to enable blockchain transaction queries";
}
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errmsg + ". Use gettransaction for wallet transactions.");
}
string strHex = EncodeHexTx(tx); string strHex = EncodeHexTx(tx);
@ -380,8 +413,9 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp)
return strHex; return strHex;
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
if (blockindex) result.pushKV("in_active_chain", in_active_chain);
result.pushKV("hex", strHex); result.pushKV("hex", strHex);
TxToJSON(tx, hashBlock, result); TxToJSON(tx, hash_block, result);
return result; return result;
} }