Skip to content

Commit 0b5cab4

Browse files
authored
Merge pull request #7 from janb84/pr33855_btck_block_tree_entry_equals
Pr33855 btck_block_tree_entry_equals
2 parents 4ba1ff8 + 385dcde commit 0b5cab4

5 files changed

Lines changed: 206 additions & 2 deletions

File tree

2.04 MB
Binary file not shown.
211 KB
Binary file not shown.

src/BitcoinKernel.Core/BlockProcessing/BlockTreeEntry.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace BitcoinKernel.Core.BlockProcessing;
77
/// <summary>
88
/// Represents an entry in the block tree (block index).
99
/// </summary>
10-
public sealed class BlockTreeEntry
10+
public sealed class BlockTreeEntry : IEquatable<BlockTreeEntry>
1111
{
1212
private readonly IntPtr _handle;
1313

@@ -49,4 +49,50 @@ public int GetHeight()
4949
{
5050
return NativeMethods.BlockTreeEntryGetHeight(_handle);
5151
}
52+
53+
/// <summary>
54+
/// Determines whether two block tree entries are equal.
55+
/// Two block tree entries are equal when they point to the same block.
56+
/// </summary>
57+
public bool Equals(BlockTreeEntry? other)
58+
{
59+
if (other is null)
60+
return false;
61+
if (ReferenceEquals(this, other))
62+
return true;
63+
return NativeMethods.BlockTreeEntryEquals(_handle, other._handle) == 1;
64+
}
65+
66+
/// <inheritdoc/>
67+
public override bool Equals(object? obj) => Equals(obj as BlockTreeEntry);
68+
69+
/// <inheritdoc/>
70+
public override int GetHashCode()
71+
{
72+
// Use the block hash bytes to compute hash code
73+
// Read directly from native without wrapping in a BlockHash that would dispose
74+
var hashPtr = NativeMethods.BlockTreeEntryGetBlockHash(_handle);
75+
if (hashPtr == IntPtr.Zero)
76+
{
77+
return 0;
78+
}
79+
var hashBytes = new byte[32];
80+
NativeMethods.BlockHashToBytes(hashPtr, hashBytes);
81+
return BitConverter.ToInt32(hashBytes, 0);
82+
}
83+
84+
/// <summary>
85+
/// Determines whether two block tree entries are equal.
86+
/// </summary>
87+
public static bool operator ==(BlockTreeEntry? left, BlockTreeEntry? right)
88+
{
89+
if (left is null)
90+
return right is null;
91+
return left.Equals(right);
92+
}
93+
94+
/// <summary>
95+
/// Determines whether two block tree entries are not equal.
96+
/// </summary>
97+
public static bool operator !=(BlockTreeEntry? left, BlockTreeEntry? right) => !(left == right);
5298
}

