Dual ledger: tamper-proof transaction tracking on both sides

Every inference request is recorded in a local JSONL ledger with a
SHA-256 hash of (id + tokens + duration + cost + shared_secret).

Both sides keep independent copies:
- Gateway (Matt's): writes to ledger.jsonl on every request
- Receiver (Seth's): receives callbacks, saves per-gateway ledger

Endpoints:
- GET /ledger — view transactions + total cost
- GET /reconcile — compare ledger vs stats, verify all hashes
- POST /config — adjust cost params live

ledger_receiver.py runs on Seth's server:
- POST /transaction — receive and verify gateway callbacks
- GET /summary — total cost per gateway
- GET /ledger — all transactions across gateways

If either side resets stats, the other's ledger has the full history.
If either side tampers with entries, hash verification catches it.

Tested: request → ledger write → reconcile → hash valid → zero discrepancy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 19:56:10 -04:00
parent 583c563daa
commit 968b00890f
3 changed files with 275 additions and 3 deletions
+124 -3
View File
@@ -19,6 +19,8 @@ import os
import time
import threading
import subprocess
import hashlib
import uuid
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import requests
@@ -74,6 +76,93 @@ def _save_cost_config(config):
COST_CONFIG = _load_cost_config()
# --- Dual Ledger ---
LEDGER_FILE = os.environ.get("LEDGER_FILE", "/var/lib/mortdecai-gateway/ledger.jsonl")
LEDGER_SECRET = os.environ.get("LEDGER_SECRET", "change_me_shared_secret")
CALLBACK_URL = os.environ.get("CALLBACK_URL", "") # Seth's server endpoint for transaction logging
_ledger_lock = threading.Lock()
def _ledger_hash(entry):
"""Create a verification hash from transaction data + shared secret."""
raw = f"{entry['id']}|{entry['tokens_in']}|{entry['tokens_out']}|{entry['duration']}|{entry['cost']}|{LEDGER_SECRET}"
return hashlib.sha256(raw.encode()).hexdigest()[:16]
def _ledger_write(entry):
"""Append a transaction to the local ledger."""
with _ledger_lock:
try:
os.makedirs(os.path.dirname(LEDGER_FILE), exist_ok=True)
with open(LEDGER_FILE, "a") as f:
f.write(json.dumps(entry) + "\n")
except Exception as e:
print(f"Ledger write failed: {e}")
def _ledger_callback(entry):
"""Send transaction to the client's server for cross-verification."""
if not CALLBACK_URL:
return
try:
requests.post(
CALLBACK_URL,
json=entry,
headers={"Content-Type": "application/json"},
timeout=5,
)
except:
pass # Non-blocking — don't fail inference because callback is down
def _ledger_record(tokens_in, tokens_out, duration, cost, energy_wh, model):
"""Record a transaction in the ledger and notify the client."""
entry = {
"id": str(uuid.uuid4())[:12],
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
"tokens_in": tokens_in,
"tokens_out": tokens_out,
"duration": round(duration, 3),
"cost": round(cost, 8),
"energy_wh": round(energy_wh, 4),
"model": model,
"billing_mode": COST_CONFIG["billing_mode"],
}
entry["hash"] = _ledger_hash(entry)
_ledger_write(entry)
# Send to client in background
threading.Thread(target=_ledger_callback, args=(entry,), daemon=True).start()
return entry
def _ledger_load():
"""Load all ledger entries."""
entries = []
try:
with open(LEDGER_FILE) as f:
for line in f:
if line.strip():
entries.append(json.loads(line))
except:
pass
return entries
def _ledger_verify(entries):
"""Verify all ledger entries against their hashes."""
results = {"total": len(entries), "valid": 0, "invalid": 0, "invalid_ids": []}
for entry in entries:
expected = _ledger_hash(entry)
if entry.get("hash") == expected:
results["valid"] += 1
else:
results["invalid"] += 1
results["invalid_ids"].append(entry.get("id", "?"))
return results
# --- Stats tracking ---
_stats_lock = threading.Lock()
_stats = {
@@ -127,10 +216,13 @@ def _calc_marginal_cost(duration_seconds):
return marginal_watts, energy_wh, cost
def _track_request(tokens_in, tokens_out, duration_seconds):
"""Track a completed inference request."""
def _track_request(tokens_in, tokens_out, duration_seconds, model="mortdecai-v4"):
"""Track a completed inference request and record in ledger."""
marginal_watts, energy_wh, cost = _calc_marginal_cost(duration_seconds)
# Record in dual ledger
_ledger_record(tokens_in, tokens_out, duration_seconds, cost, energy_wh, model)
with _stats_lock:
_stats["total_requests"] += 1
_stats["total_tokens_in"] += tokens_in
@@ -250,8 +342,9 @@ class GatewayHandler(BaseHTTPRequestHandler):
# Track token usage from response
tokens_in = data.get("prompt_eval_count", 0)
tokens_out = data.get("eval_count", 0)
model_name = (body or {}).get("model", "unknown")
if tokens_in or tokens_out:
_track_request(tokens_in, tokens_out, duration)
_track_request(tokens_in, tokens_out, duration, model_name)
# Add gateway metadata to response
if isinstance(data, dict):
@@ -305,6 +398,34 @@ class GatewayHandler(BaseHTTPRequestHandler):
self._send_json(200, COST_CONFIG)
return
if parsed.path == "/ledger":
if not self._check_auth():
return
entries = _ledger_load()
total_cost = sum(e.get("cost", 0) for e in entries)
self._send_json(200, {
"entries": len(entries),
"total_cost": round(total_cost, 6),
"last_10": entries[-10:],
})
return
if parsed.path == "/reconcile":
if not self._check_auth():
return
entries = _ledger_load()
verification = _ledger_verify(entries)
total_cost = sum(e.get("cost", 0) for e in entries)
self._send_json(200, {
"ledger_entries": len(entries),
"ledger_total_cost": round(total_cost, 6),
"stats_total_cost": round(_stats.get("total_cost", 0), 6),
"discrepancy": round(abs(total_cost - _stats.get("total_cost", 0)), 6),
"hash_verification": verification,
"status": "OK" if verification["invalid"] == 0 else "TAMPERED",
})
return
if parsed.path == "/dashboard":
self._serve_dashboard()
return