Skip to main content

DoorBudget

Struct DoorBudget 

Source
pub struct DoorBudget {
    pub memory_limit: usize,
    pub max_work_bytes: u64,
    pub sig_check_floor: u64,
    pub max_memory_bytes: u64,
}
Expand description

The DOOR’s static work bound (bsv-low W-A gate MED-1, 2026-09-09). Bitcoin script has no loops: every opcode runs at most once, so an input’s work is bounded BEFORE execution by its opcode census times the interpreter’s element limit (the stack memory limit bounds every element, and every CAT / NUM2BIN / SPLIT result with it) plus its signature checks times the transaction’s own size or a floor, whichever is more (a BIP-143 preimage hashes the prevouts and the outputs; the EC verification costs the same whatever the transaction weighs). The estimate is computed from the bytes; nothing executes past the budget, and a budget breach is the DOOR’s verdict (inconclusive: the network judges), never the interpreter’s.

THE BODY IS NOT COUNTED (bsv-low #585, door 1). Until #585 the budget also counted the BODY: 64 unproven transactions, 256 inputs per transaction, 512 KB per transaction, and the parse’s own size and counts. A valid BEEF is never left unjudged for its size or its counts, so those are gone: the walk reads the body through the streaming reader ([crate::script_door]).

TWO LIMBS, both a budget whose breach is “the network judges” and never a refusal: the WORK the interpreter will do, and the MEMORY the door will hold beside the caller’s body. Each is estimated from the bytes before it is spent.

Fields§

§memory_limit: usize

The interpreter’s stack memory limit: bounds every element, and is the unit of the work estimate (a hash-class opcode is charged one element). Not a bound on the BEEF: a script that trips it is “the network judges”, as a breach of the work is.

§max_work_bytes: u64

The whole walk’s estimated work, in bytes hashed or pushed.

§sig_check_floor: u64

The least a signature check is charged (the doors lens E585-D12-M1). A check is charged max(its transaction's bytes, this): the digest hashes about the transaction, and the EC verification behind it costs the same whatever the transaction weighs (about 95 us native). Charged the transaction’s bytes alone, a 106 byte OP_CHECKSIG OP_NOT transaction bought a verification for 106 bytes of budget: 90,000 of them walked at a fifth of the budget in 9.6 s. With the floor the budget bounds the EC verifications of one walk at max_work_bytes / sig_check_floor.

§max_memory_bytes: u64

The whole walk’s estimated memory beside the body, in bytes (the doors lens E585-D12-L3): what the index, the stream’s element in hand and the walk’s layout of a transaction hold, estimated from the frame’s lengths and counts by crate::stream_sizing::estimate BEFORE the stream is opened; and, per input, what its two scripts hold once parsed and run (the delta lens E585-D12-DELTA-M1: bsv-rs’s chunk records, their clones, a signature’s subscript, the empty stack entries the interpreter’s memory limit does not count), charged from the scripts’ chunk count and bytes BEFORE either is parsed (script_door::script_parse_charge, 512 bytes a chunk and 8 a byte).

How the two compose: the frame’s estimate is the most the walk holds at any moment apart from the input being run (what is kept, and the heaviest transaction in hand, counted once with its raw bytes and the interpreter’s raw copies of the unlocking script); the scripts’ charge is the EXPANSION of one input’s scripts past those bytes, held while that input runs and released after it. An input is run only while the frame’s estimate plus its own charge is within this limb.

Implementations§

Source§

impl DoorBudget

Source

pub const DEFAULT: DoorBudget

Sized for LOW’s real shapes with a wide margin. What the static census BOUNDS: hashing (hash-class ops × the element limit) and signature checks (× the transaction’s size or the floor, whichever is more; a CHECKMULTISIG weighted by its static key count when the count is a small-int push, else by the most keys the element limit admits).

THE FLOOR, 64 KiB (half the hash charge), chosen against LOW’s shapes as the pin door_low_shapes_sit_under_a_quarter_of_the_work_budget measures them (work, of the 64 MiB; memory, the frame’s and the largest input’s scripts, of the 48 MiB limb; work before the floor in brackets):

shapeinputsworkmemory
the real covenant settle (5 hash opcodes, 4 checks: a CHECKSIG and the stated OP_3 CHECKMULTISIG)1924,112, 1.4 % (676,284)1,282,178, 2.55 %
the real covenant refund1924,112, 1.4 % (676,284)1,282,178, 2.55 %
LOW’s JOIN: two seats (bsv-low template_tx_sizes.rs:76), the real pot lock, each coin 40 unproven P2PKH hops deep (the deepest the fleet’s record names)8216,132,638, 24.0 %67,026, 0.13 %
STRESS, not LOW’s shape: nine seats, three hops each367,082,619, 10.6 % (4,740,838)36,420, 0.07 %
a coin 30 P2PKH hops deep305,902,188, 8.8 % (3,941,856)23,720, 0.05 %

A P2PKH input is charged 196,739 bytes (a hash opcode at the element limit, a check at the floor, its 131 script bytes): 341 of them are the budget, 85 a quarter of it. The bound on EC verifications per walk is 64 MiB / 64 KiB = 1,024 (1,023 with their script bytes): measured natively, 1,000 of them walk in 87 ms in the release profile (1.4 to 1.8 s in a debug build), and the lens’s 17,000 stop at the 1,024th in 108 ms. wasm32 was not measured.

THE MEMORY, 48 MiB: three eighths of a 128 MB isolate. Beside it stand, for a 10 MB request (the figure the arithmetic is made at: since NL-6 the route caps no body, so a heavier one is the isolate’s), the request’s body, the completed BEEF the route hands the door (a second copy of up to as much) and the EF batch (the route’s 2 MB bound, twice while it is serialized): 24 MB, which leaves 56 MB for the module, the runtime, the allocator’s fragmentation and what a native estimate does not see of wasm32. The limb bounds the walk (the door’s, and since bsv-low #592 the submit’s own and the GASP anchor’s under Engine::set_walk_budget) and nothing else: the route’s own hydrated parses before it are bounded by the isolate only (the land lens E585-LAND-M1: 1.43 GB natively for a 9.9 MB body of 900,000 minimal transactions, before the route’s 429). What it lets through, by the door’s charges (script_door::DOOR_CHARGES, each an upper bound: the pins measure 1.3 to 5.3 times the heap the walk reaches): a BUMP of up to 72,944 leaves (690 bytes a level-0 leaf); a single transaction of up to about 12 MB (4 bytes a byte) whose walked inputs’ scripts are few chunks (a script is charged 520 bytes a one-byte opcode and 8 a pushed byte: an input whose two scripts are about 96,000 opcodes, or a 1 MB OP_NOP lock, is past the limb before it is parsed, the network judges; LOW’s covenant leg, 6,608 script bytes, is charged 1.3 MB); a 10 MB body of one-input, one-output P2PKH transactions (191 bytes, charged 570: 30 MB). What it stops: 111,848 transactions of no input and no output, whatever follows them.

THE OPEN RESIDUAL (the W-A gate’s delta-verify, 2026-09-09, measured): the interpreter itself meters nothing, so two classes stay charged only by their script bytes — bignum arithmetic (OP_MUL/OP_DIV/ OP_MOD on operands up to the element limit: ~1.3 ms per 30 KB×30 KB round, linear in rounds) and OP_NUM2BIN, which allocates its size operand BEFORE the memory limit is consulted (up to bsv-rs’s 1 GB element size: an isolate kill, not a refusal). None of them can refuse a spend (a breach and a trip are the door’s bound and the request proceeds); all of them cost the beta overlay CPU until bsv-rs meters work dynamically (a work_limit charged per op with the real operand sizes, and a pre-allocation check in NUM2BIN) — OWED before any PROD flip of SCRIPT_VERIFY_NETWORK_GATED. Since #585 the count of such rounds is bounded by the body alone (the script bytes are charged, the transactions and inputs are not counted). A third, as before #585 (the doors lens N7): an opcode that copies an element (OP_DUP, OP_CAT, OP_PICK) is charged its one script byte and can copy up to the element limit. The class the floor CLOSED: a CHECKMULTISIG whose key count is a computed value was charged the most keys the element limit admits (3,971) at its transaction’s bytes and run (~80 µs per key tried); at the floor those are 260 MB, past the budget, so such an input is never run at the door (the network judges). LOW’s covenant states its count (OP_3).

Trait Implementations§

Source§

impl Clone for DoorBudget

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for DoorBudget

Source§

impl Debug for DoorBudget

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for DoorBudget

Source§

impl PartialEq for DoorBudget

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for DoorBudget

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more