Skip to content

Commit 6d3595a

Browse files
Merge remote-tracking branch 'origin/main' into feat/get-transactions-ledgers
# Conflicts: # docs/architecture.md # docs/node-semantics.md # docs/notes.md # src/komet_node/server.py # src/tests/integration/test_server.py
2 parents 68ceedc + b00ec93 commit 6d3595a

8 files changed

Lines changed: 467 additions & 33 deletions

File tree

docs/architecture.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ flowchart TB
5151

5252
**[Detailed documentation](server.md)**
5353

54-
The server implements eight RPC methods — `getHealth`, `getNetwork`, `getLatestLedger`, `sendTransaction`, `getTransaction`, `getTransactions`, `getLedgers`, and `traceTransaction` — and the K semantics answer all of them.
54+
The server implements ten RPC methods — `getHealth`, `getNetwork`, `getLatestLedger`, `getVersionInfo`, `getFeeStats`, `sendTransaction`, `getTransaction`, `getTransactions`, `getLedgers`, and the komet-specific `traceTransaction` extension — and the K semantics answer all of them. JSON-RPC framing (single calls, batch arrays, notifications) is handled in Python; the semantics see one request envelope per invocation.
5555

5656
`sendTransaction` always returns `PENDING` and clients poll `getTransaction` for the result — matching the Stellar RPC async pattern even though the transaction executes synchronously. See [server.md](server.md) for details.
5757

@@ -178,6 +178,6 @@ sequenceDiagram
178178

179179
- `resultXdr` / `resultMetaXdr` in `getTransaction` responses (contract return values)
180180
- `simulateTransaction` (dry-run without state mutation)
181-
- `getEvents`, `getLedgerEntries`, `getFeeStats` and other read-only RPC methods
181+
- `getEvents`, `getLedgerEntries`, `getTransactions`, `getLedgers` and other read-only RPC methods
182182
- `ExtendFootprintTTL` and `RestoreFootprint` operations
183183
- `SCVec` / `SCMap` contract-argument types in the request encoder (`scval_to_json`)

docs/node-semantics.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ insert-handleRequestFile → handleRequestFile
4040
4141
#dispatchMethod(method, request) ← routes on the "method" field
4242
43-
├─ getHealth / getNetwork / getLatestLedger / getTransaction /
44-
│ getTransactions / getLedgers / traceTransaction → #respond(...)
43+
├─ getHealth / getNetwork / getLatestLedger / getVersionInfo / getFeeStats
44+
/ getTransaction / getTransactions / getLedgers / traceTransaction → #respond(...)
4545
4646
└─ sendTransaction → #runTx → run steps
4747
→ #finalizeTx → record receipt + bump ledger → #respond(...)
@@ -63,6 +63,8 @@ If `request.json` is absent, `insert-handleRequestFile` does not fire and K halt
6363
- `getHealth``{ "status": "healthy", "latestLedger": N, "latestLedgerCloseTime": <now>, "oldestLedger": 0, "oldestLedgerCloseTime": "0", "ledgerRetentionWindow": N+1 }` (everything since genesis is retained)
6464
- `getNetwork``{ "passphrase": ..., "protocolVersion": ... }` (passphrase/version come from the request, keeping the semantics network-agnostic; `friendbotUrl` is omitted — no friendbot)
6565
- `getLatestLedger` → reads `metadata.json` and returns `{ "id": #ledgerId(seq), "protocolVersion": ..., "sequence": <latest_ledger> }`; `#ledgerId` derives a deterministic per-ledger 64-hex id from the sequence (the semantics have no hash hooks, so it is a fixed odd-constant multiply mod 2^256, which is injective)
66+
- `getVersionInfo` → echoes the version fields the server put into the envelope (`version`, `commitHash`, `buildTimestamp`, `captiveCoreVersion` — package metadata lives on the Python side); `protocolVersion` is a JSON number
67+
- `getFeeStats` → constant `#feeDistribution` objects (komet-node has no fee market) for `sorobanInclusionFee`/`inclusionFee`, plus a live `latestLedger` number from `metadata.json`; all distribution fields except `ledgerCount` are decimal strings, matching real stellar-rpc's Go `,string` encoding
6668
- `getTransaction` → reads the hash's `receipts/receipt_<hash>.json` file; returns the stored receipt merged with the ledger-range fields (`latestLedger`/`latestLedgerCloseTime`/`oldestLedger`/`oldestLedgerCloseTime`), or `{ "status": "NOT_FOUND", ... }` (with the same ledger-range fields) when the file is absent
6769
- `getTransactions` / `getLedgers` → walk the per-ledger index files `ledgers/ledger_<seq>.json` from the envelope's `startSeq` up to the latest ledger, taking at most `limit` records (`#txInfos` / `#ledgerInfos`), and format the history page (`#txHistoryPage` / `#ledgerHistoryPage`). Parameter validation and cursor resolution happen in the server; the response `cursor` (`#pageCursor`) names the page's last record when the page is full — a TOID for transactions, the plain sequence for ledgers — and is empty otherwise. Serialization matches real stellar-rpc, including its quirks: per-transaction `createdAt` is a JSON number (unlike singular `getTransaction`), per-ledger `ledgerCloseTime` is a decimal string, and empty `resultXdr`/`resultMetaXdr` stubs are omitted (`#optXdrEntry`)
6870

