Skip to content

Commit

Permalink
Merge branch 'main' into importer
Browse files Browse the repository at this point in the history
  • Loading branch information
Scooletz committed Feb 23, 2024
2 parents 53ad39b + 93b2eab commit 3aa2830
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 61 deletions.
17 changes: 15 additions & 2 deletions src/Paprika.Tests/Data/KeyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ public void LongHash()
NibblePath.FromKey(Keccak.EmptyTreeHash).SliceTo(i)));
}

for (var x = 0; x < 16; x++)
{
var nibbles = NibblePath.Parse("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde" + x.ToString("x").ToLowerInvariant());
Unique(Key.Raw(aPath, DataType.Merkle, nibbles));

nibbles = NibblePath.Parse(x.ToString("x").ToLowerInvariant() + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde");
for (var i = 1; i < 32; i++)
{
Unique(Key.Raw(aPath, DataType.Merkle, nibbles.SliceTo(i)));
}
}

// Additional colliding ones
Unique(Key.Merkle(NibblePath.Parse("DAE3")));
Unique(Key.Merkle(NibblePath.Parse("251C")));
Expand All @@ -118,7 +130,8 @@ void Unique(in Key key)
if (hashes.TryAdd(hash, hex) == false)
{
var existing = hashes[hash];
Assert.Fail($"The hash for {hex} is the same as for {existing}");
if (existing != hex)
Assert.Fail($"The hash for {hex} is the same as for {existing}");
}
}
}
Expand All @@ -130,4 +143,4 @@ private static void ReadWriteAssert(in Key expected)
Key.ReadFrom(written, out var actual);
actual.Equals(expected).Should().BeTrue();
}
}
}
12 changes: 8 additions & 4 deletions src/Paprika/Data/Key.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.IO.Hashing;
using System.Numerics;
using System.Runtime.CompilerServices;
using Paprika.Crypto;
using Paprika.Store;
Expand Down Expand Up @@ -99,14 +100,17 @@ public static ReadOnlySpan<byte> ReadFrom(ReadOnlySpan<byte> source, out Key key
[SkipLocalsInit]
public override int GetHashCode()
{
return (int)Type ^ Path.GetHashCode() ^ StoragePath.GetHashCode();
return (int)BitOperations.Crc32C((uint)Path.GetHashCode(), (uint)StoragePath.GetHashCode()) + (byte)Type;
}

[SkipLocalsInit]
public ulong GetHashCodeULong()
{
return unchecked((ulong)(((long)Path.GetHashCode() << 32) |
(long)(StoragePath.GetHashCode() ^ (byte)Type)));
var pathHash = Path.GetHashCode();
var storageHash = StoragePath.GetHashCode();

ulong hash = BitOperations.Crc32C((uint)pathHash, (uint)storageHash) + (byte)Type;
return (((ulong)(uint)pathHash) << 32 | (uint)storageHash) ^ (hash << 32 | hash);
}

public override string ToString()
Expand All @@ -120,4 +124,4 @@ public override string ToString()
/// The predicate over a key.
/// </summary>
public delegate bool Predicate(in Key key);
}
}
73 changes: 45 additions & 28 deletions src/Paprika/Data/NibblePath.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Paprika.Crypto;
Expand Down Expand Up @@ -528,63 +530,78 @@ public override int GetHashCode()
{
ref var span = ref _span;

int hash = Length << 24;
var length = Length;
uint hash = (uint)Length << 24;
nuint length = Length;

if (_odd == OddBit)
{
// mix in first half
hash ^= (_span & 0x0F) << 20;
hash |= (uint)(_span & 0x0F) << 20;
span = ref Unsafe.Add(ref span, 1);
length -= 1;
}

if (length % 2 == 1)
{
// mix in
unchecked
{
hash ^= GetAt(length - 1) << 16;
}

hash |= (uint)GetAt((int)length - 1) << 16;
length -= 1;
}

Debug.Assert(length % 2 == 0, "Length should be even here");

length /= 2; // make it byte

// 4 bytes
var intLoop = Math.DivRem(length, sizeof(int), out var remainder);
for (var i = 0; i < intLoop; i++)
// 8 bytes
if (length >= sizeof(long))
{
unchecked
nuint offset = 0;
nuint longLoop = length - sizeof(long);
if (longLoop != 0)
{
var v = Unsafe.ReadUnaligned<int>(ref span);
hash ^= v;
span = ref Unsafe.Add(ref span, sizeof(int));
do
{
hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref span, offset)));
offset += sizeof(long);
} while (longLoop > offset);
}

// Do final hash as sizeof(long) from end rather than start
hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref span, longLoop)));

return (int)hash;
}

// 2 bytes
if (remainder >= sizeof(short))
// 4 bytes
if (length >= sizeof(int))
{
unchecked
hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned<uint>(ref span));
length -= sizeof(int);
if (length > 0)
{
var v = Unsafe.ReadUnaligned<short>(ref span);
hash ^= v;
span = ref Unsafe.Add(ref span, sizeof(short));
remainder -= sizeof(short);
// Do final hash as sizeof(long) from end rather than start
hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref span, length)));
}

return (int)hash;
}

// 1 byte
if (remainder > 0)
// 2 bytes
if (length >= sizeof(short))
{
hash ^= span;
hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned<ushort>(ref span));
length -= sizeof(short);
if (length > 0)
{
// Do final hash as sizeof(long) from end rather than start
hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned<ushort>(ref Unsafe.Add(ref span, length)));
}

return (int)hash;
}

return hash;
// 1 byte
return (int)BitOperations.Crc32C(hash, span);
}
}

Expand All @@ -601,4 +618,4 @@ public bool HasOnlyZeroes()

return true;
}
}
}
2 changes: 1 addition & 1 deletion src/Paprika/Store/BatchContextBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public Page GetWritableCopy(Page page)

public abstract void RegisterForFutureReuse(Page page);

public abstract Dictionary<Keccak, uint> IdCache { get; }
public abstract IDictionary<Keccak, uint> IdCache { get; }

/// <summary>
/// Assigns the batch identifier to a given page, marking it writable by this batch.
Expand Down
4 changes: 2 additions & 2 deletions src/Paprika/Store/IBatchContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ public interface IBatchContext : IReadOnlyBatchContext
/// </summary>
void RegisterForFutureReuse(Page page);

Dictionary<Keccak, uint> IdCache { get; }

/// <summary>
/// Assigns the batch identifier to a given page, marking it writable by this batch.
/// </summary>
Expand All @@ -55,6 +53,8 @@ public interface IReadOnlyBatchContext : IPageResolver
/// Gets the current <see cref="IBatch"/> id.
/// </summary>
uint BatchId { get; }

IDictionary<Keccak, uint> IdCache { get; }
}

public static class ReadOnlyBatchContextExtensions
Expand Down
8 changes: 7 additions & 1 deletion src/Paprika/Store/PagedDb.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Runtime.InteropServices;
using Paprika.Chain;
Expand Down Expand Up @@ -356,6 +357,9 @@ private DbAddress SetNewRoot(RootPage root)

private class ReadOnlyBatch(PagedDb db, RootPage root, string name) : IReportingReadOnlyBatch, IReadOnlyBatchContext
{
private readonly ConcurrentDictionary<Keccak, uint> _idCache = new(Environment.ProcessorCount,
RootPage.IdCacheLimit);

public RootPage Root => root;

private long _reads;
Expand Down Expand Up @@ -393,6 +397,8 @@ public void Report(IReporter state, IReporter storage)

public uint BatchId => root.Header.BatchId;

public IDictionary<Keccak, uint> IdCache => _idCache;

public Page GetAt(DbAddress address) => db._manager.GetAt(address);

public override string ToString() => $"{nameof(ReadOnlyBatch)}, Name: {name}, BatchId: {BatchId}";
Expand Down
70 changes: 47 additions & 23 deletions src/Paprika/Store/RootPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public void Accept(IPageVisitor visitor, IPageResolver resolver)
Data.AbandonedList.Accept(visitor, resolver);
}

/// <summary>
/// How many id entries should be cached per readonly batch.
/// </summary>
public const int IdCacheLimit = 2_000;

