Skip to content
Snippets Groups Projects
Commit 3a81cab6 authored by Jean-Paul Calderone's avatar Jean-Paul Calderone
Browse files

Teach PaymentController what to do on AlreadySpent errors

parent 74ea0014
No related branches found
No related tags found
1 merge request!63Expose voucher state
......@@ -39,6 +39,7 @@ from twisted.python.url import (
from twisted.internet.defer import (
Deferred,
succeed,
fail,
inlineCallbacks,
returnValue,
)
......@@ -62,6 +63,13 @@ from .model import (
)
class AlreadySpent(Exception):
"""
An attempt was made to redeem a voucher which has already been redeemed.
The redemption cannot succeed and should not be retried automatically.
"""
class IRedeemer(Interface):
"""
An ``IRedeemer`` can exchange a voucher for one or more passes.
......@@ -100,7 +108,10 @@ class IRedeemer(Interface):
:return: A ``Deferred`` which fires with a list of ``UnblindedToken``
instances on successful redemption or which fails with any error
to allow a retry to be made at some future point.
to allow a retry to be made at some future point. It may also
fail with an ``AlreadySpent`` error to indicate the redemption
server considers the voucher to have been redeemed already and
will not allow it to be redeemed.
"""
def tokens_to_passes(message, unblinded_tokens):
......@@ -150,6 +161,28 @@ class NonRedeemer(object):
)
@implementer(IRedeemer)
@attr.s
class DoubleSpentRedeemer(object):
"""
A ``DoubleSpentRedeemer`` pretends to try to redeem vouchers for ZKAPs but
always fails with an error indicating the voucher has already been spent.
"""
def random_tokens_for_voucher(self, voucher, count):
return dummy_random_tokens(voucher, count)
def redeem(self, voucher, random_tokens):
return fail(AlreadySpent(voucher))
def dummy_random_tokens(voucher, count):
return list(
RandomToken(u"{}-{}".format(voucher.number, n))
for n
in range(count)
)
@implementer(IRedeemer)
@attr.s
class DummyRedeemer(object):
......@@ -167,12 +200,7 @@ class DummyRedeemer(object):
Generate some number of random tokens to submit along with a voucher for
redemption.
"""
# Dummy token generation.
return list(
RandomToken(u"{}-{}".format(voucher.number, n))
for n
in range(count)
)
return dummy_random_tokens(voucher, count)
def redeem(self, voucher, random_tokens):
"""
......@@ -431,7 +459,18 @@ class PaymentController(object):
self.store.insert_unblinded_tokens_for_voucher(voucher, unblinded_tokens)
def _redeemFailure(self, voucher, reason):
self._log.failure("Redeeming random tokens for a voucher ({voucher}) failed.", reason, voucher=voucher)
if reason.check(AlreadySpent):
self._log.error(
"Voucher {voucher} reported as already spent during redemption.",
voucher=voucher,
)
self.store.mark_voucher_double_spent(voucher)
else:
self._log.failure(
"Redeeming random tokens for a voucher ({voucher}) failed.",
reason,
voucher=voucher,
)
return None
def _finalRedeemError(self, voucher, reason):
......
......@@ -43,6 +43,7 @@ from testtools.matchers import (
IsInstance,
HasLength,
AfterPreprocessing,
MatchesStructure,
)
from testtools.twistedsupport import (
succeeded,
......@@ -94,6 +95,7 @@ from ..controller import (
IRedeemer,
NonRedeemer,
DummyRedeemer,
DoubleSpentRedeemer,
RistrettoRedeemer,
PaymentController,
)
......@@ -146,6 +148,9 @@ class PaymentControllerTests(TestCase):
@given(tahoe_configs(), datetimes(), vouchers())
def test_redeemed_after_redeeming(self, get_config, now, voucher):
"""
A ``Voucher`` is marked as redeemed after ``IRedeemer.redeem`` succeeds.
"""
tempdir = self.useFixture(TempDir())
store = VoucherStore.from_node_config(
get_config(
......@@ -167,6 +172,35 @@ class PaymentControllerTests(TestCase):
Equals(u"redeemed"),
)
@given(tahoe_configs(), datetimes(), vouchers())
def test_double_spent_after_double_spend(self, get_config, now, voucher):
"""
A ``Voucher`` is marked as double-spent after ``IRedeemer.redeem`` fails
with ``AlreadySpent``.
"""
tempdir = self.useFixture(TempDir())
store = VoucherStore.from_node_config(
get_config(
tempdir.join(b"node"),
b"tub.port",
),
now=lambda: now,
connect=memory_connect,
)
controller = PaymentController(
store,
DoubleSpentRedeemer(),
)
controller.redeem(voucher)
persisted_voucher = store.get(voucher)
self.assertThat(
persisted_voucher,
MatchesStructure(
state=Equals(u"double-spend"),
),
)
NOWHERE = URL.from_text(u"https://127.0.0.1/")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment