写给Solidity开发者的Solana入门指南

  • Tiny熊
  • 更新于 2022-07-12 10:37
  • 阅读 6555

通过比较以太坊与Solana的编程模型开始Solana开发之旅

img

如果你熟悉以太坊和Solidity,你可能有兴趣涉足Solana生态系统。Solana的快速区块链很有前景,也很令人兴奋。另外也增加了对web3知识的认知。

但是,如果你从来没有见过Solana程序,它的语法是非常可怕的,而且文档看起来就像一个开发者的重复性笔记。它没有连贯性,你要到后面的章节才会明白某些部分。

这就是为什么我写“ 给Solidity 开发者的 Solana 开发之路系列”的原因,我会向你温和地、没有痛苦和挫折地介绍 Solana。这篇介绍 以太坊与Solana的编程模型比较,基本上它只是在ETH与SOL中如何写程序的概念性差异。

我们不会去讨论Solana在背后是如何工作,比如它的历史证明(POH)等等。这将是另一篇文章的内容。

以下是这篇文章的大纲:

  • 以太坊与Solana存储状态的不同
  • Solana账户
  • 程序衍生地址(Program Derived Addresses)

以太坊与Solana存储状态的不同

在以太坊中,你习惯于将状态直接存储在智能合约中。看一下这个简单的合约(使用 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账户

账户是一个模棱两可的词。它在不同的语境中意味着很多东西。在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账户存储结构

来自Solana wiki

data字段要么存储代码,要么存储任意数据,取决于账户是否是可执行(executable)。我们将在稍后讨论owner字段。

以太坊也有2种类型的账户:

  • 外部拥有账户 - 普通账户,可以由钱包软件生成(只需要生成一个私钥,然后导出公钥和地址,你就有一个账户)。这些账户只是存储余额和nonce。
  • 智能合约账户 - 这些存储EVM代码,还有一个存储型map,可用于存储任意数据。

以下是存储在以太坊账户中的内容,供比较:

以太坊存储结构

来自Solana wiki

codeHash是用来存储代码的,storageRoot是用来存储任意数据的。对于不可执行的账户,storageRoot被设置为一个特殊的 null哈希值,表示该账户没有存储。

在以太坊中,只有 “可执行账户"有存储。但在Solana中,所有账户都可以存储数据。然而,可执行账户数据专门用于不可变的字节码。所有其他数据都存储在非可执行账户中,非可执行账户是由可执行账户拥有的。

现在,让我们来谈谈Solana账户的owner(所有者)字段:

img

为了确保合约不能修改另一个合约的状态,每个数据账户都指定了一个所有者程序,该程序对状态更改有独占控制权。默认情况下,所有者程序是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 = 254bump = 253,等等,直到我们得到一个不在椭圆曲线上的地址。

位于椭圆曲线上是什么意思?当一个公钥位于椭圆曲线上时,意味着存在一个相应的私钥,可以使整个私钥加密算法工作。

有一个叫 findProgramDerivedAddress的函数将整个过程抽象化了。

嗯,这里有很多技术上的东西。如果我把你弄糊涂了,你只需要理解:PDA是基本上由程序ID和一些种子生成的,这样产生的地址就没有相应的私钥。

现在,这个PDA对于给Solana程序附加存储有什么用?

PDA解决了几件事:

  • 不需要管理/跟踪存储账户的私钥。只需从程序中导出一个地址,并将该地址/账户作为存储。你如何确保其他人不会修改衍生账户?因为衍生地址没有相应的私钥。所以没有人可以修改这个账户。Solana操作系统确保只有程序被允许修改PDA。你如何确保其他程序不能衍生出相同的PDA?他们不能,因为他们的程序ID是不同的。
  • PDA也可以用来在独立的账户中存储用户的特定信息。这就是种子变得有用的地方。一个常见的做法是使用最终用户的公钥作为种子生成PDA,允许程序将该用户的信息存储在自己的独立账户中。程序可以通过使用不同的种子来确定地得出任何数量的地址。这些种子可以象征性地确定地址的使用方式。例如,你可以使用用户的公钥和一个代币的符号作为种子,得到一个用于存储用户关于特定代币信息的账户(每个用户和每个代币将有一个新的PDA账户)。

总结一下,PDA只是一个账户,其所有者是一个程序,PDA不像其他账户那样有私钥由于没有相关的私钥,外部用户不能为PDA生成有效的签名。只有其种子推导PDA的程序可以控制它 -- 这是Solana OS的强制要求。

作为写给Solidity开发者的Solana入门指南,本文关于 以太坊的编程模型差异就到此为止了。


本翻译由 Duet Protocol 赞助支持。

点赞 9
收藏 13
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
Tiny熊
Tiny熊
0xD682...E8AB
登链社区发起人 通过区块链技术让世界变得更好而尽一份力。