1#[cfg(test)]
6#[path = "./unit_tests/system_tests.rs"]
7mod tests;
8
9use std::{
10 collections::{BTreeMap, BTreeSet},
11 sync::Arc,
12};
13
14use allocative::Allocative;
15use custom_debug_derive::Debug;
16use linera_base::{
17 crypto::CryptoHash,
18 data_types::{
19 Amount, ApplicationPermissions, ArithmeticError, Blob, BlobContent, BlockHeight,
20 ChainDescription, ChainOrigin, Epoch, InitialChainConfig, OracleResponse, Timestamp,
21 },
22 ensure, hex_debug,
23 identifiers::{Account, AccountOwner, BlobId, BlobType, ChainId, EventId, ModuleId, StreamId},
24 ownership::{ChainOwnership, TimeoutConfig},
25};
26use linera_views::{
27 context::Context,
28 lazy_register_view::HashedLazyRegisterView,
29 map_view::HashedMapView,
30 register_view::HashedRegisterView,
31 set_view::HashedSetView,
32 views::{ClonableView, HashableView, ReplaceContext, View},
33 ViewError,
34};
35use serde::{Deserialize, Serialize};
36
37#[cfg(test)]
38use crate::test_utils::SystemExecutionState;
39use crate::{
40 committee::Committee, util::OracleResponseExt as _, ApplicationDescription, ApplicationId,
41 ExecutionError, ExecutionRuntimeContext, MessageContext, MessageKind, OperationContext,
42 OutgoingMessage, QueryContext, QueryOutcome, ResourceController, TransactionTracker,
43};
44
45pub static EPOCH_STREAM_NAME: &[u8] = &[0];
47pub static REMOVED_EPOCH_STREAM_NAME: &[u8] = &[1];
49
50#[cfg(with_metrics)]
52mod metrics {
53 use std::sync::LazyLock;
54
55 use linera_base::prometheus_util::register_int_counter_vec;
56 use prometheus::IntCounterVec;
57
58 pub static OPEN_CHAIN_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
59 register_int_counter_vec(
60 "open_chain_count",
61 "The number of times the `OpenChain` operation was executed",
62 &[],
63 )
64 });
65}
66
67#[derive(Debug, ClonableView, HashableView, Allocative)]
69#[allocative(bound = "C")]
70pub struct SystemExecutionStateView<C> {
71 pub description: HashedLazyRegisterView<C, Option<ChainDescription>>,
73 pub epoch: HashedRegisterView<C, Epoch>,
75 pub admin_chain_id: HashedRegisterView<C, Option<ChainId>>,
77 pub committees: HashedLazyRegisterView<C, BTreeMap<Epoch, Committee>>,
88 pub ownership: HashedLazyRegisterView<C, ChainOwnership>,
90 pub balance: HashedRegisterView<C, Amount>,
92 pub balances: HashedMapView<C, AccountOwner, Amount>,
94 pub timestamp: HashedRegisterView<C, Timestamp>,
96 pub closed: HashedRegisterView<C, bool>,
98 pub application_permissions: HashedLazyRegisterView<C, ApplicationPermissions>,
100 pub used_blobs: HashedSetView<C, BlobId>,
102 pub event_subscriptions: HashedMapView<C, (ChainId, StreamId), EventSubscriptions>,
104}
105
106impl<C: Context, C2: Context> ReplaceContext<C2> for SystemExecutionStateView<C> {
107 type Target = SystemExecutionStateView<C2>;
108
109 async fn with_context(
110 &mut self,
111 ctx: impl FnOnce(&Self::Context) -> C2 + Clone,
112 ) -> Self::Target {
113 SystemExecutionStateView {
114 description: self.description.with_context(ctx.clone()).await,
115 epoch: self.epoch.with_context(ctx.clone()).await,
116 admin_chain_id: self.admin_chain_id.with_context(ctx.clone()).await,
117 committees: self.committees.with_context(ctx.clone()).await,
118 ownership: self.ownership.with_context(ctx.clone()).await,
119 balance: self.balance.with_context(ctx.clone()).await,
120 balances: self.balances.with_context(ctx.clone()).await,
121 timestamp: self.timestamp.with_context(ctx.clone()).await,
122 closed: self.closed.with_context(ctx.clone()).await,
123 application_permissions: self.application_permissions.with_context(ctx.clone()).await,
124 used_blobs: self.used_blobs.with_context(ctx.clone()).await,
125 event_subscriptions: self.event_subscriptions.with_context(ctx.clone()).await,
126 }
127 }
128}
129
130#[derive(Debug, Default, Clone, Serialize, Deserialize, Allocative)]
132pub struct EventSubscriptions {
133 pub next_index: u32,
136 pub applications: BTreeSet<ApplicationId>,
138}
139
140#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
142pub struct OpenChainConfig {
143 pub ownership: ChainOwnership,
145 pub balance: Amount,
147 pub application_permissions: ApplicationPermissions,
149}
150
151impl OpenChainConfig {
152 pub fn init_chain_config(
155 &self,
156 epoch: Epoch,
157 min_active_epoch: Epoch,
158 max_active_epoch: Epoch,
159 ) -> InitialChainConfig {
160 InitialChainConfig {
161 application_permissions: self.application_permissions.clone(),
162 balance: self.balance,
163 epoch,
164 min_active_epoch,
165 max_active_epoch,
166 ownership: self.ownership.clone(),
167 }
168 }
169}
170
171#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
173#[allow(missing_docs)]
174pub enum SystemOperation {
175 Transfer {
178 owner: AccountOwner,
179 recipient: Account,
180 amount: Amount,
181 },
182 Claim {
186 owner: AccountOwner,
187 target_id: ChainId,
188 recipient: Account,
189 amount: Amount,
190 },
191 OpenChain(OpenChainConfig),
194 CloseChain,
196 ChangeOwnership {
198 #[debug(skip_if = Vec::is_empty)]
200 super_owners: Vec<AccountOwner>,
201 #[debug(skip_if = Vec::is_empty)]
203 owners: Vec<(AccountOwner, u64)>,
204 multi_leader_rounds: u32,
206 open_multi_leader_rounds: bool,
210 timeout_config: TimeoutConfig,
212 },
213 ChangeApplicationPermissions(ApplicationPermissions),
215 PublishModule { module_id: ModuleId },
217 PublishDataBlob { blob_hash: CryptoHash },
219 VerifyBlob { blob_id: BlobId },
221 CreateApplication {
223 module_id: ModuleId,
224 #[serde(with = "serde_bytes")]
225 #[debug(with = "hex_debug")]
226 parameters: Vec<u8>,
227 #[serde(with = "serde_bytes")]
228 #[debug(with = "hex_debug", skip_if = Vec::is_empty)]
229 instantiation_argument: Vec<u8>,
230 #[debug(skip_if = Vec::is_empty)]
231 required_application_ids: Vec<ApplicationId>,
232 },
233 Admin(AdminOperation),
235 ProcessNewEpoch(Epoch),
237 ProcessRemovedEpoch(Epoch),
239 UpdateStreams(Vec<(ChainId, StreamId, u32)>),
241}
242
243#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
245#[allow(missing_docs)]
246pub enum AdminOperation {
247 PublishCommitteeBlob { blob_hash: CryptoHash },
250 CreateCommittee { epoch: Epoch, blob_hash: CryptoHash },
253 RemoveCommittee { epoch: Epoch },
257}
258
259#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
261#[allow(missing_docs)]
262pub enum SystemMessage {
263 Credit {
266 target: AccountOwner,
267 amount: Amount,
268 source: AccountOwner,
269 },
270 Withdraw {
274 owner: AccountOwner,
275 amount: Amount,
276 recipient: Account,
277 },
278}
279
280#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
282pub struct SystemQuery;
283
284#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
286#[allow(missing_docs)]
287pub struct SystemResponse {
288 pub chain_id: ChainId,
289 pub balance: Amount,
290}
291
292#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Default, Debug, Serialize, Deserialize)]
294pub struct UserData(pub Option<[u8; 32]>);
295
296#[derive(Debug)]
298#[allow(missing_docs)]
299pub struct CreateApplicationResult {
300 pub app_id: ApplicationId,
301}
302
303impl<C> SystemExecutionStateView<C>
304where
305 C: Context + Clone + 'static,
306 C::Extra: ExecutionRuntimeContext,
307{
308 pub async fn is_active(&self) -> Result<bool, ViewError> {
310 Ok(self.description.get().await?.is_some()
311 && self.ownership.get().await?.is_active()
312 && self.current_committee().await?.is_some()
313 && self.admin_chain_id.get().is_some())
314 }
315
316 pub async fn current_committee(&self) -> Result<Option<(Epoch, Arc<Committee>)>, ViewError> {
327 let epoch = *self.epoch.get();
328 if let Some(committee) = self.context().extra().get_or_load_committee(epoch).await? {
329 return Ok(Some((epoch, committee)));
330 }
331 let Some(committee) = self.committees.get().await?.get(&epoch).cloned() else {
332 return Ok(None);
333 };
334 Ok(Some((epoch, Arc::new(committee))))
335 }
336
337 async fn get_event(&self, event_id: EventId) -> Result<Arc<Vec<u8>>, ExecutionError> {
338 match self.context().extra().get_event(event_id.clone()).await? {
339 None => Err(ExecutionError::EventsNotFound(vec![event_id])),
340 Some(vec) => Ok(vec),
341 }
342 }
343
344 pub async fn execute_operation(
347 &mut self,
348 context: OperationContext,
349 operation: SystemOperation,
350 txn_tracker: &mut TransactionTracker,
351 resource_controller: &mut ResourceController<Option<AccountOwner>>,
352 ) -> Result<Option<(ApplicationId, Vec<u8>)>, ExecutionError> {
353 use SystemOperation::*;
354 let mut new_application = None;
355 match operation {
356 OpenChain(config) => {
357 let _chain_id = self
358 .open_chain(
359 config,
360 context.chain_id,
361 context.height,
362 context.timestamp,
363 txn_tracker,
364 )
365 .await?;
366 #[cfg(with_metrics)]
367 metrics::OPEN_CHAIN_COUNT.with_label_values(&[]).inc();
368 }
369 ChangeOwnership {
370 super_owners,
371 owners,
372 multi_leader_rounds,
373 open_multi_leader_rounds,
374 timeout_config,
375 } => {
376 self.ownership.set(ChainOwnership {
377 super_owners: super_owners.into_iter().collect(),
378 owners: owners.into_iter().collect(),
379 multi_leader_rounds,
380 open_multi_leader_rounds,
381 timeout_config,
382 });
383 }
384 ChangeApplicationPermissions(application_permissions) => {
385 self.application_permissions.set(application_permissions);
386 }
387 CloseChain => self.close_chain()?,
388 Transfer {
389 owner,
390 amount,
391 recipient,
392 } => {
393 let maybe_message = self
394 .transfer(context.authenticated_signer, None, owner, recipient, amount)
395 .await?;
396 txn_tracker.add_outgoing_messages(maybe_message);
397 }
398 Claim {
399 owner,
400 target_id,
401 recipient,
402 amount,
403 } => {
404 let maybe_message = self
405 .claim(
406 context.authenticated_signer,
407 None,
408 owner,
409 target_id,
410 recipient,
411 amount,
412 )
413 .await?;
414 txn_tracker.add_outgoing_messages(maybe_message);
415 }
416 Admin(admin_operation) => {
417 ensure!(
418 *self.admin_chain_id.get() == Some(context.chain_id),
419 ExecutionError::AdminOperationOnNonAdminChain
420 );
421 match admin_operation {
422 AdminOperation::PublishCommitteeBlob { blob_hash } => {
423 self.blob_published(
424 &BlobId::new(blob_hash, BlobType::Committee),
425 txn_tracker,
426 )?;
427 }
428 AdminOperation::CreateCommittee { epoch, blob_hash } => {
429 self.check_next_epoch(epoch)?;
430 let blob_id = BlobId::new(blob_hash, BlobType::Committee);
431 let committee =
432 bcs::from_bytes(self.read_blob_content(blob_id).await?.bytes())?;
433 self.blob_used(txn_tracker, blob_id).await?;
434 self.committees.get_mut().await?.insert(epoch, committee);
435 self.epoch.set(epoch);
436 txn_tracker.add_event(
437 StreamId::system(EPOCH_STREAM_NAME),
438 epoch.0,
439 bcs::to_bytes(&blob_hash)?,
440 );
441 }
442 AdminOperation::RemoveCommittee { epoch } => {
443 ensure!(
444 self.committees.get_mut().await?.remove(&epoch).is_some(),
445 ExecutionError::InvalidCommitteeRemoval
446 );
447 txn_tracker.add_event(
448 StreamId::system(REMOVED_EPOCH_STREAM_NAME),
449 epoch.0,
450 vec![],
451 );
452 }
453 }
454 }
455 PublishModule { module_id } => {
456 for blob_id in module_id.bytecode_blob_ids() {
457 self.blob_published(&blob_id, txn_tracker)?;
458 }
459 }
460 CreateApplication {
461 module_id,
462 parameters,
463 instantiation_argument,
464 required_application_ids,
465 } => {
466 let CreateApplicationResult { app_id } = self
467 .create_application(
468 context.chain_id,
469 context.height,
470 module_id,
471 parameters,
472 required_application_ids,
473 txn_tracker,
474 )
475 .await?;
476 new_application = Some((app_id, instantiation_argument));
477 }
478 PublishDataBlob { blob_hash } => {
479 self.blob_published(&BlobId::new(blob_hash, BlobType::Data), txn_tracker)?;
480 }
481 VerifyBlob { blob_id } => {
482 self.assert_blob_exists(blob_id).await?;
483 resource_controller
484 .with_state(self)
485 .await?
486 .track_blob_read(0)?;
487 self.blob_used(txn_tracker, blob_id).await?;
488 }
489 ProcessNewEpoch(epoch) => {
490 self.check_next_epoch(epoch)?;
491 let admin_chain_id = self.admin_chain_id.get().ok_or_else(|| {
492 ExecutionError::InternalError(
493 "execute_operation called for uninitialized chain",
494 )
495 })?;
496 let event_id = EventId {
497 chain_id: admin_chain_id,
498 stream_id: StreamId::system(EPOCH_STREAM_NAME),
499 index: epoch.0,
500 };
501 let bytes = txn_tracker
502 .oracle(|| async {
503 let bytes = self.get_event(event_id.clone()).await?;
504 Ok(OracleResponse::Event(
505 event_id.clone(),
506 Arc::unwrap_or_clone(bytes),
507 ))
508 })
509 .await?
510 .to_event(&event_id)?;
511 let blob_id = BlobId::new(bcs::from_bytes(&bytes)?, BlobType::Committee);
512 let committee = bcs::from_bytes(self.read_blob_content(blob_id).await?.bytes())?;
513 self.blob_used(txn_tracker, blob_id).await?;
514 self.committees.get_mut().await?.insert(epoch, committee);
515 self.epoch.set(epoch);
516 }
517 ProcessRemovedEpoch(epoch) => {
518 ensure!(
519 self.committees.get_mut().await?.remove(&epoch).is_some(),
520 ExecutionError::InvalidCommitteeRemoval
521 );
522 let admin_chain_id = self.admin_chain_id.get().ok_or_else(|| {
523 ExecutionError::InternalError(
524 "execute_operation called for uninitialized chain",
525 )
526 })?;
527 let event_id = EventId {
528 chain_id: admin_chain_id,
529 stream_id: StreamId::system(REMOVED_EPOCH_STREAM_NAME),
530 index: epoch.0,
531 };
532 txn_tracker
533 .oracle(|| async {
534 let bytes = self.get_event(event_id.clone()).await?;
535 Ok(OracleResponse::Event(event_id, Arc::unwrap_or_clone(bytes)))
536 })
537 .await?;
538 }
539 UpdateStreams(streams) => {
540 let mut missing_events = Vec::new();
541 for (chain_id, stream_id, next_index) in streams {
542 let subscriptions = self
543 .event_subscriptions
544 .get_mut_or_default(&(chain_id, stream_id.clone()))
545 .await?;
546 ensure!(
547 subscriptions.next_index < next_index,
548 ExecutionError::OutdatedUpdateStreams
549 );
550 for application_id in &subscriptions.applications {
551 txn_tracker.add_stream_to_process(
552 *application_id,
553 chain_id,
554 stream_id.clone(),
555 subscriptions.next_index,
556 next_index,
557 );
558 }
559 subscriptions.next_index = next_index;
560 let index = next_index
561 .checked_sub(1)
562 .ok_or(ArithmeticError::Underflow)?;
563 let event_id = EventId {
564 chain_id,
565 stream_id,
566 index,
567 };
568 let extra = self.context().extra();
569 txn_tracker
570 .oracle(|| async {
571 if !extra.contains_event(event_id.clone()).await? {
572 missing_events.push(event_id.clone());
573 }
574 Ok(OracleResponse::EventExists(event_id))
575 })
576 .await?;
577 }
578 ensure!(
579 missing_events.is_empty(),
580 ExecutionError::EventsNotFound(missing_events)
581 );
582 }
583 }
584
585 Ok(new_application)
586 }
587
588 fn check_next_epoch(&self, provided: Epoch) -> Result<(), ExecutionError> {
591 let expected = self.epoch.get().try_add_one()?;
592 ensure!(
593 provided == expected,
594 ExecutionError::InvalidCommitteeEpoch { provided, expected }
595 );
596 Ok(())
597 }
598
599 async fn credit(&mut self, owner: &AccountOwner, amount: Amount) -> Result<(), ExecutionError> {
600 if owner == &AccountOwner::CHAIN {
601 let new_balance = self.balance.get().saturating_add(amount);
602 self.balance.set(new_balance);
603 } else {
604 let balance = self.balances.get_mut_or_default(owner).await?;
605 *balance = balance.saturating_add(amount);
606 }
607 Ok(())
608 }
609
610 async fn credit_or_send_message(
611 &mut self,
612 source: AccountOwner,
613 recipient: Account,
614 amount: Amount,
615 ) -> Result<Option<OutgoingMessage>, ExecutionError> {
616 let source_chain_id = self.context().extra().chain_id();
617 if recipient.chain_id == source_chain_id {
618 let target = recipient.owner;
620 self.credit(&target, amount).await?;
621 Ok(None)
622 } else {
623 let message = SystemMessage::Credit {
625 amount,
626 source,
627 target: recipient.owner,
628 };
629 Ok(Some(
630 OutgoingMessage::new(recipient.chain_id, message).with_kind(MessageKind::Tracked),
631 ))
632 }
633 }
634
635 pub async fn transfer(
637 &mut self,
638 authenticated_signer: Option<AccountOwner>,
639 authenticated_application_id: Option<ApplicationId>,
640 source: AccountOwner,
641 recipient: Account,
642 amount: Amount,
643 ) -> Result<Option<OutgoingMessage>, ExecutionError> {
644 if source == AccountOwner::CHAIN {
645 let authenticated_signer =
646 authenticated_signer.ok_or(ExecutionError::UnauthenticatedTransferOwner)?;
647 ensure!(
648 self.ownership
649 .get()
650 .await?
651 .verify_owner(&authenticated_signer),
652 ExecutionError::UnauthenticatedTransferOwner
653 );
654 } else {
655 ensure!(
656 authenticated_signer == Some(source)
657 || authenticated_application_id.map(AccountOwner::from) == Some(source),
658 ExecutionError::UnauthenticatedTransferOwner
659 );
660 }
661 ensure!(
662 amount > Amount::ZERO,
663 ExecutionError::IncorrectTransferAmount
664 );
665 self.debit(&source, amount).await?;
666 self.credit_or_send_message(source, recipient, amount).await
667 }
668
669 pub async fn claim(
671 &mut self,
672 authenticated_signer: Option<AccountOwner>,
673 authenticated_application_id: Option<ApplicationId>,
674 source: AccountOwner,
675 target_id: ChainId,
676 recipient: Account,
677 amount: Amount,
678 ) -> Result<Option<OutgoingMessage>, ExecutionError> {
679 ensure!(
680 authenticated_signer == Some(source)
681 || authenticated_application_id.map(AccountOwner::from) == Some(source),
682 ExecutionError::UnauthenticatedClaimOwner
683 );
684 ensure!(amount > Amount::ZERO, ExecutionError::IncorrectClaimAmount);
685
686 let current_chain_id = self.context().extra().chain_id();
687 if target_id == current_chain_id {
688 self.debit(&source, amount).await?;
690 self.credit_or_send_message(source, recipient, amount).await
691 } else {
692 let message = SystemMessage::Withdraw {
694 amount,
695 owner: source,
696 recipient,
697 };
698 Ok(Some(
699 OutgoingMessage::new(target_id, message)
700 .with_authenticated_signer(authenticated_signer),
701 ))
702 }
703 }
704
705 async fn debit(
707 &mut self,
708 account: &AccountOwner,
709 amount: Amount,
710 ) -> Result<(), ExecutionError> {
711 let balance = if account == &AccountOwner::CHAIN {
712 self.balance.get_mut()
713 } else {
714 self.balances.get_mut(account).await?.ok_or_else(|| {
715 ExecutionError::InsufficientBalance {
716 balance: Amount::ZERO,
717 account: *account,
718 }
719 })?
720 };
721
722 balance
723 .try_sub_assign(amount)
724 .map_err(|_| ExecutionError::InsufficientBalance {
725 balance: *balance,
726 account: *account,
727 })?;
728
729 if account != &AccountOwner::CHAIN && balance.is_zero() {
730 self.balances.remove(account)?;
731 }
732
733 Ok(())
734 }
735
736 pub async fn execute_message(
738 &mut self,
739 context: MessageContext,
740 message: SystemMessage,
741 ) -> Result<Vec<OutgoingMessage>, ExecutionError> {
742 let mut outcome = Vec::new();
743 use SystemMessage::*;
744 match message {
745 Credit {
746 amount,
747 source,
748 target,
749 } => {
750 let receiver = if context.is_bouncing { source } else { target };
751 self.credit(&receiver, amount).await?;
752 }
753 Withdraw {
754 amount,
755 owner,
756 recipient,
757 } => {
758 self.debit(&owner, amount).await?;
759 if let Some(message) = self
760 .credit_or_send_message(owner, recipient, amount)
761 .await?
762 {
763 outcome.push(message);
764 }
765 }
766 }
767 Ok(outcome)
768 }
769
770 pub async fn initialize_chain(&mut self, chain_id: ChainId) -> Result<bool, ExecutionError> {
773 if self.description.get().await?.is_some() {
774 return Ok(true);
776 }
777 let description_blob = self
778 .read_blob_content(BlobId::new(chain_id.0, BlobType::ChainDescription))
779 .await?;
780 let description: ChainDescription = bcs::from_bytes(description_blob.bytes())?;
781 let InitialChainConfig {
782 ownership,
783 epoch,
784 balance,
785 min_active_epoch,
786 max_active_epoch,
787 application_permissions,
788 } = description.config().clone();
789 self.timestamp.set(description.timestamp());
790 self.description.set(Some(description));
791 self.epoch.set(epoch);
792 let committees = self
793 .context()
794 .extra()
795 .get_committees(min_active_epoch..=max_active_epoch)
796 .await?;
797 let admin_chain_id = self
798 .context()
799 .extra()
800 .get_network_description()
801 .await?
802 .ok_or(ExecutionError::NoNetworkDescriptionFound)?
803 .admin_chain_id;
804
805 self.committees.set(committees);
806 self.admin_chain_id.set(Some(admin_chain_id));
807 self.ownership.set(ownership);
808 self.balance.set(balance);
809 self.application_permissions.set(application_permissions);
810 Ok(false)
811 }
812
813 pub fn handle_query(
815 &mut self,
816 context: QueryContext,
817 _query: SystemQuery,
818 ) -> Result<QueryOutcome<SystemResponse>, ExecutionError> {
819 let response = SystemResponse {
820 chain_id: context.chain_id,
821 balance: *self.balance.get(),
822 };
823 Ok(QueryOutcome {
824 response,
825 operations: vec![],
826 })
827 }
828
829 pub async fn open_chain(
832 &mut self,
833 config: OpenChainConfig,
834 parent: ChainId,
835 block_height: BlockHeight,
836 timestamp: Timestamp,
837 txn_tracker: &mut TransactionTracker,
838 ) -> Result<ChainId, ExecutionError> {
839 let chain_index = txn_tracker.next_chain_index();
840 let chain_origin = ChainOrigin::Child {
841 parent,
842 block_height,
843 chain_index,
844 };
845 let committees = self.committees.get().await?;
846 let init_chain_config = config.init_chain_config(
847 *self.epoch.get(),
848 committees.keys().min().copied().unwrap_or(Epoch::ZERO),
849 committees.keys().max().copied().unwrap_or(Epoch::ZERO),
850 );
851 let chain_description = ChainDescription::new(chain_origin, init_chain_config, timestamp);
852 let child_id = chain_description.id();
853 self.debit(&AccountOwner::CHAIN, config.balance).await?;
854 let blob = Blob::new_chain_description(&chain_description);
855 txn_tracker.add_created_blob(blob);
856 Ok(child_id)
857 }
858
859 pub fn close_chain(&mut self) -> Result<(), ExecutionError> {
861 self.closed.set(true);
862 Ok(())
863 }
864
865 pub async fn create_application(
867 &mut self,
868 chain_id: ChainId,
869 block_height: BlockHeight,
870 module_id: ModuleId,
871 parameters: Vec<u8>,
872 required_application_ids: Vec<ApplicationId>,
873 txn_tracker: &mut TransactionTracker,
874 ) -> Result<CreateApplicationResult, ExecutionError> {
875 let application_index = txn_tracker.next_application_index();
876
877 let blob_ids = self.check_bytecode_blobs(&module_id, txn_tracker).await?;
878 for blob_id in blob_ids {
881 self.blob_used(txn_tracker, blob_id).await?;
882 }
883
884 let application_description = ApplicationDescription {
885 module_id,
886 creator_chain_id: chain_id,
887 block_height,
888 application_index,
889 parameters,
890 required_application_ids,
891 };
892 self.check_required_applications(&application_description, txn_tracker)
893 .await?;
894
895 let blob = Blob::new_application_description(&application_description);
896 self.used_blobs.insert(&blob.id())?;
897 txn_tracker.add_created_blob(blob);
898
899 Ok(CreateApplicationResult {
900 app_id: ApplicationId::from(&application_description),
901 })
902 }
903
904 async fn check_required_applications(
905 &mut self,
906 application_description: &ApplicationDescription,
907 txn_tracker: &mut TransactionTracker,
908 ) -> Result<(), ExecutionError> {
909 for required_id in &application_description.required_application_ids {
911 Box::pin(self.describe_application(*required_id, txn_tracker)).await?;
912 }
913 Ok(())
914 }
915
916 pub async fn describe_application(
918 &mut self,
919 id: ApplicationId,
920 txn_tracker: &mut TransactionTracker,
921 ) -> Result<ApplicationDescription, ExecutionError> {
922 let blob_id = id.description_blob_id();
923 let content = match txn_tracker.created_blobs().get(&blob_id) {
924 Some(content) => content.clone(),
925 None => self.read_blob_content(blob_id).await?,
926 };
927 self.blob_used(txn_tracker, blob_id).await?;
928 let description: ApplicationDescription = bcs::from_bytes(content.bytes())?;
929
930 let blob_ids = self
931 .check_bytecode_blobs(&description.module_id, txn_tracker)
932 .await?;
933 for blob_id in blob_ids {
936 self.blob_used(txn_tracker, blob_id).await?;
937 }
938
939 self.check_required_applications(&description, txn_tracker)
940 .await?;
941
942 Ok(description)
943 }
944
945 pub(crate) async fn blob_used(
948 &mut self,
949 txn_tracker: &mut TransactionTracker,
950 blob_id: BlobId,
951 ) -> Result<bool, ExecutionError> {
952 if self.used_blobs.contains(&blob_id).await? {
953 return Ok(false); }
955 self.used_blobs.insert(&blob_id)?;
956 txn_tracker.replay_oracle_response(OracleResponse::Blob(blob_id))?;
957 Ok(true)
958 }
959
960 fn blob_published(
963 &mut self,
964 blob_id: &BlobId,
965 txn_tracker: &mut TransactionTracker,
966 ) -> Result<(), ExecutionError> {
967 self.used_blobs.insert(blob_id)?;
968 txn_tracker.add_published_blob(*blob_id);
969 Ok(())
970 }
971
972 pub async fn read_blob_content(&self, blob_id: BlobId) -> Result<BlobContent, ExecutionError> {
974 match self.context().extra().get_blob(blob_id).await {
975 Ok(Some(blob)) => Ok(Arc::unwrap_or_clone(blob).into()),
976 Ok(None) => Err(ExecutionError::BlobsNotFound(vec![blob_id])),
977 Err(error) => Err(error.into()),
978 }
979 }
980
981 pub async fn assert_blob_exists(&mut self, blob_id: BlobId) -> Result<(), ExecutionError> {
983 if self.context().extra().contains_blob(blob_id).await? {
984 Ok(())
985 } else {
986 Err(ExecutionError::BlobsNotFound(vec![blob_id]))
987 }
988 }
989
990 async fn check_bytecode_blobs(
991 &self,
992 module_id: &ModuleId,
993 txn_tracker: &TransactionTracker,
994 ) -> Result<Vec<BlobId>, ExecutionError> {
995 let blob_ids = module_id.bytecode_blob_ids();
996
997 let mut missing_blobs = Vec::new();
998 for blob_id in &blob_ids {
999 if txn_tracker.created_blobs().contains_key(blob_id) {
1001 continue; }
1003 if !self.context().extra().contains_blob(*blob_id).await? {
1005 missing_blobs.push(*blob_id);
1006 }
1007 }
1008 ensure!(
1009 missing_blobs.is_empty(),
1010 ExecutionError::BlobsNotFound(missing_blobs)
1011 );
1012
1013 Ok(blob_ids)
1014 }
1015}