Skip to main content

linera_execution/
execution_state_actor.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Handle requests from the synchronous execution thread of user applications.
5
6use std::{
7    collections::{BTreeMap, BTreeSet},
8    sync::Arc,
9};
10
11use custom_debug_derive::Debug;
12use futures::{channel::mpsc, StreamExt as _};
13#[cfg(with_metrics)]
14use linera_base::prometheus_util::MeasureLatency as _;
15use linera_base::{
16    data_types::{
17        Amount, ApplicationPermissions, ArithmeticError, BlobContent, BlockHeight, OracleResponse,
18        StreamUpdate, Timestamp,
19    },
20    ensure, hex_debug, hex_vec_debug, http,
21    identifiers::{
22        Account, AccountOwner, BlobId, BlobType, ChainId, EventId, GenericApplicationId,
23        OwnerSpender, StreamId,
24    },
25    ownership::ChainOwnership,
26    time::Instant,
27};
28use linera_views::{batch::Batch, context::Context, views::View};
29use oneshot::Sender;
30use reqwest::{header::HeaderMap, Client, Url};
31use tracing::{info_span, instrument, Instrument as _};
32
33use crate::{
34    execution::UserAction,
35    runtime::ContractSyncRuntime,
36    system::{CreateApplicationResult, OpenChainConfig},
37    util::{OracleResponseExt as _, RespondExt as _},
38    ApplicationDescription, ApplicationId, ExecutionError, ExecutionRuntimeContext,
39    ExecutionStateView, JsVec, Message, MessageContext, MessageKind, ModuleId, Operation,
40    OperationContext, OutgoingMessage, ProcessStreamsContext, QueryContext, QueryOutcome,
41    ResourceController, SystemMessage, SystemOperation, TransactionTracker, UserContractCode,
42    UserServiceCode,
43};
44
45/// Actor for handling requests to the execution state.
46pub struct ExecutionStateActor<'a, C> {
47    state: &'a mut ExecutionStateView<C>,
48    txn_tracker: &'a mut TransactionTracker,
49    resource_controller: &'a mut ResourceController<Option<AccountOwner>>,
50}
51
52#[cfg(with_metrics)]
53pub(crate) mod metrics {
54    use linera_base::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
55    use prometheus::HistogramVec;
56
57    linera_base::declare_metrics! {
58        /// Histogram of the latency to load a contract bytecode.
59        pub static LOAD_CONTRACT_LATENCY: HistogramVec =
60            register_histogram_vec(
61                "load_contract_latency",
62                "Load contract latency",
63                &[],
64                exponential_bucket_latencies(250.0),
65            );
66
67        /// Histogram of the latency to load a service bytecode.
68        pub static LOAD_SERVICE_LATENCY: HistogramVec =
69            register_histogram_vec(
70                "load_service_latency",
71                "Load service latency",
72                &[],
73                exponential_bucket_latencies(250.0),
74            );
75    }
76}
77
78pub(crate) type ExecutionStateSender = mpsc::UnboundedSender<ExecutionRequest>;
79
80impl<'a, C> ExecutionStateActor<'a, C>
81where
82    C: Context + Clone + 'static,
83    C::Extra: ExecutionRuntimeContext,
84{
85    /// Creates a new execution state actor.
86    pub fn new(
87        state: &'a mut ExecutionStateView<C>,
88        txn_tracker: &'a mut TransactionTracker,
89        resource_controller: &'a mut ResourceController<Option<AccountOwner>>,
90    ) -> Self {
91        Self {
92            state,
93            txn_tracker,
94            resource_controller,
95        }
96    }
97
98    #[instrument(skip_all, fields(application_id = %id))]
99    pub(crate) async fn load_contract(
100        &mut self,
101        id: ApplicationId,
102    ) -> Result<(UserContractCode, ApplicationDescription), ExecutionError> {
103        #[cfg(with_metrics)]
104        let _latency = metrics::LOAD_CONTRACT_LATENCY.measure_latency();
105        let blob_id = id.description_blob_id();
106        let description = match self.txn_tracker.get_blob_content(&blob_id) {
107            Some(blob) => bcs::from_bytes(blob.bytes())?,
108            None => {
109                self.state
110                    .system
111                    .describe_application(id, self.txn_tracker)
112                    .await?
113            }
114        };
115        let code = self
116            .state
117            .context()
118            .extra()
119            .get_user_contract(&description, self.txn_tracker)
120            .await?;
121        Ok((code, description))
122    }
123
124    pub(crate) async fn load_service(
125        &mut self,
126        id: ApplicationId,
127    ) -> Result<(UserServiceCode, ApplicationDescription), ExecutionError> {
128        #[cfg(with_metrics)]
129        let _latency = metrics::LOAD_SERVICE_LATENCY.measure_latency();
130        let blob_id = id.description_blob_id();
131        let description = match self.txn_tracker.get_blob_content(&blob_id) {
132            Some(blob) => bcs::from_bytes(blob.bytes())?,
133            None => {
134                self.state
135                    .system
136                    .describe_application(id, self.txn_tracker)
137                    .await?
138            }
139        };
140        let code = self
141            .state
142            .context()
143            .extra()
144            .get_user_service(&description, self.txn_tracker)
145            .await?;
146        Ok((code, description))
147    }
148
149    // TODO(#1416): Support concurrent I/O.
150    #[instrument(
151        skip_all,
152        fields(request_type = %request.as_ref())
153    )]
154    pub(crate) async fn handle_request(
155        &mut self,
156        request: ExecutionRequest,
157    ) -> Result<(), ExecutionError> {
158        use ExecutionRequest::*;
159        match request {
160            #[cfg(not(web))]
161            LoadContract { id, callback } => {
162                let (code, description) = self.load_contract(id).await?;
163                callback.respond((code, description))
164            }
165            #[cfg(not(web))]
166            LoadService { id, callback } => {
167                let (code, description) = self.load_service(id).await?;
168                callback.respond((code, description))
169            }
170
171            ChainBalance { callback } => {
172                let balance = *self.state.system.balance.get();
173                callback.respond(balance);
174            }
175
176            OwnerBalance { owner, callback } => {
177                let balance = self
178                    .state
179                    .system
180                    .balances
181                    .get(&owner)
182                    .await?
183                    .unwrap_or_default();
184                callback.respond(balance);
185            }
186
187            OwnerBalances { callback } => {
188                callback.respond(self.state.system.balances.index_values().await?);
189            }
190
191            BalanceOwners { callback } => {
192                let owners = self.state.system.balances.indices().await?;
193                callback.respond(owners);
194            }
195
196            Allowance {
197                owner,
198                spender,
199                callback,
200            } => {
201                let owner_spender = OwnerSpender::new(owner, spender);
202                let allowance = self
203                    .state
204                    .system
205                    .allowances
206                    .get(&owner_spender)
207                    .await?
208                    .unwrap_or_default();
209                callback.respond(allowance);
210            }
211
212            Allowances { callback } => {
213                let entries: Vec<_> = self
214                    .state
215                    .system
216                    .allowances
217                    .index_values()
218                    .await?
219                    .into_iter()
220                    .map(|(os, amount)| (os.owner, os.spender, amount))
221                    .collect();
222                callback.respond(entries);
223            }
224
225            Transfer {
226                source,
227                destination,
228                amount,
229                signer,
230                application_id,
231                callback,
232            } => {
233                let maybe_message = self
234                    .state
235                    .system
236                    .transfer(signer, Some(application_id), source, destination, amount)
237                    .await?;
238                self.txn_tracker.add_outgoing_messages(maybe_message);
239                callback.respond(());
240            }
241
242            Claim {
243                source,
244                destination,
245                amount,
246                signer,
247                application_id,
248                callback,
249            } => {
250                let maybe_message = self
251                    .state
252                    .system
253                    .claim(
254                        signer,
255                        Some(application_id),
256                        source.owner,
257                        source.chain_id,
258                        destination,
259                        amount,
260                    )
261                    .await?;
262                self.txn_tracker.add_outgoing_messages(maybe_message);
263                callback.respond(());
264            }
265
266            Approve {
267                owner,
268                spender,
269                amount,
270                signer,
271                application_id,
272                callback,
273            } => {
274                self.state
275                    .system
276                    .approve(signer, Some(application_id), owner, spender, amount)
277                    .await?;
278                callback.respond(());
279            }
280
281            TransferFrom {
282                owner,
283                spender,
284                destination,
285                amount,
286                signer,
287                application_id,
288                callback,
289            } => {
290                let maybe_message = self
291                    .state
292                    .system
293                    .transfer_from(
294                        signer,
295                        Some(application_id),
296                        owner,
297                        spender,
298                        destination,
299                        amount,
300                    )
301                    .await?;
302                self.txn_tracker.add_outgoing_messages(maybe_message);
303                callback.respond(());
304            }
305
306            SystemTimestamp { callback } => {
307                let timestamp = self.state.system.progress.get().timestamp;
308                callback.respond(timestamp);
309            }
310
311            ChainOwnership { callback } => {
312                let ownership = self.state.system.ownership.get().await?.clone();
313                callback.respond(ownership);
314            }
315
316            ApplicationPermissions { callback } => {
317                let permissions = self
318                    .state
319                    .system
320                    .application_permissions
321                    .get()
322                    .await?
323                    .clone();
324                callback.respond(permissions);
325            }
326
327            ReadApplicationDescription {
328                application_id,
329                callback,
330            } => {
331                let blob_id = application_id.description_blob_id();
332                let description = match self.txn_tracker.get_blob_content(&blob_id) {
333                    Some(blob) => bcs::from_bytes(blob.bytes())?,
334                    None => {
335                        let blob_content = self.state.system.read_blob_content(blob_id).await?;
336                        self.state
337                            .system
338                            .blob_used(self.txn_tracker, blob_id)
339                            .await?;
340                        bcs::from_bytes(blob_content.bytes())?
341                    }
342                };
343                callback.respond(description);
344            }
345
346            ContainsKey { id, key, callback } => {
347                let view = self.state.users.try_load_entry(&id).await?;
348                let result = match view {
349                    Some(view) => view.contains_key(&key).await?,
350                    None => false,
351                };
352                callback.respond(result);
353            }
354
355            ContainsKeys { id, keys, callback } => {
356                let view = self.state.users.try_load_entry(&id).await?;
357                let result = match view {
358                    Some(view) => view.contains_keys(&keys).await?,
359                    None => vec![false; keys.len()],
360                };
361                callback.respond(result);
362            }
363
364            ReadMultiValuesBytes { id, keys, callback } => {
365                let view = self.state.users.try_load_entry(&id).await?;
366                let values = match view {
367                    Some(view) => view.multi_get(&keys).await?,
368                    None => vec![None; keys.len()],
369                };
370                callback.respond(values);
371            }
372
373            ReadValueBytes { id, key, callback } => {
374                let view = self.state.users.try_load_entry(&id).await?;
375                let result = match view {
376                    Some(view) => view.get(&key).await?,
377                    None => None,
378                };
379                callback.respond(result);
380            }
381
382            FindKeysByPrefix {
383                id,
384                key_prefix,
385                callback,
386            } => {
387                let view = self.state.users.try_load_entry(&id).await?;
388                let result = match view {
389                    Some(view) => view.find_keys_by_prefix(&key_prefix).await?,
390                    None => Vec::new(),
391                };
392                callback.respond(result);
393            }
394
395            FindKeyValuesByPrefix {
396                id,
397                key_prefix,
398                callback,
399            } => {
400                let view = self.state.users.try_load_entry(&id).await?;
401                let result = match view {
402                    Some(view) => view.find_key_values_by_prefix(&key_prefix).await?,
403                    None => Vec::new(),
404                };
405                callback.respond(result);
406            }
407
408            WriteBatch {
409                id,
410                batch,
411                callback,
412            } => {
413                let mut view = self.state.users.try_load_entry_mut(&id).await?;
414                view.write_batch(batch)?;
415                callback.respond(());
416            }
417
418            OpenChain {
419                config,
420                parent_id,
421                block_height,
422                timestamp,
423                callback,
424            } => {
425                let chain_id = self
426                    .state
427                    .system
428                    .open_chain(
429                        *config,
430                        parent_id,
431                        block_height,
432                        timestamp,
433                        self.txn_tracker,
434                    )
435                    .await?;
436                callback.respond(chain_id);
437            }
438
439            CloseChain {
440                application_id,
441                callback,
442            } => {
443                let app_permissions = self.state.system.application_permissions.get().await?;
444                if !app_permissions.can_manage_chain(&application_id) {
445                    callback.respond(Err(ExecutionError::UnauthorizedApplication(application_id)));
446                } else {
447                    self.state.system.close_chain();
448                    callback.respond(Ok(()));
449                }
450            }
451
452            ChangeOwnership {
453                application_id,
454                ownership,
455                callback,
456            } => {
457                let app_permissions = self.state.system.application_permissions.get().await?;
458                if !app_permissions.can_manage_chain(&application_id) {
459                    callback.respond(Err(ExecutionError::UnauthorizedApplication(application_id)));
460                } else {
461                    self.state.system.ownership.set(ownership);
462                    callback.respond(Ok(()));
463                }
464            }
465
466            ChangeApplicationPermissions {
467                application_id,
468                application_permissions,
469                callback,
470            } => {
471                let app_permissions = self.state.system.application_permissions.get().await?;
472                if !app_permissions.can_manage_chain(&application_id) {
473                    callback.respond(Err(ExecutionError::UnauthorizedApplication(application_id)));
474                } else {
475                    self.state
476                        .system
477                        .application_permissions
478                        .set(application_permissions);
479                    callback.respond(Ok(()));
480                }
481            }
482
483            PeekApplicationIndex { callback } => {
484                let index = self.txn_tracker.peek_application_index();
485                callback.respond(index)
486            }
487
488            CreateApplication {
489                chain_id,
490                block_height,
491                module_id,
492                parameters,
493                required_application_ids,
494                callback,
495            } => {
496                let create_application_result = self
497                    .state
498                    .system
499                    .create_application(
500                        chain_id,
501                        block_height,
502                        module_id,
503                        parameters,
504                        required_application_ids,
505                        self.txn_tracker,
506                    )
507                    .await?;
508                callback.respond(create_application_result);
509            }
510
511            PerformHttpRequest {
512                request,
513                http_responses_are_oracle_responses,
514                callback,
515            } => {
516                let system = &mut self.state.system;
517                let response = self
518                    .txn_tracker
519                    .oracle(|| async {
520                        let headers = request
521                            .headers
522                            .into_iter()
523                            .map(|http::Header { name, value }| {
524                                Ok((name.parse()?, value.try_into()?))
525                            })
526                            .collect::<Result<HeaderMap, ExecutionError>>()?;
527
528                        let url = Url::parse(&request.url)?;
529                        let host = url
530                            .host_str()
531                            .ok_or_else(|| ExecutionError::UnauthorizedHttpRequest(url.clone()))?;
532
533                        let (_epoch, committee) = system
534                            .current_committee()
535                            .await?
536                            .ok_or_else(|| ExecutionError::UnauthorizedHttpRequest(url.clone()))?;
537                        let allowed_hosts = &committee.policy().http_request_allow_list;
538
539                        ensure!(
540                            allowed_hosts.contains(host),
541                            ExecutionError::UnauthorizedHttpRequest(url)
542                        );
543
544                        let request = Client::new()
545                            .request(request.method.into(), url)
546                            .body(request.body)
547                            .headers(headers);
548                        #[cfg(not(web))]
549                        let request = request.timeout(linera_base::time::Duration::from_millis(
550                            committee.policy().http_request_timeout_ms,
551                        ));
552
553                        let response = request.send().await?;
554
555                        let mut response_size_limit =
556                            committee.policy().maximum_http_response_bytes;
557
558                        if http_responses_are_oracle_responses {
559                            response_size_limit = response_size_limit
560                                .min(committee.policy().maximum_oracle_response_bytes);
561                        }
562                        Ok(OracleResponse::Http(
563                            Self::receive_http_response(response, response_size_limit).await?,
564                        ))
565                    })
566                    .await?
567                    .to_http_response()?;
568                callback.respond(response);
569            }
570
571            ReadBlobContent { blob_id, callback } => {
572                let content = if let Some(content) = self.txn_tracker.get_blob_content(&blob_id) {
573                    content.clone()
574                } else {
575                    let content = self.state.system.read_blob_content(blob_id).await?;
576                    if blob_id.blob_type == BlobType::Data {
577                        self.resource_controller
578                            .with_state(&mut self.state.system)
579                            .await?
580                            .track_blob_read(content.bytes().len() as u64)?;
581                    }
582                    self.state
583                        .system
584                        .blob_used(self.txn_tracker, blob_id)
585                        .await?;
586                    content
587                };
588                callback.respond(content)
589            }
590
591            AssertBlobExists { blob_id, callback } => {
592                self.state.system.assert_blob_exists(blob_id).await?;
593                // Treating this as reading a size-0 blob for fee purposes.
594                if blob_id.blob_type == BlobType::Data {
595                    self.resource_controller
596                        .with_state(&mut self.state.system)
597                        .await?
598                        .track_blob_read(0)?;
599                }
600                let is_new = self
601                    .state
602                    .system
603                    .blob_used(self.txn_tracker, blob_id)
604                    .await?;
605                if is_new {
606                    self.txn_tracker
607                        .replay_oracle_response(OracleResponse::Blob(blob_id))?;
608                }
609                callback.respond(());
610            }
611
612            Emit {
613                stream_id,
614                value,
615                callback,
616            } => {
617                let count = self
618                    .state
619                    .system
620                    .stream_event_counts
621                    .get_mut_or_default(&stream_id)
622                    .await?;
623                let index = *count;
624                *count = count.checked_add(1).ok_or(ArithmeticError::Overflow)?;
625                self.resource_controller
626                    .with_state(&mut self.state.system)
627                    .await?
628                    .track_event_published(&value)?;
629                self.txn_tracker.add_event(stream_id, index, value);
630                callback.respond(index)
631            }
632
633            ReadEvent { event_id, callback } => {
634                let context = self.state.context();
635                let extra = context.extra();
636                let event = self
637                    .txn_tracker
638                    .oracle(|| async {
639                        let event = extra
640                            .get_event(event_id.clone())
641                            .await?
642                            .ok_or(ExecutionError::EventsNotFound(vec![event_id.clone()]))?;
643                        Ok(OracleResponse::Event(
644                            event_id.clone(),
645                            Arc::unwrap_or_clone(event),
646                        ))
647                    })
648                    .await?
649                    .to_event(&event_id)?;
650                self.resource_controller
651                    .with_state(&mut self.state.system)
652                    .await?
653                    .track_event_read(event.len() as u64)?;
654                callback.respond(event);
655            }
656
657            SubscribeToEvents {
658                chain_id,
659                stream_id,
660                subscriber_app_id,
661                callback,
662            } => {
663                let subscriptions = self
664                    .state
665                    .system
666                    .event_subscriptions
667                    .get_mut_or_default(&(chain_id, stream_id.clone()))
668                    .await?;
669                let next_index = match subscriptions.applications.entry(subscriber_app_id) {
670                    std::collections::btree_map::Entry::Vacant(entry) => {
671                        entry.insert(0);
672                        subscriptions.min_next_index = 0;
673                        0
674                    }
675                    std::collections::btree_map::Entry::Occupied(entry) => *entry.get(),
676                };
677                // The publisher's floor isn't available on the subscriber at subscribe time;
678                // later `UpdateStream` operations carry the real one.
679                self.txn_tracker.add_stream_to_process(
680                    subscriber_app_id,
681                    chain_id,
682                    stream_id,
683                    0,
684                    0,
685                    next_index,
686                );
687                callback.respond(());
688            }
689
690            UnsubscribeFromEvents {
691                chain_id,
692                stream_id,
693                subscriber_app_id,
694                callback,
695            } => {
696                let key = (chain_id, stream_id.clone());
697                let subscriptions = self
698                    .state
699                    .system
700                    .event_subscriptions
701                    .get_mut_or_default(&key)
702                    .await?;
703                subscriptions.applications.remove(&subscriber_app_id);
704                if subscriptions.applications.is_empty() {
705                    self.state.system.event_subscriptions.remove(&key)?;
706                } else {
707                    subscriptions.recalculate_min();
708                }
709                if let crate::GenericApplicationId::User(app_id) = stream_id.application_id {
710                    self.txn_tracker
711                        .remove_stream_to_process(app_id, chain_id, stream_id);
712                }
713                callback.respond(());
714            }
715
716            GetApplicationPermissions { callback } => {
717                let app_permissions = self.state.system.application_permissions.get().await?;
718                callback.respond(app_permissions.clone());
719            }
720
721            QueryServiceOracle {
722                deadline,
723                application_id,
724                next_block_height,
725                query,
726                callback,
727            } => {
728                let state = &mut self.state;
729                let local_time = self.txn_tracker.local_time();
730                let created_blobs = self.txn_tracker.created_blobs().clone();
731                let bytes = self
732                    .txn_tracker
733                    .oracle(|| async {
734                        let context = QueryContext {
735                            chain_id: state.context().extra().chain_id(),
736                            next_block_height,
737                            local_time,
738                        };
739                        let QueryOutcome {
740                            response,
741                            operations,
742                        } = Box::pin(state.query_user_application_with_deadline(
743                            application_id,
744                            context,
745                            query,
746                            deadline,
747                            created_blobs,
748                        ))
749                        .await?;
750                        ensure!(
751                            operations.is_empty(),
752                            ExecutionError::ServiceOracleQueryOperations(operations)
753                        );
754                        Ok(OracleResponse::Service(response))
755                    })
756                    .await?
757                    .to_service_response()?;
758                callback.respond(bytes);
759            }
760
761            AddOutgoingMessage { message, callback } => {
762                self.txn_tracker.add_outgoing_message(message);
763                callback.respond(());
764            }
765
766            SetLocalTime {
767                local_time,
768                callback,
769            } => {
770                self.txn_tracker.set_local_time(local_time);
771                callback.respond(());
772            }
773
774            AssertBefore {
775                timestamp,
776                callback,
777            } => {
778                let result = if !self
779                    .txn_tracker
780                    .replay_oracle_response(OracleResponse::Assert)?
781                {
782                    // There are no recorded oracle responses, so we check the local time.
783                    let local_time = self.txn_tracker.local_time();
784                    if local_time >= timestamp {
785                        Err(ExecutionError::AssertBefore {
786                            timestamp,
787                            local_time,
788                        })
789                    } else {
790                        Ok(())
791                    }
792                } else {
793                    Ok(())
794                };
795                callback.respond(result);
796            }
797
798            AddCreatedBlob { blob, callback } => {
799                if self.resource_controller.is_free {
800                    self.txn_tracker.mark_blob_free(blob.id());
801                }
802                self.txn_tracker.add_created_blob(blob);
803                callback.respond(());
804            }
805
806            ValidationRound { round, callback } => {
807                let validation_round = self
808                    .txn_tracker
809                    .oracle(|| async { Ok(OracleResponse::Round(round)) })
810                    .await?
811                    .to_round()?;
812                callback.respond(validation_round);
813            }
814
815            HasEmptyStorage {
816                application,
817                callback,
818            } => {
819                let view = self.state.users.try_load_entry(&application).await?;
820                let result = match view {
821                    Some(view) => view.iterative_count().await? == 0,
822                    None => true,
823                };
824                callback.respond(result);
825            }
826
827            AllowApplicationLogs { callback } => {
828                let allow = self
829                    .state
830                    .context()
831                    .extra()
832                    .execution_runtime_config()
833                    .allow_application_logs;
834                callback.respond(allow);
835            }
836
837            #[cfg(web)]
838            Log { message, level } => match level {
839                tracing::log::Level::Trace | tracing::log::Level::Debug => {
840                    tracing::debug!(target: "user_application_log", message = %message);
841                }
842                tracing::log::Level::Info => {
843                    tracing::info!(target: "user_application_log", message = %message);
844                }
845                tracing::log::Level::Warn => {
846                    tracing::warn!(target: "user_application_log", message = %message);
847                }
848                tracing::log::Level::Error => {
849                    tracing::error!(target: "user_application_log", message = %message);
850                }
851            },
852        }
853
854        Ok(())
855    }
856
857    /// Calls `process_streams` for all applications that are subscribed to streams with new
858    /// events or that have new subscriptions.
859    #[instrument(skip_all)]
860    async fn process_subscriptions(
861        &mut self,
862        context: ProcessStreamsContext,
863    ) -> Result<(), ExecutionError> {
864        // Keep track of which streams we have already processed. This is to guard against
865        // applications unsubscribing and subscribing in the process_streams call itself.
866        let mut processed = BTreeSet::new();
867        loop {
868            let to_process = self
869                .txn_tracker
870                .take_streams_to_process()
871                .into_iter()
872                .filter_map(|(app_id, updates)| {
873                    let updates = updates
874                        .into_iter()
875                        .filter_map(|update| {
876                            if !processed.insert((
877                                app_id,
878                                update.chain_id,
879                                update.stream_id.clone(),
880                            )) {
881                                return None;
882                            }
883                            Some(update)
884                        })
885                        .collect::<Vec<_>>();
886                    if updates.is_empty() {
887                        return None;
888                    }
889                    Some((app_id, updates))
890                })
891                .collect::<BTreeMap<_, _>>();
892            if to_process.is_empty() {
893                return Ok(());
894            }
895            for (app_id, updates) in to_process {
896                self.run_user_action(
897                    app_id,
898                    UserAction::ProcessStreams(context, updates),
899                    None,
900                    None,
901                )
902                .await?;
903            }
904        }
905    }
906
907    /// Calls `summarize_events` for every application that has published events to one of its
908    /// streams since the previous checkpoint, then drops those streams' pre-checkpoint anchors.
909    ///
910    /// The work list is exactly the set of user streams currently in `previous_event_blocks`:
911    /// the previous checkpoint cleared the map, so an entry means the stream published a
912    /// summary at (or any event since) the previous checkpoint. Each application may emit a
913    /// fresh summary event; the block-level event-stream bookkeeping then re-anchors that
914    /// stream to the checkpoint height (with no recertification link to the now-unguaranteed
915    /// older blocks, since the map was cleared here). A stream whose application emits nothing
916    /// stays dropped and is effectively closed: it won't be summarized again unless it
917    /// publishes new events.
918    async fn summarize_events_at_checkpoint(
919        &mut self,
920        context: OperationContext,
921    ) -> Result<(), ExecutionError> {
922        let mut updates_by_app = BTreeMap::<ApplicationId, Vec<StreamUpdate>>::new();
923        for stream_id in self.state.previous_event_blocks.indices().await? {
924            let GenericApplicationId::User(application_id) = stream_id.application_id else {
925                continue;
926            };
927            let next_index = self
928                .state
929                .system
930                .stream_event_counts
931                .get(&stream_id)
932                .await?
933                .unwrap_or(0);
934            // A summary is an absolute-state snapshot, so the application is not handed an
935            // incremental range to fold in; `previous_index` is left at 0. The summary the
936            // application emits lands at `next_index`, which becomes the stream's readable floor.
937            updates_by_app
938                .entry(application_id)
939                .or_default()
940                .push(StreamUpdate {
941                    chain_id: context.chain_id,
942                    stream_id,
943                    previous_index: 0,
944                    first_index: next_index,
945                    next_index,
946                });
947        }
948
949        // Drop every pre-checkpoint anchor. Only user streams are present, since
950        // `prepare_checkpoint` rejects chains that published system events.
951        self.state.previous_event_blocks.clear();
952
953        let process_context = ProcessStreamsContext::from(context);
954        for (application_id, updates) in updates_by_app {
955            self.run_user_action(
956                application_id,
957                UserAction::SummarizeEvents(process_context, updates),
958                None,
959                None,
960            )
961            .await?;
962        }
963        Ok(())
964    }
965
966    pub(crate) async fn run_user_action(
967        &mut self,
968        application_id: ApplicationId,
969        action: UserAction,
970        refund_grant_to: Option<Account>,
971        grant: Option<&mut Amount>,
972    ) -> Result<(), ExecutionError> {
973        self.run_user_action_with_runtime(application_id, action, refund_grant_to, grant)
974            .await
975    }
976
977    // TODO(#5034): unify with `contract_and_dependencies`
978    pub(crate) async fn service_and_dependencies(
979        &mut self,
980        application: ApplicationId,
981    ) -> Result<(Vec<UserServiceCode>, Vec<ApplicationDescription>), ExecutionError> {
982        // cyclic futures are illegal so we need to either box the frames or keep our own
983        // stack
984        let mut stack = vec![application];
985        let mut codes = vec![];
986        let mut descriptions = vec![];
987
988        while let Some(id) = stack.pop() {
989            let (code, description) = self.load_service(id).await?;
990            stack.extend(description.required_application_ids.iter().rev().copied());
991            codes.push(code);
992            descriptions.push(description);
993        }
994
995        codes.reverse();
996        descriptions.reverse();
997
998        Ok((codes, descriptions))
999    }
1000
1001    // TODO(#5034): unify with `service_and_dependencies`
1002    #[instrument(skip_all, fields(application_id = %application))]
1003    async fn contract_and_dependencies(
1004        &mut self,
1005        application: ApplicationId,
1006    ) -> Result<(Vec<UserContractCode>, Vec<ApplicationDescription>), ExecutionError> {
1007        // cyclic futures are illegal so we need to either box the frames or keep our own
1008        // stack
1009        let mut stack = vec![application];
1010        let mut codes = vec![];
1011        let mut descriptions = vec![];
1012
1013        while let Some(id) = stack.pop() {
1014            let (code, description) = self.load_contract(id).await?;
1015            stack.extend(description.required_application_ids.iter().rev().copied());
1016            codes.push(code);
1017            descriptions.push(description);
1018        }
1019
1020        codes.reverse();
1021        descriptions.reverse();
1022
1023        Ok((codes, descriptions))
1024    }
1025
1026    #[instrument(skip_all, fields(application_id = %application_id))]
1027    async fn run_user_action_with_runtime(
1028        &mut self,
1029        application_id: ApplicationId,
1030        action: UserAction,
1031        refund_grant_to: Option<Account>,
1032        grant: Option<&mut Amount>,
1033    ) -> Result<(), ExecutionError> {
1034        let chain_id = self.state.context().extra().chain_id();
1035        let mut cloned_grant = grant.as_ref().map(|x| **x);
1036        let initial_balance = self
1037            .resource_controller
1038            .with_state_and_grant(&mut self.state.system, cloned_grant.as_mut())
1039            .await?
1040            .balance()?;
1041        let mut controller = ResourceController::new(
1042            self.resource_controller.policy().clone(),
1043            self.resource_controller.tracker,
1044            initial_balance,
1045        );
1046        let is_free = matches!(
1047            &action,
1048            UserAction::Message(..)
1049                | UserAction::ProcessStreams(..)
1050                | UserAction::SummarizeEvents(..)
1051        ) && self
1052            .resource_controller
1053            .policy()
1054            .is_free_app(&application_id);
1055        controller.is_free = is_free;
1056        self.resource_controller.is_free = is_free;
1057        let (execution_state_sender, mut execution_state_receiver) =
1058            futures::channel::mpsc::unbounded();
1059
1060        let (codes, descriptions): (Vec<_>, Vec<_>) =
1061            self.contract_and_dependencies(application_id).await?;
1062
1063        let allow_application_logs = self
1064            .state
1065            .context()
1066            .extra()
1067            .execution_runtime_config()
1068            .allow_application_logs;
1069
1070        let contract_runtime_task = self
1071            .state
1072            .context()
1073            .extra()
1074            .thread_pool()
1075            .run_send(JsVec(codes), move |codes| async move {
1076                let runtime = ContractSyncRuntime::new(
1077                    execution_state_sender,
1078                    chain_id,
1079                    refund_grant_to,
1080                    controller,
1081                    &action,
1082                    allow_application_logs,
1083                );
1084
1085                for (code, description) in codes.0.into_iter().zip(descriptions) {
1086                    runtime.preload_contract(ApplicationId::from(&description), code, description);
1087                }
1088
1089                runtime.run_action(application_id, chain_id, action)
1090            })
1091            .await;
1092
1093        async {
1094            while let Some(request) = execution_state_receiver.next().await {
1095                self.handle_request(request).await?;
1096            }
1097            Ok::<(), ExecutionError>(())
1098        }
1099        .instrument(info_span!("handle_runtime_requests"))
1100        .await?;
1101
1102        let (result, controller) = contract_runtime_task.await??;
1103
1104        self.resource_controller.is_free = false;
1105
1106        self.txn_tracker.add_operation_result(result);
1107
1108        self.resource_controller
1109            .with_state_and_grant(&mut self.state.system, grant)
1110            .await?
1111            .merge_balance(initial_balance, controller.balance()?)?;
1112        self.resource_controller.tracker = controller.tracker;
1113
1114        Ok(())
1115    }
1116
1117    #[instrument(skip_all, fields(
1118        chain_id = %context.chain_id,
1119        block_height = %context.height,
1120        operation_type = %operation.as_ref(),
1121    ))]
1122    /// Executes an operation, dispatching to the system or to a user application.
1123    pub async fn execute_operation(
1124        &mut self,
1125        context: OperationContext,
1126        operation: Operation,
1127    ) -> Result<(), ExecutionError> {
1128        assert_eq!(context.chain_id, self.state.context().extra().chain_id());
1129        match operation {
1130            Operation::System(op) => match *op {
1131                SystemOperation::Checkpoint => {
1132                    let prepared = self.txn_tracker.take_prepared_checkpoint().ok_or(
1133                        ExecutionError::CheckpointPreconditionFailed(
1134                            "Checkpoint operation reached the actor without prepared inputs; \
1135                             the chain-level pre-block hook must run prepare_checkpoint first",
1136                        ),
1137                    )?;
1138                    self.state
1139                        .apply_checkpoint(prepared, self.txn_tracker)
1140                        .await?;
1141                    self.summarize_events_at_checkpoint(context).await?;
1142                }
1143                op => {
1144                    let new_application = self
1145                        .state
1146                        .system
1147                        .execute_operation(context, op, self.txn_tracker, self.resource_controller)
1148                        .await?;
1149                    if let Some((application_id, argument)) = new_application {
1150                        let user_action = UserAction::Instantiate(context, argument);
1151                        self.run_user_action(
1152                            application_id,
1153                            user_action,
1154                            context.refund_grant_to(),
1155                            None,
1156                        )
1157                        .await?;
1158                    }
1159                }
1160            },
1161            Operation::User {
1162                application_id,
1163                bytes,
1164            } => {
1165                self.run_user_action(
1166                    application_id,
1167                    UserAction::Operation(context, bytes),
1168                    context.refund_grant_to(),
1169                    None,
1170                )
1171                .await?;
1172            }
1173        }
1174        self.process_subscriptions(context.into()).await?;
1175        Ok(())
1176    }
1177
1178    #[instrument(skip_all, fields(
1179        chain_id = %context.chain_id,
1180        block_height = %context.height,
1181        origin = %context.origin,
1182        is_bouncing = %context.is_bouncing,
1183        message_type = %message.as_ref(),
1184    ))]
1185    /// Executes an incoming message, dispatching to the system or to a user application.
1186    pub async fn execute_message(
1187        &mut self,
1188        context: MessageContext,
1189        message: Message,
1190        grant: Option<&mut Amount>,
1191    ) -> Result<(), ExecutionError> {
1192        assert_eq!(context.chain_id, self.state.context().extra().chain_id());
1193        match message {
1194            Message::System(message) => {
1195                let outcome = self.state.system.execute_message(context, message).await?;
1196                self.txn_tracker.add_outgoing_messages(outcome);
1197            }
1198            Message::User {
1199                application_id,
1200                bytes,
1201            } => {
1202                self.run_user_action(
1203                    application_id,
1204                    UserAction::Message(context, bytes),
1205                    context.refund_grant_to,
1206                    grant,
1207                )
1208                .await?;
1209            }
1210        }
1211        self.process_subscriptions(context.into()).await?;
1212        Ok(())
1213    }
1214
1215    /// Bounces a message back to its sender, returning any attached grant.
1216    pub fn bounce_message(
1217        &mut self,
1218        context: MessageContext,
1219        grant: Amount,
1220        message: Message,
1221    ) -> Result<(), ExecutionError> {
1222        assert_eq!(context.chain_id, self.state.context().extra().chain_id());
1223        self.txn_tracker.add_outgoing_message(OutgoingMessage {
1224            destination: context.origin,
1225            authenticated_owner: context.authenticated_owner,
1226            refund_grant_to: context.refund_grant_to.filter(|_| !grant.is_zero()),
1227            grant,
1228            kind: MessageKind::Bouncing,
1229            message,
1230        });
1231        Ok(())
1232    }
1233
1234    /// Sends a refund of the given amount to the account designated to receive grant refunds.
1235    pub fn send_refund(
1236        &mut self,
1237        context: MessageContext,
1238        amount: Amount,
1239    ) -> Result<(), ExecutionError> {
1240        assert_eq!(context.chain_id, self.state.context().extra().chain_id());
1241        if amount.is_zero() {
1242            return Ok(());
1243        }
1244        let Some(account) = context.refund_grant_to else {
1245            return Err(ExecutionError::InternalError(
1246                "Messages with grants should have a non-empty `refund_grant_to`",
1247            ));
1248        };
1249        let message = SystemMessage::Credit {
1250            amount,
1251            source: context.authenticated_owner.unwrap_or(AccountOwner::CHAIN),
1252            target: account.owner,
1253        };
1254        self.txn_tracker.add_outgoing_message(
1255            OutgoingMessage::new(account.chain_id, message).with_kind(MessageKind::Tracked),
1256        );
1257        Ok(())
1258    }
1259
1260    /// Receives an HTTP response, returning the prepared [`http::Response`] instance.
1261    ///
1262    /// Ensures that the response does not exceed the provided `size_limit`.
1263    async fn receive_http_response(
1264        response: reqwest::Response,
1265        size_limit: u64,
1266    ) -> Result<http::Response, ExecutionError> {
1267        let status = response.status().as_u16();
1268        let maybe_content_length = response.content_length();
1269
1270        let headers = response
1271            .headers()
1272            .iter()
1273            .map(|(name, value)| http::Header::new(name.to_string(), value.as_bytes()))
1274            .collect::<Vec<_>>();
1275
1276        let total_header_size = headers
1277            .iter()
1278            .map(|header| (header.name.len() + header.value.len()) as u64)
1279            .sum();
1280
1281        let mut remaining_bytes = size_limit.checked_sub(total_header_size).ok_or(
1282            ExecutionError::HttpResponseSizeLimitExceeded {
1283                limit: size_limit,
1284                size: total_header_size,
1285            },
1286        )?;
1287
1288        if let Some(content_length) = maybe_content_length {
1289            if content_length > remaining_bytes {
1290                return Err(ExecutionError::HttpResponseSizeLimitExceeded {
1291                    limit: size_limit,
1292                    size: content_length + total_header_size,
1293                });
1294            }
1295        }
1296
1297        let mut body = Vec::with_capacity(
1298            usize::try_from(maybe_content_length.unwrap_or(0)).unwrap_or(usize::MAX),
1299        );
1300        let mut body_stream = response.bytes_stream();
1301
1302        while let Some(bytes) = body_stream.next().await.transpose()? {
1303            remaining_bytes = remaining_bytes.checked_sub(bytes.len() as u64).ok_or(
1304                ExecutionError::HttpResponseSizeLimitExceeded {
1305                    limit: size_limit,
1306                    size: bytes.len() as u64 + (size_limit - remaining_bytes),
1307                },
1308            )?;
1309
1310            body.extend(&bytes);
1311        }
1312
1313        Ok(http::Response {
1314            status,
1315            headers,
1316            body,
1317        })
1318    }
1319}
1320
1321/// Requests to the execution state.
1322#[derive(Debug, strum::AsRefStr)]
1323#[allow(missing_docs)]
1324pub enum ExecutionRequest {
1325    #[cfg(not(web))]
1326    LoadContract {
1327        id: ApplicationId,
1328        #[debug(skip)]
1329        callback: Sender<(UserContractCode, ApplicationDescription)>,
1330    },
1331
1332    #[cfg(not(web))]
1333    LoadService {
1334        id: ApplicationId,
1335        #[debug(skip)]
1336        callback: Sender<(UserServiceCode, ApplicationDescription)>,
1337    },
1338
1339    ChainBalance {
1340        #[debug(skip)]
1341        callback: Sender<Amount>,
1342    },
1343
1344    OwnerBalance {
1345        owner: AccountOwner,
1346        #[debug(skip)]
1347        callback: Sender<Amount>,
1348    },
1349
1350    OwnerBalances {
1351        #[debug(skip)]
1352        callback: Sender<Vec<(AccountOwner, Amount)>>,
1353    },
1354
1355    BalanceOwners {
1356        #[debug(skip)]
1357        callback: Sender<Vec<AccountOwner>>,
1358    },
1359
1360    Allowance {
1361        owner: AccountOwner,
1362        spender: AccountOwner,
1363        #[debug(skip)]
1364        callback: Sender<Amount>,
1365    },
1366
1367    Allowances {
1368        #[debug(skip)]
1369        callback: Sender<Vec<(AccountOwner, AccountOwner, Amount)>>,
1370    },
1371
1372    Transfer {
1373        source: AccountOwner,
1374        destination: Account,
1375        amount: Amount,
1376        #[debug(skip_if = Option::is_none)]
1377        signer: Option<AccountOwner>,
1378        application_id: ApplicationId,
1379        #[debug(skip)]
1380        callback: Sender<()>,
1381    },
1382
1383    Claim {
1384        source: Account,
1385        destination: Account,
1386        amount: Amount,
1387        #[debug(skip_if = Option::is_none)]
1388        signer: Option<AccountOwner>,
1389        application_id: ApplicationId,
1390        #[debug(skip)]
1391        callback: Sender<()>,
1392    },
1393
1394    Approve {
1395        owner: AccountOwner,
1396        spender: AccountOwner,
1397        amount: Amount,
1398        #[debug(skip_if = Option::is_none)]
1399        signer: Option<AccountOwner>,
1400        application_id: ApplicationId,
1401        #[debug(skip)]
1402        callback: Sender<()>,
1403    },
1404
1405    TransferFrom {
1406        owner: AccountOwner,
1407        spender: AccountOwner,
1408        destination: Account,
1409        amount: Amount,
1410        #[debug(skip_if = Option::is_none)]
1411        signer: Option<AccountOwner>,
1412        application_id: ApplicationId,
1413        #[debug(skip)]
1414        callback: Sender<()>,
1415    },
1416
1417    SystemTimestamp {
1418        #[debug(skip)]
1419        callback: Sender<Timestamp>,
1420    },
1421
1422    ChainOwnership {
1423        #[debug(skip)]
1424        callback: Sender<ChainOwnership>,
1425    },
1426
1427    ApplicationPermissions {
1428        #[debug(skip)]
1429        callback: Sender<ApplicationPermissions>,
1430    },
1431
1432    ReadApplicationDescription {
1433        application_id: ApplicationId,
1434        #[debug(skip)]
1435        callback: Sender<ApplicationDescription>,
1436    },
1437
1438    ReadValueBytes {
1439        id: ApplicationId,
1440        #[debug(with = hex_debug)]
1441        key: Vec<u8>,
1442        #[debug(skip)]
1443        callback: Sender<Option<Vec<u8>>>,
1444    },
1445
1446    ContainsKey {
1447        id: ApplicationId,
1448        key: Vec<u8>,
1449        #[debug(skip)]
1450        callback: Sender<bool>,
1451    },
1452
1453    ContainsKeys {
1454        id: ApplicationId,
1455        #[debug(with = hex_vec_debug)]
1456        keys: Vec<Vec<u8>>,
1457        callback: Sender<Vec<bool>>,
1458    },
1459
1460    ReadMultiValuesBytes {
1461        id: ApplicationId,
1462        #[debug(with = hex_vec_debug)]
1463        keys: Vec<Vec<u8>>,
1464        #[debug(skip)]
1465        callback: Sender<Vec<Option<Vec<u8>>>>,
1466    },
1467
1468    FindKeysByPrefix {
1469        id: ApplicationId,
1470        #[debug(with = hex_debug)]
1471        key_prefix: Vec<u8>,
1472        #[debug(skip)]
1473        callback: Sender<Vec<Vec<u8>>>,
1474    },
1475
1476    FindKeyValuesByPrefix {
1477        id: ApplicationId,
1478        #[debug(with = hex_debug)]
1479        key_prefix: Vec<u8>,
1480        #[debug(skip)]
1481        callback: Sender<Vec<(Vec<u8>, Vec<u8>)>>,
1482    },
1483
1484    WriteBatch {
1485        id: ApplicationId,
1486        batch: Batch,
1487        #[debug(skip)]
1488        callback: Sender<()>,
1489    },
1490
1491    OpenChain {
1492        config: Box<OpenChainConfig>,
1493        parent_id: ChainId,
1494        block_height: BlockHeight,
1495        timestamp: Timestamp,
1496        #[debug(skip)]
1497        callback: Sender<ChainId>,
1498    },
1499
1500    CloseChain {
1501        application_id: ApplicationId,
1502        #[debug(skip)]
1503        callback: Sender<Result<(), ExecutionError>>,
1504    },
1505
1506    ChangeOwnership {
1507        application_id: ApplicationId,
1508        ownership: ChainOwnership,
1509        #[debug(skip)]
1510        callback: Sender<Result<(), ExecutionError>>,
1511    },
1512
1513    ChangeApplicationPermissions {
1514        application_id: ApplicationId,
1515        application_permissions: ApplicationPermissions,
1516        #[debug(skip)]
1517        callback: Sender<Result<(), ExecutionError>>,
1518    },
1519
1520    PeekApplicationIndex {
1521        #[debug(skip)]
1522        callback: Sender<u32>,
1523    },
1524
1525    CreateApplication {
1526        chain_id: ChainId,
1527        block_height: BlockHeight,
1528        module_id: ModuleId,
1529        parameters: Vec<u8>,
1530        required_application_ids: Vec<ApplicationId>,
1531        #[debug(skip)]
1532        callback: Sender<CreateApplicationResult>,
1533    },
1534
1535    PerformHttpRequest {
1536        request: http::Request,
1537        http_responses_are_oracle_responses: bool,
1538        #[debug(skip)]
1539        callback: Sender<http::Response>,
1540    },
1541
1542    ReadBlobContent {
1543        blob_id: BlobId,
1544        #[debug(skip)]
1545        callback: Sender<BlobContent>,
1546    },
1547
1548    AssertBlobExists {
1549        blob_id: BlobId,
1550        #[debug(skip)]
1551        callback: Sender<()>,
1552    },
1553
1554    Emit {
1555        stream_id: StreamId,
1556        #[debug(with = hex_debug)]
1557        value: Vec<u8>,
1558        #[debug(skip)]
1559        callback: Sender<u32>,
1560    },
1561
1562    ReadEvent {
1563        event_id: EventId,
1564        callback: oneshot::Sender<Vec<u8>>,
1565    },
1566
1567    SubscribeToEvents {
1568        chain_id: ChainId,
1569        stream_id: StreamId,
1570        subscriber_app_id: ApplicationId,
1571        #[debug(skip)]
1572        callback: Sender<()>,
1573    },
1574
1575    UnsubscribeFromEvents {
1576        chain_id: ChainId,
1577        stream_id: StreamId,
1578        subscriber_app_id: ApplicationId,
1579        #[debug(skip)]
1580        callback: Sender<()>,
1581    },
1582
1583    GetApplicationPermissions {
1584        #[debug(skip)]
1585        callback: Sender<ApplicationPermissions>,
1586    },
1587
1588    QueryServiceOracle {
1589        deadline: Option<Instant>,
1590        application_id: ApplicationId,
1591        next_block_height: BlockHeight,
1592        query: Vec<u8>,
1593        #[debug(skip)]
1594        callback: Sender<Vec<u8>>,
1595    },
1596
1597    AddOutgoingMessage {
1598        message: crate::OutgoingMessage,
1599        #[debug(skip)]
1600        callback: Sender<()>,
1601    },
1602
1603    SetLocalTime {
1604        local_time: Timestamp,
1605        #[debug(skip)]
1606        callback: Sender<()>,
1607    },
1608
1609    AssertBefore {
1610        timestamp: Timestamp,
1611        #[debug(skip)]
1612        callback: Sender<Result<(), ExecutionError>>,
1613    },
1614
1615    AddCreatedBlob {
1616        blob: crate::Blob,
1617        #[debug(skip)]
1618        callback: Sender<()>,
1619    },
1620
1621    ValidationRound {
1622        round: Option<u32>,
1623        #[debug(skip)]
1624        callback: Sender<Option<u32>>,
1625    },
1626
1627    HasEmptyStorage {
1628        application: ApplicationId,
1629        #[debug(skip)]
1630        callback: Sender<bool>,
1631    },
1632
1633    AllowApplicationLogs {
1634        #[debug(skip)]
1635        callback: Sender<bool>,
1636    },
1637
1638    /// Log message from contract execution (fire-and-forget, no callback needed).
1639    #[cfg(web)]
1640    Log {
1641        message: String,
1642        level: tracing::log::Level,
1643    },
1644}