文章详细介绍了Solana区块链中的账户所有权机制,包括系统程序、BPFLoader和程序对不同类型的账户(如PDA和keypair账户)的所有权及其操作权限,并通过Rust和Typescript代码示例进行了演示。
在 Solana 中,账户的拥有者能够减少 SOL 余额、向账户写入数据以及更改拥有者。
以下是 Solana 中账户所有权的总结:
system program
拥有未分配给程序(初始化)的钱包和密钥对账户。我们现在来考察这些事实的影响。
为了说明这一点,让我们使用 Solana CLI 查看我们的 Solana 钱包地址并检查其元数据:
请注意,拥有者不是我们的地址,而是地址为 111...111
的账户。这是系统程序,也就是我们在之前的教程中看到的转移 SOL 的同一个系统程序。
只有账户的拥有者才能修改其中的数据
这包括减少 lamport 数值(如后面所述,你不需要是拥有者才能增加其他账户的 lamport 数值)。
尽管从某种形而上学的意义上讲,你“拥有”你的钱包,但你不能直接向其中写入数据或减少 lamport 余额,因为从 Solana 运行时的角度来看,你不是拥有者。
你能够在钱包中花费 SOL 的原因是因为你拥有生成地址或公钥的私钥。当 system program
识别到你为公钥生成了有效的签名时,它会将你在账户中花费 lamports 的请求视为合法,然后根据你的指示进行支出。
然而,系统程序并不提供签名者直接向账户写入数据的机制。
上述示例中显示的账户是一个密钥对账户,或者我们可以认为是一个“常规 Solana 钱包”。系统程序是密钥对账户的拥有者。
程序能够向 PDA 或者在程序外创建但由程序初始化的密钥对账户写入数据的原因是程序拥有它们。
我们将在讨论重新初始化攻击时更详细地探讨初始化,但现在重要的是要了解 初始化账户会将账户的所有者从系统程序更改为程序。
为了说明这一点,考虑以下初始化 PDA 和密钥对账户的程序。 Typescript 测试将在初始化事务之前和之后打印出拥有者。
如果我们尝试确定一个不存在的地址的拥有者,我们会得到 null
。
以下是 Rust 代码:
use anchor_lang::prelude::*;
declare_id!("C2ZKJPhNiCM6CqTneGUXJoE4o6YhMzNUes3q5WNcH3un");
#[program]
pub mod owner {
use super::*;
pub fn initialize_keypair(ctx: Context<InitializeKeypair>) -> Result<()> {
Ok(())
}
pub fn initialize_pda(ctx: Context<InitializePda>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializeKeypair<'info> {
#[account(init, payer = signer, space = 8)]
keypair: Account<'info, Keypair>,
#[account(mut)]
signer: Signer<'info>,
system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct InitializePda<'info> {
#[account(init, payer = signer, space = 8, seeds = [], bump)]
pda: Account<'info, Pda>,
#[account(mut)]
signer: Signer<'info>,
system_program: Program<'info, System>,
}
#[account]
pub struct Keypair();
#[account]
pub struct Pda();
以下是 Typescript 代码:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Owner } from "../target/types/owner";
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount * anchor.web3.LAMPORTS_PER_SOL);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor.getProvider().connection.getLatestBlockhash();
await anchor.getProvider().connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
describe("owner", () => {
// 配置客户端以使用本地集群。
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Owner as Program<Owner>;
it("Is initialized!", async () => {
console.log("program address", program.programId.toBase58());
const seeds = []
const [pda, bump_] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
console.log("owner of pda before initialize:",
await anchor.getProvider().connection.getAccountInfo(pda));
await program.methods.initializePda().accounts({pda: pda}).rpc();
console.log("owner of pda after initialize:",
(await anchor.getProvider().connection.getAccountInfo(pda)).owner.toBase58());
let keypair = anchor.web3.Keypair.generate();
console.log("owner of keypair before airdrop:",
await anchor.getProvider().connection.getAccountInfo(keypair.publicKey));
await airdropSol(keypair.publicKey, 1); // 1 SOL
console.log("owner of keypair after airdrop:",
(await anchor.getProvider().connection.getAccountInfo(keypair.publicKey)).owner.toBase58());
await program.methods.initializeKeypair()
.accounts({keypair: keypair.publicKey})
.signers([keypair]) // 签名者必须是密钥对
.rpc();
console.log("owner of keypair after initialize:",
(await anchor.getProvider().connection.getAccountInfo(keypair.publicKey)).owner.toBase58());
});
});
测试的工作原理如下:
null
。initializePDA
然后查询拥有人。得到程序的地址。null
。initializeKeypair
然后查询拥有人。得到程序的地址。测试结果的截图如下:
这就是程序能够对账户写入数据的方式:它拥有它们。在初始化期间,程序接管了账户的所有权。
练习:修改测试以打印出密钥对和 PDA 的地址。然后使用 Solana CLI 检查这些账户的拥有者应该与测试打印的一致。确保 solana-test-validator
在后台运行以便你可以使用 CLI。
让我们使用 Solana CLI 确定我们的程序的拥有者:
部署程序的钱包并不是程序的拥有者。Solana 程序能够通过部署钱包进行升级的原因是 BpfLoaderUpgradeable 能够将新字节码写入程序,并且它只接受来自预先指定地址的新字节码:最初部署程序的地址。
当我们部署(或升级)一个程序时,实际上是在调用 BPFLoaderUpgradeable 程序,正如日志中所示:
Signature: 2zBBEPWsMvf8t7wkNEDqfHJKw83aBMgwGi3G9uZ6m9qG9t4kjJA2wFEP84dkKCjiCdbh54xeEDYFeDcNS7FkyLEw
Status: Ok
Log Messages:
Program 11111111111111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 success
Program BP...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!