@@ -95,7 +97,7 @@ The trace is not part of the receipt — the executing steps already appended it
9597

9698
### traceTransaction
9799

98-
`traceTransaction` is a read-only lookup. It takes a `hash` (the same parameter `getTransaction` takes) and responds with the contents of `traces/trace_<hash>.jsonl`, or `null` when no trace file exists for that hash. Because tracing is always on, every `sendTransaction` writes this file.
100+
`traceTransaction` is a komet-specific extension, not part of the Stellar RPC specification (see [server.md](server.md#tracetransaction-komet-specific-extension)). It is a read-only lookup: it takes a `hash` (the same parameter `getTransaction` takes) and responds with the contents of `traces/trace_<hash>.jsonl`, or `null` when no trace file exists for that hash. Because tracing is always on, every `sendTransaction` writes this file.
99101

100102
### Two ways steps are delivered
101103

docs/notes.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ The tests do not yet cover `bytes` / `address` SCVal arguments or `SCVec` / `SCM
3636
- `resultXdr` / `resultMetaXdr` are empty stubs (contract return values not surfaced).
3737
- `sendTransaction`'s `errorResultXdr` is always a generic `txMALFORMED` result (no per-cause codes such as `txBAD_SEQ` — sequence numbers, fees, and signatures are not modelled), and `diagnosticEventsXdr` is never populated (both fields are optional in the spec). `TRY_AGAIN_LATER` is never returned by design: it signals mempool backpressure, which cannot arise in a synchronous node without a mempool.
3838
- `SCVec` / `SCMap` contract arguments are not yet encoded.
39-
- `simulateTransaction`, `getEvents`, `getLedgerEntries`, `getFeeStats`, and TTL/footprint operations are not implemented.
39+
- The `xdrFormat` parameter is accepted on `getTransaction`, `sendTransaction`, `getTransactions`, and `getLedgers`, but only the default `'base64'` is supported; `'json'` is rejected with `-32602`.
40+
- `simulateTransaction`, `getEvents`, `getLedgerEntries`, and TTL/footprint operations are not implemented.
41+
- `getFeeStats` reports constant distributions (there is no fee market); only its `latestLedger` is live.
4042
- `getTransactions` / `getLedgers` serve only ledgers with an index file under `ledgers/`; io-dirs created before the ledger index existed resume fine, but their earlier ledgers do not appear in the history.
4143
- Receipts are not a stable format: older io-dirs store `ledger`/`createdAt` with pre-spec types and lack `applicationOrder`/`feeBump`. Resume only io-dirs written by the same version, or start fresh.

docs/server.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ class StellarRpcServer:
2020

2121
The server is a plain `http.server.HTTPServer` (not pyk's `JsonRpcServer`). A `BaseHTTPRequestHandler` reads each POST body and calls `_handle`, which parses the JSON-RPC frame and delegates to `handle_rpc`.
2222

23+
### JSON-RPC framing
24+
25+
The server implements the JSON-RPC 2.0 framing rules, including batch calls:
26+
27+
- A single request object gets a single response object.
28+
- An **array** body is a batch: each element is validated and dispatched on its own, and the response is an array with one entry per answered element (matched by `id`; invalid elements each get an Invalid Request error with `id: null`). Batch elements run sequentially — the server is single-threaded by design, so a batch is equivalent to sending its elements one at a time. An empty array is answered with a single Invalid Request error object, per the spec.
29+
- A request without an `id` member is a **notification**: it is executed but never answered, not even with an error. A batch of only notifications (or a single notification) produces an empty response body.
30+
31+
Framing happens entirely in Python (`_handle` / `_handle_batch` / `_handle_single`); the K semantics see one request envelope per invocation regardless of how requests were framed on the wire.
32+
33+
### The `xdrFormat` parameter
34+
35+
`getTransaction` and `sendTransaction` accept the spec's optional `xdrFormat` parameter. Only `'base64'`, the spec default, is supported: XDR fields in responses are always base64 strings. The alternative `'json'` format (XDR rendered as JSON objects) is not implemented and is rejected with error `-32602` and a message saying so; any other value is rejected with `-32602` as well. The check runs before anything else, so a `sendTransaction` with a bad `xdrFormat` is rejected without executing the transaction.
36+
2337
### `handle_rpc(method, params, request_id) -> str`
2438

2539
`handle_rpc` is the dispatch entry point; it returns the JSON-RPC response envelope as a string. You can call it **without** the HTTP layer, which is convenient for scripts and tests:
@@ -86,7 +100,7 @@ Because these artifacts live on disk, the server can be stopped and restarted wi
86100

87101
## RPC methods
88102

89-
All methods are answered by the K semantics and follow the [Stellar RPC specification](https://developers.stellar.org/docs/data/apis/rpc/methods), including its serialization quirks: ledger sequence numbers and `protocolVersion` are JSON numbers, while close-time fields (`latestLedgerCloseTime`, `oldestLedgerCloseTime`, `createdAt`) are int64s serialized as JSON strings holding a decimal integer, matching what real stellar-rpc emits.
103+
All methods are answered by the K semantics and follow the [Stellar RPC specification](https://developers.stellar.org/docs/data/apis/rpc/methods) — except `traceTransaction`, which is a komet-specific extension (see below) — including its serialization quirks: ledger sequence numbers and `protocolVersion` are JSON numbers, while close-time fields (`latestLedgerCloseTime`, `oldestLedgerCloseTime`, `createdAt`) are int64s serialized as JSON strings holding a decimal integer, matching what real stellar-rpc emits.
90104

91105
### `getHealth`
92106

@@ -112,6 +126,22 @@ The node retains every ledger since genesis, so `oldestLedger` is always 0 and t
112126
{ "id": "1715609f7c74...", "protocolVersion": 22, "sequence": 4 }
113127
```
114128

129+
### `getVersionInfo`
130+
131+
`getVersionInfo` reports the komet-node package version as the RPC server version and the komet package (the K semantics of Soroban executing the transactions) as the "Captive Core". komet-node is a Python package, so no commit hash or build timestamp is baked in at build time — those fields are all-zeros / epoch placeholders with the spec-correct types. `protocolVersion` is a JSON number.
132+
133+
```json
134+
{ "version": "0.1.0", "commitHash": "0000...0000", "buildTimestamp": "1970-01-01T00:00:00", "captiveCoreVersion": "komet 0.1.79 (K semantics of Soroban)", "protocolVersion": 22 }
135+
```
136+
137+
### `getFeeStats`
138+
139+
`getFeeStats` returns constant fee distributions: komet-node has no fee market, so every statistic is the network minimum inclusion fee of 100 stroops over an empty sample (`transactionCount: "0"`, `ledgerCount: 0`). Matching real stellar-rpc, every distribution field except `ledgerCount` is a JSON string holding a decimal number; `latestLedger` is a JSON number read live from `metadata.json`.
140+
141+
```json
142+
{ "sorobanInclusionFee": { "max": "100", "min": "100", "mode": "100", "p10": "100", ..., "p99": "100", "transactionCount": "0", "ledgerCount": 0 }, "inclusionFee": { ... }, "latestLedger": 4 }
143+
```
144+
115145
### `sendTransaction`
116146

117147
`sendTransaction` submits a base64-encoded XDR transaction envelope.
@@ -127,7 +157,9 @@ Resubmitting a transaction whose hash already has a receipt returns status `DUPL
127157

128158
A transaction that decodes as XDR but cannot be processed (e.g. it contains an operation the semantics do not support) is rejected at admission time with status `ERROR` and an `errorResultXdr` carrying a `txMALFORMED` `TransactionResult` — mirroring how real stellar-rpc reports core's admission rejections. Such a transaction never reaches the ledger: no receipt is stored (`getTransaction` stays `NOT_FOUND`) and the ledger does not advance. Undecodable XDR remains a plain `-32602 Invalid params` error, as in real stellar-rpc. `TRY_AGAIN_LATER` is never returned: it signals mempool backpressure, and komet-node executes synchronously without a mempool, so the condition it reports cannot arise.
129159

130-
### `traceTransaction`
160+
### `traceTransaction` (komet-specific extension)
161+
162+
`traceTransaction` is **not part of the Stellar RPC specification** — it exists only on komet-node, and clients must not expect it from real Stellar RPC endpoints. It keeps its plain name rather than a vendor-prefixed one (`komet_traceTransaction`): the official spec has no method of that name and none is announced, so there is no collision to avoid, and renaming would break every existing client for no gain. If stellar-rpc ever claims the name, the method will be renamed with a prefix.
131163

132164
`traceTransaction` retrieves the instruction trace of a previously submitted transaction. It takes a `hash` parameter (the same one `getTransaction` takes) and returns the trace that `sendTransaction` stored on that transaction's receipt. The result is the trace itself: a JSONL string with one record per executed WebAssembly instruction, or `null` when no transaction with that hash exists.
133165

src/komet_node/kdist/node.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,62 @@ module) and the oldest by the epoch (`"0"`).
254254
</k>
255255
```
256256

257+
`getVersionInfo` echoes the version strings the Python server put into the request envelope
258+
(the package versions live in Python's package metadata, which K cannot read). Its
259+
`protocolVersion` is a JSON number, per the spec and real stellar-rpc.
260+
261+
```k
262+
rule <k> #dispatchMethod( "getVersionInfo", REQ )
263+
=> #respond( #getJSON( "id", REQ ), {
264+
"version" : #getString( "version", REQ ),
265+
"commitHash" : #getString( "commitHash", REQ ),
266+
"buildTimestamp" : #getString( "buildTimestamp", REQ ),
267+
"captiveCoreVersion" : #getString( "captiveCoreVersion", REQ ),
268+
"protocolVersion" : #getInt( "protocolVersion", REQ )
269+
})
270+
...
271+
</k>
272+
```
273+
274+
`getFeeStats` reports a constant fee distribution: komet-node has no fee market (transactions
275+
execute immediately and pay no fees), so every statistic is the network minimum inclusion fee
276+
of 100 stroops over an empty sample. Real stellar-rpc serialises every distribution field
277+
except `ledgerCount` with Go's `,string` option, so those are JSON strings holding decimal
278+
numbers, while `ledgerCount` and `latestLedger` are JSON numbers. `latestLedger` is read live
279+
from `metadata.json`.
280+
281+
```k
282+
rule <k> #dispatchMethod( "getFeeStats", REQ )
283+
=> #respond( #getJSON( "id", REQ ), {
284+
"sorobanInclusionFee" : #feeDistribution,
285+
"inclusionFee" : #feeDistribution,
286+
"latestLedger" : #getInt( "latest_ledger", String2JSON( {#readFile("metadata.json")}:>String ) )
287+
})
288+
...
289+
</k>
290+
291+
syntax JSON ::= "#feeDistribution" [function, symbol(feeDistribution)]
292+
// ----------------------------------------------------------------------
293+
rule #feeDistribution => {
294+
"max" : "100",
295+
"min" : "100",
296+
"mode" : "100",
297+
"p10" : "100",
298+
"p20" : "100",
299+
"p30" : "100",
300+
"p40" : "100",
301+
"p50" : "100",
302+
"p60" : "100",
303+
"p70" : "100",
304+
"p80" : "100",
305+
"p90" : "100",
306+
"p95" : "100",
307+
"p99" : "100",
308+
"transactionCount" : "0",
309+
"ledgerCount" : 0
310+
}
311+
```
312+
257313
`#ledgerId` derives the ledger's `id` (the hash of the ledger header on a real chain) from
258314
its sequence number. This node has no ledger headers to hash — and the semantics have no
259315
hash hooks — so the id is a deterministic stand-in: the sequence (offset by one so ledger 0

0 commit comments

Comments
 (0)