diff --git a/.github/workflows/doctests.yml b/.github/workflows/doctests.yml new file mode 100644 index 0000000..46f99bd --- /dev/null +++ b/.github/workflows/doctests.yml @@ -0,0 +1,238 @@ +name: blockchain-module Doc-Tests + +# Runs the executable blockchain-module doc-test +# (doctests/blockchain-module-runtime.test.yaml) end-to-end via the shared +# doctest CLI: builds logoscore and lgpm, packages and installs THIS commit of +# the blockchain module as an .lgx, starts the daemon, loads the module, calls +# its methods, and asserts on the output. +# +# ────────────────────────────────────────────────────────────────────────────── +# One-time setup required for the clickable report links to work: +# +# 1. Repo Settings → Pages → "Build and deployment" → Source: "Deploy from a +# branch", Branch: `gh-pages` / `(root)`. (The publish-report job creates +# the gh-pages branch on its first run.) +# 2. Nothing else — GITHUB_TOKEN already has the permissions granted below. +# +# Each run publishes the two-column HTML report to: +# https://.github.io//pr-// (pull requests) +# https://.github.io//main// (pushes to main/master) +# and (for PRs) posts/updates a comment with the links. +# +# Note: pull requests opened from forks get a read-only GITHUB_TOKEN, so the +# Pages push and PR comment are skipped for them — the downloadable artifact is +# still produced. PRs from branches in this repo get the full clickable links. +# ────────────────────────────────────────────────────────────────────────────── + +on: + pull_request: + branches: [master, main] + push: + branches: [master, main] + workflow_dispatch: + +concurrency: + group: doctests-${{ github.ref }} + cancel-in-progress: true + +jobs: + doctests: + name: blockchain-module doc-tests (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + + runs-on: ${{ matrix.os }} + timeout-minutes: 120 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + + - name: Setup Cachix + uses: cachix/cachix-action@v15 + with: + name: logos-co + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + + # Resolve the commit under test. For pull requests this is the PR's head + # commit (not the synthetic merge commit); for pushes it's the pushed + # commit. Passed to --release-for below so the doc-test packages THIS commit + # of logos-blockchain-module instead of the latest published flake. + # + # Fork PRs are the exception: their head commit lives in the fork, not in + # logos-blockchain/logos-blockchain-module, so nix could not fetch + # `github:logos-blockchain/logos-blockchain-module/`. We blank the SHA + # for forks (--release-for repo= → pins that repo to latest), so the + # doc-test still runs for fork PRs, just against master. + - name: Resolve commit under test + id: commit + shell: bash + run: | + if [ "${{ github.event_name }}" = "pull_request" ] && \ + [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ]; then + echo "sha=" >> "$GITHUB_OUTPUT" + echo "Fork PR detected — doc-test will run against latest master." + else + echo "sha=${{ github.event.pull_request.head.sha || github.sha }}" >> "$GITHUB_OUTPUT" + fi + + # The runner is the shared `doctest` CLI, invoked directly via its flake + # (github:logos-co/logos-doctest). The flake bundles Python + PyYAML + # (+ rich), so no pip install step is needed. + # + # --release-for pins the {release} placeholder for logos-blockchain-module + # to the commit under test, so + # `github:logos-blockchain/logos-blockchain-module{release}` in the spec + # becomes `.../` — the doc-test packages and exercises this PR/push + # rather than master. Every other repo URL still resolves to latest. + - name: Run blockchain-module runtime doc-test + run: | + # --continue-on-fail so the run walks every step and the published + # report is complete. The job still fails (non-zero exit) if any step + # failed; this only changes whether we stop early. + nix run github:logos-co/logos-doctest -- run \ + doctests/blockchain-module-runtime.test.yaml \ + --verbose \ + --continue-on-fail \ + --release-for logos-blockchain-module=${{ steps.commit.outputs.sha }} \ + --report "${{ runner.temp }}/blockchain-doctest-report.html" + + - name: Stage report for upload + if: always() + shell: bash + run: | + mkdir -p report-out + # Name it index.html so the published directory URL renders directly. + if [ -f "${{ runner.temp }}/blockchain-doctest-report.html" ]; then + cp "${{ runner.temp }}/blockchain-doctest-report.html" report-out/index.html + else + echo "

No report produced

