Skip to main content

linera_core/unit_tests/
test_utils.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4#![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    stream, Future, StreamExt as _,
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/// The kind of misbehavior a test validator simulates.
71#[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
83/// A validator used for testing. "Faulty" validators ignore block proposals (but not
84/// certificates or info queries) and have the wrong initial balance for all chains.
85///
86/// All methods are executed in spawned Tokio tasks, so that canceling a client task doesn't cause
87/// the validator's tasks to be canceled: In a real network, a validator also wouldn't cancel
88/// tasks if the client stopped waiting for the response.
89struct LocalValidator<S>
90where
91    S: Storage,
92{
93    state: WorkerState<S>,
94    notifier: Arc<ChannelNotifier<Notification>>,
95    /// Abort handles for the notification streams handed out by `do_subscribe`, so
96    /// tests can sever them mid-flight as a validator (or proxy) restart would.
97    notification_stream_aborts: Vec<stream::AbortHandle>,
98    /// How many times `do_subscribe` has been called. Monotonic — never drained by
99    /// `disconnect_notification_subscribers` — so a test can bound how often a client
100    /// re-subscribes, which is what pins the circuit breaker's backoff.
101    subscribe_calls: usize,
102}
103
104/// A client used by tests to talk to an in-process `LocalValidator`.
105#[derive(Clone)]
106pub struct LocalValidatorClient<S>
107where
108    S: Storage,
109{
110    public_key: ValidatorPublicKey,
111    client: Arc<Mutex<LocalValidator<S>>>,
112    fault_type: FaultType,
113    /// Held open for reading by every chain-info query, but ONLY once armed. A test takes
114    /// the write lock to stall the initial sync a probe runs after `subscribe` succeeded.
115    ///
116    /// The flag gate matters: `tokio::sync::RwLock` is write-preferring, so taking the read
117    /// unconditionally would queue every chain-info query at every validator in every test
118    /// behind a writer that only one test ever creates — and would hold the guard across
119    /// the validator mutex and the whole query.
120    chain_info_gate: Arc<tokio::sync::RwLock<()>>,
121    chain_info_gate_armed: Arc<std::sync::atomic::AtomicBool>,
122}
123
124impl<S> ValidatorNode for LocalValidatorClient<S>
125where
126    S: Storage + Clone + Send + Sync + 'static,
127{
128    type NotificationStream = NotificationStream;
129
130    fn address(&self) -> String {
131        format!("local:{}", self.public_key)
132    }
133
134    async fn handle_block_proposal(
135        &self,
136        proposal: BlockProposal,
137    ) -> Result<ChainInfoResponse, NodeError> {
138        self.spawn_and_receive(move |validator, sender| {
139            validator.do_handle_block_proposal(proposal, sender)
140        })
141        .await
142    }
143
144    async fn handle_lite_certificate(
145        &self,
146        certificate: LiteCertificate<'_>,
147        _delivery: CrossChainMessageDelivery,
148    ) -> Result<ChainInfoResponse, NodeError> {
149        let certificate = certificate.cloned();
150        self.spawn_and_receive(move |validator, sender| {
151            validator.do_handle_lite_certificate(certificate, sender)
152        })
153        .await
154    }
155
156    async fn handle_timeout_certificate(
157        &self,
158        certificate: GenericCertificate<Timeout>,
159    ) -> Result<ChainInfoResponse, NodeError> {
160        self.spawn_and_receive(move |validator, sender| {
161            validator.do_handle_certificate::<Timeout>(certificate, sender)
162        })
163        .await
164    }
165
166    async fn handle_validated_certificate(
167        &self,
168        certificate: ValidatedBlockCertificate,
169    ) -> Result<ChainInfoResponse, NodeError> {
170        self.spawn_and_receive(move |validator, sender| {
171            validator.do_handle_certificate::<ValidatedBlock>(certificate, sender)
172        })
173        .await
174    }
175
176    async fn handle_confirmed_certificate(
177        &self,
178        certificate: CacheArc<ConfirmedBlockCertificate>,
179        _delivery: CrossChainMessageDelivery,
180    ) -> Result<ChainInfoResponse, NodeError> {
181        self.spawn_and_receive(move |validator, sender| {
182            validator.do_handle_certificate::<ConfirmedBlock>(
183                CacheArc::unwrap_or_clone(certificate),
184                sender,
185            )
186        })
187        .await
188    }
189
190    async fn handle_chain_info_query(
191        &self,
192        query: ChainInfoQuery,
193    ) -> Result<ChainInfoResponse, NodeError> {
194        self.spawn_and_receive(move |validator, sender| {
195            validator.do_handle_chain_info_query(query, sender)
196        })
197        .await
198    }
199
200    async fn subscribe(&self, chains: Vec<ChainId>) -> Result<NotificationStream, NodeError> {
201        self.spawn_and_receive(move |validator, sender| validator.do_subscribe(chains, sender))
202            .await
203    }
204
205    async fn get_version_info(&self) -> Result<VersionInfo, NodeError> {
206        Ok(Default::default())
207    }
208
209    async fn get_network_description(&self) -> Result<NetworkDescription, NodeError> {
210        Ok(self
211            .client
212            .lock()
213            .await
214            .state
215            .storage_client()
216            .read_network_description()
217            .await
218            .transpose()
219            .ok_or_else(|| NodeError::ViewError {
220                error: "missing NetworkDescription".to_owned(),
221            })??)
222    }
223
224    async fn upload_blob(&self, content: BlobContent) -> Result<BlobId, NodeError> {
225        self.spawn_and_receive(move |validator, sender| validator.do_upload_blob(content, sender))
226            .await
227    }
228
229    async fn download_blob(&self, blob_id: BlobId) -> Result<BlobContent, NodeError> {
230        self.spawn_and_receive(move |validator, sender| validator.do_download_blob(blob_id, sender))
231            .await
232    }
233
234    async fn download_blobs(
235        &self,
236        blob_ids: Vec<BlobId>,
237    ) -> Result<crate::node::BlobStream, NodeError> {
238        let this = self.clone();
239        let stream = futures::stream::unfold(blob_ids.into_iter(), move |mut iter| {
240            let this = this.clone();
241            async move {
242                let blob_id = iter.next()?;
243                let result = this.download_blob(blob_id).await;
244                Some((result, iter))
245            }
246        });
247        Ok(Box::pin(stream))
248    }
249
250    async fn download_pending_blob(
251        &self,
252        chain_id: ChainId,
253        blob_id: BlobId,
254    ) -> Result<BlobContent, NodeError> {
255        self.spawn_and_receive(move |validator, sender| {
256            validator.do_download_pending_blob(chain_id, blob_id, sender)
257        })
258        .await
259    }
260
261    async fn handle_pending_blob(
262        &self,
263        chain_id: ChainId,
264        blob: BlobContent,
265    ) -> Result<ChainInfoResponse, NodeError> {
266        self.spawn_and_receive(move |validator, sender| {
267            validator.do_handle_pending_blob(chain_id, blob, sender)
268        })
269        .await
270    }
271
272    async fn download_certificate(
273        &self,
274        hash: CryptoHash,
275    ) -> Result<ConfirmedBlockCertificate, NodeError> {
276        self.spawn_and_receive(move |validator, sender| {
277            validator.do_download_certificate(hash, sender)
278        })
279        .await
280    }
281
282    async fn download_certificates(
283        &self,
284        hashes: Vec<CryptoHash>,
285    ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError> {
286        self.spawn_and_receive(move |validator, sender| {
287            validator.do_download_certificates(hashes, sender)
288        })
289        .await
290    }
291
292    async fn download_certificates_by_heights(
293        &self,
294        chain_id: ChainId,
295        heights: Vec<BlockHeight>,
296    ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError> {
297        self.spawn_and_receive(move |validator, sender| {
298            validator.do_download_certificates_by_heights(chain_id, heights, sender)
299        })
300        .await
301    }
302
303    async fn blob_last_used_by(&self, blob_id: BlobId) -> Result<CryptoHash, NodeError> {
304        self.spawn_and_receive(move |validator, sender| {
305            validator.do_blob_last_used_by(blob_id, sender)
306        })
307        .await
308    }
309
310    async fn blob_last_used_by_certificate(
311        &self,
312        blob_id: BlobId,
313    ) -> Result<ConfirmedBlockCertificate, NodeError> {
314        self.spawn_and_receive(move |validator, sender| {
315            validator.do_blob_last_used_by_certificate(blob_id, sender)
316        })
317        .await
318    }
319
320    async fn missing_blob_ids(&self, blob_ids: Vec<BlobId>) -> Result<Vec<BlobId>, NodeError> {
321        self.spawn_and_receive(move |validator, sender| {
322            validator.do_missing_blob_ids(blob_ids, sender)
323        })
324        .await
325    }
326
327    async fn event_block_heights(
328        &self,
329        event_ids: Vec<EventId>,
330    ) -> Result<Vec<Option<BlockHeight>>, NodeError> {
331        self.spawn_and_receive(move |validator, sender| {
332            validator.do_event_block_heights(event_ids, sender)
333        })
334        .await
335    }
336
337    async fn get_shard_info(
338        &self,
339        _chain_id: ChainId,
340    ) -> Result<crate::data_types::ShardInfo, NodeError> {
341        // For test purposes, return a dummy shard info
342        Ok(crate::data_types::ShardInfo {
343            shard_id: 0,
344            total_shards: 1,
345        })
346    }
347}
348
349impl<S> LocalValidatorClient<S>
350where
351    S: Storage + Clone + Send + Sync + 'static,
352{
353    fn new(public_key: ValidatorPublicKey, state: WorkerState<S>) -> Self {
354        let client = LocalValidator {
355            state,
356            notifier: Arc::new(ChannelNotifier::default()),
357            notification_stream_aborts: Vec::new(),
358            subscribe_calls: 0,
359        };
360        Self {
361            public_key,
362            client: Arc::new(Mutex::new(client)),
363            fault_type: FaultType::Honest,
364            chain_info_gate: Arc::default(),
365            chain_info_gate_armed: Arc::default(),
366        }
367    }
368
369    /// Returns the validator's public key.
370    pub fn name(&self) -> ValidatorPublicKey {
371        self.public_key
372    }
373
374    /// Returns the validator's currently configured [`FaultType`].
375    pub fn fault_type(&self) -> FaultType {
376        self.fault_type
377    }
378
379    /// How many times this validator has been asked to `subscribe`.
380    pub async fn subscribe_calls(&self) -> usize {
381        self.client.lock().await.subscribe_calls
382    }
383
384    /// Ends every notification stream previously handed out by this validator — as
385    /// clients experience when the validator (or its proxy) restarts.
386    pub async fn disconnect_notification_subscribers(&self) {
387        let mut validator = self.client.lock().await;
388        for abort in validator.notification_stream_aborts.drain(..) {
389            abort.abort();
390        }
391    }
392
393    fn set_fault_type(&mut self, fault_type: FaultType) {
394        self.fault_type = fault_type;
395    }
396
397    /// Obtains the basic `ChainInfo` data for the local validator chain, with chain manager values.
398    pub async fn chain_info_with_manager_values(
399        &mut self,
400        chain_id: ChainId,
401    ) -> Result<Box<ChainInfo>, NodeError> {
402        let query = ChainInfoQuery::new(chain_id).with_manager_values();
403        let response = self.handle_chain_info_query(query).await?;
404        Ok(response.info)
405    }
406
407    /// Executes the future produced by `f` in a new thread in a new Tokio runtime.
408    /// Returns the value that the future puts into the sender.
409    async fn spawn_and_receive<F, R, T>(&self, f: F) -> T
410    where
411        T: Send + 'static,
412        R: Future<Output = Result<(), T>> + Send,
413        F: FnOnce(Self, oneshot::Sender<T>) -> R + Send + 'static,
414    {
415        let validator = self.clone();
416        let (sender, receiver) = oneshot::channel();
417        tokio::spawn(async move {
418            if f(validator, sender).await.is_err() {
419                tracing::debug!("result could not be sent");
420            }
421        });
422        receiver.await.unwrap()
423    }
424
425    async fn do_handle_block_proposal(
426        self,
427        proposal: BlockProposal,
428        sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
429    ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
430        let result = match self.fault_type {
431            FaultType::Offline | FaultType::OfflineWithInfo => Err(NodeError::ClientIoError {
432                error: "offline".to_string(),
433            }),
434            FaultType::NoChains => Err(NodeError::InactiveChain(proposal.content.block.chain_id)),
435            FaultType::DontSendValidateVote
436            | FaultType::Honest
437            | FaultType::DontSendConfirmVote
438            | FaultType::DontProcessValidated => {
439                let (response_result, _actions) = self
440                    .client
441                    .lock()
442                    .await
443                    .state
444                    .handle_block_proposal(proposal)
445                    .await;
446                let result = response_result.map_err(NodeError::from);
447                if self.fault_type == FaultType::DontSendValidateVote {
448                    Err(NodeError::ClientIoError {
449                        error: "refusing to validate".to_string(),
450                    })
451                } else {
452                    result
453                }
454            }
455        };
456        // In a local node cross-chain messages can't get lost, so we can ignore the actions here.
457        sender.send(result)
458    }
459
460    async fn do_handle_lite_certificate(
461        self,
462        certificate: LiteCertificate<'_>,
463        sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
464    ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
465        let client = self.client.clone();
466        let validator = client.lock().await;
467        let result = async move {
468            match validator.state.full_certificate(certificate).await? {
469                Either::Left(confirmed) => {
470                    self.do_handle_certificate_internal::<ConfirmedBlock>(confirmed, &validator)
471                        .await
472                }
473                Either::Right(validated) => {
474                    self.do_handle_certificate_internal::<ValidatedBlock>(validated, &validator)
475                        .await
476                }
477            }
478        }
479        .await;
480        sender.send(result)
481    }
482
483    async fn do_handle_certificate_internal<T: ProcessableCertificate>(
484        &self,
485        certificate: T::Certificate,
486        validator: &MutexGuard<'_, LocalValidator<S>>,
487    ) -> Result<ChainInfoResponse, NodeError> {
488        match self.fault_type {
489            FaultType::DontProcessValidated if T::KIND == CertificateKind::Validated => {
490                Err(NodeError::ClientIoError {
491                    error: "refusing to process validated block".to_string(),
492                })
493            }
494            FaultType::NoChains => Err(NodeError::InactiveChain(certificate.value().chain_id())),
495            FaultType::Honest
496            | FaultType::DontSendConfirmVote
497            | FaultType::DontProcessValidated
498            | FaultType::DontSendValidateVote => {
499                let result = validator
500                    .state
501                    .fully_handle_certificate_with_notifications(certificate, &validator.notifier)
502                    .await
503                    .map_err(Into::into);
504                if T::KIND == CertificateKind::Validated
505                    && self.fault_type == FaultType::DontSendConfirmVote
506                {
507                    Err(NodeError::ClientIoError {
508                        error: "refusing to confirm".to_string(),
509                    })
510                } else {
511                    result
512                }
513            }
514            FaultType::Offline | FaultType::OfflineWithInfo => Err(NodeError::ClientIoError {
515                error: "offline".to_string(),
516            }),
517        }
518    }
519
520    async fn do_handle_certificate<T: ProcessableCertificate>(
521        self,
522        certificate: T::Certificate,
523        sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
524    ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
525        let validator = self.client.lock().await;
526        let result = self
527            .do_handle_certificate_internal::<T>(certificate, &validator)
528            .await;
529        sender.send(result)
530    }
531
532    async fn do_handle_chain_info_query(
533        self,
534        query: ChainInfoQuery,
535        sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
536    ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
537        let gate = self.chain_info_gate.clone();
538        let _open = if self
539            .chain_info_gate_armed
540            .load(std::sync::atomic::Ordering::Relaxed)
541        {
542            Some(gate.read().await)
543        } else {
544            None
545        };
546        let validator = self.client.lock().await;
547        let result = match self.fault_type {
548            FaultType::Offline => Err(NodeError::ClientIoError {
549                error: "offline".to_string(),
550            }),
551            FaultType::NoChains => Err(NodeError::InactiveChain(query.chain_id)),
552            FaultType::Honest
553            | FaultType::DontSendConfirmVote
554            | FaultType::DontProcessValidated
555            | FaultType::DontSendValidateVote
556            | FaultType::OfflineWithInfo => validator
557                .state
558                .handle_chain_info_query(query)
559                .await
560                .map_err(Into::into),
561        };
562        sender.send(result)
563    }
564
565    async fn do_subscribe(
566        self,
567        chains: Vec<ChainId>,
568        sender: oneshot::Sender<Result<NotificationStream, NodeError>>,
569    ) -> Result<(), Result<NotificationStream, NodeError>> {
570        let mut validator = self.client.lock().await;
571        validator.subscribe_calls += 1;
572        // Both offline variants refuse: a notification stream is not the info query that
573        // `OfflineWithInfo` still answers, and a probe must be able to genuinely FAIL.
574        if matches!(
575            self.fault_type,
576            FaultType::Offline | FaultType::OfflineWithInfo
577        ) {
578            return sender.send(Err(NodeError::ClientIoError {
579                error: "offline".to_string(),
580            }));
581        }
582        let rx = validator.notifier.subscribe(chains);
583        let (stream, abort) = stream::abortable(UnboundedReceiverStream::new(rx));
584        // Marking the handle aborted when the client merely DROPS its end is what makes
585        // the prune below see the real set: a dropped stream is never aborted by anyone,
586        // so without this the vec grows across every re-subscribe and
587        // `disconnect_notification_subscribers` walks handles nobody holds.
588        let abort_on_drop = AbortOnDrop(abort.clone());
589        let stream = stream.map(move |notification| {
590            let _abort_on_drop = &abort_on_drop;
591            notification
592        });
593        validator
594            .notification_stream_aborts
595            .retain(|abort| !abort.is_aborted());
596        validator.notification_stream_aborts.push(abort);
597        let stream: NotificationStream = Box::pin(stream);
598        sender.send(Ok(stream))
599    }
600
601    async fn do_upload_blob(
602        self,
603        content: BlobContent,
604        sender: oneshot::Sender<Result<BlobId, NodeError>>,
605    ) -> Result<(), Result<BlobId, NodeError>> {
606        let validator = self.client.lock().await;
607        let blob = Blob::new(content);
608        let id = blob.id();
609        let storage = validator.state.storage_client();
610        let result = match storage.maybe_write_blobs(&[blob]).await {
611            Ok(has_state) if has_state.first() == Some(&true) => Ok(id),
612            Ok(_) => Err(NodeError::BlobsNotFound(vec![id])),
613            Err(error) => Err(error.into()),
614        };
615        sender.send(result)
616    }
617
618    async fn do_download_blob(
619        self,
620        blob_id: BlobId,
621        sender: oneshot::Sender<Result<BlobContent, NodeError>>,
622    ) -> Result<(), Result<BlobContent, NodeError>> {
623        let validator = self.client.lock().await;
624        let blob = validator
625            .state
626            .storage_client()
627            .read_blob(blob_id)
628            .await
629            .map_err(Into::into);
630        let blob = match blob {
631            Ok(blob) => blob.ok_or_else(|| NodeError::BlobsNotFound(vec![blob_id])),
632            Err(error) => Err(error),
633        };
634        sender.send(blob.map(|blob| CacheArc::unwrap_or_clone(blob).into_content()))
635    }
636
637    async fn do_download_pending_blob(
638        self,
639        chain_id: ChainId,
640        blob_id: BlobId,
641        sender: oneshot::Sender<Result<BlobContent, NodeError>>,
642    ) -> Result<(), Result<BlobContent, NodeError>> {
643        let validator = self.client.lock().await;
644        let result = validator
645            .state
646            .download_pending_blob(chain_id, blob_id)
647            .await
648            .map_err(Into::into);
649        sender.send(result.map(|blob| blob.content().clone()))
650    }
651
652    async fn do_handle_pending_blob(
653        self,
654        chain_id: ChainId,
655        blob: BlobContent,
656        sender: oneshot::Sender<Result<ChainInfoResponse, NodeError>>,
657    ) -> Result<(), Result<ChainInfoResponse, NodeError>> {
658        let validator = self.client.lock().await;
659        let result = validator
660            .state
661            .handle_pending_blob(chain_id, Blob::new(blob))
662            .await
663            .map_err(Into::into);
664        sender.send(result)
665    }
666
667    async fn do_download_certificate(
668        self,
669        hash: CryptoHash,
670        sender: oneshot::Sender<Result<ConfirmedBlockCertificate, NodeError>>,
671    ) -> Result<(), Result<ConfirmedBlockCertificate, NodeError>> {
672        let validator = self.client.lock().await;
673        let certificate = validator
674            .state
675            .storage_client()
676            .read_certificate(hash)
677            .await
678            .map_err(Into::into);
679
680        let certificate = match certificate {
681            Err(error) => Err(error),
682            Ok(entry) => match entry {
683                Some(certificate) => Ok(CacheArc::unwrap_or_clone(certificate)),
684                None => {
685                    panic!("Missing certificate: {hash}");
686                }
687            },
688        };
689
690        sender.send(certificate)
691    }
692
693    async fn do_download_certificates(
694        self,
695        hashes: Vec<CryptoHash>,
696        sender: oneshot::Sender<Result<Vec<ConfirmedBlockCertificate>, NodeError>>,
697    ) -> Result<(), Result<Vec<ConfirmedBlockCertificate>, NodeError>> {
698        let validator = self.client.lock().await;
699        let certificates = validator
700            .state
701            .storage_client()
702            .read_certificates(&hashes)
703            .await
704            .map_err(Into::into);
705
706        let certificates = match certificates {
707            Err(error) => Err(error),
708            Ok(certificates) => match ResultReadCertificates::new(certificates, hashes) {
709                ResultReadCertificates::Certificates(certificates) => Ok(certificates),
710                ResultReadCertificates::InvalidHashes(hashes) => {
711                    panic!("Missing certificates: {hashes:?}")
712                }
713            },
714        };
715
716        sender.send(certificates)
717    }
718
719    async fn do_download_certificates_by_heights(
720        self,
721        chain_id: ChainId,
722        heights: Vec<BlockHeight>,
723        sender: oneshot::Sender<Result<Vec<ConfirmedBlockCertificate>, NodeError>>,
724    ) -> Result<(), Result<Vec<ConfirmedBlockCertificate>, NodeError>> {
725        // First, use do_handle_chain_info_query to get the certificate hashes
726        let (query_sender, query_receiver) = oneshot::channel();
727        let query = ChainInfoQuery::new(chain_id).with_sent_certificate_hashes_by_heights(heights);
728
729        let self_clone = self.clone();
730        self.do_handle_chain_info_query(query, query_sender)
731            .await
732            .expect("Failed to handle chain info query");
733
734        // Get the response from the chain info query
735        let chain_info_response = query_receiver.await.map_err(|_| {
736            Err(NodeError::ClientIoError {
737                error: "Failed to receive chain info response".to_string(),
738            })
739        })?;
740
741        let hashes = match chain_info_response {
742            Ok(response) => response.info.requested_sent_certificate_hashes,
743            Err(e) => {
744                return sender.send(Err(e));
745            }
746        };
747
748        // Now use do_download_certificates to get the actual certificates
749        let (cert_sender, cert_receiver) = oneshot::channel();
750        self_clone
751            .do_download_certificates(hashes, cert_sender)
752            .await?;
753
754        // Forward the result to the original sender
755        let result = cert_receiver.await.map_err(|_| {
756            Err(NodeError::ClientIoError {
757                error: "Failed to receive certificates".to_string(),
758            })
759        })?;
760
761        sender.send(result)
762    }
763
764    async fn do_blob_last_used_by(
765        self,
766        blob_id: BlobId,
767        sender: oneshot::Sender<Result<CryptoHash, NodeError>>,
768    ) -> Result<(), Result<CryptoHash, NodeError>> {
769        let validator = self.client.lock().await;
770        let blob_state = validator
771            .state
772            .storage_client()
773            .read_blob_state(blob_id)
774            .await
775            .map_err(Into::into);
776        let certificate_hash = match blob_state {
777            Err(err) => Err(err),
778            Ok(blob_state) => match blob_state {
779                None => Err(NodeError::BlobsNotFound(vec![blob_id])),
780                Some(blob_state) => blob_state
781                    .last_used_by
782                    .ok_or_else(|| NodeError::BlobsNotFound(vec![blob_id])),
783            },
784        };
785
786        sender.send(certificate_hash)
787    }
788
789    async fn do_blob_last_used_by_certificate(
790        self,
791        blob_id: BlobId,
792        sender: oneshot::Sender<Result<ConfirmedBlockCertificate, NodeError>>,
793    ) -> Result<(), Result<ConfirmedBlockCertificate, NodeError>> {
794        match self.blob_last_used_by(blob_id).await {
795            Ok(cert_hash) => {
796                let cert = self.download_certificate(cert_hash).await;
797                sender.send(cert)
798            }
799            Err(err) => sender.send(Err(err)),
800        }
801    }
802
803    async fn do_missing_blob_ids(
804        self,
805        blob_ids: Vec<BlobId>,
806        sender: oneshot::Sender<Result<Vec<BlobId>, NodeError>>,
807    ) -> Result<(), Result<Vec<BlobId>, NodeError>> {
808        let validator = self.client.lock().await;
809        let missing_blob_ids = validator
810            .state
811            .storage_client()
812            .missing_blobs(&blob_ids)
813            .await
814            .map_err(Into::into);
815        sender.send(missing_blob_ids)
816    }
817
818    async fn do_event_block_heights(
819        self,
820        event_ids: Vec<EventId>,
821        sender: oneshot::Sender<Result<Vec<Option<BlockHeight>>, NodeError>>,
822    ) -> Result<(), Result<Vec<Option<BlockHeight>>, NodeError>> {
823        let validator = self.client.lock().await;
824        let heights = validator
825            .state
826            .storage_client()
827            .read_event_block_heights(&event_ids)
828            .await
829            .map_err(Into::into);
830        sender.send(heights)
831    }
832}
833
834/// Polls `condition` until it holds, or gives up after `timeout` of real time.
835///
836/// The probe-timer tests drive a simulated clock but still have to let a spawned listener
837/// run; waiting on an observable counter rather than a fixed sleep keeps them from failing
838/// as "never repaired" on a loaded runner, where the real work is a `subscribe` plus a
839/// full sync against the storage backend under test.
840pub async fn wait_until<F, Fut>(timeout: Duration, mut condition: F) -> bool
841where
842    F: FnMut() -> Fut,
843    Fut: Future<Output = bool>,
844{
845    let deadline = std::time::Instant::now() + timeout;
846    loop {
847        if condition().await {
848            return true;
849        }
850        if std::time::Instant::now() >= deadline {
851            return false;
852        }
853        tokio::time::sleep(Duration::from_millis(20)).await;
854    }
855}
856
857/// Aborts its handle when dropped, so a notification stream the client lets go of is
858/// distinguishable from one that is still held.
859struct AbortOnDrop(stream::AbortHandle);
860
861impl Drop for AbortOnDrop {
862    fn drop(&mut self) {
863        self.0.abort();
864    }
865}
866
867/// A [`ValidatorNodeProvider`] holding the in-process test validator clients.
868#[derive(Clone)]
869pub struct NodeProvider<S>
870where
871    S: Storage,
872{
873    clients: Arc<std::sync::Mutex<Vec<LocalValidatorClient<S>>>>,
874    /// How many further calls to build the validator set must fail, so a test can make
875    /// `update_notification_streams` return `Err` at a chosen moment.
876    failures_left: Arc<std::sync::atomic::AtomicUsize>,
877    /// Every attempt to build the validator set, successful or not. Monotonic, so a test
878    /// can freeze the clock and assert the listener is PARKED rather than spinning.
879    builds: Arc<std::sync::atomic::AtomicUsize>,
880    /// Microseconds to advance the clock on each build, simulating a SLOW committee read —
881    /// the one await in `update_notification_streams`. A test uses it to prove deadlines
882    /// are computed from a clock sampled after that await rather than before it.
883    slow_build_micros: Arc<std::sync::atomic::AtomicU64>,
884    clock: Option<TestClock>,
885}
886
887impl<S> NodeProvider<S>
888where
889    S: Storage + Clone,
890{
891    fn all_nodes(&self) -> Vec<LocalValidatorClient<S>> {
892        self.clients.lock().unwrap().clone()
893    }
894
895    /// The same validators, with a private failure counter. The validators' own export
896    /// tasks resolve each other through a provider too, so without this they would race
897    /// the chain client for injected failures and a test could pass without ever failing
898    /// the update it names.
899    fn without_failure_injection(&self) -> Self {
900        Self {
901            clients: self.clients.clone(),
902            failures_left: Arc::default(),
903            // A private build counter too, so export-task builds stay out of a test's count.
904            builds: Arc::default(),
905            slow_build_micros: Arc::default(),
906            clock: self.clock.clone(),
907        }
908    }
909}
910
911impl<S> ValidatorNodeProvider for NodeProvider<S>
912where
913    S: Storage + Clone + Send + Sync + 'static,
914{
915    type Node = LocalValidatorClient<S>;
916
917    fn make_node(&self, _name: &str) -> Result<Self::Node, NodeError> {
918        unimplemented!()
919    }
920
921    fn make_nodes_from_list<A>(
922        &self,
923        validators: impl IntoIterator<Item = (ValidatorPublicKey, A)>,
924    ) -> Result<impl Iterator<Item = (ValidatorPublicKey, Self::Node)>, NodeError>
925    where
926        A: AsRef<str>,
927    {
928        self.builds
929            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
930        let slow = self
931            .slow_build_micros
932            .load(std::sync::atomic::Ordering::Relaxed);
933        if slow > 0 {
934            if let Some(clock) = &self.clock {
935                clock.add(TimeDelta::from_micros(slow));
936            }
937        }
938        if self
939            .failures_left
940            .fetch_update(
941                std::sync::atomic::Ordering::Relaxed,
942                std::sync::atomic::Ordering::Relaxed,
943                |n| n.checked_sub(1),
944            )
945            .is_ok()
946        {
947            return Err(NodeError::CannotResolveValidatorAddress {
948                address: "injected failure".to_string(),
949            });
950        }
951        let list = self.clients.lock().unwrap();
952        Ok(validators
953            .into_iter()
954            .map(|(public_key, address)| {
955                list.iter()
956                    .find(|client| client.public_key == public_key)
957                    .ok_or_else(|| NodeError::CannotResolveValidatorAddress {
958                        address: address.as_ref().to_string(),
959                    })
960                    .map(|client| (public_key, client.clone()))
961            })
962            .collect::<Result<Vec<_>, _>>()?
963            .into_iter())
964    }
965}
966
967impl<S> FromIterator<LocalValidatorClient<S>> for NodeProvider<S>
968where
969    S: Storage,
970{
971    fn from_iter<T>(iter: T) -> Self
972    where
973        T: IntoIterator<Item = LocalValidatorClient<S>>,
974    {
975        Self {
976            clients: Arc::new(std::sync::Mutex::new(iter.into_iter().collect())),
977            failures_left: Arc::default(),
978            builds: Arc::default(),
979            slow_build_micros: Arc::default(),
980            clock: None,
981        }
982    }
983}
984
985// NOTE:
986// * To communicate with a quorum of validators, chain clients iterate over a copy of
987// `validator_clients` to spawn I/O tasks.
988// * When using `LocalValidatorClient`, clients communicate with an exact quorum then stop.
989// * Most tests have 1 faulty validator out 4 so that there is exactly only 1 quorum to
990// communicate with.
991#[allow(missing_docs)]
992pub struct TestBuilder<B: StorageBuilder> {
993    storage_builder: B,
994    pub initial_committee: Committee,
995    admin_description: Option<ChainDescription>,
996    network_description: Option<NetworkDescription>,
997    genesis_storage_builder: GenesisStorageBuilder,
998    node_provider: NodeProvider<B::Storage>,
999    pub validator_storages: HashMap<ValidatorPublicKey, B::Storage>,
1000    pub validator_key_pairs: HashMap<ValidatorPublicKey, ValidatorSecretKey>,
1001    chain_client_storages: Vec<B::Storage>,
1002    pub chain_owners: BTreeMap<ChainId, AccountOwner>,
1003    pub signer: TestSigner,
1004}
1005
1006/// Builds storage instances of a specific backend for use in tests.
1007#[async_trait]
1008pub trait StorageBuilder {
1009    /// The storage type produced by this builder.
1010    type Storage: Storage + Clone + Send + Sync + 'static;
1011
1012    /// Builds a new storage instance.
1013    async fn build(&mut self) -> Result<Self::Storage, anyhow::Error>;
1014
1015    /// Returns the test clock shared by all storages built here.
1016    fn clock(&self) -> &TestClock;
1017}
1018
1019#[derive(Default)]
1020struct GenesisStorageBuilder {
1021    accounts: Vec<GenesisAccount>,
1022}
1023
1024struct GenesisAccount {
1025    description: ChainDescription,
1026    public_key: AccountPublicKey,
1027}
1028
1029impl GenesisStorageBuilder {
1030    fn add(&mut self, description: ChainDescription, public_key: AccountPublicKey) {
1031        self.accounts.push(GenesisAccount {
1032            description,
1033            public_key,
1034        })
1035    }
1036
1037    async fn build<S>(&self, storage: S) -> S
1038    where
1039        S: Storage + Clone + Send + Sync + 'static,
1040    {
1041        for account in &self.accounts {
1042            storage
1043                .create_chain(account.description.clone())
1044                .await
1045                .unwrap();
1046        }
1047        storage
1048    }
1049}
1050
1051/// A chain client wired up to the in-process test validator network.
1052pub type ChainClient<S> = crate::client::ChainClient<crate::environment::Impl<S, NodeProvider<S>>>;
1053
1054impl<S: Storage + Clone + Send + Sync + 'static> ChainClient<S> {
1055    /// Reads the hashed certificate values in descending order from the given hash.
1056    pub async fn read_confirmed_blocks_downward(
1057        &self,
1058        from: CryptoHash,
1059        limit: u32,
1060    ) -> anyhow::Result<Vec<Arc<ConfirmedBlock>>> {
1061        let mut hash = Some(from);
1062        let mut values = Vec::new();
1063        for _ in 0..limit {
1064            let Some(next_hash) = hash else {
1065                break;
1066            };
1067            let value = self.read_confirmed_block(next_hash).await?;
1068            hash = value.block().header.previous_block_hash;
1069            values.push(value);
1070        }
1071        Ok(values)
1072    }
1073}
1074
1075impl<B> TestBuilder<B>
1076where
1077    B: StorageBuilder,
1078{
1079    /// The simulated clock every storage here shares, so a test can drive the export queue's
1080    /// tick in virtual time instead of sleeping through it.
1081    pub fn clock(&self) -> &TestClock {
1082        self.storage_builder.clock()
1083    }
1084
1085    /// Creates a test setup with `count` validators, `with_faulty_validators` of which are faulty.
1086    pub async fn new(
1087        storage_builder: B,
1088        count: usize,
1089        with_faulty_validators: usize,
1090        signer: TestSigner,
1091    ) -> Result<Self, anyhow::Error> {
1092        Self::build(
1093            storage_builder,
1094            count,
1095            with_faulty_validators,
1096            signer,
1097            None,
1098            None,
1099        )
1100        .await
1101    }
1102
1103    /// Creates a test setup like [`TestBuilder::new`], in which every validator also pushes the
1104    /// blocks it executes to the rest of the committee.
1105    pub async fn new_with_block_export(
1106        storage_builder: B,
1107        count: usize,
1108        with_faulty_validators: usize,
1109        signer: TestSigner,
1110    ) -> Result<Self, anyhow::Error> {
1111        Self::build(
1112            storage_builder,
1113            count,
1114            with_faulty_validators,
1115            signer,
1116            Some(Self::test_block_export_config()),
1117            None,
1118        )
1119        .await
1120    }
1121
1122    /// Creates a test setup like [`TestBuilder::new_with_block_export`] with an explicit chain
1123    /// worker TTL, for asserting that workers expire even while export is enabled.
1124    pub async fn new_with_block_export_and_ttl(
1125        storage_builder: B,
1126        count: usize,
1127        with_faulty_validators: usize,
1128        signer: TestSigner,
1129        chain_worker_ttl: Duration,
1130    ) -> Result<Self, anyhow::Error> {
1131        Self::build(
1132            storage_builder,
1133            count,
1134            with_faulty_validators,
1135            signer,
1136            Some(Self::test_block_export_config()),
1137            Some(chain_worker_ttl),
1138        )
1139        .await
1140    }
1141
1142    /// Creates a test setup like [`TestBuilder::new_with_block_export`], with the export tuned by
1143    /// the caller. Used to shrink `max_catch_up_blocks` far below its default so that a backlog a
1144    /// test can actually produce still takes several rounds to drain.
1145    pub async fn new_with_block_export_config(
1146        storage_builder: B,
1147        count: usize,
1148        with_faulty_validators: usize,
1149        signer: TestSigner,
1150        config: crate::BlockExportConfig,
1151    ) -> Result<Self, anyhow::Error> {
1152        Self::build(
1153            storage_builder,
1154            count,
1155            with_faulty_validators,
1156            signer,
1157            Some(config),
1158            None,
1159        )
1160        .await
1161    }
1162
1163    /// The export settings the block-export tests run with: production backoff is measured in
1164    /// seconds, which would make every test that exercises a failing destination wait it out.
1165    pub fn test_block_export_config() -> crate::BlockExportConfig {
1166        crate::BlockExportConfig {
1167            retry_delay: Duration::from_millis(20),
1168            max_retry_delay: Duration::from_millis(200),
1169            ..crate::BlockExportConfig::default()
1170        }
1171    }
1172
1173    async fn build(
1174        mut storage_builder: B,
1175        count: usize,
1176        with_faulty_validators: usize,
1177        mut signer: TestSigner,
1178        block_export: Option<crate::BlockExportConfig>,
1179        chain_worker_ttl: Option<Duration>,
1180    ) -> Result<Self, anyhow::Error> {
1181        let mut validators = Vec::new();
1182        for _ in 0..count {
1183            let validator_keypair = ValidatorKeypair::generate();
1184            let account_public_key = signer.generate_new();
1185            validators.push((validator_keypair, account_public_key));
1186        }
1187        let for_committee = validators
1188            .iter()
1189            .map(|(validating, account)| (validating.public_key, *account))
1190            .collect::<Vec<_>>();
1191        let initial_committee = Committee::make_simple(for_committee);
1192        // Created up front and filled in below, so that each validator's export tasks can resolve
1193        // the others through it even though those clients do not exist yet.
1194        let node_provider = NodeProvider {
1195            clients: Arc::new(std::sync::Mutex::new(Vec::new())),
1196            failures_left: Arc::default(),
1197            builds: Arc::default(),
1198            slow_build_micros: Arc::default(),
1199            clock: Some(storage_builder.clock().clone()),
1200        };
1201        let mut validator_storages = HashMap::new();
1202        let mut validator_key_pairs = HashMap::new();
1203        let mut faulty_validators = HashSet::new();
1204        for (i, (validator_keypair, _account_public_key)) in validators.into_iter().enumerate() {
1205            let validator_public_key = validator_keypair.public_key;
1206            let storage = storage_builder.build().await?;
1207            let secret_key_copy = validator_keypair.secret_key.copy();
1208            let config = ChainWorkerConfig {
1209                nickname: format!("Node {i}"),
1210                // Export folds progress into the chain state when the worker next saves, so
1211                // give workers a lifetime instead of dropping them after every request, and fold
1212                // unthrottled so assertions see progress as it happens.
1213                ttl: chain_worker_ttl
1214                    .or_else(|| block_export.is_some().then(|| Duration::from_secs(60))),
1215                exported_heights_fold_interval: Duration::ZERO,
1216                ..ChainWorkerConfig::default()
1217            }
1218            .with_key_pair(Some(validator_keypair.secret_key));
1219            let mut state = WorkerState::new(storage.clone(), config, None);
1220            if let Some(export_config) = block_export.clone() {
1221                let handle = crate::spawn_block_export_queue(
1222                    storage.clone(),
1223                    Arc::new(node_provider.without_failure_injection()),
1224                    export_config,
1225                    Some(validator_public_key),
1226                );
1227                state = state.with_block_export(handle);
1228            }
1229            let mut validator = LocalValidatorClient::new(validator_public_key, state);
1230            if i < with_faulty_validators {
1231                faulty_validators.insert(validator_public_key);
1232                validator.set_fault_type(FaultType::NoChains);
1233            }
1234            node_provider.clients.lock().unwrap().push(validator);
1235            validator_storages.insert(validator_public_key, storage);
1236            validator_key_pairs.insert(validator_public_key, secret_key_copy);
1237        }
1238        tracing::info!(
1239            "Test will use the following faulty validators: {:?}",
1240            faulty_validators
1241        );
1242        Ok(Self {
1243            storage_builder,
1244            initial_committee,
1245            admin_description: None,
1246            network_description: None,
1247            genesis_storage_builder: GenesisStorageBuilder::default(),
1248            node_provider,
1249            validator_storages,
1250            validator_key_pairs,
1251            chain_client_storages: Vec::new(),
1252            chain_owners: BTreeMap::new(),
1253            signer,
1254        })
1255    }
1256
1257    /// Replaces the initial committee's resource control policy.
1258    pub fn with_policy(mut self, policy: ResourceControlPolicy) -> Self {
1259        let validators = self.initial_committee.validators().clone();
1260        self.initial_committee =
1261            Committee::new(validators, policy).expect("committee votes should not overflow");
1262        self
1263    }
1264
1265    /// Sets the cross-chain message chunk limit on every validator in the test setup.
1266    pub fn with_cross_chain_message_chunk_limit(self, limit: usize) -> Self {
1267        let validator_clients = self.node_provider.clients.lock().unwrap();
1268        for validator in validator_clients.iter() {
1269            let mut inner = validator.client.try_lock().expect("no contention at setup");
1270            inner.state.set_cross_chain_message_chunk_limit(limit);
1271        }
1272        drop(validator_clients);
1273        self
1274    }
1275
1276    /// Makes the next `count` attempts to build the validator set fail, so a test can
1277    /// force `update_notification_streams` to return `Err` at a chosen moment.
1278    pub fn fail_next_validator_set_builds(&self, count: usize) {
1279        self.node_provider
1280            .failures_left
1281            .store(count, std::sync::atomic::Ordering::Relaxed);
1282    }
1283
1284    /// How many of the injected validator-set-build failures are still pending, so a
1285    /// test can count how often a failing update was retried.
1286    pub fn validator_set_build_failures_left(&self) -> usize {
1287        self.node_provider
1288            .failures_left
1289            .load(std::sync::atomic::Ordering::Relaxed)
1290    }
1291
1292    /// Stalls every chain-info query at the validator at `index` until the returned guard
1293    /// is dropped, which stalls the initial sync a probe runs once `subscribe` succeeds.
1294    pub async fn stall_chain_info_queries(
1295        &self,
1296        index: usize,
1297    ) -> tokio::sync::OwnedRwLockWriteGuard<()> {
1298        let node = &self.node_provider.all_nodes()[index];
1299        node.chain_info_gate_armed
1300            .store(true, std::sync::atomic::Ordering::Relaxed);
1301        node.chain_info_gate.clone().write_owned().await
1302    }
1303
1304    /// Makes every validator-set build advance the clock, simulating a committee read slow
1305    /// enough that deadlines computed from a clock sampled BEFORE it are already elapsed.
1306    pub fn slow_validator_set_builds(&self, by: TimeDelta) {
1307        self.node_provider
1308            .slow_build_micros
1309            .store(by.as_micros(), std::sync::atomic::Ordering::Relaxed);
1310    }
1311
1312    /// Every attempt the chain clients have made to build the validator set.
1313    pub fn validator_set_builds(&self) -> usize {
1314        self.node_provider
1315            .builds
1316            .load(std::sync::atomic::Ordering::Relaxed)
1317    }
1318
1319    /// How many times the validator at `index` has been asked to `subscribe`.
1320    pub async fn subscribe_calls(&self, index: usize) -> usize {
1321        self.node_provider.all_nodes()[index]
1322            .subscribe_calls()
1323            .await
1324    }
1325
1326    /// Severs every notification stream on every validator in the test setup, as a
1327    /// fleet-wide proxy restart does. Clients keep their (now dead) ends of the streams
1328    /// until they notice and re-subscribe.
1329    pub async fn disconnect_notification_subscribers(&self) {
1330        for validator in self.node_provider.all_nodes() {
1331            validator.disconnect_notification_subscribers().await;
1332        }
1333    }
1334
1335    /// Returns the [`FaultType`] currently configured for the given validator, or `None`
1336    /// if no validator with that key is in the test setup.
1337    pub fn fault_type(&self, public_key: &ValidatorPublicKey) -> Option<FaultType> {
1338        self.node_provider
1339            .clients
1340            .lock()
1341            .unwrap()
1342            .iter()
1343            .find(|client| client.public_key == *public_key)
1344            .map(|client| client.fault_type())
1345    }
1346
1347    /// Sets the [`FaultType`] for the validators at the given indexes.
1348    pub fn set_fault_type(&mut self, indexes: impl AsRef<[usize]>, fault_type: FaultType) {
1349        let mut faulty_validators = vec![];
1350        let mut validator_clients = self.node_provider.clients.lock().unwrap();
1351        for index in indexes.as_ref() {
1352            let validator = &mut validator_clients[*index];
1353            validator.set_fault_type(fault_type);
1354            faulty_validators.push(validator.public_key);
1355        }
1356        tracing::info!(
1357            "Making the following validators {:?}: {:?}",
1358            fault_type,
1359            faulty_validators
1360        );
1361    }
1362
1363    /// Creates the root chain with the given `index`, and returns a client for it.
1364    ///
1365    /// Root chain 0 is the admin chain and needs to be initialized first, otherwise its balance
1366    /// is automatically set to zero.
1367    pub async fn add_root_chain(
1368        &mut self,
1369        index: u32,
1370        balance: Amount,
1371    ) -> anyhow::Result<ChainClient<B::Storage>> {
1372        self.add_root_chain_with_ownership(index, balance, ChainOwnership::single)
1373            .await
1374    }
1375
1376    /// Creates the root chain with the given `index` and a genesis ownership built from its
1377    /// freshly generated owner key, and returns a client for it.
1378    ///
1379    /// Root chain 0 is the admin chain and needs to be initialized first, otherwise its balance
1380    /// is automatically set to zero.
1381    pub async fn add_root_chain_with_ownership(
1382        &mut self,
1383        index: u32,
1384        balance: Amount,
1385        make_ownership: impl FnOnce(AccountOwner) -> ChainOwnership,
1386    ) -> anyhow::Result<ChainClient<B::Storage>> {
1387        // Make sure the admin chain is initialized.
1388        if self.admin_description.is_none() && index != 0 {
1389            Box::pin(self.add_root_chain(0, Amount::ZERO)).await?;
1390        }
1391        let origin = ChainOrigin::Root(index);
1392        let public_key = self.signer.generate_new();
1393        let open_chain_config = InitialChainConfig {
1394            ownership: make_ownership(public_key.into()),
1395            epoch: Epoch(0),
1396            account: AccountOwner::CHAIN,
1397            balance,
1398            application_permissions: ApplicationPermissions::default(),
1399        };
1400        let description = ChainDescription::new(origin, open_chain_config, Timestamp::from(0));
1401        let committee_blob = Blob::new_committee(bcs::to_bytes(&self.initial_committee).unwrap());
1402        if index == 0 {
1403            self.admin_description = Some(description.clone());
1404            self.network_description = Some(NetworkDescription {
1405                admin_chain_id: description.id(),
1406                // dummy values to fill the description
1407                genesis_config_hash: CryptoHash::test_hash("genesis config"),
1408                genesis_timestamp: Timestamp::from(0),
1409                genesis_committee_blob_hash: committee_blob.id().hash,
1410                name: "test network".to_string(),
1411            });
1412        }
1413        // Remember what's in the genesis store for future clients to join.
1414        self.genesis_storage_builder
1415            .add(description.clone(), public_key);
1416
1417        let network_description = self.network_description.as_ref().unwrap();
1418
1419        for validator in self.node_provider.all_nodes() {
1420            let storage = self
1421                .validator_storages
1422                .get_mut(&validator.public_key)
1423                .unwrap();
1424            storage
1425                .write_network_description(network_description)
1426                .await
1427                .expect("writing the NetworkDescription should succeed");
1428            storage
1429                .write_blob(&committee_blob)
1430                .await
1431                .expect("writing a blob should succeed");
1432            storage.create_chain(description.clone()).await.unwrap();
1433        }
1434        for storage in &mut self.chain_client_storages {
1435            storage.create_chain(description.clone()).await.unwrap();
1436        }
1437        let chain_id = description.id();
1438        self.chain_owners.insert(chain_id, public_key.into());
1439        self.make_client(chain_id, None, BlockHeight::ZERO).await
1440    }
1441
1442    /// Returns the public key and balance of each genesis root chain.
1443    pub fn genesis_chains(&self) -> Vec<(AccountPublicKey, Amount)> {
1444        let mut result = Vec::new();
1445        for (i, genesis_account) in self.genesis_storage_builder.accounts.iter().enumerate() {
1446            assert_eq!(
1447                genesis_account.description.origin(),
1448                ChainOrigin::Root(i as u32)
1449            );
1450            result.push((
1451                genesis_account.public_key,
1452                genesis_account.description.config().balance,
1453            ));
1454        }
1455        result
1456    }
1457
1458    /// Returns the admin chain's ID, panicking if it has not been initialized.
1459    pub fn admin_chain_id(&self) -> ChainId {
1460        self.admin_description
1461            .as_ref()
1462            .expect("admin chain not initialized")
1463            .id()
1464    }
1465
1466    /// Returns the admin chain description, if the admin chain has been initialized.
1467    pub fn admin_description(&self) -> Option<&ChainDescription> {
1468        self.admin_description.as_ref()
1469    }
1470
1471    /// Returns a clone of the node provider backing this test setup.
1472    pub fn make_node_provider(&self) -> NodeProvider<B::Storage> {
1473        self.node_provider.clone()
1474    }
1475
1476    /// Returns the storage of the validator at `index`, which holds every block that validator
1477    /// has processed.
1478    pub fn validator_storage(&mut self, index: usize) -> B::Storage {
1479        let public_key = self.node(index).public_key;
1480        self.validator_storages.get(&public_key).unwrap().clone()
1481    }
1482
1483    /// Returns a clone of the validator client at the given index.
1484    pub fn node(&mut self, index: usize) -> LocalValidatorClient<B::Storage> {
1485        self.node_provider.clients.lock().unwrap()[index].clone()
1486    }
1487
1488    /// Builds a fresh storage seeded with the network description and genesis chains.
1489    pub async fn make_storage(&mut self) -> anyhow::Result<B::Storage> {
1490        let storage = self.storage_builder.build().await?;
1491        let network_description = self.network_description.as_ref().unwrap();
1492        let committee_blob = Blob::new_committee(bcs::to_bytes(&self.initial_committee).unwrap());
1493        storage
1494            .write_network_description(network_description)
1495            .await
1496            .expect("writing the NetworkDescription should succeed");
1497        storage
1498            .write_blob(&committee_blob)
1499            .await
1500            .expect("writing a blob should succeed");
1501        Ok(self.genesis_storage_builder.build(storage).await)
1502    }
1503
1504    /// Creates a chain client for the given chain with the given client options.
1505    pub async fn make_client_with_options(
1506        &mut self,
1507        chain_id: ChainId,
1508        block_hash: Option<CryptoHash>,
1509        block_height: BlockHeight,
1510        options: chain_client::Options,
1511        follow_only: bool,
1512    ) -> anyhow::Result<ChainClient<B::Storage>> {
1513        // Note that new clients are only given the genesis store: they must figure out
1514        // the rest by asking validators.
1515        let storage = self.make_storage().await?;
1516        self.chain_client_storages.push(storage.clone());
1517        let mode = if follow_only {
1518            crate::client::ListeningMode::FollowChain
1519        } else {
1520            crate::client::ListeningMode::FullChain
1521        };
1522        let client = Arc::new(Client::new(
1523            crate::environment::Impl {
1524                network: self.make_node_provider(),
1525                storage,
1526                signer: self.signer.clone(),
1527                wallet: TestWallet::default(),
1528            },
1529            self.admin_chain_id(),
1530            false,
1531            [(chain_id, mode)],
1532            format!("Client node for {chain_id:.8}"),
1533            Some(Duration::from_secs(30)),
1534            Some(Duration::from_secs(1)),
1535            1000,
1536            options,
1537            DEFAULT_BLOCK_CACHE_SIZE,
1538            DEFAULT_EXECUTION_STATE_CACHE_SIZE,
1539            &crate::client::RequestsSchedulerConfig::default(),
1540        ));
1541        Ok(client.create_chain_client(
1542            chain_id,
1543            block_hash,
1544            block_height,
1545            &None,
1546            self.chain_owners.get(&chain_id).copied(),
1547            None,
1548            follow_only,
1549        ))
1550    }
1551
1552    /// Creates a chain client for the given chain with default test options.
1553    pub async fn make_client(
1554        &mut self,
1555        chain_id: ChainId,
1556        block_hash: Option<CryptoHash>,
1557        block_height: BlockHeight,
1558    ) -> anyhow::Result<ChainClient<B::Storage>> {
1559        self.make_client_with_options(
1560            chain_id,
1561            block_hash,
1562            block_height,
1563            chain_client::Options::test_default(),
1564            false,
1565        )
1566        .await
1567    }
1568
1569    /// Tries to find a (confirmation) certificate for the given chain_id and block height.
1570    pub async fn check_that_validators_have_certificate(
1571        &self,
1572        chain_id: ChainId,
1573        block_height: BlockHeight,
1574        target_count: usize,
1575    ) -> Option<ConfirmedBlockCertificate> {
1576        let query = ChainInfoQuery::new(chain_id)
1577            .with_sent_certificate_hashes_by_heights(vec![block_height]);
1578        let mut count = 0;
1579        let mut certificate = None;
1580        for validator in self.node_provider.all_nodes() {
1581            if let Ok(response) = validator.handle_chain_info_query(query.clone()).await {
1582                if response.check(validator.public_key).is_ok() {
1583                    let ChainInfo {
1584                        mut requested_sent_certificate_hashes,
1585                        ..
1586                    } = *response.info;
1587                    debug_assert!(requested_sent_certificate_hashes.len() <= 1);
1588                    if let Some(cert_hash) = requested_sent_certificate_hashes.pop() {
1589                        if let Ok(cert) = validator.download_certificate(cert_hash).await {
1590                            if cert.inner().block().header.chain_id == chain_id
1591                                && cert.inner().block().header.height == block_height
1592                            {
1593                                cert.check(&self.initial_committee).unwrap();
1594                                count += 1;
1595                                certificate = Some(cert);
1596                            }
1597                        }
1598                    }
1599                }
1600            }
1601        }
1602        assert!(count >= target_count);
1603        certificate
1604    }
1605
1606    /// Tries to find a (confirmation) certificate for the given chain_id and block height, and are
1607    /// in the expected round.
1608    pub async fn check_that_validators_are_in_round(
1609        &self,
1610        chain_id: ChainId,
1611        block_height: BlockHeight,
1612        round: Round,
1613        target_count: usize,
1614    ) {
1615        let query = ChainInfoQuery::new(chain_id);
1616        let mut count = 0;
1617        for validator in self.node_provider.all_nodes() {
1618            if let Ok(response) = validator.handle_chain_info_query(query.clone()).await {
1619                if response.info.manager.current_round == round
1620                    && response.info.next_block_height == block_height
1621                    && response.check(validator.public_key).is_ok()
1622                {
1623                    count += 1;
1624                }
1625            }
1626        }
1627        assert!(count >= target_count);
1628    }
1629
1630    /// Returns how far the validator at `index` believes it has exported the given chain to each
1631    /// of the other validators.
1632    pub async fn exported_heights(
1633        &self,
1634        index: usize,
1635        chain_id: ChainId,
1636    ) -> BTreeMap<ValidatorPublicKey, BlockHeight> {
1637        let validator = self.node_provider.all_nodes()[index].clone();
1638        let guard = validator.client.lock().await;
1639        let chain = guard.state.chain_state_view(chain_id).await.unwrap();
1640        chain.exported_heights.get().clone().into()
1641    }
1642
1643    /// Returns how many chain workers the validator at `index` currently has resident.
1644    pub async fn resident_chain_workers(&self, index: usize) -> usize {
1645        let validator = self.node_provider.all_nodes()[index].clone();
1646        let guard = validator.client.lock().await;
1647        guard.state.resident_chain_worker_count()
1648    }
1649
1650    /// Returns the next block height the validator at `index` has for the given chain.
1651    pub async fn next_block_height(&self, index: usize, chain_id: ChainId) -> BlockHeight {
1652        let validator = self.node_provider.all_nodes()[index].clone();
1653        let response = validator
1654            .handle_chain_info_query(ChainInfoQuery::new(chain_id))
1655            .await
1656            .unwrap();
1657        response.info.next_block_height
1658    }
1659
1660    /// Panics if any validator has a nonempty outbox for the given chain.
1661    pub async fn check_that_validators_have_empty_outboxes(&self, chain_id: ChainId) {
1662        for validator in self.node_provider.all_nodes() {
1663            let guard = validator.client.lock().await;
1664            let chain = guard.state.chain_state_view(chain_id).await.unwrap();
1665            assert_eq!(chain.outboxes.indices().await.unwrap(), []);
1666        }
1667    }
1668}
1669
1670#[cfg(feature = "rocksdb")]
1671/// Limit concurrency for RocksDB tests to avoid "too many open files" errors.
1672static ROCKS_DB_SEMAPHORE: Semaphore = Semaphore::const_new(5);
1673
1674/// State shared by every [`StorageBuilder`] in this module. The actual
1675/// database type varies, so `build_storage` is generic over it.
1676#[derive(Default)]
1677struct CommonStorageBuilder {
1678    namespace: String,
1679    instance_counter: usize,
1680    wasm_runtime: Option<WasmRuntime>,
1681    clock: TestClock,
1682}
1683
1684impl CommonStorageBuilder {
1685    fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1686        Self {
1687            wasm_runtime: wasm_runtime.into(),
1688            ..Self::default()
1689        }
1690    }
1691
1692    async fn build_storage<DB>(
1693        &mut self,
1694        config: DB::Config,
1695    ) -> anyhow::Result<DbStorage<DB, TestClock>>
1696    where
1697        DB: TestKeyValueDatabase + Clone + Send + Sync + 'static,
1698        DB::Store: KeyValueStore + Clone + Send + Sync + 'static,
1699        DB::Error: std::error::Error + Send + Sync + 'static,
1700    {
1701        self.instance_counter += 1;
1702        if self.namespace.is_empty() {
1703            self.namespace = generate_test_namespace();
1704        }
1705        let namespace = format!("{}_{}", self.namespace, self.instance_counter);
1706        Ok(
1707            DbStorage::new_for_testing(config, &namespace, self.wasm_runtime, self.clock.clone())
1708                .await?,
1709        )
1710    }
1711}
1712
1713/// A [`StorageBuilder`] backed by in-memory storage.
1714#[derive(Default)]
1715pub struct MemoryStorageBuilder {
1716    inner: CommonStorageBuilder,
1717}
1718
1719impl MemoryStorageBuilder {
1720    /// Creates a [`MemoryStorageBuilder`] that uses the specified [`WasmRuntime`] to run Wasm
1721    /// applications.
1722    pub fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1723        Self {
1724            inner: CommonStorageBuilder::with_wasm_runtime(wasm_runtime),
1725        }
1726    }
1727}
1728
1729#[async_trait]
1730impl StorageBuilder for MemoryStorageBuilder {
1731    type Storage = DbStorage<MemoryDatabase, TestClock>;
1732
1733    async fn build(&mut self) -> Result<Self::Storage, anyhow::Error> {
1734        let config = MemoryDatabase::new_test_config().await?;
1735        self.inner.build_storage::<MemoryDatabase>(config).await
1736    }
1737
1738    fn clock(&self) -> &TestClock {
1739        &self.inner.clock
1740    }
1741}
1742
1743#[cfg(feature = "rocksdb")]
1744/// A [`StorageBuilder`] backed by RocksDB storage.
1745pub struct RocksDbStorageBuilder {
1746    inner: CommonStorageBuilder,
1747    _permit: SemaphorePermit<'static>,
1748}
1749
1750#[cfg(feature = "rocksdb")]
1751impl RocksDbStorageBuilder {
1752    /// Creates a [`RocksDbStorageBuilder`], acquiring a concurrency permit.
1753    pub async fn new() -> Self {
1754        Self {
1755            inner: CommonStorageBuilder::default(),
1756            _permit: ROCKS_DB_SEMAPHORE.acquire().await.unwrap(),
1757        }
1758    }
1759
1760    /// Creates a [`RocksDbStorageBuilder`] that uses the specified [`WasmRuntime`] to run Wasm
1761    /// applications.
1762    #[cfg(any(feature = "wasmer", feature = "wasmtime"))]
1763    pub async fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1764        Self {
1765            inner: CommonStorageBuilder::with_wasm_runtime(wasm_runtime),
1766            _permit: ROCKS_DB_SEMAPHORE.acquire().await.unwrap(),
1767        }
1768    }
1769}
1770
1771#[cfg(feature = "rocksdb")]
1772#[async_trait]
1773impl StorageBuilder for RocksDbStorageBuilder {
1774    type Storage = DbStorage<RocksDbDatabase, TestClock>;
1775
1776    async fn build(&mut self) -> Result<Self::Storage, anyhow::Error> {
1777        let config = RocksDbDatabase::new_test_config().await?;
1778        self.inner.build_storage::<RocksDbDatabase>(config).await
1779    }
1780
1781    fn clock(&self) -> &TestClock {
1782        &self.inner.clock
1783    }
1784}
1785
1786#[cfg(all(not(target_arch = "wasm32"), feature = "storage-service"))]
1787/// A [`StorageBuilder`] backed by the storage service.
1788#[derive(Default)]
1789pub struct ServiceStorageBuilder {
1790    inner: CommonStorageBuilder,
1791}
1792
1793#[cfg(all(not(target_arch = "wasm32"), feature = "storage-service"))]
1794impl ServiceStorageBuilder {
1795    /// Creates a `ServiceStorage`.
1796    pub fn new() -> Self {
1797        Self::with_wasm_runtime(None)
1798    }
1799
1800    /// Creates a `ServiceStorage` with the given Wasm runtime.
1801    pub fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1802        Self {
1803            inner: CommonStorageBuilder::with_wasm_runtime(wasm_runtime),
1804        }
1805    }
1806}
1807
1808#[cfg(all(not(target_arch = "wasm32"), feature = "storage-service"))]
1809#[async_trait]
1810impl StorageBuilder for ServiceStorageBuilder {
1811    type Storage = DbStorage<StorageServiceDatabase, TestClock>;
1812
1813    async fn build(&mut self) -> anyhow::Result<Self::Storage> {
1814        let config = StorageServiceDatabase::new_test_config().await?;
1815        self.inner
1816            .build_storage::<StorageServiceDatabase>(config)
1817            .await
1818    }
1819
1820    fn clock(&self) -> &TestClock {
1821        &self.inner.clock
1822    }
1823}
1824
1825#[cfg(feature = "scylladb")]
1826/// A [`StorageBuilder`] backed by ScyllaDB storage.
1827#[derive(Default)]
1828pub struct ScyllaDbStorageBuilder {
1829    inner: CommonStorageBuilder,
1830}
1831
1832#[cfg(feature = "scylladb")]
1833impl ScyllaDbStorageBuilder {
1834    /// Creates a [`ScyllaDbStorageBuilder`] that uses the specified [`WasmRuntime`] to run Wasm
1835    /// applications.
1836    pub fn with_wasm_runtime(wasm_runtime: impl Into<Option<WasmRuntime>>) -> Self {
1837        Self {
1838            inner: CommonStorageBuilder::with_wasm_runtime(wasm_runtime),
1839        }
1840    }
1841}
1842
1843#[cfg(feature = "scylladb")]
1844#[async_trait]
1845impl StorageBuilder for ScyllaDbStorageBuilder {
1846    type Storage = DbStorage<ScyllaDbDatabase, TestClock>;
1847
1848    async fn build(&mut self) -> Result<Self::Storage, anyhow::Error> {
1849        let config = ScyllaDbDatabase::new_test_config().await?;
1850        self.inner.build_storage::<ScyllaDbDatabase>(config).await
1851    }
1852
1853    fn clock(&self) -> &TestClock {
1854        &self.inner.clock
1855    }
1856}
1857
1858/// Helpers for asserting on [`ClientOutcome`] results in tests.
1859pub trait ClientOutcomeResultExt<T, E> {
1860    /// Unwraps the result and panics if it's not `Committed`.
1861    /// Use this when you expect the operation to succeed without conflicts.
1862    fn unwrap_ok_committed(self) -> T;
1863
1864    /// Unwraps the result, accepting both `Committed` and `Conflict` outcomes.
1865    /// Returns the committed value or the conflicting certificate (boxed).
1866    fn unwrap_ok_or_conflict(self) -> Result<T, Box<ConfirmedBlockCertificate>>;
1867}
1868
1869impl<T, E: std::fmt::Debug> ClientOutcomeResultExt<T, E> for Result<ClientOutcome<T>, E> {
1870    fn unwrap_ok_committed(self) -> T {
1871        match self.unwrap() {
1872            ClientOutcome::Committed(t) => t,
1873            ClientOutcome::WaitForTimeout(timeout) => {
1874                panic!("unexpected timeout: {timeout}")
1875            }
1876            ClientOutcome::Conflict(certificate) => {
1877                panic!("unexpected conflict: {}", certificate.hash())
1878            }
1879        }
1880    }
1881
1882    fn unwrap_ok_or_conflict(self) -> Result<T, Box<ConfirmedBlockCertificate>> {
1883        match self.unwrap() {
1884            ClientOutcome::Committed(t) => Ok(t),
1885            ClientOutcome::Conflict(certificate) => Err(certificate),
1886            ClientOutcome::WaitForTimeout(timeout) => {
1887                panic!("unexpected timeout: {timeout}")
1888            }
1889        }
1890    }
1891}