1# Copyright (C) 2007 Jan-Klaas Kollhof
2# Copyright (C) 2011-2018 The python-bitcoinlib developers
3#
4# This file is part of python-bitcoinlib.
5#
6# It is subject to the license terms in the LICENSE file found in the top-level
7# directory of this distribution.
8#
9# No part of python-bitcoinlib, including this file, may be copied, modified,
10# propagated, or distributed except according to the terms contained in the
11# LICENSE file.
12
13"""Bitcoin Core RPC support
14
15By default this uses the standard library ``json`` module. By monkey patching,
16a different implementation can be used instead, at your own risk:
17
18>>> import simplejson
19>>> import bitcoin.rpc
20>>> bitcoin.rpc.json = simplejson
21
22(``simplejson`` is the externally maintained version of the same module and
23thus better optimized but perhaps less stable.)
24"""
25
26from __future__ import absolute_import, division, print_function, unicode_literals
27import ssl
28
29try:
30    import http.client as httplib
31except ImportError:
32    import httplib
33import base64
34import binascii
35import decimal
36import json
37import os
38import platform
39import sys
40try:
41    import urllib.parse as urlparse
42except ImportError:
43    import urlparse
44
45import bitcoin
46from bitcoin.core import COIN, x, lx, b2lx, CBlock, CBlockHeader, CTransaction, COutPoint, CTxOut
47from bitcoin.core.script import CScript
48from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret
49
50DEFAULT_USER_AGENT = "AuthServiceProxy/0.1"
51
52DEFAULT_HTTP_TIMEOUT = 30
53
54# (un)hexlify to/from unicode, needed for Python3
55unhexlify = binascii.unhexlify
56hexlify = binascii.hexlify
57if sys.version > '3':
58    unhexlify = lambda h: binascii.unhexlify(h.encode('utf8'))
59    hexlify = lambda b: binascii.hexlify(b).decode('utf8')
60
61
62class JSONRPCError(Exception):
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)
81
82        super(JSONRPCError, self).__init__(
83            'msg: %r  code: %r' %
84            (rpc_error['message'], rpc_error['code']))
85        self.error = rpc_error
86
87        return self
88
89@JSONRPCError._register_subcls
90class ForbiddenBySafeModeError(JSONRPCError):
91    RPC_ERROR_CODE = -2
92
93@JSONRPCError._register_subcls
94class InvalidAddressOrKeyError(JSONRPCError):
95    RPC_ERROR_CODE = -5
96
97@JSONRPCError._register_subcls
98class InvalidParameterError(JSONRPCError):
99    RPC_ERROR_CODE = -8
100
101@JSONRPCError._register_subcls
102class VerifyError(JSONRPCError):
103    RPC_ERROR_CODE = -25
104
105@JSONRPCError._register_subcls
106class VerifyRejectedError(JSONRPCError):
107    RPC_ERROR_CODE = -26
108
109@JSONRPCError._register_subcls
110class VerifyAlreadyInChainError(JSONRPCError):
111    RPC_ERROR_CODE = -27
112
113@JSONRPCError._register_subcls
114class InWarmupError(JSONRPCError):
115    RPC_ERROR_CODE = -28
116
117
118class BaseProxy(object):
119    """Base JSON-RPC proxy class. Contains only private methods; do not use
120    directly."""
121
122    def __init__(self,
123                 service_url=None,
124                 service_port=None,
125                 btc_conf_file=None,
126                 timeout=DEFAULT_HTTP_TIMEOUT,
127                 connection=None):
128
129        # Create a dummy connection early on so if __init__() fails prior to
130        # __conn being created __del__() can detect the condition and handle it
131        # correctly.
132        self.__conn = None
133        authpair = None
134
135        if service_url is None:
136            # Figure out the path to the bitcoin.conf file
137            if btc_conf_file is None:
138                if platform.system() == 'Darwin':
139                    btc_conf_file = os.path.expanduser('~/Library/Application Support/Bitcoin/')
140                elif platform.system() == 'Windows':
141                    btc_conf_file = os.path.join(os.environ['APPDATA'], 'Bitcoin')
142                else:
143                    btc_conf_file = os.path.expanduser('~/.bitcoin')
144                btc_conf_file = os.path.join(btc_conf_file, 'bitcoin.conf')
145
146            # Bitcoin Core accepts empty rpcuser, not specified in btc_conf_file
147            conf = {'rpcuser': ""}
148
149            # Extract contents of bitcoin.conf to build service_url
150            try:
151                with open(btc_conf_file, 'r') as fd:
152                    for line in fd.readlines():
153                        if '#' in line:
154                            line = line[:line.index('#')]
155                        if '=' not in line:
156                            continue
157                        k, v = line.split('=', 1)
158                        conf[k.strip()] = v.strip()
159
160            # Treat a missing bitcoin.conf as though it were empty
161            except FileNotFoundError:
162                pass
163
164            if service_port is None:
165                service_port = bitcoin.params.RPC_PORT
166            conf['rpcport'] = int(conf.get('rpcport', service_port))
167            conf['rpchost'] = conf.get('rpcconnect', 'localhost')
168
169            service_url = ('%s://%s:%d' %
170                ('http', conf['rpchost'], conf['rpcport']))
171
172            cookie_dir = conf.get('datadir', os.path.dirname(btc_conf_file))
173            if bitcoin.params.NAME != "mainnet":
174                cookie_dir = os.path.join(cookie_dir, bitcoin.params.NAME)
175            cookie_file = os.path.join(cookie_dir, ".cookie")
176            try:
177                with open(cookie_file, 'r') as fd:
178                    authpair = fd.read()
179            except IOError as err:
180                if 'rpcpassword' in conf:
181                    authpair = "%s:%s" % (conf['rpcuser'], conf['rpcpassword'])
182
183                else:
184                    raise ValueError('Cookie file unusable (%s) and rpcpassword not specified in the configuration file: %r' % (err, btc_conf_file))
185
186        else:
187            url = urlparse.urlparse(service_url)
188            authpair = "%s:%s" % (url.username, url.password)
189
190        self.__service_url = service_url
191        self.__url = urlparse.urlparse(service_url)
192
193        if self.__url.scheme not in ('http',):
194            raise ValueError('Unsupported URL scheme %r' % self.__url.scheme)
195
196        if self.__url.port is None:
197            port = httplib.HTTP_PORT
198        else:
199            port = self.__url.port
200        self.__id_count = 0
201
202        if authpair is None:
203            self.__auth_header = None
204        else:
205            authpair = authpair.encode('utf8')
206            self.__auth_header = b"Basic " + base64.b64encode(authpair)
207
208        if connection:
209            self.__conn = connection
210        else:
211            self.__conn = httplib.HTTPConnection(self.__url.hostname, port=port,
212                                                 timeout=timeout)
213
214    def _call(self, service_name, *args):
215        self.__id_count += 1
216
217        postdata = json.dumps({'version': '1.1',
218                               'method': service_name,
219                               'params': args,
220                               'id': self.__id_count})
221
222        headers = {
223            'Host': self.__url.hostname,
224            'User-Agent': DEFAULT_USER_AGENT,
225            'Content-type': 'application/json',
226        }
227
228        if self.__auth_header is not None:
229            headers['Authorization'] = self.__auth_header
230
231        self.__conn.request('POST', self.__url.path, postdata, headers)
232
233        response = self._get_response()
234        err = response.get('error')
235        if err is not None:
236            if isinstance(err, dict):
237                raise JSONRPCError(
238                    {'code': err.get('code', -345),
239                     'message': err.get('message', 'error message not specified')})
240            raise JSONRPCError({'code': -344, 'message': str(err)})
241        elif 'result' not in response:
242            raise JSONRPCError({
243                'code': -343, 'message': 'missing JSON-RPC result'})
244        else:
245            return response['result']
246
247    def _batch(self, rpc_call_list):
248        postdata = json.dumps(list(rpc_call_list))
249
250        headers = {
251            'Host': self.__url.hostname,
252            'User-Agent': DEFAULT_USER_AGENT,
253            'Content-type': 'application/json',
254        }
255
256        if self.__auth_header is not None:
257            headers['Authorization'] = self.__auth_header
258
259        self.__conn.request('POST', self.__url.path, postdata, headers)
260        return self._get_response()
261
262    def _get_response(self):
263        http_response = self.__conn.getresponse()
264        if http_response is None:
265            raise JSONRPCError({
266                'code': -342, 'message': 'missing HTTP response from server'})
267
268        rdata = http_response.read().decode('utf8')
269        try:
270            return json.loads(rdata, parse_float=decimal.Decimal)
271        except Exception:
272            raise JSONRPCError({
273                'code': -342,
274                'message': ('non-JSON HTTP response with \'%i %s\' from server: \'%.20s%s\''
275                            % (http_response.status, http_response.reason,
276                               rdata, '...' if len(rdata) > 20 else ''))})
277
278    def close(self):
279        if self.__conn is not None:
280            self.__conn.close()
281
282    def __del__(self):
283        if self.__conn is not None:
284            self.__conn.close()
285
286
287class RawProxy(BaseProxy):
288    """Low-level proxy to a bitcoin JSON-RPC service
289
290    Unlike ``Proxy``, no conversion is done besides parsing JSON. As far as
291    Python is concerned, you can call any method; ``JSONRPCError`` will be
292    raised if the server does not recognize it.
293    """
294    def __init__(self,
295                 service_url=None,
296                 service_port=None,
297                 btc_conf_file=None,
298                 timeout=DEFAULT_HTTP_TIMEOUT,
299                 **kwargs):
300        super(RawProxy, self).__init__(service_url=service_url,
301                                       service_port=service_port,
302                                       btc_conf_file=btc_conf_file,
303                                       timeout=timeout,
304                                       **kwargs)
305
306    def __getattr__(self, name):
307        if name.startswith('__') and name.endswith('__'):
308            # Prevent RPC calls for non-existing python internal attribute
309            # access. If someone tries to get an internal attribute
310            # of RawProxy instance, and the instance does not have this
311            # attribute, we do not want the bogus RPC call to happen.
312            raise AttributeError
313
314        # Create a callable to do the actual call
315        f = lambda *args: self._call(name, *args)
316
317        # Make debuggers show <function bitcoin.rpc.name> rather than <function
318        # bitcoin.rpc.<lambda>>
319        f.__name__ = name
320        return f
321
322
323class Proxy(BaseProxy):
324    """Proxy to a bitcoin RPC service
325
326    Unlike ``RawProxy``, data is passed as ``bitcoin.core`` objects or packed
327    bytes, rather than JSON or hex strings. Not all methods are implemented
328    yet; you can use ``call`` to access missing ones in a forward-compatible
329    way. Assumes Bitcoin Core version >= v0.16.0; older versions mostly work,
330    but there are a few incompatibilities.
331    """
332
333    def __init__(self,
334                 service_url=None,
335                 service_port=None,
336                 btc_conf_file=None,
337                 timeout=DEFAULT_HTTP_TIMEOUT,
338                 **kwargs):
339        """Create a proxy object
340
341        If ``service_url`` is not specified, the username and password are read
342        out of the file ``btc_conf_file``. If ``btc_conf_file`` is not
343        specified, ``~/.bitcoin/bitcoin.conf`` or equivalent is used by
344        default.  The default port is set according to the chain parameters in
345        use: mainnet, testnet, or regtest.
346
347        Usually no arguments to ``Proxy()`` are needed; the local bitcoind will
348        be used.
349
350        ``timeout`` - timeout in seconds before the HTTP interface times out
351        """
352
353        super(Proxy, self).__init__(service_url=service_url,
354                                    service_port=service_port,
355                                    btc_conf_file=btc_conf_file,
356                                    timeout=timeout,
357                                    **kwargs)
358
359    def call(self, service_name, *args):
360        """Call an RPC method by name and raw (JSON encodable) arguments"""
361        return self._call(service_name, *args)
362
363    def dumpprivkey(self, addr):
364        """Return the private key matching an address
365        """
366        r = self._call('dumpprivkey', str(addr))
367
368        return CBitcoinSecret(r)
369
370    def fundrawtransaction(self, tx, include_watching=False):
371        """Add inputs to a transaction until it has enough in value to meet its out value.
372
373        include_watching - Also select inputs which are watch only
374
375        Returns dict:
376
377        {'tx':        Resulting tx,
378         'fee':       Fee the resulting transaction pays,
379         'changepos': Position of added change output, or -1,
380        }
381        """
382        hextx = hexlify(tx.serialize())
383        r = self._call('fundrawtransaction', hextx, include_watching)
384
385        r['tx'] = CTransaction.deserialize(unhexlify(r['hex']))
386        del r['hex']
387
388        r['fee'] = int(r['fee'] * COIN)
389
390        return r
391
392    def generate(self, numblocks):
393        """
394        DEPRECATED (will be removed in bitcoin-core v0.19)
395
396        Mine blocks immediately (before the RPC call returns)
397
398        numblocks - How many blocks are generated immediately.
399
400        Returns iterable of block hashes generated.
401        """
402        r = self._call('generate', numblocks)
403        return (lx(blk_hash) for blk_hash in r)
404
405    def generatetoaddress(self, numblocks, addr):
406        """Mine blocks immediately (before the RPC call returns) and
407        allocate block reward to passed address. Replaces deprecated
408        "generate(self,numblocks)" method.
409
410        numblocks - How many blocks are generated immediately.
411        addr     - Address to receive block reward (CBitcoinAddress instance)
412
413        Returns iterable of block hashes generated.
414        """
415        r = self._call('generatetoaddress', numblocks, str(addr))
416        return (lx(blk_hash) for blk_hash in r)
417
418    def getaccountaddress(self, account=None):
419        """Return the current Bitcoin address for receiving payments to this
420        account."""
421        r = self._call('getaccountaddress', account)
422        return CBitcoinAddress(r)
423
424    def getbalance(self, account='*', minconf=1, include_watchonly=False):
425        """Get the balance
426
427        account - The selected account. Defaults to "*" for entire wallet. It
428        may be the default account using "".
429
430        minconf - Only include transactions confirmed at least this many times.
431        (default=1)
432
433        include_watchonly - Also include balance in watch-only addresses (see 'importaddress')
434        (default=False)
435        """
436        r = self._call('getbalance', account, minconf, include_watchonly)
437        return int(r*COIN)
438
439    def getbestblockhash(self):
440        """Return hash of best (tip) block in longest block chain."""
441        return lx(self._call('getbestblockhash'))
442
443    def getblockheader(self, block_hash, verbose=False):
444        """Get block header <block_hash>
445
446        verbose - If true a dict is returned with the values returned by
447                  getblockheader that are not in the block header itself
448                  (height, nextblockhash, etc.)
449
450        Raises IndexError if block_hash is not valid.
451        """
452        try:
453            block_hash = b2lx(block_hash)
454        except TypeError:
455            raise TypeError('%s.getblockheader(): block_hash must be bytes; got %r instance' %
456                    (self.__class__.__name__, block_hash.__class__))
457        try:
458            r = self._call('getblockheader', block_hash, verbose)
459        except InvalidAddressOrKeyError as ex:
460            raise IndexError('%s.getblockheader(): %s (%d)' %
461                    (self.__class__.__name__, ex.error['message'], ex.error['code']))
462
463        if verbose:
464            nextblockhash = None
465            if 'nextblockhash' in r:
466                nextblockhash = lx(r['nextblockhash'])
467            return {'confirmations':r['confirmations'],
468                    'height':r['height'],
469                    'mediantime':r['mediantime'],
470                    'nextblockhash':nextblockhash,
471                    'chainwork':x(r['chainwork'])}
472        else:
473            return CBlockHeader.deserialize(unhexlify(r))
474
475
476    def getblock(self, block_hash):
477        """Get block <block_hash>
478
479        Raises IndexError if block_hash is not valid.
480        """
481        try:
482            block_hash = b2lx(block_hash)
483        except TypeError:
484            raise TypeError('%s.getblock(): block_hash must be bytes; got %r instance' %
485                    (self.__class__.__name__, block_hash.__class__))
486        try:
487            # With this change ( https://github.com/bitcoin/bitcoin/commit/96c850c20913b191cff9f66fedbb68812b1a41ea#diff-a0c8f511d90e83aa9b5857e819ced344 ),
488            # bitcoin core's rpc takes 0/1/2 instead of true/false as the 2nd argument which specifies verbosity, since v0.15.0.
489            # The change above is backward-compatible so far; the old "false" is taken as the new "0".
490            r = self._call('getblock', block_hash, False)
491        except InvalidAddressOrKeyError as ex:
492            raise IndexError('%s.getblock(): %s (%d)' %
493                    (self.__class__.__name__, ex.error['message'], ex.error['code']))
494        return CBlock.deserialize(unhexlify(r))
495
496    def getblockcount(self):
497        """Return the number of blocks in the longest block chain"""
498        return self._call('getblockcount')
499
500    def getblockhash(self, height):
501        """Return hash of block in best-block-chain at height.
502
503        Raises IndexError if height is not valid.
504        """
505        try:
506            return lx(self._call('getblockhash', height))
507        except InvalidParameterError as ex:
508            raise IndexError('%s.getblockhash(): %s (%d)' %
509                    (self.__class__.__name__, ex.error['message'], ex.error['code']))
510
511    def getinfo(self):
512        """Return a JSON object containing various state info"""
513        r = self._call('getinfo')
514        if 'balance' in r:
515            r['balance'] = int(r['balance'] * COIN)
516        if 'paytxfee' in r:
517            r['paytxfee'] = int(r['paytxfee'] * COIN)
518        return r
519
520    def getmininginfo(self):
521        """Return a JSON object containing mining-related information"""
522        return self._call('getmininginfo')
523
524    def getnewaddress(self, account=None):
525        """Return a new Bitcoin address for receiving payments.
526
527        If account is not None, it is added to the address book so payments
528        received with the address will be credited to account.
529        """
530        r = None
531        if account is not None:
532            r = self._call('getnewaddress', account)
533        else:
534            r = self._call('getnewaddress')
535
536        return CBitcoinAddress(r)
537
538    def getrawchangeaddress(self):
539        """Returns a new Bitcoin address, for receiving change.
540
541        This is for use with raw transactions, NOT normal use.
542        """
543        r = self._call('getrawchangeaddress')
544        return CBitcoinAddress(r)
545
546    def getrawmempool(self, verbose=False):
547        """Return the mempool"""
548        if verbose:
549            return self._call('getrawmempool', verbose)
550
551        else:
552            r = self._call('getrawmempool')
553            r = [lx(txid) for txid in r]
554            return r
555
556    def getrawtransaction(self, txid, verbose=False):
557        """Return transaction with hash txid
558
559        Raises IndexError if transaction not found.
560
561        verbose - If true a dict is returned instead with additional
562        information on the transaction.
563
564        Note that if all txouts are spent and the transaction index is not
565        enabled the transaction may not be available.
566        """
567        try:
568            r = self._call('getrawtransaction', b2lx(txid), 1 if verbose else 0)
569        except InvalidAddressOrKeyError as ex:
570            raise IndexError('%s.getrawtransaction(): %s (%d)' %
571                    (self.__class__.__name__, ex.error['message'], ex.error['code']))
572        if verbose:
573            r['tx'] = CTransaction.deserialize(unhexlify(r['hex']))
574            del r['hex']
575            del r['txid']
576            del r['version']
577            del r['locktime']
578            del r['vin']
579            del r['vout']
580            r['blockhash'] = lx(r['blockhash']) if 'blockhash' in r else None
581        else:
582            r = CTransaction.deserialize(unhexlify(r))
583
584        return r
585
586    def getreceivedbyaddress(self, addr, minconf=1):
587        """Return total amount received by given a (wallet) address
588
589        Get the amount received by <address> in transactions with at least
590        [minconf] confirmations.
591
592        Works only for addresses in the local wallet; other addresses will
593        always show zero.
594
595        addr    - The address. (CBitcoinAddress instance)
596
597        minconf - Only include transactions confirmed at least this many times.
598        (default=1)
599        """
600        r = self._call('getreceivedbyaddress', str(addr), minconf)
601        return int(r * COIN)
602
603    def gettransaction(self, txid):
604        """Get detailed information about in-wallet transaction txid
605
606        Raises IndexError if transaction not found in the wallet.
607
608        FIXME: Returned data types are not yet converted.
609        """
610        try:
611            r = self._call('gettransaction', b2lx(txid))
612        except InvalidAddressOrKeyError as ex:
613            raise IndexError('%s.getrawtransaction(): %s (%d)' %
614                    (self.__class__.__name__, ex.error['message'], ex.error['code']))
615        return r
616
617    def gettxout(self, outpoint, includemempool=True):
618        """Return details about an unspent transaction output.
619
620        Raises IndexError if outpoint is not found or was spent.
621
622        includemempool - Include mempool txouts
623        """
624        r = self._call('gettxout', b2lx(outpoint.hash), outpoint.n, includemempool)
625
626        if r is None:
627            raise IndexError('%s.gettxout(): unspent txout %r not found' % (self.__class__.__name__, outpoint))
628
629        r['txout'] = CTxOut(int(r['value'] * COIN),
630                            CScript(unhexlify(r['scriptPubKey']['hex'])))
631        del r['value']
632        del r['scriptPubKey']
633        r['bestblock'] = lx(r['bestblock'])
634        return r
635
636    def importaddress(self, addr, label='', rescan=True):
637        """Adds an address or pubkey to wallet without the associated privkey."""
638        addr = str(addr)
639
640        r = self._call('importaddress', addr, label, rescan)
641        return r
642
643    def listunspent(self, minconf=0, maxconf=9999999, addrs=None):
644        """Return unspent transaction outputs in wallet
645
646        Outputs will have between minconf and maxconf (inclusive)
647        confirmations, optionally filtered to only include txouts paid to
648        addresses in addrs.
649        """
650        r = None
651        if addrs is None:
652            r = self._call('listunspent', minconf, maxconf)
653        else:
654            addrs = [str(addr) for addr in addrs]
655            r = self._call('listunspent', minconf, maxconf, addrs)
656
657        r2 = []
658        for unspent in r:
659            unspent['outpoint'] = COutPoint(lx(unspent['txid']), unspent['vout'])
660            del unspent['txid']
661            del unspent['vout']
662
663            # address isn't always available as Bitcoin Core allows scripts w/o
664            # an address type to be imported into the wallet, e.g. non-p2sh
665            # segwit
666            try:
667                unspent['address'] = CBitcoinAddress(unspent['address'])
668            except KeyError:
669                pass
670            unspent['scriptPubKey'] = CScript(unhexlify(unspent['scriptPubKey']))
671            unspent['amount'] = int(unspent['amount'] * COIN)
672            r2.append(unspent)
673        return r2
674
675    def lockunspent(self, unlock, outpoints):
676        """Lock or unlock outpoints"""
677        json_outpoints = [{'txid':b2lx(outpoint.hash), 'vout':outpoint.n}
678                          for outpoint in outpoints]
679        return self._call('lockunspent', unlock, json_outpoints)
680
681    def sendrawtransaction(self, tx, allowhighfees=False):
682        """Submit transaction to local node and network.
683
684        allowhighfees - Allow even if fees are unreasonably high.
685        """
686        hextx = hexlify(tx.serialize())
687        r = None
688        if allowhighfees:
689            r = self._call('sendrawtransaction', hextx, True)
690        else:
691            r = self._call('sendrawtransaction', hextx)
692        return lx(r)
693
694    def sendmany(self, fromaccount, payments, minconf=1, comment='', subtractfeefromamount=[]):
695        """Send amount to given addresses.
696
697        payments - dict with {address: amount}
698        """
699        json_payments = {str(addr):float(amount)/COIN
700                         for addr, amount in payments.items()}
701        r = self._call('sendmany', fromaccount, json_payments, minconf, comment, subtractfeefromamount)
702        return lx(r)
703
704    def sendtoaddress(self, addr, amount, comment='', commentto='', subtractfeefromamount=False):
705        """Send amount to a given address"""
706        addr = str(addr)
707        amount = float(amount)/COIN
708        r = self._call('sendtoaddress', addr, amount, comment, commentto, subtractfeefromamount)
709        return lx(r)
710
711    def signrawtransaction(self, tx, *args):
712        """Sign inputs for transaction
713
714        FIXME: implement options
715        """
716        hextx = hexlify(tx.serialize())
717        r = self._call('signrawtransaction', hextx, *args)
718        r['tx'] = CTransaction.deserialize(unhexlify(r['hex']))
719        del r['hex']
720        return r
721
722    def signrawtransactionwithwallet(self, tx, *args):
723        """Sign inputs for transaction
724            bicoincore >= 0.17.x
725
726        FIXME: implement options
727        """
728        hextx = hexlify(tx.serialize())
729        r = self._call('signrawtransactionwithwallet', hextx, *args)
730        r['tx'] = CTransaction.deserialize(unhexlify(r['hex']))
731        del r['hex']
732        return r
733
734    def submitblock(self, block, params=None):
735        """Submit a new block to the network.
736
737        params is optional and is currently ignored by bitcoind. See
738        https://en.bitcoin.it/wiki/BIP_0022 for full specification.
739        """
740        hexblock = hexlify(block.serialize())
741        if params is not None:
742            return self._call('submitblock', hexblock, params)
743        else:
744            return self._call('submitblock', hexblock)
745
746    def validateaddress(self, address):
747        """Return information about an address"""
748        r = self._call('validateaddress', str(address))
749        if r['isvalid']:
750            r['address'] = CBitcoinAddress(r['address'])
751        if 'pubkey' in r:
752            r['pubkey'] = unhexlify(r['pubkey'])
753        return r
754
755    def unlockwallet(self, password, timeout=60):
756        """Stores the wallet decryption key in memory for 'timeout' seconds.
757
758        password - The wallet passphrase.
759
760        timeout - The time to keep the decryption key in seconds.
761        (default=60)
762        """
763        r = self._call('walletpassphrase', password, timeout)
764        return r
765
766    def _addnode(self, node, arg):
767        r = self._call('addnode', node, arg)
768        return r
769
770    def addnode(self, node):
771        return self._addnode(node, 'add')
772
773    def addnodeonetry(self, node):
774        return self._addnode(node, 'onetry')
775
776    def removenode(self, node):
777        return self._addnode(node, 'remove')
778
779__all__ = (
780    'JSONRPCError',
781    'ForbiddenBySafeModeError',
782    'InvalidAddressOrKeyError',
783    'InvalidParameterError',
784    'VerifyError',
785    'VerifyRejectedError',
786    'VerifyAlreadyInChainError',
787    'InWarmupError',
788    'RawProxy',
789    'Proxy',
790)
791