sRFC 00002: Off-Chain Instruction Account Resolution

Off-Chain Accounts Resolution for Transaction Creation

Summary

This RFC proposes a solution for collecting all the Accounts needed for a Solana transaction before the transaction is sent to the network. By introducing a new field to the Anchor IDL called invocations and providing a mechanism to insert accounts directly from an on-chain account, we aim to enable clients to resolve all required accounts for a transaction with a single round-trip call to RPC operators for on-chain information.

Goals

  1. Enable clients to resolve all required accounts for a transaction using a single round-trip call to RPC operators.
  2. Provide a deterministic account ordering during transaction creation, allowing programs on-chain to unpack accounts deterministically.

Background

Currently, Solana transactions require all accounts used during execution to be passed in when the transaction is created by the client. Programs can expose information about how to deserialize and serialize their state information and program instructions through their IDL (interface description language).

Proposed Implementation

We propose adding a new field to each instruction description in the Anchor IDL called invocations. This field is an ordered array of program address, instruction data, and accounts. Clients can then recursively traverse this list to determine the accounts each invoked program needs to complete execution.

Anchor IDL with invocations field

{
  "name": "transfer",
  "accounts": [...],
  "args": [...],
  "invocations": [
    {
      "program_address": "pubkey",
      "instruction_data": "data",
      "accounts": [...]
    },
    ...
  ]
}

To handle cases where accounts have to be hardcoded, we should provide a mechanism that allows clients to insert accounts into a transaction directly from an on-chain account. This can be helpful but may also present a potential vector for crafting malicious transactions.

With these two tools, it should be possible to symbolically define the invocations ordered list so that clients can determine the full list of accounts that a program uses in a single RPC call. The full symbolic language will need further specification and design, focusing on conditionals based on instruction data and account names.

Example IDL Instruction for NFT Program’s “transfer”

{
  "name": "transfer",
  "accounts": [
    ...
  ],
  "args": [
    ...
  ],
  "invocations": [
    {
      "program_address": "pubkey",
      "instruction_data": "data",
      "accounts": [...]
    },
    ...
  ]
}

By implementing the proposed solution, we aim to improve the process of resolving accounts required for a Solana transaction and ensure deterministic ordering for unpacking accounts by programs on-chain.

Implementation

TBD

4 Likes

Random suggestion, but what if instead of manually recording invocations we add a more automated approach that collated retrieved accounts at simulation time? It’s a more involved approach, but something I’ve been mulling over and something I think would be scalable and require less work by users in the long run.

  1. Add compilation to WASM for instructions/processors so Solana programs can be called from a client.

  2. Add a mock interface for all Solana specific tasks so basically NOOP so the program can be executed on the client. This is the most work but probably has use cases beyond account resolution.

  3. Add extra code to the #[account] attribute that adds an expression to the deserialization step on simulation. When the data is retrieved from the “account” during simulation, the account address that is retrieved from will be stored in a Set. Therefore any account read from, whether it’s a dynamically determined PDA or static account, will be tracked and added to the set.

  4. After this simulation step, a Set is returned with a complete list of accounts used by the instruction.

Limitations: Account addresses that depend on non-deterministic data (random numbers, slot time, or derivation from on-chain state that is subject to change) may not function with this method.

2 Likes

I think this would work technically, but I have 2 concerns:

  1. Brute forcing transaction simulation to figure out missing accounts breaks the account design constraints that all Solana programs are built with. Accounts are a first-class constraint across the entirety of the Solana runtime. All program development implicitly requires knowledge of the accounts needed during instruction execution. Changing the runtime to support a method of execution to resolve accounts via simulation feels like a direct violation of this primary design constraint for programs.

  2. Figuring out a format for programs to emit missing accounts during simulation will end up being the same as putting required accounts into the IDL. At the point where we start adding branching logic to which accounts are required for any given instruction, then we might as well go all the way and design a language to express which accounts are needed, conditioned on other account data or instruction data. In my head, this ends up being the same approach I have described above.

I think this is really smart, and very doable. However, I think this approach adds compute cost to programs that implement this approach, and is otherwise untenable for frozen programs. For compute-restricted programs, like DeFi, this will probably be unusable.

But I think if you hack away at this, you may discover solutions around this :slight_smile:

Quick note on the original sRFC feasibility:

I think implementing this sRFC could literally be as simple as adding #[idl_annotation(cpi(program, args))] over instruction enums, like shank does. This would not require modifying on-chain code to generate an updated IDL, but would automatically generate the corresponding invocation graph in the IDL. ¯_(ツ)_/¯ hope this helps add some concrete detail until I get around to working on an implementation

3 Likes

I think I overloaded the term “simulation” in my original post :sweat_smile:. My meaning was that all IX/Processors would be compiled to WASM and executable on the client without using RPC or on-chain simulation. Simulation would be done on the client.The #[account] attribute code that keeps track of needed accounts would only be added when compiled for WASM.

But I agree that the original sRFC makes the most sense and would hugely beneficial.

3 Likes