Skip to content
Closed
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
61 changes: 59 additions & 2 deletions src/Neo/SmartContract/KeyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ namespace Neo.SmartContract;
/// </summary>
public class KeyBuilder : IEnumerable
{
public const int PrefixLength = sizeof(int) + sizeof(byte);

private readonly byte[] _cacheData;
private int _keyLength;

Expand Down Expand Up @@ -105,6 +103,65 @@ public KeyBuilder Add<T>(T key) where T : unmanaged
return Add(data);
}

internal static bool TryParse(byte[] data, Type[] types, out int id, out byte prefix, out object[] values)
{
values = Array.Empty<object>();

foreach (var t in types)
{
if (!t.IsValueType || t.IsGenericType)
{
id = default;
prefix = default;
return false;
}
}

if (data.Length < sizeof(int) + 1)
{
id = default;
prefix = default;
return false;
}

id = BinaryPrimitives.ReadInt32LittleEndian(data);
prefix = data[sizeof(int)];

values = new object[types.Length];
int offset = sizeof(int) + 1;

for (int i = 0; i < types.Length; i++)
{
int size = Marshal.SizeOf(types[i]);
if (data.Length < offset + size)
return false;

Span<byte> span = data.AsSpan(offset, size);

if (BitConverter.IsLittleEndian)
span.Reverse();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erikzhang we loose performance here, because in regular nodes we always need to reverse each key, before the change this was faster

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tested it? Approximately how much performance was lost?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a benchmark. it depends a lot on the key size, but previously an integer was written directly inverted, and now it requires an extra loop to reverse it. Also, I think a string gets reversed, and it shouldn't; only an integer should be reversed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string is not a value type.


values[i] = ReadStruct(span, types[i]);
offset += size;
}

return true;
}

static object ReadStruct(Span<byte> span, Type t)
{
byte[] buffer = span.ToArray();
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
return Marshal.PtrToStructure(handle.AddrOfPinnedObject(), t)!;
}
finally
{
handle.Free();
}
}

/// <summary>
/// Gets the storage key generated by the builder.
/// </summary>
Expand Down
25 changes: 25 additions & 0 deletions tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

using Neo.IO;
using Neo.SmartContract;
using System.Reflection;

namespace Neo.UnitTests.SmartContract;

Expand Down Expand Up @@ -42,6 +43,30 @@ public void Test()
Assert.AreEqual("010000000000000001", key.ToArray().ToHexString());
}

[TestMethod]
public void TestTryParse()
{
var key = new KeyBuilder(1, 2);
Assert.AreEqual("0100000002", key.ToArray().ToHexString());

// add int
key = new KeyBuilder(1, 2);
key = key.Add(-1);
key = key.Add(2);
Assert.AreEqual("0100000002ffffffff00000002", key.ToArray().ToHexString());

Assert.IsFalse(KeyBuilder.TryParse([], [typeof(int), typeof(int)], out int id, out byte prefix, out var ret));
Assert.IsFalse(KeyBuilder.TryParse([0, 1, 2, 3, 4, 5], [typeof(int), typeof(int)], out id, out prefix, out ret));
Assert.IsTrue(KeyBuilder.TryParse(key.ToArray(), [typeof(int), typeof(int)], out id, out prefix, out ret));

Assert.AreEqual(1, id);
Assert.AreEqual(2, prefix);
Assert.IsNotNull(ret);
Assert.HasCount(2, ret);
Assert.AreEqual(-1, ret[0]);
Assert.AreEqual(2, ret[1]);
}

[TestMethod]
public void TestAddInt()
{
Expand Down