Skip to content

Backup & Restore#404

Merged
cwilvx merged 71 commits intotestnetfrom
backup-restore
Aug 4, 2025
Merged

Backup & Restore#404
cwilvx merged 71 commits intotestnetfrom
backup-restore

Conversation

@cwilvx
Copy link
Contributor

@cwilvx cwilvx commented Aug 4, 2025

Summary by CodeRabbit

  • New Features

    • Introduced IP-based rate limiting middleware with configurable limits, blocking, and unblocking capabilities.
    • Added Twitter bot detection and a new points category for following the official Twitter account.
    • Added protected API endpoints requiring sudo keypair authentication, including campaign data retrieval and IP unblocking.
    • Expanded blockchain support by adding new Ethereum-compatible chain IDs.
    • Added comprehensive campaign data reporting aggregating user and points statistics.
    • Enhanced backup and restore processes with environment-based configuration and improved error handling.
  • Improvements

    • Reduced point values for linking social accounts and referrals; added demos follow points.
    • Streamlined cryptographic identity management using mnemonic-based seed generation.
    • Added new user account fields to track flagged status, reasons, review state, and timestamps.
    • Strengthened blockchain synchronization with genesis block hash verification and improved account restoration.
    • Refined server middleware architecture for improved request handling and extensibility.
    • Enhanced transaction verification by filtering invalid or testnet connections.
  • Bug Fixes

    • Validated identity transaction sender matches the origin address to prevent mismatches.
  • Documentation

    • Added detailed documentation for protected API endpoints and authentication procedures.

cwilvx added 30 commits April 17, 2025 23:39
- dump gcr tables into output/ as json
- load dumps into genesis
- add /genesis GET to retrieve node genesis
- update run file to cordinate dumps
+ reset faucets
+ check if genesis hash match in fastSync
+ use mnemonic instead of ed25519 private key in demos_identity file
+ add GET /genesis to expose node genesis data
+ export restore flag to skip auto restores
+ add nodeCall to get campaign data
- add rate limit middleware for all requests
- add identity tx rate limit handler
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (27)
sdk/localsdk/multichain/configs/chainIds.ts (1)

9-12: Consider the implications of duplicate chain ID mappings.

The past review comment about duplicate chain IDs is still valid. These new entries duplicate existing mainnet values and create maintenance overhead.

src/index.ts (1)

244-244: Wrap loadIdentity() call in try-catch for error handling

The loadIdentity() call should be wrapped in a try-catch block to handle potential filesystem or seed derivation errors gracefully.

src/libs/blockchain/routines/findGenesisBlock.ts (1)

17-37: Add directory existence check before reading files

The function attempts to read from the "output/" directory without verifying it exists, which could cause runtime errors.

src/libs/network/manageNodeCall.ts (1)

258-357: Remove commented code block

This large block of commented code for protected endpoints should be removed and tracked through issues or documentation instead.

src/libs/blockchain/routines/Sync.ts (1)

229-229: Replace process.exit(1) with thrown errors for better error handling

This issue was already raised in a previous review. Using process.exit(1) prevents graceful shutdown and makes the code harder to test.

Also applies to: 238-238, 246-246

src/utilities/backupAndRestore.ts (1)

27-27: Fix type mismatch for PostgreSQL port configuration

This issue was already raised in a previous review. process.env.PG_PORT returns a string, but the port property expects a number.

src/libs/blockchain/gcr/gcr_routines/identityManager.ts (1)

106-119: Redundant chain validation and potential undefined access

The check at line 114-119 is redundant since line 106 already validates the subchain exists. Also, accessing chainIds.eth[subchain] without checking if it exists first could cause issues.

src/utilities/sharedState.ts (1)

217-223: Consider making whitelisted IPs configurable

The whitelisted IPs are hardcoded in the source code. These should be loaded from environment variables or a configuration file for better flexibility and security.

src/features/incentive/PointSystem.ts (1)

221-236: Add null safety checks for Twitter identity access

The code accesses account.identities.web2["twitter"] without checking if the path exists.

run (3)

140-147: Add error handling for folder operations

The unique folder approach is good, but needs error handling.


169-172: Container name should match the folder-based approach

The function uses a fixed container name pattern that might not match the actual container.


236-247: Add error handling for directory changes

The cd commands need error handling to prevent continuing in wrong directory.

src/libs/identity/identity.ts (2)

112-124: Improve mnemonic validation and seed derivation

The current implementation has several issues:

  1. Only validates word count divisibility by 3, not actual BIP39 validity
  2. Converting hash to hex string then encoding as UTF-8 is inefficient and unusual
  3. Consider returning the actual seed bytes instead of encoded hex string

132-148: Critical: File content mismatch - saving seed but reading mnemonic

