Skip to content

POST /v5/channel/:id/pay #477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Mar 24, 2022
Merged
9 changes: 5 additions & 4 deletions adview-manager/serve/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ use wiremock::{
Mock, MockServer, ResponseTemplate,
};


use tera::{Tera, Context};
use tera::{Context, Tera};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

let serve_dir = match std::env::current_dir().unwrap() {
serve_path if serve_path.ends_with("serve") => serve_path,
adview_manager_path if adview_manager_path.ends_with("adview-manager") => adview_manager_path.join("serve"),
adview_manager_path if adview_manager_path.ends_with("adview-manager") => {
adview_manager_path.join("serve")
}
// running from the Validator stack workspace
workspace_path => workspace_path.join("adview-manager/serve")
workspace_path => workspace_path.join("adview-manager/serve"),
};

let templates_glob = format!("{}/templates/**/*.html", serve_dir.display());
Expand Down
7 changes: 6 additions & 1 deletion primitives/src/sentry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
balances::BalancesState,
spender::Spender,
validator::{ApproveState, Heartbeat, MessageTypes, NewState, Type as MessageType},
Address, Balances, CampaignId, UnifiedNum, ValidatorId, IPFS,
Address, Balances, CampaignId, UnifiedMap, UnifiedNum, ValidatorId, IPFS,
};
use chrono::{
serde::ts_milliseconds, Date, DateTime, Datelike, Duration, NaiveDate, TimeZone, Timelike, Utc,
Expand Down Expand Up @@ -590,6 +590,11 @@ pub struct AllSpendersQuery {
pub page: u64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ChannelPayRequest {
pub payouts: UnifiedMap,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ValidatorMessage {
pub from: ValidatorId,
Expand Down
2 changes: 1 addition & 1 deletion sentry/src/db/channel.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use primitives::{Channel, ChannelId, ValidatorId};
use primitives::{Channel, ChannelId};

pub use list_channels::list_channels;

Expand Down
26 changes: 20 additions & 6 deletions sentry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use {
campaign,
campaign::{campaign_list, create_campaign, update_campaign},
channel::{
add_spender_leaf, channel_list, get_accounting_for_channel, get_all_spender_limits,
get_spender_limits, last_approved,
add_spender_leaf, channel_list, channel_payout, get_accounting_for_channel,
get_all_spender_limits, get_spender_limits, last_approved,
validator_message::{
create_validator_messages, extract_params, list_validator_messages,
},
Expand Down Expand Up @@ -77,6 +77,9 @@ static CHANNEL_ACCOUNTING: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^/v5/channel/0x([a-zA-Z0-9]{64})/accounting/?$")
.expect("The regex should be valid")
});
static CHANNEL_PAY: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^/v5/channel/0x([a-zA-Z0-9]{64})/pay/?$").expect("The regex should be valid")
});

/// Regex extracted parameters.
/// This struct is created manually on each of the matched routes.
Expand Down Expand Up @@ -210,10 +213,6 @@ async fn campaigns_router<C: Locked + 'static>(
}

// TODO AIP#61: Add routes for:
// - POST /channel/:id/pay
// #[serde(rename_all = "camelCase")]
// Pay { payout: BalancesMap },
//
// - GET /channel/:id/get-leaf
async fn channels_router<C: Locked + 'static>(
mut req: Request<Body>,
Expand Down Expand Up @@ -345,6 +344,21 @@ async fn channels_router<C: Locked + 'static>(
.await?;

get_accounting_for_channel(req, app).await
}
// POST /v5/channel/:id/pay
else if let (Some(caps), &Method::POST) = (CHANNEL_PAY.captures(&path), method) {
let param = RouteParams(vec![caps
.get(1)
.map_or("".to_string(), |m| m.as_str().to_string())]);
req.extensions_mut().insert(param);

req = Chain::new()
.chain(AuthRequired)
.chain(ChannelLoad)
.apply(req, app)
.await?;

channel_payout(req, app).await
} else {
Err(ResponseError::NotFound)
}
Expand Down
14 changes: 10 additions & 4 deletions sentry/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,19 @@
//!
//! Response: [`LastApprovedResponse`][primitives::sentry::LastApprovedResponse]
//!
//! - `POST /v5/channel/:id/pay` (auth required)
//! - [`POST /v5/channel/:id/pay`](channel::channel_payout) (auth required)
//!
//! TODO: implement and document as part of issue #382
//! Channel Payout with authentication of the spender.
//!
//! This route handles withdrawals of advertiser funds for the authenticated spender.
//! It needs to ensure all campaigns are closed. It accepts a JSON body in the request which contains
//! all of the earners and updates their balances accordingly. Used when an advertiser/spender wants
//! to get their remaining funds back.
//!
//! Request JSON body: [`ChannelPayRequest`](primitives::sentry::ChannelPayRequest)
//!
//! Channel Payout with authentication of the spender
//! Response: [`SuccessResponse`](primitives::sentry::SuccessResponse)
//!
//! Withdrawals of advertiser funds - re-introduces the PAY event with a separate route.
//!
//! - `GET /v5/channel/:id/get-leaf`
//!
Expand Down
35 changes: 24 additions & 11 deletions sentry/src/routes/campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
};
use adapter::{prelude::*, Adapter, Error as AdaptorError};
use deadpool_postgres::PoolError;
use futures::future::try_join_all;
use futures::{future::try_join_all, TryFutureExt};
use hyper::{Body, Request, Response};
use primitives::{
campaign_validator::Validator,
Expand Down Expand Up @@ -115,17 +115,24 @@ pub async fn fetch_campaign_ids_for_channel(
if total_pages < 2 {
Ok(campaign_ids)
} else {
let other_pages: Vec<Vec<CampaignId>> = try_join_all((1..total_pages).map(|i| {
get_campaign_ids_by_channel(
pool,
&channel_id,
limit.into(),
i.checked_mul(limit.into()).expect("TODO"),
)
let pages_skip: Vec<u64> = (1..total_pages)
.map(|i| {
i.checked_mul(limit.into()).ok_or_else(|| {
ResponseError::FailedValidation(
"Calculating skip while fetching campaign ids results in an overflow"
.to_string(),
)
})
})
.collect::<Result<_, _>>()?;

let other_pages = try_join_all(pages_skip.into_iter().map(|skip| {
get_campaign_ids_by_channel(pool, &channel_id, limit.into(), skip)
.map_err(|e| ResponseError::BadRequest(e.to_string()))
}))
.await?;

let all_campaigns: Vec<CampaignId> = std::iter::once(campaign_ids)
let all_campaigns = std::iter::once(campaign_ids)
.chain(other_pages.into_iter())
.flat_map(|campaign_ids| campaign_ids.into_iter())
.collect();
Expand Down Expand Up @@ -1279,13 +1286,19 @@ mod test {
let new_budget = UnifiedNum::from_u64(300 * multiplier);
let delta_budget = get_delta_budget(&campaign_remaining, &campaign, new_budget).await;

assert!(matches!(&delta_budget, Err(Error::NewBudget(_))), "Got result: {delta_budget:?}");
assert!(
matches!(&delta_budget, Err(Error::NewBudget(_))),
"Got result: {delta_budget:?}"
);

// campaign_spent == new_budget
let new_budget = UnifiedNum::from_u64(400 * multiplier);
let delta_budget = get_delta_budget(&campaign_remaining, &campaign, new_budget).await;

assert!(matches!(&delta_budget, Err(Error::NewBudget(_))), "Got result: {delta_budget:?}");
assert!(
matches!(&delta_budget, Err(Error::NewBudget(_))),
"Got result: {delta_budget:?}"
);
}
// Increasing budget
{
Expand Down
Loading