Skip to content

Commit 497d0e0

Browse files
committed
Merge #10275: [rpc] Allow fetching tx directly from specified block in getrawtransaction
434526a [test] Add tests for getrawtransaction with block hash. (Karl-Johan Alm) b167951 [rpc] Allow getrawtransaction to take optional blockhash to fetch transaction from a block directly. (Karl-Johan Alm) a5f5a2c [rpc] Fix fVerbose parsing (remove excess if cases). (Karl-Johan Alm) Pull request description: [Reviewer hint: use [?w=1](https://github.com/bitcoin/bitcoin/pull/10275/files?w=1) to avoid seeing a bunch of indentation changes.] Presuming a user knows the block hash of the block containing a given transaction, this PR allows them to fetch the raw transaction, even without `-txindex`. It also enables support for getting transactions that are in orphaned blocks. Note that supplying a block hash will override mempool and txindex support in `GetTransaction`. The rationale behind this is that a transaction may be in multiple places (orphaned blocks) and if the user supplies an explicit block hash it should be adhered to. ```Bash $ # a41.. is a tx inside an orphan block ..3c6f.. -- first try getting it normally $ ./bitcoin-cli getrawtransaction a41e66ee1341aa9fb9475b98cfdc1fe1261faa56c0a49254f33065ec90f7cd79 1 error code: -5 error message: No such mempool transaction. Use -txindex to enable blockchain transaction queries. Use gettransaction for wallet transactions. $ # now try with block hash $ ./bitcoin-cli getrawtransaction a41e66ee1341aa9fb9475b98cfdc1fe1261faa56c0a49254f33065ec90f7cd79 1 0000000000000000003c6fe479122bfa4a9187493937af1734e1e5cd9f198ec7 { "hex": "01000000014e7e81144e42f6d65550e59b715d470c9301fd7ac189[...]90488ac00000000", "inMainChain": false, "txid": "a41e66ee1341aa9fb9475b98cfdc1fe1261faa56c0a49254f33065ec90f7cd79", "hash": "a41e66ee1341aa9fb9475b98cfdc1fe1261faa56c0a49254f33065ec90f7cd79", "size": 225, [...] } $ # another tx 6c66... in block 462000 $ ./bitcoin-cli getrawtransaction 6c66b98191e9d6cc671f6817142152ebf6c5cab2ef008397b5a71ac13255a735 1 00000000000000000217f2c12922e321f6d4aa933ce88005a9a493c503054a40 { "hex": "0200000004d157[...]88acaf0c0700", "inMainChain": true, "txid": "6c66b98191e9d6cc671f6817142152ebf6c5cab2ef008397b5a71ac13255a735", "hash": "6c66b98191e9d6cc671f6817142152ebf6c5cab2ef008397b5a71ac13255a735", "size": 666, [...] } $ ``` Tree-SHA512: 279be3818141edd3cc194a9ee65929331920afb30297ab2d6da07293a2d7311afee5c8b00c6457477d9f1f86e86786a9b56878ea3ee19fa2629b829d042d0cda
2 parents a13e443 + 434526a commit 497d0e0

File tree

4 files changed

+109
-60
lines changed

4 files changed

+109
-60
lines changed

src/rpc/rawtransaction.cpp

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,15 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
6464

6565
UniValue getrawtransaction(const JSONRPCRequest& request)
6666
{
67-
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
67+
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
6868
throw std::runtime_error(
69-
"getrawtransaction \"txid\" ( verbose )\n"
69+
"getrawtransaction \"txid\" ( verbose \"blockhash\" )\n"
7070

7171
"\nNOTE: By default this function only works for mempool transactions. If the -txindex option is\n"
72-
"enabled, it also works for blockchain transactions.\n"
72+
"enabled, it also works for blockchain transactions. If the block which contains the transaction\n"
73+
"is known, its hash can be provided even for nodes without -txindex. Note that if a blockhash is\n"
74+
"provided, only that block will be searched and if the transaction is in the mempool or other\n"
75+
"blocks, or if this node does not have the given block available, the transaction will not be found.\n"
7376
"DEPRECATED: for now, it also works for transactions with unspent outputs.\n"
7477

7578
"\nReturn the raw transaction data.\n"
@@ -78,13 +81,15 @@ UniValue getrawtransaction(const JSONRPCRequest& request)
7881

7982
"\nArguments:\n"
8083
"1. \"txid\" (string, required) The transaction id\n"
81-
"2. verbose (bool, optional, default=false) If false, return a string, otherwise return a json object\n"
84+
"2. verbose (bool, optional, default=false) If false, return a string, otherwise return a json object\n"
85+
"3. \"blockhash\" (string, optional) The block in which to look for the transaction\n"
8286

8387
"\nResult (if verbose is not set or set to false):\n"
8488
"\"data\" (string) The serialized, hex-encoded data for 'txid'\n"
8589

8690
"\nResult (if verbose is set to true):\n"
8791
"{\n"
92+
" \"in_active_chain\": b, (bool) Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)\n"
8893
" \"hex\" : \"data\", (string) The serialized, hex-encoded data for 'txid'\n"
8994
" \"txid\" : \"id\", (string) The transaction id (same as provided)\n"
9095
" \"hash\" : \"id\", (string) The transaction hash (differs from txid for witness transactions)\n"
@@ -132,42 +137,58 @@ UniValue getrawtransaction(const JSONRPCRequest& request)
132137
+ HelpExampleCli("getrawtransaction", "\"mytxid\"")
133138
+ HelpExampleCli("getrawtransaction", "\"mytxid\" true")
134139
+ HelpExampleRpc("getrawtransaction", "\"mytxid\", true")
140+
+ HelpExampleCli("getrawtransaction", "\"mytxid\" false \"myblockhash\"")
141+
+ HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"")
135142
);
136143

137144
LOCK(cs_main);
138145

146+
bool in_active_chain = true;
139147
uint256 hash = ParseHashV(request.params[0], "parameter 1");
148+
CBlockIndex* blockindex = nullptr;
140149

141150
// Accept either a bool (true) or a num (>=1) to indicate verbose output.
142151
bool fVerbose = false;
143152
if (!request.params[1].isNull()) {
144-
if (request.params[1].isNum()) {
145-
if (request.params[1].get_int() != 0) {
146-
fVerbose = true;
147-
}
148-
}
149-
else if(request.params[1].isBool()) {
150-
if(request.params[1].isTrue()) {
151-
fVerbose = true;
153+
fVerbose = request.params[1].isNum() ? (request.params[1].get_int() != 0) : request.params[1].get_bool();
154+
}
155+
156+
if (!request.params[2].isNull()) {
157+
uint256 blockhash = ParseHashV(request.params[2], "parameter 3");
158+
if (!blockhash.IsNull()) {
159+
BlockMap::iterator it = mapBlockIndex.find(blockhash);
160+
if (it == mapBlockIndex.end()) {
161+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block hash not found");
152162
}
153-
}
154-
else {
155-
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid type provided. Verbose parameter must be a boolean.");
163+
blockindex = it->second;
164+
in_active_chain = chainActive.Contains(blockindex);
156165
}
157166
}
158167

159168
CTransactionRef tx;
160-
uint256 hashBlock;
161-
if (!GetTransaction(hash, tx, Params().GetConsensus(), hashBlock, true))
162-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string(fTxIndex ? "No such mempool or blockchain transaction"
163-
: "No such mempool transaction. Use -txindex to enable blockchain transaction queries") +
164-
". Use gettransaction for wallet transactions.");
169+
uint256 hash_block;
170+
if (!GetTransaction(hash, tx, Params().GetConsensus(), hash_block, true, blockindex)) {
171+
std::string errmsg;
172+
if (blockindex) {
173+
if (!(blockindex->nStatus & BLOCK_HAVE_DATA)) {
174+
throw JSONRPCError(RPC_MISC_ERROR, "Block not available");
175+
}
176+
errmsg = "No such transaction found in the provided block";
177+
} else {
178+
errmsg = fTxIndex
179+
? "No such mempool or blockchain transaction"
180+
: "No such mempool transaction. Use -txindex to enable blockchain transaction queries";
181+
}
182+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errmsg + ". Use gettransaction for wallet transactions.");
183+
}
165184

166-
if (!fVerbose)
185+
if (!fVerbose) {
167186
return EncodeHexTx(*tx, RPCSerializationFlags());
187+
}
168188

169189
UniValue result(UniValue::VOBJ);
170-
TxToJSON(*tx, hashBlock, result);
190+
if (blockindex) result.push_back(Pair("in_active_chain", in_active_chain));
191+
TxToJSON(*tx, hash_block, result);
171192
return result;
172193
}
173194

@@ -995,7 +1016,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
9951016
static const CRPCCommand commands[] =
9961017
{ // category name actor (function) argNames
9971018
// --------------------- ------------------------ ----------------------- ----------
998-
{ "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose"} },
1019+
{ "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} },
9991020
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable"} },
10001021
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring"} },
10011022
{ "rawtransactions", "decodescript", &decodescript, {"hexstring"} },

src/validation.cpp

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -926,47 +926,51 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
926926
return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, pfMissingInputs, GetTime(), plTxnReplaced, bypass_limits, nAbsurdFee);
927927
}
928928

929-
/** Return transaction in txOut, and if it was found inside a block, its hash is placed in hashBlock */
930-
bool GetTransaction(const uint256 &hash, CTransactionRef &txOut, const Consensus::Params& consensusParams, uint256 &hashBlock, bool fAllowSlow)
929+
/**
930+
* Return transaction in txOut, and if it was found inside a block, its hash is placed in hashBlock.
931+
* If blockIndex is provided, the transaction is fetched from the corresponding block.
932+
*/
933+
bool GetTransaction(const uint256& hash, CTransactionRef& txOut, const Consensus::Params& consensusParams, uint256& hashBlock, bool fAllowSlow, CBlockIndex* blockIndex)
931934
{
932-
CBlockIndex *pindexSlow = nullptr;
935+
CBlockIndex* pindexSlow = blockIndex;
933936

934937
LOCK(cs_main);
935938

936-
CTransactionRef ptx = mempool.get(hash);
937-
if (ptx)
938-
{
939-
txOut = ptx;
940-
return true;
941-
}
942-
943-
if (fTxIndex) {
944-
CDiskTxPos postx;
945-
if (pblocktree->ReadTxIndex(hash, postx)) {
946-
CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION);
947-
if (file.IsNull())
948-
return error("%s: OpenBlockFile failed", __func__);
949-
CBlockHeader header;
950-
try {
951-
file >> header;
952-
fseek(file.Get(), postx.nTxOffset, SEEK_CUR);
953-
file >> txOut;
954-
} catch (const std::exception& e) {
955-
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
956-
}
957-
hashBlock = header.GetHash();
958-
if (txOut->GetHash() != hash)
959-
return error("%s: txid mismatch", __func__);
939+
if (!blockIndex) {
940+
CTransactionRef ptx = mempool.get(hash);
941+
if (ptx) {
942+
txOut = ptx;
960943
return true;
961944
}
962945

963-
// transaction not found in index, nothing more can be done
964-
return false;
965-
}
946+
if (fTxIndex) {
947+
CDiskTxPos postx;
948+
if (pblocktree->ReadTxIndex(hash, postx)) {
949+
CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION);
950+
if (file.IsNull())
951+
return error("%s: OpenBlockFile failed", __func__);
952+
CBlockHeader header;
953+
try {
954+
file >> header;
955+
fseek(file.Get(), postx.nTxOffset, SEEK_CUR);
956+
file >> txOut;
957+
} catch (const std::exception& e) {
958+
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
959+
}
960+
hashBlock = header.GetHash();
961+
if (txOut->GetHash() != hash)
962+
return error("%s: txid mismatch", __func__);
963+
return true;
964+
}
966965

967-
if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it
968-
const Coin& coin = AccessByTxid(*pcoinsTip, hash);
969-
if (!coin.IsSpent()) pindexSlow = chainActive[coin.nHeight];
966+
// transaction not found in index, nothing more can be done
967+
return false;
968+
}
969+
970+
if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it
971+
const Coin& coin = AccessByTxid(*pcoinsTip, hash);
972+
if (!coin.IsSpent()) pindexSlow = chainActive[coin.nHeight];
973+
}
970974
}
971975

