Class: OnChain::BlockChain

Inherits:
Object
  • Object
show all
Defined in:
lib/onchain/block_chain.rb,
lib/onchain/providers/blockr_api.rb,
lib/onchain/providers/chaincom_api.rb,
lib/onchain/providers/blockchaininfo_api.rb

Constant Summary collapse

ALL_SUPPLIERS =
[:chaincom, :blockr, :blockinfo ]
BALANCE_CACHE_FOR =
120
API_CACHE_FOR =
60
SERVICE_DOWN_FOR =
60
@@cache =
{}

Class Method Summary collapse

Class Method Details

.block_chain(cmd, address, params = "") ⇒ Object



101
102
103
104
105
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 101

def block_chain(cmd, address, params = "")
  base_url = "http://blockchain.info/#{cmd}/#{address}?format=json" + params
  
  fetch_response(base_url, true)
end

.blockinfo_address_history(address) ⇒ Object



5
6
7
8
9
10
11
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
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 5

def blockinfo_address_history(address)
  
  base_url = "http://blockchain.info/address/#{address}?format=json"
  json = fetch_response(base_url, true)
  
  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
      hist << row
    end
    return hist
  else
    'Error'
  end
end

.blockinfo_get_all_balances(addresses) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 45

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



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 84

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_unspent_outs(address) ⇒ Object



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 66

def blockinfo_get_unspent_outs(address)
  base_url = "http://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

.blockr(cmd, address, params = "") ⇒ Object



95
96
97
98
99
100
# File 'lib/onchain/providers/blockr_api.rb', line 95

def blockr(cmd, address, params = "")

  base_url = "http://blockr.io/api/v1/#{cmd}/#{address}" + params
  fetch_response(base_url, true)

end

.blockr_get_all_balances(addresses) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/onchain/providers/blockr_api.rb', line 72

def blockr_get_all_balances(addresses)
  
  addr = get_uncached_addresses(addresses)
  
  if addr.length == 0
    return
  end
  
  base = "https://blockr.io/api/v1/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) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/onchain/providers/blockr_api.rb', line 25

def blockr_get_balance(address)
  if cache_read(address) == nil
    json = blockr('address/balance', address)
    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_transactions(address) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/onchain/providers/blockr_api.rb', line 38

def blockr_get_transactions(address)
  base_url = "http://btc.blockr.io/api/v1/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) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/onchain/providers/blockr_api.rb', line 54

def blockr_get_unspent_outs(address)
  base_url = "http://btc.blockr.io/api/v1/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) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/onchain/providers/blockr_api.rb', line 4

