refactor: ChainStore, RpcError, typed boundaries, errors module#44
Merged
Conversation
… module
A bigger-picture pass following the earlier contained cleanups. Four related
changes, no behaviour change (90 tests pass, make check clean):
1. errors.py: one home for the internal exceptions. transaction.py raised the
interpreter's NodeInterpreterError for encoding failures, forcing a
transaction -> interpreter dependency that existed only for that type. It now
raises TransactionEncodingError and no longer imports interpreter, removing
the one semantically-wrong edge in the module graph.
2. RpcError replaces the union-encoded control flow. The envelope builders
returned dict | str | None, where str secretly meant "pre-formatted error"
and None "unknown method", forced open with isinstance(x, str). They now
return dict and raise RpcError; handle_rpc delegates to _dispatch and catches
once. request_id drops out of the builders' signatures.
3. ChainStore (store.py) owns the io-dir layout. StellarRpcServer conflated HTTP
transport, disk layout, and RPC orchestration, with ~5 scattered inline
metadata reads and path-joins. The store is now the sole reader/writer of
state.kore, metadata.json, and the receipts/ ledgers/ events/ requests/ wasms/
files; the server asks it for records. server.io_dir/state_file are kept for
callers and tests.
4. TypedDicts at the boundaries: TxRequest/SimulateRequest (encoder outputs),
SimulateResult (K result), and LedgerRecord/EventRecord (disk records).
interpreter.run widened to Mapping[str, Any] to accept them. Read-only
envelopes stay dict[str, Any] on purpose — they are built by {**base} spread
and pattern-matched dynamically by node.md, where TypedDicts fight the idiom.
test_unit.py updated to expect TransactionEncodingError.
RaoulSchaffranek
changed the base branch from
refactor/implementation-dedup-dead-code
to
main
July 23, 2026 09:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A bigger-picture architectural pass, following the contained cleanups in #43. Stacked on #43 (base is that branch, not
main) because both heavily touchserver.py— retarget tomainonce #43 merges. No behaviour change: all 90 tests pass andmake checkis clean.This grew out of three questions: what does the dependency graph look like, which stateful components can be isolated, and how strong are the types. Four related changes:
1.
errors.py— decouple the encoder from the interpretertransaction.pyraised the interpreter'sNodeInterpreterErrorfor encoding failures (sub-stroop XLM, bad strkey), which forced atransaction → interpreterimport that existed only for that exception type — the one semantically-wrong edge in the module graph. A newerrors.pycollects the internal exceptions (NodeInterpreterError,TransactionEncodingError,RpcError); the encoder now raisesTransactionEncodingErrorand no longer importsinterpreterat all.2.
RpcError— remove union-encoded control flowThe envelope builders returned
dict[str, Any] | str | None, wherestrsecretly meant "pre-formatted JSON-RPC error" andNonemeant "unknown method", disambiguated at the call site withisinstance(read_only_envelope, str). They now returndict[str, Any]and raiseRpcError;handle_rpcdelegates to a_dispatchhelper and catchesRpcErroronce.request_iddrops out of the builder signatures (it was only there for error formatting). This matches the exception-based styleledger_entries.pyalready used.3.
ChainStore(store.py) — isolate the stateful disk layerStellarRpcServerconflated HTTP transport, io-dir/disk layout, and RPC orchestration, with the disk schema smeared across ~6*_dirattributes and ~5 scattered inlinejson.loads((io_dir/'metadata.json')…)reads.ChainStorenow owns the layout and is the sole reader/writer ofstate.kore,metadata.json, and thereceipts/ ledgers/ events/ requests/ wasms/files. The server holds one and asks it for receipts, ledgers, events, and the ledger counter.server.io_dir/server.state_fileare preserved for callers and tests.4. TypedDicts at the boundaries
TxRequest/SimulateRequest— the encoder's request envelopesSimulateResult— the K simulate result shapeLedgerRecord/EventRecord— the disk records the store reads/writesinterpreter.runwidened toMapping[str, Any]so typed envelopes are assignable. FunctionalTypedDict(...)syntax is used where keys are camelCase JSON wire names (sopep8-namingstays happy).Deliberately left as
dict[str, Any]The read-only request envelopes (
_read_only_envelopeand friends) — they are built by{**base, …}spread and pattern-matched dynamically bynode.md, where aTypedDictfights the idiom (spreads widen todict[str, object], forcing casts) for little gain. Noted rather than forced.Module graph after this change
Test changes
test_unit.pynow expectsTransactionEncodingErrorfrom_xlm_to_stroops(wasNodeInterpreterError) — a direct consequence of change #1.