From 2d237bc0cca70a342acdbd93065de70fd1ece214 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Mon, 27 Apr 2020 12:42:13 -0400 Subject: [PATCH] Accept a counter when adding a voucher and tokens to the store Allow voucher additions that are only distinct on the counter value. --- src/_zkapauthorizer/controller.py | 13 ++++-- src/_zkapauthorizer/model.py | 27 ++++++++----- src/_zkapauthorizer/schema.py | 8 ++++ src/_zkapauthorizer/tests/test_model.py | 54 ++++++++++++++++++++----- 4 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py index 142d626..b438680 100644 --- a/src/_zkapauthorizer/controller.py +++ b/src/_zkapauthorizer/controller.py @@ -696,11 +696,18 @@ class PaymentController(object): """ Generate or load random tokens for a redemption attempt of a voucher. """ + counter = 0 def get_tokens(): - self._log.info("Generating random tokens for a voucher ({voucher}).", voucher=voucher) - return self.redeemer.random_tokens_for_voucher(Voucher(voucher), num_tokens) + self._log.info( + "Generating random tokens for a voucher ({voucher}).", + voucher=voucher, + ) + return self.redeemer.random_tokens_for_voucher( + Voucher(voucher), + num_tokens, + ) - return self.store.add(voucher, get_tokens) + return self.store.add(voucher, counter, get_tokens) def redeem(self, voucher, num_tokens=None): """ diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index fed05e1..0db7e74 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -225,12 +225,17 @@ class VoucherStore(object): return Voucher.from_row(refs[0]) @with_cursor - def add(self, cursor, voucher, get_tokens): + def add(self, cursor, voucher, counter, get_tokens): """ - Add a new voucher and associated random tokens to the database. If a - voucher with the given text value is already present, do nothing. + Add random tokens associated with a voucher (possibly new, possibly + existing) to the database. If the (voucher, counter) pair is already + present, do nothing. - :param unicode voucher: The text value of a voucher to add. + :param unicode voucher: The text value of a voucher with which to + associate the tokens. + + :param int counter: The redemption counter for the given voucher with + which to associate the tokens. :param list[RandomToken]: The tokens to add alongside the voucher. """ @@ -243,16 +248,17 @@ class VoucherStore(object): """ SELECT ([text]) FROM [tokens] - WHERE [voucher] = ? + WHERE [voucher] = ? AND [counter] = ? """, - (voucher,), + (voucher, counter), ) rows = cursor.fetchall() if len(rows) > 0: self._log.info( - "Loaded {count} random tokens for a voucher ({voucher}).", + "Loaded {count} random tokens for a voucher ({voucher}[{counter}]).", count=len(rows), voucher=voucher, + counter=counter, ) tokens = list( RandomToken(token_value) @@ -262,9 +268,10 @@ class VoucherStore(object): else: tokens = get_tokens() self._log.info( - "Persisting {count} random tokens for a voucher ({voucher}).", + "Persisting {count} random tokens for a voucher ({voucher}[{counter}]).", count=len(tokens), voucher=voucher, + counter=counter, ) cursor.execute( """ @@ -274,10 +281,10 @@ class VoucherStore(object): ) cursor.executemany( """ - INSERT INTO [tokens] ([voucher], [text]) VALUES (?, ?) + INSERT INTO [tokens] ([voucher], [counter], [text]) VALUES (?, ?, ?) """, list( - (voucher, token.token_value) + (voucher, counter, token.token_value) for token in tokens ), diff --git a/src/_zkapauthorizer/schema.py b/src/_zkapauthorizer/schema.py index e3a37eb..4fa33b1 100644 --- a/src/_zkapauthorizer/schema.py +++ b/src/_zkapauthorizer/schema.py @@ -138,4 +138,12 @@ _UPGRADES = { ALTER TABLE [vouchers] ADD COLUMN [counter] integer DEFAULT 0 """, ], + + 3: [ + """ + -- Reference to the counter these tokens go with. + ALTER TABLE [tokens] ADD COLUMN [counter] integer NOT NULL DEFAULT 0 + """, + + ], } diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py index e6df76c..3b0a1f5 100644 --- a/src/_zkapauthorizer/tests/test_model.py +++ b/src/_zkapauthorizer/tests/test_model.py @@ -88,6 +88,7 @@ from .strategies import ( tahoe_configs, vouchers, voucher_objects, + voucher_counters, random_tokens, unblinded_tokens, posix_safe_datetimes, @@ -124,7 +125,7 @@ class VoucherStoreTests(TestCase): previously added to the store with ``VoucherStore.add``. """ store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store - store.add(voucher, lambda: tokens) + store.add(voucher, 0, lambda: tokens) self.assertThat( store.get(voucher), MatchesStructure( @@ -134,6 +135,39 @@ class VoucherStoreTests(TestCase): ), ) + @given( + tahoe_configs(), + vouchers(), + lists(voucher_counters(), unique=True, min_size=2, max_size=2), + lists(random_tokens(), min_size=2, unique=True), + datetimes(), + ) + def test_add_with_distinct_counters(self, get_config, voucher, counters, tokens, now): + """ + ``VoucherStore.add`` adds new tokens to the store when passed the same + voucher but a different counter value. + """ + counter_a = counters[0] + counter_b = counters[1] + tokens_a = tokens[:len(tokens) / 2] + tokens_b = tokens[len(tokens) / 2:] + + store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store + added_tokens_a = store.add(voucher, counter_a, lambda: tokens_a) + added_tokens_b = store.add(voucher, counter_b, lambda: tokens_b) + + self.assertThat( + store.get(voucher), + MatchesStructure( + number=Equals(voucher), + state=Equals(Pending(counter=0)), + created=Equals(now), + ), + ) + + self.assertThat(tokens_a, Equals(added_tokens_a)) + self.assertThat(tokens_b, Equals(added_tokens_b)) + @given(tahoe_configs(), vouchers(), datetimes(), lists(random_tokens(), unique=True)) def test_add_idempotent(self, get_config, voucher, now, tokens): """ @@ -141,8 +175,8 @@ class VoucherStoreTests(TestCase): in the same state as a single call. """ store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store - first_tokens = store.add(voucher, lambda: tokens) - second_tokens = store.add(voucher, lambda: []) + first_tokens = store.add(voucher, 0, lambda: tokens) + second_tokens = store.add(voucher, 0, lambda: []) self.assertThat( store.get(voucher), MatchesStructure( @@ -168,7 +202,7 @@ class VoucherStoreTests(TestCase): """ store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store for voucher in vouchers: - store.add(voucher, lambda: []) + store.add(voucher, 0, lambda: []) self.assertThat( store.list(), @@ -306,7 +340,7 @@ class VoucherStoreTests(TestCase): # Put some tokens in it that we can backup and extract random_tokens, unblinded_tokens = paired_tokens(data, integers(min_value=1, max_value=5)) - store.add(voucher_value, lambda: random_tokens) + store.add(voucher_value, 0, lambda: random_tokens) store.insert_unblinded_tokens_for_voucher( voucher_value, public_key, @@ -478,7 +512,7 @@ class UnblindedTokenStoreTests(TestCase): """ random_tokens, unblinded_tokens = paired_tokens(data) store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store - store.add(voucher_value, lambda: random_tokens) + store.add(voucher_value, 0, lambda: random_tokens) store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded_tokens) retrieved_tokens = store.extract_unblinded_tokens(len(random_tokens)) @@ -524,7 +558,7 @@ class UnblindedTokenStoreTests(TestCase): ) store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store - store.add(voucher_value, lambda: random) + store.add(voucher_value, 0, lambda: random) store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded) loaded_voucher = store.get(voucher_value) self.assertThat( @@ -550,7 +584,7 @@ class UnblindedTokenStoreTests(TestCase): such. """ store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store - store.add(voucher_value, lambda: random_tokens) + store.add(voucher_value, 0, lambda: random_tokens) store.mark_voucher_double_spent(voucher_value) voucher = store.get(voucher_value) self.assertThat( @@ -591,7 +625,7 @@ class UnblindedTokenStoreTests(TestCase): ), ) store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store - store.add(voucher_value, lambda: random) + store.add(voucher_value, 0, lambda: random) store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded) self.assertThat( lambda: store.mark_voucher_double_spent(voucher_value), @@ -644,7 +678,7 @@ class UnblindedTokenStoreTests(TestCase): ), ) store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store - store.add(voucher_value, lambda: random) + store.add(voucher_value, 0, lambda: random) store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded) self.assertThat( -- GitLab