diff --git a/bin/dipper-service/src/config.rs b/bin/dipper-service/src/config.rs index 73ee033a..4a85f18d 100644 --- a/bin/dipper-service/src/config.rs +++ b/bin/dipper-service/src/config.rs @@ -817,8 +817,9 @@ pub struct DipsAgreementConfig { pub max_grt_per_billion_entities_per_30_days: f64, /// Number of days to look back for declined indexers (standard exclusion). Covers - /// CanceledByIndexer, expiries whose offer reached the chain, and structurally - /// persistent rejections (UNSUPPORTED_NETWORK, MANIFEST_TOO_LARGE). Default: 30 days. + /// CanceledByIndexer, AbandonedByIndexer, expiries whose offer reached the chain, and + /// structurally persistent rejections (UNSUPPORTED_NETWORK, MANIFEST_TOO_LARGE). Default: 30 + /// days. #[serde(default = "default_declined_indexer_lookback_days")] pub declined_indexer_lookback_days: i32, diff --git a/bin/dipper-service/src/registry/agreement.rs b/bin/dipper-service/src/registry/agreement.rs index c7cfeaab..06e0ed96 100644 --- a/bin/dipper-service/src/registry/agreement.rs +++ b/bin/dipper-service/src/registry/agreement.rs @@ -172,9 +172,10 @@ pub trait AgreementRegistry { indexer_ids: &[IndexerId], ) -> RegistryResult>>; - /// Get declined `CanceledByIndexer`/`Expired`/`Rejected` indexers grouped by - /// deployment. Each rejection reason gets its own exclusion window, as does an - /// expiry that never had an offer transaction; see the query for the details. + /// Get declined `CanceledByIndexer`/`Expired`/`Rejected` indexers, and those whose agreement + /// dipper ended `AbandonedByIndexer`, grouped by deployment. Each rejection reason gets its + /// own exclusion window, as does an expiry that never had an offer transaction; see the query + /// for the details. async fn get_declined_indexers_by_deployment( &self, default_lookback_days: i32, diff --git a/dipper-pgregistry/src/postgres.rs b/dipper-pgregistry/src/postgres.rs index 23faca70..6af8d05a 100644 --- a/dipper-pgregistry/src/postgres.rs +++ b/dipper-pgregistry/src/postgres.rs @@ -738,9 +738,10 @@ impl PgRegistry { .collect()) } - /// Get declined `CanceledByIndexer`/`Expired`/`Rejected` indexers grouped by - /// deployment (deployment id -> indexer ids). Each rejection reason gets its own - /// exclusion window, as does an expiry that never had an offer transaction. + /// Get declined `CanceledByIndexer`/`Expired`/`Rejected` indexers, and those whose agreement + /// dipper ended `AbandonedByIndexer`, grouped by deployment (deployment id -> indexer ids). + /// Each rejection reason gets its own exclusion window, as does an expiry that never had an + /// offer transaction. pub async fn get_declined_indexers_by_deployment( &self, default_lookback_days: i32, @@ -761,7 +762,7 @@ impl PgRegistry { deployment_id, array_agg(DISTINCT indexer_id) as indexer_ids FROM dipper_reg_indexing_agreements - WHERE status IN ($1, $2, $3) + WHERE status IN ($1, $2, $3, $20) AND ( -- PRICE_TOO_LOW: shorter lookback (until next IISA refresh) (rejection_reason = $6 @@ -816,6 +817,7 @@ impl PgRegistry { .bind(uncertain_lookback_days) // $17 .bind(SENDER_NOT_TRUSTED) // $18 .bind(UNSPECIFIED) // $19 + .bind(IndexingAgreementStatus::AbandonedByIndexer) // $20 .fetch_all(&self.pool) .await?; diff --git a/dipper-pgregistry/tests/it_registry_postgres.rs b/dipper-pgregistry/tests/it_registry_postgres.rs index a246b2a3..2f4c194f 100644 --- a/dipper-pgregistry/tests/it_registry_postgres.rs +++ b/dipper-pgregistry/tests/it_registry_postgres.rs @@ -677,6 +677,52 @@ async fn get_declined_indexers_by_deployment_returns_rejected() { assert!(declined_5e.contains(&indexer_c)); } +#[tokio::test] +async fn get_declined_indexers_by_deployment_includes_an_indexer_that_abandoned_it() { + // Otherwise the reassessment that replaces it could pick the same indexer straight back. + let (db, _temp_db) = temp_registry_db().await; + run_fixture(&db, include_str!("fixtures/0002_indexing_agreements.sql")) + .await + .expect("Failed to run fixture"); + let registry = PgRegistry::new(db); + // Fixture 0002's accepted agreement. + let agreement_id = + IndexingAgreementId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]); + let deployment: DeploymentId = "QmUzRg2HHMpbgf6Q4VHKNDbtBEJnyp5JWCh2gUX9AV6jXv" + .parse() + .unwrap(); + let indexer = indexer_id!("d609e9fdd6ce53e5a26278c50486dd6791d4d705"); + registry + .mark_indexing_agreement_as_abandoning(&agreement_id) + .await + .expect("abandon"); + registry + .mark_indexing_agreement_as_canceled_by_requester(&agreement_id) + .await + .expect("ended abandoned"); + + let within = registry + .get_declined_indexers_by_deployment(30, 1, 5, 1) + .await + .expect("Failed to get declined indexers"); + let past = registry + .get_declined_indexers_by_deployment(0, 1, 5, 1) + .await + .expect("Failed to get declined indexers"); + + assert!( + within + .get(&deployment) + .is_some_and(|ids| ids.contains(&indexer)) + ); + assert!( + !past + .get(&deployment) + .is_some_and(|ids| ids.contains(&indexer)), + "only for the standard lookback" + ); +} + #[tokio::test] async fn get_declined_indexers_by_deployment_empty_when_no_declines() { //* Given