简体   繁体   中英

My transaction fails when trying to Init VRF on Devnet using Anchor (0x0 Error)

I'm trying to use a VRF for Solana to generate random numbers, I'm using CPI to do this. When running the test on Localhost it works fine and generates the numbers.

However when I switch to Devnet it fails with the error:

Transaction simulation failed Custom Error 0x0

I'm using Orao-VRF: Github

My lib.rs code:

use anchor_lang::prelude::*;
use orao_solana_vrf::network_state_account_address;
use orao_solana_vrf::program::OraoVrf;
use orao_solana_vrf::randomness_account_address;
use orao_solana_vrf::state::NetworkState;
use orao_solana_vrf::CONFIG_ACCOUNT_SEED;
use orao_solana_vrf::RANDOMNESS_ACCOUNT_SEED;
use std::mem::size_of;

declare_id!("6ag7tVY7RizWm4xZr7Vv3N4yGio5mqS6H9VFAUFvuMQt");

#[program]
pub mod cflip {
    use orao_solana_vrf::cpi::accounts::Request;

    use super::*;

    pub fn spin_and_pull_the_trigger(
        ctx: Context<SpinAndPullTheTrigger>,
        force: [u8; 32],
    ) -> Result<()> {
        // Zero seed is illegal in VRF
        if force == [0_u8; 32] {
            return Err(Error::YouMustSpinTheCylinder.into());
        }

        // Request randomness.
        let cpi_program = ctx.accounts.vrf.to_account_info();
        let cpi_accounts = Request {
            payer: ctx.accounts.player.to_account_info(),
            network_state: ctx.accounts.config.to_account_info(),
            treasury: ctx.accounts.treasury.to_account_info(),
            request: ctx.accounts.random.to_account_info(),
            system_program: ctx.accounts.system_program.to_account_info(),
        };
        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
        orao_solana_vrf::cpi::request(cpi_ctx, force)?;

        fn get_random(randomness: &[u8; 64]) -> u8 {
            // use only first 8 bytes for simplicyty
            let value = &randomness[0..size_of::<u64>()];
            return value[0];
        }

        Ok(())
    }
}

#[derive(Accounts)]
#[instruction(force: [u8; 32])]
pub struct SpinAndPullTheTrigger<'info> {
    #[account(mut)]
    player: Signer<'info>,
    /// CHECK:
    #[account(
        mut,
        seeds = [RANDOMNESS_ACCOUNT_SEED.as_ref(), &force],
        bump,
        seeds::program = orao_solana_vrf::ID
    )]
    random: AccountInfo<'info>,
    /// CHECK:
    #[account(mut)]
    treasury: AccountInfo<'info>,
    #[account(
        mut,
        seeds = [CONFIG_ACCOUNT_SEED.as_ref()],
        bump,
        seeds::program = orao_solana_vrf::ID
    )]
    config: Account<'info, NetworkState>,
    vrf: Program<'info, OraoVrf>,
    system_program: Program<'info, System>,
}

#[error_code]
pub enum Error {
    #[msg("The player is already dead")]
    PlayerDead,
    #[msg("Unable to serialize a randomness request")]
    RandomnessRequestSerializationError,
    #[msg("Player must spin the cylinder")]
    YouMustSpinTheCylinder,
    #[msg("The cylinder is still spinning")]
    TheCylinderIsStillSpinning,
}

/// Helper that builds the instruction.
#[cfg(feature = "sdk")]
pub fn spin_and_pull_the_trigger<'a>(
    cflip: &'a anchor_client::Program,
    vrf: &anchor_client::Program,
) -> std::result::Result<anchor_client::RequestBuilder<'a>, anchor_client::ClientError> {
    let seed = rand::random();

    // vrf accounts
    let network_state_address = network_state_account_address();
    let request_address = randomness_account_address(&seed);

    let vrf_config = vrf.account::<NetworkState>(network_state_address)?.config;

    Ok(cflip
        .request()
        .accounts(crate::accounts::SpinAndPullTheTrigger {
            player: cflip.payer(),
            treasury: vrf_config.treasury,
            random: request_address,
            config: network_state_address,
            vrf: orao_solana_vrf::id(),
            system_program: anchor_client::solana_sdk::system_program::ID,
        })
        .args(crate::instruction::SpinAndPullTheTrigger { force: seed }))
}

My test.ts code:

import assert from "assert";
import * as anchor from "@project-serum/anchor";
import { Program, BN, Spl } from "@project-serum/anchor";
import {
    Keypair,
    PublicKey,
    SystemProgram,
    LAMPORTS_PER_SOL,
    SYSVAR_RENT_PUBKEY,
    SYSVAR_INSTRUCTIONS_PUBKEY,
    Ed25519Program,
} from "@solana/web3.js";
import { Orao, networkStateAccountAddress, randomnessAccountAddress } from "../js/dist";
import { Cflip } from "../target/types/cflip";
import nacl from "tweetnacl";

