Skip to content

Commit 0197b32

Browse files
authored
RPC endpoints returning null appear to be handled incorrectly (#688)
1 parent e9747b1 commit 0197b32

File tree

3 files changed

+138
-7
lines changed

3 files changed

+138
-7
lines changed

src/Stratis.Bitcoin.Features.RPC/RPCMiddleware.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ public async Task InvokeAsync(HttpContext httpContext)
7878
{
7979
// Single request, invoke single request and return single response object.
8080
response = await this.InvokeSingleAsync(httpContext, token as JObject);
81+
82+
if (response == null)
83+
response = JValue.CreateNull();
8184
}
8285
else
8386
{
@@ -231,8 +234,17 @@ private async Task<JObject> InvokeSingleAsync(HttpContext httpContext, JObject r
231234
{
232235
await this.next.Invoke(context).ConfigureAwait(false);
233236

237+
// There are two possible cases: either the RPC method did indeed not exist, or the RPC controller endpoint returned null.
238+
// We are supposed to return a JSON null in the RPC response in that case.
234239
if (responseMemoryStream.Length == 0)
240+
{
241+
if (context.Response.StatusCode == (int)HttpStatusCode.NoContent)
242+
{
243+
return null;
244+
}
245+
235246
throw new Exception("Method not found");
247+
}
236248

237249
responseMemoryStream.Position = 0;
238250
using (var streamReader = new StreamReader(responseMemoryStream))

src/Stratis.Bitcoin.IntegrationTests.Common/TestHelper.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public static bool AreNodesSynced(CoreNode node1, CoreNode node2, bool ignoreMem
6666

6767
public static (bool Passed, string Message) AreNodesSyncedMessage(CoreNode node1, CoreNode node2, bool ignoreMempool = false)
6868
{
69+
// TODO: This does not check mempool equivalence, so this method actually behaves differently in tests involving bitcoind!
6970
if (node1.runner is BitcoinCoreRunner || node2.runner is BitcoinCoreRunner)
7071
{
7172
return (node1.CreateRPCClient().GetBestBlockHash() == node2.CreateRPCClient().GetBestBlockHash(), "[BEST_BLOCK_HASH_DOES_MATCH]");
@@ -107,6 +108,8 @@ public static (bool Passed, string Message) AreNodesSyncedMessage(CoreNode node1
107108

108109
public static bool IsNodeSynced(CoreNode node)
109110
{
111+
// TODO: Need test for bitcoin runner too?
112+
110113
// If the node is at genesis it is considered synced.
111114
if (node.FullNode.ChainIndexer.Tip.Height == 0)
112115
return true;

src/Stratis.Bitcoin.IntegrationTests/RPC/RpcBitcoinMutableTests.cs

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
using NBitcoin;
88
using Newtonsoft.Json.Linq;
99
using Stratis.Bitcoin.Features.RPC;
10+
using Stratis.Bitcoin.IntegrationTests.Common;
1011
using Stratis.Bitcoin.IntegrationTests.Common.EnvironmentMockUpHelpers;
12+
using Stratis.Bitcoin.Networks;
13+
using Stratis.Bitcoin.Networks.Deployments;
1114
using Stratis.Bitcoin.Tests.Common;
15+
using Stratis.Bitcoin.Utilities.Extensions;
1216
using Xunit;
1317

1418
namespace Stratis.Bitcoin.IntegrationTests.RPC
@@ -45,10 +49,24 @@ public void GetRawMemPoolWithValidTxThenReturnsSameTx()
4549
// generate 101 blocks
4650
node.GenerateAsync(101).GetAwaiter().GetResult();
4751

52+
CoreNode sfn = builder.CreateStratisPowNode(this.regTest).Start();
53+
54+
TestHelper.ConnectAndSync(node, sfn);
55+
4856
uint256 txid = rpcClient.SendToAddress(new Key().PubKey.GetAddress(rpcClient.Network), Money.Coins(1.0m), "hello", "world");
57+
4958
uint256[] ids = rpcClient.GetRawMempool();
5059
Assert.Single(ids);
5160
Assert.Equal(txid, ids[0]);
61+
62+
RPCClient sfnRpc = sfn.CreateRPCClient();
63+
64+
// It seems to take a while for the transaction to actually propagate, so we have to wait for it before checking the txid is correct.
65+
TestBase.WaitLoop(() => sfnRpc.GetRawMempool().Length == 1);
66+
67+
ids = sfnRpc.GetRawMempool();
68+
Assert.Single(ids);
69+
Assert.Equal(txid, ids[0]);
5270
}
5371
}
5472

@@ -106,6 +124,13 @@ public void CanSendCommand()
106124

107125
RPCResponse response = rpcClient.SendCommand(RPCOperations.getinfo);
108126
Assert.NotNull(response.Result);
127+
128+
CoreNode sfn = builder.CreateStratisPowNode(this.regTest).Start();
129+
130+
RPCClient sfnRpc = sfn.CreateRPCClient();
131+
132+
response = sfnRpc.SendCommand(RPCOperations.getinfo);
133+
Assert.NotNull(response.Result);
109134
}
110135
}
111136

@@ -122,6 +147,16 @@ public void CanGetGenesisFromRPC()
122147
string actualGenesis = (string)response.Result;
123148
Assert.Equal(this.regTest.GetGenesis().GetHash().ToString(), actualGenesis);
124149
Assert.Equal(this.regTest.GetGenesis().GetHash(), rpcClient.GetBestBlockHash());
150+
151+
CoreNode sfn = builder.CreateStratisPowNode(this.regTest).Start();
152+
TestHelper.ConnectAndSync(node, sfn);
153+
154+
rpcClient = sfn.CreateRPCClient();
155+
156+
response = rpcClient.SendCommand(RPCOperations.getblockhash, 0);
157+
actualGenesis = (string)response.Result;
158+
Assert.Equal(this.regTest.GetGenesis().GetHash().ToString(), actualGenesis);
159+
Assert.Equal(this.regTest.GetGenesis().GetHash(), rpcClient.GetBestBlockHash());
125160
}
126161
}
127162

@@ -130,16 +165,45 @@ public void CanSignRawTransaction()
130165
{
131166
using (NodeBuilder builder = NodeBuilder.Create(this))
132167
{
133-
CoreNode node = builder.CreateBitcoinCoreNode(version: BitcoinCoreVersion15).Start();
168+
CoreNode node = builder.CreateBitcoinCoreNode(version: "0.18.0", useNewConfigStyle: true).Start();
169+
170+
CoreNode sfn = builder.CreateStratisPowNode(this.regTest).WithWallet().Start();
171+
172+
TestHelper.ConnectAndSync(node, sfn);
134173

135174
RPCClient rpcClient = node.CreateRPCClient();
175+
RPCClient sfnRpc = sfn.CreateRPCClient();
176+
177+
// Need one block per node so they can each fund a transaction.
178+
rpcClient.Generate(1);
179+
180+
TestHelper.ConnectAndSync(node, sfn);
181+
182+
sfnRpc.Generate(1);
183+
184+
TestHelper.ConnectAndSync(node, sfn);
185+
186+
// And then enough blocks mined on top for the coinbases to mature.
136187
rpcClient.Generate(101);
137188

189+
TestHelper.ConnectAndSync(node, sfn);
190+
138191
var tx = new Transaction();
139192
tx.Outputs.Add(new TxOut(Money.Coins(1.0m), new Key()));
140-
FundRawTransactionResponse funded = node.CreateRPCClient().FundRawTransaction(tx);
141-
Transaction signed = node.CreateRPCClient().SignRawTransaction(funded.Transaction);
142-
node.CreateRPCClient().SendRawTransaction(signed);
193+
FundRawTransactionResponse funded = rpcClient.FundRawTransaction(tx);
194+
195+
// signrawtransaction was removed in 0.18. So just use its equivalent so that we can test SFN's ability to call signrawtransaction.
196+
RPCResponse response = rpcClient.SendCommand("signrawtransactionwithwallet", tx.ToHex());
197+
198+
Assert.NotNull(response.Result["hex"]);
199+
200+
sfnRpc.WalletPassphrase(sfn.WalletPassword, 60);
201+
202+
tx = new Transaction();
203+
tx.Outputs.Add(new TxOut(Money.Coins(1.0m), new Key()));
204+
funded = sfnRpc.FundRawTransaction(tx);
205+
Transaction signed = sfnRpc.SignRawTransaction(funded.Transaction);
206+
rpcClient.SendRawTransaction(signed);
143207
}
144208
}
145209

@@ -171,9 +235,17 @@ public void CanGetBlockFromRPC()
171235
RPCClient rpcClient = node.CreateRPCClient();
172236

173237
BlockHeader response = rpcClient.GetBlockHeader(0);
238+
174239
Assert.Equal(this.regTest.GetGenesis().Header.ToBytes(), response.ToBytes());
240+
Assert.Equal(this.regTest.GenesisHash, response.GetHash());
175241

176-
response = rpcClient.GetBlockHeader(0);
242+
CoreNode sfn = builder.CreateStratisPowNode(this.regTest).Start();
243+
244+
RPCClient sfnRpc = sfn.CreateRPCClient();
245+
246+
response = sfnRpc.GetBlockHeader(0);
247+
248+
Assert.Equal(this.regTest.GetGenesis().Header.ToBytes(), response.ToBytes());
177249
Assert.Equal(this.regTest.GenesisHash, response.GetHash());
178250
}
179251
}
@@ -187,9 +259,12 @@ public void TryValidateAddress()
187259

188260
RPCClient rpcClient = node.CreateRPCClient();
189261

190-
// RegTest
191262
BitcoinAddress pkh = rpcClient.GetNewAddress();
192263
Assert.True(rpcClient.ValidateAddress(pkh).IsValid);
264+
265+
CoreNode sfn = builder.CreateStratisPowNode(this.regTest).Start();
266+
267+
Assert.True(sfn.CreateRPCClient().ValidateAddress(pkh).IsValid);
193268
}
194269
}
195270

