Skip to content

Commit

Permalink
introduce unknown conditions with cost. This opens up the possibility…
Browse files Browse the repository at this point in the history
… to soft-fork in new conditions with cost in the future. 256 condition opcodes are reserved assigned cost ranging from 1000000 to 2400000
  • Loading branch information
arvidn committed May 30, 2023
1 parent da83fa8 commit 575ce60
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 82 deletions.
45 changes: 37 additions & 8 deletions src/gen/conditions.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use super::coin_id::compute_coin_id;
use super::condition_sanitizers::{parse_amount, sanitize_announce_msg, sanitize_hash};
use super::opcodes::{
parse_opcode, ConditionOpcode, AGG_SIG_COST, AGG_SIG_ME, AGG_SIG_UNSAFE,
ASSERT_BEFORE_HEIGHT_ABSOLUTE, ASSERT_BEFORE_HEIGHT_RELATIVE, ASSERT_BEFORE_SECONDS_ABSOLUTE,
ASSERT_BEFORE_SECONDS_RELATIVE, ASSERT_COIN_ANNOUNCEMENT, ASSERT_CONCURRENT_PUZZLE,
ASSERT_CONCURRENT_SPEND, ASSERT_EPHEMERAL, ASSERT_HEIGHT_ABSOLUTE, ASSERT_HEIGHT_RELATIVE,
ASSERT_MY_AMOUNT, ASSERT_MY_BIRTH_HEIGHT, ASSERT_MY_BIRTH_SECONDS, ASSERT_MY_COIN_ID,
ASSERT_MY_PARENT_ID, ASSERT_MY_PUZZLEHASH, ASSERT_PUZZLE_ANNOUNCEMENT, ASSERT_SECONDS_ABSOLUTE,
ASSERT_SECONDS_RELATIVE, CREATE_COIN, CREATE_COIN_ANNOUNCEMENT, CREATE_COIN_COST,
CREATE_PUZZLE_ANNOUNCEMENT, REMARK, RESERVE_FEE, SOFTFORK,
compute_unknown_condition_cost, parse_opcode, ConditionOpcode, AGG_SIG_COST, AGG_SIG_ME,
AGG_SIG_UNSAFE, ASSERT_BEFORE_HEIGHT_ABSOLUTE, ASSERT_BEFORE_HEIGHT_RELATIVE,
ASSERT_BEFORE_SECONDS_ABSOLUTE, ASSERT_BEFORE_SECONDS_RELATIVE, ASSERT_COIN_ANNOUNCEMENT,
ASSERT_CONCURRENT_PUZZLE, ASSERT_CONCURRENT_SPEND, ASSERT_EPHEMERAL, ASSERT_HEIGHT_ABSOLUTE,
ASSERT_HEIGHT_RELATIVE, ASSERT_MY_AMOUNT, ASSERT_MY_BIRTH_HEIGHT, ASSERT_MY_BIRTH_SECONDS,
ASSERT_MY_COIN_ID, ASSERT_MY_PARENT_ID, ASSERT_MY_PUZZLEHASH, ASSERT_PUZZLE_ANNOUNCEMENT,
ASSERT_SECONDS_ABSOLUTE, ASSERT_SECONDS_RELATIVE, CREATE_COIN, CREATE_COIN_ANNOUNCEMENT,
CREATE_COIN_COST, CREATE_PUZZLE_ANNOUNCEMENT, REMARK, RESERVE_FEE, SOFTFORK,
};
use super::sanitize_int::{sanitize_uint, SanitizedUint};
use super::validation_error::{first, next, rest, ErrorCode, ValidationErr};
Expand Down Expand Up @@ -217,6 +217,15 @@ pub fn parse_args(
}
}
}
256..=511 => {
// All of these conditions are unknown
// but they have costs (when ENABLE_SOFTFORK_CONDITION is enabled)
if (flags & NO_UNKNOWN_CONDS) != 0 {
Err(ValidationErr(c, ErrorCode::InvalidConditionOpcode))
} else {
Ok(Condition::Softfork(compute_unknown_condition_cost(op)))
}
}
RESERVE_FEE => {
maybe_check_args_terminator(a, c, flags)?;
let fee = parse_amount(a, first(a, c)?, ErrorCode::ReserveFeeConditionFailed)?;
Expand Down Expand Up @@ -3706,6 +3715,26 @@ fn test_relative_condition_on_ephemeral(
#[case("((90 (1100000 )", 11000000000)]
// additional arguments are ignored
#[case("((90 (1 ( 42 ( 1337 )", 10000)]
// reserved opcodes with fixed cost
#[case("((256 )", 1000000)]
#[case("((257 )", 1200000)]
#[case("((258 )", 1400000)]
#[case("((259 )", 1600000)]
#[case("((260 )", 1800000)]
#[case("((261 )", 2000000)]
#[case("((262 )", 2200000)]
#[case("((263 )", 2400000)]
#[case("((264 )", 1000000)]
#[case("((265 )", 1200000)]
#[case("((266 )", 1400000)]
#[case("((504 )", 1000000)]
#[case("((505 )", 1200000)]
#[case("((506 )", 1400000)]
#[case("((507 )", 1600000)]
#[case("((508 )", 1800000)]
#[case("((509 )", 2000000)]
#[case("((510 )", 2200000)]
#[case("((511 )", 2400000)]
fn test_softfork_condition(#[case] conditions: &str, #[case] expected_cost: Cost) {
// SOFTFORK (90)
let (_, spends) = cond_test_flag(
Expand Down
169 changes: 95 additions & 74 deletions src/gen/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::gen::flags::ENABLE_SOFTFORK_CONDITION;
use clvmr::allocator::{Allocator, NodePtr, SExp};
use clvmr::cost::Cost;

pub type ConditionOpcode = u8;
pub type ConditionOpcode = u16;

// AGG_SIG is ascii "1"
pub const AGG_SIG_UNSAFE: ConditionOpcode = 49;
Expand Down Expand Up @@ -60,53 +60,74 @@ pub const SOFTFORK: ConditionOpcode = 90;
pub const CREATE_COIN_COST: Cost = 1800000;
pub const AGG_SIG_COST: Cost = 1200000;

// when ENABLE_SOFTFORK_CONDITION is enabled
// 2-byte condition opcodes whose first byte is 1 have costs, despite being unknown:

// cost = 1000000 + (byte[1] & 0x7) * 200000

pub fn compute_unknown_condition_cost(op: ConditionOpcode) -> Cost {
match op {
256..=511 => 1000000 + ((op & 0x7) as Cost) * 200000,
_ => 0,
}
}

pub fn parse_opcode(a: &Allocator, op: NodePtr, flags: u32) -> Option<ConditionOpcode> {
let buf = match a.sexp(op) {
SExp::Atom(_) => a.atom(op),
_ => return None,
};
if buf.len() != 1 {
return None;
}

match buf[0] {
AGG_SIG_UNSAFE
| AGG_SIG_ME
| CREATE_COIN
| RESERVE_FEE
| CREATE_COIN_ANNOUNCEMENT
| ASSERT_COIN_ANNOUNCEMENT
| CREATE_PUZZLE_ANNOUNCEMENT
| ASSERT_PUZZLE_ANNOUNCEMENT
| ASSERT_MY_COIN_ID
| ASSERT_MY_PARENT_ID
| ASSERT_MY_PUZZLEHASH
| ASSERT_MY_AMOUNT
| ASSERT_SECONDS_RELATIVE
| ASSERT_SECONDS_ABSOLUTE
| ASSERT_HEIGHT_RELATIVE
| ASSERT_HEIGHT_ABSOLUTE
| REMARK => Some(buf[0]),
_ => {
if (flags & ENABLE_SOFTFORK_CONDITION) != 0 && buf[0] == SOFTFORK {
Some(buf[0])
} else if (flags & ENABLE_ASSERT_BEFORE) != 0 {
match buf[0] {
ASSERT_BEFORE_SECONDS_RELATIVE
| ASSERT_BEFORE_SECONDS_ABSOLUTE
| ASSERT_BEFORE_HEIGHT_RELATIVE
| ASSERT_BEFORE_HEIGHT_ABSOLUTE
| ASSERT_CONCURRENT_SPEND
| ASSERT_CONCURRENT_PUZZLE
| ASSERT_MY_BIRTH_SECONDS
| ASSERT_MY_BIRTH_HEIGHT
| ASSERT_EPHEMERAL => Some(buf[0]),
_ => None,
if buf.len() == 2 && (flags & ENABLE_SOFTFORK_CONDITION) != 0 {
let b0 = buf[0];
if b0 != 1 {
None
} else {
// These are 2-byte condition codes whose first byte is 1
Some(ConditionOpcode::from_be_bytes(buf.try_into().unwrap()))
}
} else if buf.len() == 1 {
let b0 = buf[0] as ConditionOpcode;
match b0 {
AGG_SIG_UNSAFE
| AGG_SIG_ME
| CREATE_COIN
| RESERVE_FEE
| CREATE_COIN_ANNOUNCEMENT
| ASSERT_COIN_ANNOUNCEMENT
| CREATE_PUZZLE_ANNOUNCEMENT
| ASSERT_PUZZLE_ANNOUNCEMENT
| ASSERT_MY_COIN_ID
| ASSERT_MY_PARENT_ID
| ASSERT_MY_PUZZLEHASH
| ASSERT_MY_AMOUNT
| ASSERT_SECONDS_RELATIVE
| ASSERT_SECONDS_ABSOLUTE
| ASSERT_HEIGHT_RELATIVE
| ASSERT_HEIGHT_ABSOLUTE
| REMARK => Some(b0),
_ => {
if (flags & ENABLE_SOFTFORK_CONDITION) != 0 && b0 == SOFTFORK {
Some(b0)
} else if (flags & ENABLE_ASSERT_BEFORE) != 0 {
match b0 {
ASSERT_BEFORE_SECONDS_RELATIVE
| ASSERT_BEFORE_SECONDS_ABSOLUTE
| ASSERT_BEFORE_HEIGHT_RELATIVE
| ASSERT_BEFORE_HEIGHT_ABSOLUTE
| ASSERT_CONCURRENT_SPEND
| ASSERT_CONCURRENT_PUZZLE
| ASSERT_MY_BIRTH_SECONDS
| ASSERT_MY_BIRTH_HEIGHT
| ASSERT_EPHEMERAL => Some(b0),
_ => None,
}
} else {
None
}
} else {
None
}
}
} else {
None
}
}

Expand All @@ -122,36 +143,36 @@ use rstest::rstest;
#[cfg(test)]
#[rstest]
// leading zeros are not allowed, it makes it a different value
#[case(&[ASSERT_HEIGHT_ABSOLUTE, 0], None, None)]
#[case(&[0, ASSERT_HEIGHT_ABSOLUTE], None, None)]
#[case(&[ASSERT_HEIGHT_ABSOLUTE as u8, 0], None, None)]
#[case(&[0, ASSERT_HEIGHT_ABSOLUTE as u8], None, None)]
#[case(&[0], None, None)]
// all condition codes
#[case(&[AGG_SIG_UNSAFE], Some(AGG_SIG_UNSAFE), Some(AGG_SIG_UNSAFE))]
#[case(&[AGG_SIG_ME], Some(AGG_SIG_ME), Some(AGG_SIG_ME))]
#[case(&[CREATE_COIN], Some(CREATE_COIN), Some(CREATE_COIN))]
#[case(&[RESERVE_FEE], Some(RESERVE_FEE), Some(RESERVE_FEE))]
#[case(&[CREATE_COIN_ANNOUNCEMENT], Some(CREATE_COIN_ANNOUNCEMENT), Some(CREATE_COIN_ANNOUNCEMENT))]
#[case(&[ASSERT_COIN_ANNOUNCEMENT], Some(ASSERT_COIN_ANNOUNCEMENT), Some(ASSERT_COIN_ANNOUNCEMENT))]
#[case(&[CREATE_PUZZLE_ANNOUNCEMENT], Some(CREATE_PUZZLE_ANNOUNCEMENT), Some(CREATE_PUZZLE_ANNOUNCEMENT))]
#[case(&[ASSERT_PUZZLE_ANNOUNCEMENT], Some(ASSERT_PUZZLE_ANNOUNCEMENT), Some(ASSERT_PUZZLE_ANNOUNCEMENT))]
#[case(&[ASSERT_CONCURRENT_SPEND], None, Some(ASSERT_CONCURRENT_SPEND))]
#[case(&[ASSERT_CONCURRENT_PUZZLE], None, Some(ASSERT_CONCURRENT_PUZZLE))]
#[case(&[ASSERT_MY_COIN_ID], Some(ASSERT_MY_COIN_ID), Some(ASSERT_MY_COIN_ID))]
#[case(&[ASSERT_MY_PARENT_ID], Some(ASSERT_MY_PARENT_ID), Some(ASSERT_MY_PARENT_ID))]
#[case(&[ASSERT_MY_PUZZLEHASH], Some(ASSERT_MY_PUZZLEHASH), Some(ASSERT_MY_PUZZLEHASH))]
#[case(&[ASSERT_MY_AMOUNT], Some(ASSERT_MY_AMOUNT), Some(ASSERT_MY_AMOUNT))]
#[case(&[ASSERT_MY_BIRTH_SECONDS], None, Some(ASSERT_MY_BIRTH_SECONDS))]
#[case(&[ASSERT_MY_BIRTH_HEIGHT], None, Some(ASSERT_MY_BIRTH_HEIGHT))]
#[case(&[ASSERT_EPHEMERAL], None, Some(ASSERT_EPHEMERAL))]
#[case(&[ASSERT_SECONDS_RELATIVE],Some(ASSERT_SECONDS_RELATIVE) , Some(ASSERT_SECONDS_RELATIVE))]
#[case(&[ASSERT_SECONDS_ABSOLUTE],Some(ASSERT_SECONDS_ABSOLUTE) , Some(ASSERT_SECONDS_ABSOLUTE))]
#[case(&[ASSERT_HEIGHT_RELATIVE], Some(ASSERT_HEIGHT_RELATIVE), Some(ASSERT_HEIGHT_RELATIVE))]
#[case(&[ASSERT_HEIGHT_ABSOLUTE], Some(ASSERT_HEIGHT_ABSOLUTE), Some(ASSERT_HEIGHT_ABSOLUTE))]
#[case(&[ASSERT_BEFORE_SECONDS_RELATIVE], None, Some(ASSERT_BEFORE_SECONDS_RELATIVE))]
#[case(&[ASSERT_BEFORE_SECONDS_ABSOLUTE], None, Some(ASSERT_BEFORE_SECONDS_ABSOLUTE))]
#[case(&[ASSERT_BEFORE_HEIGHT_RELATIVE], None, Some(ASSERT_BEFORE_HEIGHT_RELATIVE))]
#[case(&[ASSERT_BEFORE_HEIGHT_ABSOLUTE], None, Some(ASSERT_BEFORE_HEIGHT_ABSOLUTE))]
#[case(&[REMARK], Some(REMARK), Some(REMARK))]
#[case(&[AGG_SIG_UNSAFE as u8], Some(AGG_SIG_UNSAFE), Some(AGG_SIG_UNSAFE))]
#[case(&[AGG_SIG_ME as u8], Some(AGG_SIG_ME), Some(AGG_SIG_ME))]
#[case(&[CREATE_COIN as u8], Some(CREATE_COIN), Some(CREATE_COIN))]
#[case(&[RESERVE_FEE as u8], Some(RESERVE_FEE), Some(RESERVE_FEE))]
#[case(&[CREATE_COIN_ANNOUNCEMENT as u8], Some(CREATE_COIN_ANNOUNCEMENT), Some(CREATE_COIN_ANNOUNCEMENT))]
#[case(&[ASSERT_COIN_ANNOUNCEMENT as u8], Some(ASSERT_COIN_ANNOUNCEMENT), Some(ASSERT_COIN_ANNOUNCEMENT))]
#[case(&[CREATE_PUZZLE_ANNOUNCEMENT as u8], Some(CREATE_PUZZLE_ANNOUNCEMENT), Some(CREATE_PUZZLE_ANNOUNCEMENT))]
#[case(&[ASSERT_PUZZLE_ANNOUNCEMENT as u8], Some(ASSERT_PUZZLE_ANNOUNCEMENT), Some(ASSERT_PUZZLE_ANNOUNCEMENT))]
#[case(&[ASSERT_CONCURRENT_SPEND as u8], None, Some(ASSERT_CONCURRENT_SPEND))]
#[case(&[ASSERT_CONCURRENT_PUZZLE as u8], None, Some(ASSERT_CONCURRENT_PUZZLE))]
#[case(&[ASSERT_MY_COIN_ID as u8], Some(ASSERT_MY_COIN_ID), Some(ASSERT_MY_COIN_ID))]
#[case(&[ASSERT_MY_PARENT_ID as u8], Some(ASSERT_MY_PARENT_ID), Some(ASSERT_MY_PARENT_ID))]
#[case(&[ASSERT_MY_PUZZLEHASH as u8], Some(ASSERT_MY_PUZZLEHASH), Some(ASSERT_MY_PUZZLEHASH))]
#[case(&[ASSERT_MY_AMOUNT as u8], Some(ASSERT_MY_AMOUNT), Some(ASSERT_MY_AMOUNT))]
#[case(&[ASSERT_MY_BIRTH_SECONDS as u8], None, Some(ASSERT_MY_BIRTH_SECONDS))]
#[case(&[ASSERT_MY_BIRTH_HEIGHT as u8], None, Some(ASSERT_MY_BIRTH_HEIGHT))]
#[case(&[ASSERT_EPHEMERAL as u8], None, Some(ASSERT_EPHEMERAL))]
#[case(&[ASSERT_SECONDS_RELATIVE as u8],Some(ASSERT_SECONDS_RELATIVE) , Some(ASSERT_SECONDS_RELATIVE))]
#[case(&[ASSERT_SECONDS_ABSOLUTE as u8],Some(ASSERT_SECONDS_ABSOLUTE) , Some(ASSERT_SECONDS_ABSOLUTE))]
#[case(&[ASSERT_HEIGHT_RELATIVE as u8], Some(ASSERT_HEIGHT_RELATIVE), Some(ASSERT_HEIGHT_RELATIVE))]
#[case(&[ASSERT_HEIGHT_ABSOLUTE as u8], Some(ASSERT_HEIGHT_ABSOLUTE), Some(ASSERT_HEIGHT_ABSOLUTE))]
#[case(&[ASSERT_BEFORE_SECONDS_RELATIVE as u8], None, Some(ASSERT_BEFORE_SECONDS_RELATIVE))]
#[case(&[ASSERT_BEFORE_SECONDS_ABSOLUTE as u8], None, Some(ASSERT_BEFORE_SECONDS_ABSOLUTE))]
#[case(&[ASSERT_BEFORE_HEIGHT_RELATIVE as u8], None, Some(ASSERT_BEFORE_HEIGHT_RELATIVE))]
#[case(&[ASSERT_BEFORE_HEIGHT_ABSOLUTE as u8], None, Some(ASSERT_BEFORE_HEIGHT_ABSOLUTE))]
#[case(&[REMARK as u8], Some(REMARK), Some(REMARK))]
fn test_parse_opcode(
#[case] input: &[u8],
#[case] expected: Option<ConditionOpcode>,
Expand Down Expand Up @@ -179,13 +200,13 @@ fn test_parse_opcode(

#[cfg(test)]
#[rstest]
#[case(&[AGG_SIG_UNSAFE], Some(AGG_SIG_UNSAFE), Some(AGG_SIG_UNSAFE))]
#[case(&[AGG_SIG_ME], Some(AGG_SIG_ME), Some(AGG_SIG_ME))]
#[case(&[CREATE_COIN], Some(CREATE_COIN), Some(CREATE_COIN))]
#[case(&[AGG_SIG_UNSAFE as u8], Some(AGG_SIG_UNSAFE), Some(AGG_SIG_UNSAFE))]
#[case(&[AGG_SIG_ME as u8], Some(AGG_SIG_ME), Some(AGG_SIG_ME))]
#[case(&[CREATE_COIN as u8], Some(CREATE_COIN), Some(CREATE_COIN))]
// the SOFTOFORK condition is only recognized when the flag is set
#[case(&[SOFTFORK], None, Some(SOFTFORK))]
#[case(&[ASSERT_EPHEMERAL], None, None)]
#[case(&[ASSERT_BEFORE_SECONDS_RELATIVE], None, None)]
#[case(&[SOFTFORK as u8], None, Some(SOFTFORK))]
#[case(&[ASSERT_EPHEMERAL as u8], None, None)]
#[case(&[ASSERT_BEFORE_SECONDS_RELATIVE as u8], None, None)]
fn test_parse_opcode_softfork(
#[case] input: &[u8],
#[case] expected: Option<ConditionOpcode>,
Expand Down

0 comments on commit 575ce60

Please sign in to comment.