lib/bitcoin/protocol/tx.rb in bitcoin-ruby-0.0.5 vs lib/bitcoin/protocol/tx.rb in bitcoin-ruby-0.0.6
- old
+ new
@@ -23,26 +23,29 @@
attr_accessor :ver
# lock time
attr_accessor :lock_time
+ # parsed / evaluated input scripts cached for later use
+ attr_reader :scripts
+
alias :inputs :in
alias :outputs :out
# compare to another tx
def ==(other)
@hash == other.hash
end
# return the tx hash in binary format
def binary_hash
- [@hash].pack("H*").reverse
+ @binary_hash ||= [@hash].pack("H*").reverse
end
# create tx from raw binary +data+
def initialize(data=nil)
- @ver, @lock_time, @in, @out = 1, 0, [], []
+ @ver, @lock_time, @in, @out, @scripts = 1, 0, [], [], []
parse_data_from_io(data) if data
end
# generate the tx hash for given +payload+ in hex format
def hash_from_payload(payload)
@@ -100,28 +103,32 @@
SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 }
# generate a signature hash for input +input_idx+.
# either pass the +outpoint_tx+ or the +script_pubkey+ directly.
- def signature_hash_for_input(input_idx, outpoint_tx, script_pubkey=nil, hash_type=nil, drop_sigs=nil, script=nil)
+ def signature_hash_for_input(input_idx, subscript, hash_type=nil)
# https://github.com/bitcoin/bitcoin/blob/e071a3f6c06f41068ad17134189a4ac3073ef76b/script.cpp#L834
# http://code.google.com/p/bitcoinj/source/browse/trunk/src/com/google/bitcoin/core/Script.java#318
# https://en.bitcoin.it/wiki/OP_CHECKSIG#How_it_works
# https://github.com/bitcoin/bitcoin/blob/c2e8c8acd8ae0c94c70b59f55169841ad195bb99/src/script.cpp#L1058
# https://en.bitcoin.it/wiki/OP_CHECKSIG
+ # Note: BitcoinQT checks if input_idx >= @in.size and returns 1 with an error message.
+ # But this check is never actually useful because BitcoinQT would crash
+ # right before VerifyScript if input index is out of bounds (inside CScriptCheck::operator()()).
+ # That's why we don't need to do such a check here.
+ #
+ # However, if you look at the case SIGHASH_TYPE[:single] below, we must
+ # return 1 because it's possible to have more inputs than outputs and BitcoinQT returns 1 as well.
return "\x01".ljust(32, "\x00") if input_idx >= @in.size # ERROR: SignatureHash() : input_idx=%d out of range
hash_type ||= SIGHASH_TYPE[:all]
pin = @in.map.with_index{|input,idx|
if idx == input_idx
- script_pubkey ||= outpoint_tx.out[ input.prev_out_index ].pk_script
- script_pubkey = script if script # force binary aa script
- script_pubkey = Bitcoin::Script.drop_signatures(script_pubkey, drop_sigs) if drop_sigs # array of signature to drop (slow)
- #p Bitcoin::Script.new(script_pubkey).to_string
- input.to_payload(script_pubkey)
+ subscript = subscript.out[ input.prev_out_index ].script if subscript.respond_to?(:out) # legacy api (outpoint_tx)
+ input.to_payload(subscript)
else
case (hash_type & 0x1f)
when SIGHASH_TYPE[:none]; input.to_payload("", "\x00\x00\x00\x00")
when SIGHASH_TYPE[:single]; input.to_payload("", "\x00\x00\x00\x00")
else; input.to_payload("")
@@ -150,29 +157,35 @@
Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
end
# verify input signature +in_idx+ against the corresponding
# output in +outpoint_tx+
- def verify_input_signature(in_idx, outpoint_tx, block_timestamp=Time.now.to_i)
+ # outpoint
+ def verify_input_signature(in_idx, outpoint_tx_or_script, block_timestamp=Time.now.to_i)
outpoint_idx = @in[in_idx].prev_out_index
script_sig = @in[in_idx].script_sig
- script_pubkey = outpoint_tx.out[outpoint_idx].pk_script
- script = script_sig + script_pubkey
+
+ # If given an entire previous transaction, take the script from it
+ script_pubkey = if outpoint_tx_or_script.respond_to?(:out)
+ outpoint_tx_or_script.out[outpoint_idx].pk_script
+ else
+ # Otherwise, it's already a script.
+ outpoint_tx_or_script
+ end
- Bitcoin::Script.new(script).run(block_timestamp) do |pubkey,sig,hash_type,drop_sigs,script|
- # this IS the checksig callback, must return true/false
- hash = signature_hash_for_input(in_idx, outpoint_tx, nil, hash_type, drop_sigs, script)
- #hash = signature_hash_for_input(in_idx, nil, script_pubkey, hash_type, drop_sigs, script)
+ @scripts[in_idx] = Bitcoin::Script.new(script_sig, script_pubkey)
+ @scripts[in_idx].run(block_timestamp) do |pubkey,sig,hash_type,subscript|
+ hash = signature_hash_for_input(in_idx, subscript, hash_type)
Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
end
end
# convert to ruby hash (see also #from_hash)
def to_hash(options = {})
@hash ||= hash_from_payload(to_payload)
h = {
- 'hash' => @hash, 'ver' => @ver,
+ 'hash' => @hash, 'ver' => @ver, # 'nid' => normalized_hash,
'vin_sz' => @in.size, 'vout_sz' => @out.size,
'lock_time' => @lock_time, 'size' => (@payload ||= to_payload).bytesize,
'in' => @in.map{|i| i.to_hash(options) },
'out' => @out.map{|o| o.to_hash(options) }
}
@@ -213,18 +226,51 @@
def self.from_file(path); new( Bitcoin::Protocol.read_binary_file(path) ); end
# read json block from a file
def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
- def validator(store, block = nil)
- @validator ||= Bitcoin::Validation::Tx.new(self, store, block)
+ # Get a Bitcoin::Validation object to validate this block. It needs a +store+
+ # to validate against, a block to validate tx chains inside one block, and
+ # optionally takes the +block_validator+ as an optimization.
+ def validator(store, block = nil, block_validator = nil)
+ @validator ||= Bitcoin::Validation::Tx.new(self, store, block, block_validator)
end
def size
payload.bytesize
end
+
+ # Checks if transaction is final taking into account height and time
+ # of a block in which it is located (or about to be included if it's unconfirmed tx).
+ def is_final?(block_height, block_time)
+ # No time lock - tx is final.
+ return true if lock_time == 0
+ # Time based nLockTime implemented in 0.1.6
+ # If lock_time is below the magic threshold treat it as a block height.
+ # If lock_time is above the threshold, it's a unix timestamp.
+ return true if lock_time < (lock_time < Bitcoin::LOCKTIME_THRESHOLD ? block_height : block_time)
+
+ inputs.each{|input| return false if !input.is_final? }
+
+ return true
+ end
+
+ def legacy_sigops_count
+ # Note: input scripts normally never have any opcodes since every input script
+ # can be statically reduced to a pushdata-only script.
+ # However, anyone is allowed to create a non-standard transaction with any opcodes in the inputs.
+ count = 0
+ self.in.each do |txin|
+ count += Bitcoin::Script.new(txin.script_sig).sigops_count_accurate(false)
+ end
+ self.out.each do |txout|
+ count += Bitcoin::Script.new(txout.pk_script).sigops_count_accurate(false)
+ end
+ count
+ end
+
DEFAULT_BLOCK_PRIORITY_SIZE = 27000
def minimum_relay_fee; calculate_minimum_fee(allow_free=true, :relay); end
def minimum_block_fee; calculate_minimum_fee(allow_free=true, :block); end
@@ -239,25 +285,41 @@
# * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000
# to be considered to fall into this category. We don't want to encourage sending
# multiple transactions instead of one big transaction to avoid fees.
# * If we are creating a transaction we allow transactions up to 1,000 bytes
# to be considered safe and assume they can likely make it into this section.
- min_fee = 0 if tx_size < (mode == :block ? 1_000 : DEFAULT_BLOCK_PRIORITY_SIZE - 1_000)
+ min_fee = 0 if tx_size < (mode == :block ? Bitcoin.network[:free_tx_bytes] : DEFAULT_BLOCK_PRIORITY_SIZE - 1_000)
end
# This code can be removed after enough miners have upgraded to version 0.9.
# Until then, be safe when sending and require a fee if any output is less than CENT
if min_fee < base_fee && mode == :block
- outputs.each{|output| (min_fee = base_fee; break) if output.value < Bitcoin::CENT }
+ outputs.each do |output|
+ if output.value < Bitcoin.network[:dust]
+ # If per dust fee, then we add min fee for each output less than dust.
+ # Otherwise, we set to min fee if there is any output less than dust.
+ if Bitcoin.network[:per_dust_fee]
+ min_fee += base_fee
+ else
+ min_fee = base_fee
+ break
+ end
+ end
+ end
end
min_fee = Bitcoin::network[:max_money] unless min_fee.between?(0, Bitcoin::network[:max_money])
min_fee
end
def is_coinbase?
inputs.size == 1 and inputs.first.coinbase?
end
+
+ def normalized_hash
+ signature_hash_for_input(-1, nil, SIGHASH_TYPE[:all]).unpack("H*")[0]
+ end
+ alias :nhash :normalized_hash
end
end
end