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

Unknown conditions with cost #170

Merged
merged 1 commit into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 => {
richardkiss marked this conversation as resolved.
Show resolved Hide resolved
// 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
richardkiss marked this conversation as resolved.
Show resolved Hide resolved
} 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
Loading