Reputation: 21
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.
Upvotes: 2
Views: 314
Reputation: 91
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());
});
});
Upvotes: 3