Skip to content

SIMD-0287: Message Headers with Compute Budget Metadata #287

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

cavemanloverboy
Copy link

@cavemanloverboy cavemanloverboy commented May 21, 2025

Summary

We introduce three new (candidate) versions for transaction format aiming at
reducing the transaction footprint for compute budget instructions.

Motivation

The use of compute budget instructions has become ubiquitous. These instructions
currently have a nontrivial serialized footprint and are presently wasteful in
their implementation. The information in these instructions can be compacted
significantly, enabling users to fit a bit more data in their transaction payload.

Detailed Design

v1: Fixed Fields for Compute Unit Limit & Price

  • Change: MessageHeader is extended to include two new fields:
    • compute_unit_price (u64)
    • compute_unit_limit (u32)
  • Serialization: These fields are serialized immediately before the existing
    three u8 signature counters.

v2: Fixed Fields for Compute Unit Limit & Price, Loaded Data & Heap Requests

  • Change: MessageHeader is extended to include four new fields:
    • compute_unit_price (u64)
    • compute_unit_limit (u32)
    • loaded_accounts_data_limit (u32)
    • requested_heap_bytes (u32)
  • Serialization: These fields are serialized immediately before the existing
    three u8 signature counters.

v3: Dynamic Header

  • Change: Introduce a new ComputeBudgetHeader struct at the front of the
    message, containing:
    • flags: u8 bitmask indicating which compute budget fields are present.
    • Optional fields (Option<u32> or Option<u64>) for the parameters
  • Serialization:
    1. Emit flags byte.
    2. For each bit set in flags, serialize the corresponding field in order
      without additional tags.
    3. Follow with the existing MessageHeader (three u8 counters) and the
      rest of the message.

Importantly, this variant supports up to 8 compute budget ixs (reserved for future use).

Example

We consider v0 (existing), v1, v2, v3 message with noop (no instructions), cu limit + price, and all four existing compute budget instructions. These are the serialized footprints.

v0 noop                    = 70
v0 with cu limit + price   = 122
v0 with full cb ix set     = 138

v1 noop                    = 82
v1 with cu limit + price   = 82
v1 with full cb ix set     = 130

v2 noop                    = 90
v2 with cu limit + price   = 90
v2 with full cb ix set     = 90

v3 noop                    = 71
v3 with cu limit + price   = 83
v3 with full cb ix set     = 91

The code for this can be found here


- **Change**: Introduce a new `ComputeBudgetHeader` struct at the front of the
message, containing:
- `flags: u8` bitmask indicating which compute budget fields are present.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what're thoughts on the below? for future proof, is there a world where >8 compute-related operations take place?

#[repr(u8)]
enum ComputeBudgetOp {
    ComputeUnitPrice(u64),
    ComputeUnitLimit(u32),
    ...
}

ComputeBudgetHeader {
    // perhaps Vec encoded with u8 length discriminator instead of u64
    ops: Vec<ComputeBudgetOp>
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in all seriousness, i think if we're worried about future proofing we can use a 16-bit flag

Copy link
Author

@cavemanloverboy cavemanloverboy May 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this alternative repr is a bit wasteful bc every element comes with a discriminator, and you will need to check for dups. with the bitflag you get uniqueness for free and uniquely determines lenth + deserialization.

Copy link
Contributor

@buffalu buffalu May 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edit: the optional fields confused me; thats for the API not for the serialization format. what you said makes sense to me

@Jac0xb
Copy link

Jac0xb commented May 21, 2025

ComputeBudget program instructions are a huge waste, transaction settings should be in the header. +1 also a good excuse to increment the ungodly V1 transaction enum to V2 transaction and stop confusing everyone.

@afalaleev
Copy link

It is possible to get access to Compute Budget instructions from a Solana Program

use solana_compute_budget_interface::check_id as check_compute_budget_id;
use solana_program::instruction::{
     get_processed_sibling_instruction, get_stack_height, TRANSACTION_LEVEL_STACK_HEIGHT,
};
....
fn get_compute_budget_priority_fee() -> Result<u64, Error> {
    ...
    let is_root_transaction = get_stack_height() == TRANSACTION_LEVEL_STACK_HEIGHT;
    if get_stack_height() != TRANSACTION_LEVEL_STACK_HEIGHT {
        return Ok(0);
    }
    ......
    for idx in 0..N {
        let Some(ix) = get_processed_sibling_instruction(idx) else {
            break;
        };
        if !check_compute_budget_id(&ix.program_id) {
            continue;
        }
        ...
        // ok, this is a Compute Budget instruction

But MessageHeader isn't available on-chain.

Solana Programs already have this possibilities and build some logic around it.
What mechanics are suggested to replace the existing functionality?

@cavemanloverboy
Copy link
Author

cavemanloverboy commented May 24, 2025

It is possible to get access to Compute Budget instructions from a Solana Program

use solana_compute_budget_interface::check_id as check_compute_budget_id;
use solana_program::instruction::{
     get_processed_sibling_instruction, get_stack_height, TRANSACTION_LEVEL_STACK_HEIGHT,
};
....
fn get_compute_budget_priority_fee() -> Result<u64, Error> {
    ...
    let is_root_transaction = get_stack_height() == TRANSACTION_LEVEL_STACK_HEIGHT;
    if get_stack_height() != TRANSACTION_LEVEL_STACK_HEIGHT {
        return Ok(0);
    }
    ......
    for idx in 0..N {
        let Some(ix) = get_processed_sibling_instruction(idx) else {
            break;
        };
        if !check_compute_budget_id(&ix.program_id) {
            continue;
        }
        ...
        // ok, this is a Compute Budget instruction

But MessageHeader isn't available on-chain.

Solana Programs already have this possibilities and build some logic around it. What mechanics are suggested to replace the existing functionality?

yea this is kind of a pain...

This is incredibly [REDACTED] but for backwards compatibility — similar to lookup tables — the instructions could be appended with equivalent compute budget instructions

@blockiosaurus
Copy link
Contributor

This is great! A solid method for future proofing without breaking V0 IXes.

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

Successfully merging this pull request may close these issues.

5 participants