Solana交易深度分析

本文记录了一位以太坊开发者深入了解Solana交易过程的经历,特别是创建和铸造代币的细节。通过对Solana交易结构的分析,文章详细介绍了交易中涉及的账户、签名、元数据和指令,并对与以太坊的不同之处进行了比较。最后,作者总结了在此过程中学到的关于Solana的知识。

一位以太坊开发者(有点)学习Solana交易的工作原理。

自2015年以来,我一直在以太坊上进行黑客攻关/构建,至今仍主要集中在基于EVM的区块链上。因此,我对以太坊在整个技术栈的工作原理有了相当不错的了解。我还构建了一款硬件钱包( Lattice),旨在提升在智能合约平台上交易的用户体验。直到最近,基于EVM的链几乎占据了100% 的市场份额。

Solana在今年早些时候开始增长,因此我查看了他们的文档,并意识到它与以太坊有多么不同。它是一个完全不同的系统,具有完全不同的权衡。尽管我是区块链的老炮,我必须承认这让我感到着迷。我终于有了一些时间来深入研究一下Solana的工作方式,因此我决定深入研究一笔交易,以此作为好的开始,所以我开始了这项工作,并在此记录下我的思考和分析。

注意:我绝对不是Solana的专家,因此某些内容可能不正确 - 如果是这种情况,请 告知我 ,我会改正文章。

设置一个钱包和铸造

在做任何事情之前,在测试网络上设置一个临时 Sollet 钱包。我实际上克隆了钱包的代码库并在本地运行,以便获得一些描述性打印来找出发生了什么,因此如果你有兴趣进行自己的深入研究,也可以随便这样做。

GitHub - project-serum/spl-token-wallet \ \ 支持SPL代币和Serum集成的Solana钱包示例。请查看sollet.io或Sollet Chrome扩展…\ \ github.com

一旦创建了钱包并切换到测试网络,请请求空投,然后铸造测试代币。铸币交易是我将在这里讨论的内容。

Solana交易的结构

我首先描述一下依据官方文档结构的通用Solana交易是如何构成的:

交易 | Solana文档 \ \ 程序执行始于向集群提交交易。Solana运行时将执行程序…\ \ docs.solana.com

基本上,交易由几个部分组成:

1. 签名 - 这是消息哈希的 ed25519 曲线签名列表,其中“消息”由元数据和“指令”组成。与以太坊不同,Solana交易可以包含多个签名者(稍后会有更明确的解释),而此签名字段需要所有签名者进行签名。任何授权交易中任何状态更新的账户都必须对该交易进行签名,类似于比特币,其中所有UTXO必须由其所有者签名,但不同之处在于关系不是1:1 - 一个在交易中多次更新的账户只需要签名一次。

2. 元数据 - 有一些元数据用于消息,我在打印输出中未获得它,并且这里简单提一下,因为它在文档中。消息有一个 头部,其中包括3个 uint8,描述有多少个账户将签署有效载荷,有多少个不会,以及有多少个是只读的。元数据还包含一个将被引用/在此交易中使用的账户列表和一个“最近”块哈希。我不确定该哈希的确切规则,但它不必是 最新 区块,只需是 一个 最近的区块。“最近”是多少才算“足够最近”?我不知道。

3. 指令 - 这是消息(以及这篇文章)的核心。当你在Solana中制作交易时,你实际上是在向运行时提供一系列“指令”,每条指令都做不同的事情来更新Solana全局系统的状态。一条指令包含三条主要信息:a) 使用的账户集,以及每个账户是否为签名者和/或可写,b) 一个程序ID,用于引用将要调用的代码位置,c) 一个带有一些数据的缓冲区,作为有效载荷。

账户和程序

“账户”本质上是Solana系统中具有关联地址的内存区域。“程序”是特殊类型的账户,其中包含静态的、可执行代码。

额外补充: _实际上有两种“地址”类型,如果你是来自以太坊的用户,这会让人感到困惑。Solana使用 ed25519,其具有32字节的公钥 - 这是一种“地址”类型(即它不是哈希值)。如果你正在创建一个希望能够进行签名的账户,你的地址需要是 ed25519 曲线上的一个点(即有效公钥)。然而,你也可以创建一个“程序派生地址”(PDA),它必须是 曲线上,这意味着没有相应的私钥。你无法直接创建一个PDA账户 - 相反,程序拥有的账户必须通过程序函数调用 System Program。PDA通常在程序需要更复杂的内部账户管理时使用(例如DeFi),而且我们在代币铸币交易中不会使用它们。

