Class: Bluzelle::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/bluzelle.rb

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Client

Returns a new instance of Client.



76
77
78
# File 'lib/bluzelle.rb', line 76

def initialize(options)
  @options = options
end

Instance Method Details

#api_mutate(method, endpoint, payload) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/bluzelle.rb', line 248

def api_mutate(method, endpoint, payload)
  url = @options['endpoint'] + endpoint
  @logger.debug("mutating url(#{url}), method(#{method})")
  uri = URI(url)
  http = Net::HTTP.new(uri.host, uri.port)
  if method == "delete"
    request = Net::HTTP::Delete.new(uri.request_uri)
  else
    request = Net::HTTP::Post.new(uri.request_uri)
  end
  request['Accept'] = 'application/json'
  request.content_type = 'application/json'
  data = Bluzelle::json_dumps payload
  @logger.debug("data(#{data})")
  request.body = data
  response = http.request(request)
  @logger.debug("response (#{response.body})...")
  data = JSON.parse(response.body)
  error = get_response_error(data)
  raise error if error
  data
end

#api_query(endpoint) ⇒ Object



237
238
239
240
241
242
243
244
245
246
# File 'lib/bluzelle.rb', line 237

def api_query(endpoint)
  url = @options['endpoint'] + endpoint
  @logger.debug("querying url(#{url})...")
  response = Net::HTTP.get_response URI(url)
  data = JSON.parse(response.body)
  error = get_response_error(data)
  raise error if error
  @logger.debug("response (#{data})...")
  data
end

#broadcast_transaction(data) ⇒ Object

Raises:



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/bluzelle.rb', line 290

def broadcast_transaction(data)
  # fee
  fee = data['fee']
  fee_gas = fee['gas'].to_i
  gas_info = @options['gas_info']
  if gas_info['max_gas'] != 0 && fee_gas > gas_info['max_gas']
    fee['gas'] = gas_info['max_gas'].to_s
  end
  if gas_info['max_fee'] != 0
    fee['amount'] = [{ 'denom' => TOKEN_NAME, 'amount' => gas_info['max_fee'].to_s }]
  elsif gasInfo['gas_price'] != 0
    fee['amount'] = [{ 'denom' => TOKEN_NAME, 'amount' => (fee_gas * gas_info['gas_price']).to_s }]
  end

  # sort
  txn = build_txn(
    fee: fee,
    msg: data['msg'][0]
  )

  # signatures
  txn['signatures'] = [{
    'account_number' => @account['account_number'].to_s,
    'pub_key' => {
      'type' => PUB_KEY_TYPE,
      'value' => Bluzelle::base64_encode(@wallet.public_key.compressed.to_bytes)
    },
    'sequence' => @account['sequence'].to_s,
    'signature' => sign_transaction(txn)
  }]

  payload = { 'mode' => 'block', 'tx' => txn }
  response = api_mutate('post', TX_COMMAND, payload)

  # https://github.com/bluzelle/blzjs/blob/45fe51f6364439fa88421987b833102cc9bcd7c0/src/swarmClient/cosmos.js#L240-L246
  # note - as of right now (3/6/20) the responses returned by the Cosmos REST interface now look like this:
  # success case: {"height":"0","txhash":"3F596D7E83D514A103792C930D9B4ED8DCF03B4C8FD93873AB22F0A707D88A9F","raw_log":"[]"}
  # failure case: {"height":"0","txhash":"DEE236DEF1F3D0A92CB7EE8E442D1CE457EE8DB8E665BAC1358E6E107D5316AA","code":4,
  #  "raw_log":"unauthorized: signature verification failed; verify correct account sequence and chain-id"}
  #
  # this is far from ideal, doesn't match their docs, and is probably going to change (again) in the future.
  unless response.fetch('code', nil)
    @account['sequence'] += 1
    if response.fetch('data', nil)
      return JSON.parse Bluzelle::hex_to_ascii response['data']
    end
    return
  end

  raw_log = response["raw_log"]
  if raw_log.include?("signature verification failed")
    @broadcast_retries += 1
    @logger.warn("transaction failed ... retrying(#{@broadcast_retries}) ...")
    if @broadcast_retries >= BROADCAST_MAX_RETRIES
      raise APIError, "transaction failed after max retry attempts"
    end

    sleep BROADCAST_RETRY_INTERVAL_SECONDS
    ()
    broadcast_transaction(txn)
    return
  end

  raise APIError, raw_log
end

#build_txn(fee:, msg:) ⇒ Object



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'lib/bluzelle.rb', line 375