def blockr_send_tx(tx_hex)	
  uri = URI.parse("http://btc.blockr.io/api/v1/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



101
102
103
104
105
106
107
# File 'lib/onchain/block_chain.rb', line 101

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



97
98
99
# File 'lib/onchain/block_chain.rb', line 97

def cache_write(key, data, max_age=0)
   @@cache[key] = [Time.now, data, max_age]
end

.chaincom_address_history(address) ⇒ Object



6
7
8
9
10
11
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
# File 'lib/onchain/providers/chaincom_api.rb', line 6

def chaincom_address_history(address)
  
  txs = Chain.get_address_transactions(address)
  
  hist = []
  txs.each do |tx|
    row = {}
    row[:time] = tx["block_time"]
    row[:addr] = {}
    row[:outs] = {}
    inputs = tx['inputs']
    val = 0
    recv = "Y"
    inputs.each do |input|
      row[:addr][input["addresses"][0]] = input["addresses"][0]
      if input["addresses"][0] == address
        recv = "N"
      end
    end
    tx["outputs"].each do |out|
      row[:outs][out["addresses"][0] ] = out["addresses"][0] 
      if recv == "Y" and out["addresses"][0]  == address
        val = val + out["value"].to_f / 100000000.0
      elsif recv == "N" and out["addresses"][0]  != address
        val = val + out["value"].to_f / 100000000.0
      end
    end
    row[:total] = val
    row[:recv] = recv
    hist << row
  end
  return hist
end

.chaincom_get_all_balances(addresses) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/onchain/providers/chaincom_api.rb', line 98

def chaincom_get_all_balances(addresses)
  
  addr = get_uncached_addresses(addresses)
  
  if addr.length == 0
    return
  end
  
  res = Chain.get_addresses(addr)
  
  if ! res.kind_of?(Array)
    res = [res]
  end
  
  res.each do |address|
    bal = address["balance"] / 100000000.0
    cache_write(address["hash"], bal, BALANCE_CACHE_FOR)
  end
end

.chaincom_get_balance(address) ⇒ Object



53
54
55
56
57
58
59
60
61
62
# File 'lib/onchain/providers/chaincom_api.rb', line 53

def chaincom_get_balance(address)
  if cache_read(address) == nil
    
    addr = Chain.get_address(address)
    bal = addr["balance"] / 100000000.0
    cache_write(address, bal, BALANCE_CACHE_FOR)
    
  end
  return cache_read(address) 
end

.chaincom_get_transactions(address) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/onchain/providers/chaincom_api.rb', line 64

def chaincom_get_transactions(address)
  
  txs = Chain.get_address_transactions(address)
  
  unspent = []
  
  txs.each do |data|
    line = []
    line << data['hash']
    line << data["outputs"][0]["value"] / 100000000.0
    unspent << line
  end
  
  return unspent
end

.chaincom_get_unspent_outs(address) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/onchain/providers/chaincom_api.rb', line 80

def chaincom_get_unspent_outs(address)
  
  uns = Chain.get_address_unspents(address)
  
  unspent = []
  
  uns.each do |data|
    line = []
    line << data['transaction_hash']
    line << data['output_index']
    line << data['script_hex']
    line << data['value']
    unspent << line
  end
  
  return unspent
end

.chaincom_send_tx(tx_hex) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/onchain/providers/chaincom_api.rb', line 40

def chaincom_send_tx(tx_hex)	
  
  begin
    tx = Chain.send_transaction(tx_hex)
    tx_hash = tx["transaction_hash"]
    ret = "{\"status\":\"success\",\"data\":\"#{tx_hash}\",\"code\":200,\"message\":\"\"}"
    return JSON.parse(ret)
  rescue => e
    ret = "{\"status\":\"failure\",\"data\":\"#{tx_hash}\",\"code\":200,\"message\":\"#{e.to_s}\"}"
    return JSON.parse(ret)
  end	
end

.fetch_response(url, do_json = true) ⇒ Object



109
110
111
112
113
114
115
116
117
118
# File 'lib/onchain/block_chain.rb', line 109

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) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/onchain/block_chain.rb', line 68

def get_available_suppliers(method_name)
  available = []
  ALL_SUPPLIERS.each do |supplier|
    if cache_read(supplier.to_s) == nil
      
      if supplier == :blockinfo and method_name == 'send_tx'
        next
      end
      
      if supplier == :blockinfo and method_name == 'get_transactions'
        next
      end
      
      if supplier == :blockr and method_name == 'address_history'
        next
      end
      
      available << supplier
    end
  end
  return available
end

.get_balance_satoshi(address) ⇒ Object



64
65
66
# File 'lib/onchain/block_chain.rb', line 64

def get_balance_satoshi(address)
  return (get_balance(address).to_f * 100000000).to_i
end

.get_uncached_addresses(addresses) ⇒ Object

Given a list of addresses, return those that don’t have balances in the cahce.



54
55
56
57
58
59
60
61
62
# File 'lib/onchain/block_chain.rb', line 54

def get_uncached_addresses(addresses)
  ret = []
  addresses.each do |address|
    if cache_read(address) == nil
      ret << address
    end
  end
  return ret
end

.method_missing(method_name, *args, &block) ⇒ Object

ALL_SUPPLIERS = [ :blockr, :blockinfo ]



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
# File 'lib/onchain/block_chain.rb', line 23

def method_missing (method_name, *args, &block)
  
  if ENV['CHAIN-API-KEY-ID'] != nil
    Chain.api_key_id = ENV['CHAIN-API-KEY-ID']
    Chain.api_key_secret = ENV['CHAIN-API-KEY-SECRET']
  end
  
  get_available_suppliers(method_name).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

.reverse_blockchain_tx(hash) ⇒ Object



107
108
109
110
111
112
113
# File 'lib/onchain/providers/blockchaininfo_api.rb', line 107

def reverse_blockchain_tx(hash)
   bytes = hash.scan(/../).map { |x| x.hex.chr }.join
   
   bytes = bytes.reverse
   
   return hash.scan(/../).reverse.join
end