Skip to content

Conversation

@Karrq
Copy link
Contributor

@Karrq Karrq commented Oct 20, 2025

This PR addresses part of the TODOs remaining in the backend code

⚠️ Breaking Changes ⚠️

Configuration BREAKING changes:

  • Add configuration items for auth expirations
  • Add configuration item for Node callaback URL
  • Configurable number of retries for Upload

API BREAKING changes:

  • Use alloy's Address for addresses instead of strings
  • Authenticate users for upload/download operations
  • FileList response now has a single tree field at the root and folder items do not have a children field
  • Remove /distribute endpoint as the backend is not in charge of this anymore

DB Migrations:

  • The Bucket table now has 3 more columns: value_prop_id, total_size, file_count

API changes:

  • Added pagination query parameters to /files and /buckets endpoints
    • The pagination parameters are optional and are: limit (the number of items to reply with) and page (which 'page' the endpoint should return)

Performance improvements:

  • Cache the MSP ID
  • Use pagination mechanism for db requests with many items

Karrq added 27 commits October 6, 2025 17:14
refactor: use raw fingerprint bytes for file info internally
refactor: get_file_info authenticates user, no need for bucket id
docs: document missing auth for download
fix(test:download): initialize test db
@Karrq Karrq requested review from TDemeco, ffarall and ftheirs October 20, 2025 15:24
@Karrq Karrq requested a review from TDemeco October 28, 2025 13:09
@Karrq Karrq added B3-backendnoteworthy Changes should be mentioned in SH backend related release notes breaking Needs to be mentioned in breaking changes D2-noauditneeded🙈 PR doesn't need to be audited B1-sdknoteworthy Changes should be mentioned in SH SDK related release notes labels Nov 3, 2025
Karrq added 2 commits November 4, 2025 21:07
This is to allow the /health endpoint to potentially trigger a reconnect
if the connection has errors
Copy link
Contributor

@HermanObst HermanObst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some preliminar comments

let payment_stream_data = self
.postgres
.get_payment_streams_for_user(user_address)
.get_payment_streams_for_user(&user_address.to_string())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing user_address downstream to also be Address instead String was a PITA?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean in the DB itself? Or the internal traits?
In the DB we keep string because technically speaking it could be a SS58 address, which was the case before the solochain runtime

Copy link
Contributor

@HermanObst HermanObst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second pass

Comment on lines +98 to +138
///
/// This method caches the MSP data to avoid repeated database hits.
/// The cache is automatically refreshed after the configured TTL expires.
pub async fn get_msp(&self, msp_onchain_id: &OnchainMspId) -> Result<Msp> {
debug!(target: "indexer_db::client::get_msp", onchain_id = %msp_onchain_id, "Fetching MSP");

// TODO: should we cache this?
// since we always reference the same msp
self.repository
// Check if we have a valid cached entry
{
let cache = self.msp_cache.read().await;
if let Some(entry) = &*cache {
// Check if the cache entry matches the requested MSP and is still valid
if entry.msp.onchain_msp_id == *msp_onchain_id
&& entry.last_refreshed.elapsed() < Duration::from_secs(MSP_CACHE_TTL_SECS)
{
return Ok(entry.msp.clone());
}
}
}

// Cache miss or expired, fetch from database
let msp = self
.repository
.get_msp_by_onchain_id(msp_onchain_id)
.await
.map_err(Into::into)
.map_err(Into::<crate::error::Error>::into)?;

// Update cache with the new value
{
let mut cache = self.msp_cache.write().await;
*cache = Some(MspCacheEntry {
msp: msp.clone(),
last_refreshed: Instant::now(),
});
}

Ok(msp)
}

/// Invalidate the MSP cache if it matches the given MSP
///
/// # Arguments
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation of having this cache?
Also, can't this cause having out of date info?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the cache because realistically speaking the MSP that we make query for will be only 1, so this way we save a few redudant calls to the db...
Not necessary tbh but not bad to have. Yeah we might be out of date but not meaningfully so

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, i think we should introduce a cache only if the load require to.
In this moment I think is best to have accurate data always.
We can discuss it in the Daily tomorrow
cc @ffarall @TDemeco

Copy link
Contributor

@HermanObst HermanObst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GigaBrain work! Lets just discuss all my comments and we are good to go

pub updated_at: NaiveDateTime,
pub merkle_root: Vec<u8>,
pub value_prop_id: String,
pub total_size: BigDecimal,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need a BigInt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely no, but this gives us flexibility to not have to depend on the runtime unit typing (technically file should change too)

Comment on lines 186 to +208
diesel::delete(file::table)
.filter(file::file_key.eq(file_key))
.execute(conn)
.await?;

// Update bucket counts if file was found
if let Some((bucket_id, file_size)) = file_info {
Bucket::decrement_file_count_and_size(conn, bucket_id, file_size).await?;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, is this happening atomically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's all in one transaction at the start of the block processing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How the db transaction is initiated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from handle_finality_notification -> index_block which then trickles the data to everything else

conn.transaction::<(), IndexBlockError, _>(move |conn| {
Box::pin(async move {
let block_number_u64: u64 = block_number.saturated_into();
let block_number_i64: i64 = block_number_u64 as i64;
ServiceState::update(conn, block_number_i64).await?;
for ev in block_events {
self.route_event(conn, &ev.event.into(), block_hash).await?;
}
Ok(())
})
})
.await?;

@Karrq Karrq added the indexer-db Changes include migrations for the Indexer DB label Nov 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B1-sdknoteworthy Changes should be mentioned in SH SDK related release notes B3-backendnoteworthy Changes should be mentioned in SH backend related release notes breaking Needs to be mentioned in breaking changes D2-noauditneeded🙈 PR doesn't need to be audited indexer-db Changes include migrations for the Indexer DB

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants