0

Very new at rust and Solana development, hoping to get some advice.

I am creating some blockchain functionality for a betting system. Players will bet on the outcome of a certain event by sending the details of their bet to my Solana program and their bet in SOL to a "fund account" PDA. When the outcome is determined, I determine who the winners are and collect their public keys and how much SOL should be sent to them. The program will use the PDA to send the SOL transactions. I am hoping this can be done in a single transaction to save on transaction fees.

I've created some simple scripts to test this functionality.

This is my rust program:

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    pubkey::Pubkey,
    program_error::ProgramError,
    system_instruction,
    program::invoke_signed,
    msg,
    log::sol_log,
};

entrypoint!(process_instruction);

// Assuming `TransferRequest` and `process_transfer_requests` from the previous example.
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct TransferRequest {
    pub recipient: Pubkey,
    pub amount: u64,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct TransferRequests {
    pub requests: Vec<TransferRequest>,
}

// Function to create a PDA for the fund account
pub fn create_fund_pda(program_id: &Pubkey) -> (Pubkey, u8) {
    // Replace "fundaccountseed" with your chosen seed
    Pubkey::find_program_address(&[b"fundAccount"], program_id)
}

// Example function to process payouts, using the PDA for transfers
fn process_payouts(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    transfer_requests: Vec<TransferRequest>,
) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();

    // Assuming the first account is the PDA for the fund and the second is the system program.
    let fund_pda_account = next_account_info(accounts_iter)?;
    let system_program_account = next_account_info(accounts_iter)?;

    let (fund_pda, bump_seed) = create_fund_pda(program_id);
    assert_eq!(&fund_pda, fund_pda_account.key);

    for request in transfer_requests {
        // Logging is optional and used here for demonstration.
        msg!(
            "Processing transfer request for recipient: {}, amount: {}",
            request.recipient,
            request.amount
        );
        
        let transfer_instruction = system_instruction::transfer(
            &fund_pda_account.key,
            &request.recipient,
            request.amount,
        );

        sol_log(&format!("Transfer instruction: {:?}", transfer_instruction));

        // Since PDAs cannot sign for themselves, you use `invoke_signed` with the appropriate seeds.
        invoke_signed(
            &transfer_instruction,
            &[fund_pda_account.clone(), system_program_account.clone()],
            &[&[&b"fundAccount"[..], &[bump_seed]]],
        )?;
    }

    Ok(())
}

/// The main entry point for the Solana program.
fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    sol_log("Received instruction data");

    // Log the received instruction data length
    sol_log(&format!(
        "Instruction data length: {}",
        instruction_data.len()
    ));

    match TransferRequests::try_from_slice(instruction_data) {
        Ok(transfer_requests) => {
            sol_log("Deserialized TransferRequests successfully");
            sol_log(&format!("Number of requests: {}", transfer_requests.requests.len()));
            // Continue processing as normal...
            process_payouts(program_id, accounts, transfer_requests.requests)
        }
        Err(e) => {
            sol_log("Failed to deserialize TransferRequests");
            sol_log(&format!("Deserialization error: {:?}", e));
            Err(ProgramError::InvalidInstructionData)
        }
    }
}

And this is my javascript program that interfaces with that:

const solanaWeb3 = require('@solana/web3.js');
const { Keypair, Transaction, TransactionInstruction, PublicKey, SystemProgram } = solanaWeb3;
const borsh = require('borsh');
const fs = require('fs');

const keypairPath = process.argv[2];
if (!keypairPath) {
    console.error('Usage: node <script.js> <keypairPath>');
    process.exit(1);
}

const keypairData = JSON.parse(fs.readFileSync(keypairPath, 'utf8'));

async function airdropToPDA(connection, fundPDA) {
    const LAMPORTS_PER_SOL = solanaWeb3.LAMPORTS_PER_SOL;
    let airdropSignature = await connection.requestAirdrop(
        fundPDA,
        1 * LAMPORTS_PER_SOL
    );
    await connection.confirmTransaction(airdropSignature);
    console.log(`Airdropped 1 SOL to ${fundPDA.toString()}`);
}


class TransferRequest {
    constructor({recipient, amount}) {
        // Ensure the recipient public key is stored as a raw 32-byte array
        this.recipient = new PublicKey(recipient).toBuffer();
        this.amount = amount;
    }
}

class TransferRequests {
    constructor({requests}) {
        this.requests = requests;
    }
}

const schema = new Map([
    [TransferRequest, {kind: 'struct', fields: [['recipient', [32]], ['amount', 'u64']]}],
    [TransferRequests, {kind: 'struct', fields: [['requests', [TransferRequest]]]}]
]);

