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, ResourceTracker};
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    /// The block's timestamp defaults to the maximum of the parent block's timestamp and the
41    /// validator's current clock time, ensuring it satisfies the validity rule that a block's
42    /// timestamp must not be earlier than its parent's. Use [`with_timestamp`](Self::with_timestamp)
43    /// to override.
44    ///
45    /// # Notes
46    ///
47    /// This is an internal method, because the [`BlockBuilder`] instance should be built by an
48    /// [`ActiveChain`]. External users should only be able to add operations and messages to the
49    /// block.
50    pub(crate) fn new(
51        chain_id: ChainId,
52        owner: AccountOwner,
53        epoch: Epoch,
54        previous_block: Option<&ConfirmedBlockCertificate>,
55        validator: TestValidator,
56    ) -> Self {
57        let previous_block_hash = previous_block.map(|certificate| certificate.hash());
58        let height = previous_block
59            .map(|certificate| {
60                certificate
61                    .inner()
62                    .height()
63                    .try_add_one()
64                    .expect("Block height limit reached")
65            })
66            .unwrap_or_default();
67        let parent_timestamp = previous_block
68            .map(|certificate| certificate.inner().timestamp())
69            .unwrap_or_default();
70        let timestamp = parent_timestamp.max(validator.clock().current_time());
71
72        BlockBuilder {
73            block: ProposedBlock {
74                epoch,
75                chain_id,
76                transactions: vec![],
77                previous_block_hash,
78                height,
79                authenticated_owner: Some(owner),
80                timestamp,
81            },
82            validator,
83        }
84    }
85
86    /// Configures the timestamp of this block.
87    ///
88    /// The timestamp must be at least as large as the parent block's timestamp (which is used as
89    /// the default). It must also be at least as large as the timestamp of any incoming message
90    /// bundle added via [`with_messages_from`](Self::with_messages_from) or
91    /// [`with_messages_from_by_action`](Self::with_messages_from_by_action).
92    pub fn with_timestamp(&mut self, timestamp: Timestamp) -> &mut Self {
93        self.block.timestamp = timestamp;
94        self
95    }
96
97    /// Adds a native token transfer to this block.
98    pub fn with_native_token_transfer(
99        &mut self,
100        sender: AccountOwner,
101        recipient: Account,
102        amount: Amount,
103    ) -> &mut Self {
104        self.with_system_operation(SystemOperation::Transfer {
105            owner: sender,
106            recipient,
107            amount,
108        })
109    }
110
111    /// Adds a [`SystemOperation`] to this block.
112    pub(crate) fn with_system_operation(&mut self, operation: SystemOperation) -> &mut Self {
113        self.block
114            .transactions
115            .push(Transaction::ExecuteOperation(operation.into()));
116        self
117    }
118
119    /// Adds an operation to change this chain's ownership.
120    pub fn with_owner_change(
121        &mut self,
122        super_owners: Vec<AccountOwner>,
123        owners: Vec<(AccountOwner, u64)>,
124        first_leader: Option<AccountOwner>,
125        multi_leader_rounds: u32,
126        open_multi_leader_rounds: bool,
127        timeout_config: TimeoutConfig,
128    ) -> &mut Self {
129        self.with_system_operation(SystemOperation::ChangeOwnership {
130            super_owners,
131            owners,
132            first_leader,
133            multi_leader_rounds,
134            open_multi_leader_rounds,
135            timeout_config,
136        })
137    }
138
139    /// Adds an application permissions change to this block.
140    pub fn with_change_application_permissions(
141        &mut self,
142        permissions: ApplicationPermissions,
143    ) -> &mut Self {
144        self.with_system_operation(SystemOperation::ChangeApplicationPermissions(permissions))
145    }
146
147    /// Adds a user `operation` to this block.
148    ///
149    /// The operation is serialized using the application ABI and added to the block, marked to be
150    /// executed by `application`.
151    pub fn with_operation<Abi>(
152        &mut self,
153        application_id: ApplicationId<Abi>,
154        operation: &Abi::Operation,
155    ) -> &mut Self
156    where
157        Abi: ContractAbi,
158    {
159        let operation = <Abi as ContractAbi>::serialize_operation(operation)
160            .expect("Failed to serialize `Operation` in BlockBuilder");
161        self.with_raw_operation(application_id.forget_abi(), operation)
162    }
163
164    /// Adds an already serialized user `operation` to this block.
165    pub fn with_raw_operation(
166        &mut self,
167        application_id: ApplicationId,
168        operation: impl Into<Vec<u8>>,
169    ) -> &mut Self {
170        self.block
171            .transactions
172            .push(Transaction::ExecuteOperation(Operation::User {
173                application_id,
174                bytes: operation.into(),
175            }));
176        self
177    }
178
179    /// Receives incoming message bundles by specifying them directly.
180    ///
181    /// Automatically advances the block's timestamp to be at least as large as the latest
182    /// bundle's timestamp, since blocks are not allowed to have a timestamp older than any of
183    /// their incoming bundles. Use [`with_timestamp`](Self::with_timestamp) afterwards to set a
184    /// later timestamp if needed.
185    ///
186    /// This is an internal method that bypasses the check to see if the messages are already
187    /// present in the inboxes of the microchain that owns this block.
188    pub(crate) fn with_incoming_bundles(
189        &mut self,
190        bundles: impl IntoIterator<Item = IncomingBundle>,
191    ) -> &mut Self {
192        for bundle in bundles {
193            self.block.timestamp = self.block.timestamp.max(bundle.bundle.timestamp);
194            self.block
195                .transactions
196                .push(Transaction::ReceiveMessages(bundle));
197        }
198        self
199    }
200
201    /// Receives all direct messages that were sent to this chain by the given certificate.
202    ///
203    /// The block's timestamp is automatically advanced to be at least as large as the
204    /// certificate's block timestamp.
205    pub fn with_messages_from(&mut self, certificate: &ConfirmedBlockCertificate) -> &mut Self {
206        self.with_messages_from_by_action(certificate, MessageAction::Accept)
207    }
208
209    /// Receives all messages that were sent to this chain by the given certificate.
210    ///
211    /// The block's timestamp is automatically advanced to be at least as large as the
212    /// certificate's block timestamp.
213    pub fn with_messages_from_by_action(
214        &mut self,
215        certificate: &ConfirmedBlockCertificate,
216        action: MessageAction,
217    ) -> &mut Self {
218        let origin = certificate.inner().chain_id();
219        let bundles =
220            certificate
221                .message_bundles_for(self.block.chain_id)
222                .map(|(_epoch, bundle)| IncomingBundle {
223                    origin,
224                    bundle,
225                    action,
226                });
227        self.with_incoming_bundles(bundles)
228    }
229
230    /// Tries to sign the prepared block with the [`TestValidator`]'s keys and return the
231    /// resulting [`Certificate`] and the [`ResourceTracker`] with execution costs.
232    /// Returns an error if block execution fails.
233    pub(crate) async fn try_sign(
234        self,
235        blobs: &[Blob],
236    ) -> Result<(ConfirmedBlockCertificate, ResourceTracker), WorkerError> {
237        let published_blobs = self
238            .block
239            .published_blob_ids()
240            .into_iter()
241            .map(|blob_id| {
242                blobs
243                    .iter()
244                    .find(|blob| blob.id() == blob_id)
245                    .expect("missing published blob")
246                    .clone()
247            })
248            .collect();
249        let (block, _, resource_tracker) = self
250            .validator
251            .worker()
252            .stage_block_execution(self.block, None, published_blobs)
253            .await?;
254
255        let value = ConfirmedBlock::new(block);
256        let vote = LiteVote::new(
257            LiteValue::new(&value),
258            Round::Fast,
259            self.validator.key_pair(),
260        );
261        let committee = self.validator.committee().await;
262        let public_key = self.validator.key_pair().public();
263        let mut builder = SignatureAggregator::new(value, Round::Fast, &committee);
264        let certificate = builder
265            .append(public_key, vote.signature)
266            .expect("Failed to sign block")
267            .expect("Committee has more than one test validator");
268
269        Ok((certificate, resource_tracker))
270    }
271}