Skip to main content

linera_core/
remote_node.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::{HashSet, VecDeque};
5
6use custom_debug_derive::Debug;
7use futures::future::try_join_all;
8use linera_base::{
9    crypto::ValidatorPublicKey,
10    data_types::{Blob, BlockHeight},
11    ensure,
12    identifiers::{BlobId, ChainId},
13};
14use linera_cache::Arc as CacheArc;
15use linera_chain::{
16    data_types::BlockProposal,
17    types::{
18        CertificateValue, Certified, ConfirmedBlockCertificate, LiteCertificate,
19        TimeoutCertificate, ValidatedBlockCertificate,
20    },
21};
22use tracing::{debug, info, instrument};
23
24use crate::{
25    data_types::{ChainInfo, ChainInfoQuery, ChainInfoResponse},
26    node::{CrossChainMessageDelivery, NodeError, ValidatorNode},
27};
28
29/// A validator node together with the validator's name.
30#[derive(Clone, Debug)]
31pub struct RemoteNode<N> {
32    /// The validator's public key, used to attribute responses and votes to it.
33    pub public_key: ValidatorPublicKey,
34    /// The client used to send requests to this validator.
35    #[debug(skip)]
36    pub node: N,
37}
38
39impl<N: ValidatorNode> RemoteNode<N> {
40    pub(crate) async fn handle_chain_info_query(
41        &self,
42        query: ChainInfoQuery,
43    ) -> Result<Box<ChainInfo>, NodeError> {
44        let chain_id = query.chain_id;
45        let response = self.node.handle_chain_info_query(query).await?;
46        self.check_and_return_info(response, chain_id)
47    }
48
49    #[instrument(level = "trace")]
50    pub(crate) async fn handle_block_proposal(
51        &self,
52        proposal: Box<BlockProposal>,
53    ) -> Result<Box<ChainInfo>, NodeError> {
54        let chain_id = proposal.content.block.chain_id;
55        let response = self.node.handle_block_proposal(*proposal).await?;
56        self.check_and_return_info(response, chain_id)
57    }
58
59    pub(crate) async fn handle_timeout_certificate(
60        &self,
61        certificate: TimeoutCertificate,
62    ) -> Result<Box<ChainInfo>, NodeError> {
63        let chain_id = certificate.inner().chain_id();
64        let response = self.node.handle_timeout_certificate(certificate).await?;
65        self.check_and_return_info(response, chain_id)
66    }
67
68    pub(crate) async fn handle_confirmed_certificate(
69        &self,
70        certificate: CacheArc<ConfirmedBlockCertificate>,
71        delivery: CrossChainMessageDelivery,
72    ) -> Result<Box<ChainInfo>, NodeError> {
73        let chain_id = certificate.inner().chain_id();
74        let response = self
75            .node
76            .handle_confirmed_certificate(certificate, delivery)
77            .await?;
78        self.check_and_return_info(response, chain_id)
79    }
80
81    pub(crate) async fn handle_validated_certificate(
82        &self,
83        certificate: ValidatedBlockCertificate,
84    ) -> Result<Box<ChainInfo>, NodeError> {
85        let chain_id = certificate.inner().chain_id();
86        let response = self.node.handle_validated_certificate(certificate).await?;
87        self.check_and_return_info(response, chain_id)
88    }
89
90    #[instrument(level = "trace")]
91    pub(crate) async fn handle_lite_certificate(
92        &self,
93        certificate: LiteCertificate<'_>,
94        delivery: CrossChainMessageDelivery,
95    ) -> Result<Box<ChainInfo>, NodeError> {
96        let chain_id = certificate.value.chain_id;
97        let response = self
98            .node
99            .handle_lite_certificate(certificate, delivery)
100            .await?;
101        self.check_and_return_info(response, chain_id)
102    }
103
104    pub(crate) async fn handle_optimized_validated_certificate(
105        &self,
106        certificate: &ValidatedBlockCertificate,
107        delivery: CrossChainMessageDelivery,
108    ) -> Result<Box<ChainInfo>, NodeError> {
109        if let Some(result) = self.try_lite_certificate(certificate, delivery).await {
110            return result;
111        }
112        self.handle_validated_certificate(certificate.clone()).await
113    }
114
115    /// Sends a confirmed certificate, preferring its compact lite form when this validator
116    /// signed it, and falling back to the full certificate otherwise.
117    pub async fn handle_optimized_confirmed_certificate(
118        &self,
119        certificate: &CacheArc<ConfirmedBlockCertificate>,
120        delivery: CrossChainMessageDelivery,
121    ) -> Result<Box<ChainInfo>, NodeError> {
122        let cert: &ConfirmedBlockCertificate = certificate;
123        if let Some(result) = self.try_lite_certificate(cert, delivery).await {
124            return result;
125        }
126        self.handle_confirmed_certificate(certificate.clone(), delivery)
127            .await
128    }
129
130    /// Tries to send a lite certificate if this validator signed it. Returns `Some` on
131    /// success or non-recoverable error, `None` if the full certificate should be sent.
132    async fn try_lite_certificate<C: Certified>(
133        &self,
134        certificate: &C,
135        delivery: CrossChainMessageDelivery,
136    ) -> Option<Result<Box<ChainInfo>, NodeError>> {
137        if !certificate.is_signed_by(&self.public_key) {
138            return None;
139        }
140        let result = self
141            .handle_lite_certificate(certificate.lite_certificate(), delivery)
142            .await;
143        match result {
144            Err(NodeError::MissingCertificateValue) => {
145                debug!(
146                    address = self.address(),
147                    certificate_hash = %certificate.hash(),
148                    kind = ?C::Value::KIND,
149                    "validator forgot a certificate value that they signed before",
150                );
151                None
152            }
153            other => Some(other),
154        }
155    }
156
157    fn check_and_return_info(
158        &self,
159        response: ChainInfoResponse,
160        chain_id: ChainId,
161    ) -> Result<Box<ChainInfo>, NodeError> {
162        let manager = &response.info.manager;
163        let proposed = manager.requested_proposed.as_ref();
164        let locking = manager.requested_locking.as_ref();
165        ensure!(
166            proposed.is_none_or(|proposal| proposal.content.block.chain_id == chain_id)
167                && locking.is_none_or(|cert| cert.chain_id() == chain_id)
168                && response.check(self.public_key).is_ok(),
169            NodeError::InvalidChainInfoResponse
170        );
171        Ok(response.info)
172    }
173
174    #[instrument(level = "trace")]
175    pub(crate) async fn download_certificate_for_blob(
176        &self,
177        blob_id: BlobId,
178    ) -> Result<ConfirmedBlockCertificate, NodeError> {
179        let certificate = self.node.blob_last_used_by_certificate(blob_id).await?;
180        if !certificate.block().requires_or_creates_blob(&blob_id) {
181            info!(
182                address = self.address(),
183                %blob_id,
184                "got invalid last used by certificate for blob from validator",
185            );
186            return Err(NodeError::InvalidCertificateForBlob(blob_id));
187        }
188        Ok(certificate)
189    }
190
191    /// Sends a pending validated block's blobs to the validator.
192    #[instrument(level = "trace")]
193    pub(crate) async fn send_pending_blobs(
194        &self,
195        chain_id: ChainId,
196        blobs: Vec<Blob>,
197    ) -> Result<(), NodeError> {
198        let tasks = blobs
199            .into_iter()
200            .map(|blob| self.node.handle_pending_blob(chain_id, blob.into_content()));
201        try_join_all(tasks).await?;
202        Ok(())
203    }
204
205    /// Downloads a blob from this validator, returning `None` if it does not have it.
206    ///
207    /// The response is rejected if its hash does not match the requested ID.
208    #[instrument(level = "trace")]
209    pub async fn download_blob(&self, blob_id: BlobId) -> Result<Option<Blob>, NodeError> {
210        match self.node.download_blob(blob_id).await {
211            Ok(blob) => {
212                let blob = Blob::new(blob);
213                if blob.id() != blob_id {
214                    tracing::info!(
215                        address = self.address(),
216                        %blob_id,
217                        "validator sent an invalid blob.",
218                    );
219                    Ok(None)
220                } else {
221                    Ok(Some(blob))
222                }
223            }
224            Err(NodeError::BlobsNotFound(_error)) => {
225                tracing::debug!(
226                    ?blob_id,
227                    address = self.address(),
228                    "validator is missing the blob",
229                );
230                Ok(None)
231            }
232            Err(error) => Err(error),
233        }
234    }
235
236    /// Streams a batch of blobs from the validator. Each yielded item is
237    /// a `Result<Blob, NodeError>` — the caller can drive the stream incrementally
238    /// and, on error, track which blob IDs still need to be fetched.
239    #[instrument(level = "trace")]
240    pub async fn download_blobs(
241        &self,
242        blob_ids: Vec<BlobId>,
243    ) -> Result<crate::node::BlobStream, NodeError> {
244        self.node.download_blobs(blob_ids).await
245    }
246
247    /// Downloads a list of certificates from the given chain.
248    #[instrument(level = "trace")]
249    pub async fn download_certificates_by_heights(
250        &self,
251        chain_id: ChainId,
252        heights: Vec<BlockHeight>,
253    ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError> {
254        let mut expected_heights = VecDeque::from(heights.clone());
255        let certificates = self
256            .node
257            .download_certificates_by_heights(chain_id, heights)
258            .await?;
259
260        if certificates.len() > expected_heights.len() {
261            return Err(NodeError::TooManyCertificatesReturned {
262                chain_id,
263                remote_node: Box::new(self.public_key),
264            });
265        }
266
267        for certificate in &certificates {
268            ensure!(
269                certificate.inner().chain_id() == chain_id,
270                NodeError::UnexpectedCertificateValue
271            );
272            if let Some(expected_height) = expected_heights.pop_front() {
273                ensure!(
274                    expected_height == certificate.inner().height(),
275                    NodeError::UnexpectedCertificateValue
276                );
277            } else {
278                return Err(NodeError::UnexpectedCertificateValue);
279            }
280        }
281
282        ensure!(
283            expected_heights.is_empty(),
284            NodeError::MissingCertificatesByHeights {
285                chain_id,
286                heights: expected_heights.into_iter().collect(),
287            }
288        );
289        Ok(certificates)
290    }
291
292    /// Checks that requesting these blobs when trying to handle this certificate is legitimate,
293    /// i.e. that there are no duplicates and the blobs are actually required.
294    pub fn check_blobs_not_found<C: Certified>(
295        &self,
296        certificate: &C,
297        blob_ids: &[BlobId],
298    ) -> Result<(), NodeError> {
299        ensure!(!blob_ids.is_empty(), NodeError::EmptyBlobsNotFound);
300        let required = certificate.value().required_blob_ids();
301        for blob_id in blob_ids {
302            if !required.contains(blob_id) {
303                info!(
304                    address = self.address(),
305                    %blob_id,
306                    "validator requested blob but it is not required",
307                );
308                return Err(NodeError::UnexpectedEntriesInBlobsNotFound);
309            }
310        }
311        let unique_missing_blob_ids = blob_ids.iter().copied().collect::<HashSet<_>>();
312        if blob_ids.len() > unique_missing_blob_ids.len() {
313            info!(
314                address = self.address(),
315                "blobs requested by validator contain duplicates",
316            );
317            return Err(NodeError::DuplicatesInBlobsNotFound);
318        }
319        Ok(())
320    }
321
322    /// Returns the validator's URL.
323    pub fn address(&self) -> String {
324        self.node.address()
325    }
326}
327
328impl<N: ValidatorNode> PartialEq for RemoteNode<N> {
329    fn eq(&self, other: &Self) -> bool {
330        self.public_key == other.public_key
331    }
332}
333
334impl<N: ValidatorNode> Eq for RemoteNode<N> {}