Skip to content

Commit e67a8c1

Browse files
feat: implement getLedgerEntries
Dispatch and state lookup live in the K semantics: new #ledgerEntries rules resolve key descriptors against the world-state cells (accounts, contracts, contractData, contractCodes) and answer with intermediate JSON entries plus the numeric latestLedger. #scVal2JSON serialises stored ScVals back to the JSON shape #decodeArg consumes, extended with the value-only types (void, string, u256, vec, map). Python handles only the XDR ends (ledger_entries.py): decoding the base64 LedgerKey params into key descriptors — with validation of keys, the 200-key cap, and xdrFormat (base64 only) — and re-encoding found entries as base64 LedgerEntryData. Unknown or untracked keys are skipped, not errors, per the spec. Because the K configuration stores uploaded wasm parsed, the server now persists the raw module bytes under wasms/<hash>.wasm at upload time and reattaches them to CONTRACT_CODE entries. lastModifiedLedgerSeq is approximated by the current ledger (the semantics do not track per-entry modification ledgers) and ACCOUNT entries synthesise the AccountEntry fields the semantics do not model beyond the balance.
1 parent a4219b6 commit e67a8c1

9 files changed

Lines changed: 622 additions & 18 deletions

File tree

docs/architecture.md

Lines changed: 4 additions & 3 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 six RPC methods — `getHealth`, `getNetwork`, `getLatestLedger`, `sendTransaction`, `getTransaction`, and `traceTransaction` — and the K semantics answer all of them.
54+
The server implements seven RPC methods — `getHealth`, `getNetwork`, `getLatestLedger`, `sendTransaction`, `getTransaction`, `getLedgerEntries`, and `traceTransaction` — and the K semantics answer all of them. For `getLedgerEntries` the semantics' answer is an intermediate shape: K performs the state lookups, and `ledger_entries.py` translates between the base64 XDR wire format (`LedgerKey` in, `LedgerEntryData` out) and the JSON the semantics exchange, since K cannot parse or produce XDR.
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

@@ -92,6 +92,7 @@ All of the server's input and output artifacts live in one directory, the *io di
9292
| `receipts/receipt_<hash>.json` | persistent | the semantics (on success) or the server (on failure) | one stored receipt per transaction, keyed by tx hash, answering `getTransaction`. Each is `{status, ledger, createdAt, envelopeXdr, resultXdr, resultMetaXdr}`. |
9393
| `traces/trace_<hash>.jsonl` | persistent | the semantics | one execution trace per transaction, keyed by tx hash — the instruction-level records, one JSON object per line. `traceTransaction` returns this file's contents. |
9494
| `requests/request_<n>.json` | persistent | the server | an archive of each incoming JSON-RPC request, numbered by a monotonic counter, kept for debugging. |
95+
| `wasms/<hash>.wasm` | persistent | the server | the raw bytes of each successfully uploaded wasm module, keyed by hex sha256. The K state stores modules parsed (`ModuleDecl`), so `getLedgerEntries` CONTRACT_CODE entries read the original bytes from here. |
9596
| `request.json` | transient | the server | the request envelope for the call in flight (`method`, `id`, `now`, and method-specific fields). The semantics remove it once they respond. |
9697
| `response.json` | transient | the semantics | the JSON-RPC response (`{jsonrpc, id, result}`) for the most recent call. The server reads it back; it is absent when a transaction gets stuck. |
9798

