def test_reject(self): tx, _ = _generate_tx(False) resp = SignTransactionResponse(tx) assert not resp.rejected resp.reject() assert resp.rejected
def handle_sign_transaction(self, f: Callable[ [SignTransactionRequest, SignTransactionResponse], None], signature: str, req_body: str) -> Tuple[int, str]: """A hook for handling a sign transaction request from Agora. :param f: A function to call with the received event. Implementations can raise :exc:`InvoiceError <agora.error.InvoiceError>` to return a 403 response with invoice error details or :exc:`WebhookRequestError <agora.error.WebhookRequestError>` to return a specific HTTP status code and body. :param signature: The Agora HMAC signature included in the request headers. :param req_body: The request body. :return: A Tuple of the status code (int) and the request body (str). """ if self.secret and not self.is_valid_signature(req_body, signature): return 401, '' try: json_req_body = json.loads(req_body) except JSONDecodeError: return 400, 'invalid request body' req = SignTransactionRequest.from_json(json_req_body, self.environment) resp = SignTransactionResponse(req.envelope) try: f(req, resp) except WebhookRequestError as e: return e.status_code, e.response_body except Exception as e: return 500, str(e) data = resp.to_json() if resp.rejected: return 403, json.dumps(data) return 200, json.dumps(data)
def test_mark_invoice_error(self): resp = SignTransactionResponse(_generate_envelope()) resp.mark_invoice_error(5, InvoiceErrorReason.SKU_NOT_FOUND) assert resp.rejected assert len(resp.invoice_errors) == 1 assert resp.invoice_errors[0].op_index == 5 assert resp.invoice_errors[0].reason == InvoiceErrorReason.SKU_NOT_FOUND
def test_sign(self): resp = SignTransactionResponse(_generate_envelope()) private_key = PrivateKey.random() resp.sign(private_key) # kp.verify throws an error if the signature doesn't match private_key.public_key.verify(resp.envelope.hash_meta(), resp.envelope.signatures[-1].signature)
def _sign_transaction(req: SignTransactionRequest, resp: SignTransactionResponse): for idx, payment in enumerate(req.payments): # Double check that the transaction crafter isn't trying to impersonate us if payment.sender == webhook_private_key.public_key(): logging.warning("rejecting: payment sender is webhook address") resp.reject() return # In this example, we don't want to sign transactions that are not sending Kin to the webhook account. Other # application use cases may not have this restrictions if payment.dest != webhook_private_key.public_key(): logging.warning( "rejecting: bad destination {}, expected {}".format( payment.dest.stellar_address, webhook_private_key.public_key().stellar_address)) resp.mark_invoice_error(idx, InvoiceErrorReason.WRONG_DESTINATION) # If the transaction crafter submitted an invoice, make sure the line item SKUs are set. # # Note: the SKU is optional, but we simulate a rejection here for testing. # Applications may wish to cross-check their own databases for the item being purchased. If the user has already # purchased said 'item', they may wish to use mark the invoice error as InvoiceErrorReason.ALREADY_PAID. if payment.invoice: for line_item in payment.invoice.items: if not line_item.sku: logging.warning("rejecting: invoice missing sku") resp.mark_invoice_error(idx, InvoiceErrorReason.SKU_NOT_FOUND) tx_hash_str = req.get_tx_hash().hex() if resp.rejected: logging.warning("transaction rejected: {} ({} payments)".format( tx_hash_str, len(req.payments))) return logging.debug("transaction approved: {} ({} payments)".format( tx_hash_str, len(req.payments))) # Note: This allows Agora to forward the transaction to the blockchain. However, it does not indicate that it will # be submitted successfully, or that the transaction will be successful. For example, the sender may have # insufficient funds. # # Backends may keep track of the transaction themselves using SignTransactionRequest.get_tx_hash() and rely on # either the Events webhook or polling to get the transaction status. resp.sign(webhook_private_key) return
def test_to_json(self): env = _generate_envelope() # not rejected resp = SignTransactionResponse(env) assert resp.to_json() == {"envelope_xdr": env.xdr().decode()} # rejected resp.reject() assert resp.to_json() == {} # rejected with invoice errors resp.mark_invoice_error(0, InvoiceErrorReason.ALREADY_PAID) assert resp.to_json() == { "invoice_errors": [{ "operation_index": 0, "reason": InvoiceErrorReason.ALREADY_PAID.to_lowercase() }] }
def test_sign(self): tx, _ = _generate_tx(False) resp = SignTransactionResponse(tx) resp.sign(_SIGNING_KEY) _SIGNING_KEY.public_key.verify(resp.transaction.message.marshal(), resp.transaction.signatures[0])
def test_reject(self): resp = SignTransactionResponse(_generate_envelope()) assert not resp.rejected resp.reject() assert resp.rejected
def _sign_tx_return_rejected(req: SignTransactionRequest, resp: SignTransactionResponse): resp.mark_invoice_error(0, InvoiceErrorReason.UNKNOWN)
def _sign_tx_success(req: SignTransactionRequest, resp: SignTransactionResponse): resp.sign(_TEST_PRIVATE_KEY)