From 0ad466ad4d251b318c7307544c615eefc9211a8c Mon Sep 17 00:00:00 2001 From: stringintech Date: Sat, 2 May 2026 01:34:44 +0330 Subject: [PATCH 1/4] Refactor kernel Api structs to use pointer getters Have owned API structs read C pointers through their handle instead of caching a separate pointer in the embedded API struct. This keeps API methods and Destroy() touching the same Go memory. That makes unsafe concurrent use visible to the race detector instead of hiding it behind a stale cached pointer. --- kernel/block_hash.go | 27 ++++++++++++++-------- kernel/block_validation_state.go | 25 ++++++++++++++------- kernel/coin.go | 27 ++++++++++++++-------- kernel/script_pubkey.go | 25 ++++++++++++++------- kernel/transaction.go | 35 ++++++++++++++++++----------- kernel/transaction_input.go | 25 ++++++++++++++------- kernel/transaction_out_point.go | 25 ++++++++++++++------- kernel/transaction_output.go | 25 ++++++++++++++------- kernel/transaction_spent_outputs.go | 25 ++++++++++++++------- kernel/txid.go | 25 ++++++++++++++------- 10 files changed, 177 insertions(+), 87 deletions(-) diff --git a/kernel/block_hash.go b/kernel/block_hash.go index feed9d4b..531ab384 100644 --- a/kernel/block_hash.go +++ b/kernel/block_hash.go @@ -27,28 +27,37 @@ type BlockHash struct { func newBlockHash(ptr *C.btck_BlockHash, fromOwned bool) *BlockHash { h := newHandle(unsafe.Pointer(ptr), blockHashCFuncs{}, fromOwned) - return &BlockHash{handle: h, blockHashApi: blockHashApi{(*C.btck_BlockHash)(h.ptr)}} + return &BlockHash{ + handle: h, + blockHashApi: blockHashApi{ + ptr: func() *C.btck_BlockHash { + return (*C.btck_BlockHash)(h.ptr) + }, + }, + } } // BlockHashView is a type-safe identifier for a block. type BlockHashView struct { blockHashApi - ptr *C.btck_BlockHash } func newBlockHashView(ptr *C.btck_BlockHash) *BlockHashView { return &BlockHashView{ - blockHashApi: blockHashApi{ptr}, - ptr: ptr, + blockHashApi: blockHashApi{ + ptr: func() *C.btck_BlockHash { + return ptr + }, + }, } } type blockHashApi struct { - ptr *C.btck_BlockHash + ptr func() *C.btck_BlockHash } func (bh *blockHashApi) blockHashPtr() *C.btck_BlockHash { - return bh.ptr + return bh.ptr() } // BlockHashLike is an interface for types that can provide a block hash pointer. @@ -68,13 +77,13 @@ func NewBlockHash(hashBytes [32]byte) *BlockHash { // Bytes returns the 32-byte representation of the block hash. func (bh *blockHashApi) Bytes() [32]byte { var output [32]C.uchar - C.btck_block_hash_to_bytes(bh.ptr, &output[0]) + C.btck_block_hash_to_bytes(bh.ptr(), &output[0]) return *(*[32]byte)(unsafe.Pointer(&output[0])) } // Copy creates a copy of the block hash. func (bh *blockHashApi) Copy() *BlockHash { - return newBlockHash(bh.ptr, false) + return newBlockHash(bh.ptr(), false) } // Equals checks if two block hashes are equal. @@ -84,7 +93,7 @@ func (bh *blockHashApi) Copy() *BlockHash { // // Returns true if the block hashes are equal. func (bh *blockHashApi) Equals(other BlockHashLike) bool { - return C.btck_block_hash_equals(bh.ptr, other.blockHashPtr()) != 0 + return C.btck_block_hash_equals(bh.ptr(), other.blockHashPtr()) != 0 } // String returns the block hash as a hex string in display order (reversed). diff --git a/kernel/block_validation_state.go b/kernel/block_validation_state.go index 6a55fa4a..05a401b8 100644 --- a/kernel/block_validation_state.go +++ b/kernel/block_validation_state.go @@ -27,7 +27,14 @@ type BlockValidationState struct { func newBlockValidationState(ptr *C.btck_BlockValidationState, fromOwned bool) *BlockValidationState { h := newHandle(unsafe.Pointer(ptr), blockValidationStateCFuncs{}, fromOwned) - return &BlockValidationState{handle: h, blockValidationStateApi: blockValidationStateApi{(*C.btck_BlockValidationState)(h.ptr)}} + return &BlockValidationState{ + handle: h, + blockValidationStateApi: blockValidationStateApi{ + ptr: func() *C.btck_BlockValidationState { + return (*C.btck_BlockValidationState)(h.ptr) + }, + }, + } } // NewBlockValidationState creates a new owned BlockValidationState. @@ -41,23 +48,25 @@ func NewBlockValidationState() *BlockValidationState { // It provides read-only access to validation state without managing the underlying memory. type BlockValidationStateView struct { blockValidationStateApi - ptr *C.btck_BlockValidationState } func newBlockValidationStateView(ptr *C.btck_BlockValidationState) *BlockValidationStateView { return &BlockValidationStateView{ - blockValidationStateApi: blockValidationStateApi{ptr}, - ptr: ptr, + blockValidationStateApi: blockValidationStateApi{ + ptr: func() *C.btck_BlockValidationState { + return ptr + }, + }, } } type blockValidationStateApi struct { - ptr *C.btck_BlockValidationState + ptr func() *C.btck_BlockValidationState } // Copy creates a copy of the block validation state. func (s *blockValidationStateApi) Copy() *BlockValidationState { - return newBlockValidationState(s.ptr, false) + return newBlockValidationState(s.ptr(), false) } // ValidationMode returns whether the block is valid, invalid, or encountered an error. @@ -67,7 +76,7 @@ func (s *blockValidationStateApi) Copy() *BlockValidationState { // - ValidationStateInvalid: Block failed validation // - ValidationStateError: Internal error during validation func (s *blockValidationStateApi) ValidationMode() ValidationMode { - mode := C.btck_block_validation_state_get_validation_mode(s.ptr) + mode := C.btck_block_validation_state_get_validation_mode(s.ptr()) return ValidationMode(mode) } @@ -76,7 +85,7 @@ func (s *blockValidationStateApi) ValidationMode() ValidationMode { // This provides detailed information about the specific validation failure, such as // consensus violations, invalid headers, or missing previous blocks. func (s *blockValidationStateApi) ValidationResult() BlockValidationResult { - result := C.btck_block_validation_state_get_block_validation_result(s.ptr) + result := C.btck_block_validation_state_get_block_validation_result(s.ptr()) return BlockValidationResult(result) } diff --git a/kernel/coin.go b/kernel/coin.go index 7be70eb3..4fc471f2 100644 --- a/kernel/coin.go +++ b/kernel/coin.go @@ -27,30 +27,39 @@ type Coin struct { func newCoin(ptr *C.btck_Coin, fromOwned bool) *Coin { h := newHandle(unsafe.Pointer(ptr), coinCFuncs{}, fromOwned) - return &Coin{handle: h, coinApi: coinApi{(*C.btck_Coin)(h.ptr)}} + return &Coin{ + handle: h, + coinApi: coinApi{ + ptr: func() *C.btck_Coin { + return (*C.btck_Coin)(h.ptr) + }, + }, + } } // CoinView holds information on a transaction output, including the height it was // spent at and whether it is a coinbase output. type CoinView struct { coinApi - ptr *C.btck_Coin } func newCoinView(ptr *C.btck_Coin) *CoinView { return &CoinView{ - coinApi: coinApi{ptr}, - ptr: ptr, + coinApi: coinApi{ + ptr: func() *C.btck_Coin { + return ptr + }, + }, } } type coinApi struct { - ptr *C.btck_Coin + ptr func() *C.btck_Coin } // Copy creates a copy of the coin. func (c *coinApi) Copy() *Coin { - return newCoin(c.ptr, false) + return newCoin(c.ptr(), false) } // GetOutput returns the transaction output contained in this coin. @@ -58,16 +67,16 @@ func (c *coinApi) Copy() *Coin { // The returned TransactionOutputView is a non-owned pointer valid for the // lifetime of this coin. func (c *coinApi) GetOutput() *TransactionOutputView { - ptr := C.btck_coin_get_output(c.ptr) + ptr := C.btck_coin_get_output(c.ptr()) return newTransactionOutputView(check(ptr)) } // ConfirmationHeight returns the block height where the transaction that created this coin was included in. func (c *coinApi) ConfirmationHeight() uint32 { - return uint32(C.btck_coin_confirmation_height(c.ptr)) + return uint32(C.btck_coin_confirmation_height(c.ptr())) } // IsCoinbase returns true if this coin originates from a coinbase transaction. func (c *coinApi) IsCoinbase() bool { - return int(C.btck_coin_is_coinbase(c.ptr)) != 0 + return int(C.btck_coin_is_coinbase(c.ptr())) != 0 } diff --git a/kernel/script_pubkey.go b/kernel/script_pubkey.go index 6c22e87e..ce720a7a 100644 --- a/kernel/script_pubkey.go +++ b/kernel/script_pubkey.go @@ -25,7 +25,14 @@ type ScriptPubkey struct { func newScriptPubkey(ptr *C.btck_ScriptPubkey, fromOwned bool) *ScriptPubkey { h := newHandle(unsafe.Pointer(ptr), scriptPubkeyCFuncs{}, fromOwned) - return &ScriptPubkey{handle: h, scriptPubkeyApi: scriptPubkeyApi{(*C.btck_ScriptPubkey)(h.ptr)}} + return &ScriptPubkey{ + handle: h, + scriptPubkeyApi: scriptPubkeyApi{ + ptr: func() *C.btck_ScriptPubkey { + return (*C.btck_ScriptPubkey)(h.ptr) + }, + }, + } } // NewScriptPubkey creates a new script pubkey from raw serialized script data. @@ -41,23 +48,25 @@ func NewScriptPubkey(rawScriptPubkey []byte) *ScriptPubkey { type ScriptPubkeyView struct { scriptPubkeyApi - ptr *C.btck_ScriptPubkey } func newScriptPubkeyView(ptr *C.btck_ScriptPubkey) *ScriptPubkeyView { return &ScriptPubkeyView{ - scriptPubkeyApi: scriptPubkeyApi{ptr}, - ptr: ptr, + scriptPubkeyApi: scriptPubkeyApi{ + ptr: func() *C.btck_ScriptPubkey { + return ptr + }, + }, } } type scriptPubkeyApi struct { - ptr *C.btck_ScriptPubkey + ptr func() *C.btck_ScriptPubkey } // Copy creates a copy of the script pubkey. func (s *scriptPubkeyApi) Copy() *ScriptPubkey { - return newScriptPubkey(s.ptr, false) + return newScriptPubkey(s.ptr(), false) } // Bytes returns the serialized representation of the script pubkey. @@ -65,7 +74,7 @@ func (s *scriptPubkeyApi) Copy() *ScriptPubkey { // Returns an error if the serialization fails. func (s *scriptPubkeyApi) Bytes() ([]byte, error) { bytes, ok := writeToBytes(func(writer C.btck_WriteBytes, user_data unsafe.Pointer) C.int { - return C.btck_script_pubkey_to_bytes(s.ptr, writer, user_data) + return C.btck_script_pubkey_to_bytes(s.ptr(), writer, user_data) }) if !ok { return nil, &SerializationError{"Failed to serialize script pubkey"} @@ -107,7 +116,7 @@ func (s *scriptPubkeyApi) Verify(amount int64, txTo *Transaction, precomputedTxD var cStatus C.btck_ScriptVerifyStatus result := C.btck_script_pubkey_verify( - s.ptr, + s.ptr(), C.int64_t(amount), (*C.btck_Transaction)(txTo.handle.ptr), cPrecomputedTxData, diff --git a/kernel/transaction.go b/kernel/transaction.go index 4468a295..125d0f63 100644 --- a/kernel/transaction.go +++ b/kernel/transaction.go @@ -26,7 +26,14 @@ type Transaction struct { func newTransaction(ptr *C.btck_Transaction, fromOwned bool) *Transaction { h := newHandle(unsafe.Pointer(ptr), transactionCFuncs{}, fromOwned) - return &Transaction{handle: h, transactionApi: transactionApi{(*C.btck_Transaction)(h.ptr)}} + return &Transaction{ + handle: h, + transactionApi: transactionApi{ + ptr: func() *C.btck_Transaction { + return (*C.btck_Transaction)(h.ptr) + }, + }, + } } // NewTransaction creates a new transaction from raw serialized transaction data. @@ -45,18 +52,20 @@ func NewTransaction(rawTransaction []byte) (*Transaction, error) { type TransactionView struct { transactionApi - ptr *C.btck_Transaction } func newTransactionView(ptr *C.btck_Transaction) *TransactionView { return &TransactionView{ - transactionApi: transactionApi{ptr}, - ptr: ptr, + transactionApi: transactionApi{ + ptr: func() *C.btck_Transaction { + return ptr + }, + }, } } type transactionApi struct { - ptr *C.btck_Transaction + ptr func() *C.btck_Transaction } // Copy creates a shallow copy of the transaction by incrementing its reference count. @@ -64,7 +73,7 @@ type transactionApi struct { // Transactions are reference-counted internally, so this operation is efficient and does // not duplicate the underlying data. func (t *transactionApi) Copy() *Transaction { - return newTransaction(t.ptr, false) + return newTransaction(t.ptr(), false) } // Bytes returns the consensus serialized representation of the transaction. @@ -72,7 +81,7 @@ func (t *transactionApi) Copy() *Transaction { // Returns an error if the serialization fails. func (t *transactionApi) Bytes() ([]byte, error) { bytes, ok := writeToBytes(func(writer C.btck_WriteBytes, userData unsafe.Pointer) C.int { - return C.btck_transaction_to_bytes(t.ptr, writer, userData) + return C.btck_transaction_to_bytes(t.ptr(), writer, userData) }) if !ok { return nil, &SerializationError{"Failed to serialize transaction"} @@ -82,18 +91,18 @@ func (t *transactionApi) Bytes() ([]byte, error) { // GetLockTime returns the transaction's nLockTime value. func (t *transactionApi) GetLockTime() uint32 { - return uint32(C.btck_transaction_get_locktime(t.ptr)) + return uint32(C.btck_transaction_get_locktime(t.ptr())) } // GetTxid returns the txid for this transaction. func (t *transactionApi) GetTxid() *TxidView { - ptr := C.btck_transaction_get_txid(t.ptr) + ptr := C.btck_transaction_get_txid(t.ptr()) return newTxidView(check(ptr)) } // CountInputs returns the number of inputs in the transaction. func (t *transactionApi) CountInputs() uint64 { - return uint64(C.btck_transaction_count_inputs(t.ptr)) + return uint64(C.btck_transaction_count_inputs(t.ptr())) } // GetInput retrieves the input at the specified index. @@ -108,7 +117,7 @@ func (t *transactionApi) GetInput(index uint64) (*TransactionInputView, error) { if index >= t.CountInputs() { return nil, ErrKernelIndexOutOfBounds } - ptr := C.btck_transaction_get_input_at(t.ptr, C.size_t(index)) + ptr := C.btck_transaction_get_input_at(t.ptr(), C.size_t(index)) return newTransactionInputView(check(ptr)), nil } @@ -185,7 +194,7 @@ func (t *transactionApi) iterInputs(from, to uint64, yield func(*TransactionInpu // CountOutputs returns the number of outputs in the transaction. func (t *transactionApi) CountOutputs() uint64 { - return uint64(C.btck_transaction_count_outputs(t.ptr)) + return uint64(C.btck_transaction_count_outputs(t.ptr())) } // GetOutput retrieves the output at the specified index. @@ -200,7 +209,7 @@ func (t *transactionApi) GetOutput(index uint64) (*TransactionOutputView, error) if index >= t.CountOutputs() { return nil, ErrKernelIndexOutOfBounds } - ptr := C.btck_transaction_get_output_at(t.ptr, C.size_t(index)) + ptr := C.btck_transaction_get_output_at(t.ptr(), C.size_t(index)) return newTransactionOutputView(check(ptr)), nil } diff --git a/kernel/transaction_input.go b/kernel/transaction_input.go index fd5849fb..9f826818 100644 --- a/kernel/transaction_input.go +++ b/kernel/transaction_input.go @@ -26,38 +26,47 @@ type TransactionInput struct { func newTransactionInput(ptr *C.btck_TransactionInput, fromOwned bool) *TransactionInput { h := newHandle(unsafe.Pointer(ptr), transactionInputCFuncs{}, fromOwned) - return &TransactionInput{handle: h, transactionInputApi: transactionInputApi{(*C.btck_TransactionInput)(h.ptr)}} + return &TransactionInput{ + handle: h, + transactionInputApi: transactionInputApi{ + ptr: func() *C.btck_TransactionInput { + return (*C.btck_TransactionInput)(h.ptr) + }, + }, + } } // TransactionInputView holds information on the TransactionOutPoint held within. type TransactionInputView struct { transactionInputApi - ptr *C.btck_TransactionInput } func newTransactionInputView(ptr *C.btck_TransactionInput) *TransactionInputView { return &TransactionInputView{ - transactionInputApi: transactionInputApi{ptr}, - ptr: ptr, + transactionInputApi: transactionInputApi{ + ptr: func() *C.btck_TransactionInput { + return ptr + }, + }, } } type transactionInputApi struct { - ptr *C.btck_TransactionInput + ptr func() *C.btck_TransactionInput } // Copy creates a copy of the transaction input. func (t *transactionInputApi) Copy() *TransactionInput { - return newTransactionInput(t.ptr, false) + return newTransactionInput(t.ptr(), false) } // GetOutPoint returns the transaction out point. func (t *transactionInputApi) GetOutPoint() *TransactionOutPointView { - ptr := C.btck_transaction_input_get_out_point(t.ptr) + ptr := C.btck_transaction_input_get_out_point(t.ptr()) return newTransactionOutPointView(check(ptr)) } // GetSequence returns the transaction input's nSequence value. func (t *transactionInputApi) GetSequence() uint32 { - return uint32(C.btck_transaction_input_get_sequence(t.ptr)) + return uint32(C.btck_transaction_input_get_sequence(t.ptr())) } diff --git a/kernel/transaction_out_point.go b/kernel/transaction_out_point.go index 9728f4f9..305f315a 100644 --- a/kernel/transaction_out_point.go +++ b/kernel/transaction_out_point.go @@ -26,38 +26,47 @@ type TransactionOutPoint struct { func newTransactionOutPoint(ptr *C.btck_TransactionOutPoint, fromOwned bool) *TransactionOutPoint { h := newHandle(unsafe.Pointer(ptr), transactionOutPointCFuncs{}, fromOwned) - return &TransactionOutPoint{handle: h, transactionOutPointApi: transactionOutPointApi{(*C.btck_TransactionOutPoint)(h.ptr)}} + return &TransactionOutPoint{ + handle: h, + transactionOutPointApi: transactionOutPointApi{ + ptr: func() *C.btck_TransactionOutPoint { + return (*C.btck_TransactionOutPoint)(h.ptr) + }, + }, + } } // TransactionOutPointView holds the txid and output index it is pointing to. type TransactionOutPointView struct { transactionOutPointApi - ptr *C.btck_TransactionOutPoint } func newTransactionOutPointView(ptr *C.btck_TransactionOutPoint) *TransactionOutPointView { return &TransactionOutPointView{ - transactionOutPointApi: transactionOutPointApi{ptr}, - ptr: ptr, + transactionOutPointApi: transactionOutPointApi{ + ptr: func() *C.btck_TransactionOutPoint { + return ptr + }, + }, } } type transactionOutPointApi struct { - ptr *C.btck_TransactionOutPoint + ptr func() *C.btck_TransactionOutPoint } // Copy creates a copy of the transaction out point. func (t *transactionOutPointApi) Copy() *TransactionOutPoint { - return newTransactionOutPoint(t.ptr, false) + return newTransactionOutPoint(t.ptr(), false) } // GetIndex returns the output position from the transaction out point. func (t *transactionOutPointApi) GetIndex() uint32 { - return uint32(C.btck_transaction_out_point_get_index(t.ptr)) + return uint32(C.btck_transaction_out_point_get_index(t.ptr())) } // GetTxid returns the txid from the out point. func (t *transactionOutPointApi) GetTxid() *TxidView { - ptr := C.btck_transaction_out_point_get_txid(t.ptr) + ptr := C.btck_transaction_out_point_get_txid(t.ptr()) return newTxidView(check(ptr)) } diff --git a/kernel/transaction_output.go b/kernel/transaction_output.go index 6ddbc220..ea336540 100644 --- a/kernel/transaction_output.go +++ b/kernel/transaction_output.go @@ -25,7 +25,14 @@ type TransactionOutput struct { func newTransactionOutput(ptr *C.btck_TransactionOutput, fromOwned bool) *TransactionOutput { h := newHandle(unsafe.Pointer(ptr), transactionOutputCFuncs{}, fromOwned) - return &TransactionOutput{handle: h, transactionOutputApi: transactionOutputApi{(*C.btck_TransactionOutput)(h.ptr)}} + return &TransactionOutput{ + handle: h, + transactionOutputApi: transactionOutputApi{ + ptr: func() *C.btck_TransactionOutput { + return (*C.btck_TransactionOutput)(h.ptr) + }, + }, + } } // NewTransactionOutput creates a transaction output from a script pubkey and an amount. @@ -40,23 +47,25 @@ func NewTransactionOutput(scriptPubkey *ScriptPubkey, amount int64) *Transaction type TransactionOutputView struct { transactionOutputApi - ptr *C.btck_TransactionOutput } func newTransactionOutputView(ptr *C.btck_TransactionOutput) *TransactionOutputView { return &TransactionOutputView{ - transactionOutputApi: transactionOutputApi{ptr}, - ptr: ptr, + transactionOutputApi: transactionOutputApi{ + ptr: func() *C.btck_TransactionOutput { + return ptr + }, + }, } } type transactionOutputApi struct { - ptr *C.btck_TransactionOutput + ptr func() *C.btck_TransactionOutput } // Copy creates a copy of the transaction output. func (t *transactionOutputApi) Copy() *TransactionOutput { - return newTransactionOutput(t.ptr, false) + return newTransactionOutput(t.ptr(), false) } // ScriptPubkey returns the script pubkey of this output. @@ -64,11 +73,11 @@ func (t *transactionOutputApi) Copy() *TransactionOutput { // The returned ScriptPubkeyView is a non-owned pointer valid for the lifetime of // this transaction output. func (t *transactionOutputApi) ScriptPubkey() *ScriptPubkeyView { - ptr := C.btck_transaction_output_get_script_pubkey(t.ptr) + ptr := C.btck_transaction_output_get_script_pubkey(t.ptr()) return newScriptPubkeyView(check(ptr)) } // Amount returns the amount in the output func (t *transactionOutputApi) Amount() int64 { - return int64(C.btck_transaction_output_get_amount(t.ptr)) + return int64(C.btck_transaction_output_get_amount(t.ptr())) } diff --git a/kernel/transaction_spent_outputs.go b/kernel/transaction_spent_outputs.go index e14eec83..0cd46225 100644 --- a/kernel/transaction_spent_outputs.go +++ b/kernel/transaction_spent_outputs.go @@ -30,33 +30,42 @@ type TransactionSpentOutputs struct { func newTransactionSpentOutputs(ptr *C.btck_TransactionSpentOutputs, fromOwned bool) *TransactionSpentOutputs { h := newHandle(unsafe.Pointer(ptr), transactionSpentOutputsCFuncs{}, fromOwned) - return &TransactionSpentOutputs{handle: h, transactionSpentOutputsApi: transactionSpentOutputsApi{(*C.btck_TransactionSpentOutputs)(h.ptr)}} + return &TransactionSpentOutputs{ + handle: h, + transactionSpentOutputsApi: transactionSpentOutputsApi{ + ptr: func() *C.btck_TransactionSpentOutputs { + return (*C.btck_TransactionSpentOutputs)(h.ptr) + }, + }, + } } type TransactionSpentOutputsView struct { transactionSpentOutputsApi - ptr *C.btck_TransactionSpentOutputs } func newTransactionSpentOutputsView(ptr *C.btck_TransactionSpentOutputs) *TransactionSpentOutputsView { return &TransactionSpentOutputsView{ - transactionSpentOutputsApi: transactionSpentOutputsApi{ptr}, - ptr: ptr, + transactionSpentOutputsApi: transactionSpentOutputsApi{ + ptr: func() *C.btck_TransactionSpentOutputs { + return ptr + }, + }, } } type transactionSpentOutputsApi struct { - ptr *C.btck_TransactionSpentOutputs + ptr func() *C.btck_TransactionSpentOutputs } // Copy creates a copy of the transaction spent outputs. func (t *transactionSpentOutputsApi) Copy() *TransactionSpentOutputs { - return newTransactionSpentOutputs(t.ptr, false) + return newTransactionSpentOutputs(t.ptr(), false) } // Count returns the number of previous transaction outputs contained in the transaction spent outputs data. func (t *transactionSpentOutputsApi) Count() uint64 { - return uint64(C.btck_transaction_spent_outputs_count(t.ptr)) + return uint64(C.btck_transaction_spent_outputs_count(t.ptr())) } // GetCoinAt returns a coin contained in the transaction spent outputs at a @@ -71,7 +80,7 @@ func (t *transactionSpentOutputsApi) GetCoinAt(index uint64) (*CoinView, error) if index >= t.Count() { return nil, ErrKernelIndexOutOfBounds } - ptr := C.btck_transaction_spent_outputs_get_coin_at(t.ptr, C.size_t(index)) + ptr := C.btck_transaction_spent_outputs_get_coin_at(t.ptr(), C.size_t(index)) return newCoinView(check(ptr)), nil } diff --git a/kernel/txid.go b/kernel/txid.go index 16570d77..48a38489 100644 --- a/kernel/txid.go +++ b/kernel/txid.go @@ -26,39 +26,48 @@ type Txid struct { func newTxid(ptr *C.btck_Txid, fromOwned bool) *Txid { h := newHandle(unsafe.Pointer(ptr), txidCFuncs{}, fromOwned) - return &Txid{handle: h, txidApi: txidApi{(*C.btck_Txid)(h.ptr)}} + return &Txid{ + handle: h, + txidApi: txidApi{ + ptr: func() *C.btck_Txid { + return (*C.btck_Txid)(h.ptr) + }, + }, + } } type TxidView struct { txidApi - ptr *C.btck_Txid } func newTxidView(ptr *C.btck_Txid) *TxidView { return &TxidView{ - txidApi: txidApi{ptr}, - ptr: ptr, + txidApi: txidApi{ + ptr: func() *C.btck_Txid { + return ptr + }, + }, } } type txidApi struct { - ptr *C.btck_Txid + ptr func() *C.btck_Txid } // Copy creates a copy of the txid. func (t *txidApi) Copy() *Txid { - return newTxid(t.ptr, false) + return newTxid(t.ptr(), false) } // Equals checks if two txids are equal. func (t *txidApi) Equals(other *Txid) bool { - return C.btck_txid_equals(t.ptr, other.txidApi.ptr) != 0 + return C.btck_txid_equals(t.ptr(), other.txidApi.ptr()) != 0 } // Bytes returns the 32-byte representation of the txid. func (t *txidApi) Bytes() [32]byte { var output [32]C.uchar - C.btck_txid_to_bytes(t.ptr, &output[0]) + C.btck_txid_to_bytes(t.ptr(), &output[0]) return *(*[32]byte)(unsafe.Pointer(&output[0])) } From 33fa29d53db9a66c49df758fb74369ee36183704 Mon Sep 17 00:00:00 2001 From: stringintech Date: Sat, 2 May 2026 14:43:22 +0330 Subject: [PATCH 2/4] Add race test for owned API pointer access Add a race-only regression test that runs Copy() concurrently with Destroy() on the same Transaction. The test proves that owned API methods read through handle.ptr. With a cached C pointer in transactionApi, the helper would not report a race because Copy() would no longer read the same Go field that Destroy() writes. --- Makefile | 3 +- kernel/transaction_race_test.go | 109 ++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 kernel/transaction_race_test.go diff --git a/Makefile b/Makefile index 7cdf9dd1..c4fa05a3 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,7 @@ build: test: go test -v ./... + go test -race ./kernel -run TestTransactionHandlePtrRaceDetector clean: rm -rf depend/bitcoin/build @@ -58,4 +59,4 @@ help: @echo " lint - Lint Go code" @echo " deps - Install development dependencies" @echo " update-kernel - Update Bitcoin dependency using git subtree" - @echo " help - Show this help message" \ No newline at end of file + @echo " help - Show this help message" diff --git a/kernel/transaction_race_test.go b/kernel/transaction_race_test.go new file mode 100644 index 00000000..4c4527d9 --- /dev/null +++ b/kernel/transaction_race_test.go @@ -0,0 +1,109 @@ +//go:build race + +// This race-only test shows why owned API structs must not cache their own C +// pointer. When transactionApi.Copy calls the pointer getter, it reads +// handle.ptr, so the race detector can catch Copy running concurrently with +// Destroy, which writes to handle.ptr. If transactionApi cached the C pointer +// instead, Copy would read that cached field and the race detector would not see +// the unsafe shared access to handle.ptr. +package kernel + +import ( + "bytes" + "encoding/hex" + "os" + "os/exec" + "regexp" + "runtime" + "strings" + "testing" +) + +func TestTransactionHandlePtrRaceDetector(t *testing.T) { + if os.Getenv("KERNEL_RACE_HELPER") == "1" { + runTransactionHandlePtrRaceHelper(t) + return + } + + // Run the helper in a subprocess because it intentionally triggers a race. + // GORACE makes the helper exit successfully after printing the first report, + // so this outer test can assert that the expected report was produced. + cmd := exec.Command(os.Args[0], "-test.run=TestTransactionHandlePtrRaceDetector", "-test.v") + for _, env := range os.Environ() { + if strings.HasPrefix(env, "GORACE=") { + continue + } + if strings.HasPrefix(env, "GOMAXPROCS=") { + continue + } + cmd.Env = append(cmd.Env, env) + } + cmd.Env = append(cmd.Env, + "KERNEL_RACE_HELPER=1", + "GOMAXPROCS=1", + "GORACE=halt_on_error=1 exitcode=0", + ) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("race helper exited unexpectedly: %v\noutput:\n%s", err, output) + } + report := string(output) + if !bytes.Contains(output, []byte("WARNING: DATA RACE")) { + t.Fatalf("expected race detector output, got:\n%s", output) + } + if !regexp.MustCompile(`(?s)[Rr]ead at .*?(\(\*transactionApi\)\.Copy\(\))`).MatchString(report) { + t.Fatalf("expected race output to mention transactionApi.Copy, got:\n%s", output) + } + if !regexp.MustCompile(`(?s)[Ww]rite at .*?(\(\*handle\)\.Destroy\(\))`).MatchString(report) { + t.Fatalf("expected race output to mention handle.Destroy, got:\n%s", output) + } +} + +func runTransactionHandlePtrRaceHelper(t *testing.T) { + rawTransaction, err := hex.DecodeString(coinbaseTxHex) + if err != nil { + t.Fatalf("DecodeString() error = %v", err) + } + + tx, err := NewTransaction(rawTransaction) + if err != nil { + t.Fatalf("NewTransaction() error = %v", err) + } + + // Race repeated public API reads against destroying the same owned value. + // Copy() is enough to prove the point because it reads handle.ptr before + // creating another owned Transaction. + ready := make(chan struct{}) + done := make(chan struct{}, 2) + + go func() { + // Warm up the copy loop so it is already active before Destroy(). + for i := 0; i < 50; i++ { + _ = tx.Copy() + runtime.Gosched() + } + close(ready) + // Keep copying after Destroy() starts to widen the race window. + for i := 0; i < 1000; i++ { + func() { + defer func() { + _ = recover() + }() + _ = tx.Copy() + }() + runtime.Gosched() + } + done <- struct{}{} + }() + + go func() { + <-ready + // Give the copy loop one more scheduling opportunity, then destroy. + runtime.Gosched() + tx.Destroy() + done <- struct{}{} + }() + + <-done + <-done +} From 25bdf2761aea62390e4cd65edbaa3701d0cde73a Mon Sep 17 00:00:00 2001 From: stringintech Date: Sat, 2 May 2026 15:33:19 +0330 Subject: [PATCH 3/4] Add sealed interfaces Add sealed interfaces for kernel types that have both owned and view forms. Each interface includes the shared API method set and compile-time assertions for both implementations, so future interface drift fails at build time. --- kernel/block_hash.go | 15 +++++++++++---- kernel/block_validation_state.go | 15 +++++++++++++++ kernel/chainstate_manager.go | 2 +- kernel/coin.go | 16 ++++++++++++++++ kernel/precomputed_transaction_data.go | 6 +++--- kernel/script_pubkey.go | 19 +++++++++++++++++-- kernel/script_pubkey_test.go | 16 +++++++++------- kernel/transaction.go | 26 ++++++++++++++++++++++++++ kernel/transaction_input.go | 15 +++++++++++++++ kernel/transaction_out_point.go | 15 +++++++++++++++ kernel/transaction_output.go | 19 +++++++++++++++++-- kernel/transaction_spent_outputs.go | 18 ++++++++++++++++++ kernel/txid.go | 20 ++++++++++++++++++-- kernel/txid_test.go | 3 +++ 14 files changed, 184 insertions(+), 21 deletions(-) diff --git a/kernel/block_hash.go b/kernel/block_hash.go index 531ab384..69cd46fa 100644 --- a/kernel/block_hash.go +++ b/kernel/block_hash.go @@ -56,15 +56,22 @@ type blockHashApi struct { ptr func() *C.btck_BlockHash } -func (bh *blockHashApi) blockHashPtr() *C.btck_BlockHash { +func (bh *blockHashApi) cPtr() *C.btck_BlockHash { return bh.ptr() } -// BlockHashLike is an interface for types that can provide a block hash pointer. +// BlockHashLike is implemented by *BlockHash and *BlockHashView. type BlockHashLike interface { - blockHashPtr() *C.btck_BlockHash + cPtr() *C.btck_BlockHash + Bytes() [32]byte + Copy() *BlockHash + Equals(BlockHashLike) bool + String() string } +var _ BlockHashLike = (*BlockHash)(nil) +var _ BlockHashLike = (*BlockHashView)(nil) + // NewBlockHash creates a new BlockHash from a 32-byte hash value. // // Parameters: @@ -93,7 +100,7 @@ func (bh *blockHashApi) Copy() *BlockHash { // // Returns true if the block hashes are equal. func (bh *blockHashApi) Equals(other BlockHashLike) bool { - return C.btck_block_hash_equals(bh.ptr(), other.blockHashPtr()) != 0 + return C.btck_block_hash_equals(bh.ptr(), other.cPtr()) != 0 } // String returns the block hash as a hex string in display order (reversed). diff --git a/kernel/block_validation_state.go b/kernel/block_validation_state.go index 05a401b8..6e5be662 100644 --- a/kernel/block_validation_state.go +++ b/kernel/block_validation_state.go @@ -64,6 +64,21 @@ type blockValidationStateApi struct { ptr func() *C.btck_BlockValidationState } +func (s *blockValidationStateApi) cPtr() *C.btck_BlockValidationState { + return s.ptr() +} + +// BlockValidationStateLike is implemented by *BlockValidationState and *BlockValidationStateView. +type BlockValidationStateLike interface { + cPtr() *C.btck_BlockValidationState + Copy() *BlockValidationState + ValidationMode() ValidationMode + ValidationResult() BlockValidationResult +} + +var _ BlockValidationStateLike = (*BlockValidationState)(nil) +var _ BlockValidationStateLike = (*BlockValidationStateView)(nil) + // Copy creates a copy of the block validation state. func (s *blockValidationStateApi) Copy() *BlockValidationState { return newBlockValidationState(s.ptr(), false) diff --git a/kernel/chainstate_manager.go b/kernel/chainstate_manager.go index 87065b00..889ec478 100644 --- a/kernel/chainstate_manager.go +++ b/kernel/chainstate_manager.go @@ -185,7 +185,7 @@ func (cm *ChainstateManager) GetActiveChain() *Chain { // BlockTreeEntry is a non-owned pointer valid for the lifetime of this chainstate // manager. func (cm *ChainstateManager) GetBlockTreeEntryByHash(blockHash BlockHashLike) *BlockTreeEntry { - ptr := C.btck_chainstate_manager_get_block_tree_entry_by_hash((*C.btck_ChainstateManager)(cm.ptr), blockHash.blockHashPtr()) + ptr := C.btck_chainstate_manager_get_block_tree_entry_by_hash((*C.btck_ChainstateManager)(cm.ptr), blockHash.cPtr()) if ptr == nil { return nil } diff --git a/kernel/coin.go b/kernel/coin.go index 4fc471f2..1bbc2500 100644 --- a/kernel/coin.go +++ b/kernel/coin.go @@ -57,6 +57,22 @@ type coinApi struct { ptr func() *C.btck_Coin } +func (c *coinApi) cPtr() *C.btck_Coin { + return c.ptr() +} + +// CoinLike is implemented by *Coin and *CoinView. +type CoinLike interface { + cPtr() *C.btck_Coin + Copy() *Coin + GetOutput() *TransactionOutputView + ConfirmationHeight() uint32 + IsCoinbase() bool +} + +var _ CoinLike = (*Coin)(nil) +var _ CoinLike = (*CoinView)(nil) + // Copy creates a copy of the coin. func (c *coinApi) Copy() *Coin { return newCoin(c.ptr(), false) diff --git a/kernel/precomputed_transaction_data.go b/kernel/precomputed_transaction_data.go index ff6fb10b..fcbad5ea 100644 --- a/kernel/precomputed_transaction_data.go +++ b/kernel/precomputed_transaction_data.go @@ -42,18 +42,18 @@ func newPrecomputedTransactionData(ptr *C.btck_PrecomputedTransactionData, fromO // - spentOutputs: Outputs spent by the transaction. May be nil for non-taproot verification. // // Returns an error if the precomputation fails. -func NewPrecomputedTransactionData(txTo *Transaction, spentOutputs []*TransactionOutput) (*PrecomputedTransactionData, error) { +func NewPrecomputedTransactionData(txTo TransactionLike, spentOutputs []TransactionOutputLike) (*PrecomputedTransactionData, error) { var cSpentOutputsPtr **C.btck_TransactionOutput if len(spentOutputs) > 0 { cSpentOutputs := make([]*C.btck_TransactionOutput, len(spentOutputs)) for i, output := range spentOutputs { - cSpentOutputs[i] = (*C.btck_TransactionOutput)(output.handle.ptr) + cSpentOutputs[i] = output.cPtr() } cSpentOutputsPtr = (**C.btck_TransactionOutput)(unsafe.Pointer(&cSpentOutputs[0])) } ptr := C.btck_precomputed_transaction_data_create( - (*C.btck_Transaction)(txTo.handle.ptr), + txTo.cPtr(), cSpentOutputsPtr, C.size_t(len(spentOutputs)), ) diff --git a/kernel/script_pubkey.go b/kernel/script_pubkey.go index ce720a7a..0aae82fb 100644 --- a/kernel/script_pubkey.go +++ b/kernel/script_pubkey.go @@ -64,6 +64,21 @@ type scriptPubkeyApi struct { ptr func() *C.btck_ScriptPubkey } +func (s *scriptPubkeyApi) cPtr() *C.btck_ScriptPubkey { + return s.ptr() +} + +// ScriptPubkeyLike is implemented by *ScriptPubkey and *ScriptPubkeyView. +type ScriptPubkeyLike interface { + cPtr() *C.btck_ScriptPubkey + Copy() *ScriptPubkey + Bytes() ([]byte, error) + Verify(int64, TransactionLike, *PrecomputedTransactionData, uint, ScriptFlags) (bool, error) +} + +var _ ScriptPubkeyLike = (*ScriptPubkey)(nil) +var _ ScriptPubkeyLike = (*ScriptPubkeyView)(nil) + // Copy creates a copy of the script pubkey. func (s *scriptPubkeyApi) Copy() *ScriptPubkey { return newScriptPubkey(s.ptr(), false) @@ -98,7 +113,7 @@ func (s *scriptPubkeyApi) Bytes() ([]byte, error) { // - bool: true if the script is valid, false if invalid (only meaningful when error is nil) // - error: non-nil if verification could not be performed due to malformed input; // nil if verification completed successfully (check bool for validity result) -func (s *scriptPubkeyApi) Verify(amount int64, txTo *Transaction, precomputedTxData *PrecomputedTransactionData, inputIndex uint, flags ScriptFlags) (bool, error) { +func (s *scriptPubkeyApi) Verify(amount int64, txTo TransactionLike, precomputedTxData *PrecomputedTransactionData, inputIndex uint, flags ScriptFlags) (bool, error) { inputCount := txTo.CountInputs() if inputIndex >= uint(inputCount) { return false, ErrVerifyScriptVerifyTxInputIndex @@ -118,7 +133,7 @@ func (s *scriptPubkeyApi) Verify(amount int64, txTo *Transaction, precomputedTxD result := C.btck_script_pubkey_verify( s.ptr(), C.int64_t(amount), - (*C.btck_Transaction)(txTo.handle.ptr), + txTo.cPtr(), cPrecomputedTxData, C.uint(inputIndex), C.btck_ScriptVerificationFlags(flags), diff --git a/kernel/script_pubkey_test.go b/kernel/script_pubkey_test.go index 95a9e511..d5903241 100644 --- a/kernel/script_pubkey_test.go +++ b/kernel/script_pubkey_test.go @@ -207,7 +207,7 @@ func TestValidTaprootMultiInput(t *testing.T) { {scriptHex: "5120ab78e077d062e7b8acd7063668b4db5355a1b5d5fd2a46a8e98e62e5e63fab77", amount: 135125}, } - outputs := make([]*TransactionOutput, len(spentOutputs)) + outputs := make([]TransactionOutputLike, len(spentOutputs)) for i, spent := range spentOutputs { spentScriptBytes, err := hex.DecodeString(spent.scriptHex) if err != nil { @@ -216,8 +216,9 @@ func TestValidTaprootMultiInput(t *testing.T) { spentScript := NewScriptPubkey(spentScriptBytes) defer spentScript.Destroy() - outputs[i] = NewTransactionOutput(spentScript, spent.amount) - defer outputs[i].Destroy() + output := NewTransactionOutput(spentScript, spent.amount) + defer output.Destroy() + outputs[i] = output } precomputed, err := NewPrecomputedTransactionData(txTo, outputs) @@ -456,9 +457,9 @@ func testVerifyScript( defer txTo.Destroy() // Create spent outputs if provided - var outputs []*TransactionOutput + var outputs []TransactionOutputLike if len(spentOutputs) > 0 { - outputs = make([]*TransactionOutput, len(spentOutputs)) + outputs = make([]TransactionOutputLike, len(spentOutputs)) for i, spent := range spentOutputs { spentScriptBytes, err := hex.DecodeString(spent.scriptHex) if err != nil { @@ -467,8 +468,9 @@ func testVerifyScript( spentScript := NewScriptPubkey(spentScriptBytes) defer spentScript.Destroy() - outputs[i] = NewTransactionOutput(spentScript, spent.amount) - defer outputs[i].Destroy() + output := NewTransactionOutput(spentScript, spent.amount) + defer output.Destroy() + outputs[i] = output } } diff --git a/kernel/transaction.go b/kernel/transaction.go index 125d0f63..dba72608 100644 --- a/kernel/transaction.go +++ b/kernel/transaction.go @@ -68,6 +68,32 @@ type transactionApi struct { ptr func() *C.btck_Transaction } +func (t *transactionApi) cPtr() *C.btck_Transaction { + return t.ptr() +} + +// TransactionLike is implemented by *Transaction and *TransactionView. +type TransactionLike interface { + cPtr() *C.btck_Transaction + Copy() *Transaction + Bytes() ([]byte, error) + GetLockTime() uint32 + GetTxid() *TxidView + CountInputs() uint64 + GetInput(uint64) (*TransactionInputView, error) + Inputs() iter.Seq[*TransactionInputView] + InputsRange(uint64, uint64) iter.Seq[*TransactionInputView] + InputsFrom(uint64) iter.Seq[*TransactionInputView] + CountOutputs() uint64 + GetOutput(uint64) (*TransactionOutputView, error) + Outputs() iter.Seq[*TransactionOutputView] + OutputsRange(uint64, uint64) iter.Seq[*TransactionOutputView] + OutputsFrom(uint64) iter.Seq[*TransactionOutputView] +} + +var _ TransactionLike = (*Transaction)(nil) +var _ TransactionLike = (*TransactionView)(nil) + // Copy creates a shallow copy of the transaction by incrementing its reference count. // // Transactions are reference-counted internally, so this operation is efficient and does diff --git a/kernel/transaction_input.go b/kernel/transaction_input.go index 9f826818..55d3d3d3 100644 --- a/kernel/transaction_input.go +++ b/kernel/transaction_input.go @@ -55,6 +55,21 @@ type transactionInputApi struct { ptr func() *C.btck_TransactionInput } +func (t *transactionInputApi) cPtr() *C.btck_TransactionInput { + return t.ptr() +} + +// TransactionInputLike is implemented by *TransactionInput and *TransactionInputView. +type TransactionInputLike interface { + cPtr() *C.btck_TransactionInput + Copy() *TransactionInput + GetOutPoint() *TransactionOutPointView + GetSequence() uint32 +} + +var _ TransactionInputLike = (*TransactionInput)(nil) +var _ TransactionInputLike = (*TransactionInputView)(nil) + // Copy creates a copy of the transaction input. func (t *transactionInputApi) Copy() *TransactionInput { return newTransactionInput(t.ptr(), false) diff --git a/kernel/transaction_out_point.go b/kernel/transaction_out_point.go index 305f315a..5b5af7a5 100644 --- a/kernel/transaction_out_point.go +++ b/kernel/transaction_out_point.go @@ -55,6 +55,21 @@ type transactionOutPointApi struct { ptr func() *C.btck_TransactionOutPoint } +func (t *transactionOutPointApi) cPtr() *C.btck_TransactionOutPoint { + return t.ptr() +} + +// TransactionOutPointLike is implemented by *TransactionOutPoint and *TransactionOutPointView. +type TransactionOutPointLike interface { + cPtr() *C.btck_TransactionOutPoint + Copy() *TransactionOutPoint + GetIndex() uint32 + GetTxid() *TxidView +} + +var _ TransactionOutPointLike = (*TransactionOutPoint)(nil) +var _ TransactionOutPointLike = (*TransactionOutPointView)(nil) + // Copy creates a copy of the transaction out point. func (t *transactionOutPointApi) Copy() *TransactionOutPoint { return newTransactionOutPoint(t.ptr(), false) diff --git a/kernel/transaction_output.go b/kernel/transaction_output.go index ea336540..a369b8a2 100644 --- a/kernel/transaction_output.go +++ b/kernel/transaction_output.go @@ -40,8 +40,8 @@ func newTransactionOutput(ptr *C.btck_TransactionOutput, fromOwned bool) *Transa // Parameters: // - scriptPubkey: ScriptPubkey defining the conditions to spend this output // - amount: The amount associated with the script pubkey for this output -func NewTransactionOutput(scriptPubkey *ScriptPubkey, amount int64) *TransactionOutput { - ptr := C.btck_transaction_output_create((*C.btck_ScriptPubkey)(scriptPubkey.handle.ptr), C.int64_t(amount)) +func NewTransactionOutput(scriptPubkey ScriptPubkeyLike, amount int64) *TransactionOutput { + ptr := C.btck_transaction_output_create(scriptPubkey.cPtr(), C.int64_t(amount)) return newTransactionOutput(check(ptr), true) } @@ -63,6 +63,21 @@ type transactionOutputApi struct { ptr func() *C.btck_TransactionOutput } +func (t *transactionOutputApi) cPtr() *C.btck_TransactionOutput { + return t.ptr() +} + +// TransactionOutputLike is implemented by *TransactionOutput and *TransactionOutputView. +type TransactionOutputLike interface { + cPtr() *C.btck_TransactionOutput + Copy() *TransactionOutput + ScriptPubkey() *ScriptPubkeyView + Amount() int64 +} + +var _ TransactionOutputLike = (*TransactionOutput)(nil) +var _ TransactionOutputLike = (*TransactionOutputView)(nil) + // Copy creates a copy of the transaction output. func (t *transactionOutputApi) Copy() *TransactionOutput { return newTransactionOutput(t.ptr(), false) diff --git a/kernel/transaction_spent_outputs.go b/kernel/transaction_spent_outputs.go index 0cd46225..d86cf6bf 100644 --- a/kernel/transaction_spent_outputs.go +++ b/kernel/transaction_spent_outputs.go @@ -58,6 +58,24 @@ type transactionSpentOutputsApi struct { ptr func() *C.btck_TransactionSpentOutputs } +func (t *transactionSpentOutputsApi) cPtr() *C.btck_TransactionSpentOutputs { + return t.ptr() +} + +// TransactionSpentOutputsLike is implemented by *TransactionSpentOutputs and *TransactionSpentOutputsView. +type TransactionSpentOutputsLike interface { + cPtr() *C.btck_TransactionSpentOutputs + Copy() *TransactionSpentOutputs + Count() uint64 + GetCoinAt(uint64) (*CoinView, error) + Coins() iter.Seq[*CoinView] + CoinsRange(uint64, uint64) iter.Seq[*CoinView] + CoinsFrom(uint64) iter.Seq[*CoinView] +} + +var _ TransactionSpentOutputsLike = (*TransactionSpentOutputs)(nil) +var _ TransactionSpentOutputsLike = (*TransactionSpentOutputsView)(nil) + // Copy creates a copy of the transaction spent outputs. func (t *transactionSpentOutputsApi) Copy() *TransactionSpentOutputs { return newTransactionSpentOutputs(t.ptr(), false) diff --git a/kernel/txid.go b/kernel/txid.go index 48a38489..1189cc10 100644 --- a/kernel/txid.go +++ b/kernel/txid.go @@ -54,14 +54,30 @@ type txidApi struct { ptr func() *C.btck_Txid } +func (t *txidApi) cPtr() *C.btck_Txid { + return t.ptr() +} + +// TxidLike is implemented by *Txid and *TxidView. +type TxidLike interface { + cPtr() *C.btck_Txid + Copy() *Txid + Equals(TxidLike) bool + Bytes() [32]byte + String() string +} + +var _ TxidLike = (*Txid)(nil) +var _ TxidLike = (*TxidView)(nil) + // Copy creates a copy of the txid. func (t *txidApi) Copy() *Txid { return newTxid(t.ptr(), false) } // Equals checks if two txids are equal. -func (t *txidApi) Equals(other *Txid) bool { - return C.btck_txid_equals(t.ptr(), other.txidApi.ptr()) != 0 +func (t *txidApi) Equals(other TxidLike) bool { + return C.btck_txid_equals(t.ptr(), other.cPtr()) != 0 } // Bytes returns the 32-byte representation of the txid. diff --git a/kernel/txid_test.go b/kernel/txid_test.go index 990aa760..2c5ea9f9 100644 --- a/kernel/txid_test.go +++ b/kernel/txid_test.go @@ -39,6 +39,9 @@ func TestTxid(t *testing.T) { if !txid.Equals(copiedTxid) { t.Error("txid.Equals(copiedTxid) = false, want true") } + if !copiedTxid.Equals(txid) { + t.Error("copiedTxid.Equals(txid) = false, want true") + } // Test String() expected := hex.EncodeToString(ReverseBytes(txidBytes[:])) From 71462d69e01676ada24e67bde49e043eeca30438 Mon Sep 17 00:00:00 2001 From: stringintech Date: Sat, 2 May 2026 15:36:09 +0330 Subject: [PATCH 4/4] Document owned and view types and sealed interfaces Describe owned objects, view lifetimes, and sealed Like interfaces in the kernel package documentation. --- kernel/kernel.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/kernel/kernel.go b/kernel/kernel.go index 36bf53aa..7563e762 100644 --- a/kernel/kernel.go +++ b/kernel/kernel.go @@ -15,4 +15,18 @@ // the resources automatically via finalizers. However, relying on finalizers may delay // resource cleanup and is not recommended for long-running programs or when working // with many objects. +// +// # Owned and View Types +// +// Some kernel objects have both an owned type and a view type, such as Transaction +// and TransactionView. Owned types hold a reference to an underlying C resource and +// provide Destroy. View types are non-owned pointers returned from another object or +// callback, and remain valid only while the object that produced them remains valid. +// +// Methods shared by an owned type and its view type are exposed through sealed +// interfaces named with the Like suffix, such as TransactionLike and TxidLike. These +// interfaces are implemented by the package's owned and view types, but cannot be +// implemented by external packages because they include an unexported pointer getter. +// This keeps APIs type-safe around kernel C pointers while still allowing callers to +// accept either owned or view values. package kernel