Repository navigation
Releases: tari-project/tari-ootle
Release list
v0.47.0
🚀 Tari Ootle v0.47.0
✨ Highlights
⚠️ Verified wallet reads and finalization — wallets check receipts and substates against the validator set (#2961, #2976, #2977)- Validator set handover — end-of-epoch blocks sign the next epoch's validator set (#2937, #2943)
⚠️ Indexer GraphQL server removed (#2926)⚠️ tari_debughost function removed (#2949)⚠️ 🔒 Confidential outputs must prove a non-zero value (#2978)
⚠️ Before you upgrade
- Validators and indexers upgrade and restart together. (#2943, #2996, #2934, #2955, #2978)
- Upgrade wallets to v0.47.0; older wallets can't read from or transact on the upgraded network. See the wallet setup guide. (#2968, #2978)
- Indexer GraphQL settings removed: drop
graphql_addressandweb_ui_public_graphql_url, or the config fails to load. (#2926) - Templates:
engine_debug!/tari_debugremoved; rebuild againsttari_template_lib0.35. (#2949) - API changes for app developers are listed in the changelog.
👛 Wallet
⚠️ Verified finalization — receipts committed only once proven (#2961)⚠️ Verified reads — substates read only with a recent proof (#2976)⚠️ ootle-rs verification against the validator set (#2977)- tari_ootle_light_client — walks the validator set handover (#2956)
- Trusted validator set status in walletd, with a web UI page (#2975, #3000)
- Account refresh from finalized transactions, 10-minute poll (#3001)
- ⚡ Batched vault fetch per account (#2999)
⚠️ 🐛 Bounded result waits, submissions deferred until anchored (#2987)- 🔒 Signing restricted to account, transaction, nonce and imported keys (#2960)
- 🔒 Proofs encrypted to destination view key (#2946)
- 🔒 Transaction-request locks bound to their creator (#2930)
- 🐛 Referenced substate kinds detected as inputs (#2942)
- 🐛 Stealth output overflow rejected (#2939, #2983)
- 🐛 Anchors on the current epoch's validator set before the first checkpoint (#3010)
- 🐛 Clearer unserved-checkpoint error (#2997)
🛡️ Validator Node
- Validator set hash per epoch, committed by end-of-epoch blocks (#2932, #2937, #2943, #3005)
- 🐛 Committees ordered by public key (#2996), certificate signatures sorted (#2912)
- 🐛 Cross-shard epoch handling — drain before committee change, abort out-of-window (#2957, #2988)
- 🐛 Transaction sequencing — locked-input deferral, post-sync re-sequencing (#2945, #2964)
- 🐛 Foreign proposals — only requested, epoch burn rate, duplicate pledge versions (#2920, #2952, #2915)
- 🔒 Bounded consensus work — future-view messages, parked blocks, ready records, missing-tx responses (#2921, #2929, #2940, #2962)
- 🔒 Engine metering — engine calls, component loads, fee checkpoint, confidential withdraw (#2925, #2933, #2934, #2955)
- 🔒 Restricted frames refuse new buckets, proofs and allocations (#2970)
- 🔒 Bounded RPC and state sync — batch responses, scans, commit epochs, page sizes (#2923, #2935, #2944, #2953, #2966, #2967)
- 🐛 Reads proven against previous epoch's end block (#2994)
- 🐛 Transaction proof root check (#2938)
- 🔒 Transport upgrade timeout, bounded RPC stream idle budget, stalled-reader sessions closed (#2948, #2958, #3004)
- 🔒 Confidential withdraw with revealed change but no change output rejected (#3006)
sync_blocksRPC removed (#2965)
🔎 Indexer
⚠️ Validator set handover served, synced checkpoints verified (#2954)⚠️ Down proofs over REST (#2968)⚠️ ⚡ Commit proof once per substate batch (#3002)- ⚡ Caching — fresh batch reads, committee proofs, settled checkpoints (#2971, #2972)
⚠️ GraphQL server removed (#2926)- 🔒 Template address verified before caching (#2913)
- 🐛 UTXOs indexed by shard, bounded stream reads (#2989, #2995)
- 🐛 Previous-epoch checkpoint sync (#2985, #2998)
🧰 Other
⚠️ engine_debug!removed from tari_template_lib (#2949)- 🐛 LP contribute returns both remainders (#2928)
- 🐛 Manifest identifier resolution (#2922)
- 🐛 Dry-run transactions refused at ingress (#2924)
- 🐛 Workspace fee payer resolution in ootle_sdk_core (#2931)
- Swarm hot-reloads
[[executables]](#2974) - 🔒 rustls 0.23.46 (#2941)
Changes: v0.46.1...v0.47.0
v0.46.1
🚀 Tari Ootle v0.46.1
✨ Highlights
⚠️ Expired cross-shard transactions abort instead of staying prepared; validators upgrade together. (#2905)- 🐛 Sturdier state sync and RPC streams. (#2910, #2917)
⚠️ IndexerGET /non-fungiblesremoved. (#2904)
🛡️ Validator Node
⚠️ Expired cross-shard transactions left in LocalPrepared after their max epoch abort. (#2905)- 🔒 State sync requires every batch of a state version to carry one epoch. (#2910)
- 🐛 RPC streams that are abandoned now end. (#2917)
- Execution time histogram in validator metrics. (#2909)
🔎 Indexer
⚠️ GET /non-fungiblesremoved, along withget_non_fungiblesin the Rust and JS indexer clients. (#2904)- 🐛 State following runs on a dedicated RPC session. (#2917)
- 🐛 Indexed receipts and templates survive conflicting sync updates. (#2901)
🧰 Other
- 🔒 ruzstd 0.9.1 for RUSTSEC-2026-0344. (#2906)
Changes: v0.46.0...v0.46.1
v0.46.0
🚀 Tari Ootle v0.46.0
✨ Highlights
⚠️ Protocol V2: block headers commit to a transaction merkle root, substate proofs bind to their shard, and foreign pledges are authenticated. Esmeralda activates it at epoch 12220; every other network starts at V2 and must be reset. (#2804, #2762, #2798, #2867)- ⚡ Faster validator storage: substate locks, block diffs and pending state tree diffs are held in memory; databases migrate on first start. (#2862, #2863, #2868, #2879)
⚠️ No return values in transaction results: read component state or events instead. (#2859)- 👛 Wallet daemon signing requests for out-of-band co-signing, with an approval page. (#2849, #2850)
- 🔒 Hardening pass: bounds on what peers, templates and transactions can make a node do.
👛 Wallet
- Signing requests for out-of-band co-signing, with an approval page in the walletd UI. (#2849, #2850)
⚠️ 🔒 Approving a transaction request needs the wallet's own session; WebRTC apps need--enable-webrtc. (#2855)- 🐛 Claimed burn fee capped at the local price. (#2803)
- 🐛 Transfer proof change nets out the revealed amount. (#2824)
- 🐛 Confidential outputs finalize against the transaction diff. (#2898)
- 🐛 Paying fees with a swap needs an explicit swap input. (#2837)
- 🔒 Indexer failover: bounded requests, and substates that don't match their id are refused. (#2802, #2813)
- 🔒
detect_inputsrefuses signed submissions. (#2854) - 🔒 Pending WebAuthn sessions bounded. (#2899)
- 🐛 Transaction builder signs the finished body. (#2852)
🛡️ Validator Node
⚠️ Transaction merkle root in V2 block headers. (#2804)⚠️ Shard-bound substate proofs and sound Down proofs. (#2762, #2867)⚠️ 🔒 Authenticated foreign pledges and embedded foreign proposals. (#2798, #2827)⚠️ Claimed burn UTXOs go to the claim key, not the sealer. (#2800)- ⚡ In-memory locks, block diffs and tree diffs; databases migrate on first start and can't be opened by an older binary. (#2862, #2863, #2868, #2879)
⚠️ Block and transaction write limits: 4 MiB of state per transaction, bounded bytes per shard, and a 1.5 MiB cap on a transaction's inputs per shard group in cross-shard pledges. (#2821, #2894)⚠️ Publish fees reflect compile work; oversized modules are refused. (#2881)⚠️ Duplicate m-of-n requirements rejected. (#2851)⚠️ State sync liveness hardening, new config settings and a schema version guard. (#2878)- 🐛 State sync proves state only at proof points, and pruned blocks stay provable. (#2864, #2876)
- 🐛 State sync batches split by encoded size. (#2812)
- 🔒 Engine metering: one compute budget across nested calls, and argument and return-value handling are charged. (#2817, #2858, #2888)
- 🔒 Engine validation: event topics and view keys checked in full; template log output bounded. (#2880, #2882, #2884, #2895)
- 🔒 Decode bounds on access rules and call arguments. (#2893)
- 🔒 Consensus: timeout certificates bound to epoch and view; sync probe bound to the local shard group. (#2835, #2836)
- 🐛 Proposal packing in random order, with a capped share for oversized transactions. (#2866)
- 🐛 Leader-skip walk saturates at the top of the height range. (#2825)
- 🐛 Committees reassigned when the epoch hash is corrected. (#2810)
- 🐛 Proof pruning: down, state version and commit proofs pruned once unused. (#2874, #2877)
- 🐛 Block commands held in a validated canonical list. (#2869)
- 🔒 Networking: idle RPC sessions close, inbound connections and peer relays capped, direct messages decoded in the consumer. (#2816, #2856, #2887, #2896)
⚠️ Metrics on their own listener (default127.0.0.1:18201), no longer on JSON-RPC. (#2815)
🔎 Indexer
- 🔒 Transaction results need committee agreement. (#2838)
- 🔒 Verified substate reads need a recent anchor. (#2885)
- 🔒 Rate limits on all public read routes; checkpoint pages capped at 20. (#2823)
- 🐛 "database is locked" no longer fails submit bursts. (#2865)
- 🐛 Cached component state keeps null. (#2870)
🧰 Other
- 🐛 Liquidity pool rejects confidential deposits. (#2900)
- 🐛 Account vault map bounded. (#2853)
- 🐛 Template constructors returning a tuple compile. (#2871)
- 🔒 Dependencies: wasmer 7.5.0; event-listener, cxx and gix-hash past unsound releases. (#2828, #2829, #2830, #2831, #2834)
- Metrics reference docs for the validator node and indexer. (#2809)
What's Changed
- chore(deps): bump hickory to 0.26.3 by @sdbondi in #2805
- fix(engine)!: mint a claimed burn UTXO to the claim key, not the sealer by @sdbondi in #2800
- feat(consensus)!: commit each block's finalized transactions to a transaction merkle root by @sdbondi in #2804
- fix(wallet): bound indexer requests so an unresponsive indexer fails over by @sdbondi in #2802
- test(smoke): add claim_burn stage by @sdbondi in #2806
- test(storage): fix broken test by @sdbondi in #2807
- fix(walletd): cap a burn claim's revealed fee at its local price by @sdbondi in #2803
- docs: metrics reference for validator node and indexer by @sdbondi in #2809
- docs: clarify covenant scope and OutputTo payment semantics by @sdbondi in #2811
- fix(epoch_manager): reassign committees when correcting the current epoch's hash by @sdbondi in #2810
- fix(validator): split metrics server from json rpc by @sdbondi in #2815
- fix(validator): split state sync batches by encoded size by @sdbondi in #2812
- fix(engine): share one WASM budget across nested template calls by @sdbondi in #2817
- fix(networking): close idle RPC sessions and cap inbound connections by @sdbondi in #2816
- fix(wallet): refuse indexer substates whose value does not match their id by @sdbondi in #2813
- chore(deps): bump sharp from 0.35.4 to 0.35.5 in /docs/developer-docs by @dependabot[bot] in #2819
- chore(deps): bump source-map-js from 1.2.1 to 1.2.2 in /docs/developer-docs by @dependabot[bot] in #2818
- fix(consensus): bound bytes written per transaction and per shard per block by @sdbondi in #2821
- fix(consensus): saturate the leader-skip walk at the top of the height range by @sdbondi in #2825
- fix(walletd): net revealed amount out of transfer proof change by @sdbondi in #2824
- fix(indexer): rate limit the remaining public read routes and cap checkpoint pages by @sdbondi in #2823
- fix(consensus): install a high certificate only at its block's height and epoch by @sdbondi in #2822
- fix(consensus): refuse commands in blocks that extend the end-of-epoch block by @sdbondi in #2820
- chore: release v0.46.0 by @sdbondi in #2826
- fix(deps): bump cxx to 1.0.195 by @sdbondi in #2831
- fix(deps): move gix-hash to 0.18 to pick up faster-hex 0.10.1 by @sdbondi in #2829
- fix(engine): drop tracing-subscriber 0.2 from the dependency graph by @sdbondi in #2830
- fix(deps): bump event-listener to 5.4.2 and ban the unsound releases by @sdbondi in #2828
- fix(consensus): bind a block's timeout certificate to its epoch and preceding view by @sdbondi in #2835
- fix(integration_tests): wait on conditions with deadlines instead of fixed sleep loops by @sdbondi in #2833
- chore(deps): bump wasmer to 7.5.0 by @sdbondi in #2834
- fix(consensus)!: authenticate foreign pledge values and embedded foreign proposals by @sdbondi in #2798
- fix(state-tree)!: bind substate value proofs to the substate's shard by @sdbondi in #2762
- fix(consensus): reuse a stored foreign proposal's authentication only for its exact commit proof by @sdbondi in #2827
- fix(wallet): require an explicit swap input when paying the fee with a swap by @sdbondi in #2837
- fix(consensus): bind the future-epoch sync probe to the local shard group by @sdbondi in #2836
- fix(indexer): require committee agreement on transaction results by @sdbondi in #2838
- fix(transaction): sign the finished body in the builder's Signable impl by @sdbondi in #2852
- feat(walletd): add signing requests for out-of-band co-signing by @sdbondi in #2849
- fix(template_lib)!: reject m_of_n rules that list a requir...
v0.45.0
Updates the minotari dependencies to 6.1.0-pre.0 and tari_crypto to 0.24.
⚠️ Upgrade notes
- V1 block IDs change. Networks already running V1 upgrade together; esmeralda, still on V0
until epoch 11925, is unaffected now but must run 0.45.0 before activation. (#2799) - Validator L1 registration and exit signatures commit to the network, and an exit also
commits to the activation epoch of the registration it exits. Exit payload files written by an
older validator no longer load. (#2799)
Other
chore!— minotari 6.1.0-pre.0 and tari_crypto 0.24. (#2799)
v0.44.0
Introduces protocol version V1, which commits each shard's state version in the state merkle root
and lets state sync verify streamed state incrementally. Esmeralda activates V1 at epoch 11925;
every other network starts at V1. Also tightens resource authorization and bounds several peer and
indexer inputs.
⚠️ Upgrade notes
- Every esmeralda validator upgrades before epoch 11925. V1 activates there and changes the state
merkle root; a node still on 0.43.x forks off. (#2784) - A synced node below its checkpoint version must resync. (#2795)
- Non-fungible data updates are denied to non-owners by default.
ResourceAccessRules::new()
setsupdate_nft_datatoDenyAll; templates that relied on the open default must allow it
explicitly. (#2786) - The resource auth hook runs on stealth and confidential actions.
AuthHookCaller::template()
returnsOption—Nonewhen a transaction instruction performed the action. (#2787) - Only a liquidity pool may mint or burn its LP tokens. Pools created before this release keep
their old rules. (#2759) - Wallet daemon settings take
indexer_urls.settings.getalso returns the configured list.
(#2753)
Consensus
feat!— Each shard's state version is committed in the state merkle root under V1. (#2784)fix— A transaction is voted forward only once its shard groups are ready. (#2771)fix— Requested transactions are decoded only once the response is matched. (#2788)
State sync
feat— Streamed state is verified incrementally with state version proofs. (#2796)fix!— Synced shards end at the checkpoint version. (#2795)fix— Synced state that never matches the checkpoint is discarded. (#2765)fix— Only a signed same-group QC counts as stall-probe progress. (#2774)
Engine
fix!— Non-fungible data updates are denied to non-owners by default. (#2786)fix— The resource auth hook runs on stealth and confidential actions. (#2787)fix— Scalar inversion is priced as an exponentiation. (#2794)fix!— Only the pool may mint or burn its LP tokens, and swaps are rejected while either
reserve is empty. (#2759, #2763)
Validator node and networking
fix— Transaction decode is bounded by byte size and instruction count. (#2761)fix— A history sync request is bounded in the epoch it may ask for. (#2776)fix— A peer can hold a messaging stream only for a bounded time. (#2770)fix— A failed RPC session start releases its per-client slot. (#2764)
Indexer
feat!— Substate and transaction receipt proofs are served on request withinclude_proofs.
(#2760)feat— Receipts and events have a retention period, and SQLite contention is reported as
metrics. (#2757)feat— The economics page reports the fees validators can claim. (#2756)fix— State versions accepted from sync peers are bounded. (#2767)fix— Checkpoint exhaust burns are bounded and overflowing economic totals refused. (#2789)fix— Rate limits key onCF-Connecting-IPor the lastX-Forwarded-Forentry. (#2755)fix— The UI shows transactions whose execution result validators have pruned. (#2768)
Wallet
feat— The wallet daemon takes several indexers and fails over between them. (#2753)fix—nfts.transferrequires transfer scope on the fee payer. (#2785)
Other
🚀 Tari Ootle v0.43.0
21 PRs merged since v0.42.0.
✨ Highlights
⚠️ Coordinated upgrade required: consensus rules changed; no state store deletion needed- 🔒 Empty proofs no longer grant access: zero-amount and empty proofs fail resource access rules (GHSA-pp4v)
- 🔒
⚠️ Covenant proofs and template compiles metered: covenant balance proofs charged; templates capped at 2,048 functions (#2739, #2747) ⚠️ 1,024 declared-input cap: enforced at ingress and in block validation (#2744)- 🔒 Hardened consensus messaging: malformed, unauthorised and mis-committee'd messages dropped (#2737, #2738)
👛 Wallet
- 🐛 Seed recovery cleanup: unused recovered accounts removed (#2735)
- Stealth input headroom: transfers select at most 992 stealth inputs (#2744)
- 🐛 Auto-claim retries: not-yet-claimable burn claims resubmitted; consensus epoch used (#2749)
- Indexer failover: indexer client and ootle-rs provider switch endpoints at runtime (#2750)
🛡️ Validator Node
- 🔒 Empty proofs no longer grant access: zero-amount and empty proofs rejected (GHSA-pp4v)
- 🔒
⚠️ Covenant balance proofs metered: duplicate and out-of-range covenant claims rejected (#2739) - 🔒
⚠️ Template compile caps: 2,048 functions and 512Ki variables, priced by function count (#2747) ⚠️ 1,024 declared-input cap per transaction (#2744)- 🔒 Foreign proposal verification: checked against the named shard group's committee (#2737)
- 🔒 Peer message admission: malformed and unauthorised messages dropped (#2738)
- 🐛 State sync serving: bounded requests served only for history the node holds (#2733)
- 🐛 Epoch resume after restart: unfinalised leaf epoch resumed correctly (#2746)
- 🐛 Faster catch-up: lagging replica syncs as soon as a proposal shows it is behind (#2743)
🔎 Indexer
- 🔒
⚠️ Bounded dry-runs: structural validation, batched input lookups, 400 on missing inputs (#2744) - 🔒 Dry-run concurrency cap: compute-weighted rate limit, 503 when saturated (#2745)
⚠️ Rate limits on by default: settrust_proxy_headersbehind a reverse proxy (#2744)- 🔒 Bounded event queries: capped scans and
before_idcursor paging (#2742) - 🐛 Wildcard topic filters: underscores matched literally (#2740)
- 🐛 Consensus epoch: dry-runs execute in it; reported as
consensus_epoch(#2749)
🧰 Other
⚠️ C FFI ABI tag: bumped toootle-sdk-ffi-c/17(#2736)- 🐛 Swarm claim key: created before validators start, reused if present (#2741, #2752)
- 🐛 macOS binaries: OpenSSL vendored (#2751)
Full Changelog: v0.42.0...v0.43.0
🚀 Tari Ootle v0.42.0
53 PRs merged since v0.41.2.
✨ Highlights
⚠️ Testnet reset⚠️ Empty tTARI faucet: get tTARI by burning Minotari (#2724)- ⚡ Faster execution: WASM instance reuse and memory pooling (#2697, #2701)
- 🔒 Protocol security fixes/hardening
- 🛡️ Validator storage: soundness fixes, durable votes persistence, faster RocksDB access and GC
👛 Wallet
⚠️ Empty faucet on public testnets (#2724)resources.get/resources.get_many: new JSON-RPC methods (#2700)- Manifest list arguments: list values and list-valued variables (#2717)
- 🐛 JS client: covers every walletd JSON-RPC route (#2699)
🛡️ Validator Node
⚠️ Web UI on the JSON-RPC port: CORS now opt-in (#2726)⚠️ L1 template registrations removed: no global templates table (#2725)⚠️ L1block_output_mrstored with each base-layer header (#2707)⚠️ Timeout certificates: bind the high QC; header commits to the TC (#2636, #2642)⚠️ Dummy block ids commit tois_dummy(#2694)⚠️ LocalOnly commands drop evidence (#2688)⚠️ Receipts list down-only substates (#2692)⚠️ Validator fee pools kept out of the transaction diff (#2693)⚠️ Claims: "must wait" distinct from "invalid" (#2686)⚠️ Substate version widened tou64(#2596, #2687)⚠️ 🐛 Substate lock fixes: per-chain, per-epoch lock resolution (#2711, #2716, #2720, #2722)- 🐛 Votes and proposals fsynced before sending (#2721)
- 🐛 Deferrable lock conflicts no longer cause block refusals (#2730)
- 🐛 Committee assignments: one per validator and epoch (#2732)
- 🐛 RPC: not-ready peers return
Unavailable(#2731) - 🐛 Write to read-declared input refused when kept, not locked (#2715)
- 🐛 Claim burns cite headers from an earlier epoch only (#2706, #2685)
- 🐛 Clean shutdown: panic recorder releases the state store (#2714)
- ⚡ Storage performance: cheaper RocksDB lookups, single pending-chain read, chunked GC (#2718, #2719, #2723)
- ⚡ Batch signature verification (#2695)
🔎 Indexer
⚠️ Stale validator snapshots shown with the probe error (#2691)- 🐛 Failed sync peers forgotten on epoch change (#2689)
🧰 Other
⚠️ Read/write intent on transaction inputs (#2640)⚠️ Metadata values are CBOR; manifest typed values are macros (#2679)⚠️ Stealth: revealed outputs name their receiver (#2645)
Changes: v0.41.2...v0.42.0
What's Changed
- test(epoch_oracles): fix timing flake in it_ticks_according_to_relative_time by @sdbondi in #2684
- fix!: reword the claim-burn "block header not found" error by @sdbondi in #2685
- feat(substate)!: widen the substate version to u64 by @sdbondi in #2596
- feat(engine)!: tell a claim that must wait from one that is invalid by @sdbondi in #2686
- refactor!: introduce a SubstateVersion newtype by @sdbondi in #2687
- feat(consensus)!: bind the committee's high certificate into the timeout certificate by @sdbondi in #2636
- feat(consensus)!: commit the block header to its timeout certificate by @sdbondi in #2642
- feat!: metadata values are CBOR; manifest typed values are macros by @sdbondi in #2679
- fix(indexer): forget failed sync peers when the epoch moves by @sdbondi in #2689
- feat(consensus)!: drop evidence from LocalOnly commands by @sdbondi in #2688
- feat(engine)!: list down-only substates in the transaction receipt by @sdbondi in #2692
- feat(indexer)!: show stale validator snapshots and the probe error by @sdbondi in #2691
- fix(engine)!: keep validator fee pools out of the transaction diff by @sdbondi in #2693
- refactor(transaction): batch-verify signatures with tari_crypto's verify_batch by @sdbondi in #2695
- fix(consensus)!: commit block ids to whether the block is a dummy by @sdbondi in #2694
- chore(deps): accept libsqlite3-sys 0.30 through 0.35 by @brianp in #2696
- test(consensus): unmute the returning validator once its misses suspend it by @sdbondi in #2698
- feat(transaction)!: declare read or write intent on each transaction input by @sdbondi in #2640
- fix(clients): cover every walletd JSON-RPC route in the JS client by @sdbondi in #2699
- feat(walletd): add resources.get and resources.get_many JSON-RPC methods by @sdbondi in #2700
- perf(engine): pool guest linear-memory reservations across instantiations by @sdbondi in #2697
- perf(engine): reuse one WASM instance per template within a transaction by @sdbondi in #2701
- ci: build a validator node + indexer docker image on self-hosted with a registry cache by @sdbondi in #2703
- chore(deps): bump cloudflare/wrangler-action from 4.0.0 to 4.1.3 by @dependabot[bot] in #2704
- chore(deps): bump taiki-e/install-action from 2.87.15 to 2.87.20 by @dependabot[bot] in #2705
- fix(engine): claim burns cite headers from an earlier epoch only by @sdbondi in #2706
- feat!: store the L1 block_output_mr with each base-layer header by @sdbondi in #2707
- feat(stealth)!: a revealed output names its receiver by @sdbondi in #2645
- feat!: collapse ProtocolVersion to a single V0 for the testnet reset by @sdbondi in #2712
- fix(storage)!: resolve a substate's latest lock from the chain that granted it by @sdbondi in #2711
- feat!: prove burn claims with the L1 burn output, and require its claim key to sign by @sdbondi in #2709
- refactor(indexer): drop bincode naming and the bincode workspace dependency by @sdbondi in #2713
- fix(validator): release the state store held by the panic recorder at shutdown by @sdbondi in #2714
- fix(storage,consensus): scope the remaining lock reads to one chain by @sdbondi in #2716
- fix(engine): refuse a write to a read-declared input when it is kept, not when it is locked by @sdbondi in #2715
- feat(manifest): accept list arguments and list-valued variables by @sdbondi in #2717
- feat(consensus)!: let a council move the exhaust burn rate without a release by @sdbondi in #2675
- fix(storage)!: keep every lock a block grants a transaction on one substate by @sdbondi in #2720
- perf(storage): cheaper RocksDB point lookups and mempool readiness checks by @sdbondi in #2718
- fix(consensus,storage): fsync votes and proposals before sending; exact multi_get for state sync by @sdbondi in #2721
- perf(storage,consensus): read the pending chain once per block instead of once per substate by @sdbondi in #2719
- fix(storage)!: place a lock on the chain by its block's epoch and height by @sdbondi in #2722
- perf(storage,consensus): run epoch and state tree GC in small transactions by @sdbondi in #2723
- feat(walletd)!: start public testnets with an empty tTARI faucet by @sdbondi in #2724
- fix(validator_node)!: serve the web UI from the JSON-RPC port and make CORS opt-in by @sdbondi in #2726
- refactor!: remove L1 code template registration leftovers and the global templates table by @sdbondi in #2725
- chore: release v0.42.0 by @sdbondi in #2729
- chore(deps): bump undici, wrangler and @cloudflare/vite-plugin in /docs/developer-docs by @dependabot[bot] in #2728
- chore(deps): bump brace-expansion from 5.0.6 to 5.0.12 in /applications/tari_swarm_daemon/webui by @dependabot[bot] in #2727
- fix(consensus): stop replicas refusing blocks over deferrable lock conflicts by @sdbondi in #2730
- fix(rpc): signal a peer not ready to serve with an Unavailable status code by @sdbondi in #2731
- fix(storage): keep one committee assignment per validator and epoch by @sdbondi in #2732
- chore(deps): upgrade minotari crates to 6.0.1-pre.2 by @sdbondi in htt...
v0.41.2
The engine and wallet security review release. It closes the ways a crafted template or payload
could crash a validator, a wallet daemon that let anyone enrol an admin credential, and a module
cache that trusted what it read from disk. Also: a validator that stops proposing is skipped as
leader instead of costing the network a timeout, and a component's owner can hand ownership on.
⚠️ Upgrade notes
- Coordinated upgrade required. Leader selection and execution change in ways that cannot be
epoch-gated, so every validator and indexer restarts on the new binary together. No reset. - Operators — every cached template recompiles once. The on-disk module cache is keyed to the
new engine fingerprint, so existing artifacts are ignored after the upgrade. - Wallet — a WebAuthn wallet accepts no second enrolment. Once one credential is registered,
webauthn.reg_startandreg_finishrefuse, and the requested permissions are no longer taken
from the caller. - Wallet — changing the indexer URL now needs
admin. A client holding onlysettings:update
can still change every other field ofsettings.set; the web UI already holdsadmin. - Template authors —
ComponentManager::get_owner_proofreturnsOption<Proof>, and an
m_of_nrule whose threshold is zero or above its requirement count is rejected. - Rust API —
ConfidentialViewVaultBalanceResponseandStealthUtxosDecryptValueResponsegain
asearchedfield;WalletStoreReader::webauthn_is_user_registeredis now
webauthn_has_any_registration;WebauthnAlreadyRegisteredRequestdropsusernameand
WebauthnFinishRegisterRequestdropsrequested_permissions.
Consensus
feat!— A validator that stops proposing is skipped as leader instead of costing the network
a timeout each time its slot comes round. Its votes keep counting, and it gets its slot back once
it is seen participating again. (#2652)feat— A node withholds its vote from a proposer it caught equivocating. (#2652)
Engine
fix!— A deeply nested CBOR payload can no longer crash a validator, including one hidden in
a published template's definition. (#2666)fix!— CBOR integers the encoder cannot produce are rejected on decode. (#2666)fix!— Removes undefined behaviour in how nested calls share the runtime. (#2667)fix!— A corrupted or stale module cache file is recompiled instead of loaded. (#2668)fix!— Closes the rest of the engine review: a multi-scalar multiplication priced by only
one of its arguments, amount arithmetic that could wrap, and four panic sites beside the
execution path. (#2672)feat!— A component's owner can now replace its owner rule, including handing ownership to
someone else or setting it toNone, which is final. (#2677)fix!—ComponentManager::get_owner_proofreturnsOption<Proof>and no longer panics for a
component whose owner is not a single public key. (#2677)fix!— Anm_of_nrule must require between 1 and all of its requirements. A zero threshold
used to admit everyone and one above the count admitted no one. (#2678)
Wallet
fix!— Any process that could reach a WebAuthn wallet's RPC port could enrol itself as an
admin. Enrolment is now refused once the wallet has a credential. (#2671)fix!— Choosing which indexer the wallet trusts now takes an administrative token. (#2673)fix— JSON-RPC parameters are no longer written to the log, where an imported spending key
could end up in plaintext. (#2673)fix— One balance-recovery request can no longer occupy a thread indefinitely. (#2673)feat!— A balance recovery now reports which values it searched, so an undecryptable balance
can be told apart from one the search never reached. (#2673)
Other
chore— All open dependency advisories are closed, and advisories now fail CI. (#2670)docs— New guide: burning Minotari and claiming TARI. (#2680)
Packages
- Crates: workspace
0.41.2;tari_bor0.16.1,tari_template_abi0.20.1,tari_template_lib
0.32.1,tari_template_lib_types0.32.1,tari_ootle_wallet_sdk0.43.2,
tari_ootle_wallet_storage_sqlite0.43.1,tari_ootle_walletd_client0.43.1. - npm:
@tari-project/ootle-ts-bindings1.54.0,@tari-project/wallet_jrpc_client1.23.0,
@tari-project/indexer-client1.7.0.
Full changelog: v0.41.1...v0.41.2
v0.41.1
The consensus audit release. It closes the ways a byzantine leader could fork a committee, stall it
or crash its replicas, and stops a peer deciding how much work a node does for it. Also: a wallet
send that could hang the daemon, a disk cache that only ever grew, and a queryable log of what a
validator saw go wrong.
⚠️ Upgrade notes
- Coordinated upgrade required. Consensus and execution both change in ways that cannot be
epoch-gated, so every validator and indexer restarts on the new binary together. No reset. - Operators — the memory budget rises. The in-memory module cache default moves from 200 MiB to
1 GiB, taking the node's enforced budget to roughly 2.3 GiB and the RAM it asks of the machine
from ~2.3 GiB to ~3.5 GiB. - Operators — two new config sections:
templates.max_disk_cache_size_bytes
(default 10 GiB, and the indexer gains atemplatessection of its own) and
[validator_node.diagnostics]. - Template authors — a non-fungible's
dataormutable_datacan no longer hold aBucketId,
ProofIdor address allocation. - Rust API —
WasmModuleCache::opentakes acap_bytesargument,
RuntimeError::transient_in_component_stateis nowtransient_value_in_substate, and
TemplateBlobisMaxBytes<MAX_TEMPLATE_BLOB_WIRE_BYTES>.
Consensus
fix!— Fixes a possible committee split, where a byzantine leader could get two conflicting
branches of the chain committed. (#2635)fix!— A byzantine leader can no longer make replicas vote for a transaction an honest leader
would have deferred. (#2650)fix!— A byzantine leader can no longer crash every replica's consensus worker, repeatedly.
(#2651)fix!— Substate lock checks are tightened, closing a gap the audit found in how output
locks are granted. (#2650)fix— A committee member can no longer take back a vote it has already cast to break a
quorum that is forming. (#2649)feat— Equivocation is recorded as evidence when a committee member votes two ways, visible
as a diagnostic event, a Prometheus counter and in db-inspector. Nothing acts on it yet. (#2649)fix— A node that crashes just after voting can no longer vote again at that height. (#2649)fix— Only committee members can feed a node's consensus, and a response is accepted only
from the peer that was asked. (#2646)fix— A peer can no longer decide how much memory and work a node spends on it — catch-up
responses, buffered messages and stored votes are all bounded. (#2647)fix— A leader that is ahead of the committee can still end its view, where it previously
had to wait one out. (#2638)refactor— A commit proof is now a fixed size, small enough that a long stall cannot push it
past what the base layer will verify. (#2643)
Engine
fix!— Transaction-scoped ids can no longer be stored in non-fungible data, where they would
outlive the transaction that named them. (#2634)fix!— Anyone may calldeposit_with_auth, which is the point of the method — it was locked
to the account owner, the one caller who never needs it. (#2627)feat!— The compiled-template caches are bounded. The on-disk one only ever grew, and would
have reached about 29 GiB at 10,000 templates. (#2619)perf— Templates are compiled before they are needed, and a cached artifact is written
off the execution path, so a transaction no longer waits on a cold compile or a disk flush.
(#2660, #2661)refactor— The maximum published-template size can now be changed without making larger
already-published templates unreadable. (#2629)
Wallet
fix— A wallet holding many equal-valued outputs could hang the whole daemon while selecting
inputs for a send. The search is now capped. (#2639)
Validator observability
feat— A validator records its own abnormal moments — leader failures, no-votes, consensus
errors, sync transitions, panics — in a bounded event log, queryable over JSON-RPC and in the web
UI. (#2644)
Release tooling and CI
feat— Release checklists, plus a pre-tag gate and a post-tag dashboard
(scripts/release_check.py,scripts/release_status.py). (#2630)fix— The release build selects packages, which is why v0.41.0 shipped no Windows binaries.
(#2631)fix— A failed required build leg turns the tag run red instead of reporting green over an
incomplete draft. Tag builds also restore the cargo cache, riscv64 is dropped, and windows-arm64
links again. (#2632, #2633)fix— The swarm burns funds into the wallet daemon, not the console wallet. (#2628)fix— The tariswap bench stamps a distinct nonce per transaction, so identical calls no
longer collide on one id. (#2626)
Tests
test— Within-epoch catch-up sync has a cucumber scenario, covering over real networking
what only the in-process harness covered. (#2648)
Full Changelog: v0.41.0...v0.41.1
v0.41.0
⚠️ Upgrade notes
Wallet
feat!— A fragmented balance is spendable again. A key-path stealth spend authorizes each
input with its own one-time key, so spending n stealth inputs takes n signatures. At a cap of
16 that made the signature cap — notSTEALTH_LIMITS, which admits 1024 inputs — the binding limit
on a multi-input spend, expressed in the wrong units and in a crate a wallet author reading the
stealth limits would never consult. A wallet whose balance was split across more than 17 stealth
UTXOs could not spend it in one transaction, and a coinjoin was capped at 17 participants. The cap
is now 1024, made affordable by verifying a transaction's whole signature set as one batch: one
multiscalar multiplication over2n + 1terms in place of n double-base multiplications, ~3x at
the cap with the per-signature cost falling as the set grows, and 2.6x cheaper to refuse an
invalid set than the per-signature path it replaces. Weights are hash-derived rather than sampled,
so a validity predicate a committee votes on does not depend on local randomness. A spend at the
cap costs 22,628 of the 24,000 weight a leader packs, so weight — not the count — is what stops one
monopolising a block.fix— A transfer and its fee now share one input budget. Selection used
STEALTH_LIMITS.max_inputs, the cap on a single statement (1000), but what bounds a wallet is
the per-transaction total (1024) — and a transfer that cannot source its fee from its own
revealed remainder carries a second statement whose inputs come out of that same total. Both
selections were capped independently, so a fragmented wallet could build a transaction of up to
2000 inputs against a ceiling of 1024, which the engine refuses and ingress refuses before that:
the wallet building what the network will not take. NowMAX_TRANSFER_INPUTS= 960 and a
FEE_INTENT_INPUT_RESERVEof 64, held back unconditionally — whether a merged statement fits the
fee-intent credit is only known after selection, and a selection that had already claimed the whole
budget could not fall back.feat— Transaction finality is driven from the indexer's event stream. The transaction
service learned of finality only by polling the indexer for every pending transaction every 5 s. It
now subscribes to/eventsand queries a transaction's result the moment aTransactionFinalized
notification names it. An aborted transaction writes no substate and is silent on the stream, so
the 5 s poll stays as a backstop — but while the stream is connected it only queries transactions
pending longer thansilent_transaction_timeout(10 s). Commits are reported as soon as the
indexer's stream delivers the receipt, an abort is noticed within ~10 s instead of ~5 s, at a third
of the previous query load./eventshas no replay, so each reconnection requests a full check.fix— A UTXO scan could silently skip outputs — no error, no gap in the frame sequence, and
nothing to notice after the fact. Two independent causes in the/utxos/streamresume protocol.
state_versionis stamped per sync batch, so every UTXO a block touches on a shard shares one
version and aLIMIT ncut landing inside a version group reported that group's version as the
watermark — the next request's> Vfilter then dropped the rest of the group, and one busy block
on a shard was enough. AndEndOfShard.max_state_versioncame fromMAX(state_version)over the
whole shard, ignoringfrom_epoch,unspent_onlyand truncation, so a truncated pass jumped its
cursor past its own undelivered remainder. Reads now end on a version boundary, and truncation is
the server's answer (has_more) rather than each client's inference.fix— A missing substate answersNotFound, not a general error. A caller polling for
something that does not exist yet — an account it has just created, an input whose creating
transaction has not reached the indexer — could not distinguish that from the wallet being broken,
and only one of the two is worth retrying. The distinction was already carried by
SubstateApiError'sIsNotFoundErrorimpl and simply never reached the JSON-RPC layer.fix—X-Accel-Buffering: noon/events,/transactions/events/streamand/utxos/stream;
nginx and the proxies following its conventions buffer upstream responses by default, which on a
long-lived SSE stream means indefinitely./utxos/streamdeclares its negotiated content type
(application/x-protobuforapplication/x-ndjson) instead of always answering
application/octet-stream.feat—put_workspacealias on the transaction builder.
Indexer and state sync
feat— Follow mode: the indexer stops polling. Each shard group holds async_statestream
open past the validator's tip and receives transitions as they commit, so cache invalidation
latency drops from one sync round (~60 s) to roughly one block. Shard groups run concurrently
under aFuturesUnordered; each winds its own stream down between messages on an epoch advance,
syncs the new epoch's checkpoints, re-resolves its committee and reopens from the cursor it holds.
A global re-plan happens only when the set of shard groups itself changes. Keepalives re-stamp the
watermark of every shard a stream has closed off, so a quiet shard stays served for as long as its
validator keeps answering andstate_sync_stream_deadline(now 600 s) is only how often a quiet
stream is reopened.feat— Per-request deadlines and keepalives made it possible. Both were already per-request
fields on the wire, but the only way to set either wasRpcClientConfig, fixed when the session is
created — andRpcMultiPoolcaches one client per peer, so buying a long deadline for one
long-lived stream applied it to every ordinary read on that session. New ACK keepalive frames let
an idle streaming response prove liveness within its deadline rather than extending it; before,
a responder with nothing to send was indistinguishable from one that had died. Restoring the
framework's dead test suite (~1,900 lines behind two commented-outmodlines) turned up several
pre-existing bugs, fixed here: read timeouts that restarted on every frame including keepalives,
so asking for keepalives made the client wait longer and an idle stream never timed out at all; a
refused session reported asOk; a fixed budget of twenty discarded stale frames tearing down a
whole pooled session; and a server that could not be driven without a libp2p transport.fix— Async_stateresponder answers only for shards it stores, at the epoch of sending. An
unbounded request for shards a node no longer serves streamed no transitions — because none arrive
for those shards any more — and then closed each shard off with a completion marker. The indexer
takes a completion marker as evidence that a shard is level with its committee, so that silence
read as freshness and the cache kept serving values it believed current. Follow mode makes this
load-bearing: a held-open stream spans epoch boundaries, and "peer went quiet" must read as
unknown, never as caught up. A peer-attributable failure now skips only that shard group rather
than costing every other group its state sync.fix— Three ways a stale value could be installed as the live head, all closed. A committee
member that is behind answers with a version this indexer has already watched the substate pass —
legitimately, from its own point of view — and the transition had already deleted the cached row
that would have ranked the answer down, so a write below the version the stream has shown is now
refused. A destroy with no successor no longer admits anUpat the destroyed version while still
admitting aDownat it. And a finalized transaction result retires what it created or destroyed
ahead of the stream, which is what thetransfer.feature"substate does not exist" failures were:
the wallet asked whether a destination account existed, that nonexistence was cached, it learned
the transfer committed, and read back the stale answer.feat— Substates the committee agrees do not exist are cached.DoesNotExistis the most
expensive lookup the indexer makes:UpandDownreturn on the first acceptable response, but
absence has nothing to prove against the state tree, so it is settled byf + 1agreement and
walks that many committee members — every time, because it was also the one result the cache would
not keep. Recorded as a row with no version rather than a sentinel, soOption's own ordering
gives the head-ranking rule for free. Served only while the shard's stream is demonstrably alive
(state_sync_keepalive_interval × 3), because a nonexistence is correct at the instant it is taken
and false ever after; transaction receipts are excluded, since they are the bulk of the stream by
count and are answered from the indexer's own tables anyway.feat— Committee members are raced on a substate read, up toREAD_RACE_WIDTH = 3in flight,
settling on the first response that decides the read. Members were asked one at a time, so an
unreachable first pick waited out the full 10 s connect timeout — on a small committee, a 1-in-n
chance per read of a multi-second stall. The decision rules move into oneCommitteeReadTally, and
DoesNotExistnow settles atf + 1throughout rather than needingf + 2on the way through the
loop.feat!— The batched substate read path carries proofs.get_substate_batchstreamed bare
values, so every batch result was written unverified — and with proof verification on the read path
refused those entries and refetched singly, so ...