async function sendBulkTransfer(connection, programId, seeds, transferRequests, payer) {
    // Derive the PDA
    const [fundPDA, bumpSeed] = PublicKey.findProgramAddressSync(seeds, programId);

    console.log("fundPDA: ", fundPDA.toBase58());

    // Prepare transfer requests
    const transfers = transferRequests.map(request => new TransferRequest({recipient: request.recipient, amount: request.amount}));
    const bulkTransferData = new TransferRequests({requests: transfers});

    const serializedData = borsh.serialize(schema, bulkTransferData);

    const keys = [
        { pubkey: fundPDA, isSigner: false, isWritable: true },
        { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }
    ];

    const instruction = new TransactionInstruction({
        keys,
        programId,
        data: serializedData,
    });

    const transaction = new Transaction().add(instruction);
    const signedTransaction = await solanaWeb3.sendAndConfirmTransaction(connection, transaction, [payer]);
    console.log("Signed transaction: ", signedTransaction);
}

async function main() {
    const connection = new solanaWeb3.Connection(solanaWeb3.clusterApiUrl('devnet'), 'confirmed');
    const programId = new solanaWeb3.PublicKey("HFaVb5gk49QzxuNNjSbJKGZ7rC1qR8LqYDNaX539Gntx");
    const seeds = [Buffer.from("fundAccount", 'utf8')];
    const payer = solanaWeb3.Keypair.fromSecretKey(new Uint8Array(keypairData));

    // Derive the PDA
    const [fundPDA, _bumpSeed] = PublicKey.findProgramAddressSync(seeds, programId);
    // Airdrop to PDA
    await airdropToPDA(connection, fundPDA);

    // in a loop, create a new keypair, extract the public key, and add it to the transferRequests array
    const transferRequests = [];
    for (let i = 0; i < 10; i++) {
        const keypair = Keypair.generate();
        const publicKey = keypair.publicKey;
        transferRequests.push(new TransferRequest({recipient: publicKey, amount: 0.001 * solanaWeb3.LAMPORTS_PER_SOL}));
    }

    //console.log("transfer requests: ", transferRequests);

    sendBulkTransfer(connection, programId, seeds, transferRequests, payer)
        .then(() => console.log("Bulk transfer submitted."))
        .catch((e) => console.error(e));
}

main().then(() => console.log("Completed")).catch(console.error);

Currently, the instructions are correctly being read by the Solana program, but it is encountering an error when trying to invoke_signed:

'Program 9h64X77j5Anpe3gyBeMx1WpkLVBnyG7nP3YHPcNyLAfK invoke [1]',
    'Program log: Received instruction data',
    'Program log: Instruction data length: 404',
    'Program log: Deserialized TransferRequests successfully',
    'Program log: Number of requests: 10',
    'Program log: Processing transfer request for recipient: 39QQCWN5n4K45B5NFL2RcuUbGrpk4a2ud7QgKaK2e5WU, amount: 1000000',
    'Program log: Transfer instruction: Instruction { program_id: 11111111111111111111111111111111, accounts: [AccountMeta { pubkey: GgfViAgvtuWuW8mHXJwYkaMWHT8t1jVvDoV3jATmfY2A, is_signer: true, is_writable: true }, AccountMeta { pubkey: 39QQCWN5n4K45B5NFL2RcuUbGrpk4a2ud7QgKaK2e5WU, is_signer: false, is_writable: true }], data: [2, 0, 0, 0, 64, 66, 15, 0, 0, 0, 0, 0] }',
    'Program 9h64X77j5Anpe3gyBeMx1WpkLVBnyG7nP3YHPcNyLAfK consumed 53253 of 200000 compute units',
    'Program 9h64X77j5Anpe3gyBeMx1WpkLVBnyG7nP3YHPcNyLAfK failed: Instruction passed to inner instruction is too large (12884933690 > 1280)'

i.e. it's saying the instruction is too large. I've tried to google this problem and mostly people say to use an earlier version of the Solana CLI but when I do that I run into all kinds of dependency problems. Any help with this would be so appreciated!

1 Answer 1

0

Be sure to upgrade all of your tools to the same consistent values, preferably 1.18 at the time of this writing, Solana crates and CLI.

You can upgrade the CLI with:

solana-install init 1.18.7

And in your Cargo.toml:

solana-program = "1.18"

More information about this particular issue at https://github.com/solana-labs/solana/issues/31960#issuecomment-1668682153

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.