mirror of
https://github.com/logos-blockchain/logos-blockchain-module.git
synced 2026-07-13 10:39:32 +00:00
add doctests
This commit is contained in:
parent
fc1439cf16
commit
bff887b060
238
.github/workflows/doctests.yml
vendored
Normal file
238
.github/workflows/doctests.yml
vendored
Normal file
@ -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://<owner>.github.io/<repo>/pr-<N>/<os>/ (pull requests)
|
||||
# https://<owner>.github.io/<repo>/main/<os>/ (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/<sha>`. 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 `.../<sha>` — 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 "<h1>No report produced</h1>" > 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>/...
|
||||
|
||||
- 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 "<!doctype html><meta charset=utf-8>"
|
||||
echo "<title>blockchain-module doc-test reports — $BASE</title>"
|
||||
echo "<style>body{font:16px system-ui;margin:40px;max-width:640px}a{color:#2563eb}</style>"
|
||||
echo "<h1>blockchain-module doc-test reports</h1>"
|
||||
echo "<p><strong>$BASE</strong> · commit <code>${GITHUB_SHA::7}</code></p><ul>"
|
||||
for os in ubuntu-latest macos-latest; do
|
||||
if [ -d "site/$BASE/$os" ]; then
|
||||
echo "<li><a href=\"./$os/\">$os</a></li>"
|
||||
fi
|
||||
done
|
||||
echo "</ul>"
|
||||
} > "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 = "<!-- blockchain-doctest-report-links -->";
|
||||
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 });
|
||||
}
|
||||
310
doctests/blockchain-module-runtime.test.yaml
Normal file
310
doctests/blockchain-module-runtime.test.yaml
Normal file
@ -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 <address-hex>"
|
||||
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 <header-id-hex>"
|
||||
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"'
|
||||
338
doctests/outputs/blockchain-module-runtime.md
Normal file
338
doctests/outputs/blockchain-module-runtime.md
Normal file
@ -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 <address-hex>
|
||||
```
|
||||
|
||||
### 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 <header-id-hex>
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
76
doctests/run.sh
Normal file
76
doctests/run.sh
Normal file
@ -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}/"
|
||||
Loading…
x
Reference in New Issue
Block a user