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
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
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)
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)
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
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) )
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), )
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)
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) ) )
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
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)
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)
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)
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)
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)