Skip to content

Commit f2debd7

Browse files
Enhance getrawtransaction method by including the block that the transaction was included in. (#184)
1 parent 4f3d3fd commit f2debd7

1 file changed

Lines changed: 68 additions & 14 deletions

File tree

src/Blockcore/Controllers/NodeController.cs

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Blockcore.Interfaces;
1515
using Blockcore.P2P;
1616
using Blockcore.P2P.Peer;
17+
using Blockcore.Primitives;
1718
using Blockcore.Utilities;
1819
using Blockcore.Utilities.JsonErrors;
1920
using Blockcore.Utilities.ModelStateErrors;
@@ -84,6 +85,8 @@ public class NodeController : Controller
8485

8586
private readonly ISelfEndpointTracker selfEndpointTracker;
8687

88+
private readonly IConsensusManager consensusManager;
89+
8790
public NodeController(
8891
ChainIndexer chainIndexer,
8992
IChainState chainState,
@@ -99,7 +102,8 @@ public NodeController(
99102
IGetUnspentTransaction getUnspentTransaction = null,
100103
INetworkDifficulty networkDifficulty = null,
101104
IPooledGetUnspentTransaction pooledGetUnspentTransaction = null,
102-
IPooledTransaction pooledTransaction = null)
105+
IPooledTransaction pooledTransaction = null,
106+
IConsensusManager consensusManager = null)
103107
{
104108
Guard.NotNull(fullNode, nameof(fullNode));
105109
Guard.NotNull(network, nameof(network));
@@ -111,6 +115,7 @@ public NodeController(
111115
Guard.NotNull(dateTimeProvider, nameof(dateTimeProvider));
112116
Guard.NotNull(asyncProvider, nameof(asyncProvider));
113117
Guard.NotNull(selfEndpointTracker, nameof(selfEndpointTracker));
118+
Guard.NotNull(consensusManager, nameof(consensusManager));
114119

115120
this.chainIndexer = chainIndexer;
116121
this.chainState = chainState;
@@ -128,6 +133,7 @@ public NodeController(
128133
this.networkDifficulty = networkDifficulty;
129134
this.pooledGetUnspentTransaction = pooledGetUnspentTransaction;
130135
this.pooledTransaction = pooledTransaction;
136+
this.consensusManager = consensusManager;
131137
}
132138

133139
/// <summary>
@@ -247,45 +253,75 @@ public IActionResult GetBlockHeader([FromQuery] string hash, bool isJsonFormat =
247253
/// </summary>
248254
/// <param name="trxid">The transaction ID (a hash of the trancaction).</param>
249255
/// <param name="verbose">A flag that specifies whether to return verbose information about the transaction.</param>
256+
/// <param name="blockHash">The hash of the block in which to look for the transaction.</param>
250257
/// <returns>Json formatted <see cref="TransactionBriefModel"/> or <see cref="TransactionVerboseModel"/>. <c>null</c> if transaction not found. Returns <see cref="IActionResult"/> formatted error if otherwise fails.</returns>
251258
/// <exception cref="ArgumentNullException">Thrown if fullNode, network, or chain are not available.</exception>
252259
/// <exception cref="ArgumentException">Thrown if trxid is empty or not a valid<see cref="uint256"/>.</exception>
253260
/// <remarks>Requires txindex=1, otherwise only txes that spend or create UTXOs for a wallet can be returned.</remarks>
254261
[Route("getrawtransaction")]
255262
[HttpGet]
256-
public async Task<IActionResult> GetRawTransactionAsync([FromQuery] string trxid, bool verbose = false)
263+
public async Task<IActionResult> GetRawTransactionAsync([FromQuery] string txid, bool verbose = false, string blockHash = null)
257264
{
258265
try
259266
{
260-
Guard.NotEmpty(trxid, nameof(trxid));
267+
Guard.NotEmpty(txid, nameof(txid));
261268

262-
uint256 txid;
263-
if (!uint256.TryParse(trxid, out txid))
269+
if (!uint256.TryParse(txid, out uint256 trxid))
264270
{
265271
throw new ArgumentException(nameof(trxid));
266272
}
267273

268-
// First tries to find a pooledTransaction. If can't, will retrieve it from the blockstore if it exists.
269-
Transaction trx = this.pooledTransaction != null ? await this.pooledTransaction.GetTransaction(txid).ConfigureAwait(false) : null;
270-
if (trx == null)
274+
uint256 hash = null;
275+
if (!string.IsNullOrEmpty(blockHash) && !uint256.TryParse(blockHash, out hash))
271276
{
272-
trx = this.blockStore?.GetTransactionById(txid);
277+
throw new ArgumentException(nameof(blockHash));
273278
}
274279

275-
if (trx == null)
280+
// Special exception for the genesis block coinbase transaction.
281+
if (trxid == this.network.GetGenesis().GetMerkleRoot().Hash)
276282
{
277-
return this.Json(null);
283+
throw new Exception("The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved.");
284+
}
285+
286+
Transaction trx = null;
287+
ChainedHeaderBlock chainedHeaderBlock = null;
288+
289+
if (hash == null)
290+
{
291+
// Look for the transaction in the mempool, and if not found, look in the indexed transactions.
292+
trx = (this.pooledTransaction == null ? null : await this.pooledTransaction.GetTransaction(trxid).ConfigureAwait(false)) ??
293+
this.blockStore.GetTransactionById(trxid);
294+
295+
if (trx == null)
296+
{
297+
throw new Exception("No such mempool transaction. Use -txindex to enable blockchain transaction queries.");
298+
}
299+
}
300+
else
301+
{
302+
// Retrieve the block specified by the block hash.
303+
chainedHeaderBlock = this.consensusManager.GetBlockData(hash);
304+
305+
if (chainedHeaderBlock == null)
306+
{
307+
throw new Exception("Block hash not found.");
308+
}
309+
310+
trx = chainedHeaderBlock.Block.Transactions.SingleOrDefault(t => t.GetHash() == trxid);
311+
312+
if (trx == null)
313+
{
314+
return this.Json(null);
315+
}
278316
}
279317

280318
if (verbose)
281319
{
282-
ChainedHeader block = this.GetTransactionBlock(txid, this.fullNode, this.chainIndexer);
320+
ChainedHeader block = chainedHeaderBlock != null ? chainedHeaderBlock.ChainedHeader : this.GetTransactionBlock(trxid);
283321
return this.Json(new TransactionVerboseModel(trx, this.network, block, this.chainState?.ConsensusTip));
284322
}
285323
else
286-
{
287324
return this.Json(new TransactionBriefModel(trx));
288-
}
289325
}
290326
catch (Exception e)
291327
{
@@ -634,5 +670,23 @@ internal static Target GetNetworkDifficulty(INetworkDifficulty networkDifficulty
634670
{
635671
return networkDifficulty?.GetNetworkDifficulty();
636672
}
673+
674+
/// <summary>
675+
/// Retrieves the block that the transaction is in.
676+
/// </summary>
677+
/// <param name="trxid">The transaction id.</param>
678+
/// <returns>Returns the <see cref="ChainedHeader"/> that the transaction is in. Returns <c>null</c> if not found.</returns>
679+
private ChainedHeader GetTransactionBlock(uint256 trxid)
680+
{
681+
ChainedHeader block = null;
682+
683+
uint256 blockid = this.blockStore?.GetBlockIdByTransactionId(trxid);
684+
if (blockid != null)
685+
{
686+
block = this.chainIndexer?.GetHeader(blockid);
687+
}
688+
689+
return block;
690+
}
637691
}
638692
}

0 commit comments

Comments
 (0)