#solana #randomness #switchboard #service #on-chain #lite #oracle

solana-randomness-service-lite

Solana程序的链上随机数服务

2个稳定版本

1.0.1 2024年2月12日
1.0.0 2024年2月9日

#1883 in 神奇豆

MIT/Apache

25KB
270

Solana Randomness Service Lite

Solana随机数服务使用一个启用了Switchboard SGX的预言机,通过回调指令为任何使用回调指令的Solana程序提供随机数。

程序ID: RANDMo5gFnqnXJW5Z52KNmd24sAo95KAd5VbiCtq5Rh

注意: 此程序ID适用于mainnet-beta和devnet。

查看solana-randomness-service以获取完整的CPI接口。

请求生命周期

  1. 用户的程序使用CPI调用,并带有随机字节数、自定义回调指令和优先级费用配置,调用simple_randomness_v1指令
    • 创建一个SimpleRandomnessV1Account账户
    • 设置自定义回调
    • 将资金包装成保证金以奖励预言机满足请求
  2. 链外启用了SGX的预言机读取请求账户
    • 在安全区内部生成随机字节
    • 构建一个包含您的回调和所需优先级费用的tx
    • 模拟tx。如果成功,将tx在链上中继。如果出错,将错误指令与错误消息一起中继,错误消息可在浏览器中查看。
  3. 交易已在中继链上
    • 预言机因满足请求而获得奖励
    • 预言机调用用户的回调指令
    • 请求账户被关闭,租金豁免返还给原始付款人

使用方法

将solana_randomness_service添加到您的Cargo.toml中

solana-randomness-service-lite = "1"

查看下面的示例程序,了解如何将Solana随机数服务集成到您的Anchor程序中。

  1. 使用您的付款人、回调和您所需的优先级费用配置调用simple_randomness_v1指令
  2. 构建随机数服务将调用的回调指令
use anchor_lang::prelude;
use solana_randomness_service_lite::{SimpleRandomnessV1Request, ID as SolanaRandomnessServiceID};

declare_id!("39hMZgeiesFXMRFt8svuKVsdCW5geiYueSRx7dxhXN4f");

#[program]
pub mod solana_randomness_consumer {
    use super::*;

    pub fn request_randomness(ctx: Context<RequestRandomness>) -> anchor_lang::prelude::Result<()> {
        msg!("Requesting randomness...");

        let request = SimpleRandomnessV1Request {
            request: ctx.accounts.randomness_request.to_account_info(),
            escrow: ctx.accounts.randomness_escrow.to_account_info(),
            state: ctx.accounts.randomness_state.to_account_info(),
            mint: ctx.accounts.randomness_mint.to_account_info(),
            payer: ctx.accounts.payer.to_account_info(),
            system_program: ctx.accounts.system_program.to_account_info(),
            token_program: ctx.accounts.token_program.to_account_info(),
            associated_token_program: ctx.accounts.associated_token_program.to_account_info(),
        };
        request.invoke(
            ctx.accounts.randomness_service.to_account_info(),
            8, // Request 8 bytes of randomness
            &solana_randomness_service_lite::Callback::new(
                ID,
                vec![
                    AccountMeta::new_readonly(ctx.accounts.randomness_state.key(), true).into(),
                    AccountMeta::new_readonly(ctx.accounts.randomness_request.key(), false).into(),
                ],
                [190, 217, 49, 162, 99, 26, 73, 234].to_vec(), // Our callback ixn discriminator. The oracle will append the randomness bytes to the end
            ),
            &Some(solana_randomness_service_lite::TransactionOptions {
                compute_units: Some(1_000_000),
                compute_unit_price: Some(100),
            }),
        )?;

        // Here we can emit some event to index our requests

        Ok(())
    }

    pub fn consume_randomness(
        _ctx: Context<ConsumeRandomness>,
        result: Vec<u8>,
    ) -> anchor_lang::prelude::Result<()> {
        msg!("Randomness received: {:?}", result);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct RequestRandomness<'info> {
    /// CHECK: manually check programID and executable status
    #[account(
        constraint = randomness_service.key() == SolanaRandomnessServiceID,
        constraint = randomness_service.executable,
    )]
    pub randomness_service: AccountInfo<'info>,

    /// The account that will be created on-chain to hold the randomness request.
    /// Used by the off-chain oracle to pickup the request and fulfill it.
    /// CHECK: todo
    #[account(
        mut,
        signer,
        owner = system_program.key(),
        constraint = randomness_request.data_len() == 0 && randomness_request.lamports() == 0,
    )]
    pub randomness_request: AccountInfo<'info>,

    /// The TokenAccount that will store the funds for the randomness request.
    /// CHECK: todo
    #[account(
        mut,
        owner = system_program.key(),
        constraint = randomness_escrow.data_len() == 0 && randomness_escrow.lamports() == 0,
    )]
    pub randomness_escrow: AccountInfo<'info>,

    /// The randomness service's state account. Responsible for storing the
    /// reward escrow and the cost per random byte.
    #[account(
        seeds = [b"STATE"],
        bump = randomness_state.bump,
        seeds::program = randomness_service.key(),
    )]
    pub randomness_state: Box<Account<'info, solana_randomness_service::State>>,

    /// The token mint to use for paying for randomness requests.
    #[account(address = NativeMint::ID)]
    pub randomness_mint: Account<'info, Mint>,

    /// The account that will pay for the randomness request.
    #[account(mut)]
    pub payer: Signer<'info>,

    /// The Solana System program. Used to allocate space on-chain for the randomness_request account.
    pub system_program: Program<'info, System>,

    /// The Solana Token program. Used to transfer funds to the randomness escrow.
    pub token_program: Program<'info, Token>,

    /// The Solana Associated Token program. Used to create the TokenAccount for the randomness escrow.
    pub associated_token_program: Program<'info, AssociatedToken>,
}

Typescript客户端

可以使用 TypeScript 客户端与链下随机性服务进行交互。

npm i @switchboard-xyz/solana-randomness-service

可以从锚点提供者初始化随机性服务客户端。

Switchboard Labs 提供了一系列链下预言机,以满足在 测试网主网 上的任何请求。

对于 本地网络,RandomnessService 将自动初始化并创建自己的 Switchboard 基础设施。当您等待请求解决时,随机性服务将自行检查和满足请求。

import * as anchor from "@coral-xyz/anchor";
import { RandomnessService } from "@switchboard-xyz/solana-randomness-service";

const provider = anchor.AnchorProvider.env();
const randomnessService = await RandomnessService.fromProvider(provider);

// Create a keypair for our request account. This account will be automatically closed on settlement and
// the rent will be returned to the original payer.
const requestKeypair = anchor.web3.Keypair.generate();

// Start watching for the settled event before triggering the request.
// If on localnet this will fulfill the randomness request for you in the background.
const settledRandomnessEventPromise = randomnessService.awaitSettledEvent(
  requestKeypair.publicKey
);

// your program makes a CPI request to the RandomnessService
const signature = await program.methods
  .requestRandomness()
  .accounts({
    randomnessService: randomnessService.programId,
    randomnessRequest: requestKeypair.publicKey,
    randomnessEscrow: anchor.utils.token.associatedAddress({
      mint: randomnessService.accounts.mint,
      owner: requestKeypair.publicKey,
    }),
    randomnessState: randomnessService.accounts.state,
    randomnessMint: randomnessService.accounts.mint,
    payer: provider.wallet.publicKey,
  })
  .signers([requestKeypair])
  .rpc();

// Await the response from the Switchboard Service
const [settledRandomnessEvent, settledSlot] =
  await settledRandomnessEventPromise;

依赖项

~12–20MB
~285K SLoC