要在Solana系统中“安装”一个程序,你将创建一个新账户(带有公钥/私钥对),并将编译好的字节码作为有效载荷发送。代码将部署到与所提供公钥匹配的地址上。Solana中有一些系统级程序,其中之一是 BPF Loader (BPF代表“ 伯克利数据包过滤器 ”) - 这是你调用以创建新程序的程序,它成为你新程序的“所有者”。

因此,你可以将“程序”看作是Solana系统中的一个内存区域,包含不可变的、无状态的可执行代码。一旦程序代码被部署,你就可以创建一个新账户并将程序标记为其“所有者” - 从现在开始我将称之为“程序拥有的账户”。 请注意,如果你用公钥/私钥对创建了一个账户并将一个程序分配为所有者,则你不能直接改变该账户的状态 - 你必须使用所链接程序的API来执行此操作,这类似于以太坊中的代理合约模式。需要明确的是,这意味着你生成的用于创建程序拥有的账户的私钥在签署创建账户的交易后可以被丢弃 - 一旦账户创建,该密钥点无法再做任何事情。

示例:铸造代币

好了,足够的背景知识。让我们看看我们的交易。这是我运行的交易:

Solscan - 最直观的Solana区块浏览器 \ \ 用户友好的Solana生态系统实时更新扫描工具。追踪你的$SOL和与Solana相关的…\ \ solscan.io

这是我本地的Sollet钱包JSON输出:

{  "instructions": [// instruction 1/5\
    {\
      "keys": [\
        {\
          "pubkey": {\
            "_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"\
          },\
          "isSigner": true,\
          "isWritable": true\
        },\
        {\
          "pubkey": {\
            "_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"\
          },\
          "isSigner": true,\
          "isWritable": true\
        }\
      ],\
      "programId": {\
        "_bn": "00"\
      },\
      "data": {\
        "type": "Buffer",\
        "data": [\
          0,   0,   0,   0,  96,  77,  22,   0,   0,  0,   0,\
          0,  82,   0,   0,   0,   0,   0,   0,   0,  6, 221,\
          246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206,\
          235, 121, 172,  28, 180, 133, 237,  95,  91, 55, 145,\
          58, 140, 245, 133, 126, 255,   0, 169\
        ]\
      }\
    }, // instruction 2/5\
    {\
      "keys": [\
        {\
          "pubkey": {\
            "_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"\
          },\
          "isSigner": false,\
          "isWritable": true\
        },\
        {\
          "pubkey": {\
            "_bn": "06a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a00000000"\
          },\
          "isSigner": false,\
          "isWritable": false\
        }\
      ],\
      "programId": {\
        "_bn": "06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"\
      },\
      "data": {\
        "type": "Buffer",\
        "data": [\
          0,  2,  99, 232, 238,  67, 168, 88,  48, 19, 186, 111,\
          239, 92,   9,  24,  31,  38, 114, 13,   6, 33, 222, 190,\
          140, 87, 234, 244, 237, 135, 231,  4, 132, 59,   0,   0,\
          0,  0,   0,   0,   0,   0,   0,  0,   0,  0,   0,   0,\
          0,  0,   0,   0,   0,   0,   0,  0,   0,  0,   0,   0,\
          0,  0,   0,   0,   0,   0,   0\
        ]\
      }\
    },     // instruction 3/5\
    {\
      "keys": [\
        {\
          "pubkey": {\
            "_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"\
          },\
          "isSigner": true,\
          "isWritable": true\
        },\
        {\
          "pubkey": {\
            "_bn": "f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c"\
          },\
          "isSigner": true,\
          "isWritable": true\
        }\
      ],\
      "programId": {\
        "_bn": "00"\
      },\
      "data": {\
        "type": "Buffer",\
        "data": [\
          0,   0,   0,   0, 240,  29,  31,   0,   0,  0,   0,\
          0, 165,   0,   0,   0,   0,   0,   0,   0,  6, 221,\
          246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206,\
          235, 121, 172,  28, 180, 133, 237,  95,  91, 55, 145,\
          58, 140, 245, 133, 126, 255,   0, 169\
        ]\
      }\
    }, // instruction 4/5\
    {\
      "keys": [\
        {\
          "pubkey": {\
            "_bn": "f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c"\
          },\
          "isSigner": false,\
          "isWritable": true\
        },\
        {\
          "pubkey": {\
            "_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"\
          },\
          "isSigner": false,\
          "isWritable": false\
        },\
        {\
          "pubkey": {\
            "_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"\
          },\
          "isSigner": false,\
          "isWritable": false\
        },\
        {\
          "pubkey": {\
            "_bn": "06a7d517192c5c51218cc94c3d4af17f58daee089ba1fd44e3dbd98a00000000"\
          },\
          "isSigner": false,\
          "isWritable": false\
        }\
      ],\
      "programId": {\
        "_bn": "06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"\
      },\
      "data": {\
        "type": "Buffer",\
        "data": [\
          1\
        ]\
      }\
    }, // instruction 5/5\
    {\
      "keys": [\
        {\
          "pubkey": {\
            "_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"\
          },\
          "isSigner": false,\
          "isWritable": true\
        },\
        {\
          "pubkey": {\
            "_bn": "f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c"\
          },\
          "isSigner": false,\
          "isWritable": true\
        },\
        {\
          "pubkey": {\
            "_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"\
          },\
          "isSigner": true,\
          "isWritable": false\
        }\
      ],\
      "programId": {\
        "_bn": "06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"\
      },\
      "data": {\
        "type": "Buffer",\
        "data": [\
           7, 232, 3, 0, 0, 0, 0, 0, 0\
        ]\
      }\
    }\
  ],  "signatures": [\
    {\
      "signature": {\
        "type": "Buffer",\
        "data": [\
          20,\
          ... // 省略这部分因为签名数据并不重要\
          5\
        ]\
      },\
      "publicKey": {\
        "_bn": "63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b"\
      }\
    },\
    {\
      "signature": {\
        "type": "Buffer",\
        "data": [\
          0,\
          ...\
          15\
        ]\
      },\
      "publicKey": {\
        "_bn": "f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab"\
      }\
    },\
    {\
      "signature": {\
        "type": "Buffer",\
        "data": [\
          230,\
          ...\
          8\
        ]\
      },\
      "publicKey": {\
        "_bn": "f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c"\
      }\
    }\
  ]
}

