Skip to content
Open
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
Binary file modified native/osx-x64/libbitcoinkernel.dylib
Binary file not shown.
15 changes: 15 additions & 0 deletions src/BitcoinKernel.Core/Abstractions/Block.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ public byte[] GetHash()
return blockHash.ToBytes();
}

/// <summary>
/// Gets the block header from this block.
/// </summary>
public BlockHeader GetHeader()
{
ThrowIfDisposed();
var headerPtr = NativeMethods.BlockGetHeader(_handle);
if (headerPtr == IntPtr.Zero)
{
throw new BlockException("Failed to get block header");
}

return new BlockHeader(headerPtr);
}

/// <summary>
/// Serializes the block to bytes.
/// </summary>
Expand Down
157 changes: 157 additions & 0 deletions src/BitcoinKernel.Core/Abstractions/BlockHeader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using BitcoinKernel.Core.Exceptions;
using BitcoinKernel.Interop;

namespace BitcoinKernel.Core.Abstractions;

/// <summary>
/// Represents a block header containing metadata about a block.
/// </summary>
public sealed class BlockHeader : IDisposable
{
private IntPtr _handle;
private bool _disposed;
private readonly bool _ownsHandle;

internal BlockHeader(IntPtr handle, bool ownsHandle = true)
{
_handle = handle != IntPtr.Zero
? handle
: throw new ArgumentException("Invalid block header handle", nameof(handle));
_ownsHandle = ownsHandle;
}

/// <summary>
/// Creates a block header from raw serialized data (80 bytes).
/// </summary>
public static BlockHeader FromBytes(byte[] rawHeaderData)
{
ArgumentNullException.ThrowIfNull(rawHeaderData, nameof(rawHeaderData));
if (rawHeaderData.Length != 80)
throw new ArgumentException("Block header must be exactly 80 bytes", nameof(rawHeaderData));

IntPtr headerPtr = NativeMethods.BlockHeaderCreate(rawHeaderData, (UIntPtr)rawHeaderData.Length);

if (headerPtr == IntPtr.Zero)
{
throw new BlockException("Failed to create block header from raw data");
}

return new BlockHeader(headerPtr);
}

internal IntPtr Handle
{
get
{
ThrowIfDisposed();
return _handle;
}
}

/// <summary>
/// Gets the block hash of this header.
/// </summary>
public byte[] GetHash()
{
ThrowIfDisposed();
var hashPtr = NativeMethods.BlockHeaderGetHash(_handle);
if (hashPtr == IntPtr.Zero)
{
throw new BlockException("Failed to get block hash from header");
}

using var blockHash = new BlockHash(hashPtr);
return blockHash.ToBytes();
}

/// <summary>
/// Gets the previous block hash from this header.
/// </summary>
public byte[] GetPrevHash()
{
ThrowIfDisposed();
var hashPtr = NativeMethods.BlockHeaderGetPrevHash(_handle);
if (hashPtr == IntPtr.Zero)
{
throw new BlockException("Failed to get previous block hash from header");
}

// The hash pointer is unowned and only valid for the lifetime of the header
var bytes = new byte[32];
NativeMethods.BlockHashToBytes(hashPtr, bytes);
return bytes;
}

/// <summary>
/// Gets the timestamp from this header (Unix epoch seconds).
/// </summary>
public uint Timestamp
{
get
{
ThrowIfDisposed();
return NativeMethods.BlockHeaderGetTimestamp(_handle);
}
}

/// <summary>
/// Gets the nBits difficulty target from this header (compact format).
/// </summary>
public uint Bits
{
get
{
ThrowIfDisposed();
return NativeMethods.BlockHeaderGetBits(_handle);
}
}

/// <summary>
/// Gets the version from this header.
/// </summary>
public int Version
{
get
{
ThrowIfDisposed();
return NativeMethods.BlockHeaderGetVersion(_handle);
}
}

/// <summary>
/// Gets the nonce from this header.
/// </summary>
public uint Nonce
{
get
{
ThrowIfDisposed();
return NativeMethods.BlockHeaderGetNonce(_handle);
}
}

private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(nameof(BlockHeader));
}

public void Dispose()
{
if (!_disposed)
{
if (_handle != IntPtr.Zero && _ownsHandle)
{
NativeMethods.BlockHeaderDestroy(_handle);
_handle = IntPtr.Zero;
}
_disposed = true;
}
}

~BlockHeader()
{
if (_ownsHandle)
Dispose();
}
}
14 changes: 13 additions & 1 deletion src/BitcoinKernel.Core/Abstractions/BlockIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,16 @@ public byte[] GetBlockHash()
? new BlockIndex(prevPtr, ownsHandle: false)
: null;
}
}

/// <summary>
/// Gets the block header associated with this block index.
/// </summary>
public BlockHeader GetBlockHeader()
{
var headerPtr = NativeMethods.BlockTreeEntryGetBlockHeader(_handle);
if (headerPtr == IntPtr.Zero)
throw new InvalidOperationException("Failed to get block header from block index");

return new BlockHeader(headerPtr);
}
}
105 changes: 105 additions & 0 deletions src/BitcoinKernel.Core/Abstractions/BlockValidationState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using BitcoinKernel.Core.Exceptions;
using BitcoinKernel.Interop;
using BitcoinKernel.Interop.Enums;

