Solana (Blockchain Platform)
Table of Contents
1. Solana
Solana 是一个高性能的区块链平台。Solana 这个名字来源于它的创建者 Anatoly, Greg and Stephen 在圣地亚哥(San Diego)为高通工作时一起冲浪的“一个海滩的名称”。
Solana 采用是的 Proof of Stake(PoS)机制。矿工把原生代币 SOL 抵押给验证节点(Validators),Validators 之间会选择一个 Leader 进行出块,同一时刻只有一个 Leader,每隔 4 Slots(即 Blocks)会轮换 Leader。
Solana 中提出了 Proof of History(PoH),它并不是一个共识机制,可以认为 PoH 实现了分布式系统的“全局时钟”。 有了 PoH,Solana 大大减少了 PBFT 协议中消息的传递,这个改版的 PBFT 被称为 Tower BFT。
When used alongside a consensus algorithm such as Proof of Work (PoW) or Proof of Stake (PoS), PoH can reduce messaging overhead in a Byzantine Fault Tolerant replicated state machine, resulting inn sub-second finality times.
摘自 Solana 白皮书:Solana: A new architecture for a high performance blockchain v0.8.13
1.1. Epoch, Slot
Solana 中有个 Epoch 的概念。 The lifetime of a leader schedule is called an epoch. 也就是说,在一个 Epoch 内,Leader Schedule 不会变。Leader Schedule 就是即轮流当 Leader 的方案,比如某个 Epoch 内确定了由验证节点 A,B,C 轮流当 Leader,那么这个 Epoch 内这个方案就不再改变了。假设有新 Validator 加入,那么在后续的 Epoch 中才可能成为 Leader。
Epoch 由 432,000 个 Slots(即 Blocks)组成, 目前一个 Slot 约 400 ms,也就是说一个 Epoch 约 2-3 天。
1.2. Lamport
Solana 中原生币被称为 SOL,而原生币的最小单位为 Lamport,它们之间的关系是:1 SOL = 1,000,000,000 Lamports(数字 1 后面有 9 个零)。
注:在计算 Prioritization Fee 场景中,我们也会看到 Micro Lamports 单位,它是比 Lamport 更小的单位,1 Lamports = 1,000,000 Micro Lamports。不过,这个单位仅是临时采用,计算完成后,还是会四舍五入到最接近的 Lamports。
1.3. Ed25519
Solana 中签名算法为 Ed25519,而 帐户地址就是 Ed25519 公钥的 base58 编码。
1.4. RPC 方法
Solana 节点所支持的 RPC 方法可参考 https://solana.com/docs/rpc/http 和 https://solana.com/docs/rpc/websocket 。
1.5. Devnet Faucet
Solana Devnet 的测试币可通过 RPC requestAirdrop 获得,或者通过网站 https://faucet.solana.com/ 在线获得。
2. 帐户(Accounts)
Solana 中,Accounts 用于存储数据,可以认为是一个 Key-Value 存储:
- Key 是 Account 的地址;
- Value 是 Account 所关联的信息,它是一个 AccountInfo 结构,这个结构包含表 1 所示的信息(这些信息不能超过 10MB 大小)。
Field | Description |
---|---|
lamports | The number of lamports owned by this account. 账户余额 |
owner | The program owner of this account. 账户的 program owner |
executable | Whether this account can process instructions. 帐户是否可执行 |
data | The raw data byte array stored by this account. 帐户关联的数据 |
rent_epoch | The next epoch that this account will owe rent |
如果 Account 上存储了 data,则需要为其支付租金(Rent)。如果 Account 的余额足够支付 2 年的租金,则会免租(Rent exempt)。
2.1. 通过 RPC 查询帐户信息
通过 RPC getAccountInfo 可以查看一个帐户的相关信息,如查询帐户地址 7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE 所关联的信息:
$ curl -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE"]} ' https://api.mainnet-beta.solana.com { "jsonrpc": "2.0", "result": { "context": { "apiVersion": "1.18.21", "slot": 289922222 }, "value": { "data": [ "", "base58" ], "executable": false, "lamports": 848526228310, "owner": "11111111111111111111111111111111", "rentEpoch": 18446744073709551615, "space": 0 } }, "id": 1 }
下面查询一个 Data 有数据的帐户,比如这个 3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa(注:这个地址是 7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE 的 USDC 的 Token Account,但它是并不是 PDA 地址)如:
$ curl -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getAccountInfo","params":["3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa", {"encoding": "base64"}]} ' https://api.mainnet-beta.solana.com { "jsonrpc": "2.0", "result": { "context": { "apiVersion": "1.18.23", "slot": 289922587 }, "value": { "data": [ "xvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWFgZQGzAuGAGJL4Cil59YX4hV0PIDR5CiRV90T6xQPXtcQiotUrbQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "base64" ], "executable": false, "lamports": 2040720, "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "rentEpoch": 18446744073709551615, "space": 165 } }, "id": 1 }
如果想知道上面 data 的具体含义,可以传入参数 {"encoding": "jsonParsed"}
,如:
curl -X POST -H "Content-Type: application/json" -d ' {"jsonrpc":"2.0","id":1, "method":"getAccountInfo","params":["3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa", {"encoding": "jsonParsed"}]} ' https://api.mainnet-beta.solana.com { "jsonrpc": "2.0", "result": { "context": { "apiVersion": "1.18.23", "slot": 290056928 }, "value": { "data": { "parsed": { "info": { "isNative": false, "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "owner": "7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE", "state": "initialized", "tokenAmount": { "amount": "396428511904708", "decimals": 6, "uiAmount": 396428511.904708, "uiAmountString": "396428511.904708" } }, "type": "account" }, "program": "spl-token", "space": 165 }, "executable": false, "lamports": 2040720, "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "rentEpoch": 18446744073709551615, "space": 165 } }, "id": 1 }
2.2. Owner 属性
每个 Account 都会关联 owner 这个非常重要的属性。
只有 Account 的 owner 才能修改 Account 的 data,也只有 Owner 才能减少 Account 的余额(即减小 lamports 字段值)。 不过有个例外:增加 Account 的余额是没有限制的,即任何 Account 都可以增加某个 Account 的余额(即增大 lamports 字段值)。
2.3. Executable 属性
每个 Account 都会关联 executable 这个非常重要的属性。 在 Solana 中,一个合约要么是代码(executable 为 true),要么是数据(executable 为 false)。代码和数据是严格分开的。
2.4. Token 涉及的 Accounts
目前,Solana 支持两种代币标准:Token Program 和 Token-2022 Program。
下面以 catwifhat(它是一种 Token-2022 代币)为例,介绍一下它涉及到的 Account。
Figure 1: Token-2022 Example (catwifhat)
注 1:Token-2022 的 Data 为 t64jZkgVt8M3MK2Y94VAPovTGpUa8F5fPpP1Kv4LghVqNe8a,对应的 hex 为 02000000be3391c30f4586abc6079d9d9debe74e17c9ec7363f26e63fd9b6ea9be6e5633。其中 02000000 表示是 UpgradeableLoaderState 枚举索引为 2 的选项,即 Program(programdata_address),而 be3391c30f4586abc6079d9d9debe74e17c9ec7363f26e63fd9b6ea9be6e5633 的 base58 编码为 DoU57AYuPFu2QU514RktNPG22QhApEjnKxnBcu4BHDTY。
注 2:上面例子中可知,帐户 H6RD8Q7qxoCD3YcjQJQvZFCgHSUxy9PhRg1FSJErnnJS 对应的 catwifhat 的 Associated Token Account 为 8ZsPBaafiapXPPWwiqMLtxvBWUJg4gii2YDCRBk6AYEk,它是一个 PDA(不在 Ed25519 曲线上,没有对应私钥)地址,通过 associatedTokenAccountAddress 可以计算出来。不过,需要说明的是,尽管 Associated Token Account 推荐采用 PDA 地址,但这并不是强制的,如 7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE 的 USDC(是 Token Program,不是 Token-2022 Program)的 Associated Token Account 就不是 PDA,而是一个有私钥的地址 3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa。
2.4.1. 一个代币可能有多个 Token Accounts
对某用户来说,一个代币可能会有多个 Token Accounts。比如 2pb9va1fRPzvgxj5w2P2Sqq2a5SQpWMbBD5fMYTNSifp 和 2xPnqU4bWhUSjZ74CibY63NrtkHHw5eKntsxf8dzwiid 都是用户 8x2uay8UgrLiX8AAYyF6AkK9z91nNtN6aLwfqPkf6TAQ 的 USDC 的 Token Accounts。
注:不推荐创建多个 Token Accounts,因为这会让 Token 转帐变得复杂,比如 Token Account1 中记录了 100 USDC,另一 Token Account2 中记录了 300 USDC,现在要转帐给别人 400 USDC 则一个交易会同时涉及 Token Account1 和 Token Account2 了。如果大家仅使用 Associated Token Account Program,则可以减少复杂性。
通过 RPC getTokenAccountsByOwner 可以找到用户的所有 Token Accounts,比如查询用户 8x2uay8UgrLiX8AAYyF6AkK9z91nNtN6aLwfqPkf6TAQ 的 USDC 的所有 Token Accounts:
$ curl https://api.mainnet-beta.solana.com -s -X POST -H "Content-Type: application/json" -d ' { "jsonrpc": "2.0", "id": 1, "method": "getTokenAccountsByOwner", "params": [ "8x2uay8UgrLiX8AAYyF6AkK9z91nNtN6aLwfqPkf6TAQ", { "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }, { "encoding": "jsonParsed" } ] } ' | jq { "jsonrpc": "2.0", "result": { "context": { "apiVersion": "2.1.11", "slot": 320094093 }, "value": [ { "account": { "data": { "parsed": { "info": { "isNative": false, "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "owner": "8x2uay8UgrLiX8AAYyF6AkK9z91nNtN6aLwfqPkf6TAQ", "state": "initialized", "tokenAmount": { "amount": "269827451550", "decimals": 6, "uiAmount": 269827.45155, "uiAmountString": "269827.45155" } }, "type": "account" }, "program": "spl-token", "space": 165 }, "executable": false, "lamports": 2039280, "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "rentEpoch": 18446744073709551615, "space": 165 }, "pubkey": "2xPnqU4bWhUSjZ74CibY63NrtkHHw5eKntsxf8dzwiid" }, { "account": { "data": { "parsed": { "info": { "isNative": false, "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "owner": "8x2uay8UgrLiX8AAYyF6AkK9z91nNtN6aLwfqPkf6TAQ", "state": "initialized", "tokenAmount": { "amount": "7020590", "decimals": 6, "uiAmount": 7.02059, "uiAmountString": "7.02059" } }, "type": "account" }, "program": "spl-token", "space": 165 }, "executable": false, "lamports": 2039280, "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "rentEpoch": 18446744073709551615, "space": 165 }, "pubkey": "2pb9va1fRPzvgxj5w2P2Sqq2a5SQpWMbBD5fMYTNSifp" } ] }, "id": 1 }
参考:https://solana.stackexchange.com/questions/4616/explanation-for-two-token-accounts-for-one-owner
2.4.2. 发送者帮接收者创建 Token Account
A 给 B 转帐 Token,如果 B 的 Token Account 没有创建,则 A 在转帐交易中需要同时帮助 B 创建 Token Account。这种情况下大家都使用 Associated Token Account 作为新 Token Account 的生成标准。
交易 25NhZZgELi5hi9xCvv6d1utXR7QQQKkc7b3bwDsa28wMtYLhSuxNy7Hw4VNwoxBiv9RveYDKT78xv1E3P3vd56uJ 就是转帐时同时帮助接收者创建 Token Account 的例子。
3. 交易
Solana 中,Tx 的格式如图 2 所示。
Figure 2: Solana Legacy Tx(摘自:https://solana.com/docs/core/transactions )
其字段的说明如下:
- Signatures: An array of digital signatures from the transaction's signers.
- Message: The actual instructions that the transaction is issuing to the network.
2.1. Message header: 3 uint8s describing "the number of required signatures for the transaction", "the number of read-only account addresses that require signatures", "the number of read-only account addresses that do not require signatures".
2.2. Account addresses: an array of addresses of the accounts that will be used in the transaction. The addresses is ordered by the privileges for the accounts. 在交易中声明所涉及的 Accounts 可以方便 Solana 进行并行处理,参考节 5.3.1。
2.3. Recent blockhash: a unique value that identifies a recent block - this ensures the transaction is not too old and is not re-processed.
2.4. Instructions: which program to call, which accounts to use, and any additional data needed for the program to execute the instruction.
说明 1、Solana 中没有采用类似 Ethereum 的 Nonce 机制来防止一个交易被处理多次。而是采用其它的方式:在 Tx 中需要指定 Recent blockhash(通过 RPC getLatestBlockhash 可以得到这个信息)。如果节点收到一个 Tx,发现 Tx 的 Recent blockhash 太旧了(即 150 blocks 之前的 blockhash,约 80-90 秒之前的 blockhash),则会直接拒绝;如果 Recent blockhash 比较新,则查看最近有没有处理相同的 Tx(这要求每个节点需要维护最近区块的所有交易列表)。这样,就可以防止一个旧 Tx 被重复处理。不过,采用 Recent blockhash 也有不好的地方:一个 Tx 在 80-90 秒内没有被打包,则也不会被打包了,需要用户重新提交 Tx,这在网络拥堵时会给用户带来麻烦。为了解决这个问题,Solana 也支持使用 Nonce 来代替 Recent blockhash,细节可以参考:https://solana.com/developers/guides/advanced/introduction-to-durable-nonces#durable-nonces 。
说明 2、Fee 并没有体现在 Tx 中。对于普通转账,目前策略是对每个签名收取 5000 Lamports。并且 由 Account addresses 中的首个地址支付 Fee。 当然也可以指定小费加快交易,参考节 3.2。
说明 3、图 2 介绍的是 Legacy Tx 格式,不是 V0 Tx 格式。
参考:
https://solana.com/docs/core/transactions
https://solana.wiki/docs/solidity-guide/transactions/
https://solanacookbook.com/core-concepts/transactions.html
3.1. Message Header 对 Accoout Addresses 分类
Tx 结构中 Message 部分的 Message Header 由 3 个 uint8 整数构成,它们的其含义分别为:
- The number of required signatures for the transaction.
- The number of read-only account addresses that require signatures.
- The number of read-only account addresses that do not require signatures.
Tx 结构中 Message 部分的 Accoout Addresses 是按照重要程度来排序的,这些地址按重要性从高到低一共可以分为 4 组:
- Accounts that are writable and signers
- Accounts that are read-only and signers
- Accounts that are writable and not signers
- Accounts that are read-only and not signers
结合 Message Header 中的信息(即 3 个 uint8),可以确定 Accoout Addresses 中每个地址分别属于哪一组,如图 3 所示。
Figure 3: Solana Tx 中的四组 Account Addresses
3.1.1. Accoout Addresses 分类实例
下面以交易 VGJxEj15eQaiQXwjvg7kYt3otikuZb4by2riKfFW8xoivAGn7tRDpAiWxppRbv8tvuTQ4FcWu5kmd9zdVeLjCjW 为例介绍一下,如何根据 Message Header 中的信息对 Accoout Addresses 进行分类。
这个交易的 Message Header 和 Accoout Addresses 如下所示:
+---------+-------------------+--------------------------------+------------------------------------------------------------------+----------------------+ | | | num required signatures | 03 | | | | +--------------------------------+------------------------------------------------------------------+----------------------+ | | message header | num readonly signed accounts | 00 | | | | +--------------------------------+------------------------------------------------------------------+----------------------+ | | | num readonly unsigned accounts | 01 | | | +-------------------+--------------------------------+------------------------------------------------------------------+----------------------+ | | | length | 06 | | | | +--------------------------------+------------------------------------------------------------------+----------------------+ | | | address 0 | 8c65b4bece04ed8d09ae5452fc56dfade99469c0815514899775865af7783f9d | Signer, Writable | | | +--------------------------------+------------------------------------------------------------------+----------------------+ | message | | address 1 | 393cad9ffeafd731e020f5bb513046502fee78e262486d1c82f11748c1d0e7c8 | Signer, Writable | | | +--------------------------------+------------------------------------------------------------------+----------------------+ | | account addresses | address 2 | 6602663e9bb4f10428464f45a81efe35c4a733dda01d9dbed6fc9f13d22eebf2 | Signer, Writable | | | +--------------------------------+------------------------------------------------------------------+----------------------+ | | | address 3 | 06da29ddbfc16a146fb6fc87aef4389f9a0a44da8ceda0e7e4dd8ed8b491609d | Non-Signer, Writable | | | +--------------------------------+------------------------------------------------------------------+----------------------+ | | | address 4 | 30e8a603e3770346bfea245fb3f51f3cb7c6c29825e1ff12217d274fb980fad7 | Non-Signer, Writable | | | +--------------------------------+------------------------------------------------------------------+----------------------+ | | | address 5 | 0000000000000000000000000000000000000000000000000000000000000000 | Non-Signer, Readonly | | +-------------------+--------------------------------+------------------------------------------------------------------+----------------------+ | | recent blockhash | dd1a4a7ec76ef335c8ab6bff529991ee03dc7c1db6861f22ba603c12d2476d53 | | | +----------------------------------------------------+------------------------------------------------------------------+----------------------+ | | instructions | ...... | | +---------+----------------------------------------------------+------------------------------------------------------------------+----------------------+
一共有 6 个 Accoout Addresses。由 Message Header 可知:
- 其中 3 个(包含 writable/readonly 帐户)帐户需要签名;
- 0 个 readonly 帐户需要签名;
- 1 个 readonly 帐户不需要签名。
- Writable 且需要签名的帐户个数是 3,即 Address 0/1/2;
- Readonly 且需要签名的帐户个数是 0;
- Writable 且不需要签名的帐户个数是 2,即 Address 3/4;
- Readonly 且不需要签名的帐户个数是 1,即 Address 5。
3.2. 手续费
- 每个签名固定收取 5000 Lamports;
- 交易执行时所消耗的计算资源;
- 小费(它是可选的)。
注 1:关于第 1 部分和第 2 部分的手续费可以通过 RPC getFeeForMessage 得知,这两部分是自动扣取的,并不需要在提交交易时指定。
注 2:如果想在交易中设置小费来加快交易的打包上链过程,则可以在交易中使用 Compute Budget 指令,它有两个主要选项,即 SetComputeUnitPrice 和 SetComputeUnitLimit。例如,交易 23cHBWBcgfBYtA4Zimyky9wxdEeoZgqWqewQAyHQYa4EbRKh7b85CrifGZ4wshRr9jr3U2kfr1a5QNN5xhf4XbPV 使用了 Compute Budget 指令(这个交易只使用了 SetComputeUnitPrice,没有使用 SetComputeUnitLimit)设置小费来加快打包上链。
参考:https://solana.com/docs/intro/transaction_fees#calculating-transaction-fees
4. 钱包基础操作(命令行演示)
下面演示一下如何通过 Solana Command Line 进行钱包的基本操作,如创建、转账、查看余额等。
4.1. 创建钱包
使用 solana-keygen new
可以创建钱包。创建第 1 个钱包:
$ solana-keygen new Generating a new keypair For added security, enter a BIP39 passphrase NOTE! This passphrase improves security of the recovery seed phrase NOT the keypair file itself, which is stored as insecure plain text BIP39 Passphrase (empty for none): Wrote new keypair to /Users/user1/.config/solana/id.json ========================================================================= pubkey: AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r # Solana 中,钱包地址就是 Ed25519 公钥的 base58 编码 ========================================================================= Save this seed phrase and your BIP39 passphrase to recover your new keypair: click amateur flip company best wet bar unhappy child jealous when nephew # 需要保密的 12 个助记词 =========================================================================
我们可以看看 keypair 文件的具体内容:
$ cat /Users/user1/.config/solana/id.json [170,94,113,29,19,58,202,69,214,236,151,17,109,229,74,36,164,33,168,35,56,95,196,47,211,66,213,236,177,124,231,208,140,101,180,190,206,4,237,141,9,174,84,82,252,86,223,173,233,148,105,192,129,85,20,137,151,117,134,90,247,120,63,157]
这个 json 文件的内容是字节数组(64 个元素),它其实就是 ed25519 的私钥和公钥(字节数组的前 32 字节是私钥,后 32 字节是公钥),参考:https://mattmazur.com/2021/11/19/splitting-a-solana-keypair-into-a-public-and-private-keys/ ,相关源码在 https://github.com/solana-labs/solana/blob/550ca7bf92db57d309fa91479b414786da968fa7/sdk/src/signer/keypair.rs#L119
通过 faucet,往第 1 个钱包转入 1 个 SOL 测试币:
$ solana airdrop 1 AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r --url https://api.devnet.solana.com Requesting airdrop of 1 SOL Signature: rCioafR3y873N5X6gritMhAJSFJdLVXR1EV6LJHLqCzMdP5A8tqANM9kF6Y3ubTwMyZd5XcasEcZnU3STbXECUy 1 SOL
上面命令输出的 Signature 后的字段就是 tx,在区块浏览器上可以查看它的信息,如:https://solscan.io/tx/rCioafR3y873N5X6gritMhAJSFJdLVXR1EV6LJHLqCzMdP5A8tqANM9kF6Y3ubTwMyZd5XcasEcZnU3STbXECUy?cluster=devnet
4.2. 推导地址
4.2.1. 从 keypair json 文件推导地址
使用 solana-keygen pubkey [KEYPAIR]
可以显示 keypair 文件所对应的地址(即 Ed25519 公钥的 base58 编码):
$ solana-keygen pubkey /Users/user1/.config/solana/id.json AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r $ solana-keygen pubkey AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r
4.2.2. 从助记词推导地址
使用 solana-keygen pubkey ASK
可以从用户输入的助记词推导出地址(即 Ed25519 公钥的 base58 编码),如:
$ solana-keygen pubkey ASK [pubkey recovery] seed phrase: <= 这里输入助记词,如 click amateur flip company best wet bar unhappy child jealous when nephew [pubkey recovery] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue: AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r
上面命令推导出的公钥是 bip32 中 Master Key 所对应的公钥,并不涉及 Derivation Path。
需要说明的是,其它钱包一般不会直接使用 bip32 的 Master Key,而是会使用一个 Derivation Path 得到子私钥,再得到它的公钥。比如 Trust Wallet 使用 m/44'/501'/0'
作为 Solana 的 Derivation Path。 solana-keygen pubkey
支持定制 Derivation Path 来推导子私钥,如:
$ solana-keygen pubkey "prompt://?full-path=m/44'/501'/0'" [pubkey recovery] seed phrase: <= 这里输入助记词,如 click amateur flip company best wet bar unhappy child jealous when nephew [pubkey recovery] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue: 6hSCKjhkkcjqXFDQCyc2MkUHKcKTJMehoxzcqVauJGoj
参考:https://forums.solana.com/t/paperwallet-addresses-bad-guide-in-solana-docs/3042
4.3. 测试转账
在测试转账前,我们先创建第 2 个钱包,以作为接收地址:
$ solana-keygen new --outfile my_solana_wallet.json Generating a new keypair For added security, enter a BIP39 passphrase NOTE! This passphrase improves security of the recovery seed phrase NOT the keypair file itself, which is stored as insecure plain text BIP39 Passphrase (empty for none): Wrote new keypair to my_solana_wallet.json ============================================================================= pubkey: TkPgkcDQYDUSDBfwTokwkZpn8spz94u4fvXii9U7CWY ============================================================================= Save this seed phrase and your BIP39 passphrase to recover your new keypair: witness magnet rail absent modify harsh name coin endorse stem position green =============================================================================
使用 solana transfer
可以往另一个地址转账,如:
$ solana transfer TkPgkcDQYDUSDBfwTokwkZpn8spz94u4fvXii9U7CWY 0.1 --allow-unfunded-recipient --url https://api.devnet.solana.com Signature: 2Z3a9hLA1rprRpUbwvPWVNtKytr9k8y7PzHunxoJG25Qes6pXaubarshG2fJj5bwGndYudnTT4U3H8UMVxYhx3ec
注:当往一个新地址转账时,需要指定 --allow-unfunded-recipient
参数。
可在区块浏览器中查看上面的转账:https://solscan.io/tx/2Z3a9hLA1rprRpUbwvPWVNtKytr9k8y7PzHunxoJG25Qes6pXaubarshG2fJj5bwGndYudnTT4U3H8UMVxYhx3ec?cluster=devnet
4.4. 查看余额
通过 RPC getBalance 可以查看余额。在命令行中,使用 solana balance
可以查看某帐户的余额,如:
$ solana balance TkPgkcDQYDUSDBfwTokwkZpn8spz94u4fvXii9U7CWY --url https://api.devnet.solana.com 0.1 SOL $ solana balance TkPgkcDQYDUSDBfwTokwkZpn8spz94u4fvXii9U7CWY --lamports --url https://api.devnet.solana.com 100000000 lamports
5. 智能合约(Solana Programs)
本质上说, Solana Programs(智能合约)就是 executable 属性为 true 的 Account。
有两种类型的 Solana Programs:
- 内置程序(Native Programs)
- 链上程序(On-chain Programs)
这两种类型的 Solana Programs 都运行在 Sealevel Runtime 基础上。Sealevel Runtime 具有并行处理能力,可以更快地处理交易。
5.1. 内置程序(Native Programs)
内置程序(Native Programs)直接部署在 Solana 区块链的节点代码中。
下面是内置程序(Native Programs)的例子:
5.2. 链上程序(On-chain Programs)
目前 Solana 支持 Rust 和 C/C++ 编写链上程序,它们会被 LLVM 编译为 BPF 字节码提交到链上执行。
下面是 Hello World 链上程序的示例:
use solana_program::{ account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, msg, }; // declare and export the program's entrypoint entrypoint!(process_instruction); // program entrypoint's implementation pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8] ) -> ProgramResult { // log a message to the blockchain msg!("Hello, world!"); // gracefully exit the program Ok(()) }
参考:https://solana.com/developers/guides/getstarted/hello-world-in-your-browser
5.2.1. 部署
目前 Solana 对序列化后 Tx 的大小限制是 1232 字节,这导致我们在部署智能合约时往往无法只通过一个 Tx 完成,而是要提交很多 Tx(具体数据依赖于智能合约的大小)才能完成智能合约的部署。
关于链上程序(智能合约)的部署细节可以参考文档:https://solana.com/docs/programs/deploying
5.3. 内存模型(一个巨大的 Heap)
可以把 Solana 的内存模型看作是一个巨大的 Heap。链上的所有状态都保存在这个堆里。每个内存区域都有一个管理它的程序(有时称为“所有者”)。使用术语 Account 来标记某个内存区域。如图 4 所示。
Figure 4: Solana 的内存模型(摘自:https://www.anchor-lang.com/docs/intro-to-solana )
5.3.1. 交易中指定关联的 Accounts(方便并行处理)
从节 3 可以知道, 在提交 Solana 交易时,需要指定和这个交易关联的 Accounts。这种设计可以方便 Solana 进行并行处理。如果两个交易关联的 Accounts 之间没有读写冲突,那么它们显然是可以并行运行的。 如图 5 所示。
Figure 5: 每个交易指定关联的 Accounts(摘自:https://www.anchor-lang.com/docs/intro-to-solana )
5.3.2. 合约代码和状态数据在不同的 Account 中
在 Ethereum 中,合约代码和状态数据是保存在一起的,Solana 在这一点上和 Ethereum 不一样。 在 Solana 中,合约代码和状态数据是分开存储的。它们保存在不同的 Account 中,对于保存合约代码的 Account 来说,它的 executable 属性为 true;对于保存合约状态数据的 Account 来说,它的 executable 属性为 false。
5.4. Counter 合约开发实例
6. 参考
Solana Documentation: https://solana.com/docs
Guide for Solidity Developers: https://solana.wiki/docs/solidity-guide/
Solana Cookbook: https://solanacookbook.com
Solana Whitepaper: Solana: A new architecture for a high performance blockchain v0.8.13
Solana Github: https://github.com/solana-labs
Solana RPC Documentation: https://solana.com/docs/rpc
Solana Proof of Stake + Proof of History Primer: https://www.shinobi-systems.com/primer.html