Skip to content

Commit f966cd3

Browse files
authored
Merge pull request #424 from AdExNetwork/issue-392-get-all-spenders-info
Issue 392 get all spenders info
2 parents b9bcbb8 + d4ea993 commit f966cd3

File tree

4 files changed

+140
-21
lines changed

4 files changed

+140
-21
lines changed

primitives/src/sentry.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub mod message {
4242

4343
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
4444
#[serde(try_from = "MessageTypes", into = "MessageTypes")]
45-
pub struct Message<T: Type>(T);
45+
pub struct Message<T: Type>(pub T);
4646

4747
impl<T: Type> Message<T> {
4848
pub fn new(message: T) -> Self {
@@ -221,6 +221,14 @@ pub struct SpenderResponse {
221221
pub spender: Spender,
222222
}
223223

224+
#[derive(Serialize, Deserialize, Debug)]
225+
#[serde(rename_all = "camelCase")]
226+
pub struct AllSpendersResponse {
227+
pub spenders: HashMap<Address, Spender>,
228+
#[serde(flatten)]
229+
pub pagination: Pagination,
230+
}
231+
224232
#[derive(Serialize, Deserialize, Debug)]
225233
pub struct ValidatorMessage {
226234
pub from: ValidatorId,

sentry/src/db/spendable.rs

+16
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ pub async fn fetch_spendable(
4444
Ok(row.map(Spendable::from))
4545
}
4646

47+
static GET_ALL_SPENDERS_STATEMENT: &str = "SELECT spender, channel_id, channel, total, still_on_create2 FROM spendable WHERE channel_id = $1";
48+
49+
// TODO: Include pagination
50+
pub async fn get_all_spendables_for_channel(
51+
pool: DbPool,
52+
channel_id: &ChannelId,
53+
) -> Result<Vec<Spendable>, PoolError> {
54+
let client = pool.get().await?;
55+
let statement = client.prepare(GET_ALL_SPENDERS_STATEMENT).await?;
56+
57+
let rows = client.query(&statement, &[channel_id]).await?;
58+
let spendables: Vec<Spendable> = rows.into_iter().map(Spendable::from).collect();
59+
60+
Ok(spendables)
61+
}
62+
4763
static UPDATE_SPENDABLE_STATEMENT: &str = "INSERT INTO spendable(spender, channel_id, channel, total, still_on_create2) VALUES($1, $2, $3, $4, $5) ON CONFLICT ON CONSTRAINT spendable_pkey DO UPDATE SET total = $4, still_on_create2 = $5 WHERE spendable.spender = $1 AND spendable.channel_id = $2 RETURNING spender, channel_id, channel, total, still_on_create2";
4864

4965
// Updates spendable entry deposit or inserts a new spendable entry if it doesn't exist

sentry/src/lib.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ use routes::analytics::{advanced_analytics, advertiser_analytics, analytics, pub
2626
use routes::campaign::{create_campaign, update_campaign};
2727
use routes::cfg::config;
2828
use routes::channel::{
29-
channel_list, channel_validate, create_channel, create_validator_messages, get_spender_limits,
30-
last_approved,
29+
channel_list, channel_validate, create_channel, create_validator_messages,
30+
get_all_spender_limits, get_spender_limits, last_approved,
3131
};
3232
use slog::Logger;
3333
use std::collections::HashMap;
@@ -76,6 +76,10 @@ static CLOSE_CAMPAIGN_BY_CAMPAIGN_ID: Lazy<Regex> = Lazy::new(|| {
7676
static CAMPAIGN_UPDATE_BY_ID: Lazy<Regex> = Lazy::new(|| {
7777
Regex::new(r"^/v5/campaign/0x([a-zA-Z0-9]{32})/?$").expect("The regex should be valid")
7878
});
79+
static CHANNEL_ALL_SPENDER_LIMITS: Lazy<Regex> = Lazy::new(|| {
80+
Regex::new(r"^/v5/channel/0x([a-zA-Z0-9]{64})/spender/all/?$")
81+
.expect("The regex should be valid")
82+
});
7983

8084
#[derive(Debug, Clone)]
8185
pub struct RouteParams(pub Vec<String>);
@@ -393,6 +397,20 @@ async fn channels_router<A: Adapter + 'static>(
393397
.await?;
394398

395399
get_spender_limits(req, app).await
400+
} else if let (Some(caps), &Method::GET) = (CHANNEL_ALL_SPENDER_LIMITS.captures(&path), method)
401+
{
402+
let param = RouteParams(vec![caps
403+
.get(1)
404+
.map_or("".to_string(), |m| m.as_str().to_string())]);
405+
req.extensions_mut().insert(param);
406+
407+
req = Chain::new()
408+
.chain(AuthRequired)
409+
.chain(ChannelLoad)
410+
.apply(req, app)
411+
.await?;
412+
413+
get_all_spender_limits(req, app).await
396414
} else {
397415
Err(ResponseError::NotFound)
398416
}

sentry/src/routes/channel.rs

+95-18
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::db::{
44
latest_new_state_v5,
55
},
66
get_channel_by_id, insert_channel, insert_validator_messages, list_channels,
7-
spendable::{fetch_spendable, update_spendable},
7+
spendable::{fetch_spendable, get_all_spendables_for_channel, update_spendable},
88
DbPool, PoolError,
99
};
1010
use crate::{success_response, Application, Auth, ResponseError, RouteParams};
@@ -13,18 +13,19 @@ use hex::FromHex;
1313
use hyper::{Body, Request, Response};
1414
use primitives::{
1515
adapter::Adapter,
16-
balances::UncheckedState,
16+
balances::{CheckedState, UncheckedState},
1717
channel_v5::Channel as ChannelV5,
1818
config::TokenInfo,
1919
sentry::{
2020
channel_list::{ChannelListQuery, LastApprovedQuery},
21-
LastApproved, LastApprovedResponse, SpenderResponse, SuccessResponse,
21+
AllSpendersResponse, LastApproved, LastApprovedResponse, Pagination, SpenderResponse,
22+
SuccessResponse,
2223
},
2324
spender::{Deposit, Spendable, Spender, SpenderLeaf},
24-
validator::MessageTypes,
25+
validator::{MessageTypes, NewState},
2526
Address, Channel, ChannelId, UnifiedNum,
2627
};
27-
use slog::error;
28+
use slog::{error, Logger};
2829
use std::{collections::HashMap, str::FromStr};
2930
use tokio_postgres::error::SqlState;
3031

@@ -287,10 +288,10 @@ pub async fn get_spender_limits<A: Adapter + 'static>(
287288
.expect("Request should have Channel")
288289
.to_owned();
289290

290-
let channel_id = channel.id();
291291
let spender = Address::from_str(&route_params.index(1))?;
292292

293-
let latest_spendable = fetch_spendable(app.pool.clone(), &spender, &channel_id).await?;
293+
let latest_spendable = fetch_spendable(app.pool.clone(), &spender, &channel.id()).await?;
294+
294295
let token_info = app
295296
.config
296297
.token_address_whitelist
@@ -311,21 +312,12 @@ pub async fn get_spender_limits<A: Adapter + 'static>(
311312
}
312313
};
313314

314-
let approve_state = match latest_approve_state_v5(&app.pool, &channel).await? {
315-
Some(approve_state) => approve_state,
316-
None => return spender_response_without_leaf(latest_spendable.deposit.total),
317-
};
318-
319-
let state_root = approve_state.msg.state_root.clone();
320-
321-
let new_state = match latest_new_state_v5(&app.pool, &channel, &state_root).await? {
315+
let new_state = match get_corresponding_new_state(&app.pool, &app.logger, &channel).await? {
322316
Some(new_state) => new_state,
323317
None => return spender_response_without_leaf(latest_spendable.deposit.total),
324318
};
325319

326-
let new_state_checked = new_state.msg.into_inner().try_checked()?;
327-
328-
let total_spent = new_state_checked.balances.spenders.get(&spender);
320+
let total_spent = new_state.balances.spenders.get(&spender);
329321

330322
let spender_leaf = total_spent.map(|total_spent| SpenderLeaf {
331323
total_spent: *total_spent,
@@ -342,6 +334,91 @@ pub async fn get_spender_limits<A: Adapter + 'static>(
342334
Ok(success_response(serde_json::to_string(&res)?))
343335
}
344336

337+
pub async fn get_all_spender_limits<A: Adapter + 'static>(
338+
req: Request<Body>,
339+
app: &Application<A>,
340+
) -> Result<Response<Body>, ResponseError> {
341+
let channel = req
342+
.extensions()
343+
.get::<ChannelV5>()
344+
.expect("Request should have Channel")
345+
.to_owned();
346+
347+
let new_state = get_corresponding_new_state(&app.pool, &app.logger, &channel).await?;
348+
349+
let mut all_spender_limits: HashMap<Address, Spender> = HashMap::new();
350+
351+
let all_spendables = get_all_spendables_for_channel(app.pool.clone(), &channel.id()).await?;
352+
353+
// Using for loop to avoid async closures
354+
for spendable in all_spendables {
355+
let spender = spendable.spender;
356+
let spender_leaf = match new_state {
357+
Some(ref new_state) => new_state.balances.spenders.get(&spender).map(|balance| {
358+
SpenderLeaf {
359+
total_spent: spendable
360+
.deposit
361+
.total
362+
.checked_sub(balance)
363+
.unwrap_or_default(),
364+
// merkle_proof: [u8; 32], // TODO
365+
}
366+
}),
367+
None => None,
368+
};
369+
370+
let spender_info = Spender {
371+
total_deposited: spendable.deposit.total,
372+
spender_leaf,
373+
};
374+
375+
all_spender_limits.insert(spender, spender_info);
376+
}
377+
378+
let res = AllSpendersResponse {
379+
spenders: all_spender_limits,
380+
pagination: Pagination {
381+
// TODO
382+
page: 1,
383+
total: 1,
384+
total_pages: 1,
385+
},
386+
};
387+
388+
Ok(success_response(serde_json::to_string(&res)?))
389+
}
390+
391+
async fn get_corresponding_new_state(
392+
pool: &DbPool,
393+
logger: &Logger,
394+
channel: &ChannelV5,
395+
) -> Result<Option<NewState<CheckedState>>, ResponseError> {
396+
let approve_state = match latest_approve_state_v5(pool, channel).await? {
397+
Some(approve_state) => approve_state,
398+
None => return Ok(None),
399+
};
400+
401+
let state_root = approve_state.msg.state_root.clone();
402+
403+
let new_state = match latest_new_state_v5(pool, channel, &state_root).await? {
404+
Some(new_state) => {
405+
let new_state = new_state.msg.into_inner().try_checked().map_err(|err| {
406+
error!(&logger, "Balances are not aligned in an approved NewState: {}", &err; "module" => "get_spender_limits");
407+
ResponseError::BadRequest("Balances are not aligned in an approved NewState".to_string())
408+
})?;
409+
Ok(Some(new_state))
410+
}
411+
None => {
412+
error!(&logger, "{}", "Fatal error! The NewState for the last ApproveState was not found"; "module" => "get_spender_limits");
413+
return Err(ResponseError::BadRequest(
414+
"Fatal error! The NewState for the last ApproveState was not found".to_string(),
415+
));
416+
}
417+
};
418+
419+
new_state
420+
}
421+
345422
#[cfg(test)]
346423
mod test {
347424
use super::*;

0 commit comments

Comments
 (0)