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, Cursor, Epoch, InitialChainConfig, OracleResponse,
21 Timestamp,
22 },
23 ensure, hex_debug,
24 identifiers::{
25 Account, AccountOwner, BlobId, BlobType, ChainId, EventId, ModuleId, OwnerSpender, StreamId,
26 },
27 ownership::{ChainOwnership, TimeoutConfig},
28};
29use linera_views::{
30 context::Context,
31 lazy_register_view::LazyRegisterView,
32 map_view::MapView,
33 register_view::RegisterView,
34 set_view::SetView,
35 views::{ClonableView, ReplaceContext, View},
36 ViewError,
37};
38use serde::{Deserialize, Serialize};
39
40#[cfg(test)]
41use crate::test_utils::SystemExecutionState;
42use crate::{
43 committee::Committee, util::OracleResponseExt as _, ApplicationDescription, ApplicationId,
44 ExecutionError, ExecutionRuntimeContext, MessageContext, MessageKind, OperationContext,
45 OutgoingMessage, QueryContext, QueryOutcome, ResourceController, TransactionTracker,
46};
47
48pub static EPOCH_STREAM_NAME: &[u8] = &[0];
50pub static REMOVED_EPOCH_STREAM_NAME: &[u8] = &[1];
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct EpochEventData {
56 pub blob_hash: CryptoHash,
58 pub timestamp: Timestamp,
60}
61
62#[cfg(with_metrics)]
64pub(crate) mod metrics {
65 use linera_base::prometheus_util::register_int_counter_vec;
66 use prometheus::IntCounterVec;
67
68 linera_base::declare_metrics! {
69 pub static OPEN_CHAIN_COUNT: IntCounterVec =
70 register_int_counter_vec(
71 "open_chain_count",
72 "The number of times the `OpenChain` operation was executed",
73 &[],
74 );
75 }
76}
77
78#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Allocative)]
82pub struct ChainProgress {
83 pub timestamp: Timestamp,
85 pub num_incoming_bundles: u32,
87 pub num_operations: u32,
89 pub num_outgoing_messages: u32,
91}
92
93#[derive(Debug, ClonableView, View, Allocative)]
95#[allocative(bound = "C")]
96pub struct SystemExecutionStateView<C> {
97 pub description: LazyRegisterView<C, Option<ChainDescription>>,
99 pub epoch: RegisterView<C, Epoch>,
101 pub admin_chain_id: RegisterView<C, Option<ChainId>>,
103 pub committee_hash: RegisterView<C, Option<CryptoHash>>,
106 pub ownership: LazyRegisterView<C, ChainOwnership>,
108 pub balance: RegisterView<C, Amount>,
110 pub balances: MapView<C, AccountOwner, Amount>,
112 pub allowances: MapView<C, OwnerSpender, Amount>,
114 pub closed: RegisterView<C, bool>,
116 pub application_permissions: LazyRegisterView<C, ApplicationPermissions>,
118 pub used_blobs: SetView<C, BlobId>,
120 pub event_subscriptions: MapView<C, (ChainId, StreamId), EventSubscriptions>,
122 pub stream_event_counts: MapView<C, StreamId, u32>,
124 pub unfinalized_message_blocks: MapView<C, ChainId, BTreeSet<Cursor>>,
137 pub pending_checkpoint_ack_targets: SetView<C, ChainId>,
143 pub progress: RegisterView<C, ChainProgress>,
145}
146
147impl<C: Context, C2: Context> ReplaceContext<C2> for SystemExecutionStateView<C> {
148 type Target = SystemExecutionStateView<C2>;
149
150 async fn with_context(
151 &mut self,
152 ctx: impl FnOnce(&Self::Context) -> C2 + Clone,
153 ) -> Self::Target {
154 SystemExecutionStateView {
155 description: self.description.with_context(ctx.clone()).await,
156 epoch: self.epoch.with_context(ctx.clone()).await,
157 admin_chain_id: self.admin_chain_id.with_context(ctx.clone()).await,
158 committee_hash: self.committee_hash.with_context(ctx.clone()).await,
159 ownership: self.ownership.with_context(ctx.clone()).await,
160 balance: self.balance.with_context(ctx.clone()).await,
161 balances: self.balances.with_context(ctx.clone()).await,
162 allowances: self.allowances.with_context(ctx.clone()).await,
163 closed: self.closed.with_context(ctx.clone()).await,
164 application_permissions: self.application_permissions.with_context(ctx.clone()).await,
165 used_blobs: self.used_blobs.with_context(ctx.clone()).await,
166 event_subscriptions: self.event_subscriptions.with_context(ctx.clone()).await,
167 stream_event_counts: self.stream_event_counts.with_context(ctx.clone()).await,
168 unfinalized_message_blocks: self
169 .unfinalized_message_blocks
170 .with_context(ctx.clone())
171 .await,
172 pending_checkpoint_ack_targets: self
173 .pending_checkpoint_ack_targets
174 .with_context(ctx.clone())
175 .await,
176 progress: self.progress.with_context(ctx.clone()).await,
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, Allocative)]
183pub struct EventSubscriptions {
184 pub min_next_index: u32,
188 pub applications: BTreeMap<ApplicationId, u32>,
191}
192
193impl Default for EventSubscriptions {
194 fn default() -> Self {
195 Self {
196 min_next_index: u32::MAX,
197 applications: BTreeMap::new(),
198 }
199 }
200}
201
202impl EventSubscriptions {
203 pub(crate) fn recalculate_min(&mut self) {
204 self.min_next_index = self
205 .applications
206 .values()
207 .copied()
208 .min()
209 .unwrap_or(u32::MAX);
210 }
211}
212
213#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
215pub struct OpenChainConfig {
216 pub ownership: ChainOwnership,
218 pub account: AccountOwner,
221 pub balance: Amount,
223 pub application_permissions: ApplicationPermissions,
225}
226
227impl OpenChainConfig {
228 pub fn init_chain_config(&self, epoch: Epoch) -> InitialChainConfig {
231 InitialChainConfig {
232 application_permissions: self.application_permissions.clone(),
233 account: self.account,
234 balance: self.balance,
235 epoch,
236 ownership: self.ownership.clone(),
237 }
238 }
239}
240
241#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
243#[allow(missing_docs)]
244pub enum SystemOperation {
245 Transfer {
248 owner: AccountOwner,
249 recipient: Account,
250 amount: Amount,
251 },
252 Claim {
256 owner: AccountOwner,
257 target_id: ChainId,
258 recipient: Account,
259 amount: Amount,
260 },
261 OpenChain(OpenChainConfig),
264 CloseChain,
266 ChangeOwnership {
268 #[debug(skip_if = Vec::is_empty)]
270 super_owners: Vec<AccountOwner>,
271 #[debug(skip_if = Vec::is_empty)]
273 owners: Vec<(AccountOwner, u64)>,
274 #[debug(skip_if = Option::is_none)]
276 first_leader: Option<AccountOwner>,
277 multi_leader_rounds: u32,
279 open_multi_leader_rounds: bool,
283 timeout_config: TimeoutConfig,
285 },
286 ChangeApplicationPermissions(ApplicationPermissions),
288 PublishModule { module_id: ModuleId },
290 PublishDataBlob { blob_hash: CryptoHash },
292 VerifyBlob { blob_id: BlobId },
294 CreateApplication {
296 module_id: ModuleId,
297 #[serde(with = "serde_bytes")]
298 #[debug(with = "hex_debug")]
299 parameters: Vec<u8>,
300 #[serde(with = "serde_bytes")]
301 #[debug(with = "hex_debug", skip_if = Vec::is_empty)]
302 instantiation_argument: Vec<u8>,
303 #[debug(skip_if = Vec::is_empty)]
304 required_application_ids: Vec<ApplicationId>,
305 },
306 Admin(AdminOperation),
308 ProcessNewEpoch(Epoch),
310 UpdateStream {
312 application_id: ApplicationId,
313 chain_id: ChainId,
314 stream_id: StreamId,
315 first_index: u32,
318 next_index: u32,
319 },
320 Checkpoint,
325}
326
327#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
329#[allow(missing_docs)]
330pub enum AdminOperation {
331 PublishCommitteeBlob { blob_hash: CryptoHash },
334 CreateCommittee { epoch: Epoch, blob_hash: CryptoHash },
337 RemoveCommittee { epoch: Epoch },
340}
341
342#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
344#[allow(missing_docs)]
345pub enum SystemMessage {
346 Credit {
349 target: AccountOwner,
350 amount: Amount,
351 source: AccountOwner,
352 },
353 Withdraw {
357 owner: AccountOwner,
358 amount: Amount,
359 recipient: Account,
360 },
361 CheckpointAck { latest_received_cursor: Cursor },
368}
369
370#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
372pub struct SystemQuery;
373
374#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
376#[allow(missing_docs)]
377pub struct SystemResponse {
378 pub chain_id: ChainId,
379 pub balance: Amount,
380}
381
382#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Default, Debug, Serialize, Deserialize)]
384pub struct UserData(pub Option<[u8; 32]>);
385
386#[derive(Debug)]
388#[allow(missing_docs)]
389pub struct CreateApplicationResult {
390 pub app_id: ApplicationId,
391}
392
393impl<C> SystemExecutionStateView<C>
394where
395 C: Context + Clone + 'static,
396 C::Extra: ExecutionRuntimeContext,
397{
398 pub async fn is_active(&self) -> Result<bool, ViewError> {
400 Ok(self.description.get().await?.is_some()
401 && self.ownership.get().await?.is_active()
402 && self.admin_chain_id.get().is_some())
403 }
404
405 pub async fn current_committee(
407 &self,
408 ) -> Result<Option<(Epoch, Arc<Committee>)>, ExecutionError> {
409 let Some(hash) = *self.committee_hash.get() else {
410 return Ok(None);
411 };
412 let epoch = *self.epoch.get();
413 let committee = self
414 .context()
415 .extra()
416 .get_or_load_committee_by_hash(hash)
417 .await?;
418 Ok(Some((epoch, committee)))
419 }
420
421 async fn get_event(&self, event_id: EventId) -> Result<Arc<Vec<u8>>, ExecutionError> {
422 match self.context().extra().get_event(event_id.clone()).await? {
423 None => Err(ExecutionError::EventsNotFound(vec![event_id])),
424 Some(vec) => Ok(vec),
425 }
426 }
427
428 pub async fn execute_operation(
431 &mut self,
432 context: OperationContext,
433 operation: SystemOperation,
434 txn_tracker: &mut TransactionTracker,
435 resource_controller: &mut ResourceController<Option<AccountOwner>>,
436 ) -> Result<Option<(ApplicationId, Vec<u8>)>, ExecutionError> {
437 use SystemOperation::*;
438 let mut new_application = None;
439 match operation {
440 OpenChain(config) => {
441 let _chain_id = self
442 .open_chain(
443 config,
444 context.chain_id,
445 context.height,
446 context.timestamp,
447 txn_tracker,
448 )
449 .await?;
450 #[cfg(with_metrics)]
451 metrics::OPEN_CHAIN_COUNT.with_label_values(&[]).inc();
452 }
453 ChangeOwnership {
454 super_owners,
455 owners,
456 first_leader,
457 multi_leader_rounds,
458 open_multi_leader_rounds,
459 timeout_config,
460 } => {
461 self.ownership.set(ChainOwnership {
462 super_owners: super_owners.into_iter().collect(),
463 owners: owners.into_iter().collect(),
464 first_leader,
465 multi_leader_rounds,
466 open_multi_leader_rounds,
467 timeout_config,
468 });
469 }
470 ChangeApplicationPermissions(application_permissions) => {
471 self.application_permissions.set(application_permissions);
472 }
473 CloseChain => self.close_chain(),
474 Transfer {
475 owner,
476 amount,
477 recipient,
478 } => {
479 let maybe_message = self
480 .transfer(context.authenticated_owner, None, owner, recipient, amount)
481 .await?;
482 txn_tracker.add_outgoing_messages(maybe_message);
483 }
484 Claim {
485 owner,
486 target_id,
487 recipient,
488 amount,
489 } => {
490 let maybe_message = self
491 .claim(
492 context.authenticated_owner,
493 None,
494 owner,
495 target_id,
496 recipient,
497 amount,
498 )
499 .await?;
500 txn_tracker.add_outgoing_messages(maybe_message);
501 }
502 Admin(admin_operation) => {
503 ensure!(
504 *self.admin_chain_id.get() == Some(context.chain_id),
505 ExecutionError::AdminOperationOnNonAdminChain
506 );
507 match admin_operation {
508 AdminOperation::PublishCommitteeBlob { blob_hash } => {
509 self.blob_published(
510 &BlobId::new(blob_hash, BlobType::Committee),
511 txn_tracker,
512 )?;
513 }
514 AdminOperation::CreateCommittee { epoch, blob_hash } => {
515 self.check_next_epoch(epoch)?;
516 let blob_id = BlobId::new(blob_hash, BlobType::Committee);
517 self.context()
519 .extra()
520 .get_or_load_committee_by_hash(blob_hash)
521 .await?;
522 self.blob_used(txn_tracker, blob_id).await?;
523 self.committee_hash.set(Some(blob_hash));
524 self.epoch.set(epoch);
525 let event_data = EpochEventData {
526 blob_hash,
527 timestamp: context.timestamp,
528 };
529 let stream_id = StreamId::system(EPOCH_STREAM_NAME);
530 let next_index = epoch.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
531 self.stream_event_counts.insert(&stream_id, next_index)?;
532 txn_tracker.add_event(stream_id, epoch.0, bcs::to_bytes(&event_data)?);
533 }
534 AdminOperation::RemoveCommittee { epoch } => {
535 let stream_id = StreamId::system(REMOVED_EPOCH_STREAM_NAME);
536 let count = self.stream_event_counts.get(&stream_id).await?.unwrap_or(0);
537 ensure!(
540 count == epoch.0 && epoch < *self.epoch.get(),
541 ExecutionError::InvalidCommitteeRemoval
542 );
543 let next_index = epoch.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
544 self.stream_event_counts.insert(&stream_id, next_index)?;
545 txn_tracker.add_event(stream_id, epoch.0, vec![]);
546 }
547 }
548 }
549 PublishModule { module_id } => {
550 for blob_id in module_id.bytecode_blob_ids() {
551 self.blob_published(&blob_id, txn_tracker)?;
552 }
553 }
554 CreateApplication {
555 module_id,
556 parameters,
557 instantiation_argument,
558 required_application_ids,
559 } => {
560 let CreateApplicationResult { app_id } = self
561 .create_application(
562 context.chain_id,
563 context.height,
564 module_id,
565 parameters,
566 required_application_ids,
567 txn_tracker,
568 )
569 .await?;
570 new_application = Some((app_id, instantiation_argument));
571 }
572 PublishDataBlob { blob_hash } => {
573 self.blob_published(&BlobId::new(blob_hash, BlobType::Data), txn_tracker)?;
574 }
575 VerifyBlob { blob_id } => {
576 self.assert_blob_exists(blob_id).await?;
577 resource_controller
578 .with_state(self)
579 .await?
580 .track_blob_read(0)?;
581 self.blob_used(txn_tracker, blob_id).await?;
582 }
583 ProcessNewEpoch(epoch) => {
584 self.check_next_epoch(epoch)?;
585 let admin_chain_id = self.admin_chain_id.get().ok_or_else(|| {
586 ExecutionError::InternalError(
587 "execute_operation called for uninitialized chain",
588 )
589 })?;
590 let event_id = EventId {
591 chain_id: admin_chain_id,
592 stream_id: StreamId::system(EPOCH_STREAM_NAME),
593 index: epoch.0,
594 };
595 let bytes = txn_tracker
596 .oracle(|| async {
597 let bytes = self.get_event(event_id.clone()).await?;
598 Ok(OracleResponse::Event(
599 event_id.clone(),
600 Arc::unwrap_or_clone(bytes),
601 ))
602 })
603 .await?
604 .to_event(&event_id)?;
605 let event_data: EpochEventData = bcs::from_bytes(&bytes)?;
606 let blob_id = BlobId::new(event_data.blob_hash, BlobType::Committee);
607 self.context()
609 .extra()
610 .get_or_load_committee_by_hash(event_data.blob_hash)
611 .await?;
612 self.blob_used(txn_tracker, blob_id).await?;
613 self.committee_hash.set(Some(event_data.blob_hash));
614 self.epoch.set(epoch);
615 }
616 UpdateStream {
617 application_id,
618 chain_id,
619 stream_id,
620 first_index,
621 next_index,
622 } => {
623 let subscriptions = self
624 .event_subscriptions
625 .get_mut_or_default(&(chain_id, stream_id.clone()))
626 .await?;
627 let app_next_index = *subscriptions
628 .applications
629 .get(&application_id)
630 .ok_or(ExecutionError::UnsubscribedUpdateStream)?;
631 ensure!(
632 app_next_index < next_index,
633 ExecutionError::OutdatedUpdateStream
634 );
635 txn_tracker.add_stream_to_process(
636 application_id,
637 chain_id,
638 stream_id.clone(),
639 app_next_index,
640 first_index,
641 next_index,
642 );
643 subscriptions
644 .applications
645 .insert(application_id, next_index);
646 subscriptions.recalculate_min();
647 let index = next_index
648 .checked_sub(1)
649 .ok_or(ArithmeticError::Underflow)?;
650 let event_id = EventId {
651 chain_id,
652 stream_id,
653 index,
654 };
655 let context = self.context();
656 let extra = context.extra();
657 let mut missing_events = Vec::new();
658 txn_tracker
659 .oracle(|| async {
660 if !extra.contains_event(event_id.clone()).await? {
661 missing_events.push(event_id.clone());
662 }
663 Ok(OracleResponse::EventExists(event_id))
664 })
665 .await?;
666 ensure!(
667 missing_events.is_empty(),
668 ExecutionError::EventsNotFound(missing_events)
669 );
670 }
671 Checkpoint => {
672 return Err(ExecutionError::InternalError(
673 "SystemOperation::Checkpoint must be dispatched at ExecutionStateView level",
674 ));
675 }
676 }
677
678 Ok(new_application)
679 }
680
681 fn check_next_epoch(&self, provided: Epoch) -> Result<(), ExecutionError> {
684 let expected = self.epoch.get().try_add_one()?;
685 ensure!(
686 provided == expected,
687 ExecutionError::InvalidCommitteeEpoch { provided, expected }
688 );
689 Ok(())
690 }
691
692 async fn credit(&mut self, owner: &AccountOwner, amount: Amount) -> Result<(), ExecutionError> {
693 if owner == &AccountOwner::CHAIN {
694 let new_balance = self.balance.get().saturating_add(amount);
695 self.balance.set(new_balance);
696 } else {
697 let balance = self.balances.get_mut_or_default(owner).await?;
698 *balance = balance.saturating_add(amount);
699 }
700 Ok(())
701 }
702
703 async fn credit_or_send_message(
704 &mut self,
705 source: AccountOwner,
706 recipient: Account,
707 amount: Amount,
708 ) -> Result<Option<OutgoingMessage>, ExecutionError> {
709 let source_chain_id = self.context().extra().chain_id();
710 if recipient.chain_id == source_chain_id {
711 let target = recipient.owner;
713 self.credit(&target, amount).await?;
714 Ok(None)
715 } else {
716 let message = SystemMessage::Credit {
718 amount,
719 source,
720 target: recipient.owner,
721 };
722 Ok(Some(
723 OutgoingMessage::new(recipient.chain_id, message).with_kind(MessageKind::Tracked),
724 ))
725 }
726 }
727
728 pub async fn transfer(
730 &mut self,
731 authenticated_owner: Option<AccountOwner>,
732 authenticated_application_id: Option<ApplicationId>,
733 source: AccountOwner,
734 recipient: Account,
735 amount: Amount,
736 ) -> Result<Option<OutgoingMessage>, ExecutionError> {
737 if source == AccountOwner::CHAIN {
738 let authenticated_owner =
739 authenticated_owner.ok_or(ExecutionError::UnauthenticatedTransferOwner)?;
740 ensure!(
741 self.ownership.get().await?.is_owner(&authenticated_owner),
742 ExecutionError::UnauthenticatedTransferOwner
743 );
744 } else {
745 ensure!(
746 authenticated_owner == Some(source)
747 || authenticated_application_id.map(AccountOwner::from) == Some(source),
748 ExecutionError::UnauthenticatedTransferOwner
749 );
750 }
751 ensure!(
752 amount > Amount::ZERO,
753 ExecutionError::IncorrectTransferAmount
754 );
755 self.debit(&source, amount).await?;
756 self.credit_or_send_message(source, recipient, amount).await
757 }
758
759 pub async fn claim(
761 &mut self,
762 authenticated_owner: Option<AccountOwner>,
763 authenticated_application_id: Option<ApplicationId>,
764 source: AccountOwner,
765 target_id: ChainId,
766 recipient: Account,
767 amount: Amount,
768 ) -> Result<Option<OutgoingMessage>, ExecutionError> {
769 ensure!(
770 authenticated_owner == Some(source)
771 || authenticated_application_id.map(AccountOwner::from) == Some(source),
772 ExecutionError::UnauthenticatedClaimOwner
773 );
774 ensure!(amount > Amount::ZERO, ExecutionError::IncorrectClaimAmount);
775
776 let current_chain_id = self.context().extra().chain_id();
777 if target_id == current_chain_id {
778 self.debit(&source, amount).await?;
780 self.credit_or_send_message(source, recipient, amount).await
781 } else {
782 let message = SystemMessage::Withdraw {
784 amount,
785 owner: source,
786 recipient,
787 };
788 Ok(Some(
789 OutgoingMessage::new(target_id, message)
790 .with_authenticated_owner(authenticated_owner),
791 ))
792 }
793 }
794
795 pub async fn approve(
797 &mut self,
798 authenticated_owner: Option<AccountOwner>,
799 authenticated_application_id: Option<ApplicationId>,
800 owner: AccountOwner,
801 spender: AccountOwner,
802 amount: Amount,
803 ) -> Result<(), ExecutionError> {
804 ensure!(
805 authenticated_owner == Some(owner)
806 || authenticated_application_id.map(AccountOwner::from) == Some(owner),
807 ExecutionError::UnauthenticatedTransferOwner
808 );
809
810 let owner_spender = OwnerSpender::new(owner, spender);
811 if amount == Amount::ZERO {
812 self.allowances.remove(&owner_spender)?;
813 return Ok(());
814 }
815 let allowance = self.allowances.get_mut_or_default(&owner_spender).await?;
816 *allowance = amount;
817
818 Ok(())
819 }
820
821 pub async fn transfer_from(
823 &mut self,
824 authenticated_owner: Option<AccountOwner>,
825 authenticated_application_id: Option<ApplicationId>,
826 owner: AccountOwner,
827 spender: AccountOwner,
828 recipient: Account,
829 amount: Amount,
830 ) -> Result<Option<OutgoingMessage>, ExecutionError> {
831 ensure!(
832 authenticated_owner == Some(spender)
833 || authenticated_application_id.map(AccountOwner::from) == Some(spender),
834 ExecutionError::UnauthenticatedTransferOwner
835 );
836 ensure!(
837 amount > Amount::ZERO,
838 ExecutionError::IncorrectTransferAmount
839 );
840
841 let owner_spender = OwnerSpender::new(owner, spender);
843 let allowance = self.allowances.get_mut_or_default(&owner_spender).await?;
844
845 allowance
846 .try_sub_assign(amount)
847 .map_err(|_| ExecutionError::InsufficientAllowance {
848 allowance: *allowance,
849 owner,
850 spender,
851 })?;
852
853 if allowance.is_zero() {
854 self.allowances.remove(&owner_spender)?;
855 }
856
857 self.debit(&owner, amount).await?;
859
860 self.credit_or_send_message(owner, recipient, amount).await
862 }
863
864 async fn debit(
866 &mut self,
867 account: &AccountOwner,
868 amount: Amount,
869 ) -> Result<(), ExecutionError> {
870 let balance = if account == &AccountOwner::CHAIN {
871 self.balance.get_mut()
872 } else {
873 self.balances.get_mut(account).await?.ok_or_else(|| {
874 ExecutionError::InsufficientBalance {
875 balance: Amount::ZERO,
876 account: *account,
877 }
878 })?
879 };
880
881 balance
882 .try_sub_assign(amount)
883 .map_err(|_| ExecutionError::InsufficientBalance {
884 balance: *balance,
885 account: *account,
886 })?;
887
888 if account != &AccountOwner::CHAIN && balance.is_zero() {
889 self.balances.remove(account)?;
890 }
891
892 Ok(())
893 }
894
895 pub async fn execute_message(
897 &mut self,
898 context: MessageContext,
899 message: SystemMessage,
900 ) -> Result<Vec<OutgoingMessage>, ExecutionError> {
901 let mut outcome = Vec::new();
902 use SystemMessage::*;
903 match message {
904 Credit {
905 amount,
906 source,
907 target,
908 } => {
909 let receiver = if context.is_bouncing { source } else { target };
910 self.credit(&receiver, amount).await?;
911 }
912 Withdraw {
913 amount,
914 owner,
915 recipient,
916 } => {
917 self.debit(&owner, amount).await?;
918 if let Some(message) = self
919 .credit_or_send_message(owner, recipient, amount)
920 .await?
921 {
922 outcome.push(message);
923 }
924 }
925 CheckpointAck {
926 latest_received_cursor,
927 } => {
928 if let Some(mut cursors) =
934 self.unfinalized_message_blocks.get(&context.origin).await?
935 {
936 let retained = cursors.split_off(&latest_received_cursor);
937 if retained.is_empty() {
938 self.unfinalized_message_blocks.remove(&context.origin)?;
939 } else {
940 self.unfinalized_message_blocks
941 .insert(&context.origin, retained)?;
942 }
943 }
944 }
945 }
946 Ok(outcome)
947 }
948
949 pub async fn initialize_chain(&mut self, chain_id: ChainId) -> Result<bool, ExecutionError> {
952 if self.description.get().await?.is_some() {
953 return Ok(true);
955 }
956 let description_blob = self
957 .read_blob_content(BlobId::new(chain_id.0, BlobType::ChainDescription))
958 .await?;
959 let description: ChainDescription = bcs::from_bytes(description_blob.bytes())?;
960 let InitialChainConfig {
961 ownership,
962 epoch,
963 account,
964 balance,
965 application_permissions,
966 } = description.config().clone();
967 self.progress.get_mut().timestamp = description.timestamp();
968 self.description.set(Some(description));
969 self.epoch.set(epoch);
970
971 let committee_hash = *self
972 .context()
973 .extra()
974 .get_committee_hashes(epoch..=epoch)
975 .await?
976 .get(&epoch)
977 .expect("get_committee_hashes returns the requested epoch on success");
978 let admin_chain_id = self
979 .context()
980 .extra()
981 .get_network_description()
982 .await?
983 .ok_or(ExecutionError::NoNetworkDescriptionFound)?
984 .admin_chain_id;
985
986 self.committee_hash.set(Some(committee_hash));
987 self.admin_chain_id.set(Some(admin_chain_id));
988 self.ownership.set(ownership);
989 if balance > Amount::ZERO {
990 self.credit(&account, balance).await?;
992 }
993 self.application_permissions.set(application_permissions);
994 Ok(false)
995 }
996
997 pub fn handle_query(
999 &mut self,
1000 context: QueryContext,
1001 _query: SystemQuery,
1002 ) -> QueryOutcome<SystemResponse> {
1003 let response = SystemResponse {
1004 chain_id: context.chain_id,
1005 balance: *self.balance.get(),
1006 };
1007 QueryOutcome {
1008 response,
1009 operations: vec![],
1010 }
1011 }
1012
1013 pub async fn open_chain(
1016 &mut self,
1017 config: OpenChainConfig,
1018 parent: ChainId,
1019 block_height: BlockHeight,
1020 timestamp: Timestamp,
1021 txn_tracker: &mut TransactionTracker,
1022 ) -> Result<ChainId, ExecutionError> {
1023 let chain_index = txn_tracker.next_chain_index();
1024 let chain_origin = ChainOrigin::Child {
1025 parent,
1026 block_height,
1027 chain_index,
1028 };
1029 let init_chain_config = config.init_chain_config(*self.epoch.get());
1030 let chain_description = ChainDescription::new(chain_origin, init_chain_config, timestamp);
1031 let child_id = chain_description.id();
1032 self.debit(&AccountOwner::CHAIN, config.balance).await?;
1033 let blob = Blob::new_chain_description(&chain_description);
1034 txn_tracker.add_created_blob(blob);
1035 Ok(child_id)
1036 }
1037
1038 pub fn close_chain(&mut self) {
1040 self.closed.set(true);
1041 }
1042
1043 pub async fn create_application(
1045 &mut self,
1046 chain_id: ChainId,
1047 block_height: BlockHeight,
1048 module_id: ModuleId,
1049 parameters: Vec<u8>,
1050 required_application_ids: Vec<ApplicationId>,
1051 txn_tracker: &mut TransactionTracker,
1052 ) -> Result<CreateApplicationResult, ExecutionError> {
1053 let application_index = txn_tracker.next_application_index();
1054
1055 let blob_ids = self.check_bytecode_blobs(&module_id, txn_tracker).await?;
1056 for blob_id in blob_ids {
1059 self.blob_used(txn_tracker, blob_id).await?;
1060 }
1061
1062 let application_description = ApplicationDescription {
1063 module_id,
1064 creator_chain_id: chain_id,
1065 block_height,
1066 application_index,
1067 parameters,
1068 required_application_ids,
1069 };
1070 self.check_required_applications(&application_description, txn_tracker)
1071 .await?;
1072
1073 let blob = Blob::new_application_description(&application_description);
1074 self.used_blobs.insert(&blob.id())?;
1075 txn_tracker.add_created_blob(blob);
1076
1077 Ok(CreateApplicationResult {
1078 app_id: ApplicationId::from(&application_description),
1079 })
1080 }
1081
1082 async fn check_required_applications(
1083 &mut self,
1084 application_description: &ApplicationDescription,
1085 txn_tracker: &mut TransactionTracker,
1086 ) -> Result<(), ExecutionError> {
1087 for required_id in &application_description.required_application_ids {
1089 Box::pin(self.describe_application(*required_id, txn_tracker)).await?;
1090 }
1091 Ok(())
1092 }
1093
1094 pub async fn describe_application(
1096 &mut self,
1097 id: ApplicationId,
1098 txn_tracker: &mut TransactionTracker,
1099 ) -> Result<ApplicationDescription, ExecutionError> {
1100 let blob_id = id.description_blob_id();
1101 let content = match txn_tracker.created_blobs().get(&blob_id) {
1102 Some(content) => content.clone(),
1103 None => self.read_blob_content(blob_id).await?,
1104 };
1105 self.blob_used(txn_tracker, blob_id).await?;
1106 let description: ApplicationDescription = bcs::from_bytes(content.bytes())?;
1107
1108 let blob_ids = self
1109 .check_bytecode_blobs(&description.module_id, txn_tracker)
1110 .await?;
1111 for blob_id in blob_ids {
1114 self.blob_used(txn_tracker, blob_id).await?;
1115 }
1116
1117 self.check_required_applications(&description, txn_tracker)
1118 .await?;
1119
1120 Ok(description)
1121 }
1122
1123 pub(crate) async fn blob_used(
1126 &mut self,
1127 txn_tracker: &mut TransactionTracker,
1128 blob_id: BlobId,
1129 ) -> Result<bool, ExecutionError> {
1130 if self.used_blobs.contains(&blob_id).await? {
1131 return Ok(false); }
1133 self.used_blobs.insert(&blob_id)?;
1134 txn_tracker.replay_oracle_response(OracleResponse::Blob(blob_id))?;
1135 Ok(true)
1136 }
1137
1138 fn blob_published(
1141 &mut self,
1142 blob_id: &BlobId,
1143 txn_tracker: &mut TransactionTracker,
1144 ) -> Result<(), ExecutionError> {
1145 self.used_blobs.insert(blob_id)?;
1146 txn_tracker.add_published_blob(*blob_id);
1147 Ok(())
1148 }
1149
1150 pub async fn read_blob_content(&self, blob_id: BlobId) -> Result<BlobContent, ExecutionError> {
1152 match self.context().extra().get_blob(blob_id).await {
1153 Ok(Some(blob)) => Ok(Arc::unwrap_or_clone(blob).into()),
1154 Ok(None) => Err(ExecutionError::BlobsNotFound(vec![blob_id])),
1155 Err(error) => Err(error.into()),
1156 }
1157 }
1158
1159 pub async fn assert_blob_exists(&mut self, blob_id: BlobId) -> Result<(), ExecutionError> {
1161 if self.context().extra().contains_blob(blob_id).await? {
1162 Ok(())
1163 } else {
1164 Err(ExecutionError::BlobsNotFound(vec![blob_id]))
1165 }
1166 }
1167
1168 async fn check_bytecode_blobs(
1169 &self,
1170 module_id: &ModuleId,
1171 txn_tracker: &TransactionTracker,
1172 ) -> Result<Vec<BlobId>, ExecutionError> {
1173 let blob_ids = module_id.bytecode_blob_ids();
1174
1175 let mut missing_blobs = Vec::new();
1176 for blob_id in &blob_ids {
1177 if txn_tracker.created_blobs().contains_key(blob_id) {
1179 continue; }
1181 if !self.context().extra().contains_blob(*blob_id).await? {
1183 missing_blobs.push(*blob_id);
1184 }
1185 }
1186 ensure!(
1187 missing_blobs.is_empty(),
1188 ExecutionError::BlobsNotFound(missing_blobs)
1189 );
1190
1191 Ok(blob_ids)
1192 }
1193}