@@ -3,48 +3,85 @@ title: Solana账户模型
3
3
description : 了解Solana的账户模型,包括账户如何存储数据和程序、rent机制、账户所有权以及程序与数据账户之间的关系。理解Solana键值存储系统的核心概念。
4
4
---
5
5
6
- 在Solana上,所有数据都包含在我们称为"accounts"(账户)的结构中。你可以将Solana上的数据视为一个公共数据库,其中有一个单一的"Accounts"表,该表中的每个条目都是具有相同基本[ Account类型] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/account/src/lib.rs#L48-L60 ) 的独立账户。
6
+ 在Solana上,所有数据都存储在所谓的"accounts"(账户)中。你可以将Solana上的数据视为一个公共数据库,其中有一个单一的"Accounts"表,这个表中的每个条目就是一个"account"。每个Solana账户都共享相同的基础
7
+ [ Account类型] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/account/src/lib.rs#L48-L60 ) 的独立账户。
7
8
8
9
![ Accounts] ( /assets/docs/core/accounts/accounts.png )
9
10
10
11
## 要点
11
12
12
- * 账户最多可以存储[ 10MiB] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/program/src/system_instruction.rs#L85 ) 的数据,这些数据可以包含可执行程序代码或程序状态。
13
- * 账户需要一笔与存储数据量成比例的lamport(SOL)[ rent存款] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/rent/src/lib.rs#L93-L97 ) ,当账户关闭时,这笔存款可以全额退还。
14
- * 每个账户都有一个程序[ owner] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/account/src/lib.rs#L55 ) (所有者)。只有拥有账户的程序才能修改其数据或扣除其lamport余额。但是,任何人都可以增加余额。
13
+ * 账户最多可以存储[ 10MiB] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/program/src/system_instruction.rs#L85 )
14
+ 数据,其中包含可执行程序代码或程序状态。
15
+ * 账户需要一笔与存储数据量成比例的lamport(SOL)[ rent存款] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/rent/src/lib.rs#L93-L97 )
16
+ lamport(SOL)余额,其金额与存储的数据量成正比,当你关闭账户时可以完全恢复这些资金。
17
+ * 每个账户都有一个程序[ owner] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/account/src/lib.rs#L55 ) 。只有拥有账户的程序才能更改其数据或扣除其lamport余额。但任何人都可以增加余额。
15
18
* ** Sysvar账户** 是存储网络集群状态的特殊账户。
16
19
* ** Program账户** 存储智能合约的可执行代码。
17
20
* ** Data账户** 由程序创建,用于存储和管理程序状态。
18
21
19
22
## 账户
20
23
21
- Solana上的每个账户都由一个唯一的32字节地址标识,这通常显示为base58编码的字符串 (例如` 14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5 ` )。
24
+ Solana上的每个账户都有一个唯一的32字节地址,通常显示为base58编码的字符串 (例如` 14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5 ` )。
22
25
23
- 账户与其地址之间的关系可以被视为键值对,其中地址作为键,用于定位账户对应的链上数据 。
26
+ 账户与其地址之间的关系类似于键值对,其中地址是定位账户对应链上数据的键。账户地址充当"Accounts"表中每个条目的"唯一ID" 。
24
27
25
28
![ Account Address] ( /assets/docs/core/accounts/account-address.svg )
26
29
27
30
大多数Solana账户使用[ Ed25519] ( https://ed25519.cr.yp.to/ ) 公钥作为其地址。
28
31
29
- 虽然公钥通常用作账户地址,但Solana还支持一种称为Program Derived
30
- Address(PDA)的功能。PDA是特殊地址,它们是从程序ID和可选输入(seed)确定性派生的。详细信息在[ Program Derived Address] ( /docs/core/pda ) 页面中介绍。
32
+ <CodeTabs flags = " r" >
33
+ ``` ts !! title="Generate Keypair"
34
+ import { Keypair } from " @solana/web3.js" ;
31
35
32
- ### 账户类型
36
+ const keypair = Keypair .generate ();
37
+ console .log (` Public Key: ${keypair .publicKey } ` );
38
+ console .log (` Secret Key: ${keypair .secretKey } ` );
33
39
34
- 账户的最大大小为[ 10MiB] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/program/src/system_instruction.rs#L85 )
35
- 而Solana上的每个账户都有相同的基本
36
- [ 账户] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/account/src/lib.rs#L48-L60 )
37
- 类型。
40
+ ```
41
+ </CodeTabs >
42
+
43
+ 虽然公钥通常用作账户地址,但Solana还支持一种称为Program Derived Addresses(PDAs)的功能。PDAs是特殊地址,你可以从程序ID和可选输入(seeds)确定性地派生出这些地址。详细信息在[ Program Derived Address] ( /docs/core/pda ) page.
44
+
45
+ <CodeTabs flags = " r" >
46
+ ``` ts !! title="Derive PDA"
47
+ import { PublicKey } from " @solana/web3.js" ;
48
+
49
+ const programAddress = new PublicKey (" 11111111111111111111111111111111" );
50
+
51
+ const seeds = [Buffer .from (" helloWorld" )];
52
+ const [pda, bump] = await PublicKey .findProgramAddressSync (
53
+ seeds ,
54
+ programAddress
55
+ );
56
+
57
+ console .log (` PDA: ${pda } ` );
58
+ console .log (` Bump: ${bump } ` );
59
+ ```
60
+ </CodeTabs >
61
+
62
+ ### Account类型
63
+
64
+ 账户的最大大小为
65
+ [ 10MiB] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/program/src/system_instruction.rs#L85 )
66
+ 并且Solana上的每个账户都共享相同的基础
67
+ [ Account] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/account/src/lib.rs#L48-L60 )
68
+ type.
38
69
39
70
![ Account Type] ( /assets/docs/core/accounts/account-type.svg )
40
71
41
- 并且Solana上的每个账户都具有相同的基本
72
+ Solana上的每个账户都有以下字段:
42
73
43
- * ` data ` : 存储账户任意数据的字节数组。对于非可执行账户,这通常存储只读状态。对于程序账户(智能合约),这包含可执行程序代码。数据字段通常被称为"账户数据"。
44
- * ` executable ` : 这个布尔标志用于指示账户是否为程序。
45
- * ` lamports ` : 账户中的lamport余额,SOL的最小单位(1 SOL = 10亿lamports)。
46
- * ` owner ` : 拥有此账户的程序ID(公钥)。只有所有者程序可以修改账户数据或扣除其lamports余额。
47
- * ` rent_epoch ` : 一个遗留字段,来自Solana曾经有一个机制定期从账户中扣除lamports的时期。虽然这个字段仍然存在于账户类型中,但自从rent收集被弃用后就不再使用了。
74
+ * ` data ` : A byte array that stores arbitrary data for an account. For
75
+ non-executable accounts, this often stores state that's meant be read from.
76
+ For program accounts (smart contracts), this contains the executable program
77
+ code. The data field is commonly called "account data."
78
+ * ` executable ` : 此标志显示账户是否为程序。
79
+ * ` lamports ` : The account's balance in lamports, the smallest unit of SOL (1 SOL
80
+ \= 1 billion lamports).
81
+ * ` owner ` : 拥有此账户的程序的程序ID(公钥)。只有所有者程序可以更改账户的数据或扣除其lamport余额。
82
+ * ` rent_epoch ` : A legacy field from when Solana had a mechanism that
83
+ periodically deducted lamports from accounts. While this field still exists in
84
+ the Account type, it is no longer used since rent collection was deprecated.
48
85
49
86
``` rust title="Base Account Type"
50
87
pub struct Account {
@@ -62,72 +99,207 @@ pub struct Account {
62
99
}
63
100
```
64
101
102
+ <CodeTabs flags = " r" >
103
+ ``` ts !! title="Fetch Account"
104
+ import { Keypair , Connection , LAMPORTS_PER_SOL } from " @solana/web3.js" ;
105
+
106
+ const keypair = Keypair .generate ();
107
+ console .log (` Public Key: ${keypair .publicKey } ` );
108
+
109
+ const connection = new Connection (" http://127.0.0.1:8899" , " confirmed" );
110
+
111
+ // 向地址注入SOL自动创建一个账户const signature = await
112
+ connection .requestAirdrop ( keypair .publicKey , LAMPORTS_PER_SOL ); await
113
+ connection .confirmTransaction (signature , " confirmed" );
114
+
115
+ const accountInfo = await connection .getAccountInfo (keypair .publicKey );
116
+ console .log (accountInfo );
117
+
118
+ ```
119
+ </CodeTabs >
120
+
65
121
### Rent
66
122
67
- Solana上的每个账户都有以下字段:[ 这里] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/rent/src/lib.rs#L93-L97 )
123
+ 要在链上存储数据,账户还必须保持与存储在账户上的数据量(以字节为单位)成
124
+ 比例的lamport(SOL)余额。这个余额被称为"rent",但它的工作方式更像是一种押金,
125
+ 因为当你关闭账户时可以收回全部金额。你可以找到计算方法
126
+ [ 这里] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/rent/src/lib.rs#L93-L97 )
68
127
使用这些
69
- [ 常量 ] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/rent/src/lib.rs#L47-L70 ) 。
128
+ [ constants ] ( https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/rent/src/lib.rs#L47-L70 ) .
70
129
71
- "rent"一词源于一种已弃用的机制,该机制会定期从低于rent阈值的账户中扣除lamports。这种机制不再活跃。
130
+ 术语"rent"来源于一种已弃用的机制,该机制会定期从低于rent阈值的账户中扣除
131
+ lamport。这种机制现在已不再活跃。
72
132
73
133
### 程序所有者
74
134
75
- Rent[ 程序] ( /docs/core/programs ) 。程序所有权是Solana账户模型的关键方面。每个账户都有一个指定的程序作为其所有者。只有所有者程序可以:
135
+ 在Solana上,"智能合约"被称为[ 程序] ( /docs/core/programs ) 。程序
136
+ 所有权是Solana账户模型的关键部分。每个账户都有一个
137
+ 指定的程序作为其所有者。只有所有者程序可以:
76
138
77
- * 修改账户的 ` 数据 ` 字段
78
- * 从账户余额中扣除lamport
139
+ * 更改账户的 ` 数据 ` field
140
+ * Deduct lamports from the account's balance
79
141
80
142
## System Program
81
143
82
- 使用这些
83
- [ 常量] ( https://github.com/anza-xyz/agave/tree/v2.1.13/programs/system/src ) 。
144
+ 默认情况下,所有新账户都归属于
145
+ [ System Program] ( https://github.com/anza-xyz/agave/tree/v2.1.13/programs/system/src ) 。
146
+ System Program执行几个关键功能:
84
147
85
- * [ 新账户创建 ] ( https://github.com/anza-xyz/agave/blob/v2.1.13/programs/system/src/system_processor.rs#L146 ) :只有System
86
- Program可以创建新账户。
87
- * [ 空间分配 ] ( https://github.com/anza-xyz/agave/blob/v2.1.13/programs/system/src/system_processor.rs#L71 ) :为每个账户的数据字段设置字节容量。
88
- * [ 转移/分配程序所有权 ] ( https://github.com/anza-xyz/agave/blob/v2.1.13/programs/system/src/system_processor.rs#L113 ) :一旦System
89
- Program创建了一个账户,它可以将指定的程序所有者重新分配给不同的程序账户。这就是自定义程序如何获得由System
90
- Program创建的新账户的所有权。
148
+ * [ New Account Creation ] ( https://github.com/anza-xyz/agave/blob/v2.1.13/programs/system/src/system_processor.rs#L146 ) :
149
+ 只有System Program可以创建新账户。
150
+ * [ Space Allocation ] ( https://github.com/anza-xyz/agave/blob/v2.1.13/programs/system/src/system_processor.rs#L71 ) :
151
+ Sets the byte capacity for the data field of each account.
152
+ * [ Transfer / Assign Program Ownership ] ( https://github.com/anza-xyz/agave/blob/v2.1.13/programs/system/src/system_processor.rs#L113 ) :
153
+ 一旦System Program创建了账户,它可以将指定的程序所有者重新分配给不同的程序账户。这就是自定义程序如何获取由System Program创建的新账户的所有权。
91
154
92
- 程序所有者
155
+ Solana上的所有"钱包"账户实际上都只是由System Program拥有的账户。这些账户中的lamport余额显示了钱包拥有的SOL数量。只有由System Program拥有的账户才能支付交易费用。
93
156
94
157
![ System Account] ( /assets/docs/core/accounts/system-account.svg )
95
158
96
159
## Sysvar账户
97
160
98
- 系统程序[ 这里] ( https://docs.anza.xyz/runtime/sysvars ) 。
161
+ Sysvar账户是位于预定义地址的特殊账户,提供对集群状态数据的访问。这些账户会随着网络集群的数据动态更新。您可以找到Sysvar账户的完整列表
162
+ [ here] ( https://docs.anza.xyz/runtime/sysvars ) .
163
+
164
+ <CodeTabs flags = " r" >
165
+ ``` ts !! title="Fetch Sysvar Clock Account"
166
+ import { Connection , SYSVAR_CLOCK_PUBKEY } from " @solana/web3.js" ;
167
+
168
+ const connection = new Connection (" http://127.0.0.1:8899" , " confirmed" );
169
+
170
+ const accountInfo = await connection .getAccountInfo (SYSVAR_CLOCK_PUBKEY );
171
+ console .log (JSON .stringify (accountInfo , null , 2 ));
172
+ ```
173
+ </CodeTabs >
174
+
175
+ ## Program账户
99
176
100
- ## 默认情况下,所有新账户都由
177
+ 部署Solana程序会创建一个可执行的程序账户。程序账户存储程序的可执行代码。
178
+
179
+ 程序账户由 [ Loader Program] ( /docs/core/programs#loader-programs ) .
101
180
102
- 除了loader-v3之外,所有加载器都将它们管理的程序的可执行代码存储在所谓的程序账户中:
103
181
![ Program Account] ( /assets/docs/core/accounts/program-account-simple.svg )
104
182
105
- Solana上的所有"钱包"账户实际上都是由系统程序拥有的账户。存储在这些账户中的lamport余额代表钱包拥有的SOL数量。只有由系统程序拥有的账户才能用作交易费用支付者。
183
+ 为简单起见,您可以将程序账户视为程序本身。当您调用程序的指令时,您需要指定程序账户的地址(通常称为"Program
184
+ ID")。
106
185
107
- ### 缓冲账户
186
+ <CodeTabs flags = " r" >
187
+ ``` ts !! title="Fetch Token Program Account"
188
+ import { Connection , PublicKey } from " @solana/web3.js" ;
108
189
109
- Sysvar 账户
190
+ const connection = new Connection ( " http://127.0.0.1:8899 " , " confirmed " );
110
191
111
- ### Sysvar 账户是位于预定义地址的特殊账户,提供对集群状态数据的访问。这些账户会随着网络集群的数据动态更新。您可以在
192
+ const accountInfo = await connection .getAccountInfo ( new
193
+ PublicKey (" TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ) );
194
+ console .log (accountInfo );
112
195
113
- 程序账户![ Programdata Account] ( /assets/docs/core/accounts/program-account-expanded.svg )
196
+ ```
197
+ </CodeTabs >
114
198
115
- 除了 loader-v3 之外,所有加载器都将它们管理的程序的可执行代码存储在所谓的程序账户中:
199
+ <Callout type = " info" >
200
+ 当您部署Solana程序时,它会存储在程序账户中。程序账户由[ 加载器程序] ( /docs/core/programs#loader-programs ) 。
201
+ 加载器有几个版本,但除了loader-v3之外的所有版本都直接将可执行代码存储在程序账户中。Loader-v3将可执行代码存储在单独的"程序数据账户"中,程序账户只是指向它。当您部署新程序时,Solana CLI默认使用最新的加载器版本。
202
+ </Callout >
116
203
117
- ## Data账户
204
+ ### 缓冲区账户
118
205
119
- 简单来说,您可以将程序账户视为程序本身。在调用程序的指令时,您需要指定程序账户的地址(通常称为"程序 ID") 。
206
+ Loader-v3有一种特殊的账户类型,用于在部署或重新部署/升级期间临时暂存程序上传。在loader-v4中,仍然有缓冲区,但它们只是普通的程序账户 。
120
207
121
- 缓冲区账户
208
+ ### 程序数据账户
122
209
123
- ![ Data Account] ( /assets/docs/core/accounts/data-account.svg )
210
+ Loader-v3的工作方式与所有其他BPF加载器程序不同。程序账户只包含程序数据账户的地址,该账户存储实际的可执行代码:
211
+ ![ Program Data Account] ( /assets/docs/core/accounts/program-account-expanded.svg )
212
+
213
+ 不要将这些程序数据账户与程序的数据账户(见下文)混淆。
124
214
125
- 程序数据账户[ System Program] ( /docs/core/accounts#system-program ) 可以创建新账户。一旦System
126
- Program创建了一个账户,它就可以将新账户的所有权转移/分配给另一个程序。
215
+ ## 数据账户
127
216
128
- Loader-v3 的工作方式与所有其他加载器不同,因为它为每个程序提供了一个间接层。程序账户只包含程序数据账户的地址,而程序数据账户则持有实际的可执行代码:
217
+ 在Solana上,程序的可执行代码存储在与程序状态不同的账户中。这类似于操作系统通常为程序及其数据使用单独的文件。
129
218
130
- 1 . 调用System Program创建一个账户,然后将所有权转移给自定义程序
131
- 2 . 调用现在拥有该账户的自定义程序,然后按照程序指令定义的方式初始化账户数据
219
+ 为了维护状态,程序定义指令来创建它们拥有的单独账户。每个这样的账户都有自己唯一的地址,并且可以存储程序定义的任何任意数据。
220
+
221
+ ![ Data Account] ( /assets/docs/core/accounts/data-account.svg )
132
222
133
- 这些程序数据账户不应与程序的数据账户(见下文)混淆。
223
+ 请注意,只有[ System Program] ( /docs/core/accounts#system-program ) 可以创建新账户。一旦System Program创建了一个账户,它就可以将新账户的所有权转移或分配给另一个程序。
224
+
225
+ 换句话说,为自定义程序创建数据账户需要两个步骤:
226
+
227
+ 1 . 调用System Program创建账户,然后将所有权转移给自定义程序
228
+ 2 . 调用自定义程序(该程序现在拥有该账户)来初始化
229
+ 账户数据,具体由程序的指令定义
230
+
231
+ 这个账户创建过程通常被抽象为单个步骤,但了解
232
+ 底层过程很有帮助。
233
+
234
+ <CodeTabs flags = " r" >
235
+ ``` ts !! title="Create Token Mint Account"
236
+ import {
237
+ Connection ,
238
+ Keypair ,
239
+ sendAndConfirmTransaction ,
240
+ SystemProgram ,
241
+ Transaction ,
242
+ LAMPORTS_PER_SOL
243
+ } from " @solana/web3.js" ;
244
+ import {
245
+ createInitializeMintInstruction ,
246
+ TOKEN_2022_PROGRAM_ID ,
247
+ MINT_SIZE ,
248
+ getMinimumBalanceForRentExemptMint
249
+ } from " @solana/spl-token" ;
250
+
251
+ // 创建连接到本地验证器
252
+ const connection = new Connection (" http://127.0.0.1:8899" , " confirmed" );
253
+ const recentBlockhash = await connection .getLatestBlockhash ();
254
+
255
+ // 为费用支付者生成新的keypair
256
+ const feePayer = Keypair .generate ();
257
+
258
+ // 向费用支付者空投1个SOL
259
+ const airdropSignature = await connection .requestAirdrop (
260
+ feePayer .publicKey ,
261
+ LAMPORTS_PER_SOL
262
+ );
263
+ await connection .confirmTransaction ({
264
+ blockhash: recentBlockhash .blockhash ,
265
+ lastValidBlockHeight: recentBlockhash .lastValidBlockHeight ,
266
+ signature: airdropSignature
267
+ });
268
+
269
+ // 生成keypair作为代币铸造地址
270
+ const mint = Keypair .generate ();
271
+
272
+ const createAccountInstruction = SystemProgram .createAccount ({
273
+ fromPubkey: feePayer .publicKey ,
274
+ newAccountPubkey: mint .publicKey ,
275
+ space: MINT_SIZE ,
276
+ lamports: await getMinimumBalanceForRentExemptMint (connection ),
277
+ programId: TOKEN_2022_PROGRAM_ID
278
+ });
279
+
280
+ const initializeMintInstruction = createInitializeMintInstruction (
281
+ mint .publicKey , // 代币铸造pubkey
282
+ 9 , // 小数位数
283
+ feePayer .publicKey , // 铸造权限
284
+ feePayer .publicKey , // 冻结权限
285
+ TOKEN_2022_PROGRAM_ID
286
+ );
287
+
288
+ const transaction = new Transaction ().add (
289
+ createAccountInstruction ,
290
+ initializeMintInstruction
291
+ );
292
+
293
+ const transactionSignature = await sendAndConfirmTransaction (
294
+ connection ,
295
+ transaction ,
296
+ [feePayer , mint ] // 签名者
297
+ );
298
+
299
+ console .log (" Mint Address: " , mint .publicKey .toBase58 ());
300
+ console .log (" Transaction Signature: " , transactionSignature );
301
+
302
+ const accountInfo = await connection .getAccountInfo (mint .publicKey );
303
+ console .log (accountInfo );
304
+ ```
305
+ </CodeTabs >
0 commit comments