src/BitcoinKernel.Interop/NativeMethods.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,13 @@ public static extern int BlockToBytes(
273273
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_block_tree_entry_get_previous")]
274274
public static extern IntPtr BlockTreeEntryGetPrevious(IntPtr block_tree_entry);
275275

276-
276+
/// <summary>
277+
/// Checks if two block tree entries are equal. Two block tree entries are equal when they
278+
/// point to the same block.
279+
/// Returns 1 if equal, 0 otherwise.
280+
/// </summary>
281+
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_block_tree_entry_equals")]
282+
public static extern int BlockTreeEntryEquals(IntPtr entry1, IntPtr entry2);
277283

278284
#endregion
279285

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using BitcoinKernel.Core.BlockProcessing;
2+
using BitcoinKernel.Core.Chain;
3+
using BitcoinKernel.Interop.Enums;
4+
5+
6+
namespace BitcoinKernel.Core.Tests;
7+
8+
public class BlockTreeEntryTests : IDisposable
9+
{
10+
private KernelContext? _context;
11+
private ChainParameters? _chainParams;
12+
private ChainstateManager? _chainstateManager;
13+
private BlockProcessor? _blockProcessor;
14+
private string? _tempDir;
15+
16+
public void Dispose()
17+
{
18+
_chainstateManager?.Dispose();
19+
_chainParams?.Dispose();
20+
_context?.Dispose();
21+
22+
if (!string.IsNullOrEmpty(_tempDir) && Directory.Exists(_tempDir))
23+
{
24+
Directory.Delete(_tempDir, true);
25+
}
26+
}
27+
28+
private void SetupWithBlocks()
29+
{
30+
_chainParams = new ChainParameters(ChainType.REGTEST);
31+
var contextOptions = new KernelContextOptions().SetChainParams(_chainParams);
32+
_context = new KernelContext(contextOptions);
33+
34+
_tempDir = Path.Combine(Path.GetTempPath(), $"test_blocktreeentry_{Guid.NewGuid()}");
35+
Directory.CreateDirectory(_tempDir);
36+
37+
var options = new ChainstateManagerOptions(_context, _tempDir, Path.Combine(_tempDir, "blocks"));
38+
_chainstateManager = new ChainstateManager(_context, _chainParams, options);
39+
_blockProcessor = new BlockProcessor(_chainstateManager);
40+
41+
// Process test blocks
42+
foreach (var rawBlock in ReadBlockData())
43+
{
44+
using var block = Abstractions.Block.FromBytes(rawBlock);
45+
_chainstateManager.ProcessBlock(block);
46+
}
47+
}
48+
49+
private static List<byte[]> ReadBlockData()
50+
{
51+
var blockData = new List<byte[]>();
52+
var testAssemblyDir = Path.GetDirectoryName(typeof(BlockTreeEntryTests).Assembly.Location);
53+
var projectDir = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(testAssemblyDir)));
54+
var blockDataFile = Path.Combine(projectDir!, "TestData", "block_data.txt");
55+
56+
foreach (var line in File.ReadLines(blockDataFile))
57+
{
58+
if (!string.IsNullOrWhiteSpace(line))
59+
{
60+
blockData.Add(Convert.FromHexString(line.Trim()));
61+
}
62+
}
63+
64+
return blockData;
65+
}
66+
67+
[Fact]
68+
public void Equals_SameBlock_ReturnsTrue()
69+
{
70+
SetupWithBlocks();
71+
var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
72+
73+
var entry1 = _blockProcessor!.GetBlockTreeEntry(tipHash);
74+
var entry2 = _blockProcessor.GetBlockTreeEntry(tipHash);
75+
76+
Assert.True(entry1!.Equals(entry2));
77+
}
78+
79+
[Fact]
80+
public void Equals_DifferentBlocks_ReturnsFalse()
81+
{
82+
SetupWithBlocks();
83+
var chain = _chainstateManager!.GetActiveChain();
84+
85+
var tipEntry = _blockProcessor!.GetBlockTreeEntry(chain.GetTip().GetBlockHash());
86+
var genesisEntry = _blockProcessor.GetBlockTreeEntry(chain.GetBlockByHeight(0)!.GetBlockHash());
87+
88+
Assert.False(tipEntry!.Equals(genesisEntry));
89+
}
90+
91+
[Fact]
92+
public void Equals_WithNull_ReturnsFalse()
93+
{
94+
SetupWithBlocks();
95+
var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
96+
97+
var entry = _blockProcessor!.GetBlockTreeEntry(tipHash);
98+
99+
Assert.False(entry!.Equals(null));
100+
}
101+
102+
[Fact]
103+
public void GetHashCode_EqualEntries_ReturnsSameHashCode()
104+
{
105+
SetupWithBlocks();
106+
var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
107+
108+
var entry1 = _blockProcessor!.GetBlockTreeEntry(tipHash);
109+
var entry2 = _blockProcessor.GetBlockTreeEntry(tipHash);
110+
111+
Assert.Equal(entry1!.GetHashCode(), entry2!.GetHashCode());
112+
}
113+
114+
[Fact]
115+
public void OperatorEquals_SameBlock_ReturnsTrue()
116+
{
117+
SetupWithBlocks();
118+
var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
119+
120+
var entry1 = _blockProcessor!.GetBlockTreeEntry(tipHash);
121+
var entry2 = _blockProcessor.GetBlockTreeEntry(tipHash);
122+
123+
Assert.True(entry1 == entry2);
124+
}
125+
126+
[Fact]
127+
public void OperatorNotEquals_DifferentBlocks_ReturnsTrue()
128+
{
129+
SetupWithBlocks();
130+
var chain = _chainstateManager!.GetActiveChain();
131+
132+
var tipEntry = _blockProcessor!.GetBlockTreeEntry(chain.GetTip().GetBlockHash());
133+
var genesisEntry = _blockProcessor.GetBlockTreeEntry(chain.GetBlockByHeight(0)!.GetBlockHash());
134+
135+
Assert.True(tipEntry != genesisEntry);
136+
}
137+
138+
[Fact]
139+
public void GetPrevious_EqualEntries_ReturnEqualPrevious()
140+
{
141+
SetupWithBlocks();
142+
var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
143+
144+
var entry1 = _blockProcessor!.GetBlockTreeEntry(tipHash);
145+
var entry2 = _blockProcessor.GetBlockTreeEntry(tipHash);
146+
147+
var prev1 = entry1!.GetPrevious();
148+
var prev2 = entry2!.GetPrevious();
149+
150+
Assert.True(prev1!.Equals(prev2));
151+
}
152+
}

0 commit comments

Comments
 (0)