Skip to content

Commit 9d9bf69

Browse files
chore: merge develop with conflicts
2 parents b184f09 + 92c9da5 commit 9d9bf69

21 files changed

Lines changed: 814 additions & 71 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,5 +369,6 @@ jobs:
369369
with:
370370
file: ${{ env.REPORT_FILES_DIR }}/code-coverage-report.${{ env.REPORT_FILES_EXT }}
371371
compare-ref: ${{ github.base_ref }} # defaults to master if this isn't supplied
372+
build-number: ${{ github.run_id }}-${{ github.run_attempt }} # include run attempt in build so that repeated CI job runs don't cause a "job already closed" error on upload
372373
fail-on-error: true
373-
374+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`BlockResponseData` version bumped from 4 to 5 to include the new `failed_txid` field. Older signers that don't send this field are handled gracefully via backwards-compatible deserialization.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Miners now track `failed_txid` from signer block rejections. When a blocking minority (>30%) of signers report the same transaction as failed, the miner excludes it from the next block proposal. A new `ProblematicTransaction` validation reject code distinguishes genuinely broken transactions (permanently blacklisted from the mempool) from context-dependent failures like deadline exceeded (excluded until a successful block only).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved performance for `fold`, `map`, and `filter` Clarity functions

clarity/src/vm/functions/sequences.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,13 @@ pub fn special_filter(
8989
sequence = Value::Sequence(
9090
sequence_data
9191
.try_retain(&mut |value: Value| -> Result<bool, VmExecutionError> {
92-
let filter_eval =
93-
apply_evaluated(&function, vec![value], exec_state, invoke_ctx)?;
92+
let filter_eval = apply_evaluated(
93+
&function,
94+
vec![value],
95+
exec_state,
96+
invoke_ctx,
97+
context,
98+
)?;
9499
if let Value::Bool(include) = filter_eval {
95100
Ok(include)
96101
} else {
@@ -163,7 +168,13 @@ pub fn special_fold(
163168
let element = element_result.map_err(|_| {
164169
VmInternalError::Expect("ERROR: Invalid sequence data successfully constructed".into())
165170
})?;
166-
acc = apply_evaluated(&function, vec![element, acc], exec_state, invoke_ctx)?;
171+
acc = apply_evaluated(
172+
&function,
173+
vec![element, acc],
174+
exec_state,
175+
invoke_ctx,
176+
context,
177+
)?;
167178
}
168179
Ok(acc)
169180
}
@@ -226,7 +237,7 @@ pub fn special_map(
226237
})?;
227238
call_args.push(value);
228239
}
229-
let res = apply_evaluated(&function, call_args, exec_state, invoke_ctx)?;
240+
let res = apply_evaluated(&function, call_args, exec_state, invoke_ctx, context)?;
230241
mapped_results.push(res);
231242
}
232243

clarity/src/vm/mod.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -414,24 +414,39 @@ pub fn apply(
414414
/// Like [`apply`], but takes pre-evaluated [`Value`]s, skipping the `eval` + `clone_with_cost`
415415
/// round-trip for every argument.
416416
///
417-
/// `fold` and `map` already have the element value and accumulator as owned `Value`s; wrapping
417+
/// `fold`, `map`, and `filter` already have the element values as owned `Value`s; wrapping
418418
/// them in `SymbolicExpression::atom_value` just to have `eval` clone them back out wastes N
419419
/// allocations per step. This function performs the same recursion/stack/memory bookkeeping
420420
/// as `apply` while bypassing the eval pass entirely.
421+
///
422+
/// For [`CallableType::SpecialFunction`]s (e.g. comparison operators `>=`, `<=`, `<`, `>`,
423+
/// or boolean operators `and`, `or`), the values are wrapped back into
424+
/// `SymbolicExpression::atom_value` so the special function can evaluate them normally
425+
/// with `eval`.
421426
pub fn apply_evaluated(
422427
function: &CallableType,
423428
args: Vec<Value>,
424429
exec_state: &mut ExecutionState,
425430
invoke_ctx: &InvocationContext,
431+
context: &LocalContext,
426432
) -> Result<Value, VmExecutionError> {
427433
let (identifier, track_recursion) = check_call_preconditions(function, exec_state)?;
428-
// SpecialFunctions require unevaluated SymbolicExpressions. They cannot appear as
429-
// fold/map step functions after type-checking, so this branch is unreachable in practice.
430-
if matches!(function, CallableType::SpecialFunction(..)) {
431-
return Err(RuntimeCheckErrorKind::Unreachable(
432-
"apply_evaluated: SpecialFunction cannot receive pre-evaluated args".into(),
433-
)
434-
.into());
434+
435+
// SpecialFunctions require unevaluated SymbolicExpressions. They evaluate their own
436+
// arguments (e.g. short-circuit in `and`/`or`). Wrap the pre-evaluated Values back
437+
// into atom_value expressions so the special function dispatch works correctly.
438+
// This path is hit when built-in operators like >=, <=, <, >, and, or are used as
439+
// step functions in fold/map/filter. Note: In this case it works like `apply`.
440+
if let CallableType::SpecialFunction(_, function) = function {
441+
let sym_args: Vec<SymbolicExpression> = args
442+
.into_iter()
443+
.map(SymbolicExpression::atom_value)
444+
.collect();
445+
exec_state.call_stack.insert(&identifier, track_recursion);
446+
let mut resp = function(&sym_args, exec_state, invoke_ctx, context);
447+
add_stack_trace(&mut resp, exec_state);
448+
exec_state.call_stack.remove(&identifier, track_recursion)?;
449+
return resp;
435450
}
436451

437452
let mut used_memory = 0;

clarity/src/vm/tests/sequences.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,3 +1309,96 @@ fn test_expected_list_application() {
13091309
RuntimeCheckErrorKind::Unreachable("Expected list application".to_string()).into();
13101310
assert_eq!(e, execute(test1).unwrap_err());
13111311
}
1312+
1313+
#[test]
1314+
fn test_map_with_special_functions() {
1315+
// special function: >=
1316+
let test = "(map >= (list 3 1 2) (list 1 2 2))";
1317+
let expected = Value::list_from(vec![
1318+
Value::Bool(true), // (>= 3 1)
1319+
Value::Bool(false), // (>= 1 2)
1320+
Value::Bool(true), // (>= 2 2)
1321+
])
1322+
.unwrap();
1323+
assert_eq!(expected, execute(test).unwrap().unwrap());
1324+
1325+
// special function: <=
1326+
let test = "(map <= (list 3 1 2) (list 1 2 2))";
1327+
let expected = Value::list_from(vec![
1328+
Value::Bool(false), // (<= 3 1)
1329+
Value::Bool(true), // (<= 1 2)
1330+
Value::Bool(true), // (<= 2 2)
1331+
])
1332+
.unwrap();
1333+
assert_eq!(expected, execute(test).unwrap().unwrap());
1334+
1335+
// special function: <
1336+
let test = "(map < (list 1 5 2) (list 3 3 3))";
1337+
let expected = Value::list_from(vec![
1338+
Value::Bool(true), // (< 1 3)
1339+
Value::Bool(false), // (< 5 3)
1340+
Value::Bool(true), // (< 2 3)
1341+
])
1342+
.unwrap();
1343+
assert_eq!(expected, execute(test).unwrap().unwrap());
1344+
1345+
// special function: >
1346+
let test = "(map > (list 5 1 3) (list 3 3 3))";
1347+
let expected = Value::list_from(vec![
1348+
Value::Bool(true), // (> 5 3)
1349+
Value::Bool(false), // (> 1 3)
1350+
Value::Bool(false), // (> 3 3)
1351+
])
1352+
.unwrap();
1353+
assert_eq!(expected, execute(test).unwrap().unwrap());
1354+
}
1355+
1356+
#[test]
1357+
fn test_fold_with_special_functions() {
1358+
// special function: and
1359+
// step 1: (and true true) => true
1360+
// step 2: (and false true) => false
1361+
let test = "(fold and (list true false) true)";
1362+
assert_eq!(Value::Bool(false), execute(test).unwrap().unwrap());
1363+
1364+
// special function: or
1365+
// step 1: (or false false) => false
1366+
// step 2: (or true false) => true
1367+
let test = "(fold or (list false true) false)";
1368+
assert_eq!(Value::Bool(true), execute(test).unwrap().unwrap());
1369+
1370+
// Comparison SpecialFunctions (>=, <=, <, >) return bool so they can't
1371+
// fold over multi-element lists (type mismatch after step 1). Verify
1372+
// they work for a single-element list where only one step executes.
1373+
1374+
// special function: >=
1375+
let test = "(fold >= (list 3) 1)";
1376+
assert_eq!(Value::Bool(true), execute(test).unwrap().unwrap());
1377+
1378+
// special function: <=
1379+
let test = "(fold <= (list 1) 3)";
1380+
assert_eq!(Value::Bool(true), execute(test).unwrap().unwrap());
1381+
1382+
// special function: <
1383+
let test = "(fold < (list 1) 3)";
1384+
assert_eq!(Value::Bool(true), execute(test).unwrap().unwrap());
1385+
1386+
// special function: >
1387+
let test = "(fold > (list 3) 1)";
1388+
assert_eq!(Value::Bool(true), execute(test).unwrap().unwrap());
1389+
}
1390+
1391+
#[test]
1392+
fn test_filter_with_special_functions() {
1393+
// special function: and
1394+
// keeps elements where (and elem) => true
1395+
let test = "(filter and (list true false true false))";
1396+
let expected = Value::list_from(vec![Value::Bool(true), Value::Bool(true)]).unwrap();
1397+
assert_eq!(expected, execute(test).unwrap().unwrap());
1398+
1399+
// special function: or
1400+
// keeps elements where (or elem) => true
1401+
let test = "(filter or (list false true false true))";
1402+
let expected = Value::list_from(vec![Value::Bool(true), Value::Bool(true)]).unwrap();
1403+
assert_eq!(expected, execute(test).unwrap().unwrap());
1404+
}

0 commit comments

Comments
 (0)