Skip to content

Commit 68ceedc

Browse files
Merge branch 'main' into feat/get-transactions-ledgers
Resolve conflicts with main.
2 parents 31b822f + bd02497 commit 68ceedc

12 files changed

Lines changed: 569 additions & 105 deletions

File tree

README.md

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,68 @@ curl -s http://localhost:8000 -H 'Content-Type: application/json' \
130130
"jsonrpc": "2.0",
131131
"id": 1,
132132
"result": [
133+
{
134+
"pos": null, "instr": ["callContract"],
135+
"from": {"type": "address", "addrType": "account", "value": "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8"},
136+
"to": {"type": "address", "addrType": "contract", "value": "6a20fec1a9081773a5f23ce370f925f236346e510438ddd6d40f6b2711c134e0"},
137+
"function": "foo", "args":[], "depth":1, "storage":[]
138+
},
133139
{"pos": 3, "instr": ["const", "i32", 1048576], "stack": [], "locals": {}},
134140
{"pos": 11, "instr": ["const", "i32", 1048576], "stack": [], "locals": {}},
135141
{"pos": 19, "instr": ["const", "i32", 1048576], "stack": [], "locals": {}},
136142
{"pos": null, "instr": ["block"], "stack": [], "locals": {}},
137-
{"pos": 3, "instr": ["const", "i64", 2], "stack": [], "locals": {}}
143+
{"pos": 3, "instr": ["const", "i64", 2], "stack": [], "locals": {}},
144+
{"pos": null, "instr": ["endWasm"], "success":true, "depth":1, "result": {"type": "void"}}
138145
]
139146
}
140147
```
141148

142-
Each trace record captures the VM state at instruction entry: `pos` is the instruction's byte offset in the binary (`null` for synthetic instructions), `instr` is the instruction and its operands, and `stack`/`locals` are the value stack and locals as `[type, value]` pairs. See [docs/interpreter.md](docs/interpreter.md) for the full trace format.
149+
A trace can contain five kinds of records:
150+
151+
- `callContract`
152+
- Wasm instruction records
153+
- `hostCall`
154+
- `contractData`
155+
- `endWasm`
156+
157+
The example above only has three of these: `callContract`, instruction records, and `endWasm`. `foo()` doesn't touch storage or call any host functions, so no `contractData` or `hostCall` records show up.
158+
159+
Here's what each record type carries:
160+
161+
- `callContract`: logged for each contract call in the transaction, including contract-to-contract calls. Records the caller, the callee, the function name, the arguments, the call depth, and the callee's storage before the call runs.
162+
- Instruction records: logged at each WebAssembly instruction's entry. `pos` is the instruction's byte offset in the binary (`null` for synthetic instructions), `instr` is the instruction and its operands, and `stack`/`locals` are the value stack and locals as `[type, value]` pairs.
163+
- `hostCall`: logged when the contract calls a host function. `instr` gives `["hostCall", moduleId, functionId]`, identifying which host function ran. `locals` holds the function's arguments, indexed by position. Host calls don't use the stack, so `stack` is absent.
164+
Here's a `hostCall` record for a call to `put_contract_data`, module id `l`, function id `_`:
165+
```jsonc
166+
{
167+
"pos": null,
168+
"instr": ["hostCall", "l", "_"],
169+
"locals": {"2": ["i64",0], "1": ["i64",530242871224172548], "0": ["i64",45954062]}
170+
}
171+
```
172+
173+
- `contractData`: logged for storage updates. Gives the contract and the storage type (`instance`, `persistent`, or `temporary`). A `put` carries the key and value as its two args; a `del` carries only the key.
174+
Here's a `contractData` record for a `put`, followed by a `del` on the same key:
175+
```jsonc
176+
{
177+
"pos": null,
178+
"instr": ["contractData", "put", "temporary"],
179+
"contract": {"type": "address", "addrType": "contract", "value": "746573742d7363"},
180+
"args": [{"type": "symbol", "value": "foo"}, {"type": "u32", "value": 123456789}]
181+
}
182+
{
183+
"pos": null,
184+
"instr": ["contractData", "del", "temporary"],
185+
"contract": {"type": "address", "addrType": "contract", "value": "746573742d7363"},
186+
"args": [{"type": "symbol", "value": "foo"}]
187+
}
188+
```
189+
190+
- `endWasm`: logged once at the end of a call. Records whether the call succeeded, its depth, and its result.
191+
192+
193+
194+
See [docs/interpreter.md](docs/interpreter.md) for the full trace format.
143195

144196
---
145197

docs/node-semantics.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ If `request.json` is absent, `insert-handleRequestFile` does not fire and K halt
5858

5959
## Dispatch and the read-only methods
6060

61-
`#dispatch` reads the `method` field and routes to a per-method rule. The read-only methods answer directly from constants and the bookkeeping files:
61+
`#dispatch` reads the `method` field and routes to a per-method rule. The read-only methods answer directly from constants and the bookkeeping files. Response shapes follow real stellar-rpc's serialization: ledger sequences and `protocolVersion` are JSON numbers; close-time fields are int64-as-string (JSON strings holding a decimal integer).
6262

63-
- `getHealth``{ "status": "healthy" }`
64-
- `getNetwork``{ "friendbotUrl": null, "passphrase": ..., "protocolVersion": ... }` (passphrase/version come from the request, keeping the semantics network-agnostic)
65-
- `getLatestLedger` → reads `metadata.json` and returns `{ "id": <64 zeros>, "protocolVersion": ..., "sequence": <latest_ledger> }`
66-
- `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
63+
- `getHealth``{ "status": "healthy", "latestLedger": N, "latestLedgerCloseTime": <now>, "oldestLedger": 0, "oldestLedgerCloseTime": "0", "ledgerRetentionWindow": N+1 }` (everything since genesis is retained)
64+
- `getNetwork``{ "passphrase": ..., "protocolVersion": ... }` (passphrase/version come from the request, keeping the semantics network-agnostic; `friendbotUrl` is omitted — no friendbot)
65+
- `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+
- `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
6767
- `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`)
6868

69-
`#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.
69+
`#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. `#respondError(ID, CODE, MSG)` is its error counterpart, writing `{jsonrpc, id, error: {code, message}}` instead; the unknown-method fallback uses it to answer `-32601 Method not found` (a safety net — the Python server filters unknown methods before they reach K).
7070

7171
---
7272

@@ -86,9 +86,11 @@ If `request.json` is absent, `insert-handleRequestFile` does not fire and K halt
8686

8787
1. writes `metadata.json` with `latest_ledger + 1`,
8888
2. writes the receipt to `receipts/receipt_<hash>.json`:
89-
`{ status: "SUCCESS", ledger, createdAt, envelopeXdr, resultXdr: "", resultMetaXdr: "" }`,
89+
`{ status: "SUCCESS", applicationOrder: 1, feeBump: false, envelopeXdr, resultXdr: "", resultMetaXdr: "", ledger, createdAt }`,
9090
3. responds with `{hash, status: "PENDING", latestLedger, latestLedgerCloseTime}`.
9191

92+
A `sendTransaction` whose hash already has a receipt never reaches `#runTx`: dispatch answers `{hash, status: "DUPLICATE", latestLedger, latestLedgerCloseTime}` directly, without executing anything or advancing the ledger. (For wasm uploads the Python server also suppresses the `<program>` injection on duplicates, since those steps would run before dispatch.)
93+
9294
The trace is not part of the receipt — the executing steps already appended it to `traces/trace_<hash>.jsonl`. Reaching `#finalizeTx` means the steps completed without getting stuck, so the status is `SUCCESS`. A failed transaction gets stuck before this point, `response.json` is never written, and the Python server records the `FAILED` receipt instead.
9395

9496
### traceTransaction

docs/notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ The tests do not yet cover `bytes` / `address` SCVal arguments or `SCVec` / `SCM
3434
## Known gaps
3535

3636
- `resultXdr` / `resultMetaXdr` are empty stubs (contract return values not surfaced).
37+
- `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.
3738
- `SCVec` / `SCMap` contract arguments are not yet encoded.
3839
- `simulateTransaction`, `getEvents`, `getLedgerEntries`, `getFeeStats`, and TTL/footprint operations are not implemented.
3940
- `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.
41+
- 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: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ server = StellarRpcServer(io_dir=Path('out'))
2929
server.handle_rpc('sendTransaction', {'transaction': xdr})
3030
```
3131

