5

I am trying to initialize a PDA using the u64 value stored in another account. The idea is that the stored counter increments and the value determines the seed.

My code:

#[derive(Accounts)]
pub struct PdaInit<'info> {
    /// CHECK: Just testing
    #[account(mut)]
    pub payer: Signer<'info>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,

    #[account(init, seeds = [b"seeds".as_ref(), stored_counter_pda.counter.to_be_bytes().as_ref()], bump, payer=payer, space = 100)]
    init_pda: Account<'info, SomeStruct>,

    #[account(mut, seeds = [b"seeds".as_ref()], bump)]
    stored_counter_pda: Account<'info, StoredCounter>,
}

#[account]
#[derive(Default)]
pub struct StoredCounter{
    pub counter: u64,
}

The problem is that I also have to pass the init_pda from the Client, so I need the seed, and I don't know if fetching the stored value of the other PDA is the correct way to deal with this. Supposing that several users on the client side send the instruction to initialize the pda at the same time, if I fetch the counter, I will get the same value for every user and only one user will get a succesfull result on the transaction.

Any help will be appreciated. Thanks.

2 Answers 2

3

Your design as proposed just fundamentally has that race condition in it. If another user beats you to instantiating Account #123, your attempt to do so will fail and you'll have to try again with #124.

There's no way around the fact that you'll need to know all the needed addresses (and surely an address you're initializing is one you need) before you construct and sign the transaction. This is a hard fact about how Solana works.

And given that you're basing your PDA derivation off of an on-chain mutable value, any mutation of that blockchain state necessarily changes the PDA. And your client can never know just if or when another client is out there about to mutate state...

1

The only possible way I can think is to divide the init_pda creation into two transactions.

The first transaction will do the following things:

  1. It will increment the counter in 'stored_counter_pda' account.
  2. creates a separate account, let's say, 'user_info' with two fields - counter and publickey.
  3. saves the incremented counter value in the counter field and the key of the signer in the publickey field.

The second transaction will do the following things:

  1. checks if the signer of the transaction is same as publickey in user_info account.
  2. if yes, allows the creation of 'init_pda' account with the value of the counter saved in user_info account.

If you want to allow the user to create only one init_pda account then assign user_info account to the PDA derived using user's public key. In that way, he can make the first and second transactions only once. Conversely, if the users can create more than one init_pda then simply assign user_info account to a newly generated keypair.

The drawback of the above method is that a user can run the first transaction without running the second transaction which will increment the counter but won't create the adjacent init_pda account. But this drawback can easily be handled in the front-end depending upon the need of your dApp.

On the brighter side, the above method ensures that every user will get unique counter value. Furthermore, any number of users can concurrently call the first transaction and each will go successful and the second transaction can be called anytime without effecting the counter value received by the other users.

3
  • Interesting approach, I will give it a shot. Thanks! Commented Sep 2, 2022 at 16:26
  • I tried your method but still need to pass the init_pda from the client. So If I want to do both transactions in one, lets say approved signature, the first transaction creates de user_info and the 2nd transaction the init_pda, but still need to pass the address from the client side, and I need the counter, which I need to fetch from the user_info, and I can't because it is still not created. Commented Sep 6, 2022 at 19:49
  • Yep! I explained it in the first line of my answer. You can't run both the transactions simultaneously else why do we even need two transactions? The idea is to create user_info account first and then initialise pda using counter saved in user_info in the second.
    – 0xShuk
    Commented Sep 7, 2022 at 8:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.