Skip to content

Releases: BobClawblaw/blockyard

BlockYard 0.1.5

Choose a tag to compare

@BobClawblaw BobClawblaw released this 08 Oct 18:24

BlockYard now ships on Simple mode. A fresh install draws its 3D boards with the new Simple
renderer: the same cubes under the same camera, flat and still, painting a frame only when the data
or the pointer changes, so a monitor left open on a laptop or a wall costs the graphics card nothing.
There is a lot of visual fun to discover if you want to explore: Settings (the gear) ->
Appearance -> 3D renderer -> Software or WebGL brings back everything the 3D engine does --
the Galaxy, Formation, Sun and Earth skies, thirty-odd idle effects, blocks that fly and bounce into
place, neon, chrome and satin finishes, and the price board's black hole, supernova and ball
lightning. WebGL is the one to try on a good graphics card.

Changed

  • npm is retired; GitHub is the only release channel. The blockyard package on npm stops at
    0.1.3 and is deprecated there. package.json is now private, so npm publish refuses it. An npm
    install moves to a checkout without copying anything: node bin/blockyard.js start from the
    checkout reads the same ~/.blockyard (docs/INSTALL.md §2).
  • The Simple renderer ships as the default (Settings -> Appearance -> 3D renderer), with its slide between
    layouts off: a resting board paints no frames at all, on any graphics card. Software and WebGL are one click away.
    A saved settings file keeps whatever it chose, so an upgrade changes nothing for anyone who has saved settings.

Fixed

  • Quality flags no longer outlive their cause. Seen 2026-09-24 on a healthy mainnet bmc at
    93% of its sync, whose node page showed five warnings, none of them current:
    • rpc-timeouts counts failures in the last 10 minutes, not since the monitor started. 48
      refused connections from one three-minute node restart read as "the node is under load"
      three hours later.
    • tip-stale clears when the chain advances. bmc need not ever log "tip fresh again".
    • A block archive laid out out of height order (how a parallel download writes it) is an
      info fact, not a warning, and is no longer counted a second time as a check-problems
      problem. Real damage (a frame that will not read, a body that does not hash) still warns.
    • The four relay line shapes (relay legs, accepts and rejects, orphans, address gossip) are
      watched for silence only outside IBD, as the two download shapes are watched only inside it.
    • nettotals-zero waits until the node has been up two minutes with peers connected. A node
      that has just restarted has moved no bytes yet, and the flag blamed the build for it.
    • inbound-handshake-failing warns at 10 failures in 10 minutes and clears when the rate
      drops. It used to go up on the first failure and stay: 3 dropped handshakes in a node's
      first minutes of listening are what any listening node draws.

