Skip to content

Add indempotency check#263

Merged
ritik4ever merged 3 commits into
Stellar-Ecosystem:mainfrom
clintjeff2:add-indempotency-check
Jun 29, 2026
Merged

Add indempotency check#263
ritik4ever merged 3 commits into
Stellar-Ecosystem:mainfrom
clintjeff2:add-indempotency-check

Conversation

@clintjeff2

@clintjeff2 clintjeff2 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

This PR improves the idempotency of the seed.js script. Previously, the script would skip seeding entirely if the total count of services was greater than or equal to the number of services in the seed list. This caused issues when the script partially failed, as subsequent runs would not attempt to register the remaining services.

The new implementation fetches all services registered by the provider and checks each service in the seed list by name. It only attempts to register services that are not already present on-chain. Additionally, registerServiceOnChain now explicitly checks for name collisions to ensure on-chain consistency.

Changes:

  • Added activeServiceExistsByName helper to backend/src/lib/contract.js.
  • Updated registerServiceOnChain in backend/src/lib/contract.js to throw an error if a service with the same name already exists for the provider.
  • Refactored backend/scripts/seed.js to use name-based idempotency.
  • Added new test cases to backend/src/lib/contract.test.js to verify name-based duplicate detection.
  • Verified all 177 backend tests pass.

Closes #75

Summary by CodeRabbit

  • New Features

    • Service setup now avoids creating duplicate entries when the same service has already been registered for a provider.
    • Duplicate checks are now based on service name, improving consistency during registration.
  • Bug Fixes

    • Seed runs are more reliable and can be re-run safely without creating repeated services.
    • Registration now clearly skips services that already exist and logs what was found.

google-labs-jules Bot and others added 2 commits June 29, 2026 11:02
- Add `activeServiceExistsByName` to `contractHelpers` in `backend/src/lib/contract.js`
- Update `registerServiceOnChain` to prevent duplicate service names for the same provider
- Refactor `seed.js` to check for existing services by name before registering, allowing for idempotent re-runs after partial failures
- Add unit tests for the new duplicate check logic
- Ensure all backend tests pass

Co-authored-by: clintjeff2 <119521983+clintjeff2@users.noreply.github.com>
…7529488458

Add seed.js idempotency check per service name
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds activeServiceExistsByName to contract.js to detect duplicate active services by provider and name. Updates registerServiceOnChain to use this name-based check. Rewrites seed.js idempotency from a count-based exit to per-name comparison using listServicesByProvider. Tests are extended for the new helper and registration guard.

Name-based service deduplication

Layer / File(s) Summary
activeServiceExistsByName helper and registration guard
backend/src/lib/contract.js
Adds contractHelpers.activeServiceExistsByName (paginates listServices to match provider+name) and its exported wrapper. Updates registerServiceOnChain duplicate check to use provider+name instead of provider+endpoint, with updated error message.
Tests for name-based duplicate checks
backend/src/lib/contract.test.js
Adds spy for activeServiceExistsByName in beforeEach, new it blocks for true/false returns, updates endpoint-duplicate test label, and adds a new test asserting rejection when provider+name already exists.
Seed script per-name idempotency
backend/scripts/seed.js
Replaces getServiceCount import with listServicesByProvider, derives providerAddress from config.server.address, builds a Set of existing service names, and skips already-registered services instead of exiting early on count.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Stellar-Ecosystem/lodestar#120: Adds the original activeServiceExists (provider+endpoint) preflight check in registerServiceOnChain that this PR extends with the name-based variant.
  • Stellar-Ecosystem/lodestar#219: Modifies registerServiceOnChain duplicate-registration behavior in the same registry flow touched by this PR.

Suggested reviewers

  • ritik4ever

🐇 No more double-dipping in the service stew,
Each name gets checked before the chain rings true.
The seed hops through, skips what's already there,
A smarter rabbit plants with extra care!
✨ Idempotent fields, fresh every time! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the main change: improving seed idempotency.
Linked Issues check ✅ Passed The PR implements per-service-name idempotency in seed.js, adds contract name-duplicate checks, and updates tests as requested for #75.
Out of Scope Changes check ✅ Passed The touched files and changes stay focused on seed idempotency, contract duplicate checks, and related tests.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/src/lib/contract.test.js (1)

80-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise the helper logic instead of only the wrapper.

These specs mock contractHelpers.activeServiceExistsByName, so they still pass if the helper matches the wrong field or ignores active. Please add a direct test that passes paged fetchServices data with active/inactive rows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/lib/contract.test.js` around lines 80 - 113, The current tests
for contractLib.activeServiceExistsByName only verify the wrapper with mocked
helpers, so they do not prove the helper filters by both name and active status.
Add a direct test around activeServiceExistsByName that exercises the real
fetchServices path with paged results containing both active and inactive rows,
and assert it returns true only when an active service matches the provider and
name while ignoring inactive or mismatched entries. Use the existing
contractHelpers.activeServiceExistsByName and fetchServices symbols to locate
the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/scripts/seed.js`:
- Around line 43-53: The seed idempotency check in the SERVICE loop is using all
results from listServicesByProvider(), so inactive services are incorrectly
treated as already registered. Update the logic in backend/scripts/seed.js
around existingServices/existingNames to filter for active services only before
building the Set, and keep the skip check in the for-of over SERVICES based on
those active names.

In `@backend/src/lib/contract.js`:
- Around line 424-435: The duplicate-check logic in activeServiceExistsByName
currently matches only provider and name, so inactive rows still block
registration. Update the predicate inside activeServiceExistsByName to also
require s.active when scanning the listServices results, and keep the paging
loop behavior unchanged so only active services prevent registerServiceOnChain.

---

Nitpick comments:
In `@backend/src/lib/contract.test.js`:
- Around line 80-113: The current tests for
contractLib.activeServiceExistsByName only verify the wrapper with mocked
helpers, so they do not prove the helper filters by both name and active status.
Add a direct test around activeServiceExistsByName that exercises the real
fetchServices path with paged results containing both active and inactive rows,
and assert it returns true only when an active service matches the provider and
name while ignoring inactive or mismatched entries. Use the existing
contractHelpers.activeServiceExistsByName and fetchServices symbols to locate
the logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a0457f89-0f4f-4b49-81b5-8df15c8285c0

📥 Commits

Reviewing files that changed from the base of the PR and between 158697a and e2be90a.

📒 Files selected for processing (3)
  • backend/scripts/seed.js
  • backend/src/lib/contract.js
  • backend/src/lib/contract.test.js

Comment thread backend/scripts/seed.js
Comment on lines +43 to +53
const existingServices = await listServicesByProvider(providerAddress);
const existingNames = new Set(existingServices.map((s) => s.name));

if (count >= SERVICES.length) {
logger.info('Registry already seeded — skipping');
process.exit(0);
}
logger.info({
total: SERVICES.length,
existing: existingNames.size,
}, 'Starting seed idempotency check');

for (const svc of SERVICES) {
if (existingNames.has(svc.name)) {
logger.info({ name: svc.name }, 'Service already registered — skipping');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Only skip names that are still active.

listServicesByProvider() includes inactive services. Building existingNames from every row means a previously deactivated service will be treated as already registered and never retried.

Suggested fix
-    const existingNames = new Set(existingServices.map((s) => s.name));
+    const existingNames = new Set(
+      existingServices.filter((s) => s.active).map((s) => s.name)
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const existingServices = await listServicesByProvider(providerAddress);
const existingNames = new Set(existingServices.map((s) => s.name));
if (count >= SERVICES.length) {
logger.info('Registry already seeded — skipping');
process.exit(0);
}
logger.info({
total: SERVICES.length,
existing: existingNames.size,
}, 'Starting seed idempotency check');
for (const svc of SERVICES) {
if (existingNames.has(svc.name)) {
logger.info({ name: svc.name }, 'Service already registered — skipping');
const existingServices = await listServicesByProvider(providerAddress);
const existingNames = new Set(
existingServices.filter((s) => s.active).map((s) => s.name)
);
logger.info({
total: SERVICES.length,
existing: existingNames.size,
}, 'Starting seed idempotency check');
for (const svc of SERVICES) {
if (existingNames.has(svc.name)) {
logger.info({ name: svc.name }, 'Service already registered — skipping');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/scripts/seed.js` around lines 43 - 53, The seed idempotency check in
the SERVICE loop is using all results from listServicesByProvider(), so inactive
services are incorrectly treated as already registered. Update the logic in
backend/scripts/seed.js around existingServices/existingNames to filter for
active services only before building the Set, and keep the skip check in the
for-of over SERVICES based on those active names.

Comment on lines +424 to +435
activeServiceExistsByName: async function (provider, name, fetchServices = listServices) {
let page = 0;
const pageSize = 20;

while (true) {
const services = await fetchServices({ page, pageSize });
if (!services.length) {
return false;
}

if (services.some((s) => s.provider === provider && s.name === name)) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Honor the active flag in this duplicate check.

listServices() returns inactive rows too, but this predicate ignores s.active. A deactivated service with the same provider/name will still block registerServiceOnChain, which contradicts the new "duplicate active service" behavior.

Suggested fix
-      if (services.some((s) => s.provider === provider && s.name === name)) {
+      if (services.some((s) => s.active && s.provider === provider && s.name === name)) {
         return true;
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
activeServiceExistsByName: async function (provider, name, fetchServices = listServices) {
let page = 0;
const pageSize = 20;
while (true) {
const services = await fetchServices({ page, pageSize });
if (!services.length) {
return false;
}
if (services.some((s) => s.provider === provider && s.name === name)) {
return true;
activeServiceExistsByName: async function (provider, name, fetchServices = listServices) {
let page = 0;
const pageSize = 20;
while (true) {
const services = await fetchServices({ page, pageSize });
if (!services.length) {
return false;
}
if (services.some((s) => s.active && s.provider === provider && s.name === name)) {
return true;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/lib/contract.js` around lines 424 - 435, The duplicate-check
logic in activeServiceExistsByName currently matches only provider and name, so
inactive rows still block registration. Update the predicate inside
activeServiceExistsByName to also require s.active when scanning the
listServices results, and keep the paging loop behavior unchanged so only active
services prevent registerServiceOnChain.

@clintjeff2

Copy link
Copy Markdown
Contributor Author

@ritik4ever , please review and merge. Assign more issues and Allow a rating for me as well please.

@ritik4ever ritik4ever left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm!

@ritik4ever ritik4ever merged commit 04e83e5 into Stellar-Ecosystem:main Jun 29, 2026
5 checks passed
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.

Add seed.js idempotency check per service name, not just total count

2 participants