" > report-out/index.html + fi + + - name: Upload blockchain-module execution report + if: always() + uses: actions/upload-artifact@v4 + with: + name: blockchain-doctest-report-${{ matrix.os }} + path: report-out/index.html + if-no-files-found: warn + + - name: Verify markdown generation + run: | + for spec in blockchain-module-runtime; do + nix run github:logos-co/logos-doctest -- generate \ + "doctests/$spec.test.yaml" \ + --release-for logos-blockchain-module=${{ steps.commit.outputs.sha }} \ + -o "/tmp/$spec.md" + test -s "/tmp/$spec.md" + done + echo "Generated markdown successfully" + + publish-report: + name: Publish report to GitHub Pages + needs: doctests + # Run even when tests fail — a failing run is exactly when you want to open + # the report. Skip on forks, where GITHUB_TOKEN can't push or comment. + if: ${{ always() && github.event.pull_request.head.repo.fork != true }} + runs-on: ubuntu-latest + + permissions: + contents: write # push to the gh-pages branch + pull-requests: write # post/update the PR comment + + # Serialize Pages pushes so two refs can't race on the gh-pages branch. + concurrency: + group: gh-pages-publish + cancel-in-progress: false + + steps: + - name: Download all reports + uses: actions/download-artifact@v4 + with: + path: artifacts + # No `name:` → downloads every artifact into artifacts//... + + - name: Arrange site directory + id: arrange + shell: bash + run: | + set -euo pipefail + if [ "${{ github.event_name }}" = "pull_request" ]; then + BASE="pr-${{ github.event.pull_request.number }}" + else + BASE="main" + fi + echo "base=$BASE" >> "$GITHUB_OUTPUT" + + mkdir -p "site/$BASE" + found="" + for os in ubuntu-latest macos-latest; do + src="artifacts/blockchain-doctest-report-$os/index.html" + if [ -f "$src" ]; then + mkdir -p "site/$BASE/$os" + cp "$src" "site/$BASE/$os/index.html" + found="$found $os" + fi + done + echo "found=$found" >> "$GITHUB_OUTPUT" + + # Landing page for this ref linking to each OS report. + { + echo "" + echo "blockchain-module doc-test reports — $BASE" + echo "" + echo "

blockchain-module doc-test reports

" + echo "

$BASE · commit ${GITHUB_SHA::7}

    " + for os in ubuntu-latest macos-latest; do + if [ -d "site/$BASE/$os" ]; then + echo "
  • $os
  • " + fi + done + echo "
" + } > "site/$BASE/index.html" + + - name: Deploy to gh-pages + if: steps.arrange.outputs.found != '' + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./site + keep_files: true # don't wipe other PRs' directories + commit_message: "Publish blockchain-module doc-test report for ${{ steps.arrange.outputs.base }} (${{ github.sha }})" + + - name: Comment on PR with report links + if: ${{ github.event_name == 'pull_request' && steps.arrange.outputs.found != '' }} + uses: actions/github-script@v7 + with: + script: | + const base = "${{ steps.arrange.outputs.base }}"; + const owner = context.repo.owner; + const repo = context.repo.repo; + const root = `https://${owner}.github.io/${repo}/${base}`; + const oses = "${{ steps.arrange.outputs.found }}".trim().split(/\s+/).filter(Boolean); + + const links = oses.map(os => `- [\`${os}\` report](${root}/${os}/)`).join("\n"); + const marker = ""; + const body = + `${marker}\n` + + `### 📊 blockchain-module doc-test report\n\n` + + `This commit of the blockchain module, packaged as an \`.lgx\` and run ` + + `through a logoscore daemon — rendered alongside the commands actually ` + + `run and their output (updated each run, commit \`${context.sha.slice(0,7)}\`):\n\n` + + `${links}\n\n` + + `_Pages can take a minute to update after the run finishes._`; + + const { data: comments } = await github.rest.issues.listComments({ + owner, repo, issue_number: context.issue.number, per_page: 100, + }); + const existing = comments.find(c => c.body && c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number: context.issue.number, body }); + } diff --git a/doctests/blockchain-module-runtime.test.yaml b/doctests/blockchain-module-runtime.test.yaml new file mode 100644 index 0000000..0d1f46f --- /dev/null +++ b/doctests/blockchain-module-runtime.test.yaml @@ -0,0 +1,310 @@ +name: "Running This Blockchain Module Against logoscore" +output: blockchain-module-runtime.md +release: "" + +intro: | + `logos-blockchain-module` is a Logos `core` module that wraps the + [logos-blockchain](https://github.com/logos-blockchain/logos-blockchain) C + bindings — a full Cryptarchia consensus node with wallet, blend, keystore and + block-explorer APIs — and ships the zk circuit binaries the node needs at + runtime. This doc-test exercises **this** blockchain-module commit end-to-end + through the headless `logoscore` runtime: + + 1. Build the `logoscore` CLI and the `lgpm` local package manager from their + published flakes. `logoscore` is the headless frontend for `logos-liblogos`, + so building it brings in the whole module-runtime stack (`logos_host`, + `liblogos_core`, the IPC layer). + 2. Build **this** blockchain module as an installable `.lgx` package straight + from its own flake's `#lgx` output, **pinned to the commit under test** — so + the module you run is built from exactly what is checked out here, not the + latest published release. + 3. Install the `.lgx` into a `./modules` directory with `lgpm`. + 4. Start `logoscore` in daemon mode (`-D`), load `liblogos_blockchain_module`, introspect + it with `module-info`, and call several of its methods — verifying the module + actually runs and round-trips real values through the logos-blockchain C + library. + + Joining the devnet means dialing real peers, NTP sync and zk proving — none of + which is reproducible in CI — so this doc-test deliberately stays **offline**. + We exercise the methods that do real, deterministic work without a running node + (generating a node user-config and its keystore, deriving the peer id, + re-syncing the config from the keystore) and we confirm the node-backed methods + (wallet, explorer, consensus) are wired up and callable by asserting on the + well-defined `The node is not running` response they return before a node is + started. Starting an actual node is covered by the UI app and the developer + guide. + + Because the module is built from the commit under test and then loaded and + called through a real `logoscore` daemon, a green run is real evidence that this + change keeps the blockchain module loadable and callable. + +what_you_build: "This `liblogos_blockchain_module`, packaged as `.lgx`, installed with `lgpm`, and called through a `logoscore` daemon." + +what_you_learn: + - How to build the `logoscore` runtime and the `lgpm` package manager from their flakes + - How a module's flake exposes a ready-to-install `.lgx` via its `#lgx` output + - How to install an `.lgx` into a modules directory with `lgpm` + - How to start the `logoscore` daemon, load a module, introspect it, and call its methods + - How to generate a node user-config and keystore, and derive the peer id, offline + - How to re-sync a user config from its keystore with `update_user_config` + - How the node-backed methods report a clear error until a node is started + - How to shut the daemon down and confirm it has exited + +prerequisites: + - | + **Nix** with flakes enabled. Install from [nixos.org](https://nixos.org/download.html), then enable flakes: + + ```bash + mkdir -p ~/.config/nix + echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf + ``` + + Verify: `nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"` + - "**A Linux or macOS machine.**" + +sections: + - title: "Build logoscore" + step: true + text: | + Build the `logoscore` CLI from its published flake. The result is symlinked to + `./logos/`. `logoscore` is the headless frontend for `logos-liblogos`, so this + one build brings in the whole module-runtime stack the daemon needs. + steps: + - title: "Build the CLI" + run: "nix build 'github:logos-co/logos-logoscore-cli' --out-link ./logos" + code_block: | + nix build 'github:logos-co/logos-logoscore-cli' --out-link ./logos + check_file: "logos/bin/logoscore" + post_text: | + The build produces `logos/bin/logoscore` plus bundled runtime libraries + and a `logos/modules/` directory containing the built-in + `capability_module` (required for the auth handshake when loading + modules). + + - title: "Build the lgpm package manager" + step: true + text: | + `lgpm` installs `.lgx` packages into a modules directory and scans what is + installed. Build it from the `logos-package-manager` flake and link it as + `./lgpm`. + steps: + - title: "Build lgpm" + run: "nix build 'github:logos-co/logos-package-manager#cli' -o lgpm" + check_file: "lgpm/bin/lgpm" + post_text: "The executable is at `./lgpm/bin/lgpm`." + + - title: "Build and install this blockchain module" + step: true + text: | + Build **this** blockchain module's `.lgx` straight from its flake's `#lgx` + output and install it into a local `./modules` directory with `lgpm`. Every + module built with + [`logos-module-builder`](https://github.com/logos-co/logos-module-builder) + exposes a ready-to-install `#lgx`. + + > The `{release}` in the URL is what pins the build to a specific commit: the + > doc-test runner expands it to a concrete ref. Locally that is this + > checkout's `HEAD` (see `run.sh`); in CI it is the commit being tested. With + > no pin it falls back to the latest `master`. + steps: + - title: "Build the module's .lgx" + text: | + Build the `#lgx` output and link it as `./blockchain-lgx`. (This compiles + the module, the logos-blockchain Rust node and its zk circuits through + Nix, so the first build is slow.) + run: "nix build 'github:logos-blockchain/logos-blockchain-module{release}#lgx' -o blockchain-lgx" + code_block: | + # From inside the clone this is simply: nix build '.#lgx' + nix build 'github:logos-blockchain/logos-blockchain-module{release}#lgx' -o blockchain-lgx + post_text: "The `.lgx` package is now under `./blockchain-lgx/`:" + extra_run: + run: "ls blockchain-lgx/*.lgx" + + - title: "Seed the modules directory with the bundled capability module" + text: | + `liblogos_blockchain_module` is loaded through the host's capability layer, so the + modules directory also needs the `capability_module` that ships with + `logoscore`. Copy it across first. + run: | + mkdir -p modules + cp -RL ./logos/modules/. ./modules/ + check_file: "modules/capability_module/manifest.json" + + - title: "Install the .lgx with lgpm" + text: | + Install the freshly-built package into `./modules`. `liblogos_blockchain_module` is + a `core` module, so it goes to `--modules-dir`. The package is unsigned + (a local dev build), so we pass `--allow-unsigned`. + run: "./lgpm/bin/lgpm --modules-dir ./modules --allow-unsigned install --file blockchain-lgx/*.lgx" + expect_contains: + - "Installed to:" + + - title: "Confirm the install" + text: "Scan the directory and confirm the module landed:" + run: "./lgpm/bin/lgpm --modules-dir ./modules list" + expect_contains: + - "liblogos_blockchain_module" + check_file: "modules/liblogos_blockchain_module/manifest.json" + + - title: "Run the daemon and call the module" + step: true + text: | + Start `logoscore` in daemon mode pointed at `./modules`, then use the client + subcommands to load `liblogos_blockchain_module`, introspect it, and call several of + its methods. Daemon output is captured in `logs.txt`. + steps: + - title: "Start the daemon" + text: | + Start logoscore in daemon mode in the background, capturing output to + `logs.txt`: + run: "sh -c './logos/bin/logoscore -D -m ./modules > logs.txt 2>&1 &'" + code_block: "logoscore -D -m ./modules > logs.txt &" + post_text: | + The `-D` flag starts the daemon. The client subcommands below connect to + this running process via the config written under `~/.logoscore/`. + + - run: "sleep 3" + + - title: "Inspect the startup log" + text: "Review the daemon's startup output:" + run: "cat logs.txt" + + - title: "Check daemon status" + text: "Verify the daemon is running:" + run: "./logos/bin/logoscore status" + code_block: "logoscore status" + + - title: "List discovered modules" + text: "`liblogos_blockchain_module` should be visible in the scan directory:" + run: "./logos/bin/logoscore list-modules" + code_block: "logoscore list-modules" + expect_contains: + - "liblogos_blockchain_module" + + - title: "Load the module" + text: "Load `liblogos_blockchain_module` into the running daemon:" + run: "./logos/bin/logoscore load-module liblogos_blockchain_module" + code_block: "logoscore load-module liblogos_blockchain_module" + expect_contains: + - "liblogos_blockchain_module" + + - title: "Confirm the module is loaded" + text: | + Re-run `status`; the module that was `not_loaded` before now reports + `loaded`: + run: "./logos/bin/logoscore status" + code_block: "logoscore status" + expect_contains: + - "liblogos_blockchain_module" + - '"status":"loaded"' + + - title: "Introspect the module with module-info" + text: | + `module-info` lists the `Q_INVOKABLE` methods the module exposes — the + same methods you can `call`: + run: "./logos/bin/logoscore module-info liblogos_blockchain_module" + code_block: "logoscore module-info liblogos_blockchain_module" + expect_contains: + - "liblogos_blockchain_module" + - "generate_user_config" + - "get_peer_id" + - "get_cryptarchia_info" + + - title: "Generate a node user-config" + text: | + `generate_user_config` takes a JSON argument describing the node and + writes a ready-to-run config file to the `output` path, plus a sibling + `keystore.yaml` holding freshly-generated default keys — no node or + network required. We write the config to `./user-config.yaml`; a `0` + result means success. The JSON is passed with logoscore's `@file` syntax + after writing it to disk: + file: + path: gen-config.json + content: | + { + "output": "./user-config.yaml" + } + run: "./logos/bin/logoscore call liblogos_blockchain_module generate_user_config @gen-config.json" + code_block: "logoscore call liblogos_blockchain_module generate_user_config @gen-config.json" + expect_contains: + - '"result":0' + check_file: "user-config.yaml" + + - title: "Confirm the keystore was written alongside the config" + text: | + `generate_user_config` also writes a `keystore.yaml` next to the config, + holding the node's freshly-generated default keys. Both files are written + relative to the daemon's working directory: + run: "ls -1 user-config.yaml keystore.yaml" + check_file: "keystore.yaml" + + - title: "Derive the node's peer id" + text: | + `get_peer_id` reads the network key out of the config we just generated + and derives the libp2p peer id from it — a deterministic, offline + round-trip through the logos-blockchain C library. The result is the + node's base58 peer id (the `12D3Koo…` form): + run: "./logos/bin/logoscore call liblogos_blockchain_module get_peer_id ./user-config.yaml" + code_block: "logoscore call liblogos_blockchain_module get_peer_id ./user-config.yaml" + expect_contains: + - '"result":"12D3Koo' + + - title: "Update the user-config from the keystore" + text: | + `update_user_config` re-syncs a user config with the keys in a keystore + file — the same offline maintenance operation the `update-config` CLI + command performs. It takes the config path and the keystore path and + returns `0` on success. We point it at the pair generated above: + run: "./logos/bin/logoscore call liblogos_blockchain_module update_user_config ./user-config.yaml ./keystore.yaml" + code_block: "logoscore call liblogos_blockchain_module update_user_config ./user-config.yaml ./keystore.yaml" + expect_contains: + - '"result":0' + + - title: "Query consensus info before the node is running" + text: | + The node-backed methods (wallet, explorer, consensus) need a started + node. Calling `get_cryptarchia_info` now — before `start` — returns the + module's well-defined `The node is not running` message. This proves the + method is wired through the IPC bridge and callable; actually starting + the node (which dials the devnet) is out of scope for this offline + doc-test: + run: "./logos/bin/logoscore call liblogos_blockchain_module get_cryptarchia_info" + code_block: "logoscore call liblogos_blockchain_module get_cryptarchia_info" + expect_contains: + - "The node is not running" + + - title: "Query a wallet balance before the node is running" + text: | + `wallet_get_balance` behaves the same way — it reports the node is not + running rather than crashing, so a frontend can surface a clean error: + run: "./logos/bin/logoscore call liblogos_blockchain_module wallet_get_balance aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + code_block: "logoscore call liblogos_blockchain_module wallet_get_balance " + expect_contains: + - "The node is not running" + + - title: "Query the block explorer before the node is running" + text: | + The explorer method `get_block` is the same: callable through the bridge, + and reporting the node is not running until one is started: + run: "./logos/bin/logoscore call liblogos_blockchain_module get_block aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + code_block: "logoscore call liblogos_blockchain_module get_block " + expect_contains: + - "The node is not running" + + - title: "Stop the daemon" + text: "Shut the daemon down cleanly:" + run: "./logos/bin/logoscore stop" + code_block: "logoscore stop" + post_text: | + The daemon removes its state file and exits. + + - run: "sleep 2" + + - title: "Confirm the daemon has stopped" + text: | + With no daemon running, the client reports `not_running` and exits + non-zero, so we add `|| true` to let the doc-test assert on the output: + run: "./logos/bin/logoscore status || true" + code_block: "logoscore status" + expect_contains: + - '"status":"not_running"' diff --git a/doctests/outputs/blockchain-module-runtime.md b/doctests/outputs/blockchain-module-runtime.md new file mode 100644 index 0000000..5406d18 --- /dev/null +++ b/doctests/outputs/blockchain-module-runtime.md @@ -0,0 +1,338 @@ +# Running This Blockchain Module Against logoscore + +`logos-blockchain-module` is a Logos `core` module that wraps the +[logos-blockchain](https://github.com/logos-blockchain/logos-blockchain) C +bindings — a full Cryptarchia consensus node with wallet, blend, keystore and +block-explorer APIs — and ships the zk circuit binaries the node needs at +runtime. This doc-test exercises **this** blockchain-module commit end-to-end +through the headless `logoscore` runtime: + +1. Build the `logoscore` CLI and the `lgpm` local package manager from their + published flakes. `logoscore` is the headless frontend for `logos-liblogos`, + so building it brings in the whole module-runtime stack (`logos_host`, + `liblogos_core`, the IPC layer). +2. Build **this** blockchain module as an installable `.lgx` package straight + from its own flake's `#lgx` output, **pinned to the commit under test** — so + the module you run is built from exactly what is checked out here, not the + latest published release. +3. Install the `.lgx` into a `./modules` directory with `lgpm`. +4. Start `logoscore` in daemon mode (`-D`), load `liblogos_blockchain_module`, introspect + it with `module-info`, and call several of its methods — verifying the module + actually runs and round-trips real values through the logos-blockchain C + library. + +Joining the devnet means dialing real peers, NTP sync and zk proving — none of +which is reproducible in CI — so this doc-test deliberately stays **offline**. +We exercise the methods that do real, deterministic work without a running node +(generating a node user-config and its keystore, deriving the peer id, +re-syncing the config from the keystore) and we confirm the node-backed methods +(wallet, explorer, consensus) are wired up and callable by asserting on the +well-defined `The node is not running` response they return before a node is +started. Starting an actual node is covered by the UI app and the developer +guide. + +Because the module is built from the commit under test and then loaded and +called through a real `logoscore` daemon, a green run is real evidence that this +change keeps the blockchain module loadable and callable. + +**What you'll build:** This `liblogos_blockchain_module`, packaged as `.lgx`, installed with `lgpm`, and called through a `logoscore` daemon. + +**What you'll learn:** + +- How to build the `logoscore` runtime and the `lgpm` package manager from their flakes +- How a module's flake exposes a ready-to-install `.lgx` via its `#lgx` output +- How to install an `.lgx` into a modules directory with `lgpm` +- How to start the `logoscore` daemon, load a module, introspect it, and call its methods +- How to generate a node user-config and keystore, and derive the peer id, offline +- How to re-sync a user config from its keystore with `update_user_config` +- How the node-backed methods report a clear error until a node is started +- How to shut the daemon down and confirm it has exited + +## Prerequisites + +- **Nix** with flakes enabled. Install from [nixos.org](https://nixos.org/download.html), then enable flakes: + +```bash +mkdir -p ~/.config/nix +echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf +``` + +Verify: `nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"` + +- **A Linux or macOS machine.** + +--- + +## Step 1: Build logoscore + +Build the `logoscore` CLI from its published flake. The result is symlinked to +`./logos/`. `logoscore` is the headless frontend for `logos-liblogos`, so this +one build brings in the whole module-runtime stack the daemon needs. + +### 1.1 Build the CLI + +```bash +nix build 'github:logos-co/logos-logoscore-cli' --out-link ./logos +``` + +The build produces `logos/bin/logoscore` plus bundled runtime libraries +and a `logos/modules/` directory containing the built-in +`capability_module` (required for the auth handshake when loading +modules). + +--- + +## Step 2: Build the lgpm package manager + +`lgpm` installs `.lgx` packages into a modules directory and scans what is +installed. Build it from the `logos-package-manager` flake and link it as +`./lgpm`. + +### 2.1 Build lgpm + +```bash +nix build 'github:logos-co/logos-package-manager#cli' -o lgpm +``` + +The executable is at `./lgpm/bin/lgpm`. + +--- + +## Step 3: Build and install this blockchain module + +Build **this** blockchain module's `.lgx` straight from its flake's `#lgx` +output and install it into a local `./modules` directory with `lgpm`. Every +module built with +[`logos-module-builder`](https://github.com/logos-co/logos-module-builder) +exposes a ready-to-install `#lgx`. + +> The `` in the URL is what pins the build to a specific commit: the +> doc-test runner expands it to a concrete ref. Locally that is this +> checkout's `HEAD` (see `run.sh`); in CI it is the commit being tested. With +> no pin it falls back to the latest `master`. + +### 3.1 Build the module's .lgx + +Build the `#lgx` output and link it as `./blockchain-lgx`. (This compiles +the module, the logos-blockchain Rust node and its zk circuits through +Nix, so the first build is slow.) + +```bash +# From inside the clone this is simply: nix build '.#lgx' +nix build 'github:logos-blockchain/logos-blockchain-module#lgx' -o blockchain-lgx +``` + +The `.lgx` package is now under `./blockchain-lgx/`: + +```bash +ls blockchain-lgx/*.lgx +``` + +### 3.2 Seed the modules directory with the bundled capability module + +`liblogos_blockchain_module` is loaded through the host's capability layer, so the +modules directory also needs the `capability_module` that ships with +`logoscore`. Copy it across first. + +```bash +mkdir -p modules +cp -RL ./logos/modules/. ./modules/ + +``` + +### 3.3 Install the .lgx with lgpm + +Install the freshly-built package into `./modules`. `liblogos_blockchain_module` is +a `core` module, so it goes to `--modules-dir`. The package is unsigned +(a local dev build), so we pass `--allow-unsigned`. + +```bash +./lgpm/bin/lgpm --modules-dir ./modules --allow-unsigned install --file blockchain-lgx/*.lgx +``` + +### 3.4 Confirm the install + +Scan the directory and confirm the module landed: + +```bash +./lgpm/bin/lgpm --modules-dir ./modules list +``` + +--- + +## Step 4: Run the daemon and call the module + +Start `logoscore` in daemon mode pointed at `./modules`, then use the client +subcommands to load `liblogos_blockchain_module`, introspect it, and call several of +its methods. Daemon output is captured in `logs.txt`. + +### 4.1 Start the daemon + +Start logoscore in daemon mode in the background, capturing output to +`logs.txt`: + +```bash +logoscore -D -m ./modules > logs.txt & +``` + +The `-D` flag starts the daemon. The client subcommands below connect to +this running process via the config written under `~/.logoscore/`. + +```bash +sleep 3 +``` + +### 4.2 Inspect the startup log + +Review the daemon's startup output: + +```bash +cat logs.txt +``` + +### 4.3 Check daemon status + +Verify the daemon is running: + +```bash +logoscore status +``` + +### 4.4 List discovered modules + +`liblogos_blockchain_module` should be visible in the scan directory: + +```bash +logoscore list-modules +``` + +### 4.5 Load the module + +Load `liblogos_blockchain_module` into the running daemon: + +```bash +logoscore load-module liblogos_blockchain_module +``` + +### 4.6 Confirm the module is loaded + +Re-run `status`; the module that was `not_loaded` before now reports +`loaded`: + +```bash +logoscore status +``` + +### 4.7 Introspect the module with module-info + +`module-info` lists the `Q_INVOKABLE` methods the module exposes — the +same methods you can `call`: + +```bash +logoscore module-info liblogos_blockchain_module +``` + +### 4.8 Generate a node user-config + +`generate_user_config` takes a JSON argument describing the node and +writes a ready-to-run config file to the `output` path, plus a sibling +`keystore.yaml` holding freshly-generated default keys — no node or +network required. We write the config to `./user-config.yaml`; a `0` +result means success. The JSON is passed with logoscore's `@file` syntax +after writing it to disk: + +```json +{ + "output": "./user-config.yaml" +} +``` + +```bash +logoscore call liblogos_blockchain_module generate_user_config @gen-config.json +``` + +### 4.9 Confirm the keystore was written alongside the config + +`generate_user_config` also writes a `keystore.yaml` next to the config, +holding the node's freshly-generated default keys. Both files are written +relative to the daemon's working directory: + +```bash +ls -1 user-config.yaml keystore.yaml +``` + +### 4.10 Derive the node's peer id + +`get_peer_id` reads the network key out of the config we just generated +and derives the libp2p peer id from it — a deterministic, offline +round-trip through the logos-blockchain C library. The result is the +node's base58 peer id (the `12D3Koo…` form): + +```bash +logoscore call liblogos_blockchain_module get_peer_id ./user-config.yaml +``` + +### 4.11 Update the user-config from the keystore + +`update_user_config` re-syncs a user config with the keys in a keystore +file — the same offline maintenance operation the `update-config` CLI +command performs. It takes the config path and the keystore path and +returns `0` on success. We point it at the pair generated above: + +```bash +logoscore call liblogos_blockchain_module update_user_config ./user-config.yaml ./keystore.yaml +``` + +### 4.12 Query consensus info before the node is running + +The node-backed methods (wallet, explorer, consensus) need a started +node. Calling `get_cryptarchia_info` now — before `start` — returns the +module's well-defined `The node is not running` message. This proves the +method is wired through the IPC bridge and callable; actually starting +the node (which dials the devnet) is out of scope for this offline +doc-test: + +```bash +logoscore call liblogos_blockchain_module get_cryptarchia_info +``` + +### 4.13 Query a wallet balance before the node is running + +`wallet_get_balance` behaves the same way — it reports the node is not +running rather than crashing, so a frontend can surface a clean error: + +```bash +logoscore call liblogos_blockchain_module wallet_get_balance +``` + +### 4.14 Query the block explorer before the node is running + +The explorer method `get_block` is the same: callable through the bridge, +and reporting the node is not running until one is started: + +```bash +logoscore call liblogos_blockchain_module get_block +``` + +### 4.15 Stop the daemon + +Shut the daemon down cleanly: + +```bash +logoscore stop +``` + +The daemon removes its state file and exits. + +```bash +sleep 2 +``` + +### 4.16 Confirm the daemon has stopped + +With no daemon running, the client reports `not_running` and exits +non-zero, so we add `|| true` to let the doc-test assert on the output: + +```bash +logoscore status +``` diff --git a/doctests/run.sh b/doctests/run.sh new file mode 100644 index 0000000..645ccbe --- /dev/null +++ b/doctests/run.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# +# Execute the blockchain-module doc-test end-to-end and regenerate its Markdown. +# +# There is one spec: +# blockchain-module-runtime.test.yaml — packages this module as an .lgx, +# installs it with lgpm, and drives it through a headless logoscore daemon. +# +# The runner is the shared `doctest` CLI +# (https://github.com/logos-co/logos-doctest), invoked directly via its flake. +# Each spec runs into ./outputs/ via --output-dir; `doctest generate` renders the +# .md; `doctest clean` then strips build artifacts, keeping only the .md. +# +# To run against a local logos-doctest checkout instead of the published flake, +# set DOCTEST, e.g.: DOCTEST="nix run path:../../logos-doctest --" ./run.sh +# +set -euo pipefail + +# Run from this doctests/ directory regardless of where the script is invoked from. +cd "$(dirname "$0")" + +# The doctest CLI. Override by exporting DOCTEST (space-separated command). +read -r -a DOCTEST <<< "${DOCTEST:-nix run github:logos-co/logos-doctest --}" +OUTPUT_DIR="./outputs" + +# Build the doc-tests against THIS repo's current commit rather than the latest +# published flake. The spec pins `github:logos-blockchain/logos-blockchain-module{release}` +# to $COMMIT via --release-for, so the runtime spec packages exactly what's +# checked out here. Override by exporting COMMIT (e.g. a tag), or set COMMIT="" to +# fall back to latest master. +# +# Note: nix fetches the commit from the GitHub remote, so $COMMIT must be pushed +# to logos-blockchain/logos-blockchain-module. A local-only / uncommitted HEAD +# won't resolve; export COMMIT="" (or push first) in that case. +COMMIT="${COMMIT-$(git rev-parse HEAD)}" +RELEASE_FOR=() +if [ -n "${COMMIT}" ]; then + RELEASE_FOR=(--release-for "logos-blockchain-module=${COMMIT}") + echo "==> Pinning logos-blockchain-module to ${COMMIT}" +else + echo "==> COMMIT empty; building from latest logos-blockchain-module master" +fi + +echo "==> Clearing previous ${OUTPUT_DIR}/" +# A prior run copies module artifacts out of the read-only nix store, so the +# directories land read-only (r-x) too. `rm -rf` can't delete files inside a +# directory it can't write to, so restore write permission first. +if [ -e "${OUTPUT_DIR}" ]; then + chmod -R u+w "${OUTPUT_DIR}" 2>/dev/null || true +fi +rm -rf "${OUTPUT_DIR}" +mkdir -p "${OUTPUT_DIR}" + +# Run each spec into ./outputs/ separately. --output-dir is single-spec, but +# passing it once per spec keeps each spec's artifacts beside its generated .md. +for spec in *.test.yaml; do + name="$(basename "${spec%.test.yaml}")" + echo "==> Running ${spec} into ${OUTPUT_DIR}/" + # ${RELEASE_FOR[@]+...} guards the expansion so an empty array doesn't trip + # `set -u` on older bash (e.g. macOS's stock 3.2). + "${DOCTEST[@]}" run "${spec}" \ + --verbose \ + --continue-on-fail \ + ${RELEASE_FOR[@]+"${RELEASE_FOR[@]}"} \ + --output-dir "${OUTPUT_DIR}/" + + echo "==> Generating ${OUTPUT_DIR}/${name}.md" + "${DOCTEST[@]}" generate "${spec}" \ + ${RELEASE_FOR[@]+"${RELEASE_FOR[@]}"} \ + -o "${OUTPUT_DIR}/${name}.md" +done + +echo "==> Cleaning build artifacts from ${OUTPUT_DIR}/ (keeps .md)" +"${DOCTEST[@]}" clean "${OUTPUT_DIR}" --verbose + +echo "==> Done. Rendered docs are in ${OUTPUT_DIR}/"