通过比较以太坊与Solana的编程模型开始Solana开发之旅
如果你熟悉以太坊和Solidity,你可能有兴趣涉足Solana生态系统。Solana的快速区块链很有前景,也很令人兴奋。另外也增加了对web3知识的认知。
但是,如果你从来没有见过Solana程序,它的语法是非常可怕的,而且文档看起来就像一个开发者的重复性笔记。它没有连贯性,你要到后面的章节才会明白某些部分。
这就是为什么我写“ 给Solidity 开发者的 Solana 开发之路系列”的原因,我会向你温和地、没有痛苦和挫折地介绍 Solana。这篇介绍 以太坊与Solana的编程模型比较,基本上它只是在ETH与SOL中如何写程序的概念性差异。
我们不会去讨论Solana在背后是如何工作,比如它的历史证明(POH)等等。这将是另一篇文章的内容。
以下是这篇文章的大纲:
在以太坊中,你习惯于将状态直接存储在智能合约中。看一下这个简单的合约(使用 Solidity 编写):
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中的样子:
#[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中,一个账户只是意味着一个存储单元。它只是一个存储任意数据的容器。
有2种类型的账户:首先是 数据账户
,它只是为程序存储数据,如我们已经提到的 AuthorAccount
。第二种类型是 程序账户
,用于 托管
程序的代码。当你在Solana上部署一个程序时,它的代码被存储在一个 程序账户
中。
一个例子:如果你有一个计数器程序,让你增加一个计数器,你必须创建两个账户:一个账户存储程序的代码,一个账户存储计数器的值。
账户有公钥/地址,以便能够引用它们,它们有私钥,用于签名以证明修改账户的权限(authority)
authority
这个词在Solana世界中使用得相当多。它只是意味着所有者(owner)--私钥的持有人。
账户也存储余额,Solana余额。Solana的原生货币单位是SOL和Lamport(为纪念Solana最大的技术影响者Leslie Lamport而命名)。1 SOL = 10⁹ Lamports。他们和ETH和Wei 类似。
为了创建一个账户,Solana需要在其存储上分配空间。Solana的存储空间不是免费的,所以创建一个账户也不是免费的。你需要向Solana支付租金来 托管
账户。但是不要担心。如果你把2年的租金存入你的账户,你就可以免去租金。每个人都只是这样做,所以在Solana上的存储基本上是免费的。
以下是一个Solana账户中存储的所有内容的摘要:
来自Solana wiki
data
字段要么存储代码,要么存储任意数据,取决于账户是否是可执行(executable)
。我们将在稍后讨论owner
字段。
以太坊也有2种类型的账户:
以下是存储在以太坊账户中的内容,供比较:
来自Solana wiki
codeHash
是用来存储代码的,storageRoot
是用来存储任意数据的。对于不可执行的账户,storageRoot
被设置为一个特殊的 null
哈希值,表示该账户没有存储。
在以太坊中,只有 “可执行账户"有存储。但在Solana中,所有账户都可以存储数据。然而,可执行账户数据专门用于不可变的字节码。所有其他数据都存储在非可执行账户中,非可执行账户是由可执行账户拥有的。
现在,让我们来谈谈Solana账户的owner
(所有者)字段:
为了确保合约不能修改另一个合约的状态,每个数据账户都指定了一个所有者程序,该程序对状态更改有独占控制权。默认情况下,所有者程序是Solana的系统程序(有点像一个操作系统)。
除了所有者之外,没有人可以修改数据账户的状态。任何人都可以把钱存入账户,但只有所有者可以提取余额。
在这一点上,你知道程序(programs)和账户在Solana上的工作。但是,在Solana中有一个小尴尬,在以太坊中是没有的。
想象一下,你在Solana上部署了一个程序,你也在AWS上部署了一个传统的web2前端,用于与该程序进行交互。每次你调用程序时,你需要传入数据账户(以修改状态)。你需要拥有数据账户的私钥,以便能够改变数据账户的状态。
钥匙的管理落在你身上。你打算把它储存在哪里?在web2服务器中作为环境变量?这不太像web3的做法。最好是把这个密钥存储在程序本身,使其更像以太坊--一种将存储附加到程序的方式。
程序衍生地址(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解决了几件事:
总结一下,PDA只是一个账户,其所有者是一个程序,PDA不像其他账户那样有私钥。由于没有相关的私钥,外部用户不能为PDA生成有效的签名。只有其种子推导PDA的程序可以控制它 -- 这是Solana OS的强制要求。
作为写给Solidity开发者的Solana入门指南,本文关于 以太坊的编程模型差异就到此为止了。
本翻译由 Duet Protocol 赞助支持。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!