Skip to content

fix(validation)!: validators throw on the calling thread, so BatchUtils.Build validates - #187

Merged
Platonenkov merged 2 commits into
devfrom
claude/batchutils-build-validation-8d5a6f
Sep 15, 2026
Merged

Platonenkov merged 2 commits into
devfrom
claude/batchutils-build-validation-8d5a6f

Conversation

@Platonenkov

@Platonenkov Platonenkov commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

What was wrong

BatchUtils.Build called Validation.Validate(...) without awaiting it, and Validation.Validate is declared async Task. An async method captures every exception into the task it returns — including the ones thrown before the first await — so a discarded task is a discarded verdict. ValidateBatch ran, decided the batch was malformed, and reported it to nobody.

A Batch built around a single inner transaction, which rippled answers with temARRAY_EMPTY, came back from Build looking well formed. So did one with more than eight inners, one with a Vault/Loan inner, one with an inner missing tfInnerBatchTxn, and one with an inner carrying a non-zero Fee — every rule in ValidateBatch was lost, not just the inner count.

The compiler had been reporting it as CS4014 since the method was written.

The fix

The 86 validators — Validation.Validate, the 79 Validation.ValidateX, Common.ValidateBaseTransaction and the handful of helpers — now return void and throw on the calling thread. Not one of them ever awaited anything: they are straight-line field checking that was async only in signature.

Adding the missing await was the alternative and is worse here. Build is synchronous and public, so awaiting would have meant Task<Batch> BuildAsync, and .GetAwaiter().GetResult() would have left the next caller the same trap. A validator that cannot be forgotten is a validator that has no task to forget.

Conditions, exception types and messages are unchanged. The migration is one edit: await Validation.Validate(tx)Validation.Validate(tx). A try/catch around the call keeps working as it is.

Version: 11.5.1.0, and why a patch

This is a breaking change released as a patch, deliberately. Semver would call it a major — the return type is part of a method's signature in IL, so an assembly built against 11.5.0.0 meets a MissingMethodException on 11.5.1.0 even where it never wrote await. It is numbered a patch because the validators are opt-in: nothing inside the SDK calls them, and BatchUtils.Build, the one caller that did, is the method this release fixes. CHANGES.md states the deviation and tells anyone calling Validation.* directly to treat the upgrade as a major one.

One thing found on the way

TestUCredentialsValidator wrapped the already-synchronous CredentialsValidator.ValidateCredentialsList in Task.Run to fit the async assertion helper's Func<Task>. That worked while the helper awaited the task — and it is exactly the shape that stops working the moment the assertion becomes synchronous, since Action accepts a lambda whose value is discarded. Nine tests went red on the first full run and now call the validator directly.

Tests

Tests/Xrpl.Tests/Models/TestUBatchUtils.cs pins the behaviour: a single inner is refused, more than eight are refused, a well-formed batch is built. Written first — the two rejection tests failed with "Expected exception of type System.ArgumentException" before the fix.

  • Unit: 1487 passed, 0 failed (Xrpl.Tests 1332 + AddressCodec 27 + BinaryCodec 50 + Keypairs 33 + GenerateEnums 19 + X402 26)
  • Integration against the CI stand (docker-compose.ci.yml): 346 passed, 0 failed
  • Solution builds clean; the CS4014 in BatchUtils.cs is gone. The one left, connection.cs:4638, is an Interlocked.Exchange discarding the previous task on purpose and is untouched.

Diff shape

131 files, but three mechanical classes: validator signatures and their call sites, the now-unused using System.Threading.Tasks;, and the test-side await removals (Helper.ThrowsExceptionAsync → the existing Helper.ThrowsException, Assert.ThrowsExactlyAsyncAssert.ThrowsExactly, and test methods that no longer await anything becoming void). The behavioural change is BatchUtils.cs plus the new test file.

