script: bound how many operations and stack entries a script may use - #57
Merged
Conversation
v1.2.1 disabled the unbounded opcodes and capped a single pushed element at
520 bytes. What it did not add was a ceiling on how many items there can be,
so a script still pays for itself once, in bytes, and then multiplies: 520
bytes per stack entry means a script of N bytes made almost entirely of
OP_DUP occupies roughly 520N in memory, on every node that validates it.
Two limits, both from Bitcoin:
- at most 201 operations per script, counted outside the fExec test so
the cost of a script cannot depend on which branch a signature happens
to take. Pushes are exempt, being already bounded by the script length
and by MAX_SCRIPT_ELEMENT_SIZE.
- at most 1000 entries across both stacks together, counted together
because OP_TOALTSTACK would otherwise move the problem rather than
solve it.
This tightens validation, so a node that does not upgrade accepts a
superset of what this one accepts and is not stranded.
Verified against the live chain: a node with these rules synced all 3910
blocks from genesis and accepted every one, so nothing in use relies on
scripts these limits would reject.
Merged
Bitflash-sh
added a commit
that referenced
this pull request
Jul 31, 2026
Six changes since 1.2.7, and the reason not to sit on this is #58: coinbase transactions were not unique, two of them overwrote older ones on the main chain, and 100 BTF stopped being reachable. Nothing in how they were built had changed since, so it could happen again at any time. The height now goes into the coinbase on both paths that build one (#59, #60), which makes a repeat impossible without any coordination. Also in: a node now notices a peer it can no longer hear and stays audible to peers that check the same way (#55); it announces its height in the handshake and says out loud when it falls behind the network (#56); script evaluation has ceilings on operation count and stack depth (#57); the tail of the chain is verified at startup and a bad tip is backed out of instead of being mined on (#61); and the wallet now believes the chain about what it has already spent, which is what a restored backup gets wrong (#62).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the post-0.1.0 hardening. #15 disabled the unbounded opcodes and capped a single pushed element at 520 bytes; it did not add a ceiling on how many items there can be.
Without one, a script pays for itself once, in bytes, and then multiplies. 520 bytes per stack entry means a script of N bytes made almost entirely of
OP_DUPoccupies roughly 520N in memory, on every node that validates it.The limits
fExectest so the cost of a script cannot depend on which branch a signature happens to take. Pushes are exempt — already bounded by the script length and byMAX_SCRIPT_ELEMENT_SIZE.OP_TOALTSTACKwould otherwise move the problem rather than solve it.Both tighten validation, so a node that does not upgrade accepts a superset of what this one accepts and is not stranded.
Verified against the live chain
A node with these rules synced all 3910 blocks from genesis and accepted every one. Nothing in use relies on scripts these limits would reject.
BIP30 was pulled out of this branch
It was meant to ship here too. Syncing from genesis with the check in place rejected a real block at height 859, so it stays out until the cause is fixed. Details and numbers in the issue that follows.