Skip to content

Conversation

@aditya1702
Copy link
Contributor

@aditya1702 aditya1702 commented Aug 27, 2025

What

Major GraphQL API improvements focusing on security, performance, and maintainability:

  1. Query Complexity Analysis & Limiting: Implements GraphQL query complexity calculation and logging to prevent DoS attacks through overly complex queries (ref:
    https://gqlgen.com/reference/complexity/)
  2. State Change Schema Refactoring: Converts from monolithic StateChange type to extensible BaseStateChange interface with concrete implementations, enabling polymorphic state change models
  3. Performance Optimizations:
    - Introduces AccountModel.BatchGetByStateChangeIDs for efficient batch loading to eliminate N+1 queries
    - Refactors pagination logic with baseStateChangeWithCursor for consistency across resolvers
  4. Code Quality Improvements: Adds shared resolver helper functions for common patterns (nullable strings, JSONB fields, arrays) and breaks down large GraphQL structs into focused objects

The state change query is now converted to the following design:

stateChanges(first: 1) {
				edges {
					node {
						stateChangeCategory
						ingestedAt
						ledgerCreatedAt
						ledgerNumber
						
						... on StandardBalanceChange {
							amount
							tokenId
						}
						... on ReservesChange {
							stateChangeReason
							sponsoredAccountId
							sponsorAccountId
						}
						... on SignerChange {
							stateChangeReason
							signerAccountId
							signerWeights
						}
						... on SignatureThresholdsChange {
							stateChangeReason
							thresholds
						}
						... on FlagsChange {
							stateChangeReason
							flags
						}
						... on MetadataChange {
							stateChangeReason
							keyValue
						}
						... on BalanceAuthorizationChange {
							stateChangeReason
							flags
						}
					}
				}
}

Why

  • Security: GraphQL's flexible query structure can be exploited for DoS attacks through deeply nested or complex queries. Query complexity limits provide essential protection.
  • Performance: The existing state change resolvers suffered from N+1 query problems when loading associated accounts. Batch loading addresses this scalability issue.
  • Maintainability: The monolithic state change approach made extending functionality difficult. The interface pattern provides better extensibility and type safety.
  • Code Quality: Duplicated resolver logic across state change types created maintenance overhead and inconsistency risks.

Issue that this PR addresses

#250 #305

@aditya1702 aditya1702 changed the base branch from main to graphql-main August 27, 2025 20:32
Introduces shared resolver helper functions for common field resolution patterns in state change GraphQL resolvers, reducing duplication and improving maintainability. Updates all state change resolvers to use these helpers for nullable/required strings, JSONB fields, and arrays. Also updates account resolution logic to use accountID directly and makes minor import and type declaration cleanups.
Updated StateChangeEdge's node field to use the BaseStateChange interface instead of the StateChange type in GraphQL schema, models, and resolvers. Added type conversion logic to resolve the correct concrete type for BaseStateChange based on StateChangeCategory. This improves extensibility and supports polymorphic state change models in the API.
Introduces baseStateChangeWithCursor and refactors pagination logic to use converted state changes for account, operation, transaction, and query resolvers. Updates test assertions to use a new extractStateChangeIDs helper for cursor extraction, improving type safety and consistency across resolver and test code.
@aditya1702 aditya1702 changed the title Limit query depth by calculating the complexity Limit query depth and convert state change schema to interface Aug 28, 2025
Introduces AccountModel.BatchGetByStateChangeIDs and supporting types to efficiently batch-load accounts associated with state changes. Updates GraphQL dataloaders and resolvers to use this method, reducing N+1 queries for state change account resolution. Includes tests for the new batch loading logic.
@aditya1702 aditya1702 marked this pull request as ready for review August 29, 2025 15:43
Copy link
Contributor

@JakeUrban JakeUrban left a comment

Choose a reason for hiding this comment

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

I just dug into the schema to start and will follow up with a review of the implementation once we align on any schema changes. Super excited to see this!

start := time.Now()
err := m.DB.SelectContext(ctx, &accountsWithStateChanges, query)
duration := time.Since(start).Seconds()
m.MetricsService.ObserveDBQueryDuration("SELECT", "accounts", duration)
Copy link
Contributor

Choose a reason for hiding this comment

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

metric collection can be pulled out into a helper/util

Refactors GraphQL schema, Go models, and resolvers to rename PaymentStateChange to BalanceStateChange and SignatureThresholdsStateChange to SignerThresholdsStateChange. Updates related field names for consistency (e.g., spenderAccountId to spenderAddress, signerAccountId to signerAddress, sponsorAccountId to sponsorAddress) and adjusts type mappings in gqlgen.yml and generated code accordingly.
Replaces 'stateChangeCategory' with 'type' and 'stateChangeReason' with 'reason' in GraphQL schema and resolvers for all StateChange types. Updates Go model methods and generated code to match new field names, ensuring consistency and explicit resolver usage for these fields.
@socket-security
Copy link

socket-security bot commented Sep 10, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedgithub.com/​basemachina/​gqlgen-complexity-reporter@​v0.1.2100100100100100

View full report

Removed unused LiabilityStateChange and AllowanceStateChange models and related GraphQL types. Added AccountStateChange, TrustlineStateChange, and BalanceAuthorizationStateChange models and schema types. Updated enums and resolvers to match new state change categories and reasons, improving clarity and maintainability of state change handling.
Updated usage of StateChangeReason in test utilities to use variables instead of pointers to constants. Refactored resolver type definitions in statechange.resolvers.go to use a type group for improved readability. Fixed test cases to use StateChangeCategoryBalance instead of StateChangeCategoryCredit. Adjusted extractStateChangeIDs to handle correct model types.
Introduced StateChangeCursorGetter interface and implemented GetCursor method for StateChange. Updated extractStateChangeIDs to use the interface, reducing repetitive type assertions and improving code maintainability.
Renamed SponsorshipStateChange to ReservesStateChange across GraphQL schema, types, and resolvers for clarity. Updated state change category and reasons to use 'RESERVES', 'SPONSOR', and 'UNSPONSOR'. Refactored effect processor logic to create separate state changes for sponsoring and sponsored accounts, improving accuracy and maintainability.
Removed unused targetChange from processSponsorshipEffect and adjusted the return value to only include sponsorChanges. Updated tests to expect the correct number and type of state changes for sponsorship updates, reflecting the new logic.
Refactors GraphQL schema and resolvers to replace SponsorshipStateChange with ReservesStateChange, updating all related types, interfaces, and field resolvers for clarity and consistency. Also updates the effects processor to remove unused parameters from processSponsorshipEffect.
Replaces panic placeholders with actual resolver implementations for various state change models in statechange.resolvers.go. Removes unused resolveStringArray helper from resolver.go. This enables proper GraphQL responses for state change queries.
Renames various GraphQL types and resolvers related to state changes for improved clarity and consistency. For example, BalanceStateChange becomes StandardBalanceChange, AccountStateChange becomes AccountChange, and similar updates are made for other state change types across gqlgen.yml, Go models, resolvers, tests, and the GraphQL schema.
@aditya1702 aditya1702 merged commit 05da5d6 into graphql-main Sep 23, 2025
8 checks passed
@aditya1702 aditya1702 deleted the query-depth branch September 23, 2025 18:55
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.

4 participants