describe("cflip", () => {
    const provider = anchor.AnchorProvider.env();
    anchor.setProvider(provider);

    const program = anchor.workspace.Cflip as Program<Cflip>;
    const vrf = new Orao(provider);

    // This accounts are for test VRF.
    const treasury = Keypair.generate();
    const fulfillmentAuthority = Keypair.generate();

    // Initial force for russian-roulette
    let force = Keypair.generate().publicKey;

    // This helper will play a single round of russian-roulette.
    async function spinAndPullTheTrigger(force: Buffer) {
        const random = randomnessAccountAddress(force);

        await program.methods
            .spinAndPullTheTrigger([...force])
            .accounts({
                player: provider.wallet.publicKey,
                vrf: vrf.programId,
                config: networkStateAccountAddress(),
                treasury: treasury.publicKey,
                random,
                systemProgram: SystemProgram.programId,
            })
            .rpc();
    }

    // This helper will fulfill randomness for our test VRF.
    async function emulateFulfill(seed: Buffer) {
        let signature = nacl.sign.detached(seed, fulfillmentAuthority.secretKey);
        await vrf.methods
            .fulfill()
            .accounts({
                instructionAcc: SYSVAR_INSTRUCTIONS_PUBKEY,
                networkState: networkStateAccountAddress(),
                request: randomnessAccountAddress(seed),
            })
            .preInstructions([
                Ed25519Program.createInstructionWithPublicKey({
                    publicKey: fulfillmentAuthority.publicKey.toBytes(),
                    message: seed,
                    signature,
                }),
            ])
            .rpc();
    }


    before(async () => {
        await provider.connection.confirmTransaction(
            await provider.connection.requestAirdrop(treasury.publicKey, 1 * LAMPORTS_PER_SOL),
            "confirmed"
          );

        // Initialize test VRF
        const fee = 1 * LAMPORTS_PER_SOL;
        const fullfillmentAuthorities = [
            fulfillmentAuthority.publicKey,
        ];
        
        const configAuthority = Keypair.generate();

        await vrf.methods
            .initNetwork(
                new BN(fee),
                configAuthority.publicKey,
                fullfillmentAuthorities,
                null
            )
            .accounts({
                networkState: networkStateAccountAddress(),
                treasury: treasury.publicKey,
            })
            .rpc();
    });

    it("spin and pull the trigger", async () => {
        await spinAndPullTheTrigger(force.toBuffer());

        await emulateFulfill(force.toBuffer());
        const randomness = await vrf.getRandomness(force.toBuffer());

        console.log(randomness.randomness);
    });
});

From what I've gathered it's something to do with this before block in the tests file:

    before(async () => {
        await provider.connection.confirmTransaction(
            await provider.connection.requestAirdrop(treasury.publicKey, 1 * LAMPORTS_PER_SOL),
            "confirmed"
          );

        // Initialize test VRF
        const fee = 1 * LAMPORTS_PER_SOL;
        const fullfillmentAuthorities = [
            fulfillmentAuthority.publicKey,
        ];
        
        const configAuthority = Keypair.generate();

        await vrf.methods
            .initNetwork(
                new BN(fee),
                configAuthority.publicKey,
                fullfillmentAuthorities,
                null
            )
            .accounts({
                networkState: networkStateAccountAddress(),
                treasury: treasury.publicKey,
            })
            .rpc();
    });

Any help would be massively appreciated. Thank you.

VRF program on devnet is already initialized with our config, so you can't initialize it. The emulateFulfill function will throw UnauthorizedFulfillmentAuthority exception because your fulfillmentAuthority key is not whitelisted. Try something like this, don't forget to change the program ID with your own cflip program ID

import * as anchor from "@project-serum/anchor";
import { Program } from "@project-serum/anchor";
import {
    Keypair,
    PublicKey,
    SystemProgram,
} from "@solana/web3.js";
import { Orao, networkStateAccountAddress, randomnessAccountAddress } from "@orao-network/solana-vrf";
import { Cflip, IDL } from "../target/types/cflip";

describe("cflip", () => {
    const provider = anchor.AnchorProvider.env();
    anchor.setProvider(provider);

    const program = new Program<Cflip>(IDL, new PublicKey("2XeJv53N1UzbupYNDH9PDakRWQFS4VX4bJDPm8P5T64J"), provider);
    const vrf = new Orao(provider);

    // Initial force for russian-roulette
    let force = Keypair.generate().publicKey;

    // This helper will play a single round of russian-roulette.
    async function spinAndPullTheTrigger(force: Buffer) {
        const random = randomnessAccountAddress(force);
        const networkState = await vrf.getNetworkState()
        const treasury = networkState.config.treasury

    await program.methods
        .spinAndPullTheTrigger([...force])
        .accounts({
            player: provider.wallet.publicKey,
            vrf: vrf.programId,
            config: networkStateAccountAddress(),
            treasury,
            random,
            systemProgram: SystemProgram.programId,
        })
        .rpc();
}

it("spin and pull the trigger", async () => {
    await spinAndPullTheTrigger(force.toBuffer());

    // Await fulfilled randomness (default commitment is "finalized"):
    const randomness = await vrf.waitFulfilled(force.toBuffer());
    console.log("Your randomness is " + randomness.fulfilled());
    });
});

What is your Anchor.toml? Also i think you use the SDK not the CPI, it would be more helpful if you posted more code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM