Skip to content

Conversation

@jstuczyn
Copy link
Contributor

@jstuczyn jstuczyn commented Jun 25, 2025

Further builds up on #5833 by integrating the performance contract into nym-api.

By setting .performance_provider.debug.use_performance_contract_data to true in the api's config, it will attempt to use the contract as the source of node scores for all annotations (and thus endpoints and rewarding). Note that if the contract address is not set or the contract itself is empty, the binary will not start. If the flag is not enabled, the binary will work like before, i.e. it will just use whatever it has in the storage.

For now this flag shouldn't be set, for there's one more missing piece of the puzzle to complete the whole thing. A separate process to actually submit aggregated data to the aforementioned contract.


This change is Reviewable

@jstuczyn jstuczyn requested a review from durch June 25, 2025 11:21
@jstuczyn jstuczyn changed the title feat: basic performance contract integration feat: basic performance contract integration [within Nym API] Jun 25, 2025
@vercel
Copy link

vercel bot commented Jun 25, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
nym-explorer-v2 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 25, 2025 1:14pm
nym-next-explorer ❌ Failed (Inspect) Jun 25, 2025 1:14pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
docs-nextra ⬜️ Ignored (Inspect) Visit Preview Jun 25, 2025 1:14pm

@jstuczyn jstuczyn force-pushed the feature/basic-performance-contract-integration branch from 23ee559 to c89267a Compare June 25, 2025 13:07
Copy link
Contributor

@durch durch left a comment

Choose a reason for hiding this comment

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

I've got one bug, and two suggestion. Apart from fixing the bug, I think implementing the performance optimisation might be worth while, not so sure on the race condition, maybe I'm just a bit pedantic/paranoid here

Ok(PerformanceContractCacheData { epoch_performance })
}

async fn refresh(&mut self) -> Result<Option<PerformanceContractEpochCacheData>, NyxdError> {
Copy link
Contributor

Choose a reason for hiding this comment

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

There might be a race condition in here, thinking of a few scenarios:

Scenario 1: New submission between check and fetch

  1. Refresher: Gets last_submission with block_height: 1000, epoch_id: 100
  2. Refresher: Determines it's different from cached (needs update)
  3. Meanwhile: New data submitted to contract at block_height: 1001, epoch_id: 101
  4. Refresher: Fetches performance data (might get epoch 101 data)
  5. Refresher: Stores last_submission as the old value from step 1
  6. Refresher: Will think there's an update (because stored != actual) but fetch same data

Scenario 2: Epoch transition during refresh

  1. Refresher: Gets last_submission for epoch 100
  2. Refresher: Gets current_epoch = 100
  3. Meanwhile: Epoch transitions to 101
  4. Refresher: Tries to fetch epoch 100 performance (might fail or get incomplete data)
  5. System could miss the epoch 101 data entirely

I think we should use the actual fetched data to determine if we have an update, not the metadata about when data was last submitted
Maybe we could fetch both pieces of data at the same time:

// Get current epoch                                                                                                                                                                                                       
let current_epoch = self                                                                                                                                                                                                   
       .mixnet_contract_cache                                                                                                                                                                                                 
       .current_interval()                                                                                                                                                                                                    
       .await                                                                                                                                                                                                                 
       .unwrap()                                                                                                                                                                                                              
       .current_epoch_absolute_id();                                                                                                                                                                                          
                                                                                                                                                                                                                         
// Fetch both submission info and performance data in parallel                                                                                                                                                             
let (last_submitted, performance_result) = tokio::join!(                                                                                                                                                                   
       self.nyxd_client.get_last_performance_contract_submission(),                                                                                                                                                           
       self.nyxd_client.get_full_epoch_performance(current_epoch)                                                                                                                                                             
);                                                                                                                                                                                                                   

Then figure out if we've already processed this and update if needed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should use the actual fetched data to determine if we have an update, not the metadata about when data was last submitted

the problem with this approach is that we'd have to fetch a lot of data, so that's why I introduced the metadata to have a quick way of seeing if there has been any updates since the last time it was polled. remember that we really don't care about data from the past epochs and are only interested if the most recent one is updated. on startup we pull the full information for the however many fallback epochs we care about and then in the loop we check "has there been anything new submitted? if so, update current epoch" - if the update was for some previous epochs. tough luck. they should have submitted it earlier (and that data would have only ever been used as fallback anyway). in 99% cases network monitors wouldn't be running behind and would actually be submitting only for the current epoch.

with that in mind, let's consider those race condition scenarios:

  1. I'm assuming in this scenario the current epoch is 101. so we'll just refresh 101 instead of getting anything for 100. it will be slightly inefficient since we'll just refresh what we already had, but it shouldn't hurt at all.
  2. so epoch will transition to 101 and we'll potentially get empty data for the current epoch. but in this instance, when anybody queries for "current" performance, we will fallback to information we already had on 100. Also, this is an incredibly unlikely scenario as network monitors are meant to (well, once implemented) submit performance measurements for epochs BEFORE the actual transition takes place

also, fetching those at the same time wouldn't have solved anything as in the world of the internet one of those would have been resolved first anyway (plus get_full_epoch_performance is actually multiple paged queries)

@durch durch self-requested a review July 1, 2025 10:22
@jstuczyn jstuczyn merged commit d0692a5 into develop Jul 1, 2025
15 of 17 checks passed
@jstuczyn jstuczyn deleted the feature/basic-performance-contract-integration branch July 1, 2025 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants