Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

O(1) OP_IF/NOTIF/ELSE/ENDIF script implementation #16902

Merged
merged 3 commits into from Mar 14, 2020

Conversation

sipa
Copy link
Member

@sipa sipa commented Sep 18, 2019

While investigating what mechanisms are possible to maximize the per-opcode verification cost of scripts, I noticed that the logic for determining whether a particular opcode is to be executed is O(n) in the nesting depth. This issue was also pointed out by Sergio Demian Lerner in https://bitslog.wordpress.com/2017/04/17/new-quadratic-delays-in-bitcoin-scripts/, and this PR implements a variant of the O(1) algorithm suggested there.

This is not a problem currently, because even with a nesting depth of 100 (the maximum possible right now due to the 201 ops limit), the slowdown caused by this on my machine is around 70 ns per opcode (or 0.25 s per block) at worst, far lower than what is possible with other opcodes.

This PR mostly serves as a proof of concept that it's possible to avoid it, which may be relevant in discussions around increasing the opcode limits in future script versions. Without it, the execution time of scripts can grow quadratically with the nesting depth, which very quickly becomes unreasonable.

This improves upon #14245 by completely removing the vfExec vector.

@DrahtBot
Copy link
Contributor

@DrahtBot DrahtBot commented Sep 18, 2019

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #18011 (Replace current benchmarking framework with nanobench by martinus)
  • #13062 (Make script interpreter independent from storage type CScript by sipa)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@ajtowns
Copy link
Contributor

@ajtowns ajtowns commented Sep 18, 2019

It might be easier to review if the operations on vfExec are pulled out into a separate class first, so that the optimisation just means updating the class from:

class FalseCounter {
private:
    std::vector<bool> flags;
public:
    bool all_true() { return !count(flags.begin(), flags.end(), false); }
    void push_back(bool f) { flags.push_back(f); }
    void pop_back() { flags.pop_back(); }
    bool empty() { return flags.empty(); }
    void toggle_top() { flags.back() = !flags.back(); }
};

to

class FalseCounter {
private:
    int depth = 0;
    int first_false = 0;
public:
    bool all_true() { return first_false == 0; }
    void push_back(bool f) {
        ++depth;
        if (first_false == 0 && !f) first_false = depth;
    }
    void pop_back() {
        if (first_false == depth) first_false = 0;
        --depth;
    }
    bool empty() { return depth == 0; }
    void toggle_top() {
        if (first_false == 0) {
            first_false = depth;
        } else if (first_false == depth) {
            first_false = 0;
        } else {
            // no action needed as change is unobservable
        }
    }
};

without changing the usage of the class. I think it'd be plausible to do a coq proof that those implementations are logically equivalent / implement the same API (at least given !empty() preconditions for pop_back() and toggle_top()).

cf https://github.com/ajtowns/bitcoin/commits/201909-vfexec-optimise

@sipa
Copy link
Member Author

@sipa sipa commented Sep 18, 2019

@ajtowns If there's interest in including this patch at some point we should pick up that approach.

@practicalswift
Copy link
Contributor

@practicalswift practicalswift commented Sep 18, 2019

Somewhat related issue: sipa/miniscript#7 ("Policies with too high nesting depth are not rejected: It is possible to create small inputs that cause extreme memory usage (in practice: OOM kill or std::bad_alloc)")

Context: Nested tresh tend to be very OP_IF/OP_ELSE/OP_ENDIF intensive (or_i) when compiled to script :)

@sipa sipa changed the title [POC] O(1) OP_IF/NOTIF/ELSE/ENDIF script implementation O(1) OP_IF/NOTIF/ELSE/ENDIF script implementation Nov 4, 2019
@sipa
Copy link
Member Author

@sipa sipa commented Nov 4, 2019

I've modified the code to follow @ajtowns's approach more closely, though with fewer commits, and extra comments.

