Skip to content

Commit

Permalink
Fast Keccak cache (wait-free and lockless) (#7336)
Browse files Browse the repository at this point in the history
Co-authored-by: Ben Adams <[email protected]>
  • Loading branch information
Scooletz and benaadams authored Sep 19, 2024
1 parent 15ded8a commit 89292b1
Show file tree
Hide file tree
Showing 15 changed files with 502 additions and 151 deletions.
121 changes: 121 additions & 0 deletions src/Nethermind/Nethermind.Core.Test/KeccakCacheTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Buffers.Binary;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
using NUnit.Framework;

namespace Nethermind.Core.Test
{
[TestFixture]
public class KeccakCacheTests
{
[Test]
public void Multiple()
{
const int spins = 10;

var random = new Random(13);
var bytes = new byte[31]; // misaligned length
random.NextBytes(bytes);

ValueHash256 expected = ValueKeccak.Compute(bytes);

for (int i = 0; i < spins; i++)
{
ValueHash256 actual = KeccakCache.Compute(bytes);
actual.Equals(expected).Should().BeTrue();
}
}

[Test]
public void Empty()
{
ReadOnlySpan<byte> span = ReadOnlySpan<byte>.Empty;
KeccakCache.Compute(span).Should().Be(ValueKeccak.Compute(span));
}

[Test]
public void Very_long()
{
ReadOnlySpan<byte> span = new byte[192];
KeccakCache.Compute(span).Should().Be(ValueKeccak.Compute(span));
}

private string[] GetBucketCollisions()
{
var random = new Random(13);
Span<byte> span = stackalloc byte[32];
string[] collisions = new string[4];

random.NextBytes(span);
var bucket = KeccakCache.GetBucket(span);

Console.WriteLine(span.ToHexString());

collisions[0] = span.ToHexString();
var found = 1;

ulong iterations = 0;
while (found < 4)
{
random.NextBytes(span);
if (KeccakCache.GetBucket(span) == bucket)
{
collisions[found] = span.ToHexString();
Console.WriteLine(span.ToHexString());
found++;
}
iterations++;
}

Console.WriteLine($"{iterations} iterations to find");
return collisions;
}

[Test]
public void Collision()
{
var colliding = GetBucketCollisions();

var collisions = colliding.Length;
var array = colliding.Select(c => Bytes.FromHexString(c)).ToArray();
var values = array.Select(a => ValueKeccak.Compute(a)).ToArray();

var bucket = KeccakCache.GetBucket(array[0]);

for (int i = 1; i < collisions; i++)
{
var input = array[i];
bucket.Should().Be(KeccakCache.GetBucket(input));
KeccakCache.Compute(input).Should().Be(values[i]);
}

Parallel.ForEach(array, (a, state, index) =>
{
ValueHash256 v = values[index];
for (int i = 0; i < 100_000; i++)
{
KeccakCache.Compute(a).Should().Be(v);
}
});
}

[Test]
public void Spin_through_all()
{
Span<byte> span = stackalloc byte[4];
for (int i = 0; i < KeccakCache.Count; i++)
{
BinaryPrimitives.WriteInt32LittleEndian(span, i);
KeccakCache.Compute(span).Should().Be(ValueKeccak.Compute(span));
}
}
}
}
16 changes: 2 additions & 14 deletions src/Nethermind/Nethermind.Core/Address.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ namespace Nethermind.Core
[TypeConverter(typeof(AddressTypeConverter))]
public class Address : IEquatable<Address>, IComparable<Address>
{
// Ensure that hashes are different for every run of the node and every node, so if are any hash collisions on
// one node they will not be the same on another node or across a restart so hash collision cannot be used to degrade
// the performance of the network as a whole.
private static readonly uint s_instanceRandom = (uint)System.Security.Cryptography.RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue);

public const int Size = 20;
private const int HexCharsCount = 2 * Size; // 5a4eab120fb44eb6684e5e32785702ff45ea344d
private const int PrefixedHexCharsCount = 2 + HexCharsCount; // 0x5a4eab120fb44eb6684e5e32785702ff45ea344d
Expand Down Expand Up @@ -201,14 +196,7 @@ public override bool Equals(object? obj)
return obj.GetType() == GetType() && Equals((Address)obj);
}

public override int GetHashCode()
{
uint hash = s_instanceRandom;
hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned<ulong>(ref MemoryMarshal.GetArrayDataReference(Bytes)));
hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Bytes), sizeof(ulong))));
hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Bytes), sizeof(long) * 2)));
return (int)hash;
}
public override int GetHashCode() => new ReadOnlySpan<byte>(Bytes).FastHash();

public static bool operator ==(Address? a, Address? b)
{
Expand Down Expand Up @@ -243,7 +231,7 @@ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destina
destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}

public Hash256 ToAccountPath => Keccak.Compute(Bytes);
public Hash256 ToAccountPath => KeccakCache.Compute(Bytes);

[SkipLocalsInit]
public ValueHash256 ToHash()
Expand Down
10 changes: 2 additions & 8 deletions src/Nethermind/Nethermind.Core/Bloom.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,7 @@ public override bool Equals(object? obj)
return Equals((Bloom)obj);
}

public override int GetHashCode()
{
return Bytes.GetSimplifiedHashCode();
}
public override int GetHashCode() => new ReadOnlySpan<byte>(Bytes).FastHash();

public void Add(LogEntry[] logEntries, Bloom? blockBloom = null)
{
Expand Down Expand Up @@ -278,10 +275,7 @@ public override readonly bool Equals(object? obj)
return Equals((Bloom)obj);
}

public override readonly int GetHashCode()
{
return Core.Extensions.Bytes.GetSimplifiedHashCode(Bytes);
}
public override readonly int GetHashCode() => Bytes.FastHash();

public readonly bool Matches(LogEntry logEntry)
{
Expand Down
14 changes: 2 additions & 12 deletions src/Nethermind/Nethermind.Core/Crypto/Hash256.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,9 @@ public ValueHash256(ReadOnlySpan<byte> bytes)

public bool Equals(Hash256? other) => _bytes.Equals(other?.ValueHash256._bytes ?? default);

public override int GetHashCode()
{
return GetChainedHashCode(s_instanceRandom);
}
public override int GetHashCode() => GetChainedHashCode(s_instanceRandom);

public int GetChainedHashCode(uint previousHash)
{
uint hash = BitOperations.Crc32C(previousHash, Unsafe.As<Vector256<byte>, ulong>(ref Unsafe.AsRef(in _bytes)));
hash = BitOperations.Crc32C(hash, Unsafe.Add(ref Unsafe.As<Vector256<byte>, ulong>(ref Unsafe.AsRef(in _bytes)), 1));
hash = BitOperations.Crc32C(hash, Unsafe.Add(ref Unsafe.As<Vector256<byte>, ulong>(ref Unsafe.AsRef(in _bytes)), 2));
hash = BitOperations.Crc32C(hash, Unsafe.Add(ref Unsafe.As<Vector256<byte>, ulong>(ref Unsafe.AsRef(in _bytes)), 3));
return (int)hash;
}
public int GetChainedHashCode(uint previousHash) => Bytes.FastHash() ^ (int)previousHash;

public int CompareTo(ValueHash256 other)
{
Expand Down
Loading

0 comments on commit 89292b1

Please sign in to comment.