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_owner: 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        first_leader: Option<AccountOwner>,
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            first_leader,
119            multi_leader_rounds,
120            open_multi_leader_rounds,
121            timeout_config,
122        })
123    }
124
125    /// Adds an application permissions change to this block.
126    pub fn with_change_application_permissions(
127        &mut self,
128        permissions: ApplicationPermissions,
129    ) -> &mut Self {
130        self.with_system_operation(SystemOperation::ChangeApplicationPermissions(permissions))
131    }
132
133    /// Adds a user `operation` to this block.
134    ///
135    /// The operation is serialized using [`bcs`] and added to the block, marked to be executed by
136    /// `application`.
137    pub fn with_operation<Abi>(
138        &mut self,
139        application_id: ApplicationId<Abi>,
140        operation: Abi::Operation,
141    ) -> &mut Self
142    where
143        Abi: ContractAbi,
144    {
145        let operation = Abi::serialize_operation(&operation)
146            .expect("Failed to serialize `Operation` in BlockBuilder");
147        self.with_raw_operation(application_id.forget_abi(), operation)
148    }
149
150    /// Adds an already serialized user `operation` to this block.
151    pub fn with_raw_operation(
152        &mut self,
153        application_id: ApplicationId,
154        operation: impl Into<Vec<u8>>,
155    ) -> &mut Self {
156        self.block
157            .transactions
158            .push(Transaction::ExecuteOperation(Operation::User {
159                application_id,
160                bytes: operation.into(),
161            }));
162        self
163    }
164
165    /// Receives incoming message bundles by specifying them directly.
166    ///
167    /// This is an internal method that bypasses the check to see if the messages are already
168    /// present in the inboxes of the microchain that owns this block.
169    pub(crate) fn with_incoming_bundles(
170        &mut self,
171        bundles: impl IntoIterator<Item = IncomingBundle>,
172    ) -> &mut Self {
173        self.block
174            .transactions
175            .extend(bundles.into_iter().map(Transaction::ReceiveMessages));
176        self
177    }
178
179    /// Receives all direct messages  that were sent to this chain by the given certificate.
180    pub fn with_messages_from(&mut self, certificate: &ConfirmedBlockCertificate) -> &mut Self {
181        self.with_messages_from_by_action(certificate, MessageAction::Accept)
182    }
183
184    /// Receives all messages that were sent to this chain by the given certificate.
185    pub fn with_messages_from_by_action(
186        &mut self,
187        certificate: &ConfirmedBlockCertificate,
188        action: MessageAction,
189    ) -> &mut Self {
190        let origin = certificate.inner().chain_id();
191        let bundles =
192            certificate
193                .message_bundles_for(self.block.chain_id)
194                .map(|(_epoch, bundle)| IncomingBundle {
195                    origin,
196                    bundle,
197                    action,
198                });
199        self.with_incoming_bundles(bundles)
200    }
201
202    /// Tries to sign the prepared block with the [`TestValidator`]'s keys and return the
203    /// resulting [`Certificate`]. Returns an error if block execution fails.
204    pub(crate) async fn try_sign(
205        self,
206        blobs: &[Blob],
207    ) -> Result<ConfirmedBlockCertificate, WorkerError> {
208        let published_blobs = self
209            .block
210            .published_blob_ids()
211            .into_iter()
212            .map(|blob_id| {
213                blobs
214                    .iter()
215                    .find(|blob| blob.id() == blob_id)
216                    .expect("missing published blob")
217                    .clone()
218            })
219            .collect();
220        let (block, _) = self
221            .validator
222            .worker()
223            .stage_block_execution(self.block, None, published_blobs)
224            .await?;
225
226        let value = ConfirmedBlock::new(block);
227        let vote = LiteVote::new(
228            LiteValue::new(&value),
229            Round::Fast,
230            self.validator.key_pair(),
231        );
232        let committee = self.validator.committee().await;
233        let public_key = self.validator.key_pair().public();
234        let mut builder = SignatureAggregator::new(value, Round::Fast, &committee);
235        let certificate = builder
236            .append(public_key, vote.signature)
237            .expect("Failed to sign block")
238            .expect("Committee has more than one test validator");
239
240        Ok(certificate)
241    }
242}