@sipa sipa force-pushed the 201909_o1nestedifs branch 2 times, most recently from 132225a to d4721cd Compare Nov 4, 2019
src/script/interpreter.cpp Outdated Show resolved Hide resolved
src/script/interpreter.cpp Outdated Show resolved Hide resolved
@sipa sipa force-pushed the 201909_o1nestedifs branch 3 times, most recently from 3d09358 to 59c0b4e Compare Nov 7, 2019
sipa and others added 3 commits Nov 7, 2019
Includes comments added by Pieter Wuille.
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.
nkohen added a commit to nkohen/bitcoin-s-core that referenced this issue Nov 7, 2019
Christewart pushed a commit to bitcoin-s/bitcoin-s that referenced this issue Nov 8, 2019
* Redid conditional interpreting without binary trees

* Fixed ControlOperationsInterpreterTest

* Implemented O(1) conditional handling as proposed here bitcoin/bitcoin#16902 because Ben Carman pointed it out

* Added some docs

* Responded to code review
public:
bool empty() { return m_stack_size == 0; }
bool all_true() { return m_first_false_pos == NO_FALSE; }
void push_back(bool f)
Copy link
Contributor

@NicolasDorier NicolasDorier Jan 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an assert(m_stack_size != NO_FALSE - 1) is needed.
Also, does not seems caller are checking this. Unsure if this can realistically taken advantage of, but I think it does not hurt the scriptevaluator to check for it.

Copy link
Contributor

@ajtowns ajtowns Jan 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd need a script with 4 billion OP_IF's to hit that (so a tx with 4GB of data or over 1G weight), so taking advantage of it shouldn't be possible... Adding the assert seems plausible though.

Copy link
Contributor

@roconnor-blockstream roconnor-blockstream Mar 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular Script length is limited to MAX_SIZE (2^25) bytes.

@jnewbery
Copy link
Member

@jnewbery jnewbery commented Feb 11, 2020

Code review ACK e6e622e

I've run the bench and this change cuts time by ~75%

before:

→ ./bench_bitcoin -filter=VerifyNestedIfScript -evals=50
WARNING: This is a debug build - may result in slower benchmarks.
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 14.0987, 0.00267213, 0.00323801, 0.00277969

after:

→ ./bench_bitcoin -filter=VerifyNestedIfScript -evals=50
WARNING: This is a debug build - may result in slower benchmarks.
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 3.15142, 0.000624684, 0.0006379, 0.000630272

I think the asserts in the final implementations of pop_back() and toggle_top() can actually be added in the intermediate commit to better illustrate that this doesn't change behaviour. I've done that, as well as adding the assert suggested in #16902 (comment) and some more commenting here: https://github.com/jnewbery/bitcoin/tree/pr16902.1. Feel free to take any of that if you think it's an improvement.

Copy link
Member

@jonatack jonatack left a comment

ACK e6e622e code review, build, benches, fuzzing

bench before

(HEAD detached at 89fb241c54)$ src/bench/bench_bitcoin -filter=VerifyNestedIfScript -evals=50
WARNING: This is a debug build - may result in slower benchmarks.
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 24.0408, 0.00434014, 0.00559945, 0.00475711
VerifyNestedIfScript, 50, 100, 23.5444, 0.00428717, 0.00569794, 0.00467757
VerifyNestedIfScript, 50, 100, 24.6077, 0.00431298, 0.00633760, 0.00485635

bench after

(pr/16902)$ src/bench/bench_bitcoin -filter=VerifyNestedIfScript -evals=50
WARNING: This is a debug build - may result in slower benchmarks.
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 5.49356, 0.000954118, 0.00140947, 0.00106701
VerifyNestedIfScript, 50, 100, 6.64376, 0.000977855, 0.00202046, 0.00132529
VerifyNestedIfScript, 50, 100, 5.88235, 0.000968067, 0.00177160, 0.00110281

