coral-xyz / anchor

⚓ Solana Sealevel Framework
https://anchor-lang.com
Apache License 2.0
3.69k stars 1.35k forks source link

using RecentBlockhashes gives an error #741

Open vicyyn opened 3 years ago

vicyyn commented 3 years ago
pub fn sysvars(ctx: Context<InitializeSysvars>) -> ProgramResult {
        Ok(())
}

#[derive(Accounts)]
pub struct InitializeSysvars<'info> {
    #[account(signer)]
    pub payer: AccountInfo<'info>,
    pub recent_blockhashes: Sysvar<'info, RecentBlockhashes>,
}
    let tx = await program.rpc.sysvars({
      accounts: {
        recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
        payer: program.provider.wallet.publicKey,
      },
    });
    console.log("Your transaction signature", tx);
  });

'Program BE5g9sKeejZf85vmpU1N16whEgAXsp9jjEFmmN8ZMHMd invoke [1]', 'Program BE5g9sKeejZf85vmpU1N16whEgAXsp9jjEFmmN8ZMHMd consumed 200000 of 200000 compute units', 'Program failed to complete: exceeded maximum number of instructions allowed (200000) at instruction #10123', 'Program BE5g9sKeejZf85vmpU1N16whEgAXsp9jjEFmmN8ZMHMd failed: Program failed to complete'

GDonoghue0 commented 3 years ago

Switching my provider URL from "https://anchor.projectserum.com" to my local JSON RPC URL (http://127.0.0.1:8899 by default I think) fixed this issue for me.

RohanKapurDEV commented 3 years ago

Having the same issue as @vicyyn on localnet. Here is the full repo example of the error.

This is the program code:

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod test_recent_bh {
    use super::*;
    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    pub recent_blockhashes: Sysvar<'info, RecentBlockhashes>,
}

Here is how I'm testing it:

import * as anchor from "@project-serum/anchor";
import { SYSVAR_RECENT_BLOCKHASHES_PUBKEY } from "@solana/web3.js";

describe("test-recent-bh", () => {
  // Configure the client to use the local cluster.
  const provider = anchor.Provider.env();
  anchor.setProvider(provider);

  const idl = JSON.parse(require("fs").readFileSync("./target/idl/test_recent_bh.json"));
  const programId = new anchor.web3.PublicKey("3vcGd5N1KYq3Gv9xPq9VNKqZPGFBRsS8SMGRgAWa98ph");
  const program = new anchor.Program(idl, programId);

  it("Is initialized!", async () => {
    const tx = await program.rpc.initialize({
      accounts: { recentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY },
      signers: [],
    });
    console.log(tx);
  });
});

The relevant part of the anchor test output error:

logs: [
    'Program 3vcGd5N1KYq3Gv9xPq9VNKqZPGFBRsS8SMGRgAWa98ph invoke [1]',
    'Program 3vcGd5N1KYq3Gv9xPq9VNKqZPGFBRsS8SMGRgAWa98ph consumed 200000 of 200000 compute units',
    'Program failed to complete: exceeded maximum number of instructions allowed (200000) at instruction #3714',
    'Program 3vcGd5N1KYq3Gv9xPq9VNKqZPGFBRsS8SMGRgAWa98ph failed: Program failed to complete'
  ]
}
    1) Is initialized!

  0 passing (91ms)
  1 failing

  1) test-recent-bh
       Is initialized!:
     Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Program failed to complete
      at Connection.sendEncodedTransaction (node_modules/@solana/web3.js/src/connection.ts:3668:13)
      at processTicksAndRejections (node:internal/process/task_queues:94:5)
      at Connection.sendRawTransaction (node_modules/@solana/web3.js/src/connection.ts:3628:20)
      at sendAndConfirmRawTransaction (node_modules/@solana/web3.js/src/util/send-and-confirm-raw-transaction.ts:27:21)
      at Provider.send (node_modules/@project-serum/anchor/src/provider.ts:114:18)
      at Object.rpc [as initialize] (node_modules/@project-serum/anchor/src/program/namespace/rpc.ts:19:23)
      at Context.<anonymous> (tests/test-recent-bh.ts:14:16)

I tried adjusting the MAX_ENTRIES and fn size_of() -> usize values in recent_blockhashes.rs to be slightly smaller to see if that'd help it but that did basically nothing and I'm out of ideas lol

