本文详细介绍了如何将想法转化为Solana程序,具体针对构建一个预测市场的实例进行探讨。内容涉及Solana账户的数据设计、关系映射、资金存储方式以及通过索引和分片优化程序性能的技术细节,适合开发者在Solana生态中实现自己的项目。
让我们设计一个 Solana 程序。Solana 生态系统提供了大量关于理解 Solana 编程模型 和创建小型演示程序的文章,这些对初学者学习 Solana 和 Anchor 非常有帮助。然而,实际程序则有更复杂的考虑。
在本文中,我们将查看一个希望制作的实际程序,并涵盖:
这个例子的目的是帮助你将自己的想法转化为 Solana 程序和账户模型。
注意:本文假设你熟悉 Anchor 的基础知识。
今天我们要设计的程序是一个类似于 Polymarket、Hedgehog 或 Drift BET 的预测市场。如果你不熟悉预测市场,它们允许人们对事件的不同结果进行投注。可能是哪个团队赢得超级碗,谁赢得“最佳导演”奥斯卡奖,政府是否会在特定时间作出宣布,或任何其他具有特定结果的现实事件。如果人们在获胜结果上投注,他们可以从下注池中获得 winnings。
以下是预测市场的核心架构,以及每个部分如何相互关联:
当事件“解决”(即我们知道获胜结果)时:
如果我们制定这个程序以使用关系型数据库,我们将考虑:
在 Solana 中,这些概念大致映射为:
以下是相同的数据,分别以传统数据库表和 Solana 账户的形式展示:
传统数据库将每条记录存储为表中的一行,而 Solana 将每条记录存储为单独的账户,具有相似记录使用相同的结构体。
在 Solana 中,数据项之间的关系运作非常不同。传统数据库使用 关系(即表之间的逻辑连接)。Solana 将一对多关系处理为 地址向量,每个地址包含一个包含该项的账户。
例如,事件有“结果”,它是结果地址的一个向量。Anchor 将其显示为 Vec<Pubkey>,虽然 PDA 地址(例如我们的结果)并不是真正的公钥。
Solana 上的一对多数据记录作为 Vec\<Pubkey> 存储,Pubkeys 是“多”侧关系的地址。
与传统数据库不同,Solana 程序还可以在账户内部存储资金——不仅仅是余额数字。
在我们的示例中,事件需要一个代币账户来存储它的胜利池。事件的 PDA 将拥有胜利池账户。当用户进行投注时,他们将代币发送到此账户,但更重要的是,当他们索取他们的投注时,我们的程序将 作为事件账户签署交易 以将代币从胜利池中转出。
PDA 账户可以成为代币账户的授权。当我们的 Solana 程序需要从该账户转移代币时,它可以使用 PDA 的种子签署交易。
我们的程序需要尽可能快速响应用户,而作为开发人员,我们希望避免为不必要的账户读取付费。我们可以通过维护运行总计和索引,使我们的程序更具响应能力和效率。
当用户索取他们的 winnings 时,我们需要准确知道每个结果上投注了多少。预测市场根据公式 胜利池 × 投注金额 ÷ 赢得结果的总投注 确定获胜用户的支付。
现在,我们仅在投注账户中存储每个投注的金额。要获取特定结果上的总投注,我们必须读取每个投注账户并加总这些金额。
相反,让我们在每个结果中添加一个字段—— total_amount——并在用户进行投注时递增。这样,当用户获胜时,我们可以轻松确定支付金额,而无需读取该结果的每一份投注。
每个结果上的总投注可以通过添加该结果中的所有投注来确定,但如果在添加新投注时维护一个运行 total_amount,我们的程序将更加快速响应。
我们还需要找出来自特定用户账户的所有投注。我们可以使用 getProgramAccounts() 获取每个投注账户,并过滤出与该用户地址匹配的账户。尽管 Helius 的快速 getProgramAccounts() 使得与其他 RPC 提供者相比,这个过程更快,但索引是常见的另一个选择。
因此,让我们创建一个索引,用于存储每个用户的投注。当用户进行新投注时,如果该项不存在,我们将创建该项并将投注添加到该用户的投注列表中:
索引允许我们快速解决常见用户查询,例如向连接的用户显示其钱包所做的所有投注列表。
我们还将在每个事件中添加标签,因此我们可以轻松找到所有带有标签的事件,如“体育”、“政治”、“欧洲”、“美国”等。我们会为此创建另一个索引:
另一种常见的用户流程是获取满足特定标签的事件。我们可以通过添加另一个索引来加快这个过程。
现在,我们可以通过查看事件标签账户轻松检索所有感兴趣的事件。
记住每个事件都有一个存储该事件投注的单一代币账户吗?每当用户添加一个新投注,代币将移动到该账户。换句话说,该账户将被写入。
Solana 快速运行的原因是它并行化操作。然而,更新单个账户的余额不能并行完成——它必须顺序完成,因为每个账户在任何特定时间必须具有单一余额。
如果一个新的事件被宣布并收到大量的投注,许多写入将同时发生到事件的代币账户,我们的程序的交易可能会显得缓慢。这被称为写入争用——多个交易竞争访问该账户。
允许在这些情况下并行化的一种方法是通过 分片。一个资源被分割成多个部分——称为 shards——可以并行访问。
根据竞猜者公钥的最后一个字节的值,Incoming payments 被发送到不同的 win_pool shards,使用 shard_num() 宏。这确保了 Incoming payments 快速执行。
稍后,管理员指令处理程序可以将这些合并为一个胜利池账户,确保我们有流动性以支付获胜者。
分片为项目创建多个副本或“片”,并在每个用户与其分片之间形成确定性映射。不同分片上的用户可以并行写入这些账户。
如果一个热门事件被最终确定,所有的获胜者同时声索他们的winnings,我们同样需要考虑写入争用。我们还需要尽可能快速地将资金从胜利池中转出。
此时的最佳选择是避免索取过程。相反,如果我们在事件最终确定后尽快按顺序发送资金,用户就不必处理缓慢的索取——他们的奖金将已经存入他们的账户。
但是,在你担心用户群体下注或索取他们的博彩之前,你必须实际上拥有用户。如果你正在规划你的 Solana 程序,并且尚未上线,处理大量用户流量的优化 是不必要的。如果你在启动时不需要这些活动,你应考虑更大优化带来的额外复杂性,并明白当你的程序变得更受欢迎时可能需要实现它们。
在本文中,我们深入探讨了如何在 Solana 上创建一个实际应用程序。你现在具备了定义 Solana 账户、建立数据关系、存储代币以及使用索引和分片优化性能的实际知识。
如果你有任何后续问题,请随时联系 @heliuslabs on X 或 加入 Helius Discord。我们将来会扩展这个预测市场的示例,所以请确保关注这些账户以获取更新。
掌握了这些新技能后,现在该将你的想法转化为 Solana 程序,并在 Solana 生态系统中赋予其生命了。祝编程愉快!
感谢, Ichigo ,审阅本文以及 r0bre ,指出账户分片技术。
- 原文链接: helius.dev/blog/how-to-t...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!