这并不是对上述交易结构的完美映射,但它包含了我们需要的所有信息。需要注意的重要一点是,所有的 _bn 项都是地址的十六进制字符串表示。在像Solscan这样的地方,地址通常以base58字符串编码/显示,从现在开始我将使用base58。还有一个 recentBlockhash,但我已将其省略,因为它与这笔交易无关,我已经讨论了这个概念。

现在让我们逐一分析每一部分。

— 指令 #1/5 —

第一条指令生成一个程序派生的账户,该账户将由 Token Program 拥有。此指令包含两个 keys

  • 7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp:这是一个签名者,需要可写,以便从我的余额中扣除SOL。
  • Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL:签名者和可写。我们对此还不太了解。

额外补充: SOL的原子单位称为“lamports”,这让我很懊恼,因为它遵循了基本上每个其他加密项目根据随机密码学家的名字为原子单位命名的传统,而不是从基础单位进行转化。对我们来说没有毫升SOL或纳米SOL。享受你的lamports。

programId设置为00,这意味着我们正在调用 System Program,这是唯一能够在系统中创建新账户的Solana程序。

data包含这个缓冲区:

[\
    0,   0,   0,   0,  96,  77,  22,   0,   0,  0,   0,\
    0,  82,   0,   0,   0,   0,   0,   0,   0,  6, 221,\
  246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206,\
  235, 121, 172,  28, 180, 133, 237,  95,  91, 55, 145,\
   58, 140, 245, 133, 126, 255,   0, 169\
]

前四个字节都是 0。程序函数通过枚举进行索引,因此每条指令的有效载荷的第一个参数引用的是在给定程序API中调用的是哪个函数。由于第一个 u320,这表明我们正在调用 System Program 的第0个函数,即 CreateAccount

额外补充: Solana系统级程序使用 u32 作为枚举索引类型,但该参数的大小取决于程序作者。我们将查看的 Token Program 使用 u8 枚举索引类型,因此我们只需检查第一个字节以确定我们正在调用哪个函数。

lamports

我们有效载荷中的第一个字段是 u64 类型,描述要转移到新账户的lamports数量:

[96,  77,  22,   0,   0,  0,   0,   0]

哇,这是个巨大的数字…还是说不是?哈哈,事实证明Solana使用的是 小端数值编码。在以太坊的C实现后,我非常高兴Solana选择了小端编码。如果你知道,你就知道。

无论如何,小端中这个数字是:

0x0000000000164d60
-> 1461600 lamports

