[ANCHOR-1302]: Observe destination accounts in SEP6/24 - #2010
Conversation
…ment observer * request_onchain_funds now upserts the destination account as transient for sep-6 and sep-24, matching existing sep-31 behavior
There was a problem hiding this comment.
Pull request overview
Registers SEP-6 and SEP-24 destination accounts with the Payment Observer.
Changes:
- Adds transient observation for SEP-6/24 destinations.
- Adds verification for explicit destination accounts.
- Critical: Registration occurs before transaction persistence, allowing payments to be permanently missed.
- Moderate: Muxed destinations are registered non-canonically and cannot be matched.
- Moderate: SEP-6/24-only deployments do not run transient-account cleanup.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
platform/src/test/kotlin/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandlerTest.kt |
Verifies SEP-6/24 registration calls. |
platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java |
Registers SEP-6/24 destination accounts for observation. |
Suppressed comments (2)
platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java:331
- This has the same muxed-account mismatch for SEP-24:
upsertstores theM...address, while observer lookups normalize muxed destinations to their baseG...address. Consequently, registering a muxed destination here does not make its payments observable. Normalize accounts centrally inPaymentObservingAccountsManager.upsertand add a muxed SEP-24 test.
paymentObservingAccountsManager.upsert(
txn24.getToAccount(), PaymentObservingAccountsManager.AccountType.TRANSIENT);
platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java:331
- The SEP-24 account is exposed to the observer before the transaction is saved in
pending_user_transfer_start(RpcTransactionStatusHandler.java:180-206). If a payment lands during this interval, the listener cannot match it by status and the observer still advances its cursor, so that payment is not reconsidered. Move registration after successful persistence or introduce retry handling for unmatched observed payments.
paymentObservingAccountsManager.upsert(
txn24.getToAccount(), PaymentObservingAccountsManager.AccountType.TRANSIENT);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…muxed keys * request_onchain_funds now registers the observed account after the transaction save completes instead of before, for sep-6/24/31 alike * payment observing accounts manager canonicalizes muxed accounts to their base account on write, matching existing read-side behavior * eviction scheduler now starts when sep-6 or sep-24 is enabled, not just sep-31
* refactor persistence operations to handle failures gracefully * add logging for store operation exceptions * update upsert to canonicalize accounts and delete stale entries * add tests for resilient persistence and legacy account cleanup
* update account merging logic to preserve newer 'last observed' timestamps * fix legacy muxed account deletion to ensure persistence on canonical write failures * add tests for updated account management behaviors
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java:368
- The acceptance criteria say SEP-31 behavior is unchanged, but this branch moves its registration from
updateTransactionWithRpcRequestto a hook that runs aftertxn31Store.save. That reverses the existing ordering and leaves an interval where the transaction is ready for payment but its non-residential destination is not observed; the observers advance their cursor past unobserved payments. Keep SEP-31 registration in its existing location and limit the new behavior to SEP-6/24, or coordinate the state change and registration so this gap cannot lose an event.
case SEP_31:
toAccount = ((JdbcSep31Transaction) txn).getToAccount();
break;
* refactor upsert and updateLastObservedTime methods to use atomic map operations * add a concurrent test to validate thread-safe account updates * add a noop payment observing account store for testing purposes
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
platform/src/main/java/org/stellar/anchor/platform/rpc/RequestOnchainFundsHandler.java:356
- This no longer leaves SEP-31 behavior unchanged: the old registration ran during
updateTransactionWithRpcRequest, beforetxn31Store.save, while this hook runs afterward. That creates a new interval where the observer will discard a payment to the destination because the account is not yet registered, and streamed events are not replayed after registration. Please preserve the established SEP-31 ordering (and apply a deliberate, race-safe ordering to SEP-6/24) rather than moving all registrations behind the save.
@Override
protected void afterTransactionSaved(JdbcSepTransaction txn, RequestOnchainFundsRequest request)
Description
RequestOnchainFundsHandler.updateTransactionWithRpcRequestassigns atoAccountfor the withdrawal/receive leg of every protocol, but only the SEP-31 branch tells the Payment Observer to watch that account — it callspaymentObservingAccountsManager.upsert(txn31.getToAccount(), TRANSIENT)unconditionally, after either the custom-destination path or the auto-generated-deposit-info path. The SEP-6 and SEP-24 branches settoAccount/withdrawAnchorAccountthe same way but never callupsert.In practice this is masked whenever the deposit info generator hands back the anchor's own distribution account, since
PaymentObserverBeansregisters every configured distribution account asRESIDENTIALat startup — already-watched, so the missingupsertis a no-op in that case. The gap only surfaces whenrequest_onchain_fundsis called with the "none" deposit-info generator and an RPC-supplieddestination_accountthat isn't the anchor's distribution account: nothing ever registers that address with the observer, so an incoming payment to it is never matched back to the transaction.Changes
RequestOnchainFundsHandler.updateTransactionWithRpcRequest: aftertxn6.setToAccount(...), callspaymentObservingAccountsManager.upsert(txn6.getToAccount(), TRANSIENT); aftertxn24.setToAccount(...), calls the equivalent fortxn24. Placed unconditionally after both branches (custom destination and auto-generated), mirroring the existing SEP-31 call.RequestOnchainFundsHandlerTest: addedverify(exactly = 1) { paymentObservingAccountsManager.upsert(DESTINATION_ACCOUNT, TRANSIENT) }to the existing SEP-24 (test_handle_ok_sep24_withExpectedAmount) and SEP-6 (test_handle_sep6_ok_withoutAmountExpected) tests that already exercise a customdestination_account.Acceptance Criteria
request_onchain_fundscall with the "none" generator and an explicitdestination_accountregisters that account with the Payment Observer asTRANSIENT.request_onchain_fundscall with the "none" generator and an explicitdestination_accountregisters that account with the Payment Observer asTRANSIENT.RequestOnchainFundsHandlerTesttests pass.Context
N/A
Testing
./gradlew :platform:test --tests "org.stellar.anchor.platform.rpc.RequestOnchainFundsHandlerTest"./gradlew :core:test :platform:test— full module regression, no failuresDocumentation
N/A
Known limitations
N/A