如何在Anchor/Rust中派生具有多个种子的PDA



我正在尝试创建一个结构,每个用户最多可以生成255个PDA来存储数据。我通过传递一个固定字符串、用户的钱包Pubkey和一个ID(作为u8(来实现这一点。然而,我总是得到一个Cross-program invocation with unauthorized signer or writable account错误;它说CCD_ 4。

我有一个工作系统,只使用钱包和固定的种子字符串来导出PDA,但这当然只允许每个用户使用一个PDA。

这是我的锚代码:

#[program]
pub mod pda_test {
use super::*;
pub fn maak_bedrijf(ctx: Context<MaakBedrijf>, naam: String, omschrijving: String, url: String, geverifieerd: bool, bump: u8, id: u8) -> Result<()> {
let bedrijf = &mut ctx.accounts.bedrijf;
require!(naam.len()         < MAX_LENGTE_NAAM,         BedrijfError::BedrijfsnaamTeLang);
require!(omschrijving.len() < MAX_LENGTE_OMSCHRIJVING, BedrijfError::OmschrijvingTeLang);
require!(url.len()          < MAX_LENGTE_URL,          BedrijfError::UrlTeLang);
bedrijf.bump     = bump;
bedrijf.eigenaar = ctx.accounts.gebruiker.key();

bedrijf.naam         = naam;
bedrijf.omschrijving = omschrijving;
bedrijf.url          = url;
bedrijf.geverifieerd = geverifieerd;
bedrijf.id = id;

Ok(())
}
}
#[account]
pub struct Bedrijf {
bump: u8,
eigenaar: Pubkey,
naam:         String,
omschrijving: String,
url:          String,
geverifieerd: bool,
id: u8
}
impl Bedrijf {
pub fn ruimte() -> usize {
32 + // een Pubkey is 32 bytes lang
1  + // de bump is een u8
4 + MAX_LENGTE_NAAM +         // 4 bytes string discriminator + max lengte v/d string
4 + MAX_LENGTE_OMSCHRIJVING + // ditto
4 + MAX_LENGTE_URL +          // ditto
1 +   // een bool wordt weergegeven als een u8, dus ook één byte (zie ook: https://github.com/near/borsh)
1     // de id is ook een u8
}
}
#[derive(Accounts)]
#[instruction(id: u8)]
pub struct MaakBedrijf<'info> {
#[account(mut)]
pub gebruiker: Signer<'info>,
#[account(
init,
payer = gebruiker,
space = 8 + Bedrijf::ruimte(), 
seeds = [b"bedrijfpda".as_ref(), &[id], gebruiker.key().as_ref()],
bump
)]
pub bedrijf: Account<'info, Bedrijf>,
pub system_program: Program<'info, System>,
}

下面是代码在我的TypeScript客户端中的样子:

const [bedrijfPDA, bump] = await PublicKey.findProgramAddress(
[
anchor.utils.bytes.utf8.encode("bedrijfpda"), // hiervoor hebben we deze seed nodig
Uint8Array.of(id),
wallet.publicKey.toBytes(),                   // en de publicKey van de wallet
],
program.programId // we leiden af vanaf ons huidige programma
);
const tx = program.methods.maakBedrijf( // maakBedrijf is de belangrijkste methode.
data.naam,
data.omschrijving,
data.url,
data.geverifieerd,
bump, // de bump die we vonden is nodig om ook door te geven aan het interne account  
id
).signers([payer]).accounts( // we geven de wallet door als een signer
{
bedrijf:       bedrijfPDA,
gebruiker:     payer.publicKey,
systemProgram: SystemProgram.programId
}
);
let werkende_transactie: anchor.web3.Transaction = undefined;

await tx.transaction().then(
async (transactie) => {
// van zodra dat we de functie uitgevoerd hebben krijgen we een Transaction-object...
let recenteHash = await connection.getLatestBlockhash() // die nog een recente blockhash nodig heeft
transactie.recentBlockhash = recenteHash.blockhash
transactie.feePayer        = payer.publicKey

transactie.sign(payer) // ook word de transactie daadwerkelijk gesignd
werkende_transactie = transactie; // en we kennen de waarde toe aan de externe variabele
//console.log("Succes: ", transactie);
},
(reden) => {console.log("Foutmelding: ", reden)}
);
await anchor.web3.sendAndConfirmTransaction(
connection,
werkende_transactie,
[payer],
{commitment: "confirmed"}
).then(
(str) => {
console.log(str)
},
(reden) => {console.log("Foutmelding: ", reden)}
);

完整的错误日志和事务详细信息如下:

ransaction {
signatures: [
{
signature: <Buffer f2 eb fe ca 6c e0 f6 33 22 de 69 7d 81 2c c7 a8 98 2b 94 f6 08 e6 15 fb ae fb 49 07 5a b5 be 77 6e 2f 90 2e 6e ed 88 31 ed 2a 94 23 be 93 0a 67 a4 fc ... 14 more bytes>,
publicKey: [PublicKey]
}
],
feePayer: PublicKey {
_bn: <BN: 5f9dc00f214bba9749dc42cfde23e4ebcf76a0f70901c748897c2b65c70f845e>
},
instructions: [
TransactionInstruction {
keys: [Array],
programId: [PublicKey],
data: <Buffer a5 2e 9e e5 9a 97 29 b9 0d 00 00 00 53 6e 61 63 6b 62 61 72 20 54 6f 6e 79 23 00 00 00 44 65 20 62 65 73 74 65 20 73 6e 61 63 6b 73 20 76 61 6e 20 68 ... 48 more bytes>
}
],
recentBlockhash: 'ERRTheoc8z1zcRzu5zfGCMUVEgjL8NsLXst6PaY7yFfw',
nonceInfo: undefined
}
Foutmelding:  SendTransactionError: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
at Connection.sendEncodedTransaction (/home/simeon/dev/rust/anchor/pda_test/node_modules/@solana/web3.js/lib/index.cjs.js:6929:13)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async Connection.sendRawTransaction (/home/simeon/dev/rust/anchor/pda_test/node_modules/@solana/web3.js/lib/index.cjs.js:6890:20)
at async Connection.sendTransaction (/home/simeon/dev/rust/anchor/pda_test/node_modules/@solana/web3.js/lib/index.cjs.js:6880:12)
at async Object.sendAndConfirmTransaction (/home/simeon/dev/rust/anchor/pda_test/node_modules/@solana/web3.js/lib/index.cjs.js:3027:21) {
logs: [
'Program A9RfydetpSqT5rLRZNmeVaeyqG84tXm8cNZkn6Zy1817 invoke [1]',
'Program log: Instruction: MaakBedrijf',
"689GkVL9FauDTVVV6hbW3vWeLa7cBzAHk1Y1sxEFhCqR's signer privilege escalated",
'Program A9RfydetpSqT5rLRZNmeVaeyqG84tXm8cNZkn6Zy1817 consumed 15025 of 200000 compute units',
'Program A9RfydetpSqT5rLRZNmeVaeyqG84tXm8cNZkn6Zy1817 failed: Cross-program invocation with unauthorized signer or writable account'
]
}

我也遇到了同样的问题。在TypeScript客户端中,将Uint8Array.of(id)替换为[id]

Anchor在客户端上有一个内置的实用程序

new anchor.BN(0).toArrayLike(Buffer)

在铁锈方面,你可以在你的种子阵列中使用这个

seeds = [b"seed", &[0 as u8]],

重要部件为&[0 as u8]

如果查看instruction宏的文档,问题出在#[instruction(id: u8)]上:https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html#instruction-属性

其中提到:

您必须按照与指令中相同的顺序列出它们,但可以省略最后一个所需参数之后的所有参数。

所以,这里的id绝对与您的说明中的顺序不同。因此,要使这项工作发挥作用,您可以执行以下操作:

pub fn maak_bedrijf(ctx: Context<MaakBedrijf>, id: u8, naam: String, omschrijving: String, url: String, geverifieerd: bool, bump: u8) -> Result<()> {

基本上,将id: u8移动为指令方法中的第一个参数。

最新更新