Skip to content

Commit

Permalink
Merge pull request #168 from Chia-Network/softfork-condition
Browse files Browse the repository at this point in the history
add new SOFTFORK condition
  • Loading branch information
arvidn committed May 30, 2023
2 parents 670268f + f6eb447 commit da83fa8
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 14 deletions.
6 changes: 5 additions & 1 deletion fuzz/fuzz_targets/parse-conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use clvmr::allocator::Allocator;
use std::collections::HashSet;
use std::sync::Arc;

use chia::gen::flags::{COND_ARGS_NIL, ENABLE_ASSERT_BEFORE, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT};
use chia::gen::flags::{
COND_ARGS_NIL, ENABLE_ASSERT_BEFORE, ENABLE_SOFTFORK_CONDITION, NO_UNKNOWN_CONDS,
STRICT_ARGS_COUNT,
};

fuzz_target!(|data: &[u8]| {
let mut a = Allocator::new();
Expand Down Expand Up @@ -38,6 +41,7 @@ fuzz_target!(|data: &[u8]| {
ENABLE_ASSERT_BEFORE | COND_ARGS_NIL,
ENABLE_ASSERT_BEFORE | STRICT_ARGS_COUNT,
NO_UNKNOWN_CONDS,
ENABLE_SOFTFORK_CONDITION,
] {
let coin_spend = Spend {
parent_id: a.new_atom(&parent_id).expect("atom failed"),
Expand Down
104 changes: 103 additions & 1 deletion src/gen/conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use super::opcodes::{
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,
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 @@ -99,6 +99,10 @@ pub enum Condition {
AssertBeforeHeightAbsolute(u32),
AssertEphemeral,

// The softfork condition is one that we don't understand, it just applies
// the specified cost
Softfork(Cost),

// this means the condition is unconditionally true and can be skipped
Skip,
SkipRelativeCondition,
Expand Down Expand Up @@ -199,6 +203,20 @@ pub fn parse_args(
}
Ok(Condition::CreateCoin(puzzle_hash, amount, a.null()))
}
SOFTFORK => {
if (flags & NO_UNKNOWN_CONDS) != 0 {
// We don't know of any new softforked-in conditions, so they
// are all unknown
Err(ValidationErr(c, ErrorCode::InvalidConditionOpcode))
} else {
match sanitize_uint(a, first(a, c)?, 4, ErrorCode::InvalidSoftforkCost)? {
// the first argument represents the cost of the condition.
// We scale it by 10000 to make the argument be a bit smaller
SanitizedUint::Ok(cost) => Ok(Condition::Softfork(cost * 10000)),
_ => Err(ValidationErr(c, ErrorCode::InvalidSoftforkCost)),
}
}
}
RESERVE_FEE => {
maybe_check_args_terminator(a, c, flags)?;
let fee = parse_amount(a, first(a, c)?, ErrorCode::ReserveFeeConditionFailed)?;
Expand Down Expand Up @@ -830,6 +848,12 @@ pub fn parse_conditions(
ret.agg_sig_unsafe.push((pk, msg));
spend.flags &= !ELIGIBLE_FOR_DEDUP;
}
Condition::Softfork(cost) => {
if *max_cost < cost {
return Err(ValidationErr(c, ErrorCode::CostExceeded));
}
*max_cost -= cost;
}
Condition::SkipRelativeCondition => {
assert_not_ephemeral(&mut spend.flags, state, ret.spends.len());
}
Expand Down Expand Up @@ -1046,6 +1070,8 @@ fn u64_to_bytes(n: u64) -> Vec<u8> {
#[cfg(test)]
use crate::gen::flags::ENABLE_ASSERT_BEFORE;
#[cfg(test)]
use crate::gen::flags::ENABLE_SOFTFORK_CONDITION;
#[cfg(test)]
use clvmr::node::Node;
#[cfg(test)]
use clvmr::number::Number;
Expand Down Expand Up @@ -3665,3 +3691,79 @@ fn test_relative_condition_on_ephemeral(
}
}
}

#[cfg(test)]
#[rstest]
// the scale factor is 10000
#[case("((90 (1 )", 10000)]
// 0 is OK (but does it make sense?)
#[case("((90 (0 )", 0)]
// the cost accumulates
#[case("((90 (1 ) ((90 (2 ) ((90 (3 )", (1 + 2 + 3) * 10000)]
// the cost can be large
#[case("((90 (10000 )", 100000000)]
// the upper cost limit in the test is 11000000000
#[case("((90 (1100000 )", 11000000000)]
// additional arguments are ignored
#[case("((90 (1 ( 42 ( 1337 )", 10000)]
fn test_softfork_condition(#[case] conditions: &str, #[case] expected_cost: Cost) {
// SOFTFORK (90)
let (_, spends) = cond_test_flag(
&format!("((({{h1}} ({{h2}} (1234 ({}))))", conditions),
ENABLE_SOFTFORK_CONDITION,
)
.unwrap();
assert_eq!(spends.cost, expected_cost);

// when NO_UNKNOWN_CONDS is enabled, any SOFTFORK condition is an error
// (because we don't know of any yet)
assert_eq!(
cond_test_flag(
&format!("((({{h1}} ({{h2}} (1234 ({}))))", conditions),
ENABLE_SOFTFORK_CONDITION | NO_UNKNOWN_CONDS
)
.unwrap_err()
.1,
ErrorCode::InvalidConditionOpcode
);

// if softfork conditions aren't enabled, they are just plain unknown
// conditions (that don't incur a cost
let (_, spends) =
cond_test_flag(&format!("((({{h1}} ({{h2}} (1234 ({}))))", conditions), 0).unwrap();
assert_eq!(spends.cost, 0);

// if softfork conditions aren't enabled, but we don't allow unknown
// conditions (mempool mode) they fail
assert_eq!(
cond_test_flag(
&format!("((({{h1}} ({{h2}} (1234 ({}))))", conditions),
NO_UNKNOWN_CONDS
)
.unwrap_err()
.1,
ErrorCode::InvalidConditionOpcode
);
}

#[cfg(test)]
#[rstest]
// the cost argument must be positive
#[case("((90 (-1 )", ErrorCode::InvalidSoftforkCost)]
// the cost argument may not exceed 2^32-1
#[case("((90 (0x0100000000 )", ErrorCode::InvalidSoftforkCost)]
// the test has a cost limit of 11000000000
#[case("((90 (0x00ffffffff )", ErrorCode::CostExceeded)]
#[case("((90 )", ErrorCode::InvalidCondition)]
fn test_softfork_condition_failures(#[case] conditions: &str, #[case] expected_err: ErrorCode) {
// SOFTFORK (90)
assert_eq!(
cond_test_flag(
&format!("((({{h1}} ({{h2}} (1234 ({}))))", conditions),
ENABLE_SOFTFORK_CONDITION
)
.unwrap_err()
.1,
expected_err
);
}
3 changes: 3 additions & 0 deletions src/gen/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub const ENABLE_ASSERT_BEFORE: u32 = 0x100000;
// disallow relative height- and time conditions on ephemeral spends
pub const NO_RELATIVE_CONDITIONS_ON_EPHEMERAL: u32 = 0x200000;

// enable softfork condition
pub const ENABLE_SOFTFORK_CONDITION: u32 = 0x400000;

pub const MEMPOOL_MODE: u32 = CLVM_MEMPOOL_MODE
| NO_UNKNOWN_CONDS
| COND_ARGS_NIL
Expand Down
63 changes: 51 additions & 12 deletions src/gen/opcodes.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::gen::flags::ENABLE_ASSERT_BEFORE;
use crate::gen::flags::ENABLE_SOFTFORK_CONDITION;
use clvmr::allocator::{Allocator, NodePtr, SExp};
use clvmr::cost::Cost;

Expand Down Expand Up @@ -50,6 +51,12 @@ pub const ASSERT_BEFORE_HEIGHT_ABSOLUTE: ConditionOpcode = 87;
// no-op condition
pub const REMARK: ConditionOpcode = 1;

// takes its cost as the first parameter, followed by future extensions
// the cost is specified in increments of 10000, to keep the values smaller
// This is a hard fork and is therefore only available when enabled by the
// ENABLE_SOFTFORK_CONDITION flag
pub const SOFTFORK: ConditionOpcode = 90;

pub const CREATE_COIN_COST: Cost = 1800000;
pub const AGG_SIG_COST: Cost = 1200000;

Expand Down Expand Up @@ -81,7 +88,9 @@ pub fn parse_opcode(a: &Allocator, op: NodePtr, flags: u32) -> Option<ConditionO
| ASSERT_HEIGHT_ABSOLUTE
| REMARK => Some(buf[0]),
_ => {
if (flags & ENABLE_ASSERT_BEFORE) != 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
Expand All @@ -102,15 +111,9 @@ pub fn parse_opcode(a: &Allocator, op: NodePtr, flags: u32) -> Option<ConditionO
}

#[cfg(test)]
fn opcode_tester(a: &mut Allocator, val: &[u8]) -> Option<ConditionOpcode> {
let v = a.new_atom(val).unwrap();
parse_opcode(&a, v, 0)
}

#[cfg(test)]
fn opcode_tester_with_assert_before(a: &mut Allocator, val: &[u8]) -> Option<ConditionOpcode> {
fn opcode_tester(a: &mut Allocator, val: &[u8], flags: u32) -> Option<ConditionOpcode> {
let v = a.new_atom(val).unwrap();
parse_opcode(&a, v, ENABLE_ASSERT_BEFORE)
parse_opcode(&a, v, flags)
}

#[cfg(test)]
Expand Down Expand Up @@ -149,15 +152,51 @@ use rstest::rstest;
#[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))]

fn test_parse_opcode(
#[case] input: &[u8],
#[case] expected: Option<ConditionOpcode>,
#[case] expected2: Option<ConditionOpcode>,
) {
let mut a = Allocator::new();
assert_eq!(opcode_tester(&mut a, input), expected);
assert_eq!(opcode_tester_with_assert_before(&mut a, input), expected2);
assert_eq!(opcode_tester(&mut a, input, 0), expected);
assert_eq!(
opcode_tester(&mut a, input, ENABLE_ASSERT_BEFORE),
expected2
);
assert_eq!(
opcode_tester(&mut a, input, ENABLE_SOFTFORK_CONDITION),
expected
);
assert_eq!(
opcode_tester(
&mut a,
input,
ENABLE_ASSERT_BEFORE | ENABLE_SOFTFORK_CONDITION
),
expected2
);
}

#[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))]
// 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)]
fn test_parse_opcode_softfork(
#[case] input: &[u8],
#[case] expected: Option<ConditionOpcode>,
#[case] expected2: Option<ConditionOpcode>,
) {
let mut a = Allocator::new();
assert_eq!(opcode_tester(&mut a, input, 0), expected);
assert_eq!(
opcode_tester(&mut a, input, ENABLE_SOFTFORK_CONDITION),
expected2
);
}

#[test]
Expand Down
4 changes: 4 additions & 0 deletions src/gen/validation_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub enum ErrorCode {
ImpossibleHeightAbsoluteConstraints,
AssertEphemeralFailed,
EphemeralRelativeCondition,
InvalidSoftforkCondition,
InvalidSoftforkCost,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -126,6 +128,8 @@ impl From<ErrorCode> for u32 {
ErrorCode::AssertMyBirthHeightFailed => 139,
ErrorCode::AssertEphemeralFailed => 140,
ErrorCode::EphemeralRelativeCondition => 141,
ErrorCode::InvalidSoftforkCondition => 142,
ErrorCode::InvalidSoftforkCost => 143,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions wheel/chia_rs.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ STRICT_ARGS_COUNT: int = ...
LIMIT_HEAP: int = ...
LIMIT_STACK: int = ...
ENABLE_ASSERT_BEFORE: int = ...
ENABLE_SOFTFORK_CONDITION: int = ...
MEMPOOL_MODE: int = ...
NO_RELATIVE_CONDITIONS_ON_EPHEMERAL: int = ...

Expand Down
1 change: 1 addition & 0 deletions wheel/generate_type_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ def run_puzzle(
LIMIT_HEAP: int = ...
LIMIT_STACK: int = ...
ENABLE_ASSERT_BEFORE: int = ...
ENABLE_SOFTFORK_CONDITION: int = ...
MEMPOOL_MODE: int = ...
NO_RELATIVE_CONDITIONS_ON_EPHEMERAL: int = ...
Expand Down
2 changes: 2 additions & 0 deletions wheel/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::run_generator::{
use chia::allocator::make_allocator;
use chia::gen::flags::COND_ARGS_NIL;
use chia::gen::flags::ENABLE_ASSERT_BEFORE;
use chia::gen::flags::ENABLE_SOFTFORK_CONDITION;
use chia::gen::flags::MEMPOOL_MODE;
use chia::gen::flags::NO_RELATIVE_CONDITIONS_ON_EPHEMERAL;
use chia::gen::flags::NO_UNKNOWN_CONDS;
Expand Down Expand Up @@ -148,6 +149,7 @@ pub fn chia_rs(py: Python, m: &PyModule) -> PyResult<()> {
m.add("NO_UNKNOWN_CONDS", NO_UNKNOWN_CONDS)?;
m.add("STRICT_ARGS_COUNT", STRICT_ARGS_COUNT)?;
m.add("ENABLE_ASSERT_BEFORE", ENABLE_ASSERT_BEFORE)?;
m.add("ENABLE_SOFTFORK_CONDITION", ENABLE_SOFTFORK_CONDITION)?;
m.add("ENABLE_BLS_OPS", ENABLE_BLS_OPS)?;
m.add("ENABLE_BLS_OPS_OUTSIDE_GUARD", ENABLE_BLS_OPS_OUTSIDE_GUARD)?;
m.add(
Expand Down

0 comments on commit da83fa8

Please sign in to comment.