Skip to content

Commit

Permalink
feat: statically infer max chunk size on write (#11)
Browse files Browse the repository at this point in the history
* feat(wip): implement methods for dealing w/ message payload pda

* feat: implement message payload methods + chunked writes

* refactor: inline variable and reword docs

* chore: add more error handling context to message payload txs

* chore: move nested message_payload module to a dedicated file

* feat: calculate max chunk size on write

* feat: MAX_CHUNK_SIZE as a lazy const
  • Loading branch information
tilacog authored Jan 6, 2025
1 parent 3ff3593 commit 2d0565e
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 5 deletions.
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);
}
}

0 comments on commit 2d0565e

Please sign in to comment.