Skip to content

fix(client)!: Request(Dictionary) отправляет api_version, а не ApiVersion - #99

Merged
Platonenkov merged 1 commit into
devfrom
claude/request-dictionary-api-version-9c597e
Aug 17, 2026
Merged

Platonenkov merged 1 commit into
devfrom
claude/request-dictionary-api-version-9c597e

Conversation

@Platonenkov

@Platonenkov Platonenkov commented Aug 17, 2026 •

Copy link
Copy Markdown
Collaborator

Закрывает #97.

Дефект

IXrplClient.Request(Dictionary<string, object>) клеймил версию под ключом nameof(ApiVersion) — буквально "ApiVersion". Словарь сериализуется дословно, а rippled знает только api_version: незнакомые поля он игнорирует и отвечает по своей версии по умолчанию.

Три написания, замеренные на mainnet, не равнозначны:

без поля версии вообще  -> transaction (v1),  верхнеуровневый hash: нет
"api_version":2         -> tx_json (v2),      верхнеуровневый hash: да
"ApiVersion":2          -> transaction (v1),  верхнеуровневый hash: нет

Написание с большой буквы неотличимо от полного отсутствия поля. Следствие: client.AccountInfo(…) уходил по v2, а client.Request(new Dictionary { ["command"] = "account_info" }) у того же клиента — по v1, формы ответов расходились, и ничто об этом не сигналило. Типизированный путь был исправен всегда — BaseRequest.ApiVersion помечен [JsonPropertyName("api_version")].

Доказательство на одном клиенте, дискриминатор — версия, которую узел заведомо не умеет:

подключились на api_version 2, затем client.ApiVersion = 99

типизированный   ServerInfo()        : ОТКАЗ  -> invalid_API_version (maximum supported 3)
нетипизированный Request(dictionary) : УСПЕХ  -> версия не доставлена

Правка

Ключ — имя с провода. Версия, заданная вызывающим прямо в словаре, по-прежнему уважается. Мусорное поле "ApiVersion" больше не едет в каждом запросе. Заодно убраны двойные скобки и неиспользуемый out var value.

Слом контракта

Выбран первый из трёх вариантов, описанных в #97. Вызывающие нетипизированный путь переезжают с API v1 на значение ApiVersion, а оно по умолчанию 2 — формы ответов меняются под кодом, который сам не менялся. Это и есть починка, а не побочный эффект: прежнее поведение игнорировало настройку целиком, так что «работает как раньше» здесь означает «настройка не работает». Кому нужна v1 — задают ["api_version"] = 1 в словаре или ApiVersion на клиенте.

Внутри репозитория словарный путь используют только тесты и демо-консоль (ledger_accept, server_info, бенчмарк ledger_data); боевой код SDK на нём не завязан.

Тесты

TestURequestApiVersion читает то, что клиент реально кладёт на провод, через сервер-перехватчик запросов. Это принципиально: поле, которое узел игнорирует, из ответа не видно — именно поэтому дефект прожил так долго и не ловился ни одним существующим тестом. Написаны до правки, падали все три; в сообщении об ошибке был виден сам провод: {"command":"ledger_current","ApiVersion":2,"id":"…"}.

Пинуется: имя поля на нетипизированном пути, что явный api_version от вызывающего не перезаписывается, и что оба пути одного клиента несут одну версию.

Отдельно отмечу пойманную ошибку в собственном тесте: первый вариант третьего теста проходил по неверной причине — я разводил пути по имени команды server_info, а Connect() сам шлёт типизированный server_info и подменял собой словарный запрос. Переписан на отдельную команду для словарного пути.

Чтобы не заводить третью копию разбора клиентских фреймов, ReadTextFrameAsync поднят из PagedResponseServer в общий WebSocketTestServerBase.

Проверки

  • юнит-тесты: 1119 / 1119
  • интеграционные на standalone-ноде в Docker (.ci-config/docker-compose.ci.yml, xrpld 3.3.0): 265 / 265, ноль пропусков

Интеграционные здесь не формальность, а проверка главного риска правки: словарный путь в тестах и демо теперь ходит по v2, и если бы какая-то команда отвечала в v2 иначе, чем ожидают модели, это вылезло бы именно там.

