Port asset tutorial, update to 0.17+ API

This commit is contained in:
Gregory Sanders 2019-03-29 07:54:07 -04:00
parent 09e20ab819
commit 4c157da8bd
6 changed files with 771 additions and 0 deletions

View file

@ -0,0 +1,507 @@
#!/usr/bin/env python3
from test_framework.authproxy import AuthServiceProxy, JSONRPCException
import os
import random
import sys
import time
import subprocess
import shutil
from decimal import Decimal
ELEMENTSPATH=""
BITCOINPATH=""
if len(sys.argv) == 2:
ELEMENTSPATH=sys.argv[0]
BITCOINPATH=sys.argv[1]
else:
ELEMENTSPATH="./src"
BITCOINPATH="./../bitcoin/src"
def startbitcoind(datadir, conf, args=""):
subprocess.Popen((BITCOINPATH+"/bitcoind -datadir="+datadir+" "+args).split(), stdout=subprocess.PIPE)
return AuthServiceProxy("http://"+conf["rpcuser"]+":"+conf["rpcpassword"]+"@127.0.0.1:"+conf["regtest.rpcport"])
def startelementsd(datadir, conf, args=""):
subprocess.Popen((ELEMENTSPATH+"/elementsd -datadir="+datadir+" "+args).split(), stdout=subprocess.PIPE)
return AuthServiceProxy("http://"+conf["rpcuser"]+":"+conf["rpcpassword"]+"@127.0.0.1:"+conf["elementsregtest.rpcport"])
def loadConfig(filename):
conf = {}
with open(filename, encoding="utf8") as f:
for line in f:
if len(line) == 0 or line[0] == "#" or len(line.split("=")) != 2:
continue
conf[line.split("=")[0]] = line.split("=")[1].strip()
conf["filename"] = filename
return conf
def sync_all(e1, e2):
totalWait = 10
while e1.getblockcount() != e2.getblockcount() or len(e1.getrawmempool()) != len(e2.getrawmempool()):
totalWait -= 1
if totalWait == 0:
raise Exception("Nodes cannot sync blocks or mempool!")
time.sleep(1)
return
# Preparations
# Make data directories for each daemon
b_datadir="/tmp/"+''.join(random.choice('0123456789ABCDEF') for i in range(5))
e1_datadir="/tmp/"+''.join(random.choice('0123456789ABCDEF') for i in range(5))
e2_datadir="/tmp/"+''.join(random.choice('0123456789ABCDEF') for i in range(5))
os.makedirs(b_datadir)
os.makedirs(e1_datadir)
os.makedirs(e2_datadir)
print("Bitcoin datadir: "+b_datadir)
print("Elements1 datadir: "+e1_datadir)
print("Elements2 datadir: "+e2_datadir)
# Also configure the nodes by copying the configuration files from
# this directory (and read them back for arguments):
shutil.copyfile("contrib/assets_tutorial/bitcoin.conf", b_datadir+"/bitcoin.conf")
shutil.copyfile("contrib/assets_tutorial/elements1.conf", e1_datadir+"/elements.conf")
shutil.copyfile("contrib/assets_tutorial/elements2.conf", e2_datadir+"/elements.conf")
bconf = loadConfig("contrib/assets_tutorial/bitcoin.conf")
e1conf = loadConfig("contrib/assets_tutorial/elements1.conf")
e2conf = loadConfig("contrib/assets_tutorial/elements2.conf")
# Startup
# Can not start since bitcoind isn't running and validatepegin is set
# elementsd attempts to connect to bitcoind to check if peg-in transactions
# are confirmed in the Bitcoin chain.
e1 = startelementsd(e1_datadir, e1conf)
time.sleep(2)
try:
e1.getinfo()
raise AssertionError("This should fail unless working bitcoind can be reached via JSON RPC")
except:
pass
# Start bitcoind, then elementsd. As long as bitcoind is in RPC warmup, elementsd will connect
bitcoin = startbitcoind(b_datadir, bconf)
e1 = startelementsd(e1_datadir, e1conf)
e2 = startelementsd(e2_datadir, e2conf)
time.sleep(3)
# Alternatively, you can set validatepegin=0 in their configs and not
# run the bitcoin node, but it is necessary for fully validating the two way peg.
# Regtest chain starts with 21M bitcoins as OP_TRUE which the wallet
# understands. This is useful for testing basic functionality and for
# blockchains that have no pegging functionality. A fee currency is required
# for anti-DoS purposes as well as asset issuance, which consumes inputs for entropy.
# In Elements there is no block subsidy. In a production sidechain it can
# be configured to start with no outputs, necessitating peg-in functionality
# for asset issuance.
e1.getwalletinfo()
# In regtest mining "target" is OP_TRUE since we have not set `-signblockscript` argument
# Generate simply works.
e1.generatetoaddress(101, e1.getnewaddress())
sync_all(e1, e2)
# WALLET
# First, send all anyone-can-spend coins to e1 then split so balances are even
e1.sendtoaddress(e1.getnewaddress(), 21000000, "", "", True)
e1.generatetoaddress(101, e1.getnewaddress())
sync_all(e1, e2)
e1.sendtoaddress(e2.getnewaddress(), 10500000, "", "", False)
e1.generatetoaddress(101, e1.getnewaddress())
sync_all(e1, e2)
# Funds should now be evenly split between the two wallets
e1.getwalletinfo()
e2.getwalletinfo()
# Have e2 send coins to themself using a blinded Elements address
# Blinded addresses start with `CTE`, unblinded `2`
addr = e2.getnewaddress()
# How do we know it's blinded? Check for blinding key, unblinded address.
e2.getaddressinfo(addr)
# Basic blinded send
txid = e2.sendtoaddress(addr, 1)
e2.generatetoaddress(1, e1.getnewaddress())
sync_all(e1, e2)
# Now let's examine the transaction, both in wallet and without
# In-wallet, take a look at blinding information
e2.gettransaction(txid)
# e1 doesn't have in wallet since it's unrelated
try:
e1.gettransaction(txid)
raise Exception("Transaction should not be in wallet")
except JSONRPCException:
pass
# Get public info, see blinded ranges, etc
e1.getrawtransaction(txid, 1)
# Now let's private import the key to attempt a spend
e1.importprivkey(e2.dumpprivkey(addr))
# We can't see output value info though
# and can not send.
e1.gettransaction(txid)
# And it won't show in balance or known outputs
e1.getwalletinfo()
# Amount for transaction is unknown, so it is not shown in listunspent.
e1.listunspent(1, 1)
# Solution: Import blinding key
e1.importblindingkey(addr, e2.dumpblindingkey(addr))
# Check again, funds should show
e1.getwalletinfo()
e1.listunspent(1, 1)
e1.gettransaction(txid)
# Let's build a blinded 2-of-2 multisig p2sh address
# 1) Get unblinded addresses from each participant
addr1 = e1.getaddressinfo(e1.getnewaddress())["unconfidential"]
addr2 = e2.getaddressinfo(e2.getnewaddress())["unconfidential"]
# 2) Get blinding keys, private and public
addrinfo1 = e1.getaddressinfo(e1.getnewaddress())
addrinfo2 = e2.getaddressinfo(addr2)
blindingkey = e1.dumpblindingkey(addrinfo1["address"])
blindingpubkey = addrinfo1["confidential_key"]
# 3) Make multisig address like usual
multisig = e1.createmultisig(2, [addrinfo1["pubkey"], addrinfo2["pubkey"]])
# 4) Blind the address using the blinding pubkey
blinded_addr = e1.createblindedaddress(multisig["address"], blindingpubkey)
e1.importaddress(multisig["redeemScript"], "", True, True) # Make sure p2sh addr is added
e2.importaddress(multisig["redeemScript"], "", True, True)
e1.importaddress(blinded_addr)
e2.importaddress(blinded_addr)
# 5) Now the address can be funded, though e2 will not be able to see values
txid = e1.sendtoaddress(blinded_addr, 1)
sync_all(e1, e2)
e2.gettransaction(txid, True)
# 6) Import the blinding privkey and decode the values
e2.importblindingkey(blinded_addr, blindingkey)
e2.gettransaction(txid, True)
# ASSETS
# Many of the RPC calls have added asset type or label
# arguments and reveal alternative asset information. With no argument all are listed:
e1.getwalletinfo()
# Notice we now see "bitcoin" as an asset. This is the asset label for the hex for "bitcoin" which can be discovered:
e1.dumpassetlabels()
# We can also issue our own assets, 1 asset and 1 reissuance token in this case
issue = e1.issueasset(1, 1)
asset = issue["asset"]
# From there you can look at the issuances you have in your wallet
e1.listissuances()
# If you gave `issueasset` a reissuance token argument greater than 0
# you can also reissue the base asset
e1.reissueasset(asset, 1)
# or make another different unblinded asset issuance, with only reissuance tokens initially
e1.issueasset(0, 1, False)
# Then two issuances for that particular asset will show
e1.listissuances(asset)
# To label any asset add a new argument like this to your elements.conf file
# then restart your daemon:
assetentry = "-assetdir="+asset+":namedasset"
# Wallet labels have no consensus meaning, only local node/wallet meaning
sync_all(e1, e2)
e1.stop()
time.sleep(5)
# Restart with a new asset label
e1 = startelementsd(e1_datadir, e1conf, assetentry)
time.sleep(5)
e1.getwalletinfo()
# To send issued assets, add an additional argument to sendtoaddress using the hex or label
e1.sendtoaddress(address=e2.getnewaddress(), amount=1, assetlabel="namedasset")
# Reissuance tokens can also be sent like any other asset
e1.sendtoaddress(address=e2.getnewaddress(), amount=1, assetlabel=issue["token"])
sync_all(e1, e2)
# e2 wallet doesn't know about label, just an unnamed asset
e2.getwalletinfo()["unconfirmed_balance"][asset]
e2.generatetoaddress(1, e2.getnewaddress())
sync_all(e1, e2)
# e2 maybe doesn't know about the issuance for the transaction sending him the new asset
e2.listissuances()
# let's import an associated address(so the wallet captures issuance transaction) and rescan
txid = issue["txid"]
addr = e1.gettransaction(txid)["details"][0]["address"]
e2.importaddress(addr)
# e2 now sees issuance, but doesn't know amounts as they are blinded
e2.listissuances()
# We need to import the issuance blinding key. We refer to issuances by their txid/vin pair
# as there is only one per input
vin = issue["vin"]
issuekey = e1.dumpissuanceblindingkey(txid, vin)
e2.importissuanceblindingkey(txid, vin, issuekey)
# Now e2 can see issuance amounts and blinds
e2.listissuances()
# Since it was also sent a reissuance token, it can reissue the base asset
e2.reissueasset(issue["asset"], 5)
# Reissuing reissuance tokens is currently not supported
try:
e2.reissueasset(issue["token"], 1)
except JSONRPCException:
pass
# For de-issuance, we can send assets or issuance tokens to an OP_RETURN output, provably burning them
e2.destroyamount(issue["asset"], 5)
# BLOCKSIGNING
# Recall blocksigning is OP_TRUE
e1.generatetoaddress(1, e1.getnewaddress())
sync_all(e1, e2)
# Let's set it to something more interesting... 2-of-2 multisig
# First lets get some keys from both clients to make our block "challenge"
addr1 = e1.getnewaddress()
addr2 = e2.getnewaddress()
valid1 = e1.getaddressinfo(addr1)
pubkey1 = valid1["pubkey"]
valid2 = e2.getaddressinfo(addr2)
pubkey2 = valid2["pubkey"]
key1 = e1.dumpprivkey(addr1)
key2 = e2.dumpprivkey(addr2)
e1.stop()
e2.stop()
time.sleep(5)
# Now filled with the pubkeys as 2-of-2 checkmultisig
signblockarg="-signblockscript=5221"+pubkey1+"21"+pubkey2+"52ae"
# Anti-DoS argument, custom chain default is ~1 sig so let's make it at least 2 sigs
blocksign_max_size="-con_max_block_sig_size=150"
extra_args=signblockarg+" "+blocksign_max_size
# Wipe out datadirs, start over
shutil.rmtree(e1_datadir)
shutil.rmtree(e2_datadir)
os.makedirs(e1_datadir)
os.makedirs(e2_datadir)
# Copy back config files
shutil.copyfile("contrib/assets_tutorial/elements1.conf", e1_datadir+"/elements.conf")
shutil.copyfile("contrib/assets_tutorial/elements2.conf", e2_datadir+"/elements.conf")
e1 = startelementsd(e1_datadir, e1conf, extra_args)
e2 = startelementsd(e2_datadir, e2conf, extra_args)
time.sleep(5)
sync_all(e1, e2)
# Now import signing keys
e1.importprivkey(key1)
e2.importprivkey(key2)
# Generate no longer works, even if keys are in wallet
try:
e1.generatetoaddress(1, e1.getnewaddress())
raise Exception("Generate shouldn't work")
except JSONRPCException:
pass
try:
e1.generatetoaddress(1, e1.getnewaddress())
raise Exception("Generate shouldn't work")
except JSONRPCException:
pass
# Let's propose and accept some blocks, e1 is master!
blockhex = e1.getnewblockhex()
# Unsigned is no good
# 0 before, 0 after
e1.getblockcount() == 0
e1.submitblock(blockhex)
# Still 0
e1.getblockcount() == 0
# Signblock tests validity except block signatures
# This signing step can be outsourced to a HSM signing to enforce business logic of any sort
# See Strong Federations paper
sign1 = e1.signblock(blockhex)
sign2 = e2.signblock(blockhex)
# We now can gather signatures any way you want, combine them into a fully signed block
blockresult = e1.combineblocksigs(blockhex, [sign1[0], sign2[0]])
blockresult["complete"] == True
signedblock = blockresult["hex"]
# Now submit the block, doesn't matter who
e2.submitblock(signedblock)
sync_all(e1, e2)
# We now have moved forward one block!
e1.getblockcount() == 1
e2.getblockcount() == 1
e1.stop()
e2.stop()
time.sleep(5)
# Further Exercises:
# - Make a python script that does round-robin consensus
# Pegging
# Everything pegging related can be done inside the Elements daemon directly, except for
# pegging out. This is due to the multisig pool aka Watchmen that controls the bitcoin
# on the Bitcoin blockchain. That is the easiest part to get wrong, and by far the most
# important as there is no going back if you lose the funds.
# Wipe out datadirs, start over
shutil.rmtree(e1_datadir)
shutil.rmtree(e2_datadir)
os.makedirs(e1_datadir)
os.makedirs(e2_datadir)
# Copy back config files
shutil.copyfile("contrib/assets_tutorial/elements1.conf", e1_datadir+"/elements.conf")
shutil.copyfile("contrib/assets_tutorial/elements2.conf", e2_datadir+"/elements.conf")
fedpegarg="-fedpegscript=5221"+pubkey1+"21"+pubkey2+"52ae"
# Back to OP_TRUE blocks, re-using pubkeys for pegin pool instead
# Keys can be the same or different, doesn't matter
e1 = startelementsd(e1_datadir, e1conf, fedpegarg)
e2 = startelementsd(e2_datadir, e2conf, fedpegarg)
time.sleep(5)
# Mature some outputs on each side
e1.generatetoaddress(101, e1.getnewaddress())
bitcoin.generatetoaddress(101, bitcoin.getnewaddress())
sync_all(e1, e2)
# Now we can actually start pegging in. Examine the pegin address fields
e1.getpeginaddress()
# Changes each time as it's a new sidechain address as well as new "tweak" for the watchmen keys
# mainchain_address : where you send your bitcoin from Bitcoin network
# sidechain_address : where the bitcoin will end up on the sidechain after pegging in
# Each call of this takes the pubkeys defined in the config file, adds a random number to them
# that is essetially the hash of the sidechain_address and other information,
# then creates a new P2SH Bitcoin address from that. We reveal that "tweak" to the functionaries
# during `claimpegin`, then they are able to calculate the necessary private key and control
# funds.
addrs = e1.getpeginaddress()
#Send funds to unique watchmen P2SH address
txid = bitcoin.sendtoaddress(addrs["mainchain_address"], 1)
# Confirmations in Bitcoin are what protects the
# sidechain from becoming fractional reserve during reorgs.
bitcoin.generatetoaddress(101, bitcoin.getnewaddress())
proof = bitcoin.gettxoutproof([txid])
raw = bitcoin.getrawtransaction(txid)
# Attempt claim!
claimtxid = e1.claimpegin(raw, proof, addrs["claim_script"])
sync_all(e1, e2)
# Other node should accept to mempool and mine
e2.generatetoaddress(1, e1.getnewaddress())
sync_all(e1, e2)
# Should see confirmations
"confirmations" in e1.getrawtransaction(claimtxid, 1)
# Pegging Out
# This command would trigger watchmen to send payment to Bitcoin address on mainchain
# The Bitcoin-side functionality is not supported directly in Elements.
# The watchmen will notice this transaction and send the funds from their collective
# wallet.
e1.sendtomainchain(bitcoin.getnewaddress(), 10)
#Exercise(s)
#1. Implement really dumb/unsafe watchmen to allow pegouts for learning purposes
# Recover tweak from pegin, add to privkey, combined tweaked pubkeys into a redeemscript, add to Core wallet
# RAW API
# Let's create a basic transaction using the raw api, blind it, sign, and send
# Create a transaction with a single destination output to other wallet
rawtx = e1.createrawtransaction([], {e2.getnewaddress():100})
# Biggest difference compared to Bitcoin is that we have explicit fee outputs
rawtx2 = e1.createrawtransaction([], {e2.getnewaddress():100, e1.getnewaddress():5, "fee":Decimal("0.1")})
# Fee outputs are unblinded, with a scriptPubKey of "", in other words ""
# scriptPubKeys are unspendable
# Next we can fund the transaction (and replaces fee with something more appropriate)
fundedtx = e1.fundrawtransaction(rawtx2)
# Blind
blindedtx = e1.blindrawtransaction(fundedtx["hex"])
# *Warning*: Raw blinding logic can be quite complicated, requiring the use of `ignoreblindfails`
# to avoid having calls fail without manually inspecting transactions in great detail.
# In general any transaction with 2 or more outputs to blind should succeed, so adding additional
# is one strategy to resolve this.
# Sign
signedtx = e1.signrawtransactionwithwallet(blindedtx)
# And send
txid = e1.sendrawtransaction(signedtx["hex"])
sync_all(e1, e2)
e2.gettransaction(txid)
# ADVANCED OPTIONS
# rawblindrawtransaction : blind a raw transaction with no access to a wallet
# -policyasset=<hex> : set network fee asset type to something other than BTC
bitcoin.stop()
e1.stop()
e2.stop()
time.sleep(2)
shutil.rmtree(e1_datadir)
shutil.rmtree(e2_datadir)

