diff --git a/src/_zkapauthorizer/_storage_client.py b/src/_zkapauthorizer/_storage_client.py index b47c5b3bc6c8cfc040476b978026c7d6e18258ce..ecb785f9a6adc2dfdf2c750ad5a452f7dc9e116c 100644 --- a/src/_zkapauthorizer/_storage_client.py +++ b/src/_zkapauthorizer/_storage_client.py @@ -450,6 +450,25 @@ class ZKAPAuthorizerStorageClient(object): tw_vectors, r_vector, ): + # type: ( + # Any, + # bytes, + # Tuple[bytes, bytes, bytes], + # Dict[ + # int, + # Tuple[ + # List[ + # Tuple[int, int, bytes, bytes], + # ], + # List[ + # Tuple[int, bytes], + # ], + # Optional[int], + # ], + # ], + # List[Tuple[int, int]], + # ) -> Deferred + # Read operations are free. num_passes = 0 @@ -458,7 +477,7 @@ class ZKAPAuthorizerStorageClient(object): tw_vectors = { sharenum: ( [ - (offset, length, "eq", specimen) + (offset, length, b"eq", specimen) for (offset, length, specimen) in test_vector ], data_vectors, diff --git a/src/_zkapauthorizer/tests/test_storage_protocol.py b/src/_zkapauthorizer/tests/test_storage_protocol.py index ffae2fff58e545382b3ca2b701ce2f1cd10ac993..7e5fb3e6d030cd8d743f2e7fd5b81ba823d335fb 100644 --- a/src/_zkapauthorizer/tests/test_storage_protocol.py +++ b/src/_zkapauthorizer/tests/test_storage_protocol.py @@ -72,6 +72,7 @@ from .storage_common import ( ) from .strategies import bytes_for_share # Not really a strategy... from .strategies import ( + TestAndWriteVectors, lease_cancel_secrets, lease_renew_secrets, posix_timestamps, @@ -79,6 +80,7 @@ from .strategies import ( sharenum_sets, sharenums, sizes, + slot_data_vectors, slot_test_and_write_vectors_for_shares, storage_indexes, write_enabler_secrets, @@ -1073,6 +1075,61 @@ class ShareTests(TestCase): ), ) + @given( + storage_index=storage_indexes(), + secrets=tuples( + write_enabler_secrets(), + lease_renew_secrets(), + lease_cancel_secrets(), + ), + sharenum=sharenums(), + data_vector=slot_data_vectors(), + replacement_data_vector=slot_data_vectors(), + ) + def test_test_vectors_match(self, storage_index, secrets, sharenum, data_vector, replacement_data_vector): + """ + If test vectors are given then the write is allowed if they match the + existing data. + """ + empty_test_vector = [] + + def write(tw_vectors): + return self.client.slot_testv_and_readv_and_writev( + storage_index, + secrets=secrets, + tw_vectors=tw_vectors, + r_vector=[], + ) + + def read(sharenum, readv): + d = self.client.slot_readv(storage_index, [sharenum], readv) + d.addCallback(lambda data: data[sharenum]) + return d + + def equal_test_vector(data_vector): + return list( + (offset, len(data), data) + for (offset, data) + in data_vector + ) + + # Create the share + d = write({ + sharenum: (empty_test_vector, data_vector, None), + }) + self.assertThat(d, is_successful_write()) + + # Write some new data to with a correct test vector. We can only be + # sure we know data from the last element of the test vector since + # earlier elements may have been overwritten. + d = write({ + sharenum: (equal_test_vector(data_vector)[-1:], replacement_data_vector, None), + }) + self.assertThat(d, is_successful_write()) + + # Check that the new data is present + assert_read_back_data(self, storage_index, secrets, {sharenum: TestAndWriteVectors(None, replacement_data_vector, None)}) + def assert_read_back_data( self, storage_index, secrets, test_and_write_vectors_for_shares