def build_txn(fee:, msg:)
  # TODO: find a better way to sort
  fee_amount = fee['amount'][0]
  txn = {
    'fee' => {
      "amount" => [
        {
          "amount" => fee_amount['amount'],
          "denom" => fee_amount['denom']
        }
      ],
      "gas" => fee['gas']
    },
    'memo' => Bluzelle::make_random_string(32)
  }
  msg_value = msg['value']
  sorted_msg_value = {}
  MSG_KEYS_ORDER.each do |key|
    val = msg_value.fetch(key, nil)
    sorted_msg_value[key] = val if val
  end
  txn['msg'] = [
    {
      "type" => msg['type'],
      "value" => sorted_msg_value
    }
  ]
  txn
end

#countObject



173
174
175
176
# File 'lib/bluzelle.rb', line 173

def count()
  url = "/crud/count/#{@options["uuid"]}"
  api_query(url)["result"]["count"].to_i
end

#create(key, value, lease: 0) ⇒ Object

mutate



117
118
119
# File 'lib/bluzelle.rb', line 117

def create(key, value, lease: 0)
  send_transaction('post', '/crud/create', { 'Key' => key, 'Lease' => lease.to_s, 'Value' => value })
end

#delete(key) ⇒ Object



128
129
130
# File 'lib/bluzelle.rb', line 128

def delete(key)
  send_transaction("delete", "/crud/delete", {"Key" => key})
end

#delete_allObject



136
137
138
# File 'lib/bluzelle.rb', line 136

def delete_all()
  send_transaction("post", "/crud/deleteall", {})
end

#get_lease(key) ⇒ Object



188
189
190
191
# File 'lib/bluzelle.rb', line 188

def get_lease(key)
  url = "/crud/getlease/#{@options["uuid"]}/#{key}"
  api_query(url)["result"]["lease"].to_i
end

#get_n_shortest_leases(n) ⇒ Object



193
194
195
196
# File 'lib/bluzelle.rb', line 193

def get_n_shortest_leases(n)
  url = "/crud/getnshortestlease/#{@options["uuid"]}/#{n}"
  api_query(url)["result"]["keyleases"]
end

#get_response_error(response) ⇒ Object



405
406
407
408
# File 'lib/bluzelle.rb', line 405

def get_response_error(response)
  error = response['error']
  return APIError.new(error) if error
end

#has(key) ⇒ Object



168
169
170
171
# File 'lib/bluzelle.rb', line 168

def has(key)
  url = "/crud/has/#{@options["uuid"]}/#{key}"
  api_query(url)["result"]["has"]
end

#key_valuesObject



183
184
185
186
# File 'lib/bluzelle.rb', line 183

def key_values()
  url = "/crud/keyvalues/#{@options["uuid"]}"
  api_query(url)["result"]["keyvalues"]
end

#keysObject



178
179
180
181
# File 'lib/bluzelle.rb', line 178

def keys()
  url = "/crud/keys/#{@options["uuid"]}"
  api_query(url)["result"]["keys"]
end

#multi_update(payload) ⇒ Object



140
141
142
143
144
145
146
# File 'lib/bluzelle.rb', line 140

def multi_update(payload)
  list = []
  payload.each do |key, value|
    list.append({"key" => key, "value" => value})
  end
  send_transaction("post", "/crud/multiupdate", {"KeyValues" => list})
end

#proven_read(key) ⇒ Object



163
164
165
166
# File 'lib/bluzelle.rb', line 163

def proven_read(key)
  url = "/crud/pread/#{@options["uuid"]}/#{key}"
  api_query(url)["result"]["value"]
end

#read(key) ⇒ Object

query



158
159
160
161
# File 'lib/bluzelle.rb', line 158

def read(key)
  url = "/crud/read/#{@options["uuid"]}/#{key}"
  api_query(url)["result"]["value"]
end

#read_accountObject



105
106
107
108
# File 'lib/bluzelle.rb', line 105

def 
  url = "/auth/accounts/#{@options['address']}"
  api_query(url)['result']['value']
end

#rename(key, new_key) ⇒ Object



132
133
134
# File 'lib/bluzelle.rb', line 132

def rename(key, new_key)
  send_transaction("post", "/crud/rename", {"Key" => key, "NewKey" => new_key})
end

#renew_all_leases(lease) ⇒ Object



152
153
154
# File 'lib/bluzelle.rb', line 152

def renew_all_leases(lease)
  send_transaction("post", "/crud/renewleaseall", {"Lease" => lease.to_s})
end

#renew_lease(key, lease) ⇒ Object



148
149
150
# File 'lib/bluzelle.rb', line 148

def renew_lease(key, lease)
  send_transaction("post", "/crud/renewlease", {"Key" => key, "Lease" => lease.to_s})
end

#send_transaction(method, endpoint, payload) ⇒ Object



271
272
273
274
275
# File 'lib/bluzelle.rb', line 271

