
Linera 数据 Blob 使用指南跨链静态数据发布、读取与 NFT 实战【免费下载链接】linera-protocolMain repository for the Linera protocol项目地址: https://gitcode.com/GitHub_Trending/li/linera-protocol数据 BlobData Blob是 Linera 协议中发布一次、全链可用的二进制数据载体适用于图片、静态资源等与链上状态分离的大型数据。本文以 docs/developers/backend/blobs.md 为核心结合 non-fungibleNFT示例应用 的完整代码与 linera-sdk 运行时实现讲解如何通过 CLI 与 GraphQL 发布 Blob、在合约与服务中读取 Blob并用assert_data_blob_exists预取数据。读完本文你将掌握 Linera 应用接入静态资源的标准姿势。什么是 Data Blob有些应用需要携带静态资产——图片、元数据或其他二进制数据。例如 non-fungible 示例应用 实现的 NFT每个 NFT 都关联一张图片。若把整张图片塞进链上状态会让区块膨胀、状态存储压力骤增且每一条需要该图片的链都要重复存储。Linera 的 Data Blob 正是为这类场景设计的它是一段二进制数据格式与用途完全由读取它的应用决定协议层不关心内容一旦在任意一条链上发布就可以在所有链上使用每个 Blob 由内容哈希DataBlobHash唯一标识天然具备内容寻址与去重能力。也就是说Blob 与链上「谁发布的」无关只与「内容是什么」有关。同一个内容的 Blob 只需要发布一次任何链上的应用都能通过哈希引用它。发布 Data BlobCLI 与 GraphQL 两条路径方式一linera publish-data-blob命令行使用linera publish-data-blob命令可以将一个文件的全部内容作为某个区块中的操作operation发布到你的某条链上linera publish-data-blob BLOB_PATH [PUBLISHER]参数说明见 CLI.md 与 command.rs 中的定义参数含义BLOB_PATH要发布的数据文件路径二进制内容原样发布PUBLISHER可选发布 Blob 的链 ID省略时使用钱包的默认链命令执行后会打印新 Blob 的 ID其中包含它的哈希。该哈希即后续读取 Blob 的唯一凭据。与之配套的还有linera read-data-blob HASH [READER]见 CLI.md用于验证某个哈希的 Blob 是否可读READER为可选的读取链 ID默认使用钱包默认链。方式二publishDataBlobGraphQL mutation你也可以运行linera service启动节点服务然后在 GraphiQL 界面默认http://localhost:8080/中调用publishDataBlobmutationmutation { publishDataBlob( chainId: $CHAIN_1, bytes: [1, 2, 3, 4] ) }chainId为 Blob 发布付费的链见 node_service.rs 的 GraphQL 定义bytes要发布的二进制内容以字节数组形式传入。从 service_requests.graphql 可以看到官方 GraphQL 客户端同样封装了publishDataBlob请求。发布成功后返回 Blob 哈希CryptoHash例如BLOB_HASH$(echo $QUERY_RESULT | jq -r .publishDataBlob)在 non-fungible 示例的 README 中这一步是铸造 NFT 的前置步骤——先用 Blob 承载 NFT 图片再把BLOB_HASH传入 mint 操作。在应用内读取 Blobruntime.read_data_blobBlob 发布后应用可以通过运行时的read_data_blob(blob_hash)读取它。这在任何链上都有效不限于发布它的那条链。在合约侧ContractRuntime::read_data_blob定义于 linera-sdk/src/contract/runtime.rs内部通过 WIT 接口base_wit::read_data_blob向宿主请求数据在服务侧ServiceRuntime::read_data_blob定义于 linera-sdk/src/service/runtime.rs同样委托给宿主实现。一个关键行为是客户端第一次执行读取某 Blob 的区块时如果本地还没有该 Blob会自动从验证者validators下载它。这意味着读取是「按需拉取」的客户端不必提前同步所有链上的全部 Blob。NFT 服务中的读取示例在 non-fungible 的服务实现 中nft、nfts、owned_nfts等 GraphQL 查询都会读取每个 NFT 关联的 Blob并把原始字节作为payload返回给前端供其显示图片async fn nft(self, token_id: String) - OptionNftOutput { // ... 从状态中取出 nft let payload self.runtime.read_data_blob(nft.blob_hash); let nft_output NftOutput::new_with_token_id(token_id, nft, payload); Some(nft_output) }合约中的「创建 读取」验证SDK 测试夹具 publish-read-data-blob 合约 给出了最直接的用法操作CreateAndReadDataBlob先在合约内调用create_data_blobruntime.rs 中的定义创建 Blob 并拿到哈希再立即用read_data_blob读回并断言内容一致Operation::CreateAndReadDataBlob(data) { let hash: DataBlobHash self.runtime.create_data_blob(data.clone()); let data_read self.runtime.read_data_blob(hash); assert_eq!(data_read, data); }注意create_data_blob属于合约运行时能力普通用户发布 Blob 走的是本文第一节的 CLI / GraphQL 路径。预取而不加载assert_data_blob_existsNFT 场景有一个特殊需求真正用 Blob 展示图片的是服务service而不是合约contract。但合约在铸造时依然希望保证「用户一收到 NFT本地就有图片」哪怕用户还没打开查看。直接调用read_data_blob会把数据真正加载进内存但此时合约并不需要内容本身。为此运行时提供了轻量断言ContractRuntime::assert_data_blob_existslinera-sdk/src/contract/runtime.rsServiceRuntime::assert_data_blob_existslinera-sdk/src/service/runtime.rs。它的语义是确保数据可用但不实际加载。在 NFT 合约的mint函数中examples/non-fungible/src/contract.rs铸造第一步就是断言 Blob 存在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_hash, *self.state.num_minted_nfts.get(), ) .expect(Failed to serialize NFT metadata); self.add_nft(Nft { token_id, owner, name, minter: owner, blob_hash, }) .await; let num_minted_nfts self.state.num_minted_nfts.get_mut(); *num_minted_nfts 1; }assert_data_blob_exists会触发与read_data_blob相同的「按需下载」逻辑首次执行时从验证者拉取 Blob却不会把字节载入合约内存。这样就把「确保可用」与「真正使用」解耦收到 NFT 时图片已就位需要展示时服务再调用read_data_blob取内容。两种读取 API 的定位对比API可用位置行为典型用途read_data_blob(hash)合约 服务返回Vecu8实际加载数据触发按需下载服务向前端输出 payload合约内需要处理二进制内容assert_data_blob_exists(hash)合约 服务只校验存在性并触发下载不返回内容合约铸造/接收时预取 Blob保证用户体验端到端实战为 NFT 发布并关联一张图片下面把完整流程串起来命令细节取自 non-fungible README。前提是linera*二进制已在 PATH 中并已通过linera_spawn启动本地网络与 faucetexport PATH$PWD/target/debug:$PATH source /dev/stdin $(linera net helper 2/dev/null)初始化钱包并申请两条链export LINERA_WALLET$LINERA_TMP_DIR/wallet.json export LINERA_KEYSTORE$LINERA_TMP_DIR/keystore.json export LINERA_STORAGErocksdb:$LINERA_TMP_DIR/client.db linera wallet init --faucet $FAUCET_URL INFO_1($(linera wallet request-chain --faucet $FAUCET_URL)) INFO_2($(linera wallet request-chain --faucet $FAUCET_URL)) CHAIN_1${INFO_1[0]} CHAIN_2${INFO_2[0]} OWNER_1${INFO_1[1]} OWNER_2${INFO_2[1]}编译并发布 NFT 应用模块创建应用实例(cd examples/non-fungible cargo build --release --target wasm32-unknown-unknown) MODULE_ID$(linera publish-module \ examples/target/wasm32-unknown-unknown/release/non_fungible_{contract,service}.wasm) APP_ID$(linera create-application $MODULE_ID)启动节点服务通过 GraphQL 发布图片 BlobPORT8080 linera service --port $PORT mutation { publishDataBlob( chainId: $CHAIN_1, bytes: [1, 2, 3, 4] # 实际使用中为图片文件的字节内容 ) }BLOB_HASH$(echo $QUERY_RESULT | jq -r .publishDataBlob)铸造 NFT把 Blob 哈希写入 NFT 元数据此时合约内的assert_data_blob_exists会确保图片数据已就位mutation { mint( minter: $OWNER_1, name: nft1, blobHash: $BLOB_HASH, ) }查询 NFT服务端通过read_data_blob把图片字节作为payload返回query { nft(tokenId: $TOKEN_ID) { tokenId, owner, name, minter, payload } }至此「发布一次 → 合约预取 → 服务读取展示」的完整 Blob 生命周期已经跑通且整个过程与具体链无关NFT 无论被跨链转移到哪条链Transfer/Claim消息图片数据都能通过同一个 Blob 哈希随时读取。设计要点小结内容寻址Blob 以内容哈希为 ID同一内容天然去重跨链共享无需额外同步协议按需拉取客户端首次执行读取 Blob 的区块时才从验证者下载避免全量同步开销合约与服务分工合约用assert_data_blob_exists轻量预取、保证可用性服务用read_data_blob实际取数、向前端输出内容发布路径统一无论用linera publish-data-blob还是publishDataBlobmutation最终都作为区块操作上链由linera-service的publish_data_blobnode_service.rs统一转发给客户端执行。如需查看完整实现可对照阅读 non-fungible 合约 与 服务以及 SDK 侧的 contract runtime 与 service runtime。【免费下载链接】linera-protocolMain repository for the Linera protocol项目地址: https://gitcode.com/GitHub_Trending/li/linera-protocol创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考