Skip to content

fix: reject non-finite withdrawal amounts#5719

Merged
Scottcjn merged 3 commits into
Scottcjn:mainfrom
william08190:fix/withdraw-request-json-validation-4420
May 19, 2026
Merged

fix: reject non-finite withdrawal amounts#5719
Scottcjn merged 3 commits into
Scottcjn:mainfrom
william08190:fix/withdraw-request-json-validation-4420

Conversation

@william08190
Copy link
Copy Markdown
Contributor

Summary

  • reject NaN and Infinity withdrawal amounts before nonce/database checks
  • add regression coverage for top-level array request bodies
  • fix the withdrawal validation test loader for the dotted integrated-node filename

Closes #4420

Tests

  • uv run --with pytest --with flask --with requests python -B -m pytest -q node/tests/test_withdraw_amount_validation.py node/tests/test_withdrawal_validation.py --tb=short -p no:cacheprovider
  • python3 -B -m py_compile node/rustchain_v2_integrated_v2.2.1_rip200.py node/tests/test_withdraw_amount_validation.py node/tests/test_withdrawal_validation.py

@william08190 william08190 requested a review from Scottcjn as a code owner May 19, 2026 11:07
@github-actions github-actions Bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related tests Test suite changes size/S PR: 11-50 lines labels May 19, 2026
Copy link
Copy Markdown

@TJCurnutte TJCurnutte left a comment

Choose a reason for hiding this comment

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

Approved. The withdrawal amount validation now rejects non-finite floats at the endpoint boundary, before nonce/database/signature handling, while preserving the existing invalid-JSON, non-numeric, negative, and minimum-withdrawal behavior.

Validation run on detached PR worktree at 04ba47dd5a3c9455d2c9d810f125bc3e61e54a4d:

git diff --check origin/main...HEAD -- node/rustchain_v2_integrated_v2.2.1_rip200.py node/tests/test_withdraw_amount_validation.py node/tests/test_withdrawal_validation.py
python3 -B -m py_compile node/rustchain_v2_integrated_v2.2.1_rip200.py node/tests/test_withdraw_amount_validation.py node/tests/test_withdrawal_validation.py
RC_ADMIN_KEY=0123456789abcdef0123456789abcdef PYTHONPATH=node python3 -B -m pytest -q node/tests/test_withdraw_amount_validation.py node/tests/test_withdrawal_validation.py --tb=short -p no:cacheprovider

Result: 13 passed, 1 warning in 0.53s.

I also ran a temp-DB Flask probe against /withdraw/request. On this PR, "NaN" and "inf" both returned HTTP 400 with amount must be a finite positive number, and a top-level array body returned HTTP 400 with Invalid JSON body. The same probe on origin/main let non-finite values pass the amount gate and fail later (NaN reached signature validation; inf reached the balance check), so this patch closes the right pre-DB validation gap.

Copy link
Copy Markdown
Contributor

@minyanyi minyanyi left a comment

Choose a reason for hiding this comment

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

I re-ran the PR's updated withdrawal validation tests on current head 04ba47d and found a Windows-specific blocker in the new fixture setup.

Reproduction:

  • python -B -m pytest -q node/tests/test_withdrawal_validation.py --tb=short

Observed result:

  • the assertions themselves run, but teardown fails for every case with PermissionError: [WinError 32] ... withdrawal-validation.db
  • the failure happens in the new tempfile.TemporaryDirectory() fixture path added by this PR, while the imported integrated module is still holding the SQLite file open when cleanup tries to unlink it

Representative failure:

E   PermissionError: [WinError 32] ... '...\\withdrawal-validation.db'
node/tests/test_withdrawal_validation.py:29: in app
    with tempfile.TemporaryDirectory() as tmpdir:

So the PR moves the file past the old syntax/import problem, but the updated regression harness is still not green on Windows because the temp DB is not released before directory cleanup. I would fix that fixture lifecycle before merge.

Copy link
Copy Markdown
Contributor

@jaxint jaxint left a comment

Choose a reason for hiding this comment

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

LGTM! Great work on this PR. 🚀

@github-actions github-actions Bot added size/M PR: 51-200 lines and removed size/S PR: 11-50 lines labels May 19, 2026
@wybbb123123
Copy link
Copy Markdown
Contributor

Review: changes requested

I reviewed PR #5719 (fix: reject non-finite withdrawal amounts) against the current withdrawal route and ran the relevant validation tests.

Validation run:

  • git diff --check main..review-pr-5719
  • python3 -B -m py_compile node/rustchain_v2_integrated_v2.2.1_rip200.py node/tests/test_withdraw_amount_validation.py node/tests/test_withdrawal_validation.py
  • uv run --with pytest --with flask --with requests python -B -m pytest -q node/tests/test_withdraw_amount_validation.py node/tests/test_withdrawal_validation.py --tb=short -p no:cacheprovider
  • Result: 13 passed

Finding:

/withdraw/request still accepts JSON booleans as withdrawal amounts before the balance/signature checks. The route does:

raw_amount = data.get('amount', 0)
amount = float(raw_amount)

In Python, float(True) becomes 1.0 and float(False) becomes 0.0, so boolean JSON values bypass the intended "amount must be a number" validation. In an API that receives JSON, booleans are not numeric withdrawal amounts and should be rejected before float conversion.

Manual Flask client repro after initializing a temp DB:

True 400 {'balance': 0.0, 'error': 'Insufficient balance'}
False 400 {'error': 'Minimum withdrawal is 0.1 RTC'}

This shows true is treated as 1.0 and reaches the balance path instead of returning amount must be a number; false is treated as 0.0 and reaches the minimum-withdrawal path.

Suggested fix:

if isinstance(raw_amount, bool):
    return jsonify({"error": "amount must be a number", "received": "bool"}), 400

Add a regression test for amount: true and amount: false so the route rejects JSON booleans before numeric business rules run.

@JeremyZeng77
Copy link
Copy Markdown
Contributor

Reviewed the non-finite withdrawal amount change.

Functional behavior looks directionally correct: NaN/inf now hit math.isfinite(amount) before balance, minimum, nonce, and signature handling, and the new assertions cover non-finite inputs plus top-level array JSON.

However, I hit a test-suite blocker on Windows when running the added/related withdrawal tests:

python -B -m pytest -q node/tests/test_withdraw_amount_validation.py --tb=short -p no:cacheprovider
6 passed, 1 warning, 1 error

ERROR at teardown of TestWithdrawAmountValidation.test_top_level_array_body_rejected
PermissionError: [WinError 32] ... import.db
node/tests/test_withdraw_amount_validation.py:39: cls._tmp.cleanup()

The module imported in setUpClass appears to keep the temporary SQLite DB handle open through class teardown, so TemporaryDirectory.cleanup() cannot remove import.db on Windows. node/tests/test_withdrawal_validation.py already uses a safer fixture pattern with app teardown, gc.collect(), and shutil.rmtree(..., ignore_errors=True). I would update test_withdraw_amount_validation.py similarly before merging, otherwise the new coverage can still fail in Windows CI/local verification even though the endpoint assertions pass.

Other checks run:

git diff --check origin/main...HEAD
python -B -m py_compile node/rustchain_v2_integrated_v2.2.1_rip200.py node/tests/test_withdraw_amount_validation.py node/tests/test_withdrawal_validation.py

@Scottcjn Scottcjn merged commit 44ad0a2 into Scottcjn:main May 19, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related size/M PR: 51-200 lines tests Test suite changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Withdraw request route returns 500 for malformed JSON and invalid amounts

7 participants