require 'rest-client' module OpenAssets module Provider # The implementation of BlockChain provider using Bitcoin Core. class BitcoinCoreProvider < BlockChainProviderBase RPC_API = [ :addmultisigaddress, :addnode, :backupwallet, :createmultisig, :createrawtransaction, :decoderawtransaction, :decodescript, :dumpprivkey, :dumpwallet, :encryptwallet, :estimatefee, :estimatepriority, :generate, :getaccountaddress, :getaccount, :getaddednodeinfo, :getaddressesbyaccount, :getbalance, :getbestblockhash, :getblock, :getblockchaininfo, :getblockcount, :getblockhash, :getchaintips, :getconnectioncount, :getdifficulty, :getgenerate, :gethashespersec, :getinfo, :getmempoolinfo, :getmininginfo, :getnettotals, :getnetworkhashps, :getnetworkinfo, :getnewaddress, :getpeerinfo, :getrawchangeaddress, :getrawmempool, :getrawtransaction, :getreceivedbyaccount, :getreceivedbyaddress, :gettransaction, :gettxout, :gettxoutproof, :gettxoutsetinfo, :getunconfirmedbalance, :getwalletinfo, :getwork, :help, :importaddress, :importprivkey, :importwallet, :keypoolrefill, :listaccounts, :listaddressgroupings, :listlockunspent, :listreceivedbyaccount, :listreceivedbyaddress, :listsinceblock, :listtransactions, :listunspent, :lockunspent, :move, :ping, :prioritisetransaction, :sendfrom, :sendmany, :sendrawtransaction, :sendtoaddress, :setaccount, :setgenerate, :settxfee, :signmessage, :signrawtransaction, :stop, :submitblock, :validateaddress, :verifychain, :verifymessage, :verifytxoutproof, :walletlock, :walletpassphrase, :walletpassphrasechange ] attr_reader :config def initialize(config) @config = config end # Get an array of unspent transaction outputs belonging to this wallet. # @param [Array] addresses If present, only outputs which pay an address in this array will be returned. # @param [Integer] min The minimum number of confirmations the transaction containing an output must have in order to be returned. Default is 1. # @param [Integer] max The maximum number of confirmations the transaction containing an output may have in order to be returned. Default is 9999999. def list_unspent(addresses = [], min = 1 , max = 9999999) request('listunspent', min, max, addresses) end # Get raw transaction. # @param [String] transaction_hash The transaction hash. # @param [String] verbose Whether to get the serialized or decoded transaction. 0: serialized transaction (Default). 1: decode transaction. # @return [String] (if verbose=0)—the serialized transaction. (if verbose=1)—the decoded transaction. (if transaction not found)—nil. def get_transaction(transaction_hash, verbose = 0) begin request('getrawtransaction', transaction_hash, verbose) rescue OpenAssets::Provider::ApiError => e nil end end # Signs a transaction in the serialized transaction format using private keys. # @param [String] tx The serialized format transaction. # @return [Bitcoin::Protocol::Tx] The signed transaction. def sign_transaction(tx) signed_tx = request('signrawtransaction', tx) raise OpenAssets::Error, 'Could not sign the transaction.' unless signed_tx['complete'] Bitcoin::Protocol::Tx.new(signed_tx['hex'].htb) end # Validates a transaction and broadcasts it to the peer-to-peer network. # @param [String] tx The serialized format transaction. # @return [String] The TXID or error message. def send_transaction(tx) request('sendrawtransaction', tx) end def method_missing(method, *params) super unless RPC_API.include?(method) request(method, *params) end private # Convert decode tx string to Bitcion::Protocol::Tx def decode_tx_to_btc_tx(tx) hash = { 'version' => tx['version'], 'lock_time' => tx['locktime'], 'hex' => tx['hex'], 'txid' => tx['txid'], 'blockhash' => tx['blockhash'], 'confirmations' => tx['confirmations'], 'time' => tx['time'], 'blocktime' => tx['blocktime'], 'in' => tx['vin'].map{|input| {'output_index' => input['vout'], 'previous_transaction_hash' => input['txid'], 'coinbase' => input['coinbase'], 'scriptSig' => input['scriptSig']['asm'], 'sequence' => input['sequence']}}, 'out' => tx['vout'].map{|out| {'amount' => out['value'], 'scriptPubKey' => out['scriptPubKey']['asm']} } } Bitcoin::Protocol::Tx.from_hash(hash) end private def server_url url = "#{@config[:schema]}://" url.concat "#{@config[:user]}:#{@config[:password]}@" url.concat "#{@config[:host]}:#{@config[:port]}" url end def request(command, *params) data = { :method => command, :params => params, :id => 'jsonrpc' } RestClient.post(server_url, data.to_json, content_type: :json) do |respdata, request, result| response = JSON.parse(respdata.gsub(/\\u([\da-fA-F]{4})/) { [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8') }) raise ApiError, response['error'] if response['error'] response['result'] end end end end end