Skip to content

Commit 3a61111

Browse files
authored
Fetch locked blobs individually, not in ChainManagerInfo. (#3121)
## Motivation Ultimately we want to transfer all blobs separately, rather than in a single message. (#3048) This PR is one step towards that goal: When the client fetches the locked block from a validator, it now requests the corresponding blobs one by one, rather than all at once. ## Proposal Add a `DownloadPendingBlob` endpoint; remove the locked blobs from the `ChainManagerInfo`. ## Test Plan Existing tests exercise this scenario, e.g. `test_finalize_locked_block_with_blobs`. ## Release Plan - Nothing to do / These changes follow the usual release cycle. ## Links - Part of #3048. - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 930557b commit 3a61111

File tree

21 files changed

+384
-54
lines changed

21 files changed

+384
-54
lines changed

linera-base/src/data_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,7 @@ impl Blob {
10921092
self.content.into_bytes()
10931093
}
10941094

1095-
/// Loads data blob content from a file.
1095+
/// Loads data blob from a file.
10961096
pub async fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
10971097
Ok(Self::new_data(fs::read(path)?))
10981098
}

linera-chain/src/manager.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -624,9 +624,6 @@ pub struct ChainManagerInfo {
624624
/// The timestamp when the current round times out.
625625
#[debug(skip_if = Option::is_none)]
626626
pub round_timeout: Option<Timestamp>,
627-
/// These are blobs belonging to the locked block.
628-
#[debug(skip_if = Vec::is_empty)]
629-
pub locked_blobs: Vec<Blob>,
630627
}
631628

