Class: OnChain::BlockChain
- Inherits:
-
Object
- Object
- OnChain::BlockChain
- Defined in:
- lib/onchain/block_chain.rb,
lib/onchain/providers/blockr_api.rb,
lib/onchain/providers/insight_api.rb,
lib/onchain/providers/blockchaininfo_api.rb
Constant Summary collapse
- ALL_SUPPLIERS =
[ :blockinfo, :insight, :blockr ]
- BALANCE_CACHE_FOR =
120
- API_CACHE_FOR =
60
- SERVICE_DOWN_FOR =
60
- @@cache =
{}
Class Method Summary collapse
- .block_chain(cmd, address, params = "") ⇒ Object
- .blockinfo_address_history(address, network = :bitcoin) ⇒ Object
- .blockinfo_get_address_info(address) ⇒ Object
- .blockinfo_get_all_balances(addresses) ⇒ Object
- .blockinfo_get_balance(address) ⇒ Object
- .blockinfo_get_transaction(txhash) ⇒ Object
- .blockinfo_get_unspent_outs(address) ⇒ Object
- .blockinfo_parse_address_tx(address, json) ⇒ Object
- .blockr(cmd, address, network, params = "") ⇒ Object
- .blockr_address_history(address, network = :bitcoin) ⇒ Object
- .blockr_get_address_info(address, network = :bitcoin) ⇒ Object
- .blockr_get_all_balances(addresses, network = :bitcoin) ⇒ Object
- .blockr_get_balance(address, network = :bitcoin) ⇒ Object
- .blockr_get_transaction(txhash, network = :bitcoin) ⇒ Object
- .blockr_get_transactions(address, network = :bitcoin) ⇒ Object
- .blockr_get_unspent_outs(address, network = :bitcoin) ⇒ Object
- .blockr_send_tx(tx_hex, network = :bitcoin) ⇒ Object
- .cache_read(key) ⇒ Object
- .cache_write(key, data, max_age = 0) ⇒ Object
- .fetch_response(url, do_json = true) ⇒ Object
- .get_available_suppliers(method_name, network) ⇒ Object
- .get_balance_satoshi(address, network = :bitcoin) ⇒ Object
- .get_history_for_addresses(addresses, network = :bitcoin) ⇒ Object
- .get_insight_url(network) ⇒ Object
-
.get_uncached_addresses(addresses) ⇒ Object
Given a list of addresses, return those that don’t have balances in the cahce.
- .get_unspent_for_amount(addresses, amount_in_satoshi, network = :bitcoin) ⇒ Object
- .get_url(network) ⇒ Object
- .insight_address_history(address, network = :bitcoin) ⇒ Object
- .insight_get_all_balances(addresses, network = :bitcoin) ⇒ Object
- .insight_get_balance(address, network = :bitcoin) ⇒ Object
- .insight_get_transaction(txhash, network = :bitcoin) ⇒ Object
- .insight_get_unspent_outs(address, network = :bitcoin) ⇒ Object
- .insight_send_tx(tx_hex, network = :bitcoin) ⇒ Object
- .method_missing(method_name, *args, &block) ⇒ Object
- .parse_address_tx(address, json, network) ⇒ Object
- .parse_insight_address_tx(address, json, network) ⇒ Object
- .reverse_blockchain_tx(hash) ⇒ Object
Class Method Details
.block_chain(cmd, address, params = "") ⇒ Object
124 125 126 127 128 |
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 124 def block_chain(cmd, address, params = "") base_url = "https://blockchain.info/#{cmd}/#{address}?format=json" + params fetch_response(base_url, true) end |
.blockinfo_address_history(address, network = :bitcoin) ⇒ Object
4 5 6 7 8 9 10 |
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 4 def blockinfo_address_history(address, network = :bitcoin) base_url = "https://blockchain.info/address/#{address}?format=json" json = fetch_response(base_url, true) blockinfo_parse_address_tx(address, json) end |
.blockinfo_get_address_info(address) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 51 def blockinfo_get_address_info(address) base = "https://blockchain.info/multiaddr?&simple=true&active=" + address json = fetch_response(URI::encode(base)) return { received: json[address]['total_received'], balance: json[address]['final_balance'], confirmed: json[address]['final_balance'] } end |
.blockinfo_get_all_balances(addresses) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 63 def blockinfo_get_all_balances(addresses) base = "https://blockchain.info/multiaddr?&simple=true&active=" addr = get_uncached_addresses(addresses) if addr.length == 0 return end addr.each do |address| base = base + address + '|' end json = fetch_response(URI::encode(base)) addresses.each do |address| bal = json[address]['final_balance'] / 100000000.0 cache_write(address, bal, BALANCE_CACHE_FOR) end end |
.blockinfo_get_balance(address) ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 102 def blockinfo_get_balance(address) if cache_read(address) == nil json = block_chain('address', address, "&limit=0") if json.key?('final_balance') bal = json['final_balance'] / 100000000.0 cache_write(address, bal, BALANCE_CACHE_FOR) else cache_write(address, 'Error', BALANCE_CACHE_FOR) end end bal = cache_read(address) if bal.class == Fixnum bal = bal.to_f end return bal end |
.blockinfo_get_transaction(txhash) ⇒ Object
119 120 121 122 |
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 119 def blockinfo_get_transaction(txhash) base = "https://blockchain.info/rawtx/#{txhash}?format=hex" return fetch_response(URI::encode(base)) end |
.blockinfo_get_unspent_outs(address) ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 84 def blockinfo_get_unspent_outs(address) base_url = "https://blockchain.info/unspent?active=#{address}" json = fetch_response(base_url, true) unspent = [] json['unspent_outputs'].each do |data| line = [] line << reverse_blockchain_tx(data['tx_hash']) line << data['tx_output_n'] line << data['script'] line << data['value'] unspent << line end return unspent end |
.blockinfo_parse_address_tx(address, json) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 12 def blockinfo_parse_address_tx(address, json) hist = [] if json.key?('txs') txs = json['txs'] txs.each do |tx| row = {} row[:time] = tx["time"] row[:addr] = {} row[:outs] = {} inputs = tx['inputs'] val = 0 recv = "Y" inputs.each do |input| row[:addr][input["prev_out"]["addr"]] = input["prev_out"]["addr"] if input["prev_out"]["addr"] == address recv = "N" end end tx["out"].each do |out| row[:outs][out["addr"]] = out["addr"] if recv == "Y" and out["addr"] == address val = val + out["value"].to_f / 100000000.0 elsif recv == "N" and out["addr"] != address val = val + out["value"].to_f / 100000000.0 end end row[:total] = val row[:recv] = recv row[:hash] = tx["hash"] hist << row end return hist else 'Error' end return hist end |
.blockr(cmd, address, network, params = "") ⇒ Object
167 168 169 170 171 172 |
# File 'lib/onchain/providers/blockr_api.rb', line 167 def blockr(cmd, address, network, params = "") base_url = get_url(network) + "#{cmd}/#{address}" + params fetch_response(base_url, true) end |
.blockr_address_history(address, network = :bitcoin) ⇒ Object
11 12 13 14 15 16 |
# File 'lib/onchain/providers/blockr_api.rb', line 11 def blockr_address_history(address, network = :bitcoin) json = blockr('address/txs', address, network) return parse_address_tx(address, json, network) end |
.blockr_get_address_info(address, network = :bitcoin) ⇒ Object
95 96 97 98 99 100 101 102 103 |
# File 'lib/onchain/providers/blockr_api.rb', line 95 def blockr_get_address_info(address, network = :bitcoin) json = blockr('address/balance', address, network) return { received: json[address]['total_received'], balance: json[address]['final_balance'], confirmed: json[address]['final_balance'] } end |
.blockr_get_all_balances(addresses, network = :bitcoin) ⇒ Object
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/onchain/providers/blockr_api.rb', line 139 def blockr_get_all_balances(addresses, network = :bitcoin) addr = get_uncached_addresses(addresses) if addr.length == 0 return end base = get_url(network) + "address/balance/" addr.each do |address| base = base + address + ',' end json = fetch_response(URI::encode(base)) json['data'].each do |data| bal = data['balance'].to_f addr = data['address'] cache_write(addr, bal, BALANCE_CACHE_FOR) end end |
.blockr_get_balance(address, network = :bitcoin) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/onchain/providers/blockr_api.rb', line 82 def blockr_get_balance(address, network = :bitcoin) if cache_read(address) == nil json = blockr('address/balance', address, network) if json.key?('data') bal = json['data']['balance'].to_f cache_write(address, bal, BALANCE_CACHE_FOR) else cache_write(address, 'Error', BALANCE_CACHE_FOR) end end return cache_read(address) end |
.blockr_get_transaction(txhash, network = :bitcoin) ⇒ Object
162 163 164 165 |
# File 'lib/onchain/providers/blockr_api.rb', line 162 def blockr_get_transaction(txhash, network = :bitcoin) base = get_url(network) + "tx/raw/" + txhash return fetch_response(URI::encode(base))['data']['tx']['hex'] end |
.blockr_get_transactions(address, network = :bitcoin) ⇒ Object
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/onchain/providers/blockr_api.rb', line 105 def blockr_get_transactions(address, network = :bitcoin) base_url = get_url(network) + "address/txs/#{address}" json = fetch_response(base_url, true) unspent = [] json['data']['txs'].each do |data| line = [] line << data['tx'] line << data['amount'].to_f unspent << line end return unspent end |
.blockr_get_unspent_outs(address, network = :bitcoin) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/onchain/providers/blockr_api.rb', line 121 def blockr_get_unspent_outs(address, network = :bitcoin) base_url = get_url(network) + "address/unspent/#{address}" json = fetch_response(base_url, true) unspent = [] json['data']['unspent'].each do |data| line = [] line << data['tx'] line << data['n'] line << data['script'] line << (data['amount'].to_f * 100000000).to_i unspent << line end return unspent end |
.blockr_send_tx(tx_hex, network = :bitcoin) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/onchain/providers/blockr_api.rb', line 61 def blockr_send_tx(tx_hex, network = :bitcoin) uri = URI.parse(get_url(network) + "tx/push") http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Post.new(uri.request_uri) request.body = '{"hex":"' + tx_hex + '"}' response = http.request(request) res = JSON.parse(response.body) mess = res["message"] stat = res["status"] if stat == 'fail' stat = 'failure' end tx_hash = res["data"] ret = "{\"status\":\"#{stat}\",\"data\":\"#{tx_hash}\",\"code\":200,\"message\":\"#{mess}\"}" return JSON.parse(ret) end |
.cache_read(key) ⇒ Object
171 172 173 174 175 176 177 |
# File 'lib/onchain/block_chain.rb', line 171 def cache_read(key) # if the API URL exists as a key in cache, we just return it # we also make sure the data is fresh if @@cache.has_key? key return @@cache[key][1] if Time.now-@@cache[key][0] < @@cache[key][2] end end |
.cache_write(key, data, max_age = 0) ⇒ Object
167 168 169 |
# File 'lib/onchain/block_chain.rb', line 167 def cache_write(key, data, max_age=0) @@cache[key] = [Time.now, data, max_age] end |
.fetch_response(url, do_json = true) ⇒ Object
179 180 181 182 183 184 185 186 187 188 |
# File 'lib/onchain/block_chain.rb', line 179 def fetch_response(url, do_json=true) resp = Net::HTTP.get_response(URI.parse(url)) data = resp.body if do_json result = JSON.parse(data) else data end end |
.get_available_suppliers(method_name, network) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/onchain/block_chain.rb', line 122 def get_available_suppliers(method_name, network) available = [] ALL_SUPPLIERS.each do |supplier| if cache_read(supplier.to_s) == nil if supplier == :blockinfo and network == :testnet3 next end if supplier == :blockinfo and network == :zcash_testnet next end if supplier == :blockr and network == :zcash_testnet next end if supplier == :blockinfo and method_name.to_s == 'get_transactions' next end if supplier == :blockr and network == :bitcoin and method_name.to_s == 'address_history' next end if supplier == :blockr and method_name.to_s == 'get_address_info' next end if supplier == :blockr and network == :bitcoin and method_name.to_s == 'get_history_for_addresses' next end available << supplier end end return available end |
.get_balance_satoshi(address, network = :bitcoin) ⇒ Object
118 119 120 |
# File 'lib/onchain/block_chain.rb', line 118 def get_balance_satoshi(address, network = :bitcoin) return (get_balance(address, network = :bitcoin).to_f * 100000000).to_i end |
.get_history_for_addresses(addresses, network = :bitcoin) ⇒ Object
64 65 66 67 68 69 70 71 72 73 |
# File 'lib/onchain/block_chain.rb', line 64 def get_history_for_addresses(addresses, network = :bitcoin) history = [] addresses.each do |address| res = address_history(address, network) res.each do |r| history << r end end return history end |
.get_insight_url(network) ⇒ Object
4 5 6 7 8 9 10 11 |
# File 'lib/onchain/providers/insight_api.rb', line 4 def get_insight_url(network) if network == :bitcoin return "https://insight.bitpay.com/api/" elsif network == :zcash_testnet return "https://explorer.testnet.z.cash/api/" end return "https://test-insight.bitpay.com/api/" end |
.get_uncached_addresses(addresses) ⇒ Object
Given a list of addresses, return those that don’t have balances in the cahce.
77 78 79 80 81 82 83 84 85 |
# File 'lib/onchain/block_chain.rb', line 77 def get_uncached_addresses(addresses) ret = [] addresses.each do |address| if cache_read(address) == nil ret << address end end return ret end |
.get_unspent_for_amount(addresses, amount_in_satoshi, network = :bitcoin) ⇒ Object
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 |
# File 'lib/onchain/block_chain.rb', line 87 def get_unspent_for_amount(addresses, amount_in_satoshi, network = :bitcoin) unspents = [] indexes = [] amount_so_far = 0 addresses.each_with_index do |address, index| if amount_so_far >= amount_in_satoshi break end unspent_outs = get_unspent_outs(address, network) unspent_outs.each do |spent| unspents << spent indexes << index amount_so_far = amount_so_far + spent[3].to_i if amount_so_far >= amount_in_satoshi break end end end change = amount_so_far - amount_in_satoshi return unspents, indexes, change end |
.get_url(network) ⇒ Object
4 5 6 7 8 9 |
# File 'lib/onchain/providers/blockr_api.rb', line 4 def get_url(network) if network == :bitcoin return "http://btc.blockr.io/api/v1/" end return "http://tbtc.blockr.io/api/v1/" end |
.insight_address_history(address, network = :bitcoin) ⇒ Object
13 14 15 16 17 18 19 20 |
# File 'lib/onchain/providers/insight_api.rb', line 13 def insight_address_history(address, network = :bitcoin) base_url = get_insight_url(network) + "addr/" + address json = fetch_response(base_url, true) return parse_insight_address_tx(address, json, network) end |
.insight_get_all_balances(addresses, network = :bitcoin) ⇒ Object
101 102 103 104 105 106 |
# File 'lib/onchain/providers/insight_api.rb', line 101 def insight_get_all_balances(addresses, network = :bitcoin) addresses.each do |address| insight_get_balance(address, network) end end |
.insight_get_balance(address, network = :bitcoin) ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/onchain/providers/insight_api.rb', line 88 def insight_get_balance(address, network = :bitcoin) if cache_read(address + network.to_s) == nil base_url = get_insight_url(network) + "addr/#{address}/balance" bal_string = fetch_response(base_url, false) bal = bal_string.to_i / 100000000.0 cache_write(address + network.to_s, bal, BALANCE_CACHE_FOR) end return cache_read(address + network.to_s) end |
.insight_get_transaction(txhash, network = :bitcoin) ⇒ Object
127 128 129 130 |
# File 'lib/onchain/providers/insight_api.rb', line 127 def insight_get_transaction(txhash, network = :bitcoin) base = get_insight_url(network) + "rawtx/" + txhash return fetch_response(URI::encode(base))['rawtx'] end |
.insight_get_unspent_outs(address, network = :bitcoin) ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/onchain/providers/insight_api.rb', line 108 def insight_get_unspent_outs(address, network = :bitcoin) base_url = get_insight_url(network) + "addr/#{address}/utxo" json = fetch_response(base_url, true) unspent = [] json.each do |data| line = [] line << data['txid'] line << data['vout'] line << data['scriptPubKey'] line << (data['amount'].to_f * 100000000).to_i unspent << line end return unspent end |
.insight_send_tx(tx_hex, network = :bitcoin) ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/onchain/providers/insight_api.rb', line 69 def insight_send_tx(tx_hex, network = :bitcoin) uri = URI.parse(get_url(network) + "tx/push") http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Post.new(uri.request_uri) request.body = '{"rawtx":"' + tx_hex + '"}' response = http.request(request) res = JSON.parse(response.body) mess = 'Unknown' stat = 'Unknown' tx_hash = res["txid"] ret = "{\"status\":\"#{stat}\",\"data\":\"#{tx_hash}\",\"code\":200,\"message\":\"#{mess}\"}" return JSON.parse(ret) end |
.method_missing(method_name, *args, &block) ⇒ Object
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 59 60 61 62 |
# File 'lib/onchain/block_chain.rb', line 32 def method_missing (method_name, *args, &block) network = :bitcoin if args.length > 0 if args[args.length - 1] == :testnet3 or args[args.length - 1] == :zcash_testnet network = args[args.length - 1] end end get_available_suppliers(method_name, network).each do |supplier| real_method = "#{supplier.to_s}_#{method_name}" begin method = self.method(real_method) begin result = method.call(*args) return result rescue => e2 # We have the method but it errored. Assume # service is down. cache_write(supplier.to_s, 'down', SERVICE_DOWN_FOR) puts e2.to_s end rescue => e puts "there's no method called '#{real_method}'" puts e.backtrace end end end |
.parse_address_tx(address, json, network) ⇒ Object
18 19 20 21 22 23 24 25 26 27 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 59 |
# File 'lib/onchain/providers/blockr_api.rb', line 18 def parse_address_tx(address, json, network) hist = [] if json.key?('data') txs = json['data']['txs'] txs.each do |tx| row = {} row[:time] = DateTime.parse(tx["time_utc"]).to_time.to_i row[:addr] = {} row[:outs] = {} row[:hash] = tx["tx"] # OK, go and get the actual transaction tx_json = blockr('tx/info', tx["tx"], network) inputs = tx_json['data']['trade']['vins'] val = 0 recv = "Y" inputs.each do |input| row[:addr][input["address"]] = input["address"] if input["address"] == address recv = "N" end end tx_json['data']['trade']["vouts"].each do |out| row[:outs][out["address"]] = out["address"] if recv == "Y" and out["address"] == address val = val + out["amount"].to_f elsif recv == "N" and out["address"] != address val = val + out["amount"].to_f end end row[:total] = val row[:recv] = recv hist << row end return hist else 'Error' end return hist end |
.parse_insight_address_tx(address, json, network) ⇒ Object
22 23 24 25 26 27 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 59 60 61 62 63 64 65 66 67 |
# File 'lib/onchain/providers/insight_api.rb', line 22 def parse_insight_address_tx(address, json, network) hist = [] if json.key?('transactions') txs = json['transactions'] txs.each do |tx| row = {} row[:hash] = tx[tx] # OK, go and get the actual transaction base_url = get_insight_url(network) + "tx/" + tx tx_json = fetch_response(base_url, true) row[:time] = tx_json["time"] row[:addr] = {} row[:outs] = {} inputs = tx_json['vin'] val = 0 recv = "Y" inputs.each do |input| row[:addr][input["addr"]] = input["addr"] if input["addr"] == address recv = "N" end end tx_json["vout"].each do |out| out_addr = out["scriptPubKey"]["addresses"][0] row[:outs][out_addr] = out_addr if recv == "Y" and out_addr == address val = val + out["value"].to_f elsif recv == "N" and out_addr != address val = val + out["value"].to_f end end row[:total] = val row[:recv] = recv hist << row end return hist else 'Error' end return hist end |
.reverse_blockchain_tx(hash) ⇒ Object
130 131 132 133 134 135 136 |
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 130 def reverse_blockchain_tx(hash) bytes = hash.scan(/../).map { |x| x.hex.chr }.join bytes = bytes.reverse return hash.scan(/../).reverse.join end |