@@ -102,7 +103,7 @@ The world state stays in KORE (rather than a JSON snapshot) because an uploaded
102103
```mermaid
103104
flowchart TB
104105
boot(["server start"]) --> exists{"state.kore exists?"}
105-
exists -->|"no"| init["empty_config() builds the idle K config in KORE<br/>write state.kore · seed metadata.json {latest_ledger: 0}<br/>create receipts/ traces/ requests/"]
106+
exists -->|"no"| init["empty_config() builds the idle K config in KORE<br/>write state.kore · seed metadata.json {latest_ledger: 0}<br/>create receipts/ traces/ requests/ wasms/"]
106107
exists -->|"yes"| reuse["use existing state.kore<br/>seed metadata.json if missing · ensure artifact dirs exist"]
107108
init --> ready(["ready for requests"])
108109
reuse --> ready
@@ -177,6 +178,6 @@ sequenceDiagram
177178

178179
- `resultXdr` / `resultMetaXdr` in `getTransaction` responses (contract return values)
179180
- `simulateTransaction` (dry-run without state mutation)
180-
- `getEvents`, `getLedgerEntries`, `getFeeStats` and other read-only RPC methods
181+
- `getEvents`, `getFeeStats` and other read-only RPC methods (`getLedgerEntries` is implemented for the entry types the K state tracks: `ACCOUNT`, `CONTRACT_DATA`, `CONTRACT_CODE`)
181182
- `ExtendFootprintTTL` and `RestoreFootprint` operations
182183
- `SCVec` / `SCMap` contract-argument types in the request encoder (`scval_to_json`)

docs/node-semantics.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ insert-handleRequestFile → handleRequestFile
3939
4040
#dispatchMethod(method, request) ← routes on the "method" field
4141
42-
├─ getHealth / getNetwork / getLatestLedger / getTransaction / traceTransaction → #respond(...)
42+
├─ getHealth / getNetwork / getLatestLedger / getTransaction / getLedgerEntries / traceTransaction → #respond(...)
4343
4444
└─ sendTransaction → #runTx → run steps
4545
→ #finalizeTx → record receipt + bump ledger → #respond(...)
@@ -62,6 +62,7 @@ If `request.json` is absent, `insert-handleRequestFile` does not fire and K halt
6262
- `getNetwork``{ "friendbotUrl": null, "passphrase": ..., "protocolVersion": ... }` (passphrase/version come from the request, keeping the semantics network-agnostic)
6363
- `getLatestLedger` → reads `metadata.json` and returns `{ "id": <64 zeros>, "protocolVersion": ..., "sequence": <latest_ledger> }`
6464
- `getTransaction` → reads the hash's `receipts/receipt_<hash>.json` file; returns the stored receipt merged with the current `latestLedger`/`latestLedgerCloseTime`, or `{ "status": "NOT_FOUND", ... }` when the file is absent
65+
- `getLedgerEntries` → looks each *key descriptor* of the request up in the world-state cells (`<accounts>`, `<contracts>`, `<contractData>`, `<contractCodes>`) and responds with `{ "entries": [...], "latestLedger": <int> }`. The server decodes the base64 `LedgerKey` XDR into the descriptors beforehand and re-encodes the found entries as `LedgerEntryData` XDR afterwards (`ledger_entries.py`) — the entries in K's response are an intermediate JSON shape (per-kind payloads such as `balance`, `wasmHash`, or an ScVal value serialised by `#scVal2JSON`, the inverse of `#decodeArg`). Keys that match nothing are skipped via an `[owise]` rule — per the spec they are not an error
6566

6667
`#respond(ID, RESULT)` is the shared terminal: it writes the JSON-RPC envelope to `response.json`, removes `request.json`, and sets the exit code to 0.
6768

@@ -141,6 +142,10 @@ rule HexBytes(S) => Int2Bytes(lengthString(S) /Int 2, String2Base(S, 16), BE)
141142
requires lengthString(S) >Int 0
142143
```
143144

145+
### `Bytes2Hex(Bytes) → String`
146+
147+
The inverse direction is K's built-in `Bytes2Hex` (hook `BYTES.bytes2hex`): it encodes `Bytes` as a lowercase, zero-padded hex string. The `getLedgerEntries` rules use it to report hashes, addresses, and `ScBytes` values to the server.
148+
144149
### `string2WasmToken(String) → WasmStringToken`
145150

146151
`string2WasmToken` wraps a K `String` into a `WasmStringToken` (`hook(STRING.string2token)`). It is required because `callTx` expects a `WasmString` for the function name.

docs/notes.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
| [`server.py`](server.md)`StellarRpcServer` | Long-running HTTP/JSON-RPC server wrapping the one-shot K interpreter; `handle_rpc` dispatch; owns the io-dir files. Holds no ledger or receipt state. |
1414
| [`transaction.py`](transaction.md)`TransactionEncoder` | XDR → request envelope + (for wasm uploads) kasmer steps; address/contract-id helpers. |
1515
| [`interpreter.py`](interpreter.md)`NodeInterpreter` | Runs request envelopes through `llvm_interpret`; persists `state.kore`. No `kast``kore` whole-config conversions. |
16-
| `scval.py` | XDR `SCVal` ↔ Komet `SCValue` (`scvalue_from_xdr`) and XDR `SCVal` → request JSON (`scval_to_json`). |
16+
| `scval.py` | XDR `SCVal` ↔ Komet `SCValue` (`scvalue_from_xdr`), XDR `SCVal` ↔ request/response JSON (`scval_to_json`, `scval_from_json`). |
17+
| `ledger_entries.py` | `getLedgerEntries` XDR translation: base64 `LedgerKey` → key descriptors for the semantics, intermediate entries → base64 `LedgerEntryData`. |
1718
| [`kdist/node.md`](node-semantics.md) | The K RPC layer: reads `request.json`, dispatches, updates `metadata.json` and the per-transaction `receipts/` files, writes `response.json`. |
1819

1920
State lives in the io dir as `state.kore` (KORE world state) and `metadata.json` (ledger counter), with per-transaction receipts and traces under `receipts/` and `traces/`. See [architecture.md](architecture.md).
@@ -22,7 +23,7 @@ State lives in the io dir as `state.kore` (KORE world state) and `metadata.json`
2223

2324
## Tests (`src/tests/integration/`)
2425

25-
- `test_server.py` drives the running HTTP server end-to-end. It exercises the read-only methods, `sendTransaction` + `getTransaction`, ledger increments, the full lifecycle (create → upload wasm → deploy → invoke), and the `traceTransaction` flows. `test_call_tx_with_args` deploys `args.wat` and calls functions with `bool`, `u32`, `i32`, `u64`, `i64`, `u128`, `i128`, and `symbol` arguments, exercising the `scval_to_json` / `#decodeArg` pipeline.
26+
- `test_server.py` drives the running HTTP server end-to-end. It exercises the read-only methods, `sendTransaction` + `getTransaction`, ledger increments, the full lifecycle (create → upload wasm → deploy → invoke), the `traceTransaction` flows, and `getLedgerEntries` (account, contract code, contract instance, and persistent storage entries via `storage.wat`, plus its parameter validation). `test_call_tx_with_args` deploys `args.wat` and calls functions with `bool`, `u32`, `i32`, `u64`, `i64`, `u128`, `i128`, and `symbol` arguments, exercising the `scval_to_json` / `#decodeArg` pipeline.
2627
- `test_integration.py` and `test_unit.py` hold small sanity checks.
2728

