Ejemplo n.º 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
Ejemplo n.º 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
Ejemplo n.º 3
0
    def get(self, request, *args, **kwargs):

        initial = dict()

        if "memo" in kwargs:
            memo = json_util.deserialize_memo(kwargs["memo"])

            if "parent_post_id" in memo:
                expected_memo_keys = ["parent_post_id", "post_type", "content"]
                self.form_class = ShortForm
            else:
                expected_memo_keys = [
                    "title", "post_type", "tag_val", "content"
                ]

            for key in expected_memo_keys:
                initial[key] = memo[key]
        else:
            # Attempt to prefill from GET parameters
            for key in "title post_type tag_val content".split():
                value = request.GET.get(key)
                if value:
                    initial[key] = value

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

        context['form'] = self.form_class(initial=initial)
        context['errors_detected'] = False

        return render(request, self.template_name, context)
Ejemplo n.º 4
0
    def post(self, request, *args, **kwargs):
        if "memo" in kwargs:
            post_preview = PostPreview()

            # Some data comes from memo
            memo = json_util.deserialize_memo(kwargs["memo"])

            if "parent_post_id" in memo:
                self.form_class = ShortForm

                parent_post_id = memo["parent_post_id"]

                # Find the parent.
                try:
                    parent = Post.objects.get(pk=parent_post_id)
                except ObjectDoesNotExist, e:
                    msg = "The post does not exist. Perhaps it was deleted request (Request: %s)".format(
                        request)
                    logger.error(msg)
                    raise PostViewException(msg)

                post_preview.parent_post_id = parent_post_id
                post_preview.title = parent.title
                post_preview.tag_val = parent.tag_val
                post_preview.tag_value = html_util.split_tags(parent.tag_val)
            else:
                post_preview.title = memo["title"]
                post_preview.tag_val = memo["tag_val"]
                post_preview.tag_value = html_util.split_tags(memo["tag_val"])

            post_preview.status = Post.OPEN
            post_preview.type = memo["post_type"]
            post_preview.date = datetime.utcfromtimestamp(
                memo["unixtime"]).replace(tzinfo=utc)
Ejemplo n.º 5
0
def gen_invoice(publish_url, memo, node_id):
    context = {}

    nodes_list = ln.get_nodes_list()

    if len(nodes_list) == 0:
        raise ln.LNUtilError("No nodes found")

    if not node_id:
        node_with_top_score = nodes_list[0]
        for node in nodes_list:
            if node["qos_score"] > node_with_top_score["qos_score"]:
                node_with_top_score = node

        node_id = node_with_top_score["id"]
    else:
        node_id = int(node_id)

    context["node_id"] = str(node_id)

    # Lookup the node name
    node_name = "Unknown"
    list_pos = 0
    for pos, n in enumerate(nodes_list):
        if n["id"] == node_id:
            node_name = n["node_name"]
            list_pos = pos

    context["node_name"] = node_name

    next_node_id = nodes_list[(list_pos + 1) % len(nodes_list)]["id"]
    context["next_node_url"] = reverse(
        publish_url,
        kwargs=dict(
            memo=memo,
            node_id=next_node_id
        )
    )
    context["open_channel_url"] = reverse(
        "open-channel-node-selected",
        kwargs=dict(
            node_id=next_node_id
        )
    )

    try:
        details = ln.add_invoice(memo, node_id=node_id)
    except ln.LNUtilError as e:
        logger.exception(e)
        raise


    context['pay_req'] = details['pay_req']

    deserialized_memo = json_util.deserialize_memo(memo)
    context['payment_amount'] = deserialized_memo.get("amt", settings.PAYMENT_AMOUNT)

    return context