这是什么?这是一个“租金”的支付,用于在Solana系统中保持程序的活跃。我不确定确切机制,但这里是关于租金的官方文档。总结看起来像是,租金费用与 storage 大小成比例。你也可以提前支付2年的租金,这样你的程序就可以永远保持活跃,这正是这里发生的事情。我不知道他们是如何得出这个结论的,但好吧。

space

下一个字段是另一个 u64,表示为此程序分配的存储空间字节数:

[82,   0,   0,   0,   0,   0,   0,   0]

因此… 82 字节?似乎少。这是因为我们实际分配的内存只用于 Token Program 的指针,一个十进制数字,也许还有其他东西。重要的是,此程序不会为用户保存余额 - 相反,用户创建自己的程序以与该程序交互。这很令人困惑。我们稍后会讨论这个。

owner

System Program Create Account 调用的最后字段是“所有者”,在这种情况下将是 Token Program。这是在每个Solana网络(即主网和测试网络)上部署的非常常见的程序,因为每个人都希望引用它来创建自己的代币。人们喜欢代币。

以下字段:

[\
    6, 221, 246, 225, 215, 101, 161,\
  147, 217, 203, 225,  70, 206, 235,\
  121, 172,  28, 180, 133, 237,  95,\
   91,  55, 145,  58, 140, 245, 133,\
  126, 255,   0, 169\
]

以base58编码结果是:

TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

这是我使用的测试网中 Token Program 的地址( 链接)。

额外补充: 我想向那些花时间来挖掘这些Solana地址的人表示敬意。我喜欢他们使用人类可读的前缀,比如 TokenBPFLoader 来命名这些地址。很有趣。这也引出了Solana和以太坊之间的重要差异。在以太坊中,你会根据你的地址和nonce在部署时得到确定性的合约地址 - 在Solana中,你可以不断创建新的密钥对,直到你得到自己喜欢的公钥,因为在Solana系统中实例化时引用的是账户地址(公钥)。这意味着“挖掘”一个地址(公钥)。

指令 #1 总结:

我们通过我们的钱包账户( 7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp )调用了 Create Account 通过 System Program——这在地址为 Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL 创建了一个新的程序拥有的账户,该账户现在拥有 1461600 lamports并由 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA 拥有(即在这个测试网上的 Token Program)。该账户标记为签名者,因为它需要对创建其内存区域的事情进行签名,并且此后将被用于存储状态。但是在交易完成后,由于账户归 Token Program 拥有,因此私钥可以丢弃,因为该账户无法修改自己的状态。

— 指令 #2/5 —

在此指令中,我们直接调用 Token Program

programId = 06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9base58 -> TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

我们的两个 keys 是:

Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL (!signer, writable)
SysvarRent111111111111111111111111111111111 (!signer, !writable)

查看 InitializeMint 参考 中的注释:

/// 该指令预期的账户:
///   0. `[writable]` 要初始化的mint.
///   1. `[]` 租金系统变量

第一个地址是我们将“铸造”的账户——这是我们在之前的指令中创建的。这个概念稍后会变得更清楚,但基本上,这个账户是(某种程度上)Token Program 的一个实例——它描述了你正在创建的代币。它将用于“铸造”代币,这些代币将发送到另一个账户(稍后会详细说明)。这个铸币账户在此指令中需要可写,因为它需要更新其状态。

第二个地址是“租金系统变量”——这似乎是Solana运行时中某些低级操作所必需的,我不知道为什么它被包含在这里,除了可能允许支付租金,但这似乎不是很重要,所以我们继续。

我们可以查看 Token Program API 来弄清楚有效载荷是如何构成的。Solscan 上的记录 表示这是调用 InitializeMint,由第一个字节0所证明,它对应于 Token Program 指令API枚举中的第0个函数索引(正如我之前提到的,这是一个 u8,而不是 u32,因为那是 Token Program 作者们想要的)。

有效载荷:

[\
    0,  2,  99, 232, 238,  67, 168, 88,  48, 19, 186, 111,\
  239, 92,   9,  24,  31,  38, 114, 13,   6, 33, 222, 190,\
  140, 87, 234, 244, 237, 135, 231,  4, 132, 59,   0,   0,\
    0,  0,   0,   0,   0,   0,   0,  0,   0,  0,   0,   0,\
    0,  0,   0,   0,   0,   0,   0,  0,   0,  0,   0,   0,\
    0,  0,   0,   0,   0,   0,   0\
]

第一个 0 是程序API枚举集中函数的索引。在这种情况下是 InitializeMint(请参见它的首个枚举索引 这里)。

