Sitelet https://github.com/jgarzik/python-bitcoinrpc/commit/fb4751c445aadac8e99630edf15db8d62d2cf12f
Skip to content

Commit fb4751c

Browse files
author
Jeff Garzik
committed
Merge pull request #38 from gavinandresen/better_batch
Better JSON 2.0 batch support
2 parents 0443b2f + 7b73c04 commit fb4751c

2 files changed

Lines changed: 75 additions & 11 deletions

File tree

‎README‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ It includes the following generic improvements:
66
- sends protocol 'version', per JSON-RPC 1.1
77
- sends proper, incrementing 'id'
88
- uses standard Python json lib
9+
- can optionally log all RPC calls and results
10+
- JSON-2.0 batch support
911

1012
It also includes the following bitcoin-specific details:
1113

1214
- sends Basic HTTP authentication headers
13-
- parses all JSON numbers that look like floats as Decimal
15+
- parses all JSON numbers that look like floats as Decimal,
16+
and serializes Decimal values to JSON-RPC connections.
1417

1518
Installation:
1619

@@ -19,3 +22,35 @@ Installation:
1922

2023
Note: This will only install bitcoinrpc. If you also want to install jsonrpc to preserve
2124
backwards compatibility, you have to replace 'bitcoinrpc' with 'jsonrpc' in setup.py and run it again.
25+
26+
Example usage:
27+
28+
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
29+
30+
# rpc_user and rpc_password are set in the bitcoin.conf file
31+
rpc_connection = AuthServiceProxy("http://%s:%s@127.0.0.1:8332"%(rpc_user, rpc_password))q
32+
best_block_hash = rpc_connection.getbestblockhash()
33+
print(rpc_connection.getblock(best_block_hash))
34+
35+
# batch support : print timestamps of blocks 0 to 99 in 2 RPC round-trips:
36+
commands = [ [ "getblockhash", height] for height in range(100) ]
37+
block_hashes = rpc_connection.batch_(commands)
38+
blocks = rpc_connection.batch_([ [ "getblock", h ] for h in block_hashes ])
39+
block_times = [ block["time"] for block in blocks ]
40+
print(block_times)
41+
42+
Logging all RPC calls to stderr:
43+
44+
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
45+
import logging
46+
47+
logging.basicConfig()
48+
logging.getLogger("BitcoinRPC").setLevel(logging.DEBUG)
49+
50+
rpc_connection = AuthServiceProxy("http://%s:%s@127.0.0.1:8332"%(rpc_user, rpc_password))
51+
print(rpc_connection.getinfo())
52+
53+
Produces output on stderr like:
54+
55+
DEBUG:BitcoinRPC:-1-> getinfo []
56+
DEBUG:BitcoinRPC:<-1- {"connections": 8, ...etc }

‎bitcoinrpc/authproxy.py‎

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@
3939
except ImportError:
4040
import httplib
4141
import base64
42-
import json
4342
import decimal
43+
import json
44+
import logging
4445
try:
4546
import urllib.parse as urlparse
4647
except ImportError:
@@ -50,6 +51,7 @@
5051

5152
HTTP_TIMEOUT = 30
5253

54+
log = logging.getLogger("BitcoinRPC")
5355

5456
class JSONRPCException(Exception):
5557
def __init__(self, rpc_error):
@@ -84,9 +86,9 @@ def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connect
8486
pass
8587
authpair = user + b':' + passwd
8688
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
87-
88-
if connection:
89-
# Callables re-use the connection of the original proxy
89+
90+
if connection:
91+
# Callables re-use the connection of the original proxy
9092
self.__conn = connection
9193
elif self.__url.scheme == 'https':
9294
self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
@@ -107,6 +109,8 @@ def __getattr__(self, name):
107109
def __call__(self, *args):
108110
AuthServiceProxy.__id_count += 1
109111

112+
log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self.__service_name,
113+
json.dumps(args, default=EncodeDecimal)))
110114
postdata = json.dumps({'version': '1.1',
111115
'method': self.__service_name,
112116
'params': args,
@@ -126,21 +130,46 @@ def __call__(self, *args):
126130
else:
127131
return response['result']
128132

129-
def _batch(self, rpc_call_list):
130-
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal)
133+
def batch_(self, rpc_calls):
134+
"""Batch RPC call.
135+
Pass array of arrays: [ [ "method", params... ], ... ]
136+
Returns array of results.
137+
"""
138+
batch_data = []
139+
for rpc_call in rpc_calls:
140+
AuthServiceProxy.__id_count += 1
141+
m = rpc_call.pop(0)
142+
batch_data.append({"jsonrpc":"2.0", "method":m, "params":rpc_call, "id":AuthServiceProxy.__id_count})
143+
144+
postdata = json.dumps(batch_data, default=EncodeDecimal)
145+
log.debug("--> "+postdata)
131146
self.__conn.request('POST', self.__url.path, postdata,
132147
{'Host': self.__url.hostname,
133148
'User-Agent': USER_AGENT,
134149
'Authorization': self.__auth_header,
135150
'Content-type': 'application/json'})
136-
137-
return self._get_response()
151+
results = []
152+
responses = self._get_response()
153+
for response in responses:
154+
if response['error'] is not None:
155+
raise JSONRPCException(response['error'])
156+
elif 'result' not in response:
157+
raise JSONRPCException({
158+
'code': -343, 'message': 'missing JSON-RPC result'})
159+
else:
160+
results.append(response['result'])
161+
return results
138162

139163
def _get_response(self):
140164
http_response = self.__conn.getresponse()
141165
if http_response is None:
142166
raise JSONRPCException({
143167
'code': -342, 'message': 'missing HTTP response from server'})
144168

145-
return json.loads(http_response.read().decode('utf8'),
146-
parse_float=decimal.Decimal)
169+
responsedata = http_response.read().decode('utf8')
170+
response = json.loads(responsedata, parse_float=decimal.Decimal)
171+
if "error" in response and response["error"] is None:
172+
log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal)))
173+
else:
174+
log.debug("<-- "+responsedata)
175+
return response

0 commit comments

Comments
 (0)