-
-
Notifications
You must be signed in to change notification settings - Fork 107
Description
Bug Report: GET Operation Fails with "missing contract parameters" (v0.1.25)
Summary
When a user attempts to join a River chat room on v0.1.25 (with actor system enabled by default), the GET operation fails with "missing contract parameters" error during the contract storage phase.
Error Details
2025-09-14T17:28:42.936402Z ERROR freenet::contract::executor::runtime: Failed to process contract handler event error=Failed to handle state storage request: StdContractError(Put { key: ContractKey(c24ab5f89301c9b85614a476e0d5c82ffc3dd60fd05a66b67bb6c949e936f623), cause: "missing contract parameters" })
Root Cause Analysis with Code
The issue occurs in the contract parameter retrieval flow when a peer receives a contract from another peer via GET operation.
1. Where the error is thrown - upsert_contract_state()
crates/core/src/contract/executor/runtime.rs:35-64
async fn upsert_contract_state(
&mut self,
key: ContractKey,
update: Either<WrappedState, StateDelta<'static>>,
related_contracts: RelatedContracts<'static>,
code: Option<ContractContainer>, // <-- This is None when error occurs
) -> Result<UpsertResult, ExecutorError> {
// ...
let params = if let Some(code) = &code {
code.params()
} else {
self.state_store
.get_params(&key)
.await
.map_err(ExecutorError::other)?
.ok_or_else(|| {
ExecutorError::request(StdContractError::Put {
key,
cause: "missing contract parameters".into(), // <-- ERROR HERE
})
})?
};2. The problematic function - get_contract_locally()
crates/core/src/contract/executor/runtime.rs:912-926
This function returns None if parameters aren't found, even if contract code exists:
async fn get_contract_locally(
&self,
key: &ContractKey,
) -> Result<Option<ContractContainer>, ExecutorError> {
let Some(parameters) = self
.state_store
.get_params(key)
.await
.map_err(ExecutorError::other)?
else {
return Ok(None); // <-- Returns None if params not in state_store!
};
let Some(contract) = self.runtime.contract_store.fetch_contract(key, ¶meters) else {
return Ok(None);
};
Ok(Some(contract))
}3. How GET operation retrieves and stores contracts
crates/core/src/operations/get.rs:526-529
First, GET tries to fetch the contract with code:
// get.rs:526-529
let get_result = op_manager
.notify_contract_handler(ContractHandlerEvent::GetQuery {
key,
return_contract_code: fetch_contract, // <-- Set to true
})
.await;This eventually calls fetch_contract() runtime.rs:870-910:
async fn fetch_contract(
&mut self,
key: ContractKey,
return_contract_code: bool,
) -> Result<StoreResponse, ExecutorError> {
// ...
let contract = if return_contract_code {
self.get_contract_locally(&key)
.await? // <-- Returns None if params missing!
} else {
None
};
// ...
Ok(StoreResponse { state, contract })
}Later, when storing the contract locally get.rs:931-938:
let res = op_manager
.notify_contract_handler(ContractHandlerEvent::PutQuery {
key,
state: value.clone(),
related_contracts: RelatedContracts::default(),
contract: contract.clone(), // <-- This is None due to missing params!
})
.await?;4. The contract store's fetch method
crates/core/src/wasm_runtime/contract_store.rs:203-222
The fetch_contract() in ContractStore requires both key AND parameters:
pub fn fetch_contract(
&self,
key: &ContractKey,
params: &Parameters<'_>, // <-- Needs params to fetch!
) -> Option<ContractContainer> {
let result = key
.code_hash()
.and_then(|code_hash| {
self.contract_cache.get(code_hash).map(|data| {
Some(ContractContainer::Wasm(ContractWasmAPIVersion::V1(
WrappedContract::new(data.value().clone(), params.clone().into_owned()),
)))
})
})
.flatten();
// ...
}The Problem Sequence
- Peer A has a contract with parameters stored
- Peer B requests the contract via GET operation
- Peer A responds with contract (state + code)
- Peer B tries to store it locally:
- Calls
fetch_contract()withreturn_contract_code=true get_contract_locally()checks for params in state_store- No params found (contract just arrived from network!)
- Returns
Nonefor contract
- Calls
- PutQuery receives
contract=None upsert_contract_state()needs params but can't get them- Fails with "missing contract parameters"
Why This Breaks Now
In v0.1.25 with the actor system, the timing and execution flow may have changed, exposing this latent issue where parameters aren't persisted before they're needed.
Key Insight
The code assumes parameters are already in state_store when get_contract_locally() is called, but for contracts received from remote peers, the parameters haven't been stored yet. The contract exists in the contract_store cache but can't be retrieved without the parameters.
Proposed Fix Options
- Store parameters when receiving contract from network - Ensure params are persisted before attempting to use the contract
- Modify
get_contract_locally()- Allow it to extract params from the contract in contract_store if not in state_store - Change the flow - Store the complete contract (with params) when first received from network
Request for Feedback
@nacho - Since you wrote much of this contract handling code, could you review this analysis? Specifically:
- Is the assumption that params should already be in state_store correct?
- Should
get_contract_locally()handle the case of missing params differently? - What's the intended flow for storing contracts received from other peers?
[AI-assisted debugging and analysis]
Metadata
Metadata
Assignees
Labels
Type
Projects
Status