Skip to content

Commit 3255a2b

Browse files
committed
PublishBlob
1 parent 34a783d commit 3255a2b

35 files changed

+835
-165
lines changed

CLI.md

+15
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ This document contains the help content for the `linera` command-line program.
2727
* [`linera service`](#linera-service)
2828
* [`linera faucet`](#linera-faucet)
2929
* [`linera publish-bytecode`](#linera-publish-bytecode)
30+
* [`linera publish-blob`](#linera-publish-blob)
3031
* [`linera create-application`](#linera-create-application)
3132
* [`linera publish-and-create`](#linera-publish-and-create)
3233
* [`linera request-application`](#linera-request-application)
@@ -77,6 +78,7 @@ A Byzantine-fault tolerant sidechain with low-latency finality and high throughp
7778
* `service` — Run a GraphQL service to explore and extend the chains of the wallet
7879
* `faucet` — Run a GraphQL service that exposes a faucet where users can claim tokens. This gives away the chain's tokens, and is mainly intended for testing
7980
* `publish-bytecode` — Publish bytecode
81+
* `publish-blob` — Publish a blob of binary data
8082
* `create-application` — Create an application
8183
* `publish-and-create` — Create an application, and publish the required bytecode
8284
* `request-application` — Request an application from another chain, so it can be used on this one
@@ -553,6 +555,19 @@ Publish bytecode
553555

554556

555557

558+
## `linera publish-blob`
559+
560+
Publish a blob of binary data
561+
562+
**Usage:** `linera publish-blob <BLOB_PATH> [PUBLISHER]`
563+
564+
###### **Arguments:**
565+
566+
* `<BLOB_PATH>` — Path to blob file to be published
567+
* `<PUBLISHER>` — An optional chain ID to publish the blob. The default chain of the wallet is used otherwise
568+
569+
570+
556571
## `linera create-application`
557572

558573
Create an application

linera-base/src/crypto.rs

+5
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,11 @@ impl CryptoHash {
427427
CryptoHash(hasher.finalize())
428428
}
429429

430+
/// Creates a hash from its raw bytes.
431+
pub fn new_from_bytes(bytes: HasherOutput) -> Self {
432+
CryptoHash(bytes)
433+
}
434+
430435
/// Reads the bytes of the hash value.
431436
pub fn as_bytes(&self) -> &HasherOutput {
432437
&self.0

linera-base/src/identifiers.rs

+51-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ use std::{
1212
use anyhow::{anyhow, Context};
1313
use linera_witty::{WitLoad, WitStore, WitType};
1414
use serde::{Deserialize, Serialize};
15+
#[cfg(with_testing)]
16+
use {
17+
proptest::{
18+
collection::{vec, VecStrategy},
19+
prelude::Arbitrary,
20+
strategy::{self, Strategy},
21+
},
22+
std::ops::RangeInclusive,
23+
};
1524

1625
use crate::{
1726
bcs_scalar,
@@ -78,7 +87,7 @@ impl Account {
7887
}
7988
}
8089

81-
impl std::fmt::Display for Account {
90+
impl Display for Account {
8291
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8392
match self.owner {
8493
Some(owner) => write!(f, "{}:{}", self.chain_id, owner),
@@ -142,20 +151,58 @@ impl ChainDescription {
142151
#[cfg_attr(with_testing, derive(Default))]
143152
pub struct ChainId(pub CryptoHash);
144153

145-
/// A blob ID.
154+
/// A content addressed blob ID i.e. the hash of the Blob.
146155
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, Serialize, Deserialize)]
147156
#[cfg_attr(with_testing, derive(Default))]
148157
pub struct BlobId(pub CryptoHash);
149158

159+
impl BlobId {
160+
/// Creates a new `BlobId` from a `Blob`
161+
pub fn new(blob: &Blob) -> Self {
162+
BlobId(CryptoHash::new(blob))
163+
}
164+
}
165+
166+
#[cfg(with_testing)]
167+
impl Arbitrary for BlobId {
168+
type Parameters = ();
169+
type Strategy = strategy::Map<VecStrategy<RangeInclusive<u8>>, fn(Vec<u8>) -> BlobId>;
170+
171+
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
172+
use generic_array::typenum::Unsigned;
173+
type HasherOutputSize = <sha3::Sha3_256 as sha3::digest::OutputSizeUser>::OutputSize;
174+
175+
vec(u8::MIN..=u8::MAX, HasherOutputSize::to_usize()).prop_map(|vector| {
176+
BlobId(CryptoHash::new_from_bytes(
177+
generic_array::GenericArray::clone_from_slice(&vector[..]),
178+
))
179+
})
180+
}
181+
}
182+
150183
/// A blob.
151-
#[derive(Serialize, Deserialize)]
184+
#[derive(Eq, PartialEq, Debug, Hash, Clone, Serialize, Deserialize)]
152185
pub struct Blob {
153186
#[serde(with = "serde_bytes")]
154187
bytes: Vec<u8>,
155188
}
156189

157190
impl BcsSignable for Blob {}
158191

192+
impl Blob {
193+
/// Loads blob from a file.
194+
pub async fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
195+
let bytes = std::fs::read(path)?;
196+
Ok(Blob { bytes })
197+
}
198+
}
199+
200+
impl Display for BlobId {
201+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202+
Display::fmt(&self.0, f)
203+
}
204+
}
205+
159206
/// The index of a message in a chain.
160207
#[derive(
161208
Eq,
@@ -803,6 +850,7 @@ doc_scalar!(
803850
doc_scalar!(AccountOwner, "An owner of an account.");
804851
doc_scalar!(Account, "An account");
805852
doc_scalar!(BlobId, "A blob id");
853+
doc_scalar!(Blob, "A blob");
806854

807855
#[cfg(test)]
808856
mod tests {

linera-chain/src/data_types.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use std::{
66
borrow::Cow,
7-
collections::{HashMap, HashSet},
7+
collections::{BTreeMap, HashMap, HashSet},
88
};
99

1010
use async_graphql::{Object, SimpleObject};
@@ -13,12 +13,13 @@ use linera_base::{
1313
data_types::{Amount, BlockHeight, OracleRecord, Round, Timestamp},
1414
doc_scalar, ensure,
1515
identifiers::{
16-
Account, ChainId, ChannelName, Destination, GenericApplicationId, MessageId, Owner,
16+
Account, Blob, BlobId, ChainId, ChannelName, Destination, GenericApplicationId, MessageId,
17+
Owner,
1718
},
1819
};
1920
use linera_execution::{
2021
committee::{Committee, Epoch, ValidatorName},
21-
BytecodeLocation, Message, MessageKind, Operation,
22+
BytecodeLocation, Message, MessageKind, Operation, SystemOperation,
2223
};
2324
use serde::{de::Deserializer, Deserialize, Serialize};
2425

@@ -78,6 +79,18 @@ impl Block {
7879
locations
7980
}
8081

82+
/// Return all the blob ids referred to in this block's operations.
83+
pub fn blob_ids(&self) -> Vec<BlobId> {
84+
let mut blob_ids = Vec::new();
85+
for operation in &self.operations {
86+
if let Operation::System(SystemOperation::PublishBlob { blob_id }) = operation {
87+
blob_ids.push(blob_id.to_owned());
88+
}
89+
}
90+
91+
blob_ids
92+
}
93+
8194
/// Returns whether the block contains only rejected incoming messages, which
8295
/// makes it admissible even on closed chains.
8396
pub fn has_only_rejected_messages(&self) -> bool {
@@ -221,6 +234,7 @@ pub struct BlockProposal {
221234
pub owner: Owner,
222235
pub signature: Signature,
223236
pub hashed_certificate_values: Vec<HashedCertificateValue>,
237+
pub blobs: BTreeMap<BlobId, Blob>,
224238
pub validated: Option<Certificate>,
225239
}
226240

@@ -800,6 +814,7 @@ impl BlockProposal {
800814
content: BlockAndRound,
801815
secret: &KeyPair,
802816
hashed_certificate_values: Vec<HashedCertificateValue>,
817+
blobs: BTreeMap<BlobId, Blob>,
803818
validated: Option<Certificate>,
804819
) -> Self {
805820
let outcome = validated
@@ -818,6 +833,7 @@ impl BlockProposal {
818833
owner: secret.public().into(),
819834
signature,
820835
hashed_certificate_values,
836+
blobs,
821837
validated,
822838
}
823839
}

linera-chain/src/manager.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ use linera_base::{
7474
crypto::{KeyPair, PublicKey},
7575
data_types::{ArithmeticError, BlockHeight, Round, Timestamp},
7676
doc_scalar, ensure,
77-
identifiers::{ChainId, Owner},
77+
identifiers::{Blob, BlobId, ChainId, Owner},
7878
ownership::ChainOwnership,
7979
};
8080
use linera_execution::committee::Epoch;
@@ -134,6 +134,8 @@ pub struct ChainManager {
134134
pub current_round: Round,
135135
/// The owners that take over in fallback mode.
136136
pub fallback_owners: BTreeMap<Owner, (PublicKey, u64)>,
137+
/// The pending blobs
138+
pub pending_blobs: BTreeMap<BlobId, Blob>,
137139
}
138140

139141
doc_scalar!(
@@ -202,6 +204,7 @@ impl ChainManager {
202204
round_timeout,
203205
current_round,
204206
fallback_owners,
207+
pending_blobs: BTreeMap::new(),
205208
})
206209
}
207210

@@ -518,6 +521,12 @@ impl ChainManager {
518521
}
519522
}
520523

524+
pub fn add_pending_blobs(&mut self, blobs: BTreeMap<BlobId, Blob>) {
525+
for (blob_id, blob) in blobs {
526+
self.pending_blobs.insert(blob_id, blob);
527+
}
528+
}
529+
521530
/// Returns the leader who is allowed to propose a block in the given round, or `None` if every
522531
/// owner is allowed to propose. Exception: In `Round::Fast`, only super owners can propose.
523532
fn round_leader(&self, round: Round) -> Option<&Owner> {

linera-chain/src/test.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
//! Test utilities
55
6+
use std::collections::BTreeMap;
7+
68
use linera_base::{
79
crypto::{CryptoHash, KeyPair},
810
data_types::{Amount, BlockHeight, Round, Timestamp},
@@ -122,7 +124,7 @@ impl BlockTestExt for Block {
122124

123125
fn into_proposal_with_round(self, key_pair: &KeyPair, round: Round) -> BlockProposal {
124126
let content = BlockAndRound { block: self, round };
125-
BlockProposal::new(content, key_pair, vec![], None)
127+
BlockProposal::new(content, key_pair, vec![], BTreeMap::new(), None)
126128
}
127129

128130
fn into_justified_proposal(
@@ -132,7 +134,7 @@ impl BlockTestExt for Block {
132134
validated: Certificate,
133135
) -> BlockProposal {
134136
let content = BlockAndRound { block: self, round };
135-
BlockProposal::new(content, key_pair, vec![], Some(validated))
137+
BlockProposal::new(content, key_pair, vec![], BTreeMap::new(), Some(validated))
136138
}
137139
}
138140

0 commit comments

Comments
 (0)