972976
if (pindexSlow) {

src/validation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ void ThreadScriptCheck();
273273
/** Check whether we are doing an initial block download (synchronizing from disk or network) */
274274
bool IsInitialBlockDownload();
275275
/** Retrieve a transaction (from memory pool, or from disk, if possible) */
276-
bool GetTransaction(const uint256 &hash, CTransactionRef &tx, const Consensus::Params& params, uint256 &hashBlock, bool fAllowSlow = false);
276+
bool GetTransaction(const uint256& hash, CTransactionRef& tx, const Consensus::Params& params, uint256& hashBlock, bool fAllowSlow = false, CBlockIndex* blockIndex = nullptr);
277277
/** Find the best known block, and make it the tip of the block chain */
278278
bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock = std::shared_ptr<const CBlock>());
279279
CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams);

test/functional/rawtransactions.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@ def run_test(self):
5050
# This will raise an exception since there are missing inputs
5151
assert_raises_rpc_error(-25, "Missing inputs", self.nodes[2].sendrawtransaction, rawtx['hex'])
5252

53+
#####################################
54+
# getrawtransaction with block hash #
55+
#####################################
56+
57+
# make a tx by sending then generate 2 blocks; block1 has the tx in it
58+
tx = self.nodes[2].sendtoaddress(self.nodes[1].getnewaddress(), 1)
59+
block1, block2 = self.nodes[2].generate(2)
60+
self.sync_all()
61+
# We should be able to get the raw transaction by providing the correct block
62+
gottx = self.nodes[0].getrawtransaction(tx, True, block1)
63+
assert_equal(gottx['txid'], tx)
64+
assert_equal(gottx['in_active_chain'], True)
65+
# We should not have the 'in_active_chain' flag when we don't provide a block
66+
gottx = self.nodes[0].getrawtransaction(tx, True)
67+
assert_equal(gottx['txid'], tx)
68+
assert 'in_active_chain' not in gottx
69+
# We should not get the tx if we provide an unrelated block
70+
assert_raises_rpc_error(-5, "No such transaction found", self.nodes[0].getrawtransaction, tx, True, block2)
71+
# An invalid block hash should raise the correct errors
72+
assert_raises_rpc_error(-8, "parameter 3 must be hexadecimal", self.nodes[0].getrawtransaction, tx, True, True)
73+
assert_raises_rpc_error(-8, "parameter 3 must be hexadecimal", self.nodes[0].getrawtransaction, tx, True, "foobar")
74+
assert_raises_rpc_error(-8, "parameter 3 must be of length 64", self.nodes[0].getrawtransaction, tx, True, "abcd1234")
75+
assert_raises_rpc_error(-5, "Block hash not found", self.nodes[0].getrawtransaction, tx, True, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
76+
5377
#########################
5478
# RAW TX MULTISIG TESTS #
5579
#########################
@@ -188,13 +212,13 @@ def run_test(self):
188212
assert_equal(self.nodes[0].getrawtransaction(txHash, True)["hex"], rawTxSigned['hex'])
189213

190214
# 6. invalid parameters - supply txid and string "Flase"
191-
assert_raises_rpc_error(-3,"Invalid type", self.nodes[0].getrawtransaction, txHash, "Flase")
215+
assert_raises_rpc_error(-1,"not a boolean", self.nodes[0].getrawtransaction, txHash, "Flase")
192216

193217
# 7. invalid parameters - supply txid and empty array
194-
assert_raises_rpc_error(-3,"Invalid type", self.nodes[0].getrawtransaction, txHash, [])
218+
assert_raises_rpc_error(-1,"not a boolean", self.nodes[0].getrawtransaction, txHash, [])
195219

196220
# 8. invalid parameters - supply txid and empty dict
197-
assert_raises_rpc_error(-3,"Invalid type", self.nodes[0].getrawtransaction, txHash, {})
221+
assert_raises_rpc_error(-1,"not a boolean", self.nodes[0].getrawtransaction, txHash, {})
198222

199223
inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : 1000}]
200224
outputs = { self.nodes[0].getnewaddress() : 1 }

0 commit comments

Comments
 (0)