Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Optional feePayerVerify simulation config param (similar to sigVerify) #3214

Open
jacksondoherty opened this issue Sep 6, 2024 · 3 comments
Labels
enhancement New feature or request

Comments

@jacksondoherty
Copy link

jacksondoherty commented Sep 6, 2024

Motivation

Returning account data using an emit instruction is a growing pattern on Solana that enhances composability by abstracting internal data layouts. For example, the TokenMetadata extension has an emit function.

Clients are expected to simulate emit and deserialize the return data. To do so, they need to provide a payer for the transaction, which must have enough lamports to process as if it were submitted. This payer requirement makes it difficult to create libraries that use the simulation workflow. For example, here is a utility function that extracts TokenMetadata using emit – we're required to add a payer parameter:

// Usage
const tokenMetadata = getEmittedTokenMetadata(connection, metadata, program, payer);

// Implementation
async function getEmittedTokenMetadata(
  connection: Connection,
  metadata: PublicKey,
  programId: PublicKey,
  payer: PublicKey
): Promise<TokenMetadata | null> {
  const emitIx = createEmitInstruction({
    programId,
    metadata,
  });
  const latestBlockhash = await connection.getLatestBlockhash();
  const txMsg = new TransactionMessage({
    payerKey: payer,
    recentBlockhash: latestBlockhash.blockhash,
    instructions: [emitIx],
  }).compileToV0Message();
  const v0Tx = new VersionedTransaction(txMsg);
  const res = (await connection.simulateTransaction(v0Tx)).value;

  if (!res.returnData?.data) {
    return null;
  }

  const data = Buffer.from(res.returnData.data[0], "base64");
  return unpack(data);
}

Here's how this might look if we had a feePayerVerify param:

// Usage
const tokenMetadata = getEmittedTokenMetadata(connection, metadata, program); // No payer required

// Implementation
async function getEmittedTokenMetadata(
  connection: Connection,
  metadata: PublicKey,
  programId: PublicKey,
): Promise<TokenMetadata | null> {
  const emitIx = createEmitInstruction({
    programId,
    metadata,
  });
  const latestBlockhash = await connection.getLatestBlockhash();
  const txMsg = new TransactionMessage({
    payerKey: Keypair.generate().publicKey,
    recentBlockhash: latestBlockhash.blockhash,
    instructions: [emitIx],
  }).compileToV0Message();
  const v0Tx = new VersionedTransaction(txMsg);
  const res = (await connection.simulateTransaction(v0Tx, { feePayerVerify: false })).value;

  if (!res.returnData?.data) {
    return null;
  }

  const data = Buffer.from(res.returnData.data[0], "base64");
  return unpack(data);
}

Optionally, we could use some sort of secure community public key as a payer, but this seems hacky.

@jacksondoherty jacksondoherty added the enhancement New feature or request label Sep 6, 2024
@buffalojoec
Copy link
Collaborator

Hey @jacksondoherty thanks for the suggestion. This is actually something we'd have to change on the validator - specifically within the transaction processing pipeline - not Web3.js.

RPC's simulate_transaction will use a Bank instance to run a simulation against the transaction processing pipeline. Sigverify happens before this pipeline is invoked, so it's trivial to disable sigverify using the aptly-named RPC parameter.

https://github.com/anza-xyz/agave/blob/b1de2e0ce873c7aa2e8470a0a3e3e1ae785ca821/rpc/src/rpc.rs#L3827-L3834

Within Bank, this isn't really super configurable (at the moment).

https://github.com/anza-xyz/agave/blob/b1de2e0ce873c7aa2e8470a0a3e3e1ae785ca821/runtime/src/bank.rs#L3318

However, the SVM API does allow for quite a bit of customization. I wonder if we can kill two birds with one stone here.

We might be able to make fees completely configurable within the SVM (transaction processing pipeline), similar to rent collection. Then, we could add a handful of new configurations to the RPC's simulate method - one of which could be to disable fee payer checks completely, by configuring fees to be zero.

Alternatively, we can just jam a check_fee_payer into the SVM API's parameters, similar to your suggestion here.

Side note: I wonder how strongly RPC providers would throw their support behind no-fee program view functions through simulation... 😅

@jacksondoherty
Copy link
Author

Hm it seems fairly involved to do it the "correct way."

The "hacky" solution makes more sense then since it still fully meets requirements. I imagine it's possible to create some sort of community public key that you can prove is randomly generated and nobody owns – perhaps inside a smart contract – and then send it a little dust. Use that inside the library functions.

Not sure if that's something you'd want in the SPL. Either way, thanks for the thoughts @buffalojoec !

@buffalojoec
Copy link
Collaborator

I imagine it's possible to create some sort of community public key that you can prove is randomly generated and nobody owns – perhaps inside a smart contract – and then send it a little dust. Use that inside the library functions.

I really don't think this is something we want to support in Web3.js or SPL, but I can leave this issue open for anyone else to weigh in. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants