feat: add delegate contract PUT, UPDATE, and SUBSCRIBE capabilities (#2829, #2831, #2830)#2885
feat: add delegate contract PUT, UPDATE, and SUBSCRIBE capabilities (#2829, #2831, #2830)#2885
Conversation
Delegates can now emit PutContractRequest to store contracts via the runtime. The request is handled fire-and-forget: the runtime upserts the contract state and sends a PutContractResponse back to the delegate. Changes: - delegate.rs: Handle PutContractRequest in process_outbound() and PutContractResponse in inbound_app_message() - contract/mod.rs: Extend handle_delegate_with_contract_requests() to process PutContractRequest via upsert_contract_state() - test-delegate-capabilities: Add PutContractState command, response handling, and unit tests Requires freenet-stdlib feat-delegate-contract-put branch which adds PutContractRequest/PutContractResponse types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…2830) Extends PR #2885 with two more delegate-contract capabilities: - UPDATE: delegates can update contract state via UpdateContractRequest (fire-and-forget, converts UpdateData to state/delta for upsert) - SUBSCRIBE: delegates can request subscription via SubscribeContractRequest (registration acknowledged, notification delivery deferred to async v2 API) Both follow the same pattern as GET/PUT: outbound request from delegate, runtime passes to executor, response sent back as inbound message. Adds 6 new unit tests in test-delegate-capabilities covering request emission and response handling (success + error) for both capabilities. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Collapse 6 identical match arms in inbound_app_message into unified loop - Extract DelegateState::for_app() and app_id() helpers in test delegate - Extract build_contract_response() shared helper for PUT/UPDATE/SUBSCRIBE Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SUBSCRIBE now returns Err (not yet implemented) instead of misleading Ok - Fix stale error message "GetContractRequest" -> "contract request" - Add WASM runtime integration tests for PUT, UPDATE, and SUBSCRIBE (verifies full round-trip through WASM serialization boundary) - Document local-only semantics: PUT/UPDATE call upsert_contract_state directly and do NOT propagate to the network via operation state machine - Document which UpdateData variants are supported (State, Delta) and why compound variants are rejected (missing related contract context) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
||
| [dependencies] | ||
| freenet-stdlib = { version = "0.1.34", features = ["contract"] } | ||
| freenet-stdlib = { git = "https://github.com/freenet/freenet-stdlib.git", branch = "feat-delegate-contract-put", features = ["contract"] } |
There was a problem hiding this comment.
will have to publish first the changes in stdlib, then update this before merging
There was a problem hiding this comment.
Acknowledged — will update to published crate version once freenet-stdlib PR #48 is merged and released. [AI-assisted - Claude]
| /// 3. Repeats until no more GetContractRequest messages | ||
| /// When a delegate emits contract request messages, this function: | ||
| /// 1. For GET: Fetches the contract state and sends GetContractResponse back to the delegate | ||
| /// 2. For PUT: Upserts the contract state (fire-and-forget, sends PutContractResponse back) |
There was a problem hiding this comment.
@claude all this operations mutate contract state, are they propagating properly from the contract actor to network ops?
There was a problem hiding this comment.
Claude finished @iduartgomez's task in 1m 7s —— View job
Analyzing state propagation 
Todo List
- Read the changed contract/mod.rs file to understand delegate operation implementation
- Trace how upsert_contract_state works
- Check if state mutations propagate to network operations
- Provide analysis and answer
Analysis
I've traced through the code to understand how delegate operations (PUT, UPDATE) propagate state changes to the network. Here's what I found:
Current Implementation
The delegate operations in this PR call upsert_contract_state directly (lines 164-172 for PUT, lines 297-305 for UPDATE in crates/core/src/contract/mod.rs). This function:
- Stores state locally via the state store
- Does emit network propagation events - specifically
NodeEvent::BroadcastStateChange
Where Network Propagation Happens
Looking at crates/core/src/contract/executor/runtime.rs:
For NEW contracts (PUT) - Line 808-836:
// Notify network peers of new contract state (automatic propagation)
if let Some(op_manager) = &self.op_manager {
if let Err(err) = op_manager
.notify_node_event(
crate::message::NodeEvent::BroadcastStateChange {
key,
new_state: incoming_state.clone(),
},
)
.await
{ /* ... */ }
}For existing contracts (UPDATE) - Line 1762-1778 in attempt_state_update():
// Notify network peers of state change (automatic propagation)
// Echo-back prevention is handled by summary comparison in p2p_protoc
if let Some(op_manager) = &self.op_manager {
if let Err(err) = op_manager
.notify_node_event(crate::message::NodeEvent::BroadcastStateChange {
key: *key,
new_state: new_state.clone(),
})
.await
{ /* ... */ }
}The Critical Difference
However, there's an important architectural distinction mentioned in the comments at lines 149-152:
"Note: This stores the state locally via upsert_contract_state but does NOT propagate to the network via the PUT operation state machine. Network propagation would require going through the full ContractHandlerEvent::PutQuery path."
What this means:
- ✅ State IS propagated via
BroadcastStateChangeevents (passive broadcast to peers) - ❌ State does NOT go through the full PUT/UPDATE operation state machines
The difference is:
- Direct
upsertpath (used by delegates): EmitsBroadcastStateChange→ peers receive notifications - Full operation path (used by normal operations): Goes through the complete operation state machine with retries, peer selection, caching strategies, etc.
Answer
Yes, state mutations ARE propagating to network operations, but through a simplified path:
- Delegate operations call
upsert_contract_state upsert_contract_statestores locally and emitsBroadcastStateChangeevents- The
OpManagerreceives these events and broadcasts to network peers
The comments in the code are slightly misleading - they say "does NOT propagate to the network" but what they really mean is "does not use the full operation state machine for propagation." The state changes DO reach the network via BroadcastStateChange events.
Is this correct? Based on the code, yes. The op_manager field is populated in production (it's only None in some test scenarios), so network propagation should work. The main limitation is that it uses a simpler broadcast mechanism rather than the sophisticated peer-selection and retry logic of the full operation state machines.
There was a problem hiding this comment.
@sanity shjouldn't this propagate throught the network? and use proper operations for that? rev-iew throughtfully this is actually the case and we are not taking shortcuts or hacking it.
There was a problem hiding this comment.
Traced this thoroughly — it does propagate properly. Both the normal client path and the delegate path converge at upsert_contract_state(), which emits BroadcastStateChange automatically. This event is handled in p2p_protoc.rs:1771+ and creates proper UpdateMsg::BroadcastTo operations sent to network peers.
The earlier "local-only" comments I added were incorrect and are now fixed. See the detailed analysis in the PR-level comment above.
[AI-assisted - Claude]
iduartgomez
left a comment
There was a problem hiding this comment.
check network propagation from ops origianted by delegates
|
this needs more tests at the integration level ; (using a real contract+delegate so we can test E2E) either modify freenet-ping-app to use delegates or use river in the 6 peer test to initiate changes and verify them (or both) |
|
Re: network propagation concern from @iduartgomez — I traced the propagation path thoroughly and the delegate operations DO propagate to the network correctly. Here's the chain:
The normal client PUT path is: client → PUT operation state machine → The delegate PUT path is: delegate → Both paths converge at I had earlier added incorrect comments saying "local-only / does NOT propagate to the network" — those are now fixed to accurately reflect that propagation IS automatic via Re: E2E integration tests — working on adding tests with real contract+delegate as requested. [AI-assisted - Claude] |
The comments incorrectly stated that PUT/UPDATE from delegates were "local-only" and didn't propagate to the network. In fact, upsert_contract_state automatically emits BroadcastStateChange which propagates updates via the same mechanism as normal client operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add two new integration tests using real WASM modules to verify the full delegate→contract capability pipeline end-to-end: - test_delegate_contract_put_and_update: Registers a delegate, has it issue PutContractRequest and UpdateContractRequest to a real contract, then verifies state via direct GET. Tests the complete flow through the WASM runtime, executor, and contract handler. - test_delegate_contract_get: PUTs a contract directly, then has a delegate issue GetContractRequest and verifies the state is returned correctly through the delegate response pipeline. Both tests use test-delegate-capabilities (WASM delegate) and test-contract-integration (WASM contract with TodoList state model). Addresses review feedback requesting E2E tests with real contract+delegate pairs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Added two E2E integration tests using real WASM modules (test-delegate-capabilities + test-contract-integration) that exercise the full delegate→contract pipeline:
Both tests use [AI-assisted - Claude] |
824b9f2 to
136c999
Compare
Pull request was closed
The feat-delegate-contract-put branch was merged and 0.1.35 published to crates.io. Update workspace and test-delegate-capabilities deps from git branch references to the published crate version. Also fix clippy clone_on_copy warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
136c999 to
e2fd946
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Closing to re-create PR — GitHub Actions stopped triggering CI after a close/reopen cycle. [AI-assisted - Claude] |
Summary
Implements three delegate-contract capabilities from issue #2827:
PutContractRequest(fire-and-forget, usesupsert_contract_state)UpdateContractRequest(fire-and-forget, convertsUpdateDatato state/delta for upsert)SubscribeContractRequest. Registration is acknowledged, but notification delivery to the delegate is deferred to Nacho's async delegate v2 API (the current API has no mechanism to push notifications to delegates)All three follow the same fire-and-forget pattern: delegate emits an outbound request, the runtime passes it to the executor, and the response is sent back as an inbound message.
Status of all #2827 capabilities
Dependencies
Requires freenet-stdlib PR freenet/freenet-stdlib#48 which adds the new types (
UpdateContractRequest,UpdateContractResponse,SubscribeContractRequest,SubscribeContractResponse).Test plan
test-delegate-capabilities(GET: 8, PUT: 3, UPDATE: 3, SUBSCRIBE: 3)wasm_runtime::delegate::test(existing + new types compile and run)cargo clippycleancargo fmtclean[AI-assisted - Claude]