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

feat: lazily infer max chunk size on write #11

Merged
merged 7 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ memmap2 = "0.9"
bytemuck = "1.19"

# Serde
bincode = "1"
serde = { version = "1", features = ["derive"] }
toml = "0.8"
serde_json = "1"
Expand All @@ -127,6 +128,7 @@ pretty_assertions = "1"
rstest = { version = "0.23" }
serial_test = "3.2"
test-log = { version = "0.2", features = ["trace"], default-features = false }
rand = "0.8.5"

# Errors
eyre = "0.6"
Expand Down
2 changes: 2 additions & 0 deletions crates/solana-gateway-task-processor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ num-traits.workspace = true
relayer-amplifier-state.workspace = true
its-instruction-builder.workspace = true
solana-transaction-status.workspace = true
bincode.workspace= true

[dev-dependencies]
serde_json.workspace = true
mockall.workspace = true
async-trait.workspace = true
solana-transaction-status.workspace = true
rand.workspace = true

[lints]
workspace = true
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,70 @@
//! This module provides functionality to handle message payloads in the Solana blockchain,
//! including initialization, writing, committing, and closing of message payload accounts.

use std::sync::LazyLock;

use axelar_solana_encoding::types::messages::Message;
use axelar_solana_gateway::state::incoming_message::command_id;
use eyre::Context as _;
use futures::stream::FuturesUnordered;
use futures::StreamExt as _;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::hash::Hash;
use solana_sdk::message::legacy::Message as SolanaMessage;
use solana_sdk::packet::PACKET_DATA_SIZE;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use solana_sdk::signature::{Keypair, Signature};
use solana_sdk::signer::Signer as _;
use solana_sdk::transaction::Transaction;

use super::send_transaction;

/// Maximum size for payload chunks in bytes.
// TODO: we should either fine tune this or make this configurable
const CHUNK_SIZE: usize = 500;
/// Maximum number of bytes we can pack into each `GatewayInstruction::WriteMessagePayload`
/// instruction.
///
/// Calculates the maximum payload size that can fit in a Solana transaction for the
/// `WriteMessagePayload` instruction. This is done by creating a baseline transaction with empty
/// payload, measuring its size, and subtracting it from the maximum Solana packet size.
///
/// The calculation is performed once on first access and cached, using random data since we only
/// care about the structure size, not the actual values.
///
/// # Panics
///
/// Will panic during initialization if:
/// - Fails to create the `WriteMessagePayload` instruction.
/// - Fails to serialize the transaction with `bincode`.
/// - Fails to convert the size from a u64 value to a usize.
///
/// Based on: `https://github.com/solana-labs/solana/pull/19654`
static MAX_CHUNK_SIZE: LazyLock<usize> = LazyLock::new(|| {
// Generate a random pubkey for all fields since we only care about size
let random_pubkey = Pubkey::new_unique();

// Create baseline instruction with empty payload data
let instruction = axelar_solana_gateway::instructions::write_message_payload(
random_pubkey,
random_pubkey,
random_pubkey.to_bytes(),
&[], // empty data
0,
)
.expect("Failed to create baseline WriteMessagePayload instruction");

let baseline_msg =
SolanaMessage::new_with_blockhash(&[instruction], Some(&random_pubkey), &Hash::default());

let tx_size = bincode::serialized_size(&Transaction {
signatures: vec![Signature::default(); baseline_msg.header.num_required_signatures.into()],
message: baseline_msg,
})
.expect("Failed to calculate transaction size")
.try_into()
.expect("Failed to convert u64 value to usize");

// Subtract baseline size and 1 byte for shortvec encoding
PACKET_DATA_SIZE.saturating_sub(tx_size).saturating_sub(1)
});

/// Handles the upload of a message payload to a Program Derived Address (PDA) account.
///
Expand Down Expand Up @@ -114,7 +163,7 @@ async fn write(
payload: &[u8],
) -> eyre::Result<()> {
let mut futures = FuturesUnordered::new();
for ChunkWithOffset { bytes, offset } in chunks_with_offset(payload, CHUNK_SIZE) {
for ChunkWithOffset { bytes, offset } in chunks_with_offset(payload, *MAX_CHUNK_SIZE) {
let ix = axelar_solana_gateway::instructions::write_message_payload(
gateway_root_pda,
keypair.pubkey(),
Expand Down Expand Up @@ -248,4 +297,11 @@ mod tests {
},]
);
}

#[test]
fn test_calculate_max_chunk_size() {
let chunk_size = *MAX_CHUNK_SIZE;
assert!(chunk_size > 0);
assert!(chunk_size < PACKET_DATA_SIZE);
}
}
Loading