public bool TryGet(scoped in Key key, IReadOnlyBatchContext batch, out ReadOnlySpan<byte> result)
{
if (key.IsState)
Expand All @@ -118,31 +123,49 @@ public bool TryGet(scoped in Key key, IReadOnlyBatchContext batch, out ReadOnlyS
return new FanOutPage(batch.GetAt(Data.StateRoot)).TryGet(key.Path, batch, out result);
}

if (Data.Ids.TryGet(key.Path, batch, out var id) == false)
Span<byte> idSpan = stackalloc byte[sizeof(uint)];

ReadOnlySpan<byte> id;
var cache = batch.IdCache;
var keccak = key.Path.UnsafeAsKeccak;

if (cache.TryGetValue(keccak, out var cachedId))
{
result = default;
return false;
if (cachedId == 0)
{
result = default;
return false;
}

WriteId(idSpan, cachedId);
id = idSpan;
}
else
{
if (Data.Ids.TryGet(key.Path, batch, out id))
{
if (cache.Count < IdCacheLimit)
{
cache[keccak] = ReadId(id);
}
}
else
{
// Not found, for now, not remember misses, remember miss
// cache[keccak] = 0;
result = default;
return false;
}
}

var path = NibblePath.FromKey(id).Append(key.StoragePath, stackalloc byte[StorageKeySize]);

return Data.Storage.TryGet(path, batch, out result);
}

/// <summary>
/// Encodes the path in a way that makes it byte-aligned and unique, but also sort:
///
/// - empty path is left empty
/// - odd-length path is padded with a single nibble with value of 0x01
/// - even-length path is padded with a single byte (2 nibbles with value of 0x00)
///
/// To ensure that <see cref="DataType.Merkle"/> and <see cref="DataType.Account"/>/<see cref="DataType.StorageCell"/>
/// of the same length can coexist, the merkle marker is added as well.
/// </summary>
public static NibblePath Encode(in NibblePath path, in Span<byte> destination, DataType type)
{
return path;
}
private static uint ReadId(ReadOnlySpan<byte> id) => BinaryPrimitives.ReadUInt32LittleEndian(id);
private static void WriteId(Span<byte> idSpan, uint cachedId) => BinaryPrimitives.WriteUInt32LittleEndian(idSpan, cachedId);


public void SetRaw(in Key key, IBatchContext batch, ReadOnlySpan<byte> rawData)
{
Expand All @@ -155,9 +178,11 @@ public void SetRaw(in Key key, IBatchContext batch, ReadOnlySpan<byte> rawData)
scoped NibblePath id;
Span<byte> idSpan = stackalloc byte[sizeof(uint)];

if (batch.IdCache.TryGetValue(key.Path.UnsafeAsKeccak, out var cachedId))
var keccak = key.Path.UnsafeAsKeccak;

if (batch.IdCache.TryGetValue(keccak, out var cachedId))
{
BinaryPrimitives.WriteUInt32LittleEndian(idSpan, cachedId);
WriteId(idSpan, cachedId);
id = NibblePath.FromKey(idSpan);
}
else
Expand All @@ -166,10 +191,10 @@ public void SetRaw(in Key key, IBatchContext batch, ReadOnlySpan<byte> rawData)
if (Data.Ids.TryGet(key.Path, batch, out var existingId) == false)
{
Data.AccountCounter++;
BinaryPrimitives.WriteUInt32LittleEndian(idSpan, Data.AccountCounter);
WriteId(idSpan, Data.AccountCounter);

// memoize in cache
batch.IdCache[key.Path.UnsafeAsKeccak] = Data.AccountCounter;
batch.IdCache[keccak] = Data.AccountCounter;

// update root
Data.Ids.Set(key.Path, idSpan, batch);
Expand All @@ -179,13 +204,12 @@ public void SetRaw(in Key key, IBatchContext batch, ReadOnlySpan<byte> rawData)
else
{
// memoize in cache
batch.IdCache[key.Path.UnsafeAsKeccak] = BinaryPrimitives.ReadUInt32LittleEndian(existingId);
batch.IdCache[keccak] = ReadId(existingId);
id = NibblePath.FromKey(existingId);
}
}

var path = id.Append(key.StoragePath, stackalloc byte[StorageKeySize]);

Data.Storage.Set(path, rawData, batch);
}
}
Expand Down

0 comments on commit 3aa2830

Please sign in to comment.