-
-
Couldn't load subscription status.
- Fork 106
Description
Client-initiated PUT requests not cached locally when forwarded to target peer
Problem
When a client publishes a contract update via fdev publish, the local node fails to cache the new state if it determines another peer should be the primary holder. This causes the publishing node to continue serving stale cached state even after successfully initiating a PUT operation.
Steps to Reproduce
- Run Freenet node in network mode with at least one peer connection
- Publish a contract update via
fdev publishfor a contract that routes to a remote peer (contract location != local peer location) - Observe PUT operation is forwarded to remote peer
- Access the contract via HTTP gateway immediately after publishing
Expected: Local node serves the newly published state
Actual: Local node serves old cached state from before the PUT
Observed Behavior
From logs during cargo make publish-river (contract BcfxyjCH4snaknrBoCiqhYc9UFvmiJvhsp5d4L5DuvRa):
[00:01:18] PUT request initiated by client
[00:01:18] Determined PUT routing target: v6MWKgqHiBMNcGtG (@ 0.923911415153386)
[00:01:18] Forwarding PUT to target peer
[00:01:18] Sending outbound message to peer
[00:02:23] Transaction timed out: 01K8M1WZYG2NCWGQR8JDN0MH01
After timeout, HTTP gateway still serves old state:
[00:02:23] fetched contract state, contract: BcfxyjCH4snaknrBoCiqhYc9UFvmiJvhsp5d4L5DuvRa,
state_size: 2134337 // OLD STATE
New state size should be: 2218761 bytes
Root Cause
In crates/core/src/operations/put.rs, the request_put() function at lines ~955-1130:
// Find the optimal target for this contract let target = op_manager .ring .closest_potentially_caching(&key, [&own_location.peer].as_slice()); if target.is_none() { // NO OTHER PEERS: Store locally then broadcast let updated_value = put_contract(op_manager, key, value, ...).await?; // ... broadcast to subscribers } else { // PEER FOUND: Forward WITHOUT storing locally let target_peer = target.unwrap(); put_op.state = Some(PutState::AwaitingResponse { ... }); let msg = PutMsg::RequestPut { id, sender: own_location, contract, related_contracts, value, htl, target: target_peer, }; // DIRECTLY FORWARDS - NO LOCAL CACHE UPDATE op_manager.notify_op_change(NetMessage::from(msg), OpEnum::Put(put_op)).await?; }
The else branch forwards the PUT without calling put_contract(), so the local node never caches the new state.
Relationship to PR #1996
PR #1996 fixed a similar issue for incoming PUT requests (when receiving from network), ensuring upsert_contract_state() persists merged state after validation.
However, that fix only applies to the process_put_request() code path. The request_put() function (client-initiated PUTs) has a separate code path that was not addressed.
Suggested Fix
The request_put() function should always call put_contract() to update the local cache before forwarding to the target peer:
if target.is_none() { // No other peers - handle locally let updated_value = put_contract(op_manager, key, value, ...).await?; // ... broadcast logic } else { // Peer found - cache locally THEN forward let target_peer = target.unwrap(); // NEW: Cache locally first let updated_value = put_contract(op_manager, key, value.clone(), related_contracts.clone(), &contract).await?; // THEN forward to target peer put_op.state = Some(PutState::AwaitingResponse { key, upstream: None, contract: contract.clone(), state: updated_value.clone(), // Use cached value subscribe, }); let msg = PutMsg::RequestPut { ... }; op_manager.notify_op_change(...).await?; }
This ensures:
- Publishing node immediately has the new state cached
- Contract is available via HTTP gateway even if remote PUT times out
- Behavior matches PR fix: persist contract state after PUT merge in upsert_contract_state #1996 's intent for local caching
- Client-initiated PUTs work the same way as network-received PUTs
Impact
Affected scenarios:
- Contract publishing via
fdev publishwhen running in network mode - Any client-initiated PUT that routes to a non-local peer
- Particularly impacts webapp contracts where publishers expect immediate availability
Not affected:
- Local mode (no other peers, always uses
target.is_none()path) - PUTs received from other peers (fixed by PR fix: persist contract state after PUT merge in upsert_contract_state #1996 )
Environment
- Freenet version:
0.1.34(includes PR fix: persist contract state after PUT merge in upsert_contract_state #1996 ) - Mode:
network(required to reproduce) - Contract type: WebApp container, but affects all contract types
Additional Context
This was discovered while publishing River webapp updates. The build timestamp embedded in the webapp confirmed the local node was serving stale state even though the PUT operation had been initiated successfully.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status