Skip to content

Поток доступен через IXrplClient: события на клиенте, проброс к соединению - #108

Merged
Platonenkov merged 2 commits into
devfrom
claude/client-stream-events-8fd79c
Aug 20, 2026
Merged

Поток доступен через IXrplClient: события на клиенте, проброс к соединению#108
Platonenkov merged 2 commits into
devfrom
claude/client-stream-events-8fd79c

Conversation

@Platonenkov

@Platonenkov Platonenkov commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

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

Зачем

Предыдущий релиз дал стрим-событиям Raw и RawTransaction — байты, которые прислал узел. Смысл этого — кошелёк показывает человеку транзакцию перед тем, как он её подпишет. Транзакции приходят потоком.

А добраться до потока можно было только так:

client.connection.OnTransaction += handler;

connection — свойство конкретного класса. Код, написанный против IXrplClient, не мог ни подписаться, ни быть покрыт тестом с подставным клиентом. Получалась возможность без контракта, через который её взять.

Что сделано

Все 16 событий, которые поднимает Connection, объявлены на IXrplClient:

IXrplClient client = ...;
client.OnTransaction += r => { /* r.RawTransaction — байты узла */ };

Прежняя форма client.connection.OnX работает без изменений — поверхность добавлена, а не перенесена.

Проброс, а не ретрансляция

Это главное решение, и оно намеренное:

public event OnTransaction OnTransaction
{
    add => connection.OnTransaction += value;
    remove => connection.OnTransaction -= value;
}

Подписчик регистрируется на том же самом Connection, к которому обратился бы напрямую. Клиент не хранит ни делегатов, ни своего списка подписчиков, ни подписки, которую никто не снимает — ссылок ровно столько же, сколько было раньше.

Ретранслирующая версия (локальное событие плюс подписка на connection, которая его перевыбрасывает) добавила бы всё перечисленное и второе место, которое нужно держать в согласии с первым.

Предпосылка безопасности проверена по коду: Connection присваивается один раз, в конструкторе, а ChangeServer меняет сессию внутри объекта, а не сам объект — подписки переживают смену сервера и реконнект.

Ломающее

IXrplClient.connection потерял сеттер и стал get-only. С обработчиками, привязанными через события, подмена соединения оставила бы их все на старом объекте, и поток замолчал бы без единого признака. В дереве сеттер снаружи не использовался.

Get-only, а не private set: инвариант «присваивается один раз» теперь проверяет компилятор, а не соглашение внутри класса на 1100 строк.

Проверка

  • dotnet build XrplCSharp.sln — 0 ошибок;
  • 1110 юнит-тестов, 0 падений;
  • форма проброса закреплена тестами, которые пересекают поверхности: подписка через клиента → снятие через connection, и обратно. Это не педантизм: первая версия теста снимала подписку той же поверхностью, которой ставила, и ретрансляцию не отличала — мутация это показала;
  • обратный случай добавлен после ревью, которое обнаружило, что клиентский remove не исполнялся ни одним тестом: замена -= на += в нём проходила весь набор.

Показательная деталь: FeeTestClient в тестах пришлось дополнить объявлениями событий. Это и есть признак того, что не хватало именно контракта — пока события жили только на Connection, подставной клиент их нести не мог.

Что не входит

Восстановление подписок после переподключения (#104) и наблюдаемость вытеснения событий из канала (#105) — отдельные задачи, контракт не меняют и в мажорный релиз не обязаны.

Summary by CodeRabbit

  • New Features

    • Added access to all connection stream events through the client interface.
    • Event subscriptions now work consistently through either the client or its connection.
    • Prevented accidental replacement of the client’s active connection.
  • Bug Fixes

    • Improved event handler removal across both client and connection access points.
    • Preserved existing connection-based event subscriptions.

The raw bytes this release puts on stream events exist so a wallet can show a
person what a node actually sent before they sign it. Transactions arrive by
stream - and the only way to receive one was client.connection.OnTransaction, a
property of a concrete class. Code written against IXrplClient could neither
subscribe nor be exercised against a substitute client, so the feature had no
contract to reach it through.

All 16 events Connection raises are now declared on IXrplClient. The existing
client.connection.OnX form keeps working; this adds a surface rather than
moving one.

Forwarded, not relayed, and that is the design rather than an implementation
detail: add/remove reach the same Connection directly, so the client holds no
delegates, no subscriber list of its own, and no subscription that nothing ever
removes. A relaying version would introduce all three plus a second place to
keep in sync. Safe because the Connection outlives the client - assigned once in
the constructor, and ChangeServer swaps the session inside it rather than the
object, so subscriptions survive a server change.

IXrplClient.connection loses its setter (breaking, though nothing in the tree
assigned it). With handlers attached through these events, replacing the
connection would strand every one of them on the old object and the stream
would go quiet with nothing to show for it.

The forwarding shape is pinned by a test that crosses surfaces - subscribe
through the client, remove through the connection - because removing through
the surface it was added to passes either way. Proven by mutation: a relaying
implementation turns it red.

FeeTestClient now declares the events, which is the tell that this was the
missing piece: a substitute client could not carry them while they lived only
on Connection.
…piler hold the invariant

Review found the forwarding tests never executed the client's remove accessor:
the existing case subscribes through the interface but removes through the
connection, so turning `connection.OnX -= value` into `+=` left the entire suite
green. Half the unsubscribe contract was unverified.

Added the mirror case - subscribe through the connection, remove through the
client - which turns that mutation red. Renamed the existing test to say what it
actually does rather than what its name claimed.

connection is get-only now instead of `private set`. The one-assignment
invariant that forwarding depends on was held only by convention inside an
1100-line class; a second assignment would have compiled and stranded every
handler on the old object. The compiler checks it now.
@Platonenkov

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 20, 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: 339cc0f2-54f7-4152-b267-0ec809771cc4

📥 Commits

Reviewing files that changed from the base of the PR and between bd2b604 and fa99460.

📒 Files selected for processing (4)
  • CHANGES.md
  • Tests/Xrpl.Tests/Client/TestUClientStreamEvents.cs
  • Tests/Xrpl.Tests/Sugar/TestUAutofillFees.cs
  • Xrpl/Client/IXrplClient.cs

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


📝 Walkthrough

Walkthrough

IXrplClient now exposes all Connection stream events through direct forwarding. Its connection property is read-only. Tests verify event delivery, cross-surface handler removal, and connection immutability.

Changes

Stream event contract

Layer / File(s) Summary
Client event contract
Xrpl/Client/IXrplClient.cs
IXrplClient declares sixteen connection, stream, warning, lifecycle, and status events. Its connection property is now get-only.
Direct event forwarding
Xrpl/Client/IXrplClient.cs, CHANGES.md
XrplClient forwards event add/remove operations directly to its persistent Connection. The mutable connection setter and obsolete commented declarations were removed.
Contract and behavior validation
Tests/Xrpl.Tests/Client/TestUClientStreamEvents.cs, Tests/Xrpl.Tests/Sugar/TestUAutofillFees.cs
Tests verify transaction delivery, raw payload preservation, handler removal across both surfaces, and the absence of a connection setter. FeeTestClient implements the expanded contract.

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

Merge Risk: ⚪ Minimal · up to fa994

The PR exposes existing connection events through the client interface while preserving direct connection usage; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Consumer
  participant IXrplClient
  participant Connection
  Consumer->>IXrplClient: subscribe to OnTransaction
  IXrplClient->>Connection: add event handler
  Connection->>Consumer: deliver transaction event
Loading

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 summarizes the main change: exposing stream events through IXrplClient and forwarding them to the connection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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/client-stream-events-8fd79c

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