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