Ejemplo n.º 6
0
    def __init__(self, *args, **kwargs):
        if "memo" in kwargs:
            memo = kwargs.pop("memo")
        else:
            memo = kwargs.get("initial").get("memo")

        memo_deserialized = json_util.deserialize_memo(memo)
        action = memo_deserialized.get("action")

        super(SignMessageForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()


        if action == "Accept":
            self.signature = forms.CharField(
                widget=forms.Textarea,
                label="Signature",
                required=True,
                error_messages={
                    'required': (

                    )
                },
                max_length=200, min_length=10,
                validators=[validators.pre_validate_signature],
                help_text="JSON output of the signmessage command or just text of the signature"
            )

            self.helper.layout = Layout(
                Field('signature', rows="4"),
                ButtonHolder(
                    Submit('submit', 'Accept Answer')
                )
            )

            self.helper.form_action = reverse(
                "accept-preview",
                kwargs=dict(memo=memo)
            )
        else:

            self.helper.layout = Layout(
                Field('signature', rows="4"),
                ButtonHolder(
                    Submit('submit', 'Sign')
                )
            )

            self.helper.form_action = reverse(
                "post-preview",
                kwargs=dict(memo=memo)
            )
Ejemplo n.º 7
0
def accept_preview_body(context, post):
    "Renders the post preview body with next steps for Accpet process"
    memo = json_util.deserialize_memo(context["memo"])

    # Don't propagate old signature
    if "sig" in memo:
        del memo["sig"]

    return dict(
        post=post,
        request=context['request'],
        payment_amount=settings.POST_PAYMENT_AMOUNT,
        form=context["form"],
        memo_json=json.dumps(memo, sort_keys=True),
    )
Ejemplo n.º 8
0
def post_preview_body(context, post_preview):
    "Renders the post preview body"

    if type(post_preview) != PostPreview:
        logger.error("post_preview is {}".format(post_preview))

    memo = post_preview.serialize_memo()
    return dict(post=post_preview,
                edit_url=post_preview.get_edit_url(memo),
                publish_url=post_preview.get_publish_url(memo),
                preview_url=post_preview.get_preview_url(post_preview.memo),
                request=context['request'],
                payment_amount=settings.POST_PAYMENT_AMOUNT,
                memo_json=json.dumps(json_util.deserialize_memo(memo),
                                     sort_keys=True),
                form=context["form"],
                user=context['user'],
                date=post_preview.date)
Ejemplo n.º 9
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)
            )
        )
