1#![allow(clippy::cast_possible_truncation)]
5
6use std::{
7 collections::{BTreeMap, HashMap, HashSet},
8 sync::Arc,
9 time::Duration,
10 vec,
11};
12
13use async_trait::async_trait;
14use futures::{
15 future::Either,
16 lock::{Mutex, MutexGuard},
17 Future,
18};
19use linera_base::{
20 crypto::{
21 AccountPublicKey, CryptoHash, ValidatorKeypair, ValidatorPublicKey, ValidatorSecretKey,
22 },
23 data_types::*,
24 identifiers::{AccountOwner, BlobId, ChainId, EventId},
25 ownership::ChainOwnership,
26};
27use linera_chain::{
28 data_types::BlockProposal,
29 types::{
30 CertificateKind, Certified, ConfirmedBlock, ConfirmedBlockCertificate, GenericCertificate,
31 LiteCertificate, Timeout, ValidatedBlock, ValidatedBlockCertificate,
32 },
33};
34use linera_execution::{committee::Committee, ResourceControlPolicy, WasmRuntime};
35use linera_storage::{Arc as CacheArc, DbStorage, ResultReadCertificates, Storage, TestClock};
36#[cfg(all(not(target_arch = "wasm32"), feature = "storage-service"))]
37use linera_storage_service::client::StorageServiceDatabase;
38use linera_version::VersionInfo;
39#[cfg(feature = "scylladb")]
40use linera_views::scylla_db::ScyllaDbDatabase;
41use linera_views::{
42 memory::MemoryDatabase,
43 random::generate_test_namespace,
44 store::{KeyValueStore, TestKeyValueDatabase},
45};
46use tokio::sync::oneshot;
47use tokio_stream::wrappers::UnboundedReceiverStream;
48#[cfg(feature = "rocksdb")]
49use {
50 linera_views::rocks_db::RocksDbDatabase,
51 tokio::sync::{Semaphore, SemaphorePermit},
52};
53
54use crate::{
55 chain_worker::ChainWorkerConfig,
56 client::{chain_client, Client},
57 data_types::*,
58 environment::{TestSigner, TestWallet},
59 node::{
60 CrossChainMessageDelivery, NodeError, NotificationStream, ValidatorNode,
61 ValidatorNodeProvider,
62 },
63 notifier::ChannelNotifier,
64 worker::{
65 Notification, ProcessableCertificate, WorkerState, DEFAULT_BLOCK_CACHE_SIZE,
66 DEFAULT_EXECUTION_STATE_CACHE_SIZE,
67 },
68};
69
70#[derive(Debug, PartialEq, Clone, Copy)]
72#[allow(missing_docs)]
73pub enum FaultType {
74 Honest,
75 Offline,
76 OfflineWithInfo,
77 NoChains,
78 DontSendConfirmVote,
79 DontProcessValidated,
80 DontSendValidateVote,
81}
82
83struct LocalValidator<S>
90where
91 S: Storage,
92{
93 state: WorkerState<S>,
94 notifier: Arc<ChannelNotifier<Notification>>,
95}
96
97#[derive(Clone)]
99pub struct LocalValidatorClient<S>
100where
101 S: Storage,
102{
103 public_key: ValidatorPublicKey,
104 client: Arc<Mutex<LocalValidator<S>>>,
105 fault_type: FaultType,
106}
107
108impl<S> ValidatorNode for LocalValidatorClient<S>
109where
110 S: Storage + Clone + Send + Sync + 'static,
111{
112 type NotificationStream = NotificationStream;
113
114 fn address(&self) -> String {
115 format!("local:{}", self.public_key)
116 }
117
118 async fn handle_block_proposal(
119 &self,
120 proposal: BlockProposal,
121 ) -> Result<ChainInfoResponse, NodeError> {
122 self.spawn_and_receive(move |validator, sender| {
123 validator.do_handle_block_proposal(proposal, sender)
124 })
125 .await
126 }
127
128 async fn handle_lite_certificate(
129 &self,
130 certificate: LiteCertificate<'_>,
131 _delivery: CrossChainMessageDelivery,
132 ) -> Result<ChainInfoResponse, NodeError> {
133 let certificate = certificate.cloned();
134 self.spawn_and_receive(move |validator, sender| {
135 validator.do_handle_lite_certificate(certificate, sender)
136 })
137 .await
138 }
139
140 async fn handle_timeout_certificate(
141 &self,
142 certificate: GenericCertificate<Timeout>,
143 ) -> Result<ChainInfoResponse, NodeError> {
144 self.spawn_and_receive(move |validator, sender| {
145 validator.do_handle_certificate::<Timeout>(certificate, sender)
146 })
147 .await
148 }
149
150 async fn handle_validated_certificate(
151 &self,
152 certificate: ValidatedBlockCertificate,
153 ) -> Result<ChainInfoResponse, NodeError> {
154 self.spawn_and_receive(move |validator, sender| {
155 validator.do_handle_certificate::<ValidatedBlock>(certificate, sender)
156 })
157 .await
158 }
159
160 async fn handle_confirmed_certificate(
161 &self,
162 certificate: CacheArc<ConfirmedBlockCertificate>,
163 _delivery: CrossChainMessageDelivery,
164 ) -> Result<ChainInfoResponse, NodeError> {
165 self.spawn_and_receive(move |validator, sender| {
166 validator.do_handle_certificate::<ConfirmedBlock>(
167 CacheArc::unwrap_or_clone(certificate),
168 sender,
169 )
170 })
171 .await
172 }
173
174 async fn handle_chain_info_query(
175 &self,
176 query: ChainInfoQuery,
177 ) -> Result<ChainInfoResponse, NodeError> {
178 self.spawn_and_receive(move |validator, sender| {
179 validator.do_handle_chain_info_query(query, sender)
180 })
181 .await
182 }
183
184 async fn subscribe(&self, chains: Vec<ChainId>) -> Result<NotificationStream, NodeError> {
185 self.spawn_and_receive(move |validator, sender| validator.do_subscribe(chains, sender))
186 .await
187 }
188
189 async fn get_version_info(&self) -> Result<VersionInfo, NodeError> {
190 Ok(Default::default())
191 }
192
193 async fn get_network_description(&self) -> Result<NetworkDescription, NodeError> {
194 Ok(self
195 .client
196 .lock()
197 .await
198 .state
199 .storage_client()
200 .read_network_description()
201 .await
202 .transpose()
203 .ok_or_else(|| NodeError::ViewError {
204 error: "missing NetworkDescription".to_owned(),
205 })??)
206 }
207
208 async fn upload_blob(&self, content: BlobContent) -> Result<BlobId, NodeError> {
209 self.spawn_and_receive(move |validator, sender| validator.do_upload_blob(content, sender))
210 .await
211 }
212
213 async fn download_blob(&self, blob_id: BlobId) -> Result<BlobContent, NodeError> {
214 self.spawn_and_receive(move |validator, sender| validator.do_download_blob(blob_id, sender))
215 .await
216 }
217
218 async fn download_blobs(
219 &self,
220 blob_ids: Vec<BlobId>,
221 ) -> Result<crate::node::BlobStream, NodeError> {
222 let this = self.clone();
223 let stream = futures::stream::unfold(blob_ids.into_iter(), move |mut iter| {
224 let this = this.clone();
225 async move {
226 let blob_id = iter.next()?;
227 let result = this.download_blob(blob_id).await;
228 Some((result, iter))
229 }
230 });
231 Ok(Box::pin(stream))
232 }
233
234 async fn download_pending_blob(
235 &self,
236 chain_id: ChainId,
237 blob_id: BlobId,
238 ) -> Result<BlobContent, NodeError> {
239 self.spawn_and_receive(move |validator, sender| {
240 validator.do_download_pending_blob(chain_id, blob_id, sender)
241 })
242 .await
243 }
244
245 async fn handle_pending_blob(
246 &self,
247 chain_id: ChainId,
248 blob: BlobContent,
249 ) -> Result<ChainInfoResponse, NodeError> {
250 self.spawn_and_receive(move |validator, sender| {
251 validator.do_handle_pending_blob(chain_id, blob, sender)
252 })
253 .await
254 }
255
256 async fn download_certificate(
257 &self,
258 hash: CryptoHash,
259 ) -> Result<ConfirmedBlockCertificate, NodeError> {
260 self.spawn_and_receive(move |validator, sender| {
261 validator.do_download_certificate(hash, sender)
262 })
263 .await
264 }
265
266 async fn download_certificates(
267 &self,
268 hashes: Vec<CryptoHash>,
269 ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError> {
270 self.spawn_and_receive(move |validator, sender| {
271 validator.do_download_certificates(hashes, sender)
272 })
273 .await
274 }
275
276 async fn download_certificates_by_heights(
277 &self,
278 chain_id: ChainId,
279 heights: Vec<BlockHeight>,
280 ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError> {
281 self.spawn_and_receive(move |validator, sender| {
282 validator.do_download_certificates_by_heights(chain_id, heights, sender)
283 })
284 .await
285 }
286
287 async fn blob_last_used_by(&self, blob_id: BlobId) -> Result<CryptoHash, NodeError> {
288 self.spawn_and_receive(move |validator, sender| {
289 validator.do_blob_last_used_by(blob_id, sender)
290 })
291 .await
292 }
293
294 async fn blob_last_used_by_certificate(
295 &self,
296 blob_id: BlobId,
297 ) -> Result<ConfirmedBlockCertificate, NodeError> {
298 self.spawn_and_receive(move |validator, sender| {
299 validator.do_blob_last_used_by_certificate(blob_id, sender)
300 })
301 .await
302 }
303
304 async fn missing_blob_ids(&self, blob_ids: Vec<BlobId>) -> Result<Vec<BlobId>, NodeError> {
305 self.spawn_and_receive(move |validator, sender| {
306 validator.do_missing_blob_ids(blob_ids, sender)
307 })
308 .await
309 }
310
311 async fn event_block_heights(
312 &self,
313 event_ids: Vec<EventId>,
314 ) -> Result<Vec<Option<BlockHeight>>, NodeError> {
315 self.spawn_and_receive(move |validator, sender| {
316 validator.do_event_block_heights(event_ids, sender)
317 })
318 .await
319 }
320
321 async fn get_shard_info(
322 &self,
323 _chain_id: ChainId,
324 ) -> Result<crate::data_types::ShardInfo, NodeError> {
325 Ok(crate::data_types::ShardInfo {
327 shard_id: 0,
328 total_shards: 1,
329 })
330 }
331}
332
333impl<S> LocalValidatorClient<S>
334where
335 S: Storage + Clone + Send + Sync + 'static,
336{
337 fn new(public_key: ValidatorPublicKey, state: WorkerState<S>) -> Self {
338 let client = LocalValidator {
339 state,
340 notifier: Arc::new(ChannelNotifier::default()),
341 };
342 Self {
343 public_key,
344 client: Arc::new(Mutex::new(client)),
345 fault_type: FaultType::Honest,
346 }
347 }
348
349 pub fn name(&self) -> ValidatorPublicKey {
351 self.public_key
352 }
353
354 pub fn fault_type(&self) -> FaultType {
356 self.fault_type
357 }
358
359 fn set_fault_type(&mut self, fault_type: FaultType) {
360 self.fault_type = fault_type;
361 }
362
363 pub async fn chain_info_with_manager_values(
365 &mut self,
366 chain_id: ChainId,
367 ) -> Result<Box<ChainInfo>, NodeError> {
368 let query = ChainInfoQuery::new(chain_id).with_manager_values();
369 let response = self.handle_chain_info_query(query).await?;
370 Ok(response.info)
371 }
372
373 async fn spawn_and_receive<F, R, T>(&self, f: F) -> T
376 where
377 T: Send + 'static,
378 R: Future<Output = Result<(), T>> + Send,
379 F: FnOnce(Self, oneshot::Sender<T>) -> R + Send + 'static,
380 {
381 let validator = self.clone();
382 let (sender, receiver) = oneshot::channel();
383 tokio::spawn(async move {
384 if f(validator, sender).await.is_err() {
385 tracing::debug!("result could not be sent");
386 }
387 });
388 receiver.await.unwrap()
389 }
390
391 async fn do_handle_block_proposal(
392 self,
393 proposal: BlockProposal,
394 sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
395 ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
396 let result = match self.fault_type {
397 FaultType::Offline | FaultType::OfflineWithInfo => Err(NodeError::ClientIoError {
398 error: "offline".to_string(),
399 }),
400 FaultType::NoChains => Err(NodeError::InactiveChain(proposal.content.block.chain_id)),
401 FaultType::DontSendValidateVote
402 | FaultType::Honest
403 | FaultType::DontSendConfirmVote
404 | FaultType::DontProcessValidated => {
405 let (response_result, _actions) = self
406 .client
407 .lock()
408 .await
409 .state
410 .handle_block_proposal(proposal)
411 .await;
412 let result = response_result.map_err(NodeError::from);
413 if self.fault_type == FaultType::DontSendValidateVote {
414 Err(NodeError::ClientIoError {
415 error: "refusing to validate".to_string(),
416 })
417 } else {
418 result
419 }
420 }
421 };
422 sender.send(result)
424 }
425
426 async fn do_handle_lite_certificate(
427 self,
428 certificate: LiteCertificate<'_>,
429 sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
430 ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
431 let client = self.client.clone();
432 let validator = client.lock().await;
433 let result = async move {
434 match validator.state.full_certificate(certificate).await? {
435 Either::Left(confirmed) => {
436 self.do_handle_certificate_internal::<ConfirmedBlock>(confirmed, &validator)
437 .await
438 }
439 Either::Right(validated) => {
440 self.do_handle_certificate_internal::<ValidatedBlock>(validated, &validator)
441 .await
442 }
443 }
444 }
445 .await;
446 sender.send(result)
447 }
448
449 async fn do_handle_certificate_internal<T: ProcessableCertificate>(
450 &self,
451 certificate: T::Certificate,
452 validator: &MutexGuard<'_, LocalValidator<S>>,
453 ) -> Result<ChainInfoResponse, NodeError> {
454 match self.fault_type {
455 FaultType::DontProcessValidated if T::KIND == CertificateKind::Validated => {
456 Err(NodeError::ClientIoError {
457 error: "refusing to process validated block".to_string(),
458 })
459 }
460 FaultType::NoChains => Err(NodeError::InactiveChain(certificate.value().chain_id())),
461 FaultType::Honest
462 | FaultType::DontSendConfirmVote
463 | FaultType::DontProcessValidated
464 | FaultType::DontSendValidateVote => {
465 let result = validator
466 .state
467 .fully_handle_certificate_with_notifications(certificate, &validator.notifier)
468 .await
469 .map_err(Into::into);
470 if T::KIND == CertificateKind::Validated
471 && self.fault_type == FaultType::DontSendConfirmVote
472 {
473 Err(NodeError::ClientIoError {
474 error: "refusing to confirm".to_string(),
475 })
476 } else {
477 result
478 }
479 }
480 FaultType::Offline | FaultType::OfflineWithInfo => Err(NodeError::ClientIoError {
481 error: "offline".to_string(),
482 }),
483 }
484 }
485
486 async fn do_handle_certificate<T: ProcessableCertificate>(
487 self,
488 certificate: T::Certificate,
489 sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
490 ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
491 let validator = self.client.lock().await;
492 let result = self
493 .do_handle_certificate_internal::<T>(certificate, &validator)
494 .await;
495 sender.send(result)
496 }
497
498 async fn do_handle_chain_info_query(
499 self,
500 query: ChainInfoQuery,
501 sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
502 ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
503 let validator = self.client.lock().await;
504 let result = match self.fault_type {
505 FaultType::Offline => Err(NodeError::ClientIoError {
506 error: "offline".to_string(),
507 }),
508 FaultType::NoChains => Err(NodeError::InactiveChain(query.chain_id)),
509 FaultType::Honest
510 | FaultType::DontSendConfirmVote
511 | FaultType::DontProcessValidated
512 | FaultType::DontSendValidateVote
513 | FaultType::OfflineWithInfo => validator
514 .state
515 .handle_chain_info_query(query)
516 .await
517 .map_err(Into::into),
518 };
519 sender.send(result)
520 }
521
522 async fn do_subscribe(
523 self,
524 chains: Vec<ChainId>,
525 sender: oneshot::Sender<Result<NotificationStream, NodeError>>,
526 ) -> Result<(), Result<NotificationStream, NodeError>> {
527 let validator = self.client.lock().await;
528 let rx = validator.notifier.subscribe(chains);
529 let stream: NotificationStream = Box::pin(UnboundedReceiverStream::new(rx));
530 sender.send(Ok(stream))
531 }
532
533 async fn do_upload_blob(
534 self,
535 content: BlobContent,
536 sender: oneshot::Sender<Result<BlobId, NodeError>>,
537 ) -> Result<(), Result<BlobId, NodeError>> {
538 let validator = self.client.lock().await;
539 let blob = Blob::new(content);
540 let id = blob.id();
541 let storage = validator.state.storage_client();
542 let result = match storage.maybe_write_blobs(&[blob]).await {
543 Ok(has_state) if has_state.first() == Some(&true) => Ok(id),
544 Ok(_) => Err(NodeError::BlobsNotFound(vec![id])),
545 Err(error) => Err(error.into()),
546 };
547 sender.send(result)
548 }
549
550 async fn do_download_blob(
551 self,
552 blob_id: BlobId,
553 sender: oneshot::Sender<Result<BlobContent, NodeError>>,
554 ) -> Result<(), Result<BlobContent, NodeError>> {
555 let validator = self.client.lock().await;
556 let blob = validator
557 .state
558 .storage_client()
559 .read_blob(blob_id)
560 .await
561 .map_err(Into::into);
562 let blob = match blob {
563 Ok(blob) => blob.ok_or_else(|| NodeError::BlobsNotFound(vec![blob_id])),
564 Err(error) => Err(error),
565 };
566 sender.send(blob.map(|blob| CacheArc::unwrap_or_clone(blob).into_content()))
567 }
568
569 async fn do_download_pending_blob(
570 self,
571 chain_id: ChainId,
572 blob_id: BlobId,
573 sender: oneshot::Sender<Result<BlobContent, NodeError>>,
574 ) -> Result<(), Result<BlobContent, NodeError>> {
575 let validator = self.client.lock().await;
576 let result = validator
577 .state
578 .download_pending_blob(chain_id, blob_id)
579 .await
580 .map_err(Into::into);
581 sender.send(result.map(|blob| blob.content().clone()))
582 }
583
584 async fn do_handle_pending_blob(
585 self,
586 chain_id: ChainId,
587 blob: BlobContent,
588 sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
589 ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
590 let validator = self.client.lock().await;
591 let result = validator
592 .state
593 .handle_pending_blob(chain_id, Blob::new(blob))
594 .await
595 .map_err(Into::into);
596 sender.send(result)
597 }
598
599 async fn do_download_certificate(
600 self,
601 hash: CryptoHash,
602 sender: oneshot::Sender<Result<ConfirmedBlockCertificate, NodeError>>,
603 ) -> Result<(), Result<ConfirmedBlockCertificate, NodeError>> {
604 let validator = self.client.lock().await;
605 let certificate = validator
606 .state
607 .storage_client()
608 .read_certificate(hash)
609 .await
610 .map_err(Into::into);
611
612 let certificate = match certificate {
613 Err(error) => Err(error),
614 Ok(entry) => match entry {
615 Some(certificate) => Ok(CacheArc::unwrap_or_clone(certificate)),
616 None => {
617 panic!("Missing certificate: {hash}");
618 }
619 },
620 };
621
622 sender.send(certificate)
623 }
624
625 async fn do_download_certificates(
626 self,
627 hashes: Vec<CryptoHash>,
628 sender: oneshot::Sender<Result<Vec<ConfirmedBlockCertificate>, NodeError>>,
629 ) -> Result<(), Result<Vec<ConfirmedBlockCertificate>, NodeError>> {
630 let validator = self.client.lock().await;
631 let certificates = validator
632 .state
633 .storage_client()
634 .read_certificates(&hashes)
635 .await
636 .map_err(Into::into);
637
638 let certificates = match certificates {
639 Err(error) => Err(error),
640 Ok(certificates) => match ResultReadCertificates::new(certificates, hashes) {
641 ResultReadCertificates::Certificates(certificates) => Ok(certificates),
642 ResultReadCertificates::InvalidHashes(hashes) => {
643 panic!("Missing certificates: {hashes:?}")
644 }
645 },
646 };
647
648 sender.send(certificates)
649 }
650
651 async fn do_download_certificates_by_heights(
652 self,
653 chain_id: ChainId,
654 heights: Vec<BlockHeight>,
655 sender: oneshot::Sender<Result<Vec<ConfirmedBlockCertificate>, NodeError>>,
656 ) -> Result<(), Result<Vec<ConfirmedBlockCertificate>, NodeError>> {
657 let (query_sender, query_receiver) = oneshot::channel();
659 let query = ChainInfoQuery::new(chain_id).with_sent_certificate_hashes_by_heights(heights);
660
661 let self_clone = self.clone();
662 self.do_handle_chain_info_query(query, query_sender)
663 .await
664 .expect("Failed to handle chain info query");
665
666 let chain_info_response = query_receiver.await.map_err(|_| {
668 Err(NodeError::ClientIoError {
669 error: "Failed to receive chain info response".to_string(),
670 })
671 })?;
672
673 let hashes = match chain_info_response {
674 Ok(response) => response.info.requested_sent_certificate_hashes,
675 Err(e) => {
676 return sender.send(Err(e));
677 }
678 };
679
680 let (cert_sender, cert_receiver) = oneshot::channel();
682 self_clone
683 .do_download_certificates(hashes, cert_sender)
684 .await?;
685
686 let result = cert_receiver.await.map_err(|_| {
688 Err(NodeError::ClientIoError {
689 error: "Failed to receive certificates".to_string(),
690 })
691 })?;
692
693 sender.send(result)
694 }
695
696 async fn do_blob_last_used_by(
697 self,
698 blob_id: BlobId,
699 sender: oneshot::Sender<Result<CryptoHash, NodeError>>,
700 ) -> Result<(), Result<CryptoHash, NodeError>> {
701 let validator = self.client.lock().await;
702 let blob_state = validator
703 .state
704 .storage_client()
705 .read_blob_state(blob_id)
706 .await
707 .map_err(Into::into);
708 let certificate_hash = match blob_state {
709 Err(err) => Err(err),
710 Ok(blob_state) => match blob_state {
711 None => Err(NodeError::BlobsNotFound(vec![blob_id])),
712 Some(blob_state) => blob_state
713 .last_used_by
714 .ok_or_else(|| NodeError::BlobsNotFound(vec![blob_id])),
715 },
716 };
717
718 sender.send(certificate_hash)
719 }
720
721 async fn do_blob_last_used_by_certificate(
722 self,
723 blob_id: BlobId,
724 sender: oneshot::Sender<Result<ConfirmedBlockCertificate, NodeError>>,
725 ) -> Result<(), Result<ConfirmedBlockCertificate, NodeError>> {
726 match self.blob_last_used_by(blob_id).await {
727 Ok(cert_hash) => {
728 let cert = self.download_certificate(cert_hash).await;
729 sender.send(cert)
730 }
731 Err(err) => sender.send(Err(err)),
732 }
733 }
734
735 async fn do_missing_blob_ids(
736 self,
737 blob_ids: Vec<BlobId>,
738 sender: oneshot::Sender<Result<Vec<BlobId>, NodeError>>,
739 ) -> Result<(), Result<Vec<BlobId>, NodeError>> {
740 let validator = self.client.lock().await;
741 let missing_blob_ids = validator
742 .state
743 .storage_client()
744 .missing_blobs(&blob_ids)
745 .await
746 .map_err(Into::into);
747 sender.send(missing_blob_ids)
748 }
749
750 async fn do_event_block_heights(
751 self,
752 event_ids: Vec<EventId>,
753 sender: oneshot::Sender<Result<Vec<Option<BlockHeight>>, NodeError>>,
754 ) -> Result<(), Result<Vec<Option<BlockHeight>>, NodeError>> {
755 let validator = self.client.lock().await;
756 let heights = validator
757 .state
758 .storage_client()
759 .read_event_block_heights(&event_ids)
760 .await
761 .map_err(Into::into);
762 sender.send(heights)
763 }
764}
765
766#[derive(Clone)]
768pub struct NodeProvider<S>(Arc<std::sync::Mutex<Vec<LocalValidatorClient<S>>>>)
769where
770 S: Storage;
771
772impl<S> NodeProvider<S>
773where
774 S: Storage + Clone,
775{
776 fn all_nodes(&self) -> Vec<LocalValidatorClient<S>> {
777 self.0.lock().unwrap().clone()
778 }
779}
780
781impl<S> ValidatorNodeProvider for NodeProvider<S>
782where
783 S: Storage + Clone + Send + Sync + 'static,
784{
785 type Node = LocalValidatorClient<S>;
786
787 fn make_node(&self, _name: &str) -> Result<Self::Node, NodeError> {
788 unimplemented!()
789 }
790
791 fn make_nodes_from_list<A>(
792 &self,
793 validators: impl IntoIterator<Item = (ValidatorPublicKey, A)>,
794 ) -> Result<impl Iterator<Item = (ValidatorPublicKey, Self::Node)>, NodeError>
795 where
796 A: AsRef<str>,
797 {
798 let list = self.0.lock().unwrap();
799 Ok(validators
800 .into_iter()
801 .map(|(public_key, address)| {
802 list.iter()
803 .find(|client| client.public_key == public_key)
804 .ok_or_else(|| NodeError::CannotResolveValidatorAddress {
805 address: address.as_ref().to_string(),
806 })
807 .map(|client| (public_key, client.clone()))
808 })
809 .collect::<Result<Vec<_>, _>>()?
810 .into_iter())
811 }
812}
813
814impl<S> FromIterator<LocalValidatorClient<S>> for NodeProvider<S>
815where
816 S: Storage,
817{
818 fn from_iter<T>(iter: T) -> Self
819 where
820 T: IntoIterator<Item = LocalValidatorClient<S>>,
821 {
822 Self(Arc::new(std::sync::Mutex::new(iter.into_iter().collect())))
823 }
824}
825
826#[allow(missing_docs)]
833pub struct TestBuilder<B: StorageBuilder> {
834 storage_builder: B,
835 pub initial_committee: Committee,
836 admin_description: Option<ChainDescription>,
837 network_description: Option<NetworkDescription>,
838 genesis_storage_builder: GenesisStorageBuilder,
839 node_provider: NodeProvider<B::Storage>,
840 pub validator_storages: HashMap<ValidatorPublicKey, B::Storage>,
841 pub validator_key_pairs: HashMap<ValidatorPublicKey, ValidatorSecretKey>,
842 chain_client_storages: Vec<B::Storage>,
843 pub chain_owners: BTreeMap<ChainId, AccountOwner>,
844 pub signer: TestSigner,
845}
846
847#[async_trait]
849pub trait StorageBuilder {
850 type Storage: Storage + Clone + Send + Sync + 'static;
852
853 async fn build(&mut self) -> Result<Self::Storage, anyhow::Error>;
855
856 fn clock(&self) -> &TestClock;
858}
859
860#[derive(Default)]
861struct GenesisStorageBuilder {
862 accounts: Vec<GenesisAccount>,
863}
864
865struct GenesisAccount {
866 description: ChainDescription,
867 public_key: AccountPublicKey,
868}
869
870impl GenesisStorageBuilder {
871 fn add(&mut self, description: ChainDescription, public_key: AccountPublicKey) {
872 self.accounts.push(GenesisAccount {
873 description,
874 public_key,
875 })
876 }
877
878 async fn build<S>(&self, storage: S) -> S
879 where
880 S: Storage + Clone + Send + Sync + 'static,
881 {
882 for account in &self.accounts {
883 storage
884 .create_chain(account.description.clone())
885 .await
886 .unwrap();
887 }
888 storage
889 }
890}
891
892pub type ChainClient<S> = crate::client::ChainClient<crate::environment::Impl<S, NodeProvider<S>>>;
894
895impl<S: Storage + Clone + Send + Sync + 'static> ChainClient<S> {
896 pub async fn read_confirmed_blocks_downward(
898 &self,
899 from: CryptoHash,
900 limit: u32,
901 ) -> anyhow::Result<Vec<Arc<ConfirmedBlock>>> {
902 let mut hash = Some(from);
903 let mut values = Vec::new();
904 for _ in 0..limit {
905 let Some(next_hash) = hash else {
906 break;
907 };
908 let value = self.read_confirmed_block(next_hash).await?;
909 hash = value.block().header.previous_block_hash;
910 values.push(value);
911 }
912 Ok(values)
913 }
914}
915
916impl<B> TestBuilder<B>
917where
918 B: StorageBuilder,
919{
920 pub fn clock(&self) -> &TestClock {
923 self.storage_builder.clock()
924 }
925
926 pub async fn new(
928 storage_builder: B,
929 count: usize,
930 with_faulty_validators: usize,
931 signer: TestSigner,
932 ) -> Result<Self, anyhow::Error> {
933 Self::build(
934 storage_builder,
935 count,
936 with_faulty_validators,
937 signer,
938 None,
939 None,
940 )
941 .await
942 }
943
944 pub async fn new_with_block_export(
947 storage_builder: B,
948 count: usize,
949 with_faulty_validators: usize,
950 signer: TestSigner,
951 ) -> Result<Self, anyhow::Error> {
952 Self::build(
953 storage_builder,
954 count,
955 with_faulty_validators,
956 signer,
957 Some(Self::test_block_export_config()),
958 None,
959 )
960 .await
961 }
962
963 pub async fn new_with_block_export_and_ttl(
966 storage_builder: B,
967 count: usize,
968 with_faulty_validators: usize,
969 signer: TestSigner,
970 chain_worker_ttl: Duration,
971 ) -> Result<Self, anyhow::Error> {
972 Self::build(
973 storage_builder,
974 count,
975 with_faulty_validators,
976 signer,
977 Some(Self::test_block_export_config()),
978 Some(chain_worker_ttl),
979 )
980 .await
981 }
982
983 pub async fn new_with_block_export_config(
987 storage_builder: B,
988 count: usize,
989 with_faulty_validators: usize,
990 signer: TestSigner,
991 config: crate::BlockExportConfig,
992 ) -> Result<Self, anyhow::Error> {
993 Self::build(
994 storage_builder,
995 count,
996 with_faulty_validators,
997 signer,
998 Some(config),
999 None,
1000 )
1001 .await
1002 }
1003
1004 pub fn test_block_export_config() -> crate::BlockExportConfig {
1007 crate::BlockExportConfig {
1008 retry_delay: Duration::from_millis(20),
1009 max_retry_delay: Duration::from_millis(200),
1010 ..crate::BlockExportConfig::default()
1011 }
1012 }
1013
1014 async fn build(
1015 mut storage_builder: B,
1016 count: usize,
1017 with_faulty_validators: usize,
1018 mut signer: TestSigner,
1019 block_export: Option<crate::BlockExportConfig>,
1020 chain_worker_ttl: Option<Duration>,
1021 ) -> Result<Self, anyhow::Error> {
1022 let mut validators = Vec::new();
1023 for _ in 0..count {
1024 let validator_keypair = ValidatorKeypair::generate();
1025 let account_public_key = signer.generate_new();
1026 validators.push((validator_keypair, account_public_key));
1027 }
1028 let for_committee = validators
1029 .iter()
1030 .map(|(validating, account)| (validating.public_key, *account))
1031 .collect::<Vec<_>>();
1032 let initial_committee = Committee::make_simple(for_committee);
1033 let node_provider = NodeProvider(Arc::new(std::sync::Mutex::new(Vec::new())));
1036 let mut validator_storages = HashMap::new();
1037 let mut validator_key_pairs = HashMap::new();
1038 let mut faulty_validators = HashSet::new();
1039 for (i, (validator_keypair, _account_public_key)) in validators.into_iter().enumerate() {
1040 let validator_public_key = validator_keypair.public_key;
1041 let storage = storage_builder.build().await?;
1042 let secret_key_copy = validator_keypair.secret_key.copy();
1043 let config = ChainWorkerConfig {
1044 nickname: format!("Node {i}"),
1045 ttl: chain_worker_ttl
1049 .or_else(|| block_export.is_some().then(|| Duration::from_secs(60))),
1050 exported_heights_fold_interval: Duration::ZERO,
1051 ..ChainWorkerConfig::default()
1052 }
1053 .with_key_pair(Some(validator_keypair.secret_key));
1054 let mut state = WorkerState::new(storage.clone(), config, None);
1055 if let Some(export_config) = block_export.clone() {
1056 let handle = crate::spawn_block_export_queue(
1057 storage.clone(),
1058 Arc::new(node_provider.clone()),
1059 export_config,
1060 Some(validator_public_key),
1061 );
1062 state = state.with_block_export(handle);
1063 }
1064 let mut validator = LocalValidatorClient::new(validator_public_key, state);
1065 if i < with_faulty_validators {
1066 faulty_validators.insert(validator_public_key);
1067 validator.set_fault_type(FaultType::NoChains);
1068 }
1069 node_provider.0.lock().unwrap().push(validator);
1070 validator_storages.insert(validator_public_key, storage);
1071 validator_key_pairs.insert(validator_public_key, secret_key_copy);
1072 }
1073 tracing::info!(
1074 "Test will use the following faulty validators: {:?}",
1075 faulty_validators
1076 );
1077 Ok(Self {
1078 storage_builder,
1079 initial_committee,
1080 admin_description: None,
1081 network_description: None,
1082 genesis_storage_builder: GenesisStorageBuilder::default(),
1083 node_provider,
1084 validator_storages,
1085 validator_key_pairs,
1086 chain_client_storages: Vec::new(),
1087 chain_owners: BTreeMap::new(),
1088 signer,
1089 })
1090 }
1091
1092 pub fn with_policy(mut self, policy: ResourceControlPolicy) -> Self {
1094 let validators = self.initial_committee.validators().clone();
1095 self.initial_committee =
1096 Committee::new(validators, policy).expect("committee votes should not overflow");
1097 self
1098 }
1099
1100 pub fn with_cross_chain_message_chunk_limit(self, limit: usize) -> Self {
1102 let validator_clients = self.node_provider.0.lock().unwrap();
1103 for validator in validator_clients.iter() {
1104 let mut inner = validator.client.try_lock().expect("no contention at setup");
1105 inner.state.set_cross_chain_message_chunk_limit(limit);
1106 }
1107 drop(validator_clients);
1108 self
1109 }
1110
1111 pub fn fault_type(&self, public_key: &ValidatorPublicKey) -> Option<FaultType> {
1114 self.node_provider
1115 .0
1116 .lock()
1117 .unwrap()
1118 .iter()
1119 .find(|client| client.public_key == *public_key)
1120 .map(|client| client.fault_type())
1121 }
1122
1123 pub fn set_fault_type(&mut self, indexes: impl AsRef<[usize]>, fault_type: FaultType) {
1125 let mut faulty_validators = vec![];
1126 let mut validator_clients = self.node_provider.0.lock().unwrap();
1127 for index in indexes.as_ref() {
1128 let validator = &mut validator_clients[*index];
1129 validator.set_fault_type(fault_type);
1130 faulty_validators.push(validator.public_key);
1131 }
1132 tracing::info!(
1133 "Making the following validators {:?}: {:?}",
1134 fault_type,
1135 faulty_validators
1136 );
1137 }
1138
1139 pub async fn add_root_chain(
1144 &mut self,
1145 index: u32,
1146 balance: Amount,
1147 ) -> anyhow::Result<ChainClient<B::Storage>> {
1148 self.add_root_chain_with_ownership(index, balance, ChainOwnership::single)
1149 .await
1150 }
1151
1152 pub async fn add_root_chain_with_ownership(
1158 &mut self,
1159 index: u32,
1160 balance: Amount,
1161 make_ownership: impl FnOnce(AccountOwner) -> ChainOwnership,
1162 ) -> anyhow::Result<ChainClient<B::Storage>> {
1163 if self.admin_description.is_none() && index != 0 {
1165 Box::pin(self.add_root_chain(0, Amount::ZERO)).await?;
1166 }
1167 let origin = ChainOrigin::Root(index);
1168 let public_key = self.signer.generate_new();
1169 let open_chain_config = InitialChainConfig {
1170 ownership: make_ownership(public_key.into()),
1171 epoch: Epoch(0),
1172 account: AccountOwner::CHAIN,
1173 balance,
1174 application_permissions: ApplicationPermissions::default(),
1175 };
1176 let description = ChainDescription::new(origin, open_chain_config, Timestamp::from(0));
1177 let committee_blob = Blob::new_committee(bcs::to_bytes(&self.initial_committee).unwrap());
1178 if index == 0 {
1179 self.admin_description = Some(description.clone());
1180 self.network_description = Some(NetworkDescription {
1181 admin_chain_id: description.id(),
1182 genesis_config_hash: CryptoHash::test_hash("genesis config"),
1184 genesis_timestamp: Timestamp::from(0),
1185 genesis_committee_blob_hash: committee_blob.id().hash,
1186 name: "test network".to_string(),
1187 });
1188 }
1189 self.genesis_storage_builder
1191 .add(description.clone(), public_key);
1192
1193 let network_description = self.network_description.as_ref().unwrap();
1194
1195 for validator in self.node_provider.all_nodes() {
1196 let storage = self
1197 .validator_storages
1198 .get_mut(&validator.public_key)
1199 .unwrap();
1200 storage
1201 .write_network_description(network_description)
1202 .await
1203 .expect("writing the NetworkDescription should succeed");
1204 storage
1205 .write_blob(&committee_blob)
1206 .await
1207 .expect("writing a blob should succeed");
1208 storage.create_chain(description.clone()).await.unwrap();
1209 }
1210 for storage in &mut self.chain_client_storages {
1211 storage.create_chain(description.clone()).await.unwrap();
1212 }
1213 let chain_id = description.id();
1214 self.chain_owners.insert(chain_id, public_key.into());
1215 self.make_client(chain_id, None, BlockHeight::ZERO).await
1216 }
1217
1218 pub fn genesis_chains(&self) -> Vec<(AccountPublicKey, Amount)> {
1220 let mut result = Vec::new();
1221 for (i, genesis_account) in self.genesis_storage_builder.accounts.iter().enumerate() {
1222 assert_eq!(
1223 genesis_account.description.origin(),
1224 ChainOrigin::Root(i as u32)
1225 );
1226 result.push((
1227 genesis_account.public_key,
1228 genesis_account.description.config().balance,
1229 ));
1230 }
1231 result
1232 }
1233
1234 pub fn admin_chain_id(&self) -> ChainId {
1236 self.admin_description
1237 .as_ref()
1238 .expect("admin chain not initialized")
1239 .id()
1240 }
1241
1242 pub fn admin_description(&self) -> Option<&ChainDescription> {
1244 self.admin_description.as_ref()
1245 }
1246
1247 pub fn make_node_provider(&self) -> NodeProvider<B::Storage> {
1249 self.node_provider.clone()
1250 }
1251
1252 pub fn validator_storage(&mut self, index: usize) -> B::Storage {
1255 let public_key = self.node(index).public_key;
1256 self.validator_storages.get(&public_key).unwrap().clone()
1257 }
1258
1259 pub fn node(&mut self, index: usize) -> LocalValidatorClient<B::Storage> {
1261 self.node_provider.0.lock().unwrap()[index].clone()
1262 }
1263
1264 pub async fn make_storage(&mut self) -> anyhow::Result<B::Storage> {
1266 let storage = self.storage_builder.build().await?;
1267 let network_description = self.network_description.as_ref().unwrap();
1268 let committee_blob = Blob::new_committee(bcs::to_bytes(&self.initial_committee).unwrap());
1269 storage
1270 .write_network_description(network_description)
1271 .await
1272 .expect("writing the NetworkDescription should succeed");
1273 storage
1274 .write_blob(&committee_blob)
1275 .await
1276 .expect("writing a blob should succeed");
1277 Ok(self.genesis_storage_builder.build(storage).await)
1278 }
1279
1280 pub async fn make_client_with_options(
1282 &mut self,
1283 chain_id: ChainId,
1284 block_hash: Option<CryptoHash>,
1285 block_height: BlockHeight,
1286 options: chain_client::Options,
1287 follow_only: bool,
1288 ) -> anyhow::Result<ChainClient<B::Storage>> {
1289 let storage = self.make_storage().await?;
1292 self.chain_client_storages.push(storage.clone());
1293 let mode = if follow_only {
1294 crate::client::ListeningMode::FollowChain
1295 } else {
1296 crate::client::ListeningMode::FullChain
1297 };
1298 let client = Arc::new(Client::new(
1299 crate::environment::Impl {
1300 network: self.make_node_provider(),
1301 storage,
1302 signer: self.signer.clone(),
1303 wallet: TestWallet::default(),
1304 },
1305 self.admin_chain_id(),
1306 false,
1307 [(chain_id, mode)],
1308 format!("Client node for {chain_id:.8}"),
1309 Some(Duration::from_secs(30)),
1310 Some(Duration::from_secs(1)),
1311 1000,
1312 options,
1313 DEFAULT_BLOCK_CACHE_SIZE,
1314 DEFAULT_EXECUTION_STATE_CACHE_SIZE,
1315 &crate::client::RequestsSchedulerConfig::default(),
1316 ));
1317 Ok(client.create_chain_client(
1318 chain_id,
1319 block_hash,
1320 block_height,
1321 &None,
1322 self.chain_owners.get(&chain_id).copied(),
1323 None,
1324 follow_only,
1325 ))
1326 }
1327
1328 pub async fn make_client(
1330 &mut self,
1331 chain_id: ChainId,
1332 block_hash: Option<CryptoHash>,
1333 block_height: BlockHeight,
1334 ) -> anyhow::Result<ChainClient<B::Storage>> {
1335 self.make_client_with_options(
1336 chain_id,
1337 block_hash,
1338 block_height,
1339 chain_client::Options::test_default(),
1340 false,
1341 )
1342 .await
1343 }
1344
1345 pub async fn check_that_validators_have_certificate(
1347 &self,
1348 chain_id: ChainId,
1349 block_height: BlockHeight,
1350 target_count: usize,
1351 ) -> Option<ConfirmedBlockCertificate> {
1352 let query = ChainInfoQuery::new(chain_id)
1353 .with_sent_certificate_hashes_by_heights(vec![block_height]);
1354 let mut count = 0;
1355 let mut certificate = None;
1356 for validator in self.node_provider.all_nodes() {
1357 if let Ok(response) = validator.handle_chain_info_query(query.clone()).await {
1358 if response.check(validator.public_key).is_ok() {
1359 let ChainInfo {
1360 mut requested_sent_certificate_hashes,
1361 ..
1362 } = *response.info;
1363 debug_assert!(requested_sent_certificate_hashes.len() <= 1);
1364 if let Some(cert_hash) = requested_sent_certificate_hashes.pop() {
1365 if let Ok(cert) = validator.download_certificate(cert_hash).await {
1366 if cert.inner().block().header.chain_id == chain_id
1367 && cert.inner().block().header.height == block_height
1368 {
1369 cert.check(&self.initial_committee).unwrap();
1370 count += 1;
1371 certificate = Some(cert);
1372 }
1373 }
1374 }
1375 }
1376 }
1377 }
1378 assert!(count >= target_count);
1379 certificate
1380 }
1381
1382 pub async fn check_that_validators_are_in_round(
1385 &self,
1386 chain_id: ChainId,
1387 block_height: BlockHeight,
1388 round: Round,
1389 target_count: usize,
1390 ) {
1391 let query = ChainInfoQuery::new(chain_id);
1392 let mut count = 0;
1393 for validator in self.node_provider.all_nodes() {
1394 if let Ok(response) = validator.handle_chain_info_query(query.clone()).await {
1395 if response.info.manager.current_round == round
1396 && response.info.next_block_height == block_height
1397 && response.check(validator.public_key).is_ok()
1398 {
1399 count += 1;
1400 }
1401 }
1402 }
1403 assert!(count >= target_count);
1404 }
1405
1406 pub async fn exported_heights(
1409 &self,
1410 index: usize,
1411 chain_id: ChainId,
1412 ) -> BTreeMap<ValidatorPublicKey, BlockHeight> {
1413 let validator = self.node_provider.all_nodes()[index].clone();
1414 let guard = validator.client.lock().await;
1415 let chain = guard.state.chain_state_view(chain_id).await.unwrap();
1416 chain.exported_heights.get().clone().into()
1417 }
1418
1419 pub async fn resident_chain_workers(&self, index: usize) -> usize {
1421 let validator = self.node_provider.all_nodes()[index].clone();
1422 let guard = validator.client.lock().await;
1423 guard.state.resident_chain_worker_count()
1424 }
1425
1426 pub async fn next_block_height(&self, index: usize, chain_id: ChainId) -> BlockHeight {
1428 let validator = self.node_provider.all_nodes()[index].clone();
1429 let response = validator
1430 .handle_chain_info_query(ChainInfoQuery::new(chain_id))
1431 .await
1432 .unwrap();
1433 response.info.next_block_height
1434 }
1435
1436 pub async fn check_that_validators_have_empty_outboxes(&self, chain_id: ChainId) {
1438 for validator in self.node_provider.all_nodes() {
1439 let guard = validator.client.lock().await;
1440 let chain = guard.state.chain_state_view(chain_id).await.unwrap();
1441 assert_eq!(chain.outboxes.indices().await.unwrap(), []);
1442 }
1443 }
1444}
1445
1446#[cfg(feature = "rocksdb")]
1447static ROCKS_DB_SEMAPHORE: Semaphore = Semaphore::const_new(5);
1449
1450#[derive(Default)]
1453struct CommonStorageBuilder {
1454 namespace: String,
1455 instance_counter: usize,
1456 wasm_runtime: Option<WasmRuntime>,
1457 clock: TestClock,
1458}
1459
1460impl CommonStorageBuilder {
1461 fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1462 Self {
1463 wasm_runtime: wasm_runtime.into(),
1464 ..Self::default()
1465 }
1466 }
1467
1468 async fn build_storage<DB>(
1469 &mut self,
1470 config: DB::Config,
1471 ) -> anyhow::Result<DbStorage<DB, TestClock>>
1472 where
1473 DB: TestKeyValueDatabase + Clone + Send + Sync + 'static,
1474 DB::Store: KeyValueStore + Clone + Send + Sync + 'static,
1475 DB::Error: std::error::Error + Send + Sync + 'static,
1476 {
1477 self.instance_counter += 1;
1478 if self.namespace.is_empty() {
1479 self.namespace = generate_test_namespace();
1480 }
1481 let namespace = format!("{}_{}", self.namespace, self.instance_counter);
1482 Ok(
1483 DbStorage::new_for_testing(config, &namespace, self.wasm_runtime, self.clock.clone())
1484 .await?,
1485 )
1486 }
1487}
1488
1489#[derive(Default)]
1491pub struct MemoryStorageBuilder {
1492 inner: CommonStorageBuilder,
1493}
1494
1495impl MemoryStorageBuilder {
1496 pub fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1499 Self {
1500 inner: CommonStorageBuilder::with_wasm_runtime(wasm_runtime),
1501 }
1502 }
1503}
1504
1505#[async_trait]
1506impl StorageBuilder for MemoryStorageBuilder {
1507 type Storage = DbStorage<MemoryDatabase, TestClock>;
1508
1509 async fn build(&mut self) -> Result<Self::Storage, anyhow::Error> {
1510 let config = MemoryDatabase::new_test_config().await?;
1511 self.inner.build_storage::<MemoryDatabase>(config).await
1512 }
1513
1514 fn clock(&self) -> &TestClock {
1515 &self.inner.clock
1516 }
1517}
1518
1519#[cfg(feature = "rocksdb")]
1520pub struct RocksDbStorageBuilder {
1522 inner: CommonStorageBuilder,
1523 _permit: SemaphorePermit<'static>,
1524}
1525
1526#[cfg(feature = "rocksdb")]
1527impl RocksDbStorageBuilder {
1528 pub async fn new() -> Self {
1530 Self {
1531 inner: CommonStorageBuilder::default(),
1532 _permit: ROCKS_DB_SEMAPHORE.acquire().await.unwrap(),
1533 }
1534 }
1535
1536 #[cfg(any(feature = "wasmer", feature = "wasmtime"))]
1539 pub async fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1540 Self {
1541 inner: CommonStorageBuilder::with_wasm_runtime(wasm_runtime),
1542 _permit: ROCKS_DB_SEMAPHORE.acquire().await.unwrap(),
1543 }
1544 }
1545}
1546
1547#[cfg(feature = "rocksdb")]
1548#[async_trait]
1549impl StorageBuilder for RocksDbStorageBuilder {
1550 type Storage = DbStorage<RocksDbDatabase, TestClock>;
1551
1552 async fn build(&mut self) -> Result<Self::Storage, anyhow::Error> {
1553 let config = RocksDbDatabase::new_test_config().await?;
1554 self.inner.build_storage::<RocksDbDatabase>(config).await
1555 }
1556
1557 fn clock(&self) -> &TestClock {
1558 &self.inner.clock
1559 }
1560}
1561
1562#[cfg(all(not(target_arch = "wasm32"), feature = "storage-service"))]
1563#[derive(Default)]
1565pub struct ServiceStorageBuilder {
1566 inner: CommonStorageBuilder,
1567}
1568
1569#[cfg(all(not(target_arch = "wasm32"), feature = "storage-service"))]
1570impl ServiceStorageBuilder {
1571 pub fn new() -> Self {
1573 Self::with_wasm_runtime(None)
1574 }
1575
1576 pub fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1578 Self {
1579 inner: CommonStorageBuilder::with_wasm_runtime(wasm_runtime),
1580 }
1581 }
1582}
1583
1584#[cfg(all(not(target_arch = "wasm32"), feature = "storage-service"))]
1585#[async_trait]
1586impl StorageBuilder for ServiceStorageBuilder {
1587 type Storage = DbStorage<StorageServiceDatabase, TestClock>;
1588
1589 async fn build(&mut self) -> anyhow::Result<Self::Storage> {
1590 let config = StorageServiceDatabase::new_test_config().await?;
1591 self.inner
1592 .build_storage::<StorageServiceDatabase>(config)
1593 .await
1594 }
1595
1596 fn clock(&self) -> &TestClock {
1597 &self.inner.clock
1598 }
1599}
1600
1601#[cfg(feature = "scylladb")]
1602#[derive(Default)]
1604pub struct ScyllaDbStorageBuilder {
1605 inner: CommonStorageBuilder,
1606}
1607
1608#[cfg(feature = "scylladb")]
1609impl ScyllaDbStorageBuilder {
1610 pub fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1613 Self {
1614 inner: CommonStorageBuilder::with_wasm_runtime(wasm_runtime),
1615 }
1616 }
1617}
1618
1619#[cfg(feature = "scylladb")]
1620#[async_trait]
1621impl StorageBuilder for ScyllaDbStorageBuilder {
1622 type Storage = DbStorage<ScyllaDbDatabase, TestClock>;
1623
1624 async fn build(&mut self) -> Result<Self::Storage, anyhow::Error> {
1625 let config = ScyllaDbDatabase::new_test_config().await?;
1626 self.inner.build_storage::<ScyllaDbDatabase>(config).await
1627 }
1628
1629 fn clock(&self) -> &TestClock {
1630 &self.inner.clock
1631 }
1632}
1633
1634pub trait ClientOutcomeResultExt<T, E> {
1636 fn unwrap_ok_committed(self) -> T;
1639
1640 fn unwrap_ok_or_conflict(self) -> Result<T, Box<ConfirmedBlockCertificate>>;
1643}
1644
1645impl<T, E: std::fmt::Debug> ClientOutcomeResultExt<T, E> for Result<ClientOutcome<T>, E> {
1646 fn unwrap_ok_committed(self) -> T {
1647 match self.unwrap() {
1648 ClientOutcome::Committed(t) => t,
1649 ClientOutcome::WaitForTimeout(timeout) => {
1650 panic!("unexpected timeout: {timeout}")
1651 }
1652 ClientOutcome::Conflict(certificate) => {
1653 panic!("unexpected conflict: {}", certificate.hash())
1654 }
1655 }
1656 }
1657
1658 fn unwrap_ok_or_conflict(self) -> Result<T, Box<ConfirmedBlockCertificate>> {
1659 match self.unwrap() {
1660 ClientOutcome::Committed(t) => Ok(t),
1661 ClientOutcome::Conflict(certificate) => Err(certificate),
1662 ClientOutcome::WaitForTimeout(timeout) => {
1663 panic!("unexpected timeout: {timeout}")
1664 }
1665 }
1666 }
1667}