1use std::{collections::BTreeMap, vec};
5
6use allocative::Allocative;
7use futures::{FutureExt, StreamExt};
8use linera_base::{
9 crypto::CryptoHash,
10 data_types::{BlobContent, BlockHeight, StreamUpdate},
11 identifiers::{AccountOwner, BlobId, StreamId},
12 time::Instant,
13 visit_allocative_simple,
14};
15use linera_views::{
16 batch::Batch,
17 common::HasherOutput,
18 context::Context,
19 key_value_store_view::KeyValueStoreView,
20 map_view::MapView,
21 reentrant_collection_view::HashedReentrantCollectionView,
22 register_view::RegisterView,
23 views::{ClonableView, HashableView, Hasher as _, ReplaceContext, View},
24 ViewError,
25};
26#[cfg(with_testing)]
27use {
28 crate::{
29 ResourceControlPolicy, ResourceTracker, TestExecutionRuntimeContext, UserContractCode,
30 },
31 linera_base::data_types::Blob,
32 linera_views::context::MemoryContext,
33 std::sync::Arc,
34};
35
36use super::{execution_state_actor::ExecutionRequest, runtime::ServiceRuntimeRequest};
37use crate::{
38 execution_state_actor::ExecutionStateActor, resources::ResourceController,
39 system::SystemExecutionStateView, ApplicationDescription, ApplicationId, BcsHashable,
40 Deserialize, ExecutionError, ExecutionRuntimeContext, JsVec, MessageContext, OperationContext,
41 ProcessStreamsContext, Query, QueryContext, QueryOutcome, Serialize, ServiceSyncRuntime,
42 Timestamp, TransactionTracker, FLAG_HISTORICAL_HASH, FLAG_HISTORICAL_HASH_SHADOW,
43 FLAG_ZERO_HASH,
44};
45
46#[derive(Debug, ClonableView, View, Allocative)]
48#[allocative(bound = "C")]
49pub struct ExecutionStateView<C> {
50 pub system: SystemExecutionStateView<C>,
52 pub users: HashedReentrantCollectionView<C, ApplicationId, KeyValueStoreView<C>>,
54 pub stream_event_counts: MapView<C, StreamId, u32>,
56 #[allocative(visit = visit_allocative_simple)]
63 pub historical_hash: RegisterView<C, Option<HasherOutput>>,
64}
65
66#[derive(Clone, Copy, Debug)]
69enum HashingMode {
70 Zero,
72 Legacy,
74 Historical { enforce: bool },
77}
78
79impl<C> HashableView for ExecutionStateView<C>
83where
84 C: Context + Clone + 'static,
85 C::Extra: ExecutionRuntimeContext,
86{
87 type Hasher = linera_views::sha3::Sha3_256;
88
89 async fn hash_mut(&mut self) -> Result<HasherOutput, ViewError> {
90 use std::io::Write as _;
91 let Self {
92 system,
93 users,
94 stream_event_counts,
95 historical_hash: _,
96 } = self;
97 let mut hasher = Self::Hasher::default();
98 hasher.write_all(system.hash_mut().await?.as_ref())?;
99 hasher.write_all(users.hash_mut().await?.as_ref())?;
100 hasher.write_all(stream_event_counts.hash_mut().await?.as_ref())?;
101 Ok(hasher.finalize())
102 }
103
104 async fn hash(&self) -> Result<HasherOutput, ViewError> {
105 use std::io::Write as _;
106 let Self {
107 system,
108 users,
109 stream_event_counts,
110 historical_hash: _,
111 } = self;
112 let mut hasher = Self::Hasher::default();
113 hasher.write_all(system.hash().await?.as_ref())?;
114 hasher.write_all(users.hash().await?.as_ref())?;
115 hasher.write_all(stream_event_counts.hash().await?.as_ref())?;
116 Ok(hasher.finalize())
117 }
118}
119
120impl<C> ExecutionStateView<C>
121where
122 C: Context + Clone + 'static,
123 C::Extra: ExecutionRuntimeContext,
124{
125 pub async fn crypto_hash_mut(&mut self) -> Result<CryptoHash, ViewError> {
127 match self.hashing_mode().await? {
128 HashingMode::Zero => {
129 self.reset_historical_hash();
130 Ok(CryptoHash::from([0; 32]))
131 }
132 HashingMode::Legacy => {
133 self.reset_historical_hash();
134 let hash = self.hash_mut().await?;
135 Ok(Self::wrap_state_hash(hash))
136 }
137 HashingMode::Historical { enforce } => {
138 let hash = self.advance_historical_hash().await?;
139 let wrapped = Self::wrap_state_hash(hash);
140 if enforce {
141 Ok(wrapped)
142 } else {
143 tracing::info!(
147 chain_id = %self.context().extra().chain_id(),
148 historical_state_hash = %wrapped,
149 "computed historical execution-state hash in shadow mode",
150 );
151 Ok(CryptoHash::from([0; 32]))
152 }
153 }
154 }
155 }
156
157 async fn hashing_mode(&self) -> Result<HashingMode, ViewError> {
161 let Some((_epoch, committee)) = self.system.current_committee().await? else {
162 return Ok(HashingMode::Legacy);
163 };
164 let flags = &committee.policy().http_request_allow_list;
165 let mode = if flags.contains(FLAG_ZERO_HASH) {
166 HashingMode::Zero
167 } else if flags.contains(FLAG_HISTORICAL_HASH) {
168 HashingMode::Historical { enforce: true }
169 } else if flags.contains(FLAG_HISTORICAL_HASH_SHADOW) {
170 HashingMode::Historical { enforce: false }
171 } else {
172 HashingMode::Legacy
173 };
174 Ok(mode)
175 }
176
177 async fn advance_historical_hash(&mut self) -> Result<HasherOutput, ViewError> {
185 let hash = match *self.historical_hash.get() {
186 None => self.hash_mut().await?,
187 Some(stored) => {
188 let Self {
193 system,
194 users,
195 stream_event_counts,
196 historical_hash: _,
197 } = self;
198 let mut batch = Batch::new();
199 system.pre_save(&mut batch)?;
200 users.pre_save(&mut batch)?;
201 stream_event_counts.pre_save(&mut batch)?;
202 make_historical_hash(Some(stored), &batch)?
203 }
204 };
205 self.historical_hash.set(Some(hash));
206 Ok(hash)
207 }
208
209 fn reset_historical_hash(&mut self) {
214 if self.historical_hash.get().is_some() {
215 self.historical_hash.set(None);
216 }
217 }
218
219 fn wrap_state_hash(hash: HasherOutput) -> CryptoHash {
222 #[derive(Serialize, Deserialize)]
223 struct ExecutionStateViewHash([u8; 32]);
224 impl BcsHashable<'_> for ExecutionStateViewHash {}
225 CryptoHash::new(&ExecutionStateViewHash(hash.into()))
226 }
227}
228
229fn make_historical_hash(
233 stored: Option<HasherOutput>,
234 batch: &Batch,
235) -> Result<HasherOutput, ViewError> {
236 let stored = stored.unwrap_or_default();
237 if batch.is_empty() {
238 return Ok(stored);
239 }
240 let mut hasher = linera_views::sha3::Sha3_256::default();
241 hasher.update_with_bytes(stored.as_ref())?;
242 hasher.update_with_bcs_bytes(&batch)?;
243 Ok(hasher.finalize())
244}
245
246impl<C: Context, C2: Context> ReplaceContext<C2> for ExecutionStateView<C> {
247 type Target = ExecutionStateView<C2>;
248
249 async fn with_context(
250 &mut self,
251 ctx: impl FnOnce(&Self::Context) -> C2 + Clone,
252 ) -> Self::Target {
253 ExecutionStateView {
254 system: self.system.with_context(ctx.clone()).await,
255 users: self.users.with_context(ctx.clone()).await,
256 stream_event_counts: self.stream_event_counts.with_context(ctx.clone()).await,
257 historical_hash: self.historical_hash.with_context(ctx.clone()).await,
258 }
259 }
260}
261
262pub struct ServiceRuntimeEndpoint {
264 pub incoming_execution_requests: futures::channel::mpsc::UnboundedReceiver<ExecutionRequest>,
266 pub runtime_request_sender: std::sync::mpsc::Sender<ServiceRuntimeRequest>,
268}
269
270#[cfg(with_testing)]
271impl ExecutionStateView<MemoryContext<TestExecutionRuntimeContext>>
272where
273 MemoryContext<TestExecutionRuntimeContext>: Context + Clone + 'static,
274{
275 pub async fn simulate_instantiation(
277 &mut self,
278 contract: UserContractCode,
279 local_time: linera_base::data_types::Timestamp,
280 application_description: ApplicationDescription,
281 instantiation_argument: Vec<u8>,
282 contract_blob: Blob,
283 service_blob: Blob,
284 ) -> Result<(), ExecutionError> {
285 let chain_id = application_description.creator_chain_id;
286 assert_eq!(chain_id, self.context().extra().chain_id);
287 let context = OperationContext {
288 chain_id,
289 authenticated_signer: None,
290 height: application_description.block_height,
291 round: None,
292 timestamp: local_time,
293 };
294
295 let action = UserAction::Instantiate(context, instantiation_argument);
296 let next_application_index = application_description.application_index + 1;
297 let next_chain_index = 0;
298
299 let application_id = From::from(&application_description);
300 let blob = Blob::new_application_description(&application_description);
301
302 self.system.used_blobs.insert(&blob.id())?;
303 self.system.used_blobs.insert(&contract_blob.id())?;
304 self.system.used_blobs.insert(&service_blob.id())?;
305
306 self.context()
307 .extra()
308 .user_contracts()
309 .pin()
310 .insert(application_id, contract);
311
312 self.context()
313 .extra()
314 .add_blobs([
315 contract_blob,
316 service_blob,
317 Blob::new_application_description(&application_description),
318 ])
319 .await?;
320
321 let tracker = ResourceTracker::default();
322 let policy = ResourceControlPolicy::no_fees();
323 let mut resource_controller = ResourceController::new(Arc::new(policy), tracker, None);
324 let mut txn_tracker = TransactionTracker::new(
325 local_time,
326 0,
327 next_application_index,
328 next_chain_index,
329 None,
330 &[],
331 );
332 txn_tracker.add_created_blob(blob);
333 Box::pin(
334 ExecutionStateActor::new(self, &mut txn_tracker, &mut resource_controller)
335 .run_user_action(application_id, action, context.refund_grant_to(), None),
336 )
337 .await?;
338
339 Ok(())
340 }
341}
342
343pub enum UserAction {
344 Instantiate(OperationContext, Vec<u8>),
345 Operation(OperationContext, Vec<u8>),
346 Message(MessageContext, Vec<u8>),
347 ProcessStreams(ProcessStreamsContext, Vec<StreamUpdate>),
348}
349
350impl UserAction {
351 pub(crate) fn signer(&self) -> Option<AccountOwner> {
352 match self {
353 UserAction::Instantiate(context, _) => context.authenticated_signer,
354 UserAction::Operation(context, _) => context.authenticated_signer,
355 UserAction::ProcessStreams(_, _) => None,
356 UserAction::Message(context, _) => context.authenticated_signer,
357 }
358 }
359
360 pub(crate) fn height(&self) -> BlockHeight {
361 match self {
362 UserAction::Instantiate(context, _) => context.height,
363 UserAction::Operation(context, _) => context.height,
364 UserAction::ProcessStreams(context, _) => context.height,
365 UserAction::Message(context, _) => context.height,
366 }
367 }
368
369 pub(crate) fn round(&self) -> Option<u32> {
370 match self {
371 UserAction::Instantiate(context, _) => context.round,
372 UserAction::Operation(context, _) => context.round,
373 UserAction::ProcessStreams(context, _) => context.round,
374 UserAction::Message(context, _) => context.round,
375 }
376 }
377
378 pub(crate) fn timestamp(&self) -> Timestamp {
379 match self {
380 UserAction::Instantiate(context, _) => context.timestamp,
381 UserAction::Operation(context, _) => context.timestamp,
382 UserAction::ProcessStreams(context, _) => context.timestamp,
383 UserAction::Message(context, _) => context.timestamp,
384 }
385 }
386}
387
388impl<C> ExecutionStateView<C>
389where
390 C: Context + Clone + 'static,
391 C::Extra: ExecutionRuntimeContext,
392{
393 pub async fn query_application(
395 &mut self,
396 context: QueryContext,
397 query: Query,
398 endpoint: Option<&mut ServiceRuntimeEndpoint>,
399 ) -> Result<QueryOutcome, ExecutionError> {
400 assert_eq!(context.chain_id, self.context().extra().chain_id());
401 match query {
402 Query::System(query) => {
403 let outcome = self.system.handle_query(context, query)?;
404 Ok(outcome.into())
405 }
406 Query::User {
407 application_id,
408 bytes,
409 } => {
410 let outcome = match endpoint {
411 Some(endpoint) => {
412 self.query_user_application_with_long_lived_service(
413 application_id,
414 context,
415 bytes,
416 &mut endpoint.incoming_execution_requests,
417 &endpoint.runtime_request_sender,
418 )
419 .await?
420 }
421 None => {
422 self.query_user_application(application_id, context, bytes)
423 .await?
424 }
425 };
426 Ok(outcome.into())
427 }
428 }
429 }
430
431 async fn query_user_application(
432 &mut self,
433 application_id: ApplicationId,
434 context: QueryContext,
435 query: Vec<u8>,
436 ) -> Result<QueryOutcome<Vec<u8>>, ExecutionError> {
437 self.query_user_application_with_deadline(
438 application_id,
439 context,
440 query,
441 None,
442 BTreeMap::new(),
443 )
444 .await
445 }
446
447 pub(crate) async fn query_user_application_with_deadline(
448 &mut self,
449 application_id: ApplicationId,
450 context: QueryContext,
451 query: Vec<u8>,
452 deadline: Option<Instant>,
453 created_blobs: BTreeMap<BlobId, BlobContent>,
454 ) -> Result<QueryOutcome<Vec<u8>>, ExecutionError> {
455 let (execution_state_sender, mut execution_state_receiver) =
456 futures::channel::mpsc::unbounded();
457 let mut txn_tracker = TransactionTracker::default().with_blobs(created_blobs);
458 let mut resource_controller = ResourceController::default();
459 let thread_pool = self.context().extra().thread_pool().clone();
460 let mut actor = ExecutionStateActor::new(self, &mut txn_tracker, &mut resource_controller);
461
462 let (codes, descriptions) = actor.service_and_dependencies(application_id).await?;
463
464 let service_runtime_task = thread_pool
465 .run_send(JsVec(codes), move |codes| async move {
466 let mut runtime = ServiceSyncRuntime::new_with_deadline(
467 execution_state_sender,
468 context,
469 deadline,
470 );
471
472 for (code, description) in codes.0.into_iter().zip(descriptions) {
473 runtime.preload_service(ApplicationId::from(&description), code, description);
474 }
475
476 runtime.run_query(application_id, query)
477 })
478 .await;
479
480 while let Some(request) = execution_state_receiver.next().await {
481 actor.handle_request(request).await?;
482 }
483
484 service_runtime_task.await?
485 }
486
487 async fn query_user_application_with_long_lived_service(
488 &mut self,
489 application_id: ApplicationId,
490 context: QueryContext,
491 query: Vec<u8>,
492 incoming_execution_requests: &mut futures::channel::mpsc::UnboundedReceiver<
493 ExecutionRequest,
494 >,
495 runtime_request_sender: &std::sync::mpsc::Sender<ServiceRuntimeRequest>,
496 ) -> Result<QueryOutcome<Vec<u8>>, ExecutionError> {
497 let (outcome_sender, outcome_receiver) = oneshot::channel();
498 let mut outcome_receiver = outcome_receiver.fuse();
499
500 runtime_request_sender
501 .send(ServiceRuntimeRequest::Query {
502 application_id,
503 context,
504 query,
505 callback: outcome_sender,
506 })
507 .expect("Service runtime thread should only stop when `request_sender` is dropped");
508
509 let mut txn_tracker = TransactionTracker::default();
510 let mut resource_controller = ResourceController::default();
511 let mut actor = ExecutionStateActor::new(self, &mut txn_tracker, &mut resource_controller);
512
513 loop {
514 futures::select! {
515 maybe_request = incoming_execution_requests.next() => {
516 if let Some(request) = maybe_request {
517 actor.handle_request(request).await?;
518 }
519 }
520 outcome = &mut outcome_receiver => {
521 return outcome.map_err(|_| ExecutionError::MissingRuntimeResponse)?;
522 }
523 }
524 }
525 }
526
527 pub async fn list_applications(
529 &self,
530 ) -> Result<Vec<(ApplicationId, ApplicationDescription)>, ExecutionError> {
531 let mut applications = vec![];
532 for app_id in self.users.indices().await? {
533 let blob_id = app_id.description_blob_id();
534 let blob_content = self.system.read_blob_content(blob_id).await?;
535 let application_description = bcs::from_bytes(blob_content.bytes())?;
536 applications.push((app_id, application_description));
537 }
538 Ok(applications)
539 }
540}
541
542#[cfg(test)]
543mod historical_hash_tests {
544 use linera_views::{batch::Batch, common::HasherOutput};
545
546 use super::make_historical_hash;
547
548 const RECORDED_CHAIN_HASH: [u8; 32] = [
549 148, 130, 122, 191, 71, 219, 62, 147, 185, 157, 252, 71, 40, 90, 125, 182, 36, 55, 7, 233,
550 90, 114, 77, 56, 106, 151, 21, 246, 183, 174, 65, 74,
551 ];
552
553 #[test]
558 fn make_historical_hash_matches_recorded_value() {
559 let stored: HasherOutput = [7u8; 32].into();
560 let mut batch = Batch::new();
561 batch.put_key_value_bytes(vec![0, 1, 2], vec![3, 4, 5]);
562 batch.put_key_value_bytes(vec![6, 7], vec![8]);
563
564 let hash = make_historical_hash(Some(stored), &batch).unwrap();
565 assert_eq!(<[u8; 32]>::from(hash), RECORDED_CHAIN_HASH);
566
567 assert_eq!(
569 make_historical_hash(Some(stored), &Batch::new()).unwrap(),
570 stored,
571 );
572 assert_ne!(
575 make_historical_hash(None, &batch).unwrap(),
576 make_historical_hash(Some(stored), &batch).unwrap(),
577 );
578 }
579}