Skip to content

Commit 92e2467

Browse files
authored
Merge pull request #599 from Rampop01/fix-governance-events-and-migration-lock
2 parents 9d27a98 + df61eb5 commit 92e2467

11 files changed

Lines changed: 1662 additions & 1657 deletions

File tree

PR_DESCRIPTION.md

Lines changed: 81 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,81 @@
1-
# Fix frontend debug logging, accessibility, dead code, and backend pagination
2-
3-
## Summary
4-
5-
This PR addresses four frontend and backend issues:
6-
- Removes debug console logging from SSE event handling and stream-detail page
7-
- Removes unused `users` query parameter that the backend ignores
8-
- Adds accessible ARIA label to the top-up amount input field
9-
- Removes dead legacy Dashboard component
10-
- Adds `page` parameter support to the stream events endpoint for consistent pagination
11-
12-
## Fixes
13-
14-
Closes #509
15-
Closes #512
16-
Closes #514
17-
Closes #517
18-
19-
## Changes
20-
21-
### Frontend (fix/issues-509-512-514-517)
22-
23-
**Issue #509: Remove debug console logging**
24-
- Removed `console.log('SSE connected:', data.clientId)` from useStreamEvents hook
25-
- Removed `console.error()` calls from SSE message and event parsing
26-
- Removed `console.log()` call from reconnect retry logic
27-
- Removed debug logging from stream-detail event handler
28-
- Simplified onmessage handler which wasn't being used for event processing
29-
30-
**Issue #512: Add accessible label to top-up input**
31-
- Added `aria-label="Top-up amount"` to the number input field on stream detail page
32-
- Maintains existing placeholder as a visual hint, not the sole accessible label
33-
- No visual changes to the UI
34-
35-
**Issue #514: Remove dead components**
36-
- Deleted `frontend/src/components/Dashboard.tsx` which contained hardcoded mock stream data
37-
- This was a legacy component not used by the application (live dashboard is at `app/dashboard/page.tsx`)
38-
- Kept `Progressbar.tsx` and `Livecounter.tsx` as they are actively used in the stream-detail page
39-
40-
### Backend (fix/issues-509-512-514-517)
41-
42-
**Issue #517: Add page parameter support**
43-
- Updated `getStreamEvents` controller to accept optional `page` query parameter
44-
- Maps 1-based page number to offset: `offset = (page - 1) * limit`
45-
- Page parameter is only used when `offset` and `cursor` are not provided
46-
- Maintains full backward compatibility with existing offset/cursor-based pagination
47-
- Mirrors the behavior of the sibling `/v1/events` endpoint
48-
49-
## Test Plan
50-
51-
- [ ] Frontend builds successfully: `cd frontend && npm run build`
52-
- [ ] Backend builds successfully: `cd backend && npm run build`
53-
- [ ] Frontend lint passes: `cd frontend && npm run lint`
54-
- [ ] Backend tests pass: `cd backend && npm run test`
55-
- Manually verify:
56-
- [ ] Stream detail page loads without console errors
57-
- [ ] SSE events update stream state without debug logs in browser console
58-
- [ ] Top-up input is properly labeled in accessibility tools (e.g., browser dev tools, screen readers)
59-
- [ ] Stream event pagination works with page parameter (e.g., `/v1/streams/123/events?page=2&limit=10`)
60-
- [ ] Backward compatibility: offset/cursor-based pagination still works
61-
62-
## Architecture Notes
63-
64-
- No schema or API contract changes (page parameter is additive)
65-
- User-scoped events continue to arrive via server-side user subscription (via authenticated public key)
66-
- Removed unused `users` parameter reduces noise in URL construction and server processing
67-
68-
## Branch
69-
70-
`fix/issues-509-512-514-517`
71-
72-
Push ready. Manual PR creation needed.
1+
## Description
2+
This PR resolves two backend issues:
3+
1. **Issue #524 [Backend] Indexer ignores fee_config_updated and admin_transferred governance events**: Added indexing and SSE broadcasting for `fee_config_updated` and `admin_transferred` contract governance events. Used an elegant, referentially safe `streamId = 0` system stream fallback to preserve Prisma schema integrity and foreign-key constraints.
4+
2. **Issue #525 [Backend] migration_lock.toml declares sqlite but the datasource is postgresql**: Fixed a production-blocking validation mismatch in Prisma by changing `provider` from `"sqlite"` to `"postgresql"` in `migration_lock.toml`.
5+
6+
## Type of Change
7+
<!-- Mark the relevant option with an 'x' -->
8+
9+
- [x] 🐛 Bug fix (non-breaking change which fixes an issue)
10+
- [x] ✨ New feature (non-breaking change which adds functionality)
11+
- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
12+
- [ ] 📚 Documentation update
13+
- [ ] 🔧 Refactoring (no functional changes)
14+
- [ ] ⚡ Performance improvement
15+
- [x] 🧪 Test addition or update
16+
17+
## Related Issues
18+
<!-- Link related issues using keywords like "Closes", "Fixes", "Resolves" -->
19+
<!-- Example: Closes #123, Fixes #456 -->
20+
21+
Closes #524
22+
Closes #525
23+
24+
## Changes Made
25+
- **Database Configuration**:
26+
- Updated `provider` in `backend/prisma/migrations/migration_lock.toml` to `"postgresql"`.
27+
- **Worker Indexer (`soroban-event-worker.ts`)**:
28+
- Added `decodeU32` helper function to decode `u32` event values.
29+
- Implemented `ensureSystemStream` to upsert a system user and stream with `streamId = 0` during transaction callbacks to prevent database foreign-key constraint violations when writing protocol-level events.
30+
- Modified `processEvent` to allow single-topic events (`topic.length === 1`) for the two new protocol events while keeping the `streamId` requirement for per-stream events.
31+
- Implemented `handleFeeConfigUpdated` and `handleAdminTransferred` handlers to write records of types `FEE_CONFIG_UPDATED` and `ADMIN_TRANSFERRED` and broadcast payloads over the SSE admin channels `stream.fee_config_updated` and `stream.admin_transferred`.
32+
- **Classification Lists**:
33+
- Added `FEE_CONFIG_UPDATED` and `ADMIN_TRANSFERRED` to allowed list arrays in `events.routes.ts` (`EVENT_TYPES`) and `stream.controller.ts` (`validEventTypes`).
34+
- **Syntax and Compile Fixes**:
35+
- Resolved a pre-existing syntax error (missing arrow function opening brace `{`) in `backend/tests/integration/stream-actions.test.ts` to allow the test runner to compile all files.
36+
37+
## Testing
38+
<!-- Describe the tests you ran and how to verify your changes -->
39+
40+
### Test Coverage
41+
- [x] Unit tests added/updated
42+
- [x] Integration tests added/updated
43+
- [x] Manual testing performed
44+
45+
### Test Steps
46+
<!-- If applicable, provide steps to test the changes -->
47+
1. Run mocked event worker unit tests validating governance event decoding, database upserts, and SSE broadcast trigger:
48+
```bash
49+
npx vitest run tests/soroban-event-worker.test.ts
50+
```
51+
2. Run database-mocked integration tests asserting the event lifecycle:
52+
```bash
53+
DATABASE_URL=postgresql://localhost/flowfi npx vitest run src/__tests__/integration/streams.test.ts
54+
```
55+
3. Verify that the Prisma migrate status check successfully resolves schema and migration lock providers without crashing:
56+
```bash
57+
DATABASE_URL=postgresql://localhost/flowfi npx prisma migrate status
58+
```
59+
60+
## Breaking Changes
61+
<!-- If this PR includes breaking changes, describe them here -->
62+
<!-- If none, you can remove this section -->
63+
64+
## Screenshots/Demo
65+
<!-- If applicable, add screenshots or a link to a demo -->
66+
67+
## Checklist
68+
<!-- Mark completed items with an 'x' -->
69+
70+
- [x] My code follows the project's style guidelines
71+
- [x] I have performed a self-review of my own code
72+
- [x] I have commented my code, particularly in hard-to-understand areas
73+
- [x] I have made corresponding changes to the documentation
74+
- [x] My changes generate no new warnings
75+
- [x] I have added tests that prove my fix is effective or that my feature works
76+
- [x] New and existing unit tests pass locally with my changes
77+
- [x] Any dependent changes have been merged and published
78+
- [x] I have checked for breaking changes and documented them if applicable
79+
80+
## Additional Notes
81+
<!-- Any additional information that reviewers should know -->

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@types/supertest": "^7.2.0",
4747
"@types/swagger-jsdoc": "^6.0.4",
4848
"@types/swagger-ui-express": "^4.1.6",
49+
"@vitest/coverage-v8": "^2.1.8",
4950
"eventsource": "^2.0.2",
5051
"nodemon": "^3.1.11",
5152
"prisma": "^7.4.1",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Please do not edit this file manually
22
# It should be added in your version-control system (e.g., Git)
3-
provider = "sqlite"
3+
provider = "postgresql"

backend/src/__tests__/integration/streams.test.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const { mockPrisma, mockSseService } = vi.hoisted(() => ({
99
addClient: vi.fn(),
1010
broadcastToStream: vi.fn(),
1111
broadcastToUser: vi.fn(),
12+
broadcastToAdmin: vi.fn(),
1213
},
1314
mockPrisma: {
1415
stream: {
@@ -243,4 +244,127 @@ describeIfDatabase('Stream Lifecycle Integration Tests', () => {
243244
expect(Array.isArray(res.body.data)).toBe(true);
244245
expect(res.body.data.length).toBe(1);
245246
});
247+
248+
it('Indexer processes fee_config_updated -> persists FEE_CONFIG_UPDATED record and broadcasts SSE', async () => {
249+
const adminKey = Keypair.random().publicKey();
250+
const oldTreasury = Keypair.random().publicKey();
251+
const newTreasury = Keypair.random().publicKey();
252+
253+
const event = {
254+
id: 'fee-config-event-1',
255+
txHash: 'hash-fee-config',
256+
ledger: 105,
257+
inSuccessfulContractCall: true,
258+
topic: [
259+
xdr.ScVal.scvSymbol('fee_config_updated'),
260+
],
261+
value: xdr.ScVal.scvMap([
262+
new xdr.ScMapEntry({
263+
key: xdr.ScVal.scvSymbol('admin'),
264+
val: nativeToScVal(adminKey, { type: 'address' }),
265+
}),
266+
new xdr.ScMapEntry({
267+
key: xdr.ScVal.scvSymbol('old_treasury'),
268+
val: nativeToScVal(oldTreasury, { type: 'address' }),
269+
}),
270+
new xdr.ScMapEntry({
271+
key: xdr.ScVal.scvSymbol('new_treasury'),
272+
val: nativeToScVal(newTreasury, { type: 'address' }),
273+
}),
274+
new xdr.ScMapEntry({
275+
key: xdr.ScVal.scvSymbol('old_fee_rate_bps'),
276+
val: nativeToScVal(100, { type: 'u32' }),
277+
}),
278+
new xdr.ScMapEntry({
279+
key: xdr.ScVal.scvSymbol('new_fee_rate_bps'),
280+
val: nativeToScVal(200, { type: 'u32' }),
281+
}),
282+
]),
283+
} as any;
284+
285+
await sorobanEventWorker.processEvent(event);
286+
287+
expect(mockPrisma.user.upsert).toHaveBeenCalledWith(
288+
expect.objectContaining({
289+
where: { publicKey: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF' },
290+
})
291+
);
292+
293+
expect(mockPrisma.stream.upsert).toHaveBeenCalledWith(
294+
expect.objectContaining({
295+
where: { streamId: 0 },
296+
})
297+
);
298+
299+
expect(mockPrisma.streamEvent.upsert).toHaveBeenCalledWith(
300+
expect.objectContaining({
301+
where: { transactionHash_eventType: { transactionHash: 'hash-fee-config', eventType: 'FEE_CONFIG_UPDATED' } },
302+
create: expect.objectContaining({
303+
streamId: 0,
304+
eventType: 'FEE_CONFIG_UPDATED',
305+
transactionHash: 'hash-fee-config',
306+
ledgerSequence: 105,
307+
}),
308+
})
309+
);
310+
311+
expect(mockSseService.broadcastToAdmin).toHaveBeenCalledWith(
312+
'stream.fee_config_updated',
313+
expect.objectContaining({
314+
admin: adminKey,
315+
oldTreasury,
316+
newTreasury,
317+
oldFeeRateBps: 100,
318+
newFeeRateBps: 200,
319+
})
320+
);
321+
});
322+
323+
it('Indexer processes admin_transferred -> persists ADMIN_TRANSFERRED record and broadcasts SSE', async () => {
324+
const previousAdmin = Keypair.random().publicKey();
325+
const newAdmin = Keypair.random().publicKey();
326+
327+
const event = {
328+
id: 'admin-transfer-event-1',
329+
txHash: 'hash-admin-transfer',
330+
ledger: 106,
331+
inSuccessfulContractCall: true,
332+
topic: [
333+
xdr.ScVal.scvSymbol('admin_transferred'),
334+
],
335+
value: xdr.ScVal.scvMap([
336+
new xdr.ScMapEntry({
337+
key: xdr.ScVal.scvSymbol('previous_admin'),
338+
val: nativeToScVal(previousAdmin, { type: 'address' }),
339+
}),
340+
new xdr.ScMapEntry({
341+
key: xdr.ScVal.scvSymbol('new_admin'),
342+
val: nativeToScVal(newAdmin, { type: 'address' }),
343+
}),
344+
]),
345+
} as any;
346+
347+
await sorobanEventWorker.processEvent(event);
348+
349+
expect(mockPrisma.streamEvent.upsert).toHaveBeenCalledWith(
350+
expect.objectContaining({
351+
where: { transactionHash_eventType: { transactionHash: 'hash-admin-transfer', eventType: 'ADMIN_TRANSFERRED' } },
352+
create: expect.objectContaining({
353+
streamId: 0,
354+
eventType: 'ADMIN_TRANSFERRED',
355+
transactionHash: 'hash-admin-transfer',
356+
ledgerSequence: 106,
357+
}),
358+
})
359+
);
360+
361+
expect(mockSseService.broadcastToAdmin).toHaveBeenCalledWith(
362+
'stream.admin_transferred',
363+
expect.objectContaining({
364+
previousAdmin,
365+
newAdmin,
366+
})
367+
);
368+
});
246369
});
370+

backend/src/controllers/stream.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ export const getStreamEvents = async (req: Request, res: Response) => {
294294

295295
const whereClause: any = { streamId: parsedStreamId };
296296
if (eventType) {
297-
const validEventTypes = ['CREATED', 'TOPPED_UP', 'WITHDRAWN', 'CANCELLED', 'COMPLETED', 'PAUSED', 'RESUMED', 'FEE_COLLECTED'];
297+
const validEventTypes = ['CREATED', 'TOPPED_UP', 'WITHDRAWN', 'CANCELLED', 'COMPLETED', 'PAUSED', 'RESUMED', 'FEE_COLLECTED', 'FEE_CONFIG_UPDATED', 'ADMIN_TRANSFERRED'];
298298
if (!validEventTypes.includes(eventType)) {
299299
return res.status(400).json({
300300
error: 'Invalid eventType parameter',

backend/src/routes/v1/events.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const EVENT_TYPES = new Set([
1717
'PAUSED',
1818
'RESUMED',
1919
'FEE_COLLECTED',
20+
'FEE_CONFIG_UPDATED',
21+
'ADMIN_TRANSFERRED',
2022
]);
2123

2224
const MAX_EVENT_LIMIT = 200;

0 commit comments

Comments
 (0)