Summary by CodeRabbit

  • Breaking Changes

    • Transaction validation APIs now execute synchronously instead of asynchronously. Applications using asynchronous validation calls must update their integrations.
  • Bug Fixes

    • Batch construction now immediately validates assembled batches and correctly rejects malformed batches, including invalid transaction counts, types, or structures.
  • Documentation

    • Expanded batch-building documentation to describe validation behavior, input requirements, and possible errors.
  • Release

    • Updated the package version to 11.5.1.0.

…ls.Build validates

Validation.Validate and the 86 validators behind it were declared async Task
without ever awaiting anything, and BatchUtils.Build called Validate without
awaiting the result. An async method captures every exception into the task it
returns, including the ones thrown before the first await, so a discarded task
is a discarded verdict: ValidateBatch ran, decided the batch was malformed and
reported it to nobody. A Batch built around a single inner transaction - which
rippled answers with temARRAY_EMPTY - came back from Build looking well formed,
and so did one with more than eight inners, with a Vault/Loan inner, with an
inner missing tfInnerBatchTxn, or with an inner carrying a non-zero Fee. The
compiler had been saying so as CS4014 since the method was written.

The validators now return void and throw on the calling thread. Conditions,
exception types and messages are unchanged; `await Validation.Validate(tx)` no
longer compiles - drop the await. Making the signature honest is the fix rather
than adding the missing await: Build is synchronous and public, so awaiting
would have meant Task<Batch> BuildAsync, and .GetAwaiter().GetResult() would
have left the next caller the same trap.

TestUCredentialsValidator wrapped the already-synchronous ValidateCredentialsList
in Task.Run to fit the async assertion helper's Func<Task>. That worked while the
helper awaited the task, and is exactly the shape that stops working once the
assertion is synchronous, since Action accepts a lambda whose value is discarded.
The nine tests call the validator directly now.

Released as 11.5.1.0 rather than a major: the validators are opt-in, nothing
inside the SDK calls them, and BatchUtils.Build - the one caller that did - is
what this release fixes. CHANGES.md states the deviation for anyone calling
Validation.* or Common.ValidateBaseTransaction directly.
@Platonenkov

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Essentials

Run ID: a259fa3a-a589-4233-89c8-c9bab86629e7

📥 Commits

Reviewing files that changed from the base of the PR and between 4f2b8ce and 6ef8ed1.

