This release contains bugfixes and improvements to the stacks-node and stacks-signer binaries, ensuring more consistent block production.
This release is compatible with chainstate directories from 4.0.x.
This release is recommended for all Stacks node and signer operators.
This release includes the following changes:
https://github.com/stacks-network/stacks-core/blob/4.0.6/CHANGELOG.md
⚠️ Breaking Changes
- Unused speculative tries from mining and block-proposal validation are no longer persisted. MARF databases automatically migrate to schema 4 on writable open, removing the legacy
mined_blockstable and its stored tries. Older binaries cannot open upgraded databases, including in read-only mode; upgrade chainstate inspection and snapshot tooling alongside the node. - Signers in this release rely on the node's new
GET /v3/blocks/:block_id/viewendpoint and thecanonicalfield on/v3/sortitionsto release a signed block that a Bitcoin fork has orphaned. Against an older node a signer stays safe but never releases such a block, which can stall a tenure after a reorg. Upgrade the stacks-node before, or together with, its signer. - Removed the
stackslib::chainstate::stacks::indexmodule; the MARF now lives in thestacks-marfcrate. Downstream library consumers should import fromstacks_marfinstead. There is no functional change.
Added
- Adds the build-time environment variable
CUSTOM_VERSION_STRINGand exposesstacks_build_infothrough node and signer Prometheus metrics, with standard version,STACKS_NODE_VERSION, Git commit, and custom version labels. Custom metadata remains excluded from public version strings. - Added
GET /v3/blocks/:block_id/view, reporting the tenure and burn view a block hangs from and whether a burnchain fork has orphaned either, and acanonicalfield on the sortition info returned by/v3/sortitions. Signers use both to tell whether a conflicting block is dead. - Consensus tests can start a tenure with a block of chosen transactions through
ConsensusChain::append_tenure_start_block, which keeps invalid transactions and returns the block's result like other test blocks. Test-only change. - The core MARF types (
MARF,MARFOpenOpts,MarfConnection,MarfTransaction) are now re-exported from the root of thestacks-marfcrate, so they can be imported asstacks_marf::MARF. The existingstacks_marf::marf::paths keep working. - New
[connection_options]settings bound how much inbound p2p work a node does in one network pass:max_inbound_messages_per_pass(default64) caps the messages handled from any one peer, andmax_inbound_processing_ms(default500) caps the time spent handling inbound messages before the node moves on to inventory sync, block downloads and StackerDB sync. Work that doesn't fit is carried over to the next pass, which starts immediately unless the node is still processing previously downloaded blocks. While a peer's leftover messages are queued, the node stops reading from that peer's socket, so a burst of messages can't overflow the inbox and drop the connection. - With the
testingfeature, the signer binary reads its test flags from the JSON file named bySTACKS_SIGNER_TEST_FLAGS_FILE, so a harness running the signer as a separate process can inject faults. - Add a single-contract
signer-manager-v2.clarreference with accumulated staker payouts, rejected withdrawal retry, per-cycle reward reserves, payout configuration, allowlisting, and signer metadata. The originalsigner-manager.clarremains available. - The signer now shrinks its database file at startup when at least 256 MiB of it is free space (e.g. after pruning) and its live data is at most 1 GiB, so the space goes back to the OS. Set
db_shrink = falsein the signer config to disable it. - Added
node.sqlite_vfsconfiguration option to select the SQLite VFS used for node databases."unix-excl"holds one exclusive lock per database for the life of the process and keeps the WAL index in memory, removing per-transaction file locking on network and clustered filesystems. The default ("default") keeps current behavior.
Changed
- A globally defined constant now wins over a local variable or parameter of the same name. Such a case should not actually exist (it would be prevented earlier). This change merely makes the behavior consistent for the unlikely case that it does happen.
- Prevent deployment of transient contracts from Epoch 4.1 on. This is a legacy construct that is no longer necessary and that only serves to confuse.
- From Epoch 4.1, Clarity cost and memory charges, in analysis and at runtime, size callable types, and unions of them, by the contract and trait identifiers they hold. Size limits are unchanged.
- Clarity docs: the
define-read-onlydescription now states that the reserved-name exception applies from Epoch 4.1. - Calling a user-defined function, whether directly, through
contract-call?or from a transaction, copies its arguments less often and holds fewer copies of them while the call runs. ClarityBackingStore::get_cc_special_cases_handlernow takes the called contract, and the node only supplies the contract-call special-case handler for calls into PoX contracts.- Once a node's Stacks tip is a Nakamoto block and the first Nakamoto reward cycle has ended, it caches the epoch 2.x block inventory data it uses to answer
GetBlocksInvrequests, instead of querying the chainstate for every request. - The burnchains, config, core, net and util_lib modules now imports the MARF from
stacks_marfdirectly instead of through thestackslib::chainstate::stacks::indexre-export. No behaviour change. - The chainstate code now imports the MARF from
stacks_marfdirectly instead of through thestackslib::chainstate::stacks::indexre-export. No behaviour change. stacks-node,clarity-cli,stacks-inspectandmarf-squashnow depend onstacks-marfdirectly instead of reaching the MARF throughstackslib's re-export, andstacks-node'stestingfeature enablesstacks-marf/testingexplicitly. No behaviour change.- The last
assert!(matches!(..))in the MARF's tests now usesassert_matches!, and redundant custom failure messages were dropped sinceassert_matches!already prints the value. Test-only change. - The node's Prometheus metrics endpoint is now served by
tiny_httpinstead ofasync-std/async-h1, removing those dependencies andhttp-types. The endpoint's responses are unchanged. The configuration docs for the node'sprometheus_bindand the signer'sendpointandmetrics_endpointnow warn that these servers do not limit request sizes and should only be reachable from trusted hosts. - The Clarity v2 parser now reads tokens by reference instead of copying each token every time it is peeked or consumed, making
parser::v2::parseabout 10% faster on pox-4. Parse results and diagnostics are unchanged. PoxVersionsmoved to pox-locking and is now the one list of PoX contracts.- Upgraded
prometheusfrom 0.9 to 0.14 with itsprotobuffeature disabled, removing unusedprotobuffrommonitoring_prombuilds of the node and signer. Exported metrics are unchanged. - Documented PoX-5 lock semantics for
lockedandunlock_heightin the/v2/accountsRPC spec. - Upgraded
rusqlitefrom 0.31 to 0.41 forlibsqlite3-sys(now 0.39.1). The bundled SQLite version moves from 3.45.0 to 3.54.1. - From Epoch 4.1, sortition draws the winning block-commit and the null miner's challenge from separate, domain-separated digests of the sortition hash and VRF seed, instead of reusing one digest for both.
- The Clarity sugar expander and definition sorter no longer deep-clone nested expressions once per enclosing list or tuple, so both passes are linear rather than quadratic in nesting depth (the sugar expander pass is about 7x faster on pox-4). The definition sorter also no longer copies the whole contract before sorting. The expanded AST is unchanged.
- Retain contract deployment interfaces in transaction receipts without retaining the full contract analysis graph.
TupleTypeSignatureandListTypeDatanow cache their value size at construction instead of recomputing it on every call, andTypeSignature::size()reads that cache for tuples and lists rather than walking the type. The cached field is not serialized: it is recomputed on deserialization, so the wire format is unchanged.
Fixed
- In Clarity 7, variable shadowing (i.e. a local binding with the same name as a top-level definition) is prevented during contract analysis in all cases. Previously, some cases would have been missed, only breaking (or leading to unexpected behavior) at runtime.
- Signers now release a signed conflict whose burn view a Bitcoin reorg has orphaned. A tenure that extends across a reorged burn block holds two blocks at the same height differing by burn view alone; without this a signer could be barred from that height for the rest of the tenure.
- Allow Nakamoto blocks with exactly 2 MiB of transaction data in mining and signer proposal validation starting in Epoch 4.1, preserving the exclusive size limit in earlier epochs.
- Fixed a node syncing from genesis stalling at the Epoch 3.0 handoff: blocks downloaded just before the switch were dropped, and the Nakamoto tenures among them were never fetched again.
- From Epoch 4.1, a contract can call
contract-call?through a trait it defines while it deploys, at the top level. Before, the deploy failed withNoSuchContract(#6831). - Fixed the miner's handling of a transaction whose receipt (its result plus its
printevents) is too large to fit into a block. The execution cost of a transaction the miner rejects is no longer left charged to the block being assembled; the Nakamoto miner drops a transaction whose receipt alone reaches 75% of the per-block receipt limit from the mempool as problematic instead of retrying it on every block; and a transaction whose receipt no longer fits in the room left in the current block is skipped and retried in a later block. - Signers no longer pre-commit to a block at a height in a tenure where they have already committed to a different block. A sibling the signer has signed bars the pre-commit permanently; one it has only pre-committed to bars it for
pre_commit_exclusivity_timeout_secs(default 30 seconds), or until enough weight rejects that sibling for reasons that will not be reconsidered, whichever comes first. Added signer database schema version 22, which creates theblocks_consensus_hash_heightindex onblocks (consensus_hash, stacks_height). - A signer's own signature over a block never goes stale against a sibling: another block at the same height in the same tenure is refused however much time has passed. Only a burnchain fork releases it, by orphaning the tenure's sortition or the block's burn view. Signatures at other heights or in other tenures are released by staleness, a reorg permit, or (on a node running this release) the same burnchain-fork checks; a signed conflict is no longer released merely because the node's Stacks chain does not reach it.
- Signers now post a block received in a miner's
BlockPushedmessage to their node only if the block's signer signatures are valid for the signer's reward set. - Signers now reload the signer set's state machine updates from StackerDB when their reward cycle starts, so a signer whose node reaches the cycle's first burn block after its peers' nodes no longer rejects every proposal with
NoSignerConsensusuntil the next burn block. - The signer now exits with an error naming the problem when it cannot open its database, for example one written by a newer release, instead of panicking.
- Signers will now attempt to defer proposals received before the global state machine has reached consensus amongst the signer set, rather than immediately rejecting with
NoSignerConsensus. This should improve block production at Bitcoin block boundaries. - Signers now take a block proposal's burn height and reward cycle from the sortition that elected the block's tenure, and ignore proposals that misstate them, rather than trusting the values the miner sends alongside the block.
- A block proposal for a tenure that the signer's node has not yet processed is rejected with the transient
NoSortitionViewreason, so the miner re-proposes it once the signer has caught up. - Update signer response processing and database lookups.
- Signers and miners no longer decode the legacy
replay_transactionsfield of inbound state machine updates, and only decode state machine update slots loaded at startup when they hold a state machine update.
Docker images have been published to GitHub Container Registry:
stacks-core
- Debian (glibc): https://github.com/stacks-network/stacks-core/pkgs/container/stacks-core/1551213839?tag=4.0.6
docker pull ghcr.io/stacks-network/stacks-core:4.0.6@sha256:c92c10b8ad4875655312044439bb0d8dddae415668749b6646384a5df5597a9b- Alpine (musl): https://github.com/stacks-network/stacks-core/pkgs/container/stacks-core/1551208803?tag=4.0.6-alpine
docker pull ghcr.io/stacks-network/stacks-core:4.0.6-alpine@sha256:2e3d22baa1f5c5ee3d7d9bb143323de1744b29fab74ac7f57322c62d6b111a5estacks-signer
- Debian (glibc): https://github.com/stacks-network/stacks-signer/pkgs/container/stacks-signer/1551202790?tag=4.0.6
docker pull ghcr.io/stacks-network/stacks-signer:4.0.6@sha256:49d76711acf7e858b6434114043ecee079aac015392e94f87613358b86dd3dda- Alpine (musl): https://github.com/stacks-network/stacks-signer/pkgs/container/stacks-signer/1551203852?tag=4.0.6-alpine
docker pull ghcr.io/stacks-network/stacks-signer:4.0.6-alpine@sha256:edfe3bbaa3ac121b86274b4a05d3abbeecd050bda24003a7155e956335be1eed