Skip to content

feat(heph): server-side credit transfer support#161

Merged
odesenfans merged 14 commits into
mainfrom
feat/credit-transfer-heph
Apr 30, 2026
Merged

feat(heph): server-side credit transfer support#161
odesenfans merged 14 commits into
mainfrom
feat/credit-transfer-heph

Conversation

@odesenfans
Copy link
Copy Markdown
Contributor

Summary

  • Implements the server-side handler for aleph_credit_transfer POST messages — heph now validates, records, and applies transfers emitted by aleph credit transfer (CLI side merged in feat(cli): add 'aleph credit transfer' subcommand #158).
  • Adds crates/heph/src/handlers/credit_transfer.rs with process_in_tx(tx, sender, item_hash, raw_content) doing deserialize → schema validate → self-transfer reject → balance check → atomic apply (recipient + sender credit_history rows, recipient upsert, sender decrement). handlers/post.rs dispatches credit-transfer posts inside a single SQL transaction so the post insert and the apply share atomicity (rollback on any error).
  • Schema delta to credit_history: nullable message_hash, counterparty, expiration_at columns + index on message_hash. No migration (greenfield DB in scope).
  • SDK refactor: hoisted the credit-transfer schema (CreditTransferContent etc.) out of the credits-feature-gated aleph_sdk::credit module into a new always-compiled aleph_sdk::credit_transfer module so heph doesn't pull alloy as a transitive dep.

Out of scope (deferred): aleph_credit_distribution / aleph_credit_expense handlers, FIFO consumption with expiration carry-over, whitelist gating, balance recompute from history, expiration enforcement (column is advisory metadata only).

Test plan

  • cargo test -p aleph-sdk credit_transfer:: — 8 schema/validation unit tests pass
  • cargo test -p heph credit_transfer — 8 handler unit tests + 3 end-to-end tests through process_message pass (success, insufficient-balance rollback verified by zero post + zero history rows, self-transfer rejection)
  • just test — full workspace, 689+ tests, 0 failures
  • just check-typing — clippy -D warnings clean
  • Manual: confirm git diff --stat origin/main..HEAD touches only the 10 files in the design (4 SDK/CLI for the schema move + 6 in heph)

🤖 Generated with Claude Code

Copy link
Copy Markdown

@foxpatch-aleph foxpatch-aleph left a comment

Choose a reason for hiding this comment

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

Well-structured implementation of server-side credit transfer support. The key design strengths are: (1) atomic post-insert + credit-apply in a single SQL transaction, preventing partial state; (2) proper validation order (deserialize → schema validate → self-transfer reject → balance check → apply); (3) parameterized SQL throughout (no injection); (4) the new credit_transfer module cleanly decouples the transfer schema from the alloy-dependent credit module; (5) solid test coverage with 8 handler unit tests and 3 end-to-end tests covering success, insufficient-balance rollback, and self-transfer rejection. The schema extension with nullable message_hash/counterparty/expiration_at is backward-compatible with existing distribution entries. No bugs or security issues found.

crates/heph/src/handlers/credit_transfer.rs (line 58): The comment about RFC3339 with Z suffix vs +00:00 is useful context — good callout for cross-ecosystem consistency with pyaleph.

crates/heph/src/handlers/post.rs (line 126): The unchecked_transaction choice is called out and justified in the comment. The commit() correctly propagates errors via map_err to ProcessingError::InternalError, ensuring the ? operator on the closure return value surfaces failures to process_message.

Copy link
Copy Markdown

@foxpatch-aleph foxpatch-aleph left a comment

Choose a reason for hiding this comment

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

The credit transfer handler is well-structured with clean separation between deserialization, validation, and apply phases. The SDK module refactor and atomic transaction wrapping are solid. However, there's a correctness bug: when a credit transfer fails mid-dispatch (e.g., insufficient balance), the message is already committed to the messages table with status processed because step 6 runs before step 7 (type-specific dispatch). On retry, the duplicate check silently skips the message, so failed credit transfers are permanently stuck as processed despite no post being created.

crates/heph/src/handlers/mod.rs (line 395): Messages table commit before type-specific dispatch creates inconsistent state for failed credit transfers. When process_post rolls back a credit transfer (e.g. insufficient balance), the messages table already has the item_hash committed with status processed. On retry, step 1 sees status processed and silently skips (line 304: "processed" => return Ok(())). The message is permanently stuck as processed despite the post never being created. Consider either: (a) inserting the message row inside the same transaction as the post/credit-apply, or (b) defaulting credit-transfer messages to pending status and only promoting to processed after the apply succeeds, or (c) setting status to rejected when dispatch fails.

crates/heph/src/handlers/credit_transfer.rs (line 52): Consider adding a test for multi-recipient transfers. The current test suite only covers single-recipient cases. A test with 2+ recipients would verify that the sender balance decrement works correctly across loop iterations (same sender row, multiple updates).

crates/heph/tests/integration.rs (line 957): Minor: body.contains("\"code\":6") is sensitive to JSON serialization format. If the JSON serializer adds spacing (e.g. "code": 6), this would fail. Consider parsing the JSON response or using a less fragile match.

@odesenfans odesenfans merged commit 9c78a7b into main Apr 30, 2026
9 checks passed
@odesenfans odesenfans deleted the feat/credit-transfer-heph branch April 30, 2026 16:06
@odesenfans odesenfans mentioned this pull request May 3, 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.

2 participants