Solana交易和指令

在Solana上,我们发送[交易]与网络交互。交易包括一个或多个[指令],每个交易代表一个待处理的特定操作。指令的执行逻辑是存储在部署到Solana网络的[programs]上。每个程序存储自己的一组指令。以下是关于交易执行方式的关键细节:执行顺序:如果一个交易包括多个指

<!--StartFragment-->

在 Solana上,我们发送 [交易]与网络交互。 交 易包括一个或多个[指令], 每个交易代表一个待 处理的特定操作。 指令的执行逻辑是存储在部署到 Solana 网络的 [programs]上。每个程序存储自己的一组指令。

以下是关于交易执行方式的关键细节:

  • 执行顺序:如果一个交易包括多个指令,这些指令将按照它们添加到交易中的顺序进行处 理。
  • 原子性:交易是原子的,意味着它要么完全完成并且所有指令都成功处理,要么完全失 败。 如果交易中的任何指令失败,则不会执行任何指令。

为简单起见,可以将交易视为请求处理一个或多个指令。

<!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

你可以将交易想象成一个信封,其中每个指令是您填写并放入信封中的文件。 然后我们发 出信封来处理文档,就像在网络上发送一个交易来处理我们的指令一样。

<!--EndFragment--> <!--StartFragment-->

关键要点

  • Solana 交易由与网络上各种程序进行交互的指令组成,其中每个指令代表一个特定操 作。
  • 每个指令指定执行指令的程序、指令所需的账户以及指令执行所需的数据。
  • 交易中的指令按照它们列出的顺序进行处理。
  • 交易是原子的,意味着要么所有指令都成功处理,要么整个交易失败。
  • 交易的最大大小为1232字节。

<!--EndFragment--> <!--StartFragment-->

基本示例 

以下是代表从发送方向接收方转移 SOL 的单个指令的交易的图示。

Solana 上的个人“钱包”是由[系统程序] 拥有的账 户。 作为 [Solana 账户模型] 的一部分,只有拥有帐户的程序才允 许修改帐户上的数据。

因此,从“钱包”账户转移 SOL 需要发送一个交易来调用 System Program 上的转移指令。

<!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

发送者账户必须包含在交易上作为签名者(is_signner),以批准扣除他们的 lamport 余 额。 发送者和接收方的账户必须是可变的(is_wrable),因为指令修改了两个账户的 lamport 余额。

交易一旦发送,系统程序将被调用来处理传输的指令。 然后,系统程序相应更新的发送者 和接受者账户的 lamport 余额。

<!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

简单 SOL 转移

这是一个使用 SystemProgram.transfer 方法构建SOL转移指令的 [Solana Playground]示例:

<!--EndFragment-->

// 定义转账金额
const transferAmount = 0.01; // 0.01 SOL

// 创建一个转账指令,从 wallet_1 到 wallet_2
const transferInstruction = SystemProgram.transfer({
  fromPubkey: sender.publicKey,
  toPubkey: receiver.publicKey,
  lamports: transferAmount * LAMPORTS_PER_SOL, // Convert transferAmount to lamports
});

//  添加转账指令到新交易
const transaction = new Transaction().add(transferInstruction);

<!--StartFragment-->

运行脚本并检查记录到控制台的交易详细信息。在下面的部分中,我们将详细介绍发生的情 况。 在下面的部分,我们过一遍运行时发生什么的细节。

<!--EndFragment--> <!--StartFragment-->

交易

Solana 的[交易]包 括:

  1. [签名]: 包含在交易中的签名数组。
  2. [消息]: 要原子处理的指令列表。

<!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

交易消息的结构包括:

  • [消息头]:指定签名者和只读账户的数量。
  • [账户地址]:指令在交易中所 需的账户地址数组。
  • [最新的 Blockhash]:作为交易的时间 戳。
  • [指令]:要执行的指令数组。

<!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

交易大小 

Solana网络遵循最大传输单元(MTU)大小为1280字节,与 [IPv6 MTU]大小约束一致,以确保快速可 靠地通过 UDP 传输集群信息。 在计算必要的标头后(IPv6的40字节和8字节的片段 头),[1232 字节仍可用于数据包], 例如序列化交易。

