diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index 764be8141d90fb3f0113ae54996d4f6691821144..f10552fc386322aa9947d0a52e942712409df89b 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -393,30 +393,33 @@ class VoucherStore(object): in refs ) - def _insert_unblinded_tokens(self, cursor, unblinded_tokens): + def _insert_unblinded_tokens(self, cursor, unblinded_tokens, group_id): """ Helper function to really insert unblinded tokens into the database. """ cursor.executemany( """ - INSERT INTO [unblinded-tokens] VALUES (?) + INSERT INTO [unblinded-tokens] ([token], [redemption-group]) VALUES (?, ?) """, list( - (token,) + (token, group_id) for token in unblinded_tokens ), ) @with_cursor - def insert_unblinded_tokens(self, cursor, unblinded_tokens): + def insert_unblinded_tokens(self, cursor, unblinded_tokens, group_id): """ Store some unblinded tokens, for example as part of a backup-restore process. :param list[unicode] unblinded_tokens: The unblinded tokens to store. + + :param int group_id: The unique identifier of the redemption group to + which these tokens belong. """ - self._insert_unblinded_tokens(cursor, unblinded_tokens) + self._insert_unblinded_tokens(cursor, unblinded_tokens, group_id) @with_cursor def insert_unblinded_tokens_for_voucher(self, cursor, voucher, public_key, unblinded_tokens, completed, spendable): @@ -451,6 +454,14 @@ class VoucherStore(object): token_count_increase = 0 sequestered_count_increase = len(unblinded_tokens) + cursor.execute( + """ + INSERT INTO [redemption-groups] ([voucher], [spendable]) VALUES (?, ?) + """, + (voucher, spendable), + ) + group_id = cursor.lastrowid + cursor.execute( """ UPDATE [vouchers] @@ -474,15 +485,15 @@ class VoucherStore(object): if cursor.rowcount == 0: raise ValueError("Cannot insert tokens for unknown voucher; add voucher first") - if spendable: - self._insert_unblinded_tokens( - cursor, - list( - t.unblinded_token - for t - in unblinded_tokens - ), - ) + self._insert_unblinded_tokens( + cursor, + list( + t.unblinded_token + for t + in unblinded_tokens + ), + group_id, + ) @with_cursor def mark_voucher_double_spent(self, cursor, voucher): @@ -549,9 +560,11 @@ class VoucherStore(object): cursor.execute( """ - SELECT [token] - FROM [unblinded-tokens] - WHERE [token] NOT IN [in-use] + SELECT T.[token] + FROM [unblinded-tokens] AS T, [redemption-groups] AS G + WHERE T.[redemption-group] = G.[rowid] + AND G.[spendable] = 1 + AND T.[token] NOT IN [in-use] LIMIT ? """, (count,), @@ -582,8 +595,10 @@ class VoucherStore(object): cursor.execute( """ SELECT count(1) - FROM [unblinded-tokens] - WHERE [token] NOT IN [in-use] + FROM [unblinded-tokens] AS T, [redemption-groups] AS G + WHERE T.[redemption-group] = G.[rowid] + AND G.[spendable] = 1 + AND T.[token] NOT IN [in-use] """, ) (count,) = cursor.fetchone() @@ -693,7 +708,7 @@ class VoucherStore(object): """ cursor.execute( """ - SELECT [token] FROM [unblinded-tokens] + SELECT [token] FROM [unblinded-tokens] ORDER BY [rowid] """, ) tokens = cursor.fetchall() diff --git a/src/_zkapauthorizer/resource.py b/src/_zkapauthorizer/resource.py index cc584940fd1cb44f329a43932d483bdb929e3508..e675ed204ad829444b6ad9d0818e2a1b09073e32 100644 --- a/src/_zkapauthorizer/resource.py +++ b/src/_zkapauthorizer/resource.py @@ -385,7 +385,7 @@ class _UnblindedTokenCollection(Resource): """ application_json(request) unblinded_tokens = load(request.content)[u"unblinded-tokens"] - self._store.insert_unblinded_tokens(unblinded_tokens) + self._store.insert_unblinded_tokens(unblinded_tokens, group_id=0) return dumps({}) diff --git a/src/_zkapauthorizer/schema.py b/src/_zkapauthorizer/schema.py index 426ca1ab9bc84ee2ece7cb61efe1268bb3c56921..2d66273e7ea6aefc481b97c6d592dbe1c5272978 100644 --- a/src/_zkapauthorizer/schema.py +++ b/src/_zkapauthorizer/schema.py @@ -172,5 +172,31 @@ _UPGRADES = { """ ALTER TABLE [vouchers] ADD COLUMN [sequestered-count] integer NOT NULL DEFAULT 0 """, + + """ + -- Create a table where rows represent a single group of unblinded + -- tokens all redeemed together. Some number of these rows represent + -- a complete redemption of a voucher. + CREATE TABLE [redemption-groups] ( + -- A unique identifier for this redemption group. + [rowid] INTEGER PRIMARY KEY, + + -- The text representation of the voucher this group is associated with. + [voucher] text, + + -- A flag indicating whether these tokens can be spent or if + -- they're being held for further inspection. + [spendable] integer + ) + """, + + """ + INSERT INTO [redemption-groups] ([voucher], [spendable]) + SELECT DISTINCT([number]), 1 FROM [vouchers] WHERE [state] = "redeemed" + """, + + """ + ALTER TABLE [unblinded-tokens] ADD COLUMN [redemption-group] integer DEFAULT 0 + """, ], }