2829
Run with `make test` (requires `make kdist-build` first).
@@ -35,4 +36,5 @@ The tests do not yet cover `bytes` / `address` SCVal arguments or `SCVec` / `SCM
3536

3637
- `resultXdr` / `resultMetaXdr` are empty stubs (contract return values not surfaced).
3738
- `SCVec` / `SCMap` contract arguments are not yet encoded.
38-
- `simulateTransaction`, `getEvents`, `getLedgerEntries`, `getFeeStats`, and TTL/footprint operations are not implemented.
39+
- `simulateTransaction`, `getEvents`, `getFeeStats`, and TTL/footprint operations are not implemented.
40+
- `getLedgerEntries` covers the entry types the K state tracks (`ACCOUNT`, `CONTRACT_DATA`, `CONTRACT_CODE`); other key types are reported as not found. `lastModifiedLedgerSeq` is approximated by the current ledger (per-entry modification ledgers are not tracked), and only the balance is real in `ACCOUNT` entries (sequence number, thresholds, etc. are synthesised constants).

docs/server.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Once the socket is bound, `serve` logs three lines to stderr: whether it is star
6464
```
6565
startup (state.kore absent):
6666
→ empty_config() → state.kore ; metadata.json {latest_ledger:0}
67-
→ create receipts/ traces/ requests/
67+
→ create receipts/ traces/ requests/ wasms/
6868
6969
per successful transaction:
7070
→ the semantics run the steps (trace → traces/trace_<hash>.jsonl),
@@ -143,6 +143,23 @@ All methods are answered by the K semantics and follow the [Stellar RPC specific
143143

144144
`resultXdr` and `resultMetaXdr` are currently empty stubs. The receipt carries no trace — use `traceTransaction` with the same hash to fetch it.
145145

146+
### `getLedgerEntries`
147+
148+
`getLedgerEntries` takes `keys` (an array of up to 200 base64-encoded `LedgerKey` XDR strings; required) and an optional `xdrFormat` (only `"base64"` is supported — `"json"` is rejected with `-32602`). It returns the entries found for the supported key types — `ACCOUNT`, `CONTRACT_DATA` (both the contract-instance entry and persistent/temporary storage), and `CONTRACT_CODE` — the ones the K world state tracks. Keys that do not resolve (unknown, or of an untracked type) are not an error; they are simply absent from `entries`.
149+
150+
**Response** (`latestLedger` and `lastModifiedLedgerSeq` are JSON numbers; `liveUntilLedgerSeq` appears only on Soroban entries):
151+
```json
152+
{
153+
"entries": [
154+
{ "key": "<base64 LedgerKey>", "xdr": "<base64 LedgerEntryData>",
155+
"lastModifiedLedgerSeq": 4, "liveUntilLedgerSeq": 4095 }
156+
],
157+
"latestLedger": 4
158+
}
159+
```
160+
161+
This is the one method whose response the server post-processes: the semantics look the keys up in the K state and answer with intermediate JSON entries, and `ledger_entries.py` re-encodes them as `LedgerEntryData` XDR (K cannot produce XDR). Because the K state keeps uploaded wasm parsed, the server stores the raw bytes under `wasms/<hash>.wasm` at upload time and reattaches them to `CONTRACT_CODE` entries. The semantics do not track per-entry modification ledgers, so `lastModifiedLedgerSeq` reports the current ledger; `ACCOUNT` entries carry the real balance but synthesised constants for the remaining required fields (sequence number 0, master weight 1, no signers).
162+
146163
---
147164

148165
## Failure fallback
@@ -161,4 +178,4 @@ komet-node [--host HOST] [--port PORT] [--io-dir DIR]
161178
|---|---|---|
162179
| `--host` | `localhost` | Bind address |
163180
| `--port` | `8000` | Port |
164-
| `--io-dir` | a fresh temp dir | Directory holding every artifact (`state.kore`, `metadata.json`, `receipts/`, `traces/`, `requests/`) |
181+
| `--io-dir` | a fresh temp dir | Directory holding every artifact (`state.kore`, `metadata.json`, `receipts/`, `traces/`, `requests/`, `wasms/`) |

0 commit comments

Comments
 (0)