chik_consensus/
opcodes.rs

1use klvmr::allocator::{Allocator, NodePtr, SExp};
2use klvmr::cost::Cost;
3
4pub type ConditionOpcode = u16;
5
6// AGG_SIG is ascii "1"
7pub const AGG_SIG_PARENT: ConditionOpcode = 43;
8pub const AGG_SIG_PUZZLE: ConditionOpcode = 44;
9pub const AGG_SIG_AMOUNT: ConditionOpcode = 45;
10pub const AGG_SIG_PUZZLE_AMOUNT: ConditionOpcode = 46;
11pub const AGG_SIG_PARENT_AMOUNT: ConditionOpcode = 47;
12pub const AGG_SIG_PARENT_PUZZLE: ConditionOpcode = 48;
13pub const AGG_SIG_UNSAFE: ConditionOpcode = 49;
14pub const AGG_SIG_ME: ConditionOpcode = 50;
15
16// the conditions below reserve coin amounts and have to be accounted for in
17// output totals
18pub const CREATE_COIN: ConditionOpcode = 51;
19pub const RESERVE_FEE: ConditionOpcode = 52;
20
21// the conditions below deal with announcements, for inter-coin communication
22pub const CREATE_COIN_ANNOUNCEMENT: ConditionOpcode = 60;
23pub const ASSERT_COIN_ANNOUNCEMENT: ConditionOpcode = 61;
24pub const CREATE_PUZZLE_ANNOUNCEMENT: ConditionOpcode = 62;
25pub const ASSERT_PUZZLE_ANNOUNCEMENT: ConditionOpcode = 63;
26pub const ASSERT_CONCURRENT_SPEND: ConditionOpcode = 64;
27pub const ASSERT_CONCURRENT_PUZZLE: ConditionOpcode = 65;
28
29pub const SEND_MESSAGE: ConditionOpcode = 66;
30pub const RECEIVE_MESSAGE: ConditionOpcode = 67;
31
32// the conditions below let coins inquire about themselves
33pub const ASSERT_MY_COIN_ID: ConditionOpcode = 70;
34pub const ASSERT_MY_PARENT_ID: ConditionOpcode = 71;
35pub const ASSERT_MY_PUZZLEHASH: ConditionOpcode = 72;
36pub const ASSERT_MY_AMOUNT: ConditionOpcode = 73;
37pub const ASSERT_MY_BIRTH_SECONDS: ConditionOpcode = 74;
38pub const ASSERT_MY_BIRTH_HEIGHT: ConditionOpcode = 75;
39pub const ASSERT_EPHEMERAL: ConditionOpcode = 76;
40
41// the conditions below ensure that we're "far enough" in the future
42// wall-clock time
43pub const ASSERT_SECONDS_RELATIVE: ConditionOpcode = 80;
44pub const ASSERT_SECONDS_ABSOLUTE: ConditionOpcode = 81;
45
46// block index
47pub const ASSERT_HEIGHT_RELATIVE: ConditionOpcode = 82;
48pub const ASSERT_HEIGHT_ABSOLUTE: ConditionOpcode = 83;
49
50// spend is not valid if block timestamp exceeds the specified one
51pub const ASSERT_BEFORE_SECONDS_RELATIVE: ConditionOpcode = 84;
52pub const ASSERT_BEFORE_SECONDS_ABSOLUTE: ConditionOpcode = 85;
53
54// spend is not valid if block height exceeds the specified height
55pub const ASSERT_BEFORE_HEIGHT_RELATIVE: ConditionOpcode = 86;
56pub const ASSERT_BEFORE_HEIGHT_ABSOLUTE: ConditionOpcode = 87;
57
58// no-op condition
59pub const REMARK: ConditionOpcode = 1;
60
61// takes its cost as the first parameter, followed by future extensions
62// the cost is specified in increments of 10000, to keep the values smaller
63pub const SOFTFORK: ConditionOpcode = 90;
64
65pub const CREATE_COIN_COST: Cost = 1_800_000;
66pub const AGG_SIG_COST: Cost = 1_200_000;
67
68pub const GENERIC_CONDITION_COST: Cost = 500;
69pub const FREE_CONDITIONS: usize = 100;
70
71// 2-byte condition opcodes have costs according to this table:
72
73// the values `100 * (17 ** idx)/(16 ** idx)` rounded to three significant decimal figures
74
75const fn calculate_cost_table() -> [u64; 256] {
76    let (a, b) = (17, 16);
77    let mut s = [0; 256];
78    let (mut num, mut den) = (100_u64, 1_u64);
79    let max = 1 << 59;
80    let mut idx = 0;
81    while idx < 256 {
82        let v = num / den;
83        let mut power_of_ten = 1000;
84        while power_of_ten < v {
85            power_of_ten *= 10;
86        }
87        power_of_ten /= 1000;
88        s[idx] = (v / power_of_ten) * power_of_ten;
89        num *= a;
90        den *= b;
91        while num > max {
92            num >>= 5;
93            den >>= 5;
94        }
95        idx += 1;
96    }
97    s
98}
99
100const COSTS: [Cost; 256] = calculate_cost_table();
101
102pub fn compute_unknown_condition_cost(op: ConditionOpcode) -> Cost {
103    if op < 256 {
104        0
105    } else {
106        COSTS[(op & 0xff) as usize]
107    }
108}
109
110pub fn parse_opcode(a: &Allocator, op: NodePtr, _flags: u32) -> Option<ConditionOpcode> {
111    let buf = match a.sexp(op) {
112        SExp::Atom => a.atom(op),
113        SExp::Pair(..) => return None,
114    };
115    let buf = buf.as_ref();
116    if buf.len() == 2 {
117        if buf[0] == 0 {
118            // no redundant leading zeroes
119            None
120        } else {
121            // These are 2-byte condition codes whose first byte is non-zero
122            Some(ConditionOpcode::from_be_bytes(buf.try_into().unwrap()))
123        }
124    } else if buf.len() == 1 {
125        let b0 = ConditionOpcode::from(buf[0]);
126        match b0 {
127            AGG_SIG_UNSAFE
128            | AGG_SIG_ME
129            | CREATE_COIN
130            | RESERVE_FEE
131            | CREATE_COIN_ANNOUNCEMENT
132            | ASSERT_COIN_ANNOUNCEMENT
133            | CREATE_PUZZLE_ANNOUNCEMENT
134            | ASSERT_PUZZLE_ANNOUNCEMENT
135            | ASSERT_MY_COIN_ID
136            | ASSERT_MY_PARENT_ID
137            | ASSERT_MY_PUZZLEHASH
138            | ASSERT_MY_AMOUNT
139            | ASSERT_SECONDS_RELATIVE
140            | ASSERT_SECONDS_ABSOLUTE
141            | ASSERT_HEIGHT_RELATIVE
142            | ASSERT_HEIGHT_ABSOLUTE
143            | REMARK
144            | ASSERT_BEFORE_SECONDS_RELATIVE
145            | ASSERT_BEFORE_SECONDS_ABSOLUTE
146            | ASSERT_BEFORE_HEIGHT_RELATIVE
147            | ASSERT_BEFORE_HEIGHT_ABSOLUTE
148            | ASSERT_CONCURRENT_SPEND
149            | ASSERT_CONCURRENT_PUZZLE
150            | ASSERT_MY_BIRTH_SECONDS
151            | ASSERT_MY_BIRTH_HEIGHT
152            | ASSERT_EPHEMERAL
153            | SOFTFORK
154            | AGG_SIG_PARENT
155            | AGG_SIG_PUZZLE
156            | AGG_SIG_AMOUNT
157            | AGG_SIG_PUZZLE_AMOUNT
158            | AGG_SIG_PARENT_AMOUNT
159            | AGG_SIG_PARENT_PUZZLE
160            | SEND_MESSAGE
161            | RECEIVE_MESSAGE => Some(b0),
162            _ => None,
163        }
164    } else {
165        None
166    }
167}
168
169#[cfg(test)]
170fn opcode_tester(a: &mut Allocator, val: &[u8], flags: u32) -> Option<ConditionOpcode> {
171    let v = a.new_atom(val).unwrap();
172    parse_opcode(a, v, flags)
173}
174
175#[cfg(test)]
176use rstest::rstest;
177
178#[cfg(test)]
179#[rstest]
180// leading zeros are not allowed, it makes it a different value
181#[case(&[ASSERT_HEIGHT_ABSOLUTE as u8, 0, 0], None)]
182#[case(&[0, ASSERT_HEIGHT_ABSOLUTE as u8], None)]
183#[case(&[0], None)]
184// all condition codes
185#[case(&[AGG_SIG_UNSAFE as u8], Some(AGG_SIG_UNSAFE))]
186#[case(&[AGG_SIG_ME as u8], Some(AGG_SIG_ME))]
187#[case(&[CREATE_COIN as u8], Some(CREATE_COIN))]
188#[case(&[RESERVE_FEE as u8], Some(RESERVE_FEE))]
189#[case(&[CREATE_COIN_ANNOUNCEMENT as u8], Some(CREATE_COIN_ANNOUNCEMENT))]
190#[case(&[ASSERT_COIN_ANNOUNCEMENT as u8], Some(ASSERT_COIN_ANNOUNCEMENT))]
191#[case(&[CREATE_PUZZLE_ANNOUNCEMENT as u8], Some(CREATE_PUZZLE_ANNOUNCEMENT))]
192#[case(&[ASSERT_PUZZLE_ANNOUNCEMENT as u8], Some(ASSERT_PUZZLE_ANNOUNCEMENT))]
193#[case(&[ASSERT_CONCURRENT_SPEND as u8], Some(ASSERT_CONCURRENT_SPEND))]
194#[case(&[ASSERT_CONCURRENT_PUZZLE as u8], Some(ASSERT_CONCURRENT_PUZZLE))]
195#[case(&[ASSERT_MY_COIN_ID as u8], Some(ASSERT_MY_COIN_ID))]
196#[case(&[ASSERT_MY_PARENT_ID as u8], Some(ASSERT_MY_PARENT_ID))]
197#[case(&[ASSERT_MY_PUZZLEHASH as u8], Some(ASSERT_MY_PUZZLEHASH))]
198#[case(&[ASSERT_MY_AMOUNT as u8], Some(ASSERT_MY_AMOUNT))]
199#[case(&[ASSERT_MY_BIRTH_SECONDS as u8], Some(ASSERT_MY_BIRTH_SECONDS))]
200#[case(&[ASSERT_MY_BIRTH_HEIGHT as u8], Some(ASSERT_MY_BIRTH_HEIGHT))]
201#[case(&[ASSERT_EPHEMERAL as u8], Some(ASSERT_EPHEMERAL))]
202#[case(&[ASSERT_SECONDS_RELATIVE as u8], Some(ASSERT_SECONDS_RELATIVE))]
203#[case(&[ASSERT_SECONDS_ABSOLUTE as u8], Some(ASSERT_SECONDS_ABSOLUTE))]
204#[case(&[ASSERT_HEIGHT_RELATIVE as u8], Some(ASSERT_HEIGHT_RELATIVE))]
205#[case(&[ASSERT_HEIGHT_ABSOLUTE as u8], Some(ASSERT_HEIGHT_ABSOLUTE))]
206#[case(&[ASSERT_BEFORE_SECONDS_RELATIVE as u8], Some(ASSERT_BEFORE_SECONDS_RELATIVE))]
207#[case(&[ASSERT_BEFORE_SECONDS_ABSOLUTE as u8], Some(ASSERT_BEFORE_SECONDS_ABSOLUTE))]
208#[case(&[ASSERT_BEFORE_HEIGHT_RELATIVE as u8], Some(ASSERT_BEFORE_HEIGHT_RELATIVE))]
209#[case(&[ASSERT_BEFORE_HEIGHT_ABSOLUTE as u8], Some(ASSERT_BEFORE_HEIGHT_ABSOLUTE))]
210#[case(&[REMARK as u8], Some(REMARK))]
211fn test_parse_opcode(#[case] input: &[u8], #[case] expected: Option<ConditionOpcode>) {
212    let mut a = Allocator::new();
213    assert_eq!(opcode_tester(&mut a, input, 0), expected);
214    assert_eq!(opcode_tester(&mut a, input, 0), expected);
215}
216
217#[cfg(test)]
218#[rstest]
219#[case(&[AGG_SIG_UNSAFE as u8], Some(AGG_SIG_UNSAFE))]
220#[case(&[AGG_SIG_ME as u8], Some(AGG_SIG_ME))]
221#[case(&[CREATE_COIN as u8], Some(CREATE_COIN))]
222// the SOFTOFORK and new AGG_SIG_* condition is only recognized when the flag is set
223#[case(&[SOFTFORK as u8], Some(SOFTFORK))]
224#[case(&[AGG_SIG_PARENT as u8], Some(AGG_SIG_PARENT))]
225#[case(&[AGG_SIG_PUZZLE as u8], Some(AGG_SIG_PUZZLE))]
226#[case(&[AGG_SIG_AMOUNT as u8], Some(AGG_SIG_AMOUNT))]
227#[case(&[AGG_SIG_PUZZLE_AMOUNT as u8], Some(AGG_SIG_PUZZLE_AMOUNT))]
228#[case(&[AGG_SIG_PARENT_AMOUNT as u8], Some(AGG_SIG_PARENT_AMOUNT))]
229#[case(&[AGG_SIG_PARENT_PUZZLE as u8], Some(AGG_SIG_PARENT_PUZZLE))]
230#[case(&[ASSERT_EPHEMERAL as u8], Some(ASSERT_EPHEMERAL))]
231#[case(&[ASSERT_BEFORE_SECONDS_RELATIVE as u8], Some(ASSERT_BEFORE_SECONDS_RELATIVE))]
232#[case(&[SEND_MESSAGE as u8], Some(SEND_MESSAGE))]
233#[case(&[RECEIVE_MESSAGE as u8], Some(RECEIVE_MESSAGE))]
234fn test_parse_opcode_softfork(#[case] input: &[u8], #[case] expected: Option<ConditionOpcode>) {
235    let mut a = Allocator::new();
236    assert_eq!(opcode_tester(&mut a, input, 0), expected);
237}
238
239#[test]
240fn test_parse_invalid_opcode() {
241    // a pair is never a valid condition
242    let mut a = Allocator::new();
243    let v1 = a.new_atom(&[0]).unwrap();
244    let v2 = a.new_atom(&[0]).unwrap();
245    let p = a.new_pair(v1, v2).unwrap();
246    assert_eq!(parse_opcode(&a, p, 0), None);
247}