Class: CoinOp::Bit::Transaction
- Inherits:
-
Object
- Object
- CoinOp::Bit::Transaction
- Includes:
- Encodings
- Defined in:
- lib/coin-op/bit/transaction.rb
Instance Attribute Summary collapse
-
#confirmations ⇒ Object
readonly
Returns the value of attribute confirmations.
-
#inputs ⇒ Object
readonly
Returns the value of attribute inputs.
-
#lock_time ⇒ Object
readonly
Returns the value of attribute lock_time.
-
#native ⇒ Object
readonly
Returns the value of attribute native.
-
#outputs ⇒ Object
readonly
Returns the value of attribute outputs.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Class Method Summary collapse
-
.build {|transaction| ... } ⇒ Object
Deprecated.
-
.data(data, network:) ⇒ Object
(also: from_data)
Construct a Transaction from a data structure of nested Hashes and Arrays.
-
.hex(hex) ⇒ Object
(also: from_hex)
Construct a Transaction from a hex representation of the raw bytes.
-
.native(tx) ⇒ Object
(also: from_native)
Construct a transaction from an instance of ::Bitcoin::Protocol::Tx.
-
.raw(raw_tx) ⇒ Object
(also: from_bytes)
Construct a Transaction from raw bytes.
Instance Method Summary collapse
-
#add_change(address, metadata = {}) ⇒ Object
Add an output to receive change for this transaction.
-
#add_input(input, network: @network) ⇒ Object
Takes one of.
-
#add_output(output, network: @network) ⇒ Object
Takes either an Output or a Hash describing an output.
-
#binary_hash ⇒ Object
Returns the transaction hash as a string of bytes.
-
#change_value ⇒ Object
Returns the value that should be assigned to a change output.
-
#estimate_fee(tx_size: nil, network: @network, fee_per_kb: nil) ⇒ Object
Estimate the fee in satoshis for this transaction.
-
#fee ⇒ Object
Returns the transaction fee computed from the actual input and output values, as opposed to the requested override fee or the estimated fee.
- #fee_override ⇒ Object
-
#funded? ⇒ Boolean
Are the currently selected inputs sufficient to cover the current outputs and the desired fee?.
-
#hex_hash ⇒ Object
Returns the transaction hash encoded as hex.
-
#initialize(options = {}) ⇒ Transaction
constructor
A new Transaction contains no inputs or outputs; these can be added with #add_input and #add_output.
-
#input_value ⇒ Object
Total value of all inputs.
-
#input_value_for(addresses) ⇒ Object
Takes a set of Bitcoin addresses and returns the value expressed in the inputs for this transaction.
-
#output_value ⇒ Object
Total value of all outputs.
-
#output_value_for(addresses) ⇒ Object
Takes a set of Bitcoin addresses and returns the value expressed in the outputs for this transaction.
-
#set_script_sigs(*input_args, &block) ⇒ Object
A convenience method for authorizing inputs in a generic manner.
-
#sig_hash(input, script = nil) ⇒ Object
Compute the digest for a given input.
-
#to_hash ⇒ Object
Returns a custom data structure representing the full transaction.
-
#to_hex ⇒ Object
Returns the transaction payload encoded as hex.
- #to_json(*a) ⇒ Object
-
#update_native {|@native| ... } ⇒ Object
Update the “native” bitcoin-ruby instances for the transaction and all its inputs.
-
#validate_script_sigs ⇒ Object
Verify that the script_sigs for all inputs are valid.
-
#validate_syntax ⇒ Object
Validate that the transaction is plausibly signable.
-
#value_for(addresses) ⇒ Object
Takes a set of Bitcoin addresses and returns the net change in value expressed in this transaction.
Methods included from Encodings
#base58, #decode_base58, #decode_hex, #hex, #int_to_byte_array
Constructor Details
#initialize(options = {}) ⇒ Transaction
A new Transaction contains no inputs or outputs; these can be added with #add_input and #add_output.
97 98 99 100 101 102 103 104 105 106 |
# File 'lib/coin-op/bit/transaction.rb', line 97 def initialize(={}) @native = Bitcoin::Protocol::Tx.new @inputs = [] @outputs = [] @lock_time = @native.lock_time = ([:lock_time] || 0) @version = @native.ver = ([:version] || 1) @network = [:network] @fee_override = [:fee] @confirmations = [:confirmations] end |
Instance Attribute Details
#confirmations ⇒ Object (readonly)
Returns the value of attribute confirmations.
93 94 95 |
# File 'lib/coin-op/bit/transaction.rb', line 93 def confirmations @confirmations end |
#inputs ⇒ Object (readonly)
Returns the value of attribute inputs.
93 94 95 |
# File 'lib/coin-op/bit/transaction.rb', line 93 def inputs @inputs end |
#lock_time ⇒ Object (readonly)
Returns the value of attribute lock_time.
93 94 95 |
# File 'lib/coin-op/bit/transaction.rb', line 93 def lock_time @lock_time end |
#native ⇒ Object (readonly)
Returns the value of attribute native.
93 94 95 |
# File 'lib/coin-op/bit/transaction.rb', line 93 def native @native end |
#outputs ⇒ Object (readonly)
Returns the value of attribute outputs.
93 94 95 |
# File 'lib/coin-op/bit/transaction.rb', line 93 def outputs @outputs end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
93 94 95 |
# File 'lib/coin-op/bit/transaction.rb', line 93 def version @version end |
Class Method Details
.build {|transaction| ... } ⇒ Object
Deprecated. Easier to use Transaction.from_data
7 8 9 10 11 |
# File 'lib/coin-op/bit/transaction.rb', line 7 def self.build(&block) transaction = self.new yield transaction transaction end |
.data(data, network:) ⇒ Object Also known as: from_data
Construct a Transaction from a data structure of nested Hashes and Arrays.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/coin-op/bit/transaction.rb', line 14 def self.data(data, network:) version, lock_time, fee, inputs, outputs, confirmations = data.values_at :version, :lock_time, :fee, :inputs, :outputs, :confirmations transaction = self.new( fee: fee, version: version, lock_time: lock_time, confirmations: confirmations, network: network ) outputs.each do |output_hash| transaction.add_output(output_hash, network: network) end #FIXME: we're not handling sig_scripts for already signed inputs. inputs.each do |input_hash| transaction.add_input(input_hash, network: network) ## FIXME: verify that the supplied and computed sig_hashes match #puts :sig_hashes_match => (data[:sig_hash] == input.sig_hash) end if inputs transaction end |
.hex(hex) ⇒ Object Also known as: from_hex
Construct a Transaction from a hex representation of the raw bytes.
48 49 50 |
# File 'lib/coin-op/bit/transaction.rb', line 48 def self.hex(hex) self.from_bytes CoinOp::Encodings.decode_hex(hex) end |
.native(tx) ⇒ Object Also known as: from_native
Construct a transaction from an instance of ::Bitcoin::Protocol::Tx
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/coin-op/bit/transaction.rb', line 53 def self.native(tx) transaction = self.new() # TODO: reconsider use of instance_eval transaction.instance_eval do @native = tx tx.inputs.each_with_index do |input, i| # We use SparseInput because it does not require the retrieval # of the previous output. Its functionality should probably be # folded into the Input class. @inputs << SparseInput.new(input.prev_out, input.prev_out_index) end tx.outputs.each_with_index do |output, i| @outputs << Output.new( :transaction => transaction, :index => i, :value => output.value, :script => {:blob => output.pk_script} ) end end report = transaction.validate_syntax unless report[:valid] == true raise "Invalid syntax: #{report[:error].to_json}" end transaction end |
.raw(raw_tx) ⇒ Object Also known as: from_bytes
Construct a Transaction from raw bytes.
43 44 45 |
# File 'lib/coin-op/bit/transaction.rb', line 43 def self.raw(raw_tx) self.native ::Bitcoin::Protocol::Tx.new(raw_tx) end |
Instance Method Details
#add_change(address, metadata = {}) ⇒ Object
Add an output to receive change for this transaction. Takes a bitcoin address and optional metadata Hash.
358 359 360 361 362 363 364 |
# File 'lib/coin-op/bit/transaction.rb', line 358 def add_change(address, ={}) add_output( value: change_value, address: address, metadata: {memo: "change"}.merge() ) end |
#add_input(input, network: @network) ⇒ Object
Takes one of
-
an instance of Input
-
an instance of Output
-
a Hash describing an Output
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/coin-op/bit/transaction.rb', line 159 def add_input(input, network: @network) # TODO: allow specifying prev_tx and index with a Hash. # Possibly stop using SparseInput. input = Input.new(input.merge(transaction: self, index: @inputs.size, network: network) ) unless input.is_a?(Input) @inputs << input self.update_native do |native| native.add_in input.native end input end |
#add_output(output, network: @network) ⇒ Object
Takes either an Output or a Hash describing an output.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/coin-op/bit/transaction.rb', line 176 def add_output(output, network: @network) if output.is_a?(Output) output.set_transaction(self, @outputs.size) else output = Output.new(output.merge(transaction: self, index: @outputs.size), network: network) end @outputs << output self.update_native do |native| native.add_out(output.native) end end |
#binary_hash ⇒ Object
Returns the transaction hash as a string of bytes.
192 193 194 195 |
# File 'lib/coin-op/bit/transaction.rb', line 192 def binary_hash update_native @native.binary_hash end |
#change_value ⇒ Object
Returns the value that should be assigned to a change output.
352 353 354 |
# File 'lib/coin-op/bit/transaction.rb', line 352 def change_value input_value - (output_value + fee_override) end |
#estimate_fee(tx_size: nil, network: @network, fee_per_kb: nil) ⇒ Object
Estimate the fee in satoshis for this transaction. Takes an optional tx_size argument because it is impossible to determine programmatically the size of the scripts used to create P2SH outputs. Rough testing of the size of a 2of3 multisig p2sh input: 297 +/- 40 bytes
298 299 300 301 302 |
# File 'lib/coin-op/bit/transaction.rb', line 298 def estimate_fee(tx_size: nil, network: @network, fee_per_kb: nil) unspents = inputs.map(&:output) Fee.estimate(unspents, outputs, network: network, tx_size: tx_size, fee_per_kb: fee_per_kb) end |
#fee ⇒ Object
Returns the transaction fee computed from the actual input and output values, as opposed to the requested override fee or the estimated fee.
306 307 308 |
# File 'lib/coin-op/bit/transaction.rb', line 306 def fee input_value - output_value rescue nil end |
#fee_override ⇒ Object
290 291 292 |
# File 'lib/coin-op/bit/transaction.rb', line 290 def fee_override @fee_override || self.estimate_fee(network: @network) end |
#funded? ⇒ Boolean
Are the currently selected inputs sufficient to cover the current outputs and the desired fee?
322 323 324 |
# File 'lib/coin-op/bit/transaction.rb', line 322 def funded? input_value >= (output_value + fee_override) end |
#hex_hash ⇒ Object
Returns the transaction hash encoded as hex
198 199 200 201 |
# File 'lib/coin-op/bit/transaction.rb', line 198 def hex_hash update_native @native.hash end |
#input_value ⇒ Object
Total value of all inputs.
327 328 329 |
# File 'lib/coin-op/bit/transaction.rb', line 327 def input_value inputs.inject(0) { |sum, input| sum += input.output.value } end |
#input_value_for(addresses) ⇒ Object
Takes a set of Bitcoin addresses and returns the value expressed in the inputs for this transaction.
339 340 341 342 |
# File 'lib/coin-op/bit/transaction.rb', line 339 def input_value_for(addresses) own = inputs.select { |input| addresses.include?(input.output.address) } own.inject(0) { |sum, input| input.output.value } end |
#output_value ⇒ Object
Total value of all outputs.
311 312 313 314 315 316 317 318 |
# File 'lib/coin-op/bit/transaction.rb', line 311 def output_value total = 0 @outputs.each do |output| total += output.value end total end |
#output_value_for(addresses) ⇒ Object
Takes a set of Bitcoin addresses and returns the value expressed in the outputs for this transaction.
346 347 348 349 |
# File 'lib/coin-op/bit/transaction.rb', line 346 def output_value_for(addresses) own = outputs.select { |output| addresses.include?(output.address) } own.inject(0) { |sum, output| output.value } end |
#set_script_sigs(*input_args, &block) ⇒ Object
A convenience method for authorizing inputs in a generic manner. Rather than iterating over the inputs manually, the user can provide this method with an array of values and a block that knows what to do with the values.
For example, if you happen to have the script sigs precomputed for some strange reason, you could do this:
tx.set_script_sigs sig_array do |input, sig|
sig
end
More realistically, if you have an array of the keypairs corresponding to the inputs:
tx.set_script_sigs keys do |input, key|
sig_hash = tx.sig_hash(input)
key.sign(sig_hash)
end
Each element of the array may be an array, which allows for easy handling of multisig situations.
276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/coin-op/bit/transaction.rb', line 276 def set_script_sigs(*input_args, &block) # No sense trying to authorize when the transaction isn't usable. report = validate_syntax unless report[:valid] == true raise "Invalid syntax: #{report[:errors].to_json}" end # Array#zip here allows us to iterate over the inputs in lockstep with any # number of sets of values. self.inputs.zip(*input_args) do |input, *input_arg| input.script_sig = yield input, *input_arg end end |
#sig_hash(input, script = nil) ⇒ Object
Compute the digest for a given input. This is the value that is actually signed in a transaction. e.g. I want to spend UTXO0 - I provide the UTXO0 as an input.
I provide as a script the script to which UTXO0 was paid.
If UTX0 was paid to a P2SH address, the script here needs to be the correct m-of-n structure, which may well not be part of the input.output object
-
This works here because we default to 2-of-3 and have consistent ordering
When we support multiple m-of-n’s, this may become a prollem.
244 245 246 247 248 249 250 251 252 |
# File 'lib/coin-op/bit/transaction.rb', line 244 def sig_hash(input, script=nil) # We only allow SIGHASH_ALL at this time # https://en.bitcoin.it/wiki/OP_CHECKSIG#Hashtype_SIGHASH_ALL_.28default.29 prev_out = input.output script ||= prev_out.script @native.signature_hash_for_input(input.index, script.to_blob) end |
#to_hash ⇒ Object
Returns a custom data structure representing the full transaction. Typically used only by #to_json.
220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/coin-op/bit/transaction.rb', line 220 def to_hash { confirmations: self.confirmations.nil? ? 0 : self.confirmations, version: self.version, lock_time: self.lock_time, hash: self.hex_hash, fee: self.fee, inputs: self.inputs, outputs: self.outputs } end |
#to_hex ⇒ Object
Returns the transaction payload encoded as hex. This value can be used by other bitcoin tools for publishing to the network.
213 214 215 216 |
# File 'lib/coin-op/bit/transaction.rb', line 213 def to_hex payload = self.native.to_payload CoinOp::Encodings.hex(payload) end |
#to_json(*a) ⇒ Object
232 233 234 |
# File 'lib/coin-op/bit/transaction.rb', line 232 def to_json(*a) self.to_hash.to_json(*a) end |
#update_native {|@native| ... } ⇒ Object
Update the “native” bitcoin-ruby instances for the transaction and all its inputs. Will be removed when we rework the wrapper classes to be lazy, rather than eager.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/coin-op/bit/transaction.rb', line 111 def update_native yield @native if block_given? @native = Bitcoin::Protocol::Tx.new(@native.to_payload) @inputs.each_with_index do |input, i| native = @native.inputs[i] # Using instance_eval here because I really don't want to expose # Input#native=. As we consume more and more of the native # functionality, we can dispense with such ugliness. input.instance_eval do @native = native end # TODO: is this re-nativization necessary for outputs, too? end end |
#validate_script_sigs ⇒ Object
Verify that the script_sigs for all inputs are valid.
139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/coin-op/bit/transaction.rb', line 139 def validate_script_sigs bad_inputs = [] valid = true @inputs.each_with_index do |input, index| # TODO: confirm whether we need to mess with the block_timestamp arg unless self.native.verify_input_signature(index, input.output.transaction.native) valid = false bad_inputs << index end end {:valid => valid, :inputs => bad_inputs} end |
#validate_syntax ⇒ Object
Validate that the transaction is plausibly signable.
131 132 133 134 135 136 |
# File 'lib/coin-op/bit/transaction.rb', line 131 def validate_syntax update_native validator = Bitcoin::Validation::Tx.new(@native, nil) valid = validator.validate :rules => [:syntax] {:valid => valid, :error => validator.error} end |
#value_for(addresses) ⇒ Object
Takes a set of Bitcoin addresses and returns the net change in value expressed in this transaction.
333 334 335 |
# File 'lib/coin-op/bit/transaction.rb', line 333 def value_for(addresses) output_value_for(addresses) - input_value_for(addresses) end |