Пример #1
0
    def get_context_data(self, **kwargs):
        context = super(AcceptPreviewView, self).get_context_data(**kwargs)

        view_obj = context.get("view")
        memo_serialized = view_obj.kwargs["memo"]

        memo = validators.validate_memo(
            json_util.deserialize_memo(memo_serialized))

        signature = kwargs.get("signature")
        if signature:
            signature = validators.pre_validate_signature(signature)

            result = ln.verifymessage(memo=json.dumps(memo, sort_keys=True),
                                      sig=signature)
            if result["valid"]:
                identity_pubkey = result["identity_pubkey"]
                context['user'] = User(id=1, pubkey=identity_pubkey)
                memo["sig"] = signature  # add signature to memo

        context['post'] = self.get_model(memo)

        # re-serialized because signature was added
        context['memo'] = json_util.serialize_memo(memo)

        # gives user a friendy error message if no LN nodes are avaialble
        context["nodes_list"] = [n["node_name"] for n in ln.get_nodes_list()]

        return context
Пример #2
0
    def get_context_data(self, **kwargs):

        context = super(PostPreviewView, self).get_context_data(**kwargs)

        view_obj = context.get("view")
        memo_serialized = view_obj.kwargs["memo"]

        memo = validators.validate_memo(
            json_util.deserialize_memo(memo_serialized))

        signature = kwargs.get("signature")
        if signature:
            signature = validators.pre_validate_signature(signature)

            result = ln.verifymessage(memo=json.dumps(memo, sort_keys=True),
                                      sig=signature)
            if result["valid"]:
                identity_pubkey = result["identity_pubkey"]
                context['user'] = User(id=1, pubkey=identity_pubkey)
                memo["sig"] = signature  # add signature to memo

        if "sig" not in memo:
            context['user'] = User(id=1, pubkey="Unknown")

        # re-serialized because signature was added
        memo_serialized = json_util.serialize_memo(memo)

        post_preview = self.get_model(memo)
        context["memo"] = memo_serialized
        context["post"] = post_preview
        context["publish_url"] = post_preview.get_publish_url(
            memo=memo_serialized)

        return context
Пример #3
0
    def post(self, request, *args, **kwargs):
        amt = request.POST.get("amt")
        assert amt is not None, "bug"

        memo = kwargs.get("memo")
        assert memo is not None, "bug"


        # change the memo to have new amt
        memo_deserialized = validators.validate_memo(
            json_util.deserialize_memo(memo)
        )

        memo_deserialized["amt"] = amt
        memo = json_util.serialize_memo(memo_deserialized)

        return HttpResponseRedirect(
            reverse(
                "vote-publish",
                kwargs=dict(memo=memo)
            )
        )
