Skip to content

Comments

feat: Implement balance checks for L2PS transactions to ensure suffic…#680

Open
Shitikyan wants to merge 1 commit intotestnetfrom
fix-l2ps-balance-check
Open

feat: Implement balance checks for L2PS transactions to ensure suffic…#680
Shitikyan wants to merge 1 commit intotestnetfrom
fix-l2ps-balance-check

Conversation

@Shitikyan
Copy link
Contributor

…ient funds before processing

@qodo-code-review
Copy link
Contributor

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Review Summary by Qodo

Implement balance checks for L2PS and regular transactions
✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Add balance validation for L2PS encrypted transactions before mempool insertion
• Implement sender balance checks for regular transactions to prevent insufficient funds
• Export L2PS_TX_FEE constant for reuse across modules
• Add error logging for failed broadcast transactions

Grey Divider

File Changes

1. src/libs/blockchain/routines/validateTransaction.ts ✨ Enhancement +76/-0

Add balance validation for transactions

• Added balance check for regular transactions before processing
• Implemented checkL2PSBalance() function to decrypt and validate L2PS transaction balances
• Added imports for L2PS-related modules and types
• Returns validation error if sender has insufficient funds

src/libs/blockchain/routines/validateTransaction.ts


2. src/libs/l2ps/L2PSTransactionExecutor.ts ✨ Enhancement +1/-1

Export L2PS transaction fee constant

• Exported L2PS_TX_FEE constant to make it accessible to other modules

src/libs/l2ps/L2PSTransactionExecutor.ts


3. src/libs/network/manageExecution.ts Error handling +3/-0

Add error logging for failed broadcasts

• Added error logging when broadcastTx fails to help with debugging
• Logs result code, extra data, and response details on failure

src/libs/network/manageExecution.ts


View more (1)
4. src/libs/network/routines/transactions/handleL2PS.ts ✨ Enhancement +41/-2

Add balance pre-check for L2PS transactions

• Added import for INativePayload type
• Imported L2PS_TX_FEE from L2PSTransactionExecutor
• Implemented checkSenderBalance() function to validate balance before mempool insertion
• Added pre-check balance validation call before processing valid L2PS transactions

src/libs/network/routines/transactions/handleL2PS.ts


Grey Divider

Qodo Logo

@sonarqubecloud
Copy link

@qodo-code-review
Copy link
Contributor

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Logging can throw on failure 🐞 Bug ⛯ Reliability
Description
In broadcastTx failure handling, manageExecution JSON.stringifies returnValue.extra; if extra is
a non-serializable object (e.g., contains circular refs or BigInt), the log statement can throw and
break the error-handling path (client may not receive the intended error response).
Code

src/libs/network/manageExecution.ts[R82-84]

+                if (!result.success) {
+                    log.error(`[SERVER] broadcastTx FAILED — returning to client: result=${returnValue.result}, extra=${JSON.stringify(returnValue.extra)}, response.extra=${result.response?.extra}`)
+                }
Evidence
manageExecution performs JSON.stringify(returnValue.extra) in the failure branch.
returnValue.extra is assigned from result.extra, and handleExecuteTransaction sometimes sets
result.extra to an object, meaning it is not guaranteed to be safely JSON-serializable.

src/libs/network/manageExecution.ts[69-85]
src/libs/network/endpointHandlers.ts[419-443]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`manageExecution` logs `broadcastTx` failures using `JSON.stringify(returnValue.extra)`. Because `extra` can be an object (and potentially non-JSON-serializable), the logging line can throw and break the error path.

### Issue Context
`returnValue.extra` is populated from `result.extra`, and `handleExecuteTransaction` assigns objects to `result.extra` in multiple branches.

### Fix Focus Areas
- src/libs/network/manageExecution.ts[69-85]

### Suggested fix
- Wrap the stringify in a try/catch and fall back to a safe representation.
- Optionally implement a small `safeStringify` helper that:
 - converts `bigint` to string
 - handles circular references (or falls back to `util.inspect`)
 - truncates overly large outputs

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. L2PS fee/balance check inconsistent 🐞 Bug ✓ Correctness
Description
The new L2PS balance pre-checks always add L2PS_TX_FEE (even when the executor does not charge any
fee for that tx type), and they don’t validate sendAmount like the executor does. This can
incorrectly reject L2PS transactions that would otherwise execute successfully (e.g., non-send
native ops, or non-native txs with only gcr_edits) and can also produce misleading errors/behavior
when the amount is malformed.
Code

src/libs/network/routines/transactions/handleL2PS.ts[R80-102]

+async function checkSenderBalance(decryptedTx: Transaction): Promise<string | null> {
+    const sender = decryptedTx.content.from as string
+    if (!sender) return "Missing sender address in decrypted transaction"
+
+    // Extract amount from native payload
+    let amount = 0
+    if (decryptedTx.content.type === "native" && Array.isArray(decryptedTx.content.data)) {
+        const nativePayload = decryptedTx.content.data[1] as INativePayload
+        if (nativePayload?.nativeOperation === "send") {
+            const [, sendAmount] = nativePayload.args as [string, number]
+            amount = sendAmount || 0
+        }
+    }
+
+    const totalRequired = amount + L2PS_TX_FEE
+    try {
+        const balance = await L2PSTransactionExecutor.getBalance(sender)
+        if (balance < BigInt(totalRequired)) {
+            return `Insufficient balance: need ${totalRequired} (${amount} + ${L2PS_TX_FEE} fee) but have ${balance}`
+        }
+    } catch (error) {
+        return `Balance check failed: ${error instanceof Error ? error.message : "Unknown error"}`
+    }
Evidence
checkSenderBalance (and the similar checkL2PSBalance in confirmTransaction) always requires
amount + L2PS_TX_FEE even when the executor’s logic only burns/checks the fee for `nativeOperation
=== "send". The executor also validates amount` as a finite integer > 0, but the new pre-check
does not. Therefore the pre-check and executor can disagree: pre-check may reject while executor
would accept, or pre-check may crash/skip in edge cases.

src/libs/network/routines/transactions/handleL2PS.ts[80-102]
src/libs/blockchain/routines/validateTransaction.ts[163-176]
src/libs/l2ps/L2PSTransactionExecutor.ts[207-220]
src/libs/l2ps/L2PSTransactionExecutor.ts[158-189]
src/libs/l2ps/L2PSTransactionExecutor.ts[260-266]
src/libs/l2ps/L2PSTransactionExecutor.ts[435-438]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`checkSenderBalance` / `checkL2PSBalance` currently require `L2PS_TX_FEE` for all decrypted L2PS transactions, but the executor only checks/burns the fee for `nativeOperation === &quot;send&quot;` and may accept other tx types without fees. This mismatch can cause false rejections. Additionally, the pre-check doesn’t validate the extracted `sendAmount` like the executor does.

### Issue Context
- Executor fee+amount checks are implemented in `handleNativeTransaction` for `nativeOperation === &quot;send&quot;`.
- Other tx types (non-native with `gcr_edits`, or unknown native operations) do not burn/check a fee.

### Fix Focus Areas
- src/libs/network/routines/transactions/handleL2PS.ts[80-102]
- src/libs/blockchain/routines/validateTransaction.ts[145-176]
- src/libs/l2ps/L2PSTransactionExecutor.ts[158-220]

### Suggested fix
1. Decide on policy:
  - **Option A (match current executor):** Only add `L2PS_TX_FEE` to `totalRequired` for txs where the executor actually burns/charges the fee (currently native `send`).
  - **Option B (enforce fee for all L2PS txs):** Update `L2PSTransactionExecutor.generateGCREdits`/handlers to also burn/check the fee for other tx categories, then keep pre-check consistent.
2. Validate amount exactly as executor does before BigInt conversion:
  - `typeof sendAmount === &#x27;number&#x27; &amp;&amp; Number.isFinite(sendAmount) &amp;&amp; Number.isInteger(sendAmount) &amp;&amp; sendAmount &gt; 0`
3. Compute required balance using BigInt arithmetic:
  - `const required = BigInt(sendAmount) + BigInt(L2PS_TX_FEE)`
  - Avoid `BigInt(amount + fee)`.
4. Consider extracting a shared helper (e.g., in `L2PSTransactionExecutor`) to compute required balance so pre-check and executor cannot drift.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Double L2PS decrypt work 🐞 Bug ➹ Performance
Description
confirmTransaction now decrypts L2PS encrypted transactions for balance pre-check, but handleL2PS
decrypts again during processing. This duplicates expensive cryptographic work and config loading
for clients using confirmTx + broadcastTx flows, increasing latency and DoS surface.
Code

src/libs/blockchain/routines/validateTransaction.ts[R151-160]

+        const parallelNetworks = ParallelNetworks.getInstance()
+        let l2psInstance = await parallelNetworks.getL2PS(l2psUid)
+        if (!l2psInstance) {
+            l2psInstance = await parallelNetworks.loadL2PS(l2psUid)
+        }
+        if (!l2psInstance) return null // No L2PS config, let handleL2PS catch it
+
+        const decryptedTx = await l2psInstance.decryptTx(tx as any)
+        if (!decryptedTx?.content?.from) return null
+
Evidence
The newly-added checkL2PSBalance decrypts the transaction during confirmTransaction. The L2PS
handling path also decrypts the same encrypted transaction in decryptAndValidate, meaning the
system may decrypt twice for the same tx lifecycle.

src/libs/blockchain/routines/validateTransaction.ts[151-160]
src/libs/network/routines/transactions/handleL2PS.ts[47-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`confirmTransaction` decrypts L2PS encrypted transactions to perform a balance pre-check, but `handleL2PS` also decrypts the same payload later. This duplicates CPU/IO work and can be abused to amplify node load.

### Issue Context
- `checkL2PSBalance()` calls `l2psInstance.decryptTx(...)` during confirmTransaction.
- `decryptAndValidate()` calls `l2psInstance.decryptTx(...)` again during actual L2PS handling.

### Fix Focus Areas
- src/libs/blockchain/routines/validateTransaction.ts[141-182]
- src/libs/network/routines/transactions/handleL2PS.ts[44-70]

### Suggested fix
- Prefer doing the balance check in one place (typically `handleL2PS` right before mempool insertion/execution).
- If confirmTx must pre-check, consider:
 - caching the decrypted transaction temporarily keyed by encrypted tx hash, then reusing it in `handleL2PS`, or
 - returning a token/reference from confirmTx that allows reuse.
- At minimum, add a guard so confirmTransaction only decrypts when strictly necessary and the node has the L2PS config loaded.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +82 to +84
if (!result.success) {
log.error(`[SERVER] broadcastTx FAILED — returning to client: result=${returnValue.result}, extra=${JSON.stringify(returnValue.extra)}, response.extra=${result.response?.extra}`)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

1. Logging can throw on failure 🐞 Bug ⛯ Reliability

In broadcastTx failure handling, manageExecution JSON.stringifies returnValue.extra; if extra is
a non-serializable object (e.g., contains circular refs or BigInt), the log statement can throw and
break the error-handling path (client may not receive the intended error response).
Agent Prompt
### Issue description
`manageExecution` logs `broadcastTx` failures using `JSON.stringify(returnValue.extra)`. Because `extra` can be an object (and potentially non-JSON-serializable), the logging line can throw and break the error path.

### Issue Context
`returnValue.extra` is populated from `result.extra`, and `handleExecuteTransaction` assigns objects to `result.extra` in multiple branches.

### Fix Focus Areas
- src/libs/network/manageExecution.ts[69-85]

### Suggested fix
- Wrap the stringify in a try/catch and fall back to a safe representation.
- Optionally implement a small `safeStringify` helper that:
  - converts `bigint` to string
  - handles circular references (or falls back to `util.inspect`)
  - truncates overly large outputs

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +80 to +102
async function checkSenderBalance(decryptedTx: Transaction): Promise<string | null> {
const sender = decryptedTx.content.from as string
if (!sender) return "Missing sender address in decrypted transaction"

// Extract amount from native payload
let amount = 0
if (decryptedTx.content.type === "native" && Array.isArray(decryptedTx.content.data)) {
const nativePayload = decryptedTx.content.data[1] as INativePayload
if (nativePayload?.nativeOperation === "send") {
const [, sendAmount] = nativePayload.args as [string, number]
amount = sendAmount || 0
}
}

const totalRequired = amount + L2PS_TX_FEE
try {
const balance = await L2PSTransactionExecutor.getBalance(sender)
if (balance < BigInt(totalRequired)) {
return `Insufficient balance: need ${totalRequired} (${amount} + ${L2PS_TX_FEE} fee) but have ${balance}`
}
} catch (error) {
return `Balance check failed: ${error instanceof Error ? error.message : "Unknown error"}`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

2. L2ps fee/balance check inconsistent 🐞 Bug ✓ Correctness

The new L2PS balance pre-checks always add L2PS_TX_FEE (even when the executor does not charge any
fee for that tx type), and they don’t validate sendAmount like the executor does. This can
incorrectly reject L2PS transactions that would otherwise execute successfully (e.g., non-send
native ops, or non-native txs with only gcr_edits) and can also produce misleading errors/behavior
when the amount is malformed.
Agent Prompt
### Issue description
`checkSenderBalance` / `checkL2PSBalance` currently require `L2PS_TX_FEE` for all decrypted L2PS transactions, but the executor only checks/burns the fee for `nativeOperation === "send"` and may accept other tx types without fees. This mismatch can cause false rejections. Additionally, the pre-check doesn’t validate the extracted `sendAmount` like the executor does.

### Issue Context
- Executor fee+amount checks are implemented in `handleNativeTransaction` for `nativeOperation === "send"`.
- Other tx types (non-native with `gcr_edits`, or unknown native operations) do not burn/check a fee.

### Fix Focus Areas
- src/libs/network/routines/transactions/handleL2PS.ts[80-102]
- src/libs/blockchain/routines/validateTransaction.ts[145-176]
- src/libs/l2ps/L2PSTransactionExecutor.ts[158-220]

### Suggested fix
1. Decide on policy:
   - **Option A (match current executor):** Only add `L2PS_TX_FEE` to `totalRequired` for txs where the executor actually burns/charges the fee (currently native `send`).
   - **Option B (enforce fee for all L2PS txs):** Update `L2PSTransactionExecutor.generateGCREdits`/handlers to also burn/check the fee for other tx categories, then keep pre-check consistent.
2. Validate amount exactly as executor does before BigInt conversion:
   - `typeof sendAmount === 'number' && Number.isFinite(sendAmount) && Number.isInteger(sendAmount) && sendAmount > 0`
3. Compute required balance using BigInt arithmetic:
   - `const required = BigInt(sendAmount) + BigInt(L2PS_TX_FEE)`
   - Avoid `BigInt(amount + fee)`.
4. Consider extracting a shared helper (e.g., in `L2PSTransactionExecutor`) to compute required balance so pre-check and executor cannot drift.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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.

1 participant