Skip to content

Commit e361dba

Browse files
committed
Fixes
- Qt six import was broken - variable in Qt support interface rename incomplete - don't put memoryview in an objectProcessorQueue - inventory data corruption fix
1 parent 8a3f1e5 commit e361dba

6 files changed

Lines changed: 47 additions & 59 deletions

File tree

src/bitmessageqt/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@
5656
import bitmessage_icons_rc # noqa:F401 pylint: disable=unused-import
5757
import helper_sent
5858

59-
from six.moves import iteritems, itervalues, range as xrange
60-
from six import text_type
59+
from six import iteritems, itervalues, text_type
6160

6261
try:
6362
from plugins.plugin import get_plugin, get_plugins

src/bitmessageqt/support.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def createSupportMessage(myapp):
141141
if paths.frozen:
142142
frozen = paths.frozen
143143
portablemode = "True" if state.appdata == paths.lookupExeFolder() else "False"
144-
cpow = "True" if proofofwork.bmpow else "False"
144+
cpow = "True" if proofofwork.BMPOW else "False"
145145
openclpow = str(
146146
config.safeGet('bitmessagesettings', 'opencl')
147147
) if openclEnabled() else "None"

src/class_objectProcessor.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,19 @@ def checkackdata(data):
141141
# bypass nonce and time, retain object type/version/stream + body
142142
readPosition = 16
143143

144-
if data[readPosition:] in state.ackdataForWhichImWatching:
144+
# data may be a memoryview, which is not hashable and thus
145+
# cannot be used as a dictionary key; convert the slice to bytes.
146+
ackcheckdata = bytes(data[readPosition:])
147+
148+
if ackcheckdata in state.ackdataForWhichImWatching:
145149
logger.info('This object is an acknowledgement bound for me.')
146-
del state.ackdataForWhichImWatching[data[readPosition:]]
150+
del state.ackdataForWhichImWatching[ackcheckdata]
147151
sqlExecute(
148152
"UPDATE sent SET status='ackreceived', lastactiontime=?"
149-
" WHERE ackdata=?", int(time.time()), data[readPosition:])
153+
" WHERE ackdata=?", int(time.time()), ackcheckdata)
150154
queues.UISignalQueue.put((
151155
'updateSentItemStatusByAckdata', (
152-
data[readPosition:],
156+
ackcheckdata,
153157
_translate(
154158
"MainWindow",
155159
"Acknowledgement of the message received %1"
@@ -208,7 +212,9 @@ def processgetpubkey(data):
208212

209213
myAddress = ''
210214
if requestedAddressVersionNumber <= 3:
211-
requestedHash = data[readPosition:readPosition + 20]
215+
# data may be a memoryview; convert slices to bytes so they
216+
# are hashable and can be used as dictionary keys.
217+
requestedHash = bytes(data[readPosition:readPosition + 20])
212218
if len(requestedHash) != 20:
213219
return logger.debug(
214220
'The length of the requested hash is not 20 bytes.'
@@ -220,7 +226,8 @@ def processgetpubkey(data):
220226
if requestedHash in shared.myAddressesByHash:
221227
myAddress = shared.myAddressesByHash[requestedHash]
222228
elif requestedAddressVersionNumber >= 4:
223-
requestedTag = data[readPosition:readPosition + 32]
229+
# data may be a memoryview; convert to bytes for hashability.
230+
requestedTag = bytes(data[readPosition:readPosition + 32])
224231
if len(requestedTag) != 32:
225232
return logger.debug(
226233
'The length of the requested tag is not 32 bytes.'
@@ -413,7 +420,8 @@ def processpubkey(self, data):
413420
'(within processpubkey) payloadLength less than 350.'
414421
' Sanity check failed.')
415422

416-
tag = data[readPosition:readPosition + 32]
423+
# data may be a memoryview; convert to bytes for hashability.
424+
tag = bytes(data[readPosition:readPosition + 32])
417425
if tag not in state.neededPubkeys:
418426
return logger.info(
419427
'We don\'t need this v4 pubkey. We didn\'t ask for it.')
@@ -807,7 +815,8 @@ def processbroadcast(self, data):
807815
' v4 broadcast: %s seconds.',
808816
time.time() - messageProcessingStartTime)
809817
elif broadcastVersion == 5:
810-
embeddedTag = data[readPosition:readPosition + 32]
818+
# data may be a memoryview; convert to bytes for hashability.
819+
embeddedTag = bytes(data[readPosition:readPosition + 32])
811820
readPosition += 32
812821
if embeddedTag not in shared.MyECSubscriptionCryptorObjects:
813822
logger.debug('We\'re not interested in this broadcast.')

src/network/bmproto.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ def bm_command_object(self):
441441
try:
442442
self.object.checkObjectByType()
443443
objectProcessorQueue.put((
444-
self.object.objectType, memoryview(self.object.data)))
444+
self.object.objectType, bytes(self.object.data)))
445445
except BMObjectInvalidError:
446446
self.stopDownloadingObject(self.object.inventoryHash, True)
447447
else:
@@ -457,8 +457,8 @@ def bm_command_object(self):
457457

458458
state.Inventory[self.object.inventoryHash] = (
459459
self.object.objectType, self.object.streamNumber,
460-
memoryview(self.payload[objectOffset:]), self.object.expiresTime,
461-
memoryview(self.object.tag)
460+
bytes(self.payload[objectOffset:]), self.object.expiresTime,
461+
bytes(self.object.tag)
462462
)
463463
self.handleReceivedObject(
464464
self.object.streamNumber, self.object.inventoryHash)

src/storage/sqlite.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ def flush(self):
112112
sqlite3.Binary(objectHash),
113113
value.type,
114114
value.stream,
115-
sqlite3.Binary(bytes(value.payload)),
115+
sqlite3.Binary(value.payload),
116116
value.expires,
117-
sqlite3.Binary(bytes(value.tag)))
117+
sqlite3.Binary(value.tag))
118118
self._inventory.clear()
119119

