linera_sdk/test/
block.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! A builder of [`Block`]s which are then signed to become [`Certificate`]s.
5//!
6//! Helps with the construction of blocks, adding operations and
7
8use linera_base::{
9    abi::ContractAbi,
10    data_types::{Amount, ApplicationPermissions, Blob, Epoch, Round, Timestamp},
11    identifiers::{AccountOwner, ApplicationId, ChainId},
12    ownership::TimeoutConfig,
13};
14use linera_chain::{
15    data_types::{
16        IncomingBundle, LiteValue, LiteVote, MessageAction, ProposedBlock, SignatureAggregator,
17    },
18    types::{ConfirmedBlock, ConfirmedBlockCertificate},
19};
20use linera_core::worker::WorkerError;
21use linera_execution::{
22    system::{Recipient, SystemOperation},
23    Operation,
24};
25
26use super::TestValidator;
27
28/// A helper type to build a block proposal using the builder pattern, and then signing them into
29/// [`ConfirmedBlockCertificate`]s using a [`TestValidator`].
30pub struct BlockBuilder {
31    block: ProposedBlock,
32    validator: TestValidator,
33}
34
35impl BlockBuilder {
36    /// Creates a new [`BlockBuilder`], initializing the block so that it belongs to a microchain.
37    ///
38    /// Initializes the block so that it belongs to the microchain identified by `chain_id` and
39    /// owned by `owner`. It becomes the block after the specified `previous_block`, or the genesis
40    /// block if [`None`] is specified.
41    ///
42    /// # Notes
43    ///
44    /// This is an internal method, because the [`BlockBuilder`] instance should be built by an
45    /// [`ActiveChain`]. External users should only be able to add operations and messages to the
46    /// block.
47    pub(crate) fn new(
48        chain_id: ChainId,
49        owner: AccountOwner,
50        epoch: Epoch,
51        previous_block: Option<&ConfirmedBlockCertificate>,
52        validator: TestValidator,
53    ) -> Self {
54        let previous_block_hash = previous_block.map(|certificate| certificate.hash());
55        let height = previous_block
56            .map(|certificate| {
57                certificate
58                    .inner()
59                    .height()
60                    .try_add_one()
61                    .expect("Block height limit reached")
62            })
63            .unwrap_or_default();
64
65        BlockBuilder {
66            block: ProposedBlock {
67                epoch,
68                chain_id,
69                incoming_bundles: vec![],
70                operations: vec![],
71                previous_block_hash,
72                height,
73                authenticated_signer: Some(owner),
74                timestamp: Timestamp::from(0),
75            },
76            validator,
77        }
78    }
79
80    /// Configures the timestamp of this block.
81    pub fn with_timestamp(&mut self, timestamp: Timestamp) -> &mut Self {
82        self.block.timestamp = timestamp;
83        self
84    }
85
86    /// Adds a native token transfer to this block.
87    pub fn with_native_token_transfer(
88        &mut self,
89        sender: AccountOwner,
90        recipient: Recipient,
91        amount: Amount,
92    ) -> &mut Self {
93        self.with_system_operation(SystemOperation::Transfer {
94            owner: sender,
95            recipient,
96            amount,
97        })
98    }
99
100    /// Adds a [`SystemOperation`] to this block.
101    pub(crate) fn with_system_operation(&mut self, operation: SystemOperation) -> &mut Self {
102        self.block.operations.push(operation.into());
103        self
104    }
105
106    /// Adds an operation to change this chain's ownership.
107    pub fn with_owner_change(
108        &mut self,
109        super_owners: Vec<AccountOwner>,
110        owners: Vec<(AccountOwner, u64)>,
111        multi_leader_rounds: u32,
112        open_multi_leader_rounds: bool,
113        timeout_config: TimeoutConfig,
114    ) -> &mut Self {
115        self.with_system_operation(SystemOperation::ChangeOwnership {
116            super_owners,
117            owners,
118            multi_leader_rounds,
119            open_multi_leader_rounds,
120            timeout_config,
121        })
122    }
123
124    /// Adds an application permissions change to this block.
125    pub fn with_change_application_permissions(
126        &mut self,
127        permissions: ApplicationPermissions,
128    ) -> &mut Self {
129        self.with_system_operation(SystemOperation::ChangeApplicationPermissions(permissions))
130    }
131
132    /// Adds a user `operation` to this block.
133    ///
134    /// The operation is serialized using [`bcs`] and added to the block, marked to be executed by
135    /// `application`.
136    pub fn with_operation<Abi>(
137        &mut self,
138        application_id: ApplicationId<Abi>,
139        operation: Abi::Operation,
140    ) -> &mut Self
141    where
142        Abi: ContractAbi,
143    {
144        let operation = Abi::serialize_operation(&operation)
145            .expect("Failed to serialize `Operation` in BlockBuilder");
146        self.with_raw_operation(application_id.forget_abi(), operation)
147    }
148
149    /// Adds an already serialized user `operation` to this block.
150    pub fn with_raw_operation(
151        &mut self,
152        application_id: ApplicationId,
153        operation: impl Into<Vec<u8>>,
154    ) -> &mut Self {
155        self.block.operations.push(Operation::User {
156            application_id,
157            bytes: operation.into(),
158        });
159        self
160    }
161
162    /// Receives incoming message bundles by specifying them directly.
163    ///
164    /// This is an internal method that bypasses the check to see if the messages are already
165    /// present in the inboxes of the microchain that owns this block.
166    pub(crate) fn with_incoming_bundles(
167        &mut self,
168        bundles: impl IntoIterator<Item = IncomingBundle>,
169    ) -> &mut Self {
170        self.block.incoming_bundles.extend(bundles);
171        self
172    }
173
174    /// Receives all direct messages  that were sent to this chain by the given certificate.
175    pub fn with_messages_from(&mut self, certificate: &ConfirmedBlockCertificate) -> &mut Self {
176        self.with_messages_from_by_action(certificate, MessageAction::Accept)
177    }
178
179    /// Receives all messages that were sent to this chain by the given certificate.
180    pub fn with_messages_from_by_action(
181        &mut self,
182        certificate: &ConfirmedBlockCertificate,
183        action: MessageAction,
184    ) -> &mut Self {
185        let origin = certificate.inner().chain_id();
186        let bundles =
187            certificate
188                .message_bundles_for(self.block.chain_id)
189                .map(|(_epoch, bundle)| IncomingBundle {
190                    origin,
191                    bundle,
192                    action,
193                });
194        self.with_incoming_bundles(bundles)
195    }
196
197    /// Tries to sign the prepared block with the [`TestValidator`]'s keys and return the
198    /// resulting [`Certificate`]. Returns an error if block execution fails.
199    pub(crate) async fn try_sign(
200        self,
201        blobs: &[Blob],
202    ) -> Result<ConfirmedBlockCertificate, WorkerError> {
203        let published_blobs = self
204            .block
205            .published_blob_ids()
206            .into_iter()
207            .map(|blob_id| {
208                blobs
209                    .iter()
210                    .find(|blob| blob.id() == blob_id)
211                    .expect("missing published blob")
212                    .clone()
213            })
214            .collect();
215        let (block, _) = self
216            .validator
217            .worker()
218            .stage_block_execution(self.block, None, published_blobs)
219            .await?;
220
221        let value = ConfirmedBlock::new(block);
222        let vote = LiteVote::new(
223            LiteValue::new(&value),
224            Round::Fast,
225            self.validator.key_pair(),
226        );
227        let committee = self.validator.committee().await;
228        let mut builder = SignatureAggregator::new(value, Round::Fast, &committee);
229        let certificate = builder
230            .append(vote.public_key, vote.signature)
231            .expect("Failed to sign block")
232            .expect("Committee has more than one test validator");
233
234        Ok(certificate)
235    }
236}