Skip to content
Snippets Groups Projects
spending.py 4.72 KiB
Newer Older
  • Learn to ignore specific revisions
  • # Copyright 2019 PrivateStorage.io, LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    """
    A module for logic controlling the manner in which ZKAPs are spent.
    """
    
    
    from zope.interface import (
        Interface,
        Attribute,
        implementer,
    )
    
    
    import attr
    
    from .eliot import (
        GET_PASSES,
    )
    
    
    class IPassGroup(Interface):
        """
        A group of passed meant to be spent together.
        """
        passes = Attribute(":ivar list[Pass] passes: The passes themselves.")
    
        def split(select_indices):
            """
            Create two new ``IPassGroup`` providers.  The first contains all passes in
            this group at the given indices.  The second contains all the others.
    
            :param list[int] select_indices: The indices of the passes to include
                in the first resulting group.
    
            :return (IPassGroup, IPassGroup): The two new groups.
            """
    
        def expand(by_amount):
            """
            Create a new ``IPassGroup`` provider which contains all of this group's
            passes and some more.
    
            :param int by_amount: The number of additional passes the resulting
                group should contain.
    
            :return IPassGroup: The new group.
            """
    
        def mark_spent():
            """
            The passes have been spent successfully.  Ensure none of them appear in
            any ``IPassGroup`` provider created in the future.
    
            :return: ``None``
            """
    
        def mark_invalid(reason):
            """
            The passes could not be spent.  Ensure none of them appear in any
            ``IPassGroup`` provider created in the future.
    
            :param unicode reason: A short description of the reason the passes
                could not be spent.
    
            :return: ``None``
            """
    
        def reset():
            """
            The passes have not been spent.  Return them to for use in a future
            ``IPassGroup`` provider.
    
            :return: ``None``
            """
    
    
    class IPassFactory(Interface):
        """
        An object which can create passes.
        """
        def get(message, num_passes):
            """
            :param unicode message: A request-binding message for the resulting passes.
    
            :param int num_passes: The number of passes to request.
    
            :return IPassGroup: A group of passes bound to the given message and
                of the requested size.
            """
    
    
    @implementer(IPassGroup)
    @attr.s
    class PassGroup(object):
        """
        Track the state of a group of passes intended as payment for an operation.
    
        :ivar unicode _message: The request binding message for this group of
            passes.
    
        :ivar IPassFactory _factory: The factory which created this pass group.
    
        :ivar list[Pass] passes: The passes of which this group consists.
        """
        _message = attr.ib()
        _factory = attr.ib()
        passes = attr.ib()
    
        def split(self, select_indices):
            selected = []
            unselected = []
            for idx, p in enumerate(self.passes):
                if idx in select_indices:
                    selected.append(p)
                else:
                    unselected.append(p)
            return (
                attr.evolve(self, passes=selected),
                attr.evolve(self, passes=unselected),
            )
    
        def expand(self, by_amount):
            return attr.evolve(
                self,
                passes=self.passes + self._factory.get(self._message, by_amount).passes,
            )
    
        def mark_spent(self):
            self._factory._mark_spent(self.passes)
    
        def mark_invalid(self, reason):
            self._factory._mark_invalid(reason, self.passes)
    
        def reset(self):
            self._factory._reset(self.passes)
    
    
    @implementer(IPassFactory)
    
    @attr.s
    class SpendingController(object):
        """
        A ``SpendingController`` gives out ZKAPs and arranges for re-spend
        attempts when necessary.
        """
        extract_unblinded_tokens = attr.ib()
        tokens_to_passes = attr.ib()
    
        def get(self, message, num_passes):
            unblinded_tokens = self.extract_unblinded_tokens(num_passes)
            passes = self.tokens_to_passes(message, unblinded_tokens)
            GET_PASSES.log(
                message=message,
                count=num_passes,
            )
    
            return PassGroup(message, self, passes)
    
        def _mark_spent(self, group):
            # TODO
            pass
    
        def _mark_invalid(self, reason, group):
            # TODO
            pass
    
        def _reset(self, group):
            # TODO
            pass