Skip to content

Commit

Permalink
Ensure only data blobs are present in SDK/System API calls (#2368)
Browse files Browse the repository at this point in the history
## Motivation

We don't want `BlobId` exposed to the SDK. Users will only be aware of `Data` blobs, the other types will be only internally used.

## Proposal

Only expose `Data` blobs to the SDK, and assume when dealing with blobs that it's a `Data` blob. Because of that we don't need to use `BlobId` everywhere, and can just use the hash of the `Data` blob in most places.

## Test Plan

CI + also retested the NFT UI
  • Loading branch information
andresilva91 authored Aug 13, 2024
1 parent 381e597 commit bdba50e
Show file tree
Hide file tree
Showing 21 changed files with 210 additions and 288 deletions.
15 changes: 9 additions & 6 deletions examples/non-fungible/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ linera service --port $PORT &

- Navigate to `http://localhost:8080/`.
- To publish a blob, run the mutation:

```gql,uri=http://localhost:8080/
mutation {
publishDataBlob(
Expand All @@ -99,23 +100,22 @@ linera service --port $PORT &

- Navigate to `http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID`.
- To mint an NFT, run the mutation:

```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
mutation {
mint(
minter: "User:7136460f0c87ae46f966f898d494c4b40c4ae8c527f4d1c0b1fa0f7cff91d20f",
name: "nft1",
blobId: {
hash: "34a20da0fdd7e24ddbff60cb7f952b053b3f0e196e622d47c3a368f690f01326",
blob_type: "Data"
}
blobHash: "34a20da0fdd7e24ddbff60cb7f952b053b3f0e196e622d47c3a368f690f01326",
)
}
```

- To check that it's there, run the query:

```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
query {
nft(tokenId: "o66hIR1qQ8oUoTZTKHK35Cg1ObRgYSpBJnnS2gyvGBQ") {
nft(tokenId: "iQe01ZJeKo8E7HPr+MfwFedBZMdZ2v3UDI0F0kHMY+A") {
tokenId,
owner,
name,
Expand All @@ -126,25 +126,28 @@ linera service --port $PORT &
```

- To check that it's assigned to the owner, run the query:

```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
query {
ownedNfts(owner: "User:7136460f0c87ae46f966f898d494c4b40c4ae8c527f4d1c0b1fa0f7cff91d20f")
}
```

- To check everything that it's there, run the query:

```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
query {
nfts
}
```

- To transfer the NFT to user `$OWNER_2`, still on chain `$CHAIN_1`, run the mutation:

```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
mutation {
transfer(
sourceOwner: "User:7136460f0c87ae46f966f898d494c4b40c4ae8c527f4d1c0b1fa0f7cff91d20f",
tokenId: "o66hIR1qQ8oUoTZTKHK35Cg1ObRgYSpBJnnS2gyvGBQ",
tokenId: "iQe01ZJeKo8E7HPr+MfwFedBZMdZ2v3UDI0F0kHMY+A",
targetAccount: {
chainId: "e476187f6ddfeb9d588c7b45d3df334d5501d6499b3f9ad5595cae86cce16a65",
owner: "User:598d18f67709fe76ed6a36b75a7c9889012d30b896800dfd027ee10e1afd49a3"
Expand Down
16 changes: 8 additions & 8 deletions examples/non-fungible/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use std::collections::BTreeSet;

use fungible::Account;
use linera_sdk::{
base::{AccountOwner, BlobId, WithContractAbi},
base::{AccountOwner, WithContractAbi},
views::{RootView, View, ViewStorageContext},
Contract, ContractRuntime,
Contract, ContractRuntime, DataBlobHash,
};
use non_fungible::{Message, Nft, NonFungibleTokenAbi, Operation, TokenId};

Expand Down Expand Up @@ -51,10 +51,10 @@ impl Contract for NonFungibleTokenContract {
Operation::Mint {
minter,
name,
blob_id,
blob_hash,
} => {
self.check_account_authentication(minter);
self.mint(minter, name, blob_id).await;
self.mint(minter, name, blob_hash).await;
}

Operation::Transfer {
Expand Down Expand Up @@ -176,14 +176,14 @@ impl NonFungibleTokenContract {
.expect("NFT {token_id} not found")
}

async fn mint(&mut self, owner: AccountOwner, name: String, blob_id: BlobId) {
self.runtime.assert_blob_exists(blob_id);
async fn mint(&mut self, owner: AccountOwner, name: String, blob_hash: DataBlobHash) {
self.runtime.assert_data_blob_exists(blob_hash);
let token_id = Nft::create_token_id(
&self.runtime.chain_id(),
&self.runtime.application_id().forget_abi(),
&name,
&owner,
&blob_id,
&blob_hash,
*self.state.num_minted_nfts.get(),
)
.expect("Failed to serialize NFT metadata");
Expand All @@ -193,7 +193,7 @@ impl NonFungibleTokenContract {
owner,
name,
minter: owner,
blob_id,
blob_hash,
})
.await;

Expand Down
27 changes: 15 additions & 12 deletions examples/non-fungible/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ linera service --port $PORT &
- Navigate to `http://localhost:8080/`.
- To publish a blob, run the mutation:
```gql,uri=http://localhost:8080/
mutation {
publishDataBlob(
Expand All @@ -103,23 +104,22 @@ linera service --port $PORT &
- Navigate to `http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID`.
- To mint an NFT, run the mutation:
```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
mutation {
mint(
minter: "User:7136460f0c87ae46f966f898d494c4b40c4ae8c527f4d1c0b1fa0f7cff91d20f",
name: "nft1",
blobId: {
hash: "34a20da0fdd7e24ddbff60cb7f952b053b3f0e196e622d47c3a368f690f01326",
blob_type: "Data"
}
blobHash: "34a20da0fdd7e24ddbff60cb7f952b053b3f0e196e622d47c3a368f690f01326",
)
}
```
- To check that it's there, run the query:
```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
query {
nft(tokenId: "o66hIR1qQ8oUoTZTKHK35Cg1ObRgYSpBJnnS2gyvGBQ") {
nft(tokenId: "iQe01ZJeKo8E7HPr+MfwFedBZMdZ2v3UDI0F0kHMY+A") {
tokenId,
owner,
name,
Expand All @@ -130,25 +130,28 @@ linera service --port $PORT &
```
- To check that it's assigned to the owner, run the query:
```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
query {
ownedNfts(owner: "User:7136460f0c87ae46f966f898d494c4b40c4ae8c527f4d1c0b1fa0f7cff91d20f")
}
```
- To check everything that it's there, run the query:
```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
query {
nfts
}
```
- To transfer the NFT to user `$OWNER_2`, still on chain `$CHAIN_1`, run the mutation:
```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
mutation {
transfer(
sourceOwner: "User:7136460f0c87ae46f966f898d494c4b40c4ae8c527f4d1c0b1fa0f7cff91d20f",
tokenId: "o66hIR1qQ8oUoTZTKHK35Cg1ObRgYSpBJnnS2gyvGBQ",
tokenId: "iQe01ZJeKo8E7HPr+MfwFedBZMdZ2v3UDI0F0kHMY+A",
targetAccount: {
chainId: "e476187f6ddfeb9d588c7b45d3df334d5501d6499b3f9ad5595cae86cce16a65",
owner: "User:598d18f67709fe76ed6a36b75a7c9889012d30b896800dfd027ee10e1afd49a3"
Expand Down Expand Up @@ -182,9 +185,9 @@ use std::fmt::{Display, Formatter};
use async_graphql::{InputObject, Request, Response, SimpleObject};
use fungible::Account;
use linera_sdk::{
base::{AccountOwner, ApplicationId, BlobId, ChainId, ContractAbi, ServiceAbi},
base::{AccountOwner, ApplicationId, ChainId, ContractAbi, ServiceAbi},
graphql::GraphQLMutationRoot,
ToBcsBytes,
DataBlobHash, ToBcsBytes,
};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -215,7 +218,7 @@ pub enum Operation {
Mint {
minter: AccountOwner,
name: String,
blob_id: BlobId,
blob_hash: DataBlobHash,
},
/// Transfers a token from a (locally owned) account to a (possibly remote) account.
Transfer {
Expand Down Expand Up @@ -255,7 +258,7 @@ pub struct Nft {
pub owner: AccountOwner,
pub name: String,
pub minter: AccountOwner,
pub blob_id: BlobId,
pub blob_hash: DataBlobHash,
}

#[derive(Debug, Serialize, Deserialize, Clone, SimpleObject, PartialEq, Eq)]
Expand Down Expand Up @@ -304,7 +307,7 @@ impl Nft {
application_id: &ApplicationId,
name: &String,
minter: &AccountOwner,
blob_id: &BlobId,
blob_hash: &DataBlobHash,
num_minted_nfts: u64,
) -> Result<TokenId, bcs::Error> {
use sha3::Digest as _;
Expand All @@ -315,7 +318,7 @@ impl Nft {
hasher.update(name);
hasher.update(name.len().to_bcs_bytes()?);
hasher.update(minter.to_bcs_bytes()?);
hasher.update(blob_id.to_bcs_bytes()?);
hasher.update(blob_hash.to_bcs_bytes()?);
hasher.update(num_minted_nfts.to_bcs_bytes()?);

Ok(TokenId {
Expand Down
14 changes: 7 additions & 7 deletions examples/non-fungible/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use async_graphql::{EmptySubscription, Object, Request, Response, Schema};
use base64::engine::{general_purpose::STANDARD_NO_PAD, Engine as _};
use fungible::Account;
use linera_sdk::{
base::{AccountOwner, BlobId, WithServiceAbi},
base::{AccountOwner, WithServiceAbi},
views::{View, ViewStorageContext},
Service, ServiceRuntime,
DataBlobHash, Service, ServiceRuntime,
};
use non_fungible::{NftOutput, Operation, TokenId};

Expand Down Expand Up @@ -82,7 +82,7 @@ impl QueryRoot {
.runtime
.try_lock()
.expect("Services only run in a single thread");
runtime.read_blob(nft.blob_id).into_inner().bytes
runtime.read_data_blob(nft.blob_hash)
};
let nft_output = NftOutput::new_with_token_id(token_id, nft, payload);
Some(nft_output)
Expand All @@ -101,7 +101,7 @@ impl QueryRoot {
.runtime
.try_lock()
.expect("Services only run in a single thread");
runtime.read_blob(nft.blob_id).into_inner().bytes
runtime.read_data_blob(nft.blob_hash)
};
let nft_output = NftOutput::new(nft, payload);
nfts.insert(nft_output.token_id.clone(), nft_output);
Expand Down Expand Up @@ -166,7 +166,7 @@ impl QueryRoot {
.runtime
.try_lock()
.expect("Services only run in a single thread");
runtime.read_blob(nft.blob_id).into_inner().bytes
runtime.read_data_blob(nft.blob_hash)
};
let nft_output = NftOutput::new(nft, payload);
result.insert(nft_output.token_id.clone(), nft_output);
Expand All @@ -180,11 +180,11 @@ struct MutationRoot;

#[Object]
impl MutationRoot {
async fn mint(&self, minter: AccountOwner, name: String, blob_id: BlobId) -> Vec<u8> {
async fn mint(&self, minter: AccountOwner, name: String, blob_hash: DataBlobHash) -> Vec<u8> {
bcs::to_bytes(&Operation::Mint {
minter,
name,
blob_id,
blob_hash,
})
.unwrap()
}
Expand Down
26 changes: 18 additions & 8 deletions examples/non-fungible/web-frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ const GET_OWNED_NFTS = gql`
`;

const MINT_NFT = gql`
mutation Mint($minter: AccountOwner!, $name: String!, $blobId: BlobId!) {
mint(minter: $minter, name: $name, blobId: $blobId)
mutation Mint(
$minter: AccountOwner!
$name: String!
$blobHash: CryptoHash!
) {
mint(minter: $minter, name: $name, blobHash: $blobHash)
}
`;

Expand Down Expand Up @@ -183,24 +187,28 @@ function App({ chainId, owner }) {
variables: {
chainId: chainId,
blobContent: {
bytes: Array.from(byteArrayFile)
bytes: Array.from(byteArrayFile),
},
},
}).then((r) => {
if ('errors' in r) {
console.log('Got error while publishing Data Blob: ' + JSON.stringify(r, null, 2));
console.log(
'Got error while publishing Data Blob: ' + JSON.stringify(r, null, 2)
);
} else {
console.log('Data Blob published: ' + JSON.stringify(r, null, 2));
const blobId = r['data']['publishDataBlob'];
const blobHash = r['data']['publishDataBlob']['hash'];
mintNft({
variables: {
minter: `User:${owner}`,
name: name,
blobId: blobId,
blobHash: blobHash,
},
}).then((r) => {
if ('errors' in r) {
console.log('Got error while minting NFT: ' + JSON.stringify(r, null, 2));
console.log(
'Got error while minting NFT: ' + JSON.stringify(r, null, 2)
);
} else {
console.log('NFT minted: ' + JSON.stringify(r, null, 2));
}
Expand Down Expand Up @@ -229,7 +237,9 @@ function App({ chainId, owner }) {
},
}).then((r) => {
if ('errors' in r) {
console.log('Error while transferring NFT: ' + JSON.stringify(r, null, 2));
console.log(
'Error while transferring NFT: ' + JSON.stringify(r, null, 2)
);
} else {
console.log('NFT transferred: ' + JSON.stringify(r, null, 2));
}
Expand Down
7 changes: 6 additions & 1 deletion linera-base/src/identifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,13 @@ pub struct BlobId {
impl BlobId {
/// Creates a new `BlobId` from a `BlobContent`
pub fn new_data(blob_content: &BlobContent) -> Self {
Self::new_data_from_hash(CryptoHash::new(blob_content))
}

/// Creates a new `BlobId` from a hash
pub fn new_data_from_hash(hash: CryptoHash) -> Self {
BlobId {
hash: CryptoHash::new(blob_content),
hash,
blob_type: BlobType::Data,
}
}
Expand Down
10 changes: 5 additions & 5 deletions linera-execution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use linera_base::{
abi::Abi,
crypto::CryptoHash,
data_types::{
Amount, ApplicationPermissions, ArithmeticError, Blob, BlobContent, BlockHeight, Resources,
Amount, ApplicationPermissions, ArithmeticError, Blob, BlockHeight, Resources,
SendMessageRequest, Timestamp,
},
doc_scalar, hex_debug,
Expand Down Expand Up @@ -492,11 +492,11 @@ pub trait BaseRuntime {
/// owner, not a super owner.
fn assert_before(&mut self, timestamp: Timestamp) -> Result<(), ExecutionError>;

/// Reads a blob content specified by a given `BlobId`.
fn read_blob_content(&mut self, blob_id: &BlobId) -> Result<BlobContent, ExecutionError>;
/// Reads a data blob content specified by a given hash.
fn read_data_blob(&mut self, hash: &CryptoHash) -> Result<Vec<u8>, ExecutionError>;

/// Asserts the existence of a blob with the given `BlobId`.
fn assert_blob_exists(&mut self, blob_id: &BlobId) -> Result<(), ExecutionError>;
/// Asserts the existence of a data blob with the given hash.
fn assert_data_blob_exists(&mut self, hash: &CryptoHash) -> Result<(), ExecutionError>;
}

pub trait ServiceRuntime: BaseRuntime {
Expand Down
Loading

0 comments on commit bdba50e

Please sign in to comment.