diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index 10604187f7ecdae991c5a9448e1fc3612de9f1bd..fe2507b22a6b97842897bb4e60b6c36e6f20bd6c 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -216,7 +216,7 @@ class VoucherStore(object): cursor.execute( """ SELECT - [number], [created], [state], [finished], [token-count] + [number], [created], [state], [finished], [token-count], [public-key] FROM [vouchers] WHERE @@ -300,7 +300,7 @@ class VoucherStore(object): cursor.execute( """ SELECT - [number], [created], [state], [finished], [token-count] + [number], [created], [state], [finished], [token-count], [public-key] FROM [vouchers] """, @@ -339,12 +339,14 @@ class VoucherStore(object): SET [state] = ? , [token-count] = ? , [finished] = ? + , [public-key] = ? WHERE [number] = ? """, ( voucher_state, len(unblinded_tokens), self.now(), + public_key, voucher, ), ) @@ -712,8 +714,20 @@ class Redeeming(object): @attr.s(frozen=True) class Redeemed(object): + """ + The voucher was successfully redeemed. Associated tokens were retrieved + and stored locally. + + :ivar datetime finished: The time when the redemption finished. + + :ivar int token_count: The number of tokens the voucher was redeemed for. + + :ivar unicode public_key: The public part of the key used to sign the + tokens for this voucher. + """ finished = attr.ib(validator=attr.validators.instance_of(datetime)) token_count = attr.ib(validator=attr.validators.instance_of((int, long))) + public_key = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(unicode))) def should_start_redemption(self): return False @@ -723,6 +737,7 @@ class Redeemed(object): u"name": u"redeemed", u"finished": self.finished.isoformat(), u"token-count": self.token_count, + u"public-key": self.public_key, } @@ -819,6 +834,7 @@ class Voucher(object): return Redeemed( parse_datetime(row[0], delimiter=u" "), row[1], + row[2], ) raise ValueError("Unknown voucher state {}".format(state)) @@ -859,6 +875,7 @@ class Voucher(object): state = Redeemed( finished=parse_datetime(state_json[u"finished"]), token_count=state_json[u"token-count"], + public_key=state_json[u"public-key"], ) elif state_name == u"unpaid": state = Unpaid( diff --git a/src/_zkapauthorizer/schema.py b/src/_zkapauthorizer/schema.py index cf71a9c2a9d11b26ba4756e505284770a6952dd3..502628aca377091c6820903b839930f73c2d6195 100644 --- a/src/_zkapauthorizer/schema.py +++ b/src/_zkapauthorizer/schema.py @@ -125,6 +125,12 @@ _UPGRADES = { ) """, ], + + 1: [ + """ + ALTER TABLE [vouchers] ADD COLUMN [public-key] text + """, + ], } def _check_consistency(): diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index 9157bdea6e8611ad340faa473c91296fff8b9a07..a63af545766b05cd166c36db151e5d9fe4f19b2e 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -308,6 +308,7 @@ def voucher_states(): Redeemed, finished=datetimes(), token_count=one_of(integers(min_value=1)), + public_key=dummy_ristretto_keys(), ), builds( DoubleSpend, diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py index f901d020fa1e23d9b26c141369996560d00a0d44..423f4950de7cadac563576da99496f0c47f18c4b 100644 --- a/src/_zkapauthorizer/tests/test_client_resource.py +++ b/src/_zkapauthorizer/tests/test_client_resource.py @@ -768,6 +768,7 @@ class VoucherTests(TestCase): state=Equals(Redeemed( finished=now, token_count=NUM_TOKENS, + public_key=None, )), ), ) @@ -913,6 +914,7 @@ class VoucherTests(TestCase): state=Redeemed( finished=now, token_count=NUM_TOKENS, + public_key=None, ), ).marshal() for voucher diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index 9333ed1d7b5c589e244ae77a9c6aa9e15a3b1acc..1c1af180ec01737e5e699da40f4575047273de27 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -116,6 +116,7 @@ from .strategies import ( tahoe_configs, vouchers, voucher_objects, + dummy_ristretto_keys, clocks, ) from .matchers import ( @@ -149,15 +150,15 @@ class PaymentControllerTests(TestCase): Equals(model_Pending()), ) - @given(tahoe_configs(), datetimes(), vouchers()) - def test_redeemed_after_redeeming(self, get_config, now, voucher): + @given(tahoe_configs(), dummy_ristretto_keys(), datetimes(), vouchers()) + def test_redeemed_after_redeeming(self, get_config, public_key, now, voucher): """ A ``Voucher`` is marked as redeemed after ``IRedeemer.redeem`` succeeds. """ store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store controller = PaymentController( store, - DummyRedeemer(), + DummyRedeemer(public_key), default_token_count=10, ) controller.redeem(voucher) @@ -168,6 +169,7 @@ class PaymentControllerTests(TestCase): Equals(model_Redeemed( finished=now, token_count=10, + public_key=public_key, )), ) diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py index bf2dbaf7e3c4849ef8c8159450cb01ee7dca45ad..e4052d267459d17e0536dbb21b8c716f77e52b3d 100644 --- a/src/_zkapauthorizer/tests/test_model.py +++ b/src/_zkapauthorizer/tests/test_model.py @@ -392,6 +392,7 @@ class UnblindedTokenStoreTests(TestCase): state=Equals(Redeemed( finished=now, token_count=num_tokens, + public_key=public_key, )), ), )