Skip to content
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
4 changes: 4 additions & 0 deletions crates/modules-interfaces/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ pub struct ExecutionData {
pub gas_data: std::collections::BTreeMap<String, String>,
/// Flat allocations with host-encoded subtree payloads.
pub message_fee_allocation: Vec<fees::MessageAllocationNode>,
/// Present only for a top-level open-bucket execution
#[serde(default)]
#[calldata(default = default_none)]
pub message_fee_cascade: Option<fees::MessageFeeCascade>,
/// Initial time-unit budget for this execution.
pub initial_time_units_allocation: u32,
/// Auditable supervisor action kinds to return in the execution result.
Expand Down
22 changes: 22 additions & 0 deletions crates/modules-interfaces/src/domain/fees/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,25 @@ impl MessageAllocationNode {
.then_some(*params)
}
}

/// Open-bucket cascade: sender-pool internal messages are raised to
/// `min_primary_fee`, then share half the remaining budget after fees and external reserves
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
genlayer_calldata::Encode,
genlayer_calldata::Decode,
)]
pub struct MessageFeeCascade {
/// Chain `minMessagePrimaryFeesForTx` of the open-bucket internal fee params
pub min_primary_fee: U256,
/// Chain `getMessageFeesBudgetRemaining` at the processing block
pub budget_remaining: U256,
/// Full gas envelope times locked receipt price, per external message
pub external_fee_reserve: U256,
}
32 changes: 24 additions & 8 deletions docs/website/src/impl-spec/04-fees.rst
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,8 @@ configured lifecycle, including appeals; the remainder becomes the child's
message-fee bucket. A zero declared budget is rejected with
``fee below_minimum`` on both funding paths. External messages declare zero

The v0.3 executor currently uses these local stand-ins for transaction-pinned
host inputs that are not available yet:

- The existing local primary-fee calculation supplies each child quote
- The transaction-pinned minimum external gas limit and per-round allocation
budget floor are not available, so both grant-node admission checks are
skipped. The enclosing child's existing local floor check remains active
- The descendant depth cap equals the 24-node cap
The transaction-pinned primary quote is not in v0.3 host input yet, so the
existing local primary-fee calculation supplies each child quote

The Python SDK takes ``use_balance: UseBalanceParams | None`` on calls,
transfers and deploys. ``None`` selects sender funding; the dataclass holds
Expand Down Expand Up @@ -403,6 +397,28 @@ not the unspent allocation allowance or consumption from earlier generations.
All v0.3 internal emissions reject zero time-unit, storage or receipt price caps
with ``Inval`` before charging fees or appending an emission

When the manager input carries ``message_fee_cascade``, a top-level execution
rewrites its final emissions before the execution hash. Each sender-pool
internal message (not ``use_balance``) gets ``fee = max(fee, min_primary_fee)``.
With ``N`` such messages and ``spare = budget_remaining - Σ fee - E`` positive, each then
gets ``floor(spare / (2 * N))`` more. Balance-funded and external messages are
untouched, and buckets are not charged for the addition: the chain carves the
child's own pool from its declared budget. A single floor is enough because the
host gives every open-pool internal wildcard the same fee params

``budget_remaining`` is read at the pinned processing block. It can still include
a prior generation's charge the chain rolls back only later, which makes the
addition smaller, never unbacked. ``E`` is ``external_fee_reserve`` times the
number of external emissions: each reserves the full chain gas envelope at the
locked receipt price. Floors still apply when the remaining budget is insufficient

Pinned descendant grants enforce the parent's ``messageBudgetFloor``,
``gas_data.maxAllocationTreeDepth`` (root depth 1) and
``gas_data.minExternalGasLimit``. When the host omits the depth limit, the
executor defaults to 5; an omitted gas floor is 0. Excess depth fails at emission
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with ``fee descendant_grant tree``; an external node whose ``gas_limit`` is below
the floor fails with ``fee descendant_grant external``

