class Pochette::Backends::Toshi
This class is not properly tested. Be careful when changing anything, and/or make sure you can run and assert your changes with a local copy of a toshi testnet database.
Public Class Methods
new(options)
click to toggle source
# File lib/pochette_toshi.rb, line 18 def initialize(options) self.class.db ||= Sequel.postgres(options) end
Public Instance Methods
balances_for(addresses, confirmations)
click to toggle source
# File lib/pochette_toshi.rb, line 60 def balances_for(addresses, confirmations) addresses.in_groups_of(500, false).reduce({}) do |accum, group| accum.merge!(balances_for_helper(group, confirmations)) end end
balances_for_helper(addresses, confirmations)
click to toggle source
# File lib/pochette_toshi.rb, line 66 def balances_for_helper(addresses, confirmations) # Addresses have denormalized sent and received columns for all # transactions. We must then calculate which portion # of that is unconfirmed in order to get the 'confirmed' balances. # And to get the 'unconfirmed' balances we also need to fetch # ledger entries for transactions that were not added to a block yet. addresses_sql = sanitize_list(addresses) confirmed_id_to_address = {} unconfirmed_id_to_address = {} result = addresses.reduce({}) do |accum, address| accum[address] = [0,0,0,0,0,0] accum end query(%{ SELECT id, address, total_received, total_sent FROM addresses WHERE address in (#{addresses_sql}) }).each do |id, address, received, sent| confirmed_id_to_address[id] = address received = received.to_d / 1_0000_0000 sent = sent.to_d / 1_0000_0000 balance = received - sent result[address] = [received, sent, balance, received, sent, balance] end # Now we take the previous 1 confirmation balances and substract # what was below threshold to get the 'confirmed' balances. query(%{ SELECT ale.address_id, sum(CASE WHEN ale.amount > 0 THEN ale.amount ELSE 0 END) as received, sum(CASE WHEN ale.amount < 0 THEN ale.amount ELSE 0 END) as sent FROM address_ledger_entries ale INNER JOIN addresses a ON a.id = ale.address_id AND a.address in (#{addresses_sql}) INNER JOIN transactions t ON t.id = ale.transaction_id AND t.pool = 1 AND t.height > #{block_height - confirmations + 1} GROUP BY ale.address_id }).each do |id, received, sent| row = result[confirmed_id_to_address[id]] row[0] = row[0] - (received.to_d / 1_0000_0000) row[1] = row[1] - (sent.to_d.abs / 1_0000_0000) row[2] = row[0] - row[1] end query(%{SELECT id, address FROM unconfirmed_addresses WHERE address in (#{addresses_sql}) }).each do |id, address| unconfirmed_id_to_address[id] = address end # And then we also add the unconfirmed stuff to the already # cached 1 confirmation balances query(%{ SELECT ale.address_id, sum(CASE WHEN ale.amount > 0 THEN ale.amount ELSE 0 END) as received, sum(CASE WHEN ale.amount < 0 THEN ale.amount ELSE 0 END) as sent FROM unconfirmed_ledger_entries ale INNER JOIN unconfirmed_addresses a ON a.id = ale.address_id AND a.address in (#{addresses_sql}) INNER JOIN unconfirmed_transactions t ON t.id = ale.transaction_id AND t.pool = 1 GROUP BY ale.address_id }).each do |id, received, sent| row = result[unconfirmed_id_to_address[id]] row[3] += (received.to_d / 1_0000_0000) row[4] += (sent.to_d.abs / 1_0000_0000) row[5] = row[3] - row[4] end return result end
block_height()
click to toggle source
# File lib/pochette_toshi.rb, line 214 def block_height query('select max(height) from blocks where branch = 0')[0][0].to_i end
incoming_for(addresses, min_date)
click to toggle source
# File lib/pochette_toshi.rb, line 22 def incoming_for(addresses, min_date) addresses.in_groups_of(500, false).collect do |group| incoming_for_helper(group, min_date) end.flatten(1) end
incoming_for_helper(addresses, min_date)
click to toggle source
# File lib/pochette_toshi.rb, line 28 def incoming_for_helper(addresses, min_date) addresses_sql = sanitize_list(addresses) current_height = block_height from_block = current_height - ((Time.now - min_date) / 60 / 60 * 6).ceil query(%{ SELECT ale.amount, a.address, t.hsh, (#{current_height + 1} - t.height) as confirmations, o.position, (SELECT string_agg(a2.address,',') as sender FROM address_ledger_entries ale2 INNER JOIN addresses a2 ON a2.id = ale2.address_id WHERE ale2.transaction_id = t.id AND ale2.input_id IS NOT NULL ) as senders FROM address_ledger_entries ale INNER JOIN addresses a ON a.id = ale.address_id AND a.address in (#{addresses_sql}) INNER JOIN transactions t ON t.id = ale.transaction_id AND t.pool = 1 AND t.height > #{from_block} INNER JOIN outputs o ON o.id = ale.output_id AND o.branch = 0 UNION SELECT ale.amount, a.address, t.hsh, 0 as confirmations, o.position, ( SELECT string_agg(a2.address,',') as sender FROM unconfirmed_ledger_entries ale2 INNER JOIN unconfirmed_addresses a2 ON a2.id = ale2.address_id WHERE ale2.transaction_id = t.id AND ale2.input_id IS NOT NULL ) as senders FROM unconfirmed_ledger_entries ale INNER JOIN unconfirmed_addresses a ON a.id = ale.address_id AND a.address in (#{addresses_sql}) INNER JOIN unconfirmed_transactions t ON t.id = ale.transaction_id AND t.pool = 1 INNER JOIN unconfirmed_outputs o ON o.id = ale.output_id }).collect{|a,addr,hsh,confs,pos,sender| [a.to_i, addr, hsh, confs.to_i, pos.to_i, sender] } end
list_transactions(txids)
click to toggle source
# File lib/pochette_toshi.rb, line 167 def list_transactions(txids) transactions = [] txids.in_groups_of(500, false).collect do |group| transactions += list_transactions_helper(group) end transactions end
list_transactions_helper(hashes)
click to toggle source
# File lib/pochette_toshi.rb, line 175 def list_transactions_helper(hashes) transactions_sql = sanitize_list(hashes) transactions = query(%{SELECT * FROM transactions WHERE hsh IN (#{transactions_sql})}) inputs = query(%{SELECT * FROM inputs WHERE hsh IN (#{transactions_sql}) ORDER BY position}) inputs_by_hsh = inputs.reduce({}) do |d, i| d[i[1]] ||= [] d[i[1]] << i d end outputs = query(%{SELECT * FROM outputs WHERE hsh IN (#{transactions_sql}) ORDER BY position}) outputs_by_hsh = outputs.reduce({}) do |d, o| d[o[1]] ||= [] d[o[1]] << o d end transactions_json = transactions.collect do |_, hsh, ver, lock_time| inputs_json = inputs_by_hsh[hsh].collect do |_, _, prev, index, script, seq, pos| { prev_hash: prev, prev_index: index.to_i, sequence: [seq[2..-1]].pack('H*').unpack('V')[0], script_sig: script[2..-1] } end outputs_json = outputs_by_hsh[hsh].collect do |_, _, amount, script| { amount: amount.to_i, script_pubkey: script[2..-1] } end { hash: hsh, version: ver.to_i, lock_time: lock_time.to_i, inputs: inputs_json, bin_outputs: outputs_json } end transactions_json end
list_unspent(addresses)
click to toggle source
Performs the low level queries to the toshi database and returns a JSON structure for the unspents and the transactions. THIS METHOD IS NOT TESTED IN CI!!!! DO NOT JUST REFACTOR THIS WITHOUT TESTING IT LOCALLY AGAINST THE ~20 GB TOSHI TESTNET DATABASE.
# File lib/pochette_toshi.rb, line 141 def list_unspent(addresses) unspents = [] addresses.in_groups_of(500, false).collect do |group| unspents += list_unspent_helper(group) end unspents end
list_unspent_helper(addresses)
click to toggle source
# File lib/pochette_toshi.rb, line 149 def list_unspent_helper(addresses) addresses_sql = sanitize_list(addresses) query(%{ SELECT a.address, o.hsh, o.position, uo.amount, o.script FROM addresses a INNER JOIN unspent_outputs uo ON uo.address_id = a.id AND uo.amount > 5000 LEFT JOIN outputs o ON o.id = uo.output_id WHERE a.address in (#{addresses_sql}) AND NOT EXISTS ( SELECT i.id FROM inputs i LEFT JOIN transactions t ON t.hsh = i.hsh WHERE i.hsh = o.hsh AND i.prev_out = '0000000000000000000000000000000000000000000000000000000000000000' AND t.height > #{block_height - 100} ) }).collect{|a,b,c,d,e| [a,b,c.to_i,d.to_i,e[2..-1]]} end
propagate(hex)
click to toggle source
# File lib/pochette_toshi.rb, line 218 def propagate(hex) domain = Pochette.testnet ? 'testnet3' : 'bitcoin' RestClient.post "https://#{domain}.toshi.io/api/v0/transactions", {"hex" => hex}.to_json, content_type: :json, accept: :json end
query(sql)
click to toggle source
# File lib/pochette_toshi.rb, line 224 def query(sql) self.db.synchronize do |conn| conn.exec(sql).values end end
sanitize_list(list)
click to toggle source
# File lib/pochette_toshi.rb, line 230 def sanitize_list(list) list.collect{|a| "'#{a}'"}.join(',') end