View file

@ -0,0 +1,9 @@
regtest=1
daemon=1
txindex=1
regtest.rpcport=18888
regtest.port=18889
rpcuser=user3
rpcpassword=password3

View file

@ -0,0 +1,44 @@
chain=elementsregtest
# Standard bitcoind stuff
rpcuser=user1
rpcpassword=password1
elementsregtest.rpcport=18884
elementsregtest.port=18886
rpcport=18884
# Over p2p we will only connect to local other elementsd
elementsregtest.connect=localhost:18887
daemon=1
# Make sure you set listen after -connect, otherwise neither
# will accept incoming connections!
listen=1
# Just for looking at random txs
txindex=1
# This is the script that controls pegged in funds in Bitcoin network
# Users will be pegging into a P2SH of this, and the "watchmen"
# can then recover these funds and send them to users who desire to peg out.
# This template is 1-of-1 checkmultisig
#fedpegscript=5121<pubkey>51ae
# This is the script that controls how blocks are made
# We have to supply a signature that satisfies this to create
# a valid block.
#signblockscript=5121<pubkey2>51ae
# We want to validate pegins by checking with bitcoind if header exists
# in best known chain, and how deep. We combine this with pegin
# proof included in the pegin to get full security.
validatepegin=1
# If in the same datadir and using standard ports, these are unneeded
# thanks to cookie auth. If not, like in our situation, elementsd needs
# more info to connect to bitcoind:
mainchainrpcport=18888
mainchainrpcuser=user3
mainchainrpcpassword=password3
# Free money to make testing easier
initialfreecoins=2100000000000000