tomlinton commented 3 years ago

@RohanKapurDEV I tried out the reproduction (with an update to the programId in the js tests) and didn't experience the issue. Not sure what'd cause this. Might be worth trying fresh.

Arrowana commented 3 years ago

Parsing the enter RecentBlockhashes cannot be done on-chain, it is a vec of 150 Entry, each entry is 32 bytes hashes + fee calculator struct. https://github.com/solana-labs/solana/blob/2515f6a04f92fbab38fff8f40cfdd9d4165b9722/sdk/program/src/sysvar/recent_blockhashes.rs#L15

At least not just like that, the best option seems to deserialize a single specific entry in the vec.

It might not happen on localhost as the test running against genesys might not have a filled RecentBlockhashes vec yet.

mariusbld commented 2 years ago

Parsing the enter RecentBlockhashes cannot be done on-chain, it is a vec of 150 Entry, each entry is 32 bytes hashes + fee calculator struct. https://github.com/solana-labs/solana/blob/2515f6a04f92fbab38fff8f40cfdd9d4165b9722/sdk/program/src/sysvar/recent_blockhashes.rs#L15

At least not just like that, the best option seems to deserialize a single specific entry in the vec.

It might not happen on localhost as the test running against genesys might not have a filled RecentBlockhashes vec yet.

@Arrowana how does one go about deserializing a single specific entry?

I'm experiencing the same issue on localhost. Using a bare-metal Solana API doesn't have the same issue, so it leads me to believe that it's Anchor-related. Code snippet below works:


/// Instruction processor
pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    // Create in iterator to safety reference accounts in the slice
    let account_info_iter = &mut accounts.iter();
    let blockhashes_sysvar_info = next_account_info(account_info_iter)?;
    let blockhashes_via_account = RecentBlockhashes::from_account_info(blockhashes_sysvar_info)?;
    Ok(())
}
Arrowana commented 2 years ago

Parsing the enter RecentBlockhashes cannot be done on-chain, it is a vec of 150 Entry, each entry is 32 bytes hashes + fee calculator struct. https://github.com/solana-labs/solana/blob/2515f6a04f92fbab38fff8f40cfdd9d4165b9722/sdk/program/src/sysvar/recent_blockhashes.rs#L15 At least not just like that, the best option seems to deserialize a single specific entry in the vec. It might not happen on localhost as the test running against genesys might not have a filled RecentBlockhashes vec yet.

@Arrowana how does one go about deserializing a single specific entry?

I'm experiencing the same issue on localhost. Using a bare-metal Solana API doesn't have the same issue, so it leads me to believe that it's Anchor-related. Code snippet below works:

/// Instruction processor
pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    // Create in iterator to safety reference accounts in the slice
    let account_info_iter = &mut accounts.iter();
    let blockhashes_sysvar_info = next_account_info(account_info_iter)?;
    let blockhashes_via_account = RecentBlockhashes::from_account_info(blockhashes_sysvar_info)?;
    Ok(())
}

"It might not happen on localhost as the test running against genesys might not have a filled RecentBlockhashes vec yet."

Maybe let it fill then execute your test.

Also RecentBlockhashes is deprecated, use SlotHashes instead.

An example in metaplex where the first hash is read by doing a manual deserialization. https://github.com/metaplex-foundation/metaplex-program-library/blob/master/candy-machine/program/src/lib.rs#L270-L271

mariusbld commented 2 years ago

@Arrowana thanks for the code snipped, it worked really well!

If you own the Metaplex code, I think there might be an issue with its current form:

        let data = recent_slothashes.data.borrow();
        let most_recent = array_ref![data, 4, 8];
        let index = u64::from_le_bytes(*most_recent);
  1. I think bincode encodes a Vec with an 8-byte length prefix, instead of 4, thus this should be let most_recent = array_ref![data, 8, 8];
  2. SlotHash entries are tuples of (Slot, Hash). Only the Hash part is "random", while Slot is an incremental number. Thus, Slot must be skipped (it's a u64): let most_recent = array_ref![data, 8 + 8, 8];
    pub type SlotHash = (Slot, Hash);
    #[repr(C)]
    #[derive(Serialize, Deserialize, PartialEq, Debug, Default)]
    pub struct SlotHashes(Vec<SlotHash>);