Skip to content

Commit 7c2c29f

Browse files
authored
Merge pull request #110 from SundaeSwap-finance/spdd-module
feat: Add SPDD and DRDD modules with optional historical storage
2 parents 33f8dc0 + aa1f495 commit 7c2c29f

File tree

26 files changed

+788
-185
lines changed

26 files changed

+788
-185
lines changed

Cargo.lock

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

common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ blake2 = "0.10"
1818
bs58 = "0.5"
1919
caryatid_sdk = "0.12"
2020
caryatid_module_clock = "0.12"
21-
caryatid_module_rest_server = "0.13"
21+
caryatid_module_rest_server = "0.14"
2222
chrono = "0.4"
2323
gcd = "2.3"
2424
fraction = "0.15"

common/src/rest_helper.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use anyhow::{anyhow, Result};
55
use caryatid_sdk::Context;
66
use futures::future::Future;
77
use num_traits::ToPrimitive;
8-
use std::sync::Arc;
8+
use std::{collections::HashMap, sync::Arc};
99
use tokio::task::JoinHandle;
1010
use tracing::{error, info};
1111

@@ -44,7 +44,7 @@ where
4444
}
4545

4646
/// Handle a REST request with path parameters
47-
pub fn handle_rest_with_parameter<F, Fut>(
47+
pub fn handle_rest_with_path_parameter<F, Fut>(
4848
context: Arc<Context<Message>>,
4949
topic: &str,
5050
handler: F,
@@ -83,6 +83,35 @@ where
8383
})
8484
}
8585

86+
// Handle a REST request with query parameters
87+
pub fn handle_rest_with_query_parameters<F, Fut>(
88+
context: Arc<Context<Message>>,
89+
topic: &str,
90+
handler: F,
91+
) -> JoinHandle<()>
92+
where
93+
F: Fn(HashMap<String, String>) -> Fut + Send + Sync + Clone + 'static,
94+
Fut: Future<Output = Result<RESTResponse>> + Send + 'static,
95+
{
96+
context.handle(topic, move |message: Arc<Message>| {
97+
let handler = handler.clone();
98+
async move {
99+
let response = match message.as_ref() {
100+
Message::RESTRequest(request) => {
101+
let params = request.query_parameters.clone();
102+
match handler(params).await {
103+
Ok(response) => response,
104+
Err(error) => RESTResponse::with_text(500, &format!("{error:?}")),
105+
}
106+
}
107+
_ => RESTResponse::with_text(500, "Unexpected message in REST request"),
108+
};
109+
110+
Arc::new(Message::RESTResponse(response))
111+
}
112+
})
113+
}
114+
86115
/// Extract parameters from the request path based on the topic pattern.
87116
/// Skips the first 3 parts of the topic as these are never parameters
88117
fn extract_params_from_topic_and_path(topic: &str, path_elements: &[String]) -> Vec<String> {
@@ -113,3 +142,37 @@ impl<T: ToPrimitive> ToCheckedF64 for T {
113142
self.to_f64().ok_or_else(|| anyhow!("Failed to convert {name} to f64"))
114143
}
115144
}
145+
146+
// Macros for extracting and validating REST query parameters
147+
#[macro_export]
148+
macro_rules! extract_strict_query_params {
149+
($params:expr, { $($key:literal => $var:ident : Option<$type:ty>,)* }) => {
150+
$(
151+
let mut $var: Option<$type> = None;
152+
)*
153+
154+
for (k, v) in &$params {
155+
match k.as_str() {
156+
$(
157+
$key => {
158+
$var = match v.parse::<$type>() {
159+
Ok(val) => Some(val),
160+
Err(_) => {
161+
return Ok($crate::messages::RESTResponse::with_text(
162+
400,
163+
concat!("Invalid ", $key, " query parameter: must be a valid type"),
164+
));
165+
}
166+
};
167+
}
168+
)*
169+
_ => {
170+
return Ok($crate::messages::RESTResponse::with_text(
171+
400,
172+
concat!("Unexpected query parameter: only allowed keys are: ", $( $key, " ", )*)
173+
));
174+
}
175+
}
176+
}
177+
};
178+
}

common/src/state_history.rs

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,112 @@
11
//! Generic state history
2-
//! Keeps per-block state to allow for rollbacks
2+
//! Keeps per-block state for rollbacks or per-epoch state for historical lookups
33
//! Use imbl collections in the state to avoid memory explosion!
44
55
use crate::params::SECURITY_PARAMETER_K;
6-
use crate::types::BlockInfo;
76
use std::collections::VecDeque;
87

98
use tracing::info;
109

11-
/// Entry in the history - S is the state to be stored
12-
struct HistoryEntry<S> {
13-
/// Block number this state is for
14-
block: u64,
10+
pub enum HistoryKind {
11+
BlockState, // Used for rollbacks, bounded at k
12+
EpochState, // Used for historical lookups, unbounded
13+
}
1514

16-
/// State to store
15+
struct HistoryEntry<S> {
16+
index: u64,
1717
state: S,
1818
}
1919

2020
/// Generic state history - S is the state to be stored
2121
pub struct StateHistory<S> {
22-
/// History, one per block
22+
/// History, one per block or epoch
2323
history: VecDeque<HistoryEntry<S>>,
2424

2525
/// Module name
2626
module: String,
27+
28+
// Block or Epoch based history
29+
kind: HistoryKind,
2730
}
2831

2932
impl<S: Clone + Default> StateHistory<S> {
3033
/// Construct
31-
pub fn new(module: &str) -> Self {
34+
pub fn new(module: &str, kind: HistoryKind) -> Self {
3235
Self {
3336
history: VecDeque::new(),
3437
module: module.to_string(),
38+
kind: kind,
3539
}
3640
}
3741

3842
/// Get the current state (if any), direct ref
3943
pub fn current(&self) -> Option<&S> {
40-
match self.history.back() {
41-
Some(entry) => Some(&entry.state),
42-
None => None,
43-
}
44+
self.history.back().map(|entry| &entry.state)
45+
}
46+
47+
/// Get the current state assuming any rollback has been done
48+
/// Cloned for modification - call commit() when done
49+
pub fn get_current_state(&self) -> S {
50+
self.history.back().map(|entry| entry.state.clone()).unwrap_or_default()
4451
}
4552

4653
/// Get the previous state for the given block, handling rollbacks if required
4754
/// State returned is cloned ready for modification - call commit() when done
48-
pub fn get_rolled_back_state(&mut self, block: &BlockInfo) -> S {
49-
loop {
50-
match self.history.back() {
51-
Some(state) => {
52-
if state.block >= block.number {
55+
pub fn get_rolled_back_state(&mut self, index: u64) -> S {
56+
match self.kind {
57+
HistoryKind::BlockState => {
58+
while let Some(entry) = self.history.back() {
59+
if entry.index >= index {
5360
info!(
5461
"{} rolling back state to {} removing block {}",
55-
self.module, block.number, state.block
62+
self.module, index, entry.index
63+
);
64+
self.history.pop_back();
65+
} else {
66+
break;
67+
}
68+
}
69+
}
70+
HistoryKind::EpochState => {
71+
while let Some(entry) = self.history.back() {
72+
if entry.index >= index {
73+
info!(
74+
"{} rolling back epoch state to {} removing epoch {}",
75+
self.module, index, entry.index
5676
);
5777
self.history.pop_back();
5878
} else {
5979
break;
6080
}
6181
}
62-
_ => break,
6382
}
6483
}
6584

6685
self.get_current_state()
6786
}
6887

69-
/// Get the current state assuming any rollback has been done
70-
/// Cloned for modification - call commit() when done
71-
pub fn get_current_state(&mut self) -> S {
72-
if let Some(current) = self.history.back() {
73-
current.state.clone()
74-
} else {
75-
S::default()
76-
}
88+
/// Get the state for a given index (if any), direct ref
89+
pub fn get_by_index(&self, index: u64) -> Option<&S> {
90+
self.history.iter().find(|entry| entry.index == index).map(|entry| &entry.state)
91+
}
92+
93+
pub fn len(&self) -> usize {
94+
self.history.len()
7795
}
7896

7997
/// Commit the new state
80-
pub fn commit(&mut self, block: &BlockInfo, state: S) {
81-
// Prune beyond 'k'
82-
while self.history.len() >= SECURITY_PARAMETER_K as usize {
83-
self.history.pop_front();
98+
pub fn commit(&mut self, index: u64, state: S) {
99+
match self.kind {
100+
HistoryKind::BlockState => {
101+
while self.history.len() >= SECURITY_PARAMETER_K as usize {
102+
self.history.pop_front();
103+
}
104+
self.history.push_back(HistoryEntry { index, state });
105+
}
106+
HistoryKind::EpochState => {
107+
self.history.push_back(HistoryEntry { index, state });
108+
}
84109
}
85-
86-
self.history.push_back(HistoryEntry {
87-
block: block.number,
88-
state,
89-
});
90110
}
91111
}
92112

common/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ pub struct StakeDelegation {
482482
}
483483

484484
/// SPO total delegation data (for SPDD)
485-
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
485+
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq)]
486486
pub struct DelegatedStake {
487487
/// Active stake - UTXO values only (used for reward calcs)
488488
pub active: Lovelace,

0 commit comments

Comments
 (0)