1#[cfg(with_metrics)]
6use std::sync::LazyLock;
7use std::{
8 collections::BTreeMap,
9 fmt::{self, Display, Formatter},
10 iter,
11};
12
13use async_graphql::Enum;
14use custom_debug_derive::Debug;
15use linera_base::{
16 crypto::{CryptoHash, PublicKey},
17 data_types::{
18 Amount, ApplicationPermissions, ArithmeticError, BlobContent, OracleResponse, Timestamp,
19 },
20 ensure, hex_debug,
21 identifiers::{
22 Account, BlobId, BlobType, BytecodeId, ChainDescription, ChainId, MessageId, Owner,
23 },
24 ownership::{ChainOwnership, TimeoutConfig},
25};
26use linera_views::{
27 context::Context,
28 map_view::HashedMapView,
29 register_view::HashedRegisterView,
30 set_view::HashedSetView,
31 views::{ClonableView, HashableView, View, ViewError},
32};
33use serde::{Deserialize, Serialize};
34use thiserror::Error;
35#[cfg(with_metrics)]
36use {linera_base::prometheus_util, prometheus::IntCounterVec};
37
38#[cfg(test)]
39use crate::test_utils::SystemExecutionState;
40use crate::{
41 committee::{Committee, Epoch},
42 ApplicationRegistryView, ChannelName, ChannelSubscription, Destination,
43 ExecutionRuntimeContext, MessageContext, MessageKind, OperationContext, QueryContext,
44 RawExecutionOutcome, RawOutgoingMessage, TransactionTracker, UserApplicationDescription,
45 UserApplicationId,
46};
47
48pub static OPEN_CHAIN_MESSAGE_INDEX: u32 = 0;
50pub static CREATE_APPLICATION_MESSAGE_INDEX: u32 = 0;
53
54#[cfg(with_metrics)]
56static OPEN_CHAIN_COUNT: LazyLock<IntCounterVec> = LazyLock::new(|| {
57 prometheus_util::register_int_counter_vec(
58 "open_chain_count",
59 "The number of times the `OpenChain` operation was executed",
60 &[],
61 )
62 .expect("Counter creation should not fail")
63});
64
65#[derive(Debug, ClonableView, HashableView)]
67pub struct SystemExecutionStateView<C> {
68 pub description: HashedRegisterView<C, Option<ChainDescription>>,
70 pub epoch: HashedRegisterView<C, Option<Epoch>>,
72 pub admin_id: HashedRegisterView<C, Option<ChainId>>,
74 pub subscriptions: HashedSetView<C, ChannelSubscription>,
76 pub committees: HashedRegisterView<C, BTreeMap<Epoch, Committee>>,
81 pub ownership: HashedRegisterView<C, ChainOwnership>,
83 pub balance: HashedRegisterView<C, Amount>,
85 pub balances: HashedMapView<C, Owner, Amount>,
87 pub timestamp: HashedRegisterView<C, Timestamp>,
89 pub registry: ApplicationRegistryView<C>,
91 pub closed: HashedRegisterView<C, bool>,
93 pub application_permissions: HashedRegisterView<C, ApplicationPermissions>,
95}
96
97#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
99pub struct OpenChainConfig {
100 pub ownership: ChainOwnership,
101 pub admin_id: ChainId,
102 pub epoch: Epoch,
103 pub committees: BTreeMap<Epoch, Committee>,
104 pub balance: Amount,
105 pub application_permissions: ApplicationPermissions,
106}
107
108#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
110pub enum SystemOperation {
111 Transfer {
114 owner: Option<Owner>,
115 recipient: Recipient,
116 amount: Amount,
117 },
118 Claim {
122 owner: Owner,
123 target_id: ChainId,
124 recipient: Recipient,
125 amount: Amount,
126 },
127 OpenChain(OpenChainConfig),
130 CloseChain,
132 ChangeOwnership {
134 super_owners: Vec<PublicKey>,
136 owners: Vec<(PublicKey, u64)>,
138 multi_leader_rounds: u32,
140 timeout_config: TimeoutConfig,
142 },
143 ChangeApplicationPermissions(ApplicationPermissions),
145 Subscribe {
147 chain_id: ChainId,
148 channel: SystemChannel,
149 },
150 Unsubscribe {
152 chain_id: ChainId,
153 channel: SystemChannel,
154 },
155 PublishBytecode { bytecode_id: BytecodeId },
157 PublishDataBlob { blob_hash: CryptoHash },
159 ReadBlob { blob_id: BlobId },
162 CreateApplication {
164 bytecode_id: BytecodeId,
165 #[serde(with = "serde_bytes")]
166 #[debug(with = "hex_debug")]
167 parameters: Vec<u8>,
168 #[serde(with = "serde_bytes")]
169 #[debug(with = "hex_debug")]
170 instantiation_argument: Vec<u8>,
171 required_application_ids: Vec<UserApplicationId>,
172 },
173 RequestApplication {
175 chain_id: ChainId,
176 application_id: UserApplicationId,
177 },
178 Admin(AdminOperation),
180}
181
182#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
184pub enum AdminOperation {
185 CreateCommittee { epoch: Epoch, committee: Committee },
189 RemoveCommittee { epoch: Epoch },
193}
194
195#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
197pub enum SystemMessage {
198 Credit {
201 target: Option<Owner>,
202 amount: Amount,
203 source: Option<Owner>,
204 },
205 Withdraw {
209 owner: Owner,
210 amount: Amount,
211 recipient: Recipient,
212 },
213 OpenChain(OpenChainConfig),
215 CreateCommittee { epoch: Epoch, committee: Committee },
217 RemoveCommittee { epoch: Epoch },
219 Subscribe {
221 id: ChainId,
222 subscription: ChannelSubscription,
223 },
224 Unsubscribe {
226 id: ChainId,
227 subscription: ChannelSubscription,
228 },
229 ApplicationCreated,
231 RegisterApplications {
234 applications: Vec<UserApplicationDescription>,
235 },
236 RequestApplication(UserApplicationId),
239}
240
241#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
243pub struct SystemQuery;
244
245#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
247pub struct SystemResponse {
248 pub chain_id: ChainId,
249 pub balance: Amount,
250}
251
252#[derive(
254 Enum, Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, clap::ValueEnum,
255)]
256pub enum SystemChannel {
257 Admin,
259}
260
261impl SystemChannel {
262 pub fn name(&self) -> ChannelName {
264 bcs::to_bytes(self)
265 .expect("`SystemChannel` can be serialized")
266 .into()
267 }
268}
269
270impl Display for SystemChannel {
271 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
272 let display_name = match self {
273 SystemChannel::Admin => "Admin",
274 };
275
276 write!(formatter, "{display_name}")
277 }
278}
279
280#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
282pub enum Recipient {
283 Burn,
285 Account(Account),
287}
288
289impl Recipient {
290 pub fn chain(chain_id: ChainId) -> Recipient {
292 Recipient::Account(Account::chain(chain_id))
293 }
294
295 #[cfg(with_testing)]
297 pub fn root(index: u32) -> Recipient {
298 Recipient::chain(ChainId::root(index))
299 }
300}
301
302#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Default, Debug, Serialize, Deserialize)]
304pub struct UserData(pub Option<[u8; 32]>);
305
306impl UserData {
307 pub fn from_option_string(opt_str: Option<String>) -> Result<Self, usize> {
308 let option_array = match opt_str {
310 Some(s) => {
311 let vec = s.into_bytes();
313 if vec.len() <= 32 {
314 let mut array = [b' '; 32];
316
317 let len = vec.len().min(32);
319 array[..len].copy_from_slice(&vec[..len]);
320
321 Some(array)
322 } else {
323 return Err(vec.len());
324 }
325 }
326 None => None,
327 };
328
329 Ok(UserData(option_array))
331 }
332}
333
334#[derive(Error, Debug)]
335pub enum SystemExecutionError {
336 #[error(transparent)]
337 ArithmeticError(#[from] ArithmeticError),
338 #[error(transparent)]
339 ViewError(#[from] ViewError),
340
341 #[error("Invalid admin ID in new chain: {0}")]
342 InvalidNewChainAdminId(ChainId),
343 #[error("Invalid committees")]
344 InvalidCommittees,
345 #[error("{epoch:?} is not recognized by chain {chain_id:}")]
346 InvalidEpoch { chain_id: ChainId, epoch: Epoch },
347 #[error("Transfer must have positive amount")]
348 IncorrectTransferAmount,
349 #[error("Transfer from owned account must be authenticated by the right signer")]
350 UnauthenticatedTransferOwner,
351 #[error("The transferred amount must not exceed the current chain balance: {balance}")]
352 InsufficientFunding { balance: Amount },
353 #[error("Required execution fees exceeded the total funding available: {balance}")]
354 InsufficientFundingForFees { balance: Amount },
355 #[error("Claim must have positive amount")]
356 IncorrectClaimAmount,
357 #[error("Claim must be authenticated by the right signer")]
358 UnauthenticatedClaimOwner,
359 #[error("Admin operations are only allowed on the admin chain.")]
360 AdminOperationOnNonAdminChain,
361 #[error("Failed to create new committee")]
362 InvalidCommitteeCreation,
363 #[error("Failed to remove committee")]
364 InvalidCommitteeRemoval,
365 #[error(
366 "Chain {0} tried to subscribe to the admin channel ({1}) of a chain that is not the admin chain"
367 )]
368 InvalidAdminSubscription(ChainId, SystemChannel),
369 #[error("Cannot subscribe to a channel ({1}) on the same chain ({0})")]
370 SelfSubscription(ChainId, SystemChannel),
371 #[error("Chain {0} tried to subscribe to channel {1} but it is already subscribed")]
372 AlreadySubscribedToChannel(ChainId, SystemChannel),
373 #[error("Invalid unsubscription request to channel {1} on chain {0}")]
374 InvalidUnsubscription(ChainId, SystemChannel),
375 #[error("Amount overflow")]
376 AmountOverflow,
377 #[error("Amount underflow")]
378 AmountUnderflow,
379 #[error("Chain balance overflow")]
380 BalanceOverflow,
381 #[error("Chain balance underflow")]
382 BalanceUnderflow,
383 #[error("Cannot set epoch to a lower value")]
384 CannotRewindEpoch,
385 #[error("Cannot decrease the chain's timestamp")]
386 TicksOutOfOrder,
387 #[error("Application {0:?} is not registered by the chain")]
388 UnknownApplicationId(Box<UserApplicationId>),
389 #[error("Chain is not active yet.")]
390 InactiveChain,
391
392 #[error("Blob not found on storage read: {0}")]
393 BlobNotFoundOnRead(BlobId),
394 #[error("Oracle response mismatch")]
395 OracleResponseMismatch,
396 #[error("No recorded response for oracle query")]
397 MissingOracleResponse,
398}
399
400impl<C> SystemExecutionStateView<C>
401where
402 C: Context + Clone + Send + Sync + 'static,
403 C::Extra: ExecutionRuntimeContext,
404{
405 pub fn is_active(&self) -> bool {
407 self.description.get().is_some()
408 && self.ownership.get().is_active()
409 && self.current_committee().is_some()
410 && self.admin_id.get().is_some()
411 }
412
413 pub fn current_committee(&self) -> Option<(Epoch, &Committee)> {
415 let epoch = self.epoch.get().as_ref()?;
416 let committee = self.committees.get().get(epoch)?;
417 Some((*epoch, committee))
418 }
419
420 pub async fn execute_operation(
423 &mut self,
424 context: OperationContext,
425 operation: SystemOperation,
426 txn_tracker: &mut TransactionTracker,
427 ) -> Result<Option<(UserApplicationId, Vec<u8>)>, SystemExecutionError> {
428 use SystemOperation::*;
429 let mut outcome = RawExecutionOutcome {
430 authenticated_signer: context.authenticated_signer,
431 refund_grant_to: context.refund_grant_to(),
432 ..RawExecutionOutcome::default()
433 };
434 let mut new_application = None;
435 match operation {
436 OpenChain(config) => {
437 let next_message_id = context.next_message_id(txn_tracker.next_message_index());
438 let messages = self.open_chain(config, next_message_id)?;
439 outcome.messages.extend(messages);
440 #[cfg(with_metrics)]
441 OPEN_CHAIN_COUNT.with_label_values(&[]).inc();
442 }
443 ChangeOwnership {
444 super_owners,
445 owners,
446 multi_leader_rounds,
447 timeout_config,
448 } => {
449 self.ownership.set(ChainOwnership {
450 super_owners: super_owners
451 .into_iter()
452 .map(|public_key| (Owner::from(public_key), public_key))
453 .collect(),
454 owners: owners
455 .into_iter()
456 .map(|(public_key, weight)| (Owner::from(public_key), (public_key, weight)))
457 .collect(),
458 multi_leader_rounds,
459 timeout_config,
460 });
461 }
462 ChangeApplicationPermissions(application_permissions) => {
463 self.application_permissions.set(application_permissions);
464 }
465 CloseChain => {
466 let messages = self.close_chain(context.chain_id).await?;
467 outcome.messages.extend(messages);
468 }
469 Transfer {
470 owner,
471 amount,
472 recipient,
473 ..
474 } => {
475 let message = self
476 .transfer(context.authenticated_signer, owner, recipient, amount)
477 .await?;
478
479 if let Some(message) = message {
480 outcome.messages.push(message)
481 }
482 }
483 Claim {
484 owner,
485 target_id,
486 recipient,
487 amount,
488 } => {
489 let message = self
490 .claim(
491 context.authenticated_signer,
492 owner,
493 target_id,
494 recipient,
495 amount,
496 )
497 .await?;
498
499 outcome.messages.push(message)
500 }
501 Admin(admin_operation) => {
502 ensure!(
503 *self.admin_id.get() == Some(context.chain_id),
504 SystemExecutionError::AdminOperationOnNonAdminChain
505 );
506 match admin_operation {
507 AdminOperation::CreateCommittee { epoch, committee } => {
508 ensure!(
509 epoch == self.epoch.get().expect("chain is active").try_add_one()?,
510 SystemExecutionError::InvalidCommitteeCreation
511 );
512 self.committees.get_mut().insert(epoch, committee.clone());
513 self.epoch.set(Some(epoch));
514 let message = RawOutgoingMessage {
515 destination: Destination::Subscribers(SystemChannel::Admin.name()),
516 authenticated: false,
517 grant: Amount::ZERO,
518 kind: MessageKind::Protected,
519 message: SystemMessage::CreateCommittee { epoch, committee },
520 };
521 outcome.messages.push(message);
522 }
523 AdminOperation::RemoveCommittee { epoch } => {
524 ensure!(
525 self.committees.get_mut().remove(&epoch).is_some(),
526 SystemExecutionError::InvalidCommitteeRemoval
527 );
528 let message = RawOutgoingMessage {
529 destination: Destination::Subscribers(SystemChannel::Admin.name()),
530 authenticated: false,
531 grant: Amount::ZERO,
532 kind: MessageKind::Protected,
533 message: SystemMessage::RemoveCommittee { epoch },
534 };
535 outcome.messages.push(message);
536 }
537 }
538 }
539 Subscribe { chain_id, channel } => {
540 ensure!(
541 context.chain_id != chain_id,
542 SystemExecutionError::SelfSubscription(context.chain_id, channel)
543 );
544 if channel == SystemChannel::Admin {
545 ensure!(
546 self.admin_id.get().as_ref() == Some(&chain_id),
547 SystemExecutionError::InvalidAdminSubscription(context.chain_id, channel)
548 );
549 }
550 let subscription = ChannelSubscription {
551 chain_id,
552 name: channel.name(),
553 };
554 ensure!(
555 !self.subscriptions.contains(&subscription).await?,
556 SystemExecutionError::AlreadySubscribedToChannel(context.chain_id, channel)
557 );
558 self.subscriptions.insert(&subscription)?;
559 let message = RawOutgoingMessage {
560 destination: Destination::Recipient(chain_id),
561 authenticated: false,
562 grant: Amount::ZERO,
563 kind: MessageKind::Protected,
564 message: SystemMessage::Subscribe {
565 id: context.chain_id,
566 subscription,
567 },
568 };
569 outcome.messages.push(message);
570 }
571 Unsubscribe { chain_id, channel } => {
572 let subscription = ChannelSubscription {
573 chain_id,
574 name: channel.name(),
575 };
576 ensure!(
577 self.subscriptions.contains(&subscription).await?,
578 SystemExecutionError::InvalidUnsubscription(context.chain_id, channel)
579 );
580 self.subscriptions.remove(&subscription)?;
581 let message = RawOutgoingMessage {
582 destination: Destination::Recipient(chain_id),
583 authenticated: false,
584 grant: Amount::ZERO,
585 kind: MessageKind::Protected,
586 message: SystemMessage::Unsubscribe {
587 id: context.chain_id,
588 subscription,
589 },
590 };
591 outcome.messages.push(message);
592 }
593 PublishBytecode { bytecode_id } => {
594 txn_tracker.replay_oracle_response(OracleResponse::Blob(BlobId::new(
595 bytecode_id.contract_blob_hash,
596 BlobType::ContractBytecode,
597 )))?;
598 txn_tracker.replay_oracle_response(OracleResponse::Blob(BlobId::new(
599 bytecode_id.service_blob_hash,
600 BlobType::ServiceBytecode,
601 )))?;
602 }
603 CreateApplication {
604 bytecode_id,
605 parameters,
606 instantiation_argument,
607 required_application_ids,
608 } => {
609 let id = UserApplicationId {
610 bytecode_id,
611 creation: context.next_message_id(txn_tracker.next_message_index()),
612 };
613 for application in required_application_ids.iter().chain(iter::once(&id)) {
614 self.check_and_record_bytecode_blobs(&application.bytecode_id, txn_tracker)
615 .await?;
616 }
617 self.registry
618 .register_new_application(
619 id,
620 parameters.clone(),
621 required_application_ids.clone(),
622 )
623 .await?;
624 let message = RawOutgoingMessage {
626 destination: Destination::Recipient(context.chain_id),
627 authenticated: false,
628 grant: Amount::ZERO,
629 kind: MessageKind::Protected,
630 message: SystemMessage::ApplicationCreated,
631 };
632 outcome.messages.push(message);
633 new_application = Some((id, instantiation_argument.clone()));
634 }
635 RequestApplication {
636 chain_id,
637 application_id,
638 } => {
639 let message = RawOutgoingMessage {
640 destination: Destination::Recipient(chain_id),
641 authenticated: false,
642 grant: Amount::ZERO,
643 kind: MessageKind::Simple,
644 message: SystemMessage::RequestApplication(application_id),
645 };
646 outcome.messages.push(message);
647 }
648 PublishDataBlob { blob_hash } => {
649 txn_tracker.replay_oracle_response(OracleResponse::Blob(BlobId::new(
650 blob_hash,
651 BlobType::Data,
652 )))?;
653 }
654 ReadBlob { blob_id } => {
655 txn_tracker.replay_oracle_response(OracleResponse::Blob(blob_id))?;
656 self.read_blob_content(blob_id).await?;
657 }
658 }
659
660 txn_tracker.add_system_outcome(outcome)?;
661 Ok(new_application)
662 }
663
664 pub async fn transfer(
665 &mut self,
666 authenticated_signer: Option<Owner>,
667 owner: Option<Owner>,
668 recipient: Recipient,
669 amount: Amount,
670 ) -> Result<Option<RawOutgoingMessage<SystemMessage, Amount>>, SystemExecutionError> {
671 if owner.is_some() {
672 ensure!(
673 authenticated_signer == owner,
674 SystemExecutionError::UnauthenticatedTransferOwner
675 );
676 }
677 ensure!(
678 amount > Amount::ZERO,
679 SystemExecutionError::IncorrectTransferAmount
680 );
681 let balance = match &owner {
682 Some(owner) => self.balances.get_mut_or_default(owner).await?,
683 None => self.balance.get_mut(),
684 };
685 balance
686 .try_sub_assign(amount)
687 .map_err(|_| SystemExecutionError::InsufficientFunding { balance: *balance })?;
688 match recipient {
689 Recipient::Account(account) => {
690 let message = RawOutgoingMessage {
691 destination: Destination::Recipient(account.chain_id),
692 authenticated: false,
693 grant: Amount::ZERO,
694 kind: MessageKind::Tracked,
695 message: SystemMessage::Credit {
696 amount,
697 source: owner,
698 target: account.owner,
699 },
700 };
701
702 Ok(Some(message))
703 }
704 Recipient::Burn => Ok(None),
705 }
706 }
707
708 pub async fn claim(
709 &self,
710 authenticated_signer: Option<Owner>,
711 owner: Owner,
712 target_id: ChainId,
713 recipient: Recipient,
714 amount: Amount,
715 ) -> Result<RawOutgoingMessage<SystemMessage, Amount>, SystemExecutionError> {
716 ensure!(
717 authenticated_signer.as_ref() == Some(&owner),
718 SystemExecutionError::UnauthenticatedClaimOwner
719 );
720 ensure!(
721 amount > Amount::ZERO,
722 SystemExecutionError::IncorrectClaimAmount
723 );
724
725 Ok(RawOutgoingMessage {
726 destination: Destination::Recipient(target_id),
727 authenticated: true,
728 grant: Amount::ZERO,
729 kind: MessageKind::Simple,
730 message: SystemMessage::Withdraw {
731 amount,
732 owner,
733 recipient,
734 },
735 })
736 }
737
738 pub async fn execute_message(
740 &mut self,
741 context: MessageContext,
742 message: SystemMessage,
743 txn_tracker: &mut TransactionTracker,
744 ) -> Result<RawExecutionOutcome<SystemMessage, Amount>, SystemExecutionError> {
745 let mut outcome = RawExecutionOutcome::default();
746 use SystemMessage::*;
747 match message {
748 Credit {
749 amount,
750 source,
751 target,
752 } => {
753 let receiver = if context.is_bouncing { source } else { target };
754 match receiver {
755 None => {
756 let new_balance = self.balance.get().saturating_add(amount);
757 self.balance.set(new_balance);
758 }
759 Some(owner) => {
760 let balance = self.balances.get_mut_or_default(&owner).await?;
761 *balance = balance.saturating_add(amount);
762 }
763 }
764 }
765 Withdraw {
766 amount,
767 owner,
768 recipient,
769 } => {
770 ensure!(
771 context.authenticated_signer == Some(owner),
772 SystemExecutionError::UnauthenticatedClaimOwner
773 );
774
775 let balance = self.balances.get_mut_or_default(&owner).await?;
776 balance
777 .try_sub_assign(amount)
778 .map_err(|_| SystemExecutionError::InsufficientFunding { balance: *balance })?;
779 match recipient {
780 Recipient::Account(account) => {
781 let message = RawOutgoingMessage {
782 destination: Destination::Recipient(account.chain_id),
783 authenticated: false,
784 grant: Amount::ZERO,
785 kind: MessageKind::Tracked,
786 message: SystemMessage::Credit {
787 amount,
788 source: Some(owner),
789 target: account.owner,
790 },
791 };
792 outcome.messages.push(message);
793 }
794 Recipient::Burn => (),
795 }
796 }
797 CreateCommittee { epoch, committee } => {
798 let chain_next_epoch = self.epoch.get().expect("chain is active").try_add_one()?;
799 ensure!(
800 epoch <= chain_next_epoch,
801 SystemExecutionError::InvalidCommitteeCreation
802 );
803 if epoch == chain_next_epoch {
804 self.committees.get_mut().insert(epoch, committee.clone());
805 self.epoch.set(Some(epoch));
806 }
807 }
808 RemoveCommittee { epoch } => {
809 self.committees.get_mut().remove(&epoch);
810 }
811 RegisterApplications { applications } => {
812 for application in applications {
813 self.check_and_record_bytecode_blobs(&application.bytecode_id, txn_tracker)
814 .await?;
815 self.registry
816 .register_application(application.clone())
817 .await?;
818 }
819 }
820 RequestApplication(application_id) => {
821 let applications = self
822 .registry
823 .describe_applications_with_dependencies(
824 vec![application_id],
825 &Default::default(),
826 )
827 .await?;
828 let message = RawOutgoingMessage {
829 destination: Destination::Recipient(context.message_id.chain_id),
830 authenticated: false,
831 grant: Amount::ZERO,
832 kind: MessageKind::Simple,
833 message: SystemMessage::RegisterApplications { applications },
834 };
835 outcome.messages.push(message);
836 }
837 Subscribe { .. } | Unsubscribe { .. } | OpenChain(_) => {}
839 ApplicationCreated => {}
841 }
842 Ok(outcome)
843 }
844
845 pub fn initialize_chain(
847 &mut self,
848 message_id: MessageId,
849 timestamp: Timestamp,
850 config: OpenChainConfig,
851 ) {
852 assert!(self.description.get().is_none());
854 assert!(!self.ownership.get().is_active());
855 assert!(self.committees.get().is_empty());
856 let OpenChainConfig {
857 ownership,
858 admin_id,
859 epoch,
860 committees,
861 balance,
862 application_permissions,
863 } = config;
864 let description = ChainDescription::Child(message_id);
865 self.description.set(Some(description));
866 self.epoch.set(Some(epoch));
867 self.committees.set(committees);
868 self.admin_id.set(Some(admin_id));
869 self.subscriptions
870 .insert(&ChannelSubscription {
871 chain_id: admin_id,
872 name: SystemChannel::Admin.name(),
873 })
874 .expect("serialization failed");
875 self.ownership.set(ownership);
876 self.timestamp.set(timestamp);
877 self.balance.set(balance);
878 self.application_permissions.set(application_permissions);
879 }
880
881 pub async fn handle_query(
882 &mut self,
883 context: QueryContext,
884 _query: SystemQuery,
885 ) -> Result<SystemResponse, SystemExecutionError> {
886 let response = SystemResponse {
887 chain_id: context.chain_id,
888 balance: *self.balance.get(),
889 };
890 Ok(response)
891 }
892
893 pub fn open_chain(
896 &mut self,
897 config: OpenChainConfig,
898 next_message_id: MessageId,
899 ) -> Result<[RawOutgoingMessage<SystemMessage, Amount>; 2], SystemExecutionError> {
900 let child_id = ChainId::child(next_message_id);
901 ensure!(
902 self.admin_id.get().as_ref() == Some(&config.admin_id),
903 SystemExecutionError::InvalidNewChainAdminId(child_id)
904 );
905 let admin_id = config.admin_id;
906 ensure!(
907 self.committees.get() == &config.committees,
908 SystemExecutionError::InvalidCommittees
909 );
910 ensure!(
911 self.epoch.get().as_ref() == Some(&config.epoch),
912 SystemExecutionError::InvalidEpoch {
913 chain_id: child_id,
914 epoch: config.epoch,
915 }
916 );
917 let balance = self.balance.get_mut();
918 balance
919 .try_sub_assign(config.balance)
920 .map_err(|_| SystemExecutionError::InsufficientFunding { balance: *balance })?;
921 let open_chain_message = RawOutgoingMessage {
922 destination: Destination::Recipient(child_id),
923 authenticated: false,
924 grant: Amount::ZERO,
925 kind: MessageKind::Protected,
926 message: SystemMessage::OpenChain(config),
927 };
928 let subscription = ChannelSubscription {
929 chain_id: admin_id,
930 name: SystemChannel::Admin.name(),
931 };
932 let subscribe_message = RawOutgoingMessage {
933 destination: Destination::Recipient(admin_id),
934 authenticated: false,
935 grant: Amount::ZERO,
936 kind: MessageKind::Protected,
937 message: SystemMessage::Subscribe {
938 id: child_id,
939 subscription,
940 },
941 };
942 Ok([open_chain_message, subscribe_message])
943 }
944
945 pub async fn close_chain(
946 &mut self,
947 id: ChainId,
948 ) -> Result<Vec<RawOutgoingMessage<SystemMessage, Amount>>, SystemExecutionError> {
949 let mut messages = Vec::new();
950 self.subscriptions
952 .for_each_index(|subscription| {
953 let message = RawOutgoingMessage {
954 destination: Destination::Recipient(subscription.chain_id),
955 authenticated: false,
956 grant: Amount::ZERO,
957 kind: MessageKind::Protected,
958 message: SystemMessage::Unsubscribe { id, subscription },
959 };
960 messages.push(message);
961 Ok(())
962 })
963 .await?;
964 self.subscriptions.clear();
965 self.closed.set(true);
966 Ok(messages)
967 }
968
969 pub async fn read_blob_content(
970 &mut self,
971 blob_id: BlobId,
972 ) -> Result<BlobContent, SystemExecutionError> {
973 self.context()
974 .extra()
975 .get_blob(blob_id)
976 .await
977 .map_err(|_| SystemExecutionError::BlobNotFoundOnRead(blob_id))
978 .map(Into::into)
979 }
980
981 pub async fn assert_blob_exists(
982 &mut self,
983 blob_id: BlobId,
984 ) -> Result<(), SystemExecutionError> {
985 if self.context().extra().contains_blob(blob_id).await? {
986 Ok(())
987 } else {
988 Err(SystemExecutionError::BlobNotFoundOnRead(blob_id))
989 }
990 }
991
992 async fn check_and_record_bytecode_blobs(
993 &mut self,
994 bytecode_id: &BytecodeId,
995 txn_tracker: &mut TransactionTracker,
996 ) -> Result<(), SystemExecutionError> {
997 let contract_bytecode_blob_id =
998 BlobId::new(bytecode_id.contract_blob_hash, BlobType::ContractBytecode);
999 ensure!(
1000 self.context()
1001 .extra()
1002 .contains_blob(contract_bytecode_blob_id)
1003 .await?,
1004 SystemExecutionError::BlobNotFoundOnRead(contract_bytecode_blob_id)
1005 );
1006 txn_tracker.replay_oracle_response(OracleResponse::Blob(contract_bytecode_blob_id))?;
1007 let service_bytecode_blob_id =
1008 BlobId::new(bytecode_id.service_blob_hash, BlobType::ServiceBytecode);
1009 ensure!(
1010 self.context()
1011 .extra()
1012 .contains_blob(service_bytecode_blob_id)
1013 .await?,
1014 SystemExecutionError::BlobNotFoundOnRead(service_bytecode_blob_id)
1015 );
1016 txn_tracker.replay_oracle_response(OracleResponse::Blob(service_bytecode_blob_id))?;
1017 Ok(())
1018 }
1019}
1020
1021#[cfg(test)]
1022mod tests {
1023 use linera_base::{
1024 data_types::{Blob, BlockHeight, Bytecode},
1025 identifiers::ApplicationId,
1026 };
1027 use linera_views::context::MemoryContext;
1028
1029 use super::*;
1030 use crate::{ExecutionOutcome, ExecutionStateView, TestExecutionRuntimeContext};
1031
1032 async fn new_view_and_context() -> (
1035 ExecutionStateView<MemoryContext<TestExecutionRuntimeContext>>,
1036 OperationContext,
1037 ) {
1038 let description = ChainDescription::Root(5);
1039 let context = OperationContext {
1040 chain_id: ChainId::from(description),
1041 authenticated_signer: None,
1042 authenticated_caller_id: None,
1043 height: BlockHeight::from(7),
1044 index: Some(2),
1045 };
1046 let state = SystemExecutionState {
1047 description: Some(description),
1048 epoch: Some(Epoch(1)),
1049 admin_id: Some(ChainId::root(0)),
1050 committees: BTreeMap::new(),
1051 ..SystemExecutionState::default()
1052 };
1053 let view = state.into_view().await;
1054 (view, context)
1055 }
1056
1057 #[tokio::test]
1058 async fn application_message_index() {
1059 let (mut view, context) = new_view_and_context().await;
1060 let contract = Bytecode::new(b"contract".into());
1061 let service = Bytecode::new(b"service".into());
1062 let contract_blob = Blob::new_contract_bytecode(contract.compress());
1063 let service_blob = Blob::new_service_bytecode(service.compress());
1064 let bytecode_id = BytecodeId::new(contract_blob.id().hash, service_blob.id().hash);
1065
1066 let operation = SystemOperation::CreateApplication {
1067 bytecode_id,
1068 parameters: vec![],
1069 instantiation_argument: vec![],
1070 required_application_ids: vec![],
1071 };
1072 let mut txn_tracker = TransactionTracker::default();
1073 view.context()
1074 .extra()
1075 .add_blobs(vec![contract_blob, service_blob]);
1076 let new_application = view
1077 .system
1078 .execute_operation(context, operation, &mut txn_tracker)
1079 .await
1080 .unwrap();
1081 let [ExecutionOutcome::System(result)] = &txn_tracker.destructure().unwrap().0[..] else {
1082 panic!("Unexpected outcome");
1083 };
1084 assert_eq!(
1085 result.messages[CREATE_APPLICATION_MESSAGE_INDEX as usize].message,
1086 SystemMessage::ApplicationCreated
1087 );
1088 let creation = MessageId {
1089 chain_id: context.chain_id,
1090 height: context.height,
1091 index: CREATE_APPLICATION_MESSAGE_INDEX,
1092 };
1093 let id = ApplicationId {
1094 bytecode_id,
1095 creation,
1096 };
1097 assert_eq!(new_application, Some((id, vec![])));
1098 }
1099
1100 #[tokio::test]
1101 async fn open_chain_message_index() {
1102 let (mut view, context) = new_view_and_context().await;
1103 let epoch = view.system.epoch.get().unwrap();
1104 let admin_id = view.system.admin_id.get().unwrap();
1105 let committees = view.system.committees.get().clone();
1106 let ownership = ChainOwnership::single(PublicKey::test_key(0));
1107 let config = OpenChainConfig {
1108 ownership,
1109 committees,
1110 epoch,
1111 admin_id,
1112 balance: Amount::ZERO,
1113 application_permissions: Default::default(),
1114 };
1115 let mut txn_tracker = TransactionTracker::default();
1116 let operation = SystemOperation::OpenChain(config.clone());
1117 let new_application = view
1118 .system
1119 .execute_operation(context, operation, &mut txn_tracker)
1120 .await
1121 .unwrap();
1122 assert_eq!(new_application, None);
1123 let [ExecutionOutcome::System(result)] = &txn_tracker.destructure().unwrap().0[..] else {
1124 panic!("Unexpected outcome");
1125 };
1126 assert_eq!(
1127 result.messages[OPEN_CHAIN_MESSAGE_INDEX as usize].message,
1128 SystemMessage::OpenChain(config)
1129 );
1130 }
1131}