📒 Files selected for processing (132)
  • CHANGES.md
  • Tests/Xrpl.Tests/Integration/transactions/TestIPermissionedDomain.cs
  • Tests/Xrpl.Tests/Models/TestAMMBid.cs
  • Tests/Xrpl.Tests/Models/TestAMMClawback.cs
  • Tests/Xrpl.Tests/Models/TestAMMCreate.cs
  • Tests/Xrpl.Tests/Models/TestAMMDeposit.cs
  • Tests/Xrpl.Tests/Models/TestAMMVote.cs
  • Tests/Xrpl.Tests/Models/TestAMMWithdraw.cs
  • Tests/Xrpl.Tests/Models/TestAccountDelete.cs
  • Tests/Xrpl.Tests/Models/TestAccountSet.cs
  • Tests/Xrpl.Tests/Models/TestBaseTransaction.cs
  • Tests/Xrpl.Tests/Models/TestCheckCancel.cs
  • Tests/Xrpl.Tests/Models/TestCheckCash.cs
  • Tests/Xrpl.Tests/Models/TestCheckCreate.cs
  • Tests/Xrpl.Tests/Models/TestClawback.cs
  • Tests/Xrpl.Tests/Models/TestCredentialsValidator.cs
  • Tests/Xrpl.Tests/Models/TestDIDDelete.cs
  • Tests/Xrpl.Tests/Models/TestDIDSet.cs
  • Tests/Xrpl.Tests/Models/TestDepositPreauth.cs
  • Tests/Xrpl.Tests/Models/TestEscrowCancel.cs
  • Tests/Xrpl.Tests/Models/TestEscrowCreate.cs
  • Tests/Xrpl.Tests/Models/TestEscrowFinish.cs
  • Tests/Xrpl.Tests/Models/TestMPTokenAuthorize.cs
  • Tests/Xrpl.Tests/Models/TestMPTokenIssuanceCreate.cs
  • Tests/Xrpl.Tests/Models/TestMPTokenIssuanceDestroy.cs
  • Tests/Xrpl.Tests/Models/TestMPTokenIssuanceSet.cs
  • Tests/Xrpl.Tests/Models/TestModelUtils.cs
  • Tests/Xrpl.Tests/Models/TestNFTokenAcceptOffer.cs
  • Tests/Xrpl.Tests/Models/TestNFTokenBurn.cs
  • Tests/Xrpl.Tests/Models/TestNFTokenCancelOffer.cs
  • Tests/Xrpl.Tests/Models/TestNFTokenCreateOffer.cs
  • Tests/Xrpl.Tests/Models/TestNFTokenMint.cs
  • Tests/Xrpl.Tests/Models/TestOfferCancel.cs
  • Tests/Xrpl.Tests/Models/TestOfferCreate.cs
  • Tests/Xrpl.Tests/Models/TestOracleDelete.cs
  • Tests/Xrpl.Tests/Models/TestOracleSet.cs
  • Tests/Xrpl.Tests/Models/TestPayment.cs
  • Tests/Xrpl.Tests/Models/TestPaymentChannelClaim.cs
  • Tests/Xrpl.Tests/Models/TestPaymentChannelCreate.cs
  • Tests/Xrpl.Tests/Models/TestPaymentChannelFund.cs
  • Tests/Xrpl.Tests/Models/TestPermissionedDomainDelete.cs
  • Tests/Xrpl.Tests/Models/TestPermissionedDomainSet.cs
  • Tests/Xrpl.Tests/Models/TestSetRegularKey.cs
  • Tests/Xrpl.Tests/Models/TestSignerListSet.cs
  • Tests/Xrpl.Tests/Models/TestTicketCreate.cs
  • Tests/Xrpl.Tests/Models/TestTrustSet.cs
  • Tests/Xrpl.Tests/Models/TestUBatchUtils.cs
  • Tests/Xrpl.Tests/Models/TestUConfidentialMPT.cs
  • Tests/Xrpl.Tests/Models/TestUModelTruth.cs
  • Tests/Xrpl.Tests/Models/TestUProtocolCompleteness.cs
  • Tests/Xrpl.Tests/Models/TestUTransactionProtocolFields.cs
  • Tests/Xrpl.Tests/Models/TestUValidationNumericTypes.cs
  • Tests/Xrpl.Tests/Wallet/TestUBatchCoSigning.cs
  • Xrpl/Models/Transactions/AMMBid.cs
  • Xrpl/Models/Transactions/AMMClawBack.cs
  • Xrpl/Models/Transactions/AMMCreate.cs
  • Xrpl/Models/Transactions/AMMDelete.cs
  • Xrpl/Models/Transactions/AMMDeposit.cs
  • Xrpl/Models/Transactions/AMMVote.cs
  • Xrpl/Models/Transactions/AMMWithdraw.cs
  • Xrpl/Models/Transactions/AccountDelete.cs
  • Xrpl/Models/Transactions/AccountSet.cs
  • Xrpl/Models/Transactions/Batch.cs
  • Xrpl/Models/Transactions/CheckCancel.cs
  • Xrpl/Models/Transactions/CheckCash.cs
  • Xrpl/Models/Transactions/CheckCreate.cs
  • Xrpl/Models/Transactions/ClawBack.cs
  • Xrpl/Models/Transactions/Common.cs
  • Xrpl/Models/Transactions/ConfidentialMPT.cs
  • Xrpl/Models/Transactions/CredentialAccept.cs
  • Xrpl/Models/Transactions/CredentialCreate.cs
  • Xrpl/Models/Transactions/CredentialDelete.cs
  • Xrpl/Models/Transactions/DIDDelete.cs
  • Xrpl/Models/Transactions/DIDSet.cs
  • Xrpl/Models/Transactions/DelegateSet.cs
  • Xrpl/Models/Transactions/DepositPreauth.cs
  • Xrpl/Models/Transactions/EscrowCancel.cs
  • Xrpl/Models/Transactions/EscrowCreate.cs
  • Xrpl/Models/Transactions/EscrowFinish.cs
  • Xrpl/Models/Transactions/LedgerStateFix.cs
  • Xrpl/Models/Transactions/LoanBrokerCoverClawback.cs
  • Xrpl/Models/Transactions/LoanBrokerCoverDeposit.cs
  • Xrpl/Models/Transactions/LoanBrokerCoverWithdraw.cs
  • Xrpl/Models/Transactions/LoanBrokerDelete.cs
  • Xrpl/Models/Transactions/LoanBrokerSet.cs
  • Xrpl/Models/Transactions/LoanDelete.cs
  • Xrpl/Models/Transactions/LoanManage.cs
  • Xrpl/Models/Transactions/LoanPay.cs
  • Xrpl/Models/Transactions/LoanSet.cs
  • Xrpl/Models/Transactions/MPTokenAuthorize.cs
  • Xrpl/Models/Transactions/MPTokenIssuanceCreate.cs
  • Xrpl/Models/Transactions/MPTokenIssuanceDestroy.cs
  • Xrpl/Models/Transactions/MPTokenIssuanceSet.cs
  • Xrpl/Models/Transactions/NFTokenAcceptOffer.cs
  • Xrpl/Models/Transactions/NFTokenBurn.cs
  • Xrpl/Models/Transactions/NFTokenCancelOffer.cs
  • Xrpl/Models/Transactions/NFTokenCreateOffer.cs
  • Xrpl/Models/Transactions/NFTokenMint.cs
  • Xrpl/Models/Transactions/NFTokenModify.cs
  • Xrpl/Models/Transactions/OfferCancel.cs
  • Xrpl/Models/Transactions/OfferCreate.cs
  • Xrpl/Models/Transactions/OracleDelete.cs
  • Xrpl/Models/Transactions/OracleSet.cs
  • Xrpl/Models/Transactions/Payment.cs
  • Xrpl/Models/Transactions/PaymentChannelClaim.cs
  • Xrpl/Models/Transactions/PaymentChannelCreate.cs
  • Xrpl/Models/Transactions/PaymentChannelFund.cs
  • Xrpl/Models/Transactions/PermissionedDomainDelete.cs
  • Xrpl/Models/Transactions/PermissionedDomainSet.cs
  • Xrpl/Models/Transactions/SetRegularKey.cs
  • Xrpl/Models/Transactions/SignerListSet.cs
  • Xrpl/Models/Transactions/SponsorshipSet.cs
  • Xrpl/Models/Transactions/SponsorshipTransfer.cs
  • Xrpl/Models/Transactions/TicketCreate.cs
  • Xrpl/Models/Transactions/TrustSet.cs
  • Xrpl/Models/Transactions/Validation.cs
  • Xrpl/Models/Transactions/VaultClawback.cs
  • Xrpl/Models/Transactions/VaultCreate.cs
  • Xrpl/Models/Transactions/VaultDelete.cs
  • Xrpl/Models/Transactions/VaultDeposit.cs
  • Xrpl/Models/Transactions/VaultSet.cs
  • Xrpl/Models/Transactions/VaultWithdraw.cs
  • Xrpl/Models/Transactions/XChainAccountCreateCommit.cs
  • Xrpl/Models/Transactions/XChainAddAccountCreateAttestation.cs
  • Xrpl/Models/Transactions/XChainAddClaimAttestation.cs
  • Xrpl/Models/Transactions/XChainClaim.cs
  • Xrpl/Models/Transactions/XChainCommit.cs
  • Xrpl/Models/Transactions/XChainCreateBridge.cs
  • Xrpl/Models/Transactions/XChainCreateClaimID.cs
  • Xrpl/Models/Transactions/XChainModifyBridge.cs
  • Xrpl/Models/Utils/BatchUtils.cs
  • Xrpl/Xrpl.csproj