Пример #4
0
    def run_one_node(self, node):
        start_time = time.time()

        invoices_details = lnclient.listinvoices(
            index_offset=node.global_checkpoint,
            rpcserver=node.rpcserver,
            mock=settings.MOCK_LN_CLIENT)

        if node not in self.all_invoices_from_db:
            invoice_list_from_db = {}
            logger.info("DB has no invoices for this node")
        else:
            invoice_list_from_db = self.all_invoices_from_db[node]

        # example of invoices_details: {"invoices": [], 'first_index_offset': '5', 'last_index_offset': '72'}
        invoice_list_from_node = invoices_details['invoices']
        self.invoice_count_from_nodes[node] = len(invoice_list_from_node)

        if settings.MOCK_LN_CLIENT:
            # Here the mock pulls invoices from DB Invoice model, while in prod invoices are pulled from the Lightning node
            # 1. Mocked lnclient.listinvoices returns an empty list
            # 2. The web front end adds the InvoiceRequest to the DB before it creates the actual invoices with lnclient.addinvoice
            # 3. Mocked API lnclient.addinvoice simply fakes converting InvoiceRequest to Invoice and saves to DB
            # 4. Here the mocked proces_tasks pulls invoices from DB Invoice model and pretends they came from lnclient.listinvoices
            # 5. After X seconds passed based on Invoice created time, here Mock update the Invoice checkpoint to "done" faking a payment

            invoice_list_from_node = []
            for invoice_obj in Invoice.objects.filter(
                    lightning_node=node, checkpoint_value="no_checkpoint"):
                invoice_request = InvoiceRequest.objects.get(
                    id=invoice_obj.invoice_request.id)
                if invoice_request.lightning_node.id != node.id:
                    continue

                mock_setteled = (invoice_obj.created + timedelta(seconds=3) <
                                 timezone.now())
                creation_unixtime = int(
                    time.mktime(invoice_obj.created.timetuple()))

                invoice_list_from_node.append({
                    "settled":
                    mock_setteled,
                    "settle_date":
                    str(int(time.time())) if mock_setteled else 0,
                    "state":
                    "SETTLED" if mock_setteled else "OPEN",
                    "memo":
                    invoice_request.memo,
                    "add_index":
                    invoice_obj.add_index,
                    "payment_request":
                    invoice_obj.pay_req,
                    "pay_req":
                    invoice_obj.pay_req,  # Old format
                    "r_hash":
                    invoice_obj.r_hash,
                    "creation_date":
                    str(creation_unixtime),
                    "expiry":
                    str(creation_unixtime + 120)
                })

        retry_mini_map = {
            int(invoice['add_index']): False
            for invoice in invoice_list_from_node
        }

        one_hour_ago = timezone.now() - timedelta(hours=1)
        recent_invoices = [
            i.id for i in invoice_list_from_db.values()
            if i.modified > one_hour_ago
        ]
        if len(recent_invoices) == 0:
            logger.info("invoice_list_from_db is empty")
        else:
            logger.info(
                "Recent invoice_list_from_db was: {}".format(recent_invoices))

        for raw_invoice in invoice_list_from_node:
            # Example of raw_invoice:
            # {
            # 'htlcs': [],
            # 'settled': False,
            # 'add_index': '5',
            # 'value': '1',
            # 'memo': '',
            # 'cltv_expiry': '40', 'description_hash': None, 'route_hints': [],
            # 'r_hash': '+fw...=', 'settle_date': '0', 'private': False, 'expiry': '3600',
            # 'creation_date': '1574459849',
            # 'amt_paid': '0', 'features': {}, 'state': 'OPEN', 'amt_paid_sat': '0',
            # 'value_msat': '1000', 'settle_index': '0',
            # 'amt_paid_msat': '0', 'r_preimage': 'd...=', 'fallback_addr': '',
            # 'payment_request': 'lnbc...'
            # }
            created = general_util.unixtime_to_datetime(
                int(raw_invoice["creation_date"]))
            if created < general_util.now() - settings.INVOICE_RETENTION:
                logger.info(
                    "Got old invoice from listinvoices, skipping... {} is older then retention {}"
                    .format(created, settings.INVOICE_RETENTION))
                continue

            add_index_from_node = int(raw_invoice["add_index"])

            invoice = invoice_list_from_db.get(add_index_from_node)

            if invoice is None:
                logger.error(
                    "Unknown add_index {}".format(add_index_from_node))
                logger.error(
                    "Raw invoice from node was: {}".format(raw_invoice))

                if raw_invoice['state'] == "CANCELED":
                    logger.error("Skipping because invoice is cancelled...")
                    retry_mini_map[
                        add_index_from_node] = False  # advance global checkpoint
                else:
                    retry_mini_map[
                        add_index_from_node] = True  # try again later

                continue

            # Validate
            if invoice.invoice_request.memo != raw_invoice["memo"]:
                logger.error(
                    "Memo in DB does not match the one in invoice request: db=({}) invoice_request=({})"
                    .format(invoice.invoice_request.memo, raw_invoice["memo"]))

                retry_mini_map[add_index_from_node] = True  # try again later
                continue

            if invoice.pay_req != raw_invoice["payment_request"]:
                logger.error(
                    "Payment request does not match the one in invoice request: db=({}) invoice_request=({})"
                    .format(invoice.pay_req, raw_invoice["payment_request"]))

                retry_mini_map[add_index_from_node] = True  # try again later
                continue

            checkpoint_helper = CheckpointHelper(
                node=node,
                invoice=invoice,
                creation_date=raw_invoice["creation_date"])

            if checkpoint_helper.is_checkpointed():
                continue

            if raw_invoice['state'] == 'CANCELED':
                checkpoint_helper.set_checkpoint("canceled")
                continue

            if raw_invoice['settled'] and (raw_invoice['state'] != 'SETTLED'
                                           or int(raw_invoice['settle_date'])
                                           == 0):
                checkpoint_helper.set_checkpoint("inconsistent")
                continue

            if time.time() > int(raw_invoice['creation_date']) + int(
                    raw_invoice['expiry']):
                checkpoint_helper.set_checkpoint("expired")
                continue

            if not raw_invoice['settled']:
                logger.info("Skipping invoice at {}: Not yet settled".format(
                    checkpoint_helper))
                retry_mini_map[
                    checkpoint_helper.add_index] = True  # try again later
                continue

            #
            # Invoice is settled
            #

            logger.info(
                "Processing invoice at {}: SETTLED".format(checkpoint_helper))

            memo = raw_invoice["memo"]
            try:
                action_details = json_util.deserialize_memo(memo)
            except json_util.JsonUtilException:
                checkpoint_helper.set_checkpoint("deserialize_failure")
                continue

            try:
                validators.validate_memo(action_details)
            except ValidationError as e:
                logger.exception(e)
                checkpoint_helper.set_checkpoint("memo_invalid")
                continue

            action = action_details.get("action")

            if action:
                if action in ["Upvote", "Accept"]:
                    vote_type = Vote.VOTE_TYPE_MAP[action]
                    change = settings.PAYMENT_AMOUNT
                    post_id = action_details["post_id"]
                    try:
                        post = Post.objects.get(pk=post_id)
                    except (ObjectDoesNotExist, ValueError):
                        logger.error(
                            "Skipping vote. The post for vote does not exist: {}"
                            .format(action_details))
                        checkpoint_helper.set_checkpoint("invalid_post")
                        continue

                    user = get_anon_user()

                    logger.info(
                        "Creating a new vote: author={}, post={}, type={}".
                        format(user, post, vote_type))
                    vote = Vote.objects.create(author=user,
                                               post=post,
                                               type=vote_type)

                    # Update user reputation
                    # TODO: reactor score logic to be shared with "mark_fake_test_data.py"
                    User.objects.filter(pk=post.author.id).update(
                        score=F('score') + change)

                    # The thread score represents all votes in a thread
                    Post.objects.filter(pk=post.root_id).update(
                        thread_score=F('thread_score') + change)

                    if vote_type == Vote.ACCEPT:
                        if "sig" not in action_details:
                            checkpoint_helper.set_checkpoint("sig_missing")
                            continue

                        sig = action_details.pop("sig")
                        sig = validators.pre_validate_signature(sig)

                        verifymessage_detail = lnclient.verifymessage(
                            msg=json.dumps(action_details, sort_keys=True),
                            sig=sig,
                            rpcserver=node.rpcserver,
                            mock=settings.MOCK_LN_CLIENT)

                        if not verifymessage_detail["valid"]:
                            checkpoint_helper.set_checkpoint(
                                "invalid_signiture")
                            continue

                        if verifymessage_detail[
                                "pubkey"] != post.parent.author.pubkey:
                            checkpoint_helper.set_checkpoint(
                                "signiture_unauthorized")
                            continue

                        if change > 0:
                            # First, un-accept all answers
                            for answer in Post.objects.filter(
                                    parent=post.parent, type=Post.ANSWER):
                                if answer.has_accepted:
                                    Post.objects.filter(pk=answer.id).update(
                                        vote_count=F('vote_count') - change,
                                        has_accepted=False)

                            # There does not seem to be a negation operator for F objects.
                            Post.objects.filter(pk=post.id).update(
                                vote_count=F('vote_count') + change,
                                has_accepted=True)
                            Post.objects.filter(pk=post.root_id).update(
                                has_accepted=True)
                        else:
                            # TODO: change "change". here change is set to payment ammount, so does not make sense to be called change
                            # TODO: detect un-accept attempt and raise "Un-accept not yet supported"
                            raise Exeption(
                                "Payment ammount has to be positive")
                    else:
                        Post.objects.filter(pk=post.id).update(
                            vote_count=F('vote_count') + change)

                        # Upvote on an Aswer is the trigger for potentian bounty awards
                        if post.type == Post.ANSWER and post.author != get_anon_user(
                        ):
                            award_bounty(question_post=post.parent)

                    checkpoint_helper.set_checkpoint("done",
                                                     action_type="upvote",
                                                     action_id=post.id)

                elif action == "Bounty":
                    valid = True
                    for keyword in ["post_id", "amt"]:
                        if keyword not in action_details:
                            logger.warn(
                                "Bounty invalid because {} is missing".format(
                                    keyword))
                            valid = False

                    if not valid:
                        logger.warn("Could not start Bounty: bounty_invalid")
                        checkpoint_helper.set_checkpoint("bounty_invalid")
                        continue

                    post_id = action_details["post_id"]
                    amt = action_details["amt"]

                    try:
                        post_obj = Post.objects.get(pk=post_id)
                    except (ObjectDoesNotExist, ValueError):
                        logger.error(
                            "Bounty invalid because post {} does not exist".
                            format(post_id))
                        checkpoint_helper.set_checkpoint(
                            "bounty_invalid_post_does_not_exist")
                        continue

                    logger.info("Starting bounty for post {}!".format(post_id))

                    new_b = Bounty(
                        post_id=post_obj,
                        amt=amt,
                        activation_time=timezone.now(),
                    )
                    new_b.save()

                    checkpoint_helper.set_checkpoint("done",
                                                     action_type="bonty",
                                                     action_id=post_id)
                else:
                    logger.error("Invalid action: {}".format(action_details))
                    checkpoint_helper.set_checkpoint("invalid_action")
                    continue
            else:
                # Posts do not include the "action" key to save on memo space
                logger.info("Action details {}".format(action_details))

                if "sig" in action_details:
                    sig = action_details.pop("sig")
                    sig = validators.pre_validate_signature(sig)

                    verifymessage_detail = lnclient.verifymessage(
                        msg=json.dumps(action_details, sort_keys=True),
                        sig=sig,
                        rpcserver=node.rpcserver,
                        mock=settings.MOCK_LN_CLIENT)

                    if not verifymessage_detail["valid"]:
                        checkpoint_helper.set_checkpoint("invalid_signiture")
                        continue
                    pubkey = verifymessage_detail["pubkey"]
                else:
                    pubkey = "Unknown"

                if "parent_post_id" in action_details:
                    # Find the parent.
                    try:
                        parent_post_id = int(action_details["parent_post_id"])
                        parent = Post.objects.get(pk=parent_post_id)
                    except (ObjectDoesNotExist, ValueError):
                        logger.error(
                            "The post parent does not exist: {}".format(
                                action_details))
                        checkpoint_helper.set_checkpoint("invalid_parent_post")
                        continue

                    title = parent.title
                    tag_val = parent.tag_val
                else:
                    title = action_details["title"]
                    tag_val = action_details["tag_val"]
                    parent = None

                user, created = User.objects.get_or_create(pubkey=pubkey)

                post = Post(
                    author=user,
                    parent=parent,
                    type=action_details["post_type"],
                    title=title,
                    content=action_details["content"],
                    tag_val=tag_val,
                )

                # TODO: Catch failures when post title is duplicate (e.g. another node already saved post)
                post.save()

                # New Answer is the trigger for potentian bounty awards
                if post.type == Post.ANSWER and user != get_anon_user():
                    award_bounty(question_post=post.parent)

                # Save tags
                if "tag_val" in action_details:
                    tags = action_details["tag_val"].split(",")
                    for tag in tags:
                        tag_obj, created = Tag.objects.get_or_create(name=tag)
                        if created:
                            logger.info("Created a new tag: {}".format(tag))

                        tag_obj.count += 1
                        post.tag_set.add(tag_obj)

                        tag_obj.save()
                        post.save()

                checkpoint_helper.set_checkpoint("done",
                                                 action_type="post",
                                                 action_id=post.id)

        # advance global checkpoint
        new_global_checkpoint = None
        for add_index in sorted(retry_mini_map.keys()):
            retry = retry_mini_map[add_index]
            if retry:
                break
            else:
                logger.info("add_index={} advances global checkpoint".format(
                    add_index))
                new_global_checkpoint = add_index

        if new_global_checkpoint:
            node.global_checkpoint = new_global_checkpoint
            node.save()
            logger.info(
                "Saved new global checkpoint {}".format(new_global_checkpoint))

        processing_wall_time = time.time() - start_time

        logger.info("Processing node {} took {:.3f} seconds".format(
            node.node_name, processing_wall_time))
        return processing_wall_time
Пример #5
0
    def post(self, request, *args, **kwargs):
        """
        POST is for checking signature of Accept "votes"
        """

        signature = request.POST.get("signature")
        kwargs["signature"] = signature

        try:
            context = super(VotePublishView, self).get_context_data()
            view_obj = context.get("view")

        except Exception as e:
            logger.exception(e)
            raise

        memo_serialized = view_obj.kwargs["memo"]

        memo = validators.validate_memo(
            json_util.deserialize_memo(memo_serialized))

        form = self.form_class(request.POST, memo=memo_serialized)

        errors_detected_skip_other_checks = False
        if signature:
            try:
                signature = validators.pre_validate_signature(signature)
                result = ln.verifymessage(memo=json.dumps(memo,
                                                          sort_keys=True),
                                          sig=signature)
            except ln.LNUtilError as msg:
                result = {
                    "valid": False,
                }
            except ValidationError as msg:
                result = {
                    "valid": False,
                }

            post = AcceptPreviewView().get_model(memo)

            if not result["valid"]:
                # Signature is invalid

                ## See: https://github.com/alevchuk/ln-central/issues/27
                # self.form_class.add_error(
                #     "signature",
                #     ValidationError("Signature is invalid. Try signing latest preview data or delete signature to be anonymous.")
                # )

                context = {
                    "post": post,
                    "form": form,
                    "errors_detected": True,
                    "show_error_summary": True,
                    "error_summary_list": ["Signature is invalid."]
                }

                errors_detected_skip_other_checks = True
            else:
                # Signature is valid, check if Accept belongs to the author of the post
                assert post.type == Post.ANSWER and post.parent.type == Post.QUESTION, "Accept only Answers to Questions"

                if post.parent.author.pubkey != result["identity_pubkey"]:
                    context = {
                        "post":
                        post,
                        "form":
                        form,
                        "errors_detected":
                        True,
                        "show_error_summary":
                        True,
                        "error_summary_list": [
                            "Signature does not belong to the Question author. Sorry, you can only accept an Answer if you are the author of the Question."
                        ]
                    }

                    errors_detected_skip_other_checks = True

        if not errors_detected_skip_other_checks:
            try:
                context = self.get_context_data(**kwargs)
            except Exception as e:
                logger.exception(e)
                raise

            context["form"] = form
            context["errors_detected"] = form.is_valid()

        return render(request, self.template_name, context)
