class Beowulf::Transaction
Constants
- VALID_OPTIONS
Public Class Methods
new(options = {})
click to toggle source
# File lib/beowulf/transaction.rb, line 17 def initialize(options = {}) #puts 'Transaction.initialize.options1', options.to_json options = options.dup options.each do |k, v| k = k.to_sym if VALID_OPTIONS.include?(k.to_sym) options.delete(k) send("#{k}=", v) end end #puts 'Transaction.initialize.options2', options.to_json @chain ||= :beowulf @chain = @chain.to_sym @chain_id = chain_id options[:chain_id] @url = options[:url] || url @operations = options[:operations] || [] @self_logger = false @logger = if options[:logger].nil? @self_logger = true Beowulf.logger else options[:logger] end unless NETWORK_CHAIN_IDS.include? @chain_id warning "Unknown chain id: #{@chain_id}" end if !!wif && !!private_key raise TransactionError, "Do not pass both wif and private_key. That's confusing." end if !!wif @private_key = Bitcoin::Key.from_base58 wif end @ref_block_num ||= nil @ref_block_prefix ||= nil @expiration ||= nil @extensions ||= [] @created_time ||= Time.now.utc.to_i @immutable_expiration = !!@expiration options = options.merge( url: @url, chain: @chain, pool_size: 1, persist: false, reuse_ssl_sessions: false ) @api = Api.new(options) @network_broadcast_api = NetworkBroadcastApi.new(options) @use_condenser_namespace = if options.keys.include? :use_condenser_namespace options[:use_condenser_namespace] else true end ObjectSpace.define_finalizer(self, self.class.finalize(@api, @network_broadcast_api, @self_logger, @logger)) end
Private Class Methods
finalize(api, network_broadcast_api, self_logger, logger)
click to toggle source
# File lib/beowulf/transaction.rb, line 317 def self.finalize(api, network_broadcast_api, self_logger, logger) proc { if !!api && !api.stopped? puts "DESTROY: #{api.inspect}" if ENV['LOG'] == 'TRACE' api.shutdown api = nil end if !!network_broadcast_api && !network_broadcast_api.stopped? puts "DESTROY: #{network_broadcast_api.inspect}" if ENV['LOG'] == 'TRACE' network_broadcast_api.shutdown network_broadcast_api = nil end begin if self_logger if !!logger && defined?(logger.close) if defined?(logger.closed?) logger.close unless logger.closed? end end end rescue IOError, NoMethodError => _; end } end
Public Instance Methods
chain_id(chain_id = nil)
click to toggle source
# File lib/beowulf/transaction.rb, line 82 def chain_id(chain_id = nil) return chain_id if !!chain_id case chain.to_s.downcase.to_sym when :beowulf then NETWORKS_BEOWULF_CHAIN_ID # when :test then NETWORKS_TEST_CHAIN_ID end end
extensions()
click to toggle source
# File lib/beowulf/transaction.rb, line 137 def extensions @extensions = @extensions.map do |ex| case ex when Extension then ex else; Extension.new(ex) end end end
extensions=(extensions)
click to toggle source
# File lib/beowulf/transaction.rb, line 146 def extensions=(extensions) @extensions = extensions end
inspect()
click to toggle source
# File lib/beowulf/transaction.rb, line 167 def inspect properties = %w( url ref_block_num ref_block_prefix expiration chain use_condenser_namespace immutable_expiration payload ).map do |prop| if !!(v = instance_variable_get("@#{prop}")) "@#{prop}=#{v}" end end.compact.join(', ') "#<#{self.class.name} [#{properties}]>" end
operations()
click to toggle source
# File lib/beowulf/transaction.rb, line 124 def operations @operations = @operations.map do |op| case op when Operation then op else; Operation.new(op) end end end
operations=(operations)
click to toggle source
# File lib/beowulf/transaction.rb, line 133 def operations=(operations) @operations = operations end
process(broadcast = false)
click to toggle source
# File lib/beowulf/transaction.rb, line 98 def process(broadcast = false) prepare if broadcast loop do response = broadcast_payload(payload) if !!response.error parser = ErrorParser.new(response) if parser.can_reprepare? debug "Error code: #{parser}, repreparing transaction ..." prepare redo end end return response end else self end ensure shutdown end
shutdown()
click to toggle source
# File lib/beowulf/transaction.rb, line 150 def shutdown @api.shutdown if !!@api @network_broadcast_api.shutdown if !!@network_broadcast_api if @self_logger if !!@logger && defined?(@logger.close) if defined?(@logger.closed?) @logger.close unless @logger.closed? end end end end
url()
click to toggle source
# File lib/beowulf/transaction.rb, line 91 def url case chain.to_s.downcase.to_sym when :beowulf then NETWORKS_BEOWULF_DEFAULT_NODE # when :test then NETWORKS_TEST_DEFAULT_NODE end end
use_condenser_namespace?()
click to toggle source
# File lib/beowulf/transaction.rb, line 163 def use_condenser_namespace? !!@use_condenser_namespace end
Private Instance Methods
broadcast_payload(payload)
click to toggle source
# File lib/beowulf/transaction.rb, line 180 def broadcast_payload(payload) puts "Transaction.broadcast_payload:", payload.to_json if use_condenser_namespace? @api.broadcast_transaction_synchronous(payload) else @network_broadcast_api.broadcast_transaction_synchronous(trx: payload) end end
canonical?(sig)
click to toggle source
# File lib/beowulf/transaction.rb, line 306 def canonical?(sig) sig = sig.unpack('C*') !( ((sig[0] & 0x80 ) != 0) || ( sig[0] == 0 ) || ((sig[1] & 0x80 ) != 0) || ((sig[32] & 0x80 ) != 0) || ( sig[32] == 0 ) || ((sig[33] & 0x80 ) != 0) ) end
digest()
click to toggle source
# File lib/beowulf/transaction.rb, line 283 def digest Digest::SHA256.digest(to_bytes) end
payload()
click to toggle source
# File lib/beowulf/transaction.rb, line 189 def payload @payload ||= { expiration: @expiration.strftime('%Y-%m-%dT%H:%M:%S'), ref_block_num: @ref_block_num, ref_block_prefix: @ref_block_prefix, operations: operations.map { |op| op.payload }, extensions: extensions.map { |ex| ex.payload }, created_time: @created_time, signatures: [hexlify(signature)] } end
prepare()
click to toggle source
# File lib/beowulf/transaction.rb, line 201 def prepare raise TransactionError, "No wif or private key." unless !!@wif || !!@private_key @payload = nil while @expiration.nil? && @ref_block_num.nil? && @ref_block_prefix.nil? @api.get_dynamic_global_properties do |properties, error| if !!error raise TransactionError, "Unable to prepare transaction.", error end @properties = properties end # You can actually go back as far as the TaPoS buffer will allow, which # is something like 50,000 blocks. block_number = @properties.last_irreversible_block_num @api.get_block(block_number) do |block, error| if !!error ap error if defined?(ap) && ENV['DEBUG'] == 'true' raise TransactionError, "Unable to prepare transaction: #{error.message || 'Unknown cause.'}" end if !!block && !!block.previous @ref_block_num = (block_number - 1) & 0xFFFF @ref_block_prefix = unhexlify(block.previous[8..-1]).unpack('V*')[0] # The expiration allows for transactions to expire if they are not # included into a block by that time. Always update it to the current # time + EXPIRE_IN_SECS. # block_time = Time.parse(@properties.time + 'Z') @expiration ||= block_time + EXPIRE_IN_SECS else # Suspect this happens when there are microforks, but it should be # rare, especially since we're asking for the last irreversible # block. if block.nil? warning "Block missing while trying to prepare transaction, retrying ..." else debug block if %w(DEBUG TRACE).include? ENV['LOG'] warning "Block structure while trying to prepare transaction, retrying ..." end @expiration = nil unless @immutable_expiration end end end self end
signature()
click to toggle source
May not find all non-canonicals, see: github.com/lian/bitcoin-ruby/issues/196
# File lib/beowulf/transaction.rb, line 288 def signature public_key_hex = @private_key.pub ec = Bitcoin::OpenSSL_EC digest_hex = digest.freeze # puts "digest_hex:", hexlify(digest_hex) count = 0 loop do count += 1 debug "#{count} attempts to find canonical signature" if count % 40 == 0 sig = ec.sign_compact(digest_hex, @private_key.priv, public_key_hex, false) next if public_key_hex != ec.recover_compact(digest_hex, sig) return sig if canonical? sig end end
to_bytes()
click to toggle source
# File lib/beowulf/transaction.rb, line 257 def to_bytes bytes = unhexlify(@chain_id) bytes << pakS(@ref_block_num) # 16-bit, 4 Hex bytes << pakI(@ref_block_prefix) # 32-bit, 8 Hex bytes << pakI(@expiration.to_i) # 32-bit, 8 Hex bytes << pakC(operations.size) # 8-bit, 2 Hex operations.each do |op| bytes << op.to_bytes # n-bit ... end if extensions.empty? bytes << 0x0000 # extensions # 16-bit, 4 Hex else bytes << pakC(extensions.size) # 8-bit, 2 Hex extensions.each do |ex| bytes << ex.to_bytes end end bytes << pakQ(@created_time.to_i) # 64-bit, 16 Hex puts "Transaction.to_bytes:", hexlify(bytes) bytes end