4

When you send a raw instruction to the network, you have to specify in the instruction data an instruction identifier, so that the program knows what function to call. For example, in python:

from solana.rpc.api import Client
from solana.transaction import Transaction, TransactionInstruction

client = Client("http://localhost:8899")

prog_id = PublicKey("EcFTD...Js3fd")

txn = Transaction().add(
    TransactionInstruction([
        # accounts
        ...
    ],
    # program address
    prog_id,
    # data
    # instruction offset
    bytes(b"\xac\x89\xb7\x0e\xcfn\xea8")
    )
)

client.send_transaction(txn, sender)

that string, b"\xac\x89\xb7\x0e\xcfn\xea8", I found using some anchor when I started building the program. Now I have multiple functions, and can't figure out what data anchor expects me to pass to call the other ones.

2 Answers 2

3

The instruction identifier of Anchor based Solana programs is a SHA256 hash of the namespace and name of the function used for the instruction with a colon in between. For example:

global:test_function

In the above, the namespace is global (the default namespace for functions you make) and "test_function" is the method signature. If you don't explicitly specify a different namespace for your functions, it's going to be the global namespace.

Anchor expects the first 8 bytes of the instruction data to be this hash.

No need to worry about including the parameters of the method/function in the hash because there is no function overloading in Rust.

It was harder than I hoped (and harder than it probably should be) to find this info but this post gave a clue: https://blog.labeleven.dev/anatomy-of-solana-program-invocations-using-anchor and this answer: How do you find a matching IDL instruction using the Anchor instruction discriminator from instruction data?

2

Most programs support multiple discrete instructions - you decide when writing your program what these instructions are and what data must accompany them

Rust enums are often used to represent discrete program instructions.

enum NoteInstruction {
    CreateNote {
        title: String,
        body: String,
        id: u64
    },
    UpdateNote {
        title: String,
        body: String,
        id: u64
    },
    DeleteNote {
        id: u64
    }
}

Given these instructions, it's standard practice to structure your program to expect the first byte (or other fixed number of bytes) to be an identifier for which instruction the program should run. This could be an integer or a string identifier. For this example, we'll use the first byte and map integers 0, 1, and 2 to instructions create, update, and delete, respectively.

impl NoteInstruction {
    // Unpack inbound buffer to associated Instruction
    // The expected format for input is a Borsh serialized vector
    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
        // Take the first byte as the variant to
        // determine which instruction to execute
        let (&variant, rest) = input.split_first().ok_or(ProgramError::InvalidInstructionData)?;
        // Use the temporary payload struct to deserialize
        let payload = NoteInstructionPayload::try_from_slice(rest).unwrap();
        // Match the variant to determine which data struct is expected by
        // the function and return the TestStruct or an error
        Ok(match variant {
            0 => Self::CreateNote {
                title: payload.title,
                body: payload.body,
                id: payload.id
            },
            1 => Self::UpdateNote {
                title: payload.title,
                body: payload.body,
                id: payload.id
            },
            2 => Self::DeleteNote {
                id: payload.id
            },
            _ => return Err(ProgramError::InvalidInstructionData)
        })
    }
}

There's a lot in this example so let's take it one step at a time:

  1. This function starts by using the split_first function on the input parameter to return a tuple. The first element, variant, is the first byte from the byte array and the second element, rest, is the rest of the byte array.

  2. The function then uses the try_from_slice method on NoteInstructionPayload to deserialize the rest of the byte array into an instance of NoteInstructionPayload called payload

  3. Finally, the function uses a match statement on variant to create and return the appropriate enum instance using information from payload

Will advise you to go through this module on the Solana course to see in detail how this works

1
  • 1
    forgot to mention, I am using anchor. I wanted to find out what anchor expects in the instruction data Commented Aug 6, 2022 at 11:56

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.