Class: BTC::Transaction
- Inherits:
-
Object
- Object
- BTC::Transaction
- Defined in:
- lib/btcruby/transaction.rb
Constant Summary collapse
- CURRENT_VERSION =
1- DEFAULT_FEE_RATE =
satoshis per 1000 bytes
10_000- DEFAULT_RELAY_FEE_RATE =
satoshis per 1000 bytes
1000
Instance Attribute Summary collapse
-
#block_hash ⇒ Object
Binary hash of the block at which transaction was included.
-
#block_height ⇒ Object
Height of the block at which transaction was included.
-
#block_id ⇒ Object
Hex-encoded block ID.
-
#block_time ⇒ Object
Time of the block at which tx was included (::Time instance or nil).
-
#confirmations ⇒ Object
Number of confirmations for this transaction (depth in the blockchan).
-
#data ⇒ Object
readonly
Binary representation of the transaction in wire format (aka payload).
-
#dictionary ⇒ Object
readonly
Dictionary representation of transaction ready to be encoded in JSON, PropertyList etc.
-
#fee ⇒ Object
If available, returns mining fee paid by this transaction.
-
#inputs ⇒ Object
List of TransactionInputs.
-
#inputs_amount ⇒ Object
If available, returns total amount of all inputs.
-
#lock_time ⇒ Object
Lock time.
-
#outputs ⇒ Object
List of TransactionOutputs.
-
#outputs_amount ⇒ Object
readonly
Total amount on all outputs (not including fees).
-
#transaction_hash ⇒ Object
readonly
32-byte transaction hash.
-
#transaction_id ⇒ Object
readonly
Hexadecimal transaction ID with bytes reversed.
-
#version ⇒ Object
Version of the transaction.
Class Method Summary collapse
-
.compute_fee(size, fee_rate: DEFAULT_FEE_RATE) ⇒ Object
Compute a fee for a transaction of a given size with a specified per-KB fee rate.
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
-
#add_input(txin) ⇒ Object
Adds another input to the transaction.
- #add_output(txout) ⇒ Object
-
#coinbase? ⇒ Boolean
Returns true if this transaction is a coinbase transaction.
-
#compute_fee(fee_rate: DEFAULT_FEE_RATE) ⇒ Object
Compute a fee for this transaction with a specified per-KB fee rate.
-
#dup ⇒ Object
Makes a deep copy of a transaction (all inputs and outputs are copied too).
- #init_with_components(version: CURRENT_VERSION, inputs: [], outputs: [], lock_time: 0) ⇒ Object
- #init_with_dictionary(dict) ⇒ Object
- #init_with_stream(stream) ⇒ Object
-
#initialize(hex: nil, data: nil, stream: nil, dictionary: nil, version: CURRENT_VERSION, inputs: [], outputs: [], lock_time: 0, block_hash: nil, block_id: nil, block_height: nil, block_time: nil, confirmations: nil, fee: nil, inputs_amount: nil) ⇒ Transaction
constructor
Initializes transaction with its attributes.
- #inspect ⇒ Object
-
#open_assets_transaction? ⇒ Boolean
Returns
trueif this transaction contains an Open Assets marker. - #remove_all_inputs ⇒ Object
- #remove_all_outputs ⇒ Object
-
#signature_hash(input_index: nil, output_script: nil, hash_type: BTC::SIGHASH_ALL, version: 0, amount: 0) ⇒ Object
Hash for signing a transaction.
-
#to_h ⇒ Object
Returns dictionary representation of the transaction.
-
#to_hex ⇒ Object
Returns hex representation of the transaction.
-
#to_s ⇒ Object
Returns hex representation of the transaction.
Constructor Details
#initialize(hex: nil, data: nil, stream: nil, dictionary: nil, version: CURRENT_VERSION, inputs: [], outputs: [], lock_time: 0, block_hash: nil, block_id: nil, block_height: nil, block_time: nil, confirmations: nil, fee: nil, inputs_amount: nil) ⇒ Transaction
Initializes transaction with its attributes. Every attribute has a valid default value.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/btcruby/transaction.rb', line 82 def initialize(hex: nil, data: nil, stream: nil, dictionary: nil, version: CURRENT_VERSION, inputs: [], outputs: [], lock_time: 0, # optional attributes block_hash: nil, block_id: nil, block_height: nil, block_time: nil, confirmations: nil, fee: nil, inputs_amount: nil) data = BTC.from_hex(hex) if hex stream = StringIO.new(data) if data if stream init_with_stream(stream) elsif dictionary init_with_dictionary(dictionary) else init_with_components(version: version, inputs: inputs, outputs: outputs, lock_time: lock_time) end @block_hash = block_hash @block_hash = BTC.hash_from_id(block_id) if block_id @block_height = block_height @block_time = block_time @confirmations = confirmations @fee = fee @inputs_amount = inputs_amount end |
Instance Attribute Details
#block_hash ⇒ Object
Binary hash of the block at which transaction was included. If not confirmed or not available, equals nil.
49 50 51 |
# File 'lib/btcruby/transaction.rb', line 49 def block_hash @block_hash end |
#block_height ⇒ Object
Height of the block at which transaction was included.
If not confirmed equals -1.
Note: block_height might not be provided by some APIs while confirmations may be.
58 59 60 |
# File 'lib/btcruby/transaction.rb', line 58 def block_height @block_height end |
#block_id ⇒ Object
Hex-encoded block ID. If not confirmed or not available, equals nil.
53 54 55 |
# File 'lib/btcruby/transaction.rb', line 53 def block_id @block_id end |
#block_time ⇒ Object
Time of the block at which tx was included (::Time instance or nil).
61 62 63 |
# File 'lib/btcruby/transaction.rb', line 61 def block_time @block_time end |
#confirmations ⇒ Object
Number of confirmations for this transaction (depth in the blockchan). 0 stands for unconfirmed mempool transaction. Default is nil ("no info").
65 66 67 |
# File 'lib/btcruby/transaction.rb', line 65 def confirmations @confirmations end |
#data ⇒ Object (readonly)
Binary representation of the transaction in wire format (aka payload).
38 39 40 |
# File 'lib/btcruby/transaction.rb', line 38 def data @data end |
#dictionary ⇒ Object (readonly)
Dictionary representation of transaction ready to be encoded in JSON, PropertyList etc.
41 42 43 |
# File 'lib/btcruby/transaction.rb', line 41 def dictionary @dictionary end |
#fee ⇒ Object
If available, returns mining fee paid by this transaction.
If set, inputs_amount is updated as (outputs_amount + fee).
Default is nil.
70 71 72 |
# File 'lib/btcruby/transaction.rb', line 70 def fee @fee end |
#inputs ⇒ Object
List of TransactionInputs. See also #add_input and #remove_all_inputs.
18 19 20 |
# File 'lib/btcruby/transaction.rb', line 18 def inputs @inputs end |
#inputs_amount ⇒ Object
If available, returns total amount of all inputs.
If set, fee is updated as (inputs_amount - outputs_amount).
Default is nil.
75 76 77 |
# File 'lib/btcruby/transaction.rb', line 75 def inputs_amount @inputs_amount end |
#lock_time ⇒ Object
Lock time. Either a block height or a unix timestamp. Default is 0.
25 26 27 |
# File 'lib/btcruby/transaction.rb', line 25 def lock_time @lock_time end |
#outputs ⇒ Object
List of TransactionOutputs. See also #add_output and #remove_all_outputs.
21 22 23 |
# File 'lib/btcruby/transaction.rb', line 21 def outputs @outputs end |
#outputs_amount ⇒ Object (readonly)
Total amount on all outputs (not including fees). Always available since outputs contain their amounts.
79 80 81 |
# File 'lib/btcruby/transaction.rb', line 79 def outputs_amount @outputs_amount end |
#transaction_hash ⇒ Object (readonly)
32-byte transaction hash
31 32 33 |
# File 'lib/btcruby/transaction.rb', line 31 def transaction_hash @transaction_hash end |
#transaction_id ⇒ Object (readonly)
Hexadecimal transaction ID with bytes reversed. Used by Chain.com, Blockchain.info, Blockr.io.
35 36 37 |
# File 'lib/btcruby/transaction.rb', line 35 def transaction_id @transaction_id end |
#version ⇒ Object
Version of the transaction. Default is CURRENT_VERSION.
15 16 17 |
# File 'lib/btcruby/transaction.rb', line 15 def version @version end |
Class Method Details
.compute_fee(size, fee_rate: DEFAULT_FEE_RATE) ⇒ Object
Compute a fee for a transaction of a given size with a specified per-KB fee rate. By default uses built-in DEFAULT_FEE_RATE. Makes sure that whole number of fee_rate amounts are paid.
430 431 432 433 434 435 |
# File 'lib/btcruby/transaction.rb', line 430 def self.compute_fee(size, fee_rate: DEFAULT_FEE_RATE) return 0 if fee_rate <= 0 fee = fee_rate*size/1000 # according to Bitcoin Core as of March 15, 2015. fee = fee_rate if fee == 0 fee end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
472 473 474 475 |
# File 'lib/btcruby/transaction.rb', line 472 def ==(other) return false if other == nil self.data == other.data end |
#add_input(txin) ⇒ Object
Adds another input to the transaction.
208 209 210 211 212 213 214 215 216 217 |
# File 'lib/btcruby/transaction.rb', line 208 def add_input(txin) raise ArgumentError, "Input is missing" if !txin if !(txin.transaction == nil || txin.transaction == self) raise ArgumentError, "Can't add an input to a transaction when it references another transaction" # sanity check end txin.transaction = self txin.index = @inputs.size @inputs << txin self end |
#add_output(txout) ⇒ Object
219 220 221 222 223 224 225 226 227 228 |
# File 'lib/btcruby/transaction.rb', line 219 def add_output(txout) raise ArgumentError, "Output is missing" if !txout if !(txout.transaction == nil || txout.transaction == self) raise ArgumentError, "Can't add an output to a transaction when it references another transaction" # sanity check end txout.transaction = self txout.index = @outputs.size @outputs << txout self end |
#coinbase? ⇒ Boolean
Returns true if this transaction is a coinbase transaction.
178 179 180 |
# File 'lib/btcruby/transaction.rb', line 178 def coinbase? self.inputs.size == 1 && self.inputs[0].coinbase? end |
#compute_fee(fee_rate: DEFAULT_FEE_RATE) ⇒ Object
Compute a fee for this transaction with a specified per-KB fee rate. By default uses built-in DEFAULT_FEE_RATE.
439 440 441 |
# File 'lib/btcruby/transaction.rb', line 439 def compute_fee(fee_rate: DEFAULT_FEE_RATE) self.class.compute_fee(self.data.bytesize, fee_rate: fee_rate) end |
#dup ⇒ Object
Makes a deep copy of a transaction (all inputs and outputs are copied too).
459 460 461 462 463 464 465 466 467 468 469 470 |
# File 'lib/btcruby/transaction.rb', line 459 def dup Transaction.new(version: @version, inputs: (@inputs || []).map{|txin|txin.dup}, outputs: (@outputs || []).map{|txout|txout.dup}, lock_time: @lock_time, block_hash: @block_hash, block_height: @block_height, block_time: @block_time, confirmations: @confirmations, fee: @fee, inputs_amount: @inputs_amount) end |
#init_with_components(version: CURRENT_VERSION, inputs: [], outputs: [], lock_time: 0) ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/btcruby/transaction.rb', line 119 def init_with_components(version: CURRENT_VERSION, inputs: [], outputs: [], lock_time: 0) @version = version || CURRENT_VERSION @inputs = inputs || [] @outputs = outputs || [] @lock_time = lock_time || 0 @inputs.each_with_index do |txin, i| txin.transaction = self txin.index = i end @outputs.each_with_index do |txout, i| txout.transaction = self txout.index = i end end |
#init_with_dictionary(dict) ⇒ Object
167 168 169 170 171 172 173 174 175 |
# File 'lib/btcruby/transaction.rb', line 167 def init_with_dictionary(dict) version = dict["ver"] || CURRENT_VERSION lock_time = dict["lock_time"] || 0 txins = dict["in"].map { |i| TransactionInput.new(dictionary: i) } txouts = dict["out"].map {|o| TransactionOutput.new(dictionary: o) } init_with_components(version: version, inputs: txins, outputs: txouts, lock_time: lock_time) end |
#init_with_stream(stream) ⇒ Object
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/btcruby/transaction.rb', line 134 def init_with_stream(stream) raise ArgumentError, "Stream is missing" if !stream if stream.eof? raise ArgumentError, "Can't parse transaction from stream because it is already closed." end if !(version = BTC::WireFormat.read_int32le(stream: stream).first) raise ArgumentError, "Failed to read version prefix from the stream." end if !(inputs_count = BTC::WireFormat.read_varint(stream: stream).first) raise ArgumentError, "Failed to read inputs count from the stream." end txins = (0...inputs_count).map do TransactionInput.new(stream: stream) end if !(outputs_count = BTC::WireFormat.read_varint(stream: stream).first) raise ArgumentError, "Failed to read outputs count from the stream." end txouts = (0...outputs_count).map do TransactionOutput.new(stream: stream) end if !(lock_time = BTC::WireFormat.read_uint32le(stream: stream).first) raise ArgumentError, "Failed to read lock_time from the stream." end init_with_components(version: version, inputs: txins, outputs: txouts, lock_time: lock_time) end |
#inspect ⇒ Object
478 479 480 481 482 483 484 485 |
# File 'lib/btcruby/transaction.rb', line 478 def inspect %{#<#{self.class.name}:#{transaction_id}} + %{ v#{version}} + (lock_time > 0 ? %{ lock_time:#{lock_time} #{lock_time > LOCKTIME_THRESHOLD ? 'sec' : 'block'}} : "") + %{ inputs:[#{inputs.map{|i|i.inspect(:light)}.join(", ")}]} + %{ outputs:[#{outputs.map{|o|o.inspect(:light)}.join(", ")}]} + %{>} end |
#open_assets_transaction? ⇒ Boolean
Returns true if this transaction contains an Open Assets marker.
Does not perform expensive validation.
Use this method to quickly filter out non-asset transactions.
185 186 187 |
# File 'lib/btcruby/transaction.rb', line 185 def open_assets_transaction? self.outputs.any? {|txout| txout.script.open_assets_marker? } end |
#remove_all_inputs ⇒ Object
230 231 232 233 234 235 236 237 238 |
# File 'lib/btcruby/transaction.rb', line 230 def remove_all_inputs return if !@inputs @inputs.each do |txin| txin.transaction = nil txin.index = nil end @inputs = [] self end |
#remove_all_outputs ⇒ Object
240 241 242 243 244 245 246 247 248 |
# File 'lib/btcruby/transaction.rb', line 240 def remove_all_outputs return if !@outputs @outputs.each do |txout| txout.transaction = nil txout.index = nil end @outputs = [] self end |
#signature_hash(input_index: nil, output_script: nil, hash_type: BTC::SIGHASH_ALL, version: 0, amount: 0) ⇒ Object
Hash for signing a transaction. You should specify an input index, output script of the previous transaction for that input, and an optional hash type (default is SIGHASH_ALL).
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/btcruby/transaction.rb', line 335 def signature_hash(input_index: nil, output_script: nil, hash_type: BTC::SIGHASH_ALL, version: 0, amount: 0) raise ArgumentError, "Should specify input_index in Transaction#signature_hash." if !input_index raise ArgumentError, "Should specify output_script in Transaction#signature_hash." if !output_script raise ArgumentError, "Should specify hash_type in Transaction#signature_hash." if !hash_type # Create a temporary copy of the transaction to apply modifications to it. tx = self.dup # Note: BitcoinQT returns a 256-bit little-endian number 1 in such case, # but it does not matter because it would crash before that in CScriptCheck::operator()(). # We normally won't enter this condition if script machine is instantiated # with transaction and input index, but it's better to check anyway. if (input_index >= tx.inputs.size) raise ArgumentError, "Input index is out of bounds for transaction: #{input_index} >= #{tx.inputs.size}" end # In case concatenating two scripts ends up with two codeseparators, # or an extra one at the end, this prevents all those possible incompatibilities. # Note: this normally never happens because there is no use for OP_CODESEPARATOR. # But we have to do that cleanup anyway to not break on rare transaction that use that for lulz. # Also: we modify the same subscript which is used several times for multisig check, # but that's what BitcoinQT does as well. output_script.delete_opcode(BTC::OP_CODESEPARATOR) # Blank out other inputs' signature scripts # and replace our input script with a subscript (which is typically a full # output script from the previous transaction). tx.inputs.each do |txin| txin.signature_script = BTC::Script.new end tx.inputs[input_index].signature_script = output_script # Blank out some of the outputs depending on BTCSignatureHashType # Default is SIGHASH_ALL - all inputs and outputs are signed. if (hash_type & BTC::SIGHASH_OUTPUT_MASK) == BTC::SIGHASH_NONE # Wildcard payee - we can pay anywhere. tx.remove_all_outputs # Blank out others' input sequence numbers to let others update transaction at will. tx.inputs.each_with_index do |txin, i| if i != input_index tx.inputs[i].sequence = 0 end end # Single mode assumes we sign an output at the same index as an input. # Outputs before the one we need are blanked out. All outputs after are simply removed. elsif (hash_type & BTC::SIGHASH_OUTPUT_MASK) == BTC::SIGHASH_SINGLE # Only lock-in the txout payee at same index as txin. output_index = input_index; # If output_index is out of bounds, BitcoinQT is returning a 256-bit little-endian 0x01 instead of failing with error. # We should do the same to stay compatible. if output_index >= tx.outputs.size return "\x01" + "\x00"*31 end # All outputs before the one we need are blanked out. All outputs after are simply removed. # This is equivalent to replacing outputs with (i-1) empty outputs and a i-th original one. my_output = tx.outputs[output_index] tx.remove_all_outputs (0...output_index).each do |i| tx.add_output(BTC::TransactionOutput.new) end tx.add_output(my_output) # Blank out others' input sequence numbers to let others update transaction at will. tx.inputs.each_with_index do |txin, i| if i != input_index txin.sequence = 0 end end end # if hashtype is none or single # Blank out other inputs completely. This is not recommended for open transactions. if (hash_type & BTC::SIGHASH_ANYONECANPAY) != 0 input = tx.inputs[input_index] tx.remove_all_inputs tx.add_input(input) end # Important: we have to hash transaction together with its hash type. # Hash type is appended as a little endian uint32 unlike 1-byte suffix of the signature. data = tx.data + BTC::WireFormat.encode_uint32le(hash_type) hash = BTC.hash256(data) # puts "" # puts "SIGHASH[#{self.transaction_id}, input #{input_index}, hashtype 0x#{hash_type.to_s(16)}]: hash = #{BTC.id_from_hash(hash)}; tx = " + tx.inspect # puts "" return hash end |
#to_h ⇒ Object
Returns dictionary representation of the transaction.
444 445 446 |
# File 'lib/btcruby/transaction.rb', line 444 def to_h self.dictionary end |
#to_hex ⇒ Object
Returns hex representation of the transaction.
454 455 456 |
# File 'lib/btcruby/transaction.rb', line 454 def to_hex BTC.to_hex(self.data) end |
#to_s ⇒ Object
Returns hex representation of the transaction.
449 450 451 |
# File 'lib/btcruby/transaction.rb', line 449 def to_s to_hex end |