Skip to content

Commit 8c04970

Browse files
committed
chore: improve idempotent-proxy-canister caller states
1 parent 3b64893 commit 8c04970

File tree

7 files changed

+104
-40
lines changed

7 files changed

+104
-40
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ strip = true
1515
opt-level = 's'
1616

1717
[workspace.package]
18-
version = "1.1.6"
18+
version = "1.2.0"
1919
edition = "2021"
2020
repository = "https://github.com/ldclabs/idempotent-proxy"
2121
keywords = ["idempotent", "reverse", "proxy", "icp"]

src/idempotent-proxy-canister/idempotent-proxy-canister.did

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,18 @@ type InitArgs = record {
3030
};
3131
type Result = variant { Ok : bool; Err : text };
3232
type Result_1 = variant { Ok; Err : text };
33-
type Result_2 = variant { Ok : StateInfo; Err : text };
3433
type StateInfo = record {
35-
freezing_threshold : nat64;
34+
proxy_token_public_key : text;
35+
service_fee : nat64;
3636
ecdsa_key_name : text;
3737
managers : vec principal;
38-
name : text;
39-
auditors : vec principal;
40-
schnorr_key_name : text;
38+
cose : opt CoseClient;
39+
uncollectible_cycles : nat;
40+
callers : nat64;
41+
agents : vec Agent;
42+
incoming_cycles : nat;
43+
proxy_token_refresh_interval : nat64;
4144
subnet_size : nat64;
42-
namespace_total : nat64;
43-
vetkd_key_name : text;
4445
};
4546
type TransformArgs = record { context : blob; response : HttpResponse };
4647
type TransformContext = record {
@@ -60,13 +61,13 @@ service : (opt ChainArgs) -> {
6061
admin_remove_callers : (vec principal) -> (Result_1);
6162
admin_remove_managers : (vec principal) -> (Result_1);
6263
admin_set_agents : (vec Agent) -> (Result_1);
63-
get_state : () -> (Result_2) query;
64-
is_caller : (principal) -> (Result) query;
64+
caller_info : (principal) -> (opt record { nat; nat64 }) query;
6565
parallel_call_all_ok : (CanisterHttpRequestArgument) -> (HttpResponse);
6666
parallel_call_any_ok : (CanisterHttpRequestArgument) -> (HttpResponse);
6767
parallel_call_cost : (CanisterHttpRequestArgument) -> (nat) query;
6868
proxy_http_request : (CanisterHttpRequestArgument) -> (HttpResponse);
6969
proxy_http_request_cost : (CanisterHttpRequestArgument) -> (nat) query;
70+
state_info : () -> (StateInfo) query;
7071
validate_admin_add_managers : (vec principal) -> (Result_1);
7172
validate_admin_remove_managers : (vec principal) -> (Result_1);
7273
validate_admin_set_agents : (vec Agent) -> (Result_1);

src/idempotent-proxy-canister/src/api.rs

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ use std::collections::BTreeSet;
77

88
use crate::{agent::Agent, cose::CoseClient, store};
99

10+
const MILLISECONDS: u64 = 1_000_000;
11+
1012
#[derive(CandidType, Deserialize, Serialize)]
1113
pub struct StateInfo {
1214
pub ecdsa_key_name: String,
1315
pub proxy_token_public_key: String,
1416
pub proxy_token_refresh_interval: u64, // seconds
1517
pub agents: Vec<Agent>,
1618
pub managers: BTreeSet<Principal>,
19+
pub callers: u64,
1720
pub subnet_size: u64,
1821
pub service_fee: u64, // in cycles
1922
pub incoming_cycles: u128,
@@ -22,8 +25,8 @@ pub struct StateInfo {
2225
}
2326

2427
#[ic_cdk::query]
25-
fn get_state() -> Result<StateInfo, String> {
26-
let s = store::state::with(|s| StateInfo {
28+
fn state_info() -> StateInfo {
29+
store::state::with(|s| StateInfo {
2730
ecdsa_key_name: s.ecdsa_key_name.clone(),
2831
proxy_token_public_key: s.proxy_token_public_key.clone(),
2932
proxy_token_refresh_interval: s.proxy_token_refresh_interval,
@@ -38,18 +41,18 @@ fn get_state() -> Result<StateInfo, String> {
3841
})
3942
.collect(),
4043
managers: s.managers.clone(),
44+
callers: s.callers.len() as u64,
4145
subnet_size: s.subnet_size,
4246
service_fee: s.service_fee,
4347
incoming_cycles: s.incoming_cycles,
4448
uncollectible_cycles: s.uncollectible_cycles,
4549
cose: s.cose.clone(),
46-
});
47-
Ok(s)
50+
})
4851
}
4952

5053
#[ic_cdk::query]
51-
fn is_caller(id: Principal) -> Result<bool, String> {
52-
store::state::with(|s| Ok(s.allowed_callers.contains(&id)))
54+
fn caller_info(id: Principal) -> Option<(u128, u64)> {
55+
store::state::with(|s| s.callers.get(&id).copied())
5356
}
5457

5558
#[ic_cdk::query]
@@ -75,7 +78,8 @@ async fn parallel_call_cost(req: CanisterHttpRequestArgument) -> u128 {
7578
/// Proxy HTTP request by all agents in sequence until one returns an status <= 500 result.
7679
#[ic_cdk::update]
7780
async fn proxy_http_request(req: CanisterHttpRequestArgument) -> HttpResponse {
78-
if !store::state::is_allowed(&ic_cdk::caller()) {
81+
let caller = ic_cdk::caller();
82+
if !store::state::is_allowed(&caller) {
7983
return HttpResponse {
8084
status: Nat::from(403u64),
8185
body: "caller is not allowed".as_bytes().to_vec(),
@@ -92,6 +96,7 @@ async fn proxy_http_request(req: CanisterHttpRequestArgument) -> HttpResponse {
9296
};
9397
}
9498

99+
let balance = ic_cdk::api::call::msg_cycles_available128();
95100
let calc = store::state::cycles_calculator();
96101
store::state::receive_cycles(
97102
calc.ingress_cost(ic_cdk::api::call::arg_data_raw_size()),
@@ -106,20 +111,31 @@ async fn proxy_http_request(req: CanisterHttpRequestArgument) -> HttpResponse {
106111
Ok(res) => {
107112
let cycles = calc.http_outcall_response_cost(calc.count_response_bytes(&res), 1);
108113
store::state::receive_cycles(cycles, true);
114+
store::state::update_caller_state(
115+
&caller,
116+
balance - ic_cdk::api::call::msg_cycles_available128(),
117+
ic_cdk::api::time() / MILLISECONDS,
118+
);
109119
return res;
110120
}
111121
Err(res) => last_err = Some(res),
112122
}
113123
}
114124

125+
store::state::update_caller_state(
126+
&caller,
127+
balance - ic_cdk::api::call::msg_cycles_available128(),
128+
ic_cdk::api::time() / MILLISECONDS,
129+
);
115130
last_err.unwrap()
116131
}
117132

118133
/// Proxy HTTP request by all agents in parallel and return the result if all are the same,
119134
/// or a 500 HttpResponse with all result.
120135
#[ic_cdk::update]
121136
async fn parallel_call_all_ok(req: CanisterHttpRequestArgument) -> HttpResponse {
122-
if !store::state::is_allowed(&ic_cdk::caller()) {
137+
let caller = ic_cdk::caller();
138+
if !store::state::is_allowed(&caller) {
123139
return HttpResponse {
124140
status: Nat::from(403u64),
125141
body: "caller is not allowed".as_bytes().to_vec(),
@@ -136,14 +152,15 @@ async fn parallel_call_all_ok(req: CanisterHttpRequestArgument) -> HttpResponse
136152
};
137153
}
138154

155+
let balance = ic_cdk::api::call::msg_cycles_available128();
139156
let calc = store::state::cycles_calculator();
140157
let cycles = calc.ingress_cost(ic_cdk::api::call::arg_data_raw_size())
141158
+ calc.http_outcall_request_cost(calc.count_request_bytes(&req), agents.len());
142159
store::state::receive_cycles(cycles, false);
143160

144161
let results =
145162
futures::future::try_join_all(agents.iter().map(|agent| agent.call(req.clone()))).await;
146-
match results {
163+
let result = match results {
147164
Err(res) => res,
148165
Ok(res) => {
149166
let mut results = res.into_iter();
@@ -164,22 +181,30 @@ async fn parallel_call_all_ok(req: CanisterHttpRequestArgument) -> HttpResponse
164181
let mut buf = vec![];
165182
into_writer(&inconsistent_results, &mut buf)
166183
.expect("failed to encode inconsistent results");
167-
return HttpResponse {
184+
HttpResponse {
168185
status: Nat::from(500u64),
169186
body: buf,
170187
headers: vec![],
171-
};
188+
}
189+
} else {
190+
base_result
172191
}
173-
174-
base_result
175192
}
176-
}
193+
};
194+
195+
store::state::update_caller_state(
196+
&caller,
197+
balance - ic_cdk::api::call::msg_cycles_available128(),
198+
ic_cdk::api::time() / MILLISECONDS,
199+
);
200+
result
177201
}
178202

179203
/// Proxy HTTP request by all agents in parallel and return the first (status <= 500) result.
180204
#[ic_cdk::update]
181205
async fn parallel_call_any_ok(req: CanisterHttpRequestArgument) -> HttpResponse {
182-
if !store::state::is_allowed(&ic_cdk::caller()) {
206+
let caller = ic_cdk::caller();
207+
if !store::state::is_allowed(&caller) {
183208
return HttpResponse {
184209
status: Nat::from(403u64),
185210
body: "caller is not allowed".as_bytes().to_vec(),
@@ -196,6 +221,7 @@ async fn parallel_call_any_ok(req: CanisterHttpRequestArgument) -> HttpResponse
196221
};
197222
}
198223

224+
let balance = ic_cdk::api::call::msg_cycles_available128();
199225
let calc = store::state::cycles_calculator();
200226
let cycles = calc.ingress_cost(ic_cdk::api::call::arg_data_raw_size())
201227
+ calc.http_outcall_request_cost(calc.count_request_bytes(&req), agents.len());
@@ -204,13 +230,20 @@ async fn parallel_call_any_ok(req: CanisterHttpRequestArgument) -> HttpResponse
204230
let result =
205231
futures::future::select_ok(agents.iter().map(|agent| agent.call(req.clone()).boxed()))
206232
.await;
207-
match result {
233+
let result = match result {
208234
Ok((res, _)) => {
209235
let cycles =
210236
calc.http_outcall_response_cost(calc.count_response_bytes(&res), agents.len());
211237
store::state::receive_cycles(cycles, true);
212238
res
213239
}
214240
Err(res) => res,
215-
}
241+
};
242+
243+
store::state::update_caller_state(
244+
&caller,
245+
balance - ic_cdk::api::call::msg_cycles_available128(),
246+
ic_cdk::api::time() / MILLISECONDS,
247+
);
248+
result
216249
}

src/idempotent-proxy-canister/src/api_admin.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,20 @@ fn admin_add_caller(id: Principal) -> Result<bool, String> {
2828
Err("anonymous caller cannot be added".to_string())?;
2929
}
3030

31-
store::state::with_mut(|r| Ok(r.allowed_callers.insert(id)))
31+
store::state::with_mut(|r| {
32+
let has = r.callers.contains_key(&id);
33+
r.callers.entry(id).or_insert((0, 0));
34+
Ok(!has)
35+
})
3236
}
3337

3438
#[ic_cdk::update(guard = "is_controller_or_manager")]
35-
fn admin_add_callers(mut args: BTreeSet<Principal>) -> Result<(), String> {
39+
fn admin_add_callers(args: BTreeSet<Principal>) -> Result<(), String> {
3640
validate_principals(&args)?;
3741
store::state::with_mut(|r| {
38-
r.allowed_callers.append(&mut args);
42+
args.into_iter().for_each(|p| {
43+
r.callers.entry(p).or_insert((0, 0));
44+
});
3945
Ok(())
4046
})
4147
}
@@ -44,7 +50,9 @@ fn admin_add_callers(mut args: BTreeSet<Principal>) -> Result<(), String> {
4450
fn admin_remove_callers(args: BTreeSet<Principal>) -> Result<(), String> {
4551
validate_principals(&args)?;
4652
store::state::with_mut(|r| {
47-
r.allowed_callers.retain(|p| !args.contains(p));
53+
args.iter().for_each(|p| {
54+
r.callers.remove(p);
55+
});
4856
Ok(())
4957
})
5058
}

src/idempotent-proxy-canister/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use candid::Principal;
22
use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse};
3-
use ic_cose_types::types::state::StateInfo;
43
use std::collections::BTreeSet;
54

65
mod agent;
@@ -13,7 +12,8 @@ mod init;
1312
mod store;
1413
mod tasks;
1514

16-
use crate::init::ChainArgs;
15+
use api::StateInfo;
16+
use init::ChainArgs;
1717

1818
fn is_controller() -> Result<(), String> {
1919
let caller = ic_cdk::caller();

src/idempotent-proxy-canister/src/store.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ use ic_stable_structures::{
99
};
1010
use serde::{Deserialize, Serialize};
1111
use serde_bytes::ByteBuf;
12-
use std::{borrow::Cow, cell::RefCell, collections::BTreeSet};
12+
use std::{
13+
borrow::Cow,
14+
cell::RefCell,
15+
collections::{BTreeMap, BTreeSet},
16+
};
1317

1418
use crate::{
1519
agent::Agent,
@@ -27,7 +31,9 @@ pub struct State {
2731
pub proxy_token_refresh_interval: u64, // seconds
2832
pub agents: Vec<Agent>,
2933
pub managers: BTreeSet<Principal>,
30-
pub allowed_callers: BTreeSet<Principal>,
34+
pub allowed_callers: BTreeSet<Principal>, //deprecated
35+
#[serde(default)]
36+
pub callers: BTreeMap<Principal, (u128, u64)>,
3137
#[serde(default)]
3238
pub subnet_size: u64,
3339
#[serde(default)]
@@ -154,7 +160,16 @@ pub mod state {
154160
}
155161

156162
pub fn is_allowed(caller: &Principal) -> bool {
157-
STATE.with(|r| r.borrow().allowed_callers.contains(caller))
163+
STATE.with(|r| r.borrow().callers.contains_key(caller))
164+
}
165+
166+
pub fn update_caller_state(caller: &Principal, cycles: u128, now_ms: u64) {
167+
STATE.with(|r| {
168+
r.borrow_mut().callers.get_mut(caller).map(|v| {
169+
v.0 = v.0.saturating_add(cycles);
170+
v.1 = now_ms;
171+
})
172+
});
158173
}
159174

160175
pub fn with<R>(f: impl FnOnce(&State) -> R) -> R {
@@ -185,7 +200,14 @@ pub mod state {
185200

186201
pub fn load() {
187202
STATE_STORE.with(|r| {
188-
let s = r.borrow_mut().get().clone();
203+
let mut s = r.borrow().get().clone();
204+
if !s.allowed_callers.is_empty() {
205+
s.allowed_callers.iter().for_each(|p| {
206+
s.callers.entry(*p).or_insert((0, 0));
207+
});
208+
s.allowed_callers.clear();
209+
}
210+
189211
STATE.with(|h| {
190212
*h.borrow_mut() = s;
191213
});

0 commit comments

Comments
 (0)