From 86fcc100ab5c771b41c1b9ac50b7c79b688d0669 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Thu, 25 Feb 2016 23:57:33 -0600 Subject: [PATCH 01/18] Run of the 2to3 script. --- run_electrum_server.py | 39 ++++++++++++++++++------------------- src/__init__.py | 16 +++++++-------- src/blockchain_processor.py | 34 ++++++++++++++++---------------- src/deserialize.py | 12 ++++++------ src/ircthread.py | 12 ++++++------ src/processor.py | 12 ++++++------ src/server_processor.py | 18 ++++++++--------- src/storage.py | 26 ++++++++++++------------- src/stratum_tcp.py | 10 +++++----- src/utils.py | 8 ++++---- 10 files changed, 93 insertions(+), 94 deletions(-) diff --git a/run_electrum_server.py b/run_electrum_server.py index dd89c8b6..39b2ea63 100755 --- a/run_electrum_server.py +++ b/run_electrum_server.py @@ -22,7 +22,7 @@ # SOFTWARE. import argparse -import ConfigParser +import configparser import logging import socket import sys @@ -46,11 +46,11 @@ logging.basicConfig() if sys.maxsize <= 2**32: - print "Warning: it looks like you are using a 32bit system. You may experience crashes caused by mmap" + print("Warning: it looks like you are using a 32bit system. You may experience crashes caused by mmap") if os.getuid() == 0: - print "Do not run this program as root!" - print "Run the install script to create a non-privileged user." + print("Do not run this program as root!") + print("Run the install script to create a non-privileged user.") sys.exit() def attempt_read_config(config, filename): @@ -82,7 +82,7 @@ def setup_network_params(config): storage.GENESIS_HASH = config.get('network', 'genesis_hash') def create_config(filename=None): - config = ConfigParser.ConfigParser() + config = configparser.ConfigParser() # set some defaults, which will be overwritten by the config file config.add_section('server') config.set('server', 'banner', 'Welcome to Electrum!') @@ -123,7 +123,7 @@ def create_config(filename=None): break if not os.path.isfile(filename): - print 'could not find electrum configuration file "%s"' % filename + print('could not find electrum configuration file "%s"' % filename) sys.exit(1) attempt_read_config(config, filename) @@ -135,24 +135,24 @@ def create_config(filename=None): def run_rpc_command(params, electrum_rpc_port): cmd = params[0] - import xmlrpclib - server = xmlrpclib.ServerProxy('http://localhost:%d' % electrum_rpc_port) + import xmlrpc.client + server = xmlrpc.client.ServerProxy('http://localhost:%d' % electrum_rpc_port) func = getattr(server, cmd) r = func(*params[1:]) if cmd == 'sessions': now = time.time() - print 'type address sub version time' + print('type address sub version time') for item in r: - print '%4s %21s %3s %7s %.2f' % (item.get('name'), + print('%4s %21s %3s %7s %.2f' % (item.get('name'), item.get('address'), item.get('subscriptions'), item.get('version'), (now - item.get('time')), - ) + )) elif cmd == 'debug': - print r + print(r) else: - print json.dumps(r, indent=4, sort_keys=True) + print(json.dumps(r, indent=4, sort_keys=True)) def cmd_banner_update(): @@ -169,18 +169,17 @@ def cmd_getinfo(): } def cmd_sessions(): - return map(lambda s: {"time": s.time, + return [{"time": s.time, "name": s.name, "address": s.address, "version": s.version, - "subscriptions": len(s.subscriptions)}, - dispatcher.request_dispatcher.get_sessions()) + "subscriptions": len(s.subscriptions)} for s in dispatcher.request_dispatcher.get_sessions()] def cmd_numsessions(): return len(dispatcher.request_dispatcher.get_sessions()) def cmd_peers(): - return server_proc.peers.keys() + return list(server_proc.peers.keys()) def cmd_numpeers(): return len(server_proc.peers) @@ -290,7 +289,7 @@ def stop_server(): try: run_rpc_command(args.command, electrum_rpc_port) except socket.error: - print "server not running" + print("server not running") sys.exit(1) sys.exit(0) @@ -301,12 +300,12 @@ def stop_server(): is_running = False if is_running: - print "server already running" + print("server already running") sys.exit(1) start_server(config) - from SimpleXMLRPCServer import SimpleXMLRPCServer + from xmlrpc.server import SimpleXMLRPCServer server = SimpleXMLRPCServer(('localhost', electrum_rpc_port), allow_none=True, logRequests=False) server.register_function(lambda: os.getpid(), 'getpid') server.register_function(shared.stop, 'stop') diff --git a/src/__init__.py b/src/__init__.py index 3434f4d6..1a5cb08d 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,9 +1,9 @@ -import utils -import storage -import deserialize -import networks -import blockchain_processor -import processor -import version +from . import utils +from . import storage +from . import deserialize +from . import networks +from . import blockchain_processor +from . import processor +from . import version import irc -import stratum_tcp +from . import stratum_tcp diff --git a/src/blockchain_processor.py b/src/blockchain_processor.py index 7b9cec35..35311d82 100644 --- a/src/blockchain_processor.py +++ b/src/blockchain_processor.py @@ -24,17 +24,17 @@ import hashlib from json import dumps, load import os -from Queue import Queue +from queue import Queue import random import sys import time import threading -import urllib +import urllib.request, urllib.parse, urllib.error -import deserialize -from processor import Processor, print_log -from storage import Storage -from utils import logger, hash_decode, hash_encode, Hash, header_from_string, header_to_string, ProfiledThread, \ +from . import deserialize +from .processor import Processor, print_log +from .storage import Storage +from .utils import logger, hash_decode, hash_encode, Hash, header_from_string, header_to_string, ProfiledThread, \ rev_hex, int_to_hex4 class BlockchainProcessor(Processor): @@ -156,7 +156,7 @@ def bitcoind(self, method, params=()): postdata = dumps({"method": method, 'params': params, 'id': 'jsonrpc'}) while True: try: - response = urllib.urlopen(self.bitcoind_url, postdata) + response = urllib.request.urlopen(self.bitcoind_url, postdata) r = load(response) response.close() except: @@ -355,7 +355,7 @@ def get_merkle(self, tx_hash, height, cache_only): tx_list = b.get('tx') tx_pos = tx_list.index(tx_hash) - merkle = map(hash_decode, tx_list) + merkle = list(map(hash_decode, tx_list)) target_hash = hash_decode(tx_hash) s = [] while len(merkle) != 1: @@ -568,7 +568,7 @@ def process(self, request, cache_only=False): txo = self.bitcoind('sendrawtransaction', params) print_log("sent tx:", txo) result = txo - except BaseException, e: + except BaseException as e: error = e.args[0] if error["code"] == -26: # If we return anything that's not the transaction hash, @@ -621,7 +621,7 @@ def get_block(self, block_hash): while True: try: - response = urllib.urlopen(self.bitcoind_url, postdata) + response = urllib.request.urlopen(self.bitcoind_url, postdata) r = load(response) response.close() except: @@ -667,7 +667,7 @@ def catch_up(self, sync=True): self.up_to_date = False try: next_block_hash = self.bitcoind('getblockhash', (self.storage.height + 1,)) - except BaseException, e: + except BaseException as e: revert = True next_block = self.get_block(next_block_hash if not revert else self.storage.last_hash) @@ -732,7 +732,7 @@ def memorypool_update(self): self.mempool_hashes = mempool_hashes # check all tx outputs - for tx_hash, tx in new_tx.iteritems(): + for tx_hash, tx in new_tx.items(): mpa = self.mempool_addresses.get(tx_hash, {}) out_values = [] out_sum = 0 @@ -754,7 +754,7 @@ def memorypool_update(self): self.mempool_unconfirmed[tx_hash] = set() # check all inputs - for tx_hash, tx in new_tx.iteritems(): + for tx_hash, tx in new_tx.items(): mpa = self.mempool_addresses.get(tx_hash, {}) # are we spending unconfirmed inputs? input_sum = 0 @@ -785,7 +785,7 @@ def memorypool_update(self): self.mempool_fees[tx_hash] += input_sum # remove deprecated entries from mempool_addresses - for tx_hash, addresses in self.mempool_addresses.items(): + for tx_hash, addresses in list(self.mempool_addresses.items()): if tx_hash not in self.mempool_hashes: del self.mempool_addresses[tx_hash] del self.mempool_values[tx_hash] @@ -795,7 +795,7 @@ def memorypool_update(self): # remove deprecated entries from mempool_hist new_mempool_hist = {} - for addr in self.mempool_hist.iterkeys(): + for addr in self.mempool_hist.keys(): h = self.mempool_hist[addr] hh = [] for tx_hash, delta in h: @@ -804,9 +804,9 @@ def memorypool_update(self): if hh: new_mempool_hist[addr] = hh # add new transactions to mempool_hist - for tx_hash in new_tx.iterkeys(): + for tx_hash in new_tx.keys(): addresses = self.mempool_addresses[tx_hash] - for addr, delta in addresses.iteritems(): + for addr, delta in addresses.items(): h = new_mempool_hist.get(addr, []) if (tx_hash, delta) not in h: h.append((tx_hash, delta)) diff --git a/src/deserialize.py b/src/deserialize.py index ccc4eaa7..7443f8b1 100644 --- a/src/deserialize.py +++ b/src/deserialize.py @@ -7,7 +7,7 @@ import struct import types -from utils import hash_160_to_pubkey_address, hash_160_to_script_address, public_key_to_pubkey_address, hash_encode,\ +from .utils import hash_160_to_pubkey_address, hash_160_to_script_address, public_key_to_pubkey_address, hash_encode,\ hash_160 @@ -170,11 +170,11 @@ def __init__(self, name, enumList): uniqueNames = [] uniqueValues = [] for x in enumList: - if isinstance(x, types.TupleType): + if isinstance(x, tuple): x, i = x - if not isinstance(x, types.StringType): + if not isinstance(x, bytes): raise EnumException("enum name is not a string: %r" % x) - if not isinstance(i, types.IntType): + if not isinstance(i, int): raise EnumException("enum value is not an integer: %r" % i) if x in uniqueNames: raise EnumException("enum name is not unique: %r" % x) @@ -235,13 +235,13 @@ def parse_Transaction(vds, is_coinbase): d['version'] = vds.read_int32() n_vin = vds.read_compact_size() d['inputs'] = [] - for i in xrange(n_vin): + for i in range(n_vin): o = parse_TxIn(vds) if not is_coinbase: d['inputs'].append(o) n_vout = vds.read_compact_size() d['outputs'] = [] - for i in xrange(n_vout): + for i in range(n_vout): o = parse_TxOut(vds, i) d['outputs'].append(o) diff --git a/src/ircthread.py b/src/ircthread.py index 90631475..cf481903 100644 --- a/src/ircthread.py +++ b/src/ircthread.py @@ -26,11 +26,11 @@ import socket import ssl import threading -import Queue +import queue import irc.client -from utils import logger -from utils import Hash -from version import VERSION +from .utils import logger +from .utils import Hash +from .version import VERSION out_msg = [] @@ -61,7 +61,7 @@ def __init__(self, processor, config): self.pruning_limit = config.get('leveldb', 'pruning_limit') self.nick = 'E_' + self.nick self.password = None - self.who_queue = Queue.Queue() + self.who_queue = queue.Queue() def getname(self): s = 'v' + VERSION + ' ' @@ -127,7 +127,7 @@ def who_thread(self): while not self.processor.shared.stopped(): try: connection, s = self.who_queue.get(timeout=1) - except Queue.Empty: + except queue.Empty: continue #logger.info("who: "+ s) connection.who(s) diff --git a/src/processor.py b/src/processor.py index e9aab6ec..87d4211e 100644 --- a/src/processor.py +++ b/src/processor.py @@ -22,14 +22,14 @@ # SOFTWARE. import json -import Queue as queue +import queue as queue import socket import threading import time import sys -from utils import random_string, timestr, print_log -from utils import logger +from .utils import random_string, timestr, print_log +from .utils import logger class Shared: @@ -94,7 +94,7 @@ def run(self): try: result = self.process(request) self.push_response(session, {'id': msg_id, 'result': result}) - except BaseException, e: + except BaseException as e: self.push_response(session, {'id': msg_id, 'error':str(e)}) except: logger.error("process error", exc_info=True) @@ -147,7 +147,7 @@ def pop_request(self): return self.request_queue.get() def get_session_by_address(self, address): - for x in self.sessions.values(): + for x in list(self.sessions.values()): if x.address == address: return x @@ -197,7 +197,7 @@ def do_dispatch(self, session, request): def get_sessions(self): with self.lock: - r = self.sessions.values() + r = list(self.sessions.values()) return r def add_session(self, session): diff --git a/src/server_processor.py b/src/server_processor.py index a6f87aec..b83d5f43 100644 --- a/src/server_processor.py +++ b/src/server_processor.py @@ -25,14 +25,14 @@ import sys import threading import time -import Queue +import queue -from processor import Processor -from utils import Hash, print_log -from version import VERSION -from utils import logger -from ircthread import IrcThread +from .processor import Processor +from .utils import Hash, print_log +from .version import VERSION +from .utils import logger +from .ircthread import IrcThread @@ -43,7 +43,7 @@ def __init__(self, config, shared): self.daemon = True self.config = config self.shared = shared - self.irc_queue = Queue.Queue() + self.irc_queue = queue.Queue() self.peers = {} if self.config.get('server', 'irc') == 'yes': @@ -60,7 +60,7 @@ def read_irc_results(self): while True: try: event, params = self.irc_queue.get(timeout=1) - except Queue.Empty: + except queue.Empty: continue #logger.info(event + ' ' + repr(params)) if event == 'join': @@ -73,7 +73,7 @@ def read_irc_results(self): def get_peers(self): - return self.peers.values() + return list(self.peers.values()) def process(self, request): diff --git a/src/storage.py b/src/storage.py index 05489ee5..05a0606f 100644 --- a/src/storage.py +++ b/src/storage.py @@ -28,8 +28,8 @@ import sys import threading -from processor import print_log, logger -from utils import bc_address_to_hash_160, hash_160_to_pubkey_address, Hash, \ +from .processor import print_log, logger +from .utils import bc_address_to_hash_160, hash_160_to_pubkey_address, Hash, \ bytes8_to_int, bytes4_to_int, int_to_bytes8, \ int_to_hex8, int_to_bytes4, int_to_hex4 @@ -52,7 +52,7 @@ def __init__(self, s): self.k = int(s[0:32].encode('hex'), 16) self.s = s[32:] if self.k==0 and self.s: - print "init error", len(self.s), "0x%0.64X" % self.k + print("init error", len(self.s), "0x%0.64X" % self.k) raise BaseException("z") def serialized(self): @@ -69,7 +69,7 @@ def is_singleton(self, key): return len(self.s) == 40 def get_singleton(self): - for i in xrange(256): + for i in range(256): if self.k == (1< 0.01 and session.message: cmd = session.parse_message() diff --git a/src/utils.py b/src/utils.py index 99d8fcf4..c480421c 100644 --- a/src/utils.py +++ b/src/utils.py @@ -21,7 +21,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from itertools import imap + import threading import time import hashlib @@ -155,7 +155,7 @@ def bc_address_to_hash_160(addr): def b58encode(v): """encode v, which is a string of bytes, to base58.""" - long_value = 0L + long_value = 0 for (i, c) in enumerate(v[::-1]): long_value += (256**i) * ord(c) @@ -180,7 +180,7 @@ def b58encode(v): def b58decode(v, length): """ decode v into a string of len bytes.""" - long_value = 0L + long_value = 0 for (i, c) in enumerate(v[::-1]): long_value += __b58chars.find(c) * (__b58base**i) @@ -246,7 +246,7 @@ def init_logger(): logger.setLevel(logging.INFO) def print_log(*args): - logger.info(" ".join(imap(str, args))) + logger.info(" ".join(map(str, args))) def print_warning(message): logger.warning(message) From 00130b613e730d1e977c17db5fdbc1a3169c205d Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Tue, 1 Mar 2016 02:24:39 -0600 Subject: [PATCH 02/18] Use bytes for most keys/values in the storage engine. Use binascii package for hex conversions. bytes is similar to Python 2's str type. --- src/storage.py | 67 ++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/src/storage.py b/src/storage.py index 05a0606f..56cd67ef 100644 --- a/src/storage.py +++ b/src/storage.py @@ -23,9 +23,8 @@ import plyvel import ast -import hashlib +from binascii import hexlify, unhexlify import os -import sys import threading from .processor import print_log, logger @@ -49,15 +48,14 @@ class Node(object): def __init__(self, s): - self.k = int(s[0:32].encode('hex'), 16) + self.k = int.from_bytes(s[0:32], 'big') self.s = s[32:] if self.k==0 and self.s: print("init error", len(self.s), "0x%0.64X" % self.k) raise BaseException("z") def serialized(self): - k = "0x%0.64X" % self.k - k = k[2:].decode('hex') + k = self.k.to_bytes(32, 'big') assert len(k) == 32 return k + self.s @@ -65,7 +63,7 @@ def has(self, c): return (self.k & (1<<(ord(c)))) != 0 def is_singleton(self, key): - assert self.s != '' + assert self.s != b'' return len(self.s) == 40 def get_singleton(self): @@ -75,7 +73,7 @@ def get_singleton(self): raise BaseException("get_singleton") def indexof(self, c): - assert self.k != 0 or self.s == '' + assert self.k != 0 or self.s == b'' x = 0 for i in range(ord(c)): if (self.k & (1< Date: Fri, 4 Mar 2016 03:48:57 -0600 Subject: [PATCH 03/18] A unit test for expected results of deserialize module's parse_Transasction in Python 3. --- src/test/test_deserialize.py | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/test/test_deserialize.py diff --git a/src/test/test_deserialize.py b/src/test/test_deserialize.py new file mode 100644 index 00000000..25b4ae0a --- /dev/null +++ b/src/test/test_deserialize.py @@ -0,0 +1,38 @@ +import unittest +try: + from electrumserver.deserialize import BCDataStream, parse_Transaction +except ImportError: + from src.deserialize import BCDataStream, parse_Transaction + +class DeserializeTest(unittest.TestCase): + example_tx_bytes = b'\x01\x00\x00\x00\x01{\x1e\xab\xe0 \x9b\x1f\xe7\x94\x12Eu\xef\x80pW\xc7z\xda!8\xaeO\xa8\xd6' \ + b'\xc4\xde\x03\x98\xa1O?\x00\x00\x00\x00\x00\xff\xff\xff\xff\x01\xf0\xca\x05*\x01\x00\x00\x00\x19v\xa9\x14' \ + b'\xcb\xc2\nvd\xf2\xf6\x9eSU\xaaBpE\xbc\x15\xe7\xc6\xc7r\x88\xac\x00\x00\x00\x00' + + def test_transaction_parsing(self): + vds = BCDataStream() + vds.write(self.example_tx_bytes) + parsed_tx = parse_Transaction(vds, is_coinbase=False) + expected_tx = { + 'inputs': [ + { + 'prevout_n': 0, + 'sequence': 4294967295, + 'prevout_hash': b'3f4fa19803dec4d6a84fae3821da7ac7577080ef75451294e71f9b20e0ab1e7b' + } + ], + 'outputs': [ + {'value': 4999990000, + 'raw_output_script': b'76a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac', + 'address': b'KaNd8ybzTDYKpyMB9X2dstvMwo5ogo5bT', + 'index': 0 + } + ], + 'version': 1, + 'lockTime': 0 + } + self.assertEqual(parsed_tx, expected_tx) + + +if __name__ == '__main__': + unittest.main() From ec4fdfccb893193c190ac7534f20ea5f0211b71a Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Fri, 4 Mar 2016 03:50:42 -0600 Subject: [PATCH 04/18] Use jsonrpc-pelix to get Python 3-compatible jsonrpclib. At some point we should consider another library. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 84c349af..a23d1b40 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ name="electrum-server", version="1.0", scripts=['run_electrum_server.py','electrum-server'], - install_requires=['plyvel','jsonrpclib', 'irc >= 11, <=14.0'], + install_requires=['plyvel','jsonrpclib-pelix', 'irc>=11, <=14.0'], package_dir={ 'electrumserver':'src' }, From dfdca6a08e3a4a3351a1dd6bbf522ec6f7812008 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Fri, 4 Mar 2016 09:56:50 -0600 Subject: [PATCH 05/18] Fix byte-string compatibility with Python 3 in hashing utilities. --- src/utils.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/utils.py b/src/utils.py index c480421c..55a8a6db 100644 --- a/src/utils.py +++ b/src/utils.py @@ -26,8 +26,10 @@ import time import hashlib import struct +from binascii import hexlify, unhexlify +from collections import deque -__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +__b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' __b58base = len(__b58chars) global PUBKEY_ADDRESS @@ -36,16 +38,16 @@ SCRIPT_ADDRESS = 5 def rev_hex(s): - return s.decode('hex')[::-1].encode('hex') + return hexlify(unhexlify(s)[::-1]) Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest() -hash_encode = lambda x: x[::-1].encode('hex') +hash_encode = lambda x: hexlify(x[::-1]) -hash_decode = lambda x: x.decode('hex')[::-1] +hash_decode = lambda x: unhexlify(x)[::-1] def header_to_string(res): @@ -140,7 +142,7 @@ def hash_160_to_address(h160, addrtype = 0): """ if h160 is None or len(h160) is not 20: return None - vh160 = chr(addrtype) + h160 + vh160 = bytes((addrtype,)) + h160 h = Hash(vh160) addr = vh160 + h[0:4] return b58encode(addr) @@ -157,14 +159,14 @@ def b58encode(v): long_value = 0 for (i, c) in enumerate(v[::-1]): - long_value += (256**i) * ord(c) + long_value += (256**i) * c - result = '' + result = deque() while long_value >= __b58base: div, mod = divmod(long_value, __b58base) - result = __b58chars[mod] + result + result.appendleft(__b58chars[mod]) long_value = div - result = __b58chars[long_value] + result + result.appendleft(__b58chars[long_value]) # Bitcoin does a little leading-zero-compression: # leading 0-bytes in the input become leading-1s @@ -175,7 +177,8 @@ def b58encode(v): else: break - return (__b58chars[0]*nPad) + result + result.extendleft(__b58chars[0:1]*nPad) + return bytes(result) def b58decode(v, length): From 094f406d098d87f8cadba6122535ad68c32153f1 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Fri, 4 Mar 2016 09:58:53 -0600 Subject: [PATCH 06/18] Fix byte-string compatibility with Python 3 in transaction parsing stack. --- src/deserialize.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/deserialize.py b/src/deserialize.py index 7443f8b1..d5a8d288 100644 --- a/src/deserialize.py +++ b/src/deserialize.py @@ -6,6 +6,7 @@ import string import struct import types +from binascii import hexlify from .utils import hash_160_to_pubkey_address, hash_160_to_script_address, public_key_to_pubkey_address, hash_encode,\ hash_160 @@ -117,7 +118,7 @@ def write_uint64(self, val): return self._write_num(' Date: Fri, 4 Mar 2016 15:04:13 -0600 Subject: [PATCH 07/18] Fix incorrect address being checked by tx parsing test. --- src/test/test_deserialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/test_deserialize.py b/src/test/test_deserialize.py index 25b4ae0a..4265f1ce 100644 --- a/src/test/test_deserialize.py +++ b/src/test/test_deserialize.py @@ -24,7 +24,7 @@ def test_transaction_parsing(self): 'outputs': [ {'value': 4999990000, 'raw_output_script': b'76a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac', - 'address': b'KaNd8ybzTDYKpyMB9X2dstvMwo5ogo5bT', + 'address': b'1KaNd8ybzTDYKpyMB9X2dstvMwo5ogo5bT', 'index': 0 } ], From b589e95c40c39e822d190f50a1073547e7a42a95 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Fri, 4 Mar 2016 15:05:57 -0600 Subject: [PATCH 08/18] Update test_utils tests to Python 3 hex conversion conventions. Assume bytes instead of character strings for most things. --- src/test/test_utils.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/test/test_utils.py b/src/test/test_utils.py index f7e22bf1..81d87e0d 100644 --- a/src/test/test_utils.py +++ b/src/test/test_utils.py @@ -1,28 +1,28 @@ -__author__ = 'erasmospunk' - +from binascii import hexlify, unhexlify import unittest -from utils import hash_160_to_address, bc_address_to_hash_160 +try: + from electrumserver.utils import hash_160_to_address, bc_address_to_hash_160 +except ImportError: + from src.utils import hash_160_to_address, bc_address_to_hash_160 class UtilTest(unittest.TestCase): def test_hash_160_to_address(self): self.assertEqual(hash_160_to_address(None), None) - self.assertEqual(hash_160_to_address('04e9fca1'.decode('hex')), None) - self.assertEqual(hash_160_to_address('04e9fca1f96e021dfaf35bbea267ec2c60787c1b1337'.decode('hex')), None) - self.assertEqual(hash_160_to_address('1ad3b0b711f211655a01142fbb8fecabe8e30b93'.decode('hex')), - '13SrAVFPVW1txSj34B8Bd6hnDbyPsVGa92') + self.assertEqual(hash_160_to_address(unhexlify('04e9fca1')), None) + self.assertEqual(hash_160_to_address(unhexlify('04e9fca1f96e021dfaf35bbea267ec2c60787c1b1337')), None) + self.assertEqual(hash_160_to_address(unhexlify('1ad3b0b711f211655a01142fbb8fecabe8e30b93')), + b'13SrAVFPVW1txSj34B8Bd6hnDbyPsVGa92') def test_bc_address_to_hash_160(self): self.assertEqual(bc_address_to_hash_160(None), None) - self.assertEqual(bc_address_to_hash_160(''), None) - self.assertEqual(bc_address_to_hash_160('13SrAVFPVW1txSj34B8Bd6hnDbyPsVGa921337'), None) - self.assertEqual(bc_address_to_hash_160('13SrAVFPVW1txSj34B8Bd6hnDbyPsVGa92').encode('hex'), - '1ad3b0b711f211655a01142fbb8fecabe8e30b93') - + self.assertEqual(bc_address_to_hash_160(b''), None) + self.assertEqual(bc_address_to_hash_160(b'13SrAVFPVW1txSj34B8Bd6hnDbyPsVGa921337'), None) + self.assertEqual(hexlify(bc_address_to_hash_160(b'13SrAVFPVW1txSj34B8Bd6hnDbyPsVGa92')), + b'1ad3b0b711f211655a01142fbb8fecabe8e30b93') if __name__ == '__main__': unittest.main() - From 870c202ccb6d8e66cbb9a19d874628ca0264874d Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Fri, 4 Mar 2016 15:09:14 -0600 Subject: [PATCH 09/18] Base 58 decoding utility function to return bytes instead of character string response. --- src/utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/utils.py b/src/utils.py index 55a8a6db..f0a04698 100644 --- a/src/utils.py +++ b/src/utils.py @@ -28,6 +28,7 @@ import struct from binascii import hexlify, unhexlify from collections import deque +from itertools import repeat __b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' __b58base = len(__b58chars) @@ -172,7 +173,7 @@ def b58encode(v): # leading 0-bytes in the input become leading-1s nPad = 0 for c in v: - if c == '\0': + if c == 0: nPad += 1 else: break @@ -187,12 +188,12 @@ def b58decode(v, length): for (i, c) in enumerate(v[::-1]): long_value += __b58chars.find(c) * (__b58base**i) - result = '' + result = deque() while long_value >= 256: div, mod = divmod(long_value, 256) - result = chr(mod) + result + result.appendleft(mod) long_value = div - result = chr(long_value) + result + result.appendleft(long_value) nPad = 0 for c in v: @@ -201,11 +202,11 @@ def b58decode(v, length): else: break - result = chr(0)*nPad + result + result.extendleft(repeat(0, nPad)) if length is not None and len(result) != length: return None - return result + return bytes(result) def EncodeBase58Check(vchIn): From 97cc9c60b9d73cabbe17b438e62ff0ff5957e76a Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 5 Mar 2016 18:31:42 +0100 Subject: [PATCH 10/18] Add `.travis.yml` for continuous integration --- .travis.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..06d531e7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +# The version of libleveldb in 12.04 is too old, better use trusty (14.04) +sudo: required +dist: trusty +language: python +python: + - "3.2" + - "3.3" + - "3.4" + - "pypy3" +# command to install dependencies +install: + - sudo apt-get install -y libleveldb-dev python-leveldb + - travis_retry python setup.py install +# command to run tests +script: + - nosetests From 7c6aec3bbc6a9232eab444a786222301a1536c80 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 5 Mar 2016 22:56:52 +0100 Subject: [PATCH 11/18] Add codecov.io coverage reports --- .travis.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 06d531e7..b2c4cbad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,15 +2,30 @@ sudo: required dist: trusty language: python + python: - "3.2" - "3.3" - "3.4" - "pypy3" + +before_install: + - pip install codecov + - pip install nose-cov + # command to install dependencies install: - sudo apt-get install -y libleveldb-dev python-leveldb - travis_retry python setup.py install + # command to run tests script: - - nosetests + - nosetests --with-coverage + +after_success: + - codecov + +matrix: + fast_finish: true + allow_failures: + - python: pypy3 From f4e9c5ef045ed76bd66bc1d72ca11b9c39b2c784 Mon Sep 17 00:00:00 2001 From: Johann Bauer Date: Sat, 5 Mar 2016 23:09:59 +0100 Subject: [PATCH 12/18] Add badges for coverage and build status to README --- .travis.yml | 1 + README.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index b2c4cbad..560b0ba0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ install: # command to run tests script: + - nosetests - nosetests --with-coverage after_success: diff --git a/README.md b/README.md index e9f1eb36..84eac4a8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![codecov.io](https://codecov.io/github/bauerj/electrum-server/coverage.svg?branch=python3-migration)](https://codecov.io/github/bauerj/electrum-server?branch=python3-migration) +[![Build Status](https://travis-ci.org/bauerj/electrum-server.svg?branch=python3-migration)](https://travis-ci.org/bauerj/electrum-server) + Electrum-server for the Electrum client ========================================= From 1abef458672c90361d4ecb6d41d5a9266c5d7754 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Tue, 8 Mar 2016 01:54:10 -0600 Subject: [PATCH 13/18] A couple of benchmarking scripts for comparing approaches and platforms. --- src/test/benchmark_byte_prepends.py | 36 +++++++++++++++++++++++++++++ src/test/benchmark_deserialize.py | 10 ++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/test/benchmark_byte_prepends.py create mode 100644 src/test/benchmark_deserialize.py diff --git a/src/test/benchmark_byte_prepends.py b/src/test/benchmark_byte_prepends.py new file mode 100644 index 00000000..f24d7411 --- /dev/null +++ b/src/test/benchmark_byte_prepends.py @@ -0,0 +1,36 @@ +if __name__ == '__main__': + from timeit import timeit + from sys import version + + basic_setup = "example_sequence = b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff'" + + print("On {},".format(version)) + test_bytearray_append_then_reverse = """thing=bytearray() +for byte in example_sequence: + thing.append(byte) +result = thing[::-1]""" + time = timeit(test_bytearray_append_then_reverse, basic_setup, number=10000) + print('appending to a bytearray then reversing it takes {:.20f}s.'.format(time)) + + + setup_deque_appendleft = "from collections import deque\n" + basic_setup + test_deque_appendleft = """thing=deque() +for byte in example_sequence: + thing.appendleft(byte) +result = bytes(thing)""" + time = timeit(test_deque_appendleft, setup_deque_appendleft, number=10000) + print('Left-appending to a deque then converting to bytes takes {:.20f}s.'.format(time)) + + test_concat_new_bytes = """thing=bytes() +for byte in example_sequence: + thing = bytes((byte,)) + thing +result = thing""" + time = timeit(test_concat_new_bytes, basic_setup, number=10000) + print('Converting the int to a single-byte bytes then concatenating takes {:.20f}s.'.format(time)) + + test_concat_sliced_bytes = """thing=bytes() +for i in range(0, len(example_sequence)): + thing = example_sequence[i:i+1] + thing +result = thing""" + time = timeit(test_concat_sliced_bytes, basic_setup, number=10000) + print('Slicing single-byte bytes then concatenating takes {:.20f}s.'.format(time)) \ No newline at end of file diff --git a/src/test/benchmark_deserialize.py b/src/test/benchmark_deserialize.py new file mode 100644 index 00000000..59140b32 --- /dev/null +++ b/src/test/benchmark_deserialize.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +from timeit import timeit +from sys import version + +if __name__ == '__main__': + parse_test_setup="from src.deserialize import BCDataStream, parse_Transaction\nexample_tx_bytes = b'\\x01\\x00\\x00\\x00\\x01{\\x1e\\xab\\xe0 \\x9b\\x1f\\xe7\\x94\\x12Eu\\xef\\x80pW\\xc7z\\xda!8\\xaeO\\xa8\\xd6\\xc4\\xde\\x03\\x98\\xa1O?\\x00\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x01\\xf0\\xca\\x05*\\x01\\x00\\x00\\x00\\x19v\\xa9\\x14\\xcb\\xc2\\nvd\\xf2\\xf6\\x9eSU\\xaaBpE\\xbc\\x15\\xe7\\xc6\\xc7r\\x88\\xac\\x00\\x00\\x00\\x00'" + parse_test_run="vds = BCDataStream()\nvds.write(example_tx_bytes)\nparsed_tx = parse_Transaction(vds, is_coinbase=False)" + print("Running...") + time = timeit(parse_test_run, parse_test_setup, number=100000) + print("Took {} seconds in {}".format(time, version)) \ No newline at end of file From c23a75e7f558921f24a9712c389270d9789572ea Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Tue, 8 Mar 2016 13:32:09 -0600 Subject: [PATCH 14/18] Unit tests for header transformations. --- src/test/test_utils.py | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/test/test_utils.py b/src/test/test_utils.py index 81d87e0d..7caf54bd 100644 --- a/src/test/test_utils.py +++ b/src/test/test_utils.py @@ -1,13 +1,12 @@ from binascii import hexlify, unhexlify import unittest try: - from electrumserver.utils import hash_160_to_address, bc_address_to_hash_160 + from electrumserver.utils import hash_160_to_address, bc_address_to_hash_160, header_from_string, header_to_string except ImportError: - from src.utils import hash_160_to_address, bc_address_to_hash_160 + from src.utils import hash_160_to_address, bc_address_to_hash_160, header_from_string, header_to_string class UtilTest(unittest.TestCase): - def test_hash_160_to_address(self): self.assertEqual(hash_160_to_address(None), None) self.assertEqual(hash_160_to_address(unhexlify('04e9fca1')), None) @@ -15,13 +14,46 @@ def test_hash_160_to_address(self): self.assertEqual(hash_160_to_address(unhexlify('1ad3b0b711f211655a01142fbb8fecabe8e30b93')), b'13SrAVFPVW1txSj34B8Bd6hnDbyPsVGa92') - def test_bc_address_to_hash_160(self): self.assertEqual(bc_address_to_hash_160(None), None) self.assertEqual(bc_address_to_hash_160(b''), None) self.assertEqual(bc_address_to_hash_160(b'13SrAVFPVW1txSj34B8Bd6hnDbyPsVGa921337'), None) self.assertEqual(hexlify(bc_address_to_hash_160(b'13SrAVFPVW1txSj34B8Bd6hnDbyPsVGa92')), - b'1ad3b0b711f211655a01142fbb8fecabe8e30b93') + b'1ad3b0b711f211655a01142fbb8fecabe8e30b93') + + def test_headers_from_string(self): + block_hex = b"03000000855d154359b794c6ba2c5586cf6278eee5bd658faa954200000000000000000035e95b187fb0d5175f681c2" \ + b"a1c499c3c91a0781c91679545a1fcabba2cd7cd801b04bd5515081518aa8afeaf020100000001000000000000000000" \ + b"0000000000000000000000000000000000000000000000ffffffff25035b9d05384d2f040f04bd550894251c009a050" \ + b"0000f5b4254434368696e612e636f6d5d20000000000100f90295000000001976a9142c30a6aaac6d96687291475d7d" \ + b"52f4b469f665a688ac0000000001000000018651ed89909def949ee3e05631bb332f339f4c667b0af7e2d94e7940a45" \ + b"68a21000000006a473044022059c947c745d94d1019990f39b2c938c9ce3e79fe9a650cff01589ec3b5ed2c2c02206d" \ + b"cff5e15ae6aa9c7d44308f16963dd5ad1dd3229e923c9de3ae1c14f6f91a02012102163e80de410646145142636833d" \ + b"8a92de4bb5c99e49bd52be5346fb1030628d4ffffffff0250ac6002000000001976a9145ca26d65ee83f441ef98b624" \ + b"763a305d50eb36cf88aca0860100000000001976a914838eb1034b719f9c47ab853aee63d505e4176a8388ac00000000" + block_bytes = unhexlify(block_hex) + block_header_bytes = block_bytes[0:80] + header = header_from_string(block_header_bytes) + self.assertEqual(header['version'], 3) + self.assertEqual(header['prev_block_hash'], b'0000000000000000004295aa8f65bde5ee7862cf86552cbac694b75943155d85') + self.assertEqual(header['merkle_root'], b'80cdd72cbaabfca1459567911c78a0913c9c491c2a1c685f17d5b07f185be935') + self.assertEqual(header['timestamp'], 1438450715) + self.assertEqual(header['bits'], 404031509) + self.assertEqual(header['nonce'], 2952694442) + + def test_headers_to_string(self): + header = { + 'version': 1, + 'prev_block_hash': b'00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81', + 'merkle_root': b'2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3', + 'timestamp': 1305998791, + 'bits': 440711666, + 'nonce': 2504433986, + } + header_hex = header_to_string(header) + expected_header_hex = b'0100000081cd02ab7e569e8bcd9317e2fe99f2de44d49ab2b8851ba4a308000000000000e320b6c2fffc8' \ + b'd750423db8b1eb942ae710e951ed797f7affc8892b0f1fc122bc7f5d74df2b9441a42a14695' + self.assertEqual(header_hex, expected_header_hex) if __name__ == '__main__': From 369d3fd6a88a1f7f6fb7c4e353df337bb12df649 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Tue, 8 Mar 2016 13:32:42 -0600 Subject: [PATCH 15/18] Make int to hex functions Python 3-compatible. --- src/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.py b/src/utils.py index f0a04698..81712321 100644 --- a/src/utils.py +++ b/src/utils.py @@ -81,11 +81,11 @@ def bytes8_to_int(s): def int_to_hex4(i): - return int_to_bytes4(i).encode('hex') + return hexlify(int_to_bytes4(i)) def int_to_hex8(i): - return int_to_bytes8(i).encode('hex') + return hexlify(int_to_bytes8(i)) def header_from_string(s): From 9e0051f061dbeccb260d2d0b2d80eef4ecb0b1cd Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sat, 17 Dec 2016 18:33:36 -0600 Subject: [PATCH 16/18] leveldb data stored as bytes, not strings. --- src/storage.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/storage.py b/src/storage.py index 56cd67ef..0bbc03c2 100644 --- a/src/storage.py +++ b/src/storage.py @@ -207,14 +207,14 @@ def __init__(self, config, shared, test_reorgs): logger.error('db init', exc_info=True) self.shared.stop() try: - self.last_hash, self.height, db_version = ast.literal_eval(self.db_undo.get('height')) + self.last_hash, self.height, db_version = ast.literal_eval(self.get(b'height')) except: print_log('Initializing database') self.height = 0 self.last_hash = GENESIS_HASH self.pruning_limit = config.getint('leveldb', 'pruning_limit') db_version = DB_VERSION - self.put_node('', Node.from_dict({})) + self.put_node(b'', Node.from_dict({})) # check version if db_version != DB_VERSION: print_log("Your database '%s' is deprecated. Please create a new database"%self.dbpath) @@ -222,19 +222,19 @@ def __init__(self, config, shared, test_reorgs): return # pruning limit try: - self.pruning_limit = ast.literal_eval(self.db_undo.get('limit')) + self.pruning_limit = ast.literal_eval(self.db_undo.get(b'limit')) except: self.pruning_limit = config.getint('leveldb', 'pruning_limit') - self.db_undo.put('version', repr(self.pruning_limit)) + self.db_undo.put(b'version', repr(self.pruning_limit).encode("utf-8")) # reorg limit try: - self.reorg_limit = ast.literal_eval(self.db_undo.get('reorg_limit')) + self.reorg_limit = ast.literal_eval(self.db_undo.get(b'reorg_limit')) except: self.reorg_limit = config.getint('leveldb', 'reorg_limit') - self.db_undo.put('reorg_limit', repr(self.reorg_limit)) + self.db_undo.put(b'reorg_limit', repr(self.reorg_limit).encode("utf-8")) # compute root hash - root_node = self.get_node('') - self.root_hash, coins = root_node.get_hash('', None) + root_node = self.get_node(b'') + self.root_hash, coins = root_node.get_hash(b'', None) # print stuff print_log("Database version %d."%db_version) print_log("Pruning limit for spent outputs is %d."%self.pruning_limit) @@ -328,14 +328,14 @@ def get_address(self, txi): return self.db_addr.get(txi) def get_undo_info(self, height): - s = self.db_undo.get("undo_info_%d" % (height % self.reorg_limit)) + s = self.db_undo.get(b"undo_info_%d" % (height % self.reorg_limit)) if s is None: print_log("no undo info for ", height) return eval(s) def write_undo_info(self, height, bitcoind_height, undo_info): if height > bitcoind_height - self.reorg_limit or self.test_reorgs: - self.db_undo.put("undo_info_%d" % (height % self.reorg_limit), repr(undo_info)) + self.db_undo.put(b"undo_info_%d" % (height % self.reorg_limit), repr(undo_info).encode("utf-8")) @staticmethod def common_prefix(word1, word2): @@ -572,7 +572,7 @@ def close(self): db.close() def save_height(self, block_hash, block_height): - self.db_undo.put('height', repr((block_hash, block_height, DB_VERSION))) + self.db_undo.put(b'height', repr((block_hash, block_height, DB_VERSION)).encode("utf-8")) def add_to_history(self, addr, tx_hash, tx_pos, value, tx_height): key = self.address_to_key(addr) From 7f6525a8d21eea98c231fdb197e8471f87ce9d41 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sat, 17 Dec 2016 21:37:06 -0600 Subject: [PATCH 17/18] Communication with bitcoind to ported to Python 3. Mostly hex conversion and working with bytes. --- src/blockchain_processor.py | 54 +++++++++++++++++++++---------------- src/utils.py | 3 +++ 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/blockchain_processor.py b/src/blockchain_processor.py index 35311d82..2006fc37 100644 --- a/src/blockchain_processor.py +++ b/src/blockchain_processor.py @@ -21,6 +21,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from binascii import hexlify, unhexlify import hashlib from json import dumps, load import os @@ -35,7 +36,7 @@ from .processor import Processor, print_log from .storage import Storage from .utils import logger, hash_decode, hash_encode, Hash, header_from_string, header_to_string, ProfiledThread, \ - rev_hex, int_to_hex4 + rev_hex, int_to_hex4, utf8_reader class BlockchainProcessor(Processor): @@ -60,7 +61,7 @@ def __init__(self, config, shared): self.max_cache_size = 100000 self.chunk_cache = {} self.cache_lock = threading.Lock() - self.headers_data = '' + self.headers_data = b'' self.headers_path = config.get('leveldb', 'path') self.mempool_fees = {} @@ -79,12 +80,19 @@ def __init__(self, config, shared): self.test_reorgs = False self.storage = Storage(config, shared, self.test_reorgs) - self.bitcoind_url = 'http://%s:%s@%s:%s/' % ( - config.get('bitcoind', 'bitcoind_user'), - config.get('bitcoind', 'bitcoind_password'), + self.bitcoind_url = 'http://%s:%s/' % ( config.get('bitcoind', 'bitcoind_host'), config.get('bitcoind', 'bitcoind_port')) + bitcoind_passwd_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() + bitcoind_passwd_mgr.add_password( + None, + self.bitcoind_url, + config.get('bitcoind', 'bitcoind_user'), + config.get('bitcoind', 'bitcoind_password') + ) + bitcoind_auth_handler = urllib.request.HTTPBasicAuthHandler(bitcoind_passwd_mgr) + self.bitcoind_opener = urllib.request.build_opener(bitcoind_auth_handler) self.sent_height = 0 self.sent_header = None @@ -102,7 +110,7 @@ def __init__(self, config, shared): def do_catch_up(self): self.header = self.block2header(self.bitcoind('getblock', (self.storage.last_hash,))) - self.header['utxo_root'] = self.storage.get_root_hash().encode('hex') + self.header['utxo_root'] = hexlify(self.storage.get_root_hash()) self.catch_up(sync=False) if not self.shared.stopped(): print_log("Blockchain is up to date.") @@ -132,7 +140,7 @@ def print_time(self, num_tx): if self.storage.height%100 == 0 \ or (self.storage.height%10 == 0 and self.storage.height >= 100000)\ or self.storage.height >= 200000: - msg = "block %d (%d %.2fs) %s" %(self.storage.height, num_tx, delta, self.storage.get_root_hash().encode('hex')) + msg = "block %d (%d %.2fs) %s" %(self.storage.height, num_tx, delta, hexlify(self.storage.get_root_hash())) msg += " (%.2ftx/s, %.2fs/block)" % (tx_per_second, seconds_per_block) run_blocks = self.storage.height - self.start_catchup_height remaining_blocks = self.bitcoind_height - self.storage.height @@ -156,8 +164,8 @@ def bitcoind(self, method, params=()): postdata = dumps({"method": method, 'params': params, 'id': 'jsonrpc'}) while True: try: - response = urllib.request.urlopen(self.bitcoind_url, postdata) - r = load(response) + response = self.bitcoind_opener.open(self.bitcoind_url, postdata.encode("utf-8")) + r = load(utf8_reader(response)) response.close() except: print_log("cannot reach bitcoind...") @@ -193,7 +201,7 @@ def init_headers(self, db_height): self.headers_filename = os.path.join(self.headers_path, 'blockchain_headers') if os.path.exists(self.headers_filename): - height = os.path.getsize(self.headers_filename)/80 - 1 # the current height + height = os.path.getsize(self.headers_filename)//80 - 1 # the current height if height > 0: prev_hash = self.hash_header(self.read_header(height)) else: @@ -231,7 +239,7 @@ def init_headers(self, db_height): @staticmethod def hash_header(header): - return rev_hex(Hash(header_to_string(header).decode('hex')).encode('hex')) + return rev_hex(hexlify(Hash(unhexlify(header_to_string(header))))) def read_header(self, block_height): if os.path.exists(self.headers_filename): @@ -246,13 +254,13 @@ def read_chunk(self, index): with open(self.headers_filename, 'rb') as f: f.seek(index*2016*80) chunk = f.read(2016*80) - return chunk.encode('hex') + return hexlify(chunk) def write_header(self, header, sync=True): if not self.headers_data: self.headers_offset = header.get('block_height') - self.headers_data += header_to_string(header).decode('hex') + self.headers_data += unhexlify(header_to_string(header)) if sync or len(self.headers_data) > 40*100: self.flush_headers() @@ -272,7 +280,7 @@ def flush_headers(self): with open(self.headers_filename, 'rb+') as f: f.seek(self.headers_offset*80) f.write(self.headers_data) - self.headers_data = '' + self.headers_data = b'' def get_chunk(self, i): # store them on disk; store the current chunk in memory @@ -291,7 +299,7 @@ def get_mempool_transaction(self, txid): except: return None vds = deserialize.BCDataStream() - vds.write(raw_tx.decode('hex')) + vds.write(unhexlify(raw_tx)) try: return deserialize.parse_Transaction(vds, is_coinbase=False) except: @@ -340,7 +348,7 @@ def get_status(self, addr, cache_only=False): if tx_points == ['*']: return '*' status = ''.join(tx.get('tx_hash') + ':%d:' % tx.get('height') for tx in tx_points) - return hashlib.sha256(status).digest().encode('hex') + return hexlify(hashlib.sha256(status).digest()) def get_merkle(self, tx_hash, height, cache_only): with self.cache_lock: @@ -389,9 +397,9 @@ def deserialize_block(block): txdict = {} # deserialized tx is_coinbase = True for raw_tx in txlist: - tx_hash = hash_encode(Hash(raw_tx.decode('hex'))) + tx_hash = hash_encode(Hash(unhexlify(raw_tx))) vds = deserialize.BCDataStream() - vds.write(raw_tx.decode('hex')) + vds.write(unhexlify(raw_tx)) try: tx = deserialize.parse_Transaction(vds, is_coinbase) except: @@ -546,7 +554,7 @@ def process(self, request, cache_only=False): elif method == 'blockchain.utxo.get_address': txid = str(params[0]) pos = int(params[1]) - txi = (txid + int_to_hex4(pos)).decode('hex') + txi = unhexlify(txid + int_to_hex4(pos)) result = self.storage.get_address(txi) elif method == 'blockchain.block.get_header': @@ -621,8 +629,8 @@ def get_block(self, block_hash): while True: try: - response = urllib.request.urlopen(self.bitcoind_url, postdata) - r = load(response) + response = self.bitcoind_opener.open(self.bitcoind_url, postdata.encode("utf-8")) + r = load(utf8_reader(response)) response.close() except: logger.error("bitcoind error (getfullblock)") @@ -704,7 +712,7 @@ def catch_up(self, sync=True): self.print_time(n) self.header = self.block2header(self.bitcoind('getblock', (self.storage.last_hash,))) - self.header['utxo_root'] = self.storage.get_root_hash().encode('hex') + self.header['utxo_root'] = hexlify(self.storage.get_root_hash()) if self.shared.stopped(): print_log( "closing database" ) @@ -766,7 +774,7 @@ def memorypool_update(self): addr, value = mpv[prev_n] self.mempool_unconfirmed[tx_hash].add(prev_hash) else: - txi = (prev_hash + int_to_hex4(prev_n)).decode('hex') + txi = unhexlify(prev_hash + int_to_hex4(prev_n)) try: addr = self.storage.get_address(txi) value = self.storage.get_utxo_value(addr,txi) diff --git a/src/utils.py b/src/utils.py index 81712321..ad7ba5a9 100644 --- a/src/utils.py +++ b/src/utils.py @@ -27,6 +27,7 @@ import hashlib import struct from binascii import hexlify, unhexlify +from codecs import getreader from collections import deque from itertools import repeat @@ -99,6 +100,8 @@ def header_from_string(s): } +utf8_reader = getreader("utf-8") + ############ functions from pywallet ##################### From 6ae25eef1c80b8f4ead59dab8e05ac565ef57016 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sat, 17 Dec 2016 21:38:36 -0600 Subject: [PATCH 18/18] Fixes to storage for Python 3's bytes vs Python 2's str. --- src/storage.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/storage.py b/src/storage.py index 0bbc03c2..155fdda6 100644 --- a/src/storage.py +++ b/src/storage.py @@ -69,7 +69,7 @@ def is_singleton(self, key): def get_singleton(self): for i in range(256): if self.k == (1<