基于事件流的去中心化社交应用Linera Protocol Social 示例深度解析【免费下载链接】linera-protocolMain repository for the Linera protocol项目地址: https://gitcode.com/GitHub_Trending/li/linera-protocol导读examples/social是 Linera Protocol 仓库中一个完整的去中心化社交应用示例其核心价值在于演示如何在 Linera 的多链microchain架构上使用**事件流Event Streams**机制实现「关注-发帖-订阅-点赞-评论」的跨链社交闭环。读完本文你将掌握Social 示例的数据结构与 ABI 设计、事件流在Subscribe/Post/Like/Comment中的底层调用链、从零启动本地网络并运行两个用户链的完整实操流程以及对应的 GraphQL 查询与前端接入方式。一、示例概览每个微链就是一个用户Social 示例examples/social/README.md的设计模型非常简洁每条微链microchain代表一个用户即该链的所有者owner。用户可以订阅其他用户也可以发布文本帖子帖子会被推送给自己的所有订阅者。从源码结构看示例由四个 Rust 文件组成contract.rs合约端处理操作Operation、跨链消息Message与事件流更新service.rs服务端将应用状态与操作暴露为 GraphQLstate.rs应用状态定义RootViewlib.rsABI 定义包括Operation、Message、Event、Post、Key等核心类型。对应地Cargo.toml 声明了两个二进制目标social_contract合约与social_service服务依赖linera-sdk、async-graphql、serde、bcs等与仓库其他示例保持一致。二、应用状态与数据结构2.1 状态SocialState每条链上应用的状态state.rs包含两部分#[derive(RootView, async_graphql::SimpleObject)] #[view(context ViewStorageContext)] pub struct SocialState { /// 本链自己发布的帖子 pub own_posts: LogViewOwnPost, /// 从所订阅作者处收到的帖子 pub received_posts: CustomMapViewKey, Post, }own_posts用LogView按发布顺序追加保存received_posts用CustomMapViewKey, Post以Key为索引保存收到的帖子。2.2 帖子索引Key收到的帖子按Key索引lib.rs由三元组构成字段类型含义timestampTimestamp帖子被包含进作者链上区块时的时间戳authorChainId发布该帖子的链indexu32该作者此前发布的帖子数量即本条帖子在作者帖子日志中的序号值得关注的是Key实现了CustomSerializelib.rs对时间戳和索引做按位取反后再序列化从而保证序列化后的字典序对应「时间倒序 → 作者 → 索引倒序」使订阅者可以按时间倒序高效地浏览最新帖子。2.3 帖子、评论与事件类型OwnPosttimestamptext 可选image_url帖子正文在作者与索引已知的上下文中使用Postkeytextimage_urllikes: u32comments: VecComment订阅者视角下带互动数据的完整帖子Commenttext 评论者chain_idEventlib.rs应用向事件流发射的事件包括Post { post, index }、Like { key }、Comment { key, comment }以及检查点checkpoint时携带最近帖子的Summary { recent_posts }。三、核心操作与跨链消息3.1 操作Operation用户通过区块内的操作驱动应用lib.rspub enum Operation { Subscribe { chain_id: ChainId }, Unsubscribe { chain_id: ChainId }, Post { text: String, image_url: OptionString }, Like { key: Key }, Comment { key: Key, comment: String }, }在 contract.rs 的execute_operation中Subscribe/Unsubscribe调用runtime.subscribe_to_events(chain_id, app_id, STREAM_NAME)/unsubscribe_from_events(...)其中流名常量STREAM_NAME: [u8] bpostscontract.rs即订阅/退订目标链上名为posts的事件流Post调用execute_post_operation取系统时间作为timestamp写入own_posts并向流发射Event::Post { post, index }contract.rsLike/Comment若目标帖子作者是其他链则通过runtime.send_message(chain_id, ...)向作者链发送Message::Like/Message::Comment若点赞/评论的是自己的帖子则直接发射对应事件contract.rs。3.2 跨链消息MessageMessagelib.rs只有Like与Comment两种变体且始终发给帖子的作者链ChainId由作者链处理后转发为事件async fn execute_message(mut self, message: Message) { match message { Message::Like { key } self.runtime.emit(STREAM_NAME.into(), Event::Like { key }), Message::Comment { key, comment } self .runtime .emit(STREAM_NAME.into(), Event::Comment { key, comment }), }; }这与 README 中的描述一致Subscribe、Unsubscribe、Posts是订阅/退订/发帖时产生的跨链消息变体前两者直接发往要订阅/退订的链发帖则写入事件流。3.3 事件流消费process_streams与summarize_events订阅者链收到流更新后process_streamscontract.rs会校验stream_id流名与application_id随后对新增的每个索引调用runtime.read_event读取事件Event::Post→execute_post_event将帖子按Key写入received_postsEvent::Like→execute_like_event将对应帖子likes 1Event::Comment→execute_comment_event向帖子追加一条带评论者chain_id的评论Event::Summary→ 将recent_posts中尚未拥有的帖子逐个补齐配合if get(key).is_none()守卫避免覆盖订阅者已记录的点赞与评论。summarize_eventscontract.rs在检查点时生成摘要取自己最近SUMMARY_POST_COUNT 10条帖子打包成Event::Summary发射到流上。这样在检查点之后才加入的订阅者也能通过摘要补齐此前可能已被丢弃的旧事件。四、端到端运行指南以下步骤完整继承自 examples/social/README.md并已在当前仓库源码中逐一验证其对应实现。4.1 环境准备在 Linera 仓库根目录下设置路径与辅助函数export PATH$PWD/target/debug:$PATH source /dev/stdin $(linera net helper 2/dev/null)4.2 启动本地网络与水龙头FAUCET_PORT8079 FAUCET_URLhttp://localhost:$FAUCET_PORT linera_spawn linera net up --with-faucet --faucet-port $FAUCET_PORT # 如果使用测试网则改为 # LINERA_TMP_DIR$(mktemp -d) # FAUCET_URLhttps://faucet.testnet-XXX.linera.net # XXX 为具体编号开启用户应用日志export LINERA_APPLICATION_LOGStrue4.3 创建两个用户钱包与链export LINERA_WALLET_1$LINERA_TMP_DIR/wallet_1.json export LINERA_KEYSTORE_1$LINERA_TMP_DIR/keystore_1.json export LINERA_STORAGE_1rocksdb:$LINERA_TMP_DIR/client_1.db export LINERA_WALLET_2$LINERA_TMP_DIR/wallet_2.json export LINERA_KEYSTORE_2$LINERA_TMP_DIR/keystore_2.json export LINERA_STORAGE_2rocksdb:$LINERA_TMP_DIR/client_2.db linera --with-wallet 1 wallet init --faucet $FAUCET_URL linera --with-wallet 2 wallet init --faucet $FAUCET_URL INFO_1($(linera --with-wallet 1 wallet request-chain --faucet $FAUCET_URL)) INFO_2($(linera --with-wallet 2 wallet request-chain --faucet $FAUCET_URL)) CHAIN_1${INFO_1[0]} CHAIN_2${INFO_2[0]} OWNER_1${INFO_1[3]} OWNER_2${INFO_2[3]}说明linera --with-wallet 1等价于显式指定--wallet $LINERA_WALLET_1 --keystore $LINERA_KEYSTORE_1 --storage $LINERA_STORAGE_1可显著减少重复输入。这里CHAIN_1/CHAIN_2是两个用户的链 ID后续 GraphQL 与前端 URL 都会用到。4.4 编译并发布应用APP_ID$(linera --with-wallet 1 project publish-and-create examples/social)该命令会编译examples/social下的合约与服务发布字节码并在链 1 上创建应用实例返回的APP_ID用于后续所有 GraphQL 请求。4.5 启动两个节点服务分别在不同端口启动两个钱包对应的节点服务linera --with-wallet 1 service --port 8080 # 等待其完成启动 sleep 2 linera --with-wallet 2 service --port 8081 # 等待其完成启动 sleep 2说明README 注释指出等待sleep是为了规避部分竞态条件对应仓库历史上的 issue 1176/1177在实际脚本化操作时建议保留足够等待时间或轮询端口就绪。4.6 用 GraphQL 完成订阅与发帖在两个浏览器标签页分别打开两个 URL对应link入口。在 8081 标签页执行echo http://localhost:8081/chains/$CHAIN_2/applications/$APP_ID然后使用以下 mutation让用户 2 订阅用户 1 的链mutation { subscribe( chainId: $CHAIN_1 ) }在 8080 标签页执行echo http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID然后发布一条帖子imageUrl为可选参数mutation { post( text: Linera Social is the new Mastodon! imageUrl: https://example.com/logo.svg # 可选 ) }4.7 验证订阅者收到帖子回到 8081 标签页此时用户 2 是订阅者查询收到的帖子query { receivedPosts { keys { timestamp author index } } }应能列出 1 条记录包含timestamp、author、index。随后用该三元组查看帖子详情query { receivedPosts { entry(key: { timestamp: 1705504131018960, author: $CHAIN_1, index: 0 }) { value { key { timestamp author index } text imageUrl comments { text chainId } likes } } } }返回结果大致如下时间戳以实际值为准{ data: { receivedPosts: { entry: { value: { key: { timestamp: 1705504131018960, author: $CHAIN_1, index: 0 }, text: Linera Social is the new Mastodon!, imageUrl: https://example.com/logo.svg, comments: [], likes: 0 } } } } }五、GraphQL 服务层与 React 前端5.1 服务端 schema 组装service.rs 在收到查询请求时将SocialState根视图提供ownPosts、receivedPosts查询与Operation::mutation_root(...)自动生成的 mutation 根组装成 async-graphql schema 并执行let schema Schema::build( self.state.clone(), Operation::mutation_root(self.runtime.clone()), EmptySubscription, ) .finish(); schema.execute(request).await这正是 README 中subscribe、post等 mutation 与receivedPosts查询能直接可用的原因——Operation上的GraphQLMutationRoot派生宏见 lib.rs自动生成了 GraphQL 变更入口。5.2 React 前端web-frontend 是一个基于 React Apollo Client Tailwind 的界面其 README 说明应用需先在网络上部署且节点服务运行在 8000 端口即把上文--port 8080/8081换成 8000并需先完成根 README 中的操作。访问格式为http://localhost:3000/$CHAIN_1/$APP_ID/$OWNER_1/$PORT例如 README 给出的实际形态http://localhost:3000/aee928d4bf3880353b4a3cd9b6f88e6cc6e5ed050860abae439e7782e9b2dfe8?appaee928d4bf3880353b4a3cd9b6f88e6cc6e5ed050860abae439e7782e9b2dfe8000000000000000000000000aee928d4bf3880353b4a3cd9b6f88e6cc6e5ed050860abae439e7782e9b2dfe8020000000000000000000000owner019d0849200869c4c88493db9966afad44194714cf76ed5d0639a7cdc37cb286port8080前端核心逻辑在 App.tsx加载时通过RECEIVED_POSTS查询拉取receivedPosts.entries渲染帖子列表同时通过节点服务的notifications(chainId: ...)订阅监听链上新事件一旦有新通知就重新查询fetchPolicy: network-only以实时刷新。发帖组件 NewPost.tsx 通过post(text: $message, imageUrl: $image)mutation 发布文字与可选图片 URL。当前版本的操作范围是「发布帖子、点赞与评论仅在创建链上」。前端可用脚本在examples/social/web-frontend目录下执行npm run tailwindTailwind 构建、npm run compile由codegen.ts生成 GraphQL 类型、npm start开发模式默认 3000 端口、npm run build生产构建、npx prettier --write .格式化。六、测试验证仓库为 Social 示例提供了集成测试 tests/cross_chain.rs覆盖了本文描述的完整跨链流程用TestValidator::with_current_module::social::SocialAbi, (), ()()启动测试验证器创建链 1、链 2在链 1 上create_application创建应用链 1 提交Operation::Subscribe { chain_id: chain2.id() }订阅链 2链 2 提交Operation::Post { text: Linera is the new Mastodon.to_string(), image_url: None }发帖chain1.handle_new_events()让链 1 处理新事件查询链 2 的ownPosts断言帖子文本查询链 1 的receivedPosts.keys断言作者为链 2 的 ID。此外tests/format.rs 与快照 format__format.snap 用于锁定 BCS 序列化格式源自 lib.rs 中SocialApplication::formats()对Operation、Message、Event、Key、OwnPost、Post、Comment的格式追踪保证 ABI 的向后兼容性lib.rs 中的单测 则验证了Key自定义序列化的往返一致性。七、小结与扩展方向通过 Social 示例可以提炼出在 Linera 上构建去中心化应用的三条核心经验事件流是「一对多」内容分发的高效原语作者链只需emit一次订阅者通过process_streams消费同一流无需作者向每个订阅者单独发消息跨链互动走「作者中心」路由点赞、评论等互动通过send_message先汇聚到作者链再由作者链发射事件从而保证互动数据的一致性归集检查点摘要Summary保证后到订阅者不错过历史summarize_events定期把最近 10 条帖子打包配合received_posts的判空守卫实现了「迟到补课」且不覆盖本地互动记录。若想进一步扩展可参照 linera-sdk 的事件流 APIsubscribe_to_events、unsubscribe_from_events、emit、read_event与 linera-execution 的运行时实现将流名、摘要条数等常量参数化或为Like/Comment增加取消与编辑能力。【免费下载链接】linera-protocolMain repository for the Linera protocol项目地址: https://gitcode.com/GitHub_Trending/li/linera-protocol创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考