Skip to content

Commit ad83a2f

Browse files
committed
Add ValidationInterfaceCallbacks
1 parent 73cc1d4 commit ad83a2f

8 files changed

+174
-29
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
.idea
1+
.idea
2+
.cache

README.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ A Go wrapper for Bitcoin Core's [`libbitcoinkernel`](https://github.com/bitcoin/
44

55
## ⚠️ Work in Progress
66

7-
This library is currently under active development. Check [TODO.md](./TODO.md) for missing wrappers.
7+
This library is experimental and not production-ready. The underlying C API is subject to change, which may cause breaking changes in this wrapper. The wrapper itself may also change based on user feedback, which is welcome and appreciated. Feel free to open issues to help make this wrapper more useful for everyone.
88

99
## Overview
1010

@@ -33,13 +33,7 @@ cd go-bitcoinkernel
3333
make build-kernel
3434
```
3535

36-
This command will:
37-
38-
- Configure Bitcoin Core's CMake build system
39-
- Build only the `libbitcoinkernel` library
40-
- Use parallel compilation for faster builds
41-
42-
Refer to Bitcoin Core's build documentation to for the minimum requirements to compile `libbitcoinkernel` from source:
36+
This command will configure Bitcoin Core's CMake build system and build only the `libbitcoinkernel` shared library. Refer to Bitcoin Core's build documentation to for the minimum requirements to compile `libbitcoinkernel` from source:
4337
([Unix](./depend/bitcoin/doc/build-unix.md),
4438
[macOS](./depend/bitcoin/doc/build-osx.md),
4539
[Windows](./depend/bitcoin/doc/build-windows.md))

TODO.md

Lines changed: 0 additions & 11 deletions
This file was deleted.

kernel/chainstate_manager_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func TestChainstateManager(t *testing.T) {
1212
suite := ChainstateManagerTestSuite{
1313
MaxBlockHeightToImport: 0, // load all blocks from data/regtest/block.txt
1414
NotificationCallbacks: nil, // no notification callbacks
15+
ValidationCallbacks: nil, // no validation callbacks
1516
}
1617
suite.Setup(t)
1718

@@ -113,6 +114,7 @@ func (s *ChainstateManagerTestSuite) TestBlockUndo(t *testing.T) {
113114
type ChainstateManagerTestSuite struct {
114115
MaxBlockHeightToImport int32 // leave zero to load all blocks
115116
NotificationCallbacks *NotificationCallbacks
117+
ValidationCallbacks *ValidationInterfaceCallbacks
116118

117119
Manager *ChainstateManager
118120
ImportedBlocksCount int32
@@ -149,6 +151,13 @@ func (s *ChainstateManagerTestSuite) Setup(t *testing.T) {
149151
}
150152
}
151153

154+
if s.ValidationCallbacks != nil {
155+
err = contextOpts.SetValidationInterface(s.ValidationCallbacks)
156+
if err != nil {
157+
t.Fatalf("SetValidationInterface() error = %v", err)
158+
}
159+
}
160+
152161
ctx, err := NewContext(contextOpts)
153162
if err != nil {
154163
t.Fatalf("NewContext() error = %v", err)
@@ -204,7 +213,6 @@ func (s *ChainstateManagerTestSuite) Setup(t *testing.T) {
204213
if len(blockLines) == 0 {
205214
t.Fatal("No block data found in blocks.txt")
206215
}
207-
t.Logf("Found %d blocks in regtest data", len(blockLines))
208216

209217
for i := 0; i < len(blockLines); i++ {
210218
blockHex := blockLines[i]

kernel/context_options.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ extern void go_notify_warning_set_bridge(void* user_data, kernel_Warning warning
1414
extern void go_notify_warning_unset_bridge(void* user_data, kernel_Warning warning);
1515
extern void go_notify_flush_error_bridge(void* user_data, char* message, size_t message_len);
1616
extern void go_notify_fatal_error_bridge(void* user_data, char* message, size_t message_len);
17+
extern void go_validation_interface_block_checked_bridge(void* user_data, const kernel_BlockPointer* block, const kernel_BlockValidationState* state);
1718
1819
// Wrapper function: C helper to set notifications with Go callbacks
1920
// Converts Handle ID to void* and passes to C library
@@ -30,6 +31,16 @@ static inline void set_notifications_wrapper(kernel_ContextOptions* opts, uintpt
3031
};
3132
kernel_context_options_set_notifications(opts, callbacks);
3233
}
34+
35+
// Wrapper function: C helper to set validation interface with Go callbacks
36+
// Converts Handle ID to void* and passes to C library
37+
static inline void set_validation_interface_wrapper(kernel_ContextOptions* opts, uintptr_t handle) {
38+
kernel_ValidationInterfaceCallbacks callbacks = {
39+
.user_data = (void*)handle,
40+
.block_checked = (kernel_ValidationInterfaceBlockChecked)go_validation_interface_block_checked_bridge,
41+
};
42+
kernel_context_options_set_validation_interface(opts, callbacks);
43+
}
3344
*/
3445
import "C"
3546
import (
@@ -43,6 +54,7 @@ var _ cManagedResource = &ContextOptions{}
4354
type ContextOptions struct {
4455
ptr *C.kernel_ContextOptions
4556
notificationHandle cgo.Handle // Prevents notification callbacks GC until Destroy() called
57+
validationHandle cgo.Handle // Prevents validation callbacks GC until Destroy() called
4658
}
4759

4860
func NewContextOptions() (*ContextOptions, error) {
@@ -87,6 +99,26 @@ func (opts *ContextOptions) SetNotifications(callbacks *NotificationCallbacks) e
8799
return nil
88100
}
89101

102+
// SetValidationInterface sets the validation interface callbacks for these context options.
103+
// The context created with these options will be configured with these validation callbacks.
104+
func (opts *ContextOptions) SetValidationInterface(callbacks *ValidationInterfaceCallbacks) error {
105+
checkReady(opts)
106+
if callbacks == nil {
107+
return ErrNilValidationInterfaceCallbacks
108+
}
109+
110+
// Create a handle for the callbacks - this prevents garbage collection
111+
// and provides a stable ID that can be passed through C code safely
112+
handle := cgo.NewHandle(callbacks)
113+
114+
// Call the C wrapper function to set all validation interface callbacks
115+
C.set_validation_interface_wrapper(opts.ptr, C.uintptr_t(handle))
116+
117+
// Store the handle to prevent GC and allow cleanup
118+
opts.validationHandle = handle
119+
return nil
120+
}
121+
90122
func (opts *ContextOptions) destroy() {
91123
if opts.ptr != nil {
92124
C.kernel_context_options_destroy(opts.ptr)
@@ -97,6 +129,11 @@ func (opts *ContextOptions) destroy() {
97129
opts.notificationHandle.Delete()
98130
opts.notificationHandle = 0
99131
}
132+
if opts.validationHandle != 0 {
133+
// Delete exposes validation callbacks to garbage collection
134+
opts.validationHandle.Delete()
135+
opts.validationHandle = 0
136+
}
100137
}
101138

102139
func (opts *ContextOptions) Destroy() {

kernel/errors.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,15 @@ var (
4848
ErrScriptVerifySpentOutputsRequired = &KernelError{Operation: "kernel_verify_script", Detail: "the taproot flag was set, so valid spent_outputs have to be provided"}
4949
ErrScriptVerifySpentOutputsMismatch = &KernelError{Operation: "kernel_verify_script", Detail: "the number of spent outputs does not match the number of inputs of the transaction"}
5050

51-
ErrInvalidChainType = errors.New("invalid chain type")
52-
ErrInvalidLogLevel = errors.New("invalid log level")
53-
ErrInvalidLogCategory = errors.New("invalid log category")
54-
ErrInvalidScriptVerifyStatus = errors.New("invalid script verify status")
55-
ErrEmptyBlockData = errors.New("empty block data")
56-
ErrEmptyScriptPubkeyData = errors.New("empty script pubkey data")
57-
ErrEmptyTransactionData = errors.New("empty transaction data")
58-
ErrNilNotificationCallbacks = errors.New("nil notification callbacks")
51+
ErrInvalidChainType = errors.New("invalid chain type")
52+
ErrInvalidLogLevel = errors.New("invalid log level")
53+
ErrInvalidLogCategory = errors.New("invalid log category")
54+
ErrInvalidScriptVerifyStatus = errors.New("invalid script verify status")
55+
ErrEmptyBlockData = errors.New("empty block data")
56+
ErrEmptyScriptPubkeyData = errors.New("empty script pubkey data")
57+
ErrEmptyTransactionData = errors.New("empty transaction data")
58+
ErrNilNotificationCallbacks = errors.New("nil notification callbacks")
59+
ErrNilValidationInterfaceCallbacks = errors.New("nil validation interface callbacks")
5960
)
6061

6162
// UninitializedError is returned when an operation is attempted on a
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package kernel
2+
3+
/*
4+
#include "kernel/bitcoinkernel.h"
5+
*/
6+
import "C"
7+
import (
8+
"runtime/cgo"
9+
"unsafe"
10+
)
11+
12+
// ValidationInterfaceCallbacks contains all the Go callback function types for validation interface.
13+
type ValidationInterfaceCallbacks struct {
14+
OnBlockChecked func(block *BlockPointer, state *BlockValidationState)
15+
}
16+
17+
//export go_validation_interface_block_checked_bridge
18+
func go_validation_interface_block_checked_bridge(user_data unsafe.Pointer, block *C.kernel_BlockPointer, state *C.kernel_BlockValidationState) {
19+
// Convert void* back to Handle - user_data contains Handle ID
20+
handle := cgo.Handle(user_data)
21+
// Retrieve original Go callback struct
22+
callbacks := handle.Value().(*ValidationInterfaceCallbacks)
23+
24+
if callbacks.OnBlockChecked != nil {
25+
// Note: BlockPointer and BlockValidationState from validation interface are const and owned by kernel library
26+
// We create wrappers but don't set finalizer since we don't own them
27+
goBlock := &BlockPointer{ptr: (*C.kernel_BlockPointer)(unsafe.Pointer(block))}
28+
goState := &BlockValidationState{ptr: (*C.kernel_BlockValidationState)(unsafe.Pointer(state))}
29+
callbacks.OnBlockChecked(goBlock, goState)
30+
}
31+
}
32+
33+
// BlockPointer wraps the C kernel_BlockPointer for validation interface callbacks
34+
type BlockPointer struct {
35+
ptr *C.kernel_BlockPointer
36+
}
37+
38+
// GetHash returns the block hash
39+
func (bp *BlockPointer) GetHash() (*BlockHash, error) {
40+
if bp.ptr == nil {
41+
return nil, ErrBlockUninitialized
42+
}
43+
44+
hashPtr := C.kernel_block_pointer_get_hash(bp.ptr)
45+
if hashPtr == nil {
46+
return nil, ErrKernelBlockGetHash
47+
}
48+
49+
return &BlockHash{ptr: hashPtr}, nil
50+
}
51+
52+
// CopyData copies the block data into a byte array
53+
func (bp *BlockPointer) CopyData() ([]byte, error) {
54+
if bp.ptr == nil {
55+
return nil, ErrBlockUninitialized
56+
}
57+
58+
byteArray := C.kernel_copy_block_pointer_data(bp.ptr)
59+
if byteArray == nil {
60+
return nil, ErrKernelCopyBlockData
61+
}
62+
defer C.kernel_byte_array_destroy(byteArray)
63+
64+
size := int(byteArray.size)
65+
if size == 0 {
66+
return nil, nil
67+
}
68+
69+
// Copy the data to Go slice
70+
data := C.GoBytes(unsafe.Pointer(byteArray.data), C.int(size))
71+
return data, nil
72+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package kernel
2+
3+
import (
4+
"encoding/hex"
5+
"testing"
6+
)
7+
8+
func TestValidationInterfaceCallbacks(t *testing.T) {
9+
var blockCheckedCalled bool
10+
var lastValidationMode ValidationMode
11+
var lastBlockData []byte
12+
13+
callbacks := &ValidationInterfaceCallbacks{
14+
OnBlockChecked: func(block *BlockPointer, state *BlockValidationState) {
15+
blockCheckedCalled = true
16+
lastValidationMode = state.ValidationMode()
17+
var err error
18+
lastBlockData, err = block.CopyData()
19+
if err != nil {
20+
t.Fatal(err)
21+
}
22+
},
23+
}
24+
25+
suite := ChainstateManagerTestSuite{
26+
MaxBlockHeightToImport: 2,
27+
ValidationCallbacks: callbacks,
28+
}
29+
suite.Setup(t)
30+
31+
if !blockCheckedCalled {
32+
t.Error("OnBlockChecked callback was not called")
33+
}
34+
35+
if lastValidationMode != ValidationStateValid {
36+
t.Errorf("Expected validation mode %d, got %d", ValidationStateValid, lastValidationMode)
37+
}
38+
39+
lastBlockDataHex := hex.EncodeToString(lastBlockData)
40+
if lastBlockDataHex != "00000020a629da61ccd6c9de14dd22d4dcf06ac4b98828801fb58275af1ed2c89e361b79677daedb5fc7781c5907a88133cd461b4865e9a4881fecfb362304ad1806acf3a7242d66ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025200ffffffff0200f2052a010000001600141409745405c4e8310a875bcd602db6b9b3dc0cf90000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000" {
41+
t.Errorf("Unexpected block data for last block")
42+
}
43+
}

0 commit comments

Comments
 (0)