这意味着 Solana 交易的总大小限制为 1232 字节。签名和消息的组合不能超过此限制。

  • 签名:每个签名需要64字节。签名的数量可以根据交易的要求而变化。 签名数量可以不 同,取决于交易的要求。
  • 消息:消息包括指令、账户和附加元数据,每个账户需要32字节。 账户加上元数据的组 合大小可以根据交易中包含的指令而变化。

<!--EndFragment-->

Screenshot (1).png

<!--StartFragment-->

消息头

[消息头]具 体规定了交易账户地址数组中包含的账户的权限。 它由三个字节组成,每个字节含有一个 u8 整数,它们共同规定:

  1. 交易所需的签名数量。
  2. 需要签名的只读账户地址的数量。
  3. 不需要签名的只读账户地址的数量。

<!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

紧凑数组格式 

在交易消息的上下文中,紧凑数组指的是以以下格式序列化的数组:

  1. 数组的长度,编码为 [compact-u16]。
  2. 编码长度后按顺序列出数组的各个项。

<!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

这种编码方法用于指定交易消息中 的[账户地址]数 组的长度。

账户地址数组 

交易消息包括一个数组,其中包含所 有[账户地址] ,这些地址是交易内指令所需的。

该数组以一个 [compact-u16] 编码开 始,后跟按账户权限排序的地址。 消息头中的元数据用于确定每个部分中的账户数量。

  • 可写且签名者账户
  • 只读且签名者的账户
  • 可写且非签名者的账户
  • 只读且非签名者的账户 <!--EndFragment-->

Screenshot (1).png

<!--StartFragment-->

最近的块哈希

所有交易都包括一 个[最近的区块哈希], 用作交易的时间戳。 区块哈希用于防止重复和消除过时的交易。

交易的区块哈希的最大年龄为150个区块(假设每个区块时间为400毫秒,约1分钟)。 如果 交易的区块哈希比最新的区块哈希旧150个区块,那么它被视为已过期。 这意味着在特定时 间范围内未处理的交易将永远不会被执行。

你可以使用[getLatestBlockhash]RPC方法来获 取当前的区块哈希以及区块哈希将有效的最后一个区块高度。 以下是一个 在[Solana Playground]上的示例。 <!--EndFragment-->

<!--StartFragment-->

指令数组 

交易消息包括一个包含所 有[请求处理的指令]的 数组。 交易消息中的指令采用以下格 式:[CompiledInstruction]。

与账户地址数组类似,这个紧凑数组以一 个[compact-u16]编码开始,后跟一个 指令数组。数组中的每个指令指定以下信息: 对于每个指令所需的每个账户,必须指定以 下信息:

  1. 程序ID:标识将处理指令的链上程序。这表示为指向账户地址数组中的一个账户地 址的u8索引。 这是一个 u8 索引,指向帐户地址数组中的帐户地址。
  2. 账户地址索引的紧凑数组:指向每个指令所需的账户地址数组的u8索引数组。
  3. 不透明u8数据的紧凑数组:特定于被调用程序的u8字节数组。 此数据指定要在程序 上调用的指令,以及指令需要的任何附加数据(例如函数参数)。 <!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

示例交易结构

以下是包括单个 [SOL转账]指令的交易结构示 例。 它显示了消息细节,包括头部、账户密钥、区块哈希和指令,以及交易的签名。

  • header:包括用于指定accountKeys数组中的读/写和签名者权限的数据。
  • accountKeys:包括交易中所有指令的账户地址。
  • recentBlockhash:交易创建时包含的区块哈希。
  • instructions:包括交易中所有指令。 每个指令中的accountprogramIdIndex通 过索引引用accountKeys数组。
  • signatures:包括交易中指令所需的所有签名。 通过使用相应账户的私钥对交易消息 进行签名来创建签名。 <!--EndFragment-->
"transaction": {
    "message": {
      "header": {
        "numReadonlySignedAccounts": 0,
        "numReadonlyUnsignedAccounts": 1,
        "numRequiredSignatures": 1
      },
      "accountKeys": [
        "3z9vL1zjN6qyAFHhHQdWYRTFAcy69pJydkZmSFBKHg1R",
        "5snoUseZG8s8CDFHrXY2ZHaCrJYsW457piktDmhyb5Jd",
        "11111111111111111111111111111111"
      ],
      "recentBlockhash": "DzfXchZJoLMG3cNftcf2sw7qatkkuwQf4xH15N5wkKAb",
      "instructions": [
        {
          "accounts": [
            0,
            1
          ],
          "data": "3Bxs4NN8M2Yn4TLb",
          "programIdIndex": 2,
          "stackHeight": null
        }
      ],
      "indexToProgramIds": {}
    },
    "signatures": [
      "5LrcE2f6uvydKRquEJ8xp19heGxSvqsVbcqUeFoiWbXe8JNip7ftPQNTAVPyTK7ijVdpkzmKKaAQR7MWMmujAhXD"
    ]
  }

