Skip to content

Commit bcec5ab

Browse files
committed
Make miniscript fuzzers avoid ops limit
Keep track of the total number of ops the constructed script will have during miniscript_stable and miniscript_smart fuzzers' GenNode, so it can abort early if the 201 ops limit would be exceeded. Also add a self-check that the final constructed node has the predicted ops size limit, so we know the fuzzer's logic for keeping track of this is correct.
1 parent 213fffa commit bcec5ab

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

src/script/miniscript.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,9 @@ struct Node {
11361136
//! Return the maximum number of ops needed to satisfy this script non-malleably.
11371137
uint32_t GetOps() const { return ops.count + ops.sat.value; }
11381138

1139+
//! Return the number of ops in the script (not counting the dynamic ones that depend on execution).
1140+
uint32_t GetStaticOps() const { return ops.count; }
1141+
11391142
//! Check the ops limit of this script against the consensus limit.
11401143
bool CheckOpsLimit() const { return GetOps() <= MAX_OPS_PER_SCRIPT; }
11411144

src/test/fuzz/miniscript.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,8 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
767767
std::vector<NodeRef> stack;
768768
/** The queue of instructions. */
769769
std::vector<std::pair<Type, std::optional<NodeInfo>>> todo{{root_type, {}}};
770+
/** Predict the number of (static) script ops. */
771+
uint32_t ops{0};
770772

771773
while (!todo.empty()) {
772774
// The expected type we have to construct.
@@ -775,6 +777,72 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
775777
// Fragment/children have not been decided yet. Decide them.
776778
auto node_info = ConsumeNode(type_needed);
777779
if (!node_info) return {};
780+
// Update predicted resource limits.
781+
switch (node_info->fragment) {
782+
case Fragment::JUST_0:
783+
case Fragment::JUST_1:
784+
break;
785+
case Fragment::PK_K:
786+
break;
787+
case Fragment::PK_H:
788+
ops += 3;
789+
break;
790+
case Fragment::OLDER:
791+
case Fragment::AFTER:
792+
ops += 1;
793+
break;
794+
case Fragment::RIPEMD160:
795+
case Fragment::SHA256:
796+
case Fragment::HASH160:
797+
case Fragment::HASH256:
798+
ops += 4;
799+
break;
800+
case Fragment::ANDOR:
801+
ops += 3;
802+
break;
803+
case Fragment::AND_V:
804+
break;
805+
case Fragment::AND_B:
806+
case Fragment::OR_B:
807+
ops += 1;
808+
break;
809+
case Fragment::OR_C:
810+
ops += 2;
811+
break;
812+
case Fragment::OR_D:
813+
ops += 3;
814+
break;
815+
case Fragment::OR_I:
816+
ops += 3;
817+
break;
818+
case Fragment::THRESH:
819+
ops += node_info->subtypes.size();
820+
break;
821+
case Fragment::MULTI:
822+
ops += 1;
823+
break;
824+
case Fragment::WRAP_A:
825+
ops += 2;
826+
break;
827+
case Fragment::WRAP_S:
828+
ops += 1;
829+
break;
830+
case Fragment::WRAP_C:
831+
ops += 1;
832+
break;
833+
case Fragment::WRAP_D:
834+
ops += 3;
835+
break;
836+
case Fragment::WRAP_V:
837+
break;
838+
case Fragment::WRAP_J:
839+
ops += 4;
840+
break;
841+
case Fragment::WRAP_N:
842+
ops += 1;
843+
break;
844+
}
845+
if (ops > MAX_OPS_PER_SCRIPT) return {};
778846
auto subtypes = node_info->subtypes;
779847
todo.back().second = std::move(node_info);
780848
todo.reserve(todo.size() + subtypes.size());
@@ -814,12 +882,18 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
814882
assert(node->GetType() << type_needed);
815883
}
816884
if (!node->IsValid()) return {};
885+
// Update resource predictions.
886+
if (node->fragment == Fragment::WRAP_V && node->subs[0]->GetType() << "x"_mst) {
887+
ops += 1;
888+
}
889+
if (ops > MAX_OPS_PER_SCRIPT) return {};
817890
// Move it to the stack.
818891
stack.push_back(std::move(node));
819892
todo.pop_back();
820893
}
821894
}
822895
assert(stack.size() == 1);
896+
assert(stack[0]->GetStaticOps() == ops);
823897
stack[0]->DuplicateKeyCheck(KEY_COMP);
824898
return std::move(stack[0]);
825899
}

0 commit comments

Comments
 (0)