Included review availability: 3 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.


📝 Walkthrough

Walkthrough

Transaction validation changed from asynchronous Task methods to synchronous void methods across transaction validators and dispatch. Validation tests now call validators and exception helpers directly. Batch validation gained tests and expanded documentation. The package version changed to 11.5.1.0.

Changes

Synchronous transaction validation

Layer / File(s) Summary
Validator contracts and dispatch
Xrpl/Models/Transactions/*.cs
Transaction validators, base validation, partial payment checks, and transaction dispatch now execute synchronously and return void. Existing validation rules and exceptions remain unchanged.
Validation test migration
Tests/Xrpl.Tests/Models/*, Tests/Xrpl.Tests/Integration/transactions/TestIPermissionedDomain.cs, Tests/Xrpl.Tests/Wallet/TestUBatchCoSigning.cs
Validation tests now use synchronous test methods, direct validator calls, and synchronous exception assertions. Credential-validator tests no longer use Task.Run.
Batch validation coverage and release metadata
Tests/Xrpl.Tests/Models/TestUBatchUtils.cs, Xrpl/Models/Utils/BatchUtils.cs, CHANGES.md, Xrpl/Xrpl.csproj
Batch construction tests cover inner-transaction count limits and valid construction. BatchUtils.Build documentation describes validation and exceptions. Release notes and the package version were updated to 11.5.1.0.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 6ef8e

No actionable merge-blocking issue remains; the validator migration and compatibility impact are explicitly documented.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.91% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 246 functions across 50 files. (82 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: synchronous validators throw on the calling thread, and BatchUtils.Build performs validation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 6.91% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 246 functions across 50 files. (82 skipped: 2 unsupported, 80 over the file limit.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/batchutils-build-validation-8d5a6f

Comment @coderabbitai help to get the list of available commands.

@Platonenkov

Copy link
Copy Markdown
Collaborator Author

CodeRabbit triage

Full review returned no actionable comments — no inline comments, and no collapsed 🧹 Nitpick or ⚠️ Outside diff range sections in the review body. Checked both places.

One pre-merge check failed, and here is the disposition.

⚠️ Docstring Coverage — 6.91% against an 80% threshold (partly addressed, mostly rejected)

The metric is scoped to functions this diff touched: 246 functions across 50 files. Almost all of them are MSTest methods whose signature changed from async Task TestX() to void TestX() because they no longer await anything. They were never documented with XML comments — no test in this repository is — and this change did not remove a single doc comment. Writing 230 docstrings onto test methods to move a percentage would add noise to the diff and document nothing a reader does not already get from the method name.

The same holds for the 37 public validators that carry no XML doc: every one of them was undocumented before this PR as well (git show HEAD~1 on any of them shows the same). The repository builds with 3354 CS1591 warnings, so undocumented public members are a pre-existing, repository-wide condition and not something this PR introduces. Fixing it belongs in its own change, where it can be reviewed as documentation rather than buried under a mechanical signature migration.

Addressed: ValidateBatch itself now carries a doc comment (73821dd) — it is the validator this release exists to make reachable, and it sat undocumented. BatchUtils.Build gained its <exception> documentation in the main commit, since it now throws where it previously did not.

CI

build-and-lint pass, unit pass, integration skipping (expected — it runs on merge_group for PRs into dev). Locally, against the docker-compose.ci.yml stand: 346 integration tests passed, 0 failed, alongside 1487 unit tests.

@Platonenkov
Platonenkov added this pull request to the merge queue Sep 15, 2026
Merged via the queue into dev with commit b73762a Sep 15, 2026
4 checks passed
@Platonenkov
Platonenkov deleted the claude/batchutils-build-validation-8d5a6f branch September 15, 2026 19:43
@Platonenkov Platonenkov mentioned this pull request Sep 15, 2026
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