Skip to content

Commit f9f5483

Browse files
committed
Merge pull request #75
8d6d739 rpc: wrapper for getblockcount (Jacob Welsh) 47b87a5 Update release notes (Jacob Welsh) 1a1b7ff rpc: wrappers for getbestblockhash, getmininginfo (Jacob Welsh) 0134cd3 rpc: Python style fixes (Jacob Welsh) 5360c00 rpc: update docstrings (Jacob Welsh) 9eaef82 rpc: split Proxy from RawProxy (**BREAKS API**) (Jacob Welsh)
2 parents 8b187ba + 8d6d739 commit f9f5483

File tree

2 files changed

+132
-61
lines changed

2 files changed

+132
-61
lines changed

bitcoin/rpc.py

Lines changed: 119 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,29 @@
4848
hexlify = lambda b: binascii.hexlify(b).decode('utf8')
4949

5050

51-
class JSONRPCException(Exception):
51+
class JSONRPCError(Exception):
52+
"""JSON-RPC protocol error"""
53+
5254
def __init__(self, rpc_error):
53-
super(JSONRPCException, self).__init__('msg: %r code: %r' %
54-
(rpc_error['message'], rpc_error['code']))
55+
super(JSONRPCException, self).__init__(
56+
'msg: %r code: %r' %
57+
(rpc_error['message'], rpc_error['code']))
5558
self.error = rpc_error
5659

5760

58-
class RawProxy(object):
59-
def __init__(self, service_url=None,
60-
service_port=None,
61-
btc_conf_file=None,
62-
timeout=DEFAULT_HTTP_TIMEOUT):
63-
"""Low-level JSON-RPC proxy
61+
# 0.4.0 compatibility
62+
JSONRPCException = JSONRPCError
6463

65-
Unlike Proxy no conversion is done from the raw JSON objects.
66-
"""
64+
65+
class BaseProxy(object):
66+
"""Base JSON-RPC proxy class. Contains only private methods; do not use
67+
directly."""
68+
69+
def __init__(self,
70+
service_url=None,
71+
service_port=None,
72+
btc_conf_file=None,
73+
timeout=DEFAULT_HTTP_TIMEOUT):
6774

6875
if service_url is None:
6976
# Figure out the path to the bitcoin.conf file
@@ -167,28 +174,14 @@ def _call(self, service_name, *args):
167174

