Skip to content

Commit 9c6074d

Browse files
Integrate Stack Analyzer into Chunker (#6)
* Test the analyzer * Fix: Stack change calculation * Disable op_if checks for now * Add debug info for OP_PICK and OP_ROLL * Change how we add stack_hints * Use same analyzer to carry over last_constant * No longer split scripts with a stack hint * Remove print * Remove unnecessary func and print last_constant * Panic at Debug * Print the latest executed opcode * Fix pushbytes debug_position * Use parent identifier for empty debug identifiers * No longer print last_opcode * Print script when throwing op_roll/pick error * Remove debug script print * Write analyzed chunk stats to file * Print stack inputs only * Implement a hard stack_limit for chunks * Implement a hard stack limit with analyzer * Print script function names in error message * Fix debug_position being off * Add function to check final state of stackstatus --------- Co-authored-by: Lukas <[email protected]>
1 parent 9bb968f commit 9c6074d

File tree

5 files changed

+566
-229
lines changed

5 files changed

+566
-229
lines changed

src/analyzer.rs

Lines changed: 177 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use crate::builder::{Block, StructuredScript};
2+
use crate::chunker::Chunk;
23
use bitcoin::blockdata::opcodes::Opcode;
34
use bitcoin::blockdata::script::{read_scriptint, Instruction};
45
use bitcoin::opcodes::all::*;
56
use bitcoin::script::PushBytes;
7+
use bitcoin::ScriptBuf;
8+
use script_macro::script;
69
use std::borrow::BorrowMut;
7-
use std::cmp::min;
10+
use std::cmp::{max, min};
811
use std::panic;
912

1013
#[derive(Debug, Clone, Default, PartialEq)]
@@ -15,6 +18,25 @@ pub struct StackStatus {
1518
pub altstack_changed: i32,
1619
}
1720

21+
impl StackStatus {
22+
pub fn total_stack(&self) -> i32 {
23+
self.stack_changed + self.altstack_changed
24+
}
25+
26+
pub fn is_valid_final_state_without_inputs(&self) -> bool {
27+
self.stack_changed == 1
28+
&& self.altstack_changed == 0
29+
&& self.deepest_altstack_accessed == 0
30+
&& self.deepest_stack_accessed == 0
31+
}
32+
33+
pub fn is_valid_final_state_with_inputs(&self) -> bool {
34+
self.stack_changed == 1
35+
&& self.altstack_changed == 0
36+
&& self.deepest_altstack_accessed == 0
37+
}
38+
}
39+
1840
#[derive(Debug, Clone)]
1941
enum IfStackEle {
2042
IfFlow(StackStatus),
@@ -24,11 +46,13 @@ enum IfStackEle {
2446

2547
#[derive(Debug, Clone)]
2648
pub struct StackAnalyzer {
49+
debug_script: StructuredScript,
50+
debug_position: usize,
2751
stack_status: StackStatus,
2852
// if_stack should be empty after analyzing
2953
if_stack: Vec<IfStackEle>,
3054
// last constant? for handling op_roll and op_pick
31-
last_constant: Option<i64>,
55+
pub last_constant: Option<i64>,
3256
}
3357

3458
impl Default for StackAnalyzer {
@@ -40,30 +64,102 @@ impl Default for StackAnalyzer {
4064
impl StackAnalyzer {
4165
pub fn new() -> Self {
4266
StackAnalyzer {
67+
debug_script: StructuredScript::new(""),
68+
debug_position: 0,
4369
stack_status: StackStatus::default(),
4470
if_stack: vec![],
4571
last_constant: None,
4672
}
4773
}
4874

49-
pub fn analyze_blocks(&mut self, blocks: &mut Vec<Box<StructuredScript>>) -> StackStatus {
50-
// println!("===============================");
51-
for block in blocks {
52-
// Maybe remove this clone?
53-
self.handle_sub_script(block.get_stack());
75+
pub fn with(start_stack: usize, start_altstack: usize, last_constant: Option<i64>) -> Self {
76+
StackAnalyzer {
77+
debug_script: StructuredScript::new(""),
78+
debug_position: 0,
79+
stack_status: StackStatus {
80+
deepest_stack_accessed: 0,
81+
stack_changed: start_stack as i32,
82+
deepest_altstack_accessed: 0,
83+
altstack_changed: start_altstack as i32,
84+
},
85+
if_stack: vec![],
86+
last_constant,
87+
}
88+
}
89+
90+
pub fn total_stack_change(&self) -> i32 {
91+
self.stack_status.altstack_changed + self.stack_status.stack_changed
92+
}
93+
94+
pub fn reset(&mut self) {
95+
self.debug_script = StructuredScript::new("");
96+
self.debug_position = 0;
97+
self.if_stack = vec![];
98+
self.stack_status = StackStatus::default();
99+
}
100+
101+
pub fn analyze_blocks(&mut self, scripts: &Vec<Box<StructuredScript>>) {
102+
for script in scripts {
103+
self.debug_script = *script.clone();
104+
self.debug_position = 0;
105+
match script.stack_hint() {
106+
Some(stack_hint) => {
107+
self.debug_position += script.len();
108+
self.stack_change(stack_hint)
109+
}
110+
None => self.merge_script(script),
111+
};
112+
}
113+
}
114+
pub fn analyze_blocks_status(&mut self, scripts: &Vec<Box<StructuredScript>>) -> StackStatus {
115+
for script in scripts {
116+
self.debug_script = *script.clone();
117+
self.debug_position = 0;
118+
match script.stack_hint() {
119+
Some(stack_hint) => {
120+
self.debug_position += script.len();
121+
self.stack_change(stack_hint)
122+
}
123+
None => self.merge_script(script),
124+
};
54125
}
55126
self.get_status()
56127
}
57128

58-
pub fn analyze(&mut self, builder: &mut StructuredScript) -> StackStatus {
59-
for block in builder.blocks.iter_mut() {
129+
pub fn analyze_status(&mut self, script: &StructuredScript) -> StackStatus {
130+
self.debug_script = script.clone();
131+
self.debug_position = 0;
132+
match script.stack_hint() {
133+
Some(stack_hint) => self.stack_change(stack_hint),
134+
None => self.merge_script(script),
135+
};
136+
self.get_status()
137+
}
138+
139+
pub fn analyze(&mut self, script: &StructuredScript) {
140+
self.debug_script = script.clone();
141+
self.debug_position = 0;
142+
match script.stack_hint() {
143+
Some(stack_hint) => self.stack_change(stack_hint),
144+
None => self.merge_script(script),
145+
};
146+
}
147+
148+
pub fn merge_script(&mut self, builder: &StructuredScript) {
149+
for block in builder.blocks.iter() {
60150
match block {
61151
Block::Call(id) => {
62152
let called_script = builder
63153
.script_map
64-
.get_mut(id)
154+
.get(id)
65155
.expect("Missing entry for a called script");
66-
self.handle_sub_script(called_script.get_stack());
156+
match called_script.stack_hint() {
157+
Some(stack_hint) => {
158+
self.debug_position += called_script.len();
159+
self.stack_change(stack_hint)
160+
}
161+
None => self.merge_script(called_script),
162+
};
67163
}
68164
Block::Script(block_script) => {
69165
for instruct in block_script.instructions() {
@@ -84,10 +180,10 @@ impl StackAnalyzer {
84180
}
85181
}
86182
}
87-
self.stack_status.clone()
88183
}
89184

90185
pub fn handle_push_slice(&mut self, bytes: &PushBytes) {
186+
self.debug_position += bytes.len() + 1;
91187
if let Ok(x) = read_scriptint(bytes.as_bytes()) {
92188
// if i64(data) < 1000, last_constant is true
93189
if (0..=1000).contains(&x) {
@@ -120,13 +216,29 @@ impl StackAnalyzer {
120216
}
121217
}
122218

219+
pub fn plain_altstack_status(x: i32, y: i32) -> StackStatus {
220+
StackStatus {
221+
deepest_stack_accessed: 0,
222+
stack_changed: 0,
223+
deepest_altstack_accessed: x,
224+
altstack_changed: y,
225+
}
226+
}
227+
123228
pub fn handle_opcode(&mut self, opcode: Opcode) {
124229
// handle if/else flow
125230
match opcode {
126231
OP_IF | OP_NOTIF => {
127232
self.stack_change(Self::opcode_stack_table(&opcode));
128233
self.if_stack.push(IfStackEle::IfFlow(Default::default()));
129234
}
235+
OP_RESERVED => {
236+
panic!(
237+
"found DEBUG in {:?}\n entire builder: {:?}",
238+
self.debug_script.debug_info(self.debug_position),
239+
self.debug_script
240+
)
241+
}
130242
OP_ELSE => match self.if_stack.pop().unwrap() {
131243
IfStackEle::IfFlow(i) => {
132244
self.if_stack
@@ -136,38 +248,47 @@ impl StackAnalyzer {
136248
panic!("shouldn't happend")
137249
}
138250
},
139-
OP_ENDIF => match self.if_stack.pop().unwrap() {
140-
IfStackEle::IfFlow(stack_status) => {
141-
assert_eq!(
251+
OP_ENDIF => {
252+
match self.if_stack.pop().unwrap() {
253+
IfStackEle::IfFlow(stack_status) => {
254+
assert_eq!(
142255
stack_status.stack_changed, 0,
143-
"only_if_flow shouldn't change stack status {:?}",
144-
stack_status
256+
"only_if_flow shouldn't change stack status {:?}\n\tat pos {:?}\n\tin {:?}",
257+
stack_status, self.debug_position, self.debug_script.debug_info(self.debug_position + 1)
145258
);
146-
assert_eq!(
259+
assert_eq!(
147260
stack_status.altstack_changed, 0,
148-
"only_if_flow shouldn't change alt stack status {:?},",
149-
stack_status
150-
);
151-
self.stack_change(stack_status);
152-
}
153-
IfStackEle::ElseFlow((stack_status1, stack_status2)) => {
154-
assert_eq!(
155-
stack_status1.stack_changed, stack_status2.stack_changed,
156-
"if_flow and else_flow should change stack in the same way"
261+
"only_if_flow shouldn't change altstack status {:?}\n\tat pos {:?}\n\tin {:?}",
262+
stack_status, self.debug_position, self.debug_script.debug_info(self.debug_position + 1)
157263
);
158-
assert_eq!(
159-
stack_status1.altstack_changed, stack_status2.altstack_changed,
160-
"if_flow and else_flow should change alt stack in the same way"
161-
);
162-
self.stack_change(Self::min_status(stack_status1, stack_status2));
264+
self.stack_change(stack_status);
265+
}
266+
IfStackEle::ElseFlow((stack_status1, stack_status2)) => {
267+
assert_eq!(
268+
stack_status1.stack_changed,
269+
stack_status2.stack_changed,
270+
"if_flow and else_flow should change stack in the same way in {:?}",
271+
self.debug_script.debug_info(self.debug_position + 1)
272+
);
273+
assert_eq!(
274+
stack_status1.altstack_changed,
275+
stack_status2.altstack_changed,
276+
"if_flow and else_flow should change altstack in the same way in {:?}",
277+
self.debug_script.debug_info(self.debug_position + 1)
278+
);
279+
self.stack_change(Self::min_status(stack_status1, stack_status2));
280+
}
163281
}
164-
},
282+
}
165283
OP_PICK => match self.last_constant {
166284
Some(x) => {
167285
self.stack_change(Self::plain_stack_status(-((x + 1 + 1) as i32), 0));
168286
}
169287
None => {
170-
panic!("need to be handled manually for op_pick")
288+
panic!(
289+
"need to be handled manually for op_pick in {:?}",
290+
self.debug_script.debug_info(self.debug_position)
291+
)
171292
}
172293
},
173294
OP_ROLL => match self.last_constant {
@@ -176,29 +297,30 @@ impl StackAnalyzer {
176297
// for [x2, x1, x0, 2, OP_PICK]
177298
}
178299
None => {
179-
panic!("need to be handled manually for op_roll")
300+
panic!(
301+
"need to be handled manually for op_roll in {:?}",
302+
self.debug_script.debug_info(self.debug_position)
303+
)
180304
}
181305
},
182306
_ => {
183307
self.stack_change(Self::opcode_stack_table(&opcode));
184308
}
185309
}
310+
self.debug_position += 1;
186311

187312
// handle last constant, used by op_roll and op_pick
188313
match opcode {
189314
OP_PUSHNUM_1 | OP_PUSHNUM_2 | OP_PUSHNUM_3 | OP_PUSHNUM_4 | OP_PUSHNUM_5
190315
| OP_PUSHNUM_6 | OP_PUSHNUM_7 | OP_PUSHNUM_8 | OP_PUSHNUM_9 | OP_PUSHNUM_10
191316
| OP_PUSHNUM_11 | OP_PUSHNUM_12 | OP_PUSHNUM_13 | OP_PUSHNUM_14 | OP_PUSHNUM_15
192317
| OP_PUSHNUM_16 => self.last_constant = Some((opcode.to_u8() - 0x50) as i64),
318+
OP_DUP => (),
319+
OP_PUSHBYTES_0 => self.last_constant = Some(0),
193320
_ => self.last_constant = None,
194321
}
195322
}
196323

197-
pub fn handle_sub_script(&mut self, stack_status: StackStatus) {
198-
self.last_constant = None;
199-
self.stack_change(stack_status);
200-
}
201-
202324
pub fn get_status(&self) -> StackStatus {
203325
assert!(self.if_stack.is_empty(), "if stack is not empty");
204326
self.stack_status.clone()
@@ -222,10 +344,23 @@ impl StackAnalyzer {
222344
let x = status.deepest_altstack_accessed.borrow_mut();
223345
let y = status.altstack_changed.borrow_mut();
224346

225-
*i = min(*i, (*j) + stack_status.deepest_stack_accessed);
347+
// The second script's deepest stack access is reduced if there are still elements left on
348+
// the stack from script 1.
349+
let elements_on_intermediate_stack = (*j) - (*i);
350+
let elements_on_intermediate_altstack = (*y) - (*x);
351+
assert!(elements_on_intermediate_stack >= 0, "Script1 changes the stack by more items than it accesses. This means there is a bug in the stack_change() logic.");
352+
assert!(elements_on_intermediate_stack >= 0, "Script1 changes the altstack by more items than it accesses. This means there is a bug in the stack_change() logic.");
353+
354+
*i += min(
355+
0,
356+
stack_status.deepest_stack_accessed + elements_on_intermediate_stack,
357+
);
226358
*j += stack_status.stack_changed;
227359

228-
*x = min(*x, (*y) + stack_status.deepest_altstack_accessed);
360+
*x += min(
361+
0,
362+
stack_status.deepest_altstack_accessed + elements_on_intermediate_altstack,
363+
);
229364
*y += stack_status.altstack_changed;
230365
}
231366

0 commit comments

Comments
 (0)