Skip to content

Commit 3c4d2b7

Browse files
authored
Merge pull request #541 from AmbireTech/sentry-validator-messages-routes-to-axum
Sentry validator messages routes to axum
2 parents 38c8954 + 66ff32c commit 3c4d2b7

File tree

5 files changed

+163
-13
lines changed

5 files changed

+163
-13
lines changed

docker-compose.harness.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ services:
1313
POSTGRES_USER: 'postgres'
1414
POSTGRES_PASSWORD: 'postgres'
1515
# harness_* databases are used by the `test_harness` crate for testing
16-
# `sentry_leader` is the default database used by `sentry` for running tests
16+
# `sentry_leader` is the default database used by `sentry` for running tests and the leader
17+
# `sentry_leader` is for running the local follower when maunally testing
1718
# `primitives` is used for running tests in the `primitives` crate
18-
POSTGRES_MULTIPLE_DATABASES: harness_leader,harness_follower,sentry_leader,primitives
19+
POSTGRES_MULTIPLE_DATABASES: harness_leader,harness_follower,sentry_leader,sentry_follower,primitives
1920
networks:
2021
- adex-external
2122

sentry/src/middleware/campaign.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ use axum::{
1010
};
1111
use hyper::{Body, Request};
1212
use primitives::{campaign::Campaign, CampaignId, ChainOf};
13+
use serde::Deserialize;
14+
15+
/// This struct is required because of routes that have more parameters
16+
/// apart from the `CampaignId`
17+
#[derive(Debug, Deserialize)]
18+
struct CampaignParam {
19+
pub id: CampaignId,
20+
}
1321

1422
#[derive(Debug)]
1523
pub struct CampaignLoad;
@@ -80,9 +88,10 @@ where
8088
let mut request_parts = RequestParts::new(request);
8189

8290
let campaign_id = request_parts
83-
.extract::<Path<CampaignId>>()
91+
.extract::<Path<CampaignParam>>()
8492
.await
85-
.map_err(|_| ResponseError::BadRequest("Bad Campaign Id".to_string()))?;
93+
.map_err(|_| ResponseError::BadRequest("Bad Campaign Id".to_string()))?
94+
.id;
8695

8796
let campaign = fetch_campaign(pool.clone(), &campaign_id)
8897
.await?

sentry/src/middleware/channel.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
use std::sync::Arc;
22

3-
use crate::{
4-
db::get_channel_by_id, middleware::Middleware, response::ResponseError,
5-
routes::routers::RouteParams, Application, Auth,
6-
};
73
use adapter::client::Locked;
84
use axum::{
95
extract::{Path, RequestParts},
@@ -15,6 +11,12 @@ use hyper::{Body, Request};
1511
use primitives::ChannelId;
1612

1713
use async_trait::async_trait;
14+
use serde::Deserialize;
15+
16+
use crate::{
17+
db::get_channel_by_id, middleware::Middleware, response::ResponseError,
18+
routes::routers::RouteParams, Application, Auth,
19+
};
1820

1921
#[derive(Debug)]
2022
pub struct ChannelLoad;
@@ -70,6 +72,13 @@ fn channel_load_old<C: Locked>(
7072
.boxed()
7173
}
7274

75+
/// This struct is required because of routes that have more parameters
76+
/// apart from the `ChannelId`
77+
#[derive(Debug, Deserialize)]
78+
struct ChannelParam {
79+
pub id: ChannelId,
80+
}
81+
7382
pub async fn channel_load<C: Locked + 'static, B>(
7483
request: axum::http::Request<B>,
7584
next: Next<B>,
@@ -86,12 +95,12 @@ where
8695
// running extractors requires a `RequestParts`
8796
let mut request_parts = RequestParts::new(request);
8897

89-
let channel_id = request_parts
90-
.extract::<Path<ChannelId>>()
98+
let channel_param = request_parts
99+
.extract::<Path<ChannelParam>>()
91100
.await
92101
.map_err(|_| ResponseError::BadRequest("Bad Channel Id".to_string()))?;
93102

94-
let channel = get_channel_by_id(&app.pool, &channel_id)
103+
let channel = get_channel_by_id(&app.pool, &channel_param.id)
95104
.await?
96105
.ok_or(ResponseError::NotFound)?;
97106

sentry/src/routes/channel.rs

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,13 @@ pub async fn channel_payout<C: Locked + 'static>(
926926
})?))
927927
}
928928

929+
/// POST `/v5/channel/dummy-deposit` request
930+
///
931+
/// Full details about the route's API and intend can be found in the [`routes`](crate::routes#post-v5channeldummy-deposit-auth-required) module
932+
///
933+
/// Request body (json): [`ChannelDummyDeposit`]
934+
///
935+
/// Response: [`SuccessResponse`]
929936
pub async fn channel_dummy_deposit_axum<C: Locked + 'static>(
930937
Extension(app): Extension<Arc<Application<C>>>,
931938
Extension(auth): Extension<Auth>,
@@ -1016,7 +1023,10 @@ pub async fn channel_dummy_deposit<C: Locked + 'static>(
10161023
/// starting with `/v5/channel/0xXXX.../validator-messages`
10171024
///
10181025
pub mod validator_message {
1026+
use std::sync::Arc;
1027+
10191028
use crate::{
1029+
application::Qs,
10201030
db::validator_message::{get_validator_messages, insert_validator_message},
10211031
Auth,
10221032
};
@@ -1025,15 +1035,17 @@ pub mod validator_message {
10251035
Application,
10261036
};
10271037
use adapter::client::Locked;
1038+
use axum::{extract::Path, Extension, Json};
10281039
use futures::future::try_join_all;
10291040
use hyper::{Body, Request, Response};
10301041
use primitives::{
10311042
sentry::{
10321043
SuccessResponse, ValidatorMessagesCreateRequest, ValidatorMessagesListQuery,
10331044
ValidatorMessagesListResponse,
10341045
},
1035-
ChainOf, Channel, DomainError, ValidatorId,
1046+
ChainOf, Channel, ChannelId, DomainError, ValidatorId,
10361047
};
1048+
use serde::Deserialize;
10371049

10381050
pub fn extract_params(
10391051
from_path: &str,
@@ -1065,6 +1077,108 @@ pub mod validator_message {
10651077
Ok((validator_id, message_types.unwrap_or_default()))
10661078
}
10671079

1080+
#[derive(Debug, Deserialize)]
1081+
pub struct MessagesListParams {
1082+
pub id: ChannelId,
1083+
// Optional filtering by ValidatorId
1084+
#[serde(default)]
1085+
pub address: Option<ValidatorId>,
1086+
/// List of validator message types separated by `+` (urlencoded):
1087+
/// e.g. `ApproveState+NewState`
1088+
#[serde(default)]
1089+
pub message_types: String,
1090+
}
1091+
1092+
/// GET `/v5/channel/0xXXX.../validator-messages`
1093+
///
1094+
/// Full details about the route's API and intend can be found in the [`routes`](crate::routes#get-v5channelidvalidator-messages) module
1095+
///
1096+
/// Request query parameters: [`ValidatorMessagesListQuery`]
1097+
///
1098+
/// Response: [`ValidatorMessagesListResponse`]
1099+
///
1100+
pub async fn list_validator_messages_axum<C: Locked + 'static>(
1101+
Extension(app): Extension<Arc<Application<C>>>,
1102+
Extension(channel_context): Extension<ChainOf<Channel>>,
1103+
Path(params): Path<MessagesListParams>,
1104+
Qs(query): Qs<ValidatorMessagesListQuery>,
1105+
) -> Result<Json<ValidatorMessagesListResponse>, ResponseError> {
1106+
let message_types = {
1107+
// We need to strip the `/` prefix from axum
1108+
let stripped = params
1109+
.message_types
1110+
.strip_prefix('/')
1111+
.unwrap_or(&params.message_types);
1112+
1113+
if !stripped.is_empty() {
1114+
stripped
1115+
.split('+')
1116+
.map(|s| s.to_string())
1117+
.collect::<Vec<_>>()
1118+
} else {
1119+
vec![]
1120+
}
1121+
};
1122+
1123+
let channel = channel_context.context;
1124+
1125+
let config_limit = app.config.msgs_find_limit as u64;
1126+
let limit = query
1127+
.limit
1128+
.filter(|n| *n >= 1)
1129+
.unwrap_or(config_limit)
1130+
.min(config_limit);
1131+
1132+
let validator_messages = get_validator_messages(
1133+
&app.pool,
1134+
&channel.id(),
1135+
&params.address,
1136+
&message_types,
1137+
limit,
1138+
)
1139+
.await?;
1140+
1141+
Ok(Json(ValidatorMessagesListResponse {
1142+
messages: validator_messages,
1143+
}))
1144+
}
1145+
1146+
/// POST `/v5/channel/0xXXX.../validator-messages`
1147+
///
1148+
/// Full details about the route's API and intend can be found in the [`routes`](crate::routes#post-v5channelidvalidator-messages-auth-required) module
1149+
///
1150+
/// Request body (json): [`ValidatorMessagesCreateRequest`]
1151+
///
1152+
/// Response: [`SuccessResponse`]
1153+
///
1154+
/// # Examples
1155+
///
1156+
/// Request:
1157+
///
1158+
/// ```
1159+
#[doc = include_str!("../../../primitives/examples/validator_messages_create_request.rs")]
1160+
/// ```
1161+
pub async fn create_validator_messages_axum<C: Locked + 'static>(
1162+
Extension(app): Extension<Arc<Application<C>>>,
1163+
Extension(auth): Extension<Auth>,
1164+
Extension(channel_context): Extension<ChainOf<Channel>>,
1165+
Json(create_request): Json<ValidatorMessagesCreateRequest>,
1166+
) -> Result<Json<SuccessResponse>, ResponseError> {
1167+
let channel = channel_context.context;
1168+
1169+
match channel.find_validator(auth.uid) {
1170+
None => Err(ResponseError::Unauthorized),
1171+
_ => {
1172+
try_join_all(create_request.messages.iter().map(|message| {
1173+
insert_validator_message(&app.pool, &channel, &auth.uid, message)
1174+
}))
1175+
.await?;
1176+
1177+
Ok(Json(SuccessResponse { success: true }))
1178+
}
1179+
}
1180+
}
1181+
10681182
/// GET `/v5/channel/0xXXX.../validator-messages`
10691183
///
10701184
/// Full details about the route's API and intend can be found in the [`routes`](crate::routes#get-v5channelidvalidator-messages) module

sentry/src/routes/routers.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ use tower::ServiceBuilder;
5858

5959
use super::{
6060
analytics::{analytics_axum, GET_ANALYTICS_ALLOWED_KEYS},
61-
channel::{channel_dummy_deposit_axum, channel_list_axum, channel_payout_axum},
61+
channel::{
62+
channel_dummy_deposit_axum, channel_list_axum, channel_payout_axum,
63+
validator_message::{create_validator_messages_axum, list_validator_messages_axum},
64+
},
6265
units_for_slot::post_units_for_slot,
6366
};
6467

@@ -162,6 +165,20 @@ pub fn channels_router_axum<C: Locked + 'static>() -> Router {
162165
.route("/accounting", get(get_accounting_for_channel_axum::<C>))
163166
.route("/last-approved", get(last_approved_axum::<C>))
164167
.nest("/spender", spender_routes)
168+
.route(
169+
"/validator-messages",
170+
post(create_validator_messages_axum::<C>)
171+
.route_layer(middleware::from_fn(authentication_required::<C, _>)),
172+
)
173+
.route(
174+
"/validator-messages",
175+
get(list_validator_messages_axum::<C>),
176+
)
177+
// We allow Message Type filtering only when filtering by a ValidatorId
178+
.route(
179+
"/validator-messages/:address/*message_types",
180+
get(list_validator_messages_axum::<C>),
181+
)
165182
.layer(
166183
// keeps the order from top to bottom!
167184
ServiceBuilder::new()

0 commit comments

Comments
 (0)