168175
response = self._get_response()
169176
if response['error'] is not None:
170-
raise JSONRPCException(response['error'])
177+
raise JSONRPCError(response['error'])
171178
elif 'result' not in response:
172-
raise JSONRPCException({
179+
raise JSONRPCError({
173180
'code': -343, 'message': 'missing JSON-RPC result'})
174181
else:
175182
return response['result']
176183

177184

178-
def __getattr__(self, name):
179-
if name.startswith('__') and name.endswith('__'):
180-
# Python internal stuff
181-
raise AttributeError
182-
183-
# Create a callable to do the actual call
184-
f = lambda *args: self._call(name, *args)
185-
186-
# Make debuggers show <function bitcoin.rpc.name> rather than <function
187-
# bitcoin.rpc.<lambda>>
188-
f.__name__ = name
189-
return f
190-
191-
192185
def _batch(self, rpc_call_list):
193186
postdata = json.dumps(list(rpc_call_list))
194187
self.__conn.request('POST', self.__url.path, postdata,
@@ -202,7 +195,7 @@ def _batch(self, rpc_call_list):
202195
def _get_response(self):
203196
http_response = self.__conn.getresponse()
204197
if http_response is None:
205-
raise JSONRPCException({
198+
raise JSONRPCError({
206199
'code': -342, 'message': 'missing HTTP response from server'})
207200

208201
return json.loads(http_response.read().decode('utf8'),
@@ -212,33 +205,79 @@ def __del__(self):
212205
self.__conn.close()
213206

214207

215-
class Proxy(RawProxy):
216-
def __init__(self, service_url=None,
217-
service_port=None,
218-
btc_conf_file=None,
219-
timeout=DEFAULT_HTTP_TIMEOUT,
220-
**kwargs):
221-
"""Create a proxy to a bitcoin RPC service
208+
class RawProxy(BaseProxy):
209+
"""Low-level proxy to a bitcoin JSON-RPC service
210+
211+
Unlike ``Proxy``, no conversion is done besides parsing JSON. As far as
212+
Python is concerned, you can call any method; ``JSONRPCError`` will be
213+
raised if the server does not recognize it.
214+
"""
215+
def __init__(self,
216+
service_url=None,
217+
service_port=None,
218+
btc_conf_file=None,
219+
timeout=DEFAULT_HTTP_TIMEOUT,
220+
**kwargs):
221+
super(RawProxy, self).__init__(service_url=service_url,
222+
service_port=service_port,
223+
btc_conf_file=btc_conf_file,
224+
timeout=timeout,
225+
**kwargs)
222226

223-
Unlike RawProxy data is passed as objects, rather than JSON. (not yet
224-
fully implemented) Assumes Bitcoin Core version >= 0.9; older versions
225-
mostly work, but there are a few incompatibilities.
227+
def __getattr__(self, name):
228+
if name.startswith('__') and name.endswith('__'):
229+
# Python internal stuff
230+
raise AttributeError
231+
232+
# Create a callable to do the actual call
233+
f = lambda *args: self._call(name, *args)
234+
235+
# Make debuggers show <function bitcoin.rpc.name> rather than <function
236+
# bitcoin.rpc.<lambda>>
237+
f.__name__ = name
238+
return f
226239

227-
If service_url is not specified the username and password are read out
228-
of the file btc_conf_file. If btc_conf_file is not specified
229-
~/.bitcoin/bitcoin.conf or equivalent is used by default. The default
230-
port is set according to the chain parameters in use: mainnet, testnet,
231-
or regtest.
232240

233-
Usually no arguments to Proxy() are needed; the local bitcoind will be
234-
used.
241+
class Proxy(BaseProxy):
242+
"""Proxy to a bitcoin RPC service
235243
236-
timeout - timeout in seconds before the HTTP interface times out
244+
Unlike ``RawProxy``, data is passed as ``bitcoin.core`` objects or packed
245+
bytes, rather than JSON or hex strings. Not all methods are implemented
246+
yet; you can use ``call`` to access missing ones in a forward-compatible
247+
way. Assumes Bitcoin Core version >= 0.9; older versions mostly work, but
248+
there are a few incompatibilities.
249+
"""
250+
251+
def __init__(self,
252+
service_url=None,
253+
service_port=None,
254+
btc_conf_file=None,
255+
timeout=DEFAULT_HTTP_TIMEOUT,
256+
**kwargs):
257+
"""Create a proxy object
258+
259+
If ``service_url`` is not specified, the username and password are read
260+
out of the file ``btc_conf_file``. If ``btc_conf_file`` is not
261+
specified, ``~/.bitcoin/bitcoin.conf`` or equivalent is used by
262+
default. The default port is set according to the chain parameters in
263+
use: mainnet, testnet, or regtest.
264+
265+
Usually no arguments to ``Proxy()`` are needed; the local bitcoind will
266+
be used.
267+
268+
``timeout`` - timeout in seconds before the HTTP interface times out
237269
"""
238-
super(Proxy, self).__init__(service_url=service_url, service_port=service_port, btc_conf_file=btc_conf_file,
270+
271+
super(Proxy, self).__init__(service_url=service_url,
272+
service_port=service_port,
273+
btc_conf_file=btc_conf_file,
239274
timeout=timeout,
240275
**kwargs)
241276

277+
def call(self, service_name, *args):
278+
"""Call an RPC method by name and raw (JSON encodable) arguments"""
279+
return self._call(service_name, *args)
280+
242281
def dumpprivkey(self, addr):
243282
"""Return the private key matching an address
244283
"""
@@ -247,19 +286,26 @@ def dumpprivkey(self, addr):
247286
return CBitcoinSecret(r)
248287

249288
def getaccountaddress(self, account=None):
250-
"""Return the current Bitcoin address for receiving payments to this account."""
289+
"""Return the current Bitcoin address for receiving payments to this
290+
account."""
251291
r = self._call('getaccountaddress', account)
252292
return CBitcoinAddress(r)
253293

254294
def getbalance(self, account='*', minconf=1):
255295
"""Get the balance
256296
257-
account - The selected account. Defaults to "*" for entire wallet. It may be the default account using "".
258-
minconf - Only include transactions confirmed at least this many times. (default=1)
297+
account - The selected account. Defaults to "*" for entire wallet. It
298+
may be the default account using "".
299+
minconf - Only include transactions confirmed at least this many times.
300+
(default=1)
259301
"""
260302
r = self._call('getbalance', account, minconf)
261303
return int(r*COIN)
262304

305+
def getbestblockhash(self):
306+
"""Return hash of best (tip) block in longest block chain."""
307+
return lx(self._call('getbestblockhash'))
308+
263309
def getblock(self, block_hash):
264310
"""Get block <block_hash>
265311
@@ -272,30 +318,38 @@ def getblock(self, block_hash):
272318
(self.__class__.__name__, block_hash.__class__))
273319
try:
274320
r = self._call('getblock', block_hash, False)
275-
except JSONRPCException as ex:
321+
except JSONRPCError as ex:
276322
raise IndexError('%s.getblock(): %s (%d)' %
277323
(self.__class__.__name__, ex.error['message'], ex.error['code']))
278324
return CBlock.deserialize(unhexlify(r))
279325

326+
def getblockcount(self):
327+
"""Return the number of blocks in the longest block chain"""
328+
return self._call('getblockcount')
329+
280330
def getblockhash(self, height):
281331
"""Return hash of block in best-block-chain at height.
282332
283333
Raises IndexError if height is not valid.
284334
"""
285335
try:
286336
return lx(self._call('getblockhash', height))
287-
except JSONRPCException as ex:
337+
except JSONRPCError as ex:
288338
raise IndexError('%s.getblockhash(): %s (%d)' %
289339
(self.__class__.__name__, ex.error['message'], ex.error['code']))
290340

291341
def getinfo(self):
292-
"""Return an object containing various state info"""
342+
"""Return a JSON object containing various state info"""
293343
r = self._call('getinfo')
294344
if 'balance' in r:
295345
r['balance'] = int(r['balance'] * COIN)
296346
r['paytxfee'] = int(r['paytxfee'] * COIN)
297347
return r
298348

349+
def getmininginfo(self):
350+
"""Return a JSON object containing mining-related information"""
351+
return self._call('getmininginfo')
352+
299353
def getnewaddress(self, account=None):
300354
"""Return a new Bitcoin address for receiving payments.
301355
@@ -341,7 +395,7 @@ def getrawtransaction(self, txid, verbose=False):
341395
"""
342396
try:
343397
r = self._call('getrawtransaction', b2lx(txid), 1 if verbose else 0)
344-
except JSONRPCException as ex:
398+
except JSONRPCError as ex:
345399
raise IndexError('%s.getrawtransaction(): %s (%d)' %
346400
(self.__class__.__name__, ex.error['message'], ex.error['code']))
347401
if verbose:
@@ -368,7 +422,8 @@ def getreceivedbyaddress(self, addr, minconf=1):
368422
always show zero.
369423
370424
addr - The address. (CBitcoinAddress instance)
371-
minconf - Only include transactions confirmed at least this many times. (default=1)
425+
minconf - Only include transactions confirmed at least this many times.
426+
(default=1)
372427
"""
373428
r = self._call('getreceivedbyaddress', str(addr), minconf)
374429
return int(r * COIN)
@@ -382,7 +437,7 @@ def gettransaction(self, txid):
382437
"""
383438
try:
384439
r = self._call('gettransaction', b2lx(txid))
385-
except JSONRPCException as ex:
440+
except JSONRPCError as ex:
386441
raise IndexError('%s.getrawtransaction(): %s (%d)' %
387442
(self.__class__.__name__, ex.error['message'], ex.error['code']))
388443
return r
@@ -441,7 +496,8 @@ def listunspent(self, minconf=0, maxconf=9999999, addrs=None):
441496

442497
def lockunspent(self, unlock, outpoints):
443498
"""Lock or unlock outpoints"""
444-
json_outpoints = [{'txid':b2lx(outpoint.hash),'vout':outpoint.n} for outpoint in outpoints]
499+
json_outpoints = [{'txid':b2lx(outpoint.hash), 'vout':outpoint.n}
500+
for outpoint in outpoints]
445501
return self._call('lockunspent', unlock, json_outpoints)
446502

447503
def sendrawtransaction(self, tx, allowhighfees=False):
@@ -459,7 +515,8 @@ def sendrawtransaction(self, tx, allowhighfees=False):
459515

460516
def sendmany(self, fromaccount, payments, minconf=1, comment=''):
461517
"""Sent amount to a given address"""
462-
json_payments = {str(addr):float(amount)/COIN for addr,amount in payments.items()}
518+
json_payments = {str(addr):float(amount)/COIN
519+
for addr, amount in payments.items()}
463520
r = self._call('sendmany', fromaccount, json_payments, minconf, comment)
464521
return lx(r)
465522

@@ -516,7 +573,8 @@ def removenode(self, node):
516573
return self._addnode(node, 'remove')
517574

518575
__all__ = (
519-
'JSONRPCException',
520-
'RawProxy',
521-
'Proxy',
576+
'JSONRPCError',
577+
'JSONRPCException',
578+
'RawProxy',
579+
'Proxy',
522580
)

release-notes.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
python-bitcoinlib release notes
22
===============================
33

4+
v0.5.0
5+
======
6+
7+
Breaking API changes:
8+
9+
* Proxy no longer has ``__getattr__`` to support arbitrary methods. Use
10+
RawProxy or Proxy.call instead. This allows new wrappers to be added safely.
11+
See docstrings for details.
12+
13+
New features:
14+
15+
* New RPC calls: getbestblockhash, getblockcount, getmininginfo
16+
417
v0.4.0
518
======
619

0 commit comments

Comments
 (0)