Skip to content

Commit de8875b

Browse files
committed
wip: refactor path cursor
1 parent ac3f130 commit de8875b

File tree

12 files changed

+239
-206
lines changed

12 files changed

+239
-206
lines changed

src/Libplanet.Store/Trie/DefaultKeyValueStore.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,5 +144,5 @@ public IEnumerable<KeyBytes> ListKeys() =>
144144
_fs.EnumerateFiles(UPath.Root)
145145
.Select(path => KeyBytes.Parse(path.GetName()));
146146

147-
private static UPath DataPath(in KeyBytes key) => UPath.Root / key.Hex;
147+
private static UPath DataPath(in KeyBytes key) => UPath.Root / $"{key:h}";
148148
}

src/Libplanet.Store/Trie/KeyBytes.cs

+15-27
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,8 @@ namespace Libplanet.Store.Trie;
1515
/// Creates a new <see cref="KeyBytes"/> instance from the given byte array.
1616
/// </remarks>
1717
/// <param name="bytes">An immutable byte array to wrap.</param>
18-
public readonly struct KeyBytes(in ImmutableArray<byte> bytes) : IEquatable<KeyBytes>
18+
public readonly struct KeyBytes(in ImmutableArray<byte> bytes) : IEquatable<KeyBytes>, IFormattable
1919
{
20-
/// <summary>
21-
/// The default <see cref="System.Text.Encoding"/>, which is <see cref="Encoding.UTF8"/>,ㅔ
22-
/// to use when creating an instance from a <see langword="string"/>.
23-
/// </summary>
24-
public static readonly Encoding Encoding = Encoding.UTF8;
25-
2620
public static readonly KeyBytes Empty = default;
2721

2822
private readonly ImmutableArray<byte> _bytes = bytes;
@@ -33,19 +27,7 @@ public readonly struct KeyBytes(in ImmutableArray<byte> bytes) : IEquatable<KeyB
3327
/// </summary>
3428
/// <param name="str">The key <see langword="string"/> to encode into bytes.</param>
3529
public KeyBytes(string str)
36-
: this(str, Encoding)
37-
{
38-
}
39-
40-
/// <summary>
41-
/// Creates a new <seealso cref="KeyBytes"/> instance from given <paramref name="str"/>
42-
/// with <paramref name="encoding"/>.
43-
/// </summary>
44-
/// <param name="str">The key <see langword="string"/> to encode into bytes.</param>
45-
/// <param name="encoding">The <see cref="System.Text.Encoding"/> to be used for
46-
/// <paramref name="str"/>.</param>
47-
private KeyBytes(string str, Encoding encoding)
48-
: this(CreateArray(str, encoding))
30+
: this(CreateArray(str))
4931
{
5032
}
5133

@@ -59,11 +41,6 @@ private KeyBytes(string str, Encoding encoding)
5941
/// </summary>
6042
public ImmutableArray<byte> ByteArray => _bytes.IsDefault ? [] : _bytes;
6143

62-
/// <summary>
63-
/// The hexadecimal string representation of the byte array.
64-
/// </summary>
65-
public string Hex => ByteUtil.Hex(ByteArray);
66-
6744
/// <summary>
6845
/// Compares two <see cref="KeyBytes"/> values.
6946
/// </summary>
@@ -138,9 +115,20 @@ public override string ToString()
138115
return $"{nameof(KeyBytes)} ({Length} B){hex}";
139116
}
140117

141-
private static ImmutableArray<byte> CreateArray(string str, Encoding encoding)
118+
/// <inheritdoc cref="IFormattable.ToString(string?, IFormatProvider?)"/>
119+
public string ToString(string? format, IFormatProvider? formatProvider)
120+
{
121+
return format switch
122+
{
123+
"h" => ByteUtil.Hex(ByteArray),
124+
"H" => ByteUtil.Hex(ByteArray).ToUpperInvariant(),
125+
_ => ToString(),
126+
};
127+
}
128+
129+
private static ImmutableArray<byte> CreateArray(string str)
142130
{
143-
var bytes = encoding.GetBytes(str);
131+
var bytes = Encoding.UTF8.GetBytes(str);
144132
return Unsafe.As<byte[], ImmutableArray<byte>>(ref bytes);
145133
}
146134
}

