Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ will not have contiguous patch numbers. Initial major and minor releases will be
in this file without a patch number. Patch version will be included for bug fixes, but
may not exactly match a publicly released version.

## [3.0.8] - 2021-10-11
## [3.0.10] - 2021-10-11

Thanks to @merl111 for his contribution (#41) in this release

Expand Down
48 changes: 13 additions & 35 deletions src/bctklib/persistence/RpcClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace Neo.BlockchainToolkit.Persistence
{
static class RpcClientExtensions
{
internal const int COR_E_KEYNOTFOUND = unchecked((int)0x80131577);

public static RpcVersion GetVersion(this RpcClient rpcClient)
{
var result = rpcClient.RpcSend(RpcClient.GetRpcName());
Expand All @@ -33,10 +35,17 @@ public static RpcStateRoot GetStateRoot(this RpcClient rpcClient, uint index)

public static byte[]? GetState(this RpcClient rpcClient, UInt256 rootHash, UInt160 scriptHash, ReadOnlySpan<byte> key)
{
var request = AsRpcRequest(RpcClient.GetRpcName(),
rootHash.ToString(), scriptHash.ToString(), Convert.ToBase64String(key));
var response = rpcClient.Send(request);
return response.AsStateResponse();
try
{
var result = rpcClient.RpcSend(RpcClient.GetRpcName(),
rootHash.ToString(), scriptHash.ToString(), Convert.ToBase64String(key));
return Convert.FromBase64String(result.AsString());
}
catch (RpcException ex)
{
if (ex.HResult == COR_E_KEYNOTFOUND) return null;
throw;
}
}

public static RpcFoundStates FindStates(this RpcClient rpcClient, UInt256 rootHash, UInt160 scriptHash, ReadOnlySpan<byte> prefix, ReadOnlySpan<byte> from = default, int? count = null)
Expand All @@ -45,36 +54,5 @@ public static RpcFoundStates FindStates(this RpcClient rpcClient, UInt256 rootHa
var result = rpcClient.RpcSend(RpcClient.GetRpcName(), @params);
return RpcFoundStates.FromJson(result);
}

public static RpcRequest AsRpcRequest(this string method, params JObject[] paraArgs)
{
return new RpcRequest
{
Id = 1,
JsonRpc = "2.0",
Method = method,
Params = paraArgs
};
}

public static byte[]? AsStateResponse(this RpcResponse response)
{
if (response.Error != null)
{
const int COR_E_KEYNOTFOUND = unchecked((int)0x80131577);
if (response.Error.Code == COR_E_KEYNOTFOUND)
{
return null;
}
else
{
throw new RpcException(response.Error.Code, response.Error.Message);
}
}
else
{
return Convert.FromBase64String(response.Result.AsString());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Neo.BlockchainToolkit.Persistence
{
public partial class StateServiceStore
{
class MemoryCacheClient : ICachingClient
internal class MemoryCacheClient : ICachingClient
{
readonly RpcClient rpcClient;

Expand Down
40 changes: 30 additions & 10 deletions src/bctklib/persistence/StateServiceStore.RocksDbCacheClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
using System.IO;
using System.Text;
using Neo.IO.Json;
Expand All @@ -12,7 +13,7 @@ namespace Neo.BlockchainToolkit.Persistence
{
public partial class StateServiceStore
{
class RocksDbCacheClient : ICachingClient
internal class RocksDbCacheClient : ICachingClient
{
readonly RpcClient rpcClient;
readonly RocksDb db;
Expand Down Expand Up @@ -58,7 +59,8 @@ public RpcFoundStates FindStates(UInt256 rootHash, UInt160 scriptHash, ReadOnlyM
if (from.Length > 0) from.Span.CopyTo(key.Slice(prefix.Length));
if (count.HasValue) BinaryPrimitives.WriteInt32LittleEndian(key.Slice(prefix.Length + from.Length), count.Value);

var json = GetCachedJson(key, family, () => {
var json = GetCachedJson(key, family, () =>
{
var @params = StateAPI.MakeFindStatesParams(rootHash, scriptHash, prefix.Span, from.Span, count);
return rpcClient.RpcSend("findstates", @params);
});
Expand All @@ -79,21 +81,39 @@ public UInt256 GetBlockHash(uint index)
var family = db.GetOrCreateColumnFamily(familyName);
var value = db.Get(key.Span, family);

RpcResponse rpcResponse;
JObject jsonValue;
if (value != null)
{
var json = JObject.Parse(value);
rpcResponse = RpcResponse.FromJson(json);
jsonValue = JObject.Parse(value);
}
else
{
var request = "getstate".AsRpcRequest(
rootHash.ToString(), scriptHash.ToString(), Convert.ToBase64String(key.Span));
rpcResponse = rpcClient.Send(request);
db.Put(key.Span, rpcResponse.ToJson().ToByteArray(false), family);
try
{
jsonValue = rpcClient.RpcSend("getstate",
rootHash.ToString(),
scriptHash.ToString(),
Convert.ToBase64String(key.Span));
}
catch (RpcException ex)
{
if (ex.HResult == RpcClientExtensions.COR_E_KEYNOTFOUND)
{
jsonValue = JObject.Null;
}
else
{
throw;
}
}
var jsonBytes = jsonValue == null
? Neo.Utility.StrictUTF8.GetBytes("null")
: jsonValue.ToByteArray(false);
db.Put(key.Span, jsonBytes, family);
}

return rpcResponse.AsStateResponse();
return jsonValue == null || jsonValue == JObject.Null ? null
: Convert.FromBase64String(jsonValue.AsString());
}

public RpcStateRoot GetStateRoot(uint index)
Expand Down
50 changes: 50 additions & 0 deletions test/test.bctklib/RocksDbCacheClientTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using Neo;
using Neo.BlockchainToolkit.Persistence;
using Neo.IO.Json;
using Neo.Network.RPC;
using RocksDbSharp;
using Xunit;

namespace test.bctklib3
{
public class RocksDbCacheClientTest
{
WriteOptions syncWriteOptions = new WriteOptions().SetSync(true);

[Fact]
public void cached_get_state_returns_expected()
{
var key = Neo.Utility.StrictUTF8.GetBytes("key");
var expected = Neo.Utility.StrictUTF8.GetBytes("this is a test");

using var rpcClient = new TestableRpcClient(() => Convert.ToBase64String(expected));

var tempPath = Utility.GetTempPath();
using var _ = Utility.GetDeleteDirectoryDisposable(tempPath);
using var client = new StateServiceStore.RocksDbCacheClient(rpcClient, tempPath);

var actual1 = client.GetState(UInt256.Zero, UInt160.Zero, key);
var actual2 = client.GetState(UInt256.Zero, UInt160.Zero, key);
Assert.Equal(expected, actual1);
Assert.Equal(expected, actual2);
}

[Fact]
public void cached_get_state_returns_null_for_key_not_found_exception()
{
var key = Neo.Utility.StrictUTF8.GetBytes("key");

using var rpcClient = new TestableRpcClient(() => throw new RpcException(-2146232969, "The given key was not present in the dictionary."));

var tempPath = Utility.GetTempPath();
using var _ = Utility.GetDeleteDirectoryDisposable(tempPath);
using var client = new StateServiceStore.RocksDbCacheClient(rpcClient, tempPath);

var actual1 = client.GetState(UInt256.Zero, UInt160.Zero, key);
var actual2 = client.GetState(UInt256.Zero, UInt160.Zero, key);
Assert.Null(actual1);
Assert.Null(actual2);
}
}
}
36 changes: 36 additions & 0 deletions test/test.bctklib/RpcClientExtensionsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using Neo;
using Neo.BlockchainToolkit.Persistence;
using Neo.IO.Json;
using Neo.Network.RPC;
using Xunit;

namespace test.bctklib3
{
public class RpcClientExtensionsTest
{
[Fact]
public void get_state_returns_expected()
{
var expected = Neo.Utility.StrictUTF8.GetBytes("this is a test");
var rpcClient = new TestableRpcClient(() => Convert.ToBase64String(expected));
var actual = rpcClient.GetState(UInt256.Zero, UInt160.Zero, default);
Assert.Equal(expected, actual);
}

[Fact]
public void get_state_returns_null_for_key_not_found_exception()
{
var rpcClient = new TestableRpcClient(() => throw new RpcException(-2146232969, "The given key was not present in the dictionary."));
var actual = rpcClient.GetState(UInt256.Zero, UInt160.Zero, default);
Assert.Null(actual);
}

[Fact]
public void get_state_throws_for_other_exception()
{
var rpcClient = new TestableRpcClient(() => throw new RpcException(-146232969, "Halt and catch fire."));
Assert.Throws<RpcException>(() => rpcClient.GetState(UInt256.Zero, UInt160.Zero, default));
}
}
}
25 changes: 25 additions & 0 deletions test/test.bctklib/TestableRpcClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Neo.IO.Json;

namespace test.bctklib3
{
class TestableRpcClient : Neo.Network.RPC.RpcClient
{
Stack<Func<JObject>> funcStack = new();

public TestableRpcClient(params Func<JObject>[] functions) : base(null)
{
foreach (var func in functions.Reverse())
{
funcStack.Push(func);
}

}
public override JObject RpcSend(string method, params JObject[] paraArgs)
{
return funcStack.Pop()();
}
}
}
2 changes: 1 addition & 1 deletion test/test.bctklib/test.bctklib.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<LangVersion>8</LangVersion>
<LangVersion>9</LangVersion>
<Nullable>enable</Nullable>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
Expand Down