632629
impl From<&ChainManager> for ChainManagerInfo {
@@ -650,7 +647,6 @@ impl From<&ChainManager> for ChainManagerInfo {
650647
current_round,
651648
leader: manager.round_leader(current_round).cloned(),
652649
round_timeout: manager.round_timeout,
653-
locked_blobs: Vec::new(),
654650
}
655651
}
656652
}
@@ -660,7 +656,6 @@ impl ChainManagerInfo {
660656
pub fn add_values(&mut self, manager: &ChainManager) {
661657
self.requested_proposed = manager.proposed.clone().map(Box::new);
662658
self.requested_locked = manager.locked.clone().map(Box::new);
663-
self.locked_blobs = manager.locked_blobs.values().cloned().collect();
664659
self.requested_confirmed = manager
665660
.confirmed_vote
666661
.as_ref()

linera-core/src/chain_worker/actor.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use linera_base::{
1414
crypto::CryptoHash,
1515
data_types::{Blob, BlockHeight, Timestamp, UserApplicationDescription},
1616
hashed::Hashed,
17-
identifiers::{ChainId, UserApplicationId},
17+
identifiers::{BlobId, ChainId, UserApplicationId},
1818
};
1919
use linera_chain::{
2020
data_types::{Block, BlockProposal, ExecutedBlock, MessageBundle, Origin, Target},
@@ -142,6 +142,13 @@ where
142142
callback: oneshot::Sender<Result<(ChainInfoResponse, NetworkActions), WorkerError>>,
143143
},
144144

145+
/// Get a blob if it belongs to the current locked block or pending proposal.
146+
DownloadPendingBlob {
147+
blob_id: BlobId,
148+
#[debug(skip)]
149+
callback: oneshot::Sender<Result<Blob, WorkerError>>,
150+
},
151+
145152
/// Update the received certificate trackers to at least the given values.
146153
UpdateReceivedCertificateTrackers {
147154
new_trackers: BTreeMap<ValidatorName, u64>,
@@ -330,6 +337,9 @@ where
330337
ChainWorkerRequest::HandleChainInfoQuery { query, callback } => callback
331338
.send(self.worker.handle_chain_info_query(query).await)
332339
.is_ok(),
340+
ChainWorkerRequest::DownloadPendingBlob { blob_id, callback } => callback
341+
.send(self.worker.download_pending_blob(blob_id).await)
342+
.is_ok(),
333343
ChainWorkerRequest::UpdateReceivedCertificateTrackers {
334344
new_trackers,
335345
callback,

linera-core/src/chain_worker/state/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,18 @@ where
294294
Ok((response, actions))
295295
}
296296

297+
/// Returns the requested blob, if it belongs to the current locked block or pending proposal.
298+
pub(super) async fn download_pending_blob(&self, blob_id: BlobId) -> Result<Blob, WorkerError> {
299+
let manager = self.chain.manager.get();
300+
manager
301+
.proposed
302+
.as_ref()
303+
.and_then(|proposal| proposal.blobs.iter().find(|blob| blob.id() == blob_id))
304+
.or_else(|| manager.locked_blobs.get(&blob_id))
305+
.cloned()
306+
.ok_or_else(|| WorkerError::BlobsNotFound(vec![blob_id]))
307+
}
308+
297309
/// Ensures that the current chain is active, returning an error otherwise.
298310
fn ensure_is_active(&mut self) -> Result<(), WorkerError> {
299311
if !self.knows_chain_is_active {

linera-core/src/client/mod.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,8 +1766,7 @@ where
17661766
}
17671767
if let Some(cert) = info.manager.requested_locked {
17681768
let hash = cert.hash();
1769-
let blobs = info.manager.locked_blobs.clone();
1770-
if let Err(err) = self.process_certificate(*cert.clone(), blobs).await {
1769+
if let Err(err) = self.try_process_locked_block_from(remote_node, cert).await {
17711770
warn!(
17721771
"Skipping certificate {hash} from validator {}: {err}",
17731772
remote_node.name
@@ -1777,6 +1776,30 @@ where
17771776
Ok(())
17781777
}
17791778

1779+
async fn try_process_locked_block_from(
1780+
&self,
1781+
remote_node: &RemoteNode<P::Node>,
1782+
certificate: Box<GenericCertificate<ValidatedBlock>>,
1783+
) -> Result<(), ChainClientError> {
1784+
let chain_id = certificate.inner().chain_id();
1785+
match self.process_certificate(*certificate.clone(), vec![]).await {
1786+
Err(LocalNodeError::BlobsNotFound(blob_ids)) => {
1787+
let mut blobs = Vec::new();
1788+
for blob_id in blob_ids {
1789+
let blob_content = remote_node
1790+
.node
1791+
.download_pending_blob(chain_id, blob_id)
1792+
.await?;
1793+
blobs.push(Blob::new(blob_content));
1794+
}
1795+
self.process_certificate(*certificate, blobs).await?;
1796+
Ok(())
1797+
}
1798+
Err(err) => Err(err.into()),
1799+
Ok(()) => Ok(()),
1800+
}
1801+
}
1802+
17801803
/// Downloads and processes from the specified validator a confirmed block certificates that
17811804
/// use the given blobs. If this succeeds, the blob will be in our storage.
17821805
async fn update_local_node_with_blobs_from(

linera-core/src/node.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,20 @@ pub trait ValidatorNode {
102102
/// Subscribes to receiving notifications for a collection of chains.
103103
async fn subscribe(&self, chains: Vec<ChainId>) -> Result<Self::NotificationStream, NodeError>;
104104

105-
// Uploads a blob content. Returns an error if the validator has not seen a
105+
// Uploads a blob. Returns an error if the validator has not seen a
106106
// certificate using this blob.
107107
async fn upload_blob(&self, content: BlobContent) -> Result<BlobId, NodeError>;
108108

109+
/// Downloads a blob. Returns an error if the validator does not have the blob.
109110
async fn download_blob(&self, blob_id: BlobId) -> Result<BlobContent, NodeError>;
110111

112+
/// Downloads a blob that belongs to a pending proposal or the locked block on a chain.
113+
async fn download_pending_blob(
114+
&self,
115+
chain_id: ChainId,
116+
blob_id: BlobId,
117+
) -> Result<BlobContent, NodeError>;
118+
111119
async fn download_certificate(
112120
&self,
113121
hash: CryptoHash,

linera-core/src/unit_tests/test_utils.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,17 @@ where
189189
.await
190190
}
191191

192+
async fn download_pending_blob(
193+
&self,
194+
chain_id: ChainId,
195+
blob_id: BlobId,
196+
) -> Result<BlobContent, NodeError> {
197+
self.spawn_and_receive(move |validator, sender| {
198+
validator.do_download_pending_blob(chain_id, blob_id, sender)
199+
})
200+
.await
201+
}
202+
192203
async fn download_certificate(
193204
&self,
194205
hash: CryptoHash,
@@ -490,6 +501,21 @@ where
490501
sender.send(blob.map(|blob| blob.into_content()))
491502
}
492503

504+
async fn do_download_pending_blob(
505+
self,
506+
chain_id: ChainId,
507+
blob_id: BlobId,
508+
sender: oneshot::Sender<Result<BlobContent, NodeError>>,
509+
) -> Result<(), Result<BlobContent, NodeError>> {
510+
let validator = self.client.lock().await;
511+
let result = validator
512+
.state
513+
.download_pending_blob(chain_id, blob_id)
514+
.await
515+
.map_err(Into::into);
516+
sender.send(result.map(|blob| blob.into_content()))
517+
}
518+
493519
async fn do_download_certificate(
494520
self,
495521
hash: CryptoHash,

linera-core/src/worker.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,32 @@ where
923923
result
924924
}
925925

926+
#[instrument(skip_all, fields(
927+
nick = self.nickname,
928+
chain_id = format!("{:.8}", chain_id)
929+
))]
930+
pub async fn download_pending_blob(
931+
&self,
932+
chain_id: ChainId,
933+
blob_id: BlobId,
934+
) -> Result<Blob, WorkerError> {
935+
trace!(
936+
"{} <-- download_pending_blob({chain_id:8}, {blob_id:8})",
937+
self.nickname
938+
);
939+
let result = self
940+
.query_chain_worker(chain_id, move |callback| {
941+
ChainWorkerRequest::DownloadPendingBlob { blob_id, callback }
942+
})
943+
.await;
944+
trace!(
945+
"{} --> {:?}",
946+
self.nickname,
947+
result.as_ref().map(|_| blob_id)
948+
);
949+
result
950+
}
951+
926952
#[instrument(skip_all, fields(
927953
nick = self.nickname,
928954
chain_id = format!("{:.8}", request.target_chain_id())

linera-execution/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ pub trait BaseRuntime {
618618
/// owner, not a super owner.
619619
fn assert_before(&mut self, timestamp: Timestamp) -> Result<(), ExecutionError>;
620620

621-
/// Reads a data blob content specified by a given hash.
621+
/// Reads a data blob specified by a given hash.
622622
fn read_data_blob(&mut self, hash: &CryptoHash) -> Result<Vec<u8>, ExecutionError>;
623623

624624
/// Asserts the existence of a data blob with the given hash.

linera-rpc/proto/rpc.proto

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ service ValidatorWorker {
3131
// Handle information queries for this chain.
3232
rpc HandleChainInfoQuery(ChainInfoQuery) returns (ChainInfoResult);
3333

34+
// Download a blob that belongs to a pending block on the given chain.
35+
rpc DownloadPendingBlob(PendingBlobRequest) returns (PendingBlobResult);
36+
3437
// Handle a (trusted!) cross-chain request.
3538
rpc HandleCrossChainRequest(CrossChainRequest) returns (google.protobuf.Empty);
3639
}
@@ -61,23 +64,26 @@ service ValidatorNode {
6164
// Request the genesis configuration hash of the network this node is part of.
6265
rpc GetGenesisConfigHash(google.protobuf.Empty) returns (CryptoHash);
6366

64-
// Downloads a blob content.
67+
// Download a blob.
6568
rpc DownloadBlob(BlobId) returns (BlobContent);
6669

67-
// Uploads a blob content. Returns an error if the validator has not seen a
70+
// Download a blob that belongs to a pending block on the given chain.
71+
rpc DownloadPendingBlob(PendingBlobRequest) returns (PendingBlobResult);
72+
73+
// Upload a blob. Returns an error if the validator has not seen a
6874
// certificate using this blob.
6975
rpc UploadBlob(BlobContent) returns (BlobId);
7076

71-
// Downloads a certificate.
77+
// Download a certificate.
7278
rpc DownloadCertificate(CryptoHash) returns (Certificate);
7379

74-
// Downloads a batch of certificates.
80+
// Download a batch of certificates.
7581
rpc DownloadCertificates(CertificatesBatchRequest) returns (CertificatesBatchResponse);
7682

77-
// Returns the hash of the `Certificate` that last used a blob.
83+
// Return the hash of the `Certificate` that last used a blob.
7884
rpc BlobLastUsedBy(BlobId) returns (CryptoHash);
7985

80-
// Returns the `BlobId`s that are not contained as `Blob`.
86+
// Return the `BlobId`s that are not contained as `Blob`.
8187
rpc MissingBlobIds(BlobIds) returns (BlobIds);
8288
}
8389

@@ -263,6 +269,21 @@ message HandleConfirmedCertificateRequest {
263269
bool wait_for_outgoing_messages = 3;
264270
}
265271

272+
// A request for a pending blob.
273+
message PendingBlobRequest {
274+
ChainId chain_id = 1;
275+
BlobId blob_id = 2;
276+
}
277+
278+
// A requested pending blob, or an error.
279+
message PendingBlobResult {
280+
oneof inner {
281+
BlobContent blob = 1;
282+
// a bincode wrapper around `NodeError`
283+
bytes error = 2;
284+
}
285+
}
286+
266287
// A certified statement from the committee.
267288
message Certificate {
268289
// The certified value

0 commit comments

Comments
 (0)