Ejemplo n.º 10
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
Ejemplo n.º 11
0
    def create(self,
               request,
               format=None,
               retry_addinvoice=False,
               retry_num=0):
        memo = request.POST["memo"]

        if retry_num >= CreateInvoiceViewSet.MAX_RETRIES:
            raise CreateInvoiceError(
                "Retry count exceeded: {}".format(retry_num))

        node = LightningNode.objects.get(id=request.POST["node_id"])
        if not node.enabled:
            # TODO: Reader should redirect user to a different node
            user_msg = "Node {} is disabled".format(node.node_name)
            logger.info("{} so can't add invoice for {}".format(
                user_msg, memo))
            raise CreateInvoiceError(user_msg)

        request_obj, created = InvoiceRequest.objects.get_or_create(
            lightning_node=node, memo=memo)

        if created or retry_addinvoice:
            logger.info("New invoice request created: {}".format(request_obj))
            # InvoiceRequest just got created? do:
            #  1. addinvoice RPC to the node
            #  2. create Invoice

            if settings.MOCK_LN_CLIENT:
                invoice_stdout = {}

                invoice_stdout["pay_req"] = "FAKE"
                invoice_stdout["r_hash"] = "FAKE"
                invoice_stdout["node_id"] = node.id

                if len(Invoice.objects.all()) == 0:
                    invoice_stdout["add_index"] = 1
                else:
                    # TODO: Mock multiple nodes. Currently Mock uses Invoice.objects.aggregate which ignores node.
                    invoice_stdout["add_index"] = Invoice.objects.aggregate(
                        Max('add_index'))["add_index__max"] + 1

                serializer = InvoiceSerializer(data=invoice_stdout,
                                               many=False)  # re-serialize
                is_valid = serializer.is_valid(
                    raise_exception=False
                )  # validate data going into the database

                invoice_obj = Invoice(
                    invoice_request=request_obj,
                    lightning_node=node,
                    pay_req=serializer.validated_data.get("pay_req"),
                    r_hash=serializer.validated_data.get("r_hash"),
                    add_index=serializer.validated_data.get("add_index"))
                invoice_obj.save()
                return Response(serializer.validated_data)

            else:
                # TODO: surface addinvoice timeout and other exceptions back to the user
                # Bounties can specify amount in the memo
                deserialized_memo = json_util.deserialize_memo(memo)
                command_results = lnclient.addinvoice(
                    memo,
                    node.rpcserver,
                    amt=deserialized_memo.get("amt", 2),
                    expiry=settings.INVOICE_EXPIRY,
                )

                if command_results["success"]:
                    # Raise node score
                    if node.qos_score > 0:
                        node.qos_score = 0

                    # precondition: qos_score is always non-positive
                    node.qos_score = node.qos_score / 2
                    node.save()

                    logger.info("Successful addinvoice on the node")

                    try:
                        invoice_stdout = json.loads(
                            command_results["stdouterr"])
                    except Exception as e:
                        msg = "Successful addinvoice yet failed to parse json output"
                        logger.exception(e)
                        logger.error(msg)

                        raise CreateInvoiceError(msg)
                else:
                    logger.info(
                        "Failed when trying to run addinvoice on the node")

                    # 1. lower node score
                    till_negative_100 = (node.qos_score + 100)
                    if till_negative_100 < 0:
                        till_negative_100 = 0

                    # precondition: qos_score is always non-positive; till_negative_100 is always non-negative
                    node.qos_score = node.qos_score - (till_negative_100 / 2)
                    node.save()

                    # 2. show error to the user
                    log_msg = "Failed when trying to run addinvoice on the node: failure_type={} failure_msg='{}' stdouterr='{}'".format(
                        command_results["failure_type"],
                        command_results["failure_msg"],
                        command_results["stdouterr"],
                    )
                    user_msg = "Failed when trying to run addinvoice on the node: failure_type={} failure_msg='{}'".format(
                        command_results["failure_type"],
                        command_results["failure_msg"],
                    )
                    logger.error(log_msg)
                    raise CreateInvoiceError(user_msg)

                invoice_stdout["node_id"] = node.id
                if "payment_request" in invoice_stdout:
                    # lncli returns "payment_request" instead of "pay_req", probably since
                    # commit 8f5d78c875b8eca436f7ee2e86e743afee262386 (Dec 20 2019)  build+lncli: default to hex encoding for byte slices
                    invoice_stdout["pay_req"] = invoice_stdout[
                        "payment_request"]

                serializer = InvoiceSerializer(data=invoice_stdout,
                                               many=False)  # re-serialize
                is_valid = serializer.is_valid(
                    raise_exception=False
                )  # validate data going into the database

                if not is_valid:
                    msg = "Output of addinvoice was not valid: errors={} stdout={}".format(
                        serializer.errors, invoice_stdout)
                    logger.error(msg)
                    raise CreateInvoiceError(msg)

                invoice_obj = Invoice(
                    invoice_request=request_obj,
                    lightning_node=node,
                    pay_req=serializer.validated_data.get("pay_req"),
                    r_hash=serializer.validated_data.get("r_hash"),
                    add_index=serializer.validated_data.get("add_index"))
                logger.info("New invoice created! {}".format(invoice_obj))

                invoice_obj.save()
                logger.info("Saved results of addinvoice to DB")

                return Response(serializer.validated_data)

        else:
            logger.info("Good it already exists: {}".format(request_obj))
            try:
                invoice_obj = Invoice.objects.get(invoice_request=request_obj,
                                                  lightning_node_id=node.id)
            except Invoice.DoesNotExist:
                logger.info("Re-trying to create new invoice")
                retry_num += 1
                time.sleep(CreateInvoiceViewSet.RETRY_SLEEP_SECONDS)
                return self.create(request,
                                   format=format,
                                   retry_addinvoice=True,
                                   retry_num=retry_num)

            logger.info("Fetched invoice from DB: {}".format(invoice_obj))
            invoice_obj.node_id = node.id
            serializer = InvoiceSerializer(invoice_obj)

            if serializer.is_valid:
                logger.info("Invoice is valid")
            else:
                msg = "Invoice is NOT valid, errors: {}".format(
                    serializer.errors)
                logger.error(msg)
                raise CreateInvoiceError(msg)

            return Response(serializer.data)
