diff --git a/kernel/chainstate_manager.go b/kernel/chainstate_manager.go index 99be3a0..eeab911 100644 --- a/kernel/chainstate_manager.go +++ b/kernel/chainstate_manager.go @@ -31,12 +31,47 @@ func newChainstateManager(ptr *C.btck_ChainstateManager) *ChainstateManager { // This is the main object for validation tasks, retrieving data from the chain, and // interacting with chainstate and indexes. // +// The chainstate manager associates with the provided kernel context and uses the specified +// data and block directories. If the directories do not exist, they will be created. +// +// Usage: +// +// chainman, err := NewChainstateManager(ctx, dataDir, blocksDir, +// WithWorkerThreads(1), +// WithBlockTreeDBInMemory, +// ) +// // Parameters: -// - options: Configuration options created by NewChainstateManagerOptions +// - context: Kernel context that the chainstate manager will associate with +// - dataDir: Path to the directory containing chainstate data +// - blocksDir: Path to the directory containing block data +// - options: Zero or more ChainstateManagerOption functional options // // Returns an error if the chainstate manager cannot be created. -func NewChainstateManager(options *ChainstateManagerOptions) (*ChainstateManager, error) { - ptr := C.btck_chainstate_manager_create((*C.btck_ChainstateManagerOptions)(options.ptr)) +func NewChainstateManager(context *Context, dataDir, blocksDir string, options ...ChainstateManagerOption) (*ChainstateManager, error) { + cDataDir := C.CString(dataDir) + defer C.free(unsafe.Pointer(cDataDir)) + + cBlocksDir := C.CString(blocksDir) + defer C.free(unsafe.Pointer(cBlocksDir)) + + // Create the options + optsPtr := C.btck_chainstate_manager_options_create((*C.btck_Context)(context.ptr), cDataDir, C.size_t(len(dataDir)), + cBlocksDir, C.size_t(len(blocksDir))) + if optsPtr == nil { + return nil, &InternalError{"Failed to create chainstate manager options"} + } + defer C.btck_chainstate_manager_options_destroy(optsPtr) + + // Apply all functional options + for _, opt := range options { + if err := opt(optsPtr); err != nil { + return nil, err + } + } + + // Create the chainstate manager + ptr := C.btck_chainstate_manager_create(optsPtr) if ptr == nil { return nil, &InternalError{"Failed to create chainstate manager"} } diff --git a/kernel/chainstate_manager_options.go b/kernel/chainstate_manager_options.go index cc451b8..0daf85e 100644 --- a/kernel/chainstate_manager_options.go +++ b/kernel/chainstate_manager_options.go @@ -2,67 +2,24 @@ package kernel /* #include "bitcoinkernel.h" -#include */ import "C" -import ( - "unsafe" -) -type chainstateManagerOptionsCFuncs struct{} +// ChainstateManagerOption is a functional option for configuring chainstate manager. +type ChainstateManagerOption func(*C.btck_ChainstateManagerOptions) error -func (chainstateManagerOptionsCFuncs) destroy(ptr unsafe.Pointer) { - C.btck_chainstate_manager_options_destroy((*C.btck_ChainstateManagerOptions)(ptr)) -} - -// ChainstateManagerOptions holds configuration options for creating a new chainstate manager. -// -// Options are initialized with sensible defaults and can be customized using the -// setter methods before creating the chainstate manager. -type ChainstateManagerOptions struct { - *uniqueHandle -} - -func newChainstateManagerOptions(ptr *C.btck_ChainstateManagerOptions) *ChainstateManagerOptions { - h := newUniqueHandle(unsafe.Pointer(ptr), chainstateManagerOptionsCFuncs{}) - return &ChainstateManagerOptions{uniqueHandle: h} -} - -// NewChainstateManagerOptions creates options for configuring a chainstate manager. -// -// The options associate with the provided kernel context and specify the data and block -// directories. If the directories do not exist, they will be created. -// -// Parameters: -// - context: Kernel context that the chainstate manager will associate with -// - dataDir: Path to the directory containing chainstate data -// - blocksDir: Path to the directory containing block data -// -// Returns an error if the options cannot be created. -func NewChainstateManagerOptions(context *Context, dataDir, blocksDir string) (*ChainstateManagerOptions, error) { - cDataDir := C.CString(dataDir) - defer C.free(unsafe.Pointer(cDataDir)) - - cBlocksDir := C.CString(blocksDir) - defer C.free(unsafe.Pointer(cBlocksDir)) - - ptr := C.btck_chainstate_manager_options_create((*C.btck_Context)(context.ptr), cDataDir, C.size_t(len(dataDir)), - cBlocksDir, C.size_t(len(blocksDir))) - if ptr == nil { - return nil, &InternalError{"Failed to create chainstate manager options"} - } - return newChainstateManagerOptions(ptr), nil -} - -// SetWorkerThreads configures the number of worker threads for parallel validation. +// WithWorkerThreads returns a ChainstateManagerOption that configures the number of worker threads for parallel validation. // // Parameters: // - threads: Number of worker threads (0 disables parallel verification, max is clamped to 15) -func (opts *ChainstateManagerOptions) SetWorkerThreads(threads int) { - C.btck_chainstate_manager_options_set_worker_threads_num((*C.btck_ChainstateManagerOptions)(opts.ptr), C.int(threads)) +func WithWorkerThreads(threads int) ChainstateManagerOption { + return func(opts *C.btck_ChainstateManagerOptions) error { + C.btck_chainstate_manager_options_set_worker_threads_num(opts, C.int(threads)) + return nil + } } -// SetWipeDBs configures which databases to wipe on startup. +// WithWipeDBs returns a ChainstateManagerOption that configures which databases to wipe on startup. // // When combined with ImportBlocks, this triggers a full reindex (if wipeBlockTree is true) // or chainstate-only reindex (if only wipeChainstate is true). @@ -72,42 +29,34 @@ func (opts *ChainstateManagerOptions) SetWorkerThreads(threads int) { // - wipeChainstate: Whether to wipe the chainstate database // // Returns an error if wipeBlockTree is true but wipeChainstate is false. -func (opts *ChainstateManagerOptions) SetWipeDBs(wipeBlockTree, wipeChainstate bool) error { - wipeBlockTreeInt := 0 - if wipeBlockTree { - wipeBlockTreeInt = 1 - } - wipeChainstateInt := 0 - if wipeChainstate { - wipeChainstateInt = 1 +func WithWipeDBs(wipeBlockTree, wipeChainstate bool) ChainstateManagerOption { + return func(opts *C.btck_ChainstateManagerOptions) error { + wipeBlockTreeInt := 0 + if wipeBlockTree { + wipeBlockTreeInt = 1 + } + wipeChainstateInt := 0 + if wipeChainstate { + wipeChainstateInt = 1 + } + result := C.btck_chainstate_manager_options_set_wipe_dbs(opts, C.int(wipeBlockTreeInt), C.int(wipeChainstateInt)) + if result != 0 { + return &InternalError{"Failed to set wipe db"} + } + return nil } - result := C.btck_chainstate_manager_options_set_wipe_dbs((*C.btck_ChainstateManagerOptions)(opts.ptr), C.int(wipeBlockTreeInt), C.int(wipeChainstateInt)) - if result != 0 { - return &InternalError{"Failed to set wipe db"} - } - return nil } -// UpdateBlockTreeDBInMemory configures whether the block tree database is stored in memory. -// -// Parameters: -// - inMemory: If true, the block tree database will be kept entirely in memory -func (opts *ChainstateManagerOptions) UpdateBlockTreeDBInMemory(inMemory bool) { - inMemoryInt := 0 - if inMemory { - inMemoryInt = 1 - } - C.btck_chainstate_manager_options_update_block_tree_db_in_memory((*C.btck_ChainstateManagerOptions)(opts.ptr), C.int(inMemoryInt)) +// WithBlockTreeDBInMemory is a ChainstateManagerOption that configures +// the block tree database to be stored in memory. +var WithBlockTreeDBInMemory ChainstateManagerOption = func(opts *C.btck_ChainstateManagerOptions) error { + C.btck_chainstate_manager_options_update_block_tree_db_in_memory(opts, C.int(1)) + return nil } -// UpdateChainstateDBInMemory configures whether the chainstate database is stored in memory. -// -// Parameters: -// - inMemory: If true, the chainstate database will be kept entirely in memory -func (opts *ChainstateManagerOptions) UpdateChainstateDBInMemory(inMemory bool) { - inMemoryInt := 0 - if inMemory { - inMemoryInt = 1 - } - C.btck_chainstate_manager_options_update_chainstate_db_in_memory((*C.btck_ChainstateManagerOptions)(opts.ptr), C.int(inMemoryInt)) +// WithChainstateDBInMemory is a ChainstateManagerOption that configures +// the chainstate database to be stored in memory. +var WithChainstateDBInMemory ChainstateManagerOption = func(opts *C.btck_ChainstateManagerOptions) error { + C.btck_chainstate_manager_options_update_chainstate_db_in_memory(opts, C.int(1)) + return nil } diff --git a/kernel/chainstate_manager_test.go b/kernel/chainstate_manager_test.go index 6b8c08c..d05e69a 100644 --- a/kernel/chainstate_manager_test.go +++ b/kernel/chainstate_manager_test.go @@ -152,47 +152,29 @@ func (s *ChainstateManagerTestSuite) Setup(t *testing.T) { dataDir := filepath.Join(tempDir, "data") blocksDir := filepath.Join(tempDir, "blocks") - contextOpts := NewContextOptions() - - chainParams, err := NewChainParameters(ChainTypeRegtest) - if err != nil { - t.Fatalf("NewChainParameters() error = %v", err) - } - t.Cleanup(func() { chainParams.Destroy() }) - - contextOpts.SetChainParams(chainParams) + var contextOpts []ContextOption + contextOpts = append(contextOpts, WithChainType(ChainTypeRegtest)) if s.NotificationCallbacks != nil { - contextOpts.SetNotifications(s.NotificationCallbacks) + contextOpts = append(contextOpts, WithNotifications(s.NotificationCallbacks)) } if s.ValidationCallbacks != nil { - contextOpts.SetValidationInterface(s.ValidationCallbacks) + contextOpts = append(contextOpts, WithValidationInterface(s.ValidationCallbacks)) } - ctx, err := NewContext(contextOpts) + ctx, err := NewContext(contextOpts...) if err != nil { t.Fatalf("NewContext() error = %v", err) } t.Cleanup(func() { ctx.Destroy() }) - opts, err := NewChainstateManagerOptions(ctx, dataDir, blocksDir) - if err != nil { - t.Fatalf("NewChainstateManagerOptions() error = %v", err) - } - t.Cleanup(func() { opts.Destroy() }) - - opts.SetWorkerThreads(1) - opts.UpdateBlockTreeDBInMemory(true) - opts.UpdateChainstateDBInMemory(true) - // Wipe both databases to enable proper initialization - err = opts.SetWipeDBs(true, true) - if err != nil { - t.Fatalf("SetWipeDBs() error = %v", err) - } - - // Create chainstate manager - manager, err := NewChainstateManager(opts) + manager, err := NewChainstateManager(ctx, dataDir, blocksDir, + WithWorkerThreads(1), + WithBlockTreeDBInMemory, + WithChainstateDBInMemory, + WithWipeDBs(true, true), + ) if err != nil { t.Fatalf("NewChainstateManager() error = %v", err) } diff --git a/kernel/context.go b/kernel/context.go index 58e888d..3024676 100644 --- a/kernel/context.go +++ b/kernel/context.go @@ -36,13 +36,38 @@ func newContext(ptr *C.btck_Context, fromOwned bool) *Context { // NewContext creates a new kernel context. // +// The context holds chain-specific parameters and callbacks for handling error and +// validation events. If no options are provided, the context assumes mainnet chain +// parameters and no callbacks. +// +// Usage: +// +// ctx, err := NewContext( +// WithChainType(ChainTypeRegtest), +// WithNotifications(notificationCallbacks), +// ) +// // Parameters: -// - options: Context configuration created by NewContextOptions (can be nil for defaults) +// - options: Zero or more ContextOption functional options // -// Returns an error if the context cannot be created. If options is nil or not configured, -// the context assumes mainnet chain parameters and no callbacks. -func NewContext(options *ContextOptions) (*Context, error) { - ptr := C.btck_context_create((*C.btck_ContextOptions)(options.ptr)) +// Returns an error if the context cannot be created. +func NewContext(options ...ContextOption) (*Context, error) { + // Create the options + optsPtr := C.btck_context_options_create() + if optsPtr == nil { + return nil, &InternalError{"Failed to create context options"} + } + defer C.btck_context_options_destroy(optsPtr) + + // Apply all functional options + for _, opt := range options { + if err := opt(optsPtr); err != nil { + return nil, err + } + } + + // Create the context + ptr := C.btck_context_create(optsPtr) if ptr == nil { return nil, &InternalError{"Failed to create context"} } diff --git a/kernel/context_options.go b/kernel/context_options.go index 60b76fb..d70e819 100644 --- a/kernel/context_options.go +++ b/kernel/context_options.go @@ -2,7 +2,6 @@ package kernel /* #include "bitcoinkernel.h" -#include #include // Bridge functions: exported Go functions that C library can call @@ -27,77 +26,66 @@ import ( "unsafe" ) -type contextOptionsCFuncs struct{} +// ContextOption is a functional option for configuring context options. +type ContextOption func(*C.btck_ContextOptions) error -func (contextOptionsCFuncs) destroy(ptr unsafe.Pointer) { - C.btck_context_options_destroy((*C.btck_ContextOptions)(ptr)) -} - -// ContextOptions holds options for creating a new kernel context. -// -// Once a kernel Context has been created from these options, they may be destroyed. -// The options hold the notification callbacks, validation interface callbacks, as well -// as the selected chain type until they are passed to the context. If no options are -// configured, the context will be instantiated with no callbacks and for mainnet. -type ContextOptions struct { - *uniqueHandle -} - -func newContextOptions(ptr *C.btck_ContextOptions) *ContextOptions { - h := newUniqueHandle(unsafe.Pointer(ptr), contextOptionsCFuncs{}) - return &ContextOptions{uniqueHandle: h} -} - -// NewContextOptions creates an empty context options object. -func NewContextOptions() *ContextOptions { - ptr := C.btck_context_options_create() - return newContextOptions(check(ptr)) -} - -// SetChainParams sets the chain params for the context options. The context created -// with the options will be configured for these chain parameters. +// WithChainType returns a ContextOption that sets the chain parameters for the context. +// The context will be configured for these chain parameters. // // Parameters: -// - chainParams: Is set to the context options. -func (opts *ContextOptions) SetChainParams(chainParams *ChainParameters) { - C.btck_context_options_set_chainparams((*C.btck_ContextOptions)(opts.ptr), (*C.btck_ChainParameters)(chainParams.ptr)) +// - chainType: The type of chain (ChainTypeMainnet, ChainTypeTestnet, ChainTypeRegtest, etc.) +func WithChainType(chainType ChainType) ContextOption { + return func(opts *C.btck_ContextOptions) error { + chainParams, err := NewChainParameters(chainType) + if err != nil { + return err + } + defer chainParams.Destroy() + C.btck_context_options_set_chainparams(opts, (*C.btck_ChainParameters)(chainParams.ptr)) + return nil + } } -// SetNotifications sets the kernel notifications for the context options. The context -// created with the options will be configured with these notifications. +// WithNotifications returns a ContextOption that sets the kernel notifications for the context. +// The context will be configured with these notifications. // // Parameters: -// - callbacks: Is set to the context options. -func (opts *ContextOptions) SetNotifications(callbacks *NotificationCallbacks) { - notificationCallbacks := C.btck_NotificationInterfaceCallbacks{ - user_data: unsafe.Pointer(cgo.NewHandle(callbacks)), - user_data_destroy: C.btck_DestroyCallback(C.go_delete_handle), - block_tip: C.btck_NotifyBlockTip(C.go_notify_block_tip_bridge), - header_tip: C.btck_NotifyHeaderTip(C.go_notify_header_tip_bridge), - progress: C.btck_NotifyProgress(C.go_notify_progress_bridge), - warning_set: C.btck_NotifyWarningSet(C.go_notify_warning_set_bridge), - warning_unset: C.btck_NotifyWarningUnset(C.go_notify_warning_unset_bridge), - flush_error: C.btck_NotifyFlushError(C.go_notify_flush_error_bridge), - fatal_error: C.btck_NotifyFatalError(C.go_notify_fatal_error_bridge), +// - callbacks: Notification callbacks to set +func WithNotifications(callbacks *NotificationCallbacks) ContextOption { + return func(opts *C.btck_ContextOptions) error { + notificationCallbacks := C.btck_NotificationInterfaceCallbacks{ + user_data: unsafe.Pointer(cgo.NewHandle(callbacks)), + user_data_destroy: C.btck_DestroyCallback(C.go_delete_handle), + block_tip: C.btck_NotifyBlockTip(C.go_notify_block_tip_bridge), + header_tip: C.btck_NotifyHeaderTip(C.go_notify_header_tip_bridge), + progress: C.btck_NotifyProgress(C.go_notify_progress_bridge), + warning_set: C.btck_NotifyWarningSet(C.go_notify_warning_set_bridge), + warning_unset: C.btck_NotifyWarningUnset(C.go_notify_warning_unset_bridge), + flush_error: C.btck_NotifyFlushError(C.go_notify_flush_error_bridge), + fatal_error: C.btck_NotifyFatalError(C.go_notify_fatal_error_bridge), + } + C.btck_context_options_set_notifications(opts, notificationCallbacks) + return nil } - C.btck_context_options_set_notifications((*C.btck_ContextOptions)(opts.ptr), notificationCallbacks) } -// SetValidationInterface sets the validation interface callbacks for the context options. The -// context created with the options will be configured for these validation -// interface callbacks. The callbacks will then be triggered from validation -// events issued by the chainstate manager created from the same context. +// WithValidationInterface returns a ContextOption that sets the validation interface callbacks. +// The callbacks will be triggered from validation events issued by the chainstate manager +// created from the same context. // // Parameters: -// - callbacks: The callbacks used for passing validation information to the user. -func (opts *ContextOptions) SetValidationInterface(callbacks *ValidationInterfaceCallbacks) { - validationCallbacks := C.btck_ValidationInterfaceCallbacks{ - user_data: unsafe.Pointer(cgo.NewHandle(callbacks)), - user_data_destroy: C.btck_DestroyCallback(C.go_delete_handle), - block_checked: C.btck_ValidationInterfaceBlockChecked(C.go_validation_interface_block_checked_bridge), - pow_valid_block: C.btck_ValidationInterfacePoWValidBlock(C.go_validation_interface_pow_valid_block_bridge), - block_connected: C.btck_ValidationInterfaceBlockConnected(C.go_validation_interface_block_connected_bridge), - block_disconnected: C.btck_ValidationInterfaceBlockDisconnected(C.go_validation_interface_block_disconnected_bridge), +// - callbacks: The callbacks used for passing validation information to the user +func WithValidationInterface(callbacks *ValidationInterfaceCallbacks) ContextOption { + return func(opts *C.btck_ContextOptions) error { + validationCallbacks := C.btck_ValidationInterfaceCallbacks{ + user_data: unsafe.Pointer(cgo.NewHandle(callbacks)), + user_data_destroy: C.btck_DestroyCallback(C.go_delete_handle), + block_checked: C.btck_ValidationInterfaceBlockChecked(C.go_validation_interface_block_checked_bridge), + pow_valid_block: C.btck_ValidationInterfacePoWValidBlock(C.go_validation_interface_pow_valid_block_bridge), + block_connected: C.btck_ValidationInterfaceBlockConnected(C.go_validation_interface_block_connected_bridge), + block_disconnected: C.btck_ValidationInterfaceBlockDisconnected(C.go_validation_interface_block_disconnected_bridge), + } + C.btck_context_options_set_validation_interface(opts, validationCallbacks) + return nil } - C.btck_context_options_set_validation_interface((*C.btck_ContextOptions)(opts.ptr), validationCallbacks) } diff --git a/kernel/context_test.go b/kernel/context_test.go index db9d097..9666921 100644 --- a/kernel/context_test.go +++ b/kernel/context_test.go @@ -7,21 +7,14 @@ import ( func TestNewContext(t *testing.T) { tests := []struct { name string - setupOption func() *ContextOptions + setupOption func() []ContextOption wantErr bool errType error }{ { name: "Valid context options", - setupOption: func() *ContextOptions { - opts := NewContextOptions() - params, err := NewChainParameters(ChainTypeMainnet) - if err != nil { - t.Fatalf("Failed to create chain parameters: %v", err) - } - defer params.Destroy() - opts.SetChainParams(params) - return opts + setupOption: func() []ContextOption { + return []ContextOption{WithChainType(ChainTypeMainnet)} }, wantErr: false, }, @@ -31,7 +24,7 @@ func TestNewContext(t *testing.T) { t.Run(tt.name, func(t *testing.T) { opts := tt.setupOption() - ctx, err := NewContext(opts) + ctx, err := NewContext(opts...) if tt.wantErr { if err == nil {