@@ -211,13 +286,22 @@ public void CanGetTxOutNoneFromRPC()
211286
{
212287
using (NodeBuilder builder = NodeBuilder.Create(this))
213288
{
214-
CoreNode node = builder.CreateBitcoinCoreNode(version: BitcoinCoreVersion15).Start();
289+
CoreNode node = builder.CreateBitcoinCoreNode(version: "0.18.0", useNewConfigStyle: true).Start();
290+
CoreNode sfn = builder.CreateStratisPowNode(this.regTest).Start();
291+
292+
TestHelper.ConnectAndSync(node, sfn);
215293

216294
RPCClient rpcClient = node.CreateRPCClient();
295+
RPCClient sfnRpc = sfn.CreateRPCClient();
217296

218297
uint256 txid = rpcClient.Generate(1).Single();
219298
UnspentTransaction resultTxOut = rpcClient.GetTxOut(txid, 0, true);
220299
Assert.Null(resultTxOut);
300+
301+
TestHelper.ConnectAndSync(node, sfn);
302+
303+
resultTxOut = sfnRpc.GetTxOut(txid, 0, true);
304+
Assert.Null(resultTxOut);
221305
}
222306
}
223307

@@ -236,6 +320,37 @@ public void CanGetTransactionBlockFromRPC()
236320
}
237321
}
238322