第二个字节是2,这是 u8 的数量,表示我们代币的小数位数。请注意,由于代币转移量u64 类型,因此必须对小数精度有相当小的限制,但我不确定该限制是什么或如何强制执行 - 这全是程序特定的内容。

接下来的32字节是 mint_authority,即创建这些初始代币铸造账户的账户。这里是我的钱包账户 7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp

最后32字节是空的,对应于可选的 freeze_authority,我想这是可以冻结此代币转移的账户。不过我们的代币是 无权限 的!

指令 #2 总结:

我们通过 Token Program 调用了 InitializeMint,以让它知道我们将在哪里铸造代币。我们在此指令的 keys 中引用了外部程序:mint账户(在指令 #1 中创建的)和 SysvarRent。再次重申,不清楚后者在做什么。

我们让 Token Program 知道我们的代币将具有 2 位小数,并且代币铸造接收者账户将由我的“权限”账户拥有,也就是我的SOL钱包账户。我们还让它知道,并没有一个账户可以冻结这个代币实例。

— 指令 #3/5 —

继续……我们将使用 System Program 创建 另一个 账户。查看我们的 keys,我们看到第一个与之前相同(我的 7j1N.. 钱包账户),而第二个是新的:

HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f

快速浏览有效载荷:

前四个字节再次是0。我们已经涵盖了这一点。我们正在调用 CreateAccount(函数索引 0)。

lamports2039280

space165 字节。

owner 再次是 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

好的,所以我们创建了另一个由 Token Program 拥有的账户,存储空间为165字节。如果你在想这两个账户的 lamport/space 比例为什么不同( 2039280 / 165 = 123591461600 / 82 = 17824)……抱歉,我没有回答。也许它们在方向上是正确的?

指令 #3 总结:

基本上,这条指令和指令 #1 是一样的。我们在地址 HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f 创建了另一个拥有更多存储空间的账户,并且相同的所有者。这显示了程序拥有的账户如何与不同部分的程序代码进行交互和更新状态,因此在Solana中的 program:program-owned-account 关系为 1:多

— 指令 #4/5 —

我们现在要对 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA 发出另一个指令调用。这次我们的有效载荷要短得多:

[ 1 ]

这意味着我们正在调用索引为1的函数,必须没有参数。此函数称为 InitializeAccount参考)。

没有实际的有效载荷,此指令的要义就是传递哪些账户。让我们看看此指令的 keys

HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f (!signer, writable)
Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL (!signer, !writable)
7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp (!signer, !writable)
SysvarRent111111111111111111111111111111111 (!signer, !writable)

我将直接粘贴 Token Program 的文档:

///   0. `[writable]`  要初始化的账户。
///   1. `[]` 与此账户关联的mint。
///   2. `[]` 新账户的所有者/多签名。
///   3. `[]` 租金系统变量

我们有一些新的术语要与这些账户关联:- HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f 是我们创建的第二个账户(指令 3)。这是我们正在初始化的 token 账户状态。它需要是可写的,以便对状态数据进行操作。这个账户将接收铸造的代币。

  • Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL 是我们创建的第一个账户(指令 1)。这是“token mint”账户,存储有关此代币 实例 的信息。我们在指令 2 中已经在该账户上设置了状态。
  • 7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp 是此持有账户的所有者。现在最好将它视为我的 SOL 账户。它可以持有 SOL 并进行签名,但不能持有代币。我需要一个单独的 token 持有账户来完成这项操作,这就是我们在这里初始化的账户。
  • rent sysvar 账户再次出现。我仍然不知道原因,但我猜原因在于 Solana 系统内部,我现在不必担心。

指令 #4 摘要

基本上我们只使用了 Token Program 来定义我们将要铸造代币的账户,并引用 mint 账户,它是一个具有不同状态数据结构的不同账户。此时,我们已准备好铸造!

— 指令 #5/5 —

在最后一条指令中,我们最后一次调用 Token Program。我们的调用数据是:

[7, 232, 3, 0, 0, 0, 0, 0, 0]

我们正在调用函数 #7,即 MintTo参考)。这里有一个名为 amountu64 参数——相当明显。我们正在铸造 1000 个代币单位,由于有两位小数,因此我们铸造 10 个整体代币。

参考代码注释:

///   * 单一授权
///   0. `[writable]` mint 账户。
///   1. `[writable]` 要铸造代币的账户。
///   2. `[signer]` mint 的铸造授权。