fuzzing results (#18127)

(pr/18127)$ src/test/fuzz/script_condition_stack 
INFO: Seed: 3449044402
INFO: Loaded 1 modules   (510932 inline 8-bit counters): 510932 [0x5642d9dc1678, 0x5642d9e3e24c), 
INFO: Loaded 1 PC tables (510932 PCs): 510932 [0x5642d9e3e250,0x5642da609f90), 
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2	INITED cov: 177 ft: 178 corp: 1/1b exec/s: 0 rss: 117Mb
#3	NEW    cov: 179 ft: 242 corp: 2/59b exec/s: 0 rss: 117Mb L: 58/58 MS: 1 InsertRepeatedBytes-
#4	NEW    cov: 179 ft: 290 corp: 3/90b exec/s: 0 rss: 117Mb L: 31/58 MS: 1 EraseBytes-
.../...
#5136	REDUCE cov: 438 ft: 2238 corp: 116/218Kb exec/s: 7 rss: 152Mb L: 269/4096 MS: 1 EraseBytes-

I'll leave the fuzzer running for a while longer.

if (m_first_false_pos == NO_FALSE) {
// The current stack is all true values; the first false will be the top.
m_first_false_pos = m_stack_size - 1;
} else if (m_first_false_pos == m_stack_size - 1) {
Copy link
Member

@jonatack jonatack Feb 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pico-nit: perhaps execute m_stack_size - 1 between the assert line 330 and the start of the conditional line 331 and use the result in lines 333 and 334 in the conditional

@jonatack
Copy link
Member

@jonatack jonatack commented Feb 12, 2020

I like the changes in @jnewbery's branch mentioned in #16902 (comment)) esp. this code comment.

@fjahr
Copy link
Contributor

@fjahr fjahr commented Feb 13, 2020

ACK e6e622e

Reviewed code, compiled, ran tests and benchmarks

I received better results from the benchmarks after the change, although improvements are not nearly as significant as the others reported. Not sure why that is, I ran them multiple times with different configs, without enable_debug for example, and results were consistent.

Before (HEAD detached at 89fb241):

$ src/bench/bench_bitcoin -filter=VerifyNestedIfScript -evals=50
WARNING: This is a debug build - may result in slower benchmarks.
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 8.34206, 0.0016061, 0.00195646, 0.00163048

After:

$ src/bench/bench_bitcoin -filter=VerifyNestedIfScript -evals=50
WARNING: This is a debug build - may result in slower benchmarks.
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 7.66019, 0.00146802, 0.00266328, 0.00149105

@MarcoFalke
Copy link
Member

@MarcoFalke MarcoFalke commented Feb 13, 2020

Can anyone explain me why everyone is benchmarking with optimizations disabled? I hope all nodes on mainnet are running -O2, at least for applications where performance matters.

@fjahr
Copy link
Contributor

@fjahr fjahr commented Feb 13, 2020

Can anyone explain me why everyone is benchmarking with optimizations disabled? I hope all nodes on mainnet are running -O2, at least for applications where performance matters.

I did both (with optimizations first). Then I tested it without because my results were different than the others here and ended up posting those because of the differences to make them more comparable. Here are some results with optimizations:

Before:

$ src/bench/bench_bitcoin -filter=VerifyNestedIfScript -evals=50
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 0.789322, 0.000153463, 0.000178044, 0.000157402

After:

$ src/bench/bench_bitcoin -filter=VerifyNestedIfScript -evals=50
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 0.77861, 0.000145174, 0.000211004, 0.000154109

@jonatack
Copy link
Member

@jonatack jonatack commented Feb 13, 2020

Fair enough. Compiled with --enable-bench CXXFLAGS="-O2"

before

((HEAD detached at 89fb241c54))$ src/bench/bench_bitcoin -filter=VerifyNestedIfScript -evals=50
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 1.154,   0.000201797, 0.000381135, 0.000218384
VerifyNestedIfScript, 50, 100, 1.11177, 0.000202117, 0.000342534, 0.000218816
VerifyNestedIfScript, 50, 100, 1.1449,  0.000201718, 0.000425754, 0.00021702
VerifyNestedIfScript, 50, 100, 1.21042, 0.000206192, 0.000390067, 0.000229842

after

((HEAD detached at origin/pr/16902))$ src/bench/bench_bitcoin -filter=VerifyNestedIfScript -evals=50
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 50, 100, 0.554961, 8.99878e-05, 0.000188464, 9.65497e-05
VerifyNestedIfScript, 50, 100, 0.475112, 8.24424e-05, 0.000149663, 9.15264e-05
VerifyNestedIfScript, 50, 100, 0.491641, 8.80607e-05, 0.000171669, 9.19584e-05
VerifyNestedIfScript, 50, 100, 0.496316, 8.20691e-05, 0.00017851,  9.32482e-05

@NicolasDorier
Copy link
Contributor

@NicolasDorier NicolasDorier commented Feb 14, 2020

ACK,

I was wondering about the assert I suggested on #16902 (comment)

I don't really know if it is good idea or not. While theorically possible, even if it was practically possible, I guess it would be more desirable to get a IF bypassed rather than a crash of the node.
Somebody make 4 billions of OP_IF probably can't steal anybody, but might want to crash nodes.

@ajtowns
Copy link
Contributor

@ajtowns ajtowns commented Mar 2, 2020

ACK e6e622e

I don't understand fjahr's results and investigating further didn't reveal anything; seems like any quadratic behaviour is getting completely lost in the noise there somehow. Might be worth adding additional benchmarks once tapscript removes the opcode limit to report the performance of very large scripts; say 10k nested IFs and 20k OP_NOPs vs 40k OP_NOPs etc?

@bitcoin bitcoin deleted a comment from DrahtBot Mar 2, 2020
@laanwj
Copy link
Member

@laanwj laanwj commented Mar 14, 2020

concept and code review ACK e6e622e

@laanwj laanwj merged commit 67dfd18 into bitcoin:master Mar 14, 2020
2 checks passed
sidhujag pushed a commit to syscoin/syscoin that referenced this issue Mar 15, 2020
e6e622e Implement O(1) OP_IF/NOTIF/ELSE/ENDIF logic (Pieter Wuille)
d0e8f4d [refactor] interpreter: define interface for vfExec (Anthony Towns)
89fb241 Benchmark script verification with 100 nested IFs (Pieter Wuille)

Pull request description:

  While investigating what mechanisms are possible to maximize the per-opcode verification cost of scripts, I noticed that the logic for determining whether a particular opcode is to be executed is O(n) in the nesting depth. This issue was also pointed out by Sergio Demian Lerner in https://bitslog.wordpress.com/2017/04/17/new-quadratic-delays-in-bitcoin-scripts/, and this PR implements a variant of the O(1) algorithm suggested there.

  This is not a problem currently, because even with a nesting depth of 100 (the maximum possible right now due to the 201 ops limit), the slowdown caused by this on my machine is around 70 ns per opcode (or 0.25 s per block) at worst, far lower than what is possible with other opcodes.

  This PR mostly serves as a proof of concept that it's possible to avoid it, which may be relevant in discussions around increasing the opcode limits in future script versions. Without it, the execution time of scripts can grow quadratically with the nesting depth, which very quickly becomes unreasonable.

  This improves upon bitcoin#14245 by completely removing the `vfExec` vector.

ACKs for top commit:
  jnewbery:
    Code review ACK e6e622e
  MarcoFalke:
    ACK e6e622e 🐴
  fjahr:
    ACK e6e622e
  ajtowns:
    ACK e6e622e
  laanwj:
    concept and code review ACK e6e622e
  jonatack:
    ACK e6e622e code review, build, benches, fuzzing

Tree-SHA512: 1dcfac3411ff04773de461959298a177f951cb5f706caa2734073bcec62224d7cd103767cfeef85cd129813e70c14c74fa8f1e38e4da70ec38a0f615aab1f7f7
sidhujag pushed a commit to syscoin-core/syscoin that referenced this issue Nov 10, 2020
e6e622e Implement O(1) OP_IF/NOTIF/ELSE/ENDIF logic (Pieter Wuille)
d0e8f4d [refactor] interpreter: define interface for vfExec (Anthony Towns)
89fb241 Benchmark script verification with 100 nested IFs (Pieter Wuille)

Pull request description:

  While investigating what mechanisms are possible to maximize the per-opcode verification cost of scripts, I noticed that the logic for determining whether a particular opcode is to be executed is O(n) in the nesting depth. This issue was also pointed out by Sergio Demian Lerner in https://bitslog.wordpress.com/2017/04/17/new-quadratic-delays-in-bitcoin-scripts/, and this PR implements a variant of the O(1) algorithm suggested there.

  This is not a problem currently, because even with a nesting depth of 100 (the maximum possible right now due to the 201 ops limit), the slowdown caused by this on my machine is around 70 ns per opcode (or 0.25 s per block) at worst, far lower than what is possible with other opcodes.

  This PR mostly serves as a proof of concept that it's possible to avoid it, which may be relevant in discussions around increasing the opcode limits in future script versions. Without it, the execution time of scripts can grow quadratically with the nesting depth, which very quickly becomes unreasonable.

  This improves upon bitcoin#14245 by completely removing the `vfExec` vector.

ACKs for top commit:
  jnewbery:
    Code review ACK e6e622e
  MarcoFalke:
    ACK e6e622e 🐴
  fjahr:
    ACK e6e622e
  ajtowns:
    ACK e6e622e
  laanwj:
    concept and code review ACK e6e622e
  jonatack:
    ACK e6e622e code review, build, benches, fuzzing

Tree-SHA512: 1dcfac3411ff04773de461959298a177f951cb5f706caa2734073bcec62224d7cd103767cfeef85cd129813e70c14c74fa8f1e38e4da70ec38a0f615aab1f7f7
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this issue Jan 8, 2021
Summary:
This is a backport of Core [[bitcoin/bitcoin#16902 | PR16902]] [1/3]
bitcoin/bitcoin@89fb241

`src/bench/verify_script.cpp`  was removed from BitcoinABC in https://reviews.bitcoinabc.org/rSTAGING1ff7b561eb7c6f30f1ec788e9503c30bc6485752

Test Plan: ninja all && src/bench/bitcoin-bench -filter=VerifyNestedIfScript

Reviewers: #bitcoin_abc, majcosta

Reviewed By: #bitcoin_abc, majcosta

Differential Revision: https://reviews.bitcoinabc.org/D8835
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this issue Jan 8, 2021
Summary:
Includes comments added by Pieter Wuille.

This is a backport of Core [[bitcoin/bitcoin#16902 | PR16902]] [2/3]
bitcoin/bitcoin@d0e8f4d

Depends on D8835

Test Plan:
```
ninja all check-all
src/bench/bitcoin-bench -filter=VerifyNestedIfScript
```

Reviewers: #bitcoin_abc, majcosta

Reviewed By: #bitcoin_abc, majcosta

Subscribers: majcosta

Differential Revision: https://reviews.bitcoinabc.org/D8836
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this issue Jan 8, 2021
Summary:
Commit message:
> 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.

PR description:
> While investigating what mechanisms are possible to maximize the per-opcode verification cost of scripts, I noticed that the logic for determining whether a particular opcode is to be executed is O(n) in the nesting depth. This issue was also pointed out by Sergio Demian Lerner in https://bitslog.wordpress.com/2017/04/17/new-quadratic-delays-in-bitcoin-scripts/, and this PR implements a variant of the O(1) algorithm suggested there.
>
> This is not a problem currently, because even with a nesting depth of 100 (the maximum possible right now due to the 201 ops limit), the slowdown caused by this on my machine is around 70 ns per opcode (or 0.25 s per block) at worst, far lower than what is possible with other opcodes.
>
> This PR mostly serves as a proof of concept that it's possible to avoid it, which may be relevant in discussions around increasing the opcode limits in future script versions. Without it, the execution time of scripts can grow quadratically with the nesting depth, which very quickly becomes unreasonable.

This is a backport of Core [[bitcoin/bitcoin#16902 | PR16902]] [3/3]
bitcoin/bitcoin@e6e622e

Depends on D8836

Test Plan:
`ninja all check-all`

Before this commit:
```
$ src/bench/bitcoin-bench -filter=VerifyNestedIfScript
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 5, 100, 0.0473822, 9.28665e-05, 9.67629e-05, 9.47128e-05
```

After:
```
$ src/bench/bitcoin-bench -filter=VerifyNestedIfScript
# Benchmark, evals, iterations, total, min, max, median
VerifyNestedIfScript, 5, 100, 0.0138046, 2.68691e-05, 3.00776e-05, 2.70254e-05
```

Reviewers: #bitcoin_abc, majcosta

Reviewed By: #bitcoin_abc, majcosta

Differential Revision: https://reviews.bitcoinabc.org/D8837
ftrader pushed a commit to bitcoin-cash-node/bitcoin-cash-node that referenced this issue Apr 14, 2021
Summary:
This is a backport of Core
[[bitcoin/bitcoin#16902 | PR16902]] [1/3]
bitcoin/bitcoin@89fb241

`src/bench/verify_script.cpp`  was removed from BitcoinABC in
https://reviews.bitcoinabc.org/rSTAGING1ff7b561eb7c6f30f1ec788e9503c30bc6485752

Test Plan: ninja all && src/bench/bitcoin-bench
-filter=VerifyNestedIfScript

Reviewers: #bitcoin_abc, majcosta

Reviewed By: #bitcoin_abc, majcosta

Differential Revision: https://reviews.bitcoinabc.org/D8835
ftrader pushed a commit to bitcoin-cash-node/bitcoin-cash-node that referenced this issue Apr 14, 2021
Summary:
Includes comments added by Pieter Wuille.

This is a backport of Core
[[bitcoin/bitcoin#16902 | PR16902]] [2/3]
bitcoin/bitcoin@d0e8f4d

Depends on D8835

Test Plan:
```
ninja all check-all
src/bench/bitcoin-bench -filter=VerifyNestedIfScript
```

Reviewers: #bitcoin_abc, majcosta

Reviewed By: #bitcoin_abc, majcosta

Subscribers: majcosta

Differential Revision: https://reviews.bitcoinabc.org/D8836
ftrader pushed a commit to bitcoin-cash-node/bitcoin-cash-node that referenced this issue Apr 14, 2021
Summary:
Commit message:
> 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.

PR description:
> While investigating what mechanisms are possible to maximize the
per-opcode verification cost of scripts, I noticed that the logic for
determining whether a particular opcode is to be executed is O(n) in the
nesting depth. This issue was also pointed out by Sergio Demian Lerner
in
https://bitslog.wordpress.com/2017/04/17/new-quadratic-delays-in-bitcoin-scripts/,
and this PR implements a variant of the O(1) algorithm suggested there.
>
> This is not a problem currently, because even with a nesting depth of
100 (the maximum possible right now due to the 201 ops limit), the
slowdown caused by this on my machine is around 70 ns per opcode (or
0.25 s per block) at worst, far lower than what is possible with other
opcodes.
>
> This PR mostly serves as a proof of concept that it's possible to
avoid it, which may be relevant in discussions around increasing the
opcode limits in future script versions. Without it, the execution time
of scripts can grow quadratically with the nesting depth, which very
quickly becomes unreasonable.

This is a backport of Core
[[bitcoin/bitcoin#16902 | PR16902]] [3/3]
bitcoin/bitcoin@e6e622e

Depends on D8836

Test Plan:
`ninja all check-all`

Before this commit:
```
$ src/bench/bitcoin-bench -filter=VerifyNestedIfScript
VerifyNestedIfScript, 5, 100, 0.0473822, 9.28665e-05, 9.67629e-05,
9.47128e-05
```

After:
```
$ src/bench/bitcoin-bench -filter=VerifyNestedIfScript
VerifyNestedIfScript, 5, 100, 0.0138046, 2.68691e-05, 3.00776e-05,
2.70254e-05
```

Reviewers: #bitcoin_abc, majcosta

Reviewed By: #bitcoin_abc, majcosta

Differential Revision: https://reviews.bitcoinabc.org/D8837
Christewart pushed a commit to bitcoin-s/bitcoin-s that referenced this issue May 1, 2021
* Redid conditional interpreting without binary trees

* Fixed ControlOperationsInterpreterTest

* Implemented O(1) conditional handling as proposed here bitcoin/bitcoin#16902 because Ben Carman pointed it out

* Added some docs

* Responded to code review
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet