bitcoinkernel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +232 -0
- data/Rakefile +8 -0
- data/lib/bitcoinkernel/block.rb +71 -0
- data/lib/bitcoinkernel/block_hash.rb +52 -0
- data/lib/bitcoinkernel/block_tree_entry.rb +58 -0
- data/lib/bitcoinkernel/block_validation_state.rb +54 -0
- data/lib/bitcoinkernel/chain.rb +48 -0
- data/lib/bitcoinkernel/chain_parameters.rb +49 -0
- data/lib/bitcoinkernel/chainstate_manager.rb +54 -0
- data/lib/bitcoinkernel/chainstate_manager_options.rb +62 -0
- data/lib/bitcoinkernel/context.rb +26 -0
- data/lib/bitcoinkernel/context_options.rb +37 -0
- data/lib/bitcoinkernel/logging.rb +81 -0
- data/lib/bitcoinkernel/script_pubkey.rb +74 -0
- data/lib/bitcoinkernel/serializable.rb +33 -0
- data/lib/bitcoinkernel/transaction.rb +90 -0
- data/lib/bitcoinkernel/transaction_input.rb +28 -0
- data/lib/bitcoinkernel/transaction_out_point.rb +42 -0
- data/lib/bitcoinkernel/transaction_output.rb +44 -0
- data/lib/bitcoinkernel/txid.rb +42 -0
- data/lib/bitcoinkernel/validation_interface.rb +107 -0
- data/lib/bitcoinkernel/version.rb +5 -0
- data/lib/bitcoinkernel.rb +207 -0
- data/sig/bitcoinkernel.rbs +4 -0
- metadata +86 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Represents the active blockchain.
|
|
5
|
+
# This is a view into the chainstate manager's active chain.
|
|
6
|
+
#
|
|
7
|
+
# Note: This class does not inherit from FFI::AutoPointer because:
|
|
8
|
+
# - There is no btck_chain_destroy function in the library
|
|
9
|
+
# - The pointer is not owned; it points to internal data of ChainstateManager
|
|
10
|
+
# - The lifetime depends on the parent ChainstateManager
|
|
11
|
+
class Chain
|
|
12
|
+
# @param [FFI::Pointer] ptr Pointer to btck_Chain
|
|
13
|
+
# @param [Boolean] owned Whether this object owns the pointer.
|
|
14
|
+
# Chain pointers from ChainstateManager are NOT owned.
|
|
15
|
+
def initialize(ptr, owned: false)
|
|
16
|
+
@ptr = ptr
|
|
17
|
+
@owned = owned
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get the height of the chain tip.
|
|
21
|
+
# @return [Integer]
|
|
22
|
+
def height
|
|
23
|
+
BitcoinKernel.btck_chain_get_height(@ptr)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Get the block tree entry at the specified height.
|
|
27
|
+
# @param [Integer] block_height The height to query
|
|
28
|
+
# @return [BlockTreeEntry, nil] The block tree entry, or nil if height is out of bounds
|
|
29
|
+
def entry_at(block_height)
|
|
30
|
+
ptr = BitcoinKernel.btck_chain_get_by_height(@ptr, block_height)
|
|
31
|
+
return nil if ptr.null?
|
|
32
|
+
BlockTreeEntry.new(ptr, owned: false)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Check if the chain contains a block tree entry.
|
|
36
|
+
# @param [BlockTreeEntry] entry The entry to check
|
|
37
|
+
# @return [Boolean]
|
|
38
|
+
def contains?(entry)
|
|
39
|
+
BitcoinKernel.btck_chain_contains(@ptr, entry.to_ptr) == 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get the underlying pointer for FFI calls.
|
|
43
|
+
# @return [FFI::Pointer]
|
|
44
|
+
def to_ptr
|
|
45
|
+
@ptr
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Represents chain parameters for a specific network.
|
|
5
|
+
class ChainParameters < FFI::AutoPointer
|
|
6
|
+
# Create chain parameters for the specified network.
|
|
7
|
+
# @param [Integer] chain_type One of ChainType constants (MAINNET, TESTNET, etc.)
|
|
8
|
+
# @return [ChainParameters]
|
|
9
|
+
def self.create(chain_type = ChainType::MAINNET)
|
|
10
|
+
ptr = BitcoinKernel.btck_chain_parameters_create(chain_type)
|
|
11
|
+
raise Error, "Failed to create chain parameters" if ptr.null?
|
|
12
|
+
new(ptr)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Create chain parameters for mainnet.
|
|
16
|
+
# @return [ChainParameters]
|
|
17
|
+
def self.mainnet
|
|
18
|
+
create(ChainType::MAINNET)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Create chain parameters for testnet.
|
|
22
|
+
# @return [ChainParameters]
|
|
23
|
+
def self.testnet
|
|
24
|
+
create(ChainType::TESTNET)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Create chain parameters for testnet4.
|
|
28
|
+
# @return [ChainParameters]
|
|
29
|
+
def self.testnet4
|
|
30
|
+
create(ChainType::TESTNET_4)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Create chain parameters for signet.
|
|
34
|
+
# @return [ChainParameters]
|
|
35
|
+
def self.signet
|
|
36
|
+
create(ChainType::SIGNET)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Create chain parameters for regtest.
|
|
40
|
+
# @return [ChainParameters]
|
|
41
|
+
def self.regtest
|
|
42
|
+
create(ChainType::REGTEST)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.release(ptr)
|
|
46
|
+
BitcoinKernel.btck_chain_parameters_destroy(ptr)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Manages chainstate and block validation.
|
|
5
|
+
class ChainstateManager < FFI::AutoPointer
|
|
6
|
+
# Create a chainstate manager.
|
|
7
|
+
# @param [ChainstateManagerOptions] options Options for the chainstate manager
|
|
8
|
+
# @return [ChainstateManager]
|
|
9
|
+
def self.create(options)
|
|
10
|
+
ptr = BitcoinKernel.btck_chainstate_manager_create(options)
|
|
11
|
+
raise Error, "Failed to create chainstate manager" if ptr.null?
|
|
12
|
+
new(ptr)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.release(ptr)
|
|
16
|
+
BitcoinKernel.btck_chainstate_manager_destroy(ptr)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Process and validate a block.
|
|
20
|
+
# @param [Block] block The block to process
|
|
21
|
+
# @return [Boolean] true if processing was successful (not indicative of validity)
|
|
22
|
+
def process_block(block)
|
|
23
|
+
new_block_ptr = FFI::MemoryPointer.new(:int)
|
|
24
|
+
result = BitcoinKernel.btck_chainstate_manager_process_block(self, block, new_block_ptr)
|
|
25
|
+
result == 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get the currently active chain.
|
|
29
|
+
# @return [Chain]
|
|
30
|
+
def active_chain
|
|
31
|
+
ptr = BitcoinKernel.btck_chainstate_manager_get_active_chain(self)
|
|
32
|
+
raise Error, "Failed to get active chain" if ptr.null?
|
|
33
|
+
Chain.new(ptr, owned: false)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Get a block tree entry by its hash.
|
|
37
|
+
# @param [BlockHash] block_hash The block hash to look up
|
|
38
|
+
# @return [BlockTreeEntry, nil] The block tree entry, or nil if not found
|
|
39
|
+
def block_tree_entry_by_hash(block_hash)
|
|
40
|
+
ptr = BitcoinKernel.btck_chainstate_manager_get_block_tree_entry_by_hash(self, block_hash)
|
|
41
|
+
return nil if ptr.null?
|
|
42
|
+
BlockTreeEntry.new(ptr, owned: false)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Read a block from disk by its block tree entry.
|
|
46
|
+
# @param [BlockTreeEntry] entry The block tree entry
|
|
47
|
+
# @return [Block]
|
|
48
|
+
def read_block(entry)
|
|
49
|
+
ptr = BitcoinKernel.btck_block_read(self, entry.to_ptr)
|
|
50
|
+
raise Error, "Failed to read block" if ptr.null?
|
|
51
|
+
Block.new(ptr)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Options for creating a ChainstateManager.
|
|
5
|
+
class ChainstateManagerOptions < FFI::AutoPointer
|
|
6
|
+
# Create chainstate manager options.
|
|
7
|
+
# @param [Context] context The kernel context
|
|
8
|
+
# @param [String] data_directory Path to the chainstate data directory
|
|
9
|
+
# @param [String] blocks_directory Path to the blocks directory
|
|
10
|
+
# @return [ChainstateManagerOptions]
|
|
11
|
+
def self.create(context:, data_directory:, blocks_directory:)
|
|
12
|
+
ptr = BitcoinKernel.btck_chainstate_manager_options_create(
|
|
13
|
+
context,
|
|
14
|
+
data_directory, data_directory.bytesize,
|
|
15
|
+
blocks_directory, blocks_directory.bytesize
|
|
16
|
+
)
|
|
17
|
+
raise Error, "Failed to create chainstate manager options" if ptr.null?
|
|
18
|
+
new(ptr)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.release(ptr)
|
|
22
|
+
BitcoinKernel.btck_chainstate_manager_options_destroy(ptr)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Set the number of worker threads for validation.
|
|
26
|
+
# @param [Integer] num Number of worker threads (0-15)
|
|
27
|
+
# @return [self]
|
|
28
|
+
def set_worker_threads(num)
|
|
29
|
+
BitcoinKernel.btck_chainstate_manager_options_set_worker_threads_num(self, num)
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Set wipe database options for reindexing.
|
|
34
|
+
# @param [Boolean] wipe_block_tree_db Wipe block tree db (should only be true if wipe_chainstate_db is also true)
|
|
35
|
+
# @param [Boolean] wipe_chainstate_db Wipe chainstate db
|
|
36
|
+
# @return [Boolean] true if successful
|
|
37
|
+
def set_wipe_dbs(wipe_block_tree_db:, wipe_chainstate_db:)
|
|
38
|
+
result = BitcoinKernel.btck_chainstate_manager_options_set_wipe_dbs(
|
|
39
|
+
self,
|
|
40
|
+
wipe_block_tree_db ? 1 : 0,
|
|
41
|
+
wipe_chainstate_db ? 1 : 0
|
|
42
|
+
)
|
|
43
|
+
result == 0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Set block tree db to be in memory.
|
|
47
|
+
# @param [Boolean] in_memory Whether to use in-memory database
|
|
48
|
+
# @return [self]
|
|
49
|
+
def set_block_tree_db_in_memory(in_memory = true)
|
|
50
|
+
BitcoinKernel.btck_chainstate_manager_options_update_block_tree_db_in_memory(self, in_memory ? 1 : 0)
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Set chainstate db to be in memory.
|
|
55
|
+
# @param [Boolean] in_memory Whether to use in-memory database
|
|
56
|
+
# @return [self]
|
|
57
|
+
def set_chainstate_db_in_memory(in_memory = true)
|
|
58
|
+
BitcoinKernel.btck_chainstate_manager_options_update_chainstate_db_in_memory(self, in_memory ? 1 : 0)
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Represents the kernel context.
|
|
5
|
+
class Context < FFI::AutoPointer
|
|
6
|
+
# Create a new context with the given options.
|
|
7
|
+
# @param [ContextOptions, nil] options Context options (nil for defaults)
|
|
8
|
+
# @return [Context]
|
|
9
|
+
def self.create(options = nil)
|
|
10
|
+
opts_ptr = options&.to_ptr
|
|
11
|
+
ptr = BitcoinKernel.btck_context_create(opts_ptr)
|
|
12
|
+
raise Error, "Failed to create context" if ptr.null?
|
|
13
|
+
new(ptr)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.release(ptr)
|
|
17
|
+
BitcoinKernel.btck_context_destroy(ptr)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Interrupt the context.
|
|
21
|
+
# @return [Boolean] true if interrupt was successful
|
|
22
|
+
def interrupt
|
|
23
|
+
BitcoinKernel.btck_context_interrupt(self) == 0
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Options for creating a Context.
|
|
5
|
+
class ContextOptions < FFI::AutoPointer
|
|
6
|
+
# Create new context options.
|
|
7
|
+
# @return [ContextOptions]
|
|
8
|
+
def self.create
|
|
9
|
+
ptr = BitcoinKernel.btck_context_options_create
|
|
10
|
+
raise Error, "Failed to create context options" if ptr.null?
|
|
11
|
+
new(ptr)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.release(ptr)
|
|
15
|
+
BitcoinKernel.btck_context_options_destroy(ptr)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Set chain parameters for this context.
|
|
19
|
+
# @param [ChainParameters] chain_params Chain parameters to use
|
|
20
|
+
# @return [self]
|
|
21
|
+
def set_chainparams(chain_params)
|
|
22
|
+
BitcoinKernel.btck_context_options_set_chainparams(self, chain_params)
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Set validation interface callbacks for this context.
|
|
27
|
+
# @param [ValidationInterface] validation_interface The validation interface with callbacks
|
|
28
|
+
# @return [self]
|
|
29
|
+
def set_validation_interface(validation_interface)
|
|
30
|
+
# Store reference to prevent garbage collection of callbacks
|
|
31
|
+
@validation_interface = validation_interface
|
|
32
|
+
callbacks = validation_interface.to_ffi_callbacks
|
|
33
|
+
BitcoinKernel.btck_context_options_set_validation_interface(self, callbacks)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Logging configuration for Bitcoin Kernel.
|
|
5
|
+
module Logging
|
|
6
|
+
# Logging options struct for FFI
|
|
7
|
+
class Options < FFI::Struct
|
|
8
|
+
layout :log_timestamps, :int,
|
|
9
|
+
:log_time_micros, :int,
|
|
10
|
+
:log_threadnames, :int,
|
|
11
|
+
:log_sourcelocations, :int,
|
|
12
|
+
:always_print_category_levels, :int
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Log categories
|
|
16
|
+
module Category
|
|
17
|
+
ALL = 0
|
|
18
|
+
BENCH = 1
|
|
19
|
+
BLOCKSTORAGE = 2
|
|
20
|
+
COINDB = 3
|
|
21
|
+
LEVELDB = 4
|
|
22
|
+
MEMPOOL = 5
|
|
23
|
+
PRUNE = 6
|
|
24
|
+
RAND = 7
|
|
25
|
+
REINDEX = 8
|
|
26
|
+
VALIDATION = 9
|
|
27
|
+
KERNEL = 10
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Log levels
|
|
31
|
+
module Level
|
|
32
|
+
TRACE = 0
|
|
33
|
+
DEBUG = 1
|
|
34
|
+
INFO = 2
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Permanently disable kernel logging output for this process.
|
|
38
|
+
# Once called, logging cannot be re-enabled.
|
|
39
|
+
# This function should only be called once and is not thread-safe.
|
|
40
|
+
def self.disable
|
|
41
|
+
BitcoinKernel.btck_logging_disable
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Set logging format options.
|
|
45
|
+
# @param [Hash] opts Logging options
|
|
46
|
+
# @option opts [Boolean] :timestamps Prepend timestamp to log messages
|
|
47
|
+
# @option opts [Boolean] :time_micros Log timestamps in microsecond precision
|
|
48
|
+
# @option opts [Boolean] :threadnames Prepend thread name to log messages
|
|
49
|
+
# @option opts [Boolean] :sourcelocations Prepend source location to log messages
|
|
50
|
+
# @option opts [Boolean] :category_levels Prepend log category and level to log messages
|
|
51
|
+
def self.set_options(timestamps: false, time_micros: false, threadnames: false,
|
|
52
|
+
sourcelocations: false, category_levels: false)
|
|
53
|
+
opts = Options.new
|
|
54
|
+
opts[:log_timestamps] = timestamps ? 1 : 0
|
|
55
|
+
opts[:log_time_micros] = time_micros ? 1 : 0
|
|
56
|
+
opts[:log_threadnames] = threadnames ? 1 : 0
|
|
57
|
+
opts[:log_sourcelocations] = sourcelocations ? 1 : 0
|
|
58
|
+
opts[:always_print_category_levels] = category_levels ? 1 : 0
|
|
59
|
+
BitcoinKernel.btck_logging_set_options(opts)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Set log level for a specific category.
|
|
63
|
+
# @param [Integer] category One of Category constants
|
|
64
|
+
# @param [Integer] level One of Level constants
|
|
65
|
+
def self.set_level(category, level)
|
|
66
|
+
BitcoinKernel.btck_logging_set_level_category(category, level)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Enable a log category.
|
|
70
|
+
# @param [Integer] category One of Category constants (use Category::ALL to enable all)
|
|
71
|
+
def self.enable_category(category)
|
|
72
|
+
BitcoinKernel.btck_logging_enable_category(category)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Disable a log category.
|
|
76
|
+
# @param [Integer] category One of Category constants (use Category::ALL to disable all)
|
|
77
|
+
def self.disable_category(category)
|
|
78
|
+
BitcoinKernel.btck_logging_disable_category(category)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Represents a script pubkey.
|
|
5
|
+
class ScriptPubkey < FFI::AutoPointer
|
|
6
|
+
include Serializable
|
|
7
|
+
serialize_with :btck_script_pubkey_to_bytes
|
|
8
|
+
|
|
9
|
+
# Create a ScriptPubkey from raw bytes.
|
|
10
|
+
# @param [String] script Raw script bytes (binary string)
|
|
11
|
+
# @return [ScriptPubkey]
|
|
12
|
+
def self.from_raw(script)
|
|
13
|
+
raw_ptr = FFI::MemoryPointer.new(:uint8, script.bytesize)
|
|
14
|
+
raw_ptr.put_bytes(0, script)
|
|
15
|
+
ptr = BitcoinKernel.btck_script_pubkey_create(raw_ptr, script.bytesize)
|
|
16
|
+
raise Error, "Failed to create script pubkey" if ptr.null?
|
|
17
|
+
new(ptr)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param [FFI::Pointer] ptr Pointer to btck_ScriptPubkey
|
|
21
|
+
# @param [Boolean] owned Whether this object owns the pointer.
|
|
22
|
+
# When obtained via TransactionOutput#script_pubkey, the pointer is NOT owned
|
|
23
|
+
# and depends on the lifetime of the parent TransactionOutput.
|
|
24
|
+
# In that case, owned should be false to prevent double-free.
|
|
25
|
+
def initialize(ptr, owned: true)
|
|
26
|
+
super(ptr)
|
|
27
|
+
self.autorelease = owned
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.release(ptr)
|
|
31
|
+
BitcoinKernel.btck_script_pubkey_destroy(ptr)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Compare two script pubkeys for equality by their serialized bytes.
|
|
35
|
+
# @param [ScriptPubkey] other The other script pubkey to compare
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
def ==(other)
|
|
38
|
+
return false unless other.is_a?(ScriptPubkey)
|
|
39
|
+
to_bytes == other.to_bytes
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Verify that the script is correctly spent by a transaction input.
|
|
43
|
+
# @param [Integer] amount Amount in satoshis
|
|
44
|
+
# @param [Transaction] tx Transaction spending this script
|
|
45
|
+
# @param [Integer] input_index Index of the input spending this script
|
|
46
|
+
# @param [Array<TransactionOutput>] spent_outputs All spent outputs (required for taproot)
|
|
47
|
+
# @param [Integer] flags Script verification flags (default: ScriptFlags::ALL)
|
|
48
|
+
# @return [Boolean] true if verification succeeded
|
|
49
|
+
def verify(amount:, tx:, input_index:, spent_outputs: [], flags: ScriptFlags::ALL)
|
|
50
|
+
status_ptr = FFI::MemoryPointer.new(:uint8)
|
|
51
|
+
|
|
52
|
+
# Create precomputed transaction data
|
|
53
|
+
precomputed_ptr = nil
|
|
54
|
+
unless spent_outputs.empty?
|
|
55
|
+
outputs_ptr = FFI::MemoryPointer.new(:pointer, spent_outputs.size)
|
|
56
|
+
spent_outputs.each_with_index do |out, i|
|
|
57
|
+
outputs_ptr.put_pointer(i * FFI::Pointer.size, out)
|
|
58
|
+
end
|
|
59
|
+
precomputed_ptr = BitcoinKernel.btck_precomputed_transaction_data_create(
|
|
60
|
+
tx, outputs_ptr, spent_outputs.size
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
begin
|
|
65
|
+
result = BitcoinKernel.btck_script_pubkey_verify(
|
|
66
|
+
self, amount, tx, precomputed_ptr, input_index, flags, status_ptr
|
|
67
|
+
)
|
|
68
|
+
result == 1
|
|
69
|
+
ensure
|
|
70
|
+
BitcoinKernel.btck_precomputed_transaction_data_destroy(precomputed_ptr) if precomputed_ptr
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Mixin for classes that can be serialized to bytes.
|
|
5
|
+
# Include this module and call `serialize_with` to define the to_bytes method.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# class Block < FFI::AutoPointer
|
|
9
|
+
# include Serializable
|
|
10
|
+
# serialize_with :btck_block_to_bytes
|
|
11
|
+
# end
|
|
12
|
+
module Serializable
|
|
13
|
+
def self.included(base)
|
|
14
|
+
base.extend(ClassMethods)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
# Define a to_bytes method using the specified FFI serialization function.
|
|
19
|
+
# @param ffi_method [Symbol] The FFI function name for serialization
|
|
20
|
+
def serialize_with(ffi_method)
|
|
21
|
+
define_method(:to_bytes) do
|
|
22
|
+
buffer = []
|
|
23
|
+
callback = proc do |bytes_ptr, size, _userdata|
|
|
24
|
+
buffer << bytes_ptr.read_bytes(size)
|
|
25
|
+
0
|
|
26
|
+
end
|
|
27
|
+
BitcoinKernel.send(ffi_method, self, callback, nil)
|
|
28
|
+
buffer.join.b
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Represents a Bitcoin transaction.
|
|
5
|
+
class Transaction < FFI::AutoPointer
|
|
6
|
+
include Serializable
|
|
7
|
+
serialize_with :btck_transaction_to_bytes
|
|
8
|
+
|
|
9
|
+
# Create a Transaction from raw serialized data.
|
|
10
|
+
# @param [String] raw_tx Serialized transaction data (binary string)
|
|
11
|
+
# @return [Transaction]
|
|
12
|
+
def self.from_raw(raw_tx)
|
|
13
|
+
raw_ptr = FFI::MemoryPointer.new(:uint8, raw_tx.bytesize)
|
|
14
|
+
raw_ptr.put_bytes(0, raw_tx)
|
|
15
|
+
ptr = BitcoinKernel.btck_transaction_create(raw_ptr, raw_tx.bytesize)
|
|
16
|
+
raise Error, "Failed to create transaction from raw data" if ptr.null?
|
|
17
|
+
new(ptr)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.release(ptr)
|
|
21
|
+
BitcoinKernel.btck_transaction_destroy(ptr)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Create a copy of this transaction.
|
|
25
|
+
# @return [Transaction] A new Transaction instance with copied data
|
|
26
|
+
def copy
|
|
27
|
+
copied_ptr = BitcoinKernel.btck_transaction_copy(self)
|
|
28
|
+
raise Error, "Failed to copy transaction" if copied_ptr.null?
|
|
29
|
+
Transaction.new(copied_ptr)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Compare two transactions for equality by their txid.
|
|
33
|
+
# @param [Transaction] other The other transaction to compare
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def ==(other)
|
|
36
|
+
return false unless other.is_a?(Transaction)
|
|
37
|
+
txid == other.txid
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Number of inputs in the transaction.
|
|
41
|
+
# @return [Integer]
|
|
42
|
+
def input_count
|
|
43
|
+
BitcoinKernel.btck_transaction_count_inputs(self)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Number of outputs in the transaction.
|
|
47
|
+
# @return [Integer]
|
|
48
|
+
def output_count
|
|
49
|
+
BitcoinKernel.btck_transaction_count_outputs(self)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Get output at specified index.
|
|
53
|
+
# @param [Integer] index Output index
|
|
54
|
+
# @return [TransactionOutput]
|
|
55
|
+
def output_at(index)
|
|
56
|
+
out_ptr = BitcoinKernel.btck_transaction_get_output_at(self, index)
|
|
57
|
+
raise Error, "Output not found at index #{index}" if out_ptr.null?
|
|
58
|
+
TransactionOutput.new(out_ptr, owned: false)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Get all outputs.
|
|
62
|
+
# @return [Array<TransactionOutput>]
|
|
63
|
+
def outputs
|
|
64
|
+
output_count.times.map { |i| output_at(i) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Get input at specified index.
|
|
68
|
+
# @param [Integer] index Input index
|
|
69
|
+
# @return [TransactionInput]
|
|
70
|
+
def input_at(index)
|
|
71
|
+
in_ptr = BitcoinKernel.btck_transaction_get_input_at(self, index)
|
|
72
|
+
raise Error, "Input not found at index #{index}" if in_ptr.null?
|
|
73
|
+
TransactionInput.new(in_ptr, owned: false)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get all inputs.
|
|
77
|
+
# @return [Array<TransactionInput>]
|
|
78
|
+
def inputs
|
|
79
|
+
input_count.times.map { |i| input_at(i) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get the txid.
|
|
83
|
+
# @return [Txid]
|
|
84
|
+
def txid
|
|
85
|
+
ptr = BitcoinKernel.btck_transaction_get_txid(self)
|
|
86
|
+
raise Error, "Failed to get txid" if ptr.null?
|
|
87
|
+
Txid.new(ptr, owned: false)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Represents a transaction input.
|
|
5
|
+
class TransactionInput < FFI::AutoPointer
|
|
6
|
+
# @param [FFI::Pointer] ptr Pointer to btck_TransactionInput
|
|
7
|
+
# @param [Boolean] owned Whether this object owns the pointer.
|
|
8
|
+
# When obtained via Transaction#input_at, the pointer is NOT owned
|
|
9
|
+
# and depends on the lifetime of the parent Transaction.
|
|
10
|
+
# In that case, owned should be false to prevent double-free.
|
|
11
|
+
def initialize(ptr, owned: true)
|
|
12
|
+
super(ptr)
|
|
13
|
+
self.autorelease = owned
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.release(ptr)
|
|
17
|
+
BitcoinKernel.btck_transaction_input_destroy(ptr)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get the outpoint (previous transaction output being spent).
|
|
21
|
+
# @return [TransactionOutPoint]
|
|
22
|
+
def out_point
|
|
23
|
+
out_point_ptr = BitcoinKernel.btck_transaction_input_get_out_point(self)
|
|
24
|
+
raise Error, "Failed to get out point" if out_point_ptr.null?
|
|
25
|
+
TransactionOutPoint.new(out_point_ptr, owned: false)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Represents a transaction outpoint (reference to a previous output).
|
|
5
|
+
class TransactionOutPoint < FFI::AutoPointer
|
|
6
|
+
# @param [FFI::Pointer] ptr Pointer to btck_TransactionOutPoint
|
|
7
|
+
# @param [Boolean] owned Whether this object owns the pointer.
|
|
8
|
+
# When obtained via TransactionInput#out_point, the pointer is NOT owned
|
|
9
|
+
# and depends on the lifetime of the parent TransactionInput.
|
|
10
|
+
# In that case, owned should be false to prevent double-free.
|
|
11
|
+
def initialize(ptr, owned: true)
|
|
12
|
+
super(ptr)
|
|
13
|
+
self.autorelease = owned
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.release(ptr)
|
|
17
|
+
BitcoinKernel.btck_transaction_out_point_destroy(ptr)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get the index of the output in the previous transaction.
|
|
21
|
+
# @return [Integer]
|
|
22
|
+
def index
|
|
23
|
+
BitcoinKernel.btck_transaction_out_point_get_index(self)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Get the txid of the previous transaction.
|
|
27
|
+
# @return [Txid]
|
|
28
|
+
def txid
|
|
29
|
+
ptr = BitcoinKernel.btck_transaction_out_point_get_txid(self)
|
|
30
|
+
raise Error, "Failed to get txid" if ptr.null?
|
|
31
|
+
Txid.new(ptr, owned: false)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Compare two transaction outpoints for equality by txid and index.
|
|
35
|
+
# @param [TransactionOutPoint] other The other outpoint to compare
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
def ==(other)
|
|
38
|
+
return false unless other.is_a?(TransactionOutPoint)
|
|
39
|
+
txid == other.txid && index == other.index
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BitcoinKernel
|
|
4
|
+
# Represents a transaction output.
|
|
5
|
+
class TransactionOutput < FFI::AutoPointer
|
|
6
|
+
# @param [FFI::Pointer] ptr Pointer to btck_TransactionOutput
|
|
7
|
+
# @param [Boolean] owned Whether this object owns the pointer.
|
|
8
|
+
# When obtained via Transaction#output_at, the pointer is NOT owned
|
|
9
|
+
# and depends on the lifetime of the parent Transaction.
|
|
10
|
+
# In that case, owned should be false to prevent double-free.
|
|
11
|
+
# If you need to keep the TransactionOutput longer than the Transaction,
|
|
12
|
+
# use btck_transaction_output_copy to create an owned copy.
|
|
13
|
+
def initialize(ptr, owned: true)
|
|
14
|
+
super(ptr)
|
|
15
|
+
self.autorelease = owned
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.release(ptr)
|
|
19
|
+
BitcoinKernel.btck_transaction_output_destroy(ptr)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get the output amount in satoshis.
|
|
23
|
+
# @return [Integer]
|
|
24
|
+
def amount
|
|
25
|
+
BitcoinKernel.btck_transaction_output_get_amount(self)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get the script pubkey.
|
|
29
|
+
# @return [ScriptPubkey]
|
|
30
|
+
def script_pubkey
|
|
31
|
+
spk_ptr = BitcoinKernel.btck_transaction_output_get_script_pubkey(self)
|
|
32
|
+
raise Error, "Failed to get script pubkey" if spk_ptr.null?
|
|
33
|
+
ScriptPubkey.new(spk_ptr, owned: false)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Compare two transaction outputs for equality by amount and script pubkey.
|
|
37
|
+
# @param [TransactionOutput] other The other output to compare
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
def ==(other)
|
|
40
|
+
return false unless other.is_a?(TransactionOutput)
|
|
41
|
+
amount == other.amount && script_pubkey == other.script_pubkey
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|