指令 keys

Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL (!signer, writable)
HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f (!signer, writable)
7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp (signer, !writable)

我们正在从所有者账户(7j1N..)对该转账进行签名。我们是从我们的“token mint”账户 Hh1E... 中铸造,并将这些代币发送到我们的新 “token holding” 账户 HJyj...。在此指令中,两个账户都需要被标记为可写,因为它们的状态数据正在被修改。

指令 #5 摘要

我们终于做了一件事!我们 铸造 了 1000 个代币单位,从我们的 mint 账户并将其转移到我们的 token holding 账户。此时,我们可以使用我们的 SOL 账户作为签名者,从我们的 holding 账户支出这些代币,因为它在程序空间(指令 #4)中被定义为该持有账户的所有者。

附注: 虽然具体程序代码不在本练习的范围内,但需要注意的是,铸造所有者账户还需要在单独的指令/交易中将自己移除权限,否则它可以继续铸造代币( credit )。

— 签名者 —

我不打算详细讨论这一点,但基本上在这一点上,你需要将指令集、元数据、签名密钥等序列化为一个“消息”。然后,你将对该消息进行哈希并从所有必要的账户签名(请记住,当你创建任何账户时,你都有它的私钥)。此簇签名密钥可通过查找至少有一个指令中标记为 isSigner=true 的所有唯一账户来确定。在本例中,资金提供者(我的 SOL 账户)、token mint 和 token holder 账户都是签名者。

上述 JSON 有效负载中 signatures 部分包含与三个不同地址相关的三个签名:

63e8ee43a8583013ba6fef5c09181f26720d0621debe8c57eaf4ed87e704843b
f7fa7a4d4e8dda19a3afbec13a5fb4a540289b869b6a5a46162b7c3e3d9161ab
f2560320922ee24d0086e5769e8b3cdf261c646f4f17184532ec7380220b980c

在 base58 中,它们分别是:

7j1NJDkeg35gtRt4nco9fN4wak6M6rD16okMYgvLJeHp
Hh1E4LwRxAiJdLV4gzKX3BpdbNXJ2AYVdv8UDrfTZAbL
HJyjV883iDC3C3vBkqSXiRqCrmFWsk9ER3MFc3KqgF8f

你现在应该能够识别所有这些。如果你查看 JSON 对象,你应该会确信这是至少在一个指令中标记为 isSigner=true 的账户的完整集合。此外,如果你打开 Solscan 记录,你将看到相同的地址。✅

总结

如果你能看到这里,我希望这意味着你觉得这有点有用,现在比之前知道了更多关于 Solana 的信息。太好了,我也是。

以下是我从这个练习中获得的一些非结构化学习总结:

  • “钱包”账户实际上无法直接做太多事情;它们可以持有 SOL、与系统级程序(例如 System Program)交互,并作为程序拥有账户的授权者(在程序空间中链接)。在这个例子中,我们创建了一个 token holding 账户,Token Program 将其视为由我的 SOL 账户“拥有”(指令 #4)——这就是我所指的“授权者”,这种联系仅存在于程序空间中,即不在 Solana 系统级别。
  • 你必须支付租金以填充账户数据,尽管确切的过程在交易级别并不是真正暴露的。租金与账户占用的 storage 空间成正比。如果你提前支付一定的金额(两年的租金),你的程序将永远生存。否则,当它无法支付租金时,它将消失。
  • 指令可以以原子交易的方式打包在一起,并且不需要彼此相关。如果我们愿意,也可以添加一条指令将 SOL 转移到一个无关的账户。
  • 程序代码函数由一个枚举索引。每条指令都有自己独立的调用数据缓冲区,其第一个参数始终为对应于要调用的函数索引的无符号整数。奇怪的是,这个整数可以是程序编写者想要的任何大小(u8-u64)。
  • 调用数据不需要像以太坊那样进行 ABI 编码,并且 Solana 不使用 256 位字,因此有效负载通常会相对较短。
  • Solana 的执行环境可能比 EVM 更高效,但在我看来,真正的性能优势在于你不需要在交易中引用 最新 的交易哈希——你只需引用 一个 最新的。这意味着你可以并行化不影响同一状态的交易,结果似乎很常见的是将状态拆分到多个由同一程序拥有的账户。

总的来说,Solana 的架构与以太坊非常不同。这确实更令人困惑,但我也可以看到它的优势。我很高兴能经历这个练习,我认为这是一个很不错的系统。我希望有机会进一步了解它!

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

0 条评论

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