Skip to content

Commit 7e32466

Browse files
authored
Merge pull request #602 from input-output-hk/javierdiaz/trace-translator
Translate traces between Cardano Tracer and Leios trace verifier
2 parents fff3fb8 + 7adc3ec commit 7e32466

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

scripts/trace-translator/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Trace translator
2+
3+
The trace translator translates traces generated by the Praos node into traces in the format expected by the Leios trace verifier.
4+
5+
## Example usage
6+
7+
The trace translator uses the standard input and the standard output to perform translations:
8+
9+
```bash
10+
trace-translator.py < praos-node.json > leios-node.json
11+
```
12+
13+
Assuming that the input file `praos-node.json` contains the following traces:
14+
15+
```json
16+
{"at":"2025-10-27T16:24:31.972897Z","ns":"Forge.Loop.StartLeadershipCheck","data":{"kind":"TraceStartLeadershipCheck","slot":0},"sev":"Info","thread":"45","host":"node-0"}
17+
{"at":"2025-10-27T16:24:31.972919Z","ns":"Forge.Loop.ForgedBlock","data":{"block":"e2a10cab9cda1227ae6d78811c730c88c66047927cba9a4a6bf41559efc38d67","blockNo":4,"blockPrev":"9bea965ee016f724368911fa77abfb95ee0145a27ed6dbc096695dbd19311ab7","kind":"TraceForgedBlock","slot":92},"sev":"Info","thread":"45","host":"node-0"}
18+
{"at":"2025-10-27T16:24:32.061155Z","ns":"BlockFetch.Client.CompletedBlockFetch","data":{"block":"e996590b50ee39d640975de80b1c4ebfaa923866437d36b2d2ab08dacda2a09d","delay":8.924e-3,"kind":"CompletedBlockFetch","peer":{"connectionId":"127.0.0.1:30001 127.0.0.1:30000"},"size":2472},"sev":"Info","thread":"74","host":"node-0"}
19+
{"at":"2025-10-27T16:24:33.061305Z","ns":"Forge.Loop.AdoptedBlock","data":{"blockHash":"26ab7db1ca55e69885665f625262118ef7db99172be6d3591ad2a8e9bfeabffa","blockSize":864,"kind":"TraceAdoptedBlock","slot":0},"sev":"Info","thread":"34","host":"node-0"}
20+
{"at":"2025-10-27T16:24:33.063728Z","ns":"Forge.Loop.StartLeadershipCheck","data":{"kind":"TraceStartLeadershipCheck","slot":1},"sev":"Info","thread":"45","host":"node-0"}
21+
{"at":"2025-10-27T16:24:33.066668Z","ns":"BlockFetch.Client.CompletedBlockFetch","data":{"block":"ffff590b50ee39d640975de80b1c4ebfaa923866437d36b2d2ab08dacda2a09d","delay":123,"kind":"CompletedBlockFetch","peer":{"connectionId":"127.0.0.1:30001 127.0.0.1:30000"},"size":2472},"sev":"Info","thread":"74","host":"node-0"}
22+
{"at":"2025-10-27T16:24:33.074447Z","ns":"Forge.Loop.StartLeadershipCheck","data":{"kind":"TraceStartLeadershipCheck","slot":2},"sev":"Info","thread":"45","host":"node-0"}
23+
{"at":"2025-10-27T16:24:33.080000Z","ns":"BlockFetch.Client.CompletedBlockFetch","data":{"block":"eeee590b50ee39d640975de80b1c4ebfaa923866437d36b2d2ab08dacda2a09d","delay":123,"kind":"CompletedBlockFetch","peer":{"connectionId":"127.0.0.1:30001 127.0.0.1:30000"},"size":2472},"sev":"Info","thread":"74","host":"node-0"}
24+
{"at":"2025-10-27T16:24:33.080001Z","ns":"Forge.Loop.StartLeadershipCheck","data":{"kind":"TraceStartLeadershipCheck","slot":3},"sev":"Info","thread":"45","host":"node-0"}
25+
```
26+
27+
Then the output file `leios-node.json` will contain the following resulting traces:
28+
29+
```json
30+
{"message": {"type": "RBReceived", "recipient": "node-0", "id": "e996590b50ee39d640975de80b1c4ebfaa923866437d36b2d2ab08dacda2a09d"}, "time_s": 0.088236}
31+
{"message": {"type": "RBGenerated", "producer": "node-0", "slot": 0, "id": "26ab7db1ca55e69885665f625262118ef7db99172be6d3591ad2a8e9bfeabffa", "endorsement": null, "parent": null, "size": 864, "tx_payload_bytes": null}, "time_s": 1.00015}
32+
{"message": {"type": "Slot", "node": "node-0", "slot": 0}, "time_s": 0.002423}
33+
{"message": {"type": "RBReceived", "recipient": "node-0", "id": "ffff590b50ee39d640975de80b1c4ebfaa923866437d36b2d2ab08dacda2a09d"}, "time_s": 0.00294}
34+
{"message": {"type": "Slot", "node": "node-0", "slot": 1}, "time_s": 0.007779}
35+
{"message": {"type": "RBReceived", "recipient": "node-0", "id": "eeee590b50ee39d640975de80b1c4ebfaa923866437d36b2d2ab08dacda2a09d"}, "time_s": 0.005553}
36+
{"message": {"type": "Slot", "node": "node-0", "slot": 2}, "time_s": 1e-06}
37+
```
38+
39+
## Limitations
40+
41+
Currently, the `parent` and `tx_payload_bytes` fields in the output traces of type `RBGenerated` are not available since these are not present in the input traces of type `Forge.Loop.AdoptedBlock`. A possible solution to the absense of the `parent` field may imply the use of the corresponding input trace of type `Forge.Loop.ForgedBlock`.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Translate trace log files generated by the Praos node into log files
5+
in the format expected by the Leios trace verifier.
6+
"""
7+
8+
import json, sys
9+
from datetime import datetime, timezone
10+
11+
def log_message(message, time):
12+
print(json.dumps({"message": message, "time_s": time.total_seconds()}))
13+
14+
fmt = "%Y-%m-%dT%H:%M:%S.%fZ"
15+
last_at = None
16+
last_SLC_slot = None
17+
18+
for line in sys.stdin:
19+
try:
20+
obj = json.loads(line)
21+
except json.JSONDecodeError as e:
22+
print(f"Error decoding JSON: {e}")
23+
exit(127)
24+
25+
if last_at is None:
26+
last_at = obj['at']
27+
28+
curr_at = obj['at']
29+
time = datetime.strptime(curr_at, fmt).replace(tzinfo=timezone.utc) \
30+
- \
31+
datetime.strptime(last_at, fmt).replace(tzinfo=timezone.utc)
32+
last_at = curr_at
33+
34+
if obj['ns'] == "Forge.Loop.AdoptedBlock":
35+
message = {
36+
"type": "RBGenerated",
37+
"producer": obj['host'],
38+
"slot": last_SLC_slot,
39+
"id": obj['data']['blockHash'],
40+
"endorsement": None,
41+
"parent": None, # FIXME: This is not available
42+
"size": obj['data']['blockSize'],
43+
"tx_payload_bytes": None, # FIXME: This is not available
44+
}
45+
elif obj['ns'] == "BlockFetch.Client.CompletedBlockFetch":
46+
message = {
47+
"type": "RBReceived",
48+
"recipient": obj['host'],
49+
"id": obj['data']['block']
50+
}
51+
else:
52+
if obj['ns'] == "Forge.Loop.StartLeadershipCheck":
53+
if last_SLC_slot is None:
54+
last_SLC_slot = obj['data']['slot']
55+
else:
56+
message = {
57+
"type": "Slot",
58+
"node": obj['host'],
59+
"slot": last_SLC_slot
60+
}
61+
log_message(message, time)
62+
last_SLC_slot = obj['data']['slot']
63+
continue
64+
65+
log_message(message, time)

0 commit comments

Comments
 (0)