Версия остаётся 10.12.0.0 — она ещё не уехала в release, запись добавлена в ту же секцию.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed API version handling for untyped requests, including correct wire-format field names and preservation of caller-selected versions.
    • Improved transaction-stream deserialization and API v1 hash handling.
    • Reduced unnecessary allocations during response parsing and warning/error callbacks.
    • Improved UTF-8 WebSocket frame processing and cancellation behavior.
    • Prevented duplicate parsing of error responses.
  • Tests

    • Added coverage for API version propagation, WebSocket communication, allocation behavior, cancellation, and response envelopes.

…sion

Словарный путь клеймил версию под nameof(ApiVersion), то есть буквально
"ApiVersion". Словарь сериализуется дословно, а rippled знает только
api_version: незнакомые поля он игнорирует и отвечает по своей версии по
умолчанию, то есть по v1.

Замер на mainnet показывает, что три написания не равнозначны: "api_version":2
даёт форму v2, а "ApiVersion":2 и полное отсутствие поля — одинаково v1. То
есть client.AccountInfo(...) уходил по v2, а client.Request({command:
account_info}) у того же клиента — по v1, и формы ответов расходились без
всякого сигнала. Типизированный путь был исправен всегда: BaseRequest.ApiVersion
помечен [JsonPropertyName("api_version")].

Теперь ключ — имя с провода, а версия, заданная вызывающим в самом словаре,
по-прежнему уважается. Мусорное поле "ApiVersion" больше не едет в каждом
запросе. Заодно убраны двойные скобки и неиспользуемый out var value.

TestURequestApiVersion читает то, что клиент реально кладёт на провод, через
сервер-перехватчик: поле, которое узел игнорирует, из ответа не видно, и именно
поэтому дефект прожил так долго. Чтобы не плодить третью копию разбора
клиентских фреймов, ReadTextFrameAsync поднят из PagedResponseServer в общий
WebSocketTestServerBase.

BREAKING CHANGE: вызывающие нетипизированный путь переезжают с API v1 на
значение ApiVersion, а оно по умолчанию 2 — формы ответов меняются под кодом,
который сам не менялся. Это и есть починка: прежнее поведение игнорировало
настройку целиком. Кому нужна v1 — задают ["api_version"] = 1 в словаре или
ApiVersion на клиенте.

Closes #97
@Platonenkov

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 17, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Review 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: Pro

Run ID: 0f9ad6f5-b291-4cc4-b623-3d4c84067631

📥 Commits

Reviewing files that changed from the base of the PR and between 9c597e2 and da98e63.

📒 Files selected for processing (6)
  • CHANGES.md
  • Tests/Xrpl.Tests/Client/PagedResponseServer.cs
  • Tests/Xrpl.Tests/Client/RequestCapturingServer.cs
  • Tests/Xrpl.Tests/Client/TestURequestApiVersion.cs
  • Tests/Xrpl.Tests/Client/WebSocketTestServerBase.cs
  • Xrpl/Client/IXrplClient.cs

Included review availability: 3 reviews are currently available. Based on recent review activity, included reviews refill at 4 per hour.


📝 Walkthrough

Walkthrough

The change corrects api_version serialization for untyped requests, adds captured-wire integration tests, and moves WebSocket frame parsing into shared test infrastructure. The changelog records these corrections and related test coverage.

Changes

API version propagation and WebSocket test support

Layer / File(s) Summary
Untyped request API-version propagation
Xrpl/Client/IXrplClient.cs, Tests/Xrpl.Tests/Client/RequestCapturingServer.cs, Tests/Xrpl.Tests/Client/TestURequestApiVersion.cs, CHANGES.md
Dictionary requests now use api_version, preserve caller-supplied values, and receive captured-wire integration coverage.
Shared WebSocket frame reader
Tests/Xrpl.Tests/Client/WebSocketTestServerBase.cs, Tests/Xrpl.Tests/Client/PagedResponseServer.cs
WebSocketTestServerBase now parses client frames, and PagedResponseServer removes its duplicate reader.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to da98e

The change corrects the request version field while preserving explicit caller overrides, with no actionable merge-blocking risk remaining after normal checks and review.

Possibly related issues

  • StaticBit-io/XrplCSharp issue 97 — Directly describes the ApiVersion versus api_version propagation defect fixed here.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: dictionary requests now send the wire field name "api_version" instead of "ApiVersion".
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/request-dictionary-api-version-9c597e

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

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