The code reads the file expecting a mnemonic string (line 137) but writes the masterSeed Uint8Array (line 143). This will fail on subsequent loads.

src/libs/network/server_rpc.ts (4)

145-150: Move protected endpoints Set to module scope for better performance

The protectedEndPoints Set is recreated on every request. Consider moving it to module scope as a constant to avoid unnecessary object creation.


256-273: Improve input validation and response clarity for rate limit unblock

The endpoint should validate that array elements are strings and provide clear success/failure feedback per IP.


352-361: Add error handling for genesis block retrieval

The genesis endpoint should handle potential errors when retrieving the genesis block.


448-451: Critical: Remove process.exit(1) - will crash the server

Calling process.exit(1) when IP data is not found will terminate the entire server process. This is a critical vulnerability that could be exploited for denial-of-service attacks.

src/libs/blockchain/gcr/gcr.ts (2)

538-539: Document or extract the magic number for balance threshold

The balance threshold 1000000000000 should be documented or extracted as a named constant to clarify its purpose and make it configurable.


589-598: Fix incorrect logic for counting users who follow demos

The current logic incorrectly counts users with demos follow points instead of tracking unique users. Also, there's unsafe property access that could cause runtime errors.

src/libs/network/middleware/rateLimiter.ts (3)

42-46: Fix cleanup interval timing and comment

The comment says "5 minutes" but the interval is set to 100 seconds. Also, consider the async file operations suggestion.


83-93: Use async file operations to prevent blocking

The synchronous writeFileSync could block the event loop under high load.


205-212: IPv6 validation regex is incomplete

The current IPv6 regex doesn't handle compressed notation (e.g., ::1, 2001:db8::8a2e:370:7334).

src/libs/blockchain/routines/beforeFindGenesisHooks.ts (3)

20-71: Critical: Remove process.exit(0) from maintenance routine

Calling process.exit(0) at line 70 will terminate the entire application. This is inappropriate for a maintenance routine that might be called during normal operation.


138-144: Improve error handling for API rate limits

The current 1-second delay might be insufficient for API rate limiting. Consider implementing exponential backoff and handling specific error types.


237-249: Fix incorrect flagging logic for Solana transactions

The logic sets flaggedReason = "only_evm_no_tx" when Solana has transactions (txcount > 0), which appears to be incorrect. The flag name suggests it should be set when EVM has no transactions, not when Solana has transactions.

src/libs/identity/tools/twitter.ts (1)

556-575: Fix return type mismatch in error handling

The method returns undefined on error but is typed to return Promise<boolean>. This type mismatch could cause runtime errors.

🧹 Nitpick comments (4)
src/libs/abstraction/index.ts (1)

34-54: Consider performance impact and improve error messaging

The bot detection logic is well-placed before signature verification, but consider:

  1. Performance: The checkIsBot method makes multiple API calls (timeline and followers data) which could introduce latency. Consider implementing caching or making this check asynchronous if it becomes a bottleneck.

  2. Error message clarity: The message "You cannot connect this Twitter/X account" when a bot is detected could be more specific to help users understand why their account was rejected.

Consider this improvement for the error message:

         if (isBot) {
             return {
                 success: false,
-                message: "You cannot connect this Twitter/X account",
+                message: "This Twitter/X account appears to be automated and cannot be connected",
             }
         }
src/libs/blockchain/gcr/handleGCR.ts (1)

470-490: Good input validation and flexible initialization

The enhanced createAccount method properly validates the public key and provides flexible initialization through the fillData parameter. The validation checks for null, type, and empty strings which prevents invalid account creation.

Consider making the error message more specific:

         if (
             !pubkey ||
             typeof pubkey !== "string" ||
             pubkey.trim().length === 0
         ) {
-            throw new Error("Invalid public key provided")
+            throw new Error("Invalid public key: must be a non-empty string")
         }
src/libs/identity/tools/twitter.ts (2)

10-18: Remove redundant property assignments in constructor

The constructor parameters are already assigned via TypeScript's parameter properties. The explicit assignments on lines 16-17 are redundant.

-    constructor(
-        private timelineData: TwitterTimelineResponse,
-        private followersData: TwitterFollowersResponse,
-        private username: string,
-    ) {
-        this.timelineData = timelineData
-        this.followersData = followersData
-    }
+    constructor(
+        private timelineData: TwitterTimelineResponse,
+        private followersData: TwitterFollowersResponse,
+        private username: string,
+    ) {}

385-404: Consider extracting quota patterns as a class constant

The quota patterns array is recreated on each call. Consider making it a static class property for better performance and maintainability.

+    private static readonly QUOTA_PATTERNS = [
+        /exceeded.*quota/i,
+        /quota.*limit.*reached/i,
+        /check.*plan.*billing/i,
+        /api.*limit.*exceeded/i,
+        /too many requests/i,
+        /billing.*details/i,
+        /current.*quota.*exceeded/i,
+        /rate.*limit.*exceeded/i,
+    ]
+
     private checkQuotaLimitMessages(): number {
-        const quotaPatterns = [
-            /exceeded.*quota/i,
-            /quota.*limit.*reached/i,
-            /check.*plan.*billing/i,
-            /api.*limit.*exceeded/i,
-            /too many requests/i,
-            /billing.*details/i,
-            /current.*quota.*exceeded/i,
-            /rate.*limit.*exceeded/i,
-        ]

         for (const tweet of this.timelineData.timeline) {
             const text = tweet.text.toLowerCase()
-            if (quotaPatterns.some(pattern => pattern.test(text))) {
+            if (TwitterBotDetector.QUOTA_PATTERNS.some(pattern => pattern.test(text))) {
                 return 1000 // Definitive bot
             }
         }
         return 0
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1bb2ef6 and ea9c8f8.

📒 Files selected for processing (30)
  • .gitignore (2 hunks)
  • data/genesis.json (1 hunks)
  • documentation/protected-endpoints.md (1 hunks)
  • package.json (2 hunks)
  • run (5 hunks)
  • sdk/localsdk/multichain/configs/chainIds.ts (1 hunks)
  • src/features/incentive/PointSystem.ts (6 hunks)
  • src/features/incentive/referrals.ts (1 hunks)
  • src/index.ts (1 hunks)
  • src/libs/abstraction/index.ts (2 hunks)
  • src/libs/blockchain/chain.ts (4 hunks)
  • src/libs/blockchain/gcr/gcr.ts (3 hunks)
  • src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (4 hunks)
  • src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts (1 hunks)
  • src/libs/blockchain/gcr/gcr_routines/identityManager.ts (3 hunks)
  • src/libs/blockchain/gcr/handleGCR.ts (2 hunks)
  • src/libs/blockchain/routines/Sync.ts (3 hunks)
  • src/libs/blockchain/routines/beforeFindGenesisHooks.ts (1 hunks)
  • src/libs/blockchain/routines/findGenesisBlock.ts (1 hunks)
  • src/libs/identity/identity.ts (3 hunks)
  • src/libs/identity/tools/crosschain.ts (1 hunks)
  • src/libs/identity/tools/twitter.ts (3 hunks)
  • src/libs/network/bunServer.ts (2 hunks)
  • src/libs/network/manageNodeCall.ts (4 hunks)
  • src/libs/network/middleware/rateLimiter.ts (1 hunks)
  • src/libs/network/routines/transactions/handleIdentityRequest.ts (1 hunks)
  • src/libs/network/server_rpc.ts (6 hunks)
  • src/model/entities/GCRv2/GCR_Main.ts (3 hunks)
  • src/utilities/backupAndRestore.ts (4 hunks)
  • src/utilities/sharedState.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (1)
src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts (1)
  • IncentiveManager (9-71)
src/libs/network/manageNodeCall.ts (1)
src/libs/blockchain/gcr/gcr_routines/ensureGCRForUser.ts (1)
  • ensureGCRForUser (8-33)
src/libs/network/routines/transactions/handleIdentityRequest.ts (1)
src/libs/blockchain/gcr/gcr_routines/identityManager.ts (1)
  • IdentityManager (52-326)
src/libs/blockchain/gcr/gcr_routines/identityManager.ts (1)
sdk/localsdk/multichain/configs/chainIds.ts (1)
  • chainIds (1-38)
src/libs/abstraction/index.ts (1)
src/libs/identity/tools/twitter.ts (1)
  • Twitter (407-584)
src/libs/blockchain/gcr/handleGCR.ts (2)
src/model/datasource.ts (1)
  • dataSource (26-51)
src/features/incentive/referrals.ts (1)
  • Referrals (6-237)
src/libs/identity/identity.ts (1)
src/utilities/sharedState.ts (1)
  • getSharedState (261-263)
src/libs/network/middleware/rateLimiter.ts (3)
src/utilities/logger.ts (1)
  • error (125-132)
src/libs/network/bunServer.ts (1)
  • Middleware (6-10)
src/utilities/sharedState.ts (1)
  • getSharedState (261-263)
🔇 Additional comments (19)
.gitignore (1)

33-33: LGTM!

The additions to .gitignore appropriately exclude runtime-generated files (Twitter data and blocked IPs list) from version control.

Also applies to: 109-109

data/genesis.json (1)

12-33: LGTM!

The genesis addresses have been updated with properly formatted hex addresses. Balance values remain unchanged.

package.json (1)

25-25: LGTM!

The script rename to "restore" better reflects its purpose, and the SDK version upgrade (2.2.70 → 2.3.4) is a backward-compatible minor version bump.

Also applies to: 53-53

src/features/incentive/referrals.ts (1)

7-8: Significant reduction in referral bonuses.

The referral bonuses have been substantially reduced: referrer bonus from 3 to 2 points (-33%) and referred user bonus from 3 to 1 point (-67%). This aligns with the broader incentive system adjustments mentioned in the AI summary.

Please verify that this reduction aligns with your business objectives and consider the potential impact on user acquisition through referrals.

src/libs/network/routines/transactions/handleIdentityRequest.ts (2)

57-62: LGTM! Enhanced security validation.

The addition of sender address validation ensures that the transaction's from_ed25519_address matches the provided sender parameter, preventing potential spoofing attacks or inconsistent request data.


71-71: LGTM! Parameter alignment with updated API.

The addition of the sender parameter aligns with the updated IdentityManager.verifyPayload method signature shown in the relevant code snippets, ensuring consistent identity verification across the codebase.

src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (3)

22-30: LGTM! Improved code formatting.

The destructuring has been reformatted for better readability, making the extracted properties more clearly visible.


231-235: LGTM! Correct parameter alignment.

The call to IncentiveManager.twitterLinked has been correctly updated to include the new required twitterUserId parameter (data.userId), aligning with the updated method signature shown in the relevant code snippets.


526-531: LGTM! Enhanced query formatting.

The .where clauses have been reformatted for improved readability, making the complex JSON queries easier to understand and maintain.

Also applies to: 548-555

src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts (1)

31-40: LGTM! Enhanced Twitter integration.

The method signature has been correctly updated to include the required twitterUserId parameter, enabling Twitter user identification for bot detection and follow verification as mentioned in the AI summary. The parameter is properly passed to the underlying awardTwitterPoints method.

documentation/protected-endpoints.md (1)

1-29: LGTM! Clear and comprehensive documentation.

The documentation effectively explains the protected endpoints functionality with practical TypeScript examples. The code samples are well-structured and demonstrate proper authentication flow using sudo keypairs for both the rate limiting and campaign data endpoints.

src/libs/blockchain/routines/findGenesisBlock.ts (1)

63-83: Well-structured recovery data merging

The recovery data merging logic correctly:

  1. Preserves existing balances in a temporary object
  2. Replaces users entirely with recovery data
  3. Merges recovery balances, overwriting existing ones
  4. Reconstructs the balances array from the merged object

This approach ensures recovery data takes precedence while maintaining any additional genesis balances.

src/libs/network/manageNodeCall.ts (1)

168-169: Good use of ensureGCRForUser for account creation

Replacing direct GCR status queries with ensureGCRForUser ensures that accounts are automatically created if they don't exist, improving the user experience by eliminating "account not found" errors for new addresses.

src/libs/blockchain/routines/Sync.ts (1)

502-514: Good optimization for integrity verification

Limiting the integrity check to the first sync attempt (fastSyncCount == 0) is a smart performance optimization, as the genesis block shouldn't change after initial verification.

src/libs/network/bunServer.ts (1)

54-74: Well-implemented middleware chain pattern

The chain-of-responsibility implementation is clean and follows established patterns. Building the chain from right to left ensures correct execution order.

src/libs/blockchain/chain.ts (2)

288-288: Good type safety improvement

Changing the return type from Promise<any> to Promise<Blocks> improves type safety and code clarity.


429-434: Proper support for backup data in genesis block

The addition of the extra field and initialization of encrypted_transactions_hashes properly supports storing recovery data in the genesis block.

src/utilities/backupAndRestore.ts (1)

48-59: Good error handling improvements

The addition of try-catch block for query execution with proper error logging and early return improves robustness.

src/model/entities/GCRv2/GCR_Main.ts (1)

36-36: Well-structured entity enhancements for incentive and moderation features

The additions properly support the new features:

  • demosFollow field enables tracking points for following the demos account
  • Flagging fields (flagged, flaggedReason, reviewed) support the moderation workflow with well-defined reason codes
  • Timestamp fields provide proper audit trail

Also applies to: 51-68

Comment on lines +499 to +503
for (const user of userAccounts) {
console.log("[GENESIS] Creating account: " + user.pubkey)
await HandleGCR.createAccount(user.pubkey, user)
console.log("[GENESIS] Account created: " + user.pubkey)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling and consider parallel account creation

The account creation loop lacks error handling and processes accounts sequentially, which could be slow for large datasets.

 // INFO: Create all users
-for (const user of userAccounts) {
-    console.log("[GENESIS] Creating account: " + user.pubkey)
-    await HandleGCR.createAccount(user.pubkey, user)
-    console.log("[GENESIS] Account created: " + user.pubkey)
-}
+const accountCreationPromises = userAccounts.map(async (user) => {
+    try {
+        console.log("[GENESIS] Creating account: " + user.pubkey)
+        await HandleGCR.createAccount(user.pubkey, user)
+        console.log("[GENESIS] Account created: " + user.pubkey)
+        return { success: true, pubkey: user.pubkey }
+    } catch (error) {
+        console.error(`[GENESIS] Failed to create account ${user.pubkey}:`, error)
+        return { success: false, pubkey: user.pubkey, error }
+    }
+})
+
+const results = await Promise.all(accountCreationPromises)
+const failedAccounts = results.filter(r => !r.success)
+
+if (failedAccounts.length > 0) {
+    throw new Error(`Failed to create ${failedAccounts.length} accounts during genesis`)
+}
📝 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
for (const user of userAccounts) {
console.log("[GENESIS] Creating account: " + user.pubkey)
await HandleGCR.createAccount(user.pubkey, user)
console.log("[GENESIS] Account created: " + user.pubkey)
}
// INFO: Create all users
const accountCreationPromises = userAccounts.map(async (user) => {
try {
console.log("[GENESIS] Creating account: " + user.pubkey)
await HandleGCR.createAccount(user.pubkey, user)
console.log("[GENESIS] Account created: " + user.pubkey)
return { success: true, pubkey: user.pubkey }
} catch (error) {
console.error(`[GENESIS] Failed to create account ${user.pubkey}:`, error)
return { success: false, pubkey: user.pubkey, error }
}
})
const results = await Promise.all(accountCreationPromises)
const failedAccounts = results.filter(r => !r.success)
if (failedAccounts.length > 0) {
throw new Error(`Failed to create ${failedAccounts.length} accounts during genesis`)
}
🤖 Prompt for AI Agents
In src/libs/blockchain/chain.ts around lines 499 to 503, the for-loop creating
accounts lacks error handling and runs sequentially, which can be inefficient
and fragile. Refactor the code to create accounts in parallel using Promise.all
or similar concurrency control, and wrap each account creation call in a
try-catch block to handle and log errors without stopping the entire process.

Comment on lines +87 to +105
static async countEthTransactionsByAddress(
address: string,
chainId: number,
): Promise<number> {
try {
const response = await this.getEthTransactionsByAddress(
address,
chainId,
)

return response.result.length
} catch (error) {
throw new Error(
`Failed to count transactions: ${
error instanceof Error ? error.message : String(error)
}`,
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Method returns page count, not total transaction count.

The countEthTransactionsByAddress method returns the number of transactions in a single page (limited by the default offset of 1), not the total transaction count for the address. This could be misleading for users expecting a complete count.

Consider either:

  1. Renaming the method to clarify it returns a page count
  2. Implementing pagination to get the total count
  3. Adding a parameter to control the page size
🤖 Prompt for AI Agents
In src/libs/identity/tools/crosschain.ts around lines 87 to 105, the method
countEthTransactionsByAddress currently returns only the number of transactions
in a single page due to the default offset limit, which can mislead users
expecting a total transaction count. To fix this, either rename the method to
indicate it returns a page count, or modify the implementation to handle
pagination and aggregate counts across all pages, or add a parameter to control
the page size so users can specify how many transactions to count per request.

Comment on lines +140 to +154
if (response.status === 404) {
return []
}

return response.data as SolanaTransactionResponse
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(
`Failed to fetch Solana transactions: ${error.message}`,
)
}

if (error instanceof AxiosError && error.response?.status === 404) {
return []
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove redundant 404 error handling.

The 404 check after the successful response (lines 140-142) is unreachable because axios throws an error for 4xx status codes. The actual 404 handling in the catch block (lines 152-154) is sufficient.

Remove the redundant check:

-            if (response.status === 404) {
-                return []
-            }
-
             return response.data as SolanaTransactionResponse
📝 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
if (response.status === 404) {
return []
}
return response.data as SolanaTransactionResponse
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(
`Failed to fetch Solana transactions: ${error.message}`,
)
}
if (error instanceof AxiosError && error.response?.status === 404) {
return []
}
return response.data as SolanaTransactionResponse
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(
`Failed to fetch Solana transactions: ${error.message}`,
)
}
if (error instanceof AxiosError && error.response?.status === 404) {
return []
}
🤖 Prompt for AI Agents
In src/libs/identity/tools/crosschain.ts between lines 140 and 154, remove the
redundant 404 status check that occurs after the response is received (lines
140-142) because axios throws an error for 4xx responses, making that check
unreachable. Keep only the 404 handling inside the catch block (lines 152-154)
to properly handle not found errors.

Comment on lines +169 to +184
static async countSolanaTransactionsByAddress(
address: string,
): Promise<number> {
try {
const response = await this.getSolanaTransactionsByAddress(address)
return Array.isArray(response)
? response.length
: (response as any).length || 0
} catch (error) {
throw new Error(
`Failed to count Solana transactions: ${
error instanceof Error ? error.message : String(error)
}`,
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Simplify response handling and clarify count limitations.

  1. The method only returns the count from a single page (with default limit of 1), not the total transaction count
  2. The complex type checking suggests uncertainty about the response type

Consider:

  1. Documenting that this returns a page count, not total count
  2. Fixing the response type definition to avoid the need for runtime type checking
🤖 Prompt for AI Agents
In src/libs/identity/tools/crosschain.ts lines 169 to 184, clarify in the method
documentation that countSolanaTransactionsByAddress returns the count of
transactions only from a single page (default limit 1), not the total
transaction count. Also, update the response type definition of
getSolanaTransactionsByAddress to a precise type that eliminates the need for
runtime type checks like Array.isArray or casting, simplifying the return
statement to directly use response.length.

private port: number
private hostname: string
private server: Server | null = null
public server: Server | null = null
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using a getter method instead of public property

Making the server property public breaks encapsulation. Consider providing a getter method for controlled access:

-public server: Server | null = null
+private server: Server | null = null
+
+getServer(): Server | null {
+    return this.server
+}
🤖 Prompt for AI Agents
In src/libs/network/bunServer.ts at line 17, the server property is declared as
public, which breaks encapsulation. Change the public server property to a
private field and add a public getter method that returns the server instance.
This will provide controlled access to the server while preserving
encapsulation.

Copy link
Contributor

@tcsenpai tcsenpai left a comment

Choose a reason for hiding this comment

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

sharedState.ts

  • The SUDO_PUBKEY is added as a new property in the SharedState class. It is set to the value of the SUDO_PUBKEY environment variable, or null if the variable is not set.
SUDO_PUBKEY = process.env.SUDO_PUBKEY || null
  • The rateLimitConfig object is introduced. It contains various settings for rate limiting, including enabled, defaultLimit, blockDurationMs, whitelistedIPs, and methodLimits.
rateLimitConfig = {
  enabled: true,
  defaultLimit: { maxRequests: 200, windowMs: 60000 },
  blockDurationMs: undefined,
  whitelistedIPs: [
    "127.0.0.1",
    ...(process.env.WHITELISTED_IPS?.split(",").map(ip => ip.trim()) || []),
  ],
  methodLimits: {
    // ...
  },
  txPerBlock: 4,
}
  • The getInfo method is modified to include the identity property, which is set to the publicKeyHex property of the SharedState class.
identity: this.publicKeyHex,

backupAndRestore.ts

  • The dumpUserData function is introduced. It is similar to the dumpUserBalances function, but it does not extract genesis balance entries into a separate array.
async function dumpUserData(): Promise<void> {
  // ...
}
  • The dumpUserBalances function is replaced with the dumpUserData function.
await dumpUserData()
  • The PG_PORT environment variable is logged to the console.
console.log("PG_PORT: " + process.env.PG_PORT)
  • The query variable is modified to exclude faucet addresses.
const query = `SELECT * FROM ${dbConfig.table} WHERE balance < 10000000000000`
  • The result variable is checked for existence before processing it.
if (!result) {
  return
}

utilities/sharedState.ts

  • The rateLimitConfig object is introduced. It contains various settings for rate limiting, including enabled, defaultLimit, blockDurationMs, whitelistedIPs, and methodLimits.
rateLimitConfig = {
  enabled: true,
  defaultLimit: { maxRequests: 200, windowMs: 60000 },
  blockDurationMs: undefined,
  whitelistedIPs: [
    "127.0.0.1",
    ...(process.env.WHITELISTED_IPS?.split(",").map(ip => ip.trim()) || []),
  ],
  methodLimits: {
    // ...
  },
  txPerBlock: 4,
}
  • The getInfo method is modified to include the identity property, which is set to the publicKeyHex property of the SharedState class.
identity: this.publicKeyHex,

utilities/backupAndRestore.ts

  • The dumpUserBalances function is replaced with the dumpUserData function.
await dumpUserData()
  • The PG_PORT environment variable is logged to the console.
console.log("PG_PORT: " + process.env.PG_PORT)
  • The query variable is modified to exclude faucet addresses.
const query = `SELECT * FROM ${dbConfig.table} WHERE balance < 10000000000000`
  • The result variable is checked for existence before processing it.
if (!result) {
  return
}

utilities/backupAndRestore.ts

  • The dumpUserData function is introduced. It is similar to the dumpUserBalances function, but it does not extract genesis balance entries into a separate array.
async function dumpUserData(): Promise<void> {
  // ...
}
  • The dumpUserBalances function is replaced with the dumpUserData function.
await dumpUserData()
  • The PG_PORT environment variable is logged to the console.
console.log("PG_PORT: " + process.env.PG_PORT)
  • The query variable is modified to exclude faucet addresses.
const query = `SELECT * FROM ${dbConfig.table} WHERE balance < 10000000000000`
  • The result variable is checked for existence before processing it.
if (!result) {
  return
}

sharedState.ts

  • The rateLimitConfig object is introduced. It contains various settings for rate limiting, including enabled, defaultLimit, blockDurationMs, whitelistedIPs, and methodLimits.
rateLimitConfig = {
  enabled: true,
  defaultLimit: { maxRequests: 200, windowMs: 60000 },
  blockDurationMs: undefined,
  whitelistedIPs: [
    "127.0.0.1",
    ...(process.env.WHITELISTED_IPS?.split(",").map(ip => ip.trim()) || []),
  ],
  methodLimits: {
    // ...
  },
  txPerBlock: 4,
}
  • The getInfo method is modified to include the identity property, which is set to the publicKeyHex property of the SharedState class.
identity: this.publicKeyHex,

backupAndRestore.ts

  • The dumpUserBalances function is replaced with the dumpUserData function.
await dumpUserData()
  • The PG_PORT environment variable is logged to the console.
console.log("PG_PORT: " + process.env.PG_PORT)
  • The query variable is modified to exclude faucet addresses.
const query = `SELECT * FROM ${dbConfig.table} WHERE balance < 10000000000000`
  • The result variable is checked for existence before processing it.
if (!result) {
  return
}

utilities/backupAndRestore.ts

  • The dumpUserData function is introduced. It is similar to the dumpUserBalances function, but it does not extract genesis balance entries into a separate array.
async function dumpUserData(): Promise<void> {
  // ...
}
  • The dumpUserBalances function is replaced with the dumpUserData function.
await dumpUserData()
  • The PG_PORT environment variable is logged to the console.
console.log("PG_PORT: " + process.env.PG_PORT)
  • The query variable is modified to exclude faucet addresses.
const query = `SELECT * FROM ${dbConfig.table} WHERE balance < 10000000000000`
  • The result variable is checked for existence before processing it.
if (!result) {
  return
}

utilities/backupAndRestore.ts

  • The dumpUserBalances function is replaced with the dumpUserData function.
await dumpUserData()
  • The PG_PORT environment variable is logged to the console.
console.log("PG_PORT: " + process.env.PG_PORT)
  • The query variable is modified to exclude faucet addresses.
const query = `SELECT * FROM ${dbConfig.table} WHERE balance < 10000000000000`
  • The result variable is checked for existence before processing it.
if (!result) {
  return
}

backupAndRestore.ts

  • The dumpUserBalances function is replaced with the dumpUserData function.
await dumpUserData()
  • The PG_PORT environment variable is logged to the console.
console.log("PG_PORT: " + process.env.PG_PORT)
  • The query variable is modified to exclude faucet addresses.
const query = `SELECT * FROM ${dbConfig.table} WHERE balance < 10000000000000`
  • The result variable is checked for existence before processing it.
if (!result) {
  return
}

Copy link
Contributor

@tcsenpai tcsenpai left a comment

Choose a reason for hiding this comment

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

Here are the comments:

src/libs/identity.ts

// Introduced new field 'reviewed' in the StoredIdentities class
// with a default value of false

src/libs/identity.ts

// Added new field 'flagged' in the StoredIdentities class
// with a default value of false

src/libs/identity.ts

// Added new field 'flaggedReason' in the StoredIdentities class
// with a default value of an empty string

src/libs/identity.ts

// Introduced new field 'demosFollow' in the StoredIdentities class
// with a default value of 0

src/utilities/backupAndRestore.ts

// Added dotenv.config() to load environment variables

src/utilities/backupAndRestore.ts

// Replaced hardcoded database connection configuration with environment variables

src/utilities/backupAndRestore.ts

// Added error handling for database queries

src/utilities/sharedState.ts

// Introduced new field 'SUDO_PUBKEY' in the SharedState class
// with a default value of null

src/utilities/sharedState.ts

// Updated the rateLimitConfig object with new values

src/utilities/sharedState.ts

// Introduced new field 'txPerBlock' in the rateLimitConfig object
// with a default value of 4

src/utilities/sharedState.ts

// Updated the getInfo method to return additional fields

Based on the repository, it appears that the changes are aimed at improving the rate limiting configuration, adding new fields to the StoredIdentities class, and updating the database connection configuration.

As for blatant bugs, I couldn't find any. However, there might be some edge cases or corner cases that are not handled properly.

@cwilvx cwilvx merged commit 1ca9ea0 into testnet Aug 4, 2025
1 check passed
Copy link
Contributor

@tcsenpai tcsenpai left a comment

Choose a reason for hiding this comment

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

Here are the code reviews:

src/model/entities/GCRv2/GCR_Main.ts

// Added new fields to the GCRMain entity
// flagged: boolean
// flaggedReason: string
// reviewed: boolean
// createdAt: Date
// updatedAt: Date

src/utilities/backupAndRestore.ts

// Updated the dumpUserBalances function to dump user data instead of balances
// Also added error handling and logging

src/utilities/backupAndRestore.ts

// Removed the WHERE balance > 0 clause to include all users in the dump
// TODO: Remove the comment after the first restore

src/utilities/backupAndRestore.ts

// Added dotenv.config() to load environment variables
// Added PG_PORT environment variable to the backup script

src/utilities/backupAndRestore.ts

// Added await dumpUserData() to execute the function

src/model/entities/GCRv2/GCR_Main.ts

// Removed the genesis_balances field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the genesis_balance field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the genesisBalances array and its creation

src/utilities/backupAndRestore.ts

// Removed the outputData creation and its fields

src/model/entities/GCRv2/GCR_Main.ts

// Added the demosFollow field to the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the console.log statements

src/model/entities/GCRv2/GCR_Main.ts

// Updated the type of the referrals field to number

src/utilities/backupAndRestore.ts

// Removed the error handling and logging

src/utilities/backupAndRestore.ts

// Removed the console.log statement

src/model/entities/GCRv2/GCR_Main.ts

// Updated the type of the discord field to number

src/utilities/backupAndRestore.ts

// Removed the console.log statement

src/utilities/backupAndRestore.ts

// Removed the console.log statement

src/model/entities/GCRv2/GCR_Main.ts

// Updated the type of the pointsAwarded field to number

src/utilities/backupAndRestore.ts

// Removed the console.log statement

src/model/entities/GCRv2/GCR_Main.ts

// Updated the type of the demosFollow field to number

src/utilities/backupAndRestore.ts

// Removed the console.log statement

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flaggedReason field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Updated the type of the flagged field to boolean

src/utilities/backupAndRestore.ts

// Updated the type of the flagged field to boolean

src/model/entities/GCRv2/GCR_Main.ts

// Removed the reviewed field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the createdAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the updatedAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the demosFollow field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flagged field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flagged field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flagged field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flaggedReason field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the reviewed field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the createdAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the updatedAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the demosFollow field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flagged field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flagged field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flagged field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flaggedReason field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the reviewed field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the createdAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the updatedAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the demosFollow field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flagged field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flagged field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flagged field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flaggedReason field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the reviewed field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the createdAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the updatedAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the demosFollow field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flagged field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flagged field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flagged field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flaggedReason field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the reviewed field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the createdAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the updatedAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the demosFollow field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flagged field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flagged field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flagged field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flaggedReason field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the reviewed field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the createdAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the updatedAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the demosFollow field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flagged field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flagged field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flagged field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the flaggedReason field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the flaggedReason field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the reviewed field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the reviewed field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the createdAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the createdAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the updatedAt field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the updatedAt field from the outputData creation

src/model/entities/GCRv2/GCR_Main.ts

// Removed the demosFollow field from the GCRMain entity

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the userBalances array

src/utilities/backupAndRestore.ts

// Removed the demosFollow field from the outputData creation

**src/model/entities/GCR

Copy link
Contributor

@tcsenpai tcsenpai left a comment

Choose a reason for hiding this comment

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

Here are the reviewed code changes with inline comments:

src/model/entities/GCRv2/GCR_Main.ts

  • Added flagged, flaggedReason, reviewed, createdAt, and updatedAt fields to the GCRMain entity.
// ...

  @Column({ type: "boolean", name: "flagged", default: false })
  flagged: boolean
  @Column({ type: "text", name: "flaggedReason", default: "" })
  flaggedReason:
      | "twitter_bot"
      | "evm_no_tx"
      | "solana_no_tx"
      | "web3_no_tx"
      | "only_evm_no_tx"
      | "manualFlag"
      | "referrerFlagged"
      | ""
  @Column({ type: "boolean", name: "reviewed", default: false })
  reviewed: boolean
  @CreateDateColumn({ type: "timestamp", name: "createdAt" })
  createdAt: Date
  @UpdateDateColumn({ type: "timestamp", name: "updatedAt" })
  updatedAt: Date

src/utilities/backupAndRestore.ts

  • Added PG_PORT environment variable to the dumpUserData function.
// ...

async function dumpUserData(): Promise<void>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants