Skip to content

Commit 129de22

Browse files
authored
Merge branch 'main' into 2091-map-proposal-elements-for-testing
2 parents b6e5705 + 9d00584 commit 129de22

File tree

83 files changed

+1459
-2016
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+1459
-2016
lines changed

catalyst-gateway/bin/src/db/event/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ impl EventDB {
240240
/// The env var "`DATABASE_URL`" can be set directly as an anv var, or in a
241241
/// `.env` file.
242242
pub fn establish_connection() {
243-
let (url, user, pass) = Settings::event_db_settings();
243+
let (url, user, pass, max_connections, max_lifetime, min_idle, connection_timeout) =
244+
Settings::event_db_settings();
244245

245246
// This was pre-validated and can't fail, but provide default in the impossible case it
246247
// does.
@@ -257,7 +258,12 @@ pub fn establish_connection() {
257258

258259
let pg_mgr = PostgresConnectionManager::new(config, tokio_postgres::NoTls);
259260

260-
let pool = Pool::builder().build_unchecked(pg_mgr);
261+
let pool = Pool::builder()
262+
.max_size(max_connections)
263+
.max_lifetime(Some(core::time::Duration::from_secs(max_lifetime.into())))
264+
.min_idle(min_idle)
265+
.connection_timeout(core::time::Duration::from_secs(connection_timeout.into()))
266+
.build_unchecked(pg_mgr);
261267

262268
if EVENT_DB_POOL.set(Arc::new(pool)).is_err() {
263269
error!("Failed to set event db pool. Called Twice?");

catalyst-gateway/bin/src/db/event/signed_docs/signed_doc_body.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ impl SignedDocBody {
5151
&self.doc_type
5252
}
5353

54+
/// Returns the document authors.
55+
pub(crate) fn authors(&self) -> &Vec<String> {
56+
&self.authors
57+
}
58+
5459
/// Returns the document metadata.
5560
pub(crate) fn metadata(&self) -> Option<&serde_json::Value> {
5661
self.metadata.as_ref()

catalyst-gateway/bin/src/db/index/queries/rbac/get_rbac_registrations.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22
33
use std::sync::Arc;
44

5+
use anyhow::Context;
6+
use cardano_blockchain_types::{Network, Point, Slot, TxnIndex};
7+
use cardano_chain_follower::ChainFollower;
8+
use catalyst_signed_doc::IdUri;
9+
use futures::{TryFutureExt, TryStreamExt};
10+
use rbac_registration::{cardano::cip509::Cip509, registration::cardano::RegistrationChain};
511
use scylla::{
612
prepared_statement::PreparedStatement, statement::Consistency,
713
transport::iterator::TypedRowStream, DeserializeRow, SerializeRow, Session,
814
};
9-
use tracing::error;
15+
use tracing::{error, warn};
1016

1117
use crate::db::{
1218
index::{
@@ -63,3 +69,78 @@ impl Query {
6369
.map_err(Into::into)
6470
}
6571
}
72+
73+
/// Returns a sorted list of all registrations for the given Catalyst ID from the
74+
/// database.
75+
async fn indexed_registrations(
76+
session: &CassandraSession, catalyst_id: &IdUri,
77+
) -> anyhow::Result<Vec<Query>> {
78+
let mut result: Vec<_> = Query::execute(session, QueryParams {
79+
catalyst_id: catalyst_id.clone().into(),
80+
})
81+
.and_then(|r| r.try_collect().map_err(Into::into))
82+
.await?;
83+
84+
result.sort_by_key(|r| r.slot_no);
85+
Ok(result)
86+
}
87+
88+
/// Build a registration chain from the given indexed data.
89+
pub(crate) async fn build_reg_chain(
90+
session: &CassandraSession, catalyst_id: &IdUri, network: Network,
91+
) -> anyhow::Result<Option<RegistrationChain>> {
92+
let regs = indexed_registrations(session, catalyst_id).await?;
93+
let mut regs_iter = regs.iter();
94+
let Some(root) = regs_iter.next() else {
95+
return Ok(None);
96+
};
97+
98+
let root = registration(network, root.slot_no.into(), root.txn_index.into())
99+
.await
100+
.context("Failed to get root registration")?;
101+
let mut chain = RegistrationChain::new(root).context("Invalid root registration")?;
102+
103+
for reg in regs_iter {
104+
// We only store valid registrations in this table, so an error here indicates a bug in
105+
// our indexing logic.
106+
let cip509 = registration(network, reg.slot_no.into(), reg.txn_index.into())
107+
.await
108+
.with_context(|| {
109+
format!(
110+
"Invalid or missing registration at {:?} block {:?} transaction",
111+
reg.slot_no, reg.txn_index,
112+
)
113+
})?;
114+
match chain.update(cip509) {
115+
Ok(c) => chain = c,
116+
Err(e) => {
117+
// This isn't a hard error because while the individual registration can be valid it
118+
// can be invalid in the context of the whole registration chain.
119+
warn!(
120+
"Unable to apply registration from {:?} block {:?} txn index: {e:?}",
121+
reg.slot_no, reg.txn_index
122+
);
123+
},
124+
}
125+
}
126+
127+
Ok(Some(chain))
128+
}
129+
130+
/// A helper function to return a RBAC registration from the given block and slot.
131+
async fn registration(network: Network, slot: Slot, txn_index: TxnIndex) -> anyhow::Result<Cip509> {
132+
let point = Point::fuzzy(slot);
133+
let block = ChainFollower::get_block(network, point)
134+
.await
135+
.context("Unable to get block")?
136+
.data;
137+
if block.point().slot_or_default() != slot {
138+
// The `ChainFollower::get_block` function can return the next consecutive block if
139+
// it cannot find the exact one. This shouldn't happen, but we need
140+
// to check anyway.
141+
return Err(anyhow::anyhow!("Unable to find exact block"));
142+
}
143+
Cip509::new(&block, txn_index, &[])
144+
.context("Invalid RBAC registration")?
145+
.context("No RBAC registration at this block and txn index")
146+
}

catalyst-gateway/bin/src/db/index/session.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ pub(crate) enum CassandraSessionError {
8080
/// Error indicating that the session has already been set.
8181
#[error("Session already set")]
8282
SessionAlreadySet,
83+
/// Should be used by the caller when it fails to acquire the initialized database
84+
/// session.
85+
#[error("Failed acquiring database session")]
86+
FailedAcquiringSession,
8387
}
8488

8589
/// All interaction with cassandra goes through this struct.

catalyst-gateway/bin/src/service/api/cardano/rbac/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl Api {
3030
/// No Authorization required, but Token permitted.
3131
auth: NoneOrRBAC,
3232
) -> registrations_get::AllResponses {
33-
let auth_catalyst_id = auth.into();
34-
registrations_get::endpoint(lookup, auth_catalyst_id).await
33+
let token = auth.into();
34+
registrations_get::endpoint(lookup, token).await
3535
}
3636
}

catalyst-gateway/bin/src/service/api/cardano/rbac/registrations_get/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,19 @@ use crate::{
3434
chain_info::ChainInfo, registration_chain::RbacRegistrationChain, response::Responses,
3535
unprocessable_content::RbacUnprocessableContent,
3636
},
37-
common::types::{
38-
cardano::query::cat_id_or_stake::CatIdOrStake, headers::retry_after::RetryAfterOption,
37+
common::{
38+
auth::rbac::token::CatalystRBACTokenV1,
39+
types::{
40+
cardano::query::cat_id_or_stake::CatIdOrStake,
41+
headers::retry_after::RetryAfterOption,
42+
},
3943
},
4044
},
4145
};
4246

4347
/// Get RBAC registration endpoint.
4448
pub(crate) async fn endpoint(
45-
lookup: Option<CatIdOrStake>, auth_catalyst_id: Option<IdUri>,
49+
lookup: Option<CatIdOrStake>, token: Option<CatalystRBACTokenV1>,
4650
) -> AllResponses {
4751
let Some(persistent_session) = CassandraSession::get(true) else {
4852
let err = anyhow!("Failed to acquire persistent db session");
@@ -78,8 +82,8 @@ pub(crate) async fn endpoint(
7882
}
7983
},
8084
None => {
81-
match auth_catalyst_id {
82-
Some(id) => id,
85+
match token {
86+
Some(token) => token.catalyst_id().clone(),
8387
None => {
8488
return Responses::UnprocessableContent(Json(RbacUnprocessableContent::new(
8589
"Either lookup parameter or token must be provided",
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
//! A module for placing common structs, functions, and variables across the `document`
2+
//! endpoint module not specified to a specific endpoint.
3+
4+
use std::collections::HashMap;
5+
6+
use catalyst_signed_doc::CatalystSignedDocument;
7+
use rbac_registration::cardano::cip509::RoleNumber;
8+
9+
use super::templates::get_doc_static_template;
10+
use crate::{
11+
db::event::{error::NotFoundError, signed_docs::FullSignedDoc},
12+
service::common::auth::rbac::token::CatalystRBACTokenV1,
13+
settings::Settings,
14+
};
15+
16+
/// Get document from the database
17+
pub(crate) async fn get_document(
18+
document_id: &uuid::Uuid, version: Option<&uuid::Uuid>,
19+
) -> anyhow::Result<CatalystSignedDocument> {
20+
// Find the doc in the static templates first
21+
if let Some(doc) = get_doc_static_template(document_id) {
22+
return Ok(doc);
23+
}
24+
25+
// If doesn't exist in the static templates, try to find it in the database
26+
let db_doc = FullSignedDoc::retrieve(document_id, version).await?;
27+
db_doc.raw().try_into()
28+
}
29+
30+
/// A struct which implements a
31+
/// `catalyst_signed_doc::providers::CatalystSignedDocumentProvider` trait
32+
pub(crate) struct DocProvider;
33+
34+
impl catalyst_signed_doc::providers::CatalystSignedDocumentProvider for DocProvider {
35+
async fn try_get_doc(
36+
&self, doc_ref: &catalyst_signed_doc::DocumentRef,
37+
) -> anyhow::Result<Option<CatalystSignedDocument>> {
38+
let id = doc_ref.id.uuid();
39+
let ver = doc_ref.ver.uuid();
40+
match get_document(&id, Some(&ver)).await {
41+
Ok(doc) => Ok(Some(doc)),
42+
Err(err) if err.is::<NotFoundError>() => Ok(None),
43+
Err(err) => Err(err),
44+
}
45+
}
46+
47+
fn future_threshold(&self) -> Option<std::time::Duration> {
48+
let signed_doc_cfg = Settings::signed_doc_cfg();
49+
Some(signed_doc_cfg.future_threshold())
50+
}
51+
52+
fn past_threshold(&self) -> Option<std::time::Duration> {
53+
let signed_doc_cfg = Settings::signed_doc_cfg();
54+
Some(signed_doc_cfg.past_threshold())
55+
}
56+
}
57+
58+
// TODO: make the struct to support multi sigs validation
59+
/// A struct which implements a
60+
/// `catalyst_signed_doc::providers::CatalystSignedDocumentProvider` trait
61+
pub(crate) struct VerifyingKeyProvider(
62+
HashMap<catalyst_signed_doc::IdUri, ed25519_dalek::VerifyingKey>,
63+
);
64+
65+
impl catalyst_signed_doc::providers::VerifyingKeyProvider for VerifyingKeyProvider {
66+
async fn try_get_key(
67+
&self, kid: &catalyst_signed_doc::IdUri,
68+
) -> anyhow::Result<Option<ed25519_dalek::VerifyingKey>> {
69+
Ok(self.0.get(kid).copied())
70+
}
71+
}
72+
73+
impl VerifyingKeyProvider {
74+
/// Attempts to construct an instance of `Self` by validating and resolving a list of
75+
/// Catalyst Document KIDs against a provided RBAC token.
76+
///
77+
/// This method performs the following steps:
78+
/// 1. Verifies that only a single KID is provided with a document (as multi-signature
79+
/// is currently unsupported).
80+
/// 2. Verifies that **all** provided KIDs match the Catalyst ID from the RBAC token.
81+
/// 3. Verifies that each provided KID is actually a signing key.
82+
/// 4. Extracts the role index and rotation from each KID.
83+
/// 5. Retrieves the latest signing public key and rotation state associated with the
84+
/// role for each KID from the registration chain.
85+
/// 6. Verifies that each provided KID uses its latest rotation.
86+
/// 7. Collects and returns a vector of tuples containing the KID, and its latest
87+
/// signing key.
88+
///
89+
/// # Errors
90+
///
91+
/// Returns an `anyhow::Error` if:
92+
/// - Any KID's short Catalyst ID does not match the one in the token.
93+
/// - Indexed registration queries or chain building fail.
94+
/// - The KID's role index and rotation parsing fails.
95+
/// - The KID is not a singing key.
96+
/// - The latest signing key for a required role cannot be found.
97+
/// - The KID is not using the latest rotation.
98+
pub(crate) async fn try_from_kids(
99+
token: &mut CatalystRBACTokenV1, kids: &[catalyst_signed_doc::IdUri],
100+
) -> anyhow::Result<Self> {
101+
if kids.len() > 1 {
102+
anyhow::bail!("Multi-signature document is currently unsupported");
103+
}
104+
105+
if kids
106+
.iter()
107+
.any(|kid| kid.as_short_id() != token.catalyst_id().as_short_id())
108+
{
109+
anyhow::bail!("RBAC Token CatID does not match with the document KIDs");
110+
}
111+
112+
let Some(reg_chain) = token.reg_chain().await? else {
113+
anyhow::bail!("Failed to retrieve a registration from corresponding Catalyst ID");
114+
};
115+
116+
let result = kids.iter().map(|kid| {
117+
if !kid.is_signature_key() {
118+
anyhow::bail!("Invalid KID {kid}: KID must be a signing key not an encryption key");
119+
}
120+
121+
let (kid_role_index, kid_rotation) = kid.role_and_rotation();
122+
let kid_role_index = RoleNumber::from(kid_role_index.to_string().parse::<u8>()?);
123+
let kid_rotation = kid_rotation.to_string().parse::<usize>()?;
124+
125+
let (latest_pk, rotation) = reg_chain
126+
.get_latest_signing_pk_for_role(&kid_role_index)
127+
.ok_or_else(|| {
128+
anyhow::anyhow!(
129+
"Failed to get last signing key for the proposer role for {kid} Catalyst ID"
130+
)
131+
})?;
132+
133+
if rotation != kid_rotation {
134+
anyhow::bail!("Invalid KID {kid}: KID's rotation ({kid_rotation}) is not the latest rotation ({rotation})");
135+
}
136+
137+
Ok((kid.clone(), latest_pk))
138+
})
139+
.collect::<Result<_, _>>()?;
140+
141+
Ok(Self(result))
142+
}
143+
}

0 commit comments

Comments
 (0)