-
Notifications
You must be signed in to change notification settings - Fork 1
feat(server): allocate allowances from HostAdmin without a forged frame #467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
841b883
a510b55
64a2e01
160363c
9a095a6
3760c82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,9 @@ use std::time::Instant; | |
| use web_time::Instant; | ||
|
|
||
| use crate::chain_runtime::RuntimeFailure; | ||
| use core::sync::atomic::{AtomicU64, Ordering}; | ||
|
|
||
| use crate::host_core::HostAllowanceOrigin; | ||
| use crate::host_logic::bulletin::preimage_key; | ||
| use crate::host_logic::dotns::{NavigateDecision, external_host, parse_navigate}; | ||
| use crate::host_logic::features::{chain_info, feature_supported, supported_chains}; | ||
|
|
@@ -605,6 +608,52 @@ impl ProductRuntimeHost { | |
| service.set_authorization_status(&request, status).await | ||
| } | ||
|
|
||
| /// Allocate product-scoped resources on the host's own initiative. | ||
| /// | ||
| /// Reaches the same authority operation as [`ResourceAllocation::request`] | ||
| /// without raising [`UserConfirmationReview::ResourceAllocation`]: that | ||
| /// review asks the user to approve a *product's* request, and there is no | ||
| /// product asking here. Hosts that want to prompt for a host-initiated | ||
| /// allocation own that decision, and `origin` records which lifecycle | ||
| /// moment asked so a host policy can branch on it. | ||
| #[instrument( | ||
| skip_all, | ||
| fields(runtime.method = "resource_allocation.host_request", origin = ?origin) | ||
| )] | ||
| pub(crate) async fn allocate_resources_for_host( | ||
| &self, | ||
| resources: Vec<v01::AllocatableResource>, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please refuse |
||
| origin: HostAllowanceOrigin, | ||
| ) -> Result<Vec<v01::AllocationOutcome>, v01::GenericError> { | ||
| let Some(session) = self.authority.current_session() else { | ||
| return Err(v01::GenericError { | ||
| reason: "No active session".to_string(), | ||
| }); | ||
| }; | ||
| let mut cx = CallContext::with_request_id(self.host_allowance_request_id(origin)); | ||
| cx.set_timeout(RESOURCE_ALLOCATION_REMOTE_AUTHORITY_RESPONSE_TIMEOUT); | ||
| let request = v01::HostRequestResourceAllocationRequest { resources }; | ||
| remote_authority_call( | ||
| &cx, | ||
| self.authority | ||
| .allocate_resources(&cx, &session, self.product_id(), request), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| ) | ||
| .await | ||
| .map(|response| response.outcomes) | ||
| .map_err(|err| v01::GenericError { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I know |
||
| reason: err.to_string(), | ||
| }) | ||
| } | ||
|
|
||
| /// Correlation id for a host-initiated allowance request. Unique per call | ||
| /// because the SSO channel matches responses on it, so two concurrent | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dont believe the SSO channel matches responses on this id? It looks like The uniqueness still seems worth keeping since this id shows up in the cancel error text. Can we fix the reason in the comment? |
||
| /// host allocations must not share one. | ||
| fn host_allowance_request_id(&self, origin: HostAllowanceOrigin) -> String { | ||
| static NEXT_HOST_ALLOWANCE_REQUEST: AtomicU64 = AtomicU64::new(0); | ||
| let sequence = NEXT_HOST_ALLOWANCE_REQUEST.fetch_add(1, Ordering::Relaxed); | ||
| format!("host-allowance-{}-{sequence}", origin.as_str()) | ||
| } | ||
|
|
||
| #[instrument(skip_all, fields(runtime.method = "permissions.remote_authorization"))] | ||
| async fn remote_permission_authorization( | ||
| &self, | ||
|
|
@@ -5517,6 +5566,70 @@ mod tests { | |
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn host_allowance_allocation_rejects_without_session() { | ||
| let host = ProductRuntimeHost::new_compat(stub_platform(), test_spawner()); | ||
| let err = futures::executor::block_on(host.allocate_resources_for_host( | ||
| vec![v01::AllocatableResource::StatementStoreAllowance], | ||
| HostAllowanceOrigin::StartupReadiness, | ||
| )) | ||
| .unwrap_err(); | ||
| assert_eq!(err.reason, "No active session"); | ||
| } | ||
|
|
||
| /// A product request on this platform raises the review and fails closed. | ||
| /// The host path must not consult it at all. Asserting the product path | ||
| /// first keeps the "no review" half from passing vacuously. | ||
| #[test] | ||
| fn host_allowance_allocation_raises_no_confirmation_review() { | ||
| let platform = Arc::new(StubPlatform { | ||
| resource_allocation_confirmed: false, | ||
| ..Default::default() | ||
| }); | ||
| let host = ProductRuntimeHost::new_compat(platform.clone(), test_spawner()); | ||
| install_pairing_session(&host, session_info()); | ||
| let reviews = || { | ||
| platform | ||
| .resource_allocation_reviews | ||
| .lock() | ||
| .expect("resource allocation review list mutex poisoned") | ||
| .len() | ||
| }; | ||
|
|
||
| let cx = CallContext::default(); | ||
| let declined = futures::executor::block_on(ResourceAllocation::request( | ||
| &host, | ||
| &cx, | ||
| resource_allocation_request(), | ||
| )); | ||
| assert!(declined.is_err(), "product path should fail closed here"); | ||
| assert_eq!(reviews(), 1, "product path must raise exactly one review"); | ||
|
|
||
| let outcome = futures::executor::block_on(host.allocate_resources_for_host( | ||
| vec![v01::AllocatableResource::StatementStoreAllowance], | ||
| HostAllowanceOrigin::ForegroundRenewal, | ||
| )); | ||
| assert_eq!( | ||
| reviews(), | ||
| 1, | ||
| "host-initiated allocation must not raise the product confirmation review" | ||
| ); | ||
| // Whatever the stub authority answers, it must not be the decline the | ||
| // product path produces from the same platform. | ||
| if let Err(err) = outcome { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| assert_ne!(err.reason, "User rejected resource allocation"); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn host_allowance_request_ids_are_unique_per_call() { | ||
| let host = ProductRuntimeHost::new_compat(stub_platform(), test_spawner()); | ||
| let first = host.host_allowance_request_id(HostAllowanceOrigin::Recovery); | ||
| let second = host.host_allowance_request_id(HostAllowanceOrigin::Recovery); | ||
| assert_ne!(first, second); | ||
| assert!(first.starts_with("host-allowance-recovery-"), "{first}"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn resource_allocation_rejects_when_user_declines() { | ||
| let host = ProductRuntimeHost::new_compat(stub_platform(), test_spawner()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please scope this claim to the local runtime. On the pairing-host role
resource_allocation_responseon the paired wallet raisesUserConfirmationReview::ResourceAllocationwithcalling_product_idset beforeallocating, so the user is prompted for something no product asked for and the
call waits up to 300 seconds. Only the signing-host role is silent. The README
paragraph needs the same qualifier, and
host_allowance_allocation_raises_no_confirmation_reviewwould be more accurateas
..._raises_no_local_confirmation_review.