跨程序调用深度解析:如何在Solana上调用任何程序?

本文深入探讨了Solana中跨程序调用(CPI)的实现方法,重点介绍了如何在没有目标程序源代码的情况下调用程序,并分析了调用Anchor程序和原生Rust程序的差异。文章通过示例代码,详细解释了Anchor程序的discriminator机制以及数据格式要求,并对比了原生Rust程序在CPI方面的灵活性。文章还提供了实际的代码示例和截图,帮助读者理解和实践。

跨程序调用深入研究。如何在 solana 上调用任何程序?

问题?

通常,要从程序 A 对程序 B 执行 CPI,我们需要程序 B 的源代码。那么如何在没有源代码的情况下调用程序 B?调用用 Anchor 创建的程序和本地 Rust 程序之间有什么区别?

我们构建什么?

你可以在这里找到代码演示:

我们的设计:

  • (1) 当客户端与 init_user() 函数交互时,它将创建一个 PDA(种子是 [programID, Pubkey]),其中 max = 0
  • max: Number of workflows initiated.
  • (2) 当客户端与 create_workflow() 函数交互时,它将创建一个 PDA(种子是 [programID, Pubkey, max]),其中 ping = 0 并且 max++(max 增加 1)
  • ping: Number of votes in a workflow.

注意:在运行 (1) 和 (2) 之前,你需要编辑两个地方的密钥对:

1. in test/workflow.ts
const keypair =  web3.Keypair.fromSecretKey(
    Uint8Array.from([64,33,98,76,216,30,65,85,81,32,240,30,164,197,23,225,253,179,10,197,190,174,155,56,130,224,202,128,189,201,48,37,20,123,160,201,77,149,50,29,89,209,232,173,89,87,250,249,192,221,235,132,195,237,147,165,80,165,155,92,70,100,203,86])
  )
2. in terminal run ```solana config get```
   you will see: Keypair Path: /home/trung1701/.config/solana/id.json
   please replace keypair in id.json
  • (3) 当客户端与 vote() 函数交互时,Workflow 程序将调用 Logic1 程序。Logic1 程序是一个构建在 Anchor 上的程序。ping++(ping 增加 1)在这个 workflow 中。
  • (4) 当客户端与 vote2() 函数交互时,Workflow 程序将调用 Logic2 程序。Logic2 程序是一个构建在 native rust 上的程序。ping++(ping 增加 1)在这个 workflow 中。

Logic1 程序和 Logic2 程序与 Workflow 程序完全分开构建。Workflow 程序没有 Logic1 程序和 Logic2 程序的 IDL 文件。因此,我们只知道 programID 已部署到 Solana 网络以与该程序交互。

在 Solana 编程中,接口定义语言 (IDL) 指定了程序的公共接口。它定义了 Solana 程序的账户结构、指令和错误代码。IDL 是 .json 文件,用于生成客户端代码,使用户可以轻松地与 Solana 程序交互。

那么 Anchor 和 Native Rust 的 CPI 有什么区别?此外,在执行 CPI 时,我们应该注意哪些数据传输事项?

使用 Anchor 的 CPI(Logic1 程序)

你们可能都知道 Anchor 中的 discriminator。它实际上是什么?

discriminator 实际上用于确定应该在 Anchor 程序中调用哪个函数。discriminator 由 "global:<NameOfFunction>" 的 Sha256 哈希的前 8 个字节定义。

例如,在 logic1 程序中有一个 vote() 函数。如何在不发生错误的情况下使 Workflow 程序调用 Logic1 程序?为此,在指令的数据字段中,我们将必须初始化一个向量,其中前 8 个字节用于初始化 discriminator。

Step 1: using sha256 for string "global:vote" and you will get the result:
    e36e9b17887eac197678a3a9928f2dfc8a1d553a698244524539ebb858a2b4d0

Step 2: Take the first 16 letters and convert hex to 8 bytes array
    e36e9b17887eac19 => [227, 110, 155, 23, 136, 126, 172, 25]

很好!然而,这只是 discriminator,你需要根据你想从客户端发送的格式来更多地关注。在我的例子中,客户端想要发送一个 struct{number1:1, number2:7}。然后我们将其转换为一个字节数组。这个字节数组的长度为 2。你可以在 Workflow 程序的 test 文件夹中的 workflow.ts 文件中找到此代码,查看注释 //test invoke from anchor program to anchor program(logic1)。为了让 Anchor 程序中的 vote 函数识别此数据,你需要 4 个字节来包含此字节数组的长度信息,然后是包含你想要发送的结构的字节数组的信息。你可以在这里了解更多关于其他数据格式的信息:https://book.anchor-lang.com/anchor_references/space.html

因此,当执行 CPI 时,我们可以看到我们在 Solana explorer 上发送的指令数据具有以下格式:

正如你所见,要对任何 Anchor 程序执行调用,我们将必须遵循 Anchor 数据结构规则。否则,我们将立即收到错误。

使用 Native Rust 的 CPI(Logic2 程序)

对于 Native Rust,我们可以正常地从另一个程序进行调用,而无需过多麻烦。这完全取决于你。所以我不会展示任何东西,我只是简单地用 Logic2 程序给出一个演示,以便你可以看到和比较。

你需要在结构中添加一个 variant 字段,然后处理它们。

总结

简而言之,当从程序 A 对用 Anchor 构建的程序 B 中的函数执行 CPI 时,我们需要注意 discriminator 声明和数据格式。对于 Native Rust,我们可以从程序 A 对程序 B 中的函数执行 CPI,而无需在指令中声明任何其他内容。但是,在 Native Rust 中,为了能够根据指令的目的处理并导航到正确的函数,我们可以向结构添加一个 variant 字段,并且程序 B 需要处理这些 variant 以导航到特定函数。

  • 原文链接: medium.com/@trungbaotran...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
trungbaotran1701
trungbaotran1701
江湖只有他的大名,没有他的介绍。