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
3 changes: 0 additions & 3 deletions .claude/settings.local.json

This file was deleted.

5 changes: 5 additions & 0 deletions .genvm-tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,8 @@ def collect_parse_version(ctx: genvm_tool.tests.stage.collection.Context):
ctx.collect_dir('tests/system/parse_version')

ctx.add_collector(collect_parse_version)

def collect_make_zip(ctx: genvm_tool.tests.stage.collection.Context):
ctx.collect_dir('tests/system/make_zip')

ctx.add_collector(collect_make_zip)
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

/.pre-commit-config.yaml
.claude/hooks
.claude/settings.local.json
.idea
.envrc
*.log
Expand Down
10 changes: 5 additions & 5 deletions crates/modules-interfaces/codegen/data/host-fns.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"name": "methods",
"values": {
"storage_read": 0,
"consume_fuel": 1,
"eth_call": 2,
"get_balance": 3,
"remaining_fuel_as_gen": 4,
"consume_time_fee_gen_wei": 1,
"external_call": 2,
"get_balance_gen_wei": 3,
"get_remaining_time_fee_gen_wei": 4,
"notify_nondet_disagreement": 5,
"consume_result": 6,
"resolve_callcontract_executor": 7,
"resolve_call_contract_executor": 7,
"run_nested": 8
}
},
Expand Down
24 changes: 12 additions & 12 deletions crates/modules-interfaces/src/abi_stub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ pub enum On {
#[serde(rename = "finalized")]
#[calldata(rename = "finalized")]
Finalized,
#[serde(rename = "accepted")]
#[calldata(rename = "accepted")]
Accepted,
#[serde(rename = "decided")]
#[calldata(rename = "decided")]
Decided,
}

#[derive(
Expand Down Expand Up @@ -57,29 +57,29 @@ pub struct MessageData {
pub value: num_bigint::BigInt,
pub is_init: bool,
/// Transaction timestamp
#[serde(default = "default_datetime")]
#[serde(default = "default_transaction_timestamp")]
#[calldata(
serialize_with = encode_datetime_rfc3339,
deserialize_with = decode_datetime_rfc3339
serialize_with = encode_transaction_timestamp_rfc3339,
deserialize_with = decode_transaction_timestamp_rfc3339
)]
#[calldata(default = default_datetime)]
pub datetime: chrono::DateTime<chrono::Utc>,
#[calldata(default = default_transaction_timestamp)]
pub transaction_timestamp: chrono::DateTime<chrono::Utc>,
}

