1use linera_base::{
9 abi::ContractAbi,
10 data_types::{Amount, ApplicationPermissions, Blob, Epoch, Timestamp},
11 identifiers::{Account, AccountOwner, ApplicationId, ChainId},
12 ownership::TimeoutConfig,
13};
14use linera_chain::{
15 data_types::{
16 BundleExecutionPolicy, IncomingBundle, MessageAction, ProposedBlock, Transaction, Vote,
17 },
18 justification::JustificationChain,
19 test::VoteTestExt,
20 types::{ConfirmedBlock, ConfirmedBlockCertificate},
21};
22use linera_core::{data_types::ChainInfoQuery, worker::WorkerError};
23use linera_execution::{system::SystemOperation, Operation, ResourceTracker};
24
25use super::TestValidator;
26
27pub struct BlockBuilder {
30 block: ProposedBlock,
31 validator: TestValidator,
32}
33
34impl BlockBuilder {
35 pub(crate) fn new(
52 chain_id: ChainId,
53 owner: AccountOwner,
54 epoch: Epoch,
55 previous_block: Option<&ConfirmedBlockCertificate>,
56 validator: TestValidator,
57 ) -> Self {
58 let previous_block_hash = previous_block.map(|certificate| certificate.hash());
59 let height = previous_block
60 .map(|certificate| {
61 certificate
62 .inner()
63 .height()
64 .try_add_one()
65 .expect("Block height limit reached")
66 })
67 .unwrap_or_default();
68 let parent_timestamp = previous_block
69 .map(|certificate| certificate.inner().timestamp())
70 .unwrap_or_default();
71 let timestamp = parent_timestamp.max(validator.clock().current_time());
72
73 BlockBuilder {
74 block: ProposedBlock {
75 epoch,
76 chain_id,
77 transactions: vec![],
78 previous_block_hash,
79 height,
80 authenticated_owner: Some(owner),
81 timestamp,
82 },
83 validator,
84 }
85 }
86
87 pub fn with_timestamp(&mut self, timestamp: Timestamp) -> &mut Self {
94 self.block.timestamp = timestamp;
95 self
96 }
97
98 pub fn with_native_token_transfer(
100 &mut self,
101 sender: AccountOwner,
102 recipient: Account,
103 amount: Amount,
104 ) -> &mut Self {
105 self.with_system_operation(SystemOperation::Transfer {
106 owner: sender,
107 recipient,
108 amount,
109 })
110 }
111
112 pub(crate) fn with_system_operation(&mut self, operation: SystemOperation) -> &mut Self {
114 self.block
115 .transactions
116 .push(Transaction::ExecuteOperation(operation.into()));
117 self
118 }
119
120 pub fn with_owner_change(
122 &mut self,
123 super_owners: Vec<AccountOwner>,
124 owners: Vec<(AccountOwner, u64)>,
125 first_leader: Option<AccountOwner>,
126 multi_leader_rounds: u32,
127 open_multi_leader_rounds: bool,
128 timeout_config: TimeoutConfig,
129 ) -> &mut Self {
130 self.with_system_operation(SystemOperation::ChangeOwnership {
131 super_owners,
132 owners,
133 first_leader,
134 multi_leader_rounds,
135 open_multi_leader_rounds,
136 timeout_config,
137 })
138 }
139
140 pub fn with_change_application_permissions(
142 &mut self,
143 permissions: ApplicationPermissions,
144 ) -> &mut Self {
145 self.with_system_operation(SystemOperation::ChangeApplicationPermissions(permissions))
146 }
147
148 #[expect(clippy::needless_pass_by_value)]
153 pub fn with_operation<Abi>(
154 &mut self,
155 application_id: ApplicationId<Abi>,
156 operation: Abi::Operation,
157 ) -> &mut Self
158 where
159 Abi: ContractAbi,
160 {
161 let operation = <Abi as ContractAbi>::serialize_operation(&operation)
162 .expect("Failed to serialize `Operation` in BlockBuilder");
163 self.with_raw_operation(application_id.forget_abi(), operation)
164 }
165
166 pub fn with_raw_operation(
168 &mut self,
169 application_id: ApplicationId,
170 operation: impl Into<Vec<u8>>,
171 ) -> &mut Self {
172 self.block
173 .transactions
174 .push(Transaction::ExecuteOperation(Operation::User {
175 application_id,
176 bytes: operation.into(),
177 }));
178 self
179 }
180
181 pub(crate) fn with_incoming_bundles(
191 &mut self,
192 bundles: impl IntoIterator<Item = IncomingBundle>,
193 ) -> &mut Self {
194 for bundle in bundles {
195 self.block.timestamp = self.block.timestamp.max(bundle.bundle.timestamp);
196 self.block
197 .transactions
198 .push(Transaction::ReceiveMessages(bundle));
199 }
200 self
201 }
202
203 pub fn with_messages_from(&mut self, certificate: &ConfirmedBlockCertificate) -> &mut Self {
208 self.with_messages_from_by_action(certificate, MessageAction::Accept)
209 }
210
211 pub fn with_messages_from_by_action(
216 &mut self,
217 certificate: &ConfirmedBlockCertificate,
218 action: MessageAction,
219 ) -> &mut Self {
220 let origin = certificate.inner().chain_id();
221 let bundles =
222 certificate
223 .message_bundles_for(self.block.chain_id)
224 .map(|(_epoch, bundle)| IncomingBundle {
225 origin,
226 bundle,
227 action,
228 });
229 self.with_incoming_bundles(bundles)
230 }
231
232 pub(crate) async fn try_sign(
236 self,
237 blobs: &[Blob],
238 ) -> Result<(ConfirmedBlockCertificate, ResourceTracker), WorkerError> {
239 let published_blobs = self
240 .block
241 .published_blob_ids()
242 .into_iter()
243 .map(|blob_id| {
244 blobs
245 .iter()
246 .find(|blob| blob.id() == blob_id)
247 .expect("missing published blob")
248 .clone()
249 })
250 .collect();
251 let (_, block, _, resource_tracker, _) = self
252 .validator
253 .worker()
254 .stage_block_execution(
255 self.block,
256 None,
257 published_blobs,
258 BundleExecutionPolicy::committed(),
259 )
260 .await?;
261
262 let value = ConfirmedBlock::new(block);
263 let info = self
268 .validator
269 .worker()
270 .handle_chain_info_query(ChainInfoQuery::new(value.chain_id()))
271 .await
272 .expect("Failed to query chain ownership")
273 .info;
274 let round = info.manager.ownership.first_round();
275 let public_key = self.validator.key_pair().public();
276 let quorum =
279 Vote::new_with_first_round(value, round, true, None, self.validator.key_pair())
280 .into_certificate(public_key);
281 let certificate =
282 ConfirmedBlockCertificate::from_parts(quorum, JustificationChain::default());
283
284 Ok((certificate, resource_tracker))
285 }
286}