# frozen_string_literal: true require('google/cloud/bigquery') require('bigdecimal/util') # @author Hernani Rodrigues Vaz module Etht BD = 'hernanirvaz.coins' # classe para processar etherscan & bigquery class Bigquery # @return [Google::Cloud::Bigquery] API bigquery attr_reader :api # @return [Google::Cloud::Bigquery::QueryJob] job bigquery attr_reader :job # @return [Thor::CoreExt::HashWithIndifferentAccess] opcoes trabalho attr_reader :ops # @param [Thor::CoreExt::HashWithIndifferentAccess] pop opcoes trabalho # @option pop [Hash] :h ({}) configuracao ajuste reposicionamento temporal # @option pop [Boolean] :v (false) mostra transacoes normais & token? # @option pop [Boolean] :t (false) mostra transacoes todas ou somente novas? # @return [Bigquery] API bigquery & API etherscan def initialize(pop) # usa env GOOGLE_APPLICATION_CREDENTIALS para obter credentials # @see https://cloud.google.com/bigquery/docs/authentication/getting-started @api = Google::Cloud::Bigquery.new @ops = pop end # @return [Carteiras] API etherscan - processar transacoes normais e tokens def transacoes @transacoes ||= Carteiras.new( { wb: sql("select * from #{BD}.walletEth order by 2") .map { |e| { id: e[:id], ax: e[:address], sl: e[:saldo].to_d.round(10) } }, nt: sql("select blocknumber,iax from #{BD}.ethtx"), nk: sql("select blocknumber,iax from #{BD}.ethkx") }, ops ) end # @return [Carteiras] API etherscan - processar carteiras & transacoes normais e tokens def carteiras transacoes end # insere transacoes novas nas tabelas etht (trx normais), ethk (trx token) def processa puts(format("%2i LINHAS INSERIDAS #{BD}.etht", n: transacoes.norml.count.positive? ? dml(etht_ins) : 0)) puts(format("%2i LINHAS INSERIDAS #{BD}.ethk", n: transacoes.token.count.positive? ? dml(ethk_ins) : 0)) end # @return [String] comando insert SQL formatado etht (trx normais) def etht_ins "insert #{BD}.etht(blocknumber,timestamp,txhash,nonce,blockhash,transactionindex,axfrom,axto,value,gas," \ 'gasprice,iserror,txreceipt_status,input,contractaddress,cumulativegasused,gasused,confirmations,dias' \ ") VALUES(#{transacoes.norml.map { |e| etht_val1(e) }.join(',')})" end # @return [String] comando insert SQL formatado ethk (trx token) def ethk_ins "insert #{BD}.ethk(blocknumber,timestamp,txhash,nonce,blockhash,axfrom,contractaddress,axto,value,tokenname," \ 'tokensymbol,tokendecimal,transactionindex,gas,gasprice,gasused,cumulativegasused,input,confirmations,dias' \ ") VALUES(#{transacoes.token.map { |e| ethk_val1(e) }.join(',')})" end # @return [String] valores formatados etht (trx normais parte1) def etht_val1(hes) "#{Integer(hes['blockNumber'])}," \ "#{Integer(hes['timeStamp'])}," \ "'#{hes['hash']}'," \ "#{Integer(hes['nonce'])}," \ "'#{hes['blockHash']}'," \ "#{Integer(hes['transactionIndex'])}," \ "'#{hes['from']}'," \ "'#{hes['to']}'," \ "#{etht_val2(hes)}" end # @return [String] valores formatados etht (trx normais parte2) def etht_val2(hes) "cast('#{hes['value']}' as numeric)," \ "cast('#{hes['gas']}' as numeric)," \ "cast('#{hes['gasPrice']}' as numeric)," \ "#{Integer(hes['isError'])}," \ "#{hes['txreceipt_status'].length.zero? ? 'null' : hes['txreceipt_status']}," \ "#{hes['input'].length.zero? ? 'null' : "'#{hes['input']}'"}," \ "#{etht_val3(hes)}" end # @return [String] valores formatados etht (trx normais parte3) def etht_val3(hes) "#{hes['contractAddress'].length.zero? ? 'null' : "'#{hes['contractAddress']}'"}," \ "0,cast('#{hes['gasUsed']}' as numeric),0," \ "#{Integer(ops[:h][hes['blockNumber']] || 0)}" end # @return [String] valores formatados ethk (trx token parte1) def ethk_val1(hes) "#{Integer(hes['blockNumber'])}," \ "#{Integer(hes['timeStamp'])}," \ "'#{hes['hash']}'," \ "#{Integer(hes['nonce'])}," \ "'#{hes['blockHash']}'," \ "'#{hes['from']}'," \ "#{ethk_val2(hes)}" end # @return [String] valores formatados ethk (trx token parte2) def ethk_val2(hes) "#{hes['contractAddress'].length.zero? ? 'null' : "'#{hes['contractAddress']}'"}," \ "'#{hes['to']}'," \ "cast('#{hes['value']}' as numeric)," \ "'#{hes['tokenName']}'," \ "'#{hes['tokenSymbol']}'," \ "#{Integer(hes['tokenDecimal'])}," \ "#{Integer(hes['transactionIndex'])}," \ "#{ethk_val3(hes)}" end # @return [String] valores formatados ethk (trx token parte3) def ethk_val3(hes) "cast('#{hes['gas']}' as numeric)," \ "cast('#{hes['gasPrice']}' as numeric)," \ "cast('#{hes['gasUsed']}' as numeric),0," \ "#{hes['input'].length.zero? ? 'null' : "'#{hes['input']}'"},0," \ "#{Integer(ops[:h][hes['blockNumber']] || 0)}" end # cria job bigquery & verifica execucao # # @param [String] sql a executar # @return [Boolean] job ok? def job?(sql) @job = api.query_job(sql) @job.wait_until_done! puts(@job.error['message']) if @job.failed? @job.failed? end # cria Data Manipulation Language (DML) job bigquery # # @param (see job?) # @return [Integer] numero linhas afetadas def dml(sql) job?(sql) ? 0 : job.num_dml_affected_rows end # cria Structured Query Language (SQL) job bigquery # # @param (see job?) # @return [Array] resultados do SQL def sql(sql) job?(sql) ? [] : job.data end end end