diff --git a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs index a3d0f0c0d..8e9153f4b 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs @@ -342,6 +342,41 @@ public async Task TransactionResultIsSuccess() Assert.Equal("SUCCESS", txStatus); } + [Fact] + public async Task TransactionResults() + { + var privateKey = new PrivateKey(); + // Because `AddActivatedAccount` doesn't need any prerequisites. + var action = new AddActivatedAccount(default); + Transaction tx = _blockChain.MakeTransaction(privateKey, new ActionBase[] { action }); + var action2 = new DailyReward + { + avatarAddress = default + }; + Transaction tx2 = _blockChain.MakeTransaction(new PrivateKey(), new ActionBase[] { action2 }); + Block block = _blockChain.ProposeBlock(_proposer); + _blockChain.Append(block, GenerateBlockCommit(block.Index, block.Hash, _proposer)); + var queryFormat = @"query {{ + transactionResults(txIds: [""{0}"", ""{1}""]) {{ + blockHash + txStatus + }} + }}"; + var result = await ExecuteAsync(string.Format( + queryFormat, + tx.Id.ToString(), + tx2.Id.ToString() + )); + Assert.NotNull(result.Data); + var transactionResults = + (object[])((Dictionary)((ExecutionNode)result.Data!).ToValue()!)["transactionResults"]; + Assert.Equal(2, transactionResults.Length); + var txStatus = (string)((Dictionary)transactionResults[0])["txStatus"]; + Assert.Equal("SUCCESS", txStatus); + txStatus = (string)((Dictionary)transactionResults[1])["txStatus"]; + Assert.Equal("FAILURE", txStatus); + } + [Fact] public async Task NcTransactionsOnTip() { diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index 8474db53d..e8ea6a5b5 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -192,42 +192,25 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) ), resolve: context => { - if (!(standaloneContext.BlockChain is BlockChain blockChain)) - { - throw new ExecutionError( - $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); - } - - if (!(standaloneContext.Store is IStore store)) - { - throw new ExecutionError( - $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.Store)} was not set yet!"); - } - - TxId txId = context.GetArgument("txId"); - if (!(store.GetFirstTxIdBlockHashIndex(txId) is { } txExecutedBlockHash)) - { - return blockChain.GetStagedTransactionIds().Contains(txId) - ? new TxResult(TxStatus.STAGING, null, null, null, null, null) - : new TxResult(TxStatus.INVALID, null, null, null, null, null); - } + var txId = context.GetArgument("txId"); + return TxResult(standaloneContext, txId); + }); - try - { - TxExecution execution = blockChain.GetTxExecution(txExecutedBlockHash, txId); - Block txExecutedBlock = blockChain[txExecutedBlockHash]; - return new TxResult( - execution.Fail ? TxStatus.FAILURE : TxStatus.SUCCESS, - txExecutedBlock.Index, - txExecutedBlock.Hash.ToString(), - execution.InputState, - execution.OutputState, - execution.ExceptionNames); - } - catch (Exception) + Field>>( + name: "transactionResults", + arguments: new QueryArguments( + new QueryArgument>>> { - return new TxResult(TxStatus.INVALID, null, null, null, null, null); + Name = "txIds", + Description = "transaction ids." } + ), + resolve: context => + { + return context.GetArgument>("txIds") + .AsParallel() + .AsOrdered() + .Select(txId => TxResult(standaloneContext, txId)); } ); @@ -312,6 +295,45 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) ); } + private static object? TxResult(StandaloneContext standaloneContext, TxId txId) + { + if (!(standaloneContext.BlockChain is BlockChain blockChain)) + { + throw new ExecutionError( + $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); + } + + if (!(standaloneContext.Store is IStore store)) + { + throw new ExecutionError( + $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.Store)} was not set yet!"); + } + + if (!(store.GetFirstTxIdBlockHashIndex(txId) is { } txExecutedBlockHash)) + { + return blockChain.GetStagedTransactionIds().Contains(txId) + ? new TxResult(TxStatus.STAGING, null, null, null, null, null) + : new TxResult(TxStatus.INVALID, null, null, null, null, null); + } + + try + { + TxExecution execution = blockChain.GetTxExecution(txExecutedBlockHash, txId); + Block txExecutedBlock = blockChain[txExecutedBlockHash]; + return new TxResult( + execution.Fail ? TxStatus.FAILURE : TxStatus.SUCCESS, + txExecutedBlock.Index, + txExecutedBlock.Hash.ToString(), + execution.InputState, + execution.OutputState, + execution.ExceptionNames); + } + catch (Exception) + { + return new TxResult(TxStatus.INVALID, null, null, null, null, null); + } + } + private IEnumerable ListBlocks(BlockChain chain, long from, long limit) { if (chain.Tip.Index < from)