src/Libplanet.Store/Trie/MerkleTrie.Insert.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ private INode Insert(INode? node, in PathCursor cursor, ValueNode value, bool al
2323
// Note: Should not be called on short node or full node's value.
2424
private INode InsertToNullNode(PathCursor cursor, ValueNode value)
2525
{
26-
if (cursor.RemainingAnyNibbles)
26+
if (cursor.HasNext)
2727
{
28-
return new ShortNode(cursor.GetRemainingNibbles(), value);
28+
return new ShortNode(cursor.NextNibbles, value);
2929
}
3030
else
3131
{
@@ -36,11 +36,11 @@ private INode InsertToNullNode(PathCursor cursor, ValueNode value)
3636
// Note: Should not be called on full node's value.
3737
private INode InsertToValueNode(ValueNode valueNode, PathCursor cursor, ValueNode value)
3838
{
39-
if (cursor.RemainingAnyNibbles)
39+
if (cursor.HasNext)
4040
{
4141
return FullNode.Empty
4242
.SetChild(FullNode.ChildrenCount - 1, valueNode)
43-
.SetChild(cursor.NextNibble, InsertToNullNode(cursor.Next(1), value));
43+
.SetChild(cursor.Current, InsertToNullNode(cursor.Next(1), value));
4444
}
4545
else
4646
{
@@ -82,9 +82,9 @@ private INode InsertToShortNode(ShortNode shortNode, in PathCursor cursor, Value
8282
// Handles value node.
8383
// Assumes next cursor nibble (including non-remaining case)
8484
// does not conflict with short node above.
85-
fullNode = nextCursor.RemainingNibbleLength > 0
85+
fullNode = nextCursor.NextLength > 0
8686
? fullNode.SetChild(
87-
nextCursor.NextNibble,
87+
nextCursor.Current,
8888
InsertToNullNode(nextCursor.Next(1), value))
8989
: fullNode.SetChild(
9090
FullNode.ChildrenCount - 1,
@@ -106,9 +106,9 @@ private INode InsertToShortNode(ShortNode shortNode, in PathCursor cursor, Value
106106

107107
private INode InsertToFullNode(FullNode fullNode, PathCursor cursor, ValueNode value)
108108
{
109-
if (cursor.RemainingAnyNibbles)
109+
if (cursor.HasNext)
110110
{
111-
byte nextNibble = cursor.NextNibble;
111+
byte nextNibble = cursor.Current;
112112
return fullNode.SetChild(
113113
nextNibble,
114114
Insert(fullNode.Children[nextNibble], cursor.Next(1), value, true));

src/Libplanet.Store/Trie/MerkleTrie.Path.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ public partial class MerkleTrie
99
node switch
1010
{
1111
null => null,
12-
ValueNode valueNode => !cursor.RemainingAnyNibbles
12+
ValueNode valueNode => !cursor.HasNext
1313
? valueNode.Value
1414
: null,
15-
ShortNode shortNode => cursor.RemainingNibblesStartWith(shortNode.Key)
15+
ShortNode shortNode => cursor.NextNibbles.StartsWith(shortNode.Key)
1616
? ResolveToValue(shortNode.Value, cursor.Next(shortNode.Key.Length))
1717
: null,
18-
FullNode fullNode => cursor.RemainingAnyNibbles
18+
FullNode fullNode => cursor.HasNext
1919
? ResolveToValue(
20-
fullNode.Children[cursor.NextNibble],
20+
fullNode.Children[cursor.Current],
2121
cursor.Next(1))
2222
: ResolveToValue(
2323
fullNode.Value,
@@ -29,25 +29,25 @@ public partial class MerkleTrie
2929

3030
private INode? ResolveToNode(INode? node, in PathCursor cursor)
3131
{
32-
if (cursor.RemainingAnyNibbles)
32+
if (cursor.HasNext)
3333
{
3434
switch (node)
3535
{
3636
case null:
3737
case ValueNode _:
3838
return null;
3939
case ShortNode shortNode:
40-
return cursor.RemainingNibblesStartWith(shortNode.Key)
40+
return cursor.NextNibbles.StartsWith(shortNode.Key)
4141
? ResolveToNode(shortNode.Value, cursor.Next(shortNode.Key.Length))
4242
: null;
4343
case FullNode fullNode:
44-
return ResolveToNode(fullNode.Children[cursor.NextNibble], cursor.Next(1));
44+
return ResolveToNode(fullNode.Children[cursor.Current], cursor.Next(1));
4545
case HashNode hashNode:
4646
return ResolveToNode(UnhashNode(hashNode), cursor);
4747
default:
4848
throw new InvalidTrieNodeException(
4949
$"An unknown type of node was encountered " +
50-
$"at {cursor.GetConsumedNibbles().Hex}: {node.GetType()}");
50+
$"at {cursor.PrevNibbles.Hex}: {node.GetType()}");
5151
}
5252
}
5353
else

src/Libplanet.Store/Trie/MerkleTrie.Proof.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static bool ValidateProof(
3131
IValue value)
3232
{
3333
HashDigest<SHA256> targetHash = stateRootHash;
34-
PathCursor cursor = new PathCursor(key);
34+
PathCursor cursor = PathCursor.Create(key);
3535
for (int i = 0; i < proof.Count; i++)
3636
{
3737
INode proofNode = proof[i];
@@ -130,7 +130,7 @@ public IReadOnlyList<INode> GenerateProof(KeyBytes key, IValue value)
130130
$"Encountered an unexpected node type {root.GetType()}");
131131

132132
List<INode> proof = new List<INode>();
133-
PathCursor cursor = new PathCursor(key);
133+
PathCursor cursor = PathCursor.Create(key);
134134

135135
while (hashNode is HashNode currentHashNode)
136136
{
@@ -217,7 +217,7 @@ private static (INode NextNode, PathCursor NextCursor)? ResolveToNextCandidateNo
217217
return (valueNode, cursor);
218218

219219
case ShortNode shortNode:
220-
if (cursor.RemainingNibblesStartWith(shortNode.Key))
220+
if (cursor.NextNibbles.StartsWith(shortNode.Key))
221221
{
222222
return ResolveToNextCandidateNode(
223223
shortNode.Value, cursor.Next(shortNode.Key.Length));
@@ -242,7 +242,7 @@ private static (INode NextNode, PathCursor NextCursor)? ResolveToNextCandidateNo
242242
}
243243
else
244244
{
245-
INode? nextNode = fullNode.Children[cursor.NextNibble];
245+
INode? nextNode = fullNode.Children[cursor.Current];
246246
if (nextNode is INode n)
247247
{
248248
return ResolveToNextCandidateNode(n, cursor.Next(1));

src/Libplanet.Store/Trie/MerkleTrie.Remove.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public partial class MerkleTrie
2626
Remove(UnhashNode(hashNode), cursor);
2727

2828
private INode? RemoveFromValueNode(ValueNode valueNode, PathCursor cursor) =>
29-
cursor.RemainingAnyNibbles
29+
cursor.HasNext
3030
? valueNode
3131
: null;
3232

@@ -68,9 +68,9 @@ public partial class MerkleTrie
6868

6969
private INode RemoveFromFullNode(FullNode fullNode, PathCursor cursor)
7070
{
71-
if (cursor.RemainingAnyNibbles)
71+
if (cursor.HasNext)
7272
{
73-
byte nextNibble = cursor.NextNibble;
73+
byte nextNibble = cursor.Current;
7474
if (fullNode.Children[nextNibble] is { } child)
7575
{
7676
INode? processedChild = Remove(child, cursor.Next(1));

src/Libplanet.Store/Trie/MerkleTrie.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public ITrie Set(in KeyBytes key, IValue value)
7272
var cache = _cache;
7373
var node = Insert(
7474
Node,
75-
new PathCursor(key),
75+
PathCursor.Create(key),
7676
new ValueNode(value),
7777
true);
7878

@@ -83,12 +83,12 @@ public ITrie Set(in KeyBytes key, IValue value)
8383
public ITrie Remove(in KeyBytes key)
8484
{
8585
var cache = _cache;
86-
var node = RemoveFromRoot(new PathCursor(key));
86+
var node = RemoveFromRoot(PathCursor.Create(key));
8787
return new MerkleTrie(keyValueStore, node, cache);
8888
}
8989

9090
/// <inheritdoc cref="ITrie.Get(KeyBytes)"/>
91-
public IValue? Get(KeyBytes key) => ResolveToValue(Node, new PathCursor(key));
91+
public IValue? Get(KeyBytes key) => ResolveToValue(Node, PathCursor.Create(key));
9292

9393
/// <inheritdoc cref="ITrie.Get(IReadOnlyList{KeyBytes})"/>
9494
public IReadOnlyList<IValue?> Get(IReadOnlyList<KeyBytes> keys)

src/Libplanet.Store/Trie/Nibbles.cs

+19
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@ public KeyBytes ToKeyBytes()
155155
return new KeyBytes(builder.ToImmutable());
156156
}
157157

158+
[Pure]
159+
public bool StartsWith(in Nibbles nibbles)
160+
{
161+
if (Length < nibbles.Length)
162+
{
163+
return false;
164+
}
165+
166+
for (var i = 0; i < nibbles.Length; i++)
167+
{
168+
if (ByteArray[i] != nibbles.ByteArray[i])
169+
{
170+
return false;
171+
}
172+
}
173+
174+
return true;
175+
}
176+
158177
public bool Equals(Nibbles other) => ByteArray.SequenceEqual(other.ByteArray);
159178

160179
/// <inheritdoc cref="object.Equals(object?)"/>

src/Libplanet.Store/Trie/PathCursor.cs

+9-32
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ public readonly struct PathCursor
1212

1313
public readonly int Offset;
1414

15-
public PathCursor(in KeyBytes keyBytes)
16-
: this(Nibbles.FromKeyBytes(keyBytes), 0)
17-
{
18-
}
19-
2015
/// <summary>
2116
/// Creates a <see cref="PathCursor"/> from <paramref name="nibbles"/>.
2217
/// </summary>
@@ -40,45 +35,27 @@ private PathCursor(in Nibbles nibbles, int offset)
4035
}
4136

4237
[Pure]
43-
public readonly int RemainingNibbleLength => Length - Offset;
38+
public readonly int NextLength => Length - Offset;
4439

4540
[Pure]
46-
public readonly bool RemainingAnyNibbles => Length > Offset;
41+
public readonly bool HasNext => Length > Offset;
4742

4843
[Pure]
49-
public readonly byte NextNibble => Nibbles[Offset];
44+
public readonly byte Current => Nibbles[Offset];
5045

5146
[Pure]
52-
public Nibbles GetConsumedNibbles() => Nibbles.Take(Offset);
47+
public Nibbles NextNibbles => Nibbles.Skip(Offset);
5348

5449
[Pure]
55-
public Nibbles GetRemainingNibbles() => Nibbles.Skip(Offset);
50+
public Nibbles PrevNibbles => Nibbles.Take(Offset);
51+
52+
public static PathCursor Create(in KeyBytes keyBytes) => new(Nibbles.FromKeyBytes(keyBytes));
5653

5754
[Pure]
5855
public PathCursor Next(int offset) => offset < 0
5956
? throw new ArgumentOutOfRangeException(nameof(offset))
6057
: new PathCursor(Nibbles, Offset + offset);
6158

62-
[Pure]
63-
public bool RemainingNibblesStartWith(in Nibbles nibbles)
64-
{
65-
if (RemainingNibbleLength < nibbles.Length)
66-
{
67-
return false;
68-
}
69-
70-
for (var i = 0; i < nibbles.Length; i++)
71-
{
72-
var offset = Offset + i;
73-
if (Nibbles[offset] != nibbles[i])
74-
{
75-
return false;
76-
}
77-
}
78-
79-
return true;
80-
}
81-
8259
/// <summary>
8360
/// Counts the number of common starting nibbles from the remaining path.
8461
/// </summary>
@@ -87,7 +64,7 @@ public bool RemainingNibblesStartWith(in Nibbles nibbles)
8764
[Pure]
8865
public int CountCommonStartingNibbles(in Nibbles nibbles)
8966
{
90-
var length = Math.Min(RemainingNibbleLength, nibbles.Length);
67+
var length = Math.Min(NextLength, nibbles.Length);
9168
var i = 0;
9269
for (; i < length; i++)
9370
{
@@ -104,7 +81,7 @@ public int CountCommonStartingNibbles(in Nibbles nibbles)
10481
[Pure]
10582
public Nibbles GetCommonStartingNibbles(in Nibbles nibbles)
10683
{
107-
var length = Math.Min(RemainingNibbleLength, nibbles.Length);
84+
var length = Math.Min(NextLength, nibbles.Length);
10885
var builder = ImmutableArray.CreateBuilder<byte>(length);
10986

11087
for (var i = 0; i < length; i++)

test/Libplanet.Tests/Store/Trie/KeyBytesTest.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ public void Hex()
7070
var b122 = KeyBytes.Create(1, 2, 2);
7171
var b1234 = KeyBytes.Create(1, 2, 3, 4);
7272

73-
Assert.Empty(empty.Hex);
74-
Assert.Equal("010203", b123.Hex);
75-
Assert.Equal("010202", b122.Hex);
76-
Assert.Equal("01020304", b1234.Hex);
73+
Assert.Empty($"{empty:h}");
74+
Assert.Equal("010203", $"{b123:h}");
75+
Assert.Equal("010202", $"{b122:h}");
76+
Assert.Equal("01020304", $"{b1234:h}");
7777
}
7878

7979
[Fact]

0 commit comments

Comments
 (0)