make wallet-demo: end-to-end SPV proof from a 'vanilla laptop'
Self-contained recipe that proves the wallet works from a machine with zero local arborist data: 1. shows the vanilla-laptop HOME is empty 2. ingests a 2-doc corpus into a separate server HOME 3. starts `arborist serve` (stub LLM, no upstream calls) 4. bootstraps the trust anchor via curl /snapshot_root 5. runs `arborist wallet ask` on the laptop HOME; verifies exit 0 6. confirms laptop HOME is STILL empty after the verified query 7. runs same call with a deadbeef anchor; verifies exit 3 8. ALL CHECKS PASSED message Two isolated mktemp HOMEs; trap cleans up server + dirs even on interrupt. Seed logic lives in arborist/wallet/_demo_seed.py so the Makefile recipe stays one logical command instead of inlining multiline Python (make recipe lines turn class/def into SyntaxError). Run: `make wallet-demo` (default port 18780; override with WALLET_DEMO_PORT=N).
This commit is contained in:
parent
296017295f
commit
8ebc81cf64
2 changed files with 129 additions and 1 deletions
52
Makefile
52
Makefile
|
|
@ -41,7 +41,8 @@ SEARCH_Q ?= computer
|
|||
textbooks-summary textbooks-urls fetch-textbooks textbooks-stats textbooks-verify \
|
||||
crawl-textbooks crawl-textbooks-stats textbook textbook-list bench-jaggedness \
|
||||
monitor-poll monitor-graph monitor-access \
|
||||
bootstrap-object-store cold-pack cold-pack-dvd cold-pack-all cold-pack-all-dvd cold-unpack cold-hydrate cold-stats cold-list
|
||||
bootstrap-object-store cold-pack cold-pack-dvd cold-pack-all cold-pack-all-dvd cold-unpack cold-hydrate cold-stats cold-list \
|
||||
wallet-demo
|
||||
|
||||
all: bootstrap fetch-cur ingest-cur verify stats ## bootstrap → fetch cur → ingest cur → verify → stats
|
||||
|
||||
|
|
@ -1485,3 +1486,52 @@ clean-db: ## drop the arborist db (keeps fetched data and venv)
|
|||
|
||||
clean-data: ## remove fetched dumps
|
||||
rm -rf $(DATA_DIR)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Wallet-in-cloud SPV demo
|
||||
# ---------------------------------------------------------------------------
|
||||
# Proves the wallet works from a 'vanilla laptop' that has NO local corpus
|
||||
# data. Two isolated HOMEs: SERVER_HOME holds a tiny ingested corpus + the
|
||||
# arborist serve process; LAPTOP_HOME is empty save for code (the venv is
|
||||
# project-level so HOME=tmp doesn't take it away). The wallet command on
|
||||
# LAPTOP_HOME fetches the trust anchor over HTTP, asks a question, verifies
|
||||
# the Merkle bundle, prints exit code. Then the same call with a deadbeef
|
||||
# anchor proves rejection. Cleans up both HOMEs on exit.
|
||||
|
||||
WALLET_DEMO_PORT ?= 18780
|
||||
|
||||
wallet-demo: bootstrap ## SPV wallet end-to-end proof: vanilla laptop queries a cloud server, verifies cryptographically [WALLET_DEMO_PORT=18780]
|
||||
@set -e; \
|
||||
SERVER_HOME=$$(mktemp -d -t arborist-srv-XXXXXX); \
|
||||
LAPTOP_HOME=$$(mktemp -d -t arborist-lap-XXXXXX); \
|
||||
trap 'kill $$SRVPID 2>/dev/null || true; wait $$SRVPID 2>/dev/null || true; rm -rf $$SERVER_HOME $$LAPTOP_HOME; true' EXIT INT TERM; \
|
||||
printf '=== 1. vanilla laptop has NO arborist data ===\n'; \
|
||||
HOME=$$LAPTOP_HOME ls -la $$LAPTOP_HOME; \
|
||||
HOME=$$LAPTOP_HOME ls -la $$LAPTOP_HOME/.arborist 2>&1 || true; \
|
||||
printf '\n=== 2. server ingests a tiny corpus ===\n'; \
|
||||
SERVER_ANCHOR=$$(HOME=$$SERVER_HOME $(PY) -m arborist.wallet._demo_seed $$SERVER_HOME); \
|
||||
printf 'SERVER snapshot_root = %s\n' "$$SERVER_ANCHOR"; \
|
||||
printf '\n=== 3. arborist serve starting on 127.0.0.1:$(WALLET_DEMO_PORT) ===\n'; \
|
||||
HOME=$$SERVER_HOME ARBORIST_WALLET_STUB=1 $(ARBORIST) --db $$SERVER_HOME/.arborist/corpus.db \
|
||||
serve --host 127.0.0.1 --port $(WALLET_DEMO_PORT) --qa-db $$SERVER_HOME/.arborist/qa.db & \
|
||||
SRVPID=$$!; \
|
||||
sleep 1.5; \
|
||||
printf '\n=== 4. vanilla laptop bootstraps trust anchor over HTTP ===\n'; \
|
||||
ANCHOR=$$(curl -sS http://127.0.0.1:$(WALLET_DEMO_PORT)/snapshot_root | $(PY) -c "import json,sys;print(json.load(sys.stdin)['snapshot_root'])"); \
|
||||
printf 'laptop fetched anchor=%s\n' "$$ANCHOR"; \
|
||||
printf '\n=== 5. vanilla laptop asks the cloud and verifies locally ===\n'; \
|
||||
RC=0; HOME=$$LAPTOP_HOME $(ARBORIST) wallet ask "what is anarchism" \
|
||||
--server-url http://127.0.0.1:$(WALLET_DEMO_PORT) \
|
||||
--trust-anchor $$ANCHOR || RC=$$?; \
|
||||
printf 'exit=%d (0 = Merkle-verified)\n' "$$RC"; \
|
||||
test "$$RC" = "0" || { printf 'FAIL: verification should have passed\n'; exit 1; }; \
|
||||
printf '\n=== 6. vanilla laptop HOME is STILL empty (no .arborist created) ===\n'; \
|
||||
HOME=$$LAPTOP_HOME ls -la $$LAPTOP_HOME; \
|
||||
printf '\n=== 7. tamper protection: same server, same question, wrong anchor ===\n'; \
|
||||
BAD_ANCHOR=deadbeef0000000000000000000000000000000000000000000000000000000000; \
|
||||
RC=0; HOME=$$LAPTOP_HOME $(ARBORIST) wallet ask "what is anarchism" \
|
||||
--server-url http://127.0.0.1:$(WALLET_DEMO_PORT) \
|
||||
--trust-anchor $$BAD_ANCHOR || RC=$$?; \
|
||||
printf 'exit=%d (3 = correctly rejected)\n' "$$RC"; \
|
||||
test "$$RC" = "3" || { printf 'FAIL: wrong anchor should have been rejected with exit 3\n'; exit 1; }; \
|
||||
printf '\n=== ALL CHECKS PASSED — wallet works from a no-data machine ===\n'
|
||||
|
|
|
|||
78
arborist/wallet/_demo_seed.py
Normal file
78
arborist/wallet/_demo_seed.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
"""Helper for `make wallet-demo`: seed a tiny corpus + emit snapshot_root.
|
||||
|
||||
Lives here so the Makefile target doesn't have to inline multiline Python
|
||||
(make's recipe-line semantics turn `class`/`def` into syntax errors).
|
||||
Usage:
|
||||
|
||||
python -m arborist.wallet._demo_seed <home_dir>
|
||||
|
||||
Writes a 2-doc corpus into ``<home_dir>/.arborist/corpus.db`` and prints
|
||||
the computed snapshot_root to stdout (one line, no formatting).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Iterator
|
||||
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.snapshot import compute_snapshot_root
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class _S(Source):
|
||||
source_type = "test"
|
||||
|
||||
def __init__(self, ds):
|
||||
self.ds = ds
|
||||
|
||||
def iter_documents(self) -> Iterator[Document]:
|
||||
yield from self.ds
|
||||
|
||||
|
||||
def main(home: str) -> int:
|
||||
arb = os.path.join(home, ".arborist")
|
||||
os.makedirs(arb, exist_ok=True)
|
||||
db_path = os.path.join(arb, "corpus.db")
|
||||
conn = connect(db_path)
|
||||
try:
|
||||
ingest_source(
|
||||
conn,
|
||||
_S([
|
||||
Document(
|
||||
uri="wallet://anarchism",
|
||||
source_type="test",
|
||||
title="Anarchism",
|
||||
content=(
|
||||
"Anarchism is a political philosophy that promotes a "
|
||||
"stateless society. " * 8
|
||||
+ "The phrase ANARCHISM-LEMMA-X7Y is a unique marker "
|
||||
"for this proof. " * 4
|
||||
),
|
||||
),
|
||||
Document(
|
||||
uri="wallet://capital",
|
||||
source_type="test",
|
||||
title="Capital",
|
||||
content=(
|
||||
"The eight forms of capital include living, social, "
|
||||
"intellectual capital. " * 8
|
||||
),
|
||||
),
|
||||
]),
|
||||
)
|
||||
snap, n = compute_snapshot_root(conn)
|
||||
finally:
|
||||
conn.close()
|
||||
print(snap)
|
||||
print(f"# seeded {n} docs into {db_path}", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: python -m arborist.wallet._demo_seed <home_dir>", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
sys.exit(main(sys.argv[1]))
|
||||
Loading…
Add table
Add a link
Reference in a new issue