120120
def clean(self):

src/tests/test_inventory_flush.py

Lines changed: 23 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# pylint: disable=import-outside-toplevel
44

55
import os
6-
import struct
76
import tempfile
87
import threading
98
import time
@@ -84,70 +83,51 @@ def _make_hash(seed):
8483
"""Return a 32-byte hash derived from *seed*."""
8584
return (b'\x00' * 31 + bytes([seed & 0xFF]))[-32:]
8685

87-
def _flush_and_check(self, obj_hash):
86+
def _flush_and_check(self, obj_hash, expected_payload=None):
8887
"""
89-
Flush the inventory to the database, clear the _objects lookup
90-
cache so that __contains__ is forced to hit sqlite, then verify
91-
the hash is found via the normal inventory API.
88+
Flush the inventory to the database, clear both in-memory
89+
caches so that __contains__ and __getitem__ are forced to
90+
hit sqlite, then verify the hash is found and (optionally)
91+
that the payload content survived the round-trip.
9292
"""
9393
self.inventory.flush()
9494
self.inventory._objects.clear()
9595
self.assertIn(obj_hash, self.inventory)
96+
if expected_payload is not None:
97+
value = self.inventory[obj_hash]
98+
self.assertEqual(
99+
bytes(value.payload), expected_payload,
100+
"Payload content corrupted after flush")
96101

97102
# -- test cases -------------------------------------------------------
98103

99-
def test_flush_with_bytes_payload(self):
100-
"""Baseline: payload and tag are plain bytes."""
104+
def test_flush_payload_roundtrip(self):
105+
"""Payload content must survive the flush round-trip."""
101106
h = self._make_hash(1)
107+
payload = b'\x80\x01' + os.urandom(64)
102108
self.inventory[h] = (
103-
2, 1, b'\x80\x01' + os.urandom(64),
109+
2, 1, payload,
104110
int(time.time()) + 3600, b'\xff' * 32)
105-
self._flush_and_check(h)
106-
107-
def test_flush_with_memoryview_payload(self):
108-
"""
109-
Reproduce the production crash: payload and tag as memoryview
110-
cause 'Error binding parameter 3 - probably unsupported type.'
111-
"""
112-
h = self._make_hash(2)
113-
self.inventory[h] = (
114-
2, 1, memoryview(b'\x80\x02' + os.urandom(64)),
115-
int(time.time()) + 3600, memoryview(b'\xee' * 32))
116-
self._flush_and_check(h)
117-
118-
def test_flush_with_bytearray_payload(self):
119-
"""bytearray is another bytes-like type that could trip sqlite3."""
120-
h = self._make_hash(3)
121-
self.inventory[h] = (
122-
2, 1, bytearray(b'\x80\x03' + os.urandom(64)),
123-
int(time.time()) + 3600, bytearray(b'\xdd' * 32))
124-
self._flush_and_check(h)
111+
self._flush_and_check(h, payload)
125112

126113
def test_flush_with_empty_tag(self):
127114
"""Empty tag (b'') must not break the INSERT."""
128-
h = self._make_hash(4)
115+
h = self._make_hash(2)
116+
payload = b'\x80\x02' + os.urandom(64)
129117
self.inventory[h] = (
130-
2, 1, b'\x80\x04' + os.urandom(64),
118+
2, 1, payload,
131119
int(time.time()) + 3600, b'')
132-
self._flush_and_check(h)
120+
self._flush_and_check(h, payload)
133121

134-
# pylint: disable=redefined-variable-type
135-
def test_flush_multiple_mixed_types(self):
136-
"""Flush a batch of items with mixed blob types."""
122+
def test_flush_multiple_items(self):
123+
"""Flush a batch and verify every row arrives."""
137124
count = 20
138125
hashes = [self._make_hash(0x10 + i) for i in range(count)]
139126
expires = int(time.time()) + 3600
140127

141128
for i, h in enumerate(hashes):
142-
payload = struct.pack('>I', i) + os.urandom(60)
143-
tag = struct.pack('>I', i) + b'\x00' * 28
144-
if i % 3 == 0:
145-
payload = memoryview(payload)
146-
tag = memoryview(tag)
147-
elif i % 3 == 1:
148-
payload = bytearray(payload)
149-
tag = bytearray(tag)
150-
self.inventory[h] = (2, 1, payload, expires, tag)
129+
self.inventory[h] = (
130+
2, 1, os.urandom(64), expires, b'\x00' * 32)
151131

152132
self.inventory.flush()
153133
self.inventory._objects.clear()

0 commit comments

Comments
 (0)