class Radiator::Transaction
Constants
- VALID_OPTIONS
Public Class Methods
new(options = {})
click to toggle source
# File lib/radiator/transaction.rb, line 18 def initialize(options = {}) 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 @url = options[:url] || url @chain ||= 'hive' @chain = @chain.to_sym @chain_id = chain_id options[:chain_id] @operations = options[:operations] || [] @self_logger = false @logger = if options[:logger].nil? @self_logger = true Radiator.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 @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/radiator/transaction.rb, line 320 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/radiator/transaction.rb, line 79 def chain_id(chain_id = nil) return chain_id if !!chain_id case chain.to_s.downcase.to_sym when :steem then NETWORKS_STEEM_CHAIN_ID when :hive database_api = Hive::DatabaseApi.new(url: @url) database_api.get_config do |config| config['HIVE_CHAIN_ID'] end rescue nil || NETWORKS_HIVE_CHAIN_ID when :test then NETWORKS_TEST_CHAIN_ID end end
inspect()
click to toggle source
# File lib/radiator/transaction.rb, line 177 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/radiator/transaction.rb, line 147 def operations @operations = @operations.map do |op| case op when Operation then op else; Operation.new(op.merge(chain: @chain)) end end end
operations=(operations)
click to toggle source
# File lib/radiator/transaction.rb, line 156 def operations=(operations) @operations = operations end
process(broadcast = false)
click to toggle source
# File lib/radiator/transaction.rb, line 101 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 rescue OperationError => e trx_builder, network_api = case @chain.to_sym when :steem then [ Steem::TransactionBuilder.new(wif: @wif), Steem::NetworkBroadcastApi.new(url: @url) ] when :hive then [ Hive::TransactionBuilder.new(wif: @wif), Hive::NetworkBroadcastApi.new(url: @url) ] end raise e if trx_builder.nil? @operations.each do |op| type = op.delete(:type) trx_builder.put({type => op}) end network_api.broadcast_transaction_synchronous(trx_builder.transaction) ensure shutdown end
shutdown()
click to toggle source
# File lib/radiator/transaction.rb, line 160 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/radiator/transaction.rb, line 93 def url case chain.to_s.downcase.to_sym when :steem then NETWORKS_STEEM_DEFAULT_NODE when :hive then NETWORKS_HIVE_DEFAULT_NODE when :test then NETWORKS_TEST_DEFAULT_NODE end end
use_condenser_namespace?()
click to toggle source
# File lib/radiator/transaction.rb, line 173 def use_condenser_namespace? !!@use_condenser_namespace end
Private Instance Methods
broadcast_payload(payload)
click to toggle source
# File lib/radiator/transaction.rb, line 190 def broadcast_payload(payload) 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
See: github.com/steemit/steem/issues/1944
# File lib/radiator/transaction.rb, line 309 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/radiator/transaction.rb, line 286 def digest Digest::SHA256.digest(to_bytes) end
payload()
click to toggle source
# File lib/radiator/transaction.rb, line 198 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: [], signatures: [hexlify(signature)] } end
prepare()
click to toggle source
# File lib/radiator/transaction.rb, line 209 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. # # Note, as of #1215, expiration exactly 'now' will be rejected: # https://github.com/steemit/steem/blob/57451b80d2cf480dcce9b399e48e56aa7af1d818/libraries/chain/database.cpp#L2870 # https://github.com/steemit/steem/issues/1215 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/radiator/transaction.rb, line 291 def signature public_key_hex = @private_key.pub ec = Bitcoin::OpenSSL_EC digest_hex = digest.freeze 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/radiator/transaction.rb, line 269 def to_bytes bytes = unhexlify(@chain_id) bytes << pakS(@ref_block_num) bytes << pakI(@ref_block_prefix) bytes << pakI(@expiration.to_i) bytes << pakC(operations.size) operations.each do |op| bytes << op.to_bytes end # FIXME Should pakC(0) instead? bytes << 0x00 # extensions bytes end