Class: Pochette::Backends::Toshi
- Inherits:
-
Object
- Object
- Pochette::Backends::Toshi
- Defined in:
- lib/pochette_toshi.rb
Overview
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.
Instance Method Summary collapse
- #balances_for(addresses, confirmations) ⇒ Object
- #balances_for_helper(addresses, confirmations) ⇒ Object
- #block_height ⇒ Object
- #incoming_for(addresses, min_date) ⇒ Object
- #incoming_for_helper(addresses, min_date) ⇒ Object
-
#initialize(options) ⇒ Toshi
constructor
A new instance of Toshi.
- #list_transactions(txids) ⇒ Object
- #list_transactions_helper(hashes) ⇒ Object
-
#list_unspent(addresses) ⇒ Object
Performs the low level queries to the toshi database and returns a JSON structure for the unspents and the transactions.
- #list_unspent_helper(addresses) ⇒ Object
- #pushtx(hex) ⇒ Object
- #query(sql) ⇒ Object
- #sanitize_list(list) ⇒ Object
Constructor Details
#initialize(options) ⇒ Toshi
Returns a new instance of Toshi.
18 19 20 |
# File 'lib/pochette_toshi.rb', line 18 def initialize() self.class.db ||= Sequel.postgres() end |
Instance Method Details
#balances_for(addresses, confirmations) ⇒ Object
60 61 62 63 64 |
# 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) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# 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 ⇒ Object
214 215 216 |
# 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) ⇒ Object
22 23 24 25 26 |
# 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) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# 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) ⇒ Object
167 168 169 170 171 172 173 |
# 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) ⇒ Object
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# 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) ⇒ Object
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.
141 142 143 144 145 146 147 |
# 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) ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# 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 |
#pushtx(hex) ⇒ Object
218 219 220 221 222 223 |
# File 'lib/pochette_toshi.rb', line 218 def pushtx(hex) domain = Pochette.testnet ? 'testnet3' : 'bitcoin' response = RestClient.post "https://#{domain}.toshi.io/api/v0/transactions", {"hex" => hex}.to_json, content_type: :json, accept: :json Oj.load(response)['hash'] end |
#query(sql) ⇒ Object
225 226 227 228 229 |
# File 'lib/pochette_toshi.rb', line 225 def query(sql) self.db.synchronize do |conn| conn.exec(sql).values end end |
#sanitize_list(list) ⇒ Object
231 232 233 |
# File 'lib/pochette_toshi.rb', line 231 def sanitize_list(list) list.collect{|a| "'#{a}'"}.join(',') end |