Releases: bsv-blockchain/teranode
Release list
v0.15.9-beta-2
What's Changed
- Re-extend transactions from the UTXO store instead of trusting supplied previous outputs by @icellan
- fix(security): backport checkpoint ancestry validation to v0.15 by @icellan in #1723
- test: fix CI fixtures after transaction re-extension by @icellan in #1722
- fix: reassignment test coverage and legacy shutdown by @icellan in #1726
Also in v0.15.9, from beta-1:
- fix(utxo): drop O(N^2) conflicting-descendant re-walk wedging block validation by @oskarszoon in #1394
- ci: honor GITHUB_REF and prefer stable tag when multiple tags point at HEAD by @oskarszoon in #1370
- fix(utxo): do not skip parent markers via cuckoo filter by @icellan in #1705
Full Changelog: v0.15.9-beta-1...v0.15.9-beta-2
v0.15.9-beta-1
What's Changed
- fix(utxo): drop O(N^2) conflicting-descendant re-walk wedging block validation by @oskarszoon in #1394 (backport of #1393)
- ci: honor GITHUB_REF and prefer stable tag when multiple tags point at HEAD by @oskarszoon in #1370
- fix(utxo): do not skip parent markers via cuckoo filter by @icellan in #1705
Notes
#1370 changes how CI stamps the version into the binary. Previously, when a commit carried
both a stable and a -beta-N tag, the reported version depended on tag order rather than the
tag actually being built — v0.15.8 reports itself as v0.15.8-beta-2 for this reason. Builds
from this tag onward take the version from the pushed tag.
#1705 disables the pruner's PrunedTxSet parent-update skip. The set is a cuckoo filter, and
a false positive suppressed the parent's deletedChildren marker on a live parent, allowing a
replay of a pruned child to recreate it unmined. Every parent now gets the marker write. See
#1701 for the remaining non-atomic path, which this does not address.
Full Changelog: v0.15.8...v0.15.9-beta-1
v0.16.0-beta-12
Notable changes
Committed secrets removed — startup is unaffected
Neither change blocks node startup, and neither affects the .operator or .docker.m contexts most operators run.
- gRPC admin API key (#1575) —
grpc_admin_api_key = testkeyis gone fromsettings.conf. A well-known placeholder is now logged at Error and ignored; the server generates a random key instead, so the protected admin RPCs (peer ban/unban, connect) are fail-closed until a real secret is supplied via thegrpc_admin_api_keyenvironment variable or a secret store. The node starts either way — only operators who actively use the gRPC admin RPCs need to act. The docker e2e stacks inject their shared test key via composeenvironment:blocks. - p2p identity keys (#1578) — removed:
p2p_private_key.operator.mainnet.stage.1(a.stage.1context), the globalalert_p2p_private_key, and the already-commented-outtx_blaster_p2p_peer_id/tx_blaster_p2p_private_keypair. p2p identity keys already auto-generate and persist top2p.keywhen unset, and the alert service generates its own when the setting is empty, so this is a no-op for normal deployments. Thep2p_private_key.docker.ss.teranode1public test fixture is kept. Asettingstest now guards against recommitting real keys.
Default / behaviour changes
maxscriptsizepolicydefault raised to 100MB (#1602).- Block catchup now starts at startup instead of waiting minutes to begin (#1135).
- Compose multinode Postgres roles provisioned
NOSUPERUSER(#1546) —compose/postgres/init.sqland thegennodestemplate only, i.e. the dev/multinode compose stack, not operator Postgres.init.sqlruns on first provisioning, so existing volumes keep their superuser roles unless you runALTER ROLE <role> NOSUPERUSER;yourself.
Highlights
Large-block correctness (64-bit overflow family) — #1484 (block tx count in 64-bit, was retrying forever), #1482 (BUMP leaf offsets in 64-bit so large-block SPV proofs cannot silently wrap).
Consensus / timestamp rules — #1481 (median-time-past evaluated against the 11 headers ending at the block's parent), #1466 (mining candidate timestamp floored at MTP+1 so block assembly never hands miners a block its own rule rejects), #1556 (quick-validation checkpoint height bound to what was actually verified this run).
Aerospike UTXO integrity — #1494 (fail a torn extra record instead of reading its outputs as unspent), #1491 (re-hash a parent tx against the outpoint it was fetched under, never cache a mismatched read), #1518 (decide transaction creation from the master record, fence the creation lock), #1510 (retry a parent still committing, stop retrying a held output, make both stores agree).
Wedge / tip-recovery — #1292 (detect and recover coinbase divergence to prevent tip wedge), #1577 (stop released blocks poisoning revalidation), #1515 (measure fork depth from the accepted chain, not a lagging counter), #1520 + #1647 (make a stalled block-assembly intake consumer visible).
p2p hardening (Goku's sweep) — websocket DoS caps and deadlines (#1380), non-blocking notification fan-out with drop metrics (#1651), GossipSub peer scoring + refuse unscored PX records (#1598), bound peer-controlled gossip strings (#1545) and escape them in the dashboard (#1531), re-validate resolved IPs for peer-supplied URLs (#1369), cap peer-attribution maps at insert (#1488), no IP bans from relay-circuit multiaddrs (#1612), http.Server timeouts (#1544), per-FSM-state outbound publish gate (#1389).
Legacy peer management — #1601 (stop the node starving itself of outbound peers), #1632 (probe unused addresses so there are good ones to dial when a peer drops), #1554 (restore plain-inventory fallback for non-sendheaders peers), #1560 (disconnect peer on outbound write error, clamp advertised block size), #1557 (honour --nobanning).
What's Changed
- fix(utxo/sql): honour BlockHeights and SubtreeIdxs when asked for on their own by @freemans13 in #1397
- feat(aerospike): configurable write CommitLevel, relax pruner deletes to COMMIT_MASTER by @icellan in #1508
- fix(model): carry the block transaction count in 64-bit so a huge block validates instead of retrying forever by @freemans13 in #1484
- fix(blockvalidation): bound the detached subtree_data fetch by @liam in #1526
- fix(blockassembly): floor the mining-candidate timestamp at median-time-past+1 so block assembly never hands miners a block its own rule rejects by @freemans13 in #1466
- p2p: run peer health checks concurrently with caching and caller context by @gokutheengineer in #1347
- fix(p2p): stop blocking the websocket notification processor on per-client blockchain gRPC by @gokutheengineer in #1378
- fix(asset): recover per-goroutine in errgroup fan-outs, bound GetUTXOsByTxID by @icellan in #1383
- fix(bump): compute BUMP leaf offsets in 64-bit so large-block SPV proofs cannot silently wrap by @freemans13 in #1482
- fix(utxo/aerospike): fail a torn extra record instead of silently reading its outputs as unspent by @freemans13 in #1494
- feat(p2p): declarative per-FSM-state outbound publish gate by @gokutheengineer in #1389
- Escape peer-controlled gossip strings in the dashboard raw JSON view by @gokutheengineer in #1531
- fix(blockassembly): detect and recover coinbase divergence to prevent tip wedge by @freemans13 in #1292
- fix(p2p): cap the peer-attribution maps at insert so a distinct-hash gossip flood cannot balloon memory and sweep cost by @freemans13 in #1488
- fix(utxo): publish block height and median block time as one atomic snapshot by @freemans13 in #1485
- fix(p2p): set http.Server timeouts on the p2p HTTP server by @gokutheengineer in #1544
- fix(utxo/aerospike): decide transaction creation from the master record, and fence the creation lock by @freemans13 in #1518
- Remove stray grpcIngress from asset service in sample manifests by @icellan in #1548
- docs(grafana): document docker-base dashboards and cross-stack gaps by @icellan in #1549
- Remove dead build-tx-blaster Makefile target by @icellan in #1551
- docs: fix duplicated/out-of-sequence 4.1.x heading numbers in assetServer.md by @icellan in #1552
- docs: correct alerting claim in technology stack overview by @icellan in #1555
- fix(blockvalidation): bind quick-validation checkpoint height to what was actually verified this run by @icellan in #1556
- fix(legacy): honour --nobanning in addBanScore by @icellan in #1557
- docs(operator): add monitoring guide, link metrics reference, fix legacy CR resource snippet by @icellan in #1558
- docs(settings): drop stale unused-topic note from validatortxsConfig longdesc by @icellan in #1561
- fix(netsync): unskip four legacy netsync tests broken by incomplete fixtures by @icellan in #1562
- fix(daemon): remove stale -faucet help text by @icellan in #1571
- test: skip container-backed tests without Docker, fix a port-bind test flake, fix gotestsum PATH fallback by @icellan in #1574
- docs(plans): stop citing a design-plan file not present in the repo by @icellan in #1550
- fix(legacy): restore plain-inventory fallback for block relay to non-sendheaders peers by @icellan in #1554
- docs(k8s): explain why HPA can't safely target the propagation deployment by @icellan in #1573
- config(policy): raise maxscriptsizepolicy default to 100MB by @galt-tr in #1602
- fix(legacy): stop the node starving itself of outbound peers by @freemans13 in #1601
- fix(blockvalidation): stop released blocks poisoning revalidation, and fix the subtree-meta peer fetch URL by @freemans13 in #1577
- test(seeder): round-trip and complete-file coverage for UTXO snapshot import by @icellan in #1572
- fix(legacy): disconnect peer on outbound write error, clamp advertised block size to wire capacity by @icellan in #1560
- fix(postgres): provision teranode/miner roles as NOSUPERUSER by @icellan in #1546
- Fix service-inventory doc drift: add Pruner overlay-service section, correct P2P bootstrap description by @icellan in #1559
- fix(asset): correct GetBlock doc examples, register by-height block routes by @icellan in https://github.com/bsv-blockchain/teranode/...
v0.16.0-beta-11
What's Changed
- refactor(utxo): remove the unused BlockIDs field from utxo.Spend by @freemans13 in #1398
- fix(utxo): bound and dedupe conflicting-descendant walks wedging block validation by @icellan in #1393
- fix(blockvalidation): verify a served block is the one requested, bound peer error bodies by @liam in #1453
Full Changelog: v0.16.0-beta-10...v0.16.0-beta-11
v0.16.0-beta-10
What's Changed
- fix(blockvalidation): validate catchup header nBits against DAA target (#1147) by @ordishs in #1273
- fix(aerospike): guard batcher enqueue against Store.Close race by @ordishs in #1361
- feat(aerospike): add batch diagnostics helpers by @ordishs in #1362
- ci: honor GITHUB_REF and prefer stable tag when multiple tags point at HEAD by @oskarszoon in #1370
- chore: SonarQube shell test operator (S7688) + remaining S1192 constants by @liam in #1358
- fix(blockvalidation): survive a peer serving cached empty subtree_data, attribute catchup failures correctly by @oskarszoon in #1371
- p2p: enforce DataHub URL blacklist on block and node_status announcements by @gokutheengineer in #1297
- p2p: Implement p2p Client.GetPeers and surface peer decode errors by @gokutheengineer in #1348
- feat(aerospike): setting to disable client-side metrics under load by @ordishs in #1363
- fix(p2p): randomize sync-peer tiebreak to resist peer-ID grinding by @gokutheengineer in #1349
- fix(p2p): bound sync-coordinator RPCs and wire Stop into server shutdown by @gokutheengineer in #1310
- test: harden subtree-meta fixture and catchup mock expectations by @ordishs in #1364
- fix(p2p): key gossip peer maps by canonical hash, parse subtree hash before use by @gokutheengineer in #1367
- batch peer-registry updates and bound gossip handler concurrency in p2p by @gokutheengineer in #1350
- fix(asset): stop serving and caching an empty subtree_data as a valid 200 by @oskarszoon in #1377
- feat(aerospike): native operate-path for mod-teranode functions by @ordishs in #1365
- perf(aerospike/pruner): route pruner writes through the native-op builder by @ordishs in #1366
- fix(alert): key the blacklist UTXO hash on the requested txid, not the stored tx's by @icellan in #1386
- fix(blockvalidation): enforce checkpoint hash-match on the direct validate path by @liam in #1387
- fix(asset): stop serving transactions reconstructed from a UTXO-set snapshot by @icellan in #1384
Full Changelog: v0.16.0-beta-9...v0.16.0-beta-10
v0.16.0-beta-9
What's Changed
- fix(asset): back off centrifuge p2p dial, stop ERROR-spam when no peer present (#1334) by @oskarszoon in #1337
- fix(legacy): stop NOT_FOUND ERROR-spam cascade from a failed parent block (#1333) by @oskarszoon in #1336
- fix(errors): strip orphaned %w in errors.New*, sweep call sites, add CI guard (#1332) by @oskarszoon in #1335
- feat(teranode-cli): register rewindblockchain subcommand (#1341) by @oskarszoon in #1345
- fix(rewindblockchain): never move BlockPersisterHeight forward on rewind (#1340) by @oskarszoon in #1342
- chore: define constants for duplicated string literals (S1192) by @liam in #1331
- feat(utxo): replace Store.Spend/Create with a single SpendAndCreate by @icellan in #1326
- fix(rewindblockchain): open the subtree store with the node's hashPrefix (#1351) by @oskarszoon in #1352
- fix(p2p): wire invalid-blocks Kafka consumer exactly once by @gokutheengineer in #1307
Full Changelog: v0.16.0-beta-8...v0.16.0-beta-9
v0.15.8
What's Changed
Ships the recovery path for testnet / teratestnet nodes wedged by the pre-v0.15.7 OP_RETURN spend bug. Adds a teranode-cli rewindblockchain subcommand that rewinds the local blockchain past the bad block so the node can re-sync from there with v0.15.7's store-time fix in effect.
Identical to v0.15.8-beta-2.
teranode-cli rewindblockchain subcommand (backport #1345 / #1341)
Registers the destructive-rewind tool as a teranode-cli subcommand:
--target-height <h>— target height (default: readstate["BlockAssembler"])--dry-run— log actions, modify nothing--assume-yes— skip the interactive confirmation prompt--force-not-idle— proceed even if FSM is notIDLE(DANGEROUS)--force-deep— allow rewind deeper than 100 blocks (coinbase-maturity risk)--verify— run post-rewind consistency checks--concurrency <n>— subtree-load concurrency
Interactive confirmation tolerates piped input (printf y | teranode-cli ...) and reports a clearer error under non-interactive contexts. Positional-argument guard rejects the swallowed-flag invocation before any store is opened.
rewindblockchain never advances BlockPersisterHeight (backport #1342 / #1340)
The rewind tool now clamps state["BlockPersisterHeight"] against the blocks-table ground truth (lowest block with persisted_at IS NULL, minus one) and refuses to write a value that would move the persister forward.
rewindblockchain opens the subtree store with the node's hashPrefix (backport #1352 / #1351)
Reads ?hashPrefix= from the subtree store URL (default 2, matching daemon.GetSubtreeStore) and passes it into NewStore, so the rewind tool shares the node's exact blob path layout and its Del calls actually match what the node wrote.
- feat(teranode-cli): register rewindblockchain subcommand by @oskarszoon (backport of #1345 / #1341)
- fix(rewindblockchain): never move BlockPersisterHeight forward on rewind by @oskarszoon (backport of #1342 / #1340)
- fix(rewindblockchain): open the subtree store with the node's hashPrefix by @oskarszoon (backport of #1352 / #1351)
Full Changelog: v0.15.7...v0.15.8
v0.15.8-beta-2
What's Changed
Rolls up all of v0.15.8-beta-1 plus one additional rewind fix.
rewindblockchain opens the subtree store with the node's hashPrefix (backport #1352 / #1351)
The rewind tool was opening the subtree blob store without the node's configured hashPrefix, so blob paths derived by the tool did not match the paths the node writes. Any Del the tool issued against a hash-prefixed subtree store would miss its target and leave the blob in place, defeating the rewind's cleanup. Fixed by reading ?hashPrefix= from the subtree store URL (default 2, matching daemon.GetSubtreeStore) and passing it into NewStore, so the rewind tool now shares the node's exact path layout.
Everything from v0.15.8-beta-1
- feat(teranode-cli): register rewindblockchain subcommand (backport #1345 / #1341) — makes the destructive-rewind recovery tool available as a
teranode-clisubcommand with all flags, prompt handling, and a positional-argument guard. - fix(rewindblockchain): never move BlockPersisterHeight forward on rewind (backport #1342 / #1340) — clamps
state[BlockPersisterHeight]against the blocks-table ground truth so the rewind tool cannot accidentally fast-forward the persister.
- fix(rewindblockchain): open the subtree store with the node's hashPrefix by @oskarszoon (backport of #1352 / #1351)
- fix(rewindblockchain): never move BlockPersisterHeight forward on rewind by @oskarszoon (backport of #1342 / #1340)
- feat(teranode-cli): register rewindblockchain subcommand by @oskarszoon (backport of #1345 / #1341)
Full Changelog: v0.15.8-beta-1...v0.15.8-beta-2
v0.15.8-beta-1
What's Changed
Ships the recovery path for testnet / teratestnet nodes wedged by the pre-v0.15.7 OP_RETURN spend bug: rewind the local blockchain past the bad block via a new teranode-cli rewindblockchain subcommand, then let the node re-sync from that point with the v0.15.7 store-time fix in effect. Also hardens the rewind tool so it can never accidentally move BlockPersisterHeight forward.
teranode-cli rewindblockchain subcommand (backport #1345 / #1341)
Registers the destructive-rewind tool as a teranode-cli subcommand so operators can run it without shipping a separate binary. Ships with:
--target-height <h>— target height (default: readstate["BlockAssembler"])--dry-run— log actions, modify nothing--assume-yes— skip the interactive confirmation prompt--force-not-idle— proceed even if FSM is notIDLE(DANGEROUS)--force-deep— allow rewind deeper than 100 blocks (coinbase-maturity risk)--verify— run post-rewind consistency checks--concurrency <n>— subtree-load concurrency
Interactive confirmation tolerates piped input (printf y | teranode-cli ...) and reports a clearer error under non-interactive contexts (kubectl exec / docker compose run without -it).
Positional-argument guard rejects the swallowed-flag invocation (--assume-yes 1749330 --force-deep) before any store is opened — Go's flag package stops at the first non-flag argument, so without the guard --target-height would silently fall back to state["BlockAssembler"] and --force-deep would be dropped.
rewindblockchain never advances BlockPersisterHeight (backport #1342 / #1340)
The rewind tool now clamps state["BlockPersisterHeight"] against the blocks-table ground truth (lowest block with persisted_at IS NULL, minus one) and refuses to write a value that would move the persister forward. Prevents an accidental fast-forward when the state key is stale or absent.
Backport scope
Both PRs are backports of main-line changes; the fix content is identical, only the target branch differs. No divergence from main.
- feat(teranode-cli): register rewindblockchain subcommand by @oskarszoon (backport of #1345 / #1341)
- fix(rewindblockchain): never move BlockPersisterHeight forward on rewind by @oskarszoon (backport of #1342 / #1340)
Full Changelog: v0.15.7...v0.15.8-beta-1
v0.15.7
What's Changed
Fixes a UTXO-set membership regression that wedged all Teranode testnets when a bare OP_RETURN output was spent. Pre-Genesis rules were being applied to post-Genesis outputs: bare OP_RETURN was dropped from the UTXO set at store time, so any later block spending it could not resolve the outpoint and the node stopped advancing.
The rule now mirrors SV Node's era-aware CScript::IsUnspendable, keyed to the output's creation (mining) height. Post-Genesis: only OP_FALSE OP_RETURN is provably unspendable; bare OP_RETURN is retained. Pre-Genesis: bare OP_RETURN, OP_FALSE OP_RETURN, and locking scripts longer than 10000 bytes are provably unspendable. The rule is value-agnostic — the output's satoshi value plays no role.
External-outpoint reconstruction applies the same era-aware filter, keyed to the parent transaction's creation-height era read from the Aerospike blockHeights bin, so reconstructed sets match what create stored.
Identical to v0.15.7-beta-1.
- fix(utxo): make ShouldStoreOutputAsUTXO era-aware and value-agnostic (backport #1116 to release/v0.15) by @oskarszoon in 01f0697
Full Changelog: v0.15.6...v0.15.7