namespace BitcoinKernel.Core.Abstractions;

/// <summary>
/// Represents the validation state of a block.
/// </summary>
public sealed class BlockValidationState : IDisposable
{
private IntPtr _handle;
private bool _disposed;

/// <summary>
/// Creates a new block validation state.
/// </summary>
public BlockValidationState()
{
_handle = NativeMethods.BlockValidationStateCreate();
if (_handle == IntPtr.Zero)
{
throw new BlockException("Failed to create block validation state");
}
}

internal BlockValidationState(IntPtr handle)
{
if (handle == IntPtr.Zero)
{
throw new ArgumentException("Invalid block validation state handle", nameof(handle));
}

_handle = handle;
}

internal IntPtr Handle
{
get
{
ThrowIfDisposed();
return _handle;
}
}

/// <summary>
/// Gets the validation mode (valid, invalid, or internal error).
/// </summary>
public ValidationMode ValidationMode
{
get
{
ThrowIfDisposed();
return NativeMethods.BlockValidationStateGetValidationMode(_handle);
}
}

/// <summary>
/// Gets the block validation result (detailed error reason if invalid).
/// </summary>
public Interop.Enums.BlockValidationResult ValidationResult
{
get
{
ThrowIfDisposed();
return NativeMethods.BlockValidationStateGetBlockValidationResult(_handle);
}
}

/// <summary>
/// Creates a copy of this block validation state.
/// </summary>
public BlockValidationState Copy()
{
ThrowIfDisposed();
var copyPtr = NativeMethods.BlockValidationStateCopy(_handle);
if (copyPtr == IntPtr.Zero)
{
throw new BlockException("Failed to copy block validation state");
}
return new BlockValidationState(copyPtr);
}

private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(nameof(BlockValidationState));
}


public void Dispose()
{
if (!_disposed)
{
if (_handle != IntPtr.Zero)
{
NativeMethods.BlockValidationStateDestroy(_handle);
_handle = IntPtr.Zero;
}
_disposed = true;
}
}

~BlockValidationState() => Dispose();
}
34 changes: 34 additions & 0 deletions src/BitcoinKernel.Core/Chain/ChainStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,40 @@ public Abstractions.Chain GetActiveChain()
return new Abstractions.Chain(chainPtr);
}

/// <summary>
/// Gets the block index with the most cumulative proof of work.
/// </summary>
public BlockIndex GetBestBlockIndex()
{
ThrowIfDisposed();
IntPtr indexPtr = NativeMethods.ChainstateManagerGetBestEntry(_handle);
if (indexPtr == IntPtr.Zero)
throw new KernelException("Failed to get best block index");

return new BlockIndex(indexPtr, ownsHandle: false);
}

/// <summary>
/// Processes and validates a block header.
/// </summary>
/// <param name="header">The block header to process.</param>
/// <param name="validationState">The validation state result.</param>
/// <returns>True if processing was successful, false otherwise.</returns>
public bool ProcessBlockHeader(BlockHeader header, out BlockValidationState validationState)
{
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(header);

using var state = new BlockValidationState();
int result = NativeMethods.ChainstateManagerProcessBlockHeader(
_handle,
header.Handle,
state.Handle);

validationState = state.Copy();
return result == 0;
}

/// <summary>
/// Processes a block through validation.
/// </summary>
Expand Down
21 changes: 14 additions & 7 deletions src/BitcoinKernel.Core/ScriptVerification/ScriptVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ public static bool VerifyScript(
}

// Create spent outputs
var kernelSpentOutputs = spentOutputs.Any()
? spentOutputs.Select(utxo => utxo.Handle).ToArray()
: null;


IntPtr precomputedDataPtr = IntPtr.Zero;
if (spentOutputs.Any())
{
var kernelSpentOutputs = spentOutputs.Select(utxo => utxo.Handle).ToArray();
precomputedDataPtr = NativeMethods.PrecomputedTransactionDataCreate(
transaction.Handle,
kernelSpentOutputs,
(nuint)spentOutputs.Count);
}

// Verify script
// Allocate memory for status byte
Expand All @@ -71,8 +75,7 @@ public static bool VerifyScript(
scriptPubkey.Handle,
amount,
transaction.Handle,
kernelSpentOutputs,
(nuint)spentOutputs.Count,
precomputedDataPtr,
inputIndex,
(uint)flags,
statusPtr);
Expand All @@ -95,6 +98,10 @@ public static bool VerifyScript(
finally
{
Marshal.FreeHGlobal(statusPtr);
if (precomputedDataPtr != IntPtr.Zero)
{
NativeMethods.PrecomputedTransactionDataDestroy(precomputedDataPtr);
}
}
return true;
}
Expand Down
Loading
Loading