译文出自:登链翻译计划[1] 译者:Tiny 熊[2]
如果你熟悉以太坊和 Solidity,你可能有兴趣涉足 Solana 生态系统。Solana 的快速区块链很有前景,也很令人兴奋。另外也增加了对 web3 知识的认知。
但是,如果你从来没有见过 Solana 程序,它的语法是非常可怕的,而且文档看起来就像一个开发者的重复性笔记。它没有连贯性,你要到后面的章节才会明白某些部分。
这就是为什么我写“ 给 Solidity 开发者的 Solana 开发之路系列”的原因,我会向你温和地、没有痛苦和挫折地介绍 Solana。这篇介绍 以太坊与 Solana 的编程模型比较,基本上它只是在 ETH 与 SOL 中如何写程序的概念性差异。
我们不会去讨论 Solana 在背后是如何工作,比如它的历史证明(POH)等等。这将是另一篇文章的内容。
以下是这篇文章的大纲:
- 以太坊与 Solana 存储状态的不同
- Solana 账户
- 程序衍生地址(Program Derived Addresses)
以太坊与 Solana 存储状态的不同
在以太坊中,你习惯于将状态直接存储在智能合约中。看一下这个简单的合约(使用 Solidity[3] 编写):
代码语言:javascript复制contract EthereumExample {
struct Author {
uint256 publications;
uint256 likes;
}
Author public author;
function publish() public {
author.publications = 1;
}
}
它只是存储了一个变量author
,并有一个辅助的publish
函数来改变author
这个变量。当你部署这个合约,代码和它的状态一起被存储在一个地址。你可以使用该地址来引用合约并读取其数据(例如,在 Etherscan 上)。
但 Solana 是不同的。
Solana 合约是无状态的。把它们想象成只是指令。他们不存储任何数据/状态。那么,数据存储在哪里呢?它被存储在独立的 账户
中。账户持有数据。当你调用 Solana 合约的函数时,你需要把数据传给函数。
因此,上面的publish
函数需要一个对存储数据的账户的引用。该函数将增加出版物的数量,但作者
变量仍将存储在同一个账户中(而不是在合约中)。合约的状态并没有改变。如果你熟悉 Java,Solana 合约就像 Java 中的静态类。
BTW,智能合约在 Solana 中被称为程序(programs)。
下面是上面的简单合约在 Solana 中的样子:
代码语言:javascript复制#[program]
pub mod solana_example {
use super::*;
pub fn publish(ctx: Context<AuthorData>) -> ProgramResult {
let author_account = &mut ctx.accounts.author_account;
author_account.publications = 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct AuthorData<'info> {
#[account(mut)]
pub author_account: Account<'info, AuthorAccount>,
}
#[account]
pub struct AuthorAccount {
pub publications: u64,
pub likes: u64,
}
注意数据是如何存储在一个单独的AuthorAccount
中的,这个数据是通过引用传递到publish
函数中的(通过Context
)。solana_example
程序本身并不存储任何东西。它只是对传入的数据进行操作。(AuthorData
有点像一个包装器,需要把AuthorAccount
传给publish
函数)。
这个语法很吓人,我知道。这就是 Rust 和 Anchor(Solana 框架)。这让我想起了我开始开发 iOS 和第一次遇到 Objective C 的日子。别担心,我们将在之后的文章中更详细地研究这个语法。它将会更有意义,我保证。
将代码与数据分开,使程序的升级变得容易。在 Solana 中,有可能将一个新版本的程序重新部署到同一地址,同时重复使用相同的数据账户--在不损失数据的情况下进行升级。(这在以太坊中更难做到)。
Solana 账户
账户
是一个模棱两可的词。它在不同的语境中意味着很多东西。在 Solana 中,一个账户只是意味着一个存储单元。它只是一个存储任意数据的容器。
有 2 种类型的账户:首先是 数据账户
,它只是为程序存储数据,如我们已经提到的 AuthorAccount
。第二种类型是 程序账户
,用于 托管
程序的代码。当你在 Solana 上部署一个程序时,它的代码被存储在一个 程序账户
中。
一个例子:如果你有一个计数器程序,让你增加一个计数器,你必须创建两个账户:一个账户存储程序的代码,一个账户存储计数器的值。
账户有公钥/地址,以便能够引用它们,它们有私钥,用于签名以证明修改账户的权限(authority)
authority
这个词在 Solana 世界中使用得相当多。它只是意味着所有者(owner)--私钥的持有人。
账户也存储余额,Solana 余额。Solana 的原生货币单位是 SOL 和 Lamport(为纪念 Solana 最大的技术影响者Leslie Lamport[4]而命名)。1 SOL = 10⁹ Lamports。他们和 ETH 和 Wei 类似。
为了创建一个账户,Solana 需要在其存储上分配空间。Solana 的存储空间不是免费的,所以创建一个账户也不是免费的。你需要向 Solana 支付租金来 托管
账户。但是不要担心。如果你把 2 年的租金存入你的账户,你就可以免去租金。每个人都只是这样做,所以在 Solana 上的存储基本上是免费的。
以下是一个 Solana 账户中存储的所有内容的摘要:
来自 Solana wiki[5]
data
字段要么存储代码,要么存储任意数据,取决于账户是否是可执行(executable)
。我们将在稍后讨论owner
字段。
以太坊也有 2 种类型的账户:
- 外部拥有账户 - 普通账户,可以由钱包软件生成(只需要生成一个私钥,然后导出公钥和地址,你就有一个账户)。这些账户只是存储余额和 nonce。
- 智能合约账户 - 这些存储 EVM 代码,还有一个存储型 map,可用于存储任意数据。
以下是存储在以太坊账户中的内容,供比较:
来自 Solana wiki[6]
codeHash
是用来存储代码的,storageRoot
是用来存储任意数据的。对于不可执行的账户,storageRoot
被设置为一个特殊的 null
哈希值,表示该账户没有存储。
在以太坊中,只有 “可执行账户"有存储。但在 Solana 中,所有账户都可以存储数据。然而,可执行账户数据专门用于不可变的字节码。所有其他数据都存储在非可执行账户中,非可执行账户是由可执行账户拥有的。
现在,让我们来谈谈 Solana 账户的owner
(所有者)字段:
为了确保合约不能修改另一个合约的状态,每个数据账户都指定了一个所有者程序,该程序对状态更改有独占控制权。默认情况下,所有者程序是 Solana 的系统程序(有点像一个操作系统)。
除了所有者之外,没有人可以修改数据账户的状态。任何人都可以把钱存入账户,但只有所有者可以提取余额。
在这一点上,你知道程序(programs)和账户在 Solana 上的工作。但是,在 Solana 中有一个小尴尬,在以太坊中是没有的。
想象一下,你在 Solana 上部署了一个程序,你也在 AWS 上部署了一个传统的 web2 前端,用于与该程序进行交互。每次你调用程序时,你需要传入数据账户(以修改状态)。你需要拥有数据账户的私钥,以便能够改变数据账户的状态。
钥匙的管理落在你身上。你打算把它储存在哪里?在 web2 服务器中作为环境变量?这不太像 web3 的做法。最好是把这个密钥存储在程序本身,使其更像以太坊--一种将存储附加到程序的方式。
程序衍生地址(PDA)解决了这个问题。
程序衍生地址(PDA)
PDA(Program Derived Addresses)本质上允许你将一个数据存储账户附加到一个无状态的程序账户。一种使 Solana 类似以太坊的方式。
它是如何工作的?在程序中,你只是从程序控制的变量中生成一个地址。这就成为一个衍生账户(一个程序衍生的地址)。Solana 操作系统提供了一个辅助函数来推导这个地址。
更具体地说,PDA 是由一个程序 ID 和一个种子集合衍生的。程序 ID 是 Solana 程序的地址。种子可以由程序任意选择(我们将看到种子的用处)。
这个过程是确定的:种子和程序 ID 的组合通过 sha256 哈希函数运行,看它们是否产生一个位于椭圆曲线上的公钥(产生的公钥有~50%位于椭圆曲线上)。如果它确实位于椭圆曲线上,我们只需添加一些东西,对我们的输入进行一些修改,然后再试一次。这个模糊因素的技术术语是 凸点
。在 Solana 中,我们从 bump = 255
开始,然后简单地向下迭代 bump = 254
、bump = 253
,等等,直到我们得到一个不在椭圆曲线上的地址。
**
位于椭圆曲线上是什么意思
**?当一个公钥位于椭圆曲线上时,意味着存在一个相应的私钥,可以使整个私钥加密算法工作。
有一个叫 findProgramDerivedAddress
的函数将整个过程抽象化了。
嗯,这里有很多技术上的东西。如果我把你弄糊涂了,你只需要理解:PDA 是基本上由程序 ID 和一些种子生成的,这样产生的地址就没有相应的私钥。
现在,这个 PDA 对于给 Solana 程序附加存储有什么用?
PDA 解决了几件事:
- 不需要管理/跟踪存储账户的私钥。只需从程序中导出一个地址,并将该地址/账户作为存储。你如何确保其他人不会修改衍生账户?因为衍生地址没有相应的私钥。所以没有人可以修改这个账户。Solana 操作系统确保只有程序被允许修改 PDA。你如何确保其他程序不能衍生出相同的 PDA?他们不能,因为他们的程序 ID 是不同的。
- PDA 也可以用来在独立的账户中存储用户的特定信息。这就是种子变得有用的地方。一个常见的做法是使用最终用户的公钥作为种子生成 PDA,允许程序将该用户的信息存储在自己的独立账户中。程序可以通过使用不同的种子来确定地得出任何数量的地址。这些种子可以象征性地确定地址的使用方式。例如,你可以使用用户的公钥和一个代币的符号作为种子,得到一个用于存储用户关于特定代币信息的账户(每个用户和每个代币将有一个新的 PDA 账户)。
总结一下,PDA 只是一个账户,其所有者是一个程序,PDA 不像其他账户那样有私钥。由于没有相关的私钥,外部用户不能为 PDA 生成有效的签名。只有其种子推导 PDA 的程序可以控制它 -- 这是 Solana OS 的强制要求。
作为写给 Solidity 开发者的 Solana 入门指南,本文关于 以太坊的编程模型差异就到此为止了。
本翻译由 Duet Protocol[7] 赞助支持。
原文:https://betterprogramming.pub/getting-started-with-solana-for-solidity-developers-ee97a9bdf7ed
参考资料
[1]
登链翻译计划: https://github.com/lbc-team/Pioneer
[2]
Tiny 熊: https://learnblockchain.cn/people/15
[3]
Solidity: https://learnblockchain.cn/docs/solidity/
[4]
Leslie Lamport: https://en.wikipedia.org/wiki/Leslie_Lamport
[5]
wiki: https://solana.wiki/zh-cn/docs/account-model/
[6]
wiki: https://solana.wiki/zh-cn/docs/account-model/
[7]
Duet Protocol: https://duet.finance/?utm_souce=learnblockchain