323+
[Fact]
324+
public void CanGetRawTransaction()
325+
{
326+
using (NodeBuilder builder = NodeBuilder.Create(this))
327+
{
328+
CoreNode node = builder.CreateBitcoinCoreNode(version: "0.18.0", useNewConfigStyle: true).Start();
329+
330+
RPCClient rpcClient = node.CreateRPCClient();
331+
332+
rpcClient.Generate(101);
333+
334+
CoreNode sfn = builder.CreateStratisPowNode(this.regTest);
335+
sfn.Start();
336+
TestHelper.ConnectAndSync(node, sfn);
337+
338+
uint256 txid = rpcClient.SendToAddress(new Key().PubKey.GetAddress(rpcClient.Network), Money.Coins(1.0m));
339+
340+
TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length == 1);
341+
342+
Transaction tx = rpcClient.GetRawTransaction(txid);
343+
344+
RPCClient sfnRpc = sfn.CreateRPCClient();
345+
346+
TestBase.WaitLoop(() => sfnRpc.GetRawMempool().Length == 1);
347+
348+
Transaction tx2 = sfnRpc.GetRawTransaction(txid);
349+
350+
Assert.Equal(tx.ToHex(), tx2.ToHex());
351+
}
352+
}
353+
239354
[Fact]
240355
public void RawTransactionIsConformsToRPC()
241356
{
@@ -251,6 +366,7 @@ public void RawTransactionIsConformsToRPC()
251366
Assert.True(JToken.DeepEquals(tx.ToString(this.testNet, RawFormat.Satoshi), tx2.ToString(this.testNet, RawFormat.Satoshi)));
252367
}
253368
}
369+
254370
[Fact]
255371
public void CanUseBatchedRequests()
256372
{

0 commit comments

Comments
 (0)