diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index e3a050852..b9eb7e0ee 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -56,8 +56,7 @@ import bitmessage_icons_rc # noqa:F401 pylint: disable=unused-import import helper_sent -from six.moves import iteritems, itervalues, range as xrange -from six import text_type +from six import iteritems, itervalues, text_type try: from plugins.plugin import get_plugin, get_plugins diff --git a/src/bitmessageqt/support.py b/src/bitmessageqt/support.py index 774b68ec2..40da6d6a0 100644 --- a/src/bitmessageqt/support.py +++ b/src/bitmessageqt/support.py @@ -141,7 +141,7 @@ def createSupportMessage(myapp): if paths.frozen: frozen = paths.frozen portablemode = "True" if state.appdata == paths.lookupExeFolder() else "False" - cpow = "True" if proofofwork.bmpow else "False" + cpow = "True" if proofofwork.BMPOW else "False" openclpow = str( config.safeGet('bitmessagesettings', 'opencl') ) if openclEnabled() else "None" diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index a434eb483..72a1cfa0f 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -141,15 +141,19 @@ def checkackdata(data): # bypass nonce and time, retain object type/version/stream + body readPosition = 16 - if data[readPosition:] in state.ackdataForWhichImWatching: + # data may be a memoryview, which is not hashable and thus + # cannot be used as a dictionary key; convert the slice to bytes. + ackcheckdata = bytes(data[readPosition:]) + + if ackcheckdata in state.ackdataForWhichImWatching: logger.info('This object is an acknowledgement bound for me.') - del state.ackdataForWhichImWatching[data[readPosition:]] + del state.ackdataForWhichImWatching[ackcheckdata] sqlExecute( "UPDATE sent SET status='ackreceived', lastactiontime=?" - " WHERE ackdata=?", int(time.time()), data[readPosition:]) + " WHERE ackdata=?", int(time.time()), ackcheckdata) queues.UISignalQueue.put(( 'updateSentItemStatusByAckdata', ( - data[readPosition:], + ackcheckdata, _translate( "MainWindow", "Acknowledgement of the message received %1" @@ -208,7 +212,9 @@ def processgetpubkey(data): myAddress = '' if requestedAddressVersionNumber <= 3: - requestedHash = data[readPosition:readPosition + 20] + # data may be a memoryview; convert slices to bytes so they + # are hashable and can be used as dictionary keys. + requestedHash = bytes(data[readPosition:readPosition + 20]) if len(requestedHash) != 20: return logger.debug( 'The length of the requested hash is not 20 bytes.' @@ -220,7 +226,8 @@ def processgetpubkey(data): if requestedHash in shared.myAddressesByHash: myAddress = shared.myAddressesByHash[requestedHash] elif requestedAddressVersionNumber >= 4: - requestedTag = data[readPosition:readPosition + 32] + # data may be a memoryview; convert to bytes for hashability. + requestedTag = bytes(data[readPosition:readPosition + 32]) if len(requestedTag) != 32: return logger.debug( 'The length of the requested tag is not 32 bytes.' @@ -413,7 +420,8 @@ def processpubkey(self, data): '(within processpubkey) payloadLength less than 350.' ' Sanity check failed.') - tag = data[readPosition:readPosition + 32] + # data may be a memoryview; convert to bytes for hashability. + tag = bytes(data[readPosition:readPosition + 32]) if tag not in state.neededPubkeys: return logger.info( 'We don\'t need this v4 pubkey. We didn\'t ask for it.') @@ -807,7 +815,8 @@ def processbroadcast(self, data): ' v4 broadcast: %s seconds.', time.time() - messageProcessingStartTime) elif broadcastVersion == 5: - embeddedTag = data[readPosition:readPosition + 32] + # data may be a memoryview; convert to bytes for hashability. + embeddedTag = bytes(data[readPosition:readPosition + 32]) readPosition += 32 if embeddedTag not in shared.MyECSubscriptionCryptorObjects: logger.debug('We\'re not interested in this broadcast.') diff --git a/src/network/bmproto.py b/src/network/bmproto.py index 3512d8815..13d6f4e77 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -441,7 +441,7 @@ def bm_command_object(self): try: self.object.checkObjectByType() objectProcessorQueue.put(( - self.object.objectType, memoryview(self.object.data))) + self.object.objectType, bytes(self.object.data))) except BMObjectInvalidError: self.stopDownloadingObject(self.object.inventoryHash, True) else: @@ -457,8 +457,8 @@ def bm_command_object(self): state.Inventory[self.object.inventoryHash] = ( self.object.objectType, self.object.streamNumber, - memoryview(self.payload[objectOffset:]), self.object.expiresTime, - memoryview(self.object.tag) + bytes(self.payload[objectOffset:]), self.object.expiresTime, + bytes(self.object.tag) ) self.handleReceivedObject( self.object.streamNumber, self.object.inventoryHash) diff --git a/src/storage/sqlite.py b/src/storage/sqlite.py index e6d2bba0c..2d769dfb0 100644 --- a/src/storage/sqlite.py +++ b/src/storage/sqlite.py @@ -112,9 +112,9 @@ def flush(self): sqlite3.Binary(objectHash), value.type, value.stream, - sqlite3.Binary(bytes(value.payload)), + sqlite3.Binary(value.payload), value.expires, - sqlite3.Binary(bytes(value.tag))) + sqlite3.Binary(value.tag)) self._inventory.clear() def clean(self): diff --git a/src/tests/test_inventory_flush.py b/src/tests/test_inventory_flush.py index de96539d3..3e851ec19 100644 --- a/src/tests/test_inventory_flush.py +++ b/src/tests/test_inventory_flush.py @@ -3,7 +3,6 @@ # pylint: disable=import-outside-toplevel import os -import struct import tempfile import threading import time @@ -84,70 +83,51 @@ def _make_hash(seed): """Return a 32-byte hash derived from *seed*.""" return (b'\x00' * 31 + bytes([seed & 0xFF]))[-32:] - def _flush_and_check(self, obj_hash): + def _flush_and_check(self, obj_hash, expected_payload=None): """ - Flush the inventory to the database, clear the _objects lookup - cache so that __contains__ is forced to hit sqlite, then verify - the hash is found via the normal inventory API. + Flush the inventory to the database, clear both in-memory + caches so that __contains__ and __getitem__ are forced to + hit sqlite, then verify the hash is found and (optionally) + that the payload content survived the round-trip. """ self.inventory.flush() self.inventory._objects.clear() self.assertIn(obj_hash, self.inventory) + if expected_payload is not None: + value = self.inventory[obj_hash] + self.assertEqual( + bytes(value.payload), expected_payload, + "Payload content corrupted after flush") # -- test cases ------------------------------------------------------- - def test_flush_with_bytes_payload(self): - """Baseline: payload and tag are plain bytes.""" + def test_flush_payload_roundtrip(self): + """Payload content must survive the flush round-trip.""" h = self._make_hash(1) + payload = b'\x80\x01' + os.urandom(64) self.inventory[h] = ( - 2, 1, b'\x80\x01' + os.urandom(64), + 2, 1, payload, int(time.time()) + 3600, b'\xff' * 32) - self._flush_and_check(h) - - def test_flush_with_memoryview_payload(self): - """ - Reproduce the production crash: payload and tag as memoryview - cause 'Error binding parameter 3 - probably unsupported type.' - """ - h = self._make_hash(2) - self.inventory[h] = ( - 2, 1, memoryview(b'\x80\x02' + os.urandom(64)), - int(time.time()) + 3600, memoryview(b'\xee' * 32)) - self._flush_and_check(h) - - def test_flush_with_bytearray_payload(self): - """bytearray is another bytes-like type that could trip sqlite3.""" - h = self._make_hash(3) - self.inventory[h] = ( - 2, 1, bytearray(b'\x80\x03' + os.urandom(64)), - int(time.time()) + 3600, bytearray(b'\xdd' * 32)) - self._flush_and_check(h) + self._flush_and_check(h, payload) def test_flush_with_empty_tag(self): """Empty tag (b'') must not break the INSERT.""" - h = self._make_hash(4) + h = self._make_hash(2) + payload = b'\x80\x02' + os.urandom(64) self.inventory[h] = ( - 2, 1, b'\x80\x04' + os.urandom(64), + 2, 1, payload, int(time.time()) + 3600, b'') - self._flush_and_check(h) + self._flush_and_check(h, payload) - # pylint: disable=redefined-variable-type - def test_flush_multiple_mixed_types(self): - """Flush a batch of items with mixed blob types.""" + def test_flush_multiple_items(self): + """Flush a batch and verify every row arrives.""" count = 20 hashes = [self._make_hash(0x10 + i) for i in range(count)] expires = int(time.time()) + 3600 for i, h in enumerate(hashes): - payload = struct.pack('>I', i) + os.urandom(60) - tag = struct.pack('>I', i) + b'\x00' * 28 - if i % 3 == 0: - payload = memoryview(payload) - tag = memoryview(tag) - elif i % 3 == 1: - payload = bytearray(payload) - tag = bytearray(tag) - self.inventory[h] = (2, 1, payload, expires, tag) + self.inventory[h] = ( + 2, 1, os.urandom(64), expires, b'\x00' * 32) self.inventory.flush() self.inventory._objects.clear()