chore: initialize monorepo with project scaffolding, configs, and CI setup
This commit is contained in:
36
apps/blockchain-node/scripts/devnet_up.sh
Normal file
36
apps/blockchain-node/scripts/devnet_up.sh
Normal file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
export PYTHONPATH="${ROOT_DIR}/src:${ROOT_DIR}/scripts:${PYTHONPATH:-}"
|
||||
|
||||
GENESIS_PATH="${ROOT_DIR}/data/devnet/genesis.json"
|
||||
python "${ROOT_DIR}/scripts/make_genesis.py" --output "${GENESIS_PATH}" --force
|
||||
|
||||
echo "[devnet] Generated genesis at ${GENESIS_PATH}"
|
||||
|
||||
declare -a CHILD_PIDS=()
|
||||
cleanup() {
|
||||
for pid in "${CHILD_PIDS[@]}"; do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
python -m aitbc_chain.main &
|
||||
CHILD_PIDS+=($!)
|
||||
echo "[devnet] Blockchain node started (PID ${CHILD_PIDS[-1]})"
|
||||
|
||||
sleep 1
|
||||
|
||||
python -m uvicorn aitbc_chain.app:app --host 127.0.0.1 --port 8080 --log-level info &
|
||||
CHILD_PIDS+=($!)
|
||||
echo "[devnet] RPC API serving at http://127.0.0.1:8080"
|
||||
|
||||
python -m uvicorn mock_coordinator:app --host 127.0.0.1 --port 8090 --log-level info &
|
||||
CHILD_PIDS+=($!)
|
||||
echo "[devnet] Mock coordinator serving at http://127.0.0.1:8090"
|
||||
|
||||
wait
|
||||
46
apps/blockchain-node/scripts/keygen.py
Normal file
46
apps/blockchain-node/scripts/keygen.py
Normal file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a pseudo devnet key pair for blockchain components."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Generate a devnet key pair")
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
help="Optional path to write the keypair JSON (prints to stdout if omitted)",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def generate_keypair() -> dict:
|
||||
private_key = secrets.token_hex(32)
|
||||
public_key = secrets.token_hex(32)
|
||||
address = "ait1" + secrets.token_hex(20)
|
||||
return {
|
||||
"private_key": private_key,
|
||||
"public_key": public_key,
|
||||
"address": address,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
keypair = generate_keypair()
|
||||
payload = json.dumps(keypair, indent=2)
|
||||
if args.output:
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(payload + "\n", encoding="utf-8")
|
||||
print(f"[keygen] wrote keypair to {args.output}")
|
||||
else:
|
||||
print(payload)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
96
apps/blockchain-node/scripts/make_genesis.py
Normal file
96
apps/blockchain-node/scripts/make_genesis.py
Normal file
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a deterministic devnet genesis file for the blockchain node."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_GENESIS = {
|
||||
"chain_id": "ait-devnet",
|
||||
"timestamp": None, # populated at runtime
|
||||
"params": {
|
||||
"mint_per_unit": 1000,
|
||||
"coordinator_ratio": 0.05,
|
||||
"base_fee": 10,
|
||||
"fee_per_byte": 1,
|
||||
},
|
||||
"accounts": [
|
||||
{
|
||||
"address": "ait1faucet000000000000000000000000000000000",
|
||||
"balance": 1_000_000_000,
|
||||
"nonce": 0,
|
||||
}
|
||||
],
|
||||
"authorities": [
|
||||
{
|
||||
"address": "ait1devproposer000000000000000000000000000000",
|
||||
"weight": 1,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Generate devnet genesis data")
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
default=Path("data/devnet/genesis.json"),
|
||||
help="Path to write the generated genesis file (default: data/devnet/genesis.json)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Overwrite the genesis file if it already exists.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--faucet-address",
|
||||
default="ait1faucet000000000000000000000000000000000",
|
||||
help="Address seeded with devnet funds.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--faucet-balance",
|
||||
type=int,
|
||||
default=1_000_000_000,
|
||||
help="Faucet balance in smallest units.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--authorities",
|
||||
nargs="*",
|
||||
default=["ait1devproposer000000000000000000000000000000"],
|
||||
help="Authority addresses included in the genesis file.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def build_genesis(args: argparse.Namespace) -> dict:
|
||||
genesis = json.loads(json.dumps(DEFAULT_GENESIS)) # deep copy via JSON
|
||||
genesis["timestamp"] = int(time.time())
|
||||
genesis["accounts"][0]["address"] = args.faucet_address
|
||||
genesis["accounts"][0]["balance"] = args.faucet_balance
|
||||
genesis["authorities"] = [
|
||||
{"address": address, "weight": 1}
|
||||
for address in args.authorities
|
||||
]
|
||||
return genesis
|
||||
|
||||
|
||||
def write_genesis(path: Path, data: dict, force: bool) -> None:
|
||||
if path.exists() and not force:
|
||||
raise SystemExit(f"Genesis file already exists at {path}. Use --force to overwrite.")
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
print(f"[genesis] wrote genesis file to {path}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
genesis = build_genesis(args)
|
||||
write_genesis(args.output, genesis, args.force)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
38
apps/blockchain-node/scripts/mock_coordinator.py
Normal file
38
apps/blockchain-node/scripts/mock_coordinator.py
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Mock coordinator API for devnet testing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI(title="Mock Coordinator API", version="0.1.0")
|
||||
|
||||
MOCK_JOBS: Dict[str, Dict[str, str]] = {
|
||||
"job_1": {"status": "complete", "price": "50000", "compute_units": 2500},
|
||||
"job_2": {"status": "complete", "price": "25000", "compute_units": 1200},
|
||||
}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health() -> Dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.post("/attest/receipt")
|
||||
def attest_receipt(payload: Dict[str, str]) -> Dict[str, str | bool]:
|
||||
job_id = payload.get("job_id")
|
||||
if job_id in MOCK_JOBS:
|
||||
return {
|
||||
"exists": True,
|
||||
"paid": True,
|
||||
"not_double_spent": True,
|
||||
"quote": MOCK_JOBS[job_id],
|
||||
}
|
||||
return {
|
||||
"exists": False,
|
||||
"paid": False,
|
||||
"not_double_spent": False,
|
||||
"quote": {},
|
||||
}
|
||||
Reference in New Issue
Block a user