Skip to main content

linera_execution/test_utils/
system_execution_state.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    collections::{BTreeMap, BTreeSet},
6    ops::Not,
7};
8
9use custom_debug_derive::Debug;
10use linera_base::{
11    crypto::CryptoHash,
12    data_types::{Amount, ApplicationPermissions, Blob, ChainDescription, Epoch, Timestamp},
13    identifiers::{AccountOwner, ApplicationId, BlobId, ChainId},
14    ownership::ChainOwnership,
15};
16use linera_views::{context::MemoryContext, views::View};
17
18use super::{dummy_chain_description, dummy_committees, MockApplication, RegisterMockApplication};
19use crate::{
20    committee::Committee, ApplicationDescription, ChainProgress, ExecutionRuntimeConfig,
21    ExecutionRuntimeContext, ExecutionStateView, TestExecutionRuntimeContext,
22};
23
24/// A system execution state, not represented as a view but as a simple struct.
25#[derive(Default, Debug, PartialEq, Eq, Clone)]
26pub struct SystemExecutionState {
27    /// The description of the chain, if it has been created.
28    pub description: Option<ChainDescription>,
29    /// The current epoch the chain is operating in.
30    pub epoch: Epoch,
31    /// The ID of the admin chain, if known.
32    pub admin_chain_id: Option<ChainId>,
33    /// The committees of validators, indexed by the epoch in which they are active.
34    pub committees: BTreeMap<Epoch, Committee>,
35    /// The ownership configuration of the chain.
36    pub ownership: ChainOwnership,
37    /// The chain's main balance.
38    pub balance: Amount,
39    /// The per-owner balances held on the chain.
40    #[debug(skip_if = BTreeMap::is_empty)]
41    pub balances: BTreeMap<AccountOwner, Amount>,
42    /// The latest timestamp recorded for the chain.
43    pub timestamp: Timestamp,
44    /// The set of blobs that have been used by the chain.
45    pub used_blobs: BTreeSet<BlobId>,
46    /// Whether the chain has been closed.
47    #[debug(skip_if = Not::not)]
48    pub closed: bool,
49    /// The application permissions configured on the chain.
50    pub application_permissions: ApplicationPermissions,
51    /// Additional blobs to make available to the chain's execution context.
52    #[debug(skip_if = Vec::is_empty)]
53    pub extra_blobs: Vec<Blob>,
54    /// The mock applications registered on the chain, indexed by their application ID.
55    #[debug(skip_if = BTreeMap::is_empty)]
56    pub mock_applications: BTreeMap<ApplicationId, MockApplication>,
57    /// Number of incoming message bundles executed so far.
58    pub num_incoming_bundles: u32,
59    /// Number of operations executed so far.
60    pub num_operations: u32,
61    /// Number of outgoing messages sent so far.
62    pub num_outgoing_messages: u32,
63}
64
65impl SystemExecutionState {
66    /// Creates a system execution state from a chain description, with dummy committees.
67    pub fn new(description: ChainDescription) -> Self {
68        let ownership = description.config().ownership.clone();
69        let account = description.config().account;
70        let initial_balance = description.config().balance;
71        let epoch = description.config().epoch;
72        let admin_chain_id = Some(dummy_chain_description(0).id());
73        let (balance, balances) = if account.is_chain() {
74            (initial_balance, BTreeMap::new())
75        } else {
76            (
77                Amount::ZERO,
78                BTreeMap::from_iter([(account, initial_balance)]),
79            )
80        };
81        SystemExecutionState {
82            epoch,
83            description: Some(description),
84            admin_chain_id,
85            ownership,
86            balance,
87            balances,
88            committees: dummy_committees(),
89            ..SystemExecutionState::default()
90        }
91    }
92
93    /// Creates a dummy system execution state for the chain with the given index, returning it
94    /// together with its chain ID.
95    pub fn dummy_chain_state(index: u32) -> (Self, ChainId) {
96        let description = dummy_chain_description(index);
97        let chain_id = description.id();
98        (Self::new(description), chain_id)
99    }
100
101    /// Builds an execution state view from this state and returns its cryptographic hash.
102    pub async fn into_hash(self) -> CryptoHash {
103        let mut view = self.into_view().await;
104        view.crypto_hash_mut()
105            .await
106            .expect("hashing from memory should not fail")
107    }
108
109    /// Converts this state into an execution state view backed by an in-memory context.
110    pub async fn into_view(self) -> ExecutionStateView<MemoryContext<TestExecutionRuntimeContext>> {
111        let chain_id = self
112            .description
113            .as_ref()
114            .expect("Chain description should be set")
115            .into();
116        self.into_view_with(chain_id, ExecutionRuntimeConfig::default())
117            .await
118    }
119
120    /// Converts this state into an execution state view for the given chain ID and runtime
121    /// configuration.
122    pub async fn into_view_with(
123        self,
124        chain_id: ChainId,
125        execution_runtime_config: ExecutionRuntimeConfig,
126    ) -> ExecutionStateView<MemoryContext<TestExecutionRuntimeContext>> {
127        // Destructure, to make sure we don't miss any fields.
128        let SystemExecutionState {
129            description,
130            epoch,
131            admin_chain_id,
132            committees,
133            ownership,
134            balance,
135            balances,
136            timestamp,
137            used_blobs,
138            closed,
139            application_permissions,
140            extra_blobs,
141            mock_applications,
142            num_incoming_bundles,
143            num_operations,
144            num_outgoing_messages,
145        } = self;
146
147        let extra = TestExecutionRuntimeContext::new(chain_id, execution_runtime_config);
148        extra
149            .add_blobs(extra_blobs)
150            .await
151            .expect("Adding blobs to the `TestExecutionRuntimeContext` should not fail");
152        for (id, mock_application) in mock_applications {
153            extra
154                .user_contracts()
155                .pin()
156                .insert(id, mock_application.clone().into());
157            extra
158                .user_services()
159                .pin()
160                .insert(id, mock_application.into());
161        }
162
163        let mut committee_hashes = BTreeMap::new();
164        for (committee_epoch, committee) in committees {
165            let blob = Blob::new_committee(bcs::to_bytes(&committee).expect("BCS should succeed"));
166            let hash = blob.id().hash;
167            extra
168                .add_blobs([blob])
169                .await
170                .expect("Adding committee blobs should not fail");
171            committee_hashes.insert(committee_epoch, hash);
172        }
173
174        let context = MemoryContext::new_for_testing(extra);
175        let mut view = ExecutionStateView::load(context)
176            .await
177            .expect("Loading from memory should work");
178        view.system.description.set(description);
179        view.system.epoch.set(epoch);
180        view.system.admin_chain_id.set(admin_chain_id);
181        view.system
182            .committee_hash
183            .set(committee_hashes.get(&epoch).copied());
184        view.system.ownership.set(ownership);
185        view.system.balance.set(balance);
186        for (account_owner, balance) in balances {
187            view.system
188                .balances
189                .insert(&account_owner, balance)
190                .expect("insertion of balances should not fail");
191        }
192        for blob_id in used_blobs {
193            view.system
194                .used_blobs
195                .insert(&blob_id)
196                .expect("inserting blob IDs should not fail");
197        }
198        view.system.closed.set(closed);
199        view.system
200            .application_permissions
201            .set(application_permissions);
202        view.system.progress.set(ChainProgress {
203            timestamp,
204            num_incoming_bundles,
205            num_operations,
206            num_outgoing_messages,
207        });
208        view
209    }
210}
211
212impl RegisterMockApplication for SystemExecutionState {
213    async fn creator_chain_id(&self) -> ChainId {
214        self.description.as_ref().expect(
215            "Can't register applications on a system state with no associated `ChainDescription`",
216        ).into()
217    }
218
219    async fn register_mock_application_with(
220        &mut self,
221        description: ApplicationDescription,
222        contract: Blob,
223        service: Blob,
224    ) -> anyhow::Result<(ApplicationId, MockApplication)> {
225        let id = ApplicationId::from(&description);
226        let application = MockApplication::default();
227
228        self.extra_blobs.extend([
229            contract,
230            service,
231            Blob::new_application_description(&description),
232        ]);
233        self.mock_applications.insert(id, application.clone());
234
235        Ok((id, application))
236    }
237}