Skip to content

Commit 8aa3922

Browse files
committed
Fix incorrect raising of IndexError in RPC
Previously due to the fact that the type of error returned by Bitcoin core wasn't checked, errors unrelated to missing blocks and txs would result in false IndexErrors instead. Additionally, this commit adds some useful subclasses for some of the different types of RPC errors.
1 parent 3ce6b76 commit 8aa3922

File tree

2 files changed

+69
-7
lines changed

2 files changed

+69
-7
lines changed

bitcoin/rpc.py

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,61 @@
6060

6161

6262
class JSONRPCError(Exception):
63-
"""JSON-RPC protocol error"""
63+
"""JSON-RPC protocol error base class
64+
65+
Subclasses of this class also exist for specific types of errors; the set
66+
of all subclasses is by no means complete.
67+
"""
68+
69+
SUBCLS_BY_CODE = {}
70+
71+
@classmethod
72+
def _register_subcls(cls, subcls):
73+
cls.SUBCLS_BY_CODE[subcls.RPC_ERROR_CODE] = subcls
74+
return subcls
75+
76+
def __new__(cls, rpc_error):
77+
assert cls is JSONRPCError
78+
cls = JSONRPCError.SUBCLS_BY_CODE.get(rpc_error['code'], cls)
79+
80+
self = Exception.__new__(cls)
6481

65-
def __init__(self, rpc_error):
6682
super(JSONRPCError, self).__init__(
6783
'msg: %r code: %r' %
6884
(rpc_error['message'], rpc_error['code']))
6985
self.error = rpc_error
7086

87+
return self
88+
89+
@JSONRPCError._register_subcls
90+
class ForbiddenBySafeModeError(JSONRPCError):
91+
RPC_ERROR_CODE = -2
92+
93+
@JSONRPCError._register_subcls
94+
class InvalidAddressOrKeyError(JSONRPCError):
95+
RPC_ERROR_CODE = -5
96+
97+
@JSONRPCError._register_subcls
98+
class InvalidParameterError(JSONRPCError):
99+
RPC_ERROR_CODE = -8
100+
101+
@JSONRPCError._register_subcls
102+
class VerifyError(JSONRPCError):
103+
RPC_ERROR_CODE = -25
104+
105+
@JSONRPCError._register_subcls
106+
class VerifyRejectedError(JSONRPCError):
107+
RPC_ERROR_CODE = -26
108+
109+
@JSONRPCError._register_subcls
110+
class VerifyAlreadyInChainError(JSONRPCError):
111+
RPC_ERROR_CODE = -27
112+
113+
@JSONRPCError._register_subcls
114+
class InWarmupError(JSONRPCError):
115+
RPC_ERROR_CODE = -28
116+
117+
71118
class BaseProxy(object):
72119
"""Base JSON-RPC proxy class. Contains only private methods; do not use
73120
directly."""
@@ -335,7 +382,7 @@ def getblockheader(self, block_hash, verbose=False):
335382
(self.__class__.__name__, block_hash.__class__))
336383
try:
337384
r = self._call('getblockheader', block_hash, verbose)
338-
except JSONRPCError as ex:
385+
except InvalidAddressOrKeyError as ex:
339386
raise IndexError('%s.getblockheader(): %s (%d)' %
340387
(self.__class__.__name__, ex.error['message'], ex.error['code']))
341388

@@ -364,7 +411,7 @@ def getblock(self, block_hash):
364411
(self.__class__.__name__, block_hash.__class__))
365412
try:
366413
r = self._call('getblock', block_hash, False)
367-
except JSONRPCError as ex:
414+
except InvalidAddressOrKeyError as ex:
368415
raise IndexError('%s.getblock(): %s (%d)' %
369416
(self.__class__.__name__, ex.error['message'], ex.error['code']))
370417
return CBlock.deserialize(unhexlify(r))
@@ -380,7 +427,7 @@ def getblockhash(self, height):
380427
"""
381428
try:
382429
return lx(self._call('getblockhash', height))
383-
except JSONRPCError as ex:
430+
except InvalidParameterError as ex:
384431
raise IndexError('%s.getblockhash(): %s (%d)' %
385432
(self.__class__.__name__, ex.error['message'], ex.error['code']))
386433

@@ -442,7 +489,7 @@ def getrawtransaction(self, txid, verbose=False):
442489
"""
443490
try:
444491
r = self._call('getrawtransaction', b2lx(txid), 1 if verbose else 0)
445-
except JSONRPCError as ex:
492+
except InvalidAddressOrKeyError as ex:
446493
raise IndexError('%s.getrawtransaction(): %s (%d)' %
447494
(self.__class__.__name__, ex.error['message'], ex.error['code']))
448495
if verbose:
@@ -485,7 +532,7 @@ def gettransaction(self, txid):
485532
"""
486533
try:
487534
r = self._call('gettransaction', b2lx(txid))
488-
except JSONRPCError as ex:
535+
except InvalidAddressOrKeyError as ex:
489536
raise IndexError('%s.getrawtransaction(): %s (%d)' %
490537
(self.__class__.__name__, ex.error['message'], ex.error['code']))
491538
return r
@@ -622,6 +669,13 @@ def removenode(self, node):
622669

623670
__all__ = (
624671
'JSONRPCError',
672+
'ForbiddenBySafeModeError',
673+
'InvalidAddressOrKeyError',
674+
'InvalidParameterError',
675+
'VerifyError',
676+
'VerifyRejectedError',
677+
'VerifyAlreadyInChainError',
678+
'InWarmupError',
625679
'RawProxy',
626680
'Proxy',
627681
)

release-notes.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ Breaking API changes:
1212
* The alias `JSONRPCException = JSONRPCError` has been removed. This alias was
1313
added for compatibility with v0.4.0 of python-bitcoinlib.
1414

15+
* Where appropriate, `RPC_INVALID_ADDRESS_OR_KEY` errors are now caught
16+
properly, which means that rather than raising `IndexError`, RPC commands
17+
such as `getblock` may raise `JSONRPCError` instead. For instance during
18+
initial startup previously python-bitcoinlib would incorrectly raise
19+
`IndexError` rather than letting the callee know that RPC was unusable. Along
20+
those lines, `JSONRPCError` subclasses have been added for some (but not
21+
all!) of the types of RPC errors Bitcoin Core returns.
22+
1523
Bugfixes:
1624

1725
* Fixed a spurious `AttributeError` when `bitcoin.rpc.Proxy()` fails.

0 commit comments

Comments
 (0)