Skip to content

Add minCost parameter #295

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
12 changes: 8 additions & 4 deletions C/elements/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,24 @@
* NULL != tx;
* NULL != taproot;
* unsigned char genesisBlockHash[32]
* 0 <= budget;
* 0 <= minCost <= budget;
* NULL != amr implies unsigned char amr[32]
* unsigned char program[program_len]
* unsigned char witness[witness_len]
*/
extern bool simplicity_elements_execSimplicity( simplicity_err* error, unsigned char* ihr
, const elementsTransaction* tx, uint_fast32_t ix, const elementsTapEnv* taproot
, const unsigned char* genesisBlockHash
, int64_t budget
, int64_t minCost, int64_t budget
, const unsigned char* amr
, const unsigned char* program, size_t program_len
, const unsigned char* witness, size_t witness_len) {
simplicity_assert(NULL != error);
simplicity_assert(NULL != tx);
simplicity_assert(NULL != taproot);
simplicity_assert(NULL != genesisBlockHash);
simplicity_assert(0 <= budget);
simplicity_assert(0 <= minCost);
simplicity_assert(minCost <= budget);
simplicity_assert(NULL != program || 0 == program_len);
simplicity_assert(NULL != witness || 0 == witness_len);

Expand Down Expand Up @@ -120,7 +121,10 @@ extern bool simplicity_elements_execSimplicity( simplicity_err* error, unsigned
if (IS_OK(*error)) {
txEnv env = simplicity_elements_build_txEnv(tx, taproot, &genesis_hash, ix);
static_assert(BUDGET_MAX <= UBOUNDED_MAX, "BUDGET_MAX doesn't fit in ubounded.");
*error = evalTCOProgram(dag, type_dag, (size_t)dag_len, &(ubounded){budget <= BUDGET_MAX ? (ubounded)budget : BUDGET_MAX}, &env);
*error = evalTCOProgram( dag, type_dag, (size_t)dag_len
, minCost <= BUDGET_MAX ? (ubounded)minCost : BUDGET_MAX
, &(ubounded){budget <= BUDGET_MAX ? (ubounded)budget : BUDGET_MAX}
, &env);
}
simplicity_free(type_dag);
}
Expand Down
25 changes: 16 additions & 9 deletions C/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ typedef struct boundsAnalysis {
* When maxCells < UBOUNDED_MAX, if the bounds on the number of cells needed for evaluation of 'dag' on an idealized Bit Machine exceeds maxCells,
* then return SIMPLICITY_ERR_EXEC_MEMORY.
* When maxCost < UBOUNDED_MAX, if the bounds on the dag's CPU cost exceeds 'maxCost', then return SIMPLICITY_ERR_EXEC_BUDGET.
* If the bounds on the dag's CPU cost is less than or equal to 'minCost', then return SIMPLICITY_ERR_OVERWEIGHT.
* Otherwise returns SIMPLICITY_NO_ERR.
*
* Precondition: NULL != cellsBound
Expand All @@ -583,11 +584,9 @@ typedef struct boundsAnalysis {
* and if maxCells < UBOUNDED_MAX then '*cellsBound' bounds the number of cells needed for evaluation of 'dag' on an idealized Bit Machine
* and if maxCells < UBOUNDED_MAX then '*UWORDBound' bounds the number of UWORDs needed for the frames during evaluation of 'dag'
* and if maxCells < UBOUNDED_MAX then '*frameBound' bounds the number of stack frames needed during execution of 'dag'.
*
* :TODO: The cost calculations below are estimated and need to be replaced by experimental data.
*/
simplicity_err simplicity_analyseBounds( ubounded *cellsBound, ubounded *UWORDBound, ubounded *frameBound, ubounded *costBound
, ubounded maxCells, ubounded maxCost, const dag_node* dag, const type* type_dag, const size_t len) {
, ubounded maxCells, ubounded minCost, ubounded maxCost, const dag_node* dag, const type* type_dag, const size_t len) {
static_assert(DAG_LEN_MAX <= SIZE_MAX / sizeof(boundsAnalysis), "bound array too large.");
static_assert(1 <= DAG_LEN_MAX, "DAG_LEN_MAX is zero.");
static_assert(DAG_LEN_MAX - 1 <= UINT32_MAX, "bound array index does not fit in uint32_t.");
Expand Down Expand Up @@ -741,15 +740,17 @@ simplicity_err simplicity_analyseBounds( ubounded *cellsBound, ubounded *UWORDBo
*/
return (maxCells < *cellsBound) ? SIMPLICITY_ERR_EXEC_MEMORY
: (maxCost < *costBound) ? SIMPLICITY_ERR_EXEC_BUDGET
: (*costBound <= minCost) ? SIMPLICITY_ERR_OVERWEIGHT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In d2c2de5:

I think we want this <= to be a <. Then both minCost and maxCost are inclusive bounds, which I think is what's implied by their names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with renaming these fields, but as per the design in #290, the ranges must be non-overlapping:

Notice that the strictness of the inequalities is important if we are being exact. If the rule were instead l <= c <= b then if it were to happen that c were exactly l down to the the exact milliWU, there would be two annex sizes that would support that cost, which would add a small malleability in the allowed size of the annex.

Copy link
Contributor

@apoelstra apoelstra May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the code you allow l == b while in #290 you sorta imply, but don't explicitly say, that l would be b - 1 except that because of encoding edge cases sometimes it'll have to be b - 2.

You could have just as easily used non-strict inequalities, and said that l would be b, except that because of encoding edge cases sometimes it'll have to be b - 1.

If you did that, you would be redefining l, but I think everything would still make sense. And maybe be a bit simpler to understand.

I'm fine with keeping the design as-is, matching #290, but if we do then I definitely think we should rename minCost. It could be called minCostMinusOne, or maxInvalidCost, or something. Hopefully you can think of a less ugly name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I allow l == b in the sense that it isn't precondition violating to pass those arguments, however if you do pass arguments such that l == b then there is no code-path that will return SIMPLICTY_OK; it will always at least either return SIMPLICITY_ERR_EXEC_MEMORY or SIMPLICITY_ERR_OVERWEIGHT (or possibly some other error if you have like type errors and such that are found first).

I allow l == b so that degenerate requests can be made without violating my precondition.

That said, I'm happy to drop that precondition and just double check that everything remains sensible when the user passes b < l.

You could have just as easily used non-strict inequalities, and said that l would be b, except that because of encoding edge cases sometimes it'll have to be b - 1.

Unfortunately no. The issues is that the API takes mincosts and maxcosts in terms of WU, but the weight calculations are done in milliWU. Obviously subtracting 1 WU is not the same as subtracting 1 milliWU.

The conversion from WU to milliWU is done on line 386. Maybe I should change variable names to note their units to make this more apparent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I understand!

Can you add a comment explaining that both values are in milliWU, but the granularity of the cost bounds will be in wu, where wu n is represented by the half-open interval (1000 * (n - 1), 1000 * n], and that we therefore check minCost using a strict inequality and maxCost using a <= inequality.

I think that the existing names are fine as long as you provide that intuition pump.

: SIMPLICITY_NO_ERROR;
}

/* Run the Bit Machine on the well-typed Simplicity expression 'dag[len]' of type A |- B.
* If bitSize(A) > 0, initialize the active read frame's data with 'input[ROUND_UWORD(bitSize(A))]'.
*
* If malloc fails, returns 'SIMPLICITY_ERR_MALLOC'.
* When a budget is given, if static analysis results determines the bound on cpu requirements exceed the allowed budget, returns 'SIMPLICITY_ERR_EXEC_BUDGET'
* If static analysis results determines the bound on memory allocation requirements exceed the allowed limits, returns 'SIMPLICITY_ERR_EXEC_MEMORY'
* When a budget is given, if static analysis results determines the bound on cpu requirements exceed the allowed budget, returns 'SIMPLICITY_ERR_EXEC_BUDGET'.
* If static analysis results determines the bound on cpu requirements is less than or equal to the minCost, returns 'SIMPLICITY_ERR_OVERWEIGHT'.
* If static analysis results determines the bound on memory allocation requirements exceed the allowed limits, returns 'SIMPLICITY_ERR_EXEC_MEMORY'.
* If during execution some jet execution fails, returns 'SIMPLICITY_ERR_EXEC_JET'.
* If during execution some 'assertr' or 'assertl' combinator fails, returns 'SIMPLICITY_ERR_EXEC_ASESRT'.
*
Expand All @@ -764,19 +765,25 @@ simplicity_err simplicity_analyseBounds( ubounded *cellsBound, ubounded *UWORDBo
* bitSize(A) == 0 or UWORD input[ROUND_UWORD(bitSize(A))];
* bitSize(B) == 0 or UWORD output[ROUND_UWORD(bitSize(B))];
* if NULL != budget then *budget <= BUDGET_MAX
* if NULL != budget then minCost <= *budget
* minCost <= BUDGET_MAX
* if 'dag[len]' represents a Simplicity expression with primitives then 'NULL != env';
*/
simplicity_err simplicity_evalTCOExpression( flags_type anti_dos_checks, UWORD* output, const UWORD* input
, const dag_node* dag, type* type_dag, size_t len, const ubounded* budget, const txEnv* env
) {
, const dag_node* dag, type* type_dag, size_t len, ubounded minCost, const ubounded* budget, const txEnv* env
) {
simplicity_assert(1 <= len);
simplicity_assert(len <= DAG_LEN_MAX);
if (budget) { simplicity_assert(*budget <= BUDGET_MAX); }
if (budget) {
simplicity_assert(*budget <= BUDGET_MAX);
simplicity_assert(minCost <= *budget);
}
simplicity_assert(minCost <= BUDGET_MAX);
static_assert(1 <= UBOUNDED_MAX, "UBOUNDED_MAX is zero.");
static_assert(BUDGET_MAX <= (UBOUNDED_MAX - 1) / 1000, "BUDGET_MAX is too large.");
static_assert(CELLS_MAX < UBOUNDED_MAX, "CELLS_MAX is too large.");
ubounded cellsBound, UWORDBound, frameBound, costBound;
simplicity_err result = simplicity_analyseBounds(&cellsBound, &UWORDBound, &frameBound, &costBound, CELLS_MAX, budget ? *budget*1000 : UBOUNDED_MAX, dag, type_dag, len);
simplicity_err result = simplicity_analyseBounds(&cellsBound, &UWORDBound, &frameBound, &costBound, CELLS_MAX, minCost*1000, budget ? *budget*1000 : UBOUNDED_MAX, dag, type_dag, len);
if (!IS_OK(result)) return result;

/* frameBound is at most 2*len. */
Expand Down
25 changes: 16 additions & 9 deletions C/eval.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ typedef unsigned char flags_type;
* When maxCells < UBOUNDED_MAX, if the bounds on the number of cells needed for evaluation of 'dag' on an idealized Bit Machine exceeds maxCells,
* then return SIMPLICITY_ERR_EXEC_MEMORY.
* When maxCost < UBOUNDED_MAX, if the bounds on the dag's CPU cost exceeds 'maxCost', then return SIMPLICITY_ERR_EXEC_BUDGET.
* If the bounds on the dag's CPU cost is less than or equal to 'minCost', then return SIMPLICITY_ERR_OVERWEIGHT.
* Otherwise returns SIMPLICITY_NO_ERR.
*
* Precondition: NULL != cellsBound
Expand All @@ -32,14 +33,15 @@ typedef unsigned char flags_type;
* and if maxCells < UBOUNDED_MAX then '*frameBound' bounds the number of stack frames needed during execution of 'dag'.
*/
simplicity_err simplicity_analyseBounds( ubounded *cellsBound, ubounded *UWORDBound, ubounded *frameBound, ubounded *costBound
, ubounded maxCells, ubounded maxCost, const dag_node* dag, const type* type_dag, const size_t len);
, ubounded maxCells, ubounded minCost, ubounded maxCost, const dag_node* dag, const type* type_dag, const size_t len);

/* Run the Bit Machine on the well-typed Simplicity expression 'dag[len]' of type A |- B.
* If bitSize(A) > 0, initialize the active read frame's data with 'input[ROUND_UWORD(bitSize(A))]'.
*
* If malloc fails, returns 'SIMPLICITY_ERR_MALLOC'.
* When a budget is given, if static analysis results determines the bound on cpu requirements exceed the allowed budget, returns 'SIMPLICITY_ERR_EXEC_BUDGET'
* If static analysis results determines the bound on memory allocation requirements exceed the allowed limits, returns 'SIMPLICITY_ERR_EXEC_MEMORY'
* When a budget is given, if static analysis results determines the bound on cpu requirements exceed the allowed budget, returns 'SIMPLICITY_ERR_EXEC_BUDGET'.
* If static analysis results determines the bound on cpu requirements is less than or equal to the minCost, returns 'SIMPLICITY_ERR_OVERWEIGHT'.
* If static analysis results determines the bound on memory allocation requirements exceed the allowed limits, returns 'SIMPLICITY_ERR_EXEC_MEMORY'.
* If during execution some jet execution fails, returns 'SIMPLICITY_ERR_EXEC_JET'.
* If during execution some 'assertr' or 'assertl' combinator fails, returns 'SIMPLICITY_ERR_EXEC_ASESRT'.
*
Expand All @@ -54,17 +56,20 @@ simplicity_err simplicity_analyseBounds( ubounded *cellsBound, ubounded *UWORDBo
* bitSize(A) == 0 or UWORD input[ROUND_UWORD(bitSize(A))];
* bitSize(B) == 0 or UWORD output[ROUND_UWORD(bitSize(B))];
* if NULL != budget then *budget <= BUDGET_MAX
* if NULL != budget then minCost <= *budget
* minCost <= BUDGET_MAX
* if 'dag[len]' represents a Simplicity expression with primitives then 'NULL != env';
*/
simplicity_err simplicity_evalTCOExpression( flags_type anti_dos_checks, UWORD* output, const UWORD* input
, const dag_node* dag, type* type_dag, size_t len, const ubounded* budget, const txEnv* env
);
, const dag_node* dag, type* type_dag, size_t len, ubounded minCost, const ubounded* budget, const txEnv* env
);

/* Run the Bit Machine on the well-typed Simplicity program 'dag[len]'.
*
* If malloc fails, returns 'SIMPLICITY_ERR_MALLOC'.
* When a budget is given, if static analysis results determines the bound on cpu requirements exceed the allowed budget, returns 'SIMPLICITY_ERR_EXEC_BUDGET'
* If static analysis results determines the bound on memory allocation requirements exceed the allowed limits, returns 'SIMPLICITY_ERR_EXEC_MEMORY'
* When a budget is given, if static analysis results determines the bound on cpu requirements exceed the allowed budget, returns 'SIMPLICITY_ERR_EXEC_BUDGET'.
* If static analysis results determines the bound on cpu requirements is less than or equal to the minCost, returns 'SIMPLICITY_ERR_OVERWEIGHT'.
* If static analysis results determines the bound on memory allocation requirements exceed the allowed limits, returns 'SIMPLICITY_ERR_EXEC_MEMORY'.
* If during execution some jet execution fails, returns 'SIMPLICITY_ERR_EXEC_JET'.
* If during execution some 'assertr' or 'assertl' combinator fails, returns 'SIMPLICITY_ERR_EXEC_ASESRT'.
* If not every non-HIDDEN dag node is executed, returns 'SIMPLICITY_ERR_ANTIDOS'
Expand All @@ -74,9 +79,11 @@ simplicity_err simplicity_evalTCOExpression( flags_type anti_dos_checks, UWORD*
*
* Precondition: dag_node dag[len] and 'dag' is well-typed with 'type_dag' for an expression of type ONE |- ONE;
* if NULL != budget then *budget <= BUDGET_MAX
* if NULL != budget then minCost <= *budget
* minCost <= BUDGET_MAX
* if 'dag[len]' represents a Simplicity expression with primitives then 'NULL != env';
*/
static inline simplicity_err evalTCOProgram(const dag_node* dag, type* type_dag, size_t len, const ubounded* budget, const txEnv* env) {
return simplicity_evalTCOExpression(CHECK_ALL, NULL, NULL, dag, type_dag, len, budget, env);
static inline simplicity_err evalTCOProgram(const dag_node* dag, type* type_dag, size_t len, ubounded minCost, const ubounded* budget, const txEnv* env) {
return simplicity_evalTCOExpression(CHECK_ALL, NULL, NULL, dag, type_dag, len, minCost, budget, env);
}
#endif
4 changes: 2 additions & 2 deletions C/include/simplicity/elements/exec.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
* NULL != tx;
* NULL != taproot;
* unsigned char genesisBlockHash[32]
* 0 <= budget;
* 0 <= minCost <= budget;
* NULL != amr implies unsigned char amr[32]
* unsigned char program[program_len]
* unsigned char witness[witness_len]
*/
extern bool simplicity_elements_execSimplicity( simplicity_err* error, unsigned char* ihr
, const elementsTransaction* tx, uint_fast32_t ix, const elementsTapEnv* taproot
, const unsigned char* genesisBlockHash
, int64_t budget
, int64_t minCost, int64_t budget
, const unsigned char* amr
, const unsigned char* program, size_t program_len
, const unsigned char* witness, size_t witness_len);
Expand Down
3 changes: 3 additions & 0 deletions C/include/simplicity/errorCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ typedef enum {
SIMPLICITY_ERR_ANTIDOS = -42,
SIMPLICITY_ERR_HIDDEN_ROOT = -44,
SIMPLICITY_ERR_AMR = -46,
SIMPLICITY_ERR_OVERWEIGHT = -48,
} simplicity_err;

/* Check if failure is permanent (or success which is always permanent). */
Expand Down Expand Up @@ -102,6 +103,8 @@ static inline const char * SIMPLICITY_ERR_MSG(simplicity_err err) {
return "Program's root is HIDDEN";
case SIMPLICITY_ERR_AMR:
return "Program's AMR does not match";
case SIMPLICITY_ERR_OVERWEIGHT:
return "Program's budget is too large";
default:
return "Unknown error code";
}
Expand Down
Loading
Loading