Пример #6
0
    def post(self, request, *args, **kwargs):
        """
        Post is used when checking signature
        """
        signature = request.POST.get("signature")
        kwargs["signature"] = signature

        view = super(AcceptPreviewView, self).get(request, *args, **kwargs)
        view_obj = super(AcceptPreviewView,
                         self).get_context_data().get("view")
        memo_serialized = view_obj.kwargs["memo"]

        memo = validators.validate_memo(
            json_util.deserialize_memo(memo_serialized))
        post = self.get_model(memo)

        form = self.form_class(request.POST, memo=memo_serialized)

        errors_detected_skip_other_checks = False
        if signature:
            signature = validators.pre_validate_signature(signature)

            try:
                result = ln.verifymessage(memo=json.dumps(memo,
                                                          sort_keys=True),
                                          sig=signature)
            except ln.LNUtilError as msg:
                result = {
                    "valid": False,
                }

            if not result["valid"]:
                # Signature is invalid

                ## See: https://github.com/alevchuk/ln-central/issues/27
                # self.form_class.add_error(
                #     "signature",
                #     ValidationError("Signature is invalid. Try signing latest preview data or delete signature to be anonymous.")
                # )

                context = {
                    "post":
                    post,
                    "memo":
                    memo_serialized,
                    "form":
                    form,
                    "errors_detected":
                    True,
                    "show_error_summary":
                    True,
                    "error_summary_list": [
                        "Signature is invalid. Try signing the latest message shown bellow."
                    ]
                }

                errors_detected_skip_other_checks = True

        if not errors_detected_skip_other_checks:
            try:
                context = self.get_context_data(**kwargs)
            except Exception as e:
                logger.exception(e)
                raise

            context["form"] = form

            if not form.is_valid():
                hide_form_errors = False
                if not signature:
                    error_summary_list = [
                        "Signature is required here. Yet when you clicked \"Accept Answer\" the signature was empty."
                    ]
                    hide_form_errors = True
                else:
                    error_summary_list = [
                        "Signature is required here. Yet when you clicked \"Accept Answer\" the signature was invalid."
                    ]

                if hide_form_errors:
                    form = self.form_class(memo=memo_serialized)

                context = {
                    "post": post,
                    "memo": context["memo"],
                    "form": form,
                    "errors_detected": True,
                    "show_error_summary": True,
                    "error_summary_list": error_summary_list
                }
            else:
                # Now check if the signature belongs to author of the question
                pubkey = result["identity_pubkey"]

                if result["identity_pubkey"] == post.parent.author.pubkey:
                    # Looks good! Let's generate an invoice
                    return HttpResponseRedirect(
                        post.get_accept_publish_url(memo=context["memo"]))
                else:
                    context = {
                        "post":
                        post,
                        "memo":
                        context["memo"],
                        "form":
                        form,
                        "errors_detected":
                        True,
                        "show_error_summary":
                        True,
                        "error_summary_list": [
                            "Question has a different author. You can only accept answers if you're the author of the question."
                        ]
                    }

        return render(request, self.template_name, context)
Пример #7
0
    def post(self, request, *args, **kwargs):
        """
        POST is for checking signature
        """

        signature = request.POST.get("signature")
        kwargs["signature"] = signature

        view = super(PostPreviewView, self).get(request, *args, **kwargs)

        try:
            view_obj = super(PostPreviewView,
                             self).get_context_data().get("view")
        except Exception as e:
            logger.exception(e)
            raise

        memo_serialized = view_obj.kwargs["memo"]

        memo = validators.validate_memo(
            json_util.deserialize_memo(memo_serialized))

        form = self.form_class(request.POST, memo=memo_serialized)

        errors_detected_skip_other_checks = False
        if signature:
            try:
                signature = validators.pre_validate_signature(signature)
                result = ln.verifymessage(memo=json.dumps(memo,
                                                          sort_keys=True),
                                          sig=signature)
            except ln.LNUtilError as msg:
                result = {
                    "valid": False,
                }
            except ValidationError as msg:
                result = {
                    "valid": False,
                }

            if not result["valid"]:
                # Signature is invalid

                ## See: https://github.com/alevchuk/ln-central/issues/27
                # self.form_class.add_error(
                #     "signature",
                #     ValidationError("Signature is invalid. Try signing latest preview data or delete signature to be anonymous.")
                # )

                post_preview = self.get_model(memo)

                context = {
                    "post":
                    post_preview,
                    "form":
                    form,
                    "user":
                    User.objects.get(pubkey="Unknown"),
                    "errors_detected":
                    True,
                    "show_error_summary":
                    True,
                    "error_summary_list": [
                        "Signature is invalid. Try signing latest preview data or delete signature to be anonymous."
                    ]
                }

                errors_detected_skip_other_checks = True

        if not errors_detected_skip_other_checks:
            try:
                context = self.get_context_data(**kwargs)
            except Exception as e:
                logger.exception(e)
                raise

            context["form"] = form
            context["errors_detected"] = not form.is_valid()

        return render(request, self.template_name, context)