The parent allocation's aggregate capacity is a separate invariant. If
``L = appealRounds + 1`` novel executions may each emit a child carrying budget
``C``, preserving that descendant capacity for every execution requires::
Expand Down
29 changes: 28 additions & 1 deletion docs/website/src/impl-spec/appendix/manager-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,12 @@ components:
additionalProperties:
type: string
default: {}
description: Host-provided `node` fee constants
description: |
Host-provided `node` fee constants. `maxAllocationTreeDepth` is the
parent's pinned grant depth limit as a decimal uint256; v0.3 defaults
to 5 when absent (root depth is 1). `minExternalGasLimit` is the
parent's pinned external-node gas floor as a decimal uint256; 0 when
absent
message_fee_allocation:
type: array
items:
Expand Down Expand Up @@ -1088,6 +1093,28 @@ components:
maximum uint256, children_budget zero and an empty subtree. It is an ordinary
exhausted candidate. Open-pool and allocation-absent executions MUST omit the
guard; open-pool and view execution may use ordinary wildcards
message_fee_cascade:
type: object
nullable: true
default: null
additionalProperties: false
required: [min_primary_fee, budget_remaining, external_fee_reserve]
properties:
min_primary_fee:
type: integer
format: uint256
description: Chain `minMessagePrimaryFeesForTx` of the open-pool internal fee params
budget_remaining:
type: integer
format: uint256
description: Chain `getMessageFeesBudgetRemaining` at the pinned processing block
external_fee_reserve:
type: integer
format: uint256
description: Full external gas envelope times locked receipt gas price, per external emission
description: |
Open-pool cascade, see the fees impl-spec. Send it only for an open-pool
execution; null leaves declared budgets as metered
record_actions:
type: array
items:
Expand Down
7 changes: 7 additions & 0 deletions docs/website/src/spec/appendix/internal-constants.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ nondet_blocks

Value: ``4096``

.. _gvm-def-consts-value-top-limits-default-allocation-tree-depth:

default_allocation_tree_depth
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Value: ``5``

.. _gvm-def-consts-value-top-limits-locked-slots:

locked_slots
Expand Down
2 changes: 1 addition & 1 deletion executors/v0.3.x
Submodule v0.3.x updated 32 files
+1 −0 executor/codegen/data/internal-constants.json
+1 −0 executor/crates/common/src/internal_constants.rs
+1 −2 executor/install/config/genvm.yaml
+6 −1 executor/src/lib.rs
+60 −0 executor/src/rt/cascade.rs
+139 −0 executor/src/rt/cascade_test.rs
+17 −16 executor/src/rt/fees.rs
+1 −0 executor/src/rt/mod.rs
+20 −4 executor/src/wasi/genlayer_sdk/descendant_grant.rs
+11 −6 executor/src/wasi/genlayer_sdk/message.rs
+85 −4 executor/src/wasi/genlayer_sdk/tests.rs
+44 −7 executor/tests/message_fee_time_units.rs
+1 −0 tests/integration/exploit/leader_fault_uncatchable/leader_fault_uncatchable.0.hash
+1 −0 tests/integration/exploit/leader_fault_uncatchable/leader_fault_uncatchable.0_0.hash
+1 −0 tests/integration/exploit/leader_fault_uncatchable/leader_fault_uncatchable.0_0_bad.hash
+1 −0 tests/integration/exploit/leader_fault_uncatchable/leader_fault_uncatchable.0_0_ok.hash
+2 −2 tests/integration/exploit/leader_fault_uncatchable/leader_fault_uncatchable.0_0_ok.stdout
+1 −0 tests/integration/exploit/leader_fault_uncatchable/leader_fault_uncatchable.0_0_sync.hash
+2 −2 tests/integration/exploit/leader_fault_uncatchable/leader_fault_uncatchable.jsonnet
+0 −5 tests/integration/exploit/leader_fault_uncatchable/leader_fault_uncatchable.skip
+1 −0 tests/integration/message/open_bucket_cascade/open_bucket_cascade.0.hash
+1 −0 tests/integration/message/open_bucket_cascade/open_bucket_cascade.0.stdout
+1 −0 tests/integration/message/open_bucket_cascade/open_bucket_cascade.0_0.hash
+4 −0 tests/integration/message/open_bucket_cascade/open_bucket_cascade.0_0.stdout
+24 −0 tests/integration/message/open_bucket_cascade/open_bucket_cascade.jsonnet
+29 −0 tests/integration/message/open_bucket_cascade/open_bucket_cascade.py
+2 −0 tests/integration/message/use_balance_descendants/use_balance_descendants.0_depth_five.stdout
+1 −0 tests/integration/message/use_balance_descendants/use_balance_descendants.0_depth_six.stdout
+2 −0 tests/integration/message/use_balance_descendants/use_balance_descendants.0_external_at_floor.stdout
+1 −0 tests/integration/message/use_balance_descendants/use_balance_descendants.0_external_below_floor.stdout
+27 −0 tests/integration/message/use_balance_descendants/use_balance_descendants.jsonnet
+32 −1 tests/integration/message/use_balance_descendants/use_balance_descendants.py
11 changes: 11 additions & 0 deletions implementation/src/manager/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,10 @@ pub struct Request {
#[serde(default)]
#[calldata(default = default_message_fee_allocation)]
pub message_fee_allocation: Vec<genvm_modules_interfaces::fees::MessageAllocationNode>,
/// Open-bucket cascade inputs; absent means no cascade
#[serde(default)]
#[calldata(default = default_message_fee_cascade)]
pub message_fee_cascade: Option<genvm_modules_interfaces::fees::MessageFeeCascade>,
/// Initial time-unit budget for this execution.
pub initial_time_units_allocation: u32,
/// Auditable supervisor action kinds to return in the execution result.
Expand Down Expand Up @@ -1062,6 +1066,10 @@ fn default_message_fee_allocation() -> Vec<genvm_modules_interfaces::fees::Messa
Vec::new()
}