32-
For `sendTransaction` it builds the request envelope with `encoder.build_tx_request` and runs it with `interpreter.run`; for the read-only methods (`getHealth`, `getNetwork`, `getLatestLedger`, `getTransaction`, `getTransactions`, `getLedgers`, `traceTransaction`) it builds a small envelope and runs it. For the history methods (`getTransactions`, `getLedgers`) the server also validates the pagination parameters — limit range, `startLedger` bounds, `cursor`/`startLedger` exclusivity — and resolves the cursor to the first ledger sequence to serve, because the JSON-RPC parameter-error path lives here. In every case the *content* of the response is produced by the semantics (`node.md`), not by Python — the exceptions are the failure fallback (below) and the per-ledger XDR artifacts (`_record_closed_ledger`), which K cannot construct. Each call is logged to stderr.
32+
For `sendTransaction` it builds the request envelope with `encoder.build_tx_request` and runs it with `interpreter.run` (skipping the `<program>` wasm injection when the hash already has a receipt, so a duplicate is not re-executed — the semantics answer `DUPLICATE`); for the read-only methods (`getHealth`, `getNetwork`, `getLatestLedger`, `getTransaction`, `getTransactions`, `getLedgers`, `traceTransaction`) it builds a small envelope and runs it. For the history methods (`getTransactions`, `getLedgers`) the server also validates the pagination parameters — limit range, `startLedger` bounds, `cursor`/`startLedger` exclusivity — and resolves the cursor to the first ledger sequence to serve, because the JSON-RPC parameter-error path lives here. In every case the *content* of the response is produced by the semantics (`node.md`), not by Python — the exceptions are the failure fallback (below) and the per-ledger XDR artifacts (`_record_closed_ledger`), which K cannot construct. Each call is logged to stderr.
3333

3434
---
3535

@@ -86,24 +86,30 @@ Because these artifacts live on disk, the server can be stopped and restarted wi
8686

8787
## RPC methods
8888

