Skip to main content

ADA(派生账号) 和 PDA 使用

· 5 min read
v1xingyue

在solana的数据账号使用过程中,牵扯两种账号

  • PDA (Program Derived Account)

由 createProgramAddressSync 产生。 通常由特定程序(通常是一个智能合约)关联额外的账户。该账号没有私钥,故除程序本身外,无法完成数据签名,无法完成完整的数据交易。

  • ADA (Account Derived Account)

由 createWithSeed 方法产生。 有一个账号公钥派生出来的关联账户,数据签名权限属于主账号。也即,需要主账号的签名才能完成完整的数据交易。

solana中,根据数据签名,决定了数据的真实所有权。即 我的数据我做主

本文主要分析这两种账号的异同。

地址生成逻辑介绍如下

  • PDA 地址生成规则
  1. buffer = [seed,programId,"ProgramDerivedAddress"]
  2. 对buffer 取 sha256
  3. 如果在曲线上,那么抛出error, 如果不在,那么直接返回作为 使用地址

createProgramAddressSync

  • ADA 生成
  1. buffer=[fromPublicKey,seed,programId]
  2. buffer 取 sha256, 直接返回

createWithSeed

区别在于,数据的托管使用逻辑.

  • 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()[..])?;

参考资料