Added

  • A third renderer, Simple (Settings, Appearance, "3D renderer"; 2.5d in a saved settings file), for a machine whose graphics
    are not to be worked at all. The same cubes under the same oblique camera, flat: no sky, no idle
    effects, no flight between layouts, no shadows or finishes, the price line as one stroke. It
    paints one frame when the data or the pointer changes and nothing in between, so a resting board
    costs no frames a second on either the processor or the graphics card. Hover, click-through and
    a game's own overlay work as before. The page's own pulses (the live dot, the block rail and
    meter, the overdue warning) hold still with it: with every board parked, those alone kept the
    page compositing 60 frames a second on the graphics card. "Simple sky": Still (the default) is
    a star field drawn once and left alone, no galaxy, nothing turning; Slow keeps the board's sky and
    repaints it twice a second from a timer, about a fiftieth of the shipped sky's frames, so the
    stars drift and the Earth sky's day passes; Off is a plain dark panel. The Formation and the Sun
    stand down to the star field there. Measured on the live monitor, composited frames in ten
    seconds: Block space 600 with the shipped sky, 14 with the Simple sky off, 34 with it slow.
    Measured in headless Chromium at 2560x1300 under the shipped sky and effects, frames painted in
    thirty seconds at rest: 0, against 1,875 on Software (scripts/renderer-25d-check.mjs); whole
    pages of the live monitor, composited frames in ten seconds: Overview 600 -> 6.
  • Software and WebGL are much lighter on the graphics card. Three changes, each measured in
    Chromium on Block space at 1600x1000 CSS pixels and device scale 2, over 10 seconds:
    • The page's pulses tick instead of animating (Settings, Appearance, "Page pulses": Tick is
      the default, Smooth is the old continuous fade, Still holds them). The live dot, the block
      meter's edge and the overdue warning were endless CSS animations. Any endless animation makes
      the browser redraw the whole page at every refresh of the screen, 120 times a second on a
      ProMotion display. Now one attribute flips every 1.3 seconds. Software went from 1,206
      composited frames in 10 seconds to 426. Stepped CSS timing was tried first and did not help
      (1,202). The Simple renderer keeps its pulses still, as before.
    • WebGL draws at one pixel per CSS pixel (Settings, Appearance, "WebGL resolution": 1x is
      the default, with Full and ¾x). On a Retina screen that is a quarter of the pixels. The GPU
      process went from 33% busy to 21.5%, and its compositor from 25-31% to 17%. The glow setting
      made no measurable difference.
    • A frame-rate cap (Settings, Appearance, "Frame rate cap": 60 is the default, with 30 and
      Full). A moving board paints at most 60 frames a second instead of at every refresh. A resting
      sky still repaints at most 30 a second. Between paints the board now waits on a timer rather
      than asking for animation frames it will not paint, because every frame asked for is a frame
      composited. On a simulated 120 Hz display, a resting sky asked for 60 frames a second to paint
      30; now it asks for 30.
  • The Detailed board is Core's block, strictly ordered. It used to be the mempool sorted by each
    transaction's own fee rate and cut at 1,000,000 vB. That put a child paying for its parent (CPFP)
    where its own rate said, and left the parent out. On Core's pool on 2026-10-01, 316 of about 3,600
    transactions on the board would not be in Core's block, and 317 of Core's were missing. The board
    is now the block the "being built" card shows: Core's chunk fee rate, each package whole, and
    Core's own fill at the end of the block. On the same pool it was within 5 transactions and 0.06% in
    fees of the node's own getblocktemplate. Each square is ordered and coloured by the rate its
    package was taken at, and no square sits below one that pays more.
  • The block is assembled once per pool update, in about 60 ms instead of 260–400 ms. It stops as
    Core's block assembler does (after 1,000 misses in a row once the block is within 4,000 weight
    units of full), and its sort no longer uses localeCompare. The board and the "being built" card
    share the result.
  • The Detailed board's scale is fitted to within 0.2%. In strict order the only empty space is
    along the block's top edge. The 5% scale steps could give away up to 5% of the board there; a
    finer search now closes in on the smallest scale that fits.
  • scripts/manage-users.js reads a piped password. It treated every chunk on stdin as one
    keystroke, so a password piped in (echo "$PW" | node scripts/manage-users.js create alice)
    arrived as one chunk. The newline went into the password, the prompt never finished, and the
    script exited having done nothing. It now reads character by character, and the end of the input
    also ends the password. Found as a test that failed only under load.
  • Three tests no longer fail under load. Each was reproduced with every core busy and then
    fixed at the cause:
    • the user-management test was the piped-password bug above (6 of 12 failed, then 0 of 12);
    • the RPC lane test now asserts the rate ceiling against submission time, not the gap between
      two actual starts, which one late timer can shrink (3 of 16 failed, then 0 of 16);
    • the index-resume test's crash snapshot now retries when a file vanishes under the copy while
      the build's other worker renames it (1 of 16 failed, then 0 of 32).
  • Steady (5 s flight), a Refresh animation between Rapid (3 s) and Quick (10 s). On a found block
    over the live pool, with 2,243 blocks moving, it settled in 4.9 s.
  • Board order (Settings, Block space): Exact, the default, lays the board out afresh on
    every update, richest at the bottom, so the order is always the fee order and most blocks move
    each time. Stable keeps every block still waiting in its place and puts new ones in the free
    space: far fewer moves, but a rich newcomer can sit above cheaper blocks until the board is
    re-sorted. That was the behaviour earlier the same day; the operator saw the board out of order
    ("not accurately redraw in order of most expensive to least expensive").
  • Effects on the Detailed board cost 20-36% less. Three changes, each checked pixel for pixel
    against the old renderer:
    • The WebGL colour cache no longer thrashes. It held 8,192 colours and was cleared when full;
      a Detailed board has more than 11,000 distinct shades, so every frame parsed every colour again.
      It now keeps two generations instead.
    • A resting cube's faces are reused from frame to frame while an effect is not touching it.
    • A retained layer: while an effect only tints or lights some cubes, the resting board is kept
      (on the graphics card, or in Software's layer), and only the touched cubes are drawn over it.
      Effects that lift, hide, shrink or pull cubes, draw a ring into the floor, or make cubes
      see-through are still drawn whole, because a cop...
Read more

BlockYard 0.1.4

Choose a tag to compare

@BobClawblaw BobClawblaw released this 22 Sep 18:39

A fifth AI security audit, every finding fixed the same day — brutally honest and extremely
technical by request, with the DOS Diversions (the x86 interpreter and everything around it) read
hardest of all: it came back the best-defended part of the codebase (a provably sound memory
model, no eval/Function/WebAssembly anywhere in the client, a corrupted game binary contained
to its own worker), with one real gap, now closed. 1 HIGH, 4 MEDIUM, 10 LOW/informational, 15 in
total, all fixed against a regression test that failed first
(SECURITY-AUDIT-2026-09-22.md,
REMEDIATION-2026-09-22.md). The headline fix reaches past the
finding that prompted it: the RPC allowlist that keeps every wallet method off this monitor is now
enforced by the RPC transport itself, not only by the route that happens to check it first, so no
future code path can reach a wallet method by skipping a check. Two effect-timing bugs from 0.1.3's
WebGL work are fixed alongside it.

Security

  • The RPC transport itself refuses a wallet method with no explicit authorization, not only
    the console route in front of it. Verified against a real regtest node's wallet flow, not a mock.
  • The audit trail is hash-chained. Each entry's hash covers the one before it, so an entry
    edited or removed in place is detectable — npm run verify-audit checks it. Tamper-evident, not
    tamper-proof: there is no secret key, and the report says exactly what that does and does not
    cover.
  • Login gets the same cross-site protection every other state-changing route has. A cross-site
    form could previously log a visitor's browser into an attacker-chosen account.
  • A reverse-proxy documentation bug that would have let a client spoof the address the rate limits
    and the CIDR gate trust, once trustProxy was turned on following the (now-fixed) example.
  • manage-users.js passwd/disable revoke a user's existing sessions, matching the web routes.
  • SSE responses carry the same CSP and security headers every other response does.
  • The DOS Diversions' executable loaders validate header fields against the real file length
    instead of trusting them.
  • /games/* is rate-limited; Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy are
    sent; an IPv6 /64 can no longer rotate past the login throttle; a decoded backslash is refused in
    static file serving on every platform, not only where it would matter; a file descriptor leak and
    a missing bounds check in the address-index build; a dead exception clause in the RPC allowlist;
    a response-size cap on the pool-map refresh script.

Fixed

  • The pulsar wind effect's duration now follows its (much wider) span. 0.1.3's fix for effects
    blinking in and out on the Markets board widened the pulsar's crossing to fly from beyond one
    edge of the panel to beyond the other, without lengthening its time to match — so the same
    duration now covered several times the distance, and the passage rushed by. Its duration is
    derived from the actual span it has to cross, so a wide panel gets a proportionally longer
    passage instead of a faster one.
  • The black hole's orbit runs on the effect's own clock, not the page's. The candles/cubes it
    pulls in span were timed off the page's own uptime (performance.now() since load, unbounded,
    never reset) rather than the effect's elapsed time — invisible on a freshly loaded page, and
    increasingly wrong on a Kiosk display or any tab left open for hours, where the same brief
    capture ramp could whip everything round many extra turns before settling. Fixed to read elapsed
    time off the effect's own bounded progress.

1,468 tests, from 1,445.

BlockYard 0.1.3

Choose a tag to compare

@BobClawblaw BobClawblaw released this 22 Sep 00:48

BlockYard 0.1.3 — 2026-09-22

A release in two halves. The first is the skies and the renderer: every 3D board, sky and
effect now draws on either of two renderers -- Software, as before, or WebGL, the same
picture on the graphics card, many times faster on a big display, with a glow of its own -- and two
new skies stand behind the boards: the Formation, a galaxy assembling itself after the TNG50
film, and the Sun, our star as the Solar Dynamics Observatory sees it at 171 Å, built as a
model rather than a picture -- differential rotation, sunspots, three-dimensional coronal loops,
flares, filament eruptions and mass ejections after the observatory's own films, prominences,
coronal rain, an activity cycle. The block finishes were rebuilt (chrome is polished steel now,
satin brushed metal, neon real tubes), ball lightning throws real lightning, the supernova is NASA's
frame by frame, and the Markets page gained 1 h, 3 h and 12 h charts and a sixth exchange,
Gemini. The second half, prepared on 2026-09-19, is about the node BlockYard was built beside:
Bitcoin Machine Code -- an experimental, Core-compatible node in x86-64 assembly -- is named as
BlockYard's first-class companion; the monitor reads every line of its log (100% of every current
log, where it read 53% a release ago) and its RPC is measured against Core's. The RPC lane
keeps up to four calls in flight per node, a run of chart fixes came from watching a syncing
node beside a synced one, Wolfenstein 3D's menu takes keys again, and every shareware game
ships whole with its own terms. The administrative suite grew to M7 on the way and is excluded
from this release, as it is from every release. 1,445 tests, from 1,070.

Upgrading from 0.1.2

  • The RPC lane now keeps up to four calls in flight per node (rpc.maxInFlight, default 4,
    matching Bitcoin Core's four RPC threads). It does not ask any node for more calls a second.
    A node that serves one connection at a time should get "rpc": { "maxInFlight": 1 } on its own
    entry.
  • One more outbound host, only with market polling on: api.gemini.com, for the sixth exchange. The
    security notes list every host contacted.
  • Nothing to migrate. No setting was renamed, no file moved. The renderer ships on Software, as before;
    WebGL is a choice in Display settings, Appearance.
  • A fresh server starts at the shipped defaults. Since 0.1.0 a server with no settings file took whatever the
    first browser to reach it remembered from an earlier install -- which on a laptop reused for a fresh clone meant
    shadows, the metallic sheen and market polling all on. It starts at the defaults now (shadows off, simple cubes,
    sheen off, polling off), and nothing is uploaded until someone changes a setting. An install that already has
    config/blockyard.json is untouched; reset in Display settings puts it back to the defaults.
  • The Markets board ships under the Formation, in the bottom-left corner.

Added

  • A second renderer: WebGL. Settings, Appearance, 3D renderer: Software (the 2D canvas,
    as before, and still the default) or WebGL. Every 3D board, sky and effect draws on either:
    the viewer's drawing calls are implemented a second time on WebGL2 (public/js/gl2d.js), so an
    effect is written once and runs on both. The switch is live, and a browser without WebGL2, a
    shader that will not compile or a lost graphics context goes back to Software by itself, so a
    board is never blank. Checked in a real browser, the same frame on both renderers, for every
    sky and every effect on both boards (node scripts/gl-compare.mjs): mean difference under 2 in
    255 per channel. Scorched Yard's land and sky stay on Software: the game reads their pixels.
    WebGL also has a finish of its own, WebGL glow: neon lines, stars, sparks and white-hot cores
    throw real light (bloom), and wide faint glows lose their bands. The blocks never glow: their
    colour is the feerate.
  • The Formation, a fifth sky: a galaxy assembling itself on a loop, after the TNG50 film. On a
    graphics card it is the film's own main view -- the gas as a glowing density field, black to
    violet to magenta to orange, a pale spiral galaxy at the middle, satellites falling in -- and
    where there is no graphics card it is drawn as specks on the 2D canvas, in the same colours, in the
    same place, and as perpetual. Its galaxy can sit
    behind the board or in any corner (Formation centre), it has its own brightness and flow
    sliders beside Formation speed, and thirteen palettes (Formation colours): the film's own, and twelve
    measured to stay clear of the chart's green, red and yellow. It never loops and
    never fades: satellites keep falling in on new orbits, shed wakes and stir the gas they cross.
  • The Galaxy sky on WebGL also has light between its stars (a haze along the arms, a warm bulge), stars
    that are soft points with halos and diffraction spikes on the brightest, and distant galaxies that are
    smudges of light rather than stacked ellipses.
  • The Galaxy sky's nebulae are gas on WebGL: billowing clouds with filaments, hollows and a second hue,
    and ragged dust lanes that darken what is under them, where Software stacks translucent ellipses. And a
    Rotation speed slider for the spiral, from still to a turn in under a minute.
  • The lightning ball's lightning is lightning: channels that hold their shape while they slam on, re-strike
    and die, tortuous at every scale, forking into hair-thin branches, several alive at once, with a flare where
    each lands. On WebGL they glow. (It was a handful of zig-zags re-randomised every frame.)
  • The supernova on WebGL is after NASA's animation frame by frame: a smooth white-hot breakout with a cyan
    rim, opening into lavender and violet billows round a magenta heart, soft blue gas ahead of it: a continuous
    volume of gas where there were soft blobs. Ball
    lightning
    throws real bolts on both boards -- channels that hold their shape, fork and re-strike -- crackles with
    held tendrils, carries sparks in orbit, and on Markets flies in from beyond the screen's edge and out past the other.
    The grid-tracing ball is the Plasma ball now and throws none.
  • The Sun, a new sky (Settings, Sky): our sun as the Solar Dynamics Observatory sees it at 171 ångström, in
    gold -- the bright lace of the magnetic network over boiling plasma, turning faster at its equator than at its poles,
    darkened toward its limb, with streamers off it -- and active regions: white-hot footpoints with fans of coronal
    loops, carried round at their own latitude's rate, flaring, their loops standing off the limb; and filament
    eruptions
    , after the observatory's own films: a dark filament lights, lifts and is flung off as a red arch of
    plasma behind a bright front, ribbons and an arcade of loops light where it was, and the corona dims -- about half
    fail and fall back. Its loops are true three-dimensional arches (they foreshorten, cross, and stand tall on the limb);
    there are sunspots, red prominences that show as dark filaments on the disk, coronal rain, flares with their flash,
    diffraction cross and wave, helmet streamers and polar plumes, an activity-cycle slider from solar minimum to maximum,
    five other SDO channels as colour palettes, and a detail setting for slower graphics. Sliders for its size (from a small disk to its surface
    filling the whole panel as the background), brightness, rotation and position. The research and the plan are docs/PLAN-SUN-SKY.md.
  • Markets: 1 h, 3 h and 12 h charts, made of 1-, 5- and 15-minute candles (about sixty bars each) from the same
    five exchanges, on both the flat chart and the 3D board, with minute labels on the time axis. The server fetches a
    finer grain only while a chart that short is open (/api/markets?tf=), and stops ten minutes after the last ask.
  • The Kiosk follows the Markets page: the exchange and range chosen on Markets -- on this browser or any other --
    reach an open Kiosk within seconds, without a reload; its caption gives the chart's real span and bar size.
  • Gemini is the sixth exchange on Markets: its price, candles at every grain and its whole order book. With market
    polling on, the server now also contacts api.gemini.com (the security notes and the Settings text list every host).
  • The block finishes on WebGL: Chrome is new -- a room's strip lights in polished steel, soft bands running
    across the whole board, on both renderers -- and costs what a plain board does (it was twice that); Satin is
    rebuilt as brushed metal (a sheen across every face, machined bevels, a brushed grain on WebGL); Neon is one
    soft stroke an outline with a white core, a coloured tube and a halo (a live neon board 38.7 -> 9.2 ms a frame on an RTX 5090).
  • Show frame rate (Settings, Appearance): frames painted in the last second, the processor's
    milliseconds a frame and the renderer, top right of every 3D board.

Fixed (2026-09-22)

  • The Kiosk follows the Markets page. The exchange and range chosen on Markets -- on this browser or any
    other -- reach an open Kiosk within seconds, without a reload. Display settings were fetched once, at page load,
    so a Kiosk on a wall kept the old choice for days; every open page now re-reads them every fifteen seconds and
    when its tab comes back into view (never over a change it is still saving). The Kiosk's caption gives the chart's
    real span and bar size -- it counted candles as hours, so a 3 h chart of five-minute bars read "last 36 h".
  • The price was cut off on a short chart. The room kept beside the 3D candle board for the price tags was nine
    grid units; with sixty one-minute bars a unit is six pixels on a tablet and the current price ran off the panel.
    It is a share of the panel now, with the old nine units as the floor.
  • **An old...
Read more

BlockYard 0.1.2

Choose a tag to compare

@BobClawblaw BobClawblaw released this 17 Sep 04:41

A day of hardening, and a new game. The headline is the third AI security audit, run on
2026-09-16 over the whole codebase by four auditors in parallel, and every one of its findings
fixed the same day with a test that holds it: one high (a client that opened the live stream and
stopped reading was never dropped, so memory grew without bound), eight medium, seventeen low and
six informational. The address index learned to resume an interrupted build instead of starting
its hours-long read again, and is now checked in the suite against a second, deliberately naive
implementation; property tests over Bitcoin Core's file formats found and fixed three flaws in
reading undo data. The skies were made clear: every board picks the Galaxy or the Earth sky in
one place, and the Earth sky gained a warm sun with a halo, a moon that glows through its real
phases, and softer clouds. And under Diversions, Scorched Yard: Scorched Earth on the block
engine, with the manual's weapons and computer players who each wear their own colour and aim like
people, up to eight tanks, a cheat mode that draws
the firing solution, and wind that is a real fluid simulation flowing over the hills. The games now
load only when their page is first opened. 1,070 tests, from 945.

Upgrading from 0.1.0

  • Node.js 22.2 or newer is required (was 22.0): the resumable index build uses Node's built-in
    CRC-32.
  • With accounts off, the node connection form works only from the monitor's own machine
    (127.0.0.1). auth.openNodeConfigFromNetwork: true restores the old reach; with accounts on,
    an admin can use it from anywhere, as before.
  • Wallet RPCs are refused in the RPC console, reads included (getbalance, listunspent, …).
  • The shipped systemd unit is sandboxed. If you install it again, edit its ReadWritePaths=
    lines to your install's data and config directories, and add your address index directory.
  • The address index build refuses a directory that holds anything an index does not write, a
    symlink, or a home, working or blocks directory. Point addressIndex at a new or empty
    directory, or at an existing index.
  • Sky settings are migrated from the old global sky and per-board star switches to one Sky
    choice per board; each board keeps what it drew.
  • scripts/pool-map.js asks before replacing the pool map (--yes to skip), and
    scripts/setup.js takes the RPC password from --rpc-password-file or stdin; the plain
    --rpc-password still works, with a warning.

Security

  • The third audit's low and informational findings, fixed the same day (docs/SECURITY-AUDIT-2026-09-16.md, tests in test/audit-2026-09-16-low-*.test.js), so every finding of that audit is closed. Browser: the post-sign-in redirect stays on this site; the page's key-value lists escape every value unless it is markup the page built, and the server passes the node's chain name, pruned and IBD flags and unbroadcast count on only in their own types; RPC URLs shown to viewers lose any username and password; viewers see the last two parts of cookie, log and settings paths; a malformed explorer link opens the explorer's home. HTTP: a malformed escape in a route is a 400; the sign-in lockout is per username and address, so one address can no longer lock an account for everyone (an address trying many accounts, or an account tried from many addresses, still locks at a higher count). Collectors: a coinbase with a truncated push no longer throws or stalls pool attribution; an order book far from the others is not drawn, the depth grid is capped and market responses are size-capped with redirects refused; long log lines can no longer take seconds to parse. Index: the height table is sized from the tip and cannot loop; a transaction whose input count disagrees with its undo record fails the build; a lock file stops two builds sharing a directory; files are created owner-only and temporary files are never written through a symlink; --workers is validated; malformed self-written files are cut or skipped rather than crashing. Scripts and supply chain: setup.js no longer echoes the RPC password and takes it from a file or stdin; pool-map.js refuses non-HTTPS redirects and shows a diff before replacing the map; CI actions are pinned to commit hashes; config/*.bak-* is ignored; the game files have a hash manifest. SECURITY.md names 0.1.x as supported, and the smoke script's open-access instance now really opens access
  • The third audit's high and medium findings, fixed the same day (docs/SECURITY-AUDIT-2026-09-16.md, tests in test/audit-2026-09-16.test.js). Stalled stream readers: a client whose socket is full is sent nothing more until it drains (the newest snapshot waits, as the stream's comments always claimed), and is dropped at 4 MB buffered or after a minute blocked; one address or account holds at most 16 streams. Open mode and scripts: with accounts off, the node connection form (save and test) answers only a caller on this machine's loopback address, since a browser check cannot stop a script; auth.openNodeConfigFromNetwork restores the old reach. A save that moves the node to another host drops the old endpoint's rpcUser, rpcPassword and cookieFile, and testing a foreign endpoint reports the kind of failure, never what it answered. The index build refuses a symlink, the filesystem root, a home or working directory, the node's blocks directory, or a directory holding anything an index does not write, and only ever removes its own files. Wallet RPCs are all refused by name, reads included. Request bodies must arrive within 30 seconds; the event stream is unaffected. Audit rows clamp every string to 1,024 characters. The npm package no longer includes the gitignored private notes, and a test holds it to tracked files. The shipped systemd unit is sandboxed (ProtectSystem=strict with ReadWritePaths for data/ and config/, ProtectHome=read-only, no capabilities, a system-call filter, UMask=0077), and the app was started and exercised under exactly those settings
  • Third AI security audit (docs/SECURITY-AUDIT-2026-09-16.md), every finding fixed the same day (above). One high: a client that opens the live event stream and stops reading is never dropped, and 400 such connections took a test instance from 73 MB to 1.5 GB in three minutes. Eight medium: in open mode a script (not a browser) can rewrite the node connection and receive the node cookie after a restart, and can use the connection probe to read internal URLs; the address index build deletes its output directory recursively without checking it; the RPC console admits listdescriptors and gethdkeys, which return private keys from an unlocked wallet; request bodies have no read deadline; oversized RPC method names flush the audit trail; the npm package includes the gitignored private notes; the shipped systemd unit is barely sandboxed. Seventeen low and six informational. Both earlier audits' fixes re-verified

Address index

  • An interrupted address index build resumes. Stopping BlockYard, Ctrl-C in the installer, a crash or a reboot used to throw away the whole build, which reads about 880 GB and runs for hours on a shared disk. The build now keeps a small journal beside its output, written atomically with a checksum of its own contents, and the next start carries on from it: block files already scanned are not read again, and buckets already sorted are not sorted again. Nothing it cannot prove finished is used: the bucket files are cut back to the length the journal recorded, so rows from a file that was half appended are gone, and a bucket about to be sorted is checked against the length and CRC-32 the scan recorded. The resumed index is byte for byte the one an uninterrupted build writes, which the tests check after stops at seven points, from the directory the stopped build left and from a copy taken at the instant of the stop. A journal for another index format or file selection, for a tip the node's chain no longer has, or that is torn or damaged is thrown away, the log says why, and the build starts over. The manifest is still written last. The progress and its ETA pick up from the resumed position. Also fixed on the way: a worker that died between two jobs left the pool waiting for it forever, and when two things went wrong at once the build reported the second rather than the cause
  • Fixed: three flaws in reading Bitcoin Core's undo files, found by new property tests. The suite now generates thousands of seeded encodings (CompactSizes, Core VARINTs, compressed amounts, undo records, legacy and segwit transactions, blocks, block and undo files), checks each decoder against an oracle written from Core's format, and truncates and flips bytes to hold every decoder to throwing or decoding exactly. An output amount above about 10 million BTC was rounded when read, and is now exact. An undo record cut inside its last script was accepted as whole, and is now refused. A corrupt VARINT could decode as an enormous height, and is now refused past Core's own 32-bit limit. No real chain data reaches the first flaw
  • The address index is checked automatically against a second implementation. Until now its correctness was checked by hand, with scripts against a live node, and history had no independent check at all. The suite now builds a seeded synthetic chain (301 blocks, 6,346 transactions, 384 addresses across eight script types, XOR-obfuscated block and undo files with a stale block and reordered, duplicated records), indexes it with the real build, and compares every address's balance and whole history, walked through the store's paged queries past the counting limit, with a deliberately naive replay of the UTXO set that shares no indexing code. It compares again after the live follower folds and merges sixty more blocks, after a reorganisation inside the tail ...
Read more

BlockYard 0.1.0

Choose a tag to compare

@BobClawblaw BobClawblaw released this 16 Sep 01:00

The first minor release, two days after the initial 0.0.9, and everything since it in one place:
the 0.0.10 that was prepared on 2026-09-15 was never published, and its changes ship here. Four
things shaped it. The first outside review of 0.0.9 on bitcointalk, which called out a monitor
that bound every interface with no sign-in: 0.1.0 ships hardened — this machine only, sign-in on,
HTTPS with a certificate it makes itself, and no outbound connection at all until the exchange
feed is switched on. A day of use on a laptop screen, which found every tab a scroll: every
page now packs to one screen at 1093×945, and the Mining tab follows mempool.space's dashboard
with its View more panels. Appearance: the layout's colours are a setting — light or
dark, five themes, or nine colours of your own. And the DOS Diversions: Wolfenstein 3D, DOOM
and Quake, unmodified shareware on a PC emulated in the browser, written here with no
dependencies, because "can it run DOOM?" deserved a straight answer. 944 tests, from 862.

Upgrading from 0.0.9: docs/INSTALL.md §11 — the first start after
the update serves HTTPS, creates the admin account and prints its password once.

Security

  • Hardened defaults (after the first outside review of 0.0.9): the monitor binds 127.0.0.1
    and requires sign-in out of the box — the first start creates the admin account and prints its
    password once (or takes BLOCKYARD_ADMIN_PASSWORD). Reach it from elsewhere over an SSH tunnel,
    or bind a LAN address with BLOCKYARD_BIND / server.hosts; open read-only access is a choice
    (BLOCKYARD_AUTH=0), announced at boot. The boot banner says how to reach a loopback-only
    monitor. Existing installs keep whatever config/local.json says.
  • HTTPS by default. Every listener serves HTTPS; with no certificate named, the first start
    makes the monitor its own self-signed one under <data>/tls/ — written without dependencies
    (server/tls/selfsigned.js: a v3 X.509 certificate assembled in DER and signed with ECDSA P-256
    over SHA-256), naming the bound hosts, this machine's addresses and hostname — and remakes it
    near expiry or when a bound address is missing from it. blockyard tls remakes it by hand
    (--san to add names, --print to export it). Your own certificate still replaces it;
    BLOCKYARD_TLS=0 serves plain HTTP behind a proxy. The installer's summary and port probe know
    both schemes.
  • The installer asks twice before a non-loopback bind. A LAN or wildcard address is written
    only when the operator types it and confirms it; the default it offers is 127.0.0.1.
  • Market polling is off by default — a new checkbox, Display settings → Markets & Price →
    Enable market polling
    , shared by every screen. A fresh install makes no outbound connection
    but to the node: the Markets and Kiosk tabs say that polling is off and where the switch is,
    Overview's price line stays hidden and the explorer shows no dollar figures. Ticking it takes
    effect at once; unticking parks the feed at once. BLOCKYARD_MARKETS=0 still removes the feed
    so that the checkbox cannot turn it on. Existing installs tick the box once.

Appearance

  • A first tab in Display settings for the colours of the layout. A theme mode — Light, Dark,
    or System, which follows the operating system and changes with it — and theme cards with swatch
    strips of the face the mode picks: BlockYard (the shipped look, the default; its dark face
    is value for value what the monitor drew before), Mono (clean grayscale), Nous (GitHub's
    chrome with a Nous blue accent), GitHub (Light Default and Dark Default) and Catppuccin
    (Latte and Mocha). Custom is nine colour pickers — page, panels, text, muted text, accent,
    lines, good, warning, bad — with the rest of a face derived from them (a raised panel, the
    softer rules, the fainter text, the darker accent of a pressed button), and whether Custom is
    light or dark read off its page colour; customise copies the theme on screen into
    the pickers so a custom scheme starts from a look. Saved on the server with the other settings,
    so every screen of the monitor draws the same; the sign-in page draws in the theme the browser
    last saw.
  • The theme is applied through the CSSOM (the page's custom properties rewritten on <html>),
    which the content-security policy permits where a style attribute is refused. The charts' axis
    text, grid lines and tooltips, the order-book depth chart's two sides and the market charts'
    grounds follow the theme — they were literals of the dark look, and a light theme would have
    drawn grey-on-grey. The 3D boards stay space, and the Explorer's block and transaction pages
    keep their own dark cards.

Mining

  • The Mining tab has the network row: reward stats over the last 144 blocks, the difficulty
    period (blocks remaining, the estimated and the previous change, the next halving), a week of
    pools with luck and counts, a year of hashrate with the difficulty's steps, and the last twelve
    adjustments as a table — every figure from this node alone (server/collect/network.js:
    getblockstats for the reward window, block headers for the periods and a daily sample, the
    week's coinbases read in the background at the lowest RPC priority).
  • mempool.space's layout: two columns in its order (reward stats, difficulty adjustment;
    pools, hashrate; recent blocks, adjustments) under the block flow, our own panels beneath, and
    View more » on four cards opening that card full screen — the whole week of pools as a
    table, the year's chart large, the whole attributed window of blocks, a year of adjustments.
  • The pool chart is a labelled pie: every pool named beside its slice and joined to it by a
    leader in the slice's colour, the small ones stacked, slices under half a percent gathered as
    "Other" — and the "Other" threshold rises until every label fits the card, so no name runs off
    the bottom at any window height. The pie and the year chart size themselves to the window.
  • Dollar figures on the reward stats while market polling is on, from a new /api/price that
    answers the cached spot price and never starts the exchange polling itself.
  • The Mempool space viewer left this page — it is on Overview, Block space and Mempool — and with
    it the full-pool poll the page used to ask for. The reward window is exactly 144 blocks.
  • At a narrow window the block flow spans the width and is thinner, and the packages table scrolls
    inside its card instead of bleeding into the neighbour; the three stacks become two below 1400px.
  • A streak of stale drops of the full-pool poll is one story: one warning event when it
    starts, a counter on Node & RPC while it lasts, one event when the poll answers again with the
    count and the span. On a day the node answered slowly for thirteen hours, each drop had been its
    own event, 188 of the feed's 200 rows.

Every tab on one screen

Measured at 1093×945 and 1600×1000 with a dead-height probe, page by page:

  • Globally: tighter card padding and heading spacing, and a short card beside a tall one keeps
    its own height instead of being stretched to match (Chain & Sync's drill-down card had a
    thousand pixels of nothing under its input; Node & RPC's poll cadence and connection cards
    hundreds).
  • Overview: the block flow card a quarter shorter, and the Block space board capped to the
    viewport (it set the whole right column's height), so the events are on the first screen.
  • Block space: the card tighter and the board sized so the whole page, note included, fits the
    viewport without a scroll.
  • Markets: the board takes what the window leaves, and the exchange table and the order-book
    depth sit side by side beneath it (under 1400px the table drops the pair, 24 h range and volume
    columns; all still in the summary line and on the Kiosk) instead of the depth chart starting a
    thousand pixels down.
  • Kiosk: tighter heads and gaps, the price panel's padding trimmed, the Block flow strip a
    quarter shorter, and the full-screen button in that strip's head instead of floating over its
    cards.
  • Mempool: keeps its two columns down to 900px (it stacked from 1100), and the room over goes
    to its time-series charts, a fifth taller.
  • Explorer: a smaller title, the block strip's padding and scrollbar band trimmed, tighter
    table rows and the table reaching the window's bottom edge — three more blocks on the first
    screen.
  • Peers: a long onion or i2p address no longer makes the whole page scroll sideways (it clips
    with an ellipsis and carries the full text as its title; a wide table scrolls inside its card),
    rows and service badges tighter, the top strip's cards at their own heights.
  • Network: Throughput beside a stack of Upload and Traffic accounting, the throughput chart
    taller, and the sources table taking the rest of the window.
  • Events: the feed reaches the window's bottom edge (it stopped a hundred pixels short).
  • Chain & Sync: one-line notes under the block-size and tip charts (the longer sentences are
    their tooltips), the transaction-rate figures as one wrapping line, values that keep to their
    line, and a shorter uptime label — the first three rows sit level and the drill-down and indexes
    reach the first screen.
  • Node & RPC: two stacks (RPC etiquette with its figures in two columns, Data quality and Node
    connection; Poll cadence, self-telemetry beside the log tail, Panel sources and the RPC
    console) instead of grid rows that left holes beside the short cards.
  • Admin: two stacks (Users over the audit log; Node actions over Change own password, whose
    two fields share a row). Sign-in and 404: the same tighter card.
  • Display settings: rows, note and groups tighter, the sheet a little wider so hints wrap
    less, and on t...
Read more

BlockYard 0.0.9

Choose a tag to compare

@BobClawblaw BobClawblaw released this 14 Sep 20:10

The initial release. Everything in it, like everything before it, was written by an AI directed by a human
operator, and audited by AI (docs/SECURITY-AUDIT.md, docs/SECURITY-AUDIT-2026-09-14.md). It is
experimental pre-release software. BlockYard runs on the machine that runs Bitcoin Core (25.0 or
later), because the explorer's address index is built from the node's own block files; a node on
another machine, and the experimental node that earlier measurements were taken on, are not supported.
862 unit tests, no dependencies. Tagged v0.0.9. The address index takes a few hours to build on first start (about two on four workers on NVMe); every other page works meanwhile.

The 3D boards' effects, per board

  • Each 3D board has its own effects list. The one Effects tab is now two: Space effects
    (the Block space board's 28 switches — every effect but the two drawn on a price line) and
    Market effects (the Markets board's 12: ripple, outline sweep, tide, cascade, twinkle,
    scan line, fireworks, solar flare, wave, energy pulse, pipe bulge, ball lightning — the ones
    that translate to a candle chart; ball lightning is half its Block space size there). Each has its own no-repeat window and its own
    all on / all off, which the no-repeat slider had silently taken away. A saved settings store is
    migrated (schema 4): the Markets list starts as a copy of the list there was.
  • Each effects tab sets its board's cadence. Between effects, at least / at most
    (seconds, 5–9 by default, up to ten minutes on Space effects and five on Market effects) on both tabs, and First effect after landing
    (1.2 s by default, up to two minutes) on Space effects only — the candle board has no landing
    — drive the scheduler's own timers, per board.
    The no-repeat slider on each tab tops out at that list's length.
  • Nothing on the Markets board waits its turn. The pulse, the bulge and ball lightning were
    held to a clock of 2.5-6 minutes between plays; they are ordinary picks now, and the list you
    leave on decides how often any one of them comes round.
  • Everything on the Markets board moves along the hours — left or right, never toward the
    viewer — and lights the candles or the line: fronts (outline, scan, tide, wave) run along the
    chart, rings (ripple, shockwave, nova) start on the candle row, the light cycles ride in from
    the two ends with their walls on the candle tops, the lightning ball rides the price line, and
    ball lightning flies through the chart's own height. Candle faces light under an effect now
    (the camera is low, and a glow painted on a candle's top alone was invisible).
  • Ball lightning strikes candles. cellTops began each tile at its own fractional x and y,
    which on the candle board (tiles between grid lines) stored nothing, so every cell top was
    zero and no arc found a target. Half the arcs now chain on from the struck block to
    another in electric green, the instant they land, and half of those on to a third block.
  • The lightning ball is one pale gradient with a white-hot heart, and a pale burn behind it,
    instead of five stacked discs of deepening blue that read as a dark blot.

The explorer's address history, from an index of our own

  • Address pages show full history and balances on Bitcoin Core. This was the explorer's one
    real gap, and it was not a bug: Core has no address index at any setting. getaddressbalance
    and getaddresstxids are insight-style extensions carried by forks, and stock Core answers
    Method not found (measured 2026-09-13 against two Core nodes). mempool.space
    shows the same address's history only because electrs builds that index from the block files
    itself. So this monitor now does the same, in a form it can afford: server/chain/.
  • Reading the node's own files, not asking the node. blk*.dat on a current Core is
    XOR-obfuscated at rest (-blocksxor, default since v28, the key in blocks/xor.dat) -- the
    first spike concluded "not a Core datadir" and was wrong. De-obfuscated, server/chain/blockfile.js
    frames the records; server/chain/tx.js decodes transactions and blocks, checked
    field-for-field against getblock <hash> 3 (91,813 spent coins in 16 sampled file pairs, 0
    mismatches); and the undo (rev*.dat) records supply each spent coin's script and amount,
    which is how the spending side is known without replaying the UTXO set. Pairing blocks with
    undo records by trying every candidate was quadratic on the tiny early blocks (file 0: 35 minutes
    and not finished); Core appends undo in connection order, so blocks are put in chain order by
    their previous-block links and walked in step -- file 0 pairs in 5.9 s.
  • A lean row. One 21-byte row per (script, transaction that touched it): 8 bytes of
    sha256(script), the height, the position in the block, and the net satoshis the transaction
    moved for that script -- so a balance is a sum, with no node call. A script paid and spent in the
    same transaction is one row (18% fewer). The full decoder builds Core's verbose shape and was 5.9
    of the 9.5 single-core hours measured for the chain; the index needs only each output's value and
    script bytes, so server/chain/index/rows.js walks the raw transaction itself and is checked
    row-for-row against rows from the full decoder.
  • Flat sorted files, no database. Measured against node:sqlite on three real file pairs
    (docs/MEASUREMENTS.md §29): 21 bytes per row against 24-27, a 5.5 M rows/s sort against a
    1.4 M rows/s key-ordered load, and no B-tree collapse once the keys outgrow memory. 256 sorted
    segments by key prefix with a sparse index (one key per 4,096 rows) held in memory; a lookup
    binary-searches the sparse keys and reads the one 86 KB block that can hold its key.
  • Built and measured (§30): node scripts/index-build.js --out <dir> --workers 16 read all
    5,756 file pairs in 29 min 45 s (7.8 CPU-hours across 16 workers) and wrote
    5,890,519,289 rows, 123.7 GB -- every height present exactly once, two stale blocks skipped,
    and within 1.5% of the projection. 40 of 40 balances equal scantxoutset at the same
    height, to the satoshi; every (script, transaction) pair of four whole blocks from 2009 to the
    tip found at its height and position with its amount. Lookups: 0.25 ms median first touch,
    0.03 ms warm; a 2.3 M-transaction address's whole history summed for its balance in 83 ms.
    For scale: scantxoutset, the only thing Core offers, took 26.5 s for one scan of 40 addresses
    holding the node's RPC thread, and answers only the current balance, never a history.
  • It follows the chain. server/chain/index/live.js, started by the server for each configured
    index directory, polls every 30 s, fetches each new block with getblock <hash> 3 over RPC (so
    the files are read once, at the build), and logs the rows before serving them in a
    CRC-framed live.log that is replayed on restart and drops a record torn by a crash. A
    reorganisation rolls the tail back to the fork; blocks 100 deep are folded into immutable
    sorted layers and layers past 32 are merged; a reorganisation below what is folded stops the
    follower and the page says to rebuild. Checked live: 12 blocks caught up in 6.5 s, 40 of 40
    balances then equal to scantxoutset, and on release day the index reached a new block 16 s
    after the node did. The rows it derives through RPC agree row-for-row with the rows built from
    the files (test/chain-index-live.test.js).
  • The address page reads it. addressIndex in a node's config names the directory (one index
    serves every node on the same chain); the page shows the transaction count, balance, total
    received and sent, and the transactions newest first with the net change each made, 25 a
    page, deep pages costing no more than the first. The reply carries index.tip, index.behind,
    index.following and index.stale, and the page says when the index is behind the node or has
    stopped following. Checked live: every row of four pages, including page 4 of a 2.3
    M-transaction address, matched the node's decoded transaction for txid, height and amount.
  • Without an index, the page is honest rather than empty. A node's refusal used to become [],
    then txCount: 0, then "no transactions in this node's address index" -- a fabricated zero
    indistinguishable from an unused address. The reply now carries indexed: false with a null
    count, the page says the index is absent, and the dead RPCs are not re-sent on every view: a
    "method not found" is remembered per node for ten minutes, then asked again, because the daemon
    behind a node id can change.
  • A rebuild in place is safe. buildIndex empties its output directory first, so a follower's
    live.log and layers -- holding the reorganised-away blocks -- cannot survive into a new index and
    a fresh follower catches up on the chain as it is (test/chain-index-live.test.js). Stop the
    server, run the same command, start it; the advice to build into a fresh directory is withdrawn.
  • A missing address index is built by BlockYard itself, in the background (operator: "Is it
    possible to run step 6 in the background, and have a status notification in blockyard when the
    index process is finished?"). On start, an addressIndex directory with no index in it is built
    on worker threads while every page keeps serving; the progress is a quality flag on the Overview
    and a line on the address page (phase, files done, rows so far, time left); an event marks the
    start, the finish and a failure, and the browser toasts it; when it finishes the follower starts
    and address pages work with no restart. addressIndexBuild: "manual" keeps it from happening.
    The installer's step 6 offers background (the default), here, or later.
  • The build shares the machine with the node, and behaves like it. Every...
Read more