New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[rpc] Allow fetching tx directly from specified block in getrawtransaction #10275
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,12 +64,15 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) | |
|
||
UniValue getrawtransaction(const JSONRPCRequest& request) | ||
{ | ||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) | ||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) | ||
throw std::runtime_error( | ||
"getrawtransaction \"txid\" ( verbose )\n" | ||
"getrawtransaction \"txid\" ( verbose \"blockhash\" )\n" | ||
|
||
"\nNOTE: By default this function only works for mempool transactions. If the -txindex option is\n" | ||
"enabled, it also works for blockchain transactions.\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" | ||
"DEPRECATED: for now, it also works for transactions with unspent outputs.\n" | ||
|
||
"\nReturn the raw transaction data.\n" | ||
|
@@ -78,13 +81,15 @@ UniValue getrawtransaction(const JSONRPCRequest& request) | |
|
||
"\nArguments:\n" | ||
"1. \"txid\" (string, required) The transaction id\n" | ||
"2. verbose (bool, optional, default=false) If false, return a string, otherwise return a json object\n" | ||
"2. verbose (bool, optional, default=false) If false, return a string, 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 false):\n" | ||
"\"data\" (string) The serialized, hex-encoded data for 'txid'\n" | ||
|
||
"\nResult (if verbose is set to true):\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" | ||
" \"txid\" : \"id\", (string) The transaction id (same as provided)\n" | ||
" \"hash\" : \"id\", (string) The transaction hash (differs from txid for witness transactions)\n" | ||
|
@@ -132,30 +137,58 @@ UniValue getrawtransaction(const JSONRPCRequest& request) | |
+ HelpExampleCli("getrawtransaction", "\"mytxid\"") | ||
+ HelpExampleCli("getrawtransaction", "\"mytxid\" true") | ||
+ HelpExampleRpc("getrawtransaction", "\"mytxid\", true") | ||
+ HelpExampleCli("getrawtransaction", "\"mytxid\" false \"myblockhash\"") | ||
+ HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"") | ||
); | ||
|
||
LOCK(cs_main); | ||
|
||
bool in_active_chain = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Strike that. gcc would throw a warning. |
||
uint256 hash = ParseHashV(request.params[0], "parameter 1"); | ||
CBlockIndex* blockindex = nullptr; | ||
|
||
// Accept either a bool (true) or a num (>=1) to indicate verbose output. | ||
bool fVerbose = false; | ||
if (!request.params[1].isNull()) { | ||
fVerbose = request.params[1].isNum() ? (request.params[1].get_int() != 0) : request.params[1].get_bool(); | ||
} | ||
|
||
if (!request.params[2].isNull()) { | ||
uint256 blockhash = ParseHashV(request.params[2], "parameter 3"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could use parameter name instead of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is used in a ton of places. I never saw consensus on what exactly to do and chose to stick with it for now. As you said, different PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: these lines are confusing for me:
I don't think this is a huge problem - if all zeroes is supplied as the block hash, we'll just drop through and search for the transaction in the {mempool , blockchain (if fTxIndex) , UTXO set} as if no block hash had been provided. However, it's a bit confusing for the code reader since it implies to me that we're expecting |
||
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); | ||
} | ||
} | ||
|
||
CTransactionRef tx; | ||
uint256 hashBlock; | ||
if (!GetTransaction(hash, tx, Params().GetConsensus(), hashBlock, true)) | ||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string(fTxIndex ? "No such mempool or blockchain transaction" | ||
: "No such mempool transaction. Use -txindex to enable blockchain transaction queries") + | ||
". Use gettransaction for wallet transactions."); | ||
uint256 hash_block; | ||
if (!GetTransaction(hash, tx, Params().GetConsensus(), hash_block, true, blockindex)) { | ||
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"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we would also hit this error if the block had been pruned. In that case, this error is misleading. Is there any way to test above for whether the block has been pruned and error out before doing the transaction lookup? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! I added code (3c0cb43) that checks if pruning is enabled, and if it is, checks if the block is actually available by reading it from disk and setting the error message based on the results of that. |
||
} 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."); | ||
} | ||
|
||
if (!fVerbose) | ||
if (!fVerbose) { | ||
return EncodeHexTx(*tx, RPCSerializationFlags()); | ||
} | ||
|
||
UniValue result(UniValue::VOBJ); | ||
TxToJSON(*tx, hashBlock, result); | ||
if (blockindex) result.push_back(Pair("in_active_chain", in_active_chain)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
TxToJSON(*tx, hash_block, result); | ||
return result; | ||
} | ||
|
||
|
@@ -983,7 +1016,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) | |
static const CRPCCommand commands[] = | ||
{ // category name actor (function) argNames | ||
// --------------------- ------------------------ ----------------------- ---------- | ||
{ "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose"} }, | ||
{ "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} }, | ||
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable"} }, | ||
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring"} }, | ||
{ "rawtransactions", "decodescript", &decodescript, {"hexstring"} }, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -926,47 +926,51 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa | |
return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, pfMissingInputs, GetTime(), plTxnReplaced, bypass_limits, nAbsurdFee); | ||
} | ||
|
||
/** 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) | ||
/** | ||
* 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, CTransactionRef& txOut, const Consensus::Params& consensusParams, uint256& hashBlock, bool fAllowSlow, CBlockIndex* blockIndex) | ||
{ | ||
CBlockIndex *pindexSlow = nullptr; | ||
CBlockIndex* pindexSlow = blockIndex; | ||
|
||
LOCK(cs_main); | ||
|
||
CTransactionRef ptx = mempool.get(hash); | ||
if (ptx) | ||
{ | ||
txOut = ptx; | ||
return true; | ||
} | ||
|
||
if (fTxIndex) { | ||
CDiskTxPos postx; | ||
if (pblocktree->ReadTxIndex(hash, postx)) { | ||
CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION); | ||
if (file.IsNull()) | ||
return error("%s: OpenBlockFile failed", __func__); | ||
CBlockHeader header; | ||
try { | ||
file >> header; | ||
fseek(file.Get(), postx.nTxOffset, SEEK_CUR); | ||
file >> txOut; | ||
} catch (const std::exception& e) { | ||
return error("%s: Deserialize or I/O error - %s", __func__, e.what()); | ||
} | ||
hashBlock = header.GetHash(); | ||
if (txOut->GetHash() != hash) | ||
return error("%s: txid mismatch", __func__); | ||
if (!blockIndex) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps the diff can be less disruptive by moving everything inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a huge fan of changing code just to make diffs smaller, and as I mention above, you can put |
||
CTransactionRef ptx = mempool.get(hash); | ||
if (ptx) { | ||
txOut = ptx; | ||
return true; | ||
} | ||
|
||
// transaction not found in index, nothing more can be done | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this line was lost in a rebase? Shouldn't remove it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed! |
||
return false; | ||
} | ||
if (fTxIndex) { | ||
CDiskTxPos postx; | ||
if (pblocktree->ReadTxIndex(hash, postx)) { | ||
CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION); | ||
if (file.IsNull()) | ||
return error("%s: OpenBlockFile failed", __func__); | ||
CBlockHeader header; | ||
try { | ||
file >> header; | ||
fseek(file.Get(), postx.nTxOffset, SEEK_CUR); | ||
file >> txOut; | ||
} catch (const std::exception& e) { | ||
return error("%s: Deserialize or I/O error - %s", __func__, e.what()); | ||
} | ||
hashBlock = header.GetHash(); | ||
if (txOut->GetHash() != hash) | ||
return error("%s: txid mismatch", __func__); | ||
return true; | ||
} | ||
|
||
if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it | ||
const Coin& coin = AccessByTxid(*pcoinsTip, hash); | ||
if (!coin.IsSpent()) pindexSlow = chainActive[coin.nHeight]; | ||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should skip this logic if user has provided the blockhash? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is inside the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops! Not good at counting brackets. |
||
const Coin& coin = AccessByTxid(*pcoinsTip, hash); | ||
if (!coin.IsSpent()) pindexSlow = chainActive[coin.nHeight]; | ||
} | ||
} | ||
|
||
if (pindexSlow) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: alignment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
becomes
(two \ vs four \ means indentation diff in code)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, of course you're correct. The
size
,vsize
,version
, ... fields below aren't aligned by the same logic, but that's beyond the scope of this PR. Please disregard my previous comment.