def send_transaction(method, endpoint, payload)
  @broadcast_retries = 0
  txn = validate_transaction(method, endpoint, payload)
  broadcast_transaction(txn)
end

#set_accountObject



99
100
101
# File 'lib/bluzelle.rb', line 99

def 
  @account = 
end

#set_private_keyObject



85
86
87
88
89
# File 'lib/bluzelle.rb', line 85

def set_private_key
  seed = BipMnemonic.to_seed(mnemonic: @options['mnemonic'])
  master = MoneyTree::Master.new(seed_hex: seed)
  @wallet = master.node_for_path(HD_PATH)
end

#setup_loggingObject



80
81
82
83
# File 'lib/bluzelle.rb', line 80

def setup_logging
  @logger = Logger.new(STDOUT)
  @logger.level = if @options['debug'] then Logger::DEBUG else Logger::FATAL end
end

#sign_transaction(txn) ⇒ Object



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/bluzelle.rb', line 356

def sign_transaction(txn)
  payload = {
    'account_number' => @account['account_number'].to_s,
    'chain_id' => @options['chain_id'],
    'fee' => txn['fee'],
    'memo' => txn['memo'],
    'msgs' => txn['msg'],
    'sequence' => @account['sequence'].to_s
  }
  payload = Bluzelle::json_dumps(payload)

  pk = Secp256k1::PrivateKey.new(privkey: @wallet.private_key.to_bytes, raw: true)
  rs = pk.ecdsa_sign payload
  r = rs.slice(0, 32).read_string.reverse
  s = rs.slice(32, 32).read_string.reverse
  sig = "#{r}#{s}"
  Bluzelle::base64_encode sig
end

#tx_countObject



210
211
212
213
# File 'lib/bluzelle.rb', line 210

def tx_count()
  res = send_transaction("post", "/crud/count", {})
  res["count"].to_i
end

#tx_get_lease(key) ⇒ Object



225
226
227
228
# File 'lib/bluzelle.rb', line 225

def tx_get_lease(key)
  res = send_transaction("post", "/crud/getlease", {"Key" => key})
  res["lease"].to_i
end

#tx_get_n_shortest_leases(n) ⇒ Object



230
231
232
233
# File 'lib/bluzelle.rb', line 230

def tx_get_n_shortest_leases(n)
  res = send_transaction("post", "/crud/getnshortestlease", {"N" => n.to_s})
  res["keyleases"]
end

#tx_has(key) ⇒ Object



205
206
207
208
# File 'lib/bluzelle.rb', line 205

def tx_has(key)
  res = send_transaction("post", "/crud/has", {"Key" => key})
  res["has"]
end

#tx_key_valuesObject



220
221
222
223
# File 'lib/bluzelle.rb', line 220

def tx_key_values()
  res = send_transaction("post", "/crud/keyvalues", {})
  res["keyvalues"]
end

#tx_keysObject



215
216
217
218
# File 'lib/bluzelle.rb', line 215

def tx_keys()
  res = send_transaction("post", "/crud/keys", {})
  res["keys"]
end

#tx_read(key) ⇒ Object



200
201
202
203
# File 'lib/bluzelle.rb', line 200

def tx_read(key)
  res = send_transaction("post", "/crud/read", {"Key" => key})
  res["value"]
end

#update(key, value, lease: nil) ⇒ Object



121
122
123
124
125
126
# File 'lib/bluzelle.rb', line 121

def update(key, value, lease: nil)
  payload = {"Key" => key}
  payload["Lease"] = lease.to_s if lease.is_a? Integer
  payload["Value"] = value
  send_transaction("post", "/crud/update", payload)
end

#validate_transaction(method, endpoint, payload) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/bluzelle.rb', line 277

def validate_transaction(method, endpoint, payload)
  address = @options['address']
  payload = payload.merge({
    'BaseReq' => {
      'chain_id' => @options['chain_id'],
      'from' => address
    },
    'Owner' => address,
    'UUID' => @options['uuid']
  })
  api_mutate(method, endpoint, payload)['value']
end

#verify_addressObject



91
92
93
94
95
96
97
# File 'lib/bluzelle.rb', line 91

def verify_address
  b = Digest::RMD160.digest(Digest::SHA256.digest(@wallet.public_key.compressed.to_bytes))
  address = Bech32.encode(ADDRESS_PREFIX, Bech32.convert_bits(b, from_bits: 8, to_bits: 5, pad: true))
  if address != @options['address']
    raise OptionsError, 'bad credentials(verify your address and mnemonic)'
  end
end

#versionObject



110
111
112
113
# File 'lib/bluzelle.rb', line 110

def version()
  url = "/node_info"
  api_query(url)["application_version"]["version"]
end