Add chinese translations to docs (#17125)
* import zh translations * Fix broken links * fix whitespace
This commit is contained in:
@ -0,0 +1,91 @@
|
||||
---
|
||||
title: "账户"
|
||||
---
|
||||
|
||||
## 在交易之间存储状态
|
||||
|
||||
如果程序需要在交易之间存储状态,则可以使用_accounts_进行存储。 帐户类似于Linux等操作系统中的文件。 就像文件一样,帐户可以保存任意数据,并且该数据会在程序的生存期内持续存在。 帐户也像文件一样,包含元数据,该元数据告诉运行时允许谁访问数据以及如何访问数据。
|
||||
|
||||
与文件不同,该帐户包含文件生存期内的元数据。 该存在时间用“代币”表示,即称为_lamports_的许多局部原生代币。 帐户保存在验证节点的内存中,并支付[“rent”](#rent)留在那里。 每个验证节点都会定期扫描所有帐户并收取租金。 任何掉落到零零花的账户都将被清除。 如果帐户包含足够数量的Lamport,也可以标记为[rent-exempt](#rent-exemption)。
|
||||
|
||||
与Linux用户使用路径查找文件的方式相同,Solana客户端使用_address_查找帐户。 该地址是一个256位公共密钥。
|
||||
|
||||
## 签名者
|
||||
|
||||
交易可以包括与交易所引用的账户的公共密钥相对应的数字[签名](terminology.md#signature)。 当存在相应的数字签名时,它表示该帐户的私钥持有人已签名并因此“授权”了该交易,因此该帐户称为_signer_。 帐户是否为签名者将作为帐户元数据的一部分传达给程序。 然后,程序可以使用该信息来制定权限决策。
|
||||
|
||||
## 只读
|
||||
|
||||
事务可以[指示](transactions.md#message-header-format)它引用的某些帐户被视为_只读帐户_,以便能够在事务之间进行并行帐户处理。 运行时允许多个程序同时读取只读帐户。 如果程序尝试修改只读帐户,则运行时将拒绝该事务。
|
||||
|
||||
## 可执行
|
||||
|
||||
如果某个帐户在其元数据中被标记为“可执行”,则将其视为可以通过将帐户的公钥包含在指令的[程序ID](transactions.md#program-id)中来执行的程序。 在成功的程序部署过程中,拥有该帐户的加载程序将帐户标记为可执行文件。 例如,在BPF程序部署期间,一旦加载程序确定帐户数据中的BPF字节码有效,则加载程序会将程序帐户永久标记为可执行文件。 一旦可执行,运行时将强制该帐户的数据(程序) 是不可变的。
|
||||
|
||||
## 创建
|
||||
|
||||
为了创建一个帐户,客户端生成一个_keypair_并使用`SystemProgram::CreateAccount`指令注册其公共密钥,并预先分配了固定的存储大小(以字节为单位)。 当前帐户数据的最大大小为10MB。
|
||||
|
||||
帐户地址可以是任意的256位值,并且高级用户可以使用一些机制来创建派生地址(`SystemProgram::CreateAccountWithSeed`,[`Pubkey::CreateProgramAddress`](calling-between-programs.md#program-derived-addresses))。
|
||||
|
||||
从未通过系统程序创建的帐户也可以传递到程序。 当指令引用以前尚未创建的帐户时,程序将通过系统程序拥有的帐户,该帐户具有0个Lamport和0个数据。 但是,该帐户将反映它是否是该交易的签名者,因此可以用作授权。 在这种情况下,授权机构向程序传达与帐户的公共密钥相关联的私有密钥的持有者对交易进行了签名。 该程序可能知道该帐户的公钥,也可能将其记录在另一个帐户中,并表示对该程序控制或执行的资产或操作具有某种所有权或授权。
|
||||
|
||||
## 程序的所有权和分配
|
||||
|
||||
创建的帐户由称为System程序的内置程序初始化为_owned_,并适当地称为_system account_。 帐户包含“所有者”元数据。 所有者是一个程序ID。 如果运行时的ID与所有者匹配,则运行时将授予该程序对该帐户的写访问权限。 对于System程序,运行时允许客户端转移Lamport,并且重要的是_转移_帐户所有权,这意味着将所有者更改为其他程序ID。 如果某个帐户不属于某个程序,则仅允许该程序读取其数据并将该帐户记入贷方。
|
||||
|
||||
## 承租
|
||||
|
||||
使帐户在Solana上保持活动状态会产生称为_rent_的存储成本,因为集群必须积极维护数据以处理其上的任何将来的事务。 这与比特币和以太坊不同,在比特币和以太坊中,存储帐户不会产生任何费用。
|
||||
|
||||
租金是在当前时期通过事务在第一次访问(包括初始帐户创建) 时通过运行时从帐户余额中扣除的,如果没有交易,则在每个时期一次。 该费用目前是固定费率,以字节乘以时期为单位。 该费用将来可能会更改。
|
||||
|
||||
为了简化租金计算,租金始终是在一个完整的时期内收取的。 租金不是按比例分配的,这意味着部分时期既不收费也不退款。 这意味着,在创建帐户时,收取的首笔租金不是针对当前的部分时期,而是针对下一个完整的时期而预先收取的租金。 随后的租金收取是未来的进一步时期。 另一方面,如果一个已出租的帐户的余额降到另一个租金费用的中间时期以下,则该帐户将在当前时期继续存在,并在即将到来的时期开始时立即被清除。
|
||||
|
||||
如果帐户保持最低余额,则可以免交租金。 此免租金描述如下。
|
||||
|
||||
### 租金计算
|
||||
|
||||
注意:租金率将来可能会改变。
|
||||
|
||||
在撰写本文时,在testnet和mainnet-beta群集上,固定租金为每字节纪元19.055441478439427兰特。 一个[epoch](terminology.md#epoch)的目标是2天(对于devnet,租金为每字节纪元 0.3608183131797095 lamports,长度为54m36s长)。
|
||||
|
||||
计算得出该值的目标是每兆字节天0.01 SOL(与每兆字节年3.56SOL完全匹配):
|
||||
|
||||
```text
|
||||
租金:19.055441478439427=10_000_000(0.01SOL)*365(一年中大约一天)/(1024*1024)(1MiB)/(365.25/2)(一年中的纪元)
|
||||
```
|
||||
|
||||
租金计算以`f64`精度完成,最终结果在Lamports中被截断为`u64`。
|
||||
|
||||
租金计算包括帐户大小的帐户元数据(地址、所有者、lamports等)。 因此,用于租金计算的最小帐户为128字节。
|
||||
|
||||
例如,创建的帐户初始转移了10,000 lamports,并且没有其他数据。 租金会在创建时立即从中扣除,从而产生7,561 lamports的余款:
|
||||
|
||||
|
||||
```text
|
||||
租金:2,439=19.055441478439427(租金)*128字节(最小帐户大小)*1(纪元)
|
||||
帐户余额:7,561=10,000(转让的兰特)-2,439(此帐户的时期租金)
|
||||
```
|
||||
|
||||
即使没有活动,帐户余额也将在下一个时期减少到5,122 lamports:
|
||||
|
||||
```text
|
||||
帐户余额:5,122=7,561(当前余额) -2,439(该帐户的租金,用于某个时期)
|
||||
```
|
||||
|
||||
因此,如果转移的兰特小于或等于2439,则最小尺寸帐户将在创建后立即删除。
|
||||
|
||||
### 免租金
|
||||
|
||||
另外,通过存入至少2年的租金,可以使一个帐户完全免收租金。 每次帐户余额减少时都会进行检查,一旦余额低于最低金额,便会立即从租金中扣除。
|
||||
|
||||
运行时要求程序可执行帐户免租金,以免被清除。
|
||||
|
||||
注意:请使用[`getMinimumBalanceForRentExemption`RPC端点](developing/clients/jsonrpc-api.md#getminimumbalanceforrentexemption)计算特定帐户大小的最小余额。 以下计算仅是说明性的。
|
||||
|
||||
例如,一个程序可执行文件的大小为15,000字节,则需要105,290,880 lamports(=〜0.105SOL) 的余额才能免租:
|
||||
|
||||
```text
|
||||
105,290,880=19.055441478439427(手续费率)*(128+15_000)(包括元数据的帐户大小)*((365.25/2)*2)(以2年为周期)
|
||||
```
|
@ -0,0 +1,198 @@
|
||||
---
|
||||
title: 程序之间的调用
|
||||
---
|
||||
|
||||
## 跨程序调用 {#cross-program-invocations}
|
||||
|
||||
Solana运行时允许程序通过称为跨程序调用的机制相互调用。 程序之间的调用是通过一个程序调用另一个程序的指令来实现的。 调用程序将暂停,直到被调用的程序完成对指令的处理为止。
|
||||
|
||||
例如,客户可以创建一个交易来修改两个帐户,每个帐户都由单独的链上程序拥有:
|
||||
|
||||
```rust,ignore
|
||||
let message = Message::new(vec![
|
||||
token_instruction::pay(&alice_pubkey),
|
||||
acme_instruction::launch_missiles(&bob_pubkey),
|
||||
]);
|
||||
client.send_and_confirm_message(&[&alice_keypair, &bob_keypair], &message);
|
||||
```
|
||||
|
||||
客户可以代之以允许`acme`程序代表客户方便地调用`token`指令:
|
||||
|
||||
```rust,ignore
|
||||
let message = Message::new(vec![
|
||||
acme_instruction::pay_and_launch_missiles(&alice_pubkey, &bob_pubkey),
|
||||
]);
|
||||
client.send_and_confirm_message(&[&alice_keypair, &bob_keypair], &message);
|
||||
```
|
||||
|
||||
给定两个链上程序`token`和`acme`,每个程序分别执行指令`pay()`和`launch_missiles()`,可以通过调用`token`模块中定义的函数来实现acme跨程序调用:
|
||||
|
||||
```rust,ignore
|
||||
mod acme {
|
||||
use token_instruction;
|
||||
|
||||
fn launch_missiles(accounts: &[AccountInfo]) -> Result<()> {
|
||||
...
|
||||
}
|
||||
|
||||
fn pay_and_launch_missiles(accounts: &[AccountInfo]) -> Result<()> {
|
||||
let alice_pubkey = accounts[1].key;
|
||||
let instruction = token_instruction::pay(&alice_pubkey);
|
||||
invoke(&instruction, accounts)?;
|
||||
|
||||
launch_missiles(accounts)?;
|
||||
}
|
||||
```
|
||||
|
||||
Solana的运行时内置了`invoke()`,它负责通过指令的`program_id`字段将给定指令路由到`token`程序。
|
||||
|
||||
请注意,`invoke` 要求调用者传递被调用指令所需的所有帐户。 这意味着可执行帐户(与指令的程序ID匹配的帐户) 和传递给指令处理器的帐户都可以。
|
||||
|
||||
在调用`pay()`之前,运行时必须确保`acme`没有修改`token`拥有的任何帐户。 它通过在`acme`调用`invoke`时将运行时策略应用于帐户的当前状态,而不是在`acme`指令开始时将帐户的初始状态应用到帐户的当前状态。 在`pay()`完成之后,运行时必须再次通过应用运行时的策略来确保`token`不会修改`acme`拥有的任何帐户,但是这次使用`token`程序ID。 最后,在`pay_and_launch_missiles()`完成之后,运行时必须再次使用runtime 策略,这通常是正常的时间,但要使用所有更新的`pre_ *`变量。 如果执行直到`pay()`为止的`pay_and_launch_missiles()`没有任何无效的帐户更改,`pay()`没有任何无效的更改,并且从`pay()`一直执行到`pay_and_launch_missiles()`返回,则没有无效的更改,然后,runtime可以过渡性地假设`pay_and_launch_missiles()`总体上没有进行无效的帐户更改,因此可以提交所有这些帐户修改。
|
||||
|
||||
### 需要特权的指令
|
||||
|
||||
运行时使用授予调用者程序的特权来确定可以扩展给被调用者的特权。 在这种情况下,特权是指签名者和可写帐户。 例如,如果调用者正在处理的指令包含签名者或可写帐户,则调用者可以调用也包含该签名者和/或可写帐户的指令。
|
||||
|
||||
此特权扩展依赖于程序是不可变的这一事实。 对于`acme`程序,运行时可以安全地将事务签名视为`token`指令签名。 当运行时看到`token`指令引用`alice_pubkey`时,它将在`acme`指令中查找密钥,以查看该密钥是否与已签名的帐户相对应。 在这种情况下,它会这样做并因此授权`token`程序修改Alice的帐户。
|
||||
|
||||
### 程序签名帐户
|
||||
|
||||
程序可以使用[程序派生地址](#program-derived-addresses)发出包含未在原始交易中签名的已签名帐户的指令。
|
||||
|
||||
为了用程序派生的地址签署一个帐户,程序可以`invoke_signed()`。
|
||||
|
||||
```rust,ignore
|
||||
invoke_signed(
|
||||
&instruction,
|
||||
accounts,
|
||||
&[&["First addresses seed"],
|
||||
&["Second addresses first seed", "Second addresses second seed"]],
|
||||
)?;
|
||||
```
|
||||
|
||||
### 调用深度
|
||||
|
||||
跨程序调用允许程序直接调用其他程序,但当前深度限制为4。
|
||||
|
||||
### 可重入
|
||||
|
||||
目前,可重入仅限于以固定深度为上限的直接自递归。 此限制可防止程序可能在不知道稍后会被调用回状态的情况下从中间状态调用另一个程序的情况。 直接递归可以使程序在被调用时完全控制其状态。
|
||||
|
||||
## 程序派生地址
|
||||
|
||||
程序派生的地址允许在[程序之间调用](#cross-program-invocations)时使用以编程方式生成的签名。
|
||||
|
||||
使用程序派生的地址,可以向某个程序授予某个帐户的权限,然后再将该权限转移给另一个帐户。 这是可能的,因为该程序可以在授予权限的事务中充当签名者。
|
||||
|
||||
例如,如果两个用户想要对Solana中的游戏结果押注,则他们每个人都必须将其下注的资产转移到将遵守协议的某些中介机构上。 当前,在Solana中尚无办法将此中介程序作为程序来实现,因为该中介程序无法将资产转让给获胜者。
|
||||
|
||||
对于许多DeFi应用程序来说,此功能是必需的,因为它们要求将资产转移到托管代理,直到发生确定新所有者的事件为止。
|
||||
|
||||
- 去中心化交易所,可在匹配的买价和卖价之间转移资产。
|
||||
|
||||
- 将资产转移给获胜者的拍卖。
|
||||
|
||||
- 收集奖品并将其重新分配给获奖者的游戏或预测市场。
|
||||
|
||||
程序派生地址:
|
||||
|
||||
1. 允许程序控制特定的地址(称为程序地址),以使任何外部用户都无法生成带有这些地址签名的有效交易。
|
||||
|
||||
2. 允许程序以编程方式签名通过[跨程序调用](#cross-program-invocations)调用的指令中存在的程序地址。
|
||||
|
||||
在这两个条件下,用户可以安全地将链上资产的权限转移或分配给程序地址,然后程序可以自行决定在其他地方分配该权限。
|
||||
|
||||
### 程序地址的私钥
|
||||
|
||||
程序地址不在ed25519曲线上,因此没有与之关联的有效私钥,因此无法生成签名。 虽然它没有自己的私钥,但是程序可以使用它来发布包含程序地址作为签名者的指令。
|
||||
|
||||
### 基于哈希的生成程序地址
|
||||
|
||||
程序地址是使用256位抗映像前哈希函数从种子和程序ID的集合中确定性地得出的。 程序地址一定不能位于ed25519曲线上,以确保没有关联的私钥。 如果发现地址位于曲线上,则在生成过程中将返回错误。 对于给定的种子和程序ID集合,这种情况大约发生50/50的变化。 如果发生这种情况,可以使用另一组种子或种子凹凸(附加的8位种子) 来查找曲线外的有效程序地址。
|
||||
|
||||
程序的确定性程序地址遵循与使用`system_instruction::create_address_with_seed`实现的用`SystemInstruction::CreateAccountWithSeed`创建的帐户类似的派生路径。
|
||||
|
||||
作为参考,该实现如下:
|
||||
|
||||
```rust,ignore
|
||||
pub fn create_address_with_seed(
|
||||
base: &Pubkey,
|
||||
seed: &str,
|
||||
program_id: &Pubkey,
|
||||
) -> Result<Pubkey, SystemError> {
|
||||
if seed.len() > MAX_ADDRESS_SEED_LEN {
|
||||
return Err(SystemError::MaxSeedLengthExceeded);
|
||||
}
|
||||
|
||||
Ok(Pubkey::new(
|
||||
hashv(&[base.as_ref(), seed.as_ref(), program_id.as_ref()]).as_ref(),
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
程序可以使用种子确定性地派生任意数量的地址。 这些种子可以象征性地标识地址的使用方式。
|
||||
|
||||
来自`Pubkey`::
|
||||
|
||||
```rust,ignore
|
||||
///生成派生程序地址
|
||||
/// *种子,用于派生密钥的符号关键字
|
||||
/// * program_id,为该地址派生的程序
|
||||
pub fn create_program_address(
|
||||
seeds: &[&[u8]],
|
||||
program_id: &Pubkey,
|
||||
) -> Result<Pubkey, PubkeyError>
|
||||
```
|
||||
|
||||
### 使用程序地址
|
||||
|
||||
客户可以使用`create_program_address`函数生成目标地址。
|
||||
|
||||
```rust,ignore
|
||||
//确定性地导出托管密钥
|
||||
let escrow_pubkey = create_program_address(&[&["escrow"]], &escrow_program_id);
|
||||
|
||||
//使用该密钥构造传输消息
|
||||
let message = Message::new(vec![
|
||||
token_instruction::transfer(&alice_pubkey, &escrow_pubkey, 1),
|
||||
]);
|
||||
|
||||
//处理将一个1令牌传输到托管的消息
|
||||
client.send_and_confirm_message(&[&alice_keypair], &message);
|
||||
```
|
||||
|
||||
程序可以使用相同的函数来生成相同的地址。 程序在下面的功能中从程序地址发出`token_instruction::transfer`,就好像它具有用于签署交易的私钥一样。
|
||||
|
||||
```rust,ignore
|
||||
fn transfer_one_token_from_escrow(
|
||||
program_id: &Pubkey,
|
||||
keyed_accounts: &[KeyedAccount]
|
||||
) -> Result<()> {
|
||||
|
||||
//用户提供目的地
|
||||
let alice_pubkey = keyed_accounts[1].unsigned_key();
|
||||
|
||||
//确定性派生托管公钥。
|
||||
let escrow_pubkey = create_program_address(&[&["escrow"]], program_id);
|
||||
|
||||
//创建转移指令
|
||||
let instruction = token_instruction::transfer(&escrow_pubkey, &alice_pubkey, 1);
|
||||
|
||||
//运行时确定性地从当前
|
||||
//执行程序ID和提供的关键字。
|
||||
//如果派生地址与指令中标记为已签名的键匹配
|
||||
//然后该密钥被接受为已签名。
|
||||
invoke_signed(&instruction, &[&["escrow"]])?
|
||||
}
|
||||
```
|
||||
|
||||
### 需要签名者的说明
|
||||
|
||||
用`create_program_address`生成的地址与任何其他公钥都没有区别。 运行时验证地址是否属于程序的唯一方法是使程序提供用于生成地址的种子。
|
||||
|
||||
运行时将在内部调用`create_program_address`,并将结果与指令中提供的地址进行比较。
|
||||
|
||||
## 示例
|
||||
|
||||
请参阅 [使用Rust开发](developing/on-chain-programs/developing-rust.md#examples) 和 [使用C开发](developing/on-chain-programs/developing-c.md#examples)以获取有关如何使用跨程序调用的示例。
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: "概述"
|
||||
---
|
||||
|
||||
一个[app](terminology.md#app)通过向Solana集群发送带有一个或多个[指令](transactions.md#instructions)的[事务](transactions.md)来与之交互。 Solana [runtime](runtime.md) 会将这些指令传递给应用程序开发人员事先部署的[程序](terminology.md#program)。 例如,一条指令可能告诉程序将[lamports](terminology.md#lamports)从一个[账户](accounts.md)转移到另一个,或创建一个交互协议来管理Lamport的转移方式。 指令针对每个交易顺序地和原子级地执行。 如果任何指令无效,则交易中的所有帐户更改都将被丢弃。
|
||||
|
||||
要立即开始开发,您可以构建,部署和运行[示例](developing/on-chain-programs/examples.md)之一。
|
@ -0,0 +1,86 @@
|
||||
---
|
||||
title: "运行时(runtime)"
|
||||
---
|
||||
|
||||
## 程序功能
|
||||
|
||||
运行时仅允许所有者程序借记帐户或修改其数据。 然后,程序为客户是否可以修改其拥有的帐户定义其他规则。 在系统程序的情况下,它允许用户通过识别交易签名来转移灯饰。 如果看到客户端使用密钥对的_private key_签署了交易,则表明客户端已授权代币传输。
|
||||
|
||||
换句话说,给定程序拥有的整个帐户集可以视为键值存储,其中键是帐户地址,值是特定于程序的任意二进制数据。 程序作者可以决定如何管理尽可能多的帐户的整个程序状态。
|
||||
|
||||
运行时执行每个事务的指令后,它将使用帐户元数据来验证是否违反了访问策略。 如果程序违反了该策略,则运行时会丢弃交易中所有指令所做的所有帐户更改,并将交易标记为失败。
|
||||
|
||||
### 政策
|
||||
|
||||
程序处理完指令后,运行时将验证程序仅执行了允许的操作,并且结果符合运行时策略。
|
||||
|
||||
该策略如下:
|
||||
- 只有帐户所有者可以更改所有者。
|
||||
- 仅在帐户可写时。
|
||||
- 并且仅在该帐户不可执行时
|
||||
- 并且仅当数据为零初始化或为空时。
|
||||
- 未分配给该程序的帐户的余额不能减少。
|
||||
- 只读帐户和可执行帐户的余额可能不会更改。
|
||||
- 只有系统程序拥有该帐户,只有系统程序才能更改数据大小。
|
||||
- 只有所有者可以更改帐户数据。
|
||||
- 如果该帐户可写。
|
||||
- 并且该帐户不可执行。
|
||||
- 可执行文件是单向的(false->true),只有帐户所有者可以设置。
|
||||
- 没有与此帐户相关联的rent_epoch的修改。
|
||||
|
||||
## 计算预算 {#compute-budget}
|
||||
|
||||
为了防止程序滥用计算资源,必须为事务中的每个指令分配一个计算预算。 预算由计算单元组成,在程序执行各种操作和程序可能不会超出的范围时会消耗这些计算单元。 当程序消耗了全部预算或超出界限时,运行时将暂停程序并返回错误。
|
||||
|
||||
以下操作会产生计算成本:
|
||||
- 执行BPF指令
|
||||
- 呼叫系统电话
|
||||
- 记录
|
||||
- 创建程序地址
|
||||
- 跨程序调用
|
||||
- ...
|
||||
|
||||
对于跨程序调用,所调用的程序将继承其父级的预算。 如果被调用的程序消耗了预算或超出了限制,则整个调用链和父级都将停止。
|
||||
|
||||
可以在Solana SDK中找到当前的[计算预算](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/src/process_instruction.rs#L65)。
|
||||
|
||||
例如,如果当前预算是:
|
||||
|
||||
```rust
|
||||
max_units: 200,000,
|
||||
log_units: 100,
|
||||
log_u64_units: 100,
|
||||
create_program address units: 1500,
|
||||
invoke_units: 1000,
|
||||
max_invoke_depth: 4,
|
||||
max_call_depth: 64,
|
||||
stack_frame_size: 4096,
|
||||
log_pubkey_units: 100,
|
||||
```
|
||||
|
||||
然后程序
|
||||
- 如果不执行其他操作,则可以执行200,000条BPF指令
|
||||
- 可以记录2,000条日志消息
|
||||
- 堆栈使用量不能超过4k
|
||||
- 不能超过BPF通话深度64
|
||||
- 不能超过4个级别的跨程序调用。
|
||||
|
||||
由于计算预算在程序执行时会逐渐消耗,因此总预算消耗将是其执行的各种操作成本的组合。
|
||||
|
||||
在运行时,程序可以记录剩余多少计算预算。 有关更多信息,请参见[debugging](developing/on-chain-programs/debugging.md#monitoring-compute-budget-consumption)。
|
||||
|
||||
预算值取决于功能启用,请查看计算预算的[new](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/src/process_instruction.rs#L97)函数列出预算的编制方式。 需要了解[功能](runtime.md#features)的工作方式以及正在使用的群集上启用的功能才能确定当前预算的值。
|
||||
|
||||
## 新的功能
|
||||
|
||||
随着Solana的发展,可能会引入新功能或补丁,这些新功能或补丁会更改集群的行为以及程序的运行方式。 行为的变化必须在群集的各个节点之间进行协调,如果节点不协调,则这些变化可能会导致共识破裂。 Solana支持一种称为运行时功能的机制,以方便平滑地采用更改。
|
||||
|
||||
运行时功能是历时协调的事件,将在集群中发生一个或多个行为更改。 对Solana的新更改(将更改行为)使用功能门进行包装,并且默认情况下处于禁用状态。 然后使用Solana工具激活功能,将其标记为未决,一旦标记为未决,该功能将在下一个时期激活。
|
||||
|
||||
要确定激活了哪些功能,请使用[Solana命令行工具](cli/install-solana-cli-tools.md):
|
||||
|
||||
```bash
|
||||
solana功能状态
|
||||
```
|
||||
|
||||
如果首先遇到问题,请确保您使用的Solana工具版本与`solana cluster-version`返回的版本相匹配。 如果它们不匹配,请[安装正确的工具套件](cli/install-solana-cli-tools.md)。
|
@ -0,0 +1,113 @@
|
||||
---
|
||||
title: "交易"
|
||||
---
|
||||
|
||||
程序执行从将[transaction](terminology.md#transaction)提交到集群开始。 Solana运行时将执行一个程序,以按顺序和原子方式处理事务中包含的每个[指令](terminology.md#instruction)。
|
||||
|
||||
## 交易剖析
|
||||
|
||||
本节介绍事务的二进制格式
|
||||
|
||||
### 交易格式
|
||||
|
||||
事务包含签名的[compact-array](#compact-array-format),然后是[message](#message-format)。 签名数组中的每个项目都是给定消息的[数字签名](#signature-format)。 Solana运行时验证签名数是否与[message heade](#message-header-format)的前8位中的数字匹配。 它还验证每个签名是否由与邮件帐户地址数组中相同索引处的公钥相对应的私钥签名。
|
||||
|
||||
#### 签名格式
|
||||
|
||||
每个数字签名均为ed25519二进制格式,占用64个字节。
|
||||
|
||||
### 邮件格式
|
||||
|
||||
一条消息包含[header](#message-header-format),然后是[account address](#account-addresses-format)的紧凑数组,然后是最近的[blockhash](#blockhash-format),紧接着是[指令](#instruction-format)的紧凑数组。
|
||||
|
||||
#### 邮件标题格式
|
||||
|
||||
消息头包含三个无符号的8位值。 第一个值是包含交易中所需签名的数量。 第二个值是那些对应的只读帐户地址的数量。 邮件标题中的第三个值是不需要签名的只读帐户地址的数量。
|
||||
|
||||
#### 帐户地址格式
|
||||
|
||||
要求签名的地址出现在帐户地址数组的开头,其地址首先请求写访问权限,然后请求只读帐户。 不需要签名的地址跟在需要签名的地址之后,再次是先读写帐户,然后是只读帐户。
|
||||
|
||||
#### Blockhash区块链哈希值格式
|
||||
|
||||
区块哈希包含一个32字节的SHA-256哈希。 它用于指示客户最后一次观察分类帐的时间。 当区块哈希值太旧时,验证程序将拒绝交易。
|
||||
|
||||
### 指令格式
|
||||
|
||||
一条指令包含一个程序ID索引,后跟一个帐户地址索引的紧凑数组,然后是一个不透明的8位数据的紧凑数组。 程序ID索引用于标识可以解释不透明数据的链上程序。 程序ID索引是消息的帐户地址数组中帐户地址的无符号8位索引。 帐户地址索引是同一数组中的无符号8位索引。
|
||||
|
||||
### 紧凑数组格式
|
||||
|
||||
紧凑数组被序列化为数组长度,随后是每个数组项。 数组长度是一种特殊的多字节编码,称为compact-u16。
|
||||
|
||||
#### Compact-u16格式
|
||||
|
||||
一个compact-u16是16位的多字节编码。 第一个字节在其低7位中包含该值的低7位。 如果该值大于0x7f,则设置高位,并将该值的后7位放入第二个字节的低7位。 如果该值大于0x3fff,则设置高位,并将该值的其余2位放入第三个字节的低2位。
|
||||
|
||||
### 帐户地址格式
|
||||
|
||||
帐户地址是32字节的任意数据。 当地址需要数字签名时,运行时会将其解释为ed25519密钥对的公钥。
|
||||
|
||||
## 指示
|
||||
|
||||
每个[instruction](terminology.md#instruction)都指定一个程序,应传递给该程序的交易帐户的子集以及一个传递给该程序的数据字节数组。 该程序解释数据数组并在指令指定的帐户上运行。 该程序可以成功返回,或者带有错误代码。 错误返回会导致整个事务立即失败。
|
||||
|
||||
程序通常提供帮助程序功能来构造它们支持的指令。 例如,系统程序提供了以下Rust助手来构建[`SystemInstruction::CreateAccount`](https://github.com/solana-labs/solana/blob/6606590b8132e56dab9e60b3f7d20ba7412a736c/sdk/program/src/system_instruction.rs#L63)指令:
|
||||
|
||||
```rust
|
||||
pub fn create_account(
|
||||
from_pubkey: &Pubkey,
|
||||
to_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
space: u64,
|
||||
owner: &Pubkey,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*from_pubkey, true),
|
||||
AccountMeta::new(*to_pubkey, true),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::CreateAccount {
|
||||
lamports,
|
||||
space,
|
||||
owner: *owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
可以在这里找到:
|
||||
|
||||
https://github.com/solana-labs/solana/blob/6606590b8132e56dab9e60b3f7d20ba7412a736c/sdk/program/src/system_instruction.rs#L220
|
||||
|
||||
### 程序ID
|
||||
|
||||
指令的[程序ID](terminology.md#program-id)指定将处理该指令的程序。 程序帐户的所有者指定应使用哪个加载程序来加载和执行程序,并且数据包含有关运行时应如何执行程序的信息。
|
||||
|
||||
对于[已部署的BPF程序](developing/on-chain-programs/overview.md),所有者是BPF加载程序,帐户数据包含BPF字节码。 一旦成功部署,程序帐户便会被加载程序永久标记为可执行文件。 运行时将拒绝指定不可执行程序的事务。
|
||||
|
||||
|
||||
与已部署的程序不同,[runtime facilities](developing/runtime-facilities/programs.md)的处理方式有所不同,因为它们直接内置在Solana运行时中。
|
||||
|
||||
### 帐户
|
||||
|
||||
指令引用的帐户代表链上状态,并且既作为程序的输入又作为输出。 有关帐户的更多信息,请参见[帐户](accounts.md)章节。
|
||||
|
||||
### 指令数据
|
||||
|
||||
每个指令都带有一个通用字节数组,该字节数组与帐户一起传递给程序。 指令数据的内容是特定于程序的,通常用于传达程序应执行的操作以及这些操作在帐户所包含的内容之外可能需要的任何其他信息。
|
||||
|
||||
程序可以自由指定如何将信息编码到指令数据字节数组中。 数据编码方式的选择应考虑到解码的开销,因为该步骤是由链上程序执行的。 据观察,一些常见的编码(例如Rust的bincode) 效率很低。
|
||||
|
||||
[Solana程序库的代币程序](https://github.com/solana-labs/solana-program-library/tree/master/token)提供了一个示例,说明如何有效地对指令数据进行编码,但是请注意,这种方法仅支持固定大小的类型。 代币利用[Pack](https://github.com/solana-labs/solana/blob/master/sdk/program/src/program_pack.rs)特征来对代币指令和代币的指令数据进行编码/解码帐户状态。
|
||||
|
||||
## 签名
|
||||
|
||||
每笔交易都明确列出了交易指令所引用的所有帐户公钥。 这些公钥的子集每个都带有交易签名。 这些签名向链上程序发出信号,表明帐户持有人已授权交易。 通常,程序使用授权来允许借记帐户或修改其数据。 有关如何将授权传达给程序的更多信息,请参见[帐户](accounts.md#signers)。
|
||||
|
||||
|
||||
## 最近的区块链哈希值
|
||||
|
||||
事务包括最近的[blockhash](terminology.md#blockhash),以防止重复并赋予事务生命周期。 任何与上一个交易完全相同的交易都会被拒绝,因此添加一个更新的区块哈希可以使多个交易重复完全相同的操作。 事务还具有由Blockhash定义的生存期,因为Blockhash太旧的任何事务都将被拒绝。
|
Reference in New Issue
Block a user