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