opello.{com,net,org}

Python and JSON-RPC

Sunday, December 1, 2013 categories: code, jsonrpc, python

In playing with Bitcoin, in this case specifically with the bitcoin-qt client, I found myself wanting to more granularly control which of my coins I spent. This probably isn't something most people care about, or maybe even solve by using multiple wallets, but I thought that it would be nice to choose which addresses were used for transactions. I found that I was not alone in that desire. But sadly, the patch has yet to be merged despite going through a number of iterations.

Enter contrib/spendfrom/spendfrom.py. This Python script purported to solve the problem to some extent. But it wasn't quite as easy as I had hoped to get working. There is a README.md that highlights a dependency on "jsonrpc." That seemed easy enough, since I run Windows on this particular machine, I tried using C:\Python27\Scripts\easy_install.exe jsonrpc which indeed installed jsonrpc just not the one linked in the documentation, which I overlooked.

Once I got the right jsonrpc checked out from Bazaar, and copied to my site-packages directory, I thought I was good to go. However, I ran into a problem that spendfrom.py tests for:

def check_json_precision():
    """Make sure json library being used does not lose precision converting BTC values"""
    n = Decimal("20000000.00000003")
    satoshis = int(json.loads(json.dumps(float(n)))*1.0e8)
    if satoshis != 2000000000000003:
        raise RuntimeError("JSON encode/decode loses precision")

So I started digging, and found that the json object that jsonrpc comes with did some serialization by using unicode() that showed a loss of precision with the given value. This was pretty easy to verify, and I pushed a change to a github hosted version of the json-rpc.org bzr repository, thanks to git-remote-bzr. The change inserts a call to repr() before passing that string to unicode().

Finally, to get everything to work, I replaced all of the uses of Decimal() in the spendfrom.py script to just use float() and have not seen any issues. Granted I have not done a huge number of transactions, nor have I done any that were more than four decimal places. Hopefully I didn't introduce some insidious bug.