1use 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 BundleExecutionPolicy, IncomingBundle, LiteValue, LiteVote, MessageAction, ProposedBlock,
17 SignatureAggregator, Transaction,
18 },
19 types::{ConfirmedBlock, ConfirmedBlockCertificate},
20};
21use linera_core::worker::WorkerError;
22use linera_execution::{system::SystemOperation, Operation, ResourceTracker};
23
24use super::TestValidator;
25
26pub struct BlockBuilder {
29 block: ProposedBlock,
30 validator: TestValidator,
31}
32
33impl BlockBuilder {
34 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 pub fn with_timestamp(&mut self, timestamp: Timestamp) -> &mut Self {
93 self.block.timestamp = timestamp;
94 self
95 }
96
97 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 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 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 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 #[expect(clippy::needless_pass_by_value)]
152 pub fn with_operation<Abi>(
153 &mut self,
154 application_id: ApplicationId<Abi>,
155 operation: Abi::Operation,
156 ) -> &mut Self
157 where
158 Abi: ContractAbi,
159 {
160 let operation = <Abi as ContractAbi>::serialize_operation(&operation)
161 .expect("Failed to serialize `Operation` in BlockBuilder");
162 self.with_raw_operation(application_id.forget_abi(), operation)
163 }
164
165 pub fn with_raw_operation(
167 &mut self,
168 application_id: ApplicationId,
169 operation: impl Into<Vec<u8>>,
170 ) -> &mut Self {
171 self.block
172 .transactions
173 .push(Transaction::ExecuteOperation(Operation::User {
174 application_id,
175 bytes: operation.into(),
176 }));
177 self
178 }
179
180 pub(crate) fn with_incoming_bundles(
190 &mut self,
191 bundles: impl IntoIterator<Item = IncomingBundle>,
192 ) -> &mut Self {
193 for bundle in bundles {
194 self.block.timestamp = self.block.timestamp.max(bundle.bundle.timestamp);
195 self.block
196 .transactions
197 .push(Transaction::ReceiveMessages(bundle));
198 }
199 self
200 }
201
202 pub fn with_messages_from(&mut self, certificate: &ConfirmedBlockCertificate) -> &mut Self {
207 self.with_messages_from_by_action(certificate, MessageAction::Accept)
208 }
209
210 pub fn with_messages_from_by_action(
215 &mut self,
216 certificate: &ConfirmedBlockCertificate,
217 action: MessageAction,
218 ) -> &mut Self {
219 let origin = certificate.inner().chain_id();
220 let bundles =
221 certificate
222 .message_bundles_for(self.block.chain_id)
223 .map(|(_epoch, bundle)| IncomingBundle {
224 origin,
225 bundle,
226 action,
227 });
228 self.with_incoming_bundles(bundles)
229 }
230
231 pub(crate) async fn try_sign(
235 self,
236 blobs: &[Blob],
237 ) -> Result<(ConfirmedBlockCertificate, ResourceTracker), WorkerError> {
238 let published_blobs = self
239 .block
240 .published_blob_ids()
241 .into_iter()
242 .map(|blob_id| {
243 blobs
244 .iter()
245 .find(|blob| blob.id() == blob_id)
246 .expect("missing published blob")
247 .clone()
248 })
249 .collect();
250 let (_, block, _, resource_tracker, _) = self
251 .validator
252 .worker()
253 .stage_block_execution(
254 self.block,
255 None,
256 published_blobs,
257 BundleExecutionPolicy::committed(),
258 )
259 .await?;
260
261 let value = ConfirmedBlock::new(block);
262 let vote = LiteVote::new(
263 LiteValue::new(&value),
264 Round::Fast,
265 self.validator.key_pair(),
266 );
267 let committee = self.validator.committee().await;
268 let public_key = self.validator.key_pair().public();
269 let mut builder = SignatureAggregator::new(value, Round::Fast, &committee);
270 let certificate = builder
271 .append(public_key, vote.signature)
272 .expect("Failed to sign block")
273 .expect("Committee has more than one test validator");
274
275 Ok((certificate, resource_tracker))
276 }
277}