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