在solana的数据账号使用过程中,牵扯两种账号
- PDA (Program Derived Account)
由 createProgramAddressSync 产生。 通常由特定程序(通常是一个智能合约)关联额外的账户。该账号没有私钥,故除程序本身外,无法完成数据签名,无法完成完整的数据交易。
- ADA (Account Derived Account)
由 createWithSeed 方法产生。 有一个账号公钥派生出来的关联账户,数据签名权限属于主账号。也即,需要主账号的签名才能完成完整的数据交易。
solana中,根据数据签名,决定了数据的真实所有权。即 我的数据我做主
本文主要分析这两种账号的异同。
地址生成逻辑介绍如下
- PDA 地址生成规则
- buffer = [seed,programId,"ProgramDerivedAddress"]
- 对buffer 取 sha256
- 如果在曲线上,那么抛出error, 如果不在,那么直接返回作为 使用地址
- ADA 生成
- buffer=[fromPublicKey,seed,programId]
- buffer 取 sha256, 直接返回
区别在于,数据的托管使用逻辑.
- ADA 数据签名权限,在于账户本身。即 我的数据我做主,未经允许(我未签名)不能修改。
- PDA 数据签名权限在于合约。经过程序签名,可以修改 account 的数据和提取其中的sol。
ADA 账号使用
数据操作,有配套的函数对应,内部包含 xxxxWithSeedParams 类型的参数,完成对应的操作。 操作数据,需要 主账户的签名,这一点决定了,账号的真实所有权。
- SystemProgram.createAccountWithSeed 初始化账号
- SystemProgram.assign 重新分配owner
- SystemProgram.allocate 分配空间
- SystemProgram.transfer 转移SOL
PDA 账号使用
- 客户端只用于账户地址推导,不能初始化。初始化过程在合约内部完成。
- 因其签名权限,必须在合约内部完成。他的操作权限完全属于智能合约。
ADA 账号使用 example
const seed = "ada.creator";
// 初始化ada 账户
let ada_account = await web3.PublicKey.createWithSeed(
signer.publicKey,
seed,
program
);
console.log("ada_account address: ", ada_account.toBase58());
let ada_info = await connection.getAccountInfo(ada_account);
// 根据是否存在账号,决定是否初始化
if (ada_info) {
console.log(ada_info);
} else {
console.log("ada account not found");
const transaction = new web3.Transaction().add(
web3.SystemProgram.createAccountWithSeed({
newAccountPubkey: ada_account,
fromPubkey: signer.publicKey,
basePubkey: signer.publicKey,
programId: program,
seed,
lamports: web3.LAMPORTS_PER_SOL,
space: 20,
})
);
PDA 使用 example
客户端部分代码逻辑
const pda_seed = "pda.creator";
const obj = new Model();
const [pda, bump_seed] = web3.PublicKey.findProgramAddressSync(
[signer.publicKey.toBuffer(), new TextEncoder().encode(movie.title)],
program
);
console.log("pda address : ", pda.toBase58());
const instruction = new web3.TransactionInstruction({
keys: [
{
// 付钱的账户
pubkey: signer.publicKey,
isSigner: true,
isWritable: false,
},
{
// PDA将存储数据
pubkey: pda,
isSigner: false,
isWritable: true,
},
{
// 系统程序将用于创建PDA
pubkey: web3.SystemProgram.programId,
isSigner: false,
isWritable: false,
},
],
// 传输数据
data: obj.serialize(),
programId: program,
});
const transaction = new web3.Transaction().add(instruction);
const signature = await web3.sendAndConfirmTransaction(
connection,
transaction,
[signer]
);
console.log(signature);
合约部分代码逻辑
// 获取账户迭代器
let account_info_iter = &mut accounts.iter();
// 获取账户
let initializer = next_account_info(account_info_iter)?;
let pda_account = next_account_info(account_info_iter)?;
let system_program = next_account_info(account_info_iter)?;
// 构造PDA账户
let (pda, bump_seed) =
Pubkey::find_program_address(&[initializer.key.as_ref(), title.as_bytes()], program_id);
// 和客户端比对
if pda != *pda_account.key {
msg!("Invalid seeds for PDA");
return Err(ProgramError::InvalidArgument);
}
// 计算所需的租金
let rent = Rent::get()?;
let rent_lamports = rent.minimum_balance(total_len);
// 创建账户
invoke_signed(
&system_instruction::create_account(
initializer.key,
pda_account.key,
rent_lamports,
total_len
.try_into()
.map_err(|_| Error::ConvertUsizeToU64Failed)?,
program_id,
),
&[
initializer.clone(),
pda_account.clone(),
system_program.clone(),
],
&[&[initializer.key.as_ref(), title.as_bytes(), &[bump_seed]]],
)?;
// MovieAccountState 定义的state类型
let mut account_data =
try_from_slice_unchecked::<MovieAccountState>(&pda_account.data.borrow()).unwrap();
account_data.title = title;
account_data.rating = rating;
account_data.description = description;
account_data.is_initialized = true;
// 写入pda 数据本身
account_data.serialize(&mut &mut pda_account.data.borrow_mut()[..])?;