Skip to content

feat: real resultXdr/resultMetaXdr and FAILED transaction reporting#37

Open
RaoulSchaffranek wants to merge 5 commits into
mainfrom
feat/tx-result-xdr
Open

feat: real resultXdr/resultMetaXdr and FAILED transaction reporting#37
RaoulSchaffranek wants to merge 5 commits into
mainfrom
feat/tx-result-xdr

Conversation

@RaoulSchaffranek

@RaoulSchaffranek RaoulSchaffranek commented Jul 3, 2026

Copy link
Copy Markdown
Member

getTransaction previously served empty-string stubs for resultXdr/resultMetaXdr, which violates the RPC spec (real base64 XDR or omitted) — on SUCCESS receipts written by the semantics and on the FAILED receipts the server records for stuck transactions alike.

What changed:

  • The K semantics record the contract call's return value in the receipt as a JSON-encoded SCVal (returnValue), extracted from the host stack after the invocation instead of resetting the host first. New #scValToJSON covers the SCVal surface (ints, bool, void, symbol, string, bytes, addresses, vec, map).
  • The server rewrites that internal field into real resultXdr (base64 TransactionResult, txSUCCESS/txFAILED) and resultMetaXdr (base64 TransactionMeta v3 with sorobanMeta.returnValue) before the receipt is served. XDR construction lives in Python (result_xdr.py, scval.py additions) because K cannot build XDR — per the architecture notes this is the sanctioned exception to "formatting belongs in K".
  • FAILED receipts now carry a real error resultXdr (txFAILED with INVOKE_HOST_FUNCTION_TRAPPED) instead of the stubs, pinning ledger to the latest ledger at failure time and createdAt to submission time; resultMetaXdr is omitted since a rolled-back run produces no meta.
  • Fixed the zero-length edge of the receipt's hex encoding: #bytesToHex on empty Bytes returned "0" (odd-length, non-hex), which broke the XDR rewrite for contracts returning empty Bytes. Empty Bytes now encode as the empty string. _attach_result_xdr is also hardened to persist the receipt without the internal returnValue field even if the rewrite fails, so stored receipts never leak K-internal fields.

Testing: new integration tests decode the receipts with stellar_sdk and cover the invocation return value via sorobanMeta.returnValue (add(2, 3) -> U32(5)), the empty-Bytes regression (SCV_BYTES of b""), decodable txSUCCESS results across the whole lifecycle (create account, upload, deploy, invoke), FAILED receipts with a decodable txFAILED result, NOT_FOUND field omission, and that returnValue never appears in responses. Full test_server.py suite passes (31 tests); flake8/isort/black clean, no new mypy errors.

Per the official RPC spec, resultXdr/resultMetaXdr are base64-encoded
TransactionResult/TransactionMeta XDR structs, present when the status
is SUCCESS or FAILED; the current empty-string stubs are not valid XDR.

The new tests decode the receipts with stellar_sdk and check the
success result code, the invocation return value reported via
sorobanMeta.returnValue (add(2, 3) -> U32(5)), the txFAILED error
result for a transaction that invokes a missing contract (with
ledger/createdAt shaped consistently with SUCCESS receipts), and that
NOT_FOUND responses omit all transaction fields.

The three XDR-content tests fail until the feature lands; the
NOT_FOUND guard already passes.
Receipts now carry a real base64 TransactionResult in resultXdr and
TransactionMeta v3 in resultMetaXdr instead of empty-string stubs.

The semantics record the contract call's return value: uncheckedCallTx
no longer resets the host after the call, so #recordAndRespond can read
the returned ScVal off the hostStack, serialise it into the receipt's
internal returnValue field (JSON-encoded via the new #scValToJSON), and
reset the host afterwards. The server rewrites that field into the
spec-mandated XDR structs (new result_xdr.py builders, scval_from_json
decoder), since K cannot construct XDR. An invocation's return value is
reported as sorobanMeta.returnValue in the meta, matching stellar-rpc.

Failed transactions now store a txFAILED TransactionResult in the
FAILED receipt (resultMetaXdr is omitted: a rolled-back run produces no
meta), keeping ledger/createdAt encoded as on SUCCESS receipts.

Fees and ledger-entry change sets in the synthesised structs are
zero/empty because komet-node does not track them; documented in
docs/notes.md.
The receipt's internal returnValue field is a K-side implementation
detail that the server rewrites into resultXdr/resultMetaXdr; assert it
never appears in getTransaction responses on any status. The leak is
reachable today: if _attach_result_xdr raises, the receipt keeps the
raw field and getTransaction serves it as-is.

Add a regression test for exactly that trigger: a contract returning
empty Bytes. #bytesToHex renders zero-length bytes as "0", which
scval_from_json rejects (odd-length hex), so sendTransaction currently
returns Internal error and the receipt leaks. The test pins the correct
behaviour (sorobanMeta.returnValue = SCV_BYTES of b"") and fails until
the encoding is fixed.
Base2String yields "0" for the zero-length case, an odd-length non-hex
value that broke the receipt's XDR rewrite for contracts returning empty
Bytes. Add a dedicated K rule mapping empty Bytes to the empty string.
Also harden _attach_result_xdr: persist the receipt without the internal
returnValue field even when the rewrite fails, so a stored receipt never
leaks K-internal fields to getTransaction.
@RaoulSchaffranek RaoulSchaffranek changed the title Real result XDR in getTransaction receipts feat: real resultXdr/resultMetaXdr and FAILED transaction reporting Jul 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.

1 participant