Ejemplo n.º 12
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)
Ejemplo n.º 13
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)
Ejemplo n.º 14
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)
Ejemplo n.º 15
0
    def create(self,
               request,
               format=None,
               retry_addinvoice=False,
               retry_num=0):
        memo = request.POST["memo"]

        if retry_num >= CreateInvoiceViewSet.MAX_RETRIES:
            raise CreateInvoiceError(
                "Retry count exceeded: {}".format(retry_num))

        node = LightningNode.objects.get(id=request.POST["node_id"])
        request_obj, created = InvoiceRequest.objects.get_or_create(
            lightning_node=node, memo=memo)

        if created or retry_addinvoice:
            logger.info("New invoice request created: {}".format(request_obj))
            # InvoiceRequest just got created? do:
            #  1. addinvoice RPC to the node
            #  2. create Invoice

            if settings.MOCK_LN_CLIENT:
                invoice_stdout = {}

                invoice_stdout["pay_req"] = "FAKE"
                invoice_stdout["r_hash"] = "FAKE"
                invoice_stdout["node_id"] = node.id

                if len(Invoice.objects.all()) == 0:
                    invoice_stdout["add_index"] = 1
                else:
                    # TODO: Mock multiple nodes. Currently Mock uses Invoice.objects.aggregate which ignores node.
                    invoice_stdout["add_index"] = Invoice.objects.aggregate(
                        Max('add_index'))["add_index__max"] + 1

                serializer = InvoiceSerializer(data=invoice_stdout,
                                               many=False)  # re-serialize
                is_valid = serializer.is_valid(
                    raise_exception=False
                )  # validate data going into the database

                invoice_obj = Invoice(
                    invoice_request=request_obj,
                    lightning_node=node,
                    pay_req=serializer.validated_data.get("pay_req"),
                    r_hash=serializer.validated_data.get("r_hash"),
                    add_index=serializer.validated_data.get("add_index"))
                invoice_obj.save()
                return Response(serializer.validated_data)

            else:
                # TODO: surface addinvoice timeout and other exceptions back to the user
                # Bonties can specify amount in the memo, everithing else defaults to settings.PAYMENT_AMOUNT
                deserialized_memo = json_util.deserialize_memo(memo)
                invoice_stdout = lnclient.addinvoice(
                    memo,
                    node.rpcserver,
                    amt=deserialized_memo.get("amt", settings.PAYMENT_AMOUNT),
                    expiry=settings.INVOICE_EXPIRY,
                )
                logger.info("Finished addinvoice on the node")

                invoice_stdout["node_id"] = node.id
                if "payment_request" in invoice_stdout:
                    # lncli returns "payment_request" instead of "pay_req", probably since
                    # commit 8f5d78c875b8eca436f7ee2e86e743afee262386 (Dec 20 2019)  build+lncli: default to hex encoding for byte slices
                    invoice_stdout["pay_req"] = invoice_stdout[
                        "payment_request"]

                serializer = InvoiceSerializer(data=invoice_stdout,
                                               many=False)  # re-serialize
                is_valid = serializer.is_valid(
                    raise_exception=False
                )  # validate data going into the database

                if not is_valid:
                    msg = "Output of addinvoice was not valid: errors={} stdout={}".format(
                        serializer.errors, invoice_stdout)
                    logger.error(msg)
                    raise CreateInvoiceError(msg)

                invoice_obj = Invoice(
                    invoice_request=request_obj,
                    lightning_node=node,
                    pay_req=serializer.validated_data.get("pay_req"),
                    r_hash=serializer.validated_data.get("r_hash"),
                    add_index=serializer.validated_data.get("add_index"))
                logger.info("New invoice created! {}".format(invoice_obj))

                invoice_obj.save()
                logger.info("Saved results of addinvoice to DB")

                return Response(serializer.validated_data)

        else:
            logger.info("Good it already exists: {}".format(request_obj))
            try:
                invoice_obj = Invoice.objects.get(invoice_request=request_obj,
                                                  lightning_node_id=node.id)
            except Invoice.DoesNotExist:
                logger.info("Re-trying to create new invoice")
                retry_num += 1
                time.sleep(CreateInvoiceViewSet.RETRY_SLEEP_SECONDS)
                return self.create(request,
                                   format=format,
                                   retry_addinvoice=True,
                                   retry_num=retry_num)

            logger.info("Fetched invoice from DB: {}".format(invoice_obj))
            invoice_obj.node_id = node.id
            serializer = InvoiceSerializer(invoice_obj)

            if serializer.is_valid:
                logger.info("Invoice is valid")
            else:
                msg = "Invoice is NOT valid, errors: {}".format(
                    serializer.errors)
                logger.error(msg)
                raise CreateInvoiceError(msg)

            return Response(serializer.data)