<!--StartFragment-->

指令

一 个[指令]是 对链上执行特定操作的请求,也是[程序]中最小 的连续执行逻辑单元。

构建要添加到交易中的指令时,每个指令必须包括以下信息:

  • 程序地址:指定被调用的程序。
  • 账户:列出每个指令读取或写入的每个账户,包括其他程序,使用 AccountMeta 结构。
  • 指令数据:一个字节数组,指定要在程序上调用 的[指令处理程序],以及指令处理程序所需 的任何附加数据(函数参数)。

<!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

账户元数据

对于每个指令所需的每个账户,必须指定以下信息:

  • pubkey:账户的链上地址
  • is_signer:指定账户是否在交易中作为签名者
  • is_writable:指定账户数据是否将被修改 这些信息被称 为[账户元数据]。 <!--EndFragment-->

Screenshot (1).png <!--StartFragment-->

通过指定指令所需的所有账户,以及每个账户是否可写,可以并行处理交易。

例如,两个不包含写入相同状态的账户的交易可以同时执行。

<!--EndFragment--> <!--StartFragment-->

示例指令结构

以下是一个 [SOL 转账]指令结构的示例,详 细说明了指令所需的账户密钥、程序 ID 和数据。

  • keys:包括每个指令所需的AccountMeta (账户元数据)。
  • programId:包含执行指令的程序地址。
  • data:指令数据,作为字节缓冲区 <!--EndFragment-->
{
  "keys": [
    {
      "pubkey": "3z9vL1zjN6qyAFHhHQdWYRTFAcy69pJydkZmSFBKHg1R",
      "isSigner": true,
      "isWritable": true
    },
    {
      "pubkey": "BpvxsLYKQZTH42jjtWHZpsVSa7s6JVwLKwBptPSHXuZc",
      "isSigner": false,
      "isWritable": true
    }
  ],
  "programId": "11111111111111111111111111111111",
  "data": [2,0,0,0,128,150,152,0,0,0,0,0]
}

<!--StartFragment-->

扩展示例

构建程序指令的详细信息通常由客户端库抽象掉。 但是,如果没有可用的库,你总是可以 手动构建指令。

手动SOL转账

这是一个 [Solana Playground]示 例,展示了如何手动构建 SOL 转账指令:

<!--EndFragment-->

// 定义转账金额
const transferAmount = 0.01; // 0.01 SOL

// 系统程序转移指令的指令索引
const transferInstructionIndex = 2;

// 为要传递给传输指令的数据创建一个缓冲区
const instructionData = Buffer.alloc(4 + 8); // uint32 + uint64
// 将指令索引写入缓冲区
instructionData.writeUInt32LE(transferInstructionIndex, 0);
// 将转帐金额写入缓冲区
instructionData.writeBigUInt64LE(BigInt(transferAmount * LAMPORTS_PER_SOL), 4);

// 手动创建一个传输指令,用于将SOL从发送方传输到接收方
const transferInstruction = new TransactionInstruction({
  keys: [
    { pubkey: sender.publicKey, isSigner: true, isWritable: true },
    { pubkey: receiver.publicKey, isSigner: false, isWritable: true },
  ],
  programId: SystemProgram.programId,
  data: instructionData,
});

// 将转账指令添加到新交易中
const transaction = new Transaction().add(transferInstruction);

<!--StartFragment-->

在背后,使用 SystemProgram.transfer 方法 的[简单例子]在功能上等同于上面更详 细的示例。 SystemProgram.transfer方法简单地隐藏了为每个指令所需的账户创建指令 数据缓冲区和AccountMeta的细节。 <!--EndFragment-->

作者:https://t.me/+P3Z7P_xQxbNlZWZl 来源:https://www.fabipingtai.com

  • 原创
  • 学分: 15
  • 分类: Solana
  • 标签:
点赞 2
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
加密女士
加密女士
无代码发币平台