fn decode_datetime_rfc3339(
fn decode_transaction_timestamp_rfc3339(
val: genlayer_calldata::Value,
) -> Result<chrono::DateTime<chrono::Utc>, genlayer_calldata::codec::DecodeError> {
let genlayer_calldata::Value::Str(s) = val else {
return Err(genlayer_calldata::codec::DecodeError::Unexpected(
"expected string for datetime",
"expected string for transaction_timestamp",
));
};
chrono::DateTime::parse_from_rfc3339(&s)
.map(|dt| dt.to_utc())
.map_err(|e| genlayer_calldata::codec::DecodeError::UserError(Box::new(e)))
}

fn encode_datetime_rfc3339<W: genlayer_calldata::Writer>(
fn encode_transaction_timestamp_rfc3339<W: genlayer_calldata::Writer>(
dt: &chrono::DateTime<chrono::Utc>,
enc: &mut genlayer_calldata::Encoder<W>,
) -> Result<(), W::Error> {
Expand All @@ -88,7 +88,7 @@ fn encode_datetime_rfc3339<W: genlayer_calldata::Writer>(
enc.push_str(&s)
}

fn default_datetime() -> chrono::DateTime<chrono::Utc> {
fn default_transaction_timestamp() -> chrono::DateTime<chrono::Utc> {
chrono::DateTime::parse_from_rfc3339("2024-11-26T06:42:42.424242Z")
.unwrap()
.to_utc()
Expand Down
14 changes: 7 additions & 7 deletions crates/modules-interfaces/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub struct NestedExecutionData {
#[derive(Debug, Clone, PartialEq, Eq, genlayer_calldata::Encode, genlayer_calldata::Decode)]
#[calldata(tag = "type")]
pub enum ExecutionEmission {
EthSend {
ExternalMessage {
address: genlayer_calldata::Address,
calldata: Bytes,
value: U256,
Expand All @@ -100,7 +100,7 @@ pub enum ExecutionEmission {

fee_params: fees::ExternalMessageParams,
},
PostMessage {
InternalMessage {
call_key: crate::CallKey,
address: genlayer_calldata::Address,
calldata: genlayer_calldata::codec::Maybe<genlayer_calldata::Value>,
Expand All @@ -115,7 +115,7 @@ pub enum ExecutionEmission {
/// balance rather than the sender's prefunded message-fee pool.
use_balance: bool,
},
DeployContract {
InternalDeployMessage {
calldata: genlayer_calldata::codec::Maybe<genlayer_calldata::Value>,
code: Bytes,
value: U256,
Expand All @@ -126,10 +126,10 @@ pub enum ExecutionEmission {

fee_params: fees::InternalMessageParams,
subtree: bytes::Bytes,
/// Chain `useBalance`; see `ExecutionEmission::PostMessage::use_balance`.
/// Chain `useBalance`; see `ExecutionEmission::InternalMessage::use_balance`.
use_balance: bool,
},
EmitEvent {
Event {
topics: Vec<Bytes>,
blob: genlayer_calldata::codec::Maybe<genlayer_calldata::Map<genlayer_calldata::Value>>,
storage_fee: U256,
Expand Down Expand Up @@ -403,7 +403,7 @@ pub struct ReportedResult {
pub data: genlayer_calldata::unparsed::Maybe<genlayer_calldata::Value>,
pub backtrace: Option<Backtrace>,
pub wasm_store_hashes: WasmStoreHashes,
pub storage_changes: Vec<StorageDelta>,
pub storage_deltas: Vec<StorageDelta>,

pub emissions: Vec<ExecutionEmission>,

Expand All @@ -413,5 +413,5 @@ pub struct ReportedResult {
pub data_fees_remaining: Vec<primitive_types::U256>,
pub data_fees_consumed: BucketsConsumed,

pub llm_consumption: primitive_types::U256,
pub llm_consumed_gen_wei: primitive_types::U256,
}
2 changes: 1 addition & 1 deletion crates/modules-interfaces/src/domain/fees/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ fn encode_node(node: &MessageAllocationNode, parent_index: U256) -> Vec<u8> {
let (message_type, on_acceptance, fee_params) = match &node.fee_params {
MessageAllocationNodeParams::Internal(params) => (
MESSAGE_TYPE_INTERNAL,
matches!(node.on, On::Accepted),
matches!(node.on, On::Decided),
encode_internal_params(params),
),
// External messages have no acceptance/finalize lifecycle.
Expand Down
40 changes: 20 additions & 20 deletions crates/modules-interfaces/src/host_fns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ use serde::{Deserialize, Serialize};
#[repr(u8)]
pub enum Methods {
StorageRead = 0,
ConsumeFuel = 1,
EthCall = 2,
GetBalance = 3,
RemainingFuelAsGen = 4,
ConsumeTimeFeeGenWei = 1,
ExternalCall = 2,
GetBalanceGenWei = 3,
GetRemainingTimeFeeGenWei = 4,
NotifyNondetDisagreement = 5,
ConsumeResult = 6,
ResolveCallcontractExecutor = 7,
ResolveCallContractExecutor = 7,
RunNested = 8,
}

Expand All @@ -32,26 +32,26 @@ impl Methods {
pub fn value(self) -> u8 {
match self {
Methods::StorageRead => 0,
Methods::ConsumeFuel => 1,
Methods::EthCall => 2,
Methods::GetBalance => 3,
Methods::RemainingFuelAsGen => 4,
Methods::ConsumeTimeFeeGenWei => 1,
Methods::ExternalCall => 2,
Methods::GetBalanceGenWei => 3,
Methods::GetRemainingTimeFeeGenWei => 4,
Methods::NotifyNondetDisagreement => 5,
Methods::ConsumeResult => 6,
Methods::ResolveCallcontractExecutor => 7,
Methods::ResolveCallContractExecutor => 7,
Methods::RunNested => 8,
}
}
pub fn str_snake_case(self) -> &'static str {
match self {
Methods::StorageRead => "storage_read",
Methods::ConsumeFuel => "consume_fuel",
Methods::EthCall => "eth_call",
Methods::GetBalance => "get_balance",
Methods::RemainingFuelAsGen => "remaining_fuel_as_gen",
Methods::ConsumeTimeFeeGenWei => "consume_time_fee_gen_wei",
Methods::ExternalCall => "external_call",
Methods::GetBalanceGenWei => "get_balance_gen_wei",
Methods::GetRemainingTimeFeeGenWei => "get_remaining_time_fee_gen_wei",
Methods::NotifyNondetDisagreement => "notify_nondet_disagreement",
Methods::ConsumeResult => "consume_result",
Methods::ResolveCallcontractExecutor => "resolve_callcontract_executor",
Methods::ResolveCallContractExecutor => "resolve_call_contract_executor",
Methods::RunNested => "run_nested",
}
}
Expand All @@ -63,13 +63,13 @@ impl TryFrom<u8> for Methods {
fn try_from(value: u8) -> Result<Self, ()> {
match value {
0 => Ok(Methods::StorageRead),
1 => Ok(Methods::ConsumeFuel),
2 => Ok(Methods::EthCall),
3 => Ok(Methods::GetBalance),
4 => Ok(Methods::RemainingFuelAsGen),
1 => Ok(Methods::ConsumeTimeFeeGenWei),
2 => Ok(Methods::ExternalCall),
3 => Ok(Methods::GetBalanceGenWei),
4 => Ok(Methods::GetRemainingTimeFeeGenWei),
5 => Ok(Methods::NotifyNondetDisagreement),
6 => Ok(Methods::ConsumeResult),
7 => Ok(Methods::ResolveCallcontractExecutor),
7 => Ok(Methods::ResolveCallContractExecutor),
8 => Ok(Methods::RunNested),
_ => Err(()),
}
Expand Down
4 changes: 2 additions & 2 deletions crates/modules-interfaces/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,11 +537,11 @@ pub mod llm {
pub enum Message {
Prompt {
payload: PromptPayload,
remaining_fuel_as_gen: primitive_types::U256,
remaining_time_fee_gen_wei: primitive_types::U256,
},
PromptTemplate {
payload: PromptTemplatePayload,
remaining_fuel_as_gen: primitive_types::U256,
remaining_time_fee_gen_wei: primitive_types::U256,
},
}

Expand Down
4 changes: 2 additions & 2 deletions crates/modules-interfaces/src/nested.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ pub enum ExecutorSelector {
#[serde(rename_all = "snake_case")]
pub enum NestedStorageType {
Default,
LatestFinal,
LatestNonFinal,
LatestFinalized,
LatestDecided,
}

/// Permission bits carried across an executor boundary.
Expand Down
6 changes: 6 additions & 0 deletions docs/adr/011. runner ids and runtime registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ returns the resulting runner id. It is allowed only in deterministic mode and on
holds the new `register_runners` permission (permission char `u`). A friendly
`genlayer.vm.register_runner(code) -> str` wrapper is exposed in the Python SDK.

> **Superseded.** The `register_runners` permission was removed from v0.3: no
> `u` char was ever parsed, the root VM always held the bit, and containment
> (a `custom:` id resolves only from the registering VM's loaded set, which dies
> with it) already carries what the permission was meant to buy.
> `RegisterRunner` now requires deterministic mode alone.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Add a companion `gl_call` `MapFile { runner, path_in_runner, path_in_vfs }` that maps
a file (or, when `path_in_runner` ends with `/`, a directory subtree) from any
runner into the VM filesystem at runtime, sharing the exact logic of the `MapFile`
Expand Down
19 changes: 10 additions & 9 deletions docs/adr/013. pre-validating untrusted decoded inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ bucket that raises it needs a permission the child lacks); the child also can't
reach the derived namespace (it has `spawn_nondet: false`). Both invariants must
be re-checked if the nondet child's permissions change — a non-trie code trips
the `debug_assert!` in debug and, in release, is an honest-node hash split.
Consequence: `leaders_result` is always detail-free while a validator's own
Consequence: `leader_result` is always detail-free while a validator's own
error keeps its detail; the default `compare_vm_errors` compares `public_code`
only, so it is unaffected — custom comparators must not read a detail from
`leaders_result`.
`leader_result`.

### Rule 2 — message payload shape

Expand All @@ -150,12 +150,13 @@ untrusted entry calldata is decoded:

- **Top-level host entry** — decoded in `run_with_impl` before permissions are
read or any runner loads; failure (or a `""` present on `is_init`) is a
`malformed_entry` VM error **result**. `PostMessage`/`DeployContract` emit
messages that later execute as top-level entries, so they pass here too.
- **Every `gl_call` message** — `CallContract`, `PostMessage` and
`DeployContract` carry the calldata as a typed field, so a malformed payload
fails the `gl_call::Message` decode and answers **`Errno::Inval`**, like the
other argument checks; the caller can recover.
`malformed_entry` VM error **result**.
`EmitInternalMessage`/`EmitInternalDeployMessage` emit messages that later
execute as top-level entries, so they pass here too.
- **Every `gl_call` message** — `CallContract`, `EmitInternalMessage` and
`EmitInternalDeployMessage` carry the calldata as a typed field, so a
malformed payload fails the `gl_call::Message` decode and answers
**`Errno::Inval`**, like the other argument checks; the caller can recover.

### Run modes

Expand Down Expand Up @@ -218,7 +219,7 @@ calldata, extra keys or observable nondet details.
- **Re-encode the `Return` payload instead of rejecting** — silently normalizes
malformed consensus input; the validator's bytes would differ from the
proposal while claiming agreement.
- **Validate lazily when the contract reads `leaders_result`** — too late, the
- **Validate lazily when the contract reads `leader_result`** — too late, the
payload is already in the hash.
- **Enforce the message shape in each SDK** — once per language, differently,
and a runner can't reject a call before it starts.
Expand Down
8 changes: 4 additions & 4 deletions docs/adr/015. cross-major contract calls.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ observed mismatch, asks the host which executor should run the callee.
The resolution and the execution are separate calls, to different peers,
because they answer to different authorities.

1. **`resolve_callcontract_executor`**, on host connection 0 — the host that
1. **`resolve_call_contract_executor`**, on host connection 0 — the host that
initiated the transaction — when the run request sets
`hook_cross_contract_calls`. Otherwise it goes to the manager on host 1,
which answers null; see the implementation notes.
Expand Down Expand Up @@ -135,7 +135,7 @@ computed by the caller and carried on the wire. The table is complete against
| `ExecutionData` field | Nested value |
| --- | --- |
| `calldata` | Derived `CallContract` calldata. The child is always `Main`; v0.2 rewrites its legacy `method` key to the current empty key at the boundary. |
| `message` | Derived child message: target contract, inherited sender/origin/signer, chain and datetime, `value = 0`, `is_init = false`. v0.2's contract-facing message has no signer field, so its executor carries the signer beside the message and forwards it unchanged; contracts on that line still cannot observe it. |
| `message` | Derived child message: target contract, inherited sender/origin/signer, chain and `transaction_timestamp`, `value = 0`, `is_init = false`. Each executor maps `transaction_timestamp` back to the contract-facing `datetime`; v0.2's contract-facing message has no signer field, so its executor carries the signer beside the message and forwards it unchanged; contracts on that line still cannot observe it. |
| `code` | `None`; the child reads the target code through host 0 storage. |
| `host_data`, `gas_data` | Copied by the manager from the outer request, not carried in the envelope. |
| `method_hosts` | Constructed by the manager: host 0 by default, `consume_result` and `run_nested` on host 1. |
Expand Down Expand Up @@ -286,7 +286,7 @@ Infrastructure faults are internal errors; contract-produced outcomes stay
contract-visible. The dividing line is whether a contract can reach the
condition.

Internal: a host that cannot answer `resolve_callcontract_executor` (the host is
Internal: a host that cannot answer `resolve_call_contract_executor` (the host is
the authority on which line owns an address); a nested callee reporting an
internal error, which propagates unchanged; a callee reporting any effect, which
the manager refuses outright since a `CallContract` child holds none of the
Expand Down Expand Up @@ -355,7 +355,7 @@ determinism hazard named above.
updates Rust, Python and RST — see
[genvm-tool.md](../contributing/howto/genvm-tool.md).
- **Resolution is opt-in.** A run request carries `hook_cross_contract_calls`,
default false. False routes `resolve_callcontract_executor` to host 1, where
default false. False routes `resolve_call_contract_executor` to host 1, where
the manager answers null; true routes it to host 0 so the node decides. The
default is "manager answers null" because a node that does not route across
majors should pay neither the round-trip nor the cost of implementing a host
Expand Down
18 changes: 10 additions & 8 deletions docs/contributing/howto/committing/runners.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ Before committing:

1. Set `dev-mode.nix` back to `false`
2. Set every `"test"` hash in `current.nix` to `null`
3. Run `runners/support/versions/hash-updater.py`, from anywhere inside the
repo. It builds the umbrella's `#runners-all` with `--keep-going`, writes
every reported `got:` value back into `current.nix`, and repeats until a
fixed point. Hashes are content-addressed and depend on resolved dependency
uids, so they must be discovered bottom-up, which the script handles. It
needs the executor nested under the manager umbrella; a standalone checkout
cannot build `runners-all`
4. Commit once every hash is a real `sha256-…` value
3. Stage the runner changes and run pre-commit; fix and restage them before
hash discovery so the hooks and Nix inspect the same source tree
4. Run `runners/support/versions/hash-updater.py`, from anywhere inside the
repo. It builds the umbrella's `#runners-all` with `--keep-going`, writes
every reported `got:` value back into `current.nix`, and repeats until a
fixed point. Hashes are content-addressed and depend on resolved dependency
uids, so they must be discovered bottom-up, which the script handles. It
needs the executor nested under the manager umbrella; a standalone checkout
cannot build `runners-all`
5. Stage `current.nix` and commit once every hash is a real `sha256-…` value

**Never restore an old hash by hand.** It becomes wrong the moment any source or
dependency changes, and only `hash-updater.py` produces a correct one
Expand Down
6 changes: 3 additions & 3 deletions docs/contributing/howto/committing/submodules.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ are normally ahead-only. Runner hash hygiene first: [runners.md](runners.md)

## Why There Are Two Runner Trees

Forward-rolling lines share the top-level `runners/<id>/<aa>/<rest>.tar` tree,
named by Crockford base32 of `sha256(tar)`; frozen v0.2.x keeps
`executor/<version>/legacy-runners/` with Nix base32 hashes. `flake.nix` splits
Forward-rolling lines share the top-level `runners/<id>/<aa>/<rest>.zip` tree,
named by Crockford base32 of `sha256(zip)`; frozen v0.2.x keeps
`executor/<version>/legacy-runners/` as ustar tars with Nix base32 hashes. `flake.nix` splits
the runner list per line, and `genvm check` verifies each with its own scheme
Loading
Loading