View file

@ -0,0 +1,19 @@
rpcuser=user2
rpcpassword=password2
elementsregtest.rpcport=18885
elementsregtest.port=18887
elementsregtest.connect=localhost:18886
daemon=1
listen=1
txindex=1
#fedpegscript=51<pubkey>51ae
#signblockscript=51<pubkey2>51ae
mainchainrpcport=18888
mainchainrpcuser=user3
mainchainrpcpassword=password3
validatepegin=1
initialfreecoins=2100000000000000

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,191 @@
"""
Copyright (c) 2011 Jeff Garzik
AuthServiceProxy has the following improvements over python-jsonrpc's
ServiceProxy class:
- HTTP connections persist for the life of the AuthServiceProxy object
(if server supports HTTP/1.1)
- sends protocol 'version', per JSON-RPC 1.1
- sends proper, incrementing 'id'
- sends Basic HTTP authentication headers
- parses all JSON numbers that look like floats as Decimal
- uses standard Python json lib
Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
Copyright (c) 2007 Jan-Klaas Kollhof
This file is part of jsonrpc.
jsonrpc is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this software; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
try:
import http.client as httplib
except ImportError:
import httplib
import base64
import decimal
import json
import logging
import socket
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
USER_AGENT = "AuthServiceProxy/0.1"
HTTP_TIMEOUT = 30
log = logging.getLogger("BitcoinRPC")
class JSONRPCException(Exception):
def __init__(self, rpc_error):
try:
errmsg = '%(message)s (%(code)i)' % rpc_error
except (KeyError, TypeError):
errmsg = ''
Exception.__init__(self, errmsg)
self.error = rpc_error
def EncodeDecimal(o):
if isinstance(o, decimal.Decimal):
return str(o)
raise TypeError(repr(o) + " is not JSON serializable")
class AuthServiceProxy(object):
__id_count = 0
# ensure_ascii: escape unicode as \uXXXX, passed to json.dumps
def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True):
self.__service_url = service_url
self._service_name = service_name
self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests
self.__url = urlparse.urlparse(service_url)
if self.__url.port is None:
port = 80
else:
port = self.__url.port
(user, passwd) = (self.__url.username, self.__url.password)
try:
user = user.encode('utf8')
except AttributeError:
pass
try:
passwd = passwd.encode('utf8')
except AttributeError:
pass
authpair = user + b':' + passwd
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
if connection:
# Callables re-use the connection of the original proxy
self.__conn = connection
elif self.__url.scheme == 'https':
self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
timeout=timeout)
else:
self.__conn = httplib.HTTPConnection(self.__url.hostname, port,
timeout=timeout)
def __getattr__(self, name):
if name.startswith('__') and name.endswith('__'):
# Python internal stuff
raise AttributeError
if self._service_name is not None:
name = "%s.%s" % (self._service_name, name)
return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
def _request(self, method, path, postdata):
'''
Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout).
This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5.
'''
headers = {'Host': self.__url.hostname,
'User-Agent': USER_AGENT,
'Authorization': self.__auth_header,
'Content-type': 'application/json'}
try:
self.__conn.request(method, path, postdata, headers)
return self._get_response()
except httplib.BadStatusLine as e:
if e.line == "''": # if connection was closed, try again
self.__conn.close()
self.__conn.request(method, path, postdata, headers)
return self._get_response()
else:
raise
except (BrokenPipeError,ConnectionResetError):
# Python 3.5+ raises BrokenPipeError instead of BadStatusLine when the connection was reset
# ConnectionResetError happens on FreeBSD with Python 3.4
self.__conn.close()
self.__conn.request(method, path, postdata, headers)
return self._get_response()
def __call__(self, *args, **argsn):
AuthServiceProxy.__id_count += 1
log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self._service_name,
json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
if args and argsn:
raise ValueError('Cannot handle both named and positional arguments')
postdata = json.dumps({'version': '1.1',
'method': self._service_name,
'params': args or argsn,
'id': AuthServiceProxy.__id_count}, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
response = self._request('POST', self.__url.path, postdata.encode('utf-8'))
if response['error'] is not None:
raise JSONRPCException(response['error'])
elif 'result' not in response:
raise JSONRPCException({
'code': -343, 'message': 'missing JSON-RPC result'})
else:
return response['result']
def _batch(self, rpc_call_list):
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
log.debug("--> "+postdata)
return self._request('POST', self.__url.path, postdata.encode('utf-8'))
def _get_response(self):
try:
http_response = self.__conn.getresponse()
except socket.timeout as e:
raise JSONRPCException({
'code': -344,
'message': '%r RPC took longer than %f seconds. Consider '
'using larger timeout for calls that take '
'longer to return.' % (self._service_name,
self.__conn.timeout)})
if http_response is None:
raise JSONRPCException({
'code': -342, 'message': 'missing HTTP response from server'})
content_type = http_response.getheader('Content-Type')
if content_type != 'application/json':
raise JSONRPCException({
'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)})
responsedata = http_response.read().decode('utf8')
response = json.loads(responsedata, parse_float=decimal.Decimal)
if "error" in response and response["error"] is None:
log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
else:
log.debug("<-- "+responsedata)
return response