89-
All methods are answered by the K semantics and follow the [Stellar RPC specification](https://developers.stellar.org/docs/data/apis/rpc/methods).
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.
9090

9191
### `getHealth`
9292

93-
`getHealth` returns `{"status": "healthy"}`.
93+
```json
94+
{ "status": "healthy", "latestLedger": 4, "latestLedgerCloseTime": "1716000000", "oldestLedger": 0, "oldestLedgerCloseTime": "0", "ledgerRetentionWindow": 5 }
95+
```
96+
97+
The node retains every ledger since genesis, so `oldestLedger` is always 0 and the retention window covers all ledgers so far. Per-ledger close times are not recorded: the latest close time is approximated by the request time and the oldest by the epoch.
9498

9599
### `getNetwork`
96100

97101
```json
98-
{ "friendbotUrl": null, "passphrase": "Test SDF Network ; September 2015", "protocolVersion": "22" }
102+
{ "passphrase": "Test SDF Network ; September 2015", "protocolVersion": 22 }
99103
```
100104

105+
`friendbotUrl` is omitted (not `null`) because the node runs no friendbot — matching real stellar-rpc's `omitempty` serialization.
106+
101107
### `getLatestLedger`
102108

103-
`getLatestLedger` returns the current ledger sequence (the `latest_ledger` from `metadata.json`), which increments by 1 per successfully committed transaction.
109+
`getLatestLedger` returns the current ledger sequence (the `latest_ledger` from `metadata.json`), which increments by 1 per successfully committed transaction. The `id` is a deterministic per-ledger stand-in for the ledger-header hash, derived from the sequence (see `#ledgerId` in [node-semantics.md](node-semantics.md)).
104110

105111
```json
106-
{ "id": "0000...0000", "protocolVersion": "22", "sequence": 4 }
112+
{ "id": "1715609f7c74...", "protocolVersion": 22, "sequence": 4 }
107113
```
108114

109115
### `sendTransaction`
@@ -114,9 +120,13 @@ All methods are answered by the K semantics and follow the [Stellar RPC specific
114120

115121
**Response**:
116122
```json
117-
{ "hash": "<64-char hex>", "status": "PENDING", "latestLedger": "5", "latestLedgerCloseTime": "1716000000" }
123+
{ "hash": "<64-char hex>", "status": "PENDING", "latestLedger": 5, "latestLedgerCloseTime": "1716000000" }
118124
```
119125

126+
Resubmitting a transaction whose hash already has a receipt returns status `DUPLICATE` without re-executing it: the ledger does not advance and the stored receipt is untouched.
127+
128+
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.
129+
120130
### `traceTransaction`
121131

122132
`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.
@@ -127,7 +137,7 @@ All methods are answered by the K semantics and follow the [Stellar RPC specific
127137

128138
### `getTransaction`
129139

130-
`getTransaction` reads the hash's `receipts/receipt_<hash>.json` file.
140+
`getTransaction` reads the hash's `receipts/receipt_<hash>.json` file. The `hash` parameter must be a 64-character hex string; anything else is rejected with `-32602 Invalid params` (this and `traceTransaction` share the validation).
131141

132142
| Status | Meaning |
133143
|---|---|
@@ -138,13 +148,17 @@ All methods are answered by the K semantics and follow the [Stellar RPC specific
138148
**`SUCCESS` response**:
139149
```json
140150
{
141-
"status": "SUCCESS", "ledger": "5", "createdAt": "1716000000",
151+
"status": "SUCCESS", "applicationOrder": 1, "feeBump": false,
142152
"envelopeXdr": "<base64 XDR>", "resultXdr": "", "resultMetaXdr": "",
143-
"latestLedger": "5", "latestLedgerCloseTime": "1716000000"
153+
"ledger": 5, "createdAt": "1716000000",
154+
"latestLedger": 5, "latestLedgerCloseTime": "1716000000",
155+
"oldestLedger": 0, "oldestLedgerCloseTime": "0"
144156
}
145157
```
146158

147-
`resultXdr` and `resultMetaXdr` are currently empty stubs. The receipt carries no trace — use `traceTransaction` with the same hash to fetch it.
159+
The ledger-range fields (`latestLedger` through `oldestLedgerCloseTime`) are present on every response, `NOT_FOUND` included. Every ledger contains exactly one transaction, so `applicationOrder` is always 1; fee-bump envelopes are not supported, so `feeBump` is always false. `resultXdr` and `resultMetaXdr` are currently empty stubs. The receipt carries no trace — use `traceTransaction` with the same hash to fetch it.
160+
161+
Receipts are an internal format, not a stable one: receipts written by older komet-node versions stored `ledger` as a string and lack `applicationOrder`/`feeBump`, and `getTransaction` returns stored receipts verbatim. Start from a fresh io-dir after upgrading rather than resuming one with old receipts.
148162

149163
### `getTransactions`
150164

flake.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# `k` package directly (replacing the imperative `kup install k`); its
1010
# nixpkgs is intentionally NOT followed, so the k-framework binary caches
1111
# are hit instead of rebuilding K against our nixpkgs.
12-
k-framework.url = "github:runtimeverification/k/v7.1.319";
12+
k-framework.url = "github:runtimeverification/k/v7.1.323";
1313
# Use the same uv2nix as k-framework so we inherit the pyproject-nix version
1414
# that fixes the missing 'riscv64' attribute in pep600.nix (pep599.manyLinuxTargetMachines
1515
# lookup now uses `or tagArch` as a safe default for unknown architectures).

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ readme = "README.md"
1010
requires-python = "~=3.10"
1111
dependencies = [
1212
"stellar-sdk>=13.2.1",
13-
"komet@git+https://github.com/runtimeverification/komet.git@v0.1.79",
14-
"kframework>=7.1.318,<7.1.321",
13+
"komet@git+https://github.com/runtimeverification/komet.git@v0.1.85",
14+
"kframework>=7.1.323,<7.1.324",
1515
]
1616

1717
[[project.authors]]

0 commit comments

Comments
 (0)