diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 109b55a..a3cb1af 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -96,7 +96,7 @@ jobs:
platform: linux_amd64
env:
- TEST_VERSION: '0.0.3-alpha.4'
+ TEST_VERSION: '0.0.4-alpha.pr14.5'
TEST_REPO: 'stringintech/kernel-bindings-tests'
TEST_DIR: '.conformance-tests'
diff --git a/examples/BasicUsage/Program.cs b/examples/BasicUsage/Program.cs
index d7c21db..9a0f422 100644
--- a/examples/BasicUsage/Program.cs
+++ b/examples/BasicUsage/Program.cs
@@ -38,16 +38,16 @@ static void FullChainstateExample()
{
var chain = chainstate.GetActiveChain();
Console.WriteLine($" Chain height: {chain.Height}");
- Console.WriteLine($" Genesis hash: {Convert.ToHexString(chain.GetGenesis().GetBlockHash())}");
+ Console.WriteLine($" Genesis hash: {Convert.ToHexString(chain.GetGenesis().GetHash())}");
if (chain.Height > 0)
{
var tip = chain.GetTip();
- Console.WriteLine($" Tip hash: {Convert.ToHexString(tip.GetBlockHash())}");
+ Console.WriteLine($" Tip hash: {Convert.ToHexString(tip.GetHash())}");
var genesis = chain.GetBlockByHeight(0);
if (genesis != null)
- Console.WriteLine($" Block 0 hash: {Convert.ToHexString(genesis.GetBlockHash())}");
+ Console.WriteLine($" Block 0 hash: {Convert.ToHexString(genesis.GetHash())}");
}
Console.WriteLine(" Chain queries working");
diff --git a/examples/BlockProcessing/Program.cs b/examples/BlockProcessing/Program.cs
index 1962399..40683c5 100644
--- a/examples/BlockProcessing/Program.cs
+++ b/examples/BlockProcessing/Program.cs
@@ -59,7 +59,7 @@ static void Main(string[] args)
var activeChain = chainstate.GetActiveChain();
Console.WriteLine($"Block processed! Chain height: {activeChain.Height}");
var tip = activeChain.GetTip();
- Console.WriteLine($" - Tip: {BitConverter.ToString(tip.GetBlockHash()).Replace("-", "")}");
+ Console.WriteLine($" - Tip: {BitConverter.ToString(tip.GetHash()).Replace("-", "")}");
}
else
{
diff --git a/native/linux-x64/libbitcoinkernel.so b/native/linux-x64/libbitcoinkernel.so
index 3b6cb11..2527e43 100755
Binary files a/native/linux-x64/libbitcoinkernel.so and b/native/linux-x64/libbitcoinkernel.so differ
diff --git a/native/osx-x64/libbitcoinkernel.dylib b/native/osx-x64/libbitcoinkernel.dylib
index 8e7369f..6071804 100755
Binary files a/native/osx-x64/libbitcoinkernel.dylib and b/native/osx-x64/libbitcoinkernel.dylib differ
diff --git a/src/BitcoinKernel.Interop/Enums/BlockCheckFlags.cs b/src/BitcoinKernel.Interop/Enums/BlockCheckFlags.cs
new file mode 100644
index 0000000..c501ceb
--- /dev/null
+++ b/src/BitcoinKernel.Interop/Enums/BlockCheckFlags.cs
@@ -0,0 +1,31 @@
+namespace BitcoinKernel.Interop.Enums;
+
+///
+/// Flags controlling optional context-free block checks performed by
+/// btck_block_check. The base checks (size limits, coinbase structure,
+/// transaction checks, sigop limits) always run; these flags toggle the
+/// optional proof-of-work and merkle-root checks.
+///
+[Flags]
+public enum BlockCheckFlags : uint
+{
+ ///
+ /// Run the base context-free block checks only.
+ ///
+ Base = 0,
+
+ ///
+ /// Run CheckProofOfWork via CheckBlockHeader.
+ ///
+ Pow = 1U << 0,
+
+ ///
+ /// Verify merkle root (and mutation detection).
+ ///
+ Merkle = 1U << 1,
+
+ ///
+ /// Enable all optional context-free block checks.
+ ///
+ All = Pow | Merkle
+}
diff --git a/src/BitcoinKernel.Interop/Enums/ChainType.cs b/src/BitcoinKernel.Interop/Enums/ChainType.cs
index f7e9ea1..95fe543 100644
--- a/src/BitcoinKernel.Interop/Enums/ChainType.cs
+++ b/src/BitcoinKernel.Interop/Enums/ChainType.cs
@@ -1,6 +1,7 @@
namespace BitcoinKernel.Interop.Enums;
-public enum ChainType : uint
+// btck_ChainType is a uint8_t in bitcoinkernel.h.
+public enum ChainType : byte
{
MAINNET = 0,
TESTNET = 1,
diff --git a/src/BitcoinKernel.Interop/Enums/LogLevel.cs b/src/BitcoinKernel.Interop/Enums/LogLevel.cs
index c5b3c5d..9ae128c 100644
--- a/src/BitcoinKernel.Interop/Enums/LogLevel.cs
+++ b/src/BitcoinKernel.Interop/Enums/LogLevel.cs
@@ -1,6 +1,7 @@
namespace BitcoinKernel.Interop.Enums;
-public enum LogLevel : uint
+// btck_LogLevel is a uint8_t in bitcoinkernel.h.
+public enum LogLevel : byte
{
TRACE = 0,
DEBUG = 1,
diff --git a/src/BitcoinKernel.Interop/Enums/TxValidationResult.cs b/src/BitcoinKernel.Interop/Enums/TxValidationResult.cs
new file mode 100644
index 0000000..88122cd
--- /dev/null
+++ b/src/BitcoinKernel.Interop/Enums/TxValidationResult.cs
@@ -0,0 +1,72 @@
+namespace BitcoinKernel.Interop.Enums;
+
+///
+/// A granular "reason" why a transaction was invalid.
+///
+public enum TxValidationResult : uint
+{
+ ///
+ /// Initial value. Tx has not yet been rejected.
+ ///
+ UNSET = 0,
+
+ ///
+ /// Invalid by consensus rules.
+ ///
+ CONSENSUS = 1,
+
+ ///
+ /// Inputs (covered by txid) failed policy rules.
+ ///
+ INPUTS_NOT_STANDARD = 2,
+
+ ///
+ /// Otherwise didn't meet local policy rules.
+ ///
+ NOT_STANDARD = 3,
+
+ ///
+ /// Transaction was missing some of its inputs.
+ ///
+ MISSING_INPUTS = 4,
+
+ ///
+ /// Transaction spends a coinbase too early, or violates locktime/sequence locks.
+ ///
+ PREMATURE_SPEND = 5,
+
+ ///
+ /// Witness may have been malleated or is prior to SegWit activation.
+ ///
+ WITNESS_MUTATED = 6,
+
+ ///
+ /// Transaction is missing a witness.
+ ///
+ WITNESS_STRIPPED = 7,
+
+ ///
+ /// Tx already in mempool or conflicts with a tx in the chain.
+ ///
+ CONFLICT = 8,
+
+ ///
+ /// Violated mempool's fee/size/descendant/RBF/etc limits.
+ ///
+ MEMPOOL_POLICY = 9,
+
+ ///
+ /// This node does not have a mempool so can't validate the transaction.
+ ///
+ NO_MEMPOOL = 10,
+
+ ///
+ /// Fails some policy, but might be acceptable if submitted in a (different) package.
+ ///
+ RECONSIDERABLE = 11,
+
+ ///
+ /// Transaction was not validated because package failed.
+ ///
+ UNKNOWN = 12
+}
diff --git a/src/BitcoinKernel.Interop/NativeMethods.cs b/src/BitcoinKernel.Interop/NativeMethods.cs
index 1b5c770..d897c0c 100644
--- a/src/BitcoinKernel.Interop/NativeMethods.cs
+++ b/src/BitcoinKernel.Interop/NativeMethods.cs
@@ -96,6 +96,13 @@ static NativeMethods()
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_chain_parameters_destroy")]
public static extern void ChainParametersDestroy(IntPtr chain_params);
+ ///
+ /// Gets the consensus parameters from chain parameters. The returned pointer
+ /// is unowned and only valid for the lifetime of the chain parameters.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_chain_parameters_get_consensus_params")]
+ public static extern IntPtr ChainParametersGetConsensusParams(IntPtr chain_parameters);
+
#endregion
#region Chainstate Manager
@@ -143,14 +150,14 @@ public static extern IntPtr ChainstateManagerGetBlockTreeEntryByHash(
public static extern IntPtr ChainstateManagerGetBestEntry(IntPtr manager);
///
- /// Processes and validates a block header.
- /// Returns 0 on success.
+ /// Processes and validates a block header. Returns a newly-allocated
+ /// btck_BlockValidationState (owned by the caller) describing the outcome,
+ /// or IntPtr.Zero on failure.
///
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_chainstate_manager_process_block_header")]
- public static extern int ChainstateManagerProcessBlockHeader(
+ public static extern IntPtr ChainstateManagerProcessBlockHeader(
IntPtr manager,
- IntPtr header,
- IntPtr block_validation_state);
+ IntPtr header);
///
/// Imports blocks from an array of file paths.
@@ -320,6 +327,26 @@ public static extern int BlockToBytes(
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_block_get_transaction_at")]
public static extern IntPtr BlockGetTransactionAt(IntPtr block, nuint index);
+ ///
+ /// Returns the ancestor of a block tree entry at the given height.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_block_tree_entry_get_ancestor")]
+ public static extern IntPtr BlockTreeEntryGetAncestor(IntPtr block_tree_entry, int height);
+
+ ///
+ /// Performs context-free validation checks on a block.
+ /// Runs base checks (size, coinbase, tx, sigops) plus optional POW and
+ /// merkle-root checks controlled by . The
+ /// validation_state is updated in-place.
+ /// Returns 1 if the block passed the checks, 0 otherwise.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_block_check")]
+ public static extern int BlockCheck(
+ IntPtr block,
+ IntPtr consensus_params,
+ BlockCheckFlags flags,
+ IntPtr validation_state);
+
#endregion
#region BlockHash Operations
@@ -409,6 +436,15 @@ public static extern IntPtr BlockHeaderCreate(
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_block_header_destroy")]
public static extern void BlockHeaderDestroy(IntPtr header);
+ ///
+ /// Serializes a block header to 80 bytes.
+ /// Returns 0 on success.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_block_header_to_bytes")]
+ public static extern int BlockHeaderToBytes(
+ IntPtr header,
+ [MarshalAs(UnmanagedType.LPArray, SizeConst = 80)] byte[] output);
+
#endregion
#region Chain Operations
@@ -519,6 +555,20 @@ public static extern int TransactionToBytes(
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_transaction_output_destroy")]
public static extern void TransactionOutputDestroy(IntPtr output);
+ ///
+ /// Gets the nLockTime value of a transaction.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_transaction_get_locktime")]
+ public static extern uint TransactionGetLocktime(IntPtr transaction);
+
+ ///
+ /// Runs context-free consensus validation on a transaction.
+ /// The validation_state is reset on entry and updated in-place.
+ /// Returns 1 if valid, 0 if invalid.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_transaction_check")]
+ public static extern int TransactionCheck(IntPtr tx, IntPtr validation_state);
+
#endregion
#region PrecomputedTransactionData Operations
@@ -867,6 +917,12 @@ public static extern IntPtr TransactionSpentOutputsGetCoinAt(
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_transaction_input_destroy")]
public static extern void TransactionInputDestroy(IntPtr transaction_input);
+ ///
+ /// Gets the nSequence value of a transaction input.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_transaction_input_get_sequence")]
+ public static extern uint TransactionInputGetSequence(IntPtr transaction_input);
+
#endregion
#region TransactionOutPoint Operations
@@ -897,4 +953,33 @@ public static extern IntPtr TransactionSpentOutputsGetCoinAt(
#endregion
+ #region Tx Validation State
+
+ ///
+ /// Creates a new transaction validation state.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_tx_validation_state_create")]
+ public static extern IntPtr TxValidationStateCreate();
+
+ ///
+ /// Gets the validation mode from a transaction validation state.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_tx_validation_state_get_validation_mode")]
+ public static extern ValidationMode TxValidationStateGetValidationMode(IntPtr validation_state);
+
+ ///
+ /// Gets the transaction validation result from a transaction validation state.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_tx_validation_state_get_tx_validation_result")]
+ public static extern TxValidationResult TxValidationStateGetTxValidationResult(IntPtr validation_state);
+
+ ///
+ /// Destroys a transaction validation state.
+ ///
+ [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "btck_tx_validation_state_destroy")]
+ public static extern void TxValidationStateDestroy(IntPtr validation_state);
+
+ #endregion
+
+
}
diff --git a/src/BitcoinKernel/Chain/ChainStateManager.cs b/src/BitcoinKernel/Chain/ChainStateManager.cs
index cd2fb1c..2604c1a 100644
--- a/src/BitcoinKernel/Chain/ChainStateManager.cs
+++ b/src/BitcoinKernel/Chain/ChainStateManager.cs
@@ -88,14 +88,15 @@ public bool ProcessBlockHeader(BlockHeader header, out BlockValidationState vali
ThrowIfDisposed();
ArgumentNullException.ThrowIfNull(header);
- using var state = new BlockValidationState();
- int result = NativeMethods.ChainstateManagerProcessBlockHeader(
- _handle,
- header.Handle,
- state.Handle);
+ // The native call returns a newly-allocated validation state (owned here).
+ var statePtr = NativeMethods.ChainstateManagerProcessBlockHeader(_handle, header.Handle);
+ if (statePtr == IntPtr.Zero)
+ {
+ throw new ChainstateManagerException("Failed to process block header");
+ }
- validationState = state.Copy();
- return result == 0;
+ validationState = new BlockValidationState(statePtr);
+ return validationState.ValidationMode == Interop.Enums.ValidationMode.VALID;
}
///
diff --git a/src/BitcoinKernel/Primitives/Block.cs b/src/BitcoinKernel/Primitives/Block.cs
index b856e23..c572907 100644
--- a/src/BitcoinKernel/Primitives/Block.cs
+++ b/src/BitcoinKernel/Primitives/Block.cs
@@ -57,9 +57,18 @@ public int TransactionCount
}
///
- /// Gets the block hash.
+ /// Gets the block hash as a 32-byte array.
///
public byte[] GetHash()
+ {
+ using var blockHash = GetBlockHash();
+ return blockHash.ToBytes();
+ }
+
+ ///
+ /// Gets the block hash as an owned object.
+ ///
+ public BlockHash GetBlockHash()
{
ThrowIfDisposed();
var hashPtr = NativeMethods.BlockGetHash(_handle);
@@ -68,8 +77,22 @@ public byte[] GetHash()
throw new BlockException("Failed to get block hash");
}
- using var blockHash = new BlockHash(hashPtr);
- return blockHash.ToBytes();
+ return new BlockHash(hashPtr);
+ }
+
+ ///
+ /// Creates an owned copy of this block.
+ ///
+ public Block Copy()
+ {
+ ThrowIfDisposed();
+ var copy = NativeMethods.BlockCopy(_handle);
+ if (copy == IntPtr.Zero)
+ {
+ throw new BlockException("Failed to copy block");
+ }
+
+ return new Block(copy);
}
///
@@ -93,19 +116,19 @@ public BlockHeader GetHeader()
public byte[] ToBytes()
{
ThrowIfDisposed();
- byte[]? result = null;
+ var result = new List();
NativeMethods.BlockToBytes(_handle, (data, size, userData) =>
{
unsafe
{
var span = new ReadOnlySpan((byte*)data, (int)size);
- result = span.ToArray();
+ result.AddRange(span);
}
return 0;
}, IntPtr.Zero);
- return result ?? Array.Empty();
+ return result.ToArray();
}
///
diff --git a/src/BitcoinKernel/Primitives/BlockHash.cs b/src/BitcoinKernel/Primitives/BlockHash.cs
index 6efa7b3..f7dab4f 100644
--- a/src/BitcoinKernel/Primitives/BlockHash.cs
+++ b/src/BitcoinKernel/Primitives/BlockHash.cs
@@ -5,7 +5,7 @@ namespace BitcoinKernel.Primitives;
///
/// Represents a block hash.
///
-public sealed class BlockHash : IDisposable
+public sealed class BlockHash : IDisposable, IEquatable
{
private IntPtr _handle;
private bool _disposed;
@@ -60,6 +60,33 @@ public byte[] ToBytes()
return bytes;
}
+ ///
+ /// Creates an owned copy of this block hash.
+ ///
+ public BlockHash Copy()
+ {
+ ThrowIfDisposed();
+ var copy = NativeMethods.BlockHashCopy(_handle);
+ if (copy == IntPtr.Zero)
+ throw new BlockException("Failed to copy block hash");
+
+ return new BlockHash(copy);
+ }
+
+ ///
+ /// Determines whether this block hash equals another.
+ ///
+ public bool Equals(BlockHash? other)
+ {
+ if (other is null) return false;
+ ThrowIfDisposed();
+ return NativeMethods.BlockHashEquals(_handle, other.Handle) != 0;
+ }
+
+ public override bool Equals(object? obj) => obj is BlockHash other && Equals(other);
+
+ public override int GetHashCode() => Convert.ToHexString(ToBytes()).GetHashCode();
+
private void ThrowIfDisposed()
{
if (_disposed)
diff --git a/src/BitcoinKernel/Primitives/BlockHeader.cs b/src/BitcoinKernel/Primitives/BlockHeader.cs
index 1568deb..c179a5a 100644
--- a/src/BitcoinKernel/Primitives/BlockHeader.cs
+++ b/src/BitcoinKernel/Primitives/BlockHeader.cs
@@ -49,9 +49,27 @@ internal IntPtr Handle
}
///
- /// Gets the block hash of this header.
+ /// Gets the block hash of this header as a 32-byte array.
///
public byte[] GetHash()
+ {
+ using var blockHash = GetBlockHash();
+ return blockHash.ToBytes();
+ }
+
+ ///
+ /// Gets the previous block hash from this header as a 32-byte array.
+ ///
+ public byte[] GetPrevHash()
+ {
+ using var prevHash = GetPrevBlockHash();
+ return prevHash.ToBytes();
+ }
+
+ ///
+ /// Gets the block hash of this header as an owned object.
+ ///
+ public BlockHash GetBlockHash()
{
ThrowIfDisposed();
var hashPtr = NativeMethods.BlockHeaderGetHash(_handle);
@@ -60,14 +78,13 @@ public byte[] GetHash()
throw new BlockException("Failed to get block hash from header");
}
- using var blockHash = new BlockHash(hashPtr);
- return blockHash.ToBytes();
+ return new BlockHash(hashPtr);
}
///
- /// Gets the previous block hash from this header.
+ /// Gets the previous block hash of this header as an owned object.
///
- public byte[] GetPrevHash()
+ public BlockHash GetPrevBlockHash()
{
ThrowIfDisposed();
var hashPtr = NativeMethods.BlockHeaderGetPrevHash(_handle);
@@ -76,12 +93,48 @@ public byte[] GetPrevHash()
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);
+ // The returned pointer is unowned (tied to the header lifetime), so copy it
+ // into an independently owned block hash.
+ var copy = NativeMethods.BlockHashCopy(hashPtr);
+ if (copy == IntPtr.Zero)
+ {
+ throw new BlockException("Failed to copy previous block hash");
+ }
+
+ return new BlockHash(copy);
+ }
+
+ ///
+ /// Serializes this header to its 80-byte representation.
+ ///
+ public byte[] ToBytes()
+ {
+ ThrowIfDisposed();
+ var bytes = new byte[80];
+ int result = NativeMethods.BlockHeaderToBytes(_handle, bytes);
+ if (result != 0)
+ {
+ throw new BlockException("Failed to serialize block header");
+ }
+
return bytes;
}
+ ///
+ /// Creates an owned copy of this block header.
+ ///
+ public BlockHeader Copy()
+ {
+ ThrowIfDisposed();
+ var copy = NativeMethods.BlockHeaderCopy(_handle);
+ if (copy == IntPtr.Zero)
+ {
+ throw new BlockException("Failed to copy block header");
+ }
+
+ return new BlockHeader(copy);
+ }
+
///
/// Gets the timestamp from this header (Unix epoch seconds).
///
diff --git a/src/BitcoinKernel/Primitives/BlockIndex.cs b/src/BitcoinKernel/Primitives/BlockIndex.cs
index b39905c..47b3057 100644
--- a/src/BitcoinKernel/Primitives/BlockIndex.cs
+++ b/src/BitcoinKernel/Primitives/BlockIndex.cs
@@ -26,19 +26,30 @@ internal BlockIndex(IntPtr handle, bool ownsHandle)
public int Height => NativeMethods.BlockTreeEntryGetHeight(_handle);
///
- /// Gets the block hash.
+ /// Gets the block hash of this entry as a 32-byte array.
///
- public byte[] GetBlockHash()
+ public byte[] GetHash()
+ {
+ using var hash = GetBlockHash();
+ return hash.ToBytes();
+ }
+
+ ///
+ /// Gets the block hash of this entry as an owned object.
+ ///
+ public BlockHash GetBlockHash()
{
var hashPtr = NativeMethods.BlockTreeEntryGetBlockHash(_handle);
if (hashPtr == IntPtr.Zero)
throw new InvalidOperationException("Failed to get block hash");
- // The hash pointer is owned by the block tree entry, so we just read the bytes
- // without wrapping it in a BlockHash that would try to destroy it
- var bytes = new byte[32];
- NativeMethods.BlockHashToBytes(hashPtr, bytes);
- return bytes;
+ // The returned pointer is owned by the block tree entry, so copy it into an
+ // independently owned block hash.
+ var copy = NativeMethods.BlockHashCopy(hashPtr);
+ if (copy == IntPtr.Zero)
+ throw new InvalidOperationException("Failed to copy block hash");
+
+ return new BlockHash(copy);
}
///
diff --git a/src/BitcoinKernel/Primitives/OutPoint.cs b/src/BitcoinKernel/Primitives/OutPoint.cs
new file mode 100644
index 0000000..40df9b1
--- /dev/null
+++ b/src/BitcoinKernel/Primitives/OutPoint.cs
@@ -0,0 +1,96 @@
+using BitcoinKernel.Exceptions;
+using BitcoinKernel.Interop;
+
+namespace BitcoinKernel.Primitives;
+
+///
+/// Represents a transaction out point (the txid and output index a transaction input spends).
+///
+public sealed class OutPoint : IDisposable
+{
+ private IntPtr _handle;
+ private bool _disposed;
+ private readonly bool _ownsHandle;
+
+ internal OutPoint(IntPtr handle, bool ownsHandle = true)
+ {
+ _handle = handle != IntPtr.Zero
+ ? handle
+ : throw new ArgumentException("Invalid out point handle", nameof(handle));
+ _ownsHandle = ownsHandle;
+ }
+
+ internal IntPtr Handle
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return _handle;
+ }
+ }
+
+ ///
+ /// Gets the index of the referenced output.
+ ///
+ public uint Index
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return NativeMethods.TransactionOutPointGetIndex(_handle);
+ }
+ }
+
+ ///
+ /// Gets the txid of the referenced transaction. The returned txid is a
+ /// non-owning view whose lifetime is tied to this out point.
+ ///
+ public Txid GetTxid()
+ {
+ ThrowIfDisposed();
+ var txidPtr = NativeMethods.TransactionOutPointGetTxid(_handle);
+ if (txidPtr == IntPtr.Zero)
+ throw new TransactionException("Failed to get txid from out point");
+
+ return new Txid(txidPtr, ownsHandle: false);
+ }
+
+ ///
+ /// Creates an owned copy of this out point.
+ ///
+ public OutPoint Copy()
+ {
+ ThrowIfDisposed();
+ var copy = NativeMethods.TransactionOutPointCopy(_handle);
+ if (copy == IntPtr.Zero)
+ throw new TransactionException("Failed to copy out point");
+
+ return new OutPoint(copy);
+ }
+
+ private void ThrowIfDisposed()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException(nameof(OutPoint));
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ if (_handle != IntPtr.Zero && _ownsHandle)
+ {
+ NativeMethods.TransactionOutPointDestroy(_handle);
+ _handle = IntPtr.Zero;
+ }
+ _disposed = true;
+ }
+ GC.SuppressFinalize(this);
+ }
+
+ ~OutPoint()
+ {
+ if (_ownsHandle)
+ Dispose();
+ }
+}
diff --git a/src/BitcoinKernel/Primitives/ScriptPubKey.cs b/src/BitcoinKernel/Primitives/ScriptPubKey.cs
index f54ca3e..7cdc8bc 100644
--- a/src/BitcoinKernel/Primitives/ScriptPubKey.cs
+++ b/src/BitcoinKernel/Primitives/ScriptPubKey.cs
@@ -56,6 +56,39 @@ public static ScriptPubKey FromHex(string hexString)
internal IntPtr Handle => _handle;
+ ///
+ /// Serializes this script pubkey to bytes.
+ ///
+ public byte[] ToBytes()
+ {
+ var bytes = new List();
+ NativeMethods.WriteBytes writer = (data, len, _) =>
+ {
+ var buffer = new byte[len];
+ System.Runtime.InteropServices.Marshal.Copy(data, buffer, 0, (int)len);
+ bytes.AddRange(buffer);
+ return 0;
+ };
+
+ int result = NativeMethods.ScriptPubkeyToBytes(_handle, writer, IntPtr.Zero);
+ if (result != 0)
+ throw new TransactionException("Failed to serialize script pubkey");
+
+ return bytes.ToArray();
+ }
+
+ ///
+ /// Creates an owned copy of this script pubkey.
+ ///
+ public ScriptPubKey Copy()
+ {
+ var copy = NativeMethods.ScriptPubkeyCopy(_handle);
+ if (copy == IntPtr.Zero)
+ throw new TransactionException("Failed to copy script pubkey");
+
+ return new ScriptPubKey(copy, ownsHandle: true);
+ }
+
public void Dispose()
{
if (!_disposed && _handle != IntPtr.Zero)
diff --git a/src/BitcoinKernel/Primitives/Transaction.cs b/src/BitcoinKernel/Primitives/Transaction.cs
index d560a41..dc557a0 100644
--- a/src/BitcoinKernel/Primitives/Transaction.cs
+++ b/src/BitcoinKernel/Primitives/Transaction.cs
@@ -90,19 +90,26 @@ internal Transaction(IntPtr handle, bool ownsHandle = true)
public int OutputCount => (int)NativeMethods.TransactionCountOutputs(_handle);
///
- /// Gets the transaction ID (txid) as bytes.
+ /// Gets the transaction ID (txid) as a non-owning object whose
+ /// lifetime is tied to this transaction.
///
- /// The transaction ID as a byte array.
- public byte[] GetTxid()
+ public Txid GetTxid()
{
IntPtr txidPtr = NativeMethods.TransactionGetTxid(_handle);
if (txidPtr == IntPtr.Zero)
throw new TransactionException("Failed to get transaction ID");
- const int TxidSize = 32;
- byte[] txid = new byte[TxidSize];
- Marshal.Copy(txidPtr, txid, 0, TxidSize);
- return txid;
+ return new Txid(txidPtr, ownsHandle: false);
+ }
+
+ ///
+ /// Gets the transaction ID (txid) as a 32-byte array.
+ ///
+ /// The transaction ID as a byte array.
+ public byte[] GetTxidBytes()
+ {
+ using var txid = GetTxid();
+ return txid.ToBytes();
}
///
@@ -111,14 +118,37 @@ public byte[] GetTxid()
/// The transaction ID as a hex string.
public string GetTxidHex()
{
- byte[] txid = GetTxid();
- return Convert.ToHexString(txid).ToLowerInvariant();
+ return Convert.ToHexString(GetTxidBytes()).ToLowerInvariant();
}
+ ///
+ /// Serializes this transaction to bytes.
+ ///
+ public byte[] ToBytes()
+ {
+ var bytes = new List();
+ NativeMethods.WriteBytes writer = (data, len, _) =>
+ {
+ var buffer = new byte[len];
+ Marshal.Copy(data, buffer, 0, (int)len);
+ bytes.AddRange(buffer);
+ return 0;
+ };
+
+ int result = NativeMethods.TransactionToBytes(_handle, writer, IntPtr.Zero);
+ if (result != 0)
+ throw new TransactionException("Failed to serialize transaction");
+ return bytes.ToArray();
+ }
+
+ ///
+ /// Gets the transaction input at the specified index as a non-owning
+ /// whose lifetime is tied to this transaction.
+ ///
/// Thrown when index is out of range.
/// Thrown when input retrieval fails.
- public IntPtr GetInputAt(int index)
+ public TransactionInput GetInputAt(int index)
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, InputCount);
@@ -127,7 +157,7 @@ public IntPtr GetInputAt(int index)
if (inputPtr == IntPtr.Zero)
throw new TransactionException($"Failed to get input at index {index}");
- return inputPtr;
+ return new TransactionInput(inputPtr, ownsHandle: false);
}
/// The TxOut at the specified index.
diff --git a/src/BitcoinKernel/Primitives/TransactionInput.cs b/src/BitcoinKernel/Primitives/TransactionInput.cs
new file mode 100644
index 0000000..2e5d929
--- /dev/null
+++ b/src/BitcoinKernel/Primitives/TransactionInput.cs
@@ -0,0 +1,96 @@
+using BitcoinKernel.Exceptions;
+using BitcoinKernel.Interop;
+
+namespace BitcoinKernel.Primitives;
+
+///
+/// Represents a transaction input.
+///
+public sealed class TransactionInput : IDisposable
+{
+ private IntPtr _handle;
+ private bool _disposed;
+ private readonly bool _ownsHandle;
+
+ internal TransactionInput(IntPtr handle, bool ownsHandle = true)
+ {
+ _handle = handle != IntPtr.Zero
+ ? handle
+ : throw new ArgumentException("Invalid transaction input handle", nameof(handle));
+ _ownsHandle = ownsHandle;
+ }
+
+ internal IntPtr Handle
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return _handle;
+ }
+ }
+
+ ///
+ /// Gets the nSequence value of this input.
+ ///
+ public uint Sequence
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return NativeMethods.TransactionInputGetSequence(_handle);
+ }
+ }
+
+ ///
+ /// Gets the out point spent by this input. The returned out point is a
+ /// non-owning view whose lifetime is tied to this input.
+ ///
+ public OutPoint GetOutPoint()
+ {
+ ThrowIfDisposed();
+ var outPointPtr = NativeMethods.TransactionInputGetOutPoint(_handle);
+ if (outPointPtr == IntPtr.Zero)
+ throw new TransactionException("Failed to get out point from transaction input");
+
+ return new OutPoint(outPointPtr, ownsHandle: false);
+ }
+
+ ///
+ /// Creates an owned copy of this transaction input.
+ ///
+ public TransactionInput Copy()
+ {
+ ThrowIfDisposed();
+ var copy = NativeMethods.TransactionInputCopy(_handle);
+ if (copy == IntPtr.Zero)
+ throw new TransactionException("Failed to copy transaction input");
+
+ return new TransactionInput(copy);
+ }
+
+ private void ThrowIfDisposed()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException(nameof(TransactionInput));
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ if (_handle != IntPtr.Zero && _ownsHandle)
+ {
+ NativeMethods.TransactionInputDestroy(_handle);
+ _handle = IntPtr.Zero;
+ }
+ _disposed = true;
+ }
+ GC.SuppressFinalize(this);
+ }
+
+ ~TransactionInput()
+ {
+ if (_ownsHandle)
+ Dispose();
+ }
+}
diff --git a/src/BitcoinKernel/Primitives/TxOut.cs b/src/BitcoinKernel/Primitives/TxOut.cs
index ca89601..da07078 100644
--- a/src/BitcoinKernel/Primitives/TxOut.cs
+++ b/src/BitcoinKernel/Primitives/TxOut.cs
@@ -72,29 +72,22 @@ public IntPtr GetScriptPubkeyPtr()
}
///
- /// Gets the script pubkey as a byte array.
+ /// Gets the script pubkey of this output as a non-owning
+ /// object whose lifetime is tied to this output.
///
- /// The script pubkey bytes.
- public byte[] GetScriptPubkey()
+ public ScriptPubKey GetScriptPubkey()
{
- IntPtr scriptPtr = GetScriptPubkeyPtr();
-
- var bytes = new List();
- NativeMethods.WriteBytes writer = (data, len, _) =>
- {
- var buffer = new byte[len];
- Marshal.Copy(data, buffer, 0, (int)len);
- bytes.AddRange(buffer);
- return 0;
- };
-
- int result = NativeMethods.ScriptPubkeyToBytes(scriptPtr, writer, IntPtr.Zero);
- if (result != 0)
- {
- throw new TransactionException("Failed to serialize script pubkey");
- }
+ return new ScriptPubKey(GetScriptPubkeyPtr(), ownsHandle: false);
+ }
- return bytes.ToArray();
+ ///
+ /// Gets the script pubkey of this output as a byte array.
+ ///
+ /// The script pubkey bytes.
+ public byte[] GetScriptPubkeyBytes()
+ {
+ using var scriptPubkey = GetScriptPubkey();
+ return scriptPubkey.ToBytes();
}
///
diff --git a/src/BitcoinKernel/Primitives/Txid.cs b/src/BitcoinKernel/Primitives/Txid.cs
new file mode 100644
index 0000000..40c266c
--- /dev/null
+++ b/src/BitcoinKernel/Primitives/Txid.cs
@@ -0,0 +1,96 @@
+using System.Runtime.InteropServices;
+using BitcoinKernel.Exceptions;
+using BitcoinKernel.Interop;
+
+namespace BitcoinKernel.Primitives;
+
+///
+/// Represents a transaction id (txid).
+///
+public sealed class Txid : IDisposable, IEquatable
+{
+ private IntPtr _handle;
+ private bool _disposed;
+ private readonly bool _ownsHandle;
+
+ internal Txid(IntPtr handle, bool ownsHandle = true)
+ {
+ _handle = handle != IntPtr.Zero
+ ? handle
+ : throw new ArgumentException("Invalid txid handle", nameof(handle));
+ _ownsHandle = ownsHandle;
+ }
+
+ internal IntPtr Handle
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return _handle;
+ }
+ }
+
+ ///
+ /// Converts the txid to a 32-byte array.
+ ///
+ public byte[] ToBytes()
+ {
+ ThrowIfDisposed();
+ var bytes = new byte[32];
+ NativeMethods.TxidToBytes(_handle, bytes);
+ return bytes;
+ }
+
+ ///
+ /// Creates an owned copy of this txid.
+ ///
+ public Txid Copy()
+ {
+ ThrowIfDisposed();
+ var copy = NativeMethods.TxidCopy(_handle);
+ if (copy == IntPtr.Zero)
+ throw new TransactionException("Failed to copy txid");
+
+ return new Txid(copy);
+ }
+
+ ///
+ /// Determines whether this txid equals another.
+ ///
+ public bool Equals(Txid? other)
+ {
+ if (other is null) return false;
+ ThrowIfDisposed();
+ return NativeMethods.TxidEquals(_handle, other.Handle) != 0;
+ }
+
+ public override bool Equals(object? obj) => obj is Txid other && Equals(other);
+
+ public override int GetHashCode() => Convert.ToHexString(ToBytes()).GetHashCode();
+
+ private void ThrowIfDisposed()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException(nameof(Txid));
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ if (_handle != IntPtr.Zero && _ownsHandle)
+ {
+ NativeMethods.TxidDestroy(_handle);
+ _handle = IntPtr.Zero;
+ }
+ _disposed = true;
+ }
+ GC.SuppressFinalize(this);
+ }
+
+ ~Txid()
+ {
+ if (_ownsHandle)
+ Dispose();
+ }
+}
diff --git a/tests/BitcoinKernel.Tests/BlockHeaderTests.cs b/tests/BitcoinKernel.Tests/BlockHeaderTests.cs
index 6762204..aae920b 100644
--- a/tests/BitcoinKernel.Tests/BlockHeaderTests.cs
+++ b/tests/BitcoinKernel.Tests/BlockHeaderTests.cs
@@ -223,7 +223,7 @@ public void BlockIndex_GetBlockHeader_HashShouldMatchIndexHash()
var chain = _chainstateManager!.GetActiveChain();
var tip = chain.GetTip();
- var tipHash = tip.GetBlockHash();
+ var tipHash = tip.GetHash();
using var header = tip.GetBlockHeader();
var headerHash = header.GetHash();
@@ -241,7 +241,7 @@ public void ChainstateManager_GetBestBlockIndex_ShouldReturnTip()
var tip = chain.GetTip();
Assert.Equal(tip.Height, bestIndex.Height);
- Assert.Equal(tip.GetBlockHash(), bestIndex.GetBlockHash());
+ Assert.Equal(tip.GetHash(), bestIndex.GetHash());
}
[Fact]
diff --git a/tests/BitcoinKernel.Tests/BlockProcessingTests.cs b/tests/BitcoinKernel.Tests/BlockProcessingTests.cs
index a4b4215..dbd6cb7 100644
--- a/tests/BitcoinKernel.Tests/BlockProcessingTests.cs
+++ b/tests/BitcoinKernel.Tests/BlockProcessingTests.cs
@@ -257,7 +257,7 @@ public void TestScanTransactions()
Assert.NotNull(output);
// Verify we can get the script pubkey from the output
- var scriptPubkeyBytes = output.GetScriptPubkey();
+ var scriptPubkeyBytes = output.GetScriptPubkeyBytes();
Assert.NotNull(scriptPubkeyBytes);
Assert.True(scriptPubkeyBytes.Length >= 0, "Script pubkey should have valid length");
@@ -286,14 +286,14 @@ public void TestChainOperations()
var genesis = chain.GetGenesis();
Assert.NotNull(genesis);
Assert.Equal(0, genesis.Height);
- var genesisHash = genesis.GetBlockHash();
+ var genesisHash = genesis.GetHash();
Assert.NotNull(genesisHash);
// Test tip block
var tip = chain.GetTip();
Assert.NotNull(tip);
var tipHeight = tip.Height;
- var tipHash = tip.GetBlockHash();
+ var tipHash = tip.GetHash();
Assert.True(tipHeight > 0);
Assert.False(genesisHash.SequenceEqual(tipHash));
@@ -302,13 +302,13 @@ public void TestChainOperations()
var genesisViaHeight = chain.GetBlockByHeight(0);
Assert.NotNull(genesisViaHeight);
Assert.Equal(0, genesisViaHeight.Height);
- Assert.True(genesisHash.SequenceEqual(genesisViaHeight.GetBlockHash()));
+ Assert.True(genesisHash.SequenceEqual(genesisViaHeight.GetHash()));
// Test accessing block by height - tip
var tipViaHeight = chain.GetBlockByHeight(tipHeight);
Assert.NotNull(tipViaHeight);
Assert.Equal(tipHeight, tipViaHeight.Height);
- Assert.True(tipHash.SequenceEqual(tipViaHeight.GetBlockHash()));
+ Assert.True(tipHash.SequenceEqual(tipViaHeight.GetHash()));
// Test invalid height returns null
var invalidEntry = chain.GetBlockByHeight(9999);
diff --git a/tests/BitcoinKernel.Tests/BlockTreeEntryTests.cs b/tests/BitcoinKernel.Tests/BlockTreeEntryTests.cs
index 8a2e39c..e3911fa 100644
--- a/tests/BitcoinKernel.Tests/BlockTreeEntryTests.cs
+++ b/tests/BitcoinKernel.Tests/BlockTreeEntryTests.cs
@@ -69,7 +69,7 @@ private static List ReadBlockData()
public void Equals_SameBlock_ReturnsTrue()
{
SetupWithBlocks();
- var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
+ var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetHash();
var entry1 = _blockProcessor!.GetBlockTreeEntry(tipHash);
var entry2 = _blockProcessor.GetBlockTreeEntry(tipHash);
@@ -83,8 +83,8 @@ public void Equals_DifferentBlocks_ReturnsFalse()
SetupWithBlocks();
var chain = _chainstateManager!.GetActiveChain();
- var tipEntry = _blockProcessor!.GetBlockTreeEntry(chain.GetTip().GetBlockHash());
- var genesisEntry = _blockProcessor.GetBlockTreeEntry(chain.GetBlockByHeight(0)!.GetBlockHash());
+ var tipEntry = _blockProcessor!.GetBlockTreeEntry(chain.GetTip().GetHash());
+ var genesisEntry = _blockProcessor.GetBlockTreeEntry(chain.GetBlockByHeight(0)!.GetHash());
Assert.False(tipEntry!.Equals(genesisEntry));
}
@@ -93,7 +93,7 @@ public void Equals_DifferentBlocks_ReturnsFalse()
public void Equals_WithNull_ReturnsFalse()
{
SetupWithBlocks();
- var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
+ var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetHash();
var entry = _blockProcessor!.GetBlockTreeEntry(tipHash);
@@ -104,7 +104,7 @@ public void Equals_WithNull_ReturnsFalse()
public void GetHashCode_EqualEntries_ReturnsSameHashCode()
{
SetupWithBlocks();
- var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
+ var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetHash();
var entry1 = _blockProcessor!.GetBlockTreeEntry(tipHash);
var entry2 = _blockProcessor.GetBlockTreeEntry(tipHash);
@@ -116,7 +116,7 @@ public void GetHashCode_EqualEntries_ReturnsSameHashCode()
public void OperatorEquals_SameBlock_ReturnsTrue()
{
SetupWithBlocks();
- var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
+ var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetHash();
var entry1 = _blockProcessor!.GetBlockTreeEntry(tipHash);
var entry2 = _blockProcessor.GetBlockTreeEntry(tipHash);
@@ -130,8 +130,8 @@ public void OperatorNotEquals_DifferentBlocks_ReturnsTrue()
SetupWithBlocks();
var chain = _chainstateManager!.GetActiveChain();
- var tipEntry = _blockProcessor!.GetBlockTreeEntry(chain.GetTip().GetBlockHash());
- var genesisEntry = _blockProcessor.GetBlockTreeEntry(chain.GetBlockByHeight(0)!.GetBlockHash());
+ var tipEntry = _blockProcessor!.GetBlockTreeEntry(chain.GetTip().GetHash());
+ var genesisEntry = _blockProcessor.GetBlockTreeEntry(chain.GetBlockByHeight(0)!.GetHash());
Assert.True(tipEntry != genesisEntry);
}
@@ -140,7 +140,7 @@ public void OperatorNotEquals_DifferentBlocks_ReturnsTrue()
public void GetPrevious_EqualEntries_ReturnEqualPrevious()
{
SetupWithBlocks();
- var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetBlockHash();
+ var tipHash = _chainstateManager!.GetActiveChain().GetTip().GetHash();
var entry1 = _blockProcessor!.GetBlockTreeEntry(tipHash);
var entry2 = _blockProcessor.GetBlockTreeEntry(tipHash);
diff --git a/tools/kernel-bindings-test-handler/Handlers/MethodDispatcher.cs b/tools/kernel-bindings-test-handler/Handlers/MethodDispatcher.cs
index 001a7ce..e9d72ef 100644
--- a/tools/kernel-bindings-test-handler/Handlers/MethodDispatcher.cs
+++ b/tools/kernel-bindings-test-handler/Handlers/MethodDispatcher.cs
@@ -175,13 +175,261 @@ public Response BlockCreate(string id, string? refName, BtckBlockCreateParams p)
}
}
- public Response BlockTreeEntryGetBlockHash(string id, BtckBlockTreeEntryGetBlockHashParams p)
+ public Response BlockGetHash(string id, string? refName, BtckBlockRefParams p)
{
+ if (refName == null) return RefError(id);
+ if (p.Block?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetBlockHash());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockGetHeader(string id, string? refName, BtckBlockRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Block?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetHeader());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockCopy(string id, string? refName, BtckBlockRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Block?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).Copy());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockCountTransactions(string id, BtckBlockRefParams p)
+ {
+ if (p.Block?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Get(r).TransactionCount);
+ }
+
+ public Response BlockGetTransactionAt(string id, string? refName, BtckBlockGetTransactionAtParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Block?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ var tx = Get(r).GetTransaction(p.TransactionIndex);
+ if (tx == null) return Responses.EmptyError(id);
+ _registry.Register(refName, tx);
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockToBytes(string id, BtckBlockRefParams p)
+ {
+ if (p.Block?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Hex(Get(r).ToBytes()));
+ }
+
+ public Response BlockDestroy(string id, BtckBlockRefParams p)
+ {
+ if (p.Block?.Ref is { } r) _registry.Destroy(r);
+ return Responses.Null(id);
+ }
+
+ // ── Block Hash ────────────────────────────────────────────────────────────
+
+ public Response BlockHashCreate(string id, string? refName, BtckBlockHashCreateParams p)
+ {
+ if (refName == null) return RefError(id);
+
+ try
+ {
+ var hash = BlockHash.FromBytes(Convert.FromHexString(p.BlockHashHex));
+ _registry.Register(refName, hash);
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockHashToBytes(string id, BtckBlockHashRefParams p)
+ {
+ if (p.BlockHash?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Hex(Get(r).ToBytes()));
+ }
+
+ public Response BlockHashEquals(string id, BtckBlockHashEqualsParams p)
+ {
+ if (p.Hash1?.Ref is not { } r1) return RefError(id);
+ if (p.Hash2?.Ref is not { } r2) return RefError(id);
+ return Responses.Ok(id, Get(r1).Equals(Get(r2)));
+ }
+
+ public Response BlockHashCopy(string id, string? refName, BtckBlockHashRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.BlockHash?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).Copy());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockHashDestroy(string id, BtckBlockHashRefParams p)
+ {
+ if (p.BlockHash?.Ref is { } r) _registry.Destroy(r);
+ return Responses.Null(id);
+ }
+
+ // ── Block Header ──────────────────────────────────────────────────────────
+
+ public Response BlockHeaderCreate(string id, string? refName, BtckBlockHeaderCreateParams p)
+ {
+ if (refName == null) return RefError(id);
+
+ try
+ {
+ var header = BlockHeader.FromBytes(Convert.FromHexString(p.RawBlockHeader));
+ _registry.Register(refName, header);
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockHeaderToBytes(string id, BtckBlockHeaderRefParams p)
+ {
+ if (p.Header?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Hex(Get(r).ToBytes()));
+ }
+
+ public Response BlockHeaderGetHash(string id, string? refName, BtckBlockHeaderRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Header?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetBlockHash());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockHeaderGetPrevHash(string id, string? refName, BtckBlockHeaderRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Header?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetPrevBlockHash());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockHeaderGetVersion(string id, BtckBlockHeaderRefParams p)
+ {
+ if (p.Header?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Get(r).Version);
+ }
+
+ public Response BlockHeaderGetTimestamp(string id, BtckBlockHeaderRefParams p)
+ {
+ if (p.Header?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Get(r).Timestamp);
+ }
+
+ public Response BlockHeaderGetBits(string id, BtckBlockHeaderRefParams p)
+ {
+ if (p.Header?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Get(r).Bits);
+ }
+
+ public Response BlockHeaderGetNonce(string id, BtckBlockHeaderRefParams p)
+ {
+ if (p.Header?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Get(r).Nonce);
+ }
+
+ public Response BlockHeaderCopy(string id, string? refName, BtckBlockHeaderRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Header?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).Copy());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response BlockHeaderDestroy(string id, BtckBlockHeaderRefParams p)
+ {
+ if (p.Header?.Ref is { } r) _registry.Destroy(r);
+ return Responses.Null(id);
+ }
+
+ // ── Block Tree Entry ──────────────────────────────────────────────────────
+
+ public Response BlockTreeEntryGetBlockHash(string id, string? refName, BtckBlockTreeEntryGetBlockHashParams p)
+ {
+ if (refName == null) return RefError(id);
if (p.BlockTreeEntry?.Ref is not { } bteRef) return RefError(id);
- var hashBytes = GetVal(bteRef).GetBlockHash();
- // Reverse bytes to get display (big-endian) order
- return Responses.Ok(id, Convert.ToHexString(hashBytes.Reverse().ToArray()).ToLowerInvariant());
+ try
+ {
+ _registry.Register(refName, GetVal(bteRef).GetBlockHash());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
}
// ── Script Pubkey ─────────────────────────────────────────────────────────
@@ -192,7 +440,8 @@ public Response ScriptPubkeyCreate(string id, string? refName, BtckScriptPubkeyC
try
{
- var spk = ScriptPubKey.FromHex(p.ScriptPubKeyHex);
+ // Use FromBytes (not FromHex) so an empty script pubkey (empty hex) is accepted.
+ var spk = ScriptPubKey.FromBytes(Convert.FromHexString(p.ScriptPubKeyHex));
_registry.Register(refName, spk);
return Responses.Ref(id, refName);
}
@@ -202,10 +451,26 @@ public Response ScriptPubkeyCreate(string id, string? refName, BtckScriptPubkeyC
}
}
- public Response ScriptPubkeyDestroy(string id, BtckScriptPubkeyDestroyParams p)
+ public Response ScriptPubkeyCopy(string id, string? refName, BtckScriptPubkeyRefParams p)
{
- if (p.ScriptPubKey?.Ref is { } r) _registry.Destroy(r);
- return Responses.Null(id);
+ if (refName == null) return RefError(id);
+ if (p.ScriptPubKey?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).Copy());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response ScriptPubkeyToBytes(string id, BtckScriptPubkeyRefParams p)
+ {
+ if (p.ScriptPubKey?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Hex(Get(r).ToBytes()));
}
public Response ScriptPubkeyVerify(string id, BtckScriptPubkeyVerifyParams p)
@@ -244,6 +509,12 @@ public Response ScriptPubkeyVerify(string id, BtckScriptPubkeyVerifyParams p)
}
}
+ public Response ScriptPubkeyDestroy(string id, BtckScriptPubkeyDestroyParams p)
+ {
+ if (p.ScriptPubKey?.Ref is { } r) _registry.Destroy(r);
+ return Responses.Null(id);
+ }
+
// ── Transaction ───────────────────────────────────────────────────────────
public Response TransactionCreate(string id, string? refName, BtckTransactionCreateParams p)
@@ -262,12 +533,217 @@ public Response TransactionCreate(string id, string? refName, BtckTransactionCre
}
}
+ public Response TransactionCopy(string id, string? refName, BtckTransactionRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Transaction?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).Copy());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response TransactionCountInputs(string id, BtckTransactionRefParams p)
+ {
+ if (p.Transaction?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Get(r).InputCount);
+ }
+
+ public Response TransactionCountOutputs(string id, BtckTransactionRefParams p)
+ {
+ if (p.Transaction?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Get(r).OutputCount);
+ }
+
+ public Response TransactionGetTxid(string id, string? refName, BtckTransactionRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Transaction?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetTxid());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response TransactionToBytes(string id, BtckTransactionRefParams p)
+ {
+ if (p.Transaction?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Hex(Get(r).ToBytes()));
+ }
+
+ public Response TransactionGetInputAt(string id, string? refName, BtckTransactionGetInputAtParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Transaction?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetInputAt(p.InputIndex));
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response TransactionGetOutputAt(string id, string? refName, BtckTransactionGetOutputAtParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Transaction?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetOutputAt(p.OutputIndex));
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
public Response TransactionDestroy(string id, BtckTransactionDestroyParams p)
{
if (p.Transaction?.Ref is { } r) _registry.Destroy(r);
return Responses.Null(id);
}
+ // ── Transaction Input ─────────────────────────────────────────────────────
+
+ public Response TransactionInputGetOutPoint(string id, string? refName, BtckTransactionInputRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.TransactionInput?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetOutPoint());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response TransactionInputCopy(string id, string? refName, BtckTransactionInputRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.TransactionInput?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).Copy());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response TransactionInputDestroy(string id, BtckTransactionInputRefParams p)
+ {
+ if (p.TransactionInput?.Ref is { } r) _registry.Destroy(r);
+ return Responses.Null(id);
+ }
+
+ // ── Transaction Out Point ─────────────────────────────────────────────────
+
+ public Response TransactionOutPointGetIndex(string id, BtckTransactionOutPointRefParams p)
+ {
+ if (p.TransactionOutPoint?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Get(r).Index);
+ }
+
+ public Response TransactionOutPointGetTxid(string id, string? refName, BtckTransactionOutPointRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.TransactionOutPoint?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetTxid());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response TransactionOutPointCopy(string id, string? refName, BtckTransactionOutPointRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.TransactionOutPoint?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).Copy());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response TransactionOutPointDestroy(string id, BtckTransactionOutPointRefParams p)
+ {
+ if (p.TransactionOutPoint?.Ref is { } r) _registry.Destroy(r);
+ return Responses.Null(id);
+ }
+
+ // ── Txid ──────────────────────────────────────────────────────────────────
+
+ public Response TxidToBytes(string id, BtckTxidRefParams p)
+ {
+ if (p.Txid?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Hex(Get(r).ToBytes()));
+ }
+
+ public Response TxidEquals(string id, BtckTxidEqualsParams p)
+ {
+ if (p.Txid1?.Ref is not { } r1) return RefError(id);
+ if (p.Txid2?.Ref is not { } r2) return RefError(id);
+ return Responses.Ok(id, Get(r1).Equals(Get(r2)));
+ }
+
+ public Response TxidCopy(string id, string? refName, BtckTxidRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.Txid?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).Copy());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response TxidDestroy(string id, BtckTxidRefParams p)
+ {
+ if (p.Txid?.Ref is { } r) _registry.Destroy(r);
+ return Responses.Null(id);
+ }
+
// ── Transaction Output ────────────────────────────────────────────────────
public Response TransactionOutputCreate(string id, string? refName, BtckTransactionOutputCreateParams p)
@@ -288,6 +764,44 @@ public Response TransactionOutputCreate(string id, string? refName, BtckTransact
}
}
+ public Response TransactionOutputCopy(string id, string? refName, BtckTransactionOutputRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.TransactionOutput?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).Copy());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
+ public Response TransactionOutputGetAmount(string id, BtckTransactionOutputRefParams p)
+ {
+ if (p.TransactionOutput?.Ref is not { } r) return RefError(id);
+ return Responses.Ok(id, Get(r).Amount);
+ }
+
+ public Response TransactionOutputGetScriptPubkey(string id, string? refName, BtckTransactionOutputRefParams p)
+ {
+ if (refName == null) return RefError(id);
+ if (p.TransactionOutput?.Ref is not { } r) return RefError(id);
+
+ try
+ {
+ _registry.Register(refName, Get(r).GetScriptPubkey());
+ return Responses.Ref(id, refName);
+ }
+ catch
+ {
+ return Responses.EmptyError(id);
+ }
+ }
+
public Response TransactionOutputDestroy(string id, BtckTransactionOutputDestroyParams p)
{
if (p.TransactionOutput?.Ref is { } r) _registry.Destroy(r);
@@ -331,6 +845,8 @@ public Response PrecomputedTransactionDataDestroy(string id, BtckPrecomputedTran
// ── Parsing helpers ───────────────────────────────────────────────────────
+ private static string Hex(byte[] bytes) => Convert.ToHexString(bytes).ToLowerInvariant();
+
private static ChainType ParseChainType(string s) => s switch
{
"btck_ChainType_MAINNET" => ChainType.MAINNET,
diff --git a/tools/kernel-bindings-test-handler/Program.cs b/tools/kernel-bindings-test-handler/Program.cs
index f7afa58..97469ca 100644
--- a/tools/kernel-bindings-test-handler/Program.cs
+++ b/tools/kernel-bindings-test-handler/Program.cs
@@ -113,10 +113,100 @@ private static Response Dispatch(Request request, MethodDispatcher dispatcher, J
dispatcher.BlockCreate(id, request.Ref,
Deserialize(request.Params, opts)),
+ "btck_block_get_hash" =>
+ dispatcher.BlockGetHash(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_get_header" =>
+ dispatcher.BlockGetHeader(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_copy" =>
+ dispatcher.BlockCopy(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_count_transactions" =>
+ dispatcher.BlockCountTransactions(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_get_transaction_at" =>
+ dispatcher.BlockGetTransactionAt(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_to_bytes" =>
+ dispatcher.BlockToBytes(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_destroy" =>
+ dispatcher.BlockDestroy(id,
+ Deserialize(request.Params, opts)),
+
"btck_block_tree_entry_get_block_hash" =>
- dispatcher.BlockTreeEntryGetBlockHash(id,
+ dispatcher.BlockTreeEntryGetBlockHash(id, request.Ref,
Deserialize(request.Params, opts)),
+ // ── Block Hash ────────────────────────────────────────────────
+ "btck_block_hash_create" =>
+ dispatcher.BlockHashCreate(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_hash_to_bytes" =>
+ dispatcher.BlockHashToBytes(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_hash_equals" =>
+ dispatcher.BlockHashEquals(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_hash_copy" =>
+ dispatcher.BlockHashCopy(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_hash_destroy" =>
+ dispatcher.BlockHashDestroy(id,
+ Deserialize(request.Params, opts)),
+
+ // ── Block Header ──────────────────────────────────────────────
+ "btck_block_header_create" =>
+ dispatcher.BlockHeaderCreate(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_header_to_bytes" =>
+ dispatcher.BlockHeaderToBytes(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_header_get_hash" =>
+ dispatcher.BlockHeaderGetHash(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_header_get_prev_hash" =>
+ dispatcher.BlockHeaderGetPrevHash(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_header_get_version" =>
+ dispatcher.BlockHeaderGetVersion(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_header_get_timestamp" =>
+ dispatcher.BlockHeaderGetTimestamp(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_header_get_bits" =>
+ dispatcher.BlockHeaderGetBits(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_header_get_nonce" =>
+ dispatcher.BlockHeaderGetNonce(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_header_copy" =>
+ dispatcher.BlockHeaderCopy(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_block_header_destroy" =>
+ dispatcher.BlockHeaderDestroy(id,
+ Deserialize(request.Params, opts)),
+
// ── Script Pubkey ─────────────────────────────────────────────
"btck_script_pubkey_create" =>
dispatcher.ScriptPubkeyCreate(id, request.Ref,
@@ -126,6 +216,14 @@ private static Response Dispatch(Request request, MethodDispatcher dispatcher, J
dispatcher.ScriptPubkeyDestroy(id,
Deserialize(request.Params, opts)),
+ "btck_script_pubkey_copy" =>
+ dispatcher.ScriptPubkeyCopy(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_script_pubkey_to_bytes" =>
+ dispatcher.ScriptPubkeyToBytes(id,
+ Deserialize(request.Params, opts)),
+
"btck_script_pubkey_verify" =>
dispatcher.ScriptPubkeyVerify(id,
Deserialize(request.Params, opts)),
@@ -139,6 +237,81 @@ private static Response Dispatch(Request request, MethodDispatcher dispatcher, J
dispatcher.TransactionDestroy(id,
Deserialize(request.Params, opts)),
+ "btck_transaction_copy" =>
+ dispatcher.TransactionCopy(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_count_inputs" =>
+ dispatcher.TransactionCountInputs(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_count_outputs" =>
+ dispatcher.TransactionCountOutputs(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_get_txid" =>
+ dispatcher.TransactionGetTxid(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_to_bytes" =>
+ dispatcher.TransactionToBytes(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_get_input_at" =>
+ dispatcher.TransactionGetInputAt(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_get_output_at" =>
+ dispatcher.TransactionGetOutputAt(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ // ── Transaction Input ─────────────────────────────────────────
+ "btck_transaction_input_get_out_point" =>
+ dispatcher.TransactionInputGetOutPoint(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_input_copy" =>
+ dispatcher.TransactionInputCopy(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_input_destroy" =>
+ dispatcher.TransactionInputDestroy(id,
+ Deserialize(request.Params, opts)),
+
+ // ── Transaction Out Point ─────────────────────────────────────
+ "btck_transaction_out_point_get_index" =>
+ dispatcher.TransactionOutPointGetIndex(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_out_point_get_txid" =>
+ dispatcher.TransactionOutPointGetTxid(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_out_point_copy" =>
+ dispatcher.TransactionOutPointCopy(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_out_point_destroy" =>
+ dispatcher.TransactionOutPointDestroy(id,
+ Deserialize(request.Params, opts)),
+
+ // ── Txid ──────────────────────────────────────────────────────
+ "btck_txid_to_bytes" =>
+ dispatcher.TxidToBytes(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_txid_equals" =>
+ dispatcher.TxidEquals(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_txid_copy" =>
+ dispatcher.TxidCopy(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_txid_destroy" =>
+ dispatcher.TxidDestroy(id,
+ Deserialize(request.Params, opts)),
+
// ── Transaction Output ────────────────────────────────────────
"btck_transaction_output_create" =>
dispatcher.TransactionOutputCreate(id, request.Ref,
@@ -148,6 +321,18 @@ private static Response Dispatch(Request request, MethodDispatcher dispatcher, J
dispatcher.TransactionOutputDestroy(id,
Deserialize(request.Params, opts)),
+ "btck_transaction_output_copy" =>
+ dispatcher.TransactionOutputCopy(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_output_get_amount" =>
+ dispatcher.TransactionOutputGetAmount(id,
+ Deserialize(request.Params, opts)),
+
+ "btck_transaction_output_get_script_pubkey" =>
+ dispatcher.TransactionOutputGetScriptPubkey(id, request.Ref,
+ Deserialize(request.Params, opts)),
+
// ── Precomputed Transaction Data ──────────────────────────────
"btck_precomputed_transaction_data_create" =>
dispatcher.PrecomputedTransactionDataCreate(id, request.Ref,
diff --git a/tools/kernel-bindings-test-handler/Protocol/Request.cs b/tools/kernel-bindings-test-handler/Protocol/Request.cs
index 46d37c1..388d40d 100644
--- a/tools/kernel-bindings-test-handler/Protocol/Request.cs
+++ b/tools/kernel-bindings-test-handler/Protocol/Request.cs
@@ -117,6 +117,63 @@ public class BtckBlockCreateParams
public string RawBlock { get; set; } = string.Empty;
}
+/// A single block reference parameter.
+public class BtckBlockRefParams
+{
+ [JsonPropertyName("block")]
+ public RefType? Block { get; set; }
+}
+
+public class BtckBlockGetTransactionAtParams
+{
+ [JsonPropertyName("block")]
+ public RefType? Block { get; set; }
+
+ [JsonPropertyName("transaction_index")]
+ public int TransactionIndex { get; set; }
+}
+
+// ── Block Hash ────────────────────────────────────────────────────────────────
+
+public class BtckBlockHashCreateParams
+{
+ [JsonPropertyName("block_hash")]
+ public string BlockHashHex { get; set; } = string.Empty;
+}
+
+/// A single block_hash reference parameter.
+public class BtckBlockHashRefParams
+{
+ [JsonPropertyName("block_hash")]
+ public RefType? BlockHash { get; set; }
+}
+
+public class BtckBlockHashEqualsParams
+{
+ [JsonPropertyName("hash1")]
+ public RefType? Hash1 { get; set; }
+
+ [JsonPropertyName("hash2")]
+ public RefType? Hash2 { get; set; }
+}
+
+// ── Block Header ──────────────────────────────────────────────────────────────
+
+public class BtckBlockHeaderCreateParams
+{
+ [JsonPropertyName("raw_block_header")]
+ public string RawBlockHeader { get; set; } = string.Empty;
+}
+
+/// A single header reference parameter.
+public class BtckBlockHeaderRefParams
+{
+ [JsonPropertyName("header")]
+ public RefType? Header { get; set; }
+}
+
+// ── Block Tree Entry ──────────────────────────────────────────────────────────
+
public class BtckBlockTreeEntryGetBlockHashParams
{
[JsonPropertyName("block_tree_entry")]
@@ -131,7 +188,8 @@ public class BtckScriptPubkeyCreateParams
public string ScriptPubKeyHex { get; set; } = string.Empty;
}
-public class BtckScriptPubkeyDestroyParams
+/// A single script_pubkey reference parameter.
+public class BtckScriptPubkeyRefParams
{
[JsonPropertyName("script_pubkey")]
public RefType? ScriptPubKey { get; set; }
@@ -158,6 +216,12 @@ public class BtckScriptPubkeyVerifyParams
public JsonElement? Flags { get; set; }
}
+public class BtckScriptPubkeyDestroyParams
+{
+ [JsonPropertyName("script_pubkey")]
+ public RefType? ScriptPubKey { get; set; }
+}
+
// ── Transaction ───────────────────────────────────────────────────────────────
public class BtckTransactionCreateParams
@@ -166,12 +230,73 @@ public class BtckTransactionCreateParams
public string RawTransaction { get; set; } = string.Empty;
}
+/// A single transaction reference parameter.
+public class BtckTransactionRefParams
+{
+ [JsonPropertyName("transaction")]
+ public RefType? Transaction { get; set; }
+}
+
+public class BtckTransactionGetInputAtParams
+{
+ [JsonPropertyName("transaction")]
+ public RefType? Transaction { get; set; }
+
+ [JsonPropertyName("input_index")]
+ public int InputIndex { get; set; }
+}
+
+public class BtckTransactionGetOutputAtParams
+{
+ [JsonPropertyName("transaction")]
+ public RefType? Transaction { get; set; }
+
+ [JsonPropertyName("output_index")]
+ public int OutputIndex { get; set; }
+}
+
public class BtckTransactionDestroyParams
{
[JsonPropertyName("transaction")]
public RefType? Transaction { get; set; }
}
+// ── Transaction Input ─────────────────────────────────────────────────────────
+
+/// A single transaction_input reference parameter.
+public class BtckTransactionInputRefParams
+{
+ [JsonPropertyName("transaction_input")]
+ public RefType? TransactionInput { get; set; }
+}
+
+// ── Transaction Out Point ─────────────────────────────────────────────────────
+
+/// A single transaction_out_point reference parameter.
+public class BtckTransactionOutPointRefParams
+{
+ [JsonPropertyName("transaction_out_point")]
+ public RefType? TransactionOutPoint { get; set; }
+}
+
+// ── Txid ──────────────────────────────────────────────────────────────────────
+
+/// A single txid reference parameter.
+public class BtckTxidRefParams
+{
+ [JsonPropertyName("txid")]
+ public RefType? Txid { get; set; }
+}
+
+public class BtckTxidEqualsParams
+{
+ [JsonPropertyName("txid1")]
+ public RefType? Txid1 { get; set; }
+
+ [JsonPropertyName("txid2")]
+ public RefType? Txid2 { get; set; }
+}
+
// ── Transaction Output ────────────────────────────────────────────────────────
public class BtckTransactionOutputCreateParams
@@ -183,6 +308,13 @@ public class BtckTransactionOutputCreateParams
public long Amount { get; set; }
}
+/// A single transaction_output reference parameter.
+public class BtckTransactionOutputRefParams
+{
+ [JsonPropertyName("transaction_output")]
+ public RefType? TransactionOutput { get; set; }
+}
+
public class BtckTransactionOutputDestroyParams
{
[JsonPropertyName("transaction_output")]
diff --git a/tools/kernel-bindings-test-handler/Protocol/Response.cs b/tools/kernel-bindings-test-handler/Protocol/Response.cs
index 358d641..768373c 100644
--- a/tools/kernel-bindings-test-handler/Protocol/Response.cs
+++ b/tools/kernel-bindings-test-handler/Protocol/Response.cs
@@ -8,7 +8,7 @@ namespace BitcoinKernel.TestHandler.Protocol;
///
public class Response
{
- [JsonPropertyName("id")]
+ [JsonIgnore]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("result")]