mirror of
https://github.com/Blockstream/satellite-api.git
synced 2025-03-12 18:45:59 +01:00
The same server now can handle multiple logical channels, on which the transmitter logic runs independently. That is, while previously a single message would be in transmitting state at a time, now multiple messages can be in transmitting state as long as they belong to distinct logical channels. The supported channels each have different permissions. The user channel is where users can post, get, and delete messages as needed. In contrast, the other channels do not grant all permissions to users. Some are read-only (users can get but not post) and there is a channel (the auth channel) on which users have no permissions (neither get nor post). For the channels on which users do not have all permissions (get, post, and delete), this patch adds admin-specific routes, which are prefixed by /admin/. The /admin/ route is protected via SSL in production and allows the admin host to send GET/POST/DELETE requests normally. Hence, for instance, the admin host can post a message on the auth channel (with POST /admin/order) and read it (with GET /admin/order) for transmission over satellite, whereas regulars cannot. With this scheme, the auth channel messages are accessible exclusively over satellite (and not over the internet). The admin routes were added to the following endpoints: - /order/<uuid> (GET and DELETE requests) - /order (POST request) - /orders/<state> (GET request) - /message/<tx_seq_num> (GET request) The messages posted by the admin are not paid, so this patch removes the requirement of invoice generation and payment. Only paid orders now generate an invoice. Thus, the POST request to the /order/ endpoint does not return an invoice for non-paid (admin-only) messages. Also, this patch updates the queue page to display the orders separately for each channel. The query string channel parameter determines which channel the page shows. Finally, this patch updates the events published into the Redis db on transmission. The event includes the corresponding logical channel so that SSE events can be subscribed independently for each channel.
88 lines
3.8 KiB
Python
88 lines
3.8 KiB
Python
from http import HTTPStatus
|
|
|
|
errors = {
|
|
'PARAM_COERCION':
|
|
(2, "type coercion error", "{} does not have the expected type",
|
|
HTTPStatus.INTERNAL_SERVER_ERROR),
|
|
'BID_TOO_SMALL': (102, "Bid too low",
|
|
"The minimum bid for this message is {} millisatoshis.",
|
|
HTTPStatus.BAD_REQUEST),
|
|
'ORDER_NOT_FOUND': (104, "Order not found", "UUID {} not found",
|
|
HTTPStatus.NOT_FOUND),
|
|
'INVALID_AUTH_TOKEN': (109, "Unauthorized", "Invalid authentication token",
|
|
HTTPStatus.UNAUTHORIZED),
|
|
'LIGHTNING_CHARGE_INVOICE_ERROR':
|
|
(110, "Invoice Creation Error", "Lightning Charge invoice creation error",
|
|
HTTPStatus.BAD_REQUEST),
|
|
'LIGHTNING_CHARGE_WEBHOOK_REGISTRATION_ERROR':
|
|
(111, "Invoice Creation Error",
|
|
"Lightning Charge webhook registration error", HTTPStatus.BAD_REQUEST),
|
|
'INVOICE_ID_NOT_FOUND_ERROR': (112, "Not found", "Invoice id {} not found",
|
|
HTTPStatus.NOT_FOUND),
|
|
'SEQUENCE_NUMBER_NOT_FOUND':
|
|
(114, "Sequence number not found",
|
|
"Sent order with sequence number {} not found", HTTPStatus.NOT_FOUND),
|
|
'MESSAGE_FILE_TOO_SMALL': (117, "Message too small",
|
|
"Minimum message size is {} byte",
|
|
HTTPStatus.BAD_REQUEST),
|
|
'MESSAGE_FILE_TOO_LARGE': (118, "Message too large",
|
|
"Message size exceeds max size of {} MB",
|
|
HTTPStatus.REQUEST_ENTITY_TOO_LARGE),
|
|
'ORDER_CANCELLATION_ERROR': (120, "Cannot cancel order",
|
|
"Order already {}", HTTPStatus.BAD_REQUEST),
|
|
'ORDER_BUMP_ERROR': (121, "Cannot bump order", "Order already {}",
|
|
HTTPStatus.BAD_REQUEST),
|
|
'ORPHANED_INVOICE': (122, "Payment problem", "Orphaned invoice",
|
|
HTTPStatus.NOT_FOUND),
|
|
'INVOICE_ALREADY_PAID': (123, "Payment problem", "Invoice already paid",
|
|
HTTPStatus.BAD_REQUEST),
|
|
'MESSAGE_MISSING':
|
|
(126, "Message upload problem",
|
|
"Either a message file or a message parameter is required",
|
|
HTTPStatus.BAD_REQUEST),
|
|
'LIGHTNING_CHARGE_INFO_FAILED':
|
|
(128, "Lightning Charge communication error",
|
|
"Failed to fetch information about the Lightning node",
|
|
HTTPStatus.INTERNAL_SERVER_ERROR),
|
|
'INVOICE_ALREADY_EXPIRED': (129, "Payment problem",
|
|
"Invoice already expired",
|
|
HTTPStatus.BAD_REQUEST),
|
|
'ORDER_CHANNEL_UNAUTHORIZED_OP': (130, "Unauthorized channel operation",
|
|
"Operation not supported on channel {}",
|
|
HTTPStatus.UNAUTHORIZED),
|
|
}
|
|
|
|
|
|
def _err_to_dict(key, *args):
|
|
"""Translate an error key to the full error response dictionary"""
|
|
assert (key in errors)
|
|
code = errors[key][0]
|
|
title = errors[key][1]
|
|
detail = errors[key][2].format(*args)
|
|
return {
|
|
'message': title,
|
|
'errors': [{
|
|
'title': title,
|
|
'detail': detail,
|
|
'code': code
|
|
}]
|
|
}
|
|
|
|
|
|
def get_http_error_resp(key, *args):
|
|
"""Return the HTTP error response
|
|
|
|
Returns: Pair with error response dictionary and the HTTP error code. The
|
|
former contains the satellite-specific error code and information.
|
|
|
|
"""
|
|
return _err_to_dict(key, *args), errors[key][3]
|
|
|
|
|
|
def assert_error(err_data, key):
|
|
"""Verify that the error response is as expected for the given error key"""
|
|
assert 'message' in err_data
|
|
assert 'errors' in err_data
|
|
# Check title and code (but not detail, which is set dynamically)
|
|
assert err_data['errors'][0]['title'] == errors[key][1]
|
|
assert err_data['errors'][0]['code'] == errors[key][0]
|