fn default_message_fee_cascade() -> Option<genvm_modules_interfaces::fees::MessageFeeCascade> {
None
}

fn default_record_actions() -> Vec<String> {
Vec::new()
}
Expand Down Expand Up @@ -1998,6 +2006,7 @@ fn execution_data_from_request(
bucket_totals: req.bucket_totals.clone(),
gas_data: req.gas_data.clone(),
message_fee_allocation: req.message_fee_allocation.clone(),
message_fee_cascade: req.message_fee_cascade,
initial_time_units_allocation: req.initial_time_units_allocation,
record_actions: req.record_actions.clone(),
remaining_recursion: if req.debug_mode >= genvm_common::DebugMode::Unsafe {
Expand Down Expand Up @@ -2229,6 +2238,7 @@ impl Ctx {
leader_public_data: None,
gas_data: parent_req.gas_data.clone(),
message_fee_allocation: Vec::new(),
message_fee_cascade: None,
initial_time_units_allocation: 0,
record_actions: Vec::new(),
host_genvm_id: None,
Expand All @@ -2243,6 +2253,7 @@ impl Ctx {
execution_data.leader_public_data = None;
execution_data.record_actions.clear();
execution_data.message_fee_allocation.clear();
execution_data.message_fee_cascade = None;
execution_data.remaining_recursion =
Some(cross_major_recursion(envelope.remaining_recursion));
execution_data.nested = Some(genvm_modules_interfaces::NestedExecutionData {
Expand Down
2 changes: 2 additions & 0 deletions implementation/src/manager/run_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,7 @@ fn request_field_names() -> Vec<&'static str> {
leader_public_data,
gas_data,
message_fee_allocation,
message_fee_cascade,
initial_time_units_allocation,
record_actions,
host_genvm_id,
Expand Down Expand Up @@ -1139,6 +1140,7 @@ fn request_field_names() -> Vec<&'static str> {
"leader_public_data",
"gas_data",
"message_fee_allocation",
"message_fee_cascade",
"initial_time_units_allocation",
"record_actions",
"host_genvm_id",
Expand Down
3 changes: 3 additions & 0 deletions tests/runner/genvm_tool_plugins/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,9 @@ def resolve_executor(address, state, advisory_major):
request_extra['permissions'] = case_permissions
if hook_calls:
request_extra['hook_cross_contract_calls'] = True
message_fee_cascade = single_conf.get('message_fee_cascade')
if message_fee_cascade is not None:
request_extra['message_fee_cascade'] = message_fee_cascade
bucket_totals = base_host.default_bucket_totals(self._test_case.executor_major)
bucket_totals.update(single_conf.get('bucket_totals', {}))

Expand Down
Loading