Skip to content

Commit e6e622e

Browse files
committed
Implement O(1) OP_IF/NOTIF/ELSE/ENDIF logic
This optimization was first suggested by Sergio Demian Lerner in https://bitslog.wordpress.com/2017/04/17/new-quadratic-delays-in-bitcoin-scripts/. The implementation follows the suggested approach there, but with a slightly simpler representation.
1 parent d0e8f4d commit e6e622e

File tree

1 file changed

+46
-6
lines changed

1 file changed

+46
-6
lines changed

src/script/interpreter.cpp

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -289,16 +289,56 @@ namespace {
289289
* expose whether the stack is empty and whether or not any false values are
290290
* present at all. To implement OP_ELSE, a toggle_top modifier is added, which
291291
* flips the last value without returning it.
292+
*
293+
* This uses an optimized implementation that does not materialize the
294+
* actual stack. Instead, it just stores the size of the would-be stack,
295+
* and the position of the first false value in it.
292296
*/
293297
class ConditionStack {
294298
private:
295-
std::vector<bool> m_flags;
299+
//! A constant for m_first_false_pos to indicate there are no falses.
300+
static constexpr uint32_t NO_FALSE = std::numeric_limits<uint32_t>::max();
301+
302+
//! The size of the implied stack.
303+
uint32_t m_stack_size = 0;
304+
//! The position of the first false value on the implied stack, or NO_FALSE if all true.
305+
uint32_t m_first_false_pos = NO_FALSE;
306+
296307
public:
297-
bool empty() { return m_flags.empty(); }
298-
bool all_true() { return !std::count(m_flags.begin(), m_flags.end(), false); }
299-
void push_back(bool f) { m_flags.push_back(f); }
300-
void pop_back() { m_flags.pop_back(); }
301-
void toggle_top() { m_flags.back() = !m_flags.back(); }
308+
bool empty() { return m_stack_size == 0; }
309+
bool all_true() { return m_first_false_pos == NO_FALSE; }
310+
void push_back(bool f)
311+
{
312+
if (m_first_false_pos == NO_FALSE && !f) {
313+
// The stack consists of all true values, and a false is added.
314+
// The first false value will appear at the current size.
315+
m_first_false_pos = m_stack_size;
316+
}
317+
++m_stack_size;
318+
}
319+
void pop_back()
320+
{
321+
assert(m_stack_size > 0);
322+
--m_stack_size;
323+
if (m_first_false_pos == m_stack_size) {
324+
// When popping off the first false value, everything becomes true.
325+
m_first_false_pos = NO_FALSE;
326+
}
327+
}
328+
void toggle_top()
329+
{
330+
assert(m_stack_size > 0);
331+
if (m_first_false_pos == NO_FALSE) {
332+
// The current stack is all true values; the first false will be the top.
333+
m_first_false_pos = m_stack_size - 1;
334+
} else if (m_first_false_pos == m_stack_size - 1) {
335+
// The top is the first false value; toggling it will make everything true.
336+
m_first_false_pos = NO_FALSE;
337+
} else {
338+
// There is a false value, but not on top. No action is needed as toggling
339+
// anything but the first false value is unobservable.
340+
}
341+
}
302342
};
303343
}
304344

0 commit comments

Comments
 (0)